Overview

Classes

  • GF_Personal_Data
  • GFAddOn
  • GFAddOnFeedsTable
  • GFAPI
  • GFFeedAddOn
  • GFPaymentAddOn
  • GFPaymentStatsTable
  • Overview
  • Class
   1: <?php
   2: 
   3: if ( ! class_exists( 'GFForms' ) ) {
   4:     die();
   5: }
   6: 
   7: /**
   8:  * Specialist Add-On class designed for use by Add-Ons that require form feed settings
   9:  * on the form settings tab.
  10:  *
  11:  * @package GFFeedAddOn
  12:  */
  13: 
  14: require_once( 'class-gf-addon.php' );
  15: 
  16: abstract class GFFeedAddOn extends GFAddOn {
  17: 
  18:     /**
  19:      * If set to true, Add-On can have multiple feeds configured. If set to false, feed list page doesn't exist and only one feed can be configured.
  20:      * @var bool
  21:      */
  22:     protected $_multiple_feeds = true;
  23: 
  24:     /**
  25:      * If true, only first matching feed will be processed. Multiple feeds can still be configured, but only one is executed during the submission (i.e. Payment Add-Ons)
  26:      * @var bool
  27:      */
  28:     protected $_single_feed_submission = false;
  29: 
  30:     /**
  31:      * If $_single_feed_submission is true, $_single_submission_feed will store the current single submission feed as stored by the get_single_submission_feed() method.
  32:      * @var mixed (bool | Feed Object)
  33:      */
  34:     protected $_single_submission_feed = false;
  35: 
  36:     /**
  37:      * If true, users can configure what order feeds are executed in from the feed list page.
  38:      * @var bool
  39:      */
  40:     protected $_supports_feed_ordering = false;
  41: 
  42:     /**
  43:      * If true, feeds will be processed asynchronously in the background.
  44:      *
  45:      * @since 2.2
  46:      * @var bool
  47:      */
  48:     protected $_async_feed_processing = false;
  49: 
  50:     /**
  51:      * If true, feeds w/ conditional logic will evaluated on the frontend and a JS event will be triggered when the feed
  52:      * is applicable and inapplicable.
  53:      *
  54:      * @since 2.4
  55:      * @var bool
  56:      */
  57:     protected $_supports_frontend_feeds = false;
  58: 
  59:     /**
  60:      * If true, maybe_delay_feed() checks will be bypassed allowing the feeds to be processed.
  61:      * @var bool
  62:      */
  63:     protected $_bypass_feed_delay = false;
  64: 
  65:     /**
  66:      * @var string Version number of the Add-On Framework
  67:      */
  68:     private $_feed_version = '0.14';
  69:     private $_feed_settings_fields = array();
  70:     private $_current_feed_id = false;
  71: 
  72:     /**
  73:      * @since 2.4
  74:      * @var array Feeds w/ conditional logic that impacts frontend form behavior.
  75:      */
  76:     private static $_frontend_feeds = array();
  77: 
  78:     /**
  79:      * Plugin starting point. Handles hooks and loading of language files.
  80:      */
  81:     public function init() {
  82: 
  83:         parent::init();
  84: 
  85:         add_filter( 'gform_entry_post_save', array( $this, 'maybe_process_feed' ), 10, 2 );
  86:         add_action( 'gform_after_delete_form', array( $this, 'delete_feeds' ) );
  87: 
  88:         // Register GFFrontendFeeds
  89:         if( $this->_supports_frontend_feeds && ! has_action( 'gform_register_init_scripts', array( __class__, 'register_frontend_feeds_init_script' ) ) ) {
  90:             add_action( 'gform_register_init_scripts', array( __class__, 'register_frontend_feeds_init_script' ) );
  91:         }
  92: 
  93:     }
  94: 
  95:     /**
  96:      * Override this function to add AJAX hooks or to add initialization code when an AJAX request is being performed
  97:      */
  98:     public function init_ajax() {
  99: 
 100:         parent::init_ajax();
 101: 
 102:         add_action( "wp_ajax_gf_feed_is_active_{$this->_slug}", array( $this, 'ajax_toggle_is_active' ) );
 103:         add_action( 'wp_ajax_gf_save_feed_order', array( $this, 'ajax_save_feed_order' ) );
 104: 
 105:     }
 106: 
 107:     /**
 108:      * Override this function to add initialization code (i.e. hooks) for the admin site (WP dashboard)
 109:      */
 110:     public function init_admin() {
 111: 
 112:         parent::init_admin();
 113: 
 114:         add_filter( 'gform_notification_events', array( $this, 'notification_events' ), 10, 2 );
 115:         add_filter( 'gform_notes_avatar', array( $this, 'notes_avatar' ), 10, 2 );
 116:         add_action( 'gform_post_form_duplicated', array( $this, 'post_form_duplicated' ), 10, 2 );
 117: 
 118:     }
 119: 
 120:     /**
 121:      * Performs upgrade tasks when the version of the Add-On changes. To add additional upgrade tasks, override the upgrade() function, which will only get executed when the plugin version has changed.
 122:      */
 123:     public function setup() {
 124:         // upgrading Feed Add-On base class
 125:         $installed_version = get_option( 'gravityformsaddon_feed-base_version' );
 126:         if ( $installed_version != $this->_feed_version ) {
 127:             $this->upgrade_base( $installed_version );
 128:             update_option( 'gravityformsaddon_feed-base_version', $this->_feed_version );
 129:         }
 130: 
 131:         parent::setup();
 132:     }
 133: 
 134:     private function upgrade_base( $previous_version ) {
 135:         global $wpdb;
 136: 
 137:         require_once( ABSPATH . '/wp-admin/includes/upgrade.php' );
 138:         if ( ! empty( $wpdb->charset ) ) {
 139:             $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
 140:         }
 141:         if ( ! empty( $wpdb->collate ) ) {
 142:             $charset_collate .= " COLLATE $wpdb->collate";
 143:         }
 144: 
 145:         $sql = "CREATE TABLE {$wpdb->prefix}gf_addon_feed (
 146:                   id mediumint(8) unsigned not null auto_increment,
 147:                   form_id mediumint(8) unsigned not null,
 148:                   is_active tinyint(1) not null default 1,
 149:                   feed_order mediumint(8) unsigned not null default 0,
 150:                   meta longtext,
 151:                   addon_slug varchar(50),
 152:                   event_type varchar(20),
 153:                   PRIMARY KEY  (id),
 154:                   KEY addon_form (addon_slug,form_id)
 155:                 ) $charset_collate;";
 156: 
 157:         gf_upgrade()->dbDelta( $sql );
 158: 
 159:     }
 160: 
 161:     /**
 162:      * Gets called when Gravity Forms upgrade process is completed. This function is intended to be used internally, override the upgrade() function to execute database update scripts.
 163:      * @param $db_version - Current Gravity Forms database version
 164:      * @param $previous_db_version - Previous Gravity Forms database version
 165:      * @param $force_upgrade - True if this is a request to force an upgrade. False if this is a standard upgrade (due to version change)
 166:      */
 167:     public function post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade ) {
 168: 
 169:         // Forcing Upgrade
 170:         if ( $force_upgrade ) {
 171: 
 172:             $installed_version = get_option( 'gravityformsaddon_feed-base_version' );
 173: 
 174:             $this->upgrade_base( $installed_version );
 175: 
 176:             update_option( 'gravityformsaddon_feed-base_version', $this->_feed_version );
 177: 
 178:         }
 179: 
 180:         parent::post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade );
 181:     }
 182: 
 183:     public function scripts() {
 184: 
 185:         $min     = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
 186:         $scripts = array(
 187:             array(
 188:                 'handle'  => 'gform_form_admin',
 189:                 'enqueue' => array( array( 'admin_page' => array( 'form_settings' ) ) ),
 190:             ),
 191:             array(
 192:                 'handle'  => 'gform_gravityforms',
 193:                 'enqueue' => array( array( 'admin_page' => array( 'form_settings' ) ) ),
 194:             ),
 195:             array(
 196:                 'handle'  => 'gform_forms',
 197:                 'enqueue' => array( array( 'admin_page' => array( 'form_settings' ) ) ),
 198:             ),
 199:             array(
 200:                 'handle'  => 'json2',
 201:                 'enqueue' => array( array( 'admin_page' => array( 'form_settings' ) ) ),
 202:             ),
 203:             array(
 204:                 'handle'  => 'gform_placeholder',
 205:                 'enqueue' => array(
 206:                     array(
 207:                         'admin_page'  => array( 'form_settings' ),
 208:                         'field_types' => array( 'feed_condition' ),
 209:                     ),
 210:                 )
 211:             ),
 212:         );
 213: 
 214:         if ( $this->_supports_feed_ordering ) {
 215:             $scripts[] = array(
 216:                 'handle'    => 'gaddon_feedorder',
 217:                 'src'       => $this->get_gfaddon_base_url() . "/js/gaddon_feedorder{$min}.js",
 218:                 'version'   => GFCommon::$version,
 219:                 'deps'      => array( 'jquery', 'jquery-ui-sortable' ),
 220:                 'in_footer' => false,
 221:                 'enqueue'   => array(
 222:                     array(
 223:                         'admin_page' => array( 'form_settings' )
 224:                     ),
 225:                 ),
 226:             );
 227:         }
 228: 
 229:         if( $this->_supports_frontend_feeds ) {
 230:             $scripts[] = array(
 231:                 'handle'  => 'gaddon_frontend',
 232:                 'src'     => $this->get_gfaddon_base_url() . "/js/gaddon_frontend{$min}.js",
 233:                 'deps'    => array( 'jquery', 'gform_conditional_logic' ),
 234:                 'version' => GFCommon::$version,
 235:                 'enqueue' => array( array( $this, 'has_frontend_feeds' ) ),
 236:             );
 237:         }
 238: 
 239:         return array_merge( parent::scripts(), $scripts );
 240:     }
 241: 
 242:     public function uninstall() {
 243:         global $wpdb;
 244:         $sql = $wpdb->prepare( "DELETE FROM {$wpdb->prefix}gf_addon_feed WHERE addon_slug=%s", $this->_slug );
 245:         $wpdb->query( $sql );
 246: 
 247:     }
 248: 
 249:     //-------- Front-end methods ---------------------------
 250: 
 251:     /**
 252:      * Determines what feeds need to be processed for the provided entry.
 253:      *
 254:      * @access public
 255:      * @param array $entry The Entry Object currently being processed.
 256:      * @param array $form The Form Object currently being processed.
 257:      *
 258:      * @return array $entry
 259:      */
 260:     public function maybe_process_feed( $entry, $form ) {
 261: 
 262:         if ( 'spam' === $entry['status'] ) {
 263:             $this->log_debug( "GFFeedAddOn::maybe_process_feed(): Entry #{$entry['id']} is marked as spam; not processing feeds for {$this->_slug}." );
 264: 
 265:             return $entry;
 266:         }
 267: 
 268:         $this->log_debug( __METHOD__ . "(): Checking for feeds to process for entry #{$entry['id']} for {$this->_slug}." );
 269: 
 270:         $feeds = false;
 271: 
 272:         // If this is a single submission feed, get the first feed. Otherwise, get all feeds.
 273:         if ( $this->_single_feed_submission ) {
 274:             $feed = $this->get_single_submission_feed( $entry, $form );
 275:             if ( $feed ) {
 276:                 $feeds = array( $feed );
 277:             }
 278:         } else {
 279:             $feeds = $this->get_feeds( $form['id'] );
 280:         }
 281: 
 282:         // Run filters before processing feeds.
 283:         $feeds = $this->pre_process_feeds( $feeds, $entry, $form );
 284: 
 285:         // If there are no feeds to process, return.
 286:         if ( empty( $feeds ) ) {
 287:             $this->log_debug( __METHOD__ . "(): No feeds to process for entry #{$entry['id']}." );
 288:             return $entry;
 289:         }
 290: 
 291:         // Determine if feed processing needs to be delayed.
 292:         $is_delayed = $this->maybe_delay_feed( $entry, $form );
 293: 
 294:         // Initialize array of feeds that have been processed.
 295:         $processed_feeds = array();
 296: 
 297:         // Loop through feeds.
 298:         foreach ( $feeds as $feed ) {
 299: 
 300:             // Get the feed name.
 301:             $feed_name = rgempty( 'feed_name', $feed['meta'] ) ? rgar( $feed['meta'], 'feedName' ) : rgar( $feed['meta'], 'feed_name' );
 302: 
 303:             // If this feed is inactive, log that it's not being processed and skip it.
 304:             if ( ! $feed['is_active'] ) {
 305:                 $this->log_debug( "GFFeedAddOn::maybe_process_feed(): Feed is inactive, not processing feed (#{$feed['id']} - {$feed_name}) for entry #{$entry['id']}." );
 306:                 continue;
 307:             }
 308: 
 309:             // If this feed's condition is not met, log that it's not being processed and skip it.
 310:             if ( ! $this->is_feed_condition_met( $feed, $form, $entry ) ) {
 311:                 $this->log_debug( "GFFeedAddOn::maybe_process_feed(): Feed condition not met, not processing feed (#{$feed['id']} - {$feed_name}) for entry #{$entry['id']}." );
 312:                 continue;
 313:             }
 314: 
 315:             // process feed if not delayed
 316:             if ( ! $is_delayed ) {
 317: 
 318:                 // If asynchronous feed processing is enabled, add it to the processing queue.
 319:                 if ( $this->is_asynchronous( $feed, $entry, $form ) ) {
 320: 
 321:                     // Log that feed processing is being delayed.
 322:                     $this->log_debug( "GFFeedAddOn::maybe_process_feed(): Adding feed (#{$feed['id']} - {$feed_name}) for entry #{$entry['id']} for {$this->_slug} to the processing queue." );
 323: 
 324:                     // Add feed to processing queue.
 325:                     gf_feed_processor()->push_to_queue(
 326:                         array(
 327:                             'addon' => $this,
 328:                             'feed'  => $feed,
 329:                             'entry_id' => $entry['id'],
 330:                             'form_id'  => $form['id'],
 331:                         )
 332:                     );
 333: 
 334:                 } else {
 335: 
 336:                     // All requirements are met; process feed.
 337:                     $this->log_debug( "GFFeedAddOn::maybe_process_feed(): Starting to process feed (#{$feed['id']} - {$feed_name}) for entry #{$entry['id']} for {$this->_slug}" );
 338:                     $returned_entry = $this->process_feed( $feed, $entry, $form );
 339: 
 340:                     // If returned value from the process feed call is an array containing an id, set the entry to its value.
 341:                     if ( is_array( $returned_entry ) && rgar( $returned_entry, 'id' ) ) {
 342:                         $entry = $returned_entry;
 343:                     }
 344: 
 345:                     /**
 346:                      * Perform a custom action when a feed has been processed.
 347:                      *
 348:                      * @param array $feed The feed which was processed.
 349:                      * @param array $entry The current entry object, which may have been modified by the processed feed.
 350:                      * @param array $form The current form object.
 351:                      * @param GFAddOn $addon The current instance of the GFAddOn object which extends GFFeedAddOn or GFPaymentAddOn (i.e. GFCoupons, GF_User_Registration, GFStripe).
 352:                      *
 353:                      * @since 2.0
 354:                      */
 355:                     do_action( 'gform_post_process_feed', $feed, $entry, $form, $this );
 356:                     do_action( "gform_{$this->_slug}_post_process_feed", $feed, $entry, $form, $this );
 357: 
 358:                     // Log that Add-On has been fulfilled.
 359:                     $this->log_debug( 'GFFeedAddOn::maybe_process_feed(): Marking entry #' . $entry['id'] . ' as fulfilled for ' . $this->_slug );
 360:                     gform_update_meta( $entry['id'], "{$this->_slug}_is_fulfilled", true );
 361: 
 362:                     // Adding this feed to the list of processed feeds
 363:                     $processed_feeds[] = $feed['id'];
 364:                 }
 365: 
 366:             } else {
 367: 
 368:                 // Log that feed processing is being delayed.
 369:                 $this->log_debug( 'GFFeedAddOn::maybe_process_feed(): Feed processing is delayed, not processing feed for entry #' . $entry['id'] . ' for ' . $this->_slug );
 370: 
 371:                 // Delay feed.
 372:                 $this->delay_feed( $feed, $entry, $form );
 373: 
 374:             }
 375:         }
 376: 
 377:         // If any feeds were processed, save the processed feed IDs.
 378:         if ( ! empty( $processed_feeds ) ) {
 379: 
 380:             // Get current processed feeds.
 381:             $meta = gform_get_meta( $entry['id'], 'processed_feeds' );
 382: 
 383:             // If no feeds have been processed for this entry, initialize the meta array.
 384:             if ( empty( $meta ) ) {
 385:                 $meta = array();
 386:             }
 387: 
 388:             // Add this Add-On's processed feeds to the entry meta.
 389:             $meta[ $this->_slug ] = $processed_feeds;
 390: 
 391:             // Update the entry meta.
 392:             gform_update_meta( $entry['id'], 'processed_feeds', $meta );
 393: 
 394:         }
 395: 
 396:         // Return the entry object.
 397:         return $entry;
 398: 
 399:     }
 400: 
 401:     /**
 402:      * Determines if feed processing is delayed by the PayPal Standard Add-On.
 403:      *
 404:      * Also enables use of the gform_is_delayed_pre_process_feed filter.
 405:      *
 406:      * @param array $entry The Entry Object currently being processed.
 407:      * @param array $form The Form Object currently being processed.
 408:      *
 409:      * @return bool
 410:      */
 411:     public function maybe_delay_feed( $entry, $form ) {
 412:         if ( $this->_bypass_feed_delay ) {
 413:             return false;
 414:         }
 415: 
 416:         $is_delayed = false;
 417:         $slug       = $this->get_slug();
 418: 
 419:         if ( $slug != 'gravityformspaypal' && class_exists( 'GFPayPal' ) && function_exists( 'gf_paypal' ) ) {
 420:             if ( gf_paypal()->is_payment_gateway( $entry['id'] ) ) {
 421:                 $paypal_feed = gf_paypal()->get_single_submission_feed( $entry );
 422:                 if ( $paypal_feed && $this->is_delayed( $paypal_feed ) ) {
 423:                     $is_delayed = true;
 424:                 }
 425:             }
 426:         }
 427: 
 428:         /**
 429:          * Allow feed processing to be delayed.
 430:          *
 431:          * @param bool $is_delayed Is feed processing delayed?
 432:          * @param array $form The Form Object currently being processed.
 433:          * @param array $entry The Entry Object currently being processed.
 434:          * @param string $slug The Add-On slug e.g. gravityformsmailchimp
 435:          */
 436:         $is_delayed = apply_filters( 'gform_is_delayed_pre_process_feed', $is_delayed, $form, $entry, $slug );
 437:         $is_delayed = apply_filters( 'gform_is_delayed_pre_process_feed_' . $form['id'], $is_delayed, $form, $entry, $slug );
 438: 
 439:         return $is_delayed;
 440:     }
 441: 
 442:     /**
 443:      * Retrieves the delay setting for the current add-on from the PayPal feed.
 444:      *
 445:      * @param array $paypal_feed The PayPal feed which is being used to process the current submission.
 446:      *
 447:      * @return bool|null
 448:      */
 449:     public function is_delayed( $paypal_feed ) {
 450:         $delay = rgar( $paypal_feed['meta'], 'delay_' . $this->_slug );
 451: 
 452:         return $delay;
 453:     }
 454: 
 455:     /**
 456:      * Determines if feed processing should happen asynchronously.
 457:      *
 458:      * @since  2.2
 459:      * @access public
 460:      *
 461:      * @param array $feed  The Feed Object currently being processed.
 462:      * @param array $form  The Form Object currently being processed.
 463:      * @param array $entry The Entry Object currently being processed.
 464:      *
 465:      * @return bool
 466:      */
 467:     public function is_asynchronous( $feed, $entry, $form ) {
 468:         if ( $this->_bypass_feed_delay ) {
 469:             return false;
 470:         }
 471: 
 472:         /**
 473:          * Allow feed to be processed asynchronously.
 474:          *
 475:          * @since 2.2
 476:          *
 477:          * @param bool   $is_asynchronous Is feed being processed asynchronously?
 478:          * @param array  $feed            The Feed Object currently being processed.
 479:          * @param array  $entry           The Entry Object currently being processed.
 480:          * @param array  $form            The Form Object currently being processed.
 481:          */
 482:         $is_asynchronous = gf_apply_filters( array( 'gform_is_feed_asynchronous', $form['id'], $feed['id'] ), $this->_async_feed_processing, $feed, $entry, $form );
 483: 
 484:         return $is_asynchronous;
 485: 
 486:     }
 487: 
 488:     /**
 489:      * Processes feed action.
 490:      *
 491:      * @since  Unknown
 492:      * @access public
 493:      *
 494:      * @param array  $feed  The Feed Object currently being processed.
 495:      * @param array  $entry The Entry Object currently being processed.
 496:      * @param array  $form  The Form Object currently being processed.
 497:      *
 498:      * @return array|null Returns a modified entry object or null.
 499:      */
 500:     public function process_feed( $feed, $entry, $form ) {
 501: 
 502:         return;
 503:     }
 504: 
 505:     public function delay_feed( $feed, $entry, $form ) {
 506: 
 507:         return;
 508:     }
 509: 
 510:     public function is_feed_condition_met( $feed, $form, $entry ) {
 511: 
 512:         $feed_meta            = $feed['meta'];
 513:         $is_condition_enabled = rgar( $feed_meta, 'feed_condition_conditional_logic' ) == true;
 514:         $logic                = rgars( $feed_meta, 'feed_condition_conditional_logic_object/conditionalLogic' );
 515: 
 516:         if ( ! $is_condition_enabled || empty( $logic ) ) {
 517:             return true;
 518:         }
 519: 
 520:         return GFCommon::evaluate_conditional_logic( $logic, $form, $entry );
 521:     }
 522: 
 523:     /**
 524:      * Create nonce for asynchronous feed processing.
 525:      *
 526:      * @since  2.2
 527:      * @access public
 528:      *
 529:      * @return string The nonce.
 530:      */
 531:     public function create_feed_nonce() {
 532: 
 533:         $action = 'gform_' . $this->_slug . '_process_feed';
 534:         $i      = wp_nonce_tick();
 535: 
 536:         return substr( wp_hash( $i . $action, 'nonce' ), - 12, 10 );
 537: 
 538:     }
 539: 
 540:     /**
 541:      * Verify nonce for asynchronous feed processing.
 542:      *
 543:      * @since  1.0
 544:      * @access public
 545:      * @param  string $nonce Nonce to be verified.
 546:      *
 547:      * @return int|bool
 548:      */
 549:     public function verify_feed_nonce( $nonce ) {
 550: 
 551:         $action = 'gform_' . $this->_slug . '_process_feed';
 552:         $i      = wp_nonce_tick();
 553: 
 554:         // Nonce generated 0-12 hours ago.
 555:         if ( substr( wp_hash( $i . $action, 'nonce' ), - 12, 10 ) === $nonce ) {
 556:             return 1;
 557:         }
 558: 
 559:         // Nonce generated 12-24 hours ago.
 560:         if ( substr( wp_hash( ( $i - 1 ) . $action, 'nonce' ), - 12, 10 ) === $nonce ) {
 561:             return 2;
 562:         }
 563: 
 564:         // Log that nonce was unable to be verified.
 565:         $this->log_error( __METHOD__ . '(): Aborting. Unable to verify nonce.' );
 566: 
 567:         return false;
 568: 
 569:     }
 570: 
 571:     /**
 572:      * Retrieves notification events supported by Add-On.
 573:      *
 574:      * @access public
 575:      * @param array $form
 576:      * @return array
 577:      */
 578:     public function supported_notification_events( $form ) {
 579: 
 580:         return array();
 581: 
 582:     }
 583: 
 584:     /**
 585:      * Add notifications events supported by Add-On to notification events list.
 586:      *
 587:      * @access public
 588:      * @param array $events
 589:      * @param array $form
 590:      * @return array $events
 591:      */
 592:     public function notification_events( $events, $form ) {
 593: 
 594:         /* Get the supported notification events for this Add-On. */
 595:         $supported_events = $this->supported_notification_events( $form );
 596: 
 597:         /* If no events are supported, return the current array of events. */
 598:         if ( empty( $supported_events ) ) {
 599:             return $events;
 600:         }
 601: 
 602:         return array_merge( $events, $supported_events );
 603: 
 604:     }
 605: 
 606:     //--------  Feed data methods  -------------------------
 607: 
 608:     public function get_feeds( $form_id = null ) {
 609:         global $wpdb;
 610: 
 611:         $form_filter = is_numeric( $form_id ) ? $wpdb->prepare( 'AND form_id=%d', absint( $form_id ) ) : '';
 612: 
 613:         $sql = $wpdb->prepare(
 614:             "SELECT * FROM {$wpdb->prefix}gf_addon_feed
 615:                                WHERE addon_slug=%s {$form_filter} ORDER BY `feed_order`, `id` ASC", $this->_slug
 616:         );
 617: 
 618:         $results = $wpdb->get_results( $sql, ARRAY_A );
 619:         foreach ( $results as &$result ) {
 620:             $result['meta'] = json_decode( $result['meta'], true );
 621:         }
 622: 
 623:         return $results;
 624:     }
 625: 
 626:     /***
 627:      * Queries and returns all active feeds for this Add-On
 628:      *
 629:      * @since 2.4
 630:      *
 631:      * @param int $form_id The Form Id to get feeds from.
 632:      *
 633:      * @return array Returns an array with all active feeds associated with the specified form Id
 634:      */
 635:     public function get_active_feeds( $form_id = null ) {
 636:         global $wpdb;
 637: 
 638:         $form_filter = is_numeric( $form_id ) ? $wpdb->prepare( 'AND form_id=%d', absint( $form_id ) ) : '';
 639: 
 640:         $sql = $wpdb->prepare(
 641:             "SELECT * FROM {$wpdb->prefix}gf_addon_feed
 642:                                WHERE addon_slug=%s AND is_active=1 {$form_filter} ORDER BY `feed_order`, `id` ASC", $this->_slug
 643:         );
 644: 
 645:         $results = $wpdb->get_results( $sql, ARRAY_A );
 646:         foreach ( $results as &$result ) {
 647:             $result['meta'] = json_decode( $result['meta'], true );
 648:         }
 649: 
 650:         return $results;
 651:     }
 652: 
 653:     public function get_feeds_by_slug( $slug, $form_id = null ) {
 654:         global $wpdb;
 655: 
 656:         $form_filter = is_numeric( $form_id ) ? $wpdb->prepare( 'AND form_id=%d', absint( $form_id ) ) : '';
 657: 
 658:         $sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}gf_addon_feed
 659:                                WHERE addon_slug=%s {$form_filter} ORDER BY `feed_order`, `id` ASC", $slug );
 660: 
 661:         $results = $wpdb->get_results( $sql, ARRAY_A );
 662:         foreach( $results as &$result ) {
 663:             $result['meta'] = json_decode( $result['meta'], true );
 664:         }
 665: 
 666:         return $results;
 667:     }
 668: 
 669:     public function get_current_feed() {
 670:         $feed_id = $this->get_current_feed_id();
 671: 
 672:         return empty( $feed_id ) ? false : $this->get_feed( $feed_id );
 673:     }
 674: 
 675:     public function get_current_feed_id() {
 676:         if ( $this->_current_feed_id ) {
 677:             return $this->_current_feed_id;
 678:         } elseif ( ! rgempty( 'gf_feed_id' ) ) {
 679:             return rgpost( 'gf_feed_id' );
 680:         } else {
 681:             return rgget( 'fid' );
 682:         }
 683:     }
 684: 
 685:     public function get_feed( $id ) {
 686:         global $wpdb;
 687: 
 688:         $sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}gf_addon_feed WHERE id=%d", $id );
 689: 
 690:         $row = $wpdb->get_row( $sql, ARRAY_A );
 691:         if ( ! $row ) {
 692:             return false;
 693:         }
 694: 
 695:         $row['meta'] = json_decode( $row['meta'], true );
 696: 
 697:         return $row;
 698:     }
 699: 
 700:     public function get_feeds_by_entry( $entry_id ) {
 701:         $processed_feeds = gform_get_meta( $entry_id, 'processed_feeds' );
 702:         if ( ! $processed_feeds ) {
 703:             return false;
 704:         }
 705: 
 706:         return rgar( $processed_feeds, $this->_slug );
 707:     }
 708: 
 709:     public function has_feed( $form_id, $meets_conditional_logic = null ) {
 710: 
 711:         $feeds = $this->get_feeds( $form_id );
 712:         if ( ! $feeds ) {
 713:             return false;
 714:         }
 715: 
 716:         $has_active_feed = false;
 717: 
 718:         if ( $meets_conditional_logic ) {
 719:             $form  = GFFormsModel::get_form_meta( $form_id );
 720:             $entry = GFFormsModel::create_lead( $form );
 721:         }
 722: 
 723:         foreach ( $feeds as $feed ) {
 724:             if ( ! $has_active_feed && $feed['is_active'] ) {
 725:                 $has_active_feed = true;
 726:             }
 727: 
 728:             if ( $meets_conditional_logic && $feed['is_active'] && $this->is_feed_condition_met( $feed, $form, $entry ) ) {
 729:                 return true;
 730:             }
 731:         }
 732: 
 733:         return $meets_conditional_logic ? false : $has_active_feed;
 734:     }
 735: 
 736:     public function get_single_submission_feed( $entry = false, $form = false ) {
 737: 
 738:         if ( ! $entry && ! $form ) {
 739:             return false;
 740:         }
 741: 
 742:         $feed = false;
 743: 
 744:         if ( ! empty( $this->_single_submission_feed ) && ( ! $form || $this->_single_submission_feed['form_id'] == $form['id'] ) ) {
 745: 
 746:             $feed = $this->_single_submission_feed;
 747: 
 748:         } elseif ( $entry['id'] ) {
 749: 
 750:             $feeds = $this->get_feeds_by_entry( $entry['id'] );
 751: 
 752:             if ( empty( $feeds ) ) {
 753:                 $feed = $this->get_single_submission_feed_by_form( $form, $entry );
 754:             } else {
 755:                 $feed = $this->get_feed( $feeds[0] );
 756:             }
 757: 
 758:         } elseif ( $form ) {
 759: 
 760:             $feed                          = $this->get_single_submission_feed_by_form( $form, $entry );
 761:             $this->_single_submission_feed = $feed;
 762: 
 763:         }
 764: 
 765:         return $feed;
 766:     }
 767: 
 768:     /**
 769:      * Return the active feed to be used when processing the current entry, evaluating conditional logic if configured.
 770:      *
 771:      * @param array $form The current form.
 772:      * @param array|false $entry The current entry.
 773:      *
 774:      * @return bool|array
 775:      */
 776:     public function get_single_submission_feed_by_form( $form, $entry ) {
 777:         if ( $form ) {
 778:             $feeds = $this->get_feeds( $form['id'] );
 779: 
 780:             foreach ( $feeds as $_feed ) {
 781:                 if ( $_feed['is_active'] && $this->is_feed_condition_met( $_feed, $form, $entry ) ) {
 782: 
 783:                     return $_feed;
 784:                 }
 785:             }
 786:         }
 787: 
 788:         return false;
 789:     }
 790: 
 791:     public function pre_process_feeds( $feeds, $entry, $form ) {
 792: 
 793:         /**
 794:          * Modify feeds before they are processed.
 795:          *
 796:          * @param array $feeds An array of $feed objects
 797:          * @param array $entry Current entry for which feeds will be processed
 798:          * @param array $form Current form object.
 799:          *
 800:          * @since 2.0
 801:          *
 802:          * @return array An array of $feeds
 803:          */
 804:         $feeds = apply_filters( 'gform_addon_pre_process_feeds', $feeds, $entry, $form );
 805:         $feeds = apply_filters( "gform_addon_pre_process_feeds_{$form['id']}", $feeds, $entry, $form );
 806:         $feeds = apply_filters( "gform_{$this->_slug}_pre_process_feeds", $feeds, $entry, $form );
 807:         $feeds = apply_filters( "gform_{$this->_slug}_pre_process_feeds_{$form['id']}", $feeds, $entry, $form );
 808: 
 809:         return $feeds;
 810:     }
 811: 
 812:     /**
 813:      * Get default feed name.
 814:      *
 815:      * @since  Unknown
 816:      * @access public
 817:      *
 818:      * @return string
 819:      */
 820:     public function get_default_feed_name() {
 821: 
 822:         /**
 823:          * Query db to look for two formats that the feed name could have been auto-generated with
 824:          * format from migration to add-on framework: 'Feed ' . $counter
 825:          * new auto-generated format when adding new feed: $short_title . ' Feed ' . $counter
 826:          */
 827: 
 828:         // Set to zero unless a new number is found while checking existing feed names (will be incremented by 1 at the end).
 829:         $counter_to_use = 0;
 830: 
 831:         // Get Add-On feeds.
 832:         $feeds_to_filter = $this->get_feeds_by_slug( $this->_slug );
 833: 
 834:         // If feeds were found, loop through and increase counter.
 835:         if ( $feeds_to_filter ) {
 836: 
 837:             // Loop through feeds and look for name pattern to find what to make default feed name.
 838:             foreach ( $feeds_to_filter as $check ) {
 839: 
 840:                 // Get feed name and trim.
 841:                 $name = rgars( $check, 'meta/feed_name' ) ? rgars( $check, 'meta/feed_name' ) : rgars( $check, 'meta/feedName' );
 842:                 $name = trim( $name );
 843: 
 844:                 // Prepare feed name pattern.
 845:                 $pattern = '/(^Feed|^' . $this->_short_title . ' Feed)\s\d+/';
 846: 
 847:                 // Search for feed name pattern.
 848:                 preg_match( $pattern,$name,$matches );
 849: 
 850:                 // If matches were found, increase counter.
 851:                 if ( $matches ) {
 852: 
 853:                     // Number should be characters at the end after a space
 854:                     $last_space = strrpos( $matches[0], ' ' );
 855: 
 856:                     $digit = substr( $matches[0], $last_space );
 857: 
 858:                     // Counter in existing feed name greater, use it instead.
 859:                     if ( $digit >= $counter_to_use ){
 860:                         $counter_to_use = $digit;
 861:                     }
 862: 
 863:                 }
 864: 
 865:             }
 866: 
 867:         }
 868: 
 869:         // Set default feed name
 870:         $value = $this->_short_title . ' Feed ' . ($counter_to_use + 1);
 871: 
 872:         return $value;
 873: 
 874:     }
 875: 
 876:     public function is_unique_feed_name( $name, $form_id ) {
 877:         $feeds = $this->get_feeds( $form_id );
 878:         foreach ( $feeds as $feed ) {
 879:             $feed_name = rgars( $feed, 'meta/feed_name' ) ? rgars( $feed, 'meta/feed_name' ) : rgars( $feed, 'meta/feedName' );
 880:             if ( strtolower( $feed_name ) === strtolower( $name ) ) {
 881:                 return false;
 882:             }
 883:         }
 884: 
 885:         return true;
 886:     }
 887: 
 888:     public function update_feed_meta( $id, $meta ) {
 889:         global $wpdb;
 890: 
 891:         $meta = json_encode( $meta );
 892:         $wpdb->update( "{$wpdb->prefix}gf_addon_feed", array( 'meta' => $meta ), array( 'id' => $id ), array( '%s' ), array( '%d' ) );
 893: 
 894:         return $wpdb->rows_affected > 0;
 895:     }
 896: 
 897:     public function update_feed_active( $id, $is_active ) {
 898:         global $wpdb;
 899:         $is_active = $is_active ? '1' : '0';
 900: 
 901:         $wpdb->update( "{$wpdb->prefix}gf_addon_feed", array( 'is_active' => $is_active ), array( 'id' => $id ), array( '%d' ), array( '%d' ) );
 902: 
 903:         return $wpdb->rows_affected > 0;
 904:     }
 905: 
 906:     public function insert_feed( $form_id, $is_active, $meta ) {
 907:         global $wpdb;
 908: 
 909:         $meta = json_encode( $meta );
 910:         $wpdb->insert( "{$wpdb->prefix}gf_addon_feed", array( 'addon_slug' => $this->_slug, 'form_id' => $form_id, 'is_active' => $is_active, 'meta' => $meta ), array( '%s', '%d', '%d', '%s' ) );
 911: 
 912:         return $wpdb->insert_id;
 913:     }
 914: 
 915:     public function delete_feed( $id ) {
 916:         global $wpdb;
 917: 
 918:         $wpdb->delete( "{$wpdb->prefix}gf_addon_feed", array( 'id' => $id ), array( '%d' ) );
 919:     }
 920: 
 921:     public function delete_feeds( $form_id = null ) {
 922:         global $wpdb;
 923: 
 924:         $form_filter = is_numeric( $form_id ) ? $wpdb->prepare( 'AND form_id=%d', absint( $form_id ) ) : '';
 925: 
 926:         $sql = $wpdb->prepare(
 927:             "SELECT id FROM {$wpdb->prefix}gf_addon_feed
 928:                                WHERE addon_slug=%s {$form_filter} ORDER BY `feed_order`, `id` ASC", $this->_slug
 929:         );
 930: 
 931:         $feed_ids = $wpdb->get_col( $sql );
 932: 
 933:         if ( ! empty( $feed_ids ) ) {
 934:             array_map( array( $this, 'delete_feed' ), $feed_ids );
 935:         }
 936: 
 937:     }
 938: 
 939:     /**
 940:      * Duplicates the feed.
 941:      *
 942:      * @since  1.9.15
 943:      * @access public
 944:      *
 945:      * @param int|array $id          The ID of the feed to be duplicated or the feed object when duplicating a form.
 946:      * @param mixed     $new_form_id False when using feed actions or the ID of the new form when duplicating a form.
 947:      *
 948:      * @uses   GFFeedAddOn::can_duplicate_feed()
 949:      * @uses   GFFeedAddOn::get_feed()
 950:      * @uses   GFFeedAddOn::insert_feed()
 951:      * @uses   GFFeedAddOn::is_unique_feed_name()
 952:      *
 953:      * @return int New feed ID.
 954:      */
 955:     public function duplicate_feed( $id, $new_form_id = false ) {
 956: 
 957:         // Get original feed.
 958:         $original_feed = is_array( $id ) ? $id : $this->get_feed( $id );
 959: 
 960:         // If feed doesn't exist, exit.
 961:         if ( ! $original_feed || ! $this->can_duplicate_feed( $original_feed ) ) {
 962:             return;
 963:         }
 964: 
 965:         // Get feed name key.
 966:         $feed_name_key = rgars( $original_feed, 'meta/feed_name' ) ? 'feed_name' : 'feedName';
 967: 
 968:         // Make sure the new feed name is unique.
 969:         $count     = 2;
 970:         $feed_name = rgars( $original_feed, 'meta/' . $feed_name_key ) . ' - ' . esc_html__( 'Copy 1', 'gravityforms' );
 971:         while ( ! $this->is_unique_feed_name( $feed_name, $original_feed['form_id'] ) ) {
 972:             $feed_name = rgars( $original_feed, 'meta/' . $feed_name_key ) . ' - ' . sprintf( esc_html__( 'Copy %d', 'gravityforms' ), $count );
 973:             $count++;
 974:         }
 975: 
 976:         // Copy the feed meta.
 977:         $meta                   = $original_feed['meta'];
 978:         $meta[ $feed_name_key ] = $feed_name;
 979: 
 980:         if ( ! $new_form_id ) {
 981:             $new_form_id = $original_feed['form_id'];
 982:         }
 983: 
 984:         // Create the new feed.
 985:         return $this->insert_feed( $new_form_id, $original_feed['is_active'], $meta );
 986: 
 987:     }
 988: 
 989:     /**
 990:      * Maybe duplicate feeds when a form is duplicated.
 991:      *
 992:      * @param int $form_id The ID of the original form.
 993:      * @param int $new_id The ID of the duplicate form.
 994:      */
 995:     public function post_form_duplicated( $form_id, $new_id ) {
 996: 
 997:         $feeds = $this->get_feeds( $form_id );
 998: 
 999:         if ( ! $feeds ) {
1000:             return;
1001:         }
1002: 
1003:         foreach ( $feeds as $feed ) {
1004:             $this->duplicate_feed( $feed, $new_id );
1005:         }
1006: 
1007:     }
1008: 
1009:     /**
1010:      * Save order of feeds.
1011:      *
1012:      * @since  2.0
1013:      * @access public
1014:      *
1015:      * @param array $feed_order Array of feed IDs in desired order.
1016:      */
1017:     public function save_feed_order( $feed_order ) {
1018: 
1019:         global $wpdb;
1020: 
1021:         // Reindex feed order to start at 1 instead of 0.
1022:         $feed_order = array_combine( range( 1, count( $feed_order ) ), array_values( $feed_order ) );
1023: 
1024:         // Swap array keys and values.
1025:         $feed_order = array_flip( $feed_order );
1026: 
1027:         // Update each feed.
1028:         foreach ( $feed_order as $feed_id => $position ) {
1029: 
1030:             $wpdb->update(
1031:                 $wpdb->prefix . 'gf_addon_feed',
1032:                 array( 'feed_order' => $position ),
1033:                 array( 'id' => $feed_id ),
1034:                 array( '%d' ),
1035:                 array( '%d' )
1036:             );
1037: 
1038:         }
1039: 
1040:     }
1041: 
1042:     //---------- Form Settings Pages --------------------------
1043: 
1044:     public function form_settings_init() {
1045:         parent::form_settings_init();
1046:     }
1047: 
1048:     public function ajax_toggle_is_active() {
1049:         $feed_id   = rgpost( 'feed_id' );
1050:         $is_active = rgpost( 'is_active' );
1051: 
1052:         $this->update_feed_active( $feed_id, $is_active );
1053:         die();
1054:     }
1055: 
1056:     public function ajax_save_feed_order() {
1057:         check_ajax_referer( 'gform_feed_order', 'nonce' );
1058: 
1059:         if ( ! $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
1060:             return;
1061:         }
1062: 
1063:         $addon      = sanitize_text_field( rgpost( 'addon' ) );
1064:         $form_id    = absint( rgpost( 'form_id' ) );
1065:         $feed_order = rgpost( 'feed_order' ) ? rgpost( 'feed_order' ) : array();
1066:         $feed_order = array_map( 'absint', $feed_order );
1067: 
1068:         if ( $addon == $this->_slug ) {
1069:             $this->save_feed_order( $feed_order );
1070:         }
1071:     }
1072: 
1073:     public function form_settings_sections() {
1074:         return array();
1075:     }
1076: 
1077:     public function form_settings( $form ) {
1078:         if ( ! $this->_multiple_feeds || $this->is_detail_page() ) {
1079: 
1080:             // feed edit page
1081:             $feed_id = $this->_multiple_feeds ? $this->get_current_feed_id() : $this->get_default_feed_id( $form['id'] );
1082: 
1083:             $this->feed_edit_page( $form, $feed_id );
1084:         } else {
1085:             // feed list UI
1086:             $this->feed_list_page( $form );
1087:         }
1088:     }
1089: 
1090:     public function is_feed_list_page() {
1091:         return ! isset( $_GET['fid'] );
1092:     }
1093: 
1094:     public function is_detail_page() {
1095:         return ! $this->is_feed_list_page();
1096:     }
1097: 
1098:     public function form_settings_header() {
1099:         if ( $this->is_feed_list_page() ) {
1100:             $title = $this->form_settings_title();
1101:             $url = add_query_arg( array( 'fid' => 0 ) );
1102:             return $title . " <a class='add-new-h2' href='" . esc_html( $url ) . "'>" . esc_html__( 'Add New', 'gravityforms' ) . '</a>';
1103:         }
1104:     }
1105: 
1106:     public function form_settings_title() {
1107:         return sprintf( esc_html__( '%s Feeds', 'gravityforms' ), $this->get_short_title() );
1108:     }
1109: 
1110:     public function feed_edit_page( $form, $feed_id ) {
1111: 
1112:         $title = '<h3><span>' . $this->feed_settings_title() . '</span></h3>';
1113: 
1114:         if ( ! $this->can_create_feed() ) {
1115:             echo $title . '<div>' . $this->configure_addon_message() . '</div>';
1116: 
1117:             return;
1118:         }
1119: 
1120:         // Save feed if appropriate
1121:         $feed_id = $this->maybe_save_feed_settings( $feed_id, $form['id'] );
1122: 
1123:         $this->_current_feed_id = $feed_id; // So that current feed functions work when creating a new feed
1124: 
1125:         ?>
1126:         <script type="text/javascript">
1127:             <?php GFFormSettings::output_field_scripts() ?>
1128:         </script>
1129:         <?php
1130: 
1131:         echo $title;
1132: 
1133:         $feed = $this->get_feed( $feed_id );
1134:         $this->set_settings( $feed['meta'] );
1135: 
1136:         GFCommon::display_admin_message();
1137: 
1138:         $this->render_settings( $this->get_feed_settings_fields( $form ) );
1139: 
1140:     }
1141: 
1142:     public function settings( $sections ) {
1143: 
1144:         parent::settings( $sections );
1145: 
1146:         ?>
1147:         <input type="hidden" name="gf_feed_id" value="<?php echo esc_attr( $this->get_current_feed_id() ); ?>" />
1148:         <?php
1149: 
1150:     }
1151: 
1152:     public function feed_settings_title() {
1153:         return esc_html__( 'Feed Settings', 'gravityforms' );
1154:     }
1155: 
1156:     public function feed_list_page( $form = null ) {
1157:         $action = $this->get_bulk_action();
1158:         if ( $action ) {
1159:             check_admin_referer( 'feed_list', 'feed_list' );
1160:             $this->process_bulk_action( $action );
1161:         }
1162: 
1163:         $single_action = rgpost( 'single_action' );
1164:         if ( ! empty( $single_action ) ) {
1165:             check_admin_referer( 'feed_list', 'feed_list' );
1166:             $this->process_single_action( $single_action );
1167:         }
1168: 
1169:         ?>
1170: 
1171:         <h3><span><?php echo $this->feed_list_title() ?></span></h3>
1172:         <form id="gform-settings" action="" method="post">
1173:             <?php
1174:             $feed_list = $this->get_feed_table( $form );
1175:             $feed_list->prepare_items();
1176:             $feed_list->display();
1177:             ?>
1178: 
1179:             <!--Needed to save state after bulk operations-->
1180:             <input type="hidden" value="gf_edit_forms" name="page">
1181:             <input type="hidden" value="settings" name="view">
1182:             <input type="hidden" value="<?php echo esc_attr( $this->_slug ); ?>" name="subview">
1183:             <input type="hidden" value="<?php echo esc_attr( rgar( $form, 'id' ) ); ?>" name="id">
1184:             <input id="single_action" type="hidden" value="" name="single_action">
1185:             <input id="single_action_argument" type="hidden" value="" name="single_action_argument">
1186:             <?php wp_nonce_field( 'feed_list', 'feed_list' ) ?>
1187:         </form>
1188: 
1189:         <script type="text/javascript">
1190:             <?php
1191: 
1192:                 GFCommon::gf_vars();
1193: 
1194:                 if ( $this->_supports_feed_ordering ) {
1195: 
1196:                     // Prepare feed ordering options.
1197:                     $feed_order_options = array(
1198:                         'addon'  => $this->_slug,
1199:                         'formId' => rgar( $form, 'id' ),
1200:                         'nonce'  => wp_create_nonce( 'gform_feed_order' ),
1201:                     );
1202: 
1203:                     echo 'jQuery( document ).ready( function() {
1204:                         window.GFFeedOrderObj = new GFFeedOrder( ' . json_encode( $feed_order_options ) . ');
1205:                     } );';
1206: 
1207:                 }
1208: 
1209:             ?>
1210:         </script>
1211: 
1212:     <?php
1213:     }
1214: 
1215:     public function get_feed_table( $form ) {
1216: 
1217:         $columns               = $this->feed_list_columns();
1218:         $column_value_callback = array( $this, 'get_column_value' );
1219:         $feeds                 = $this->get_feeds( rgar( $form, 'id' ) );
1220:         $bulk_actions          = $this->get_bulk_actions();
1221:         $action_links          = $this->get_action_links();
1222:         $no_item_callback      = array( $this, 'feed_list_no_item_message' );
1223:         $message_callback      = array( $this, 'feed_list_message' );
1224: 
1225:         return new GFAddOnFeedsTable( $feeds, $this->_slug, $columns, $bulk_actions, $action_links, $column_value_callback, $no_item_callback, $message_callback, $this );
1226:     }
1227: 
1228:     public function feed_list_title() {
1229:         if ( ! $this->can_create_feed() ) {
1230:             return $this->form_settings_title();
1231:         }
1232: 
1233:         $url = add_query_arg( array( 'fid' => '0' ) );
1234:         $url = esc_url( $url );
1235: 
1236:         return $this->form_settings_title() . " <a class='add-new-h2' href='{$url}'>" . esc_html__( 'Add New', 'gravityforms' ) . '</a>';
1237:     }
1238: 
1239:     public function maybe_save_feed_settings( $feed_id, $form_id ) {
1240: 
1241:         if ( ! rgpost( 'gform-settings-save' ) ) {
1242:             return $feed_id;
1243:         }
1244: 
1245:         check_admin_referer( $this->_slug . '_save_settings', '_' . $this->_slug . '_save_settings_nonce' );
1246: 
1247:         if ( ! $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
1248:             GFCommon::add_error_message( esc_html__( "You don't have sufficient permissions to update the form settings.", 'gravityforms' ) );
1249:             return $feed_id;
1250:         }
1251: 
1252:         // store a copy of the previous settings for cases where action would only happen if value has changed
1253:         $feed = $this->get_feed( $feed_id );
1254:         $this->set_previous_settings( $feed['meta'] );
1255: 
1256:         $settings = $this->get_posted_settings();
1257:         $sections = $this->get_feed_settings_fields();
1258:         $settings = $this->trim_conditional_logic_vales( $settings, $form_id );
1259: 
1260:         $is_valid = $this->validate_settings( $sections, $settings );
1261:         $result   = false;
1262: 
1263:         if ( $is_valid ) {
1264:             $settings = $this->filter_settings( $sections, $settings );
1265:             $feed_id = $this->save_feed_settings( $feed_id, $form_id, $settings );
1266:             if ( $feed_id ) {
1267:                 GFCommon::add_message( $this->get_save_success_message( $sections ) );
1268:             } else {
1269:                 GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
1270:             }
1271:         } else {
1272:             GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
1273:         }
1274: 
1275:         return $feed_id;
1276:     }
1277: 
1278:     public function trim_conditional_logic_vales( $settings, $form_id ) {
1279:         if ( ! is_array( $settings ) ) {
1280:             return $settings;
1281:         }
1282:         if ( isset( $settings['feed_condition_conditional_logic_object'] ) && is_array( $settings['feed_condition_conditional_logic_object'] ) ) {
1283:             $form                                                = GFFormsModel::get_form_meta( $form_id );
1284:             $settings['feed_condition_conditional_logic_object'] = GFFormsModel::trim_conditional_logic_values_from_element( $settings['feed_condition_conditional_logic_object'], $form );
1285:         }
1286: 
1287:         return $settings;
1288:     }
1289: 
1290:     public function get_save_success_message( $sections ) {
1291:         if ( ! $this->is_detail_page() )
1292:             return parent::get_save_success_message( $sections );
1293: 
1294:         $save_button = $this->get_save_button( $sections );
1295: 
1296:         return isset( $save_button['messages']['success'] ) ? $save_button['messages']['success'] : esc_html__( 'Feed updated successfully.', 'gravityforms' );
1297:     }
1298: 
1299:     public function get_save_error_message( $sections ) {
1300:         if ( ! $this->is_detail_page() )
1301:             return parent::get_save_error_message( $sections );
1302: 
1303:         $save_button = $this->get_save_button( $sections );
1304: 
1305:         return isset( $save_button['messages']['error'] ) ? $save_button['messages']['error'] : esc_html__( 'There was an error updating this feed. Please review all errors below and try again.', 'gravityforms' );
1306:     }
1307: 
1308:     public function save_feed_settings( $feed_id, $form_id, $settings ) {
1309: 
1310:         if ( $feed_id ) {
1311:             $this->update_feed_meta( $feed_id, $settings );
1312:             $result = $feed_id;
1313:         } else {
1314:             $result = $this->insert_feed( $form_id, true, $settings );
1315:         }
1316: 
1317:         return $result;
1318:     }
1319: 
1320:     public function get_feed_settings_fields() {
1321: 
1322:         if ( ! empty( $this->_feed_settings_fields ) ) {
1323:             return $this->_feed_settings_fields;
1324:         }
1325: 
1326:         /**
1327:          * Filter the feed settings fields (typically before they are rendered on the Feed Settings edit view).
1328:          *
1329:          * @param array $feed_settings_fields An array of feed settings fields which will be displayed on the Feed Settings edit view.
1330:          * @param object $addon The current instance of the GFAddon object (i.e. GF_User_Registration, GFPayPal).
1331:          *
1332:          * @since 2.0
1333:          *
1334:          * @return array
1335:          */
1336:         $feed_settings_fields = apply_filters( 'gform_addon_feed_settings_fields', $this->feed_settings_fields(), $this );
1337:         $feed_settings_fields = apply_filters( "gform_{$this->_slug}_feed_settings_fields", $feed_settings_fields, $this );
1338: 
1339:         $this->_feed_settings_fields = $this->add_default_feed_settings_fields_props( $feed_settings_fields );
1340: 
1341:         return $this->_feed_settings_fields;
1342:     }
1343: 
1344:     public function feed_settings_fields() {
1345:         return array();
1346:     }
1347: 
1348:     public function add_default_feed_settings_fields_props( $fields ) {
1349: 
1350:         foreach ( $fields as &$section ) {
1351:             foreach ( $section['fields'] as &$field ) {
1352:                 switch ( $field['type'] ) {
1353: 
1354:                     case 'hidden':
1355:                         $field['hidden'] = true;
1356:                         break;
1357:                 }
1358: 
1359:                 if ( rgar( $field, 'name' ) === 'feedName' ) {
1360:                     $field['default_value'] = $this->get_default_feed_name();
1361:                 }
1362:             }
1363:         }
1364: 
1365:         return $fields;
1366:     }
1367: 
1368:     private function get_bulk_action() {
1369:         $action = rgpost( 'action' );
1370:         if ( empty( $action ) || $action == '-1' ) {
1371:             $action = rgpost( 'action2' );
1372:         }
1373: 
1374:         return empty( $action ) || $action == '-1' ? false : $action;
1375:     }
1376: 
1377:     /***
1378:      * Override this function to add custom bulk actions
1379:      */
1380:     public function get_bulk_actions() {
1381:         $bulk_actions = array(
1382:             'delete'    => esc_html__( 'Delete', 'gravityforms' ),
1383:         );
1384: 
1385:         return $bulk_actions;
1386:     }
1387: 
1388:     /***
1389:      * Override this function to process custom bulk actions added via the get_bulk_actions() function
1390:      *
1391:      * @param string $action : The bulk action selected by the user
1392:      */
1393:     public function process_bulk_action( $action ) {
1394:         if ( 'delete' === $action ) {
1395:             $feeds = rgpost( 'feed_ids' );
1396:             if ( is_array( $feeds ) ) {
1397:                 foreach ( $feeds as $feed_id ) {
1398:                     $this->delete_feed( $feed_id );
1399:                 }
1400:             }
1401:         }
1402:         if ( 'duplicate' === $action ) {
1403:             $feeds = rgpost( 'feed_ids' );
1404:             if ( is_array( $feeds ) ) {
1405:                 foreach ( $feeds as $feed_id ) {
1406:                     $this->duplicate_feed( $feed_id );
1407:                 }
1408:             }
1409:         }
1410:     }
1411: 
1412:     public function process_single_action( $action ) {
1413:         if ( $action == 'delete' ) {
1414:             $feed_id = absint( rgpost( 'single_action_argument' ) );
1415:             $this->delete_feed( $feed_id );
1416:         }
1417:         if ( $action == 'duplicate' ) {
1418:             $feed_id = absint( rgpost( 'single_action_argument' ) );
1419:             $this->duplicate_feed( $feed_id );
1420:         }
1421:     }
1422: 
1423:     public function get_action_links() {
1424:         $feed_id       = '_id_';
1425:         $edit_url      = add_query_arg( array( 'fid' => $feed_id ) );
1426:         $links         = array(
1427:             'edit'      => '<a href="' . esc_url( $edit_url ) . '">' . esc_html__( 'Edit', 'gravityforms' ) . '</a>',
1428:             'duplicate' => '<a href="#" onclick="gaddon.duplicateFeed(\'' . esc_js( $feed_id ) . '\');" onkeypress="gaddon.duplicateFeed(\'' . esc_js( $feed_id ) . '\');">' . esc_html__( 'Duplicate', 'gravityforms' ) . '</a>',
1429:             'delete'    => '<a class="submitdelete" onclick="javascript: if(confirm(\'' . esc_js( __( 'WARNING: You are about to delete this item.', 'gravityforms' ) ) . esc_js( __( "'Cancel' to stop, 'OK' to delete.", 'gravityforms' ) ) . '\')){ gaddon.deleteFeed(\'' . esc_js( $feed_id ) . '\'); }" onkeypress="javascript: if(confirm(\'' . esc_js( __( 'WARNING: You are about to delete this item.', 'gravityforms' ) ) . esc_js( __( "'Cancel' to stop, 'OK' to delete.", 'gravityforms' ) ) . '\')){ gaddon.deleteFeed(\'' . esc_js( $feed_id ) . '\'); }" style="cursor:pointer;">' . esc_html__( 'Delete', 'gravityforms' ) . '</a>'
1430:         );
1431: 
1432:         return $links;
1433:     }
1434: 
1435:     public function feed_list_columns() {
1436:         return array();
1437:     }
1438: 
1439:     /**
1440:      * Override this function to change the message that is displayed when the feed list is empty
1441:      * @return string The message
1442:      */
1443:     public function feed_list_no_item_message() {
1444:         $url = add_query_arg( array( 'fid' => 0 ) );
1445:         return sprintf( esc_html__( "You don't have any feeds configured. Let's go %screate one%s!", 'gravityforms' ), "<a href='" . esc_url( $url ) . "'>", '</a>' );
1446:     }
1447: 
1448:     /**
1449:      * Override this function to force a message to be displayed in the feed list (instead of data). Useful to alert users when main plugin settings haven't been completed.
1450:      * @return string|false
1451:      */
1452:     public function feed_list_message() {
1453:         if ( ! $this->can_create_feed() ) {
1454:             return $this->configure_addon_message();
1455:         }
1456: 
1457:         return false;
1458:     }
1459: 
1460:     public function configure_addon_message() {
1461: 
1462:         $settings_label = sprintf( __( '%s Settings', 'gravityforms' ), $this->get_short_title() );
1463:         $settings_link  = sprintf( '<a href="%s">%s</a>', esc_url( $this->get_plugin_settings_url() ), $settings_label );
1464: 
1465:         return sprintf( __( 'To get started, please configure your %s.', 'gravityforms' ), $settings_link );
1466: 
1467:     }
1468: 
1469:     /**
1470:      * Override this function to prevent the feed creation UI from being rendered.
1471:      * @return boolean|true
1472:      */
1473:     public function can_create_feed() {
1474:         return true;
1475:     }
1476: 
1477:     /**
1478:      * Override this function to allow the feed to being duplicated.
1479:      *
1480:      * @access public
1481:      * @param int|array $id The ID of the feed to be duplicated or the feed object when duplicating a form.
1482:      * @return boolean|true
1483:      */
1484:     public function can_duplicate_feed( $id ) {
1485:         return false;
1486:     }
1487: 
1488:     public function get_column_value( $item, $column ) {
1489:         if ( is_callable( array( $this, "get_column_value_{$column}" ) ) ) {
1490:             return call_user_func( array( $this, "get_column_value_{$column}" ), $item );
1491:         } elseif ( isset( $item[ $column ] ) ) {
1492:             return $item[ $column ];
1493:         } elseif ( isset( $item['meta'][ $column ] ) ) {
1494:             return $item['meta'][ $column ];
1495:         }
1496:     }
1497: 
1498: 
1499:     public function update_form_settings( $form, $new_form_settings ) {
1500:         $feed_id = rgar( $new_form_settings, 'id' );
1501:         foreach ( $new_form_settings as $key => $value ) {
1502:             $form[ $this->_slug ]['feeds'][ $feed_id ][ $key ] = $value;
1503:         }
1504: 
1505:         return $form;
1506:     }
1507: 
1508:     public function get_default_feed_id( $form_id ) {
1509:         global $wpdb;
1510: 
1511:         $sql = $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}gf_addon_feed WHERE addon_slug=%s AND form_id = %d LIMIT 0,1", $this->_slug, $form_id );
1512: 
1513:         $feed_id = $wpdb->get_var( $sql );
1514:         if ( ! $feed_id ) {
1515:             $feed_id = 0;
1516:         }
1517: 
1518:         return $feed_id;
1519:     }
1520: 
1521:     public function settings_feed_condition( $field, $echo = true ) {
1522: 
1523:         $conditional_logic = $this->get_feed_condition_conditional_logic();
1524:         $checkbox_field = $this->get_feed_condition_checkbox( $field );
1525: 
1526:         $hidden_field = $this->get_feed_condition_hidden_field();
1527:         $instructions = isset( $field['instructions'] ) ? $field['instructions'] : esc_html__( 'Process this feed if', 'gravityforms' );
1528:         $html         = $this->settings_checkbox( $checkbox_field, false );
1529:         $html .= $this->settings_hidden( $hidden_field, false );
1530:         $html .= '<div id="feed_condition_conditional_logic_container"><!-- dynamically populated --></div>';
1531:         $html .= '<script type="text/javascript"> var feedCondition = new FeedConditionObj({' .
1532:             'strings: { objectDescription: "' . esc_attr( $instructions ) . '" },' .
1533:             'logicObject: ' . $conditional_logic .
1534:             '}); </script>';
1535: 
1536:         if ( $this->field_failed_validation( $field ) ) {
1537:             $html .= $this->get_error_icon( $field );
1538:         }
1539: 
1540:         if ( $echo ) {
1541:             echo $html;
1542:         }
1543: 
1544:         return $html;
1545:     }
1546: 
1547:     public function get_feed_condition_checkbox( $field ) {
1548:         $checkbox_label = isset( $field['checkbox_label'] ) ? $field['checkbox_label'] : esc_html__( 'Enable Condition', 'gravityforms' );
1549: 
1550:         $checkbox_field  = array(
1551:             'name'    => 'feed_condition_conditional_logic',
1552:             'type'    => 'checkbox',
1553:             'choices' => array(
1554:                 array(
1555:                     'label' => $checkbox_label,
1556:                     'name'  => 'feed_condition_conditional_logic',
1557:                 ),
1558:             ),
1559:             'onclick' => 'ToggleConditionalLogic( false, "feed_condition" );',
1560:         );
1561: 
1562:         return $checkbox_field;
1563:     }
1564: 
1565:     public function get_feed_condition_hidden_field() {
1566:         $conditional_logic = $this->get_feed_condition_conditional_logic();
1567:         $hidden_field = array(
1568:             'name'  => 'feed_condition_conditional_logic_object',
1569:             'value' => $conditional_logic,
1570:         );
1571:         return $hidden_field;
1572:     }
1573: 
1574:     public function get_feed_condition_conditional_logic() {
1575:         $conditional_logic_object = $this->get_setting( 'feed_condition_conditional_logic_object' );
1576:         if ( $conditional_logic_object ) {
1577:             $form_id           = rgget( 'id' );
1578:             $form              = GFFormsModel::get_form_meta( $form_id );
1579:             $conditional_logic = json_encode( GFFormsModel::trim_conditional_logic_values_from_element( $conditional_logic_object, $form ) );
1580:         } else {
1581:             $conditional_logic = '{}';
1582:         }
1583:         return $conditional_logic;
1584:     }
1585: 
1586:     public function validate_feed_condition_settings( $field, $settings ) {
1587:         $checkbox_field = $this->get_feed_condition_checkbox( $field );
1588:         $this->validate_checkbox_settings( $checkbox_field, $settings );
1589: 
1590:         if ( ! isset( $settings['feed_condition_conditional_logic_object'] ) ) {
1591:             return;
1592:         }
1593: 
1594:         $conditional_logic_object = $settings['feed_condition_conditional_logic_object'];
1595:         if ( ! isset( $conditional_logic_object['conditionalLogic'] ) ) {
1596:             return;
1597:         }
1598:         $conditional_logic = $conditional_logic_object['conditionalLogic'];
1599:         $conditional_logic_safe = GFFormsModel::sanitize_conditional_logic( $conditional_logic );
1600:         if ( serialize( $conditional_logic ) != serialize( $conditional_logic_safe ) ) {
1601:             $this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
1602:         }
1603:     }
1604: 
1605:     public static function add_entry_meta( $form ) {
1606:         $entry_meta = GFFormsModel::get_entry_meta( $form['id'] );
1607:         $keys       = array_keys( $entry_meta );
1608:         foreach ( $keys as $key ) {
1609:             array_push( $form['fields'], array( 'id' => $key, 'label' => $entry_meta[ $key ]['label'] ) );
1610:         }
1611: 
1612:         return $form;
1613:     }
1614: 
1615:     public function has_feed_condition_field() {
1616: 
1617:         $fields = $this->settings_fields_only( 'feed' );
1618: 
1619:         foreach ( $fields as $field ) {
1620:             if ( $field['type'] == 'feed_condition' ) {
1621:                 return true;
1622:             }
1623:         }
1624: 
1625:         return false;
1626:     }
1627: 
1628:     public function add_delayed_payment_support( $options ) {
1629:         $this->delayed_payment_integration = $options;
1630: 
1631:         if ( is_admin() ) {
1632:             add_filter( 'gform_gravityformspaypal_feed_settings_fields', array( $this, 'add_paypal_post_payment_actions' ) );
1633:         }
1634: 
1635:         add_action( 'gform_paypal_fulfillment', array( $this, 'paypal_fulfillment' ), 10, 4 );
1636:     }
1637: 
1638:     /**
1639:      * Add the Post Payments Actions setting to the PayPal feed.
1640:      *
1641:      * @param array $feed_settings_fields The PayPal feed settings.
1642:      *
1643:      * @return array
1644:      */
1645:     public function add_paypal_post_payment_actions( $feed_settings_fields ) {
1646: 
1647:         $form_id = absint( rgget( 'id' ) );
1648:         if ( $this->has_feed( $form_id ) ) {
1649: 
1650:             $addon_label = rgar( $this->delayed_payment_integration, 'option_label' );
1651:             $choice      = array(
1652:                 'label' => $addon_label ? $addon_label : sprintf( esc_html__( 'Process %s feed only when payment is received.', 'gravityforms' ), $this->get_short_title() ),
1653:                 'name'  => 'delay_' . $this->_slug,
1654:             );
1655: 
1656:             $field_name = 'post_payment_actions';
1657:             $field      = $this->get_field( $field_name, $feed_settings_fields );
1658: 
1659:             if ( ! $field ) {
1660: 
1661:                 $fields = array(
1662:                     array(
1663:                         'name'    => $field_name,
1664:                         'label'   => esc_html__( 'Post Payment Actions', 'gravityforms' ),
1665:                         'type'    => 'checkbox',
1666:                         'choices' => array( $choice ),
1667:                         'tooltip' => '<h6>' . esc_html__( 'Post Payment Actions', 'gravityforms' ) . '</h6>' . esc_html__( 'Select which actions should only occur after payment has been received.', 'gravityforms' )
1668:                     )
1669:                 );
1670: 
1671:                 $feed_settings_fields = $this->add_field_after( 'options', $fields, $feed_settings_fields );
1672: 
1673:             } else {
1674: 
1675:                 $field['choices'][]   = $choice;
1676:                 $feed_settings_fields = $this->replace_field( $field_name, $field, $feed_settings_fields );
1677: 
1678:             }
1679:         }
1680: 
1681:         return $feed_settings_fields;
1682:     }
1683: 
1684:     public function paypal_fulfillment( $entry, $paypal_config, $transaction_id, $amount ) {
1685: 
1686:         $this->log_debug( 'GFFeedAddOn::paypal_fulfillment(): Checking PayPal fulfillment for transaction ' . $transaction_id . ' for ' . $this->_slug );
1687:         $is_fulfilled = gform_get_meta( $entry['id'], "{$this->_slug}_is_fulfilled" );
1688:         if ( $is_fulfilled || ! $this->is_delayed( $paypal_config ) ) {
1689:             $this->log_debug( 'GFFeedAddOn::paypal_fulfillment(): Entry ' . $entry['id'] . ' is already fulfilled or feeds are not delayed. No action necessary.' );
1690:             return false;
1691:         }
1692: 
1693:         $form                     = RGFormsModel::get_form_meta( $entry['form_id'] );
1694:         $this->_bypass_feed_delay = true;
1695:         $this->maybe_process_feed( $entry, $form );
1696: 
1697:     }
1698: 
1699:     //--------------- Notes ------------------
1700:     public function add_feed_error( $error_message, $feed, $entry, $form ) {
1701: 
1702:         /* Log debug error before we prepend the error name. */
1703:         $backtrace = debug_backtrace();
1704:         $method    = $backtrace[1]['class'] . '::' . $backtrace[1]['function'];
1705:         $this->log_error( $method . '(): ' . $error_message );
1706: 
1707:         /* Prepend feed name to the error message. */
1708:         $feed_name     = rgars( $feed, 'meta/feed_name' ) ? rgars( $feed, 'meta/feed_name' ) : rgars( $feed, 'meta/feedName' );
1709:         $error_message = $feed_name . ': ' . $error_message;
1710: 
1711:         /* Add error note to the entry. */
1712:         $this->add_note( $entry['id'], $error_message, 'error' );
1713: 
1714:         /* Get Add-On slug */
1715:         $slug = str_replace( 'gravityforms', '', $this->_slug );
1716: 
1717:         /* Process any error actions. */
1718:         gf_do_action( array( "gform_{$slug}_error", $form['id'] ), $feed, $entry, $form );
1719: 
1720:     }
1721: 
1722:     // TODO: Review for Deprecation ------------------
1723: 
1724:     public function get_paypal_feed( $form_id, $entry ) {
1725: 
1726:         if ( ! class_exists( 'GFPayPal' ) ) {
1727:             return false;
1728:         }
1729: 
1730:         if ( method_exists( 'GFPayPal', 'get_config_by_entry' ) ) {
1731:             $feed = GFPayPal::get_config_by_entry( $entry );
1732:         } elseif ( method_exists( 'GFPayPal', 'get_config' ) ) {
1733:             $feed = GFPayPal::get_config( $form_id );
1734:         } else {
1735:             $feed = false;
1736:         }
1737: 
1738:         return $feed;
1739:     }
1740: 
1741:     public function has_paypal_payment( $feed, $form, $entry ) {
1742: 
1743:         $products = GFCommon::get_product_fields( $form, $entry );
1744: 
1745:         $payment_field   = $feed['meta']['transactionType'] === 'product' ? $feed['meta']['paymentAmount'] : $feed['meta']['recurringAmount'];
1746:         $setup_fee_field = rgar( $feed['meta'], 'setupFee_enabled' ) ? $feed['meta']['setupFee_product'] : false;
1747:         $trial_field     = rgar( $feed['meta'], 'trial_enabled' ) ? rgars( $feed, 'meta/trial_product' ) : false;
1748: 
1749:         $amount       = 0;
1750:         $line_items   = array();
1751:         $discounts    = array();
1752:         $fee_amount   = 0;
1753:         $trial_amount = 0;
1754:         foreach ( $products['products'] as $field_id => $product ) {
1755: 
1756:             $quantity      = $product['quantity'] ? $product['quantity'] : 1;
1757:             $product_price = GFCommon::to_number( $product['price'] );
1758: 
1759:             $options = array();
1760:             if ( is_array( rgar( $product, 'options' ) ) ) {
1761:                 foreach ( $product['options'] as $option ) {
1762:                     $options[] = $option['option_name'];
1763:                     $product_price += $option['price'];
1764:                 }
1765:             }
1766: 
1767:             $is_trial_or_setup_fee = false;
1768: 
1769:             if ( ! empty( $trial_field ) && $trial_field === $field_id ) {
1770: 
1771:                 $trial_amount          = $product_price * $quantity;
1772:                 $is_trial_or_setup_fee = true;
1773: 
1774:             } elseif ( ! empty( $setup_fee_field ) && $setup_fee_field === $field_id ) {
1775: 
1776:                 $fee_amount            = $product_price * $quantity;
1777:                 $is_trial_or_setup_fee = true;
1778:             }
1779: 
1780:             // Do not add to line items if the payment field selected in the feed is not the current field.
1781:             if ( is_numeric( $payment_field ) && $payment_field != $field_id ) {
1782:                 continue;
1783:             }
1784: 
1785:             // Do not add to line items if the payment field is set to "Form Total" and the current field was used for trial or setup fee.
1786:             if ( $is_trial_or_setup_fee && ! is_numeric( $payment_field ) ) {
1787:                 continue;
1788:             }
1789: 
1790:             $amount += $product_price * $quantity;
1791: 
1792:         }
1793: 
1794: 
1795:         if ( ! empty( $products['shipping']['name'] ) && ! is_numeric( $payment_field ) ) {
1796:             $line_items[] = array( 'id'          => '',
1797:                                    'name'        => $products['shipping']['name'],
1798:                                    'description' => '',
1799:                                    'quantity'    => 1,
1800:                                    'unit_price'  => GFCommon::to_number( $products['shipping']['price'] ),
1801:                                    'is_shipping' => 1
1802:             );
1803:             $amount += $products['shipping']['price'];
1804:         }
1805: 
1806:         return $amount > 0;
1807:     }
1808: 
1809:     public function is_delayed_payment( $entry, $form, $is_delayed ) {
1810:         if ( $this->_slug == 'gravityformspaypal' ) {
1811:             return false;
1812:         }
1813: 
1814:         $paypal_feed = $this->get_paypal_feed( $form['id'], $entry );
1815:         if ( ! $paypal_feed ) {
1816:             return false;
1817:         }
1818: 
1819:         $has_payment = self::get_paypal_payment_amount( $form, $entry, $paypal_feed ) > 0;
1820: 
1821:         return rgar( $paypal_feed['meta'], "delay_{$this->_slug}" ) && $has_payment && ! $is_delayed;
1822:     }
1823: 
1824:     public static function get_paypal_payment_amount( $form, $entry, $paypal_config ) {
1825: 
1826:         // TODO: need to support old "paypal_config" format as well as new format when delayed payment suported feed addons are released
1827:         $products        = GFCommon::get_product_fields( $form, $entry, true );
1828:         $recurring_field = rgar( $paypal_config['meta'], 'recurring_amount_field' );
1829:         $total           = 0;
1830:         foreach ( $products['products'] as $id => $product ) {
1831: 
1832:             if ( $paypal_config['meta']['type'] != 'subscription' || $recurring_field == $id || $recurring_field == 'all' ) {
1833:                 $price = GFCommon::to_number( $product['price'] );
1834:                 if ( is_array( rgar( $product, 'options' ) ) ) {
1835:                     foreach ( $product['options'] as $option ) {
1836:                         $price += GFCommon::to_number( $option['price'] );
1837:                     }
1838:                 }
1839: 
1840:                 $total += $price * $product['quantity'];
1841:             }
1842:         }
1843: 
1844:         if ( 'all' === $recurring_field && ! empty( $products['shipping']['price'] ) ) {
1845:             $total += floatval( $products['shipping']['price'] );
1846:         }
1847: 
1848:         return $total;
1849:     }
1850: 
1851: 
1852: 
1853:     public function has_frontend_feeds( $form ) {
1854:         $result = $this->register_frontend_feeds( $form );
1855:         return ! empty( $result );
1856:     }
1857: 
1858:     /***
1859:      * Registers front end feeds with the private $_frontend_feeds array.
1860:      *
1861:      * @since 2.4
1862:      *
1863:      * @param array $form The current Form Object
1864:      *
1865:      * @return bool Returns true if one ore more feeds were registered, false if no feeds were registered
1866:      */
1867:     public function register_frontend_feeds( $form ) {
1868: 
1869:         if ( ! isset( self::$_frontend_feeds[ $form['id'] ] ) ) {
1870:             self::$_frontend_feeds[ $form['id'] ] = array();
1871:         }
1872: 
1873:         $feeds = $this->get_frontend_feeds( $form );
1874: 
1875:         $this->add_frontend_feeds( $form['id'], $feeds );
1876: 
1877:         return ! empty( $feeds );
1878:     }
1879: 
1880:     /***
1881:      * Loads front end feeds into the private $_frontend_feeds array, making sure not to add duplicate feeds.
1882:      *
1883:      * @since 2.4
1884:      *
1885:      * @param int   $form_id The current Form Id
1886:      * @param array $feeds   An array of all feeds to be loaded into the $_frontend_feeds variable
1887:      */
1888:     public function add_frontend_feeds( $form_id, $feeds ) {
1889: 
1890:         foreach ( $feeds as $feed ) {
1891:             $filter = array( 'feedId' => $feed['feedId'], 'addonSlug' => $feed['addonSlug'] );
1892:             $found = wp_list_filter( self::$_frontend_feeds[ $form_id ], $filter );
1893: 
1894:             if ( empty( $found ) ) {
1895:                 self::$_frontend_feeds[ $form_id ][] = $feed;
1896:             }
1897:         }
1898:     }
1899: 
1900:     /***
1901:      * Gets an array of all feeds eligible to be a Front End Feed.
1902:      *
1903:      * @since 2.4
1904:      *
1905:      * @param array $form The Form object to get Frontend Feeds from
1906:      *
1907:      * @return array An array with feeds eligible to be a Front End Feed. By default only feedId, addonSlug, conditionalLogic and isSingleFeed properties are returned in the array.
1908:      */
1909:     public function get_frontend_feeds( $form ) {
1910: 
1911:         if ( ! $this->_supports_frontend_feeds ) {
1912:             return array();
1913:         }
1914: 
1915:         $feeds = $this->get_active_feeds( $form['id'] );
1916:         if ( empty( $feeds ) ) {
1917:             return array();
1918:         }
1919: 
1920:         $frontend_feeds = array();
1921: 
1922:         foreach ( $feeds as $feed ) {
1923: 
1924:             $_feed = array(
1925:                 'feedId'           => $feed['id'],
1926:                 'addonSlug'        => $this->_slug,
1927:                 'conditionalLogic' => rgars( $feed, 'meta/feed_condition_conditional_logic' ) === '0' ? false : rgars( $feed, 'meta/feed_condition_conditional_logic_object/conditionalLogic', false ),
1928:                 'isSingleFeed'     => $this->_single_feed_submission,
1929:             );
1930: 
1931:             $_feed = apply_filters( 'gform_addon_frontend_feed',                           $_feed, $form, $feed );
1932:             $_feed = apply_filters( "gform_addon_frontend_feed_{$form['id']}",             $_feed, $form, $feed );
1933:             $_feed = apply_filters( "gform_{$this->_slug}_frontend_feed",                  $_feed, $form, $feed );
1934:             $_feed = apply_filters( "gform_{$this->_slug}_frontend_feed_{$form['id']}",    $_feed, $form, $feed );
1935: 
1936:             $frontend_feeds[] = $_feed;
1937: 
1938:         }
1939: 
1940:         return $frontend_feeds;
1941:     }
1942: 
1943:     /***
1944:      * Registers frontend feeds by rendering the GFFrontEndFeeds() JS object.
1945:      *
1946:      * @since 2.4
1947:      *
1948:      * @param array $form The current Form object
1949:      */
1950:     public static function register_frontend_feeds_init_script( $form ) {
1951: 
1952:         $feeds = rgar( self::$_frontend_feeds, $form['id'] );
1953:         if ( empty( $feeds ) ) {
1954:             return;
1955:         }
1956: 
1957:         $args = array(
1958:             'formId' => $form['id'],
1959:             'feeds'  => $feeds,
1960:         );
1961: 
1962:         $script = sprintf( '; new GFFrontendFeeds( %s );', json_encode( $args ) );
1963: 
1964:         GFFormDisplay::add_init_script( $form['id'], 'gaddon_frontend_feeds', GFFormDisplay::ON_PAGE_RENDER, $script );
1965: 
1966:     }
1967: 
1968: }
1969: 
1970: if ( ! class_exists( 'WP_List_Table' ) ) {
1971:     require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
1972: }
1973: 
1974: class GFAddOnFeedsTable extends WP_List_Table {
1975: 
1976:     private $_feeds;
1977:     private $_slug;
1978:     private $_columns;
1979:     private $_bulk_actions;
1980:     private $_action_links;
1981:     private $_addon_class;
1982: 
1983:     private $_column_value_callback = array();
1984:     private $_no_items_callback = array();
1985:     private $_message_callback = array();
1986: 
1987:     function __construct( $feeds, $slug, $columns = array(), $bulk_actions, $action_links, $column_value_callback, $no_items_callback, $message_callback, $addon_class ) {
1988:         $this->_bulk_actions          = $bulk_actions;
1989:         $this->_feeds                 = $feeds;
1990:         $this->_slug                  = $slug;
1991:         $this->_columns               = $columns;
1992:         $this->_column_value_callback = $column_value_callback;
1993:         $this->_action_links          = $action_links;
1994:         $this->_no_items_callback     = $no_items_callback;
1995:         $this->_message_callback      = $message_callback;
1996:         $this->_addon_class           = $addon_class;
1997: 
1998:         $standard_cols = array(
1999:             'cb'        => esc_html__( 'Checkbox', 'gravityforms' ),
2000:             'is_active' => '',
2001:         );
2002: 
2003:         $all_cols = array_merge( $standard_cols, $columns );
2004: 
2005:         $this->_column_headers = array(
2006:             $all_cols,
2007:             array(),
2008:             array(),
2009:             rgar( array_keys( $all_cols ), 2 ),
2010:         );
2011: 
2012:         parent::__construct(
2013:             array(
2014:                 'singular' => esc_html__( 'feed', 'gravityforms' ),
2015:                 'plural'   => esc_html__( 'feeds', 'gravityforms' ),
2016:                 'ajax'     => false,
2017:             )
2018:         );
2019:     }
2020: 
2021:     function prepare_items() {
2022:         $this->items = isset( $this->_feeds ) ? $this->_feeds : array();
2023:     }
2024: 
2025:     function get_columns() {
2026:         return $this->_column_headers[0];
2027:     }
2028: 
2029:     function get_bulk_actions() {
2030:         return $this->_bulk_actions;
2031:     }
2032: 
2033:     function no_items() {
2034:         echo call_user_func( $this->_no_items_callback );
2035:     }
2036: 
2037:     function display_rows_or_placeholder() {
2038:         $message = call_user_func( $this->_message_callback );
2039: 
2040:         if ( $message !== false ) {
2041:             ?>
2042:             <tr class="no-items">
2043:                 <td class="colspanchange" colspan="<?php echo $this->get_column_count() ?>">
2044:                     <?php echo $message ?>
2045:                 </td>
2046:             </tr>
2047:         <?php
2048:         } else {
2049:             parent::display_rows_or_placeholder();
2050:         }
2051: 
2052:     }
2053: 
2054:     function column_default( $item, $column ) {
2055: 
2056:         if ( is_callable( $this->_column_value_callback ) ) {
2057:             $value = call_user_func( $this->_column_value_callback, $item, $column );
2058:         }
2059: 
2060:         // Adding action links to the first column of the list
2061:         $columns = array_keys( $this->_columns );
2062:         if ( is_array( $columns ) && count( $columns ) > 0 && $columns[0] == $column ) {
2063:             $value = $this->add_action_links( $item, $column, $value );
2064:         }
2065: 
2066:         return $value;
2067:     }
2068: 
2069:     function column_cb( $item ) {
2070:         $feed_id = rgar( $item, 'id' );
2071: 
2072:         return sprintf(
2073:             '<input type="checkbox" name="feed_ids[]" value="%s" />', esc_attr( $feed_id )
2074:         );
2075:     }
2076: 
2077:     function add_action_links( $item, $column, $value ) {
2078: 
2079:         /**
2080:          * Adds action links to feed items
2081:          *
2082:          * @param array  $this->_action_links Action links to be filtered.
2083:          * @param array  $item                The feed item being filtered.
2084:          * @param string $column              The column ID
2085:          */
2086:         $actions = apply_filters( $this->_slug . '_feed_actions', $this->_action_links, $item, $column );
2087: 
2088:         // Replacing _id_ merge variable with actual feed id
2089:         foreach ( $actions as $action => &$link ) {
2090:             $link = str_replace( '_id_', $item['id'], $link );
2091:         }
2092: 
2093:         if ( ! $this->_addon_class->can_duplicate_feed( $item['id'] ) ) {
2094:             unset( $actions['duplicate'] );
2095:         }
2096: 
2097:         return sprintf( '%1$s %2$s', $value, $this->row_actions( $actions ) );
2098:     }
2099: 
2100:     function _column_is_active( $item, $classes, $data, $primary ) {
2101: 
2102:         // Open cell as a table header.
2103:         echo '<th scope="row" class="manage-column column-is_active">';
2104: 
2105:         // Get feed active state.
2106:         $is_active = intval( rgar( $item, 'is_active' ) );
2107: 
2108:         // Get active image URL.
2109:         $src = GFCommon::get_base_url() . "/images/active{$is_active}.png";
2110: 
2111:         // Get image title tag.
2112:         $title = $is_active ? esc_attr__( 'Active', 'gravityforms' ) : esc_attr__( 'Inactive', 'gravityforms' );
2113: 
2114:         // Display feed active button.
2115:         echo sprintf( '<img src="%s" title="%s" onclick="gaddon.toggleFeedActive(this, \'%s\', \'%s\');" onkeypress="gaddon.toggleFeedActive(this, \'%s\', \'%s\');" style="cursor:pointer";/>', $src, $title, esc_js( $this->_slug ), esc_js( $item['id'] ), esc_js( $this->_slug ), esc_js( $item['id'] ) );
2116: 
2117:         // Close cell.
2118:         echo '</th>';
2119: 
2120:     }
2121: 
2122: }
2123: 
Gravity Forms API API documentation generated by ApiGen 2.8.0