Overview

Classes

  • GF_Personal_Data
  • GFAddOn
  • GFAddOnFeedsTable
  • GFAPI
  • GFFeedAddOn
  • GFPaymentAddOn
  • GFPaymentStatsTable
  • Overview
  • Class
   1: <?php
   2: /**
   3:  * @package GFAddOn
   4:  * @author  Rocketgenius
   5:  */
   6: 
   7: if ( ! class_exists( 'GFForms' ) ) {
   8:     die();
   9: }
  10: 
  11: /**
  12:  * Class GFAddOn
  13:  *
  14:  * Handles all tasks mostly common to any Gravity Forms Add-On, including third party ones.
  15:  */
  16: abstract class GFAddOn {
  17: 
  18:     /**
  19:      * @var string Version number of the Add-On
  20:      */
  21:     protected $_version;
  22:     /**
  23:      * @var string Gravity Forms minimum version requirement
  24:      */
  25:     protected $_min_gravityforms_version;
  26:     /**
  27:      * @var string URL-friendly identifier used for form settings, add-on settings, text domain localization...
  28:      */
  29:     protected $_slug;
  30:     /**
  31:      * @var string Relative path to the plugin from the plugins folder. Example "gravityforms/gravityforms.php"
  32:      */
  33:     protected $_path;
  34:     /**
  35:      * @var string Full path the the plugin. Example: __FILE__
  36:      */
  37:     protected $_full_path;
  38:     /**
  39:      * @var string URL to the Gravity Forms website. Example: 'http://www.gravityforms.com' OR affiliate link.
  40:      */
  41:     protected $_url;
  42:     /**
  43:      * @var string Title of the plugin to be used on the settings page, form settings and plugins page. Example: 'Gravity Forms MailChimp Add-On'
  44:      */
  45:     protected $_title;
  46:     /**
  47:      * @var string Short version of the plugin title to be used on menus and other places where a less verbose string is useful. Example: 'MailChimp'
  48:      */
  49:     protected $_short_title;
  50:     /**
  51:      * @var array Members plugin integration. List of capabilities to add to roles.
  52:      */
  53:     protected $_capabilities = array();
  54:     /**
  55:      * @var string The hook suffix for the app menu
  56:      */
  57:     public $app_hook_suffix;
  58: 
  59:     private $_saved_settings = array();
  60:     private $_previous_settings = array();
  61: 
  62:     /**
  63:      * @var array Stores a copy of setting fields that failed validation; only populated after validate_settings() has been called.
  64:      */
  65:     private $_setting_field_errors = array();
  66: 
  67:     // ------------ Permissions -----------
  68:     /**
  69:      * @var string|array A string or an array of capabilities or roles that have access to the settings page
  70:      */
  71:     protected $_capabilities_settings_page = array();
  72:     /**
  73:      * @var string|array A string or an array of capabilities or roles that have access to the form settings
  74:      */
  75:     protected $_capabilities_form_settings = array();
  76:     /**
  77:      * @var string|array A string or an array of capabilities or roles that have access to the plugin page
  78:      */
  79:     protected $_capabilities_plugin_page = array();
  80:     /**
  81:      * @var string|array A string or an array of capabilities or roles that have access to the app menu
  82:      */
  83:     protected $_capabilities_app_menu = array();
  84:     /**
  85:      * @var string|array A string or an array of capabilities or roles that have access to the app settings page
  86:      */
  87:     protected $_capabilities_app_settings = array();
  88:     /**
  89:      * @var string|array A string or an array of capabilities or roles that can uninstall the plugin
  90:      */
  91:     protected $_capabilities_uninstall = array();
  92: 
  93:     // ------------ RG Autoupgrade -----------
  94: 
  95:     /**
  96:      * @var bool Used by Rocketgenius plugins to activate auto-upgrade.
  97:      * @ignore
  98:      */
  99:     protected $_enable_rg_autoupgrade = false;
 100: 
 101:     // ------------ Private -----------
 102: 
 103:     private $_no_conflict_scripts = array();
 104:     private $_no_conflict_styles = array();
 105:     private $_preview_styles = array();
 106:     private $_print_styles = array();
 107:     private static $_registered_addons = array( 'active' => array(), 'inactive' => array() );
 108: 
 109:     /**
 110:      * Class constructor which hooks the instance into the WordPress init action
 111:      */
 112:     function __construct() {
 113: 
 114:         add_action( 'init', array( $this, 'init' ) );
 115: 
 116:         if ( $this->_enable_rg_autoupgrade ) {
 117:             require_once( 'class-gf-auto-upgrade.php' );
 118:             $is_gravityforms_supported = $this->is_gravityforms_supported( $this->_min_gravityforms_version );
 119:             new GFAutoUpgrade( $this->_slug, $this->_version, $this->_min_gravityforms_version, $this->_title, $this->_full_path, $this->_path, $this->_url, $is_gravityforms_supported );
 120:         }
 121: 
 122:         $this->pre_init();
 123:     }
 124: 
 125:     /**
 126:      * Registers an addon so that it gets initialized appropriately
 127:      *
 128:      * @param string $class - The class name
 129:      * @param string $overrides - Specify the class to replace/override
 130:      */
 131:     public static function register( $class, $overrides = null ) {
 132: 
 133:         //Ignore classes that have been marked as inactive
 134:         if ( in_array( $class, self::$_registered_addons['inactive'] ) ) {
 135:             return;
 136:         }
 137: 
 138:         //Mark classes as active. Override existing active classes if they are supposed to be overridden
 139:         $index = array_search( $overrides, self::$_registered_addons['active'] );
 140:         if ( $index !== false ) {
 141:             self::$_registered_addons['active'][ $index ] = $class;
 142:         } else {
 143:             self::$_registered_addons['active'][] = $class;         
 144:         }
 145: 
 146:         //Mark overridden classes as inactive.
 147:         if ( ! empty( $overrides ) ) {
 148:             self::$_registered_addons['inactive'][] = $overrides;
 149:         }
 150: 
 151:     }
 152: 
 153:     /**
 154:      * Gets all active, registered Add-Ons.
 155:      *
 156:      * @since  Unknown
 157:      * @access public
 158:      *
 159:      * @uses GFAddOn::$_registered_addons
 160:      *
 161:      * @return array Active, registered Add-Ons.
 162:      */
 163:     public static function get_registered_addons() {
 164:         return self::$_registered_addons['active'];
 165:     }
 166: 
 167:     /**
 168:      * Initializes all addons.
 169:      */
 170:     public static function init_addons() {
 171: 
 172:         //Removing duplicate add-ons
 173:         $active_addons = array_unique( self::$_registered_addons['active'] );
 174: 
 175:         foreach ( $active_addons as $addon ) {
 176: 
 177:             call_user_func( array( $addon, 'get_instance' ) );
 178: 
 179:         }
 180:     }
 181: 
 182:     /**
 183:      * Gets executed before all init functions. Override this function to perform initialization tasks that must be done prior to init
 184:      */
 185:     public function pre_init() {
 186: 
 187:         if ( $this->is_gravityforms_supported() ) {
 188: 
 189:             //Entry meta
 190:             if ( $this->method_is_overridden( 'get_entry_meta' ) ) {
 191:                 add_filter( 'gform_entry_meta', array( $this, 'get_entry_meta' ), 10, 2 );
 192:             }
 193:         }
 194:     }
 195: 
 196:     /**
 197:      * Plugin starting point. Handles hooks and loading of language files.
 198:      */
 199:     public function init() {
 200: 
 201:         $this->load_text_domain();
 202: 
 203:         add_filter( 'gform_logging_supported', array( $this, 'set_logging_supported' ) );
 204: 
 205:         add_action( 'gform_post_upgrade', array( $this, 'post_gravityforms_upgrade' ), 10, 3 );
 206: 
 207:         // Get minimum requirements state.
 208:         $meets_requirements = $this->meets_minimum_requirements();
 209: 
 210:         if ( RG_CURRENT_PAGE == 'admin-ajax.php' ) {
 211: 
 212:             //If gravity forms is supported, initialize AJAX
 213:             if ( $this->is_gravityforms_supported() && $meets_requirements['meets_requirements'] ) {
 214:                 $this->init_ajax();
 215:             }
 216:         } elseif ( is_admin() ) {
 217: 
 218:             $this->init_admin();
 219: 
 220:         } else {
 221: 
 222:             if ( $this->is_gravityforms_supported() && $meets_requirements['meets_requirements'] ) {
 223:                 $this->init_frontend();
 224:             }
 225:         }
 226: 
 227: 
 228:     }
 229: 
 230:     /**
 231:      * Override this function to add initialization code (i.e. hooks) for the admin site (WP dashboard)
 232:      */
 233:     public function init_admin() {
 234: 
 235:         // enqueues admin scripts
 236:         add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10, 0 );
 237: 
 238:         // message enforcing min version of Gravity Forms
 239:         if ( isset( $this->_min_gravityforms_version ) && RG_CURRENT_PAGE == 'plugins.php' && false === $this->_enable_rg_autoupgrade ) {
 240:             add_action( 'after_plugin_row_' . $this->_path, array( $this, 'plugin_row' ) );
 241:         }
 242: 
 243:         // STOP HERE IF GRAVITY FORMS IS NOT SUPPORTED
 244:         if ( isset( $this->_min_gravityforms_version ) && ! $this->is_gravityforms_supported( $this->_min_gravityforms_version ) ) {
 245:             return;
 246:         }
 247:         
 248:         // STOP HERE IF CANNOT PASS MINIMUM REQUIREMENTS CHECK.
 249:         $meets_requirements = $this->meets_minimum_requirements();
 250:         if ( ! $meets_requirements['meets_requirements'] ) {
 251:             $this->failed_requirements_init();
 252:             return;
 253:         }
 254: 
 255:         $this->setup();
 256: 
 257:         // Add form settings only when there are form settings fields configured or form_settings() method is implemented
 258:         if ( self::has_form_settings_page() ) {
 259:             $this->form_settings_init();
 260:         }
 261: 
 262:         // Add plugin page when there is a plugin page configured or plugin_page() method is implemented
 263:         if ( self::has_plugin_page() ) {
 264:             $this->plugin_page_init();
 265:         }
 266: 
 267:         // Add addon settings page only when there are addon settings fields configured or settings_page() method is implemented
 268:         if ( self::has_plugin_settings_page() ) {
 269:             if ( $this->current_user_can_any( $this->_capabilities_settings_page ) ) {
 270:                 $this->plugin_settings_init();
 271:             }
 272:         }
 273: 
 274:         // creates the top level app left menu
 275:         if ( self::has_app_menu() ) {
 276:             if ( $this->current_user_can_any( $this->_capabilities_app_menu ) ) {
 277:                 add_action( 'admin_menu', array( $this, 'create_app_menu' ) );
 278:             }
 279:         }
 280: 
 281: 
 282:         // Members plugin integration.
 283:         if ( GFForms::has_members_plugin( '2.0' ) ) {
 284:             add_action( 'members_register_cap_groups', array( $this, 'members_register_cap_group' ), 11 );
 285:             add_action( 'members_register_caps', array( $this, 'members_register_caps' ), 11 );
 286:         } else if ( GFForms::has_members_plugin() ) {
 287:             add_filter( 'members_get_capabilities', array( $this, 'members_get_capabilities' ) );
 288:         }
 289: 
 290:         // User Role Editor integration.
 291:         add_filter( 'ure_capabilities_groups_tree', array( $this, 'filter_ure_capabilities_groups_tree' ), 11 );
 292:         add_filter( 'ure_custom_capability_groups', array( $this, 'filter_ure_custom_capability_groups' ), 10, 2 );
 293: 
 294:         // Results page
 295:         if ( $this->method_is_overridden( 'get_results_page_config' ) ) {
 296:             $results_page_config  = $this->get_results_page_config();
 297:             $results_capabilities = rgar( $results_page_config, 'capabilities' );
 298:             if ( $results_page_config && $this->current_user_can_any( $results_capabilities ) ) {
 299:                 $this->results_page_init( $results_page_config );
 300:             }
 301:         }
 302: 
 303:         // Locking
 304:         if ( $this->method_is_overridden( 'get_locking_config' ) ) {
 305:             require_once( GFCommon::get_base_path() . '/includes/locking/class-gf-locking.php' );
 306:             require_once( 'class-gf-addon-locking.php' );
 307:             $config = $this->get_locking_config();
 308:             new GFAddonLocking( $config, $this );
 309:         }
 310: 
 311:         // No conflict scripts
 312:         add_filter( 'gform_noconflict_scripts', array( $this, 'register_noconflict_scripts' ) );
 313:         add_filter( 'gform_noconflict_styles', array( $this, 'register_noconflict_styles' ) );
 314:         add_action( 'gform_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10, 2 );
 315: 
 316:     }
 317: 
 318:     /**
 319:      * Override this function to add initialization code (i.e. hooks) for the public (customer facing) site
 320:      */
 321:     public function init_frontend() {
 322: 
 323:         $this->setup();
 324: 
 325:         add_filter( 'gform_preview_styles', array( $this, 'enqueue_preview_styles' ), 10, 2 );
 326:         add_filter( 'gform_print_styles', array( $this, 'enqueue_print_styles' ), 10, 2 );
 327:         add_action( 'gform_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10, 2 );
 328: 
 329:     }
 330: 
 331:     /**
 332:      * Override this function to add AJAX hooks or to add initialization code when an AJAX request is being performed
 333:      */
 334:     public function init_ajax() {
 335:         if ( rgpost( 'view' ) == 'gf_results_' . $this->_slug ) {
 336:             require_once( GFCommon::get_base_path() . '/tooltips.php' );
 337:             require_once( 'class-gf-results.php' );
 338:             $gf_results = new GFResults( $this->_slug, $this->get_results_page_config() );
 339:             add_action( 'wp_ajax_gresults_get_results_gf_results_' . $this->_slug, array( $gf_results, 'ajax_get_results' ) );
 340:             add_action( 'wp_ajax_gresults_get_more_results_gf_results_' . $this->_slug, array( $gf_results, 'ajax_get_more_results' ) );
 341:         } elseif ( $this->method_is_overridden( 'get_locking_config' ) ) {
 342:             require_once( GFCommon::get_base_path() . '/includes/locking/class-gf-locking.php' );
 343:             require_once( 'class-gf-addon-locking.php' );
 344:             $config = $this->get_locking_config();
 345:             new GFAddonLocking( $config, $this );
 346:         }
 347:     }
 348: 
 349: 
 350:     //--------------  Minimum Requirements Check  ---------------
 351: 
 352:     /**
 353:      * Override this function to provide a list of requirements needed to use Add-On.
 354:      *
 355:      * Custom requirements can be defined by adding a callback to the minimum requirements array.
 356:      * A custom requirement receives and should return an array with two parameters:
 357:      *   bool  $meets_requirements If the custom requirements check passed.
 358:      *   array $errors             An array of error messages to present to the user.
 359:      *
 360:      * Following is an example of the array that is expected to be returned by this function:
 361:      * @example https://gist.github.com/JeffMatson/a8d23e16e333e5116060906c6f091aa7
 362:      *
 363:      * @since  2.2
 364:      * @access public
 365:      *
 366:      * @return array
 367:      */
 368:     public function minimum_requirements() {
 369: 
 370:         return array();
 371: 
 372:     }
 373: 
 374:     /**
 375:      * Performs a check to see if WordPress environment meets minimum requirements need to use Add-On.
 376:      *
 377:      * @since  2.2
 378:      * @access public
 379:      *
 380:      * @uses GFAddOn::minimum_requirements()
 381:      * @uses GFAddOn::get_slug()
 382:      *
 383:      * @return bool|array
 384:      */
 385:     public function meets_minimum_requirements() {
 386: 
 387:         // Get minimum requirements.
 388:         $requirements = $this->minimum_requirements();
 389: 
 390:         // Prepare response.
 391:         $meets_requirements = array( 'meets_requirements' => true, 'errors' => array() );
 392: 
 393:         // If no minimum requirements are defined, return.
 394:         if ( empty( $requirements ) ) {
 395:             return $meets_requirements;
 396:         }
 397: 
 398:         // Loop through requirements.
 399:         foreach ( $requirements as $requirement_type => $requirement ) {
 400: 
 401:             // If requirement is a callback, run it.
 402:             if ( is_callable( $requirement ) ) {
 403:                 $meets_requirements = call_user_func( $requirement, $meets_requirements );
 404:                 continue;
 405:             }
 406: 
 407:             // Set requirement type to lowercase.
 408:             $requirement_type = strtolower( $requirement_type );
 409: 
 410:             // Run base requirement checks.
 411:             switch ( $requirement_type ) {
 412: 
 413:                 case 'add-ons':
 414: 
 415:                     // Initialize active Add-Ons array.
 416:                     $active_addons = array();
 417: 
 418:                     // Loop through active Add-Ons.
 419:                     foreach ( self::$_registered_addons['active'] as $active_addon ) {
 420: 
 421:                         // Get Add-On instance.
 422:                         $active_addon = call_user_func( array( $active_addon, 'get_instance' ) );
 423: 
 424:                         // Add to active Add-Ons array.
 425:                         $active_addons[ $active_addon->get_slug() ] = array(
 426:                             'slug'    => $active_addon->get_slug(),
 427:                             'title'   => $active_addon->_title,
 428:                             'version' => $active_addon->_version,
 429:                         );
 430: 
 431:                     }
 432: 
 433:                     // Loop through Add-Ons.
 434:                     foreach ( $requirement as $addon_slug => $addon_requirements ) {
 435: 
 436:                         // If Add-On requirements is not an array, set Add-On slug to requirements value.
 437:                         if ( ! is_array( $addon_requirements ) ) {
 438:                             $addon_slug = $addon_requirements;
 439:                         }
 440: 
 441:                         // If Add-On is not active, set error.
 442:                         if ( ! isset( $active_addons[ $addon_slug ] ) ) {
 443: 
 444:                             // Get Add-On name.
 445:                             $addon_name = rgar( $addon_requirements, 'name' ) ? $addon_requirements['name'] : $addon_slug;
 446: 
 447:                             $meets_requirements['meets_requirements'] = false;
 448:                             $meets_requirements['errors'][]           = sprintf( esc_html__( 'Required Gravity Forms Add-On is missing: %s.', 'gravityforms' ), $addon_name );
 449:                             continue;
 450: 
 451:                         }
 452: 
 453:                         // If Add-On does not meet minimum version, set error.
 454:                         if ( rgar( $addon_requirements, 'version' ) && ! version_compare( $active_addons[ $addon_slug ]['version'], $addon_requirements['version'], '>=' ) ) {
 455:                             $meets_requirements['meets_requirements'] = false;
 456:                             $meets_requirements['errors'][]           = sprintf( esc_html__( 'Required Gravity Forms Add-On "%s" does not meet minimum version requirement: %s.', 'gravityforms' ), $active_addons[ $addon_slug ]['title'], $addon_requirements['version'] );
 457:                             continue;
 458:                         }
 459:                     }
 460: 
 461:                     break;
 462: 
 463:                 case 'plugins':
 464: 
 465:                     // Loop through plugins.
 466:                     foreach ( $requirement as $plugin_path => $plugin_name ) {
 467: 
 468:                         // If plugin name is not defined, set plugin path to name.
 469:                         if ( is_int( $plugin_path ) ) {
 470:                             $plugin_path = $plugin_name;
 471:                         }
 472: 
 473:                         // If plugin is not active, set error.
 474:                         if ( ! is_plugin_active( $plugin_path ) ) {
 475:                             $meets_requirements['meets_requirements'] = false;
 476:                             $meets_requirements['errors'][]           = sprintf( esc_html__( 'Required WordPress plugin is missing: %s.', 'gravityforms' ), $plugin_name );
 477:                             continue;
 478:                         }
 479:                     }
 480: 
 481:                 case 'php':
 482: 
 483:                     // Check version.
 484:                     if ( rgar( $requirement, 'version' ) && ! version_compare( PHP_VERSION, $requirement['version'], '>=' ) ) {
 485:                         $meets_requirements['meets_requirements'] = false;
 486:                         $meets_requirements['errors'][]           = sprintf( esc_html__( 'Current PHP version (%s) does not meet minimum PHP version requirement (%s).', 'gravityforms' ), PHP_VERSION, $requirement['version'] );
 487:                     }
 488: 
 489:                     // Check extensions.
 490:                     if ( rgar( $requirement, 'extensions' ) ) {
 491: 
 492:                         // Loop through extensions.
 493:                         foreach ( $requirement['extensions'] as $extension => $extension_requirements ) {
 494: 
 495:                             // If extension requirements is not an array, set extension name to requirements value.
 496:                             if ( ! is_array( $extension_requirements ) ) {
 497:                                 $extension = $extension_requirements;
 498:                             }
 499: 
 500:                             // If PHP extension is not loaded, set error.
 501:                             if ( ! extension_loaded( $extension ) ) {
 502:                                 $meets_requirements['meets_requirements'] = false;
 503:                                 $meets_requirements['errors'][]           = sprintf( esc_html__( 'Required PHP extension missing: %s', 'gravityforms' ), $extension );
 504:                                 continue;
 505:                             }
 506: 
 507:                             // If PHP extension does not meet minimum version, set error.
 508:                             if ( rgar( $extension_requirements, 'version' ) && ! version_compare( phpversion( $extension ), $extension_requirements['version'], '>=' ) ) {
 509:                                 $meets_requirements['meets_requirements'] = false;
 510:                                 $meets_requirements['errors'][]           = sprintf( esc_html__( 'Required PHP extension "%s" does not meet minimum version requirement: %s.', 'gravityforms' ), $extension, $extension_requirements['version'] );
 511:                                 continue;
 512:                             }
 513: 
 514:                         }
 515: 
 516:                     }
 517: 
 518:                     // Check functions.
 519:                     if ( rgar( $requirement, 'functions' ) ) {
 520: 
 521:                         // Loop through functions.
 522:                         foreach ( $requirement['functions'] as $function ) {
 523:                             if ( ! function_exists( $function ) ) {
 524:                                 $meets_requirements['meets_requirements'] = false;
 525:                                 $meets_requirements['errors'][]           = sprintf( esc_html__( 'Required PHP function missing: %s', 'gravityforms' ), $function );
 526:                             }
 527:                         }
 528: 
 529:                     }
 530: 
 531:                     break;
 532: 
 533:                 case 'wordpress':
 534: 
 535:                     // Check version.
 536:                     if ( rgar( $requirement, 'version' ) && ! version_compare( get_bloginfo( 'version' ), $requirement['version'], '>=' ) ) {
 537:                         $meets_requirements['meets_requirements'] = false;
 538:                         $meets_requirements['errors'][]           = sprintf( esc_html__( 'Current WordPress version (%s) does not meet minimum WordPress version requirement (%s).', 'gravityforms' ), get_bloginfo( 'version' ), $requirement['version'] );
 539:                     }
 540: 
 541:                     break;
 542: 
 543:             }
 544: 
 545:         }
 546: 
 547:         return $meets_requirements;
 548: 
 549:     }
 550:     
 551:     /**
 552:      * Register failed requirements page under Gravity Forms settings.
 553:      *
 554:      * @since  2.2
 555:      * @access public
 556:      *
 557:      * @uses GFAddOn::current_user_can_any()
 558:      * @uses GFAddOn::get_short_title()
 559:      * @uses GFAddOn::plugin_settings_title()
 560:      * @uses GFCommon::get_base_path()
 561:      * @uses RGForms::add_settings_page()
 562:      */
 563:     public function failed_requirements_init() {
 564: 
 565:         // Get failed requirements.
 566:         $failed_requirements = $this->meets_minimum_requirements();
 567: 
 568:         // Prepare errors list.
 569:         $errors = '';
 570:         foreach ( $failed_requirements['errors'] as $error ) {
 571:             $errors .= sprintf( '<li>%s</li>', esc_html( $error ) );
 572:         }
 573: 
 574:         // Prepare error message.
 575:         $error_message = sprintf(
 576:             '%s<br />%s<ol>%s</ol>',
 577:             sprintf( esc_html__( '%s is not able to run because your WordPress environment has not met the minimum requirements.', 'gravityforms' ), $this->_title ),
 578:             sprintf( esc_html__( 'Please resolve the following issues to use %s:', 'gravityforms' ), $this->get_short_title() ),
 579:             $errors
 580:         );
 581: 
 582:         // Add error message.
 583:         if ( $this->is_form_list() || $this->is_entry_list() || $this->is_form_settings() || $this->is_plugin_settings() || GFForms::get_page() === 'system_status' ) {
 584:             GFCommon::add_error_message( $error_message );
 585:         }
 586: 
 587:     }
 588: 
 589:     //--------------  Setup  ---------------
 590: 
 591:     /**
 592:      * 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.
 593:      */
 594:     public function setup() {
 595: 
 596:         //Upgrading add-on
 597:         $installed_version = get_option( 'gravityformsaddon_' . $this->_slug . '_version' );
 598: 
 599:         //Making sure version has really changed. Gets around aggressive caching issue on some sites that cause setup to run multiple times.
 600:         if ( $installed_version != $this->_version ) {
 601:             $installed_version = GFForms::get_wp_option( 'gravityformsaddon_' . $this->_slug . '_version' );
 602:         }
 603: 
 604:         //Upgrade if version has changed
 605:         if ( $installed_version != $this->_version ) {
 606: 
 607:             $this->upgrade( $installed_version );
 608:             update_option( 'gravityformsaddon_' . $this->_slug . '_version', $this->_version );
 609:         }
 610:     }
 611: 
 612:     /**
 613:      * Override this function to add to add database update scripts or any other code to be executed when the Add-On version changes
 614:      */
 615:     public function upgrade( $previous_version ) {
 616:         return;
 617:     }
 618: 
 619: 
 620:     /**
 621:      * 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.
 622:      * @param $db_version - Current Gravity Forms database version
 623:      * @param $previous_db_version - Previous Gravity Forms database version
 624:      * @param $force_upgrade - True if this is a request to force an upgrade. False if this is a standard upgrade (due to version change)
 625:      */
 626:     public function post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade ){
 627: 
 628:         // Forcing Upgrade
 629:         if( $force_upgrade ){
 630: 
 631:             $installed_version = get_option( 'gravityformsaddon_' . $this->_slug . '_version' );
 632: 
 633:             $this->upgrade( $installed_version );
 634:             update_option( 'gravityformsaddon_' . $this->_slug . '_version', $this->_version );
 635: 
 636:         }
 637: 
 638:     }
 639: 
 640:     //--------------  Script enqueuing  ---------------
 641: 
 642:     /**
 643:      * Override this function to provide a list of styles to be enqueued.
 644:      * When overriding this function, be sure to call parent::styles() to ensure the base class scripts are enqueued.
 645:      * See scripts() for an example of the format expected to be returned.
 646:      */
 647:     public function styles() {
 648:         $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
 649:         return array(
 650:             array(
 651:                 'handle'  => 'gaddon_form_settings_css',
 652:                 'src'     => GFAddOn::get_gfaddon_base_url() . "/css/gaddon_settings{$min}.css",
 653:                 'version' => GFCommon::$version,
 654:                 'enqueue' => array(
 655:                     array( 'admin_page' => array( 'form_settings', 'plugin_settings', 'plugin_page', 'app_settings' ) ),
 656:                 )
 657:             ),
 658:             array(
 659:                 'handle'  => 'gaddon_results_css',
 660:                 'src'     => GFAddOn::get_gfaddon_base_url() . "/css/gaddon_results{$min}.css",
 661:                 'version' => GFCommon::$version,
 662:                 'enqueue' => array(
 663:                     array( 'admin_page' => array( 'results' ) ),
 664:                 )
 665:             ),
 666:         );
 667:     }
 668: 
 669: 
 670:     /**
 671:      * Override this function to provide a list of scripts to be enqueued.
 672:      * When overriding this function, be sure to call parent::scripts() to ensure the base class scripts are enqueued.
 673:      * Following is an example of the array that is expected to be returned by this function:
 674:      * <pre>
 675:      * <code>
 676:      *
 677:      *    array(
 678:      *        array(
 679:      *            'handle'    => 'maskedinput',
 680:      *            'src'       => GFCommon::get_base_url() . '/js/jquery.maskedinput-1.3.min.js',
 681:      *            'version'   => GFCommon::$version,
 682:      *            'deps'      => array( 'jquery' ),
 683:      *            'in_footer' => false,
 684:      *
 685:      *            // Determines where the script will be enqueued. The script will be enqueued if any of the conditions match.
 686:      *            'enqueue'   => array(
 687:      *                // admin_page - Specified one or more pages (known pages) where the script is supposed to be enqueued.
 688:      *                // To enqueue scripts in the front end (public website), simply don't define this setting.
 689:      *                array( 'admin_page' => array( 'form_settings', 'plugin_settings' ) ),
 690:      *
 691:      *                // tab - Specifies a form settings or plugin settings tab in which the script is supposed to be enqueued.
 692:      *                // If none are specified, the script will be enqueued in any of the form settings or plugin_settings page
 693:      *                array( 'tab' => 'signature'),
 694:      *
 695:      *                // query - Specifies a set of query string ($_GET) values.
 696:      *                // If all specified query string values match the current requested page, the script will be enqueued
 697:      *                array( 'query' => 'page=gf_edit_forms&view=settings&id=_notempty_' )
 698:      *
 699:      *                // post - Specifies a set of post ($_POST) values.
 700:      *                // If all specified posted values match the current request, the script will be enqueued
 701:      *                array( 'post' => 'posted_field=val' )
 702:      *            )
 703:      *        ),
 704:      *        array(
 705:      *            'handle'   => 'super_signature_script',
 706:      *            'src'      => $this->get_base_url() . '/super_signature/ss.js',
 707:      *            'version'  => $this->_version,
 708:      *            'deps'     => array( 'jquery'),
 709:      *            'callback' => array( $this, 'localize_scripts' ),
 710:      *            'strings'  => array(
 711:      *                // Accessible in JavaScript using the global variable "[script handle]_strings"
 712:      *                'stringKey1' => __( 'The string', 'gravityforms' ),
 713:      *                'stringKey2' => __( 'Another string.', 'gravityforms' )
 714:      *            )
 715:      *            "enqueue"  => array(
 716:      *                // field_types - Specifies one or more field types that requires this script.
 717:      *                // The script will only be enqueued if the current form has a field of any of the specified field types.
 718:      *                // Only applies when a current form is available.
 719:      *                array( 'field_types' => array( 'signature' ) )
 720:      *            )
 721:      *        )
 722:      *    );
 723:      *
 724:      * </code>
 725:      * </pre>
 726:      */
 727:     public function scripts() {
 728:         $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
 729:         return array(
 730:             array(
 731:                 'handle'  => 'gform_form_admin',
 732:                 'enqueue' => array( array( 'admin_page' => array( 'form_settings' ) ) )
 733:             ),
 734:             array(
 735:                 'handle'  => 'gform_gravityforms',
 736:                 'enqueue' => array( array( 'admin_page' => array( 'form_settings' ) ) )
 737:             ),
 738:             array(
 739:                 'handle'  => 'google_charts',
 740:                 'src'     => 'https://www.google.com/jsapi',
 741:                 'version' => GFCommon::$version,
 742:                 'enqueue' => array(
 743:                     array( 'admin_page' => array( 'results' ) ),
 744:                 )
 745:             ),
 746:             array(
 747:                 'handle'   => 'gaddon_results_js',
 748:                 'src'      => GFAddOn::get_gfaddon_base_url() . "/js/gaddon_results{$min}.js",
 749:                 'version'  => GFCommon::$version,
 750:                 'deps'     => array( 'jquery', 'sack', 'jquery-ui-resizable', 'gform_datepicker_init', 'google_charts', 'gform_field_filter' ),
 751:                 'callback' => array( 'GFResults', 'localize_results_scripts' ),
 752:                 'enqueue'  => array(
 753:                     array( 'admin_page' => array( 'results' ) ),
 754:                 )
 755:             ),
 756:             array(
 757:                 'handle'  => 'gaddon_repeater',
 758:                 'src'     => GFAddOn::get_gfaddon_base_url() . "/js/repeater{$min}.js",
 759:                 'version' => GFCommon::$version,
 760:                 'deps'    => array( 'jquery' ),
 761:                 'enqueue' => array(
 762:                     array(
 763:                         'admin_page' => array( 'form_settings' ),
 764:                     ),
 765:                 ),
 766:             ),
 767:             array(
 768:                 'handle'   => 'gaddon_fieldmap_js',
 769:                 'src'      => GFAddOn::get_gfaddon_base_url() . "/js/gaddon_fieldmap{$min}.js",
 770:                 'version'  => GFCommon::$version,
 771:                 'deps'     => array( 'jquery', 'gaddon_repeater' ),
 772:                 'enqueue'  => array(
 773:                     array( 'admin_page' => array( 'form_settings' ) ),
 774:                 )
 775:             ),
 776:             array(
 777:                 'handle'   => 'gaddon_genericmap_js',
 778:                 'src'      => GFAddOn::get_gfaddon_base_url() . "/js/gaddon_genericmap{$min}.js",
 779:                 'version'  => GFCommon::$version,
 780:                 'deps'     => array( 'jquery', 'gaddon_repeater' ),
 781:                 'enqueue'  => array(
 782:                     array( 'admin_page' => array( 'form_settings' ) ),
 783:                 )
 784:             ),
 785:             array(
 786:                 'handle'   => 'gaddon_selectcustom_js',
 787:                 'src'      => GFAddOn::get_gfaddon_base_url() . "/js/gaddon_selectcustom{$min}.js",
 788:                 'version'  => GFCommon::$version,
 789:                 'deps'     => array( 'jquery' ),
 790:                 'enqueue'  => array(
 791:                     array( 'admin_page' => array( 'form_settings', 'plugin_settings' ) ),
 792:                 )
 793:             ),
 794:         );
 795:     }
 796: 
 797: 
 798:     /**
 799:      * Target of admin_enqueue_scripts and gform_enqueue_scripts hooks.
 800:      * Not intended to be overridden by child classes.
 801:      * In order to enqueue scripts and styles, override the scripts() and styles() functions
 802:      *
 803:      * @ignore
 804:      */
 805:     public function enqueue_scripts( $form = '', $is_ajax = false ) {
 806: 
 807:         if ( empty( $form ) ) {
 808:             $form = $this->get_current_form();
 809:         }
 810: 
 811:         //Enqueueing scripts
 812:         $scripts = $this->scripts();
 813:         foreach ( $scripts as $script ) {
 814:             $src       = isset( $script['src'] ) ? $script['src'] : false;
 815:             $deps      = isset( $script['deps'] ) ? $script['deps'] : array();
 816:             $version   = array_key_exists( 'version', $script ) ? $script['version'] : false;
 817:             $in_footer = isset( $script['in_footer'] ) ? $script['in_footer'] : false;
 818:             wp_register_script( $script['handle'], $src, $deps, $version, $in_footer );
 819:             if ( isset( $script['enqueue'] ) && $this->_can_enqueue_script( $script['enqueue'], $form, $is_ajax ) ) {
 820:                 $this->add_no_conflict_scripts( array( $script['handle'] ) );
 821:                 wp_enqueue_script( $script['handle'] );
 822:                 if ( isset( $script['strings'] ) ) {
 823:                     wp_localize_script( $script['handle'], $script['handle'] . '_strings', $script['strings'] );
 824:                 }
 825:                 if ( isset( $script['callback'] ) && is_callable( $script['callback'] ) ) {
 826:                     $args = compact( 'form', 'is_ajax' );
 827:                     call_user_func_array( $script['callback'], $args );
 828:                 }
 829:             }
 830:         }
 831: 
 832:         //Enqueueing styles
 833:         $styles = $this->styles();
 834:         foreach ( $styles as $style ) {
 835:             $src     = isset( $style['src'] ) ? $style['src'] : false;
 836:             $deps    = isset( $style['deps'] ) ? $style['deps'] : array();
 837:             $version = array_key_exists( 'version', $style ) ? $style['version'] : false;
 838:             $media   = isset( $style['media'] ) ? $style['media'] : 'all';
 839:             wp_register_style( $style['handle'], $src, $deps, $version, $media );
 840:             if ( $this->_can_enqueue_script( $style['enqueue'], $form, $is_ajax ) ) {
 841:                 $this->add_no_conflict_styles( array( $style['handle'] ) );
 842:                 if ( $this->is_preview() ) {
 843:                     $this->_preview_styles[] = $style['handle'];
 844:                 } elseif ( $this->is_print() ) {
 845:                     $this->_print_styles[] = $style['handle'];
 846:                 } else {
 847:                     wp_enqueue_style( $style['handle'] );
 848:                 }
 849:             }
 850:         }
 851:     }
 852: 
 853:     /**
 854:      * Target of gform_preview_styles. Enqueue styles to the preview page.
 855:      * Not intended to be overridden by child classes
 856:      *
 857:      * @ignore
 858:      */
 859:     public function enqueue_preview_styles( $preview_styles, $form ) {
 860:         return array_merge( $preview_styles, $this->_preview_styles );
 861:     }
 862: 
 863: 
 864:     /**
 865:      * Target of gform_print_styles. Enqueue styles to the print entry page.
 866:      * Not intended to be overridden by child classes
 867:      *
 868:      * @ignore
 869:      */
 870:     public function enqueue_print_styles( $print_styles, $form ) {
 871:         if ( false === $print_styles ) {
 872:             $print_styles = array();
 873:         }
 874: 
 875:         $styles = $this->styles();
 876:         foreach ( $styles as $style ) {
 877:             if ( $this->_can_enqueue_script( $style['enqueue'], $form, false ) ) {
 878:                 $this->add_no_conflict_styles( array( $style['handle'] ) );
 879:                 $src     = isset( $style['src'] ) ? $style['src'] : false;
 880:                 $deps    = isset( $style['deps'] ) ? $style['deps'] : array();
 881:                 $version = isset( $style['version'] ) ? $style['version'] : false;
 882:                 $media   = isset( $style['media'] ) ? $style['media'] : 'all';
 883:                 wp_register_style( $style['handle'], $src, $deps, $version, $media );
 884:                 $print_styles[] = $style['handle'];
 885:             }
 886:         }
 887: 
 888:         return array_merge( $print_styles, $this->_print_styles );
 889:     }
 890: 
 891: 
 892:     /**
 893:      * Adds scripts to the list of white-listed no conflict scripts.
 894:      *
 895:      * @param $scripts
 896:      */
 897:     private function add_no_conflict_scripts( $scripts ) {
 898:         $this->_no_conflict_scripts = array_merge( $scripts, $this->_no_conflict_scripts );
 899: 
 900:     }
 901: 
 902:     /**
 903:      * Adds styles to the list of white-listed no conflict styles.
 904:      *
 905:      * @param $styles
 906:      */
 907:     private function add_no_conflict_styles( $styles ) {
 908:         $this->_no_conflict_styles = array_merge( $styles, $this->_no_conflict_styles );
 909:     }
 910: 
 911:     private function _can_enqueue_script( $enqueue_conditions, $form = array(), $is_ajax = false ) {
 912:         if ( empty( $enqueue_conditions ) ) {
 913:             return false;
 914:         }
 915:         
 916:         foreach ( $enqueue_conditions as $condition ) {
 917:             if ( is_callable( $condition ) ) {
 918:                 $callback_matches = call_user_func( $condition, $form, $is_ajax );
 919:                 if ( $callback_matches ) {
 920:                     return true;
 921:                 }
 922:             } else {
 923:                 $query_matches      = isset( $condition['query'] ) ? $this->_request_condition_matches( $_GET, $condition['query'] ) : true;
 924:                 $post_matches       = isset( $condition['post'] ) ? $this->_request_condition_matches( $_POST, $condition['post'] ) : true;
 925:                 $admin_page_matches = isset( $condition['admin_page'] ) ? $this->_page_condition_matches( $condition['admin_page'], rgar( $condition, 'tab' ) ) : true;
 926:                 $field_type_matches = isset( $condition['field_types'] ) ? $this->_field_condition_matches( $condition['field_types'], $form ) : true;
 927: 
 928:                 if ( $query_matches && $post_matches && $admin_page_matches && $field_type_matches ) {
 929:                     return true;
 930:                 }
 931:             }
 932:         }
 933: 
 934:         return false;
 935:     }
 936: 
 937:     private function _request_condition_matches( $request, $query ) {
 938:         parse_str( $query, $query_array );
 939:         foreach ( $query_array as $key => $value ) {
 940: 
 941:             switch ( $value ) {
 942:                 case '_notempty_' :
 943:                     if ( rgempty( $key, $request ) ) {
 944:                         return false;
 945:                     }
 946:                     break;
 947:                 case '_empty_' :
 948:                     if ( ! rgempty( $key, $request ) ) {
 949:                         return false;
 950:                     }
 951:                     break;
 952:                 default :
 953:                     if ( rgar( $request, $key ) != $value ) {
 954:                         return false;
 955:                     }
 956:                     break;
 957:             }
 958:         }
 959: 
 960:         return true;
 961:     }
 962: 
 963:     private function _page_condition_matches( $pages, $tab ) {
 964:         if ( ! is_array( $pages ) ) {
 965:             $pages = array( $pages );
 966:         }
 967: 
 968:         foreach ( $pages as $page ) {
 969:             switch ( $page ) {
 970:                 case 'form_editor' :
 971:                     if ( $this->is_form_editor() ) {
 972:                         return true;
 973:                     }
 974: 
 975:                     break;
 976: 
 977:                 case 'form_list' :
 978:                     if ( $this->is_form_list() ) {
 979:                         return true;
 980:                     }
 981: 
 982:                     break;
 983: 
 984:                 case 'form_settings' :
 985:                     if ( $this->is_form_settings( $tab ) ) {
 986:                         return true;
 987:                     }
 988: 
 989:                     break;
 990: 
 991:                 case 'plugin_settings' :
 992:                     if ( $this->is_plugin_settings( $tab ) ) {
 993:                         return true;
 994:                     }
 995: 
 996:                     break;
 997: 
 998:                 case 'app_settings' :
 999:                     if ( $this->is_app_settings( $tab ) ) {
1000:                         return true;
1001:                     }
1002: 
1003:                     break;
1004: 
1005:                 case 'plugin_page' :
1006:                     if ( $this->is_plugin_page() ) {
1007:                         return true;
1008:                     }
1009: 
1010:                     break;
1011: 
1012:                 case 'entry_list' :
1013:                     if ( $this->is_entry_list() ) {
1014:                         return true;
1015:                     }
1016: 
1017:                     break;
1018: 
1019:                 case 'entry_view' :
1020:                     if ( $this->is_entry_view() ) {
1021:                         return true;
1022:                     }
1023: 
1024:                     break;
1025: 
1026:                 case 'entry_edit' :
1027:                     if ( $this->is_entry_edit() ) {
1028:                         return true;
1029:                     }
1030: 
1031:                     break;
1032: 
1033:                 case 'results' :
1034:                     if ( $this->is_results() ) {
1035:                         return true;
1036:                     }
1037: 
1038:                     break;
1039: 
1040:                 case 'customizer' :
1041:                     if ( is_customize_preview() ) {
1042:                         return true;
1043:                     }
1044: 
1045:                     break;
1046: 
1047:             }
1048:         }
1049: 
1050:         return false;
1051: 
1052:     }
1053: 
1054:     private function _field_condition_matches( $field_types, $form ) {
1055:         if ( ! is_array( $field_types ) ) {
1056:             $field_types = array( $field_types );
1057:         }
1058: 
1059:         /* @var GF_Field[] $fields */
1060:         $fields = GFAPI::get_fields_by_type( $form, $field_types );
1061:         if ( count( $fields ) > 0 ) {
1062:             foreach ( $fields as $field ) {
1063:                 if ( $field->is_administrative() && ! $field->allowsPrepopulate && ! GFForms::get_page() ) {
1064:                     continue;
1065:                 }
1066: 
1067:                 return true;
1068:             }
1069:         }
1070: 
1071:         return false;
1072:     }
1073: 
1074:     /**
1075:      * Target for the gform_noconflict_scripts filter. Adds scripts to the list of white-listed no conflict scripts.
1076:      *
1077:      * Not intended to be overridden or called directed by Add-Ons.
1078:      *
1079:      * @ignore
1080:      *
1081:      * @param array $scripts Array of scripts to be white-listed
1082:      *
1083:      * @return array
1084:      */
1085:     public function register_noconflict_scripts( $scripts ) {
1086:         //registering scripts with Gravity Forms so that they get enqueued when running in no-conflict mode
1087:         return array_merge( $scripts, $this->_no_conflict_scripts );
1088:     }
1089: 
1090:     /**
1091:      * Target for the gform_noconflict_styles filter. Adds styles to the list of white-listed no conflict scripts.
1092:      *
1093:      * Not intended to be overridden or called directed by Add-Ons.
1094:      *
1095:      * @ignore
1096:      *
1097:      * @param array $styles Array of styles to be white-listed
1098:      *
1099:      * @return array
1100:      */
1101:     public function register_noconflict_styles( $styles ) {
1102:         //registering styles with Gravity Forms so that they get enqueued when running in no-conflict mode
1103:         return array_merge( $styles, $this->_no_conflict_styles );
1104:     }
1105: 
1106: 
1107: 
1108:     //--------------  Entry meta  --------------------------------------
1109: 
1110:     /**
1111:      * Override this method to activate and configure entry meta.
1112:      *
1113:      *
1114:      * @param array $entry_meta An array of entry meta already registered with the gform_entry_meta filter.
1115:      * @param int   $form_id    The form id
1116:      *
1117:      * @return array The filtered entry meta array.
1118:      */
1119:     public function get_entry_meta( $entry_meta, $form_id ) {
1120:         return $entry_meta;
1121:     }
1122: 
1123: 
1124:     //--------------  Results page  --------------------------------------
1125:     /**
1126:      * Returns the configuration for the results page. By default this is not activated.
1127:      * To activate the results page override this function and return an array with the configuration data.
1128:      *
1129:      * Example:
1130:      * public function get_results_page_config() {
1131:      *      return array(
1132:      *       "title" => 'Quiz Results',
1133:      *       "capabilities" => array("gravityforms_quiz_results"),
1134:      *       "callbacks" => array(
1135:      *          "fields" => array($this, 'results_fields'),
1136:      *          "calculation" => array($this, 'results_calculation'),
1137:      *          "markup" => array($this, 'results_markup'),
1138:      *              )
1139:      *       );
1140:      * }
1141:      *
1142:      */
1143:     public function get_results_page_config() {
1144:         return false;
1145:     }
1146: 
1147:     /**
1148:      * Initializes the result page functionality. To activate result page functionality, override the get_results_page_config() function.
1149:      *
1150:      * @param $results_page_config - configuration returned by get_results_page_config()
1151:      */
1152:     public function results_page_init( $results_page_config ) {
1153:         require_once( 'class-gf-results.php' );
1154: 
1155:         if ( isset( $results_page_config['callbacks']['filters'] ) ) {
1156:             add_filter( 'gform_filters_pre_results', $results_page_config['callbacks']['filters'], 10, 2 );
1157:         }
1158: 
1159:         if ( isset( $results_page_config['callbacks']['filter_ui'] ) ) {
1160:             add_filter( 'gform_filter_ui', $results_page_config['callbacks']['filter_ui'], 10, 5 );
1161:         }
1162: 
1163:         $gf_results = new GFResults( $this->_slug, $results_page_config );
1164:         $gf_results->init();
1165:     }
1166: 
1167:     //--------------  Logging integration  --------------------------------------
1168: 
1169:     public function set_logging_supported( $plugins ) {
1170:         $plugins[ $this->_slug ] = $this->_title;
1171: 
1172:         return $plugins;
1173:     }
1174: 
1175: 
1176: 
1177: 
1178: 
1179:     // # PERMISSIONS ---------------------------------------------------------------------------------------------------
1180: 
1181:     /**
1182:      * Checks whether the Members plugin is installed and activated.
1183:      *
1184:      * Not intended to be overridden or called directly by Add-Ons.
1185:      *
1186:      * @ignore
1187:      *
1188:      * @return bool
1189:      */
1190:     public function has_members_plugin() {
1191:         return function_exists( 'members_get_capabilities' );
1192:     }
1193: 
1194:     /**
1195:      * Not intended to be overridden or called directly by Add-Ons.
1196:      *
1197:      * @ignore
1198:      *
1199:      * @param $caps
1200:      *
1201:      * @return array
1202:      */
1203:     public function members_get_capabilities( $caps ) {
1204:         return array_merge( $caps, $this->_capabilities );
1205:     }
1206: 
1207:     /**
1208:      * Register the Gravity Forms Add-Ons capabilities group with the Members plugin.
1209:      *
1210:      * @since  2.4
1211:      * @access public
1212:      */
1213:     public function members_register_cap_group() {
1214: 
1215:         members_register_cap_group(
1216:             'gravityforms_addons',
1217:             array(
1218:                 'label' => esc_html__( 'GF Add-Ons', 'gravityforms' ),
1219:                 'icon'  => 'dashicons-gravityforms',
1220:                 'caps'  => array(),
1221:             )
1222:         );
1223: 
1224:     }
1225: 
1226:     /**
1227:      * Register the Add-On capabilities and their human readable labels with the Members plugin.
1228:      *
1229:      * @since  2.4
1230:      * @access public
1231:      *
1232:      * @uses   GFAddOn::get_short_title()
1233:      */
1234:     public function members_register_caps() {
1235: 
1236:         // Get capabilities.
1237:         $caps = $this->get_members_caps();
1238: 
1239:         // If no capabilities were found, exit.
1240:         if ( empty( $caps ) ) {
1241:             return;
1242:         }
1243: 
1244:         // Register capabilities.
1245:         foreach ( $caps as $cap => $label ) {
1246:             members_register_cap(
1247:                 $cap,
1248:                 array(
1249:                     'label' => sprintf( '%s: %s', $this->get_short_title(), $label ),
1250:                     'group' => 'gravityforms_addons',
1251:                 )
1252:             );
1253:         }
1254: 
1255:     }
1256: 
1257:     /**
1258:      * Get Add-On capabilities and their human readable labels.
1259:      *
1260:      * @since  2.4
1261:      * @access public
1262:      *
1263:      * @return array
1264:      */
1265:     public function get_members_caps() {
1266: 
1267:         // Initialize capabilities array.
1268:         $caps = array();
1269: 
1270:         // Add capabilities.
1271:         if ( ! empty( $this->_capabilities_form_settings ) && is_string( $this->_capabilities_form_settings ) ) {
1272:             $caps[ $this->_capabilities_form_settings ] = esc_html__( 'Form Settings', 'gravityforms' );
1273:         }
1274:         if ( ! empty( $this->_capabilities_uninstall ) && is_string( $this->_capabilities_uninstall ) ) {
1275:             $caps[ $this->_capabilities_uninstall ] = esc_html__( 'Uninstall', 'gravityforms' );
1276:         }
1277:         if ( ! empty( $this->_capabilities_plugin_page ) && is_string( $this->_capabilities_plugin_page ) ) {
1278:             $caps[ $this->_capabilities_plugin_page ] = esc_html__( 'Add-On Page', 'gravityforms' );
1279:         }
1280:         if ( ! empty( $this->_capabilities_settings_page ) && is_string( $this->_capabilities_settings_page ) ) {
1281:             $caps[ $this->_capabilities_settings_page ] = esc_html__( 'Add-On Settings', 'gravityforms' );
1282:         }
1283: 
1284:         return $caps;
1285: 
1286:     }
1287: 
1288:     /**
1289:      * Register Gravity Forms Add-Ons capabilities group with User Role Editor plugin.
1290:      *
1291:      * @since  2.4
1292:      *
1293:      * @param array $groups Existing capabilities groups.
1294:      *
1295:      * @return array
1296:      */
1297:     public static function filter_ure_capabilities_groups_tree( $groups = array() ) {
1298: 
1299:         $groups['gravityforms_addons'] = array(
1300:             'caption' => esc_html__( 'Gravity Forms Add-Ons', 'gravityforms' ),
1301:             'parent'  => 'gravityforms',
1302:             'level'   => 3,
1303:         );
1304: 
1305:         return $groups;
1306: 
1307:     }
1308: 
1309:     /**
1310:      * Register Gravity Forms capabilities with Gravity Forms group in User Role Editor plugin.
1311:      *
1312:      * @since  2.4
1313:      *
1314:      * @param array  $groups Current capability groups.
1315:      * @param string $cap_id Capability identifier.
1316:      *
1317:      * @return array
1318:      */
1319:     public function filter_ure_custom_capability_groups( $groups = array(), $cap_id = '' ) {
1320: 
1321:         // Get Add-On capabilities.
1322:         $caps = $this->_capabilities;
1323: 
1324:         // If capability belongs to Add-On, register it to group.
1325:         if ( in_array( $cap_id, $caps, true ) ) {
1326:             $groups[] = 'gravityforms_addons';
1327:         }
1328: 
1329:         return $groups;
1330: 
1331:     }
1332: 
1333:     /**
1334:      * Checks whether the current user is assigned to a capability or role.
1335:      *
1336:      * @since  Unknown
1337:      * @access public
1338:      *
1339:      * @param string|array $caps An string or array of capabilities to check
1340:      *
1341:      * @return bool Returns true if the current user is assigned to any of the capabilities.
1342:      */
1343:     public function current_user_can_any( $caps ) {
1344:         return GFCommon::current_user_can_any( $caps );
1345:     }
1346: 
1347: 
1348:     //------- Settings Helper Methods (Common to all settings pages) -------------------
1349: 
1350:     /***
1351:      * Renders the UI of all settings page based on the specified configuration array $sections
1352:      *
1353:      * @param array $sections - Configuration array containing all fields to be rendered grouped into sections
1354:      */
1355:     public function render_settings( $sections ) {
1356: 
1357:         if ( ! $this->has_setting_field_type( 'save', $sections ) ) {
1358:             $sections = $this->add_default_save_button( $sections );
1359:         }
1360: 
1361:         ?>
1362: 
1363:         <form id="gform-settings" action="" method="post">
1364:             <?php wp_nonce_field( $this->_slug . '_save_settings', '_' . $this->_slug . '_save_settings_nonce' ) ?>
1365:             <?php $this->settings( $sections ); ?>
1366: 
1367:         </form>
1368: 
1369:     <?php
1370:     }
1371: 
1372:     /***
1373:      * Renders settings fields based on the specified configuration array $sections
1374:      *
1375:      * @param array $sections - Configuration array containing all fields to be rendered grouped into sections
1376:      */
1377:     public function settings( $sections ) {
1378:         $is_first = true;
1379:         foreach ( $sections as $section ) {
1380:             if ( $this->setting_dependency_met( rgar( $section, 'dependency' ) ) ) {
1381:                 $this->single_section( $section, $is_first );
1382:             }
1383: 
1384:             $is_first = false;
1385:         }
1386:     }
1387: 
1388:     /***
1389:      * Displays the UI for a field section
1390:      *
1391:      * @param array $section  - The section to be displayed
1392:      * @param bool  $is_first - true for the first section in the list, false for all others
1393:      */
1394:     public function single_section( $section, $is_first = false ) {
1395: 
1396:         extract(
1397:             wp_parse_args(
1398:                 $section, array(
1399:                     'title'       => false,
1400:                     'description' => false,
1401:                     'id'          => '',
1402:                     'class'       => false,
1403:                     'style'       => '',
1404:                     'tooltip'     => false,
1405:                     'tooltip_class' => ''
1406:                 )
1407:             )
1408:         );
1409: 
1410:         $classes = array( 'gaddon-section' );
1411: 
1412:         if ( $is_first ) {
1413:             $classes[] = 'gaddon-first-section';
1414:         }
1415: 
1416:         if ( $class )
1417:             $classes[] = $class;
1418: 
1419:         ?>
1420: 
1421:         <div
1422:             id="<?php echo $id; ?>"
1423:             class="<?php echo implode( ' ', $classes ); ?>"
1424:             style="<?php echo $style; ?>"
1425:             >
1426: 
1427:             <?php if ( $title ): ?>
1428:                 <h4 class="gaddon-section-title gf_settings_subgroup_title">
1429:                     <?php echo $title; ?>
1430:                     <?php if( $tooltip ): ?>
1431:                         <?php gform_tooltip( $tooltip, $tooltip_class ); ?>
1432:                     <?php endif; ?>
1433:                 </h4>
1434:             <?php endif; ?>
1435: 
1436:             <?php if ( $description ): ?>
1437:                 <div class="gaddon-section-description"><?php echo $description; ?></div>
1438:             <?php endif; ?>
1439: 
1440:             <table class="form-table gforms_form_settings">
1441: 
1442:                 <?php
1443:                 foreach ( $section['fields'] as $field ) {
1444: 
1445:                     if ( ! $this->setting_dependency_met( rgar( $field, 'dependency' ) ) )
1446:                         continue;
1447: 
1448:                     if ( is_callable( array( $this, "single_setting_row_{$field['type']}" ) ) ) {
1449:                         call_user_func( array( $this, "single_setting_row_{$field['type']}" ), $field );
1450:                     } else {
1451:                         $this->single_setting_row( $field );
1452:                     }
1453:                 }
1454:                 ?>
1455: 
1456:             </table>
1457: 
1458:         </div>
1459: 
1460:     <?php
1461:     }
1462: 
1463:     /***
1464:      * Displays the UI for the field container row
1465:      *
1466:      * @param array $field - The field to be displayed
1467:      */
1468:     public function single_setting_row( $field ) {
1469: 
1470:         $display = rgar( $field, 'hidden' ) || rgar( $field, 'type' ) == 'hidden' ? 'style="display:none;"' : '';
1471: 
1472:         // Prepare setting description.
1473:         $description = rgar( $field, 'description' ) ? '<span class="gf_settings_description">' . $field['description'] . '</span>' : null;
1474: 
1475:         ?>
1476: 
1477:         <tr id="gaddon-setting-row-<?php echo $field['name'] ?>" <?php echo $display; ?>>
1478:             <th>
1479:                 <?php $this->single_setting_label( $field ); ?>
1480:             </th>
1481:             <td>
1482:                 <?php
1483:                     $this->single_setting( $field );
1484:                     echo $description;
1485:                 ?>
1486:             </td>
1487:         </tr>
1488: 
1489:     <?php
1490:     }
1491: 
1492:     /**
1493:      * Displays the label for a field, including the tooltip and requirement indicator.
1494:      */
1495:     public function single_setting_label( $field ) {
1496: 
1497:         echo rgar( $field, 'label' );
1498: 
1499:         if ( isset( $field['tooltip'] ) ) {
1500:             echo $this->maybe_get_tooltip( $field );
1501:         }
1502: 
1503:         if ( rgar( $field, 'required' ) ) {
1504:             echo ' ' . $this->get_required_indicator( $field );
1505:         }
1506: 
1507:     }
1508: 
1509:     public function single_setting_row_save( $field ) {
1510:         ?>
1511: 
1512:         <tr>
1513:             <td colspan="2">
1514:                 <?php $this->single_setting( $field ); ?>
1515:             </td>
1516:         </tr>
1517: 
1518:     <?php
1519:     }
1520: 
1521:     /***
1522:      * Calls the appropriate field function to handle rendering of each specific field type
1523:      *
1524:      * @param array $field - The field to be rendered
1525:      */
1526:     public function single_setting( $field ) {
1527:         if ( is_callable( rgar( $field, 'callback' ) ) ) {
1528:             call_user_func( $field['callback'], $field );
1529:         } elseif ( is_callable( array( $this, "settings_{$field['type']}" ) ) ) {
1530:             call_user_func( array( $this, "settings_{$field['type']}" ), $field );
1531:         } else {
1532:             printf( esc_html__( "Field type '%s' has not been implemented", 'gravityforms' ), esc_html( $field['type'] ) );
1533:         }
1534:     }
1535: 
1536:     /***
1537:      * Sets the current saved settings to a class variable so that it can be accessed by lower level functions in order to initialize inputs with the appropriate values
1538:      *
1539:      * @param array $settings : Settings to be saved
1540:      */
1541:     public function set_settings( $settings ) {
1542:         $this->_saved_settings = $settings;
1543:     }
1544: 
1545:     /***
1546:      * Sets the previous settings to a class variable so that it can be accessed by lower level functions providing support for
1547:      * verifying whether a value was changed before executing an action
1548:      *
1549:      * @param array $settings : Settings to be stored
1550:      */
1551:     public function set_previous_settings( $settings ) {
1552:         $this->_previous_settings = $settings;
1553:     }
1554: 
1555:     public function get_previous_settings() {
1556:         return $this->_previous_settings;
1557:     }
1558: 
1559: 
1560:     /***
1561:      * Gets settings from $_POST variable, returning a name/value collection of setting name and setting value
1562:      */
1563:     public function get_posted_settings() {
1564:         global $_gaddon_posted_settings;
1565: 
1566:         if ( isset( $_gaddon_posted_settings ) ) {
1567:             return $_gaddon_posted_settings;
1568:         }
1569: 
1570:         $_gaddon_posted_settings = array();
1571:         if ( count( $_POST ) > 0 ) {
1572:             foreach ( $_POST as $key => $value ) {
1573:                 if ( preg_match( '|_gaddon_setting_(.*)|', $key, $matches ) ) {
1574:                     $_gaddon_posted_settings[ $matches[1] ] = self::maybe_decode_json( stripslashes_deep( $value ) );
1575:                 }
1576:             }
1577:         }
1578: 
1579:         return $_gaddon_posted_settings;
1580:     }
1581: 
1582:     public static function maybe_decode_json( $value ) {
1583:         if ( self::is_json( $value ) ) {
1584:             return json_decode( $value, ARRAY_A );
1585:         }
1586: 
1587:         return $value;
1588:     }
1589: 
1590:     public static function is_json( $value ) {
1591:         if ( is_string( $value ) && in_array( substr( $value, 0, 1 ), array( '{', '[' ) ) && is_array( json_decode( $value, ARRAY_A ) ) ) {
1592:             return true;
1593:         }
1594: 
1595:         return false;
1596:     }
1597: 
1598:     /***
1599:      * Gets the "current" settings, which are settings from $_POST variables if this is a postback request, or the current saved settings for a get request.
1600:      */
1601:     public function get_current_settings() {
1602:         //try getting settings from post
1603:         $settings = $this->get_posted_settings();
1604: 
1605:         //if nothing has been posted, get current saved settings
1606:         if ( empty( $settings ) ) {
1607:             $settings = $this->_saved_settings;
1608:         }
1609: 
1610:         return $settings;
1611:     }
1612: 
1613:     /***
1614:      * Retrieves the setting for a specific field/input
1615:      *
1616:      * @param string $setting_name  The field or input name
1617:      * @param string $default_value Optional. The default value
1618:      * @param bool|array $settings Optional. THe settings array
1619:      *
1620:      * @return string|array
1621:      */
1622:     public function get_setting( $setting_name, $default_value = '', $settings = false ) {
1623: 
1624:         if ( ! $settings ) {
1625:             $settings = $this->get_current_settings();
1626:         }
1627: 
1628:         if ( false === $settings ) {
1629:             return $default_value;
1630:         }
1631: 
1632:         if ( strpos( $setting_name, '[' ) !== false ) {
1633:             $path_parts = explode( '[', $setting_name );
1634:             foreach ( $path_parts as $part ) {
1635:                 $part = trim( $part, ']' );
1636:                 if ( $part != '0'){
1637:                     if ( empty( $part ) ) {
1638:                         return $settings;
1639:                     }
1640:                 }
1641:                 if ( false === isset( $settings[ $part ] ) ) {
1642:                     return $default_value;
1643:                 }
1644: 
1645:                 $settings = rgar( $settings, $part );
1646:             }
1647:             $setting = $settings;
1648:         } else {
1649:             if ( false === isset( $settings[ $setting_name ] ) ) {
1650:                 return $default_value;
1651:             }
1652:             $setting = $settings[ $setting_name ];
1653:         }
1654: 
1655:         return $setting;
1656:     }
1657: 
1658:     /***
1659:      * Determines if a dependent field has been populated.
1660:      *
1661:      * @param string $dependency - Field or input name of the "parent" field.
1662:      *
1663:      * @return bool - true if the "parent" field has been filled out and false if it has not.
1664:      *
1665:      */
1666:     public function setting_dependency_met( $dependency ) {
1667: 
1668:         // if no dependency, always return true
1669:         if ( ! $dependency ) {
1670:             return true;
1671:         }
1672: 
1673:         //use a callback if one is specified in the configuration
1674:         if ( is_callable( $dependency ) ) {
1675:             return call_user_func( $dependency );
1676:         }
1677: 
1678:         if ( is_array( $dependency ) ) {
1679:             //supports: 'dependency' => array("field" => 'myfield', 'values' => array("val1", 'val2'))
1680:             $dependency_field = $dependency['field'];
1681:             $dependency_value = $dependency['values'];
1682:         } else {
1683:             //supports: 'dependency' => 'myfield'
1684:             $dependency_field = $dependency;
1685:             $dependency_value = '_notempty_';
1686:         }
1687: 
1688:         if ( ! is_array( $dependency_value ) ) {
1689:             $dependency_value = array( $dependency_value );
1690:         }
1691: 
1692:         $current_value = $this->get_setting( $dependency_field );
1693: 
1694:         foreach ( $dependency_value as $val ) {
1695:             if ( $current_value == $val ) {
1696:                 return true;
1697:             }
1698: 
1699:             if ( $val == '_notempty_' && ! rgblank( $current_value ) ) {
1700:                 return true;
1701:             }
1702:         }
1703: 
1704:         return false;
1705:     }
1706: 
1707:     public function has_setting_field_type( $type, $fields ) {
1708:         if ( ! empty( $fields ) ) {
1709:             foreach ( $fields as &$section ) {
1710:                 foreach ( $section['fields'] as $field ) {
1711:                     if ( rgar( $field, 'type' ) == $type ) {
1712:                         return true;
1713:                     }
1714:                 }
1715:             }
1716:         }
1717:         return false;
1718:     }
1719: 
1720:     public function add_default_save_button( $sections ) {
1721:         $sections[ count( $sections ) - 1 ]['fields'][] = array( 'type' => 'save' );
1722: 
1723:         return $sections;
1724:     }
1725: 
1726:     public function get_save_success_message( $sections ) {
1727:         $save_button = $this->get_save_button( $sections );
1728: 
1729:         return isset( $save_button['messages']['success'] ) ? $save_button['messages']['success'] : sprintf( esc_html__( '%s settings updated.', 'gravityforms' ), $this->get_short_title() );
1730:     }
1731: 
1732:     public function get_save_error_message( $sections ) {
1733:         $save_button = $this->get_save_button( $sections );
1734: 
1735:         return isset( $save_button['messages']['error'] ) ? $save_button['messages']['error'] : esc_html__( 'There was an error while saving your settings.', 'gravityforms' );
1736:     }
1737: 
1738:     public function get_save_button( $sections ) {
1739:         $sections = array_values( $sections );
1740:         $fields   = $sections[ count( $sections ) - 1 ]['fields'];
1741: 
1742:         foreach ( $fields as $field ) {
1743:             if ( $field['type'] == 'save' )
1744:                 return $field;
1745:         }
1746: 
1747:         return false;
1748:     }
1749: 
1750: 
1751: 
1752:     //------------- Field Types ------------------------------------------------------
1753: 
1754:     /***
1755:      * Renders and initializes a text field based on the $field array
1756:      *
1757:      * @param array $field - Field array containing the configuration options of this field
1758:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
1759:      *
1760:      * @return string The HTML for the field
1761:      */
1762:     public function settings_text( $field, $echo = true ) {
1763: 
1764:         $field['type']       = 'text'; //making sure type is set to text
1765:         $field['input_type'] = rgar( $field, 'input_type' ) ? rgar( $field, 'input_type' ) : 'text';
1766:         $attributes          = $this->get_field_attributes( $field );
1767:         $default_value       = rgar( $field, 'value' ) ? rgar( $field, 'value' ) : rgar( $field, 'default_value' );
1768:         $value               = $this->get_setting( $field['name'], $default_value );
1769: 
1770:         $html    = '';
1771: 
1772:         $html .= '<input
1773:                     type="' . esc_attr( $field['input_type'] ) . '"
1774:                     name="_gaddon_setting_' . esc_attr( $field['name'] ) . '"
1775:                     value="' . esc_attr( htmlspecialchars( $value, ENT_QUOTES ) ) . '" ' .
1776:                  implode( ' ', $attributes ) .
1777:                  ' />';
1778:                  
1779:         $html .= rgar( $field, 'after_input' );
1780: 
1781:         $feedback_callback = rgar( $field, 'feedback_callback' );
1782:         if ( is_callable( $feedback_callback ) ) {
1783:             $is_valid = call_user_func_array( $feedback_callback, array( $value, $field ) );
1784:             $icon     = '';
1785:             if ( $is_valid === true ) {
1786:                 $icon = 'icon-check fa-check gf_valid'; // check icon
1787:             } elseif ( $is_valid === false ) {
1788:                 $icon = 'icon-remove fa-times gf_invalid'; // x icon
1789:             }
1790: 
1791:             if ( ! empty( $icon ) ) {
1792:                 $html .= "&nbsp;&nbsp;<i class=\"fa {$icon}\"></i>";
1793:             }
1794:         }
1795: 
1796:         if ( $this->field_failed_validation( $field ) ) {
1797:             $html .= $this->get_error_icon( $field );
1798:         }
1799: 
1800:         if ( $echo ) {
1801:             echo $html;
1802:         }
1803: 
1804:         return $html;
1805:     }
1806: 
1807:     /***
1808:      * Renders and initializes a textarea field based on the $field array
1809:      *
1810:      * @param array $field - Field array containing the configuration options of this field
1811:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
1812:      *
1813:      * @return string The HTML for the field
1814:      */
1815:     public function settings_textarea( $field, $echo = true ) {
1816:         $field['type'] = 'textarea'; //making sure type is set to textarea
1817:         $attributes    = $this->get_field_attributes( $field );
1818:         $default_value = rgar( $field, 'value' ) ? rgar( $field, 'value' ) : rgar( $field, 'default_value' );
1819:         $value         = $this->get_setting( $field['name'], $default_value );
1820: 
1821:         $name    = '' . esc_attr( $field['name'] );
1822:         $html    = '';
1823: 
1824:         if ( rgar( $field, 'use_editor' ) ) {
1825:             
1826:             $html .= '<span class="mt-gaddon-editor mt-_gaddon_setting_'. $field['name'] .'"></span>';
1827:             
1828:             ob_start();
1829:             
1830:             wp_editor( $value, '_gaddon_setting_'. $field['name'], array( 'autop' => false, 'editor_class' => 'merge-tag-support mt-wp_editor mt-manual_position mt-position-right' ) );
1831:             
1832:             $html .= ob_get_contents();
1833:             ob_end_clean();
1834:             
1835:         } else {
1836:             
1837:             $html .= '<textarea
1838:                     name="_gaddon_setting_' . $name . '" ' .
1839:                  implode( ' ', $attributes ) .
1840:                  '>' .
1841:                      esc_textarea( $value ) .
1842:                  '</textarea>';
1843:             
1844:         }
1845: 
1846:         if ( $this->field_failed_validation( $field ) ) {
1847:             $html .= $this->get_error_icon( $field );
1848:         }
1849: 
1850:         if ( $echo ) {
1851:             echo $html;
1852:         }
1853: 
1854:         return $html;
1855:     }
1856: 
1857: 
1858:     /***
1859:      * Renders and initializes a hidden field based on the $field array
1860:      *
1861:      * @param array $field - Field array containing the configuration options of this field
1862:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
1863:      *
1864:      * @return string The HTML for the field
1865:      */
1866:     public function settings_hidden( $field, $echo = true ) {
1867:         $field['type'] = 'hidden'; //making sure type is set to hidden
1868:         $attributes    = $this->get_field_attributes( $field );
1869: 
1870:         $default_value = rgar( $field, 'value' ) ? rgar( $field, 'value' ) : rgar( $field, 'default_value' );
1871:         $value         = $this->get_setting( $field['name'], $default_value );
1872: 
1873:         if ( is_array( $value ) ) {
1874:             $value = json_encode( $value );
1875:         }
1876: 
1877:         $html = '<input
1878:                     type="hidden"
1879:                     name="_gaddon_setting_' . esc_attr( $field['name'] ) . '"
1880:                     value=\'' . esc_attr( $value ) . '\' ' .
1881:                 implode( ' ', $attributes ) .
1882:                 ' />';
1883: 
1884:         if ( $echo ) {
1885:             echo $html;
1886:         }
1887: 
1888:         return $html;
1889:     }
1890: 
1891:     /***
1892:      * Renders and initializes a checkbox field or a collection of checkbox fields based on the $field array
1893:      *
1894:      * @param array $field - Field array containing the configuration options of this field
1895:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
1896:      *
1897:      * @return string The HTML for the field
1898:      */
1899:     public function settings_checkbox( $field, $echo = true ) {
1900: 
1901:         $field['type'] = 'checkbox'; //making sure type is set to checkbox
1902: 
1903:         $field_attributes   = $this->get_field_attributes( $field, array() );
1904:         $have_icon          = $this->choices_have_icon( rgar( $field, 'choices' ) );
1905:         $horizontal         = rgar( $field, 'horizontal' ) || $have_icon ? ' gaddon-setting-inline' : '';
1906: 
1907: 
1908: 
1909:         $html = '';
1910:         $default_choice_attributes = array( 'onclick' => 'jQuery(this).siblings("input[type=hidden]").val(jQuery(this).prop("checked") ? 1 : 0);', 'onkeypress' => 'jQuery(this).siblings("input[type=hidden]").val(jQuery(this).prop("checked") ? 1 : 0);' );
1911:         $is_first_choice = true;
1912:         if ( is_array( $field['choices'] ) ) {
1913:             foreach ( $field['choices'] as $choice ) {
1914:                 $choice['id']      = sanitize_title( $choice['name'] );
1915:                 $choice_attributes = $this->get_choice_attributes( $choice, $field_attributes, $default_choice_attributes );
1916:                 $value             = $this->get_setting( $choice['name'], rgar( $choice, 'default_value' ) );
1917:                 $tooltip           = $this->maybe_get_tooltip( $choice );
1918: 
1919:                 //displaying error message after first checkbox item
1920:                 $error_icon = '';
1921:                 if ( $is_first_choice ){
1922:                     $error_icon = $this->field_failed_validation( $field ) ? $this->get_error_icon( $field ) : '';
1923:                 }
1924:                 
1925:                 // Add icon to choice if choices have icon
1926:                 if ( $have_icon ) {
1927:                     $choice['icon'] = rgar( $choice, 'icon' ) ? $choice['icon'] : 'fa-cog';
1928:                 }
1929: 
1930:                 $html .= $this->checkbox_item( $choice, $horizontal, $choice_attributes, $value, $tooltip, $error_icon );
1931: 
1932:                 $is_first_choice = false;
1933:             }
1934:         }
1935: 
1936:         if ( $echo ) {
1937:             echo $html;
1938:         }
1939: 
1940:         return $html;
1941:     }
1942: 
1943: 
1944:     /**
1945:      * Returns the markup for an individual checkbox item give the parameters
1946:      *
1947:      * @param $choice           - Choice array with all configured properties
1948:      * @param $horizontal_class - CSS class to style checkbox items horizontally
1949:      * @param $attributes       - String containing all the attributes for the input tag.
1950:      * @param $value            - Currently selection (1 if field has been checked. 0 or null otherwise)
1951:      * @param $tooltip          - String containing a tooltip for this checkbox item.
1952:      *
1953:      * @return string - The markup of an individual checkbox item
1954:      */
1955:     public function checkbox_item( $choice, $horizontal_class, $attributes, $value, $tooltip, $error_icon = '' ) {
1956:         
1957:         $hidden_field_value = $value == '1' ? '1' : '0';
1958:         $icon_class         = rgar( $choice, 'icon' ) ? ' gaddon-setting-choice-visual' : '';
1959:     
1960:         $checkbox_item  = '<div id="gaddon-setting-checkbox-choice-' . $choice['id'] . '" class="gaddon-setting-checkbox' . $horizontal_class . $icon_class . '">';
1961:         $checkbox_item .= '<input type=hidden name="_gaddon_setting_' . esc_attr( $choice['name'] ) . '" value="' . $hidden_field_value . '" />';
1962: 
1963:         if ( is_callable( array( $this, "checkbox_input_{$choice['name']}" ) ) ) {
1964:             $markup = call_user_func( array( $this, "checkbox_input_{$choice['name']}" ), $choice, $attributes, $value, $tooltip );
1965:         } else {
1966:             $markup = $this->checkbox_input( $choice, $attributes, $value, $tooltip );
1967:         }
1968:         
1969:         $checkbox_item .= $markup . $error_icon . '</div>';
1970: 
1971:         return $checkbox_item;
1972:     }
1973: 
1974:     /**
1975:      * Returns the markup for an individual checkbox input and its associated label
1976:      *
1977:      * @param $choice     - Choice array with all configured properties
1978:      * @param $attributes - String containing all the attributes for the input tag.
1979:      * @param $value      - Currently selection (1 if field has been checked. 0 or null otherwise)
1980:      * @param $tooltip    - String containing a tooltip for this checkbox item.
1981:      *
1982:      * @return string - The markup of an individual checkbox input and its associated label
1983:      */
1984:     public function checkbox_input( $choice, $attributes, $value, $tooltip ) {
1985:         
1986:         $icon_tag = '';
1987:         
1988:         if ( rgar( $choice, 'icon' ) ) {
1989:             
1990:             /* Get the defined icon. */
1991:             $icon = rgar( $choice, 'icon' ) ? $choice['icon'] : 'fa-cog';
1992:             
1993:             /* Set icon tag based on icon type. */
1994:             if ( filter_var( $icon, FILTER_VALIDATE_URL ) ) {
1995:                 $icon_tag = '<img src="' . esc_attr( $icon ) .'" />';
1996:             } else {
1997:                 $icon_tag = '<i class="fa ' . esc_attr( $icon ) .'"></i>';
1998:             }
1999:             
2000:             $icon_tag .= '<br />';
2001: 
2002:         }
2003: 
2004:         $markup  = '<input type = "checkbox" ' . implode( ' ', $attributes ) . ' ' . checked( $value, '1', false ) . ' />';
2005:         $markup .= '<label for="' . esc_attr( $choice['id'] ) . '"><span>' . $icon_tag . esc_html( $choice['label'] ) . $tooltip . '</span></label>';
2006:         
2007: 
2008:         return $markup;
2009:     }
2010: 
2011: 
2012:     /***
2013:      * Renders and initializes a radio field or a collection of radio fields based on the $field array
2014:      *
2015:      * @param array $field - Field array containing the configuration options of this field
2016:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
2017:      *
2018:      * @return string Returns the markup for the radio buttons
2019:      *
2020:      */
2021:     public function settings_radio( $field, $echo = true ) {
2022: 
2023:         $field['type'] = 'radio'; //making sure type is set to radio
2024: 
2025:         $selected_value   = $this->get_setting( $field['name'], rgar( $field, 'default_value' ) );
2026:         $field_attributes = $this->get_field_attributes( $field );
2027:         $have_icon        = $this->choices_have_icon( rgar( $field, 'choices' ) );
2028:         $horizontal       = rgar( $field, 'horizontal' ) || $have_icon ? ' gaddon-setting-inline' : '';
2029:         $html             = '';
2030:         
2031:         if ( is_array( $field['choices'] ) ) {
2032:             
2033:             
2034:             foreach ( $field['choices'] as $i => $choice ) {
2035: 
2036:                 if ( rgempty( 'id', $choice ) ) {
2037:                     $choice['id'] = $field['name'] . $i;
2038:                 }
2039: 
2040:                 $choice_attributes = $this->get_choice_attributes( $choice, $field_attributes );
2041:                 $tooltip           = $this->maybe_get_tooltip( $choice );
2042:                 $radio_value       = isset( $choice['value'] ) ? $choice['value'] : $choice['label'];
2043:                 $checked           = checked( $selected_value, $radio_value, false );
2044:                 
2045:                 if ( $have_icon ) {
2046:                     
2047:                     /* Get the defined icon. */
2048:                     $icon = rgar( $choice, 'icon' ) ? $choice['icon'] : 'fa-cog';
2049:                     
2050:                     /* Set icon tag based on icon type. */
2051:                     if ( filter_var( $icon, FILTER_VALIDATE_URL ) ) {
2052:                         $icon_tag = '<img src="' . esc_attr( $icon ) .'" />';
2053:                     } else {
2054:                         $icon_tag = '<i class="fa ' . esc_attr( $icon ) .'"></i>';
2055:                     }
2056:                     
2057:                     $html .= '<div id="gaddon-setting-radio-choice-' . $choice['id'] . '" class="gaddon-setting-radio gaddon-setting-choice-visual' . $horizontal . '">';
2058:                     $html .= '<input type="radio" name="_gaddon_setting_' . esc_attr( $field['name'] ) . '" ' .
2059:                              'value="' . $radio_value . '" ' . implode( ' ', $choice_attributes ) . ' ' . $checked . ' />';
2060:                     $html .= '<label for="' . esc_attr( $choice['id'] ) . '">';
2061:                     $html .= '<span>' . $icon_tag . '<br />' . esc_html( $choice['label'] ) . $tooltip . '</span>';
2062:                     $html .= '</label>';
2063:                     $html .= '</div>';
2064:                     
2065:                 } else {
2066:                 
2067:                     $html .= '<div id="gaddon-setting-radio-choice-' . $choice['id'] . '" class="gaddon-setting-radio' . $horizontal . '">';
2068:                     $html .= '<label for="' . esc_attr( $choice['id'] ) . '">';
2069:                     $html .= '<input type="radio" name="_gaddon_setting_' . esc_attr( $field['name'] ) . '" ' .
2070:                              'value="' . $radio_value . '" ' . implode( ' ', $choice_attributes ) . ' ' . $checked . ' />';
2071:                     $html .= '<span>' . esc_html( $choice['label'] ) . $tooltip . '</span>';
2072:                     $html .= '</label>';
2073:                     $html .= '</div>';
2074:                     
2075:                 }
2076:                 
2077:             }
2078:             
2079:         }
2080: 
2081:         if ( $this->field_failed_validation( $field ) ) {
2082:             $html .= $this->get_error_icon( $field );
2083:         }
2084: 
2085:         if ( $echo ) {
2086:             echo $html;
2087:         }
2088: 
2089:         return $html;
2090:     }
2091:     
2092:     /**
2093:      * Determines if any of the available settings choices have an icon.
2094:      * 
2095:      * @access public
2096:      * @param array $choices (default: array())
2097:      * @return bool
2098:      */
2099:     public function choices_have_icon( $choices = array() ) {
2100:         
2101:         $have_icon = false;
2102:         
2103:         foreach ( $choices as $choice ) {
2104:             if ( rgar( $choice, 'icon' ) ) {
2105:                 $have_icon = true;
2106:             }
2107:         }
2108:         
2109:         return $have_icon;
2110:         
2111:     }
2112: 
2113:     /***
2114:      * Renders and initializes a drop down field based on the $field array
2115:      *
2116:      * @param array $field - Field array containing the configuration options of this field
2117:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
2118:      *
2119:      * @return string The HTML for the field
2120:      */
2121:     public function settings_select( $field, $echo = true ) {
2122: 
2123:         $field['type'] = 'select'; // making sure type is set to select
2124:         $attributes    = $this->get_field_attributes( $field );
2125:         $value         = $this->get_setting( $field['name'], rgar( $field, 'default_value' ) );
2126:         $name          = '' . esc_attr( $field['name'] );
2127: 
2128:         // If no choices were provided and there is a no choices message, display it.
2129:         if ( ( empty( $field['choices'] ) || ! rgar( $field, 'choices' ) ) && rgar( $field, 'no_choices' ) ) {
2130: 
2131:             $html = $field['no_choices'];
2132: 
2133:         } else {
2134: 
2135:             $html = sprintf(
2136:                 '<select name="%1$s" %2$s>%3$s</select>',
2137:                 '_gaddon_setting_' . $name, implode( ' ', $attributes ), $this->get_select_options( $field['choices'], $value )
2138:             );
2139: 
2140:             $html .= rgar( $field, 'after_select' );
2141: 
2142:         }
2143: 
2144:         if ( $this->field_failed_validation( $field ) ) {
2145:             $html .= $this->get_error_icon( $field );
2146:         }
2147: 
2148:         if ( $echo ) {
2149:             echo $html;
2150:         }
2151: 
2152:         return $html;
2153:     }
2154: 
2155:     /**
2156:      * Renders and initializes a drop down field with a input field for custom input based on the $field array.
2157:      * 
2158:      * @param array $field - Field array containing the configuration options of this field
2159:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
2160:      * 
2161:      * @return string The HTML for the field
2162:      */
2163:     public function settings_select_custom( $field, $echo = true ) {
2164:         
2165:         /* Prepare select field */
2166:         $select_field             = $field;
2167:         $select_field_value       = $this->get_setting( $select_field['name'], rgar( $select_field, 'default_value' ) );
2168:         $select_field['onchange'] = '';
2169:         $select_field['class']    = ( isset( $select_field['class'] ) ) ? $select_field['class'] . 'gaddon-setting-select-custom' : 'gaddon-setting-select-custom';
2170: 
2171:         /* Prepare input field */
2172:         $input_field          = $field;
2173:         $input_field['name'] .= '_custom';
2174:         $input_field['style'] = 'width:200px;max-width:90%;';
2175:         $input_field_display  = '';
2176: 
2177:         /* Loop through select choices and make sure option for custom exists */
2178:         $has_gf_custom = false;
2179:         foreach ( $select_field['choices'] as $choice ) {
2180:             
2181:             if ( rgar( $choice, 'name' ) == 'gf_custom' || rgar( $choice, 'value' ) == 'gf_custom' ) {
2182:                 $has_gf_custom = true;
2183:             }
2184:             
2185:             /* If choice has choices, check inside those choices. */
2186:             if ( rgar( $choice, 'choices' ) ) {
2187:                 foreach ( $choice['choices'] as $subchoice ) {
2188:                     if ( rgar( $subchoice, 'name' ) == 'gf_custom' || rgar( $subchoice, 'value' ) == 'gf_custom' ) {
2189:                         $has_gf_custom = true;
2190:                     }
2191:                 }
2192:             }
2193:             
2194:         }
2195:         if ( ! $has_gf_custom ) {
2196:             $select_field['choices'][] = array(
2197:                 'label' => esc_html__( 'Add Custom', 'gravityforms' ) .' ' . $select_field['label'],
2198:                 'value' => 'gf_custom'
2199:             );
2200:         }
2201:         
2202:         /* If select value is "gf_custom", hide the select field and display the input field. */
2203:         if ( $select_field_value == 'gf_custom' || ( count( $select_field['choices'] ) == 1 && $select_field['choices'][0]['value'] == 'gf_custom' ) ) {
2204:             $select_field['style'] = 'display:none;';
2205:         } else {
2206:             $input_field_display   = ' style="display:none;"';
2207:         }
2208:                                 
2209:         /* Add select field */
2210:         $html = $this->settings_select( $select_field, false );
2211:         
2212:         /* Add input field */
2213:         $html .= '<div class="gaddon-setting-select-custom-container"'. $input_field_display .'>';
2214:         $html .= count( $select_field['choices'] ) > 1 ? '<a href="#" class="select-custom-reset">Reset</a>' : '';
2215:         $html .= $this->settings_text( $input_field, false );
2216:         $html .= '</div>';
2217: 
2218:         if ( $echo ) {
2219:             echo $html;
2220:         }
2221: 
2222:         return $html;
2223:         
2224:     }
2225: 
2226:     /**
2227:      * Prepares an HTML string of options for a drop down field.
2228:      * 
2229:      * @param array  $choices - Array containing all the options for the drop down field
2230:      * @param string $selected_value - The value currently selected for the field
2231:      * 
2232:      * @return string The HTML for the select options
2233:      */
2234:     public function get_select_options( $choices, $selected_value ) {
2235: 
2236:         $options = '';
2237: 
2238:         foreach ( $choices as $choice ) {
2239: 
2240:             if ( isset( $choice['choices'] ) ) {
2241: 
2242:                 $options .= sprintf( '<optgroup label="%1$s">%2$s</optgroup>', esc_attr( $choice['label'] ), $this->get_select_options( $choice['choices'], $selected_value ) );
2243: 
2244:             } else {
2245: 
2246:                 if ( ! isset( $choice['value'] ) ) {
2247:                     $choice['value'] = $choice['label'];
2248:                 }
2249: 
2250:                 $options .= $this->get_select_option( $choice, $selected_value );
2251: 
2252:             }
2253:         }
2254: 
2255:         return $options;
2256:     }
2257: 
2258:     /**
2259:      * Prepares an HTML string for a single drop down field option.
2260:      * 
2261:      * @access protected
2262:      * @param array  $choice - Array containing the settings for the drop down option
2263:      * @param string $selected_value - The value currently selected for the field
2264:      * 
2265:      * @return string The HTML for the select choice
2266:      */
2267:     public function get_select_option( $choice, $selected_value ) {
2268:         if ( is_array( $selected_value ) ) {
2269:             $selected = in_array( $choice['value'], $selected_value ) ? "selected='selected'" : '';
2270:         } else {
2271:             $selected = selected( $selected_value, $choice['value'], false );
2272:         }
2273: 
2274:         return sprintf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $choice['value'] ), $selected, $choice['label'] );
2275:     }
2276: 
2277: 
2278: 
2279: 
2280: 
2281:     //------------- Field Map Field Type --------------------------
2282: 
2283:     /**
2284:      * Renders and initializes a generic map field based on the $field array whose choices are populated by the fields to be mapped.
2285:      *
2286:      * @since  2.2
2287:      * @access public
2288:      *
2289:      * @uses GFAddOn::field_failed_validation()
2290:      * @uses GFCommon::get_base_url()
2291:      * @uses GFAddOn::get_current_forn()
2292:      * @uses GFAddOn::get_error_icon()
2293:      * @uses GFAddOn::get_mapping_field()
2294:      * @uses GFAddOn::settings_hidden()
2295:      *
2296:      * @param array $field Field array containing the configuration options of this field.
2297:      * @param bool  $echo  Determines if field contents should automatically be displayed. Defaults to true.
2298:      *
2299:      * @return string The HTML for the field
2300:      */
2301:     public function settings_generic_map( $field, $echo = true ) {
2302: 
2303:         // Initialize return HTML string.
2304:         $html = '';
2305: 
2306:         // Shift field map choices to key property.
2307:         if ( isset( $field['field_map' ] ) ) {
2308:             $field['key_choices'] = $field['field_map'];
2309:         }
2310:         
2311:         // Shift legacy title properties.
2312:         foreach ( array( 'key', 'value' ) as $type ) {
2313:             if ( isset( $field[ $type . '_field_title' ] ) ) {
2314:                 $field[ $type . '_field' ]['title'] = $field[ $type . '_field_title' ];
2315:                 unset( $field[ $type . '_field_title' ] );
2316:             }
2317:         }
2318: 
2319:         // Set merge tags state to false if not defined.
2320:         if ( ! rgar( $field, 'merge_tags' ) ) {
2321:             $field['merge_tags'] = false;
2322:         }
2323: 
2324:         // Initialize field objects for child fields.
2325:         $value_field = $key_field = $custom_key_field = $custom_value_field = $field;
2326: 
2327:         // Define custom placeholder.
2328:         $custom_placeholder = 'gf_custom';
2329: 
2330:         // Define key field properties.
2331:         $key_field['name']    .= '_key';
2332:         $key_field['choices']  = rgar( $field, 'key_choices' ) ? $field['key_choices'] : rgars( $field, 'key_field/choices' );
2333:         $key_field['class']    = 'key key_{i}';
2334: 
2335:         // Define custom key field properties.
2336:         $custom_key_field['name']        .= '_custom_key_{i}';
2337:         $custom_key_field['class']        = 'custom_key custom_key_{i}';
2338:         $custom_key_field['value']        = '{custom_key}';
2339:         $custom_key_field['placeholder']  = rgars( $field, 'key_field/placeholder' ) ? $field['key_field']['placeholder'] : esc_html__( 'Custom Key', 'gravityforms' );
2340: 
2341:         // Define value field properties.
2342:         $value_field['name']   .= '_custom_value';
2343:         $value_field['choices'] = rgar( $field, 'value_choices' ) ? $field['value_choices'] : rgars( $field, 'value_field/choices' );
2344:         $value_field['class']   = 'value value_{i}';
2345: 
2346:         // Define custom value field properties.
2347:         $custom_value_field['name']        .= '_custom_value_{i}';
2348:         $custom_value_field['class']        = 'custom_value custom_value_{i}';
2349:         $custom_value_field['value']        = '{custom_value}';
2350:         $custom_value_field['placeholder']  = rgars( $field, 'value_field/placeholder' ) ? $field['value_field']['placeholder'] : esc_html__( 'Custom Value', 'gravityforms' );
2351: 
2352:         // Get key/field column titles.
2353:         $key_field_title   = rgars( $field, 'key_field/title' ) ? $field['key_field']['title'] : esc_html__( 'Key', 'gravityforms' );
2354:         $value_field_title = rgars( $field, 'value_field/title' ) ? $field['value_field']['title'] : esc_html__( 'Value', 'gravityforms' );
2355: 
2356:         // Remove unneeded field properties.
2357:         $unneeded_props = array( 'field_map', 'key_choices', 'value_choices', 'placeholders', 'callback' );
2358:         foreach ( $unneeded_props as $unneeded_prop ) {
2359:             unset( $field[ $unneeded_prop ] );
2360:             unset( $key_field[ $unneeded_prop ] );
2361:             unset( $value_field[ $unneeded_prop ] );
2362:             unset( $custom_key_field[ $unneeded_prop ] );
2363:             unset( $custom_value_field[ $unneeded_prop ] );
2364:         }
2365: 
2366:         // If field failed validation, display error icon.
2367:         if ( $this->field_failed_validation( $field ) ) {
2368:             $html .= $this->get_error_icon( $field );
2369:         }
2370: 
2371:         // Display hidden field containing dynamic field map value.
2372:         $html .= $this->settings_hidden( $field, false );
2373: 
2374:         // Display map table.
2375:         $html .= '
2376:             <table class="settings-field-map-table" cellspacing="0" cellpadding="0">
2377:                 <thead>
2378:                     <tr>
2379:                         <th>' . $key_field_title . '</th>
2380:                         <th>' . $value_field_title . '</th>
2381:                     </tr>
2382:                 </thead>
2383:                 <tbody class="repeater">
2384:                     <tr>
2385:                         ' . $this->get_mapping_field( 'key', $key_field, $custom_key_field ) .
2386:                             $this->get_mapping_field( 'value', $value_field, $custom_value_field ) . '
2387:                         <td>
2388:                             {buttons}
2389:                         </td>
2390:                     </tr>
2391:                 </tbody>
2392:             </table>';
2393: 
2394:         // Get generic map limit.
2395:         $limit = empty( $field['limit'] ) ? 0 : $field['limit'];
2396: 
2397:         // Initialize generic map via Javascript.
2398:         $html .= "
2399:             <script type=\"text/javascript\">
2400:             jQuery( document ).ready( function() {
2401:                 var genericMap". esc_attr( $field['name'] ) ." = new GFGenericMap({
2402:                     'baseURL':        '". GFCommon::get_base_url() ."',
2403:                     'fieldId':        '". esc_attr( $field['name'] ) ."',
2404:                     'fieldName':      '". $field['name'] ."',
2405:                     'keyFieldName':   '". $key_field['name'] ."',
2406:                     'valueFieldName': '". $value_field['name'] ."',
2407:                     'mergeTags':      " . var_export( $field['merge_tags'], true ) . ",
2408:                     'limit':          '". $limit . "'
2409:                 });
2410:             });
2411:             </script>";
2412: 
2413:         // If automatic display is enabled, echo field HTML.
2414:         if ( $echo ) {
2415:             echo $html;
2416:         }
2417: 
2418:         return $html;
2419: 
2420:     }
2421: 
2422:     /**
2423:      * Renders and initializes a field map field based on the $field array whose choices are populated by the fields to be mapped.
2424:      *
2425:      * @since  unknown
2426:      * @access public
2427:      *
2428:      * @uses GFAddOn::field_map_table_header()
2429:      * @uses GFAddOn::get_mapped_field_name()
2430:      * @uses GFAddOn::get_required_indicator()
2431:      * @uses GFAddOn::maybe_get_tooltip()
2432:      * @uses GFAddOn::setting_dependency_met()
2433:      * @uses GFAddOn::settings_field_map_select()
2434:      *
2435:      * @param array $field Field array containing the configuration options of this field.
2436:      * @param bool  $echo  Determines if field contents should automatically be displayed. Defaults to true.
2437:      *
2438:      * @return string The HTML for the field
2439:      */
2440:     public function settings_field_map( $field, $echo = true ) {
2441: 
2442:         // Initialize return HTML string.
2443:         $html = '';
2444: 
2445:         // Get field map choices.
2446:         $field_map = rgar( $field, 'field_map' );
2447: 
2448:         // If no field map choices exist, return HTML.
2449:         if ( empty( $field_map ) ) {
2450:             return $html;
2451:         }
2452: 
2453:         // Get current form ID.
2454:         $form_id = rgget( 'id' );
2455: 
2456:         // Display field map table header.
2457:         $html .= '<table class="settings-field-map-table" cellspacing="0" cellpadding="0">' .
2458:                         $this->field_map_table_header() .
2459:                     '<tbody>';
2460: 
2461:         // Loop through field map choices.
2462:         foreach ( $field['field_map'] as $child_field ) {
2463: 
2464:             // If field map choice does not meet the dependencies required to be displayed, skip it.
2465:             if ( ! $this->setting_dependency_met( rgar( $child_field, 'dependency' ) ) ) {
2466:                 continue;
2467:             }
2468: 
2469:             // Get field map choice name, tooltip and required indicator.
2470:             $child_field['name'] = $this->get_mapped_field_name( $field, $child_field['name'] );
2471:             $tooltip             = $this->maybe_get_tooltip( $child_field );
2472:             $required            = rgar( $child_field, 'required' ) ? ' ' . $this->get_required_indicator( $child_field ) : '';
2473: 
2474:             // Display field map choice row.
2475:             $html .= '
2476:                 <tr>
2477:                     <td>
2478:                         <label for="' . $child_field['name'] . '">' . $child_field['label'] . $tooltip . $required . '</label>
2479:                     </td>
2480:                     <td>' .
2481:                      $this->settings_field_map_select( $child_field, $form_id ) .
2482:                      '</td>
2483:             </tr>';
2484: 
2485:         }
2486: 
2487:         // Close field map table.
2488:         $html .= '
2489:                 </tbody>
2490:             </table>';
2491: 
2492:         // If automatic display is enabled, echo field HTML.
2493:         if ( $echo ) {
2494:             echo $html;
2495:         }
2496: 
2497:         return $html;
2498: 
2499:     }
2500: 
2501:     /**
2502:      * Renders and initializes a dynamic field map field based on the $field array whose choices are populated by the fields to be mapped.
2503:      *
2504:      * @since  1.9.5.13
2505:      * @access public
2506:      *
2507:      * @uses GFAddOn::field_failed_validation()
2508:      * @uses GFAddOn::get_current_form()
2509:      * @uses GFAddOn::get_error_icon()
2510:      * @uses GFAddOn::get_mapping_field()
2511:      * @uses GFAddOn::settings_field_map_select()
2512:      * @uses GFAddOn::settings_hidden()
2513:      * @uses GFAddOn::settings_select()
2514:      * @uses GFAddOn::settings_text()
2515:      * @uses GFCommon::get_base_url()
2516:      *
2517:      * @param array $field Field array containing the configuration options of this field.
2518:      * @param bool  $echo  Determines if field contents should automatically be displayed. Defaults to true.
2519:      *
2520:      * @return string The HTML for the field
2521:      */
2522:     public function settings_dynamic_field_map( $field, $echo = true ) {
2523: 
2524:         // Initialize return HTML string.
2525:         $html = '';
2526: 
2527:         // Initialize field objects for child fields.
2528:         $value_field = $key_field = $custom_key_field = $field;
2529: 
2530:         // Get current form object.
2531:         $form = $this->get_current_form();
2532: 
2533:         // Change disable custom property to enable custom key property.
2534:         if ( isset( $field['disabled_custom'] ) ) {
2535:             $field['enable_custom_key'] = ! rgar( $field, 'disabled_custom' );
2536:             unset( $field['disabled_custom'] );
2537:         }
2538: 
2539:         // Define key field properties.
2540:         $key_field['name']    .= '_key';
2541:         $key_field['choices']  = isset( $field['field_map'] ) ? $field['field_map'] : null;
2542:         $key_field['class']    = 'key key_{i}';
2543: 
2544:         // Define custom key field properties.
2545:         $custom_key_field['name']  .= '_custom_key_{i}';
2546:         $custom_key_field['class']  = 'custom_key custom_key_{i}';
2547:         $custom_key_field['value']  = '{custom_key}';
2548: 
2549:         // Define custom key value.
2550:         $custom_key = 'gf_custom';
2551: 
2552:         // Define value field properties.
2553:         $value_field['name']  .= '_custom_value';
2554:         $value_field['class']  = 'value value_{i}';
2555: 
2556:         // Remove unneeded field properties.
2557:         unset( $field['field_map'], $value_field['field_map'], $key_field['field_map'], $custom_key_field['field_map'] );
2558: 
2559:         // If field failed validation, display error icon.
2560:         if ( $this->field_failed_validation( $field ) ) {
2561:             $html .= $this->get_error_icon( $field );
2562:         }
2563: 
2564:         // Display dynamic field map table.
2565:         $html .= '
2566:             <table class="settings-field-map-table" cellspacing="0" cellpadding="0">
2567:                 <tbody class="repeater">
2568:                     <tr>
2569:                         '. $this->get_mapping_field( 'key', $key_field, $custom_key_field ) .'
2570:                         <td>' .
2571:                             $this->settings_field_map_select( $value_field, $form['id'] ) . '
2572:                         </td>
2573:                         <td>
2574:                             {buttons}
2575:                         </td>
2576:                     </tr>
2577:                 </tbody>
2578:             </table>';
2579: 
2580:         // Display hidden field containing dynamic field map value.
2581:         $html .= $this->settings_hidden( $field, false );
2582: 
2583:         // Get dynamic field map limit.
2584:         $limit = empty( $field['limit'] ) ? 0 : $field['limit'];
2585: 
2586:         // Initialize dynamic field map via Javascript.
2587:         $html .= "
2588:             <script type=\"text/javascript\">
2589:                 var dynamicFieldMap". esc_attr( $field['name'] ) ." = new gfieldmap({
2590:                     'baseURL':      '". GFCommon::get_base_url() ."',
2591:                     'fieldId':      '". esc_attr( $field['name'] ) ."',
2592:                     'fieldName':    '". $field['name'] ."',
2593:                     'keyFieldName': '". $key_field['name'] ."',
2594:                     'limit':        '". $limit . "'
2595:                 });
2596:             </script>";
2597: 
2598:         // If automatic display is enabled, echo field HTML.
2599:         if ( $echo ) {
2600:             echo $html;
2601:         }
2602: 
2603:         return $html;
2604: 
2605:     }
2606: 
2607:     /**
2608:      * Renders a field select field for field maps.
2609:      *
2610:      * @since  unknown
2611:      * @access public
2612:      *
2613:      * @uses GFAddOn::get_field_map_choices()
2614:      * @uses GF_Field::get_form_editor_field_title()
2615:      *
2616:      * @param array $field    Field array containing the configuration options of this field.
2617:      * @param int   $form_id  Form ID to retrieve fields from.
2618:      *
2619:      * @return string The HTML for the field
2620:      */
2621:     public function settings_field_map_select( $field, $form_id ) {
2622: 
2623:         // Get field types to only display.
2624:         $field_type = rgempty( 'field_type', $field ) ? null : $field['field_type'];
2625: 
2626:         // Get field types to exclude.
2627:         $exclude_field_types = rgempty( 'exclude_field_types', $field ) ? null : $field['exclude_field_types'];
2628: 
2629:         // Get form field choices based on field type inclusions/exclusions.
2630:         $field['choices'] = $this->get_field_map_choices( $form_id, $field_type, $exclude_field_types );
2631: 
2632:         // If no choices were found, return error.
2633:         if ( empty( $field['choices'] ) || ( count( $field['choices'] ) == 1 && rgblank( $field['choices'][0]['value'] ) ) ) {
2634: 
2635:             if ( ( ! is_array( $field_type ) && ! rgblank( $field_type ) ) || ( is_array( $field_type ) && count( $field_type ) == 1 ) ) {
2636: 
2637:                 $type = is_array( $field_type ) ? $field_type[0] : $field_type;
2638:                 $type = ucfirst( GF_Fields::get( $type )->get_form_editor_field_title() );
2639: 
2640:                 return sprintf( __( 'Please add a %s field to your form.', 'gravityforms' ), $type );
2641: 
2642:             }
2643: 
2644:         }
2645: 
2646:         // Set default value.
2647:         $field['default_value'] = $this->get_default_field_select_field( $field );
2648: 
2649:         return $this->settings_select( $field, false );
2650: 
2651:     }
2652: 
2653:     /**
2654:      * Prepares the markup for mapping field key and value fields.
2655:      *
2656:      * @since  2.2
2657:      * @access public
2658:      *
2659:      * @uses GFAddOn::get_current_form()
2660:      * @uses GFAddOn::get_field_map_choices()
2661:      *
2662:      * @param string $type The field type being prepared; key or value.
2663:      * @param array  $select_field The drop down field properties.
2664:      * @param array  $text_field   The text field properties.
2665:      *
2666:      * @return string
2667:      */
2668:     public function get_mapping_field( $type, $select_field, $text_field ) {
2669: 
2670:         // If use form fields as choices flag is set, add as choices.
2671:         if ( isset( $select_field['choices'] ) && ! is_array( $select_field['choices'] ) && 'form_fields' === strtolower( $select_field['choices'] ) ) {
2672: 
2673:             // Set choices to form fields.
2674:             $select_field['choices'] = $this->get_field_map_choices( rgget( 'id' ) );
2675: 
2676:         }
2677: 
2678:         // If field has no choices, display custom field only.
2679:         if ( empty( $select_field['choices'] ) ) {
2680: 
2681:             // Set field value to custom key.
2682:             $select_field['value'] = 'gf_custom';
2683: 
2684:             // Display field row.
2685:             return sprintf(
2686:                 '<td>%s<div class="custom-%s-container">%s</div></td>',
2687:                 $this->settings_hidden( $select_field, false ),
2688:                 $type,
2689:                 $this->settings_text( $text_field, false )
2690:             );
2691: 
2692:         } else {
2693:             
2694:             // Set initial additional classes.
2695:             $additional_classes = array();
2696: 
2697:             // Set has custom key flag.
2698:             $has_gf_custom = false;
2699: 
2700:             // Loop through key field choices.
2701:             foreach ( $select_field['choices'] as $choice ) {
2702: 
2703:                 // If choice name or value is the custom key, set custom key flag to true and exit loop.
2704:                 if ( rgar( $choice, 'name' ) == 'gf_custom' || rgar( $choice, 'value' ) == 'gf_custom' ) {
2705:                     $has_gf_custom = true;
2706:                     break;
2707:                 }
2708: 
2709:                 // If choice has sub-choices, check for custom key option.
2710:                 if ( rgar( $choice, 'choices' ) ) {
2711: 
2712:                     // Loop through sub-choices.
2713:                     foreach ( $choice['choices'] as $subchoice ) {
2714: 
2715:                         // If sub-choice name or value is the custom key, set custom key flag to true and exit loop.
2716:                         if ( rgar( $subchoice, 'name' ) == 'gf_custom' || rgar( $subchoice, 'value' ) == 'gf_custom' ) {
2717:                             $has_gf_custom = true;
2718:                             break;
2719:                         }
2720:                     }
2721: 
2722:                 }
2723: 
2724:             }
2725: 
2726:             // If custom key option is not found and we're allowed to add it, add it.
2727:             if ( ! $has_gf_custom ) {
2728: 
2729:                 if ( $type == 'key' ) {
2730:                     
2731:                     $enable_custom = rgars( $select_field, 'key_field/custom_value' ) ? (bool) $select_field['key_field']['custom_value'] : ! (bool) rgar( $select_field, 'disable_custom' );
2732:                     $enable_custom = isset( $select_field['enable_custom_key'] ) ? (bool) $select_field['enable_custom_key'] : $enable_custom;
2733:                     $label         = esc_html__( 'Add Custom Key', 'gravityforms' );
2734:                     
2735:                 } else {
2736:                     
2737:                     // Add merge tag class.
2738:                     if ( rgars( $select_field, 'value_field/merge_tags' ) ) {
2739:                         $additional_classes[] = 'supports-merge-tags';
2740:                     }
2741:                     
2742:                     $enable_custom = rgars( $select_field, 'value_field/custom_value' ) ? (bool) $select_field['value_field']['custom_value'] : (bool) rgars( $select_field, 'enable_custom_value' );
2743:                     $label         = esc_html__( 'Add Custom Value', 'gravityforms' );
2744:                     
2745:                 }
2746: 
2747:                 if ( $enable_custom ) {
2748:                     $select_field['choices'][] = array(
2749:                         'label' => $label,
2750:                         'value' => 'gf_custom'
2751:                     );
2752:                 }
2753: 
2754:             }
2755: 
2756:             // Display field row.
2757:             return sprintf(
2758:                 '<th>%s<div class="custom-%s-container %s">%s<a href="#" class="custom-%s-reset">%s</a></div></th>',
2759:                 $this->settings_select( $select_field, false ),
2760:                 $type,
2761:                 implode( ' ', $additional_classes ),
2762:                 $this->settings_text( $text_field, false ),
2763:                 $type,
2764:                 esc_html__( 'Reset', 'gravityforms' )
2765:             );
2766: 
2767:         }
2768: 
2769:     }
2770: 
2771:     /**
2772:      * Heading row for field map table.
2773:      *
2774:      * @since  2.2
2775:      * @access public
2776:      *
2777:      * @uses GFAddOn::field_map_title()
2778:      *
2779:      * @return string
2780:      */
2781:     public function field_map_table_header() {
2782: 
2783:         return '<thead>
2784:                     <tr>
2785:                         <th>' . $this->field_map_title() . '</th>
2786:                         <th>' . esc_html__( 'Form Field', 'gravityforms' ) . '</th>
2787:                     </tr>
2788:                 </thead>';
2789: 
2790:     }
2791: 
2792:     /**
2793:      * Heading for field map field column.
2794:      *
2795:      * @since  2.2
2796:      * @access public
2797:      *
2798:      * @used-by GFAddOn::field_map_table_header()
2799:      *
2800:      * @return string
2801:      */
2802:     public function field_map_title() {
2803: 
2804:         return esc_html__( 'Field', 'gravityforms' );
2805: 
2806:     }
2807: 
2808:     /**
2809:      * Get field map choices for specific form.
2810:      *
2811:      * @since  unknown
2812:      * @access public
2813:      *
2814:      * @uses GFCommon::get_label()
2815:      * @uses GFFormsModel::get_entry_meta()
2816:      * @uses GFFormsModel::get_form_meta()
2817:      * @uses GF_Field::get_entry_inputs()
2818:      * @uses GF_Field::get_form_editor_field_title()
2819:      * @uses GF_Field::get_input_type()
2820:      *
2821:      * @param int          $form_id             Form ID to display fields for.
2822:      * @param array|string $field_type          Field types to only include as choices. Defaults to null.
2823:      * @param array|string $exclude_field_types Field types to exclude from choices. Defaults to null.
2824:      *
2825:      * @return array
2826:      */
2827:     public static function get_field_map_choices( $form_id, $field_type = null, $exclude_field_types = null ) {
2828: 
2829:         $form = GFFormsModel::get_form_meta( $form_id );
2830: 
2831:         $fields = array();
2832: 
2833:         // Setup first choice
2834:         if ( rgblank( $field_type ) || ( is_array( $field_type ) && count( $field_type ) > 1 ) ) {
2835: 
2836:             $first_choice_label = __( 'Select a Field', 'gravityforms' );
2837: 
2838:         } else {
2839: 
2840:             $type = is_array( $field_type ) ? $field_type[0] : $field_type;
2841:             $type = ucfirst( GF_Fields::get( $type )->get_form_editor_field_title() );
2842: 
2843:             $first_choice_label = sprintf( __( 'Select a %s Field', 'gravityforms' ), $type );
2844: 
2845:         }
2846: 
2847:         $fields[] = array( 'value' => '', 'label' => $first_choice_label );
2848: 
2849:         // if field types not restricted add the default fields and entry meta
2850:         if ( is_null( $field_type ) ) {
2851:             $fields[] = array( 'value' => 'id', 'label' => esc_html__( 'Entry ID', 'gravityforms' ) );
2852:             $fields[] = array( 'value' => 'date_created', 'label' => esc_html__( 'Entry Date', 'gravityforms' ) );
2853:             $fields[] = array( 'value' => 'ip', 'label' => esc_html__( 'User IP', 'gravityforms' ) );
2854:             $fields[] = array( 'value' => 'source_url', 'label' => esc_html__( 'Source Url', 'gravityforms' ) );
2855:             $fields[] = array( 'value' => 'form_title', 'label' => esc_html__( 'Form Title', 'gravityforms' ) );
2856: 
2857:             $entry_meta = GFFormsModel::get_entry_meta( $form['id'] );
2858:             foreach ( $entry_meta as $meta_key => $meta ) {
2859:                 $fields[] = array( 'value' => $meta_key, 'label' => rgars( $entry_meta, "{$meta_key}/label" ) );
2860:             }
2861:         }
2862: 
2863:         // Populate form fields
2864:         if ( is_array( $form['fields'] ) ) {
2865:             foreach ( $form['fields'] as $field ) {
2866:                 $input_type = $field->get_input_type();
2867:                 $inputs     = $field->get_entry_inputs();
2868:                 $field_is_valid_type = ( empty( $field_type ) || ( is_array( $field_type ) && in_array( $input_type, $field_type ) ) || ( ! empty( $field_type ) && $input_type == $field_type ) );
2869: 
2870:                 if ( is_null( $exclude_field_types ) ) {
2871:                     $exclude_field = false;
2872:                 } elseif ( is_array( $exclude_field_types ) ) {
2873:                     if ( in_array( $input_type, $exclude_field_types ) ) {
2874:                         $exclude_field = true;
2875:                     } else {
2876:                         $exclude_field = false;
2877:                     }
2878:                 } else {
2879:                     //not array, so should be single string
2880:                     if ( $input_type == $exclude_field_types ) {
2881:                         $exclude_field = true;
2882:                     } else {
2883:                         $exclude_field = false;
2884:                     }
2885:                 }
2886: 
2887:                 if ( is_array( $inputs ) && $field_is_valid_type && ! $exclude_field ) {
2888:                     //If this is an address field, add full name to the list
2889:                     if ( $input_type == 'address' ) {
2890:                         $fields[] = array(
2891:                             'value' => $field->id,
2892:                             'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')' )
2893:                         );
2894:                     }
2895:                     //If this is a name field, add full name to the list
2896:                     if ( $input_type == 'name' ) {
2897:                         $fields[] = array(
2898:                             'value' => $field->id,
2899:                             'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')' )
2900:                         );
2901:                     }
2902:                     //If this is a checkbox field, add to the list
2903:                     if ( $input_type == 'checkbox' ) {
2904:                         $fields[] = array(
2905:                             'value' => $field->id,
2906:                             'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Selected', 'gravityforms' ) . ')' )
2907:                         );
2908:                     }
2909: 
2910:                     foreach ( $inputs as $input ) {
2911:                         $fields[] = array(
2912:                             'value' => $input['id'],
2913:                             'label' => strip_tags( GFCommon::get_label( $field, $input['id'] ) )
2914:                         );
2915:                     }
2916:                 } elseif ( $input_type == 'list' && $field->enableColumns && $field_is_valid_type && ! $exclude_field ) {
2917:                     $fields[] = array(
2918:                         'value' => $field->id,
2919:                         'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')' )
2920:                     );
2921:                     $col_index = 0;
2922:                     foreach ( $field->choices as $column ) {
2923:                         $fields[] = array(
2924:                             'value' => $field->id . '.' . $col_index,
2925:                             'label' => strip_tags( GFCommon::get_label( $field ) . ' (' . esc_html( rgar( $column, 'text' ) ) . ')' ),
2926:                         );
2927:                         $col_index ++;
2928:                     }
2929:                 } elseif ( ! $field->displayOnly && $field_is_valid_type && ! $exclude_field ) {
2930:                     $fields[] = array( 'value' => $field->id, 'label' => strip_tags( GFCommon::get_label( $field ) ) );
2931:                 }
2932:             }
2933:         }
2934: 
2935:         /**
2936:          * Filter the choices available in the field map drop down.
2937:          *
2938:          * @since 2.0.7.11
2939:          *
2940:          * @param array             $fields              The value and label properties for each choice.
2941:          * @param int               $form_id             The ID of the form currently being configured.
2942:          * @param null|array        $field_type          Null or the field types to be included in the drop down.
2943:          * @param null|array|string $exclude_field_types Null or the field type(s) to be excluded from the drop down.
2944:          */
2945:         $fields = apply_filters( 'gform_addon_field_map_choices', $fields, $form_id, $field_type, $exclude_field_types );
2946: 
2947:         if ( function_exists( 'get_called_class' ) ) {
2948:             $callable = array( get_called_class(), 'get_instance' );
2949:             if ( is_callable( $callable ) ) {
2950:                 $add_on = call_user_func( $callable );
2951:                 $slug   = $add_on->get_slug();
2952: 
2953:                 $fields = apply_filters( "gform_{$slug}_field_map_choices", $fields, $form_id, $field_type, $exclude_field_types );
2954:             }
2955:         }
2956: 
2957:         return $fields;
2958:     }
2959: 
2960:     /**
2961:      * Get input name for field map field.
2962:      *
2963:      * @since  unknown
2964:      * @access public
2965:      *
2966:      * @used-by GFAddOn::settings_field_map()
2967:      * @used-by GFAddOn::validate_field_map_settings()
2968:      *
2969:      * @param array  $parent_field Field map field.
2970:      * @param string $field_name   Child field.
2971:      *
2972:      * @return string
2973:      */
2974:     public function get_mapped_field_name( $parent_field, $field_name ) {
2975: 
2976:         return "{$parent_field['name']}_{$field_name}";
2977: 
2978:     }
2979: 
2980:     /**
2981:      * Get mapped key/value pairs for standard field map.
2982:      *
2983:      * @since  unknown
2984:      * @access public
2985:      *
2986:      * @param array  $feed       Feed object.
2987:      * @param string $field_name Field map field name.
2988:      *
2989:      * @return array
2990:      */
2991:     public static function get_field_map_fields( $feed, $field_name ) {
2992: 
2993:         // Initialize return fields array.
2994:         $fields = array();
2995: 
2996:         // Get prefix for mapped field map keys.
2997:         $prefix = "{$field_name}_";
2998: 
2999:         // Loop through feed meta.
3000:         foreach ( $feed['meta'] as $name => $value ) {
3001: 
3002:             // If field name matches prefix, add value to return array.
3003:             if ( strpos( $name, $prefix ) === 0 ) {
3004:                 $name            = str_replace( $prefix, '', $name );
3005:                 $fields[ $name ] = $value;
3006:             }
3007: 
3008:         }
3009: 
3010:         return $fields;
3011: 
3012:     }
3013: 
3014:     /**
3015:      * Get mapped key/value pairs for dynamic field map.
3016:      *
3017:      * @since  1.9.9.9
3018:      * @access public
3019:      *
3020:      * @param array  $feed       Feed object.
3021:      * @param string $field_name Dynamic field map field name.
3022:      *
3023:      * @return array
3024:      */
3025:     public static function get_dynamic_field_map_fields( $feed, $field_name ) {
3026: 
3027:         // Initialize return fields array.
3028:         $fields = array();
3029: 
3030:         // Get dynamic field map field.
3031:         $dynamic_fields = rgars( $feed, 'meta/' . $field_name );
3032: 
3033:         // If dynamic field map field is found, loop through mapped fields and add to array.
3034:         if ( ! empty( $dynamic_fields ) ) {
3035: 
3036:             // Loop through mapped fields.
3037:             foreach ( $dynamic_fields as $dynamic_field ) {
3038: 
3039:                 // Get mapped key or replace with custom value.
3040:                 $field_key = 'gf_custom' === $dynamic_field['key'] ? $dynamic_field['custom_key'] : $dynamic_field['key'];
3041: 
3042:                 // Add mapped field to return array.
3043:                 $fields[ $field_key ] = $dynamic_field['value'];
3044: 
3045:             }
3046: 
3047:         }
3048: 
3049:         return $fields;
3050: 
3051:     }
3052: 
3053:     /**
3054:      * Get mapped key/value pairs for generic map.
3055:      *
3056:      * @since  2.2
3057:      * @access public
3058:      *
3059:      * @param array  $feed       Feed object or settings array.
3060:      * @param string $field_name Generic map field name.
3061:      * @param array  $form       Form object. Defaults to empty array.
3062:      * @param array  $entry      Entry object. Defaults to empty array.
3063:      *
3064:      * @uses GFCommon::replace_variables()
3065:      *
3066:      * @return array
3067:      */
3068:     public function get_generic_map_fields( $feed, $field_name, $form = array(), $entry = array() ) {
3069: 
3070:         // Initialize return fields array.
3071:         $fields = array();
3072: 
3073:         // Get generic map field.
3074:         $generic_fields = rgar( $feed, 'meta' ) ? rgars( $feed, 'meta/' . $field_name ) : rgar( $feed, $field_name );
3075: 
3076:         // If generic map field is found, loop through mapped fields and add to array.
3077:         if ( ! empty( $generic_fields ) ) {
3078: 
3079:             // Loop through mapped fields.
3080:             foreach ( $generic_fields as $generic_field ) {
3081: 
3082:                 // Get mapped key or replace with custom value.
3083:                 $field_key = 'gf_custom' === $generic_field['key'] ? $generic_field['custom_key'] : $generic_field['key'];
3084: 
3085:                 // Get mapped field choice or replace with custom value.
3086:                 if ( 'gf_custom' === $generic_field['value'] ) {
3087:                     
3088:                     // If form isn't set, use custom value. Otherwise, replace merge tags.
3089:                     $field_value = empty( $form ) ? $generic_field['custom_value'] : GFCommon::replace_variables( $generic_field['custom_value'], $form, $entry, false, false, false, 'text' );
3090:                 
3091:                 } else {
3092:                 
3093:                     // If form isn't set, use value. Otherwise, get field value.
3094:                     $field_value = empty( $form ) ? $generic_field['value'] : $this->get_field_value( $form, $entry, $generic_field['value'] );
3095:                 
3096:                 }
3097: 
3098:                 // Add mapped field to return array.
3099:                 $fields[ $field_key ] = $field_value;
3100: 
3101:             }
3102: 
3103:         }
3104: 
3105:         return $fields;
3106: 
3107:     }
3108: 
3109: 
3110: 
3111: 
3112: 
3113:     //------------ Field Select Field Type ------------------------
3114: 
3115:     /**
3116:      * Renders and initializes a drop down field based on the $field array whose choices are populated by the form's fields.
3117:      * 
3118:      * @param array $field - Field array containing the configuration options of this field
3119:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
3120:      *
3121:      * @return string The HTML for the field
3122:      */
3123:     public function settings_field_select( $field, $echo = true ) {
3124: 
3125:         $field = $this->prepare_field_select_field( $field );
3126: 
3127:         $html = $this->settings_select( $field, false );
3128: 
3129:         if ( $echo ) {
3130:             echo $html;
3131:         }
3132: 
3133:         return $html;
3134:     }
3135: 
3136:     public function prepare_field_select_field( $field ) {
3137:         $args = is_array( rgar( $field, 'args' ) ) ? rgar( $field, 'args' ) : array( rgar( $field, 'args' ) );
3138: 
3139:         $args = wp_parse_args(
3140:             $args, array(
3141:                 'append_choices'       => array(),
3142:                 'disable_first_choice' => false,
3143:             )
3144:         );
3145: 
3146:         $field['choices'] = array();
3147: 
3148:         if ( ! $args['disable_first_choice'] ) {
3149: 
3150:             // Setup first choice
3151:             if ( empty( $args['input_types'] ) || ( is_array( $args['input_types'] ) && count( $args['input_types'] ) > 1 ) ) {
3152: 
3153:                 $first_choice_label = __( 'Select a Field', 'gravityforms' );
3154: 
3155:             } else {
3156: 
3157:                 $type = is_array( $args['input_types'] ) ? $args['input_types'][0] : $args['input_types'];
3158:                 $type = ucfirst( GF_Fields::get( $type )->get_form_editor_field_title() );
3159: 
3160:                 $first_choice_label = sprintf( __( 'Select a %s Field', 'gravityforms' ), $type );
3161: 
3162:             }
3163: 
3164:             $field['choices'][] = array( 'value' => '', 'label' => $first_choice_label );
3165: 
3166:         }
3167: 
3168:         $field['choices'] = array_merge( $field['choices'], $this->get_form_fields_as_choices( $this->get_current_form(), $args ) );
3169: 
3170:         if ( ! empty( $args['append_choices'] ) ) {
3171:             $field['choices'] = array_merge( $field['choices'], $args['append_choices'] );
3172:         }
3173: 
3174:         // Set default value.
3175:         $field['default_value'] = $this->get_default_field_select_field( $field );
3176: 
3177:         return $field;
3178: 
3179:     }
3180:     
3181:     /**
3182:      * Returns the field to be selected by default for field select fields based on matching labels.
3183:      *
3184:      * @access public
3185:      * @param  array $field - Field array containing the configuration options of this field
3186:      *
3187:      * @return string|null
3188:      */
3189:     public function get_default_field_select_field( $field ) {
3190: 
3191:         // Prepare field name.
3192:         $field_name = str_replace( '.', '_', $field['name'] );
3193: 
3194:         // If field's value is already set, return it.
3195:         if ( $this->get_setting( $field_name ) ) {
3196:             return $this->get_setting( $field_name );
3197:         }
3198: 
3199:         // If field's default value is not an array and not empty, return it.
3200:         if ( ! rgempty( 'default_value', $field ) && ! is_array( $field['default_value'] ) ) {
3201:             return $field['default_value'];
3202:         }
3203:         
3204:         // Set default value if auto populate is not disabled.
3205:         if ( rgar( $field, 'auto_mapping' ) !== false ) {
3206: 
3207:             $field_label = rgar( $field, 'label' );
3208: 
3209:             // Initialize array to store auto-population choices.
3210:             $default_value_choices = array( $field_label );
3211: 
3212:             // Define global aliases to help with the common case mappings.
3213:             $global_aliases = array(
3214:                 __('First Name', 'gravityforms') => array( __( 'Name (First)', 'gravityforms' ) ),
3215:                 __('Last Name', 'gravityforms') => array( __( 'Name (Last)', 'gravityforms' ) ),
3216:                 __('Address', 'gravityforms') => array( __( 'Address (Street Address)', 'gravityforms' ) ),
3217:                 __('Address 2', 'gravityforms') => array( __( 'Address (Address Line 2)', 'gravityforms' ) ),
3218:                 __('City', 'gravityforms') => array( __( 'Address (City)', 'gravityforms' ) ),
3219:                 __('State', 'gravityforms') => array( __( 'Address (State / Province)', 'gravityforms' ) ),
3220:                 __('Zip', 'gravityforms') => array( __( 'Address (Zip / Postal Code)', 'gravityforms' ) ),
3221:                 __('Country', 'gravityforms') => array( __( 'Address (Country)', 'gravityforms' ) ),
3222:             );
3223: 
3224:             // If one or more global aliases are defined for this particular field label, merge them into auto-population choices.
3225:             if ( isset( $global_aliases[ $field_label ] ) ){
3226:                 $default_value_choices = array_merge( $default_value_choices, $global_aliases[ $field_label ] );
3227:             }
3228: 
3229:             // If field aliases are defined, merge them into auto-population choices.
3230:             if ( rgars( $field, 'default_value/aliases' ) ) {
3231:                 $default_value_choices = array_merge( $default_value_choices, $field['default_value']['aliases'] );
3232:             }
3233: 
3234:             // Convert all auto-population choices to lowercase.
3235:             $default_value_choices = array_map( 'strtolower', $default_value_choices );
3236:             
3237:             // Loop through fields.
3238:             foreach ( $field['choices'] as $choice ) {
3239:                 
3240:                 // If choice value is empty, skip it.
3241:                 if ( rgblank( $choice['value'] ) ) {
3242:                     continue;
3243:                 }
3244: 
3245:                 // If lowercase field label matches a default value choice, set it to the default value.
3246:                 if ( in_array( strtolower( $choice['label'] ), $default_value_choices ) ) {
3247:                     return $choice['value'];
3248:                 }
3249:                 
3250:             }
3251:             
3252:         }
3253:         
3254:         return null;    
3255:         
3256:     }
3257: 
3258:     /**
3259:      * Retrieve an array of form fields formatted for select, radio and checkbox settings fields.
3260:      * 
3261:      * @access public
3262:      * @param array $form - The form object
3263:      * @param array $args - Additional settings to check for (field and input types to include, callback for applicable input type)
3264:      *
3265:      * @return array The array of formatted form fields
3266:      */
3267:     public function get_form_fields_as_choices( $form, $args = array() ) {
3268: 
3269:         $fields = array();
3270: 
3271:         if ( ! is_array( $form['fields'] ) ) {
3272:             return $fields;
3273:         }
3274: 
3275:         $args = wp_parse_args(
3276:             $args, array(
3277:                 'field_types'    => array(),
3278:                 'input_types'    => array(),
3279:                 'callback'       => false
3280:             )
3281:         );
3282: 
3283:         foreach ( $form['fields'] as $field ) {
3284: 
3285:             if ( ! empty( $args['field_types'] ) && ! in_array( $field->type, $args['field_types'] ) ) {
3286: 
3287:                 continue;
3288: 
3289:             }
3290: 
3291:             $input_type               = GFFormsModel::get_input_type( $field );
3292:             $is_applicable_input_type = empty( $args['input_types'] ) || in_array( $input_type, $args['input_types'] );
3293: 
3294:             if ( is_callable( $args['callback'] ) ) {
3295:                 $is_applicable_input_type = call_user_func( $args['callback'], $is_applicable_input_type, $field, $form );
3296:             }
3297: 
3298:             if ( ! $is_applicable_input_type ) {
3299:                 continue;
3300:             }
3301: 
3302:             if ( ! empty( $args['property'] ) && ( ! isset( $field->{$args['property']} ) || $field->{$args['property']} != $args['property_value'] ) ) {
3303:                 continue;
3304:             }
3305: 
3306:             $inputs = $field->get_entry_inputs();
3307:             if ( is_array( $inputs ) ) {
3308:                 // if this is an address field, add full name to the list
3309:                 if ( $input_type == 'address' ) {
3310:                     $fields[] = array(
3311:                         'value' => $field->id,
3312:                         'label' => GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')'
3313:                     );
3314:                 }
3315:                 // if this is a name field, add full name to the list
3316:                 if ( $input_type == 'name' ) {
3317:                     $fields[] = array(
3318:                         'value' => $field->id,
3319:                         'label' => GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')'
3320:                     );
3321:                 }
3322:                 // if this is a checkbox field, add to the list
3323:                 if ( $input_type == 'checkbox' ) {
3324:                     $fields[] = array(
3325:                         'value' => $field->id,
3326:                         'label' => GFCommon::get_label( $field ) . ' (' . esc_html__( 'Selected', 'gravityforms' ) . ')'
3327:                     );
3328:                 }
3329: 
3330:                 foreach ( $inputs as $input ) {
3331:                     $fields[] = array(
3332:                         'value' => $input['id'],
3333:                         'label' => GFCommon::get_label( $field, $input['id'] )
3334:                     );
3335:                 }
3336:             } elseif ( $input_type == 'list' && $field->enableColumns ) {
3337:                 $fields[] = array(
3338:                     'value' => $field->id,
3339:                     'label' => GFCommon::get_label( $field ) . ' (' . esc_html__( 'Full', 'gravityforms' ) . ')'
3340:                 );
3341:                 $col_index = 0;
3342:                 foreach ( $field->choices as $column ) {
3343:                     $fields[] = array(
3344:                         'value' => $field->id . '.' . $col_index,
3345:                         'label' => GFCommon::get_label( $field ) . ' (' . rgar( $column, 'text' ) . ')',
3346:                     );
3347:                     $col_index ++;
3348:                 }
3349:             } elseif ( ! $field->displayOnly ) {
3350:                 $fields[] = array( 'value' => $field->id, 'label' => GFCommon::get_label( $field ) );
3351:             } else {
3352:                 $fields[] = array(
3353:                     'value' => $field->id,
3354:                     'label' => GFCommon::get_label( $field )
3355:                 );
3356:             }
3357:         }
3358: 
3359:         return $fields;
3360:     }
3361: 
3362:     /**
3363:      * Renders and initializes a checkbox field that displays a select field when checked based on the $field array.
3364:      * 
3365:      * @access public
3366:      * @param array $field - Field array containing the configuration options of this field
3367:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
3368:      *
3369:      * @return string The HTML for the field
3370:      */
3371:     public function settings_checkbox_and_select( $field, $echo = true ) {
3372: 
3373:         $field = $this->prepare_settings_checkbox_and_select( $field );
3374: 
3375:         $checkbox_field = $field['checkbox'];
3376:         $select_field = $field['select'];
3377: 
3378:         $is_enabled = $this->get_setting( $checkbox_field['name'] );
3379: 
3380:         // get markup
3381: 
3382:         $html = sprintf(
3383:             '%s <span id="%s" class="%s">%s %s</span>',
3384:             $this->settings_checkbox( $checkbox_field, false ),
3385:             $select_field['name'] . 'Span',
3386:             $is_enabled ? '' : 'gf_invisible',
3387:             $this->settings_select( $select_field, false ),
3388:             $this->maybe_get_tooltip( $select_field )
3389:         );
3390: 
3391:         if ( $echo ) {
3392:             echo $html;
3393:         }
3394: 
3395:         return $html;
3396:     }
3397: 
3398:     public function prepare_settings_checkbox_and_select( $field ) {
3399: 
3400:         // prepare checkbox
3401: 
3402:         $checkbox_input = rgars( $field, 'checkbox' );
3403: 
3404:         $checkbox_field = array(
3405:             'type'       => 'checkbox',
3406:             'name'       => $field['name'] . 'Enable',
3407:             'label'      => esc_html__( 'Enable', 'gravityforms' ),
3408:             'horizontal' => true,
3409:             'value'      => '1',
3410:             'choices'    => false,
3411:             'tooltip'    => false
3412:         );
3413: 
3414:         $checkbox_field = wp_parse_args( $checkbox_input, $checkbox_field );
3415: 
3416:         // prepare select
3417: 
3418:         $select_input = rgars( $field, 'select' );
3419: 
3420:         $select_field = array(
3421:             'name'    => $field['name'] . 'Value',
3422:             'type'    => 'select',
3423:             'class'   => '',
3424:             'tooltip' => false
3425:         );
3426: 
3427:         $select_field['class'] .= ' ' . $select_field['name'];
3428: 
3429:         $select_field = wp_parse_args( $select_input, $select_field );
3430: 
3431:         // a little more with the checkbox
3432:         if( empty( $checkbox_field['choices'] ) ) {
3433:             $checkbox_field['choices'] = array(
3434:                 array(
3435:                     'name'          => $checkbox_field['name'],
3436:                     'label'         => $checkbox_field['label'],
3437:                     'onchange'      => sprintf( "( function( $, elem ) {
3438:                         $( elem ).parents( 'td' ).css( 'position', 'relative' );
3439:                         if( $( elem ).prop( 'checked' ) ) {
3440:                             $( '%1\$s' ).css( 'visibility', 'visible' );
3441:                             $( '%1\$s' ).fadeTo( 400, 1 );
3442:                         } else {
3443:                             $( '%1\$s' ).fadeTo( 400, 0, function(){
3444:                                 $( '%1\$s' ).css( 'visibility', 'hidden' );   
3445:                             } );
3446:                         }
3447:                     } )( jQuery, this );",
3448:                         "#{$select_field['name']}Span" )
3449:                 )
3450:             );
3451:         }
3452: 
3453:         $field['select'] = $select_field;
3454:         $field['checkbox'] = $checkbox_field;
3455: 
3456:         return $field;
3457:     }
3458: 
3459:     /***
3460:      * Renders the save button for settings pages
3461:      *
3462:      * @param array $field - Field array containing the configuration options of this field
3463:      * @param bool  $echo  = true - true to echo the output to the screen, false to simply return the contents as a string
3464:      *
3465:      * @return string The HTML
3466:      */
3467:     public function settings_save( $field, $echo = true ) {
3468: 
3469:         $field['type']  = 'submit';
3470:         $field['name']  = 'gform-settings-save';
3471:         $field['class'] = 'button-primary gfbutton';
3472: 
3473:         if ( ! rgar( $field, 'value' ) ) {
3474:             $field['value'] = esc_html__( 'Update Settings', 'gravityforms' );
3475:         }
3476: 
3477:         $attributes = $this->get_field_attributes( $field );
3478: 
3479:         $html = '<input
3480:                     type="' . esc_attr( $field['type'] ) . '"
3481:                     name="' . esc_attr( $field['name'] ) . '"
3482:                     value="' . esc_attr( $field['value'] ) . '" ' . implode( ' ', $attributes ) . ' />';
3483: 
3484:         if ( $echo ) {
3485:             echo $html;
3486:         }
3487: 
3488:         return $html;
3489:     }
3490: 
3491:     /**
3492:      * Parses the properties of the $field meta array and returns a set of HTML attributes to be added to the HTML element.
3493:      *
3494:      * @param array $field   - current field meta to be parsed.
3495:      * @param array $default - default set of properties. Will be appended to the properties specified in the $field array
3496:      *
3497:      * @return array - resulting HTML attributes ready to be included in the HTML element.
3498:      */
3499:     public function get_field_attributes( $field, $default = array() ) {
3500: 
3501:         /**
3502:          * Each nonstandard property will be extracted from the $props array so it is not auto-output in the field HTML
3503:          *
3504:          * @param array $field The current field meta to be parsed
3505:          */
3506:         $no_output_props = apply_filters(
3507:             'gaddon_no_output_field_properties',
3508:             array(
3509:                 'default_value', 'label', 'choices', 'feedback_callback', 'checked', 'checkbox_label', 'value', 'type',
3510:                 'validation_callback', 'required', 'hidden', 'tooltip', 'dependency', 'messages', 'name', 'args',
3511:                 'exclude_field_types', 'field_type', 'after_input', 'input_type', 'icon', 'save_callback',
3512:                 'enable_custom_value', 'enable_custom_key', 'merge_tags', 'key_field', 'value_field', 'callback',
3513:             ), $field
3514:         );
3515: 
3516:         $default_props = array(
3517:             'class'         => '', // will default to gaddon-setting
3518:             'default_value' => '', // default value that should be selected or entered for the field
3519:         );
3520: 
3521:         // Property switch case
3522:         switch ( $field['type'] ) {
3523:             case 'select':
3524:                 $default_props['choices'] = array();
3525:                 break;
3526:             case 'checkbox':
3527:                 $default_props['checked']        = false;
3528:                 $default_props['checkbox_label'] = '';
3529:                 $default_props['choices']        = array();
3530:                 break;
3531:             case 'text':
3532:             default:
3533:                 break;
3534:         }
3535: 
3536:         $props          = wp_parse_args( $field, $default_props );
3537:         $props['id']    = rgempty( 'id', $props ) ? rgar( $props, 'name' ) : rgar( $props, 'id' );
3538:         $props['class'] = trim( "{$props['class']} gaddon-setting gaddon-{$props['type']}" );
3539: 
3540:         // extract no-output properties from $props array so they are not auto-output in the field HTML
3541:         foreach ( $no_output_props as $prop ) {
3542:             if ( isset( $props[ $prop ] ) ) {
3543:                 ${$prop} = $props[ $prop ];
3544:                 unset( $props[ $prop ] );
3545:             }
3546:         }
3547: 
3548:         //adding default attributes
3549:         foreach ( $default as $key => $value ) {
3550:             if ( isset( $props[ $key ] ) ) {
3551:                 $props[ $key ] = $value . $props[ $key ];
3552:             } else {
3553:                 $props[ $key ] = $value;
3554:             }
3555:         }
3556: 
3557:         // get an array of property strings, example: name='myFieldName'
3558:         $prop_strings = array();
3559:         foreach ( $props as $prop => $value ) {
3560:             $prop_strings[ $prop ] = "{$prop}='" . esc_attr( $value ) . "'";
3561:         }
3562: 
3563:         return $prop_strings;
3564:     }
3565: 
3566:     /**
3567:      * Parses the properties of the $choice meta array and returns a set of HTML attributes to be added to the HTML element.
3568:      *
3569:      * @param array $choice           - current choice meta to be parsed.
3570:      * @param array $field_attributes - current field's attributes.
3571:      *
3572:      * @return array - resulting HTML attributes ready to be included in the HTML element.
3573:      */
3574:     public function get_choice_attributes( $choice, $field_attributes, $default_choice_attributes = array() ) {
3575:         $choice_attributes = $field_attributes;
3576:         foreach ( $choice as $prop => $val ) {
3577:             $no_output_choice_attributes = array(
3578:                 'default_value', 'label', 'checked', 'value', 'type',
3579:                 'validation_callback', 'required', 'tooltip',
3580:             );
3581:             if ( in_array( $prop, $no_output_choice_attributes ) || is_array( $val ) ) {
3582:                 unset( $choice_attributes[ $prop ] );
3583:             } else {
3584:                 $choice_attributes[ $prop ] = "{$prop}='" . esc_attr( $val ) . "'";
3585:             }
3586:         }
3587: 
3588:         //Adding default attributes. Either creating a new attribute or pre-pending to an existing one.
3589:         foreach ( $default_choice_attributes as $default_attr_name => $default_attr_value ) {
3590: 
3591:             if ( isset( $choice_attributes[ $default_attr_name ] ) ) {
3592:                 $choice_attributes[ $default_attr_name ] = $this->prepend_attribute( $default_attr_name, $default_attr_value, $choice_attributes[ $default_attr_name ] );
3593:             }
3594:             else {
3595:                 $choice_attributes[ $default_attr_name ] = "{$default_attr_name}='" . esc_attr( $default_attr_value ) . "'";
3596:             }
3597:         }
3598: 
3599:         return $choice_attributes;
3600:     }
3601: 
3602:     /***
3603:      * @param $name - The name of the attribute to be added
3604:      * @param $attribute - The attribute value to be added
3605:      * @param $current_attribute - The full string containing the current attribute value
3606:      * @return mixed - The new attribute string with the new value added to the beginning of the list
3607:      */
3608:     public function prepend_attribute( $name, $attribute, $current_attribute ) {
3609:         return str_replace( "{$name}='", "{$name}='{$attribute}", $current_attribute );
3610:     }
3611: 
3612:     /**
3613:      * Validates settings fields.
3614:      * Validates that all fields are valid. Fields can be invalid when they are blank and marked as required or if it fails a custom validation check.
3615:      * To specify a custom validation, use the 'validation_callback' field meta property and implement the validation function with the custom logic.
3616:      *
3617:      * @param $fields   - A list of all fields from the field meta configuration
3618:      * @param $settings - A list of submitted settings values
3619:      *
3620:      * @return bool - Returns true if all fields have passed validation, and false otherwise.
3621:      */
3622:     public function validate_settings( $fields, $settings ) {
3623: 
3624:         foreach ( $fields as $section ) {
3625: 
3626:             if ( ! $this->setting_dependency_met( rgar( $section, 'dependency' ) ) ) {
3627:                 continue;
3628:             }
3629: 
3630:             foreach ( $section['fields'] as $field ) {
3631: 
3632:                 if ( ! $this->setting_dependency_met( rgar( $field, 'dependency' ) ) ) {
3633:                     continue;
3634:                 }
3635: 
3636:                 $field_setting = rgar( $settings, rgar( $field, 'name' ) );
3637: 
3638:                 if ( is_callable( rgar( $field, 'validation_callback' ) ) ) {
3639:                     call_user_func( rgar( $field, 'validation_callback' ), $field, $field_setting );
3640:                     continue;
3641:                 }
3642: 
3643:                 if ( is_callable( array( $this, 'validate_' . $field['type'] . '_settings' ) ) ) {
3644:                     call_user_func( array( $this, 'validate_' . $field['type'] . '_settings' ), $field, $settings );
3645:                     continue;
3646:                 }
3647: 
3648:                 if ( rgar( $field, 'required' ) && rgblank( $field_setting ) ) {
3649:                     $this->set_field_error( $field, rgar( $field, 'error_message' ) );
3650:                 }
3651:             }
3652:         }
3653: 
3654:         $field_errors = $this->get_field_errors();
3655:         $is_valid     = empty( $field_errors );
3656: 
3657:         return $is_valid;
3658:     }
3659: 
3660:     public function validate_text_settings( $field, $settings ) {
3661:         $field_setting = rgar( $settings, rgar( $field, 'name' ) );
3662: 
3663:         if ( rgar( $field, 'required' ) && rgblank( $field_setting ) ) {
3664:             $this->set_field_error( $field, rgar( $field, 'error_message' ) );
3665:         }
3666: 
3667:         $field_setting_safe = sanitize_text_field( $field_setting );
3668: 
3669:         if ( $field_setting !== $field_setting_safe ) {
3670:             $message = esc_html__( 'The text you have entered is not valid. For security reasons, some characters are not allowed. ', 'gravityforms' );
3671:             $script = sprintf( 'jQuery("input[name=\"_gaddon_setting_%s\"]").val(jQuery(this).data("safe"));', $field['name'] );
3672:             $double_encoded_safe_value = htmlspecialchars( htmlspecialchars( $field_setting_safe, ENT_QUOTES ), ENT_QUOTES );
3673:             $message .= sprintf( " <a href='javascript:void(0);' onclick='%s' data-safe='%s'>%s</a>", htmlspecialchars( $script, ENT_QUOTES ), $double_encoded_safe_value, esc_html__('Fix it', 'gravityforms' ) );
3674:             $this->set_field_error( $field, $message );
3675:         }
3676: 
3677:     }
3678: 
3679:     public function validate_textarea_settings( $field, $settings ) {
3680:         $field_setting = rgar( $settings, rgar( $field, 'name' ) );
3681: 
3682:         if ( rgar( $field, 'required' ) && rgblank( $field_setting ) ) {
3683:             $this->set_field_error( $field, rgar( $field, 'error_message' ) );
3684:         }
3685: 
3686:         $field_setting_safe = $this->maybe_wp_kses( $field_setting );
3687: 
3688:         if ( $field_setting !== $field_setting_safe ) {
3689:             $message = esc_html__( 'The text you have entered is not valid. For security reasons, some characters are not allowed. ', 'gravityforms' );
3690:             $script = sprintf( 'jQuery("textarea[name=\"_gaddon_setting_%s\"]").val(jQuery(this).data("safe"));', $field['name'] );
3691:             $double_encoded_safe_value = htmlspecialchars( htmlspecialchars( $field_setting_safe, ENT_QUOTES ), ENT_QUOTES );
3692:             $message .= sprintf( " <a href='javascript:void(0);' onclick='%s' data-safe='%s'>%s</a>", htmlspecialchars( $script, ENT_QUOTES ), $double_encoded_safe_value, esc_html__('Fix it', 'gravityforms' ) );
3693:             $this->set_field_error( $field, $message );
3694:         }
3695:     }
3696: 
3697:     public function validate_radio_settings( $field, $settings ) {
3698:         $field_setting = rgar( $settings, rgar( $field, 'name' ) );
3699: 
3700:         if ( rgar( $field, 'required' ) && rgblank( $field_setting ) ) {
3701:             $this->set_field_error( $field, rgar( $field, 'error_message' ) );
3702:             return;
3703:         }
3704: 
3705:         if ( rgblank( $field_setting ) ){
3706:             return; //Nothing is selected. Let it pass validation
3707:         }
3708: 
3709:         foreach( $field['choices'] as $choice ) {
3710:             if ( $this->is_choice_valid( $choice, $field_setting ) ) {
3711:                 return; // Choice is valid
3712:             }
3713:         }
3714:         $this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
3715:     }
3716: 
3717:     public function validate_select_settings( $field, $settings ) {
3718:         $field_name = str_replace( '[]', '', $field['name'] );
3719:         $field_setting = rgar( $settings, $field_name );
3720: 
3721:         $multiple = rgar( $field, 'multiple' ) == 'multiple';
3722:         $required =  rgar( $field, 'required' );
3723: 
3724:         if ( ! $multiple && $required && rgblank( $field_setting ) ) {
3725:             $this->set_field_error( $field, rgar( $field, 'error_message' ) );
3726:             return;
3727:         }
3728: 
3729:         if ( rgblank( $field_setting ) ) {
3730:             return;
3731:         }
3732: 
3733:         if ( $multiple ) {
3734:             $selected = 0;
3735:             foreach( $field['choices'] as $choice ) {
3736:                 if ( isset( $choice['choices'] ) ) {
3737:                     foreach( $choice['choices'] as $optgroup_choice ) {
3738:                         if ( $this->is_choice_valid( $optgroup_choice, $field_setting ) ) {
3739:                             $selected++;
3740:                         }
3741:                     }
3742:                 } else {
3743:                     if ( $this->is_choice_valid( $choice, $field_setting ) ) {
3744:                         $selected++;
3745:                     }
3746:                 }
3747:             }
3748: 
3749:             if ( $required && $selected == 0 ) {
3750:                 $this->set_field_error( $field, rgar( $field, 'error_message' ) );
3751:                 return;
3752:             }
3753: 
3754:             if ( ! $required && $selected !== count( $field_setting ) ) {
3755:                 $this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
3756:             }
3757:         } else {
3758:             foreach( $field['choices'] as $choice ) {
3759:                 if ( isset( $choice['choices'] ) ) {
3760:                     foreach( $choice['choices'] as $optgroup_choice ) {
3761:                         if ( $this->is_choice_valid( $optgroup_choice, $field_setting ) ) {
3762:                             return;
3763:                         }
3764:                     }
3765:                 } else {
3766:                     if ( $this->is_choice_valid( $choice, $field_setting ) ) {
3767:                         return; // Choice is valid
3768:                     }
3769:                 }
3770:             }
3771:             $this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
3772:         }
3773: 
3774:     }
3775: 
3776:     public function validate_checkbox_settings( $field, $settings ) {
3777: 
3778:         if ( ! is_array( rgar( $field, 'choices' ) ) ) {
3779:             return;
3780:         }
3781: 
3782:         $selected = 0;
3783: 
3784:         foreach ( $field['choices'] as $choice ) {
3785:             $value = $this->get_setting( $choice['name'], '', $settings );
3786:             if ( ! in_array( $value, array( '1', '0' ) ) ) {
3787:                 $this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
3788:                 return;
3789:             }
3790: 
3791:             if ( $value === '1' ) {
3792:                 $selected++;
3793:             }
3794:         }
3795: 
3796: 
3797:         if ( rgar( $field, 'required' ) && $selected < 1 ) {
3798:             $this->set_field_error( $field, rgar( $field, 'error_message' ) );
3799:         }
3800:     }
3801: 
3802:     public function validate_select_custom_settings( $field, $settings ) {
3803: 
3804:         if ( ! is_array( rgar( $field, 'choices' ) ) ) {
3805:             return;
3806:         }
3807:         
3808:         $select_value = rgar( $settings, $field['name'] );
3809:         $custom_value = rgar( $settings, $field['name'] . '_custom' );
3810: 
3811:         if ( rgar( $field, 'required' ) && rgblank( $select_value ) ) {
3812:             $this->set_field_error( $field );
3813:             return;
3814:         }
3815: 
3816:         if ( rgar( $field, 'required' ) && $select_value == 'gf_custom' && rgblank( $custom_value ) ) {
3817:             $custom_field          = $field;
3818:             $custom_field['name'] .= '_custom';
3819:             $this->set_field_error( $custom_field );
3820:             return;
3821:         }
3822: 
3823:         if ( $select_value != 'gf_custom' ) {
3824:             foreach( $field['choices'] as $choice ) {
3825:                 if ( isset( $choice['choices'] ) ) {
3826:                     foreach ( $choice['choices'] as $optgroup_choice ) {
3827:                         if ( $this->is_choice_valid( $optgroup_choice, $select_value ) ) {
3828:                             return;
3829:                         }
3830:                     }
3831:                 } else {
3832:                     if ( $this->is_choice_valid( $choice, $select_value ) ) {
3833:                         return;
3834:                     }
3835:                 }
3836:             }
3837:             $this->set_field_error( $field, esc_html__( 'Invalid value', 'gravityforms' ) );
3838:         }
3839:     }
3840: 
3841:     public function validate_field_select_settings( $field, $settings ) {
3842:         $field = $this->prepare_field_select_field( $field );
3843:         $this->validate_select_settings( $field, $settings );
3844:     }
3845: 
3846:     public function validate_field_map_settings( $field, $settings ) {
3847: 
3848:         $field_map = rgar( $field, 'field_map' );
3849: 
3850:         if ( empty( $field_map ) ) {
3851:             return;
3852:         }
3853: 
3854:         foreach ( $field_map as $child_field ) {
3855: 
3856:             if ( ! $this->setting_dependency_met( rgar( $child_field, 'dependency' ) ) ) {
3857:                 continue;
3858:             }
3859: 
3860:             $child_field['name'] = $this->get_mapped_field_name( $field, $child_field['name'] );
3861:             $setting_value       = rgar( $settings, $child_field['name'] );
3862: 
3863:             if ( rgar( $child_field, 'required' ) && rgblank( $setting_value ) ) {
3864:                 $this->set_field_error( $child_field );
3865:             } elseif ( rgar( $child_field, 'validation_callback' ) && is_callable( rgar( $child_field, 'validation_callback' ) ) ) {
3866:                 call_user_func( rgar( $child_field, 'validation_callback' ), $child_field, $field );
3867:             }
3868:         }
3869: 
3870:     }
3871: 
3872:     public function validate_checkbox_and_select_settings( $field, $settings ) {
3873:         $field = $this->prepare_settings_checkbox_and_select( $field );
3874: 
3875:         $checkbox_field = $field['checkbox'];
3876:         $select_field = $field['select'];
3877: 
3878:         $this->validate_checkbox_settings( $checkbox_field, $settings );
3879:         $this->validate_select_settings( $select_field, $settings );
3880:     }
3881: 
3882:     /**
3883:      * Helper to determine if the current choice is a match for the submitted field value.
3884:      *
3885:      * @param array $choice The choice properties.
3886:      * @param string|array $value The submitted field value.
3887:      *
3888:      * @return bool
3889:      */
3890:     public function is_choice_valid( $choice, $value ) {
3891:         $choice_value = isset( $choice['value'] ) ? $choice['value'] : $choice['label'];
3892: 
3893:         return is_array( $value ) ? in_array( $choice_value, $value ) : $choice_value == $value;
3894:     }
3895: 
3896:     /**
3897:      * Sets the validation error message
3898:      * Sets the error message to be displayed when a field fails validation.
3899:      * When implementing a custom validation callback function, use this function to specify the error message to be displayed.
3900:      *
3901:      * @param array  $field         - The current field meta
3902:      * @param string $error_message - The error message to be displayed
3903:      */
3904:     public function set_field_error( $field, $error_message = '' ) {
3905: 
3906:         // set default error message if none passed
3907:         if ( ! $error_message ) {
3908:             $error_message = esc_html__( 'This field is required.', 'gravityforms' );
3909:         }
3910: 
3911:         $this->_setting_field_errors[ $field['name'] ] = $error_message;
3912:     }
3913: 
3914:     /**
3915:      * Gets the validation errors for a field.
3916:      * Returns validation errors associated with the specified field or a list of all validation messages (if a field isn't specified)
3917:      *
3918:      * @param array|boolean $field - Optional. The field meta. When specified, errors for this field will be returned
3919:      *
3920:      * @return mixed - If a field is specified, a string containing the error message will be returned. Otherwise, an array of all errors will be returned
3921:      */
3922:     public function get_field_errors( $field = false ) {
3923: 
3924:         if ( ! $field ) {
3925:             return $this->_setting_field_errors;
3926:         }
3927: 
3928:         return isset( $this->_setting_field_errors[ $field['name'] ] ) ? $this->_setting_field_errors[ $field['name'] ] : array();
3929:     }
3930: 
3931:     /**
3932:      * Gets the invalid field icon
3933:      * Returns the markup for an alert icon to indicate and highlight invalid fields.
3934:      *
3935:      * @param array $field - The field meta.
3936:      *
3937:      * @return string - The full markup for the icon
3938:      */
3939:     public function get_error_icon( $field ) {
3940: 
3941:         $error = $this->get_field_errors( $field );
3942: 
3943:         return '<span
3944:             class="gf_tooltip tooltip"
3945:             title="<h6>' . esc_html__( 'Validation Error', 'gravityforms' ) . '</h6>' . $error . '"
3946:             style="display:inline-block;position:relative;right:-3px;top:1px;font-size:14px;">
3947:                 <i class="fa fa-exclamation-circle icon-exclamation-sign gf_invalid"></i>
3948:             </span>';
3949:     }
3950: 
3951:     /**
3952:      * Returns the tooltip markup if a tooltip is configured for the supplied item (field/child field/choice).
3953:      *
3954:      * @param array $item The item properties.
3955:      *
3956:      * @return string
3957:      */
3958:     public function maybe_get_tooltip( $item ) {
3959:         $html = '';
3960: 
3961:         if ( isset( $item['tooltip'] ) ) {
3962:             $html = ' ' . gform_tooltip( $item['tooltip'], rgar( $item, 'tooltip_class' ), true );
3963:         }
3964: 
3965:         return $html;
3966:     }
3967: 
3968:     /**
3969:      * Gets the required indicator
3970:      * Gets the markup of the required indicator symbol to highlight fields that are required
3971:      *
3972:      * @param $field - The field meta.
3973:      *
3974:      * @return string - Returns markup of the required indicator symbol
3975:      */
3976:     public function get_required_indicator( $field ) {
3977:         return '<span class="required">*</span>';
3978:     }
3979: 
3980:     /**
3981:      * Checks if the specified field failed validation
3982:      *
3983:      * @param $field - The field meta to be checked
3984:      *
3985:      * @return bool|mixed - Returns a validation error string if the field has failed validation. Otherwise returns false
3986:      */
3987:     public function field_failed_validation( $field ) {
3988:         $field_error = $this->get_field_errors( $field );
3989: 
3990:         return ! empty( $field_error ) ? $field_error : false;
3991:     }
3992: 
3993:     /**
3994:      * Filter settings fields.
3995:      * Runs through each field and applies the 'save_callback', if set, before saving the settings.
3996:      * To specify a custom save filter, use the 'save_callback' field meta property and implement the save filter function with the custom logic.
3997:      *
3998:      * @param $fields   - A list of all fields from the field meta configuration
3999:      * @param $settings - A list of submitted settings values
4000:      *
4001:      * @return $settings - The updated settings values.
4002:      */
4003:     public function filter_settings( $fields, $settings ) {
4004: 
4005:         foreach ( $fields as $section ) {
4006: 
4007:             if ( ! $this->setting_dependency_met( rgar( $section, 'dependency' ) ) ) {
4008:                 continue;
4009:             }
4010: 
4011:             foreach ( $section['fields'] as $field ) {
4012: 
4013:                 if ( ! $this->setting_dependency_met( rgar( $field, 'dependency' ) ) ) {
4014:                     continue;
4015:                 }
4016: 
4017:                 $field_setting = rgar( $settings, rgar( $field, 'name' ) );
4018: 
4019:                 if ( is_callable( rgar( $field, 'save_callback' ) ) ) {
4020:                     $settings[ $field['name'] ] = call_user_func( rgar( $field, 'save_callback' ), $field, $field_setting );
4021:                     continue;
4022:                 }
4023: 
4024:             }
4025:         }
4026: 
4027:         return $settings;
4028:     }
4029: 
4030:     public function add_field_before( $name, $fields, $settings ) {
4031:         return $this->add_field( $name, $fields, $settings, 'before' );
4032:     }
4033: 
4034:     public function add_field_after( $name, $fields, $settings ) {
4035:         return $this->add_field( $name, $fields, $settings, 'after' );
4036:     }
4037: 
4038:     public function add_field( $name, $fields, $settings, $pos ) {
4039: 
4040:         if ( rgar( $fields, 'name' ) ) {
4041:             $fields = array( $fields );
4042:         }
4043: 
4044:         $pos_mod = $pos == 'before' ? 0 : 1;
4045: 
4046:         foreach ( $settings as &$section ) {
4047:             for ( $i = 0; $i < count( $section['fields'] ); $i ++ ) {
4048:                 if ( $section['fields'][ $i ]['name'] == $name ) {
4049:                     array_splice( $section['fields'], $i + $pos_mod, 0, $fields );
4050:                     break 2;
4051:                 }
4052:             }
4053:         }
4054: 
4055:         return $settings;
4056:     }
4057: 
4058:     public function remove_field( $name, $settings ) {
4059: 
4060:         foreach ( $settings as &$section ) {
4061:             for ( $i = 0; $i < count( $section['fields'] ); $i ++ ) {
4062:                 if ( $section['fields'][ $i ]['name'] == $name ) {
4063:                     array_splice( $section['fields'], $i, 1 );
4064:                     break 2;
4065:                 }
4066:             }
4067:         }
4068: 
4069:         return $settings;
4070:     }
4071: 
4072:     public function replace_field( $name, $fields, $settings ) {
4073: 
4074:         if ( rgar( $fields, 'name' ) ) {
4075:             $fields = array( $fields );
4076:         }
4077: 
4078:         foreach ( $settings as &$section ) {
4079:             for ( $i = 0; $i < count( $section['fields'] ); $i ++ ) {
4080:                 if ( $section['fields'][ $i ]['name'] == $name ) {
4081:                     array_splice( $section['fields'], $i, 1, $fields );
4082:                     break 2;
4083:                 }
4084:             }
4085:         }
4086: 
4087:         return $settings;
4088: 
4089:     }
4090: 
4091:     public function get_field( $name, $settings ) {
4092:         foreach ( $settings as $section ) {
4093:             for ( $i = 0; $i < count( $section['fields'] ); $i ++ ) {
4094:                 if ( $section['fields'][ $i ]['name'] == $name ) {
4095:                     return $section['fields'][ $i ];
4096:                 }
4097:             }
4098:         }
4099: 
4100:         return false;
4101:     }
4102: 
4103:     public function build_choices( $key_value_pairs ) {
4104: 
4105:         $choices = array();
4106: 
4107:         if ( ! is_array( $key_value_pairs ) ) {
4108:             return $choices;
4109:         }
4110: 
4111:         $first_key  = key( $key_value_pairs );
4112:         $is_numeric = is_int( $first_key ) && $first_key === 0;
4113: 
4114:         foreach ( $key_value_pairs as $value => $label ) {
4115:             if ( $is_numeric ) {
4116:                 $value = $label;
4117:             }
4118:             $choices[] = array( 'value' => $value, 'label' => $label );
4119:         }
4120: 
4121:         return $choices;
4122:     }
4123: 
4124:     //--------------  Simple Condition  ------------------------------------------------
4125: 
4126:     /**
4127:      * Helper to create a simple conditional logic set of fields. It creates one row of conditional logic with Field/Operator/Value inputs.
4128:      *
4129:      * @param mixed $setting_name_root - The root name to be used for inputs. It will be used as a prefix to the inputs that make up the conditional logic fields.
4130:      *
4131:      * @return string The HTML
4132:      */
4133:     public function simple_condition( $setting_name_root ) {
4134: 
4135:         $conditional_fields = $this->get_conditional_logic_fields();
4136: 
4137:         $value_input = esc_js( '_gaddon_setting_' . esc_attr( $setting_name_root ) . '_value' );
4138:         $object_type = esc_js( "simple_condition_{$setting_name_root}" );
4139: 
4140:         $str = $this->settings_select( array(
4141:             'name'     => "{$setting_name_root}_field_id",
4142:             'type'     => 'select',
4143:             'choices'  => $conditional_fields,
4144:             'class'    => 'optin_select',
4145:             'onchange' => "jQuery('#" . esc_js( $setting_name_root ) . "_container').html(GetRuleValues('{$object_type}', 0, jQuery(this).val(), '', '{$value_input}'));"
4146:         ), false );
4147: 
4148:         $str .= $this->settings_select( array(
4149:             'name'     => "{$setting_name_root}_operator",
4150:             'type'     => 'select',
4151:             'onchange' => "SetRuleProperty('{$object_type}', 0, 'operator', jQuery(this).val()); jQuery('#" . esc_js( $setting_name_root ) . "_container').html(GetRuleValues('{$object_type}', 0, jQuery('#{$setting_name_root}_field_id').val(), '', '{$value_input}'));",
4152:             'choices'  => array(
4153:                 array(
4154:                     'value' => 'is',
4155:                     'label' => esc_html__( 'is', 'gravityforms' ),
4156:                 ),
4157:                 array(
4158:                     'value' => 'isnot',
4159:                     'label' => esc_html__( 'is not', 'gravityforms' ),
4160:                 ),
4161:                 array(
4162:                     'value' => '>',
4163:                     'label' => esc_html__( 'greater than', 'gravityforms' ),
4164:                 ),
4165:                 array(
4166:                     'value' => '<',
4167:                     'label' => esc_html__( 'less than', 'gravityforms' ),
4168:                 ),
4169:                 array(
4170:                     'value' => 'contains',
4171:                     'label' => esc_html__( 'contains', 'gravityforms' ),
4172:                 ),
4173:                 array(
4174:                     'value' => 'starts_with',
4175:                     'label' => esc_html__( 'starts with', 'gravityforms' ),
4176:                 ),
4177:                 array(
4178:                     'value' => 'ends_with',
4179:                     'label' => esc_html__( 'ends with', 'gravityforms' ),
4180:                 ),
4181:             ),
4182: 
4183:         ), false );
4184: 
4185:         $str .= sprintf( "<span id='%s_container'></span>", esc_attr( $setting_name_root ) );
4186: 
4187:         $field_id = $this->get_setting( "{$setting_name_root}_field_id" );
4188: 
4189:         $value    = $this->get_setting( "{$setting_name_root}_value" );
4190:         $operator = $this->get_setting( "{$setting_name_root}_operator" );
4191:         if ( empty( $operator ) ) {
4192:             $operator = 'is';
4193:         }
4194: 
4195:         $field_id_attribute = ! empty( $field_id ) ? $field_id : 'jQuery("#' . esc_attr( $setting_name_root ) . '_field_id").val()';
4196: 
4197:         $str .= "<script type='text/javascript'>
4198:             var " . esc_attr( $setting_name_root ) . "_object = {'conditionalLogic':{'rules':[{'fieldId':'{$field_id}','operator':'{$operator}','value':'" . esc_attr( $value ) . "'}]}};
4199: 
4200:             jQuery(document).ready(
4201:                 function(){
4202:                     gform.addFilter( 'gform_conditional_object', 'SimpleConditionObject' );
4203: 
4204:                     jQuery('#" . esc_attr( $setting_name_root ) . "_container').html(
4205:                                             GetRuleValues('{$object_type}', 0, {$field_id_attribute}, '" . esc_attr( $value ) . "', '_gaddon_setting_" . esc_attr( $setting_name_root ) . "_value'));
4206: 
4207:                     }
4208:             );
4209:             </script>";
4210: 
4211:         return $str;
4212:     }
4213: 
4214:     /**
4215:      * Override this to define the array of choices which should be used to populate the Simple Condition fields drop down.
4216:      *
4217:      * Each choice should have 'label' and 'value' properties.
4218:      *
4219:      * @return array
4220:      */
4221:     public function get_conditional_logic_fields() {
4222:         return array();
4223:     }
4224: 
4225:     /**
4226:      * Evaluate the rules defined for the Simple Condition field.
4227:      *
4228:      * @param string $setting_name_root The root name used as the prefix to the inputs that make up the Simple Condition field.
4229:      * @param array $form The form currently being processed.
4230:      * @param array $entry The entry currently being processed.
4231:      * @param array $feed The feed currently being processed or an empty array when the field is stored in the form settings.
4232:      *
4233:      * @return bool
4234:      */
4235:     public function is_simple_condition_met( $setting_name_root, $form, $entry, $feed = array() ) {
4236: 
4237:         $settings = empty( $feed ) ? $this->get_form_settings( $form ) : rgar( $feed, 'meta', array() );
4238: 
4239:         $is_enabled = rgar( $settings, $setting_name_root . '_enabled' );
4240: 
4241:         if ( ! $is_enabled ) {
4242:             // The setting is not enabled so we handle it as if the rules are met.
4243: 
4244:             return true;
4245:         }
4246: 
4247:         // Build the logic array to be used by Gravity Forms when evaluating the rules.
4248:         $logic = array(
4249:             'logicType' => 'all',
4250:             'rules'     => array(
4251:                 array(
4252:                     'fieldId'  => rgar( $settings, $setting_name_root . '_field_id' ),
4253:                     'operator' => rgar( $settings, $setting_name_root . '_operator' ),
4254:                     'value'    => rgar( $settings, $setting_name_root . '_value' ),
4255:                 ),
4256:             )
4257:         );
4258: 
4259:         return GFCommon::evaluate_conditional_logic( $logic, $form, $entry );
4260:     }
4261: 
4262: 
4263:     //--------------  Form settings  ---------------------------------------------------
4264: 
4265:     /**
4266:      * Initializes form settings page
4267:      * Hooks up the required scripts and actions for the Form Settings page
4268:      */
4269:     public function form_settings_init() {
4270:         $view    = rgget( 'view' );
4271:         $subview = rgget( 'subview' );
4272:         add_filter( 'gform_form_settings_menu', array( $this, 'add_form_settings_menu' ), 10, 2 );
4273: 
4274:         if ( rgget( 'page' ) == 'gf_edit_forms' && $view == 'settings' && $subview == $this->_slug && $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
4275:             require_once( GFCommon::get_base_path() . '/tooltips.php' );
4276:             add_action( 'gform_form_settings_page_' . $this->_slug, array( $this, 'form_settings_page' ) );
4277:         }
4278:     }
4279: 
4280:     /**
4281:      * Initializes plugin settings page
4282:      * Hooks up the required scripts and actions for the Plugin Settings page
4283:      */
4284:     public function plugin_page_init() {
4285: 
4286:         if ( $this->current_user_can_any( $this->_capabilities_plugin_page ) ) {
4287:             //creates the subnav left menu
4288:             add_filter( 'gform_addon_navigation', array( $this, 'create_plugin_page_menu' ) );
4289:         }
4290: 
4291:     }
4292: 
4293:     /**
4294:      * Creates plugin page menu item
4295:      * Target of gform_addon_navigation filter. Creates a menu item in the left nav, linking to the plugin page
4296:      *
4297:      * @param $menus - Current list of menu items
4298:      *
4299:      * @return array - Returns a new list of menu items
4300:      */
4301:     public function create_plugin_page_menu( $menus ) {
4302: 
4303:         $menus[] = array( 'name' => $this->_slug, 'label' => $this->get_short_title(), 'callback' => array( $this, 'plugin_page_container' ), 'permission' => $this->_capabilities_plugin_page );
4304: 
4305:         return $menus;
4306:     }
4307: 
4308:     /**
4309:      * Renders the form settings page.
4310:      *
4311:      * Not intended to be overridden or called directly by Add-Ons.
4312:      * Sets up the form settings page.
4313:      *
4314:      * @ignore
4315:      */
4316:     public function form_settings_page() {
4317: 
4318:         GFFormSettings::page_header( $this->_title );
4319:         ?>
4320:         <div class="gform_panel gform_panel_form_settings" id="form_settings">
4321: 
4322:             <?php
4323:             $form = $this->get_current_form();
4324: 
4325:             $form_id = $form['id'];
4326:             $form    = gf_apply_filters( array( 'gform_admin_pre_render', $form_id ), $form );
4327: 
4328:             if ( $this->method_is_overridden( 'form_settings' ) ) {
4329: 
4330:                 //enables plugins to override settings page by implementing a form_settings() function
4331:                 $this->form_settings( $form );
4332:             } else {
4333: 
4334:                 //saves form settings if save button was pressed
4335:                 $this->maybe_save_form_settings( $form );
4336: 
4337:                 //reads current form settings
4338:                 $settings = $this->get_form_settings( $form );
4339:                 $this->set_settings( $settings );
4340: 
4341:                 //reading addon fields
4342:                 $sections = $this->form_settings_fields( $form );
4343: 
4344:                 GFCommon::display_admin_message();
4345: 
4346:                 $page_title = $this->form_settings_page_title();
4347:                 if ( empty( $page_title ) ) {
4348:                     $page_title = rgar( $sections[0], 'title' );
4349: 
4350:                     //using first section title as page title, so disable section title
4351:                     $sections[0]['title'] = false;
4352:                 }
4353:                 $icon = $this->form_settings_icon();
4354:                 if ( empty( $icon ) ) {
4355:                     $icon = '<i class="fa fa-cogs"></i>';
4356:                 }
4357: 
4358:                 ?>
4359:                 <h3><span><?php echo $icon ?> <?php echo $page_title ?></span></h3>
4360:                 <?php
4361: 
4362:                 //rendering settings based on fields and current settings
4363:                 $this->render_settings( $sections );
4364:             }
4365:             ?>
4366: 
4367:             <script type="text/javascript">
4368:                 var form = <?php echo json_encode( $this->get_current_form() ) ?>;
4369:             </script>
4370:         </div>
4371:         <?php
4372:         GFFormSettings::page_footer();
4373:     }
4374: 
4375:     /***
4376:      * Saves form settings if the submit button was pressed
4377:      *
4378:      * @param array $form The form object
4379:      *
4380:      * @return null|true|false True on success, false on error, null on no action
4381:      */
4382:     public function maybe_save_form_settings( $form ) {
4383: 
4384:         if ( $this->is_save_postback() ) {
4385: 
4386:             check_admin_referer( $this->_slug . '_save_settings', '_' . $this->_slug . '_save_settings_nonce' );
4387: 
4388:             if ( ! $this->current_user_can_any( $this->_capabilities_form_settings ) ) {
4389:                 GFCommon::add_error_message( esc_html__( "You don't have sufficient permissions to update the form settings.", 'gravityforms' ) );
4390:                 return false;
4391:             }
4392: 
4393:             // store a copy of the previous settings for cases where action would only happen if value has changed
4394:             $this->set_previous_settings( $this->get_form_settings( $form ) );
4395: 
4396:             $settings = $this->get_posted_settings();
4397:             $sections = $this->form_settings_fields( $form );
4398: 
4399:             $is_valid = $this->validate_settings( $sections, $settings );
4400:             $result   = false;
4401: 
4402:             if ( $is_valid ) {
4403:                 $settings = $this->filter_settings( $sections, $settings );
4404:                 $result = $this->save_form_settings( $form, $settings );
4405:             }
4406: 
4407:             if ( $result ) {
4408:                 GFCommon::add_message( $this->get_save_success_message( $sections ) );
4409:             } else {
4410:                 GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
4411:             }
4412: 
4413:             return $result;
4414:         }
4415: 
4416:     }
4417: 
4418:     /***
4419:      * Saves form settings to form object
4420:      *
4421:      * @param array $form
4422:      * @param array $settings
4423:      *
4424:      * @return true|false True on success or false on error
4425:      */
4426:     public function save_form_settings( $form, $settings ) {
4427:         $form[ $this->_slug ] = $settings;
4428:         $result               = GFFormsModel::update_form_meta( $form['id'], $form );
4429: 
4430:         return ! ( false === $result );
4431:     }
4432: 
4433:     /**
4434:      * Checks whether the current Add-On has a form settings page.
4435:      *
4436:      * @return bool
4437:      */
4438:     private function has_form_settings_page() {
4439:         return $this->method_is_overridden( 'form_settings_fields' ) || $this->method_is_overridden( 'form_settings' );
4440:     }
4441: 
4442:     /**
4443:      * Custom form settings page
4444:      * Override this function to implement a complete custom form settings page.
4445:      * Before overriding this function, consider using the form_settings_fields() and specifying your field meta.
4446:      */
4447:     public function form_settings( $form ) {
4448:     }
4449: 
4450:     /**
4451:      * Custom form settings title
4452:      * Override this function to display a custom title on the Form Settings Page.
4453:      * By default, the first section in the configuration done in form_settings_fields() will be used as the page title.
4454:      * Use this function to override that behavior and add a custom page title.
4455:      */
4456:     public function form_settings_page_title() {
4457:         return '';
4458:     }
4459: 
4460:     /**
4461:      * Override this function to customize the form settings icon
4462:      */
4463:     public function form_settings_icon() {
4464:         return '';
4465:     }
4466: 
4467:     /**
4468:      * Checks whether the current Add-On has a plugin page.
4469:      *
4470:      * @return bool
4471:      */
4472:     private function has_plugin_page() {
4473:         return $this->method_is_overridden( 'plugin_page' );
4474:     }
4475: 
4476:     /**
4477:      * Override this function to create a custom plugin page
4478:      */
4479:     public function plugin_page() {
4480:     }
4481: 
4482:     /**
4483:      * Override this function to customize the plugin page icon
4484:      */
4485:     public function plugin_page_icon() {
4486:         return '';
4487:     }
4488: 
4489:     /**
4490:      * Override this function to customize the plugin page title
4491:      */
4492:     public function plugin_page_title() {
4493:         return $this->_title;
4494:     }
4495: 
4496:     /**
4497:      * Plugin page container
4498:      * Target of the plugin menu left nav icon. Displays the outer plugin page markup and calls plugin_page() to render the actual page.
4499:      * Override plugin_page() in order to provide a custom plugin page
4500:      */
4501:     public function plugin_page_container() {
4502:         ?>
4503:         <div class="wrap">
4504:             <?php
4505:             $icon = $this->plugin_page_icon();
4506:             if ( ! empty( $icon ) ) {
4507:                 ?>
4508:                 <img alt="<?php echo $this->get_short_title() ?>" style="margin: 15px 7px 0pt 0pt; float: left;" src="<?php echo $icon ?>" />
4509:             <?php
4510:             }
4511:             ?>
4512: 
4513:             <h2 class="gf_admin_page_title"><?php echo $this->plugin_page_title() ?></h2>
4514:             <?php
4515: 
4516:             $this->plugin_page();
4517:             ?>
4518:         </div>
4519:     <?php
4520:     }
4521: 
4522:     /**
4523:      * Checks whether the current Add-On has a top level app menu.
4524:      *
4525:      * @return bool
4526:      */
4527:     public function has_app_menu() {
4528:         return $this->has_app_settings() || $this->method_is_overridden( 'get_app_menu_items' );
4529:     }
4530: 
4531:     /**
4532:      * Creates a top level app menu. Adds the app settings page automatically if it's configured.
4533:      * Target of the WordPress admin_menu action.
4534:      * Not intended to be overridden or called directly by add-ons.
4535:      */
4536:     public function create_app_menu() {
4537: 
4538:         $has_full_access = current_user_can( 'gform_full_access' );
4539:         $min_cap         = GFCommon::current_user_can_which( $this->_capabilities_app_menu );
4540:         if ( empty( $min_cap ) ) {
4541:             $min_cap = 'gform_full_access';
4542:         }
4543: 
4544:         $menu_items = $this->get_app_menu_items();
4545: 
4546:         $addon_menus = array();
4547: 
4548:         /**
4549:          * Filters through addon menus (filter by addon slugs)
4550:          *
4551:          * @param array $addon_menus A modifiable array of admin addon menus
4552:          */
4553:         $addon_menus = apply_filters( 'gform_addon_app_navigation_' . $this->_slug, $addon_menus );
4554: 
4555:         $parent_menu = self::get_parent_menu( $menu_items, $addon_menus );
4556: 
4557:         if ( empty( $parent_menu ) ) {
4558:             return;
4559:         }
4560: 
4561:         // Add a top-level left nav
4562:         $callback = isset( $parent_menu['callback'] ) ? $parent_menu['callback'] : array( $this, 'app_tab_page' );
4563: 
4564:         global $menu;
4565:         $number = 10;
4566:         $menu_position = '16.' . $number;
4567:         while ( isset( $menu[$menu_position] ) ) {
4568:             $number += 10;
4569:             $menu_position = '16.' . $number;
4570:         }
4571: 
4572:         /**
4573:          * Modify the menu position of an add-on menu
4574:          *
4575:          * @param int $menu_position The Menu position of the add-on menu
4576:          */
4577:         $menu_position = apply_filters( 'gform_app_menu_position_' . $this->_slug, $menu_position );
4578:         $this->app_hook_suffix = add_menu_page( $this->get_short_title(), $this->get_short_title(), $has_full_access ? 'gform_full_access' : $min_cap, $parent_menu['name'], $callback, $this->get_app_menu_icon(), $menu_position );
4579: 
4580:         if ( method_exists( $this, 'load_screen_options' ) ) {
4581:             add_action( "load-$this->app_hook_suffix", array( $this, 'load_screen_options' ) );
4582:         }
4583: 
4584:         // Adding submenu pages
4585:         foreach ( $menu_items as $menu_item ) {
4586:             $callback = isset( $menu_item['callback'] ) ? $menu_item['callback'] : array( $this, 'app_tab_page' );
4587:             add_submenu_page( $parent_menu['name'], $menu_item['label'], $menu_item['label'], $has_full_access || empty( $menu_item['permission'] ) ? 'gform_full_access' : $menu_item['permission'], $menu_item['name'], $callback );
4588:         }
4589: 
4590:         if ( is_array( $addon_menus ) ) {
4591:             foreach ( $addon_menus as $addon_menu ) {
4592:                 add_submenu_page( $parent_menu['name'], $addon_menu['label'], $addon_menu['label'], $has_full_access ? 'gform_full_access' : $addon_menu['permission'], $addon_menu['name'], $addon_menu['callback'] );
4593:             }
4594:         }
4595: 
4596:         if ( $this->has_app_settings() ) {
4597:             add_submenu_page( $parent_menu['name'], esc_html__( 'Settings', 'gravityforms' ), esc_html__( 'Settings', 'gravityforms' ), $has_full_access ? 'gform_full_access' : $this->_capabilities_app_settings, $this->_slug . '_settings', array( $this, 'app_tab_page' ) );
4598:         }
4599: 
4600:     }
4601: 
4602:     /**
4603:      * Returns the parent menu item
4604:      *
4605:      * @param $menu_items
4606:      * @param $addon_menus
4607:      *
4608:      * @return array|bool The parent menu araray or false if none
4609:      */
4610:     private function get_parent_menu( $menu_items, $addon_menus ) {
4611:         $parent = false;
4612:         if ( GFCommon::current_user_can_any( $this->_capabilities_app_menu ) ) {
4613:             foreach ( $menu_items as $menu_item ) {
4614:                 if ( $this->current_user_can_any( $menu_item['permission'] ) ) {
4615:                     $parent = $menu_item;
4616:                     break;
4617:                 }
4618:             }
4619:         } elseif ( is_array( $addon_menus ) && sizeof( $addon_menus ) > 0 ) {
4620:             foreach ( $addon_menus as $addon_menu ) {
4621:                 if ( $this->current_user_can_any( $addon_menu['permission'] ) ) {
4622:                     $parent = array( 'name' => $addon_menu['name'], 'callback' => $addon_menu['callback'] );
4623:                     break;
4624:                 }
4625:             }
4626:         } elseif ( $this->has_app_settings() && $this->current_user_can_any( $this->_capabilities_app_settings ) ) {
4627:             $parent = array( 'name' => $this->_slug . '_settings', 'callback' => array( $this, 'app_settings' ) );
4628:         }
4629: 
4630:         return $parent;
4631:     }
4632: 
4633:     /**
4634:      * Override this function to create a top level app menu.
4635:      *
4636:      * e.g.
4637:      * $menu_item['name'] = 'gravitycontacts';
4638:      * $menu_item['label'] = __("Contacts", 'gravitycontacts');
4639:      * $menu_item['permission'] = 'gravitycontacts_view_contacts';
4640:      * $menu_item['callback'] = array($this, 'app_menu');
4641:      *
4642:      * @return array The array of menu items
4643:      */
4644:     public function get_app_menu_items() {
4645:         return array();
4646:     }
4647: 
4648:     /**
4649:      * Override this function to specify a custom icon for the top level app menu.
4650:      * Accepts a dashicon class or a URL.
4651:      *
4652:      * @return string
4653:      */
4654:     public function get_app_menu_icon() {
4655:         return '';
4656:     }
4657: 
4658:     /**
4659:      * Override this function to load custom screen options.
4660:      *
4661:      * e.g.
4662:      * $screen = get_current_screen();
4663:      * if(!is_object($screen) || $screen->id != $this->app_hook_suffix)
4664:      *     return;
4665:      *
4666:      * if($this->is_contact_list_page()){
4667:      *     $args = array(
4668:      *         'label' => __('Contacts per page', 'gravitycontacts'),
4669:      *         'default' => 20,
4670:      *         'option' => 'gcontacts_per_page'
4671:      *     );
4672:      * add_screen_option( 'per_page', $args );
4673:      */
4674:     public function load_screen_options() {
4675:     }
4676: 
4677:     /**
4678:      * Handles the rendering of app menu items that implement the tabs UI.
4679:      *
4680:      * Not intended to be overridden or called directly by add-ons.
4681:      */
4682:     public function app_tab_page() {
4683:         $page        = sanitize_text_field( rgget( 'page' ) );
4684:         $current_tab = sanitize_text_field( rgget( 'view' ) );
4685: 
4686:         if ( $page == $this->_slug . '_settings' ) {
4687: 
4688:             $tabs = $this->get_app_settings_tabs();
4689: 
4690:         } else {
4691: 
4692:             $menu_items = $this->get_app_menu_items();
4693: 
4694:             $current_menu_item = false;
4695:             foreach ( $menu_items as $menu_item ) {
4696:                 if ( $menu_item['name'] == $page ) {
4697:                     $current_menu_item = $menu_item;
4698:                     break;
4699:                 }
4700:             }
4701: 
4702:             if ( empty( $current_menu_item ) ) {
4703:                 return;
4704:             }
4705: 
4706:             if ( empty( $current_menu_item['tabs'] ) ) {
4707:                 return;
4708:             }
4709: 
4710:             $tabs = $current_menu_item['tabs'];
4711:         }
4712: 
4713:         if ( empty( $current_tab ) ) {
4714:             foreach ( $tabs as $tab ) {
4715:                 if ( ! isset( $tab['permission'] ) || $this->current_user_can_any( $tab['permission'] ) ) {
4716:                     $current_tab = $tab['name'];
4717:                     break;
4718:                 }
4719:             }
4720:         }
4721: 
4722:         if ( empty( $current_tab ) ) {
4723:             wp_die( esc_html__( "You don't have adequate permission to view this page", 'gravityforms' ) );
4724:         }
4725: 
4726:         foreach ( $tabs as $tab ) {
4727:             if ( $tab['name'] == $current_tab && isset( $tab['callback'] ) && is_callable( $tab['callback'] ) ) {
4728:                 if ( isset( $tab['permission'] ) && ! $this->current_user_can_any( $tab['permission'] ) ) {
4729:                     wp_die( esc_html__( "You don't have adequate permission to view this page", 'gravityforms' ) );
4730:                 }
4731: 
4732:                 $title = rgar( $tab,'title' );
4733: 
4734:                 if ( empty( $title ) ) {
4735:                     $title = isset( $tab['label'] ) ? $tab['label'] : $tab['name'];
4736:                 }
4737: 
4738:                 $this->app_tab_page_header( $tabs, $current_tab, $title, '' );
4739:                 call_user_func( $tab['callback'] );
4740:                 $this->app_tab_page_footer();
4741: 
4742:                 return;
4743:             }
4744:         }
4745: 
4746:         $this->app_tab_page_header( $tabs, $current_tab, $current_tab, '' );
4747:         /**
4748:          * Fires when an addon page and tab is accessed.
4749:          *
4750:          * Typically used to render settings tab content.
4751:          */
4752:         $action_hook = 'gform_addon_app_' . $page . '_' . str_replace( ' ', '_', $current_tab );
4753:         do_action( $action_hook );
4754:         $this->app_tab_page_footer();
4755: 
4756:     }
4757: 
4758:     /**
4759:      * Returns the form settings for the Add-On
4760:      *
4761:      * @param $form
4762:      *
4763:      * @return array
4764:      */
4765:     public function get_form_settings( $form ) {
4766:         return rgar( $form, $this->_slug );
4767:     }
4768: 
4769:     /**
4770:      * Add the form settings tab.
4771:      *
4772:      * Override this function to add the tab conditionally.
4773:      *
4774:      *
4775:      * @param $tabs
4776:      * @param $form_id
4777:      *
4778:      * @return array
4779:      */
4780:     public function add_form_settings_menu( $tabs, $form_id ) {
4781: 
4782:         $tabs[] = array( 'name' => $this->_slug, 'label' => $this->get_short_title(), 'query' => array( 'fid' => null ), 'capabilities' => $this->_capabilities_form_settings );
4783: 
4784:         return $tabs;
4785:     }
4786: 
4787:     /**
4788:      * Override this function to specify the settings fields to be rendered on the form settings page
4789:      */
4790:     public function form_settings_fields( $form ) {
4791:         // should return an array of sections, each section contains a title, description and an array of fields
4792:         return array();
4793:     }
4794: 
4795:     //--------------  Plugin Settings  ---------------------------------------------------
4796: 
4797:     public function plugin_settings_init() {
4798:         $subview = rgget( 'subview' );
4799:         RGForms::add_settings_page(
4800:             array(
4801:                 'name'      => $this->_slug,
4802:                 'tab_label' => $this->get_short_title(),
4803:                 'title'     => $this->plugin_settings_title(),
4804:                 'handler'   => array( $this, 'plugin_settings_page' ),
4805:             )
4806:         );
4807:         if ( rgget( 'page' ) == 'gf_settings' && $subview == $this->_slug && $this->current_user_can_any( $this->_capabilities_settings_page ) ) {
4808:             require_once( GFCommon::get_base_path() . '/tooltips.php' );
4809:         }
4810: 
4811:         add_filter( 'plugin_action_links', array( $this, 'plugin_settings_link' ), 10, 2 );
4812: 
4813:     }
4814: 
4815:     public function plugin_settings_link( $links, $file ) {
4816:         if ( $file != $this->_path ) {
4817:             return $links;
4818:         }
4819: 
4820:         array_unshift( $links, '<a href="' . admin_url( 'admin.php' ) . '?page=gf_settings&subview=' . $this->_slug . '">' . esc_html__( 'Settings', 'gravityforms' ) . '</a>' );
4821: 
4822:         return $links;
4823:     }
4824: 
4825:     /**
4826:      * Plugin settings page
4827:      */
4828:     public function plugin_settings_page() {
4829:         $icon = $this->plugin_settings_icon();
4830:         if ( empty( $icon ) ) {
4831:             $icon = '<i class="fa fa-cogs"></i>';
4832:         }
4833:         ?>
4834: 
4835:         <h3><span><?php echo $icon ?> <?php echo $this->plugin_settings_title() ?></span></h3>
4836: 
4837:         <?php if ( $this->has_deprecated_elements() ) : ?>
4838:         <div class="push-alert-red" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
4839:             <?php esc_html_e( 'This add-on needs to be updated. Please contact the developer.', 'gravityforms' ); ?>
4840:         </div>
4841:         <?php endif; ?>
4842: 
4843:         <?php
4844: 
4845:         if ( $this->method_is_overridden( 'plugin_settings' ) ) {
4846:             //enables plugins to override settings page by implementing a plugin_settings() function
4847:             $this->plugin_settings();
4848:         } elseif ( $this->maybe_uninstall() ) {
4849:             ?>
4850:             <div class="push-alert-gold" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
4851:                 <?php printf( esc_html__( '%s has been successfully uninstalled. It can be re-activated from the %splugins page%s.', 'gravityforms'), $this->_title, "<a href='plugins.php'>", '</a>' ); ?>
4852:             </div>
4853:         <?php
4854:         } else {
4855:             //saves settings page if save button was pressed
4856:             $this->maybe_save_plugin_settings();
4857: 
4858:             //reads main addon settings
4859:             $settings = $this->get_plugin_settings();
4860:             $this->set_settings( $settings );
4861: 
4862:             //reading addon fields
4863:             $sections = $this->plugin_settings_fields();
4864: 
4865:             GFCommon::display_admin_message();
4866: 
4867:             //rendering settings based on fields and current settings
4868:             $this->render_settings( $sections, $settings );
4869: 
4870:             //renders uninstall section
4871:             $this->render_uninstall();
4872: 
4873:         }
4874: 
4875:     }
4876: 
4877:     public function plugin_settings_title() {
4878:         return sprintf( esc_html__( "%s Settings", "gravityforms" ), $this->get_short_title() );
4879:     }
4880: 
4881:     public function plugin_settings_icon() {
4882:         return '';
4883:     }
4884: 
4885:     /**
4886:      * Override this function to add a custom settings page.
4887:      */
4888:     public function plugin_settings() {
4889:     }
4890: 
4891:     /**
4892:      * Checks whether the current Add-On has a settings page.
4893:      *
4894:      * @return bool
4895:      */
4896:     public function has_plugin_settings_page() {
4897:         return $this->method_is_overridden( 'plugin_settings_fields' ) || $this->method_is_overridden( 'plugin_settings_page' ) || $this->method_is_overridden( 'plugin_settings' );
4898:     }
4899: 
4900:     /**
4901:      * Returns the currently saved plugin settings
4902:      * @return mixed
4903:      */
4904:     public function get_plugin_settings() {
4905:         return get_option( 'gravityformsaddon_' . $this->_slug . '_settings' );
4906:     }
4907: 
4908:     /**
4909:      * Get plugin setting
4910:      * Returns the plugin setting specified by the $setting_name parameter
4911:      *
4912:      * @param string $setting_name - Plugin setting to be returned
4913:      *
4914:      * @return mixed  - Returns the specified plugin setting or null if the setting doesn't exist
4915:      */
4916:     public function get_plugin_setting( $setting_name ) {
4917:         $settings = $this->get_plugin_settings();
4918: 
4919:         return isset( $settings[ $setting_name ] ) ? $settings[ $setting_name ] : null;
4920:     }
4921: 
4922:     /**
4923:      * Updates plugin settings with the provided settings
4924:      *
4925:      * @param array $settings - Plugin settings to be saved
4926:      */
4927:     public function update_plugin_settings( $settings ) {
4928:         update_option( 'gravityformsaddon_' . $this->_slug . '_settings', $settings );
4929:     }
4930: 
4931:     /**
4932:      * Saves the plugin settings if the submit button was pressed
4933:      *
4934:      */
4935:     public function maybe_save_plugin_settings() {
4936: 
4937:         if ( $this->is_save_postback() ) {
4938: 
4939:             check_admin_referer( $this->_slug . '_save_settings', '_' . $this->_slug . '_save_settings_nonce' );
4940: 
4941:             if ( ! $this->current_user_can_any( $this->_capabilities_settings_page ) ) {
4942:                 GFCommon::add_error_message( esc_html__( "You don't have sufficient permissions to update the settings.", 'gravityforms' ) );
4943:                 return false;
4944:             }
4945: 
4946:             // store a copy of the previous settings for cases where action would only happen if value has changed
4947:             $this->set_previous_settings( $this->get_plugin_settings() );
4948: 
4949:             $settings = $this->get_posted_settings();
4950:             $sections = $this->plugin_settings_fields();
4951:             $is_valid = $this->validate_settings( $sections, $settings );
4952: 
4953:             if ( $is_valid ) {
4954:                 $settings = $this->filter_settings( $sections, $settings );
4955:                 $this->update_plugin_settings( $settings );
4956:                 GFCommon::add_message( $this->get_save_success_message( $sections ) );
4957:             } else {
4958:                 GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
4959:             }
4960:         }
4961: 
4962:     }
4963: 
4964:     /**
4965:      * Override this function to specify the settings fields to be rendered on the plugin settings page
4966:      * @return array
4967:      */
4968:     public function plugin_settings_fields() {
4969:         // should return an array of sections, each section contains a title, description and an array of fields
4970:         return array();
4971:     }
4972: 
4973:     //--------------  App Settings  ---------------------------------------------------
4974: 
4975:     /**
4976:      * Returns the tabs for the settings app menu item
4977:      *
4978:      * Not intended to be overridden or called directly by add-ons.
4979:      *
4980:      * @return array|mixed|void
4981:      */
4982:     public function get_app_settings_tabs() {
4983: 
4984:         // Build left side options, always have app Settings first and Uninstall last, put add-ons in the middle
4985: 
4986:         $setting_tabs = array( array( 'name' => 'settings', 'label' => esc_html__( 'Settings', 'gravityforms' ), 'callback' => array( $this, 'app_settings_tab' ) ) );
4987: 
4988:         /**
4989:          * Filters the tabs within the settings menu.
4990:          *
4991:          * This filter is appended by the page slug.  Ex: gform_addon_app_settings_menu_SLUG
4992:          *
4993:          * @param array $setting_tabs Contains the information on the settings tabs.
4994:          */
4995:         $setting_tabs = apply_filters( 'gform_addon_app_settings_menu_' . $this->_slug, $setting_tabs );
4996: 
4997:         if ( $this->current_user_can_uninstall() ) {
4998:             $setting_tabs[] = array( 'name' => 'uninstall', 'label' => esc_html__( 'Uninstall', 'gravityforms' ), 'callback' => array( $this, 'app_settings_uninstall_tab' ) );
4999:         }
5000: 
5001:         ksort( $setting_tabs, SORT_NUMERIC );
5002: 
5003:         return $setting_tabs;
5004:     }
5005: 
5006:     /**
5007:      * Renders the app settings uninstall tab.
5008:      *
5009:      * Not intended to be overridden or called directly by add-ons.
5010:      */
5011:     public function app_settings_uninstall_tab() {
5012: 
5013:         if ( $this->maybe_uninstall() ) {
5014:             ?>
5015:             <div class="push-alert-gold" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
5016:                 <?php printf( esc_html__( '%s has been successfully uninstalled. It can be re-activated from the %splugins page%s.', 'gravityforms' ), esc_html( $this->_title ), "<a href='plugins.php'>", '</a>' ); ?>
5017:             </div>
5018:         <?php
5019: 
5020:         } else {
5021:             if ( $this->current_user_can_uninstall() ) {
5022:             ?>
5023:             <form action="" method="post">
5024:                 <?php wp_nonce_field( 'uninstall', 'gf_addon_uninstall' ) ?>
5025:                 <?php  ?>
5026:                     <h3>
5027:                         <span><i class="fa fa-times"></i> <?php printf( esc_html__( 'Uninstall %s', 'gravityforms' ), $this->get_short_title() ); ?></span>
5028:                     </h3>
5029: 
5030:                     <div class="delete-alert alert_red">
5031: 
5032:                         <h3>
5033:                             <i class="fa fa-exclamation-triangle gf_invalid"></i> <?php esc_html_e( 'Warning', 'gravityforms' ); ?>
5034:                         </h3>
5035: 
5036:                         <div class="gf_delete_notice">
5037:                             <?php echo $this->uninstall_warning_message() ?>
5038:                         </div>
5039: 
5040:                         <?php
5041:                         $uninstall_button = '<input type="submit" name="uninstall" value="' . sprintf( esc_attr__( 'Uninstall %s', 'gravityforms' ), $this->get_short_title() ) . '" class="button" onclick="return confirm(\'' . esc_js( $this->uninstall_confirm_message() ) . '\');" onkeypress="return confirm(\'' . esc_js( $this->uninstall_confirm_message() ) . '\');"/>';
5042:                         echo $uninstall_button;
5043:                         ?>
5044: 
5045:                     </div>
5046:             </form>
5047:             <?php
5048:             }
5049:         }
5050:     }
5051: 
5052:     /**
5053:      * Renders the header for the tabs UI.
5054:      *
5055:      * @param        $tabs
5056:      * @param        $current_tab
5057:      * @param        $title
5058:      * @param string $message
5059:      */
5060:     public function app_tab_page_header( $tabs, $current_tab, $title, $message = '' ) {
5061: 
5062:         // Print admin styles
5063:         wp_print_styles( array( 'jquery-ui-styles', 'gform_admin' ) );
5064: 
5065:         ?>
5066: 
5067:         <div class="wrap <?php echo GFCommon::get_browser_class() ?>">
5068: 
5069:         <?php if ( $message ) { ?>
5070:             <div id="message" class="updated"><p><?php echo $message; ?></p></div>
5071:         <?php } ?>
5072: 
5073:         <h2><?php echo esc_html( $title ) ?></h2>
5074: 
5075:         <div id="gform_tab_group" class="gform_tab_group vertical_tabs">
5076:         <ul id="gform_tabs" class="gform_tabs">
5077:             <?php
5078:             foreach ( $tabs as $tab ) {
5079:                 if ( isset( $tab['permission'] ) && ! $this->current_user_can_any( $tab['permission'] ) ) {
5080:                     continue;
5081:                 }
5082:                 $label = isset( $tab['label'] ) ? $tab['label'] : $tab['name'];
5083:                 ?>
5084:                 <li <?php echo urlencode( $current_tab ) == $tab['name'] ? "class='active'" : '' ?>>
5085:                     <a href="<?php echo esc_url( add_query_arg( array( 'view' => $tab['name'] ) ) ); ?>"><?php echo esc_html( $label ) ?></a>
5086:                 </li>
5087:             <?php
5088:             }
5089:             ?>
5090:         </ul>
5091: 
5092:         <div id="gform_tab_container" class="gform_tab_container">
5093:         <div class="gform_tab_content" id="tab_<?php echo esc_attr( $current_tab ) ?>">
5094: 
5095:     <?php
5096:     }
5097: 
5098:     /**
5099:      * Renders the footer for the tabs UI.
5100:      *
5101:      */
5102:     public function app_tab_page_footer() {
5103:         ?>
5104:         </div> <!-- / gform_tab_content -->
5105:         </div> <!-- / gform_tab_container -->
5106:         </div> <!-- / gform_tab_group -->
5107: 
5108:         <br class="clear" style="clear: both;" />
5109: 
5110:         </div> <!-- / wrap -->
5111: 
5112:     <?php
5113:     }
5114: 
5115:     public function app_settings_tab() {
5116: 
5117:         require_once( GFCommon::get_base_path() . '/tooltips.php' );
5118: 
5119:         $icon = $this->app_settings_icon();
5120:         if ( empty( $icon ) ) {
5121:             $icon = '<i class="fa fa-cogs"></i>';
5122:         }
5123:         ?>
5124: 
5125:         <h3><span><?php echo $icon ?> <?php echo $this->app_settings_title() ?></span></h3>
5126: 
5127:         <?php
5128: 
5129:         if ( $this->method_is_overridden( 'app_settings' ) ) {
5130:             //enables plugins to override settings page by implementing a plugin_settings() function
5131:             $this->app_settings();
5132:         } elseif ( $this->maybe_uninstall() ) {
5133:             ?>
5134:             <div class="push-alert-gold" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
5135:                 <?php printf( esc_html__( '%s has been successfully uninstalled. It can be re-activated from the %splugins page%s.', 'gravityforms' ), esc_html( $this->_title ), "<a href='plugins.php'>", '</a>' ); ?>
5136:             </div>
5137:         <?php
5138:         } else {
5139:             //saves settings page if save button was pressed
5140:             $this->maybe_save_app_settings();
5141: 
5142:             //reads main addon settings
5143:             $settings = $this->get_app_settings();
5144:             $this->set_settings( $settings );
5145: 
5146:             //reading addon fields
5147:             $sections = $this->app_settings_fields();
5148: 
5149:             GFCommon::display_admin_message();
5150: 
5151:             //rendering settings based on fields and current settings
5152:             $this->render_settings( $sections, $settings );
5153: 
5154:         }
5155: 
5156:     }
5157: 
5158:     /**
5159:      * Override this function to specific a custom app settings title
5160:      *
5161:      * @return string
5162:      */
5163:     public function app_settings_title() {
5164:         return sprintf( esc_html__( '%s Settings', 'gravityforms' ), $this->get_short_title() );
5165:     }
5166: 
5167:     /**
5168:      * Override this function to specific a custom app settings icon
5169:      *
5170:      * @return string
5171:      */
5172:     public function app_settings_icon() {
5173:         return '';
5174:     }
5175: 
5176:     /**
5177:      * Checks whether the current Add-On has a settings page.
5178:      *
5179:      * @return bool
5180:      */
5181:     public function has_app_settings() {
5182:         return $this->method_is_overridden( 'app_settings_fields' ) || $this->method_is_overridden( 'app_settings' );
5183:     }
5184: 
5185:     /**
5186:      * Override this function to add a custom app settings page.
5187:      */
5188:     public function app_settings() {
5189:     }
5190: 
5191:     /**
5192:      * Returns the currently saved plugin settings
5193:      * @return mixed
5194:      */
5195:     public function get_app_settings() {
5196:         return get_option( 'gravityformsaddon_' . $this->_slug . '_app_settings' );
5197:     }
5198: 
5199:     /**
5200:      * Get app setting
5201:      * Returns the app setting specified by the $setting_name parameter
5202:      *
5203:      * @param string $setting_name - Plugin setting to be returned
5204:      *
5205:      * @return mixed  - Returns the specified plugin setting or null if the setting doesn't exist
5206:      */
5207:     public function get_app_setting( $setting_name ) {
5208:         $settings = $this->get_app_settings();
5209: 
5210:         return isset( $settings[ $setting_name ] ) ? $settings[ $setting_name ] : null;
5211:     }
5212: 
5213:     /**
5214:      * Updates app settings with the provided settings
5215:      *
5216:      * @param array $settings - App settings to be saved
5217:      */
5218:     public function update_app_settings( $settings ) {
5219:         update_option( 'gravityformsaddon_' . $this->_slug . '_app_settings', $settings );
5220:     }
5221: 
5222:     /**
5223:      * Saves the plugin settings if the submit button was pressed
5224:      *
5225:      */
5226:     public function maybe_save_app_settings() {
5227: 
5228:         if ( $this->is_save_postback() ) {
5229: 
5230:             check_admin_referer( $this->_slug . '_save_settings', '_' . $this->_slug . '_save_settings_nonce' );
5231: 
5232:             if ( ! $this->current_user_can_any( $this->_capabilities_app_settings ) ) {
5233:                 GFCommon::add_error_message( esc_html__( "You don't have sufficient permissions to update the settings.", 'gravityforms' ) );
5234:                 return false;
5235:             }
5236: 
5237:             // store a copy of the previous settings for cases where action would only happen if value has changed
5238:             $this->set_previous_settings( $this->get_app_settings() );
5239: 
5240:             $settings = $this->get_posted_settings();
5241:             $sections = $this->app_settings_fields();
5242:             $is_valid = $this->validate_settings( $sections, $settings );
5243: 
5244:             if ( $is_valid ) {
5245:                 $settings = $this->filter_settings( $sections, $settings );
5246:                 $this->update_app_settings( $settings );
5247:                 GFCommon::add_message( $this->get_save_success_message( $sections ) );
5248:             } else {
5249:                 GFCommon::add_error_message( $this->get_save_error_message( $sections ) );
5250:             }
5251:         }
5252: 
5253:     }
5254: 
5255:     /**
5256:      * Override this function to specify the settings fields to be rendered on the plugin settings page
5257:      * @return array
5258:      */
5259:     public function app_settings_fields() {
5260:         // should return an array of sections, each section contains a title, description and an array of fields
5261:         return array();
5262:     }
5263: 
5264:     /**
5265:      * Returns an flattened array of field settings for the specified settings type ignoring sections.
5266:      *
5267:      * @param string $settings_type The settings type. e.g. 'plugin'
5268:      *
5269:      * @return array
5270:      */
5271:     public function settings_fields_only( $settings_type = 'plugin' ) {
5272: 
5273:         $fields = array();
5274: 
5275:         if ( ! is_callable( array( $this, "{$settings_type}_settings_fields" ) ) ) {
5276:             return $fields;
5277:         }
5278: 
5279:         $sections = call_user_func( array( $this, "{$settings_type}_settings_fields" ) );
5280: 
5281:         foreach ( $sections as $section ) {
5282:             foreach ( $section['fields'] as $field ) {
5283:                 $fields[] = $field;
5284:             }
5285:         }
5286: 
5287:         return $fields;
5288:     }
5289: 
5290:     //--------------  Uninstall  ---------------
5291: 
5292:     /**
5293:      * Override this function to customize the markup for the uninstall section on the plugin settings page
5294:      */
5295:     public function render_uninstall() {
5296: 
5297:         ?>
5298:         <form action="" method="post">
5299:             <?php wp_nonce_field( 'uninstall', 'gf_addon_uninstall' ) ?>
5300:             <?php if ( $this->current_user_can_uninstall() ) { ?>
5301: 
5302:                 <div class="hr-divider"></div>
5303: 
5304:                 <h3><span><i class="fa fa-times"></i> <?php printf( esc_html__( 'Uninstall %s Add-On', 'gravityforms' ), $this->get_short_title() ) ?></span></h3>
5305:                 <div class="delete-alert alert_red">
5306:                     <h3><i class="fa fa-exclamation-triangle gf_invalid"></i> <?php printf( esc_html__('Warning', 'gravityforms' ) ); ?></h3>
5307:                     <div class="gf_delete_notice">
5308:                         <?php echo $this->uninstall_warning_message() ?>
5309:                     </div>
5310:                     <input type="submit" name="uninstall" value="<?php esc_attr_e( 'Uninstall  Add-On', 'gravityforms' ) ?>" class="button" onclick="return confirm('<?php echo esc_js( $this->uninstall_confirm_message() ); ?>');" onkeypress="return confirm('<?php echo esc_js( $this->uninstall_confirm_message() ); ?>');">
5311:                 </div>
5312: 
5313:             <?php
5314:             }
5315:             ?>
5316:         </form>
5317:     <?php
5318:     }
5319: 
5320:     public function uninstall_warning_message() {
5321:         return sprintf( esc_html__( '%sThis operation deletes ALL %s settings%s. If you continue, you will NOT be able to retrieve these settings.', 'gravityforms' ), '<strong>', esc_html( $this->get_short_title() ), '</strong>' );
5322:     }
5323: 
5324:     public function uninstall_confirm_message() {
5325:         return sprintf( __( "Warning! ALL %s settings will be deleted. This cannot be undone. 'OK' to delete, 'Cancel' to stop", 'gravityforms' ), __( $this->get_short_title() ) );
5326:     }
5327:     /**
5328:      * Not intended to be overridden or called directly by Add-Ons.
5329:      *
5330:      * @ignore
5331:      */
5332:     public function maybe_uninstall() {
5333:         if ( rgpost( 'uninstall' ) ) {
5334:             check_admin_referer( 'uninstall', 'gf_addon_uninstall' );
5335: 
5336:             return $this->uninstall_addon();
5337:         }
5338: 
5339:         return false;
5340:     }
5341: 
5342:     /**
5343:      * Removes all settings and deactivates the Add-On.
5344:      *
5345:      * Not intended to be overridden or called directly by Add-Ons.
5346:      *
5347:      * @ignore
5348:      */
5349:     public function uninstall_addon() {
5350: 
5351:         if ( ! $this->current_user_can_uninstall() ) {
5352:             die( esc_html__( "You don't have adequate permission to uninstall this add-on: " . $this->_title, 'gravityforms' ) );
5353:         }
5354: 
5355:         $continue = $this->uninstall();
5356:         if ( false === $continue ) {
5357:             return false;
5358:         }
5359: 
5360:         global $wpdb;
5361:         $lead_meta_table = GFFormsModel::get_lead_meta_table_name();
5362: 
5363:         $forms        = GFFormsModel::get_forms();
5364:         $all_form_ids = array();
5365: 
5366:         // remove entry meta
5367:         foreach ( $forms as $form ) {
5368:             $all_form_ids[] = $form->id;
5369:             $entry_meta     = $this->get_entry_meta( array(), $form->id );
5370:             if ( is_array( $entry_meta ) ) {
5371:                 foreach ( array_keys( $entry_meta ) as $meta_key ) {
5372:                     $sql = $wpdb->prepare( "DELETE from $lead_meta_table WHERE meta_key=%s", $meta_key );
5373:                     $wpdb->query( $sql );
5374:                 }
5375:             }
5376:         }
5377: 
5378:         //remove form settings
5379:         if ( ! empty( $all_form_ids ) ) {
5380:             $form_metas = GFFormsModel::get_form_meta_by_id( $all_form_ids );
5381:             require_once( GFCommon::get_base_path() . '/form_detail.php' );
5382:             foreach ( $form_metas as $form_meta ) {
5383:                 if ( isset( $form_meta[ $this->_slug ] ) ) {
5384:                     unset( $form_meta[ $this->_slug ] );
5385:                     $form_json = json_encode( $form_meta );
5386:                     GFFormDetail::save_form_info( $form_meta['id'], addslashes( $form_json ) );
5387:                 }
5388:             }
5389:         }
5390: 
5391:         //removing options
5392:         delete_option( 'gravityformsaddon_' . $this->_slug . '_settings' );
5393:         delete_option( 'gravityformsaddon_' . $this->_slug . '_app_settings' );
5394:         delete_option( 'gravityformsaddon_' . $this->_slug . '_version' );
5395: 
5396: 
5397:         //Deactivating plugin
5398:         deactivate_plugins( $this->_path );
5399:         update_option( 'recently_activated', array( $this->_path => time() ) + (array) get_option( 'recently_activated' ) );
5400: 
5401:         return true;
5402: 
5403:     }
5404: 
5405:     /**
5406:      * Called when the user chooses to uninstall the Add-On  - after permissions have been checked and before removing
5407:      * all Add-On settings and Form settings.
5408:      *
5409:      * Override this method to perform additional functions such as dropping database tables.
5410:      *
5411:      *
5412:      * Return false to cancel the uninstall request.
5413:      */
5414:     public function uninstall() {
5415:         return true;
5416:     }
5417: 
5418:     //--------------  Enforce minimum GF version  ---------------------------------------------------
5419: 
5420:     /**
5421:      * Target for the after_plugin_row action hook. Checks whether the current version of Gravity Forms
5422:      * is supported and outputs a message just below the plugin info on the plugins page.
5423:      *
5424:      * Not intended to be overridden or called directly by Add-Ons.
5425:      *
5426:      * @ignore
5427:      */
5428:     public function plugin_row() {
5429:         if ( ! self::is_gravityforms_supported( $this->_min_gravityforms_version ) ) {
5430:             $message = $this->plugin_message();
5431:             self::display_plugin_message( $message, true );
5432:         }
5433:     }
5434: 
5435:     /**
5436:      * Returns the message that will be displayed if the current version of Gravity Forms is not supported.
5437:      *
5438:      * Override this method to display a custom message.
5439:      */
5440:     public function plugin_message() {
5441:         $message = sprintf( esc_html__( 'Gravity Forms %s is required. Activate it now or %spurchase it today!%s', 'gravityforms' ), $this->_min_gravityforms_version, "<a href='https://www.gravityforms.com'>", '</a>' );
5442: 
5443:         return $message;
5444:     }
5445: 
5446:     /**
5447:      * Formats and outs a message for the plugin row.
5448:      *
5449:      * Not intended to be overridden or called directly by Add-Ons.
5450:      *
5451:      * @ignore
5452:      *
5453:      * @param      $message
5454:      * @param bool $is_error
5455:      */
5456:     public static function display_plugin_message( $message, $is_error = false ) {
5457:         $style = $is_error ? 'style="background-color: #ffebe8;"' : '';
5458:         echo '</tr><tr class="plugin-update-tr"><td colspan="5" class="plugin-update"><div class="update-message" ' . $style . '>' . $message . '</div></td>';
5459:     }
5460: 
5461:     //--------------- Logging -------------------------------------------------------------
5462: 
5463:     /**
5464:      * Writes an error message to the Gravity Forms log. Requires the Gravity Forms logging Add-On.
5465:      *
5466:      * Not intended to be overridden by Add-Ons.
5467:      *
5468:      * @ignore
5469:      */
5470:     public function log_error( $message ) {
5471:         if ( class_exists( 'GFLogging' ) ) {
5472:             GFLogging::include_logger();
5473:             GFLogging::log_message( $this->_slug, $message, KLogger::ERROR );
5474:         }
5475:     }
5476: 
5477:     /**
5478:      * Writes an error message to the Gravity Forms log. Requires the Gravity Forms logging Add-On.
5479:      *
5480:      * Not intended to be overridden by Add-Ons.
5481:      *
5482:      * @ignore
5483:      */
5484:     public function log_debug( $message ) {
5485:         if ( class_exists( 'GFLogging' ) ) {
5486:             GFLogging::include_logger();
5487:             GFLogging::log_message( $this->_slug, $message, KLogger::DEBUG );
5488:         }
5489:     }
5490:     
5491:     //--------------- Locking ------------------------------------------------------------
5492: 
5493:     /**
5494:      * Returns the configuration for locking
5495:      *
5496:      * e.g.
5497:      *
5498:      *  array(
5499:      *     "object_type" => 'contact',
5500:      *     "capabilities" => array("gravityforms_contacts_edit_contacts"),
5501:      *     "redirect_url" => admin_url("admin.php?page=gf_contacts"),
5502:      *     "edit_url" => admin_url(sprintf("admin.php?page=gf_contacts&id=%d", $contact_id)),
5503:      *     "strings" => $strings
5504:      *     );
5505:      *
5506:      * Override this method to implement locking
5507:      */
5508:     public function get_locking_config() {
5509:         return array();
5510:     }
5511: 
5512: 
5513:     /**
5514:      * Returns TRUE if the current page is the edit page. Otherwise, returns FALSE
5515:      *
5516:      * Override this method to implement locking on the edit page.
5517:      */
5518:     public function is_locking_edit_page() {
5519:         return false;
5520:     }
5521: 
5522:     /**
5523:      * Returns TRUE if the current page is the list page. Otherwise, returns FALSE
5524:      *
5525:      * Override this method to display locking info on the list page.
5526:      */
5527:     public function is_locking_list_page() {
5528:         return false;
5529:     }
5530: 
5531:     /**
5532:      * Returns TRUE if the current page is the view page. Otherwise, returns FALSE
5533:      *
5534:      * Override this method to display locking info on the view page.
5535:      */
5536:     public function is_locking_view_page() {
5537:         return false;
5538:     }
5539: 
5540:     /**
5541:      * Returns the ID of the object to be locked. E.g. Form ID
5542:      *
5543:      * Override this method to implement locking
5544:      */
5545:     public function get_locking_object_id() {
5546:         return 0;
5547:     }
5548: 
5549:     /**
5550:      * Outputs information about the user currently editing the specified object
5551:      *
5552:      * @param int  $object_id The Object ID
5553:      * @param bool $echo      Whether to echo
5554:      *
5555:      * @return string The markup for the lock info
5556:      */
5557:     public function lock_info( $object_id, $echo = true ) {
5558:         $gf_locking = new GFAddonLocking( $this->get_locking_config(), $this );
5559:         $lock_info  = $gf_locking->lock_info( $object_id, false );
5560:         if ( $echo ) {
5561:             echo $lock_info;
5562:         }
5563: 
5564:         return $lock_info;
5565:     }
5566: 
5567:     /**
5568:      * Outputs class for the row for the specified Object ID on the list page.
5569:      *
5570:      * @param int  $object_id The object ID
5571:      * @param bool $echo      Whether to echo
5572:      *
5573:      * @return string The markup for the class
5574:      */
5575:     public function list_row_class( $object_id, $echo = true ) {
5576:         $gf_locking = new GFAddonLocking( $this->get_locking_config(), $this );
5577:         $class      = $gf_locking->list_row_class( $object_id, false );
5578:         if ( $echo ) {
5579:             echo $class;
5580:         }
5581: 
5582:         return $class;
5583:     }
5584: 
5585:     /**
5586:      * Checked whether an object is locked
5587:      *
5588:      * @param int|mixed $object_id The object ID
5589:      *
5590:      * @return bool
5591:      */
5592:     public function is_object_locked( $object_id ) {
5593:         $gf_locking = new GFAddonLocking( $this->get_locking_config(), $this );
5594: 
5595:         return $gf_locking->is_locked( $object_id );
5596:     }
5597: 
5598:     //------------- Field Value Retrieval -------------------------------------------------
5599: 
5600:     /**
5601:      * Returns the value of the mapped field.
5602:      *
5603:      * @param string $setting_name
5604:      * @param array $form
5605:      * @param array $entry
5606:      * @param mixed $settings
5607:      *
5608:      * @return string
5609:      */
5610:     public function get_mapped_field_value( $setting_name, $form, $entry, $settings = false ) {
5611: 
5612:         $field_id = $this->get_setting( $setting_name, '', $settings );
5613: 
5614:         return $this->get_field_value( $form, $entry, $field_id );
5615:     }
5616: 
5617:     /**
5618:      * Returns the value of the selected field.
5619:      *
5620:      * @access private
5621:      *
5622:      * @param array $form
5623:      * @param array $entry
5624:      * @param string $field_id
5625:      *
5626:      * @return string field value
5627:      */
5628:     public function get_field_value( $form, $entry, $field_id ) {
5629: 
5630:         $field_value = '';
5631: 
5632:         switch ( strtolower( $field_id ) ) {
5633: 
5634:             case 'form_title':
5635:                 $field_value = rgar( $form, 'title' );
5636:                 break;
5637: 
5638:             case 'date_created':
5639:                 $date_created = rgar( $entry, strtolower( $field_id ) );
5640:                 if ( empty( $date_created ) ) {
5641:                     //the date created may not yet be populated if this function is called during the validation phase and the entry is not yet created
5642:                     $field_value = gmdate( 'Y-m-d H:i:s' );
5643:                 } else {
5644:                     $field_value = $date_created;
5645:                 }
5646:                 break;
5647: 
5648:             case 'ip':
5649:             case 'source_url':
5650:             case 'id':
5651:                 $field_value = rgar( $entry, strtolower( $field_id ) );
5652:                 break;
5653: 
5654:             default:
5655:                 $field = GFFormsModel::get_field( $form, $field_id );
5656: 
5657:                 if ( is_object( $field ) ) {
5658:                     $is_integer = $field_id == intval( $field_id );
5659:                     $input_type = $field->get_input_type();
5660: 
5661:                     if ( $is_integer && $input_type == 'address' ) {
5662: 
5663:                         $field_value = $this->get_full_address( $entry, $field_id );
5664: 
5665:                     } elseif ( $is_integer && $input_type == 'name' ) {
5666: 
5667:                         $field_value = $this->get_full_name( $entry, $field_id );
5668: 
5669:                     } elseif ( is_callable( array( $this, "get_{$input_type}_field_value" ) ) ) {
5670: 
5671:                         $field_value = call_user_func( array( $this, "get_{$input_type}_field_value" ), $entry, $field_id, $field );
5672: 
5673:                     } else {
5674: 
5675:                         $field_value = $field->get_value_export( $entry, $field_id );
5676: 
5677:                     }
5678:                 } else {
5679: 
5680:                     $field_value = rgar( $entry, $field_id );
5681: 
5682:                 }
5683: 
5684:         }
5685: 
5686:         /**
5687:          * A generic filter allowing the field value to be overridden. Form and field id modifiers supported.
5688:          *
5689:          * @param string $field_value The value to be overridden.
5690:          * @param array $form The Form currently being processed.
5691:          * @param array $entry The Entry currently being processed.
5692:          * @param string $field_id The ID of the Field currently being processed.
5693:          * @param string $slug The add-on slug e.g. gravityformsactivecampaign.
5694:          *
5695:          * @since 1.9.15.12
5696:          *
5697:          * @return string
5698:          */
5699:         $field_value = gf_apply_filters( array( 'gform_addon_field_value', $form['id'], $field_id ), $field_value, $form, $entry, $field_id, $this->_slug );
5700: 
5701:         return $this->maybe_override_field_value( $field_value, $form, $entry, $field_id );
5702:     }
5703: 
5704:     /**
5705:      * Enables use of the gform_SLUG_field_value filter to override the field value. Override this function to prevent the filter being used or to implement a custom filter.
5706:      *
5707:      * @param string $field_value
5708:      * @param array $form
5709:      * @param array $entry
5710:      * @param string $field_id
5711:      *
5712:      * @return string
5713:      */
5714:     public function maybe_override_field_value( $field_value, $form, $entry, $field_id ) {
5715:         /* Get Add-On slug */
5716:         $slug = str_replace( 'gravityforms', '', $this->_slug );
5717: 
5718:         return gf_apply_filters( array(
5719:             "gform_{$slug}_field_value",
5720:             $form['id'],
5721:             $field_id
5722:         ), $field_value, $form, $entry, $field_id );
5723:     }
5724: 
5725:     /**
5726:      * Returns the combined value of the specified Address field.
5727:      *
5728:      * @param array $entry
5729:      * @param string $field_id
5730:      *
5731:      * @return string
5732:      */
5733:     public function get_full_address( $entry, $field_id ) {
5734: 
5735:         return GF_Fields::get( 'address' )->get_value_export( $entry, $field_id );
5736:     }
5737: 
5738:     /**
5739:      * Returns the combined value of the specified Name field.
5740:      *
5741:      * @param array $entry
5742:      * @param string $field_id
5743:      *
5744:      * @return string
5745:      */
5746:     public function get_full_name( $entry, $field_id ) {
5747: 
5748:         return GF_Fields::get( 'name' )->get_value_export( $entry, $field_id );
5749:     }
5750: 
5751:     /**
5752:      * Returns the value of the specified List field.
5753:      *
5754:      * @param array $entry
5755:      * @param string $field_id
5756:      * @param GF_Field_List $field
5757:      *
5758:      * @return string
5759:      */
5760:     public function get_list_field_value( $entry, $field_id, $field ) {
5761: 
5762:         return $field->get_value_export( $entry, $field_id );
5763:     }
5764:     
5765:     /**
5766:      * Returns the field ID of the first field of the desired type.
5767:      * 
5768:      * @access public
5769:      * @param string $field_type
5770:      * @param int $subfield_id (default: null)
5771:      * @param int $form_id (default: null)
5772:      * @return string
5773:      */
5774:     public function get_first_field_by_type( $field_type, $subfield_id = null, $form_id = null, $return_first_only = true ) {
5775:         
5776:         /* Get the current form ID. */
5777:         if ( rgblank( $form_id ) ) {
5778:             
5779:             $form_id = rgget( 'id' );
5780:             
5781:         }
5782:         
5783:         /* Get the form. */
5784:         $form = GFAPI::get_form( $form_id );
5785:         
5786:         /* Get the request field type for the form. */
5787:         $fields = GFAPI::get_fields_by_type( $form, array( $field_type ) );
5788:         
5789:         if ( count( $fields ) == 0 || ( count( $fields ) > 1 && $return_first_only ) ) {
5790:             
5791:             return null;
5792:             
5793:         } else {
5794:             
5795:             if ( rgblank( $subfield_id ) ) {
5796:                 
5797:                 return $fields[0]->id;
5798:                 
5799:             } else {
5800:                 
5801:                 return $fields[0]->id . '.' . $subfield_id;
5802:                 
5803:             }
5804:             
5805:         }
5806:         
5807:     }
5808:     
5809:     //--------------- Notes ------------------
5810:     /**
5811:      * Override this function to specify a custom avatar (i.e. the payment gateway logo) for entry notes created by the Add-On
5812:      * @return  string - A fully qualified URL for the avatar
5813:      */
5814:     public function note_avatar() {
5815:         return false;
5816:     }
5817: 
5818:     public function notes_avatar( $avatar, $note ) {
5819:         if ( $note->user_name == $this->_short_title && empty( $note->user_id ) && $this->method_is_overridden( 'note_avatar', 'GFAddOn' ) ) {
5820:             $new_avatar = $this->note_avatar();
5821:         }
5822: 
5823:         return empty( $new_avatar ) ? $avatar : "<img alt='{$this->_short_title}' src='{$new_avatar}' class='avatar avatar-48' height='48' width='48' />";
5824:     }
5825: 
5826:     public function add_note( $entry_id, $note, $note_type = null ) {
5827: 
5828:         $user_id   = 0;
5829:         $user_name = $this->_short_title;
5830: 
5831:         GFFormsModel::add_note( $entry_id, $user_id, $user_name, $note, $note_type );
5832: 
5833:     }
5834: 
5835:     //--------------  Helper functions  ---------------------------------------------------
5836: 
5837:     protected final function method_is_overridden( $method_name, $base_class = 'GFAddOn' ) {
5838:         $reflector = new ReflectionMethod( $this, $method_name );
5839:         $name      = $reflector->getDeclaringClass()->getName();
5840: 
5841:         return $name !== $base_class;
5842:     }
5843:     
5844:     /**
5845:      * Returns the url of the root folder of the current Add-On.
5846:      *
5847:      * @param string $full_path Optional. The full path the the plugin file.
5848:      *
5849:      * @return string
5850:      */
5851:     public function get_base_url( $full_path = '' ) {
5852:         if ( empty( $full_path ) ) {
5853:             $full_path = $this->_full_path;
5854:         }
5855: 
5856:         return plugins_url( null, $full_path );
5857:     }
5858: 
5859:     /**
5860:      * Returns the url of the Add-On Framework root folder.
5861:      *
5862:      * @return string
5863:      */
5864:     final public static function get_gfaddon_base_url() {
5865:         return plugins_url( null, __FILE__ );
5866:     }
5867: 
5868:     /**
5869:      * Returns the physical path of the Add-On Framework root folder.
5870:      *
5871:      * @return string
5872:      */
5873:     final public static function get_gfaddon_base_path() {
5874:         return self::_get_base_path();
5875:     }
5876: 
5877:     /**
5878:      * Returns the physical path of the plugins root folder.
5879:      *
5880:      * @param string $full_path
5881:      *
5882:      * @return string
5883:      */
5884:     public function get_base_path( $full_path = '' ) {
5885:         if ( empty( $full_path ) ) {
5886:             $full_path = $this->_full_path;
5887:         }
5888:         $folder = basename( dirname( $full_path ) );
5889: 
5890:         return WP_PLUGIN_DIR . '/' . $folder;
5891:     }
5892: 
5893:     /**
5894:      * Returns the physical path of the Add-On Framework root folder
5895:      *
5896:      * @return string
5897:      */
5898:     private static function _get_base_path() {
5899:         $folder = basename( dirname( __FILE__ ) );
5900: 
5901:         return GFCommon::get_base_path() . '/includes/' . $folder;
5902:     }
5903: 
5904:     /**
5905:      * Returns the URL of the Add-On Framework root folder
5906:      *
5907:      * @return string
5908:      */
5909:     private static function _get_base_url() {
5910:         $folder = basename( dirname( __FILE__ ) );
5911: 
5912:         return GFCommon::get_base_url() . '/includes/' . $folder;
5913:     }
5914: 
5915:     /**
5916:      * Checks whether the Gravity Forms is installed.
5917:      *
5918:      * @return bool
5919:      */
5920:     public function is_gravityforms_installed() {
5921:         return class_exists( 'GFForms' );
5922:     }
5923: 
5924:     public function table_exists( $table_name ) {
5925:         
5926:         return GFCommon::table_exists( $table_name );
5927:         
5928:     }
5929: 
5930:     /**
5931:      * Checks whether the current version of Gravity Forms is supported
5932:      *
5933:      * @param $min_gravityforms_version
5934:      *
5935:      * @return bool|mixed
5936:      */
5937:     public function is_gravityforms_supported( $min_gravityforms_version = '' ) {
5938:         if ( isset( $this->_min_gravityforms_version ) && empty( $min_gravityforms_version ) ) {
5939:             $min_gravityforms_version = $this->_min_gravityforms_version;
5940:         }
5941: 
5942:         if ( empty( $min_gravityforms_version ) ) {
5943:             return true;
5944:         }
5945: 
5946:         if ( class_exists( 'GFCommon' ) ) {
5947:             $is_correct_version = version_compare( GFCommon::$version, $min_gravityforms_version, '>=' );
5948: 
5949:             return $is_correct_version;
5950:         } else {
5951:             return false;
5952:         }
5953:     }
5954: 
5955:     /**
5956:      * Returns this plugin's short title. Used to display the plugin title in small areas such as tabs
5957:      */
5958:     public function get_short_title() {
5959:         return isset( $this->_short_title ) ? $this->_short_title : $this->_title;
5960:     }
5961:     
5962:     /**
5963:      * Return this plugin's version.
5964:      *
5965:      * @since  2.0
5966:      * @access public
5967:      *
5968:      * @return string
5969:      */
5970:     public function get_version() {
5971:         return $this->_version;
5972:     }
5973: 
5974:     /**
5975:      * Returns the unescaped URL for the plugin settings tab associated with this plugin
5976:      *
5977:      */
5978:     public function get_plugin_settings_url() {
5979:         return add_query_arg( array( 'page' => 'gf_settings', 'subview' => $this->_slug ), admin_url( 'admin.php' ) );
5980:     }
5981: 
5982:     /**
5983:      * Returns the current form object based on the id query var. Otherwise returns false
5984:      */
5985:     public function get_current_form() {
5986: 
5987:         return rgempty( 'id', $_GET ) ? false : GFFormsModel::get_form_meta( rgget( 'id' ) );
5988:     }
5989: 
5990:     /**
5991:      * Returns TRUE if the current request is a postback, otherwise returns FALSE
5992:      */
5993:     public function is_postback() {
5994:         return is_array( $_POST ) && count( $_POST ) > 0;
5995:     }
5996: 
5997:     /**
5998:      * Returns TRUE if the settings "Save" button was pressed
5999:      */
6000:     public function is_save_postback() {
6001:         return ! rgempty( 'gform-settings-save' );
6002:     }
6003: 
6004:     /**
6005:      * Returns TRUE if the current page is the form editor page. Otherwise, returns FALSE
6006:      */
6007:     public function is_form_editor() {
6008: 
6009:         if ( rgget( 'page' ) == 'gf_edit_forms' && ! rgempty( 'id', $_GET ) && rgempty( 'view', $_GET ) ) {
6010:             return true;
6011:         }
6012: 
6013:         return false;
6014:     }
6015: 
6016:     /**
6017:      * Returns TRUE if the current page is the form list page. Otherwise, returns FALSE
6018:      */
6019:     public function is_form_list() {
6020: 
6021:         if ( rgget( 'page' ) == 'gf_edit_forms' && rgempty( 'id', $_GET ) && rgempty( 'view', $_GET ) ) {
6022:             return true;
6023:         }
6024: 
6025:         return false;
6026:     }
6027: 
6028:     /**
6029:      * Returns TRUE if the current page is the form settings page, or a specific form settings tab (specified by the $tab parameter). Otherwise returns FALSE
6030:      *
6031:      * @param string $tab - Specifies a specific form setting page/tab
6032:      *
6033:      * @return bool
6034:      */
6035:     public function is_form_settings( $tab = null ) {
6036: 
6037:         $is_form_settings = rgget( 'page' ) == 'gf_edit_forms' && rgget( 'view' ) == 'settings';
6038:         $is_tab           = $this->_tab_matches( $tab );
6039: 
6040:         if ( $is_form_settings && $is_tab ) {
6041:             return true;
6042:         } else {
6043:             return false;
6044:         }
6045:     }
6046: 
6047:     private function _tab_matches( $tabs ) {
6048:         if ( $tabs == null ) {
6049:             return true;
6050:         }
6051: 
6052:         if ( ! is_array( $tabs ) ) {
6053:             $tabs = array( $tabs );
6054:         }
6055: 
6056:         $current_tab = rgempty( 'subview', $_GET ) ? 'settings' : rgget( 'subview' );
6057: 
6058:         foreach ( $tabs as $tab ) {
6059:             if ( strtolower( $tab ) == strtolower( $current_tab ) ) {
6060:                 return true;
6061:             }
6062:         }
6063:     }
6064: 
6065:     /**
6066:      * Returns TRUE if the current page is the plugin settings main page, or a specific plugin settings tab (specified by the $tab parameter). Otherwise returns FALSE
6067:      *
6068:      * @param string $tab - Specifies a specific plugin setting page/tab.
6069:      *
6070:      * @return bool
6071:      */
6072:     public function is_plugin_settings( $tab = '' ) {
6073: 
6074:         $is_plugin_settings = rgget( 'page' ) == 'gf_settings';
6075:         $is_tab             = $this->_tab_matches( $tab );
6076: 
6077:         if ( $is_plugin_settings && $is_tab ) {
6078:             return true;
6079:         } else {
6080:             return false;
6081:         }
6082:     }
6083: 
6084:     /**
6085:      * Returns TRUE if the current page is the app settings main page, or a specific apps settings tab (specified by the $tab parameter). Otherwise returns FALSE
6086:      *
6087:      * @param string $tab - Specifies a specific app setting page/tab.
6088:      *
6089:      * @return bool
6090:      */
6091:     public function is_app_settings( $tab = '' ) {
6092: 
6093:         $is_app_settings = rgget( 'page' ) == $this->_slug . '_settings';
6094:         $is_tab          = $this->_tab_matches( $tab );
6095: 
6096:         if ( $is_app_settings && $is_tab ) {
6097:             return true;
6098:         } else {
6099:             return false;
6100:         }
6101:     }
6102: 
6103:     /**
6104:      * Returns TRUE if the current page is the plugin page. Otherwise returns FALSE
6105:      * @return bool
6106:      */
6107:     public function is_plugin_page() {
6108: 
6109:         return strtolower( rgget( 'page' ) ) == strtolower( $this->_slug );
6110:     }
6111: 
6112:     /**
6113:      * Returns TRUE if the current page is the entry view page. Otherwise, returns FALSE
6114:      * @return bool
6115:      */
6116:     public function is_entry_view() {
6117:         if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'entry' && ( ! isset( $_POST['screen_mode'] ) || rgpost( 'screen_mode' ) == 'view' ) ) {
6118:             return true;
6119:         }
6120: 
6121:         return false;
6122:     }
6123: 
6124:     /**
6125:      * Returns TRUE if the current page is the entry edit page. Otherwise, returns FALSE
6126:      * @return bool
6127:      */
6128:     public function is_entry_edit() {
6129:         if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'entry' && rgpost( 'screen_mode' ) == 'edit' ) {
6130:             return true;
6131:         }
6132: 
6133:         return false;
6134:     }
6135: 
6136:     public function is_entry_list() {
6137:         if ( rgget( 'page' ) == 'gf_entries' && ( rgget( 'view' ) == 'entries' || rgempty( 'view', $_GET ) ) ) {
6138:             return true;
6139:         }
6140: 
6141:         return false;
6142:     }
6143: 
6144:     /**
6145:      * Returns TRUE if the current page is the results page. Otherwise, returns FALSE
6146:      */
6147:     public function is_results() {
6148:         if ( rgget( 'page' ) == 'gf_entries' && rgget( 'view' ) == 'gf_results_' . $this->_slug ) {
6149:             return true;
6150:         }
6151: 
6152:         return false;
6153:     }
6154: 
6155:     /**
6156:      * Returns TRUE if the current page is the print page. Otherwise, returns FALSE
6157:      */
6158:     public function is_print() {
6159:         if ( rgget( 'gf_page' ) == 'print-entry' ) {
6160:             return true;
6161:         }
6162: 
6163:         return false;
6164:     }
6165: 
6166:     /**
6167:      * Returns TRUE if the current page is the preview page. Otherwise, returns FALSE
6168:      */
6169:     public function is_preview() {
6170:         if ( rgget( 'gf_page' ) == 'preview' ) {
6171:             return true;
6172:         }
6173: 
6174:         return false;
6175:     }
6176: 
6177:     public function has_deprecated_elements() {
6178:         $deprecated = GFAddOn::get_all_deprecated_protected_methods( get_class( $this ) );
6179:         if ( ! empty( $deprecated ) ) {
6180:             return true;
6181:         }
6182: 
6183:         return false;
6184:     }
6185: 
6186:     public static function get_all_deprecated_protected_methods($add_on_class_name = ''){
6187:         $deprecated = array();
6188:         $deprecated = array_merge( $deprecated, self::get_deprecated_protected_methods_for_base_class( 'GFAddOn', $add_on_class_name )) ;
6189:         $deprecated = array_merge( $deprecated, self::get_deprecated_protected_methods_for_base_class( 'GFFeedAddOn', $add_on_class_name ) ) ;
6190:         $deprecated = array_merge( $deprecated, self::get_deprecated_protected_methods_for_base_class( 'GFPaymentAddOn', $add_on_class_name ) ) ;
6191:         return $deprecated;
6192:     }
6193: 
6194:     public static function get_deprecated_protected_methods_for_base_class( $base_class_name, $add_on_class_name = '' ) {
6195:         $deprecated = array();
6196: 
6197:         if ( ! class_exists( $base_class_name ) ) {
6198:             return $deprecated;
6199:         }
6200: 
6201:         $base_class_names = array(
6202:             'GFAddOn',
6203:             'GFFeedAddOn',
6204:             'GFPaymentAddOn'
6205:         );
6206: 
6207:         $base_class = new ReflectionClass( $base_class_name );
6208: 
6209:         $classes = empty($add_on_class_name) ? get_declared_classes() : array( $add_on_class_name );
6210: 
6211:         foreach ( $classes as $class ) {
6212:             if ( ! is_subclass_of( $class, $base_class_name ) || in_array( $class, $base_class_names ) ) {
6213:                 continue;
6214:             }
6215: 
6216:             $add_on_class   = new ReflectionClass( $class );
6217:             $add_on_methods = $add_on_class->getMethods( ReflectionMethod::IS_PROTECTED );
6218:             foreach ( $add_on_methods as $method ) {
6219:                 $method_name               = $method->getName();
6220:                 $base_has_method           = $base_class->hasMethod( $method_name );
6221:                 $is_declared_by_base_class = $base_has_method && $base_class->getMethod( $method_name )->getDeclaringClass()->getName() == $base_class_name;
6222:                 $is_overridden             = $method->getDeclaringClass()->getName() == $class;
6223:                 if ( $is_declared_by_base_class && $is_overridden ) {
6224:                     $deprecated[] = $class . '::' . $method_name;
6225:                 }
6226:             }
6227:         }
6228:         return $deprecated;
6229:     }
6230: 
6231:     public function maybe_wp_kses( $html, $allowed_html = 'post', $allowed_protocols = array() ) {
6232:         return GFCommon::maybe_wp_kses( $html, $allowed_html, $allowed_protocols );
6233:     }
6234: 
6235:     /**
6236:      * Returns the slug for the add-on.
6237:      *
6238:      * @since 2.0
6239:      */
6240:     public function get_slug() {
6241:         return $this->_slug;
6242:     }
6243: 
6244:     /**
6245:      * Returns the path for the add-on.
6246:      *
6247:      * @since 2.2
6248:      */
6249:     public function get_path() {
6250:         return $this->_path;
6251:     }
6252: 
6253:     /**
6254:      * Get all or a specific capability for Add-On.
6255:      *
6256:      * @since  2.2.5.27
6257:      * @access public
6258:      *
6259:      * @param string $capability Capability to return.
6260:      *
6261:      * @return string|array
6262:      */
6263:     public function get_capabilities( $capability = '' ) {
6264: 
6265:         if ( rgblank( $capability ) ) {
6266:             return $this->_capabilities;
6267:         }
6268: 
6269:         return isset( $this->{'_capabilities_' . $capability} ) ? $this->{'_capabilities_' . $capability} : array();
6270: 
6271:     }
6272: 
6273:     /**
6274:      * Initializing translations.
6275:      *
6276:      * @since 2.0.7
6277:      */
6278:     public function load_text_domain() {
6279:         GFCommon::load_gf_text_domain( $this->_slug, plugin_basename( dirname( $this->_full_path ) ) );
6280:     }
6281: 
6282:     /***
6283:      * Determines if the current user has the proper cabalities to uninstall this add-on
6284:      * Add-ons that have been network activated can only be uninstalled by a network admin.
6285:      *
6286:      * @since 2.3.1.12
6287:      * @access public
6288:      *
6289:      * @return bool True if current user can uninstall this add-on. False otherwise
6290:      */
6291:     public function current_user_can_uninstall(){
6292: 
6293:         return GFCommon::current_user_can_uninstall( $this->_capabilities_uninstall, $this->_path );
6294: 
6295:     }
6296: }
6297: 
Gravity Forms API API documentation generated by ApiGen 2.8.0