1: <?php
   2:    3:    4:    5:    6: 
   7: 
   8: 
   9: if ( ! class_exists( 'GFForms' ) ) {
  10:     die();
  11: }
  12: 
  13: 
  14: require_once( 'class-gf-feed-addon.php' );
  15: 
  16:   17:   18:   19:   20:   21:   22:   23:   24: 
  25: abstract class GFPaymentAddOn extends GFFeedAddOn {
  26: 
  27:       28:   29:   30:   31:   32:   33:   34:   35:   36: 
  37:     private $_payment_version = '1.3';
  38: 
  39:       40:   41:   42:   43:   44:   45:   46:   47:   48:   49:   50:   51:   52: 
  53:     protected $_requires_credit_card = false;
  54: 
  55:       56:   57:   58:   59:   60:   61:   62:   63:   64:   65:   66: 
  67:     protected $_supports_callbacks = false;
  68: 
  69:       70:   71:   72:   73:   74:   75:   76:   77:   78:   79: 
  80:     protected $authorization = array();
  81: 
  82:       83:   84:   85:   86:   87:   88:   89:   90:   91:   92: 
  93:     protected $redirect_url = '';
  94: 
  95:       96:   97:   98:   99:  100:  101:  102:  103:  104:  105: 
 106:     protected $current_feed = false;
 107: 
 108:      109:  110:  111:  112:  113:  114:  115:  116:  117:  118: 
 119:     protected $current_submission_data = false;
 120: 
 121:      122:  123:  124:  125:  126:  127:  128:  129:  130:  131:  132: 
 133:     protected $is_payment_gateway = false;
 134: 
 135:      136:  137:  138:  139:  140:  141:  142:  143:  144: 
 145:     protected $_single_feed_submission = true;
 146: 
 147:      148:  149:  150:  151:  152:  153:  154:  155:  156:  157:  158:  159: 
 160:     protected $_requires_smallest_unit = false;
 161: 
 162:     
 163:      164:  165:  166:  167:  168:  169:  170:  171:  172:  173:  174:  175:  176: 
 177:     public function pre_init() {
 178:         parent::pre_init();
 179: 
 180:         
 181:         add_action( 'parse_request', array( $this, 'maybe_process_callback' ) );
 182: 
 183:         if ( $this->payment_method_is_overridden( 'check_status' ) ) {
 184:             $this->setup_cron();
 185:         }
 186: 
 187:     }
 188: 
 189:      190:  191:  192:  193:  194:  195:  196:  197:  198:  199:  200:  201: 
 202:     public function init() {
 203: 
 204:         parent::init();
 205: 
 206:         add_filter( 'gform_confirmation', array( $this, 'confirmation' ), 20, 4 );
 207: 
 208:         add_filter( 'gform_validation', array( $this, 'maybe_validate' ), 20 );
 209:         add_filter( 'gform_entry_post_save', array( $this, 'entry_post_save' ), 10, 2 );
 210: 
 211:         if ( $this->_requires_credit_card ) {
 212:             add_filter( 'gform_register_init_scripts', array( $this, 'register_creditcard_token_script' ), 10, 3 );
 213:             add_filter( 'gform_field_content', array( $this, 'add_creditcard_token_input' ), 10, 5 );
 214:             add_filter( 'gform_form_args', array( $this, 'force_ajax_for_creditcard_tokens' ), 10, 1 );
 215:         }
 216: 
 217:     }
 218: 
 219:      220:  221:  222:  223:  224:  225:  226:  227:  228:  229:  230:  231:  232: 
 233:     public function init_admin() {
 234: 
 235:         parent::init_admin();
 236: 
 237:         if ( $this->_requires_credit_card ) {
 238:             
 239:             add_filter( 'gform_enable_credit_card_field', '__return_true' );
 240:         }
 241: 
 242:         add_filter( 'gform_currencies', array( $this, 'supported_currencies' ) );
 243: 
 244:         add_filter( 'gform_delete_lead', array( $this, 'entry_deleted' ) );
 245:         add_action( 'gform_before_delete_field', array( $this, 'before_delete_field' ), 10, 2 );
 246: 
 247:         if ( rgget( 'page' ) == 'gf_entries' ) {
 248:             add_action( 'gform_payment_details', array( $this, 'entry_info' ), 10, 2 );
 249:         }
 250:     }
 251: 
 252:      253:  254:  255:  256:  257:  258:  259:  260:  261:  262:  263: 
 264:     public function init_ajax() {
 265:         parent::init_ajax();
 266: 
 267:         add_action( 'wp_ajax_gaddon_cancel_subscription', array( $this, 'ajax_cancel_subscription' ) );
 268:     }
 269: 
 270:      271:  272:  273:  274:  275:  276:  277:  278:  279:  280:  281:  282: 
 283:     public function setup() {
 284: 
 285:         parent::setup();
 286: 
 287:         $installed_version = get_option( 'gravityformsaddon_payment_version' );
 288: 
 289: 
 290:         $installed_addons = get_option( 'gravityformsaddon_payment_addons' );
 291:         if ( ! is_array( $installed_addons ) ) {
 292:             $installed_addons = array();
 293:         }
 294: 
 295:         if ( $installed_version != $this->_payment_version ) {
 296:             $this->upgrade_payment( $installed_version );
 297: 
 298:             $installed_addons = array( $this->_slug );
 299:             update_option( 'gravityformsaddon_payment_addons', $installed_addons );
 300:         }
 301:         elseif ( ! in_array( $this->_slug, $installed_addons ) ) {
 302: 
 303:             $this->upgrade_payment( $installed_version );
 304: 
 305:             $installed_addons[] = $this->_slug;
 306:             update_option( 'gravityformsaddon_payment_addons', $installed_addons );
 307:         }
 308: 
 309: 
 310:         update_option( 'gravityformsaddon_payment_version', $this->_payment_version );
 311: 
 312:     }
 313: 
 314:      315:  316:  317:  318:  319:  320:  321:  322:  323:  324:  325:  326:  327:  328:  329:  330: 
 331:     private function upgrade_payment( $previous_versions ) {
 332:         global $wpdb;
 333: 
 334:         $charset_collate = GFFormsModel::get_db_charset();
 335: 
 336:         $sql = "CREATE TABLE {$wpdb->prefix}gf_addon_payment_transaction (
 337:                   id int(10) unsigned not null auto_increment,
 338:                   lead_id int(10) unsigned not null,
 339:                   transaction_type varchar(30) not null,
 340:                   transaction_id varchar(50),
 341:                   subscription_id varchar(50),
 342:                   is_recurring tinyint(1) not null default 0,
 343:                   amount decimal(19,2),
 344:                   date_created datetime,
 345:                   PRIMARY KEY  (id),
 346:                   KEY lead_id (lead_id),
 347:                   KEY transaction_type (transaction_type),
 348:                   KEY type_lead (lead_id,transaction_type)
 349:                 ) $charset_collate;";
 350: 
 351:         gf_upgrade()->dbDelta( $sql );
 352: 
 353: 
 354:         if ( $this->_supports_callbacks ) {
 355:             $sql = "CREATE TABLE {$wpdb->prefix}gf_addon_payment_callback (
 356:                       id int(10) unsigned not null auto_increment,
 357:                       lead_id int(10) unsigned not null,
 358:                       addon_slug varchar(250) not null,
 359:                       callback_id varchar(250),
 360:                       date_created datetime,
 361:                       PRIMARY KEY  (id),
 362:                       KEY addon_slug_callback_id (addon_slug(50),callback_id(100))
 363:                     ) $charset_collate;";
 364: 
 365:             gf_upgrade()->dbDelta( $sql );
 366: 
 367:             
 368:             gf_upgrade()->drop_index( "{$wpdb->prefix}gf_addon_payment_callback", 'slug_callback_id' );
 369:         }
 370: 
 371: 
 372:     }
 373: 
 374:      375:  376:  377:  378:  379: 
 380:     public function post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade ){
 381: 
 382:         
 383:         if( $force_upgrade ){
 384: 
 385:             $installed_version = get_option( 'gravityformsaddon_payment_version' );
 386: 
 387:             $this->upgrade_payment( $installed_version );
 388: 
 389:             update_option( 'gravityformsaddon_payment_version', $this->_payment_version );
 390: 
 391:         }
 392: 
 393:         parent::post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade );
 394:     }
 395: 
 396:     
 397: 
 398:      399:  400:  401:  402:  403:  404:  405:  406:  407:  408:  409:  410:  411:  412: 
 413:     public function confirmation( $confirmation, $form, $entry, $ajax ) {
 414: 
 415:         if ( empty( $this->redirect_url ) ) {
 416:             return $confirmation;
 417:         }
 418: 
 419:         $confirmation = array( 'redirect' => $this->redirect_url );
 420: 
 421:         return $confirmation;
 422:     }
 423: 
 424:      425:  426:  427:  428:  429:  430:  431:  432:  433:  434:  435:  436:  437:  438:  439:  440: 
 441:     public function redirect_url( $feed, $submission_data, $form, $entry ) {
 442: 
 443:     }
 444: 
 445:      446:  447:  448:  449:  450:  451:  452:  453:  454:  455:  456:  457:  458:  459: 
 460:     public function maybe_validate( $validation_result ) {
 461: 
 462:         $form            = $validation_result['form'];
 463:         $is_last_page    = GFFormDisplay::is_last_page( $form );
 464:         $failed_honeypot = false;
 465: 
 466:         if ( $is_last_page && rgar( $form, 'enableHoneypot' ) ) {
 467:             $honeypot_id     = GFFormDisplay::get_max_field_id( $form ) + 1;
 468:             $failed_honeypot = ! rgempty( "input_{$honeypot_id}" );
 469:         }
 470: 
 471:         
 472:         $is_heartbeat = rgpost('action') == 'heartbeat';
 473: 
 474:         if ( ! $validation_result['is_valid'] || ! $is_last_page || $failed_honeypot || $is_heartbeat) {
 475:             return $validation_result;
 476:         }
 477: 
 478:         return $this->validation( $validation_result );
 479:     }
 480: 
 481:      482:  483:  484:  485:  486:  487:  488:  489:  490:  491:  492:  493:  494:  495:  496:  497:  498:  499:  500:  501:  502:  503:  504:  505: 
 506:     public function validation( $validation_result ) {
 507: 
 508:         if ( ! $validation_result['is_valid'] ) {
 509:             return $validation_result;
 510:         }
 511: 
 512:         $form  = $validation_result['form'];
 513:         $entry = GFFormsModel::create_lead( $form );
 514:         $feed  = $this->get_payment_feed( $entry, $form );
 515: 
 516:         if ( ! $feed ) {
 517:             return $validation_result;
 518:         }
 519: 
 520:         $submission_data = $this->get_submission_data( $feed, $form, $entry );
 521: 
 522:         
 523:         if ( floatval( $submission_data['payment_amount'] ) <= 0 ) {
 524: 
 525:             $this->log_debug( __METHOD__ . '(): Payment amount is zero or less. Not sending to payment gateway.' );
 526: 
 527:             return $validation_result;
 528:         }
 529: 
 530: 
 531:         $this->is_payment_gateway      = true;
 532:         $this->current_feed            = $this->_single_submission_feed = $feed;
 533:         $this->current_submission_data = $submission_data;
 534: 
 535:         $performed_authorization = false;
 536:         $is_subscription         = $feed['meta']['transactionType'] == 'subscription';
 537: 
 538:         if ( $this->payment_method_is_overridden( 'authorize' ) && ! $is_subscription ) {
 539: 
 540:             
 541:             $this->authorization = $this->authorize( $feed, $submission_data, $form, $entry );
 542: 
 543:             $performed_authorization = true;
 544: 
 545:         } elseif ( $this->payment_method_is_overridden( 'subscribe' ) && $is_subscription ) {
 546: 
 547:             $subscription = $this->subscribe( $feed, $submission_data, $form, $entry );
 548: 
 549:             $this->authorization['is_authorized'] = rgar($subscription,'is_success');
 550:             $this->authorization['error_message'] = rgar( $subscription, 'error_message' );
 551:             $this->authorization['subscription']  = $subscription;
 552: 
 553:             $performed_authorization = true;
 554:         }
 555: 
 556:         if ( $performed_authorization ) {
 557:             $this->log_debug( __METHOD__ . "(): Authorization result for form #{$form['id']} submission => " . print_r( $this->authorization, 1 ) );
 558:         }
 559: 
 560:         if ( $performed_authorization && ! rgar( $this->authorization, 'is_authorized' ) ) {
 561:             $validation_result = $this->get_validation_result( $validation_result, $this->authorization );
 562: 
 563:             
 564:             GFFormDisplay::set_current_page( $validation_result['form']['id'], $validation_result['credit_card_page'] );
 565:         }
 566: 
 567:         return $validation_result;
 568:     }
 569: 
 570:      571:  572:  573:  574:  575:  576:  577:  578:  579:  580:  581:  582:  583:  584:  585:  586:  587:  588:  589:  590:  591:  592:  593:  594:  595:  596:  597:  598:  599:  600:  601:  602:  603:  604:  605:  606: 
 607:     public function authorize( $feed, $submission_data, $form, $entry ) {
 608: 
 609:     }
 610: 
 611:      612:  613:  614:  615:  616:  617:  618:  619:  620:  621:  622:  623:  624:  625:  626:  627:  628:  629:  630:  631:  632:  633:  634:  635:  636:  637: 
 638:     public function capture( $authorization, $feed, $submission_data, $form, $entry ) {
 639: 
 640:     }
 641: 
 642:      643:  644:  645:  646:  647:  648:  649:  650:  651:  652:  653:  654:  655:  656:  657:  658:  659:  660:  661:  662:  663:  664:  665:  666:  667:  668:  669:  670:  671:  672:  673:  674:  675:  676:  677:  678:  679:  680:  681:  682:  683:  684:  685:  686:  687:  688: 
 689:     public function subscribe( $feed, $submission_data, $form, $entry ) {
 690: 
 691:     }
 692: 
 693:      694:  695:  696:  697:  698:  699:  700:  701:  702:  703:  704:  705:  706:  707:  708: 
 709:     public function cancel( $entry, $feed ) {
 710:         return false;
 711:     }
 712: 
 713:      714:  715:  716:  717:  718:  719:  720:  721:  722:  723:  724:  725: 
 726:     public function get_validation_result( $validation_result, $authorization_result ) {
 727: 
 728:         $credit_card_page = 0;
 729:         foreach ( $validation_result['form']['fields'] as &$field ) {
 730:             if ( $field->type == 'creditcard' ) {
 731:                 $field->failed_validation  = true;
 732:                 $field->validation_message = $authorization_result['error_message'];
 733:                 $credit_card_page          = $field->pageNumber;
 734:                 break;
 735:             }
 736:         }
 737: 
 738:         $validation_result['credit_card_page'] = $credit_card_page;
 739:         $validation_result['is_valid']         = false;
 740: 
 741:         return $validation_result;
 742: 
 743:     }
 744: 
 745:      746:  747:  748:  749:  750:  751:  752:  753:  754:  755:  756:  757:  758:  759:  760:  761:  762:  763:  764: 
 765:     public function entry_post_save( $entry, $form ) {
 766: 
 767:         if ( ! $this->is_payment_gateway ) {
 768:             return $entry;
 769:         }
 770: 
 771:         
 772:         gform_update_meta( $entry['id'], 'payment_gateway', $this->_slug );
 773: 
 774:         $feed = $this->current_feed;
 775: 
 776:         if ( ! empty( $this->authorization ) ) {
 777:             
 778: 
 779:             if ( $feed['meta']['transactionType'] == 'subscription' ) {
 780: 
 781:                 $entry = $this->process_subscription( $this->authorization, $feed, $this->current_submission_data, $form, $entry );
 782: 
 783:             } else {
 784: 
 785:                 if ( $this->payment_method_is_overridden( 'capture' ) && rgempty( 'captured_payment', $this->authorization ) ) {
 786: 
 787:                     $this->authorization['captured_payment'] = $this->capture( $this->authorization, $feed, $this->current_submission_data, $form, $entry );
 788: 
 789:                 }
 790: 
 791:                 $entry = $this->process_capture( $this->authorization, $feed, $this->current_submission_data, $form, $entry );
 792:             }
 793:         } elseif ( $this->payment_method_is_overridden( 'redirect_url' ) ) {
 794: 
 795:             
 796: 
 797:             
 798:             $this->redirect_url = $this->redirect_url( $feed, $this->current_submission_data, $form, $entry );
 799: 
 800:             
 801:             $entry['transaction_type'] = rgars( $feed, 'meta/transactionType' ) == 'subscription' ? 2 : 1;
 802:             $entry['payment_status']   = 'Processing';
 803: 
 804:         }
 805: 
 806:         return $entry;
 807:     }
 808: 
 809:      810:  811:  812:  813:  814:  815:  816:  817:  818:  819:  820:  821:  822:  823:  824:  825:  826:  827: 
 828:     public function process_capture( $authorization, $feed, $submission_data, $form, $entry ) {
 829: 
 830:         $payment = rgar( $authorization, 'captured_payment' );
 831:         if ( empty( $payment ) && rgar( $authorization, 'is_authorized' ) ) {
 832:             if ( ! rgar( $authorization, 'amount' ) ) {
 833:                 $authorization['amount'] = rgar( $submission_data, 'payment_amount' );
 834:             }
 835: 
 836:             $this->complete_authorization( $entry, $authorization );
 837: 
 838:             return $entry;
 839:         }
 840: 
 841:         $this->log_debug( __METHOD__ . "(): Updating entry #{$entry['id']} with result => " . print_r( $payment, 1 ) );
 842: 
 843:         if ( $payment['is_success'] ) {
 844: 
 845:             $entry['is_fulfilled']     = '1';
 846:             $payment['payment_status'] = 'Paid';
 847:             $payment['payment_date']   = gmdate( 'Y-m-d H:i:s' );
 848:             $payment['type']           = 'complete_payment';
 849:             $this->complete_payment( $entry, $payment );
 850: 
 851:         } else {
 852: 
 853:             $entry['payment_status'] = 'Failed';
 854:             $payment['type']         = 'fail_payment';
 855:             $payment['note']         = sprintf( esc_html__( 'Payment failed to be captured. Reason: %s', 'gravityforms' ), $payment['error_message'] );
 856:             $this->fail_payment( $entry, $payment );
 857: 
 858:         }
 859: 
 860:         return $entry;
 861: 
 862:     }
 863: 
 864:      865:  866:  867:  868:  869:  870:  871:  872:  873:  874:  875:  876:  877:  878:  879:  880:  881:  882:  883:  884:  885: 
 886:     public function process_subscription( $authorization, $feed, $submission_data, $form, $entry ) {
 887: 
 888:         $subscription = rgar( $authorization, 'subscription' );
 889:         if ( empty( $subscription ) ) {
 890:             return $entry;
 891:         }
 892: 
 893:         $this->log_debug( __METHOD__ . "(): Updating entry #{$entry['id']} with result => " . print_r( $subscription, 1 ) );
 894: 
 895:         
 896:         $payment      = rgar( $subscription, 'captured_payment' );
 897:         $payment_name = rgempty( 'name', $payment ) ? esc_html__( 'Initial payment', 'gravityforms' ) : $payment['name'];
 898: 
 899:         if ( $payment && $payment['is_success'] ) {
 900: 
 901:             $this->insert_transaction( $entry['id'], 'payment', $payment['transaction_id'], $payment['amount'], false, rgar( $subscription, 'subscription_id' ) );
 902: 
 903:             $amount_formatted = GFCommon::to_money( $payment['amount'], $entry['currency'] );
 904:             $note             = sprintf( esc_html__( '%s has been captured successfully. Amount: %s. Transaction Id: %s', 'gravityforms' ), $payment_name, $amount_formatted, $payment['transaction_id'] );
 905:             $this->add_note( $entry['id'], $note, 'success' );
 906: 
 907:         } elseif ( $payment && ! $payment['is_success'] ) {
 908: 
 909:             $this->add_note( $entry['id'], sprintf( esc_html__( 'Failed to capture %s. Reason: %s.', 'gravityforms' ), $payment['error_message'], $payment_name ), 'error' );
 910: 
 911:         }
 912: 
 913:         
 914:         if ( $subscription['is_success'] ) {
 915: 
 916:             $entry = $this->start_subscription( $entry, $subscription );
 917: 
 918:         } else {
 919: 
 920:             $entry['payment_status'] = 'Failed';
 921:             GFAPI::update_entry( $entry );
 922: 
 923:             $this->add_note( $entry['id'], sprintf( esc_html__( 'Subscription failed to be created. Reason: %s', 'gravityforms' ), $subscription['error_message'] ), 'error' );
 924: 
 925:             $subscription['type'] = 'fail_create_subscription';
 926:             $this->post_payment_action( $entry, $subscription );
 927: 
 928:         }
 929: 
 930:         return $entry;
 931: 
 932:     }
 933: 
 934:      935:  936:  937:  938:  939:  940:  941:  942:  943:  944:  945:  946:  947:  948:  949:  950:  951:  952:  953:  954:  955:  956:  957:  958:  959: 
 960:     public function insert_transaction( $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring = null, $subscription_id = null ) {
 961:         global $wpdb;
 962: 
 963:         
 964:         $payment_count = $wpdb->get_var( $wpdb->prepare( "SELECT count(id) FROM {$wpdb->prefix}gf_addon_payment_transaction WHERE lead_id=%d", $entry_id ) );
 965:         $is_recurring  = $payment_count > 0 && $transaction_type == 'payment' ? 1 : 0;
 966:         $subscription_id = empty( $subscription_id ) ? '' : $subscription_id;
 967: 
 968:         $sql = $wpdb->prepare(
 969:             " INSERT INTO {$wpdb->prefix}gf_addon_payment_transaction (lead_id, transaction_type, transaction_id, amount, is_recurring, date_created, subscription_id)
 970:                                 values(%d, %s, %s, %f, %d, utc_timestamp(), %s)", $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring, $subscription_id
 971:         );
 972:         $wpdb->query( $sql );
 973: 
 974:         $txn_id = $wpdb->insert_id;
 975: 
 976:          977:  978:  979:  980:  981:  982:  983:  984:  985:  986:  987: 
 988:         do_action( 'gform_post_payment_transaction', $txn_id, $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring, $subscription_id );
 989:         if ( has_filter( 'gform_post_payment_transaction' ) ) {
 990:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_transaction.' );
 991:         }
 992: 
 993:         return $txn_id;
 994:     }
 995: 
 996:      997:  998:  999: 1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 
1016:     public function get_payment_feed( $entry, $form = false ) {
1017:         $submission_feed = false;
1018: 
1019:         
1020:         if ( $entry['id'] ) {
1021:             $feeds           = $this->get_feeds_by_entry( $entry['id'] );
1022:             $submission_feed = empty( $feeds ) ? false : $this->get_feed( $feeds[0] );
1023:         } elseif ( $form ) {
1024: 
1025:             
1026:             $feeds = $this->get_feeds( $form['id'] );
1027:             $feeds = $this->pre_process_feeds( $feeds, $entry, $form );
1028: 
1029:             foreach ( $feeds as $feed ) {
1030:                 if ( $feed['is_active'] && $this->is_feed_condition_met( $feed, $form, $entry ) ) {
1031:                     $submission_feed = $feed;
1032:                     break;
1033:                 }
1034:             }
1035:         }
1036: 
1037: 
1038:         return $submission_feed;
1039:     }
1040: 
1041:     1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 
1055:     public function is_payment_gateway( $entry_id ) {
1056: 
1057:         if ( $this->is_payment_gateway ) {
1058:             return true;
1059:         }
1060: 
1061:         $gateway = gform_get_meta( $entry_id, 'payment_gateway' );
1062: 
1063:         return $gateway == $this->_slug;
1064:     }
1065: 
1066:     1067: 1068: 1069: 1070: 1071: 1072: 1073: 1074: 1075: 1076: 1077: 1078: 1079: 1080: 1081: 1082: 1083: 1084: 
1085:     public function get_submission_data( $feed, $form, $entry ) {
1086: 
1087:         $submission_data = array();
1088: 
1089:         $submission_data['form_title'] = $form['title'];
1090: 
1091:         
1092:         $billing_fields = $this->billing_info_fields();
1093:         foreach ( $billing_fields as $billing_field ) {
1094:             $field_name                     = $billing_field['name'];
1095:             $input_id                       = rgar( $feed['meta'], "billingInformation_{$field_name}" );
1096:             $submission_data[ $field_name ] = $this->get_field_value( $form, $entry, $input_id );
1097:         }
1098: 
1099:         
1100:         $card_field = $this->get_credit_card_field( $form );
1101:         if ( $card_field ) {
1102: 
1103:             $submission_data['card_number']          = $this->remove_spaces_from_card_number( rgpost( "input_{$card_field->id}_1" ) );
1104:             $submission_data['card_expiration_date'] = rgpost( "input_{$card_field->id}_2" );
1105:             $submission_data['card_security_code']   = rgpost( "input_{$card_field->id}_3" );
1106:             $submission_data['card_name']            = rgpost( "input_{$card_field->id}_5" );
1107: 
1108:         }
1109: 
1110:         
1111:         $order_info      = $this->get_order_data( $feed, $form, $entry );
1112:         $submission_data = array_merge( $submission_data, $order_info );
1113: 
1114:         1115: 1116: 1117: 1118: 1119: 1120: 1121: 1122: 1123: 1124: 1125: 
1126: 
1127:         return gf_apply_filters( array( 'gform_submission_data_pre_process_payment', $form['id'] ), $submission_data, $feed, $form, $entry );
1128:     }
1129: 
1130:     1131: 1132: 1133: 1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 
1145:     public function get_credit_card_field( $form ) {
1146:         $fields = GFAPI::get_fields_by_type( $form, array( 'creditcard' ) );
1147: 
1148:         return empty( $fields ) ? false : $fields[0];
1149:     }
1150: 
1151:     1152: 1153: 1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162: 1163: 
1164:     public function has_credit_card_field( $form ) {
1165:         return $this->get_credit_card_field( $form ) !== false;
1166:     }
1167: 
1168:     1169: 1170: 1171: 1172: 1173: 1174: 1175: 1176: 1177: 1178: 1179: 1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187: 1188: 1189: 1190: 
1191:     public function get_order_data( $feed, $form, $entry ) {
1192: 
1193:         $products = GFCommon::get_product_fields( $form, $entry );
1194: 
1195:         $payment_field   = $feed['meta']['transactionType'] == 'product' ? rgars( $feed, 'meta/paymentAmount' ) : rgars( $feed, 'meta/recurringAmount' );
1196:         $setup_fee_field = rgar( $feed['meta'], 'setupFee_enabled' ) ? $feed['meta']['setupFee_product'] : false;
1197:         $trial_field     = rgar( $feed['meta'], 'trial_enabled' ) ? rgars( $feed, 'meta/trial_product' ) : false;
1198: 
1199:         $amount       = 0;
1200:         $line_items   = array();
1201:         $discounts    = array();
1202:         $fee_amount   = 0;
1203:         $trial_amount = 0;
1204:         foreach ( $products['products'] as $field_id => $product ) {
1205: 
1206:             $quantity      = $product['quantity'] ? $product['quantity'] : 1;
1207:             $product_price = GFCommon::to_number( $product['price'], $entry['currency'] );
1208: 
1209:             $options = array();
1210:             if ( is_array( rgar( $product, 'options' ) ) ) {
1211:                 foreach ( $product['options'] as $option ) {
1212:                     $options[] = $option['option_name'];
1213:                     $product_price += $option['price'];
1214:                 }
1215:             }
1216: 
1217:             $is_trial_or_setup_fee = false;
1218: 
1219:             if ( ! empty( $trial_field ) && $trial_field == $field_id ) {
1220: 
1221:                 $trial_amount          = $product_price * $quantity;
1222:                 $is_trial_or_setup_fee = true;
1223: 
1224:             } elseif ( ! empty( $setup_fee_field ) && $setup_fee_field == $field_id ) {
1225: 
1226:                 $fee_amount            = $product_price * $quantity;
1227:                 $is_trial_or_setup_fee = true;
1228:             }
1229: 
1230:             
1231:             if ( is_numeric( $payment_field ) && $payment_field != $field_id ) {
1232:                 continue;
1233:             }
1234: 
1235:             
1236:             if ( $is_trial_or_setup_fee && ! is_numeric( $payment_field ) ) {
1237:                 continue;
1238:             }
1239: 
1240:             $amount += $product_price * $quantity;
1241: 
1242:             $description = '';
1243:             if ( ! empty( $options ) ) {
1244:                 $description = esc_html__( 'options: ', 'gravityforms' ) . ' ' . implode( ', ', $options );
1245:             }
1246: 
1247:             if ( $product_price >= 0 ) {
1248:                 $line_items[] = array(
1249:                     'id'          => $field_id,
1250:                     'name'        => $product['name'],
1251:                     'description' => $description,
1252:                     'quantity'    => $quantity,
1253:                     'unit_price'  => GFCommon::to_number( $product_price, $entry['currency'] ),
1254:                     'options'     => rgar( $product, 'options' )
1255:                 );
1256:             } else {
1257:                 $discounts[] = array(
1258:                     'id'          => $field_id,
1259:                     'name'        => $product['name'],
1260:                     'description' => $description,
1261:                     'quantity'    => $quantity,
1262:                     'unit_price'  => GFCommon::to_number( $product_price, $entry['currency'] ),
1263:                     'options'     => rgar( $product, 'options' )
1264:                 );
1265:             }
1266:         }
1267: 
1268:         if ( $trial_field == 'enter_amount' ) {
1269:             $trial_amount = rgar( $feed['meta'], 'trial_amount' ) ? GFCommon::to_number( rgar( $feed['meta'], 'trial_amount' ), $entry['currency'] ) : 0;
1270:         }
1271: 
1272:         if ( ! empty( $products['shipping']['name'] ) && ! is_numeric( $payment_field ) ) {
1273:             $line_items[] = array(
1274:                 'id'          => $products['shipping']['id'],
1275:                 'name'        => $products['shipping']['name'],
1276:                 'description' => '',
1277:                 'quantity'    => 1,
1278:                 'unit_price'  => GFCommon::to_number( $products['shipping']['price'], $entry['currency'] ),
1279:                 'is_shipping' => 1
1280:             );
1281:             $amount += $products['shipping']['price'];
1282:         }
1283: 
1284:         return array(
1285:             'payment_amount' => $amount,
1286:             'setup_fee'      => $fee_amount,
1287:             'trial'          => $trial_amount,
1288:             'line_items'     => $line_items,
1289:             'discounts'      => $discounts
1290:         );
1291:     }
1292: 
1293:     1294: 1295: 1296: 1297: 1298: 1299: 1300: 1301: 1302: 1303: 
1304:     public function is_callback_valid() {
1305:         if ( rgget( 'callback' ) != $this->_slug ) {
1306:             return false;
1307:         }
1308: 
1309:         return true;
1310:     }
1311: 
1312: 
1313:     
1314: 
1315:     1316: 1317: 1318: 1319: 1320: 1321: 1322: 1323: 1324: 1325: 1326: 1327: 1328: 1329: 1330: 1331: 1332: 
1333:     public function maybe_process_callback() {
1334: 
1335:         
1336:         if ( ! $this->is_callback_valid() ) {
1337:             return;
1338:         }
1339: 
1340:         
1341:         
1342:         $this->log_debug( __METHOD__ . '(): Initializing callback processing for: ' . $this->_slug );
1343: 
1344:         $callback_action = $this->callback();
1345: 
1346:         $this->log_debug( __METHOD__ . '(): Result from gateway callback => ' . print_r( $callback_action, true ) );
1347: 
1348:         $result = false;
1349:         if ( is_wp_error( $callback_action ) ) {
1350:             $this->display_callback_error( $callback_action );
1351:         } elseif ( $callback_action && is_array( $callback_action ) && rgar( $callback_action, 'type' ) && ! rgar( $callback_action, 'abort_callback' ) ) {
1352: 
1353:             $result = $this->process_callback_action( $callback_action );
1354: 
1355:             $this->log_debug( __METHOD__ . '(): Result of callback action => ' . print_r( $result, true ) );
1356: 
1357:             if ( is_wp_error( $result ) ) {
1358:                 $this->display_callback_error( $result );
1359:             } elseif ( ! $result ) {
1360:                 status_header( 200 );
1361:                 echo 'Callback could not be processed.';
1362:             } else {
1363:                 status_header( 200 );
1364:                 echo 'Callback processed successfully.';
1365:             }
1366:         } else {
1367:             status_header( 200 );
1368:             echo 'Callback bypassed';
1369:         }
1370: 
1371:         $this->post_callback( $callback_action, $result );
1372: 
1373:         die();
1374:     }
1375: 
1376:     1377: 1378: 1379: 1380: 1381: 1382: 1383: 1384: 1385: 1386: 1387: 1388: 
1389:     private function display_callback_error( $error ) {
1390: 
1391:         $data   = $error->get_error_data();
1392:         $status = ! rgempty( 'status_header', $data ) ? $data['status_header'] : 200;
1393: 
1394:         status_header( $status );
1395:         echo $error->get_error_message();
1396:     }
1397: 
1398:     1399: 1400: 1401: 1402: 1403: 1404: 1405: 1406: 1407: 1408: 1409: 1410: 1411: 1412: 1413: 1414: 1415: 1416: 1417: 1418: 1419: 1420: 1421: 1422: 1423: 1424: 1425: 1426: 1427: 1428: 1429: 1430: 1431: 1432: 1433: 
1434:     private function process_callback_action( $action ) {
1435:         $this->log_debug( __METHOD__ . '(): Processing callback action.' );
1436:         $action = wp_parse_args(
1437:             $action, array(
1438:                 'type'             => false,
1439:                 'amount'           => false,
1440:                 'amount_formatted' => false,
1441:                 'transaction_type' => false,
1442:                 'transaction_id'   => false,
1443:                 'subscription_id'  => false,
1444:                 'entry_id'         => false,
1445:                 'payment_status'   => false,
1446:                 'note'             => false,
1447:             )
1448:         );
1449: 
1450:         $result = false;
1451: 
1452:         if ( rgar( $action, 'id' ) && $this->is_duplicate_callback( $action['id'] ) ) {
1453:             return new WP_Error( 'duplicate', sprintf( esc_html__( 'This webhook has already been processed (Event Id: %s)', 'gravityforms' ), $action['id'] ) );
1454:         }
1455: 
1456:         $entry = GFAPI::get_entry( $action['entry_id'] );
1457:         if ( ! $entry || is_wp_error( $entry ) ) {
1458:             return $result;
1459:         }
1460: 
1461:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1462: 
1463:         1464: 1465: 1466: 1467: 1468: 1469: 1470: 
1471:         do_action( 'gform_action_pre_payment_callback', $action, $entry );
1472:         if ( has_filter( 'gform_action_pre_payment_callback' ) ) {
1473:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_action_pre_payment_callback.' );
1474:         }
1475: 
1476:         switch ( $action['type'] ) {
1477:             case 'complete_payment':
1478:                 $result = $this->complete_payment( $entry, $action );
1479:                 break;
1480:             case 'refund_payment':
1481:                 $result = $this->refund_payment( $entry, $action );
1482:                 break;
1483:             case 'fail_payment':
1484:                 $result = $this->fail_payment( $entry, $action );
1485:                 break;
1486:             case 'add_pending_payment':
1487:                 $result = $this->add_pending_payment( $entry, $action );
1488:                 break;
1489:             case 'void_authorization':
1490:                 $result = $this->void_authorization( $entry, $action );
1491:                 break;
1492:             case 'create_subscription':
1493:                 $result = $this->start_subscription( $entry, $action );
1494:                 $result = rgar( $result, 'payment_status' ) == 'Active' && rgar( $result, 'transaction_id' ) == rgar( $action, 'subscription_id' );
1495:                 break;
1496:             case 'cancel_subscription':
1497:                 $feed   = $this->get_payment_feed( $entry );
1498:                 $result = $this->cancel_subscription( $entry, $feed, $action['note'] );
1499:                 break;
1500:             case 'expire_subscription':
1501:                 $result = $this->expire_subscription( $entry, $action );
1502:                 break;
1503:             case 'add_subscription_payment':
1504:                 $result = $this->add_subscription_payment( $entry, $action );
1505:                 break;
1506:             case 'fail_subscription_payment':
1507:                 $result = $this->fail_subscription_payment( $entry, $action );
1508:                 break;
1509:             default:
1510:                 
1511:                 if ( is_callable( array( $this, rgar( $action, 'callback' ) ) ) ) {
1512:                     $result = call_user_func_array( array( $this, $action['callback'] ), array( $entry, $action ) );
1513:                 }
1514:                 break;
1515:         }
1516: 
1517:         if ( rgar( $action, 'id' ) && $result ) {
1518:             $this->register_callback( $action['id'], $action['entry_id'] );
1519:         }
1520: 
1521:         1522: 1523: 1524: 1525: 1526: 1527: 1528: 1529: 1530: 1531: 1532: 1533: 1534: 1535: 1536: 1537: 1538: 1539: 1540: 
1541:         do_action( 'gform_post_payment_callback', $entry, $action, $result );
1542:         if ( has_filter( 'gform_post_payment_callback' ) ) {
1543:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_callback.' );
1544:         }
1545: 
1546:         return $result;
1547:     }
1548: 
1549:     1550: 1551: 1552: 1553: 1554: 1555: 1556: 1557: 1558: 1559: 1560: 1561: 1562: 1563: 
1564:     public function register_callback( $callback_id, $entry_id ) {
1565:         global $wpdb;
1566: 
1567:         $wpdb->insert( "{$wpdb->prefix}gf_addon_payment_callback", array(
1568:             'addon_slug'   => $this->_slug,
1569:             'callback_id'  => $callback_id,
1570:             'lead_id'      => $entry_id,
1571:             'date_created' => gmdate( 'Y-m-d H:i:s' )
1572:         ) );
1573:     }
1574: 
1575:     1576: 1577: 1578: 1579: 1580: 1581: 1582: 1583: 1584: 1585: 1586: 1587: 1588: 1589: 
1590:     public function is_duplicate_callback( $callback_id ) {
1591:         global $wpdb;
1592: 
1593:         $sql = $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}gf_addon_payment_callback WHERE addon_slug=%s AND callback_id=%s", $this->_slug, $callback_id );
1594:         if ( $wpdb->get_var( $sql ) ) {
1595:             return true;
1596:         }
1597: 
1598:         return false;
1599:     }
1600: 
1601:     public function callback() {
1602:     }
1603: 
1604:     public function post_callback( $callback_action, $result ) {
1605:     }
1606: 
1607: 
1608:     
1609: 
1610:     public function add_pending_payment( $entry, $action ) {
1611:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1612:         if ( empty( $action['payment_status'] ) ) {
1613:             $action['payment_status'] = 'Pending';
1614:         }
1615: 
1616:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1617: 
1618:         if ( empty( $action['note'] ) ) {
1619:             $action['note'] = sprintf( esc_html__( 'Payment is pending. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1620:         }
1621: 
1622:         GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1623:         $this->add_note( $entry['id'], $action['note'] );
1624:         $this->post_payment_action( $entry, $action );
1625: 
1626:         return true;
1627:     }
1628: 
1629:     public function complete_authorization( &$entry, $action ) {
1630:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1631:         if ( ! rgar( $action, 'payment_status' ) ) {
1632:             $action['payment_status'] = 'Authorized';
1633:         }
1634: 
1635:         if ( ! rgar( $action, 'transaction_type' ) ) {
1636:             $action['transaction_type'] = 'authorization';
1637:         }
1638: 
1639:         if ( ! rgar( $action, 'payment_date' ) ) {
1640:             $action['payment_date'] = gmdate( 'y-m-d H:i:s' );
1641:         }
1642: 
1643:         $entry['transaction_id']   = rgar( $action, 'transaction_id' );
1644:         $entry['transaction_type'] = '1';
1645:         $entry['payment_status']   = $action['payment_status'];
1646: 
1647:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1648: 
1649:         if ( ! rgar( $action, 'note' ) ) {
1650:             $action['note'] = sprintf( esc_html__( 'Payment has been authorized. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1651:         }
1652: 
1653:         GFAPI::update_entry( $entry );
1654:         $this->add_note( $entry['id'], $action['note'], 'success' );
1655:         $this->post_payment_action( $entry, $action );
1656: 
1657:         return true;
1658:     }
1659: 
1660:     public function complete_payment( &$entry, $action ) {
1661:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1662:         if ( ! rgar( $action, 'payment_status' ) ) {
1663:             $action['payment_status'] = 'Paid';
1664:         }
1665: 
1666:         if ( ! rgar( $action, 'transaction_type' ) ) {
1667:             $action['transaction_type'] = 'payment';
1668:         }
1669: 
1670:         if ( ! rgar( $action, 'payment_date' ) ) {
1671:             $action['payment_date'] = gmdate( 'y-m-d H:i:s' );
1672:         }
1673: 
1674:         $entry['is_fulfilled']     = '1';
1675:         $entry['transaction_id']   = rgar( $action, 'transaction_id' );
1676:         $entry['transaction_type'] = '1';
1677:         $entry['payment_status']   = $action['payment_status'];
1678:         $entry['payment_amount']   = rgar( $action, 'amount' );
1679:         $entry['payment_date']     = $action['payment_date'];
1680:         $entry['payment_method']   = rgar( $action, 'payment_method' );
1681: 
1682:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1683: 
1684:         if ( ! rgar( $action, 'note' ) ) {
1685:             $action['note'] = sprintf( esc_html__( 'Payment has been completed. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1686:         }
1687: 
1688:         GFAPI::update_entry( $entry );
1689:         $this->insert_transaction( $entry['id'], $action['transaction_type'], $action['transaction_id'], $action['amount'] );
1690:         $this->add_note( $entry['id'], $action['note'], 'success' );
1691: 
1692:         1693: 1694: 1695: 1696: 1697: 1698: 1699: 1700: 1701: 1702: 1703: 1704: 1705: 1706: 1707: 1708: 1709: 1710: 1711: 1712: 1713: 1714: 1715: 1716: 1717: 1718: 1719: 1720: 
1721:         do_action( 'gform_post_payment_completed', $entry, $action );
1722:         if ( has_filter( 'gform_post_payment_completed' ) ) {
1723:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_completed.' );
1724:         }
1725:         $this->post_payment_action( $entry, $action );
1726: 
1727:         return true;
1728:     }
1729: 
1730:     public function refund_payment( $entry, $action ) {
1731:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1732:         if ( empty( $action['payment_status'] ) ) {
1733:             $action['payment_status'] = 'Refunded';
1734:         }
1735: 
1736:         if ( empty( $action['transaction_type'] ) ) {
1737:             $action['transaction_type'] = 'refund';
1738:         }
1739: 
1740:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1741: 
1742:         if ( empty( $action['note'] ) ) {
1743:             $action['note'] = sprintf( esc_html__( 'Payment has been refunded. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1744:         }
1745: 
1746:         GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1747:         $this->insert_transaction( $entry['id'], $action['transaction_type'], $action['transaction_id'], $action['amount'] );
1748:         $this->add_note( $entry['id'], $action['note'] );
1749: 
1750:         1751: 1752: 1753: 1754: 1755: 1756: 1757: 1758: 1759: 1760: 1761: 1762: 1763: 1764: 1765: 1766: 1767: 1768: 1769: 1770: 1771: 1772: 1773: 1774: 1775: 1776: 1777: 1778: 
1779:         do_action( 'gform_post_payment_refunded', $entry, $action );
1780:         if ( has_filter( 'gform_post_payment_refunded' ) ) {
1781:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_refunded.' );
1782:         }
1783:         $this->post_payment_action( $entry, $action );
1784: 
1785:         return true;
1786:     }
1787: 
1788:     public function fail_payment( $entry, $action ) {
1789:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1790:         if ( empty( $action['payment_status'] ) ) {
1791:             $action['payment_status'] = 'Failed';
1792:         }
1793: 
1794:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1795: 
1796:         if ( empty( $action['note'] ) ) {
1797:             $action['note'] = sprintf( esc_html__( 'Payment has failed. Amount: %s.', 'gravityforms' ), $action['amount_formatted'] );
1798:         }
1799: 
1800:         GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1801:         $this->add_note( $entry['id'], $action['note'] );
1802:         $this->post_payment_action( $entry, $action );
1803: 
1804:         return true;
1805:     }
1806: 
1807:     public function void_authorization( $entry, $action ) {
1808:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1809:         if ( empty( $action['payment_status'] ) ) {
1810:             $action['payment_status'] = 'Voided';
1811:         }
1812: 
1813:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1814: 
1815:         if ( empty( $action['note'] ) ) {
1816:             $action['note'] = sprintf( esc_html__( 'Authorization has been voided. Transaction Id: %s', 'gravityforms' ), $action['transaction_id'] );
1817:         }
1818: 
1819:         GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1820:         $this->add_note( $entry['id'], $action['note'] );
1821:         $this->post_payment_action( $entry, $action );
1822: 
1823:         return true;
1824:     }
1825: 
1826:     1827: 1828: 1829: 1830: 1831: 1832: 1833: 1834: 
1835: 
1836:     public function start_subscription( $entry, $subscription ) {
1837:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1838:         if ( ! $this->has_subscription( $entry ) ) {
1839:             $entry['payment_status']   = 'Active';
1840:             $entry['payment_amount']   = $subscription['amount'];
1841:             $entry['payment_date']     = ! rgempty( 'subscription_start_date', $subscription ) ? $subscription['subscription_start_date'] : gmdate( 'Y-m-d H:i:s' );
1842:             $entry['transaction_id']   = $subscription['subscription_id'];
1843:             $entry['transaction_type'] = '2'; 
1844:             $entry['is_fulfilled']     = '1';
1845: 
1846:             $result = GFAPI::update_entry( $entry );
1847:             $this->add_note( $entry['id'], sprintf( esc_html__( 'Subscription has been created. Subscription Id: %s.', 'gravityforms' ), $subscription['subscription_id'] ), 'success' );
1848: 
1849:             $subscription = $this->maybe_add_action_amount_formatted( $subscription, $entry['currency'] );
1850: 
1851:             if ( empty( $subscription['payment_status'] ) ) {
1852:                 $subscription['payment_status'] = 'Active';
1853:             }
1854: 
1855:             1856: 1857: 1858: 1859: 1860: 
1861:             do_action( 'gform_post_subscription_started', $entry, $subscription );
1862:             if ( has_filter( 'gform_post_subscription_started' ) ) {
1863:                 $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_subscription_started.' );
1864:             }
1865: 
1866:             $subscription['type'] = 'create_subscription';
1867:             $this->post_payment_action( $entry, $subscription );
1868: 
1869:         }
1870: 
1871:         return $entry;
1872:     }
1873: 
1874:     1875: 1876: 1877: 1878: 1879: 1880: 1881: 
1882:     public function add_subscription_payment( $entry, $action ) {
1883:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1884:         if ( empty( $action['transaction_type'] ) ) {
1885:             $action['transaction_type'] = 'payment';
1886:         }
1887: 
1888:         if ( empty( $action['payment_status'] ) ) {
1889:             $action['payment_status'] = 'Active';
1890:         }
1891: 
1892:         
1893:         if ( strtolower( $entry['payment_status'] ) != 'active' ) {
1894:             $entry['payment_status'] = 'Active';
1895:             GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Active' );
1896:         }
1897: 
1898:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1899: 
1900:         if ( empty( $action['note'] ) ) {
1901:             $action['note'] = sprintf( esc_html__( 'Subscription has been paid. Amount: %s. Subscription Id: %s', 'gravityforms' ), $action['amount_formatted'], $action['subscription_id'] );
1902:         }
1903: 
1904:         $transaction_id = ! empty( $action['transaction_id'] ) ? $action['transaction_id'] : $action['subscription_id'];
1905: 
1906:         $this->insert_transaction( $entry['id'], $action['transaction_type'], $transaction_id, $action['amount'], null, rgar( $action, 'subscription_id') );
1907:         $this->add_note( $entry['id'], $action['note'], 'success' );
1908: 
1909:         1910: 1911: 1912: 1913: 1914: 1915: 1916: 1917: 1918: 1919: 1920: 1921: 1922: 1923: 1924: 1925: 1926: 1927: 1928: 1929: 1930: 1931: 1932: 1933: 1934: 1935: 1936: 1937: 
1938:         do_action( 'gform_post_add_subscription_payment', $entry, $action );
1939:         if ( has_filter( 'gform_post_add_subscription_payment' ) ) {
1940:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_add_subscription_payment.' );
1941:         }
1942:         $this->post_payment_action( $entry, $action );
1943: 
1944:         return true;
1945:     }
1946: 
1947:     public function fail_subscription_payment( $entry, $action ) {
1948:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1949:         if ( empty( $action['payment_status'] ) ) {
1950:             $action['payment_status'] = 'Failed';
1951:         }
1952: 
1953:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1954: 
1955:         if ( empty( $action['note'] ) ) {
1956:             $action['note'] = sprintf( esc_html__( 'Subscription payment has failed. Amount: %s. Subscription Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['subscription_id'] );
1957:         }
1958: 
1959:         GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Failed' );
1960:         $this->add_note( $entry['id'], $action['note'], 'error' );
1961: 
1962:         
1963:         1964: 1965: 
1966:         do_action( 'gform_subscription_payment_failed', $entry, $action['subscription_id'] );
1967:         if ( has_filter( 'gform_subscription_payment_failed' ) ) {
1968:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_subscription_payment_failed.' );
1969:         }
1970:         1971: 1972: 1973: 1974: 1975: 1976: 1977: 1978: 1979: 1980: 1981: 1982: 1983: 1984: 1985: 1986: 1987: 1988: 1989: 1990: 1991: 1992: 1993: 1994: 1995: 1996: 1997: 1998: 
1999:         do_action( 'gform_post_fail_subscription_payment', $entry, $action );
2000:         if ( has_filter( 'gform_post_fail_subscription_payment' ) ) {
2001:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_fail_subscription_payment.' );
2002:         }
2003:         $this->post_payment_action( $entry, $action );
2004: 
2005:         return true;
2006:     }
2007: 
2008:     public function cancel_subscription( $entry, $feed, $note = null ) {
2009:         $this->log_debug( __METHOD__ . '(): Processing request.' );
2010:         if ( ! $note ) {
2011:             $note = sprintf( esc_html__( 'Subscription has been cancelled. Subscription Id: %s.', 'gravityforms' ), $entry['transaction_id'] );
2012:         }
2013: 
2014:         if ( strtolower( $entry['payment_status'] ) == 'cancelled' ) {
2015:             $this->log_debug( __METHOD__ . '(): Subscription is already canceled.' );
2016: 
2017:             return false;
2018:         }
2019: 
2020:         GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Cancelled' );
2021:         $this->add_note( $entry['id'], $note );
2022: 
2023:         
2024:         do_action( 'gform_subscription_canceled', $entry, $feed, $entry['transaction_id'] );
2025: 
2026:         
2027:         do_action( 'gform_subscription_cancelled', $entry, $feed, $entry['transaction_id'] );
2028: 
2029:         if ( has_filter( 'gform_subscription_canceled' ) || has_filter( 'gform_subscription_cancelled' ) ) {
2030:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_subscription_canceled.' );
2031:         }
2032: 
2033:         $action = array(
2034:             'type'            => 'cancel_subscription',
2035:             'subscription_id' => $entry['transaction_id'],
2036:             'entry_id'        => $entry['id'],
2037:             'payment_status'  => 'Cancelled',
2038:             'note'            => $note,
2039:         );
2040:         $this->post_payment_action( $entry, $action );
2041: 
2042:         return true;
2043:     }
2044: 
2045:     public function expire_subscription( $entry, $action ) {
2046:         $this->log_debug( __METHOD__ . '(): Processing request.' );
2047:         if ( empty( $action['payment_status'] ) ) {
2048:             $action['payment_status'] = 'Expired';
2049:         }
2050: 
2051:         if ( empty( $action['note'] ) ) {
2052:             $action['note'] = sprintf( esc_html__( 'Subscription has expired. Subscriber Id: %s', 'gravityforms' ), $action['subscription_id'] );
2053:         }
2054: 
2055:         GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Expired' );
2056:         $this->add_note( $entry['id'], $action['note'] );
2057:         $this->post_payment_action( $entry, $action );
2058: 
2059:         return true;
2060:     }
2061: 
2062:     public function has_subscription( $entry ) {
2063:         if ( rgar( $entry, 'transaction_type' ) == 2 && ! rgempty( 'transaction_id', $entry ) ) {
2064:             return true;
2065:         } else {
2066:             return false;
2067:         }
2068:     }
2069: 
2070:     2071: 2072: 2073: 2074: 2075: 2076: 2077: 2078: 2079: 
2080:     public function get_entry_by_transaction_id( $transaction_id ) {
2081:         if ( empty( $transaction_id ) ) {
2082:             return false;
2083:         }
2084: 
2085:         global $wpdb;
2086: 
2087:         $entry_table_name = self::get_entry_table_name();
2088: 
2089:         $sql      = $wpdb->prepare( "SELECT id FROM {$entry_table_name} WHERE transaction_id = %s", $transaction_id );
2090:         $entry_id = $wpdb->get_var( $sql );
2091: 
2092:         if ( ! $entry_id ) {
2093:             $sql      = $wpdb->prepare( "SELECT lead_id FROM {$wpdb->prefix}gf_addon_payment_transaction WHERE transaction_id = %s", $transaction_id );
2094:             $entry_id = $wpdb->get_var( $sql );
2095:         }
2096: 
2097:         return $entry_id ? $entry_id : false;
2098:     }
2099: 
2100:     2101: 2102: 2103: 2104: 2105: 2106: 2107: 2108: 
2109:     public function post_payment_action( $entry, $action ) {
2110:         do_action( 'gform_post_payment_action', $entry, $action );
2111:         if ( has_filter( 'gform_post_payment_action' ) ) {
2112:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_action.' );
2113:         }
2114: 
2115:         $form             = GFAPI::get_form( $entry['form_id'] );
2116:         $supported_events = $this->supported_notification_events( $form );
2117:         if ( ! empty( $supported_events ) ) {
2118:             if ( ! empty( $action['payment_status'] ) ) {
2119:                 $action['payment_status'] = GFCommon::get_entry_payment_status_text( $action['payment_status'] );
2120:             }
2121:             GFAPI::send_notifications( $form, $entry, rgar( $action, 'type' ), array( 'payment_action' => $action ) );
2122:         }
2123:     }
2124: 
2125: 
2126:     
2127:     public function setup_cron() {
2128:         
2129:         $cron_name = "{$this->_slug}_cron";
2130: 
2131:         add_action( $cron_name, array( $this, 'check_status' ) );
2132: 
2133:         if ( ! wp_next_scheduled( $cron_name ) ) {
2134:             wp_schedule_event( time(), 'hourly', $cron_name );
2135:         }
2136: 
2137: 
2138:     }
2139: 
2140:     public function check_status() {
2141: 
2142:     }
2143: 
2144:     
2145:     public function feed_list_columns() {
2146:         return array(
2147:             'feedName'        => esc_html__( 'Name', 'gravityforms' ),
2148:             'transactionType' => esc_html__( 'Transaction Type', 'gravityforms' ),
2149:             'amount'          => esc_html__( 'Amount', 'gravityforms' )
2150:         );
2151:     }
2152: 
2153:     public function get_column_value_transactionType( $feed ) {
2154:         switch ( rgar( $feed['meta'], 'transactionType' ) ) {
2155:             case 'subscription' :
2156:                 return esc_html__( 'Subscription', 'gravityforms' );
2157:                 break;
2158:             case 'product' :
2159:                 return esc_html__( 'Products and Services', 'gravityforms' );
2160:                 break;
2161:             case 'donation' :
2162:                 return esc_html__( 'Donations', 'gravityforms' );
2163:                 break;
2164: 
2165:         }
2166: 
2167:         return esc_html__( 'Unsupported transaction type', 'gravityforms' );
2168:     }
2169: 
2170:     public function get_column_value_amount( $feed ) {
2171:         $form     = $this->get_current_form();
2172:         $field_id = $feed['meta']['transactionType'] == 'subscription' ? rgars( $feed, 'meta/recurringAmount' ) : rgars( $feed, 'meta/paymentAmount' );
2173:         if ( $field_id == 'form_total' ) {
2174:             $label = esc_html__( 'Form Total', 'gravityforms' );
2175:         } else {
2176:             $field = GFFormsModel::get_field( $form, $field_id );
2177:             $label = GFCommon::get_label( $field );
2178:         }
2179: 
2180:         return $label;
2181:     }
2182: 
2183: 
2184:     
2185: 
2186:     2187: 2188: 2189: 2190: 
2191:     public function feed_list_title() {
2192:         if ( $this->_requires_credit_card && ! $this->has_credit_card_field( $this->get_current_form() ) ) {
2193:             return $this->form_settings_title();
2194:         }
2195: 
2196:         return parent::feed_list_title();
2197:     }
2198: 
2199:     public function feed_list_message() {
2200: 
2201:         if ( $this->_requires_credit_card && ! $this->has_credit_card_field( $this->get_current_form() ) ) {
2202:             return $this->requires_credit_card_message();
2203:         }
2204: 
2205:         return parent::feed_list_message();
2206:     }
2207: 
2208:     public function requires_credit_card_message() {
2209:         $url = add_query_arg( array( 'view' => null, 'subview' => null ) );
2210: 
2211:         return sprintf( esc_html__( "You must add a Credit Card field to your form before creating a feed. Let's go %sadd one%s!", 'gravityforms' ), "<a href='" . esc_url( $url ) . "'>", '</a>' );
2212:     }
2213: 
2214:     public function feed_settings_fields() {
2215: 
2216:         return array(
2217: 
2218:             array(
2219:                 'description' => '',
2220:                 'fields'      => array(
2221:                     array(
2222:                         'name'     => 'feedName',
2223:                         'label'    => esc_html__( 'Name', 'gravityforms' ),
2224:                         'type'     => 'text',
2225:                         'class'    => 'medium',
2226:                         'required' => true,
2227:                         'tooltip'  => '<h6>' . esc_html__( 'Name', 'gravityforms' ) . '</h6>' . esc_html__( 'Enter a feed name to uniquely identify this setup.', 'gravityforms' )
2228:                     ),
2229:                     array(
2230:                         'name'     => 'transactionType',
2231:                         'label'    => esc_html__( 'Transaction Type', 'gravityforms' ),
2232:                         'type'     => 'select',
2233:                         'onchange' => "jQuery(this).parents('form').submit();",
2234:                         'choices'  => array(
2235:                             array(
2236:                                 'label' => esc_html__( 'Select a transaction type', 'gravityforms' ),
2237:                                 'value' => ''
2238:                             ),
2239:                             array(
2240:                                 'label' => esc_html__( 'Products and Services', 'gravityforms' ),
2241:                                 'value' => 'product'
2242:                             ),
2243:                             array( 'label' => esc_html__( 'Subscription', 'gravityforms' ), 'value' => 'subscription' ),
2244:                         ),
2245:                         'tooltip'  => '<h6>' . esc_html__( 'Transaction Type', 'gravityforms' ) . '</h6>' . esc_html__( 'Select a transaction type.', 'gravityforms' )
2246:                     ),
2247:                 )
2248:             ),
2249:             array(
2250:                 'title'      => esc_html__( 'Subscription Settings', 'gravityforms' ),
2251:                 'dependency' => array(
2252:                     'field'  => 'transactionType',
2253:                     'values' => array( 'subscription' )
2254:                 ),
2255:                 'fields'     => array(
2256:                     array(
2257:                         'name'     => 'recurringAmount',
2258:                         'label'    => esc_html__( 'Recurring Amount', 'gravityforms' ),
2259:                         'type'     => 'select',
2260:                         'choices'  => $this->recurring_amount_choices(),
2261:                         'required' => true,
2262:                         'tooltip'  => '<h6>' . esc_html__( 'Recurring Amount', 'gravityforms' ) . '</h6>' . esc_html__( "Select which field determines the recurring payment amount, or select 'Form Total' to use the total of all pricing fields as the recurring amount.", 'gravityforms' )
2263:                     ),
2264:                     array(
2265:                         'name'    => 'billingCycle',
2266:                         'label'   => esc_html__( 'Billing Cycle', 'gravityforms' ),
2267:                         'type'    => 'billing_cycle',
2268:                         'tooltip' => '<h6>' . esc_html__( 'Billing Cycle', 'gravityforms' ) . '</h6>' . esc_html__( 'Select your billing cycle.  This determines how often the recurring payment should occur.', 'gravityforms' )
2269:                     ),
2270:                     array(
2271:                         'name'    => 'recurringTimes',
2272:                         'label'   => esc_html__( 'Recurring Times', 'gravityforms' ),
2273:                         'type'    => 'select',
2274:                         'choices' => array(
2275:                                          array(
2276:                                              'label' => esc_html__( 'infinite', 'gravityforms' ),
2277:                                              'value' => '0'
2278:                                          )
2279:                                      ) + $this->get_numeric_choices( 1, 100 ),
2280:                         'tooltip' => '<h6>' . esc_html__( 'Recurring Times', 'gravityforms' ) . '</h6>' . esc_html__( 'Select how many times the recurring payment should be made.  The default is to bill the customer until the subscription is canceled.', 'gravityforms' )
2281:                     ),
2282:                     array(
2283:                         'name'  => 'setupFee',
2284:                         'label' => esc_html__( 'Setup Fee', 'gravityforms' ),
2285:                         'type'  => 'setup_fee',
2286:                     ),
2287:                     array(
2288:                         'name'    => 'trial',
2289:                         'label'   => esc_html__( 'Trial', 'gravityforms' ),
2290:                         'type'    => 'trial',
2291:                         'hidden'  => $this->get_setting( 'setupFee_enabled' ),
2292:                         'tooltip' => '<h6>' . esc_html__( 'Trial Period', 'gravityforms' ) . '</h6>' . esc_html__( 'Enable a trial period.  The user\'s recurring payment will not begin until after this trial period.', 'gravityforms' )
2293:                     ),
2294:                 )
2295:             ),
2296:             array(
2297:                 'title'      => esc_html__( 'Products & Services Settings', 'gravityforms' ),
2298:                 'dependency' => array(
2299:                     'field'  => 'transactionType',
2300:                     'values' => array( 'product', 'donation' )
2301:                 ),
2302:                 'fields'     => array(
2303:                     array(
2304:                         'name'          => 'paymentAmount',
2305:                         'label'         => esc_html__( 'Payment Amount', 'gravityforms' ),
2306:                         'type'          => 'select',
2307:                         'choices'       => $this->product_amount_choices(),
2308:                         'required'      => true,
2309:                         'default_value' => 'form_total',
2310:                         'tooltip'       => '<h6>' . esc_html__( 'Payment Amount', 'gravityforms' ) . '</h6>' . esc_html__( "Select which field determines the payment amount, or select 'Form Total' to use the total of all pricing fields as the payment amount.", 'gravityforms' )
2311:                     ),
2312:                 )
2313:             ),
2314:             array(
2315:                 'title'      => esc_html__( 'Other Settings', 'gravityforms' ),
2316:                 'dependency' => array(
2317:                     'field'  => 'transactionType',
2318:                     'values' => array( 'subscription', 'product', 'donation' )
2319:                 ),
2320:                 'fields'     => $this->other_settings_fields()
2321:             ),
2322: 
2323:         );
2324:     }
2325: 
2326:     public function other_settings_fields() {
2327:         $other_settings = array(
2328:             array(
2329:                 'name'      => 'billingInformation',
2330:                 'label'     => esc_html__( 'Billing Information', 'gravityforms' ),
2331:                 'type'      => 'field_map',
2332:                 'field_map' => $this->billing_info_fields(),
2333:                 'tooltip'   => '<h6>' . esc_html__( 'Billing Information', 'gravityforms' ) . '</h6>' . esc_html__( 'Map your Form Fields to the available listed fields.', 'gravityforms' )
2334:             ),
2335:         );
2336: 
2337:         $option_choices = $this->option_choices();
2338:         if ( ! empty( $option_choices ) ) {
2339:             $other_settings[] = array(
2340:                 'name'    => 'options',
2341:                 'label'   => esc_html__( 'Options', 'gravityforms' ),
2342:                 'type'    => 'checkbox',
2343:                 'choices' => $option_choices,
2344:             );
2345:         }
2346: 
2347:         $other_settings[] = array(
2348:             'name'    => 'conditionalLogic',
2349:             'label'   => esc_html__( 'Conditional Logic', 'gravityforms' ),
2350:             'type'    => 'feed_condition',
2351:             'tooltip' => '<h6>' . esc_html__( 'Conditional Logic', 'gravityforms' ) . '</h6>' . esc_html__( 'When conditions are enabled, form submissions will only be sent to the payment gateway when the conditions are met. When disabled, all form submissions will be sent to the payment gateway.', 'gravityforms' )
2352:         );
2353: 
2354:         return $other_settings;
2355:     }
2356: 
2357:     public function settings_billing_cycle( $field, $echo = true ) {
2358: 
2359:         $intervals = $this->supported_billing_intervals();
2360:         
2361:         $unit = $this->get_setting( $field['name'] . '_unit' );
2362:         
2363:         $interval_keys = array_keys( $intervals );
2364:         if ( ! $unit ) {
2365:             $first_interval = $intervals[ $interval_keys[0] ];
2366:         } else {
2367:             $first_interval = $intervals[ $unit ];
2368:         }
2369:         $length_field = array(
2370:             'name'    => $field['name'] . '_length',
2371:             'type'    => 'select',
2372:             'choices' => $this->get_numeric_choices( $first_interval['min'], $first_interval['max'] )
2373:         );
2374: 
2375:         $html = $this->settings_select( $length_field, false );
2376: 
2377:         
2378:         $choices = array();
2379:         foreach ( $intervals as $unit => $interval ) {
2380:             if ( ! empty( $interval ) ) {
2381:                 $choices[] = array( 'value' => $unit, 'label' => $interval['label'] );
2382:             }
2383:         }
2384: 
2385:         $unit_field = array(
2386:             'name'     => $field['name'] . '_unit',
2387:             'type'     => 'select',
2388:             'onchange' => "loadBillingLength('" . esc_attr( $field['name'] ) . "')",
2389:             'choices'  => $choices,
2390:         );
2391: 
2392:         $html .= ' ' . $this->settings_select( $unit_field, false );
2393: 
2394:         $html .= "<script type='text/javascript'>var " . $field['name'] . '_intervals = ' . json_encode( $intervals ) . ';</script>';
2395: 
2396:         if ( $echo ) {
2397:             echo $html;
2398:         }
2399: 
2400:         return $html;
2401:     }
2402: 
2403:     public function settings_setup_fee( $field, $echo = true ) {
2404: 
2405:         $enabled_field = array(
2406:             'name'       => $field['name'] . '_checkbox',
2407:             'type'       => 'checkbox',
2408:             'horizontal' => true,
2409:             'choices'    => array(
2410:                 array(
2411:                     'label'    => esc_html__( 'Enabled', 'gravityforms' ),
2412:                     'name'     => $field['name'] . '_enabled',
2413:                     'value'    => '1',
2414:                     'onchange' => "if(jQuery(this).prop('checked')){jQuery('#{$field['name']}_product').show('slow'); jQuery('#gaddon-setting-row-trial').hide('slow');} else {jQuery('#{$field['name']}_product').hide('slow'); jQuery('#gaddon-setting-row-trial').show('slow');}",
2415:                 ),
2416:             )
2417:         );
2418: 
2419:         $html = $this->settings_checkbox( $enabled_field, false );
2420: 
2421:         $form = $this->get_current_form();
2422: 
2423:         $is_enabled = $this->get_setting( "{$field['name']}_enabled" );
2424: 
2425:         $product_field = array(
2426:             'name'    => $field['name'] . '_product',
2427:             'type'    => 'select',
2428:             'class'   => $is_enabled ? '' : 'hidden',
2429:             'choices' => $this->get_payment_choices( $form )
2430:         );
2431: 
2432:         $html .= ' ' . $this->settings_select( $product_field, false );
2433: 
2434:         if ( $echo ) {
2435:             echo $html;
2436:         }
2437: 
2438:         return $html;
2439:     }
2440: 
2441:     public function set_trial_onchange( $field ) {
2442: 
2443:         return "if(jQuery(this).prop('checked')){jQuery('#{$field['name']}_product').show('slow');if (jQuery('#{$field['name']}_product').val() == 'enter_amount'){jQuery('#{$field['name']}_amount').show();}} else {jQuery('#{$field['name']}_product').hide('slow');jQuery('#{$field['name']}_amount').hide();}";
2444: 
2445:     }
2446: 
2447:     public function settings_trial( $field, $echo = true ) {
2448: 
2449:         
2450:         $enabled_field = array(
2451:             'name'       => $field['name'] . '_checkbox',
2452:             'type'       => 'checkbox',
2453:             'horizontal' => true,
2454:             'choices'    => array(
2455:                 array(
2456:                     'label'    => esc_html__( 'Enabled', 'gravityforms' ),
2457:                     'name'     => $field['name'] . '_enabled',
2458:                     'value'    => '1',
2459:                     'onchange' => $this->set_trial_onchange( $field )
2460:                 ),
2461:             )
2462:         );
2463: 
2464:         $html = $this->settings_checkbox( $enabled_field, false );
2465: 
2466:         
2467:         $form            = $this->get_current_form();
2468:         $payment_choices = array_merge( $this->get_payment_choices( $form ), array(
2469:             array(
2470:                 'label' => esc_html__( 'Enter an amount', 'gravityforms' ),
2471:                 'value' => 'enter_amount'
2472:             )
2473:         ) );
2474: 
2475:         $product_field = array(
2476:             'name'     => $field['name'] . '_product',
2477:             'type'     => 'select',
2478:             'class'    => $this->get_setting( "{$field['name']}_enabled" ) ? '' : 'hidden',
2479:             'onchange' => "if(jQuery(this).val() == 'enter_amount'){ jQuery('#{$field['name']}_amount').show();} else { jQuery('#{$field['name']}_amount').hide(); }",
2480:             'choices'  => $payment_choices,
2481:         );
2482: 
2483:         $html .= ' ' . $this->settings_select( $product_field, false );
2484: 
2485:         
2486:         $amount_field = array(
2487:             'type'  => 'text',
2488:             'name'  => "{$field['name']}_amount",
2489:             'class' => $this->get_setting( "{$field['name']}_enabled" ) && $this->get_setting( "{$field['name']}_product" ) == 'enter_amount' ? 'gform_currency' : 'hidden gform_currency',
2490:         );
2491: 
2492:         $html .= ' ' . $this->settings_text( $amount_field, false );
2493: 
2494: 
2495:         if ( $echo ) {
2496:             echo $html;
2497:         }
2498: 
2499:         return $html;
2500:     }
2501: 
2502:     public function recurring_amount_choices() {
2503:         $form                = $this->get_current_form();
2504:         $recurring_choices   = $this->get_payment_choices( $form );
2505:         $recurring_choices[] = array( 'label' => esc_html__( 'Form Total', 'gravityforms' ), 'value' => 'form_total' );
2506: 
2507:         return $recurring_choices;
2508:     }
2509: 
2510:     public function product_amount_choices() {
2511:         $form              = $this->get_current_form();
2512:         $product_choices   = $this->get_payment_choices( $form );
2513:         $product_choices[] = array( 'label' => esc_html__( 'Form Total', 'gravityforms' ), 'value' => 'form_total' );
2514: 
2515:         return $product_choices;
2516:     }
2517: 
2518:     public function option_choices() {
2519: 
2520:         $option_choices = array(
2521:             array(
2522:                 'label' => esc_html__( 'Sample Option', 'gravityforms' ),
2523:                 'name'  => 'sample_option',
2524:                 'value' => 'sample_option'
2525:             ),
2526:         );
2527: 
2528:         return $option_choices;
2529:     }
2530: 
2531:     public function billing_info_fields() {
2532: 
2533:         $fields = array(
2534:             array( 'name' => 'email', 'label' => esc_html__( 'Email', 'gravityforms' ), 'required' => false ),
2535:             array( 'name' => 'address', 'label' => esc_html__( 'Address', 'gravityforms' ), 'required' => false ),
2536:             array( 'name' => 'address2', 'label' => esc_html__( 'Address 2', 'gravityforms' ), 'required' => false ),
2537:             array( 'name' => 'city', 'label' => esc_html__( 'City', 'gravityforms' ), 'required' => false ),
2538:             array( 'name' => 'state', 'label' => esc_html__( 'State', 'gravityforms' ), 'required' => false ),
2539:             array( 'name' => 'zip', 'label' => esc_html__( 'Zip', 'gravityforms' ), 'required' => false ),
2540:             array( 'name' => 'country', 'label' => esc_html__( 'Country', 'gravityforms' ), 'required' => false ),
2541:         );
2542: 
2543:         return $fields;
2544:     }
2545: 
2546:     public function get_numeric_choices( $min, $max ) {
2547:         $choices = array();
2548:         for ( $i = $min; $i <= $max; $i ++ ) {
2549:             $choices[] = array( 'label' => $i, 'value' => $i );
2550:         }
2551: 
2552:         return $choices;
2553:     }
2554: 
2555:     public function supported_billing_intervals() {
2556: 
2557:         $billing_cycles = array(
2558:             'day'   => array( 'label' => esc_html__( 'day(s)', 'gravityforms' ), 'min' => 1, 'max' => 365 ),
2559:             'week'  => array( 'label' => esc_html__( 'week(s)', 'gravityforms' ), 'min' => 1, 'max' => 52 ),
2560:             'month' => array( 'label' => esc_html__( 'month(s)', 'gravityforms' ), 'min' => 1, 'max' => 12 ),
2561:             'year'  => array( 'label' => esc_html__( 'year(s)', 'gravityforms' ), 'min' => 1, 'max' => 10 )
2562:         );
2563: 
2564:         return $billing_cycles;
2565:     }
2566: 
2567:     public function get_payment_choices( $form ) {
2568:         $fields  = GFAPI::get_fields_by_type( $form, array( 'product' ) );
2569:         $choices = array(
2570:             array( 'label' => esc_html__( 'Select a product field', 'gravityforms' ), 'value' => '' ),
2571:         );
2572: 
2573:         foreach ( $fields as $field ) {
2574:             $field_id    = $field->id;
2575:             $field_label = RGFormsModel::get_label( $field );
2576:             $choices[]   = array( 'value' => $field_id, 'label' => $field_label );
2577:         }
2578: 
2579:         return $choices;
2580:     }
2581: 
2582:     
2583:     public function get_results_page_config() {
2584: 
2585:         return array(
2586:             'title'        => _x( 'Sales', 'toolbar label', 'gravityforms' ),
2587:             'search_title' => _x( 'Filter', 'metabox title', 'gravityforms' ),
2588:             'capabilities' => array( 'gravityforms_view_entries' ),
2589:             'callbacks'    => array(
2590:                 'fields'    => array( $this, 'results_fields' ),
2591:                 'data'      => array( $this, 'results_data' ),
2592:                 'markup'    => array( $this, 'results_markup' ),
2593:                 'filter_ui' => array( $this, 'results_filter_ui' )
2594:             )
2595:         );
2596:     }
2597: 
2598:     public function results_fields( $form ) {
2599: 
2600:         if ( $this->has_feed( $form['id'] ) ) {
2601:             return $form['fields'];
2602:         } else {
2603:             return false;
2604:         }
2605: 
2606:     }
2607: 
2608: 
2609:     public function results_markup( $html, $data, $form, $fields ) {
2610: 
2611:         $html = "<table width='100%' id='gaddon-results-summary'>
2612:                     <tr>
2613:                         <td class='gaddon-results-summary-label'>" . esc_html__( 'Today', 'gravityforms' ) . "</td>
2614:                         <td class='gaddon-results-summary-label'>" . esc_html__( 'Yesterday', 'gravityforms' ) . "</td>
2615:                         <td class='gaddon-results-summary-label'>" . esc_html__( 'Last 30 Days', 'gravityforms' ) . "</td>
2616:                         <td class='gaddon-results-summary-label'>" . esc_html__( 'Total', 'gravityforms' ) . "</td>
2617:                     </tr>
2618:                     <tr>
2619:                         <td class='gaddon-results-summary-data'>
2620:                             <div class='gaddon-results-summary-data-box'>
2621:                                 <div class='gaddon-results-summary-primary'>{$data['summary']['today']['revenue']}</div>
2622:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['today']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2623:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['today']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div>
2624:                             </div>
2625:                         </td>
2626:                         <td class='gaddon-results-summary-data'>
2627:                             <div class='gaddon-results-summary-data-box'>
2628:                                 <div class='gaddon-results-summary-primary'>{$data['summary']['yesterday']['revenue']}</div>
2629:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['yesterday']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2630:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['yesterday']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div>
2631:                             </div>
2632:                         </td>
2633: 
2634:                         <td class='gaddon-results-summary-data'>
2635:                             <div class='gaddon-results-summary-data-box'>
2636:                                 <div class='gaddon-results-summary-primary'>{$data['summary']['last30']['revenue']}</div>
2637:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['last30']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2638:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['last30']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div>
2639:                             </div>
2640:                         </td>
2641:                         <td class='gaddon-results-summary-data'>
2642:                             <div class='gaddon-results-summary-data-box'>
2643:                                 <div class='gaddon-results-summary-primary'>{$data['summary']['total']['revenue']}</div>
2644:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['total']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2645:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['total']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . '</div>
2646:                             </div>
2647:                         </td>
2648: 
2649:                     </tr>
2650:                  </table>';
2651: 
2652:         if ( $data['row_count'] == '0' ) {
2653:             $html .= "<div class='updated' style='padding:20px; margin-top:40px;'>" . esc_html__( "There aren't any transactions that match your criteria.", 'gravityforms' ) . '</div>';
2654:         } else {
2655:             $chart_data = $this->get_chart_data( $data );
2656:             $html .= $this->get_sales_chart( $chart_data );
2657: 
2658:             
2659:             $sales_table = new GFPaymentStatsTable( $data['table']['header'], $data['data'], $data['row_count'], $data['page_size'] );
2660:             $sales_table->prepare_items();
2661:             ob_start();
2662:             $sales_table->display();
2663:             $html .= ob_get_clean();
2664:         }
2665: 
2666:         $html .= '</form>';
2667: 
2668:         return $html;
2669:     }
2670: 
2671:     public function get_chart_data( $data ) {
2672:         $hAxis_column = $data['chart']['hAxis']['column'];
2673:         $vAxis_column = $data['chart']['vAxis']['column'];
2674: 
2675:         $chart_data = array();
2676:         foreach ( $data['data'] as $row ) {
2677:             $hAxis_value                = $row[ $hAxis_column ];
2678:             $chart_data[ $hAxis_value ] = $row[ $vAxis_column ];
2679:         }
2680: 
2681:         return array(
2682:             'hAxis_title' => $data['chart']['hAxis']['label'],
2683:             'vAxis_title' => $data['chart']['vAxis']['label'],
2684:             'data'        => $chart_data
2685:         );
2686:     }
2687: 
2688:     public static function get_sales_chart( $sales_data ) {
2689:         $markup = '';
2690: 
2691:         $data_table   = array();
2692:         $data_table[] = array( $sales_data['hAxis_title'], $sales_data['vAxis_title'] );
2693: 
2694:         foreach ( $sales_data['data'] as $key => $value ) {
2695:             $data_table[] = array( (string) $key, $value );
2696:         }
2697: 
2698:         $chart_options = array(
2699:             'series' => array(
2700:                 '0' => array(
2701:                     'color'           => '#66CCFF',
2702:                     'visibleInLegend' => 'false',
2703:                 ),
2704:             ),
2705:             'hAxis'  => array(
2706:                 'title' => $sales_data['hAxis_title'],
2707:             ),
2708:             'vAxis'  => array(
2709:                 'title' => $sales_data['vAxis_title'],
2710:             )
2711:         );
2712: 
2713:         $data_table_json = json_encode( $data_table );
2714:         $options_json    = json_encode( $chart_options );
2715:         $div_id          = 'gquiz-results-chart-field-score-frequencies';
2716:         $markup .= "<div class='gresults-chart-wrapper' style='width:100%;height:250px' id='{$div_id}'></div>";
2717:         $markup .= "<script>
2718:                         jQuery('#{$div_id}')
2719:                             .data('datatable',{$data_table_json})
2720:                             .data('options', {$options_json})
2721:                             .data('charttype', 'column');
2722:                     </script>";
2723: 
2724:         return $markup;
2725: 
2726:     }
2727: 
2728:     public function results_data( $form, $fields, $search_criteria, $state_array ) {
2729: 
2730:         $summary = $this->get_sales_summary( $form['id'] );
2731: 
2732:         $data = $this->get_sales_data( $form['id'], $search_criteria, $state_array );
2733: 
2734:         return array(
2735:             'entry_count' => $data['row_count'],
2736:             'row_count'   => $data['row_count'],
2737:             'page_size'   => $data['page_size'],
2738:             'status'      => 'complete',
2739:             'summary'     => $summary,
2740:             'data'        => $data['rows'],
2741:             'chart'       => $data['chart'],
2742:             'table'       => $data['table'],
2743:         );
2744:     }
2745: 
2746:     private function get_mysql_tz_offset() {
2747:         $tz_offset = get_option( 'gmt_offset' );
2748: 
2749:         
2750:         if ( is_numeric( substr( $tz_offset, 0, 1 ) ) ) {
2751:             $tz_offset = '+' . $tz_offset;
2752:         }
2753: 
2754:         return $tz_offset . ':00';
2755:     }
2756: 
2757:     public function get_sales_data( $form_id, $search, $state ) {
2758:         global $wpdb;
2759: 
2760:         $data = array(
2761:             'chart' => array(
2762:                 'hAxis' => array(),
2763:                 'vAxis' => array(
2764:                     'column' => 'revenue',
2765:                     'label'  => esc_html__( 'Revenue', 'gravityforms' )
2766:                 )
2767:             ),
2768:             'table' => array(
2769:                 'header' => array(
2770:                     'orders'             => esc_html__( 'Orders', 'gravityforms' ),
2771:                     'subscriptions'      => esc_html__( 'Subscriptions', 'gravityforms' ),
2772:                     'recurring_payments' => esc_html__( 'Recurring Payments', 'gravityforms' ),
2773:                     'refunds'            => esc_html__( 'Refunds', 'gravityforms' ),
2774:                     'revenue'            => esc_html__( 'Revenue', 'gravityforms' )
2775:                 )
2776:             ),
2777:             'rows'  => array()
2778:         );
2779: 
2780:         $tz_offset = $this->get_mysql_tz_offset();
2781: 
2782:         $page_size = 10;
2783:         $group     = strtolower( rgpost( 'group' ) );
2784:         switch ( $group ) {
2785: 
2786:             case 'weekly' :
2787:                 $select        = "concat(left(transaction.week,4), ' - ', right(transaction.week,2)) as week";
2788:                 $select_inner1 = "yearweek(CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "')) week";
2789:                 $select_inner2 = "yearweek(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) week";
2790:                 $group_by      = 'week';
2791:                 $order_by      = 'week desc';
2792:                 $join          = 'lead.week = transaction.week';
2793: 
2794:                 $data['chart']['hAxis']['column'] = 'week';
2795:                 $data['chart']['hAxis']['label']  = esc_html__( 'Week', 'gravityforms' );
2796:                 $data['table']['header']          = array_merge( array( 'week' => esc_html__( 'Week', 'gravityforms' ) ), $data['table']['header'] );
2797: 
2798:                 $current_period_format = 'o - W';
2799:                 $decrement_period = 'week';
2800:                 $result_period = 'week';
2801:                 break;
2802: 
2803:             case 'monthly' :
2804:                 $select        = "date_format(transaction.inner_month, '%%Y') as year, date_format(transaction.inner_month, '%%c') as month, '' as month_abbrev, '' as month_year";
2805:                 $select_inner1 = "date_format(CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "'), '%%Y-%%m-01') inner_month";
2806:                 $select_inner2 = "date_format(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "'), '%%Y-%%m-01') inner_month";
2807:                 $group_by      = 'inner_month';
2808:                 $order_by      = 'year desc, (month+0) desc';
2809:                 $join          = 'lead.inner_month = transaction.inner_month';
2810: 
2811:                 $data['chart']['hAxis']['column'] = 'month_year';
2812:                 $data['chart']['hAxis']['label']  = esc_html__( 'Month', 'gravityforms' );
2813:                 $data['table']['header']          = array_merge( array( 'month_year' => esc_html__( 'Month', 'gravityforms' ) ), $data['table']['header'] );
2814: 
2815:                 $current_period_format = 'n'; 
2816:                 $decrement_period = 'month';
2817:                 $result_period = 'month';
2818:                 break;
2819: 
2820:             default : 
2821:                 $select        = "transaction.date, date_format(transaction.date, '%%c') as month, day(transaction.date) as day, dayname(transaction.date) as day_of_week, '' as month_day";
2822:                 $select_inner1 = "date(CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "')) as date";
2823:                 $select_inner2 = "date(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) as date";
2824:                 $group_by      = 'date';
2825:                 $order_by      = 'date desc';
2826:                 $join          = 'lead.date = transaction.date';
2827: 
2828:                 $data['chart']['hAxis']['column'] = 'month_day';
2829:                 $data['chart']['hAxis']['label']  = esc_html__( 'Day', 'gravityforms' );
2830:                 $data['table']['header']          = array_merge( array(
2831:                     'date'        => esc_html__( 'Date', 'gravityforms' ),
2832:                     'day_of_week' => esc_html__( 'Day', 'gravityforms' )
2833:                 ), $data['table']['header'] );
2834: 
2835:                 $current_period_format = 'Y-m-d';
2836:                 $decrement_period = 'day';
2837:                 $result_period = 'date';
2838:                 break;
2839:         }
2840: 
2841:         $lead_date_filter        = '';
2842:         $transaction_date_filter = '';
2843:         if ( isset( $search['start_date'] ) ) {
2844:             $lead_date_filter        = $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(l.payment_date, '+00:00', '" . $tz_offset . "')) >= 0", $search['start_date'] );
2845:             $transaction_date_filter = $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) >= 0", $search['start_date'] );
2846:         }
2847: 
2848:         if ( isset( $search['end_date'] ) ) {
2849:             $search['end_date']      .= ' 23:59:59';
2850:             $lead_date_filter        .= $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(l.payment_date, '+00:00', '" . $tz_offset . "')) <= 0", $search['end_date'] );
2851:             $transaction_date_filter .= $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) <= 0", $search['end_date'] );
2852:         }
2853: 
2854:         $payment_method        = rgpost( 'payment_method' );
2855:         $payment_method_filter = '';
2856:         if ( ! empty( $payment_method ) ) {
2857:             $payment_method_filter = $wpdb->prepare( ' AND l.payment_method=%s', $payment_method );
2858:         }
2859: 
2860:         $current_page = rgempty( 'paged' ) ? 1 : absint( rgpost( 'paged' ) );
2861:         $offset       = $page_size * ( $current_page - 1 );
2862: 
2863:         $entry_table_name = self::get_entry_table_name();
2864: 
2865:         $sql = $wpdb->prepare(
2866:             " SELECT SQL_CALC_FOUND_ROWS {$select}, lead.orders, lead.subscriptions, transaction.refunds, transaction.recurring_payments, transaction.revenue
2867:                                 FROM (
2868:                                   SELECT  {$select_inner1},
2869:                                           sum( if(transaction_type = 1,1,0) ) as orders,
2870:                                           sum( if(transaction_type = 2,1,0) ) as subscriptions
2871:                                   FROM {$entry_table_name} l
2872:                                   WHERE l.status='active' AND form_id=%d {$lead_date_filter} {$payment_method_filter}
2873:                                   GROUP BY {$group_by}
2874:                                 ) AS lead
2875: 
2876:                                 RIGHT OUTER JOIN(
2877:                                   SELECT  {$select_inner2},
2878:                                           sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue,
2879:                                           sum( if(t.transaction_type = 'refund', 1, 0) ) as refunds,
2880:                                           sum( if(t.transaction_type = 'payment' AND t.is_recurring = 1, 1, 0) ) as recurring_payments
2881:                                   FROM {$wpdb->prefix}gf_addon_payment_transaction t
2882:                                   INNER JOIN {$entry_table_name} l ON l.id = t.lead_id
2883:                                   WHERE l.status='active' AND l.form_id=%d {$lead_date_filter} {$transaction_date_filter} {$payment_method_filter}
2884:                                   GROUP BY {$group_by}
2885: 
2886:                                 ) AS transaction on {$join}
2887:                                 ORDER BY {$order_by}
2888:                                 LIMIT $page_size OFFSET $offset
2889:                                 ", $form_id, $form_id
2890:         );
2891: 
2892:         GFCommon::log_debug( "sales sql: {$sql}" );
2893: 
2894:         $results = $wpdb->get_results( $sql, ARRAY_A );
2895: 
2896:         $display_results = array();
2897:         $current_period = date( $current_period_format );
2898: 
2899:         if ( isset( $search['start_date'] ) || isset( $search['end_date'] ) ) {
2900:             foreach ( $results as &$result ) {
2901:                 $result['orders']             = intval( $result['orders'] );
2902:                 $result['subscriptions']      = intval( $result['subscriptions'] );
2903:                 $result['refunds']            = intval( $result['refunds'] );
2904:                 $result['recurring_payments'] = intval( $result['recurring_payments'] );
2905:                 $result['revenue']            = floatval( $result['revenue'] );
2906: 
2907:                 $result = $this->format_chart_h_axis( $result );
2908: 
2909:             }
2910: 
2911:             $data['row_count'] = $wpdb->get_var( 'SELECT FOUND_ROWS()' );
2912:             $data['page_size'] = $page_size;
2913: 
2914:             $data['rows'] = $results;
2915: 
2916:         } else {
2917:             $current_date = date( 'Y-m-d' );
2918:             $current_period_timestamp = strtotime( $current_date );
2919:             for ( $i = 1;  $i <= 10 ; $i++ ) {
2920:                 $result_for_date = false;
2921:                 foreach ( $results as $result ) {
2922:                     if ( $result[ $result_period ] == $current_period ) {
2923:                         $display_result = $result;
2924:                         $result_for_date = true;
2925:                         break;
2926:                     }
2927:                 }
2928:                 if ( ! $result_for_date ) {
2929:                     $display_result = array(
2930:                         $result_period      => $current_period,
2931:                         'month'              => date( 'm', $current_period_timestamp ),
2932:                         'day'                => date( 'd', $current_period_timestamp ),
2933:                         'day_of_week'        => date( 'l', $current_period_timestamp ),
2934:                         'month_day'          => '',
2935:                         'year' => date( 'Y', $current_period_timestamp ),
2936:                         'month_abbrev' => '',
2937:                         'orders'             => '0',
2938:                         'subscriptions'      => '0',
2939:                         'refunds'            => '0',
2940:                         'recurring_payments' => '0',
2941:                         'revenue'            => '0.00',
2942:                     );
2943:                 }
2944: 
2945:                 $display_result['orders']             = intval( $display_result['orders'] );
2946:                 $display_result['subscriptions']      = intval( $display_result['subscriptions'] );
2947:                 $display_result['refunds']            = intval( $display_result['refunds'] );
2948:                 $display_result['recurring_payments'] = intval( $display_result['recurring_payments'] );
2949:                 $display_result['revenue']            = floatval( $display_result['revenue'] );
2950:                 $display_result = $this->format_chart_h_axis( $display_result );
2951: 
2952:                 $display_results[] = $display_result;
2953: 
2954:                 $decremented_date = $current_date . ' ' . ( $i * -1 ) . ' ' . $decrement_period;
2955: 
2956:                 $current_period_timestamp = strtotime( $decremented_date );
2957: 
2958:                 $current_period = date( $current_period_format, $current_period_timestamp );
2959: 
2960:             }
2961:             $data['row_count'] = $page_size;
2962:             $data['page_size'] = $page_size;
2963: 
2964:             $data['rows'] = $display_results;
2965:         }
2966: 
2967:         return $data;
2968: 
2969:     }
2970: 
2971:     public function format_chart_h_axis( $result ) {
2972:         $months = array(
2973:             esc_html__( 'Jan', 'gravityforms' ),
2974:             esc_html__( 'Feb', 'gravityforms' ),
2975:             esc_html__( 'Mar', 'gravityforms' ),
2976:             esc_html__( 'Apr', 'gravityforms' ),
2977:             esc_html__( 'May', 'gravityforms' ),
2978:             esc_html__( 'Jun', 'gravityforms' ),
2979:             esc_html__( 'Jul', 'gravityforms' ),
2980:             esc_html__( 'Aug', 'gravityforms' ),
2981:             esc_html__( 'Sep', 'gravityforms' ),
2982:             esc_html__( 'Oct', 'gravityforms' ),
2983:             esc_html__( 'Nov', 'gravityforms' ),
2984:             esc_html__( 'Dec', 'gravityforms' ),
2985:         );
2986: 
2987:         if ( isset( $result['month_abbrev'] ) ) {
2988:             $result['month_abbrev'] = $months[ intval( $result['month'] ) - 1 ];
2989:             $result['month_year']   = $months[ intval( $result['month'] ) - 1 ] . ', ' . $result['year'];
2990: 
2991:             return $result;
2992:         } elseif ( isset( $result['month_day'] ) ) {
2993:             $result['month_day'] = $months[ intval( $result['month'] ) - 1 ] . ' ' . $result['day'];
2994: 
2995:             return $result;
2996:         }
2997: 
2998:         return $result;
2999:     }
3000: 
3001:     public function get_sales_summary( $form_id ) {
3002:         global $wpdb;
3003: 
3004:         $tz_offset = $this->get_mysql_tz_offset();
3005:         $entry_table_name = self::get_entry_table_name();
3006: 
3007:         $summary = $wpdb->get_results(
3008:             $wpdb->prepare(
3009:                 "
3010:                     SELECT transaction.date, lead.orders, lead.subscriptions, transaction.revenue
3011:                     FROM (
3012:                        SELECT  date( CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "') ) as date,
3013:                                sum( if(transaction_type = 1,1,0) ) as orders,
3014:                                sum( if(transaction_type = 2,1,0) ) as subscriptions
3015:                        FROM {$entry_table_name}
3016:                        WHERE status='active' AND form_id = %d AND datediff(now(), CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "') ) <= 30
3017:                        GROUP BY date
3018:                      ) AS lead
3019: 
3020:                      RIGHT OUTER JOIN(
3021:                        SELECT  date( CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "') ) as date,
3022:                                sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue
3023:                        FROM {$wpdb->prefix}gf_addon_payment_transaction t
3024:                          INNER JOIN {$entry_table_name} l ON l.id = t.lead_id
3025:                        WHERE l.form_id=%d AND l.status='active'
3026:                        GROUP BY date
3027:                      ) AS transaction on lead.date = transaction.date
3028:                     ORDER BY date desc", $form_id, $form_id
3029:             ), ARRAY_A
3030:         );
3031: 
3032:         $total_summary = $wpdb->get_results(
3033:             $wpdb->prepare(
3034:                 "
3035:                     SELECT sum( if(transaction_type = 1,1,0) ) as orders,
3036:                            sum( if(transaction_type = 2,1,0) ) as subscriptions
3037:                     FROM {$entry_table_name}
3038:                     WHERE form_id=%d AND status='active'", $form_id
3039:             ), ARRAY_A
3040:         );
3041: 
3042:         $total_revenue = $wpdb->get_var(
3043:             $wpdb->prepare(
3044:                 "
3045:                     SELECT sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue
3046:                     FROM {$wpdb->prefix}gf_addon_payment_transaction t
3047:                     INNER JOIN {$entry_table_name} l ON l.id = t.lead_id
3048:                     WHERE l.form_id=%d AND status='active'", $form_id
3049:             )
3050:         );
3051: 
3052: 
3053:         $result = array(
3054:             'today'     => array( 'revenue' => GFCommon::to_money( 0 ), 'orders' => 0, 'subscriptions' => 0 ),
3055:             'yesterday' => array( 'revenue' => GFCommon::to_money( 0 ), 'orders' => 0, 'subscriptions' => 0 ),
3056:             'last30'    => array( 'revenue' => 0, 'orders' => 0, 'subscriptions' => 0 ),
3057:             'total'     => array(
3058:                 'revenue'       => GFCommon::to_money( $total_revenue ),
3059:                 'orders'        => $total_summary[0]['orders'],
3060:                 'subscriptions' => $total_summary[0]['subscriptions']
3061:             )
3062:         );
3063: 
3064:         $local_time = GFCommon::get_local_timestamp();
3065:         $today      = gmdate( 'Y-m-d', $local_time );
3066:         $yesterday  = gmdate( 'Y-m-d', strtotime( '-1 day', $local_time ) );
3067: 
3068:         foreach ( $summary as $day ) {
3069:             if ( $day['date'] == $today ) {
3070:                 $result['today']['revenue']       = GFCommon::to_money( $day['revenue'] );
3071:                 $result['today']['orders']        = $day['orders'];
3072:                 $result['today']['subscriptions'] = $day['subscriptions'];
3073:             } elseif ( $day['date'] == $yesterday ) {
3074:                 $result['yesterday']['revenue']       = GFCommon::to_money( $day['revenue'] );
3075:                 $result['yesterday']['orders']        = $day['orders'];
3076:                 $result['yesterday']['subscriptions'] = $day['subscriptions'];
3077:             }
3078: 
3079:             $is_within_30_days = strtotime( $day['date'] ) >= strtotime( '-30 days', $local_time );
3080:             if ( $is_within_30_days ) {
3081:                 $result['last30']['revenue'] += floatval( $day['revenue'] );
3082:                 $result['last30']['orders'] += floatval( $day['orders'] );
3083:                 $result['last30']['subscriptions'] += floatval( $day['subscriptions'] );
3084:             }
3085:         }
3086: 
3087:         $result['last30']['revenue'] = GFCommon::to_money( $result['last30']['revenue'] );
3088: 
3089:         return $result;
3090:     }
3091: 
3092:     public function results_filter_ui( $filter_ui, $form_id, $page_title, $gf_page, $gf_view ) {
3093: 
3094:         if ( $gf_view == "gf_results_{$this->_slug}" ) {
3095:             unset( $filter_ui['fields'] );
3096:         }
3097: 
3098:         $view_markup = "<div>
3099:                     <select id='gaddon-sales-group' name='group'>
3100:                         <option value='daily' " . selected( 'daily', rgget( 'group' ), false ) . '>' . esc_html__( 'Daily', 'gravityforms' ) . "</option>
3101:                         <option value='weekly' " . selected( 'weekly', rgget( 'group' ), false ) . '>' . esc_html__( 'Weekly', 'gravityforms' ) . "</option>
3102:                         <option value='monthly' " . selected( 'monthly', rgget( 'group' ), false ) . '>' . esc_html__( 'Monthly', 'gravityforms' ) . '</option>
3103:                     </select>
3104:                   </div>';
3105:         $view_filter = array(
3106:             'view' => array(
3107:                 'label'   => esc_html__( 'View', 'gravityforms' ),
3108:                 'tooltip' => '<h6>' . esc_html__( 'View', 'gravityforms' ) . '</h6>' . esc_html__( 'Select how you would like the sales data to be displayed.', 'gravityforms' ),
3109:                 'markup'  => $view_markup
3110:             )
3111:         );
3112: 
3113:         $payment_methods = $this->get_payment_methods( $form_id );
3114: 
3115:         $payment_method_markup = "
3116:                 <div>
3117:                     <select id='gaddon-sales-group' name='payment_method'>
3118:                         <option value=''>" . esc_html__( _x( 'Any', 'regarding a payment method', 'gravityforms' ) ) . '</option>';
3119: 
3120:         foreach ( $payment_methods as $payment_method ) {
3121:             $payment_method_markup .= "<option value='" . esc_attr( $payment_method ) . "' " . selected( $payment_method, rgget( 'payment_method' ), false ) . '>' . $payment_method . '</option>';
3122:         }
3123:         $payment_method_markup .= '
3124:                     </select>
3125:                  </div>';
3126: 
3127:         $payment_method_filter = array(
3128:             'payment_method' => array(
3129:                 'label'   => esc_html__( 'Payment Method', 'gravityforms' ),
3130:                 'tooltip' => '',
3131:                 'markup'  => $payment_method_markup
3132:             )
3133:         );
3134: 
3135: 
3136:         $filter_ui = array_merge( $view_filter, $payment_method_filter, $filter_ui );
3137: 
3138:         return $filter_ui;
3139: 
3140:     }
3141: 
3142:     public function get_payment_methods( $form_id ) {
3143:         global $wpdb;
3144: 
3145:         $entry_table_name = self::get_entry_table_name();
3146: 
3147:         $payment_methods = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT payment_method FROM {$entry_table_name} WHERE form_id=%d", $form_id ) );
3148: 
3149:         return array_filter( $payment_methods, array( $this, 'array_filter_non_blank' ) );
3150:     }
3151: 
3152:     public function array_filter_non_blank( $value ) {
3153:         if ( empty( $value ) || $value == 'null' ) {
3154:             return false;
3155:         }
3156: 
3157:         return true;
3158:     }
3159: 
3160:     3161: 3162: 3163: 3164: 3165: 3166: 3167: 3168: 3169: 3170: 3171: 
3172:     public static function get_entry_table_name() {
3173: 
3174:         return version_compare( self::get_gravityforms_db_version(), '2.3-dev-1', '<' ) ? GFFormsModel::get_lead_table_name() : GFFormsModel::get_entry_table_name();
3175: 
3176:     }
3177: 
3178:     3179: 3180: 3181: 3182: 3183: 3184: 3185: 3186: 3187: 3188: 3189: 
3190:     public static function get_entry_meta_table_name() {
3191: 
3192:         return version_compare( self::get_gravityforms_db_version(), '2.3-dev-1', '<' ) ? GFFormsModel::get_lead_meta_table_name() : GFFormsModel::get_entry_meta_table_name();
3193: 
3194:     }
3195: 
3196:     3197: 3198: 3199: 3200: 3201: 3202: 3203: 3204: 3205: 
3206:     public static function get_gravityforms_db_version() {
3207: 
3208:         return method_exists( 'GFFormsModel', 'get_database_version' ) ? GFFormsModel::get_database_version() : GFForms::$version;
3209: 
3210:     }
3211: 
3212:     
3213:     public function uninstall() {
3214:         global $wpdb;
3215: 
3216:         $entry_meta_table_name = self::get_entry_meta_table_name();
3217: 
3218:         
3219:         $sql = $wpdb->prepare(
3220:             "DELETE FROM {$wpdb->prefix}gf_addon_payment_transaction
3221:                                 WHERE lead_id IN
3222:                                    (SELECT lead_id FROM {$entry_meta_table_name} WHERE meta_key='payment_gateway' AND meta_value=%s)", $this->_slug
3223:         );
3224:         $wpdb->query( $sql );
3225: 
3226:         
3227:         $sql = $wpdb->prepare( "DELETE FROM {$wpdb->prefix}gf_addon_payment_callback WHERE addon_slug=%s", $this->_slug );
3228:         $wpdb->query( $sql );
3229: 
3230:         
3231:         wp_clear_scheduled_hook( $this->_slug . '_cron' );
3232: 
3233:         parent::uninstall();
3234:     }
3235: 
3236:     
3237:     public function scripts() {
3238:         $min     = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
3239:         $scripts = array(
3240:             array(
3241:                 'handle'  => 'gaddon_payment',
3242:                 'src'     => $this->get_gfaddon_base_url() . "/js/gaddon_payment{$min}.js",
3243:                 'version' => GFCommon::$version,
3244:                 'strings' => array(
3245:                     'subscriptionCancelWarning' => __( "Warning! This subscription will be canceled. This cannot be undone. 'OK' to cancel subscription, 'Cancel' to stop", 'gravityforms' ),
3246:                     'subscriptionCancelNonce'   => wp_create_nonce( 'gaddon_cancel_subscription' ),
3247:                     'subscriptionCanceled'      => __( 'Canceled', 'gravityforms' ),
3248:                     'subscriptionError'         => __( 'The subscription could not be canceled. Please try again later.', 'gravityforms' )
3249:                 ),
3250:                 'enqueue' => array(
3251:                     array( 'admin_page' => array( 'form_settings' ), 'tab' => $this->_slug ),
3252:                     array( 'admin_page' => array( 'entry_view' ) ),
3253:                 )
3254:             ),
3255:             array(
3256:                 'handle'    => 'gaddon_token',
3257:                 'src'       => $this->get_gfaddon_base_url() . "/js/gaddon_token{$min}.js",
3258:                 'version'   => GFCommon::$version,
3259:                 'deps'      => array( 'jquery' ),
3260:                 'in_footer' => false,
3261:                 'enqueue'   => array(
3262:                     array( $this, 'enqueue_creditcard_token_script' )
3263:                 )
3264:             ),
3265:             array(
3266:                 'handle'  => 'gform_form_admin',
3267:                 'enqueue' => array(
3268:                     array( 'admin_page' => array( 'entry_edit' ) ),
3269:                 ),
3270:             ),
3271:         );
3272: 
3273:         return array_merge( parent::scripts(), $scripts );
3274:     }
3275: 
3276: 
3277:     
3278:     3279: 3280: 3281: 3282: 3283: 3284: 3285: 3286: 
3287:     public function creditcard_token_info( $form ) {
3288: 
3289:         return array();
3290: 
3291:     }
3292: 
3293:     3294: 3295: 3296: 3297: 3298: 3299: 3300: 3301: 3302: 3303: 3304: 3305: 
3306:     public function add_creditcard_token_input( $content, $field, $value, $entry_id, $form_id ) {
3307: 
3308:         if ( ! $this->has_feed( $form_id ) || GFFormsModel::get_input_type( $field ) != 'creditcard' ) {
3309:             return $content;
3310:         }
3311: 
3312:         $form = GFAPI::get_form( $form_id );
3313:         if ( ! $this->creditcard_token_info( $form ) ) {
3314:             return $content;
3315:         }
3316: 
3317:         $slug = str_replace( 'gravityforms', '', $this->_slug );
3318:         $content .= '<input type=\'hidden\' name=\'' . $slug . '_response\' id=\'gf_' . $slug . '_response\' value=\'' . rgpost( $slug . '_response' ) . '\' />';
3319: 
3320:         return $content;
3321: 
3322:     }
3323: 
3324:     3325: 3326: 3327: 3328: 3329: 3330: 3331: 3332: 
3333:     public function force_ajax_for_creditcard_tokens( $args ) {
3334: 
3335:         $form = GFAPI::get_form( rgar( $args, 'form_id' ) );
3336: 
3337:         $args['ajax'] = $this->enqueue_creditcard_token_script( $form ) ? true : $args['ajax'];
3338: 
3339:         return $args;
3340: 
3341:     }
3342: 
3343:     3344: 3345: 3346: 3347: 3348: 3349: 3350: 3351: 
3352:     public function enqueue_creditcard_token_script( $form ) {
3353: 
3354:         return $form && $this->has_feed( $form['id'] ) && $this->creditcard_token_info( $form );
3355: 
3356:     }
3357: 
3358:     3359: 3360: 3361: 3362: 3363: 3364: 3365: 3366: 3367: 3368: 
3369:     public function register_creditcard_token_script( $form, $field_values, $is_ajax ) {
3370: 
3371:         if ( ! $this->enqueue_creditcard_token_script( $form ) ) {
3372:             return;
3373:         }
3374: 
3375:         
3376:         $gftoken = array(
3377:             'callback'      => 'GF_' . str_replace( ' ', '', $this->_short_title ),
3378:             'feeds'         => $this->creditcard_token_info( $form ),
3379:             'formId'        => rgar( $form, 'id' ),
3380:             'hasPages'      => GFCommon::has_pages( $form ),
3381:             'pageCount'     => GFFormDisplay::get_max_page_number( $form ),
3382:             'responseField' => '#gf_' . str_replace( 'gravityforms', '', $this->_slug ) . '_response'
3383:         );
3384: 
3385:         
3386:         $gftoken['fields'] = $this->get_creditcard_token_entry_fields( $gftoken['feeds'] );
3387: 
3388:         $script = 'new GFToken( ' . json_encode( $gftoken ) . ' );';
3389:         GFFormDisplay::add_init_script( $form['id'], 'GFToken', GFFormDisplay::ON_PAGE_RENDER, $script );
3390: 
3391:     }
3392: 
3393:     3394: 3395: 3396: 3397: 3398: 3399: 3400: 3401: 
3402:     public function get_creditcard_token_entry_fields( $feeds ) {
3403: 
3404:         $fields = array();
3405: 
3406:         foreach ( $feeds as $feed ) {
3407:             foreach ( $feed['billing_fields'] as $billing_field ) {
3408:                 $fields[] = $billing_field;
3409:             }
3410:         }
3411: 
3412:         return array_unique( $fields );
3413: 
3414:     }
3415: 
3416:     
3417:     3418: 3419: 3420: 3421: 3422: 3423: 
3424:     public function supported_currencies( $currencies ) {
3425:         return $currencies;
3426:     }
3427: 
3428:     3429: 3430: 3431: 3432: 3433: 3434: 
3435:     public function get_currency( $currency_code = '' ) {
3436:         if ( empty( $currency_code ) ) {
3437:             $currency_code = GFCommon::get_currency();
3438:         }
3439: 
3440:         return new RGCurrency( $currency_code );
3441:     }
3442: 
3443:     3444: 3445: 3446: 3447: 3448: 3449: 3450: 3451: 3452: 
3453:     public function get_amount_export( $amount, $currency_code = '' ) {
3454:         $currency = $this->get_currency( $currency_code );
3455:         $amount   = $currency->to_number( $amount );
3456: 
3457:         if ( $this->_requires_smallest_unit && ! $currency->is_zero_decimal() ) {
3458:             return (int) round( $amount * 100 );
3459:         }
3460: 
3461:         return $amount;
3462:     }
3463: 
3464:     3465: 3466: 3467: 3468: 3469: 3470: 3471: 
3472:     public function get_amount_import( $amount, $currency_code = '' ) {
3473:         $currency = $this->get_currency( $currency_code );
3474: 
3475:         if ( $this->_requires_smallest_unit && ! $currency->is_zero_decimal() ) {
3476:             return $amount / 100;
3477:         }
3478: 
3479:         return $amount;
3480:     }
3481: 
3482:     3483: 3484: 3485: 3486: 3487: 3488: 3489: 3490: 3491: 
3492:     public function maybe_add_action_amount_formatted( $action, $currency_code = '' ) {
3493:         if ( ! empty( $action['amount'] ) && empty( $action['amount_formatted'] ) ) {
3494:             $action['amount_formatted'] = GFCommon::to_money( $action['amount'], $currency_code );
3495:         }
3496: 
3497:         return $action;
3498:     }
3499: 
3500: 
3501:     
3502:     public function entry_info( $form_id, $entry ) {
3503: 
3504:         
3505:         if ( ! $this->payment_method_is_overridden( 'cancel' ) ) {
3506:             return;
3507:         }
3508: 
3509:         
3510:         $cancelsub_button = '';
3511:         if ( $entry['transaction_type'] == '2' && $entry['payment_status'] <> 'Cancelled' && $this->is_payment_gateway( $entry['id'] ) ) {
3512:             ?>
3513:             <input id="cancelsub" type="button" name="cancelsub"
3514:                    value="<?php esc_html_e( 'Cancel Subscription', 'gravityforms' ) ?>" class="button"
3515:                    onclick="cancel_subscription(<?php echo absint( $entry['id'] ); ?>);"
3516:                    onkeypress="cancel_subscription(<?php echo absint( $entry['id'] ); ?>);"/>
3517:             <img src="<?php echo GFCommon::get_base_url() ?>/images/spinner.gif" id="subscription_cancel_spinner"
3518:                  style="display: none;"/>
3519: 
3520:             <script type="text/javascript">
3521: 
3522:             </script>
3523: 
3524:             <?php
3525:         }
3526:     }
3527: 
3528:     3529: 3530: 3531: 3532: 
3533:     public function entry_deleted( $entry_id ) {
3534:         global $wpdb;
3535: 
3536:         
3537:         $wpdb->delete( "{$wpdb->prefix}gf_addon_payment_transaction", array( 'lead_id' => $entry_id ), array( '%d' ) );
3538: 
3539:         
3540:         $wpdb->delete( "{$wpdb->prefix}gf_addon_payment_callback", array( 'lead_id' => $entry_id ), array( '%d' ) );
3541:     }
3542: 
3543:     public function ajax_cancel_subscription() {
3544:         check_ajax_referer( 'gaddon_cancel_subscription', 'gaddon_cancel_subscription' );
3545: 
3546:         $entry_id = $_POST['entry_id'];
3547: 
3548:         $this->log_debug( __METHOD__ . '(): Processing request for entry #' . $entry_id );
3549: 
3550:         $entry = GFAPI::get_entry( $entry_id );
3551:         $form  = GFAPI::get_form( $entry['form_id'] );
3552:         $feed  = $this->get_payment_feed( $entry, $form );
3553: 
3554:         
3555:         if ( empty ( $feed ) ) {
3556:             $this->log_debug( __METHOD__ . '(): Aborting. Entry does not have a feed.' );
3557: 
3558:             return;
3559:         }
3560: 
3561:         if ( $this->cancel( $entry, $feed ) ) {
3562:             $this->cancel_subscription( $entry, $feed );
3563:             die( '1' );
3564:         } else {
3565:             $this->log_debug( __METHOD__ . '(): Aborting. Unable to cancel subscription.' );
3566:             die( '0' );
3567:         }
3568: 
3569:     }
3570: 
3571:     3572: 3573: 3574: 3575: 3576: 3577: 
3578:     public function before_delete_field( $form_id, $field_id ) {
3579:         if ( $this->_requires_credit_card ) {
3580:             $form  = GFAPI::get_form( $form_id );
3581:             $field = $this->get_credit_card_field( $form );
3582: 
3583:             if ( is_object( $field ) && $field->id == $field_id ) {
3584:                 $feeds = $this->get_feeds( $form_id );
3585:                 foreach ( $feeds as $feed ) {
3586:                     if ( $feed['is_active'] ) {
3587:                         $this->update_feed_active( $feed['id'], 0 );
3588:                     }
3589:                 }
3590:             }
3591:         }
3592:     }
3593: 
3594: 
3595:     
3596: 
3597:     private function payment_method_is_overridden( $method_name, $base_class = 'GFPaymentAddOn' ) {
3598:         return parent::method_is_overridden( $method_name, $base_class );
3599:     }
3600: 
3601:     public function authorization_error( $error_message ) {
3602:         return array( 'error_message' => $error_message, 'is_success' => false, 'is_authorized' => false );
3603:     }
3604: 
3605:     public function remove_spaces_from_card_number( $card_number ) {
3606:         $card_number = str_replace( array( "\t", "\n", "\r", ' ' ), '', $card_number );
3607: 
3608:         return $card_number;
3609:     }
3610: 
3611:     public function get_supports_callback(){
3612:         return $this->_supports_callbacks;
3613:     }
3614: }
3615: 
3616: if ( ! class_exists( 'WP_List_Table' ) ) {
3617:     require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
3618: }
3619: 
3620: 
3621: class GFPaymentStatsTable extends WP_List_Table {
3622: 
3623:     private $_rows = array();
3624:     private $_page_size = 10;
3625:     private $_total_items = 0;
3626: 
3627:     function __construct( $columns, $rows, $total_count, $page_size ) {
3628:         $this->_rows        = $rows;
3629:         $this->_total_items = $total_count;
3630:         $this->_page_size   = $page_size;
3631: 
3632:         $this->_column_headers = array(
3633:             $columns,
3634:             array(),
3635:             array(),
3636:             rgar( array_values( $columns ), 2 ),
3637:         );
3638: 
3639:         parent::__construct(
3640:             array(
3641:                 'singular' => esc_html__( 'sale', 'gravityforms' ),
3642:                 'plural'   => esc_html__( 'sales', 'gravityforms' ),
3643:                 'ajax'     => false,
3644:                 'screen'   => 'gaddon_sales',
3645:             )
3646:         );
3647:     }
3648: 
3649:     function prepare_items() {
3650:         $this->items = $this->_rows;
3651: 
3652:         $this->set_pagination_args( array( 'total_items' => $this->_total_items, 'per_page' => $this->_page_size ) );
3653:     }
3654: 
3655:     function no_items() {
3656:         esc_html_e( "There hasn't been any sales in the specified date range.", 'gravityforms' );
3657:     }
3658: 
3659:     function get_columns() {
3660:         return $this->_column_headers[0];
3661:     }
3662: 
3663:     function column_default( $item, $column ) {
3664:         return rgar( $item, $column );
3665:     }
3666: 
3667:     function column_revenue( $item ) {
3668:         return GFCommon::to_money( $item['revenue'] );
3669:     }
3670: 
3671:     function ( $which ) {
3672:         if ( empty( $this->_pagination_args ) ) {
3673:             return;
3674:         }
3675: 
3676:         $total_items = $this->get_pagination_arg( 'total_items' );
3677:         $total_pages = $this->get_pagination_arg( 'total_pages' );
3678: 
3679:         $output = '<span class="displaying-num">' . sprintf( _n( '1 item', '%s items', $total_items, 'gravityforms' ), number_format_i18n( $total_items ) ) . '</span>';
3680: 
3681:         $current = $this->get_pagenum();
3682: 
3683:         $page_links = array();
3684: 
3685:         $disable_first = $disable_last = '';
3686:         if ( $current == 1 ) {
3687:             $disable_first = ' disabled';
3688:         }
3689:         if ( $current == $total_pages ) {
3690:             $disable_last = ' disabled';
3691:         }
3692: 
3693:         $page_links[] = sprintf(
3694:             "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"1\"); gresults.getResults();' onkeypress='gresults.setCustomFilter(\"paged\", \"1\"); gresults.getResults();'>%s</a>",
3695:             'first-page' . $disable_first,
3696:             esc_attr__( 'Go to the first page', 'gravityforms' ),
3697:             '«'
3698:         );
3699: 
3700:         $page_links[] = sprintf(
3701:             "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");' onkeypress='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");'>%s</a>",
3702:             'prev-page' . $disable_first,
3703:             esc_attr__( 'Go to the previous page', 'gravityforms' ),
3704:             max( 1, $current - 1 ),
3705:             max( 1, $current - 1 ),
3706:             '‹'
3707:         );
3708: 
3709: 
3710:         $html_current_page = $current;
3711: 
3712:         $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
3713:         $page_links[]     = '<span class="paging-input">' . sprintf( esc_html_x( '%1$s of %2$s', 'paging', 'gravityforms' ), $html_current_page, $html_total_pages ) . '</span>';
3714: 
3715:         $page_links[] = sprintf(
3716:             "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");' onkeypress='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");'>%s</a>",
3717:             'next-page' . $disable_last,
3718:             esc_attr__( 'Go to the next page', 'gravityforms' ),
3719:             min( $total_pages, $current + 1 ),
3720:             min( $total_pages, $current + 1 ),
3721:             '›'
3722:         );
3723: 
3724:         $page_links[] = sprintf(
3725:             "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");'>%s</a>",
3726:             'last-page' . $disable_last,
3727:             esc_attr__( 'Go to the last page', 'gravityforms' ),
3728:             $total_pages,
3729:             '»'
3730:         );
3731: 
3732:         $pagination_links_class = 'pagination-links';
3733:         if ( ! empty( $infinite_scroll ) ) {
3734:             $pagination_links_class = ' hide-if-js';
3735:         }
3736:         $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
3737: 
3738:         if ( $total_pages ) {
3739:             $page_class = $total_pages < 2 ? ' one-page' : '';
3740:         } else {
3741:             $page_class = ' no-pages';
3742:         }
3743: 
3744:         $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
3745: 
3746:         echo $this->_pagination;
3747:     }
3748: 
3749: }
3750: