This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,59 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GFAddonLocking extends GFLocking {
protected $_strings;
/* @var GFAddOn $_addon */
protected $_addon;
/**
* e.g.
*
* array(
* "object_type" => 'contact',
* "capabilities" => array("gravityforms_contacts_edit_contacts"),
* "redirect_url" => admin_url("admin.php?page=gf_contacts"),
* "edit_url" => admin_url(sprintf("admin.php?page=gf_contacts&id=%d", $contact_id)),
* "strings" => $strings
* );
*
* @param array $config
* @param GFAddOn $addon
*/
public function __construct( $config, $addon ) {
$this->_addon = $addon;
$capabilities = isset( $config['capabilities'] ) ? $config['capabilities'] : array();
$redirect_url = isset( $config['redirect_url'] ) ? $config['redirect_url'] : '';
$edit_url = isset( $config['edit_url'] ) ? $config['edit_url'] : '';
$object_type = isset( $config['object_type'] ) ? $config['object_type'] : '';
$this->_strings = isset( $config['strings'] ) ? $config['strings'] : array();
parent::__construct( $object_type, $redirect_url, $edit_url, $capabilities );
}
public function get_strings() {
return array_merge( parent::get_strings(), $this->_strings );
}
protected function is_edit_page() {
return $this->_addon->is_locking_edit_page();
}
protected function is_list_page() {
return $this->_addon->is_locking_list_page();
}
protected function is_view_page() {
return $this->_addon->is_locking_view_page();
}
protected function get_object_id() {
return $this->_addon->get_locking_object_id();
}
protected function is_object_locked( $object_id ) {
return $this->is_object_locked( $object_id );
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,361 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
class GFAutoUpgrade {
protected $_version;
protected $_min_gravityforms_version;
protected $_slug;
protected $_title;
protected $_full_path;
protected $_path;
protected $_url;
protected $_is_gravityforms_supported;
public function __construct( $slug, $version, $min_gravityforms_version, $title, $full_path, $path, $url, $is_gravityforms_supported ) {
$this->_slug = $slug;
$this->_version = $version;
$this->_min_gravityforms_version = $min_gravityforms_version;
$this->_title = $title;
$this->_full_path = $full_path;
$this->_path = $path;
$this->_url = $url;
$this->_is_gravityforms_supported = $is_gravityforms_supported;
add_action( 'init', array( $this, 'init' ) );
}
public function init() {
if ( is_admin() ) {
GFCommon::load_gf_text_domain();
add_action( 'install_plugins_pre_plugin-information', array( $this, 'display_changelog' ), 9 );
add_action( 'gform_after_check_update', array( $this, 'flush_version_info' ) );
add_action( 'gform_updates', array( $this, 'display_updates' ) );
add_filter( 'gform_updates_list', array( $this, 'get_update_info' ) );
if ( RG_CURRENT_PAGE == 'plugins.php' ) {
add_action( 'after_plugin_row_' . $this->_path, array( $this, 'rg_plugin_row' ) );
} elseif ( in_array( RG_CURRENT_PAGE, array( 'admin-ajax.php' ) ) ) {
add_action( 'wp_ajax_gf_get_changelog', array( $this, 'ajax_display_changelog' ) );
}
}
// Check for updates. The check might not run the admin context. E.g. from WP-CLI.
add_filter( 'transient_update_plugins', array( $this, 'check_update' ) );
add_filter( 'site_transient_update_plugins', array( $this, 'check_update' ) );
add_filter( 'auto_update_plugin', function ( $update, $item ) {
return GFForms::maybe_auto_update( $update, $item, $this->_slug, $this->_version );
}, 10, 2 );
// ManageWP premium update filters
add_filter( 'mwp_premium_update_notification', array( $this, 'premium_update_push' ) );
add_filter( 'mwp_premium_perform_update', array( $this, 'premium_update' ) );
}
/**
* Displays messages for the Gravity Forms listing on the Plugins page.
*
* Displays if Gravity Forms isn't supported.
*
* @since Unknown
* @since 2.4.15 Update to improve multisite updates.
*/
public function rg_plugin_row() {
if ( ! $this->_is_gravityforms_supported ) {
$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>' );
GFAddOn::display_plugin_message( $message, 'error' );
}
}
//Integration with ManageWP
public function premium_update_push( $premium_update ) {
if ( ! function_exists( 'get_plugin_data' ) ) {
include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
}
$update = $this->get_version_info( $this->_slug );
if ( rgar( $update, 'is_valid_key' ) == true && version_compare( $this->_version, $update['version'], '<' ) ) {
$plugin_data = get_plugin_data( $this->_full_path );
$plugin_data['type'] = 'plugin';
$plugin_data['slug'] = $this->_path;
$plugin_data['new_version'] = isset( $update['version'] ) ? $update['version'] : false;
$premium_update[] = $plugin_data;
}
return $premium_update;
}
//Integration with ManageWP
public function premium_update( $premium_update ) {
if ( ! function_exists( 'get_plugin_data' ) ) {
include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
}
$update = $this->get_version_info( $this->_slug );
if ( rgar( $update, 'is_valid_key' ) == true && version_compare( $this->_version, $update['version'], '<' ) ) {
$plugin_data = get_plugin_data( $this->_full_path );
$plugin_data['slug'] = $this->_path;
$plugin_data['type'] = 'plugin';
$plugin_data['url'] = isset( $update['url'] ) ? $update['url'] : false; // OR provide your own callback function for managing the update
array_push( $premium_update, $plugin_data );
}
return $premium_update;
}
public function flush_version_info() {
$this->set_version_info( $this->_slug, false );
}
private function set_version_info( $plugin_slug, $version_info ) {
if ( function_exists( 'set_site_transient' ) ) {
set_site_transient( $plugin_slug . '_version', $version_info, 60 * 60 * 12 );
} else {
set_transient( $plugin_slug . '_version', $version_info, 60 * 60 * 12 );
}
}
public function check_update( $option ) {
if ( empty( $option ) ) {
return $option;
}
$key = $this->get_key();
$version_info = $this->get_version_info( $this->_slug );
if ( rgar( $version_info, 'is_error' ) == '1' ) {
return $option;
}
if ( empty( $option->response[ $this->_path ] ) ) {
$option->response[ $this->_path ] = new stdClass();
}
$new_version = rgar( $version_info, 'version', '0' );
$plugin = array(
'plugin' => $this->_path,
'url' => $this->_url,
'slug' => $this->_slug,
'package' => $version_info['url'] ? str_replace( '{KEY}', $key, $version_info['url'] ) : '',
'new_version' => $new_version,
'id' => '0',
'minimum_requirements' => $version_info['minimum_requirements'],
'version_latest' => $version_info['version_latest']
);
//Empty response means that the key is invalid. Do not queue for upgrade
if ( ! rgar( $version_info, 'is_valid_key' ) || version_compare( $this->_version, $new_version, '>=' ) ) {
unset( $option->response[ $this->_path ] );
$option->no_update[ $this->_path ] = (object) $plugin;
} else {
$option->response[ $this->_path ] = (object) $plugin;
}
// Check minimum requirements. If not met, remove from response and add to no_update.
$minimum_requirements_evaluation_result = GFCommon::evaluate_minimum_requirements(
is_array( $version_info['minimum_requirements'] ?? null )
? $version_info['minimum_requirements']
: []
);
if ( $minimum_requirements_evaluation_result['block'] == true ) {
if ( isset( $option->response[ $this->_path ] ) ) {
unset( $option->response[ $this->_path ] );
}
$option->no_update[ $this->_path ] = (object) $plugin;
}
return $option;
}
// Displays current version details on plugins page and updates page
public function display_changelog() {
if ( isset( $_REQUEST['plugin'] ) && $_REQUEST['plugin'] != $this->_slug ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
$change_log = $this->get_changelog();
echo $change_log; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
exit;
}
/**
* Get changelog with admin-ajax.php in GFForms::maybe_display_update_notification().
*
* @since 2.4.15
*/
public function ajax_display_changelog() {
check_admin_referer();
$this->display_changelog();
}
private function get_changelog() {
$key = $this->get_key();
$body = "key={$key}";
$options = array( 'method' => 'POST', 'timeout' => 3, 'body' => $body );
$options['headers'] = array(
'Content-Type' => 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' ),
'Content-Length' => strlen( $body ),
'User-Agent' => 'WordPress/' . get_bloginfo( 'version' ),
);
$raw_response = GFCommon::post_to_manager( 'changelog.php', $this->get_remote_request_params( $this->_slug, $key, $this->_version ), $options );
if ( is_wp_error( $raw_response ) || 200 != $raw_response['response']['code'] ) {
$text = sprintf( esc_html__( 'Oops!! Something went wrong.%sPlease try again or %scontact us%s.', 'gravityforms' ), '<br/>', "<a href='" . esc_attr( GFCommon::get_support_url() ). "'>", '</a>' );
} else {
$text = $raw_response['body'];
if ( substr( $text, 0, 10 ) != '<!--GFM-->' ) {
$text = '';
}
}
return stripslashes( $text );
}
private function get_version_info( $offering, $use_cache = true ) {
$version_info = GFCommon::get_version_info( $use_cache );
$info = array(
'is_valid_key' => rgar( $version_info, 'is_valid_key' ),
'version' => rgars( $version_info, "offerings/{$offering}/version" ),
'url' => rgars( $version_info, "offerings/{$offering}/url" ),
'is_available' => rgars( $version_info, "offerings/{$offering}/is_available" ),
'minimum_requirements' => rgars( $version_info, "offerings/{$offering}/minimum_requirements" ),
'version_latest' => rgars( $version_info, "offerings/{$offering}/version_latest" ),
);
return $info;
}
private function get_remote_request_params( $offering, $key, $version ) {
global $wpdb;
return sprintf( 'of=%s&key=%s&v=%s&wp=%s&php=%s&mysql=%s', urlencode( $offering ), urlencode( $key ), urlencode( $version ), urlencode( get_bloginfo( 'version' ) ), urlencode( phpversion() ), urlencode( GFCommon::get_db_version() ) );
}
private function get_key() {
if ( $this->_is_gravityforms_supported ) {
return GFCommon::get_key();
} else {
return '';
}
}
public function get_update_info( $updates ) {
$force_check = rgget( 'force-check' ) == 1;
$version_info = $this->get_version_info( $this->_slug, ! $force_check );
$plugin_file = $this->_path;
$upgrade_url = wp_nonce_url( 'update.php?action=upgrade-plugin&amp;plugin=' . urlencode( $plugin_file ), 'upgrade-plugin_' . $plugin_file );
if ( ! rgar( $version_info, 'is_valid_key' ) ) {
$version_icon = 'dashicons-no';
$version_message = sprintf(
'<p>%s</p>',
sprintf(
esc_html( '%sRegister%s your copy of Gravity Forms to receive access to automatic updates and support. Need a license key? %sPurchase one now%s.', 'gravityforms' ),
'<a href="admin.php?page=gf_settings">',
'</a>',
'<a href="https://www.gravityforms.com">',
'</a>'
)
);
} elseif ( version_compare( $this->_version, $version_info['version'], '<' ) ) {
$details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . urlencode( $this->_slug ) . '&section=changelog&TB_iframe=true&width=600&height=800' );
$message_link_text = sprintf( esc_html__( 'View version %s details', 'gravityforms' ), $version_info['version'] );
$message_link = sprintf( '<a href="%s" class="thickbox" title="%s">%s</a>', esc_url( $details_url ), esc_attr( $this->_title ), $message_link_text );
$message = sprintf( esc_html__( 'There is a new version of %1$s available. %s.', 'gravityforms' ), $this->_title, $message_link );
$version_icon = 'dashicons-no';
$version_message = $message;
} else {
$version_icon = 'dashicons-yes';
$version_message = sprintf( esc_html__( 'Your version of %s is up to date.', 'gravityforms' ), $this->_title );
}
$updates[] = array(
'name' => esc_html( $this->_title ),
'is_valid_key' => rgar( $version_info, 'is_valid_key' ),
'path' => $this->_path,
'slug' => $this->_slug,
'latest_version' => $version_info['version'],
'installed_version' => $this->_version,
'upgrade_url' => $upgrade_url,
'download_url' => $version_info['url'],
'version_icon' => $version_icon,
'version_message' => $version_message,
);
return $updates;
}
public function display_updates() {
?>
<div class="wrap <?php echo esc_attr( GFCommon::get_browser_class() ); ?>">
<h2><?php esc_html_e( $this->_title ); ?></h2>
<?php
$force_check = rgget( 'force-check' ) == 1;
$version_info = $this->get_version_info( $this->_slug, ! $force_check );
if ( ! rgar( $version_info, 'is_valid_key' ) ) {
?>
<div class="gf_update_expired alert_red">
<?php printf( esc_html__( '%sRegister%s your copy of Gravity Forms to receive access to automatic updates and support. Need a license key? %sPurchase one now%s.', 'gravityforms' ), '<a href="admin.php?page=gf_settings">','</a>','<a href="https://www.gravityforms.com">', '</a>' ); ?>
</div>
<?php
} elseif ( version_compare( $this->_version, $version_info['version'], '<' ) ) {
if ( rgar( $version_info, 'is_valid_key' ) ) {
$plugin_file = $this->_path;
$upgrade_url = wp_nonce_url( 'update.php?action=upgrade-plugin&amp;plugin=' . urlencode( $plugin_file ), 'upgrade-plugin_' . $plugin_file );
$details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . urlencode( $this->_slug ) . '&section=changelog&TB_iframe=true&width=600&height=800' );
$message_link_text = sprintf( esc_html__( 'View version %s details', 'gravityforms' ), $version_info['version'] );
$message_link = sprintf( '<a href="%s" class="thickbox" title="%s">%s</a>', esc_url( $details_url ), esc_attr( $this->_title ), $message_link_text );
$message = sprintf( esc_html__( 'There is a new version of %1$s available. %s.', 'gravityforms' ), $this->_title, $message_link );
?>
<div class="gf_update_outdated alert_yellow">
<?php echo $message . ' <p>' . sprintf( esc_html__( 'You can update to the latest version automatically or download the update and install it manually. %sUpdate Automatically%s %sDownload Update%s', 'gravityforms' ), "</p><a class='button-primary' href='{$upgrade_url}'>", '</a>', "&nbsp;<a class='button' href='{$version_info['url']}'>", '</a>' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped?>
</div>
<?php
}
} else {
?>
<div class="gf_update_current alert_green">
<?php printf( esc_html__( 'Your version of %s is up to date.', 'gravityforms' ), esc_html( $this->_title ) ); ?>
</div>
<?php
}
?>
</div>
<?php
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,429 @@
<?php
use Gravity_Forms\Gravity_Forms\Async\GF_Background_Process;
if ( ! class_exists( 'GFForms' ) ) {
die();
}
if ( ! class_exists( 'Gravity_Forms\Gravity_Forms\Async\GF_Background_Process' ) ) {
require_once GF_PLUGIN_DIR_PATH . 'includes/async/class-gf-background-process.php';
}
/**
* GF_Feed_Processor Class.
*
* @since 2.2
*/
class GF_Feed_Processor extends GF_Background_Process {
/**
* Contains instances of this class, if available.
*
* @since 2.2
* @since 2.9.25 Changed to an array.
*
* @var self[] $_instances If available, contains instances of this class.
*/
private static $_instances = array();
/**
* The action name.
*
* @since 2.2
* @access protected
* @var string
*/
protected $action = 'gf_feed_processor';
/**
* Indicates if the task uses an array that supports the attempts key.
*
* @since 2.9.9
*
* @var bool
*/
protected $supports_attempts = true;
/**
* Null or the add-on instance.
*
* @since 2.9.25
*
* @var null|GFFeedAddOn
*/
protected $add_on = null;
/**
* Instantiates the class.
*
* @since 2.9.25
*
* @param bool|array $allowed_batch_data_classes Optional. Array of class names that can be unserialized. Default true (any class).
* @param null|GFFeedAddOn $add_on Optional. The add-on instance.
*/
public function __construct( $allowed_batch_data_classes = true, $add_on = null ) {
if ( $add_on instanceof GFFeedAddOn ) {
$this->action = str_replace( 'gf', 'gf_' . $add_on->get_short_slug(), $this->action );
$this->add_on = $add_on;
}
parent::__construct( $allowed_batch_data_classes );
}
/**
* Get instance of this class.
*
* @since 2.2
* @since 2.9.25 Added the $add_on param.
*
* @param null|GFFeedAddOn $add_on Optional. The add-on instance.
*
* @return GF_Feed_Processor
*/
public static function get_instance( $add_on = null ) {
$key = $add_on instanceof GFFeedAddOn ? $add_on->get_slug() : 0;
if ( empty( self::$_instances[ $key ] ) ) {
self::$_instances[ $key ] = new self( true, $add_on );
}
return self::$_instances[ $key ];
}
/**
* Push to queue
*
* @since 2.9.25
* @remove-in 3.0
*
* @param mixed $data Data.
*
* @return $this
*/
public function push_to_queue( $data ) {
if ( ! is_string( rgar( $data, 'addon' ) ) ) {
_doing_it_wrong( __METHOD__, "Support for passing an add-on instance to `\$data['addon']` is deprecated since v2.7.15 and will be removed in v3.0. Please pass the add-ons fully qualified class name (including its namespace) instead.", '' );
$data['addon'] = get_class( $data['addon'] );
}
return parent::push_to_queue( $data );
}
/**
* Processes the task.
*
* @since 2.2
* @since 2.9.4 Updated to use the add-on save_entry_feed_status(), post_process_feed(), and fullfill_entry() methods.
*
* @access protected
*
* @param array $item {
* The task arguments.
*
* @type string $addon The add-on class name.
* @type array $feed The feed.
* @type int $entry_id The entry ID.
* @type int $form_id The form ID.
* @type int $attempts The number of attempts. Only included if the task has been processed before.
* }
*
* @return bool|array
*/
protected function task( $item ) {
$feed = $item['feed'];
$feed_name = GFAPI::get_feed_name( $feed );
$feed_id = (int) rgar( $feed, 'id' );
$entry_id = (int) rgar( $item, 'entry_id' );
if ( $this->add_on instanceof GFFeedAddOn ) {
$addon = $this->add_on;
$feed_slug = rgar( $feed, 'addon_slug' );
if ( $feed_slug !== $addon->get_slug() ) {
$this->log_error( __METHOD__ . "(): Aborting. Feed (#{$feed_id} - {$feed_name}) for entry #{$entry_id} belongs to a different add-on ({$feed_slug})." );
return false;
}
} else {
/**
* Passing an instance of the add-on.
* @deprecated 2.7.15
* @remove-in 3.0
*/
$addon = $item['addon'];
$callable = array( is_string( $addon ) ? $addon : get_class( $addon ), 'get_instance' );
if ( is_callable( $callable ) ) {
$addon = call_user_func( $callable );
}
if ( ! $addon instanceof GFFeedAddOn ) {
GFCommon::log_error( __METHOD__ . "(): Aborting. Add-on ({$feed['addon_slug']}) not found for feed (#{$feed_id} - {$feed_name}) and entry #{$entry_id}." );
return false;
}
}
$addon->log_debug( __METHOD__ . "(): Preparing to process feed (#{$feed_id} - {$feed_name}) for entry #{$entry_id}." );
$entry = GFAPI::get_entry( $entry_id );
$addon_slug = $addon->get_slug();
// Remove task if entry cannot be found.
if ( is_wp_error( $entry ) ) {
$addon->log_error( __METHOD__ . "(): Aborting. Entry #{$entry_id} not found for feed (#{$feed_id} - {$feed_name})." );
return false;
}
$form_id = (int) rgar( $item, 'form_id' );
$form = $this->filter_form( GFAPI::get_form( $form_id ), $entry );
if ( ! $this->can_process_feed( $feed, $entry, $form, $addon ) ) {
return false;
}
$max_attempts = 1;
/**
* Allow the number of retries to be modified before the feed is abandoned.
*
* if $max_attempts > 1 and if GFFeedAddOn::process_feed() throws an error or returns a WP_Error then the feed
* will be attempted again. Once the maximum number of attempts has been reached then the feed will be abandoned.
*
* @since 2.4
*
* @param int $max_attempts The maximum number of retries allowed. Default: 1.
* @param array $form The form array
* @param array $entry The entry array
* @param string $addon_slug The add-on slug
* @param array $feed The feed array
*/
$max_attempts = apply_filters( 'gform_max_async_feed_attempts', $max_attempts, $form, $entry, $addon_slug, $feed );
// Remove task if it was attempted too many times but failed to complete.
if ( $item['attempts'] > $max_attempts ) {
$addon->log_error( __METHOD__ . "(): Aborting. Feed (#{$feed_id} - {$feed_name}) attempted too many times for entry #{$entry_id}. Attempt number: {$item['attempts']}. Limit: {$max_attempts}." );
return false;
}
$addon->log_debug( __METHOD__ . "(): Starting to process feed (#{$feed_id} - {$feed_name}) for entry #{$entry_id}. Attempt number: {$item['attempts']}." );
// Keeping the try catch in case any third-party add-ons still throw exceptions.
try {
$result = $addon->process_feed( $feed, $entry, $form );
} catch ( Exception $e ) {
$addon->save_entry_feed_status( $e, $entry_id, $feed_id, $form_id );
$addon->log_error( __METHOD__ . "(): Aborting. Error occurred during processing of feed (#{$feed_id} - {$feed_name}) for entry #{$entry_id}: {$e->getMessage()}" );
$error = new WP_Error( $e->getCode(), $e->getMessage() );
return $addon->is_feed_error_retryable( true, $error, $feed, $entry, $form ) ? $item : false;
}
$addon->save_entry_feed_status( $result, $entry_id, $feed_id, $form_id );
if ( is_wp_error( $result ) ) {
/** @var WP_Error $result */
$addon->log_error( __METHOD__ . "(): Aborting. Error occurred during processing of feed (#{$feed_id} - {$feed_name}) for entry #{$entry_id}: {$result->get_error_message()}" );
return $addon->is_feed_error_retryable( true, $result, $feed, $entry, $form ) ? $item : false;
}
// If returned value from the process feed call is an array containing an ID, update entry and set the entry to its value.
if ( (int) rgar( $result, 'id' ) === $entry_id ) {
// Save updated entry.
if ( $entry !== $result ) {
GFAPI::update_entry( $result );
}
// Set entry to returned entry.
$entry = $result;
}
$addon->post_process_feed( $feed, $entry, $form );
$addon->fulfill_entry( $entry_id, $form_id );
// Update the entry meta.
GFAPI::update_processed_feeds_meta( $entry_id, $addon_slug, $feed_id, $form_id );
$addon->log_debug( __METHOD__ . "(): Completed processing of feed (#{$feed_id} - {$feed_name}) for entry #{$entry_id}." );
return false;
}
/**
* Determines if the task can be processed based on its attempts property.
*
* Overridden, returning true, so the existing feed-specific filters are used instead during task().
*
* @since 2.9.9
*
* @param mixed $task The task about to be processed.
* @param object $batch The batch currently being processed.
* @param int $task_num The number that identifies the task in the logs.
*
* @return bool
*/
protected function can_process_task( $task, $batch, $task_num ) {
return true;
}
/**
* Determines if the feed can be processed based on the contents of the processed feeds entry meta.
*
* @since 2.9.2
*
* @param array $entry The entry being processed.
* @param array $feed The feed queued for processing.
* @param array $form The form the entry belongs to.
* @param GFFeedAddOn $addon The current instance of the add-on the feed belongs to.
*
* @return bool
*/
public function can_process_feed( $feed, $entry, $form, $addon ) {
return $addon->can_process_feed( $feed, $entry, $form );
}
/**
* Logs the error that occurred during feed processing.
*
* @since 2.9.8
*
* @param array $error The error returned by error_get_last().
*
* @return void
*/
protected function handle_error( $error ) {
parent::handle_error( $error );
$task = $this->get_current_task();
if ( empty( $task ) ) {
return;
}
if ( $this->add_on ) {
$addon = $this->add_on;
} else {
$callable = array( $task['addon'], 'get_instance' );
if ( ! is_callable( $callable ) ) {
return;
}
$addon = call_user_func( $callable );
}
$feed = $task['feed'];
$feed_id = (int) rgar( $feed, 'id' );
$entry_id = (int) rgar( $task, 'entry_id' );
$addon->log_error( __METHOD__ . "(): Aborting. Error occurred during processing of feed (#{$feed_id} - {$addon->get_feed_name( $feed )}) for entry #{$entry_id}: {$error['message']}" );
$addon->save_entry_feed_status( new WP_Error( $error['type'], $error['message'] ), $entry_id, $feed_id, (int) rgar( $task, 'form_id' ) );
}
/**
* Increments the item attempts property and updates the batch in the database.
*
* @depecated 2.9.9
* @remove-in 4.0
*
* @since 2.4
* @since 2.9.4 Updated to use get_current_branch() instead of making a db request to get the batch.
*
* @param array $item {
* The task arguments.
*
* @type string $addon The add-on class name.
* @type array $feed The feed.
* @type int $entry_id The entry ID.
* @type int $form_id The form ID.
* @type int $attempts The number of processing attempts. Only included if the task has been processed before.
* }
*
* @return array
*/
protected function increment_attempts( $item ) {
$batch = $this->get_current_batch();
$item_feed = rgar( $item, 'feed' );
$item_entry_id = rgar( $item, 'entry_id' );
foreach ( $batch->data as $key => $task ) {
$task_feed = rgar( $task, 'feed' );
$task_entry_id = rgar( $task, 'entry_id' );
if ( $item_feed['id'] === $task_feed['id'] && $item_entry_id === $task_entry_id ) {
$batch->data[ $key ]['attempts'] = isset( $batch->data[ $key ]['attempts'] ) ? $batch->data[ $key ]['attempts'] + 1 : 1;
$item['attempts'] = $batch->data[ $key ]['attempts'];
break;
}
}
$this->update( $batch->key, $batch->data );
return $item;
}
/**
* Writes a message to the add-on or core log.
*
* @since 2.9.25
*
* @param string $message The message to be logged.
*
* @return void
*/
public function log_debug( $message ) {
if ( $this->add_on ) {
$this->add_on->log_debug( $message );
} else {
parent::log_debug( $message );
}
}
/**
* Writes an error message to the add-on or core log.
*
* @since 2.9.25
*
* @param string $message The message to be logged.
*
* @return void
*/
public function log_error( $message ) {
if ( $this->add_on ) {
$this->add_on->log_error( $message );
} else {
parent::log_error( $message );
}
}
/**
* Returns the action portion of logging statements.
*
* @since 2.9.25
*
* @return string
*/
protected function get_action_for_log() {
return $this->add_on ? '' : parent::get_action_for_log();
}
}
/**
* Returns an instance of the GF_Feed_Processor class
*
* @since 2.2
* @since 2.9.25 Added the $add_on param.
*
* @param null|GFFeedAddOn $add_on Optional. The add-on instance.
*
* @return GF_Feed_Processor
*/
function gf_feed_processor( $add_on = null ) {
return GF_Feed_Processor::get_instance( $add_on );
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,958 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
if ( ! class_exists( 'GFResults' ) ) {
class GFResults {
protected $_slug;
protected $_title;
protected $_icon;
protected $_callbacks;
protected $_capabilities;
protected $_search_title;
public function __construct( $slug, $config ) {
$this->_slug = $slug;
$this->_title = rgar( $config, 'title' );
$this->_icon = rgar( $config, 'icon' );
$this->_search_title = rgempty( 'search_title', $config ) ? esc_html__( 'Results Filters', 'gravityforms' ) : rgar( $config, 'search_title' );
$this->_callbacks = isset( $config['callbacks'] ) ? $config['callbacks'] : array();
$this->_capabilities = isset( $config['capabilities'] ) ? $config['capabilities'] : array();
}
public function init() {
if ( ! GFCommon::current_user_can_any( $this->_capabilities ) ) {
return;
}
// is any GF page
if ( GFForms::is_gravity_page() ) {
// add top toolbar menu item
add_filter( 'gform_toolbar_menu', array( $this, 'add_toolbar_menu_item' ), 10, 2 );
// add custom form action
add_filter( 'gform_form_actions', array( $this, 'add_form_action' ), 10, 2 );
}
// is results page
if ( rgget( 'view' ) == "gf_results_{$this->_slug}" ) {
// add the results view
add_action( 'gform_entries_view', array( $this, 'add_view' ), 10, 2 );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
// tooltips
require_once( GFCommon::get_base_path() . '/tooltips.php' );
add_filter( 'gform_tooltips', array( $this, 'add_tooltips' ) );
add_filter( 'admin_title', array( $this, 'set_unique_page_title' ), 100, 2 );
}
}
public function enqueue_admin_scripts() {
wp_enqueue_script( 'jquery-ui-resizable' );
wp_enqueue_script( 'jquery-ui-datepicker' );
wp_enqueue_script( 'google_charts' );
wp_enqueue_style( 'gaddon_results_css' );
wp_enqueue_script( 'gaddon_results_js' );
$this->localize_results_scripts();
}
public static function localize_results_scripts() {
// Get current page protocol
$protocol = isset( $_SERVER['HTTPS'] ) ? 'https://' : 'http://';
// Output admin-ajax.php URL with same protocol as current page
$vars = array(
'ajaxurl' => admin_url( 'admin-ajax.php', $protocol ),
'imagesUrl' => GFCommon::get_base_url() . '/images'
);
wp_localize_script( 'gaddon_results_js', 'gresultsVars', $vars );
$strings = array(
'ajaxError' => wp_strip_all_tags( __( 'Error retrieving results. If the problem persists, please contact support.', 'gravityforms' ) )
);
wp_localize_script( 'gaddon_results_js', 'gresultsStrings', $strings );
}
private function get_fields( $form ) {
return isset( $this->_callbacks['fields'] ) ? call_user_func( $this->_callbacks['fields'], $form ) : $form['fields'];
}
public function add_form_action( $actions, $form_id ) {
return $this->filter_menu_items( $actions, $form_id, true );
}
public function add_toolbar_menu_item( $menu_items, $form_id ) {
return $this->filter_menu_items( $menu_items, $form_id, false );
}
public function filter_menu_items( $menu_items, $form_id, $compact ) {
$form_meta = GFFormsModel::get_form_meta( $form_id );
$results_fields = $this->get_fields( $form_meta );
if ( false === empty( $results_fields ) ) {
$form_id = $form_meta['id'];
$link_class = '';
$page = GFForms::get_page_query_arg();
if ( $page == 'gf_new_form' ) {
$link_class = 'gf_toolbar_disabled';
} elseif ( $page == 'gf_entries' && rgget( 'view' ) == 'gf_results_' . $this->_slug ) {
$link_class = 'gf_toolbar_active';
}
$id = rgget( 'id' );
if ( empty( $id ) ) {
//on the form list page, do not use icons.
$icon = '';
} else {
$icon = $this->_icon;
if ( empty( $icon ) ) {
$icon = '<i class="fa fa-bar-chart-o fa-lg"></i>';
}
}
$sub_menu_items = array();
$sub_menu_items[] = array(
'label' => $this->_title,
'icon' => $icon,
'title' => esc_html__( 'View results generated by this form', 'gravityforms' ),
'link_class' => $link_class,
'url' => admin_url( "admin.php?page=gf_entries&view=gf_results_{$this->_slug}&id={$form_id}" ),
'capabilities' => $this->_capabilities,
);
$duplicate_submenus = wp_filter_object_list( rgars( $menu_items, 'results/sub_menu_items' ), array( 'label' => $sub_menu_items[0]['label'] ) );
if ( count( $duplicate_submenus ) > 0 ) {
return $menu_items;
}
// If there's already a menu item with the key "results" then merge the two.
if ( isset( $menu_items['results'] ) ) {
$existing_link_class = $menu_items['results']['link_class'];
$link_class == empty( $existing_link_class ) ? $link_class : $existing_link_class;
$existing_capabilities = $menu_items['results']['capabilities'];
$merged_capabilities = array_merge( $existing_capabilities, $this->_capabilities );
$existing_sub_menu_items = isset( $menu_items['results']['sub_menu_items'] ) ? $menu_items['results']['sub_menu_items'] : $menu_items['results']['single_menu_item'];
$merged_sub_menu_items = array_merge( $existing_sub_menu_items, $sub_menu_items );
$menu_items['results']['link_class'] = $link_class;
$menu_items['results']['capabilities'] = $merged_capabilities;
$menu_items['results']['sub_menu_items'] = $merged_sub_menu_items;
$menu_items['results']['label'] = esc_html__( 'Results', 'gravityforms' );
$menu_items['results']['icon'] = '<i class="fa fa-bar-chart-o fa-lg"></i>';
} else {
// so far during the page cycle this is the only menu item for this key.
$menu_items['results'] = array(
'label' => $compact ? esc_html__( 'Results', 'gravityforms' ) : $this->_title,
'icon' => $icon,
'title' => esc_attr__( 'View results generated by this form', 'gravityforms' ),
'url' => admin_url( "admin.php?page=gf_entries&view=gf_results_{$this->_slug}&id={$form_id}" ),
'menu_class' => 'gf_form_toolbar_results',
'link_class' => $link_class,
'capabilities' => $this->_capabilities,
'single_menu_item' => $sub_menu_items,
'priority' => 750,
);
}
}
return $menu_items;
}
public function add_view( $view, $form_id ) {
if ( $view == 'gf_results_' . $this->_slug ) {
$form_id = absint( $form_id );
GFResults::results_page( $form_id, $this->_title, 'gf_entries', $view );
}
}
public function results_page($form_id, $page_title, $gf_page, $gf_view ) {
$form_id = absint( $form_id );
if ( empty( $form_id ) ) {
$forms = RGFormsModel::get_forms();
if ( ! empty( $forms ) ) {
$form_id = $forms[0]->id;
}
}
$form = GFFormsModel::get_form_meta( $form_id );
$form = gf_apply_filters( array( 'gform_form_pre_results', $form_id ), $form );
// Set up filter vars
$start_date = preg_replace( '/[^0-9-]/', '', rgget( 'start' ) );
$end_date = preg_replace( '/[^0-9-]/', '', rgget( 'end' ) );
$all_fields = $form['fields'];
$filter_settings = GFCommon::get_field_filter_settings( $form );
$filter_settings = apply_filters( 'gform_filters_pre_results', $filter_settings, $form );
$filter_settings = array_values( $filter_settings ); // reset the numeric keys in case some filters have been unset
$filter_fields = rgget( 'f' );
$filter_operators = rgget( 'o' );
$filter_values = rgget( 'v' );
$filters = array();
$init_vars = array();
if ( ! empty( $filter_fields ) ) {
$init_vars['mode'] = rgget( 'mode' );
foreach ( $filter_fields as $i => $filter_field ) {
$filters[ $i ]['field'] = $filter_field;
$filters[ $i ]['operator'] = $filter_operators[ $i ];
$filters[ $i ]['value'] = $filter_values[ $i ];
}
$init_vars['filters'] = $filters;
}
?>
<script type="text/javascript">
var gresultsFields = <?php echo json_encode( $all_fields ); ?>;
var gresultsFilterSettings = <?php echo json_encode( $filter_settings ); ?>;
var gresultsInitVars = <?php echo json_encode( $init_vars ); // nosemgrep scanner.php.lang.security.xss.direct-reflected ?>;
<?php GFCommon::gf_global() ?>
<?php GFCommon::gf_vars() ?>
</script>
<div class="wrap gforms_edit_form <?php echo esc_attr( GFCommon::get_browser_class() ); ?>">
<?php //GFCommon::form_page_title( $form ); ?>
<?php //GFCommon::display_dismissible_message(); ?>
<?php //GFForms::top_toolbar(); ?>
<?php GFForms::admin_header(array(), true ) ;?>
<?php if ( false === empty( $all_fields ) ) : ?>
<div id="poststuff" class="metabox-holder has-right-sidebar">
<div id="side-info-column" class="inner-sidebar">
<div id="gresults-results-filter" class="gform-settings-panel__content postbox">
<h2><?php echo esc_html( $this->_search_title ); ?></h2>
<div id="gresults-results-filter-content">
<form id="gresults-results-filter-form" action="" method="GET">
<?php wp_nonce_field( 'gf_results', '_gf_results_nonce' ); ?>
<input type="hidden" id="gresults-page-slug" name="page"
value="<?php echo esc_attr( $gf_page ); ?>">
<input type="hidden" id="gresults-view-slug" name="view"
value="<?php echo esc_attr( $gf_view ); ?>">
<input type="hidden" id="gresults-form-id" name="id"
value="<?php echo esc_attr( $form_id ); ?>">
<?php
$filter_ui = array(
'fields' => array(
'label' => esc_attr__( 'Include results if', 'gravityforms' ),
'tooltip' => 'gresults_filters',
'markup' => '<div class="gform-settings-field__conditional-logic"><div id="gresults-results-field-filters-container">
<!-- placeholder populated by js -->
</div></div>',
),
'start_date' => array(
'label' => esc_attr__( 'Start date', 'gravityforms' ),
'markup' => '<div class="gform-settings-field gform-settings-field__date_time">
<span class="gform-settings-input__container"><input type="text" id="gresults-results-filter-date-start" name="start" value="' . esc_attr( $start_date ) . '"/><button type="button" class="ui-datepicker-trigger"><span class="screen-reader-text">'.esc_html__( 'Open Date Picker', 'gravityforms' ).'</span><svg width="18" height="18" role="presentation" focusable="false" fill="#9092B2" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M13.0909 1.6364V1.231C13.0909.5513 13.6357 0 14.3182 0c.6778 0 1.2273.5468 1.2273 1.2311v.4053h.8254c.8997 0 1.6291.7349 1.6291 1.6288v13.106C18 17.2707 17.2721 18 16.3709 18H1.6291C.7294 18 0 17.2651 0 16.3712V3.2652c0-.8996.728-1.6288 1.6291-1.6288h.8254V1.231C2.4545.5513 2.9993 0 3.6818 0c.6778 0 1.2273.5468 1.2273 1.2311v.4053h2.4545V1.231C7.3636.5513 7.9084 0 8.591 0c.6778 0 1.2273.5468 1.2273 1.2311v.4053h3.2727zM1.6364 7.3636v9h14.7272v-9H1.6364z"></path></svg></button></span>
</div>',
),
'end_date' => array(
'label' => esc_attr__( 'End date', 'gravityforms' ),
'markup' => '<div class="gform-settings-field gform-settings-field__date_time" >
<span class="gform-settings-input__container"><input type="text" id="gresults-results-filter-date-end" name="end" value="' . esc_attr( $end_date ) . '"/><button type="button" class="ui-datepicker-trigger"><span class="screen-reader-text">'.esc_html__( 'Open Date Picker', 'gravityforms' ).'</span><svg width="18" height="18" role="presentation" focusable="false" fill="#9092B2" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M13.0909 1.6364V1.231C13.0909.5513 13.6357 0 14.3182 0c.6778 0 1.2273.5468 1.2273 1.2311v.4053h.8254c.8997 0 1.6291.7349 1.6291 1.6288v13.106C18 17.2707 17.2721 18 16.3709 18H1.6291C.7294 18 0 17.2651 0 16.3712V3.2652c0-.8996.728-1.6288 1.6291-1.6288h.8254V1.231C2.4545.5513 2.9993 0 3.6818 0c.6778 0 1.2273.5468 1.2273 1.2311v.4053h2.4545V1.231C7.3636.5513 7.9084 0 8.591 0c.6778 0 1.2273.5468 1.2273 1.2311v.4053h3.2727zM1.6364 7.3636v9h14.7272v-9H1.6364z"></path></svg></button></span>
</div>',
),
);
$filter_ui = apply_filters( 'gform_filter_ui', $filter_ui, $form_id, $page_title, $gf_page, $gf_view );
foreach ( $filter_ui as $name => $filter ) {
?>
<div class="gform-settings-field__header"><label class='gform-settings-label'><?php echo esc_html( $filter['label'] ); ?><?php gform_tooltip( rgar( $filter, 'tooltip' ), 'tooltip_bottomleft' ) ?></label></div>
<?php
echo $filter['markup']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
<div id="gresults-results-filter-buttons">
<input type="submit" id="gresults-results-filter-submit-button"
class="button primary large"
value="<?php esc_attr_e( 'Apply filters', 'gravityforms' ); ?>">
<input type="button" id="gresults-results-filter-clear-button"
class="button large"
value="<?php esc_attr_e( 'Clear', 'gravityforms' ); ?>"
onclick="gresults.clearFilterForm();"
onkeypress="gresults.clearFilterForm();">
<div class="gresults-filter-loading"
style="display:none; float:right; margin-top:5px;">
<i class='gform-spinner'></i>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="gresults-filter-loading" style="display:none;margin:0 5px 10px 0;">
<i class='gform-spinner'></i>&nbsp;
<a href="javascript:void(0);" onclick="javascript:gresultsAjaxRequest.abort()" onkeypress="javascript:gresultsAjaxRequest.abort()"><?php esc_html_e( 'Cancel', 'gravityforms' ); ?></a>
</div>
<div id="gresults-results-wrapper">
<div id="gresults-results">&nbsp;
</div>
</div>
<?php
else :
esc_html_e( 'This form does not have any fields that can be used for results', 'gravityforms' );
endif ?>
</div>
<?php GFForms::admin_footer() ?>
<?php
}
public static function add_tooltips( $tooltips ) {
$tooltips['gresults_total_score'] = '<h6>' . esc_html__( 'Total Score', 'gravityforms' ) . '</h6>' . esc_html__( 'Scores are weighted calculations. Items ranked higher are given a greater score than items that are ranked lower. The total score for each item is the sum of the weighted scores.', 'gravityforms' );
$tooltips['gresults_agg_rank'] = '<h6>' . esc_html__( 'Aggregate Rank', 'gravityforms' ) . '</h6>' . esc_html__( 'The aggregate rank is the overall rank for all entries based on the weighted scores for each item.', 'gravityforms' );
$tooltips['gresults_date_range'] = '<h6>' . esc_html__( 'Date Range', 'gravityforms' ) . '</h6>' . esc_html__( 'Date Range is optional, if no date range is specified it will be ignored.', 'gravityforms' );
$tooltips['gresults_filters'] = '<h6>' . esc_html__( 'Filters', 'gravityforms' ) . '</h6>' . esc_html__( 'Narrow the results by adding filters. Note that some field types support more options than others.', 'gravityforms' );
$tooltips['gresults_average_row_score'] = '<h6>' . esc_html__( 'Average Row Score', 'gravityforms' ) . '</h6>' . esc_html__( 'The average (mean) score for each row: the sum of all the scores for each row divided by the total number of entries.', 'gravityforms' );
$tooltips['gresults_average_global_score'] = '<h6>' . esc_html__( 'Average Global Score', 'gravityforms' ) . '</h6>' . esc_html__( 'The average (mean) score for the whole field. The sum of the total scores divided by the number of entries.', 'gravityforms' );
$tooltips['gresults_average_score'] = '<h6>' . esc_html__( 'Average Score', 'gravityforms' ) . '</h6>' . esc_html__( 'The average (mean) score: The sum of the scores divided by the number of entries.', 'gravityforms' );
return $tooltips;
}
public function ajax_get_results() {
check_ajax_referer( 'gf_results', '_gf_results_nonce' );
if ( ! GFAPI::current_user_can_any( $this->_capabilities ) ) {
wp_die( 'Not allowed' );
}
// tooltips
require_once( GFCommon::get_base_path() . '/tooltips.php' );
add_filter( 'gform_tooltips', array( $this, 'add_tooltips' ) );
$output = array();
$html = '';
$form_id = rgpost( 'id' );
$form_id = absint( $form_id );
$form = GFFormsModel::get_form_meta( $form_id );
$form = gf_apply_filters( array( 'gform_form_pre_results', $form_id ), $form );
$search_criteria['status'] = 'active';
$fields = $this->get_fields( $form );
$total_entries = GFAPI::count_entries( $form_id, $search_criteria );
if ( $total_entries == 0 ) {
$html = esc_html__( 'No results.', 'gravityforms' );
} else {
$search_criteria = array();
$search_criteria['field_filters'] = GFCommon::get_field_filters_from_post( $form );
$start_date = preg_replace( '/[^0-9-]/', '', rgpost( 'start' ) );
$end_date = preg_replace( '/[^0-9-]/', '', rgpost( 'end' ) );
if ( $start_date ) {
$search_criteria['start_date'] = $start_date;
}
if ( $end_date ) {
$search_criteria['end_date'] = $end_date;
}
$search_criteria['status'] = 'active';
$output['s'] = http_build_query( $search_criteria );
$state_array = null;
if ( isset( $_POST['state'] ) ) {
$state = rgpost( 'state' );
$posted_check_sum = rgpost( 'checkSum' );
$generated_check_sum = self::generate_checksum( $state );
$state_array = json_decode( base64_decode( $state ), true );
if ( $generated_check_sum !== $posted_check_sum ) {
$output['status'] = 'complete';
$output['html'] = esc_html__( 'There was an error while processing the entries. Please contact support.', 'gravityforms' );
echo json_encode( $output );
die();
}
}
$data = isset( $this->_callbacks['data'] ) ? call_user_func( $this->_callbacks['data'], $form, $fields, $search_criteria, $state_array ) : $this->get_results_data( $form, $fields, $search_criteria, $state_array );
$entry_count = $data['entry_count'];
if ( 'incomplete' === rgar( $data, 'status' ) ) {
$state = base64_encode( json_encode( $data ) );
$output['status'] = 'incomplete';
$output['stateObject'] = $state;
$output['checkSum'] = self::generate_checksum( $state );
$output['html'] = sprintf( esc_html__( 'Entries processed: %1$d of %2$d', 'gravityforms' ), rgar( $data, 'offset' ), $entry_count );
echo json_encode( $output );
die();
}
if ( $total_entries > 0 ) {
$html = isset( $this->_callbacks['markup'] ) ? call_user_func( $this->_callbacks['markup'], $html, $data, $form, $fields ) : '';
if ( empty( $html ) ) {
foreach ( $fields as $field ) {
$field_id = $field->id;
$html .= "<div class='gresults-results-field gform-settings-panel' id='gresults-results-field-{$field_id}'>";
$html .= "<header class='gform-settings-panel__header'><legend class='gform-settings-panel__title'>" . esc_html( GFCommon::get_label( $field ) ) . '</legend></header>';
$html .= '<div class="gform-settings-panel__content">' . self::get_field_results( $form_id, $data, $field, $search_criteria ) . '</div>';
$html .= '</div>';
}
}
} else {
$html .= esc_html__( 'No results', 'gravityforms' );
}
}
$output['html'] = $html;
$output['status'] = 'complete';
$output['searchCriteria'] = $search_criteria;
echo json_encode( $output );
die();
}
public function ajax_get_more_results() {
check_ajax_referer( 'gf_results', '_gf_results_nonce' );
if ( ! GFAPI::current_user_can_any( $this->_capabilities ) ) {
wp_die( 'Not allowed' );
}
$form_id = rgpost( 'form_id' );
$field_id = rgpost( 'field_id' );
$offset = rgpost( 'offset' );
$search_criteria = rgpost( 'search_criteria' );
if ( empty( $search_criteria ) ) {
$search_criteria = array();
}
$page_size = 10;
$form = RGFormsModel::get_form_meta( $form_id );
$form_id = $form['id'];
$field = RGFormsModel::get_field( $form, $field_id );
$more_remaining = false;
$html = self::get_default_field_results( $form_id, $field, $search_criteria, $offset, $page_size, $more_remaining );
$response = array();
$response['more_remaining'] = $more_remaining;
$response['html'] = $html;
$response['offset'] = $offset;
echo json_encode( $response ); // nosemgrep scanner.php.lang.security.xss.direct-reflected
die();
}
private static function generate_checksum( $data ) {
return wp_hash( crc32( ( $data ) ) );
}
public static function get_total_entries( $form ) {
$totals = RGFormsModel::get_form_counts( $form['id'] );
return $totals['total'];
}
public static function get_field_results( $form_id, $data, $field, $search_criteria ) {
if ( empty( $data['entry_count'] ) || empty ( $data['field_data'] ) ) {
return esc_html__( 'No entries for this field', 'gravityforms' );
}
$field_data = $data['field_data'];
$entry_count = $data['entry_count'];
if ( empty( $field_data[ $field->id ] ) ) {
return esc_html__( 'No entries for this field', 'gravityforms' );
}
$field_results = '';
$field_type = GFFormsModel::get_input_type( $field );
switch ( $field_type ) {
case 'radio' :
case 'checkbox' :
case 'select' :
case 'rating' :
case 'multiselect' :
$results = $field_data[ $field->id ];
$non_zero_results = is_array( $results ) ? array_filter( $results ) : $results;
if ( empty( $non_zero_results ) ) {
$field_results .= esc_html__( 'No entries for this field', 'gravityforms' );
return $field_results;
}
$choices = $field->choices;
$data_table = array();
$data_table [] = array( esc_html__( 'Choice', 'gravityforms' ), esc_html__( 'Frequency', 'gravityforms' ) );
foreach ( $choices as $choice ) {
$text = $choice['text'];
$val = $results[ $choice['value'] ];
$data_table [] = array( $text, $val );
}
$bar_height = 40;
$chart_area_height = ( count( $choices ) * ( $bar_height + 20 ) );
$chart_options = array(
'isStacked' => true,
'height' => ( $chart_area_height + $bar_height ),
'fontSize' => 14,
'chartArea' => array(
'top' => 0,
'left' => 200,
'height' => $chart_area_height,
'width' => '100%',
),
'series' => array(
'0' => array(
'color' => 'silver',
'visibleInLegend' => 'false',
),
),
'hAxis' => array(
'viewWindowMode' => 'explicit',
'viewWindow' => array( 'min' => 0 ),
'title' => esc_html__( 'Frequency', 'gravityforms' ),
),
'tooltip' => array(
'isHtml' => true,
),
);
$data_table_json = htmlentities( json_encode( $data_table ), ENT_QUOTES, 'UTF-8', true );
$options_json = htmlentities( json_encode( $chart_options ), ENT_QUOTES, 'UTF-8', true );
$div_id = 'gresults-results-chart-field-' . $field->id;
$height = ''; // = sprintf("height:%dpx", (count($choices) * $bar_height));
$field_results .= sprintf( '<div class="gresults-chart-wrapper" style="width: 100%%;%s" id=%s data-datatable=\'%s\' data-options=\'%s\' data-charttype="bar" ></div>', $height, $div_id, $data_table_json, $options_json );
break;
case 'likert' :
$results = $field_data[ $field->id ];
$multiple_rows = $field->gsurveyLikertEnableMultipleRows ? true : false;
$scoring_enabled = $field->gsurveyLikertEnableScoring && class_exists( 'GFSurvey' ) ? true : false;
$n = 100;
$xr = 255;
$xg = 255;
$xb = 255;
$yr = 100;
$yg = 250;
$yb = 100;
$field_results .= "<div class='gsurvey-likert-field-results'>";
$field_results .= "<table class='gsurvey-likert'>";
$field_results .= '<tr>';
if ( $multiple_rows ) {
$field_results .= '<td></td>';
}
foreach ( $field->choices as $choice ) {
$field_results .= "<td class='gsurvey-likert-choice-label'>" . $choice['text'] . '</td>';
}
if ( $multiple_rows && $scoring_enabled ) {
$field_results .= sprintf( '<td>%s %s</td>', esc_html__( 'Average Score', 'gravityforms' ), gform_tooltip( 'gresults_average_row_score', null, true ) );
}
$field_results .= '</tr>';
foreach ( $field->gsurveyLikertRows as $row ) {
$row_text = $row['text'];
$row_value = $row['value'];
$max = 0;
foreach ( $field->choices as $choice ) {
if ( $multiple_rows ) {
$choice_value = rgar( $choice, 'value' );
$results_row = rgar( $results, $row_value );
$results_for_choice = rgar( $results_row, $choice_value );
$max = max( array( $max, $results_for_choice ) );
} else {
$max = max( array( $max, $results[ $choice['value'] ] ) );
}
}
$field_results .= '<tr>';
if ( $multiple_rows ) {
$field_results .= "<td class='gsurvey-likert-row-label'>" . $row_text . '</td>';
}
foreach ( $field->choices as $choice ) {
$val = $multiple_rows ? $results[ $row_value ][ $choice['value'] ] : $results[ $choice['value'] ];
$percent = $max > 0 ? round( $val / $max * 100, 0 ) : 0;
$red = (int) ( ( $xr + ( ( $percent * ( $yr - $xr ) ) / ( $n - 1 ) ) ) );
$green = (int) ( ( $xg + ( ( $percent * ( $yg - $xg ) ) / ( $n - 1 ) ) ) );
$blue = (int) ( ( $xb + ( ( $percent * ( $yb - $xb ) ) / ( $n - 1 ) ) ) );
$clr = 'rgb(' . $red . ',' . $green . ',' . $blue . ')';
$field_results .= "<td class='gsurvey-likert-results' style='background-color:{$clr}'>" . $val . '</td>';
}
if ( $multiple_rows && $scoring_enabled ) {
$row_sum = $results[ $row_value ]['row_score_sum'];
$average_row_score = $row_sum == 0 ? 0 : round( $row_sum / $entry_count, 3 );
$field_results .= "<td class='gsurvey-likert-results'>" . $average_row_score . '</td>';
}
$field_results .= '</tr>';
if ( false === $multiple_rows ) {
break;
}
}
$field_results .= '</table>';
$field_results .= '</div>';
if ( $scoring_enabled ) {
$sum = $results['sum_of_scores'];
$average_score = $sum == 0 ? 0 : round( $sum / $entry_count, 3 );
if ( $multiple_rows ) {
$average_global_score_tooltip = gform_tooltip( 'gresults_average_global_score', null, true );
$field_results .= sprintf( "<div class='gsurvey-likert-score'>%s %s: %s</div>", esc_html__( 'Average global score', 'gravityforms' ), $average_global_score_tooltip, $average_score );
} else {
$field_results .= sprintf( "<div class='gsurvey-likert-score'>%s %s: %s</div>", esc_html__( 'Average score', 'gravityforms' ), gform_tooltip( 'gresults_average_score', null, true ), $average_score );
}
}
break;
case 'rank' :
$results = $field_data[ $field->id ];
arsort( $results );
$field_results .= "<div class='gsurvey-rank-field-results'>";
$field_results .= ' <table>';
$field_results .= " <tr class='gresults-results-field-table-header'>";
$field_results .= " <td class='gresults-rank-field-label'>";
$field_results .= esc_html__( 'Item', 'gravityforms' );
$field_results .= ' </td>';
$field_results .= " <td class='gresults-rank-field-score'>";
$field_results .= esc_html__( 'Total Score', 'gravityforms' ) . '&nbsp;' . gform_tooltip( 'gresults_total_score', null, true );
$field_results .= ' </td>';
$field_results .= " <td class='gresults-rank-field-rank'>";
$field_results .= esc_html__( 'Aggregate Rank', 'gravityforms' ) . '&nbsp;' . gform_tooltip( 'gresults_agg_rank', null, true );
$field_results .= ' </td>';
$field_results .= ' </tr>';
$agg_rank = 1;
foreach ( $results as $choice_val => $score ) {
$field_results .= '<tr>';
$field_results .= " <td class='gresults-rank-field-label' style='text-align:left;'>";
$field_results .= RGFormsModel::get_choice_text( $field, $choice_val );
$field_results .= ' </td>';
$field_results .= " <td class='gresults-rank-field-score'>";
$field_results .= $score;
$field_results .= ' </td>';
$field_results .= " <td class='gresults-rank-field-rank'>";
$field_results .= $agg_rank;
$field_results .= ' </td>';
$field_results .= '</tr>';
$agg_rank ++;
}
$field_results .= '</table>';
$field_results .= '</div>';
break;
default :
$page_size = 5;
$offset = 0;
$field_id = $field->id;
$more_remaining = false;
$default_field_results = self::get_default_field_results( $form_id, $field, $search_criteria, $offset, $page_size, $more_remaining );
$field_results .= "<div class='gresults-results-field-sub-label'>" . esc_html__( 'Latest values:', 'gravityforms' ) . '</div>';
$field_results .= "<ul id='gresults-results-field-content-{$field_id}' class='gresults-results-field-content' data-offset='{$offset}'>";
$field_results .= $default_field_results;
$field_results .= '</ul>';
if ( $more_remaining ) {
$field_results .= "<a id='gresults-results-field-more-link-{$field_id}' class='gresults-results-field-more-link' href='javascript:void(0)' onclick='gresults.getMoreResults({$form_id},{$field_id})' onkeypress='gresults.getMoreResults({$form_id},{$field_id})'>" . esc_html__( 'Show more', 'gravityforms' ) . '</a>';
}
break;
}
return $field_results;
}
public function get_results_data( $form, $fields, $search_criteria = array(), $state_array = array(), $max_execution_time = 15 /* seconds */ ) {
// todo: add hooks to modify $max_execution_time and $page_size?
$page_size = 150;
$time_start = microtime( true );
$form_id = $form['id'];
$data = array();
$offset = 0;
$entry_count = 0;
$field_data = array();
if ( $state_array ) {
//get counts from state
$data = $state_array;
$offset = (int) rgar( $data, 'offset' );
unset( $data['offset'] );
$entry_count = $offset;
$field_data = rgar( $data, 'field_data' );
} else {
//initialize counts
foreach ( $fields as $field ) {
$field_type = GFFormsModel::get_input_type( $field );
if ( false === isset( $field->choices ) ) {
$field_data[ $field->id ] = 0;
continue;
}
$choices = $field->choices;
if ( $field_type == 'likert' && rgar( $field, 'gsurveyLikertEnableMultipleRows' ) ) {
foreach ( $field->gsurveyLikertRows as $row ) {
foreach ( $choices as $choice ) {
$field_data[ $field->id ][ $row['value'] ][ $choice['value'] ] = 0;
}
if ( rgar( $field, 'gsurveyLikertEnableScoring' ) ) {
$field_data[ $field->id ][ $row['value'] ]['row_score_sum'] = 0;
}
}
} else {
if ( ! empty( $choices ) && is_array( $choices ) ) {
foreach ( $choices as $choice ) {
$field_data[ $field->id ][ $choice['value'] ] = 0;
}
} else {
$field_data[ $field->id ] = 0;
}
}
if ( $field_type == 'likert' && rgar( $field, 'gsurveyLikertEnableScoring' ) ) {
$field_data[ $field->id ]['sum_of_scores'] = 0;
}
}
}
$count_search_leads = GFAPI::count_entries( $form_id, $search_criteria );
$data['entry_count'] = $count_search_leads;
$entries_left = $count_search_leads - $offset;
while ( $entries_left > 0 ) {
$paging = array(
'offset' => $offset,
'page_size' => $page_size,
);
$search_leads_time_start = microtime( true );
$leads = GFFormsModel::search_leads( $form_id, $search_criteria, null, $paging );
$search_leads_time_end = microtime( true );
$search_leads_time = $search_leads_time_end - $search_leads_time_start;
$leads_in_search = count( $leads );
$entry_count += $leads_in_search;
$leads_processed = 0;
foreach ( $leads as $lead ) {
$lead_time_start = microtime( true );
foreach ( $fields as $field ) {
$field_type = GFFormsModel::get_input_type( $field );
$field_id = $field->id;
$value = RGFormsModel::get_lead_field_value( $lead, $field );
if ( $field_type == 'likert' && rgar( $field, 'gsurveyLikertEnableMultipleRows' ) ) {
if ( empty( $value ) ) {
continue;
}
foreach ( $value as $value_vector ) {
if ( empty( $value_vector ) ) {
continue;
}
list( $row_val, $col_val ) = explode( ':', $value_vector, 2 );
if ( isset( $field_data[ $field->id ][ $row_val ] ) && isset( $field_data[ $field->id ][ $row_val ][ $col_val ] ) ) {
$field_data[ $field->id ][ $row_val ][ $col_val ] ++;
if ( $field->gsurveyLikertEnableScoring ) {
$field_data[ $field->id ][ $row_val ]['row_score_sum'] += $this->get_likert_row_score( $row_val, $field, $lead );
}
}
}
} elseif ( $field_type == 'rank' ) {
$score = count( rgar( $field, 'choices' ) );
$values = explode( ',', $value );
foreach ( $values as $ranked_value ) {
$field_data[ $field->id ][ $ranked_value ] += $score;
$score --;
}
} else {
if ( empty( $field->choices ) ) {
if ( false === empty( $value ) ) {
$field_data[ $field_id ] ++;
}
continue;
}
$choices = $field->choices;
foreach ( $choices as $choice ) {
$choice_is_selected = false;
if ( is_array( $value ) ) {
$choice_value = rgar( $choice, 'value' );
if ( in_array( $choice_value, $value ) ) {
$choice_is_selected = true;
}
} else {
if ( RGFormsModel::choice_value_match( $field, $choice, $value ) ) {
$choice_is_selected = true;
}
}
if ( $choice_is_selected ) {
$field_data[ $field_id ][ $choice['value'] ] ++;
}
}
}
if ( $field_type == 'likert' && rgar( $field, 'gsurveyLikertEnableScoring' ) ) {
$field_data[ $field->id ]['sum_of_scores'] += $this->get_likert_score( $field, $lead );
}
}
$leads_processed ++;
$lead_time_end = microtime( true );
$total_execution_time = $lead_time_end - $search_leads_time_start;
$lead_execution_time = $lead_time_end - $lead_time_start;
if ( $total_execution_time + $lead_execution_time > $max_execution_time ) {
break;
}
}
$data['field_data'] = $field_data;
if ( isset( $this->_callbacks['calculation'] ) ) {
$data = call_user_func( $this->_callbacks['calculation'], $data, $form, $fields, $leads );
$field_data = $data['field_data'];
}
$offset += $leads_processed;
$entries_left -= $leads_processed;
$time_end = microtime( true );
$execution_time = ( $time_end - $time_start );
if ( $entries_left > 0 && $execution_time + $search_leads_time > $max_execution_time ) {
$data['status'] = 'incomplete';
$data['offset'] = $offset;
$progress = $data['entry_count'] > 0 ? round( $data['offset'] / $data['entry_count'] * 100 ) : 0;
$data['progress'] = $progress;
break;
}
if ( $entries_left <= 0 ) {
$data['status'] = 'complete';
}
}
$data['timestamp'] = time();
return $data;
}
public function get_likert_row_score( $row_val, $field, $entry ) {
return is_callable( array(
'GFSurvey',
'get_likert_row_score',
) ) ? GFSurvey::get_likert_row_score( $row_val, $field, $entry ) : 0;
}
public function get_likert_score( $field, $entry ) {
return is_callable( array(
'GFSurvey',
'get_field_score',
) ) ? GFSurvey::get_field_score( $field, $entry ) : 0;
}
/**
* Sets a unique page title to the results page based on the title
* and the form the user is viewing.
*
* @since 2.8.16
*
* @filter admin_title
*
* @param string $admin_title The page title with extra context added.
* @param string $title The original page title.
*
* @return string
*/
public function set_unique_page_title( $admin_title, $title ) {
$form_id = rgget( 'id' );
if ( ! $form_id ) {
$forms = RGFormsModel::get_forms( null, 'title' );
$form_id = ( ! empty( $forms ) ) ? $forms[0]->id : '';
}
$form = GFAPI::get_form( $form_id );
$form_title = rgar( $form, 'title', esc_html__( 'Form Not Found', 'gravityforms' ) );
$admin_title = sprintf( '%1$s &lsaquo; %2$s &lsaquo; %3$s', esc_html( $this->_title ), esc_html( $form_title ), esc_html( $admin_title ) );
return $admin_title;
}
public static function get_default_field_results( $form_id, $field, $search_criteria, &$offset, $page_size, &$more_remaining = false ) {
$field_results = '';
$sorting = array( 'key' => 'date_created', 'direction' => 'DESC' );
$c = 0;
do {
$paging = array( 'offset' => $offset, 'page_size' => $page_size );
$leads = GFFormsModel::search_leads( $form_id, $search_criteria, $sorting, $paging );
foreach ( $leads as $lead ) {
$value = RGFormsModel::get_lead_field_value( $lead, $field );
$content = apply_filters( 'gform_entries_field_value', $value, $form_id, $field->id, $lead );
if ( is_array( $content ) ) {
$content = join( ' ', $content );
}
if ( ! empty( $content ) ) {
$field_results .= "<li>" . wp_kses_post( $content ) . "</li>";
$c ++;
}
}
$offset += $page_size;
} while ( $c < $page_size && ! empty( $leads ) );
if ( ! empty( $leads ) ) {
$more_remaining = true;
}
return $field_results;
}
}
}
@@ -0,0 +1,60 @@
.settings-field-map-table { }
.settings-field-map-table thead th { font-weight: bold; border-bottom: 1px solid #ccc; }
.settings-field-map-table tbody td { border-bottom: 1px dotted #eee; }
.settings-field-map-table select { max-width: 90%; }
.settings-field-map-table .custom-key-reset,
.settings-field-map-table .custom-value-reset,
.gaddon-setting-select-custom-container .select-custom-reset {
background: url( ../../../images/xit.gif ) no-repeat scroll 0 0 transparent;
cursor:pointer;
display:none;
position:absolute;
text-indent:-9999px;
width:10px;
height: 10px;
-moz-transition: none;
-webkit-transition: none;
-o-transition: color 0 ease-in;
transition: none;
}
.settings-field-map-table .custom-key-reset {
margin-top: 10px;
margin-left: 165px;
}
.settings-field-map-table .repeater th { padding-left: 0px; }
.settings-field-map-table .custom-key-reset:hover,
.settings-field-map-table .custom-value-reset:hover,
.gaddon-setting-select-custom-container .select-custom-reset:hover {
background-position-x: -10px;
}
.settings-field-map-table .custom-key-container:hover .custom-key-reset,
.settings-field-map-table .custom-key-container:hover .custom-value-reset,
.gaddon-setting-select-custom-container:hover .select-custom-reset {
display:block;
}
.gaddon-setting-select-custom-container { display:inline-block;position:relative; }
.gaddon-setting-select-custom-container .select-custom-reset {
left: 171px;
top: 10px;
}
.gaddon-section .required { color: #f00; }
.gaddon-setting-inline{
display:inline;
margin-right:6px;
}
.mt-gaddon-editor {
float: right;
position: relative;
right: 21px;
top: 70px;
}
.mt-gaddon-editor ~ .wp-editor-wrap {
margin-right: 23px;
}
@@ -0,0 +1 @@
.settings-field-map-table thead th{font-weight:700;border-bottom:1px solid #ccc}.settings-field-map-table tbody td{border-bottom:1px dotted #eee}.settings-field-map-table select{max-width:90%}.gaddon-setting-select-custom-container .select-custom-reset,.settings-field-map-table .custom-key-reset,.settings-field-map-table .custom-value-reset{background:url(../../../images/xit.gif) no-repeat scroll 0 0 transparent;cursor:pointer;display:none;position:absolute;text-indent:-9999px;width:10px;height:10px;-moz-transition:none;-webkit-transition:none;-o-transition:color 0 ease-in;transition:none}.settings-field-map-table .custom-key-reset{margin-top:10px;margin-left:165px}.settings-field-map-table .repeater th{padding-left:0}.gaddon-setting-select-custom-container .select-custom-reset:hover,.settings-field-map-table .custom-key-reset:hover,.settings-field-map-table .custom-value-reset:hover{background-position-x:-10px}.gaddon-setting-select-custom-container:hover .select-custom-reset,.settings-field-map-table .custom-key-container:hover .custom-key-reset,.settings-field-map-table .custom-key-container:hover .custom-value-reset{display:block}.gaddon-setting-select-custom-container{display:inline-block;position:relative}.gaddon-setting-select-custom-container .select-custom-reset{left:171px;top:10px}.gaddon-section .required{color:red}.gaddon-setting-inline{display:inline;margin-right:6px}.mt-gaddon-editor{float:right;position:relative;right:21px;top:70px}.mt-gaddon-editor~.wp-editor-wrap{margin-right:23px}
@@ -0,0 +1,293 @@
/* --------- TODO: remove quiz specific styles ------------------*/
table#gquiz-results-summary {
table-layout: fixed;
}
table#gquiz-results-summary td {
font-weight: bold;
text-align: center;
padding: 10px;
}
table#gquiz-results-summary td.gquiz-results-summary-label {
font-size: 1.2em;
}
table#gquiz-results-summary td.gquiz-results-summary-data {
font-size: 2em;
}
.gquiz-results-summary-data-box {
width: 75%;
padding: 15px 0;
min-width: 0;
margin: auto;
}
.gaddon-results-summary-secondary{
padding-top: 4px;
font-weight: normal;
font-size:13px;
}
.gaddon-results-summary-primary{
padding:5px;
}
.gquiz-field-precentages-correct {
float: inline-start;
margin-block-start: 8px;
margin-inline-start: 64px;
}
/* -------------------------------------*/
table#gaddon-results-summary {
table-layout: fixed;
}
table#gaddon-results-summary td {
font-weight: bold;
text-align: center;
padding: 10px;
}
table#gaddon-results-summary td.gaddon-results-summary-label {
font-size: 1.2em;
}
table#gaddon-results-summary td.gaddon-results-summary-data {
font-size: 2em;
}
.gaddon-results-summary-data-box {
border: 1px solid silver;
padding: 10px;
width: 75%;
margin: auto;
}
.gaddon-field-precentages-correct {
float: left;
margin: 15px 0 0 50px;
}
/*-------------*/
#gresults-results {
margin-right: 300px;
}
.gform-admin .gresults-filter-loading .gform-spinner {
display: inline-block;
}
.gf-results .gf-result-box {
background: #fff;
border-radius: 3px;
border: 1px solid #e3e6ef;
box-shadow: 0 1px 4px rgb(18 25 97 / 8%);
padding: 1.562rem 1rem;
}
.gf-results .gf-result-box {
margin-right: 0;
margin-bottom: 1.25rem;
}
.gf-results .gf-result-box__primary {
display: flex;
}
@media screen and ( min-width: 782px ) {
.gf-results {
display: flex;
justify-content: space-between;
margin-bottom: 1.25rem;
}
.gf-results .gf-result-box {
flex: 1 1 0px;
margin-inline-end: 1.25rem;
margin-bottom: 0;
}
.gf-results .gf-result-box:last-child {
margin-inline-end: 0;
}
}
.gresults-results-field {
margin-bottom:20px;
}
.gresults-results-field table{
border-bottom: 1px solid silver;
border-spacing: 0;
}
.gresults-results-field table td{
padding:5px;
border-right: 1px solid silver;
border-top: 1px solid silver;
text-align:center;
}
.gresults-results-field-table-header{
background-color: #eeeeee;
}
.gresults-results-field table td:first-child {
border-left: 1px solid silver;
}
ul.gresults-results-field-content,
ul.gresults-results-field-content li{
list-style:disc outside none;
}
ul.gresults-results-field-content{
margin:10px;
}
td.gresults-rank-field-score,
td.gresults-rank-field-rank{
width:110px;
}
.gsurvey-rank-field-results table{
width:100%;
}
/* filter box */
#gresults-results-filter-content {
padding:12px;
border-top: 1px solid #EBEBF2
}
.gresults-results-filter-section-label {
font-size:1.2em;
font-weight:bold;
margin-top:10px;
margin-bottom:10px;
}
#gresults-results-filter label {
margin-top:10px;
display:block;
}
#gresults-results-filter-content #gform-no-filters {
color: #242748;
}
.gresults-remove,
.gresults-add {
margin-top:2px;
vertical-align:middle;
cursor:pointer;
}
.gresults-add {
margin-left:5px;
}
#gresults-no-filters{
color:silver;
}
.gresults-datepicker,
.gresults-filter-value,
.gresults-filter-field {
width:150px;
box-sizing:border-box;
-ms-box-sizing:border-box;
-moz-box-sizing:border-box;
-webkit-box-sizing:border-box;
height: 2em;
padding: 2px;
}
.gresults-filter-operator {
width:70px;
}
#gresults-results-filter-buttons {
clear:both;
margin-top:20px;
width:180px;
}
.gresults-results-filter-field-label {
font-size:1.1em;
font-weight:bold;
margin-bottom:10px;
}
.gresults-results-filter-field {
margin-bottom:20px;
}
.gresults-results-filter-field ul li label {
margin-left:5px;
}
.gresults-results-filter-title {
font-size:1.5em;
font-weight:bold;
}
/*
#gresults-results-filter {
visibility: hidden;
}
*/
#gresults-results-field-filters-container.resizable {
border-bottom:5px double #ddd;
min-height:120px;
}
#gresults-results-field-filters {
height:100%;
overflow-y:auto;
}
.ui-resizable {
position:relative;
}
.ui-resizable-handle {
position:absolute;
font-size:0.1px;
z-index:99999;
display:block;
}
.ui-resizable-s {
cursor:s-resize;
height:7px;
width:100%;
bottom:-5px;
left:0px;
}
.gsurvey-likert-score{
margin-top:5px;
}
/* NEW */
#gquiz-results-summary { margin: 60px 0; }
.gresults-chart-wrapper { border-top: 1px solid #dfdfdf; margin: 0 0 28px; }
.gquiz-field-precentages-correct + .gresults-chart-wrapper { margin: 0 0 14px; }
.gresults-label-group { display: block; clear: right; }
.gresults-label-group .gresults-label { display: inline-block; width: 65px; }
.gresults-group-correct .gresults-value { color: green; }
.gresults-group-incorrect .gresults-value { color: red; }
@@ -0,0 +1 @@
table#gquiz-results-summary{table-layout:fixed}table#gquiz-results-summary td{font-weight:700;text-align:center;padding:10px}table#gquiz-results-summary td.gquiz-results-summary-label{font-size:1.2em}table#gquiz-results-summary td.gquiz-results-summary-data{font-size:2em}.gquiz-results-summary-data-box{width:75%;padding:15px 0;min-width:0;margin:auto}.gaddon-results-summary-secondary{padding-top:4px;font-weight:400;font-size:13px}.gaddon-results-summary-primary{padding:5px}.gquiz-field-precentages-correct{float:inline-start;margin-block-start:8px;margin-inline-start:64px}table#gaddon-results-summary{table-layout:fixed}table#gaddon-results-summary td{font-weight:700;text-align:center;padding:10px}table#gaddon-results-summary td.gaddon-results-summary-label{font-size:1.2em}table#gaddon-results-summary td.gaddon-results-summary-data{font-size:2em}.gaddon-results-summary-data-box{border:1px solid silver;padding:10px;width:75%;margin:auto}.gaddon-field-precentages-correct{float:left;margin:15px 0 0 50px}#gresults-results{margin-right:300px}.gform-admin .gresults-filter-loading .gform-spinner{display:inline-block}.gf-results .gf-result-box{background:#fff;border-radius:3px;border:1px solid #e3e6ef;box-shadow:0 1px 4px rgb(18 25 97 / 8%);padding:1.562rem 1rem}.gf-results .gf-result-box{margin-right:0;margin-bottom:1.25rem}.gf-results .gf-result-box__primary{display:flex}@media screen and (min-width:782px){.gf-results{display:flex;justify-content:space-between;margin-bottom:1.25rem}.gf-results .gf-result-box{flex:1 1 0px;margin-inline-end:1.25rem;margin-bottom:0}.gf-results .gf-result-box:last-child{margin-inline-end:0}}.gresults-results-field{margin-bottom:20px}.gresults-results-field table{border-bottom:1px solid silver;border-spacing:0}.gresults-results-field table td{padding:5px;border-right:1px solid silver;border-top:1px solid silver;text-align:center}.gresults-results-field-table-header{background-color:#eee}.gresults-results-field table td:first-child{border-left:1px solid silver}ul.gresults-results-field-content,ul.gresults-results-field-content li{list-style:disc outside none}ul.gresults-results-field-content{margin:10px}td.gresults-rank-field-rank,td.gresults-rank-field-score{width:110px}.gsurvey-rank-field-results table{width:100%}#gresults-results-filter-content{padding:12px;border-top:1px solid #ebebf2}.gresults-results-filter-section-label{font-size:1.2em;font-weight:700;margin-top:10px;margin-bottom:10px}#gresults-results-filter label{margin-top:10px;display:block}#gresults-results-filter-content #gform-no-filters{color:#242748}.gresults-add,.gresults-remove{margin-top:2px;vertical-align:middle;cursor:pointer}.gresults-add{margin-left:5px}#gresults-no-filters{color:silver}.gresults-datepicker,.gresults-filter-field,.gresults-filter-value{width:150px;box-sizing:border-box;-ms-box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;height:2em;padding:2px}.gresults-filter-operator{width:70px}#gresults-results-filter-buttons{clear:both;margin-top:20px;width:180px}.gresults-results-filter-field-label{font-size:1.1em;font-weight:700;margin-bottom:10px}.gresults-results-filter-field{margin-bottom:20px}.gresults-results-filter-field ul li label{margin-left:5px}.gresults-results-filter-title{font-size:1.5em;font-weight:700}#gresults-results-field-filters-container.resizable{border-bottom:5px double #ddd;min-height:120px}#gresults-results-field-filters{height:100%;overflow-y:auto}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:.1px;z-index:99999;display:block}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.gsurvey-likert-score{margin-top:5px}#gquiz-results-summary{margin:60px 0}.gresults-chart-wrapper{border-top:1px solid #dfdfdf;margin:0 0 28px}.gquiz-field-precentages-correct+.gresults-chart-wrapper{margin:0 0 14px}.gresults-label-group{display:block;clear:right}.gresults-label-group .gresults-label{display:inline-block;width:65px}.gresults-group-correct .gresults-value{color:green}.gresults-group-incorrect .gresults-value{color:red}
@@ -0,0 +1,308 @@
/* ------------------ Field Map ------------------ */
.gforms_form_settings.form-table .settings-field-map-table thead th {
font-weight: bold;
text-align: left;
padding-left:0px;
}
table.settings-field-map-table tbody td {
padding: 0px 0px 8px 0px;
}
.settings-field-map-table td:first-child {
width: 220px;
}
.settings-field-map-table td:last-child {
white-space: nowrap;
}
.settings-field-map-table .repeater th, .settings-field-map-table .repeater td:nth-child(2) {
padding-left: 0px;
padding-top: 0px;
width: 220px;
}
.settings-field-map-table select {
font-family: inherit;
height: 25px;
width: 210px;
}
.settings-field-map-table .chosen-container,
.settings-field-map-table .select2-container {
width: 210px !important;
}
.settings-field-map-table .custom-key-container,
.settings-field-map-table .custom-value-container {
position: relative;
width: 210px;
}
.settings-field-map-table .custom-key-container input,
.settings-field-map-table .custom-value-container input {
width: 210px;
}
.settings-field-map-table .custom-key-container input:not(:only-child),
.settings-field-map-table .custom-value-container input:not(:only-child) {
padding-right: 30px;
}
.settings-field-map-table .custom-key-container.supports-merge-tags input:not(:only-child),
.settings-field-map-table .custom-value-container.supports-merge-tags input:not(:only-child) {
padding-right: 50px;
}
.settings-field-map-table .custom-value-container.supports-merge-tags .all-merge-tags {
height: 25px;
position: absolute;
right: 36px;
top: 0;
}
.settings-field-map-table .custom-value-container.supports-merge-tags .all-merge-tags .tooltip-merge-tag {
background-position: center;
height: 25px;
margin: 0;
width: 25px;
}
.settings-field-map-table .custom-key-reset,
.settings-field-map-table .custom-value-reset {
background: url( '../images/field-map-reset.png' ) no-repeat center #ddd;
background-size: 10px 10px;
cursor: pointer;
display: inline-block;
height: 25px;
opacity: .3;
overflow: hidden;
position: absolute;
right: 11px;
text-indent: -9999px;
top: 0;
transition: opacity .25s ease-in-out;
width: 25px;
z-index: 2;
}
.settings-field-map-table .custom-key-reset:hover,
.settings-field-map-table .custom-value-reset:hover {
opacity: 1;
}
.settings-field-map-table .add-item span,
.settings-field-map-table .remove-item span {
background: url( '../images/field-map-buttons.png' ) no-repeat center top transparent;
background-size: 20px 100px;
cursor: pointer;
display: inline-block;
height: 25px;
overflow: hidden;
text-indent: -9999px;
width: 20px;
}
.settings-field-map-table .add-item span:hover {
background-position: 0 -25px;
}
.settings-field-map-table .remove-item span {
background-position: 0 -50px;
}
.settings-field-map-table .remove-item span:hover {
background-position: 0 -75px;
}
@media screen and ( max-width: 782px ) {
.settings-field-map-table .custom-key-container input:not(:only-child),
.settings-field-map-table .custom-value-container input:not(:only-child) {
padding-right: 45px;
}
.settings-field-map-table .custom-key-reset,
.settings-field-map-table .custom-value-reset {
height: 40px;
right: 0;
width: 40px;
}
}
/* ---------------- Select Custom ---------------- */
.gaddon-setting-select-custom-container .select-custom-reset {
background: url( ../../../images/xit.gif ) no-repeat scroll 0 0 transparent;
cursor:pointer;
display:none;
position:absolute;
text-indent:-9999px;
width:10px;
height: 10px;
-moz-transition: none;
-webkit-transition: none;
-o-transition: color 0 ease-in;
transition: none;
z-index: 99;
}
.gaddon-setting-select-custom-container .select-custom-reset:hover { background-position-x: -10px; }
.gaddon-setting-select-custom-container:hover .select-custom-reset {
display:block;
}
.gaddon-setting-select-custom-container {
display:inline-block;
position:relative;
width: 210px;
}
.gaddon-setting-select-custom-container .select-custom-reset {
left: 171px;
top: 10px;
}
.gaddon-section .required { color: #f00; }
.gaddon-setting-inline{
display:inline;
margin-right:6px;
}
.gaddon-section-description ol { }
.gaddon-section-description ol li {
list-style: decimal;
}
.repeater-buttons .add-item {
margin-right: 6px;
}
.add-item, .remove-item {
color: #444;
}
.mt-gaddon-editor {
float: right;
position: relative;
right: 0.625rem;
top: 2.8125rem;
z-index: 1;
}
div#tab_notification .mt-gaddon-editor {
top: 3.3125rem;
}
html[dir="rtl"] .mt-gaddon-editor {
float: left;
}
.mt-gaddon-editor ~ .wp-editor-wrap {
margin-right: 23px;
}
/* Visual Radio Buttons */
.gaddon-setting-choice-visual {
display: inline-block;
margin-bottom: 5px;
text-align: center;
}
.gaddon-setting-choice-visual label {
background: #F9F9F9;
border: 1px solid #eee;
display: inline-block;
}
.gaddon-setting-choice-visual label > span {
display: inline-block;
-webkit-filter: brightness( 1.8 ) grayscale( 1 ) opacity( .5 );
-moz-filter: brightness( 1.8 ) grayscale( 1 ) opacity( .5 );
filter: brightness( 1.8 ) grayscale( 1 ) opacity( .5 );
height: 65px;
min-width: 110px;
padding: 5px 10px 0;
-webkit-transition: all 100ms ease-in;
-moz-transition: all 100ms ease-in;
transition: all 100ms ease-in;
vertical-align: top;
}
.gaddon-setting-choice-visual label > span > i {
color: #0074a2;
display: inline-block;
font-size: 2.5em;
height: 32px;
margin: 5px;
width: 32px;
}
.gaddon-setting-choice-visual label > span > img{
height: 32px;
margin: 5px;
vertical-align: middle;
width: 32px;
}
.gaddon-setting-choice-visual input {
display: none;
}
.gaddon-setting-choice-visual input:checked + label {
background-color: #fff;
border: 1px solid #ccc;
}
.gaddon-setting-choice-visual input:checked + label > span {
-webkit-filter: none;
-moz-filter: none;
filter: none;
}
.gaddon-setting-choice-visual input:not([disabled]):not([checked]) + label > span:hover{
-webkit-filter: brightness(1.2) grayscale(.5) opacity(.9);
-moz-filter: brightness(1.2) grayscale(.5) opacity(.9);
filter: brightness(1.2) grayscale(.5) opacity(.9);
}
/* Feed Ordering */
.ui-sortable-helper {
background-color: #fff !important;
-webkit-box-shadow: 6px 6px 28px -9px rgba(0,0,0,0.75);
-moz-box-shadow: 6px 6px 28px -9px rgba(0,0,0,0.75);
box-shadow: 6px 6px 28px -9px rgba(0,0,0,0.75);
transform: rotate(1deg);
-moz-transform: rotate(1deg);
-webkit-transform: rotate(1deg);
}
.wp-list-table.feed-list-sortable .sort-column {
vertical-align: top;
width: 2.2em;
}
.wp-list-table.feed-list-sortable .feed-sort-handle {
cursor: move;
font-size: 1.25rem;
width: 2.2em;
}
/* ------------------ Feed List ------------------ */
@media screen and ( max-width: 782px ) {
.wp-list-table tbody tr:not(.inline-edit-row):not(.no-items) td:not(.column-primary)::before {
content: attr(data-colname) ":";
font-weight: bold;
}
.wp-list-table.feeds .manage-column {
vertical-align: top;
}
.wp-list-table.feeds .manage-column img {
margin-top: 16px;
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
<?php
//Nothing to see here
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,2 @@
<?php
//Nothing to see here
@@ -0,0 +1,111 @@
var GFFeedOrder = function( args ) {
var self = this,
$ = jQuery;
/**
* Initialize Feed Ordering
*/
self.init = function() {
// Assign options to instance.
self.options = args;
// Prepare sorting handle.
var sortHandleMarkup = '<td class="sort-column"><i class="gform-icon gform-icon--drag-indicator feed-sort-handle"></i></td>';
// Add sorting handle to table.
$( '.wp-list-table thead tr, .wp-list-table tfoot tr' ).append( '<th class="sort-column"></th>' );
$( '.wp-list-table tbody tr' ).append( sortHandleMarkup );
// Initialize sorting.
self.initSorting();
};
/**
* Initialize jQuery UI Sortable.
*/
self.initSorting = function() {
$( '.wp-list-table tbody' ).sortable(
{
cursor: 'move',
handle: '.feed-sort-handle',
placeholder: 'feed-placeholder',
tolerance: 'pointer',
create: function() { $( '.wp-list-table' ).addClass( 'feed-list-sortable' ); },
helper: self.fixSortableColumnWidths,
start: self.setPlaceholderHeight,
update: self.updateFeedOrder,
}
);
}
/**
* Fix table column widths.
*/
self.fixSortableColumnWidths = function( event, tr ) {
var $originals = tr.children(),
$helper = tr.clone();
$helper.children().each( function( index ) {
$( this ).width( $originals.eq( index ).width() );
} );
return $helper;
}
/**
* Get order of feeds.
*/
self.getFeedOrder = function() {
// Get all the checkboxes from the feed list table.
var feed_checkboxes = $( '.wp-list-table tbody .check-column input[type="checkbox"]' );
// Map a function to the feed checkboxes array that returns the checkbox value.
return feed_checkboxes.map( function() {
return $( this ).val();
} ).get();
}
/**
* Set height of the placeholder draggable feed.
*/
self.setPlaceholderHeight = function( event, ui ) {
// Set the height of the placeholder to the height of the feed being moved.
$( '.wp-list-table .feed-placeholder' ).height( ui.item.height() );
}
/**
* Save the feed ordering to the database.
*/
self.updateFeedOrder = function( event, ui ) {
$.ajax(
ajaxurl,
{
method: 'POST',
dataType: 'JSON',
data: {
action: 'gf_save_feed_order',
addon: self.options.addon,
form_id: self.options.formId,
feed_order: self.getFeedOrder(),
nonce: self.options.nonce,
}
}
);
}
this.init();
}
@@ -0,0 +1 @@
var GFFeedOrder=function(e){var o=this,i=jQuery;o.init=function(){o.options=e;i(".wp-list-table thead tr, .wp-list-table tfoot tr").append('<th class="sort-column"></th>'),i(".wp-list-table tbody tr").append('<td class="sort-column"><i class="gform-icon gform-icon--drag-indicator feed-sort-handle"></i></td>'),o.initSorting()},o.initSorting=function(){i(".wp-list-table tbody").sortable({cursor:"move",handle:".feed-sort-handle",placeholder:"feed-placeholder",tolerance:"pointer",create:function(){i(".wp-list-table").addClass("feed-list-sortable")},helper:o.fixSortableColumnWidths,start:o.setPlaceholderHeight,update:o.updateFeedOrder})},o.fixSortableColumnWidths=function(e,t){var o=t.children(),t=t.clone();return t.children().each(function(e){i(this).width(o.eq(e).width())}),t},o.getFeedOrder=function(){return i('.wp-list-table tbody .check-column input[type="checkbox"]').map(function(){return i(this).val()}).get()},o.setPlaceholderHeight=function(e,t){i(".wp-list-table .feed-placeholder").height(t.item.height())},o.updateFeedOrder=function(e,t){i.ajax(ajaxurl,{method:"POST",dataType:"JSON",data:{action:"gf_save_feed_order",addon:o.options.addon,form_id:o.options.formId,feed_order:o.getFeedOrder(),nonce:o.options.nonce}})},this.init()};
@@ -0,0 +1,136 @@
var gfieldmap = function( options ) {
var self = this;
self.options = options;
self.UI = jQuery( '#gaddon-setting-row-'+ self.options.fieldName );
self.init = function() {
self.bindEvents();
self.setupData();
self.setupRepeater();
};
self.bindEvents = function() {
self.UI.on( 'change', 'select[name="_gaddon_setting_'+ self.options.keyFieldName +'"]', function() {
var $select = jQuery( this ),
$selectElm = $select.data( 'chosen' ) ? $select.siblings( '.chosen-container' ) : ( $select.data( 'select2' ) ? $select.siblings( '.select2-container' ) : $select ),
$input = $select.siblings( '.custom-key-container' );
if( $select.val() != 'gf_custom' ) {
return;
}
$selectElm.fadeOut( function() {
$input.fadeIn().focus();
} );
} );
self.UI.on( 'click', 'a.custom-key-reset', function( event ) {
event.preventDefault();
var $reset = jQuery( this ),
$input = $reset.parents( '.custom-key-container' ),
$select = $input.siblings( 'select.key' ),
$selectElm = $select.data( 'chosen' ) ? $select.siblings( '.chosen-container' ) : ( $select.data( 'select2' ) ? $select.siblings( '.select2-container' ) : $select );
$input.fadeOut( function() {
$input.find( 'input' ).val( '' ).change();
$select.val( '' ).trigger( 'change' );
$selectElm.fadeIn().focus();
} );
} );
self.UI.closest( 'form' ).on( 'submit', function( event ) {
jQuery( '[name^="_gaddon_setting_'+ self.options.fieldName +'_"]' ).each( function( i ) {
jQuery( this ).removeAttr( 'name' );
} );
} );
};
self.setupData = function() {
var data = jQuery( '#' + self.options.fieldId ).val();
self.data = data ? jQuery.parseJSON( data ) : null;
if ( ! self.data ) {
self.data = [ {
key: '',
value: '',
custom_key: ''
} ];
}
}
self.setupRepeater = function() {
var limit;
if (self.options.limit > 0){
limit = self.options.limit;
}
else{
limit = 0;
}
self.UI.find( 'tbody.repeater' ).repeater( {
limit: limit,
items: self.data,
addButtonMarkup: '<i class="gficon-add"></i>',
removeButtonMarkup: '<i class="gficon-subtract"></i>',
callbacks: {
add: function( obj, $elem, item ) {
var key_select = $elem.find( 'select[name="_gaddon_setting_'+ self.options.keyFieldName +'"]' );
if ( ! item.custom_key && key_select.length > 0 ) {
$elem.find( '.custom-key-container' ).hide();
} else {
$elem.find( '.key' ).hide();
}
gform.doAction( 'gform_fieldmap_add_row', obj, $elem, item );
},
save: function( obj, data ) {
data = jQuery.extend( {}, data );
for ( var i = 0; i < data.length; i++ ) {
if ( data[i].custom_key != '' ) {
data[i].custom = 1;
data[i].key = data[i].custom_key;
}
delete data[i].custom_key;
}
jQuery( '#'+ self.options.fieldId ).val( jQuery.toJSON( data ) );
}
}
} );
}
return self.init();
};
@@ -0,0 +1 @@
var gfieldmap=function(e){var a=this;return a.options=e,a.UI=jQuery("#gaddon-setting-row-"+a.options.fieldName),a.init=function(){a.bindEvents(),a.setupData(),a.setupRepeater()},a.bindEvents=function(){a.UI.on("change",'select[name="_gaddon_setting_'+a.options.keyFieldName+'"]',function(){var e=jQuery(this),t=e.data("chosen")?e.siblings(".chosen-container"):e.data("select2")?e.siblings(".select2-container"):e,n=e.siblings(".custom-key-container");"gf_custom"==e.val()&&t.fadeOut(function(){n.fadeIn().focus()})}),a.UI.on("click","a.custom-key-reset",function(e){e.preventDefault();var t=jQuery(this).parents(".custom-key-container"),n=t.siblings("select.key"),i=n.data("chosen")?n.siblings(".chosen-container"):n.data("select2")?n.siblings(".select2-container"):n;t.fadeOut(function(){t.find("input").val("").change(),n.val("").trigger("change"),i.fadeIn().focus()})}),a.UI.closest("form").on("submit",function(e){jQuery('[name^="_gaddon_setting_'+a.options.fieldName+'_"]').each(function(e){jQuery(this).removeAttr("name")})})},a.setupData=function(){var e=jQuery("#"+a.options.fieldId).val();a.data=e?jQuery.parseJSON(e):null,a.data||(a.data=[{key:"",value:"",custom_key:""}])},a.setupRepeater=function(){var e=0<a.options.limit?a.options.limit:0;a.UI.find("tbody.repeater").repeater({limit:e,items:a.data,addButtonMarkup:'<i class="gficon-add"></i>',removeButtonMarkup:'<i class="gficon-subtract"></i>',callbacks:{add:function(e,t,n){var i=t.find('select[name="_gaddon_setting_'+a.options.keyFieldName+'"]');(!n.custom_key&&0<i.length?t.find(".custom-key-container"):t.find(".key")).hide(),gform.doAction("gform_fieldmap_add_row",e,t,n)},save:function(e,t){t=jQuery.extend({},t);for(var n=0;n<t.length;n++)""!=t[n].custom_key&&(t[n].custom=1,t[n].key=t[n].custom_key),delete t[n].custom_key;jQuery("#"+a.options.fieldId).val(jQuery.toJSON(t))}}})},a.init()};
@@ -0,0 +1,247 @@
var GFFrontendFeeds = function( args ) {
var self = this,
$ = jQuery;
/**
* Initialize Feed Ordering
*/
self.init = function() {
// Assign options to instance.
self.options = args;
self.triggerInputIds = self.getTriggerInputIds( self.options.feeds );
self.activeFeeds = [];
self.evaluateFeeds();
self.bindEvents();
};
self.bindEvents = function() {
gform.addAction( 'gform_input_change', function( elem, formId, inputId ) {
gform.logger.log( 'GFFrontendFeeds: gform_input_change event triggered. formId ' + formId + ', inputId ' + inputId );
var fieldId = parseInt( inputId ) + '';
var isTriggeredInput = $.inArray( inputId, self.triggerInputIds ) !== -1 || $.inArray( fieldId , self.triggerInputIds ) !== -1 ;
if( self.options.formId == formId && isTriggeredInput ) {
gform.logger.log( 'GFFrontendFeeds: Evaluating feeds. self.options.formId=' + self.options.formId + ', isTriggeredInput=' + isTriggeredInput + ', triggerInputIds=' + self.triggerInputIds.join( ',' ) );
self.evaluateFeeds();
} else {
gform.logger.log( 'GFFrontendFeeds: Bypassing feed evaluation. self.options.formId=' + self.options.formId + ', isTriggeredInput=' + isTriggeredInput + ', triggerInputIds=' + self.triggerInputIds.join( ',' ) );
}
} );
};
self.saveToState = function() {
const feeds = self.options.feeds.map(({ feedId, isActivated, transactionType }) => ({ feedId, isActivated, transactionType }));
gform.logger.log( 'GFFrontendFeeds: Saving feeds to state: ' + JSON.stringify( feeds ) );
gform.state.set( self.options.formId, 'feeds', self.options.feeds );
}
self.evaluateFeeds = function() {
var feed, isMatch, isActivated;
for( i = 0; i < self.options.feeds.length; i++ ) {
feed = self.options.feeds[ i ];
isMatch = self.evaluateFeed( feed, self.options.formId );
isActivated = self.isFeedActivated( feed );
if( ! isMatch && isActivated !== null ) {
self.deactivateFeed( feed );
} else if( isMatch && ! isActivated && ( ! feed.isSingleFeed || ( feed.isSingleFeed && self.hasPriority( feed.feedId, feed.addonSlug ) ) ) ) {
self.activateFeed( feed );
}
}
/**
* Fires after the conditional logic on the form has been evaluated.
*
* @since 2.4
*
* @param array $feeds A collection of feed objects.
* @param int $formId The form id.
*/
gform.doAction( 'gform_frontend_feeds_evaluated', self.options.feeds, self.options.formId, self );
gform.doAction( 'gform_frontend_feeds_evaluated_{0}'.gformFormat( self.options.formId ), self.options.feeds, self.options.formId, self );
gform.doAction( 'gform_{0}_frontend_feeds_evaluated'.gformFormat( feed.addonSlug ), self.options.feeds, self.options.formId, self );
gform.doAction( 'gform_{0}_frontend_feeds_evaluated_{0}'.gformFormat( feed.addonSlug, self.options.formId ), self.options.feeds, self.options.formId, self );
self.saveToState();
};
self.evaluateFeed = function( feed, formId ) {
// Feeds with no configured conditional logic always a match.
if( ! feed.conditionalLogic ) {
return true;
}
return gf_get_field_action( formId, feed.conditionalLogic ) == 'show';
};
self.getTriggerInputIds = function() {
var inputIds = [];
for( var i = 0; i < self.options.feeds.length; i++ ) {
var feed = self.options.feeds[ i ];
if( ! feed.conditionalLogic ) {
continue;
}
for( var j = 0; j < feed.conditionalLogic.rules.length; j++ ) {
var rule = self.options.feeds[i].conditionalLogic.rules[j];
if( $.inArray( rule.fieldId, inputIds ) == -1 ) {
inputIds.push( rule.fieldId );
}
}
}
return inputIds;
};
self.isFeedActivated = function( feed ) {
if( typeof feed != 'object' ) {
feed = self.getFeed( feed );
if( ! feed ) {
return false;
}
}
return typeof feed.isActivated != 'undefined' ? feed.isActivated : null;
};
self.getFeed = function( feedId ) {
for( var i = 0; i < self.options.feeds.length; i++ ) {
var feed = self.options.feeds[ i ];
if( feed.feedId == feedId ) {
return feed;
}
}
return false;
};
self.getFeedsByAddon = function( addonSlug, currentFeed, onlyActive ) {
var feeds = [];
for( var i = 0; i < self.options.feeds.length; i++ ) {
var feed = self.options.feeds[ i ];
if( feed.addonSlug == addonSlug
&& ! ( currentFeed && feed.feedId == currentFeed.feedId )
) {
if( onlyActive ) {
if( self.isFeedActivated( feed ) ) {
feeds.push( feed );
}
} else {
feeds.push( feed );
}
}
}
return feeds;
};
self.activateFeed = function( feeds ) {
if( feeds.feedId ) {
feeds = [ feeds ];
}
for( var i = 0; i < feeds.length; i++ ) {
var feed = feeds[ i ];
feed.isActivated = true;
/**
* Fires after the conditional logic on the form has been evaluated and the feed has been found to be active.
*
* @since 2.4
*
* @param array $feeds A collection of feed objects.
* @param int $formId The form id.
*/
gform.doAction( 'gform_frontend_feed_activated', feed, self.options.formId );
gform.doAction( 'gform_frontend_feed_activated_{0}'.gformFormat( self.options.formId ), feed, self.options.formId );
gform.doAction( 'gform_{0}_frontend_feed_activated'.gformFormat( feed.addonSlug ), feed, self.options.formId );
gform.doAction( 'gform_{0}_frontend_feed_activated_{0}'.gformFormat( feed.addonSlug, self.options.formId ), feed, self.options.formId );
if( feed.isSingleFeed ) {
self.deactivateFeed( self.getFeedsByAddon( feed.addonSlug, feed ) );
}
}
};
self.deactivateFeed = function( feeds ) {
if( feeds.feedId ) {
feeds = [ feeds ];
}
for( var i = 0; i < feeds.length; i++ ) {
var feed = feeds[ i ],
isActivated = self.isFeedActivated( feed );
if( isActivated === null || isActivated === false ) {
continue;
}
feed.isActivated = false;
/**
* Fires after the conditional logic on the form has been evaluated and the feed has become inactive.
*
* @since 2.4
*
* @param array $feeds A collection of feed objects.
* @param int $formId The form id.
*/
gform.doAction( 'gform_frontend_feed_deactivated', feed, self.options.formId );
gform.doAction( 'gform_frontend_feed_deactivated_{0}'.gformFormat( self.options.formId ), feed, self.options.formId );
gform.doAction( 'gform_{0}_frontend_feed_deactivated'.gformFormat( feed.addonSlug ), feed, self.options.formId );
gform.doAction( 'gform_{0}_frontend_feed_deactivated_{0}'.gformFormat( feed.addonSlug, self.options.formId ), feed, self.options.formId );
}
};
self.hasPriority = function( feedId, addonSlug ) {
var addonFeeds = self.getFeedsByAddon( addonSlug );
for( var i = 0; i <= addonFeeds.length; i++ ) {
var feed = addonFeeds[i];
if( feed.feedId != feedId && feed.isActivated ) {
return false;
} else if ( feed.feedId == feedId ) {
return true;
}
}
return false;
};
this.init();
};
@@ -0,0 +1 @@
var GFFrontendFeeds=function(e){var f=this,r=jQuery;f.init=function(){f.options=e,f.triggerInputIds=f.getTriggerInputIds(f.options.feeds),f.activeFeeds=[],f.evaluateFeeds(),f.bindEvents()},f.bindEvents=function(){gform.addAction("gform_input_change",function(e,o,t){gform.logger.log("GFFrontendFeeds: gform_input_change event triggered. formId "+o+", inputId "+t);var d=parseInt(t)+"",t=-1!==r.inArray(t,f.triggerInputIds)||-1!==r.inArray(d,f.triggerInputIds);f.options.formId==o&&t?(gform.logger.log("GFFrontendFeeds: Evaluating feeds. self.options.formId="+f.options.formId+", isTriggeredInput="+t+", triggerInputIds="+f.triggerInputIds.join(",")),f.evaluateFeeds()):gform.logger.log("GFFrontendFeeds: Bypassing feed evaluation. self.options.formId="+f.options.formId+", isTriggeredInput="+t+", triggerInputIds="+f.triggerInputIds.join(","))})},f.saveToState=function(){var e=f.options.feeds.map(({feedId:e,isActivated:o,transactionType:t})=>({feedId:e,isActivated:o,transactionType:t}));gform.logger.log("GFFrontendFeeds: Saving feeds to state: "+JSON.stringify(e)),gform.state.set(f.options.formId,"feeds",f.options.feeds)},f.evaluateFeeds=function(){var e,o,t;for(i=0;i<f.options.feeds.length;i++)e=f.options.feeds[i],o=f.evaluateFeed(e,f.options.formId),t=f.isFeedActivated(e),o||null===t?!o||t||e.isSingleFeed&&(e.isSingleFeed,!f.hasPriority(e.feedId,e.addonSlug))||f.activateFeed(e):f.deactivateFeed(e);gform.doAction("gform_frontend_feeds_evaluated",f.options.feeds,f.options.formId,f),gform.doAction("gform_frontend_feeds_evaluated_{0}".gformFormat(f.options.formId),f.options.feeds,f.options.formId,f),gform.doAction("gform_{0}_frontend_feeds_evaluated".gformFormat(e.addonSlug),f.options.feeds,f.options.formId,f),gform.doAction("gform_{0}_frontend_feeds_evaluated_{0}".gformFormat(e.addonSlug,f.options.formId),f.options.feeds,f.options.formId,f),f.saveToState()},f.evaluateFeed=function(e,o){return!e.conditionalLogic||"show"==gf_get_field_action(o,e.conditionalLogic)},f.getTriggerInputIds=function(){for(var e=[],o=0;o<f.options.feeds.length;o++){var t=f.options.feeds[o];if(t.conditionalLogic)for(var d=0;d<t.conditionalLogic.rules.length;d++){var n=f.options.feeds[o].conditionalLogic.rules[d];-1==r.inArray(n.fieldId,e)&&e.push(n.fieldId)}}return e},f.isFeedActivated=function(e){return!("object"!=typeof e&&!(e=f.getFeed(e)))&&(void 0!==e.isActivated?e.isActivated:null)},f.getFeed=function(e){for(var o=0;o<f.options.feeds.length;o++){var t=f.options.feeds[o];if(t.feedId==e)return t}return!1},f.getFeedsByAddon=function(e,o,t){for(var d=[],n=0;n<f.options.feeds.length;n++){var r=f.options.feeds[n];r.addonSlug!=e||o&&r.feedId==o.feedId||t&&!f.isFeedActivated(r)||d.push(r)}return d},f.activateFeed=function(e){e.feedId&&(e=[e]);for(var o=0;o<e.length;o++){var t=e[o];t.isActivated=!0,gform.doAction("gform_frontend_feed_activated",t,f.options.formId),gform.doAction("gform_frontend_feed_activated_{0}".gformFormat(f.options.formId),t,f.options.formId),gform.doAction("gform_{0}_frontend_feed_activated".gformFormat(t.addonSlug),t,f.options.formId),gform.doAction("gform_{0}_frontend_feed_activated_{0}".gformFormat(t.addonSlug,f.options.formId),t,f.options.formId),t.isSingleFeed&&f.deactivateFeed(f.getFeedsByAddon(t.addonSlug,t))}},f.deactivateFeed=function(e){e.feedId&&(e=[e]);for(var o=0;o<e.length;o++){var t=e[o],d=f.isFeedActivated(t);null!==d&&!1!==d&&(t.isActivated=!1,gform.doAction("gform_frontend_feed_deactivated",t,f.options.formId),gform.doAction("gform_frontend_feed_deactivated_{0}".gformFormat(f.options.formId),t,f.options.formId),gform.doAction("gform_{0}_frontend_feed_deactivated".gformFormat(t.addonSlug),t,f.options.formId),gform.doAction("gform_{0}_frontend_feed_deactivated_{0}".gformFormat(t.addonSlug,f.options.formId),t,f.options.formId))}},f.hasPriority=function(e,o){for(var t=f.getFeedsByAddon(o),d=0;d<=t.length;d++){var n=t[d];if(n.feedId!=e&&n.isActivated)return!1;if(n.feedId==e)return!0}return!1},this.init()};
@@ -0,0 +1,166 @@
var GFGenericMap = function( options ) {
var self = this;
self.options = options;
self.UI = jQuery( '#gaddon-setting-row-'+ self.options.fieldName );
self.init = function() {
self.bindEvents();
self.setupData();
self.setupRepeater();
};
self.bindEvents = function() {
self.UI.on( 'change', 'select[name="_gaddon_setting_'+ self.options.keyFieldName +'"]', function() {
var $select = jQuery( this ),
$selectElm = $select.data( 'chosen' ) ? $select.siblings( '.chosen-container' ) : ( $select.data( 'select2' ) ? $select.siblings( '.select2-container' ) : $select ),
$input = $select.siblings( '.custom-key-container' );
if( $select.val() != 'gf_custom' ) {
return;
}
$selectElm.fadeOut( function() {
$input.fadeIn().focus();
} );
} );
self.UI.on( 'change', 'select[name="_gaddon_setting_'+ self.options.valueFieldName +'"]', function() {
var $select = jQuery( this ),
$selectElm = $select.data( 'chosen' ) ? $select.siblings( '.chosen-container' ) : ( $select.data( 'select2' ) ? $select.siblings( '.select2-container' ) : $select ),
$input = $select.siblings( '.custom-value-container' );
if ( $select.val() != 'gf_custom' ) {
return;
}
$selectElm.fadeOut( function() {
$input.fadeIn().focus();
} );
} );
self.UI.on( 'click', 'a.custom-key-reset', function( event ) {
event.preventDefault();
var $reset = jQuery( this ),
$input = $reset.parents( '.custom-key-container' ),
$select = $input.siblings( 'select.key' ),
$selectElm = $select.data( 'chosen' ) ? $select.siblings( '.chosen-container' ) : ( $select.data( 'select2' ) ? $select.siblings( '.select2-container' ) : $select );
$input.fadeOut( function() {
$input.find( 'input' ).val( '' ).change();
$select.val( '' ).trigger( 'change' );
$selectElm.fadeIn().focus();
} );
} );
self.UI.on( 'click', 'a.custom-value-reset', function( event ) {
event.preventDefault();
var $reset = jQuery( this ),
$input = $reset.parents( '.custom-value-container' ),
$select = $input.siblings( 'select.value' ),
$selectElm = $select.data( 'chosen' ) ? $select.siblings( '.chosen-container' ) : ( $select.data( 'select2' ) ? $select.siblings( '.select2-container' ) : $select );
$input.fadeOut( function() {
$input.find( 'input' ).val( '' ).change();
$select.val( '' ).trigger( 'change' );
$selectElm.fadeIn().focus();
} );
} );
self.UI.closest( 'form' ).on( 'submit', function( event ) {
jQuery( '[name^="_gaddon_setting_'+ self.options.fieldName +'_"]' ).each( function( i ) {
jQuery( this ).removeAttr( 'name' );
} );
} );
};
self.setupData = function() {
var data = jQuery( '#' + self.options.fieldId ).val();
self.data = data ? jQuery.parseJSON( data ) : null;
if ( ! self.data ) {
self.data = [ {
key: '',
value: '',
custom_key: '',
custom_value: ''
} ];
}
}
self.setupRepeater = function() {
var limit = self.options.limit > 0 ? self.options.limit : 0;
self.UI.find( 'tbody.repeater' ).repeater( {
limit: limit,
items: self.data,
addButtonMarkup: '<i class="gficon-add"></i>',
removeButtonMarkup: '<i class="gficon-subtract"></i>',
callbacks: {
add: function( obj, $elem, item ) {
var key_select = $elem.find( 'select[name="_gaddon_setting_'+ self.options.keyFieldName +'"]' );
if ( ! item.custom_key && ( key_select.length > 0 && key_select.val() !== 'gf_custom' ) ) {
$elem.find( '.custom-key-container' ).hide();
} else {
$elem.find( '.key' ).hide();
}
var value_select = $elem.find( 'select[name="_gaddon_setting_'+ self.options.valueFieldName +'"]' );
if ( ! item.custom_value && ( value_select.length > 0 && value_select.val() !== 'gf_custom' ) ) {
$elem.find( '.custom-value-container' ).hide();
} else {
$elem.find( '.value' ).hide();
}
if ( self.options.mergeTags ) {
new gfMergeTagsObj( form, $elem.find( '.custom-value-container input' ) );
$elem.find( '.custom-value-container' ).addClass( 'supports-merge-tags' );
}
if ( window.hasOwnProperty( 'gform' ) ) {
gform.doAction( 'gform_fieldmap_add_row', obj, $elem, item );
}
},
save: function( obj, data ) {
jQuery( '#'+ self.options.fieldId ).val( JSON.stringify( data ) );
}
}
} );
}
return self.init();
};
@@ -0,0 +1 @@
var GFGenericMap=function(e){var i=this;return i.options=e,i.UI=jQuery("#gaddon-setting-row-"+i.options.fieldName),i.init=function(){i.bindEvents(),i.setupData(),i.setupRepeater()},i.bindEvents=function(){i.UI.on("change",'select[name="_gaddon_setting_'+i.options.keyFieldName+'"]',function(){var e=jQuery(this),n=e.data("chosen")?e.siblings(".chosen-container"):e.data("select2")?e.siblings(".select2-container"):e,t=e.siblings(".custom-key-container");"gf_custom"==e.val()&&n.fadeOut(function(){t.fadeIn().focus()})}),i.UI.on("change",'select[name="_gaddon_setting_'+i.options.valueFieldName+'"]',function(){var e=jQuery(this),n=e.data("chosen")?e.siblings(".chosen-container"):e.data("select2")?e.siblings(".select2-container"):e,t=e.siblings(".custom-value-container");"gf_custom"==e.val()&&n.fadeOut(function(){t.fadeIn().focus()})}),i.UI.on("click","a.custom-key-reset",function(e){e.preventDefault();var n=jQuery(this).parents(".custom-key-container"),t=n.siblings("select.key"),a=t.data("chosen")?t.siblings(".chosen-container"):t.data("select2")?t.siblings(".select2-container"):t;n.fadeOut(function(){n.find("input").val("").change(),t.val("").trigger("change"),a.fadeIn().focus()})}),i.UI.on("click","a.custom-value-reset",function(e){e.preventDefault();var n=jQuery(this).parents(".custom-value-container"),t=n.siblings("select.value"),a=t.data("chosen")?t.siblings(".chosen-container"):t.data("select2")?t.siblings(".select2-container"):t;n.fadeOut(function(){n.find("input").val("").change(),t.val("").trigger("change"),a.fadeIn().focus()})}),i.UI.closest("form").on("submit",function(e){jQuery('[name^="_gaddon_setting_'+i.options.fieldName+'_"]').each(function(e){jQuery(this).removeAttr("name")})})},i.setupData=function(){var e=jQuery("#"+i.options.fieldId).val();i.data=e?jQuery.parseJSON(e):null,i.data||(i.data=[{key:"",value:"",custom_key:"",custom_value:""}])},i.setupRepeater=function(){var e=0<i.options.limit?i.options.limit:0;i.UI.find("tbody.repeater").repeater({limit:e,items:i.data,addButtonMarkup:'<i class="gficon-add"></i>',removeButtonMarkup:'<i class="gficon-subtract"></i>',callbacks:{add:function(e,n,t){var a=n.find('select[name="_gaddon_setting_'+i.options.keyFieldName+'"]'),a=((!t.custom_key&&0<a.length&&"gf_custom"!==a.val()?n.find(".custom-key-container"):n.find(".key")).hide(),n.find('select[name="_gaddon_setting_'+i.options.valueFieldName+'"]'));(!t.custom_value&&0<a.length&&"gf_custom"!==a.val()?n.find(".custom-value-container"):n.find(".value")).hide(),i.options.mergeTags&&(new gfMergeTagsObj(form,n.find(".custom-value-container input")),n.find(".custom-value-container").addClass("supports-merge-tags")),window.hasOwnProperty("gform")&&gform.doAction("gform_fieldmap_add_row",e,n,t)},save:function(e,n){jQuery("#"+i.options.fieldId).val(JSON.stringify(n))}}})},i.init()};
@@ -0,0 +1,74 @@
function loadBillingLength(setting_name){
var intervals = window[setting_name + "_intervals"]
if(!intervals)
return;
var unit = jQuery("#" + setting_name + "_unit").val();
var min = gform.utils.escapeHtml( intervals[unit]["min"] );
var max = gform.utils.escapeHtml( intervals[unit]["max"] );
var lengthField = jQuery("#" + setting_name + "_length");
var length = lengthField.val();
var str = "";
for(var i=min; i<=max; i++){
var selected = length == i ? "selected='selected'" : "";
str += "<option value='" + i + "' " + selected + ">" + i + "</option>";
}
lengthField.html( str );
}
function cancel_subscription( entryId ) {
if ( !confirm( gaddon_payment_strings.subscriptionCancelWarning ) )
return;
jQuery( "#subscription_cancel_spinner" ).show();
jQuery( "#cancelsub" ).prop( "disabled", true );
jQuery.post(
ajaxurl,
{
action: "gaddon_cancel_subscription",
entry_id: entryId,
gaddon_cancel_subscription: gaddon_payment_strings.subscriptionCancelNonce
},
function ( response ) {
jQuery( "#subscription_cancel_spinner" ).hide();
if ( response.success === true ) {
jQuery( "#gform_payment_status" ).html( gform.utils.escapeHtml( gaddon_payment_strings.subscriptionCanceled ) );
jQuery( "#cancelsub" ).hide();
} else {
jQuery( "#cancelsub" ).prop( "disabled", false );
if ( response.success === false ) {
alert( gaddon_payment_strings.subscriptionError );
}
}
}
);
}
function gaddon_cancel_subscription_confirmed( element ) {
var entryId = jQuery( element ).data( 'entry-id' );
jQuery( '#subscription_cancel_spinner' ).show();
jQuery( '#cancelsub' ).prop( 'disabled', true );
jQuery.post(
ajaxurl,
{
action: 'gaddon_cancel_subscription',
entry_id: entryId,
gaddon_cancel_subscription: gaddon_payment_strings.subscriptionCancelNonce
},
function ( response ) {
jQuery( '#subscription_cancel_spinner' ).hide();
if ( response.success === true ) {
jQuery( '#gform_payment_status' ).html( gform.utils.escapeHtml( gaddon_payment_strings.subscriptionCanceled ) );
jQuery( '#cancelsub' ).hide();
} else {
jQuery( '#cancelsub' ).prop( 'disabled', false );
if ( response.success === false ) {
gform.alert( gaddon_payment_strings.subscriptionError );
}
}
}
);
}
@@ -0,0 +1 @@
function loadBillingLength(n){var s=window[n+"_intervals"];if(s){for(var e=jQuery("#"+n+"_unit").val(),c=gform.utils.escapeHtml(s[e].min),i=gform.utils.escapeHtml(s[e].max),s=jQuery("#"+n+"_length"),r=s.val(),a="",t=c;t<=i;t++)a+="<option value='"+t+"' "+(r==t?"selected='selected'":"")+">"+t+"</option>";s.html(a)}}function cancel_subscription(n){confirm(gaddon_payment_strings.subscriptionCancelWarning)&&(jQuery("#subscription_cancel_spinner").show(),jQuery("#cancelsub").prop("disabled",!0),jQuery.post(ajaxurl,{action:"gaddon_cancel_subscription",entry_id:n,gaddon_cancel_subscription:gaddon_payment_strings.subscriptionCancelNonce},function(n){jQuery("#subscription_cancel_spinner").hide(),!0===n.success?(jQuery("#gform_payment_status").html(gform.utils.escapeHtml(gaddon_payment_strings.subscriptionCanceled)),jQuery("#cancelsub").hide()):(jQuery("#cancelsub").prop("disabled",!1),!1===n.success&&alert(gaddon_payment_strings.subscriptionError))}))}function gaddon_cancel_subscription_confirmed(n){n=jQuery(n).data("entry-id");jQuery("#subscription_cancel_spinner").show(),jQuery("#cancelsub").prop("disabled",!0),jQuery.post(ajaxurl,{action:"gaddon_cancel_subscription",entry_id:n,gaddon_cancel_subscription:gaddon_payment_strings.subscriptionCancelNonce},function(n){jQuery("#subscription_cancel_spinner").hide(),!0===n.success?(jQuery("#gform_payment_status").html(gform.utils.escapeHtml(gaddon_payment_strings.subscriptionCanceled)),jQuery("#cancelsub").hide()):(jQuery("#cancelsub").prop("disabled",!1),!1===n.success&&gform.alert(gaddon_payment_strings.subscriptionError))})}
@@ -0,0 +1,265 @@
var gresultsAjaxRequest;
var gresults = {
drawCharts: function () {
var containers = jQuery('.gresults-chart-wrapper');
containers.each(function (index, elem) {
var id = jQuery(elem).attr('id');
var options = jQuery(elem).data('options');
var datatable = jQuery(elem).data('datatable');
var chartType = jQuery(elem).data('charttype');
var data_array = datatable;
var data = google.visualization.arrayToDataTable(data_array);
var cont = document.getElementById(id);
var chart;
if (chartType == "bar") {
chart = new google.visualization.BarChart(cont);
} else if (chartType == "pie") {
chart = new google.visualization.PieChart(cont);
} else if (chartType == "column") {
chart = new google.visualization.ColumnChart(cont);
}
chart.draw(data, options);
});
// For RTL languages, set the text-anchor of bar chart labels to "start" to keep proper alignment.
const isRTL = document.documentElement.dir === 'rtl';
const isResultsWrapper = document.querySelector('.gresults-chart-wrapper svg')
if (isResultsWrapper && isRTL) {
document.querySelectorAll('text[text-anchor="end"]').forEach(function(el) {
el.setAttribute('text-anchor', 'start');
})
}
},
renderStateData: function (state) {
var results = jQuery("#gresults-results");
results.data('searchcriteria', state.searchCriteria);
jQuery("#gresults-results-filter").html(state.filterUI);
results.css('opacity', 0);
results.html(state.html);
gresults.drawCharts();
results.fadeTo("slow", 1);
var filterContainer = jQuery("#gresults-results-field-filters-container");
filterContainer.resizable();
filterContainer.resizable('destroy');
filterContainer.resizable({
handles: 's'
});
},
getResults: function () {
gresults.recordFormState();
var gresultsData = jQuery('#gresults-results-filter-form').serialize();
gresults.sendRequest(gresultsData)
},
sendRequest: function (gresultsData, serverStateObject, checkSum) {
var results = jQuery("#gresults-results");
var filterButtons = jQuery("#gresults-results-filter-buttons input");
var viewSlug = jQuery("#gresults-view-slug").val();
var nonce = jQuery("#_gf_results_nonce").val()
var data_str = "action=gresults_get_results_" + viewSlug + "&" + gresultsData + '&_gf_results_nonce' + nonce ;
if (serverStateObject)
data_str += "&state=" + serverStateObject + "&checkSum=" + checkSum;
gresultsAjaxRequest = jQuery.ajax({
url : ajaxurl,
type : 'POST',
dataType : 'json',
data : data_str,
beforeSend: function (xhr, opts) {
results.fadeTo("slow", 0.33);
results.html('');
gform.utils.trigger( { event: 'gform/page_loader/show' } );
filterButtons.attr('disabled', 'disabled');
}
})
.done(function (response) {
if (!response || response === -1) {
gform.utils.trigger( { event: 'gform/page_loader/hide' } );
results.html(gresultsStrings.ajaxError);
} else {
if (response.status === "complete") {
filterButtons.removeAttr('disabled');
gform.utils.trigger( { event: 'gform/page_loader/hide' } );
results.html(response.html);
jQuery("#gresults-results").data('searchcriteria', response.searchCriteria); //used in 'more' links
var filterUI = jQuery("#gresults-results-filter").html();
gresults.drawCharts();
results.fadeTo("slow", 1);
if (window.history.replaceState) {
if (!history.state) {
history.replaceState({"html": response.html, "filterUI": filterUI, "searchCriteria": response.searchCriteria}, "", "?" + gresultsData);
} else {
history.pushState({"html": response.html, "filterUI": filterUI, "searchCriteria": response.searchCriteria}, "", "?" + gresultsData);
}
}
gresults.drawCharts();
if (window["gform_initialize_tooltips"])
gform_initialize_tooltips();
} else if (response.status === "incomplete") {
serverStateObject = response.stateObject;
gresults.sendRequest(gresultsData, serverStateObject, response.checkSum);
results.html(response.html);
} else {
gform.utils.trigger( { event: 'gform/page_loader/hide' } );
results.html(gresultsStrings.ajaxError);
}
}
})
.fail(function (error) {
filterButtons.removeAttr('disabled');
results.fadeTo("fast", 1);
var msg = error.statusText;
gform.utils.trigger( { event: 'gform/page_loader/hide' } );
if (msg == "abort") {
msg = "Request cancelled";
} else {
msg = gresultsStrings.ajaxError;
}
results.html(msg);
})
},
getMoreResults: function (formId, fieldId) {
var container = jQuery('#gresults-results-field-content-' + fieldId),
results = jQuery("#gresults-results"),
offset = jQuery(container).data('offset'),
viewSlug = jQuery("#gresults-view-slug").val(),
searchCriteria = results.data('searchcriteria'),
nonce = jQuery("#_gf_results_nonce").val();
jQuery.ajax({
url : ajaxurl,
type : 'POST',
dataType: 'json',
data : {
action: 'gresults_get_more_results_' + viewSlug,
view: viewSlug,
form_id: formId,
field_id: fieldId,
offset: offset,
search_criteria: searchCriteria,
_gf_results_nonce: nonce
},
success : function (response) {
if (response === -1) {
//permission denied
}
else {
if (response.html)
jQuery(container).append(response.html);
if (!response.more_remaining)
jQuery('#gresults-results-field-more-link-' + fieldId).hide();
jQuery(container).data('offset', response.offset);
}
}
});
return false;
},
clearFilterForm: function () {
jQuery("#gresults-results-field-filters-container").off('click', '.gform-add').gfFilterUI(gresultsFilterSettings, [], true);
jQuery('#gresults-results-filter-form').find('input, select').each(function () {
switch (this.type) {
case 'text':
case 'select-one':
jQuery(this).val('').change();
break;
case 'checkbox':
case 'radio':
this.checked = false;
}
});
},
recordFormState: function () {
jQuery("#gresults-results-filter-form input[type='radio']").each(function () {
if (this.checked) {
jQuery(this).prop("defaultChecked", true);
} else {
jQuery(this).prop("defaultChecked", false);
}
});
jQuery("#gresults-results-filter-form input[type='checkbox']").each(function () {
if (this.checked) {
jQuery(this).prop("defaultChecked", true);
} else {
jQuery(this).prop("defaultChecked", false);
}
});
jQuery("#gresults-results-filter-form input[type='text']").each(function () {
jQuery(this).prop("defaultValue", jQuery(this).val());
});
jQuery("#gresults-results-filter-form select option").each(function () {
jQuery(this).prop("defaultSelected", jQuery(this).prop('selected'));
});
},
setCustomFilter: function(key, value){
elementId = "gresults-custom-" + key;
if(jQuery('#' + elementId).length == 0)
jQuery('#gresults-results-filter-form').append("<input type='hidden' id='" + elementId + "' name='" + key + "' value='" + value + "'>");
else
jQuery('#' + elementId).val(value);
}
};
google.load('visualization', '1', {packages: ['corechart']});
google.setOnLoadCallback(gresults.drawCharts);
jQuery( window ).on( 'load', function () {
if (jQuery("#gresults-results").length > 0) {
jQuery("#gresults-results-field-filters-container").gfFilterUI(gresultsFilterSettings, gresultsInitVars, true);
var $window = jQuery(window);
$window.resize(function (e) {
if (e.target === window) {
gresults.drawCharts();
}
});
window.onpopstate = function (e) {
if (e.state)
gresults.renderStateData(e.state)
};
jQuery("#gresults-results-filter-date-start, #gresults-results-filter-date-end").datepicker({dateFormat: 'yy-mm-dd', changeMonth: true, changeYear: true});
jQuery('.ui-datepicker-trigger').on('click', function() {
jQuery(this).parent().find('input').datepicker( 'show' );
});
jQuery("#gresults-results-filter-form").submit(function (e) {
gresults.getResults();
return false;
});
// Ensure we only treat a state as valid if it contains our expected data.
const initialState = history.state;
const hasGResultsState = initialState && typeof initialState === 'object' && ( 'html' in initialState ) && ( 'searchCriteria' in initialState );
if ( hasGResultsState ) {
gresults.renderStateData( initialState );
} else {
gresults.getResults();
}
if (window["gform_initialize_tooltips"])
gform_initialize_tooltips();
}
});
File diff suppressed because one or more lines are too long
@@ -0,0 +1,131 @@
window.GFToken = null;
( function( $ ) {
GFToken = function( args ) {
for ( var prop in args ) {
if ( args.hasOwnProperty( prop ) )
this[prop] = args[prop];
}
this.form = $( '#gform_' + this.formId );
this.init = function() {
var GFTokenObj = this;
this.tokens = {};
/* Initialize spinner. */
if ( ! this.isAjax )
gformInitSpinner( this.formId );
/* If multipage form, run on gform_page_loaded. */
if ( this.hasPages ) {
$( document ).bind( 'gform_page_loaded', function( event, form_id, current_page ) {
if ( form_id != GFTokenObj.formId)
return;
if ( current_page != GFTokenObj.pageCount)
GFTokenObj.saveEntryData();
} );
}
this.form.submit( function() {
GFTokenObj.onSubmit();
} );
};
this.onSubmit = function() {
if ( this.form.data('gftokensubmitting') ) {
return;
} else {
event.preventDefault();
this.form.data( 'gftokensubmitting', true );
}
this.saveEntryData();
this.processTokens();
}
this.processTokens = function() {
/* Process feeds. */
for ( var feed_id in this.feeds ) {
this.active_feed = this.feeds[feed_id];
/* Create new feed object so we can store the billing information. */
var feed = {
'billing_fields': {},
'id': this.active_feed.id,
'name': this.active_feed.name
};
/* Add billing information to feed object. */
for ( var billing_field in this.active_feed.billing_fields ) {
field_id = this.active_feed.billing_fields[ billing_field ];
feed.billing_fields[ billing_field ] = this.entry_data[ field_id ];
}
/* Get credit card token response. */
window[ this.callback ].createToken( feed, this );
}
}
this.saveEntryData = function() {
var GFPaymentObj = this,
input_prefix = 'input_' + this.formId + '_';
if ( ! this.entry_data )
this.entry_data = {};
this.form.find( 'input[id^="' + input_prefix + '"], select[id^="' + input_prefix + '"], textarea[id^="' + input_prefix + '"]' ).each( function() {
var input_id = $( this ).attr( 'id' ).replace( input_prefix, '' ).replace( '_', '.' );
if ( $.inArray( input_id, GFPaymentObj.fields ) >= 0 )
GFPaymentObj.entry_data[ input_id ] = $( this ).val();
} );
}
this.saveToken = function( token ) {
/* Add token response to tokens array. */
this.tokens[ this.active_feed.id ] = {
'feed_id': this.active_feed.id,
'response': token
};
if ( this.tokens.length == this.feeds.length ) {
/* Add tokens to form. */
this.form.find( this.responseField ).val( $.toJSON( this.tokens ) );
/* Submit the form. */
this.form.submit();
}
}
this.init();
}
} )( jQuery );
@@ -0,0 +1 @@
window.GFToken=null,(n=>{GFToken=function(i){for(var t in i)i.hasOwnProperty(t)&&(this[t]=i[t]);this.form=n("#gform_"+this.formId),this.init=function(){var s=this;this.tokens={},this.isAjax||gformInitSpinner(this.formId),this.hasPages&&n(document).bind("gform_page_loaded",function(i,t,e){t==s.formId&&e!=s.pageCount&&s.saveEntryData()}),this.form.submit(function(){s.onSubmit()})},this.onSubmit=function(){this.form.data("gftokensubmitting")||(event.preventDefault(),this.form.data("gftokensubmitting",!0),this.saveEntryData(),this.processTokens())},this.processTokens=function(){for(var i in this.feeds){this.active_feed=this.feeds[i];var t,e={billing_fields:{},id:this.active_feed.id,name:this.active_feed.name};for(t in this.active_feed.billing_fields)field_id=this.active_feed.billing_fields[t],e.billing_fields[t]=this.entry_data[field_id];window[this.callback].createToken(e,this)}},this.saveEntryData=function(){var t=this,e="input_"+this.formId+"_";this.entry_data||(this.entry_data={}),this.form.find('input[id^="'+e+'"], select[id^="'+e+'"], textarea[id^="'+e+'"]').each(function(){var i=n(this).attr("id").replace(e,"").replace("_",".");0<=n.inArray(i,t.fields)&&(t.entry_data[i]=n(this).val())})},this.saveToken=function(i){this.tokens[this.active_feed.id]={feed_id:this.active_feed.id,response:i},this.tokens.length==this.feeds.length&&(this.form.find(this.responseField).val(n.toJSON(this.tokens)),this.form.submit())},this.init()}})(jQuery);
@@ -0,0 +1,2 @@
<?php
//Nothing to see here
@@ -0,0 +1,329 @@
/**
* jQuery Repeater
*
* Easily create a section of repeatable items.
*
* 1. Include repeater.js
* 2. Define a template to be used by the repeater.
* a. Input elements should have a class "property_{i}" (do not replace {i} with an index, the script will handle this.
* b. The template should include a container for the "row" of elements.
* c. Use the {buttons} merge tag to indicate the location of the repeater buttons.
*
* Example:
* <div class="repeater">
* <!-- Template Start -->
* <div class="row">
* <input class="name_{i}" />
* <input class="age_{i}" />
* {buttons}
* </div>
* <!-- / Template Ends -->
* </div>
*
* 3. Define a "save" callback to handle how your data is saved. It will give you an array of objects representing your data.
*
*/
jQuery.fn.repeater = function( options ) {
var self = this,
defaults = {
template: '',
limit: 5,
items: [{}],
saveEvents: 'blur change',
saveElements: 'input, select',
addButtonMarkup: '+',
removeButtonMarkup: '-',
minItemCount: 1,
callbacks: {
save: function() { },
beforeAdd: function() { },
add: function() { },
beforeAddNew: function() { },
addNew: function() { },
beforeRemove: function() { },
remove: function() { },
repeaterButtons: function() { return false; }
}
};
self.options = jQuery.extend( true, {}, defaults, options );
self.elem = jQuery( this );
self.items = self.options.items;
self.callbacks = self.options.callbacks;
self._template = self.options.template;
self._baseObj = self.items[0];
self.init = function() {
self.stashTemplate();
self.elem.addClass( 'repeater' );
self.refresh();
self.bindEvents();
return self;
}
self.bindEvents = function() {
self.options.saveEvents = self.getNamespacedEvents( self.options.saveEvents );
self.elem.off( 'click.repeater', 'a.add-item' );
self.elem.on( 'click.repeater', 'a.add-item:not(.inactive)', function() {
self.addNewItem( this );
});
self.elem.off( 'click.repeater', 'a.remove-item' );
self.elem.on( 'click.repeater', 'a.remove-item', function( event ){
self.removeItem( this );
});
self.elem.off( self.options.saveEvents, self.options.saveElements );
self.elem.on( self.options.saveEvents, self.options.saveElements, function() {
self.save();
});
}
self.stashTemplate = function() {
// if no template provided or in "storage", use current HTML
if( ! self._template )
self._template = self.elem.html();
self._template = jQuery.trim( self._template );
}
self.addItem = function( item, index ) {
var itemMarkup = self.getItemMarkup( item, index),
itemElem = jQuery( itemMarkup ).addClass( 'item-' + index );
self.callbacks.beforeAdd( self, itemElem, item, index );
self.append( itemElem );
self.populateSelects( item, index );
self.callbacks.add( self, itemElem, item, index );
}
self.getItemMarkup = function( item, index ) {
var itemMarkup = self._template;
for( var property in item ) {
if( ! item.hasOwnProperty( property ) )
continue;
itemMarkup = itemMarkup.replace( /{i}/g, index );
itemMarkup = itemMarkup.replace( '{buttons}', self.getRepeaterButtonsMarkup( index ) );
itemMarkup = itemMarkup.replace( new RegExp( '{' + property + '}', 'g' ), escapeAttr( item[property] ) );
}
return itemMarkup;
}
self.getRepeaterButtonsMarkup = function( index ) {
var buttonsMarkup = self.callbacks.repeaterButtons( self, index );
if( ! buttonsMarkup )
buttonsMarkup = self.getDefaultButtonsMarkup( index );
return buttonsMarkup;
}
self.getDefaultButtonsMarkup = function( index ) {
var cssClass = self.items.length >= self.options.limit && self.options.limit !== 0 ? 'inactive' : '',
buttons = '<a class="add-item ' + cssClass + '" data-index="' + index + '">' + self.options.addButtonMarkup + '</a>';
if( self.items.length > self.options.minItemCount )
buttons += '<a class="remove-item" data-index="' + index + '">' + self.options.removeButtonMarkup + '</a>';
return '<div class="repeater-buttons">' + buttons + '</div>';
}
self.populateSelects = function( item, index ) {
// after appending the row, check each property to see if it is a select and then populate
for ( var property in item ) {
if ( ! item.hasOwnProperty( property ) ) {
continue;
}
var input = self.elem.find( '.' + property + '_' + index );
if ( ! input.is( 'select' ) ) {
continue;
}
if ( jQuery.isArray( item[ property ] ) ) {
input.val( item[ property ] );
} else {
input.find( 'option[value="' + item[ property ] + '"]' ).prop( 'selected', true );
}
}
}
self.addNewItem = function( elemOrItem, index ) {
var isElem = self.isElement( elemOrItem ),
index = parseInt( typeof index !== 'undefined' ? index : ( isElem ? parseInt( jQuery( elemOrItem ).attr( 'data-index' ), 10 ) + 1 : self.items.length ), 10 ),
item = isElem ? self.getBaseObject() : elemOrItem;
self.callbacks.beforeAddNew( self, index );
self.items.splice( index, 0, item );
self.callbacks.addNew( self, index );
self.refresh().save();
return self;
}
self.removeItem = function( elemOrIndex ) {
var index = self.isElement( elemOrIndex ) ? jQuery( elemOrIndex ).attr( 'data-index' ) : elemOrIndex;
self.callbacks.beforeRemove( self, index );
// using delete (over splice) to maintain the correct indexes for
// the items array when saving the data from the UI
delete self.items[index];
self.callbacks.remove( self, index );
self.save().refresh();
}
self.refresh = function() {
self.elem.empty();
for( var i = 0; i < self.items.length; i++ ) {
self.addItem( self.items[i], i );
}
return self;
}
self.save = function() {
var keys = self.getBaseObjectKeys(),
data = [];
for( var i = 0; i < self.items.length; i++ ) {
if( typeof self.items[i] == 'undefined' )
continue;
var item = {};
for( var j = 0; j < keys.length; j++ ) {
var key = keys[j],
id = '.' + key + '_' + i,
value = self.elem.find( id ).val();
item[key] = typeof value == 'undefined' ? false : value;
}
data.push( item );
}
// save data to items
self.items = data;
// save data externally via callback
self.callbacks.save( self, data );
return self;
}
/**
* Loops through the current items array and retrieves the object properties of the
* first valid item object. Originally this would simply pull the object keys from
* the first index of the items array; however, when the first item has been
* 'deleted' (see the save() method), it will be undefined.
*/
self.getBaseObjectKeys = function() {
var keys = [],
items = self.items.length > 0 ? self.items : [ self._baseObj ];
for( var i = 0; i < items.length; i++ ) {
if( typeof items[i] == 'undefined' )
continue;
for( var key in items[i] ) {
if( ! items[i].hasOwnProperty( key ) )
continue;
keys.push( key );
}
break;
}
return keys;
}
self.getBaseObject = function() {
var item = {},
keys = self.getBaseObjectKeys();
for( var i = 0; i < keys.length; i++ ) {
item[keys[i]] = '';
}
return item;
}
self.getNamespacedEvents = function( events ) {
var events = events.split( ' ' ),
namespacedEvents = [];
for( var i = 0; i < events.length; i++ ) {
namespacedEvents.push( events[i] + '.repeater' );
}
return namespacedEvents.join( ' ' );
}
/**
* http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
* @param obj
* @returns {boolean}
*/
self.isElement = function( obj ) {
try {
//Using W3 DOM2 (works for FF, Opera and Chrom)
return obj instanceof HTMLElement;
}
catch(e){
//Browsers not supporting W3 DOM2 don't have HTMLElement and
//an exception is thrown and we end up here. Testing some
//properties that all elements have. (works on IE7)
return (typeof obj==="object") &&
(obj.nodeType===1) && (typeof obj.style === "object") &&
(typeof obj.ownerDocument ==="object");
}
}
return self.init();
};
@@ -0,0 +1 @@
jQuery.fn.repeater=function(e){var i=this;return i.options=jQuery.extend(!0,{},{template:"",limit:5,items:[{}],saveEvents:"blur change",saveElements:"input, select",addButtonMarkup:"+",removeButtonMarkup:"-",minItemCount:1,callbacks:{save:function(){},beforeAdd:function(){},add:function(){},beforeAddNew:function(){},addNew:function(){},beforeRemove:function(){},remove:function(){},repeaterButtons:function(){return!1}}},e),i.elem=jQuery(this),i.items=i.options.items,i.callbacks=i.options.callbacks,i._template=i.options.template,i._baseObj=i.items[0],i.init=function(){return i.stashTemplate(),i.elem.addClass("repeater"),i.refresh(),i.bindEvents(),i},i.bindEvents=function(){i.options.saveEvents=i.getNamespacedEvents(i.options.saveEvents),i.elem.off("click.repeater","a.add-item"),i.elem.on("click.repeater","a.add-item:not(.inactive)",function(){i.addNewItem(this)}),i.elem.off("click.repeater","a.remove-item"),i.elem.on("click.repeater","a.remove-item",function(e){i.removeItem(this)}),i.elem.off(i.options.saveEvents,i.options.saveElements),i.elem.on(i.options.saveEvents,i.options.saveElements,function(){i.save()})},i.stashTemplate=function(){i._template||(i._template=i.elem.html()),i._template=jQuery.trim(i._template)},i.addItem=function(e,t){var n=i.getItemMarkup(e,t),n=jQuery(n).addClass("item-"+t);i.callbacks.beforeAdd(i,n,e,t),i.append(n),i.populateSelects(e,t),i.callbacks.add(i,n,e,t)},i.getItemMarkup=function(e,t){var n,a=i._template;for(n in e)e.hasOwnProperty(n)&&(a=(a=(a=a.replace(/{i}/g,t)).replace("{buttons}",i.getRepeaterButtonsMarkup(t))).replace(new RegExp("{"+n+"}","g"),escapeAttr(e[n])));return a},i.getRepeaterButtonsMarkup=function(e){return i.callbacks.repeaterButtons(i,e)||i.getDefaultButtonsMarkup(e)},i.getDefaultButtonsMarkup=function(e){var t='<a class="add-item '+(i.items.length>=i.options.limit&&0!==i.options.limit?"inactive":"")+'" data-index="'+e+'">'+i.options.addButtonMarkup+"</a>";return i.items.length>i.options.minItemCount&&(t+='<a class="remove-item" data-index="'+e+'">'+i.options.removeButtonMarkup+"</a>"),'<div class="repeater-buttons">'+t+"</div>"},i.populateSelects=function(e,t){for(var n in e){var a;e.hasOwnProperty(n)&&(a=i.elem.find("."+n+"_"+t)).is("select")&&(jQuery.isArray(e[n])?a.val(e[n]):a.find('option[value="'+e[n]+'"]').prop("selected",!0))}},i.addNewItem=function(e,t){var n=i.isElement(e),t=parseInt(void 0!==t?t:n?parseInt(jQuery(e).attr("data-index"),10)+1:i.items.length,10),n=n?i.getBaseObject():e;return i.callbacks.beforeAddNew(i,t),i.items.splice(t,0,n),i.callbacks.addNew(i,t),i.refresh().save(),i},i.removeItem=function(e){e=i.isElement(e)?jQuery(e).attr("data-index"):e;i.callbacks.beforeRemove(i,e),delete i.items[e],i.callbacks.remove(i,e),i.save().refresh()},i.refresh=function(){i.elem.empty();for(var e=0;e<i.items.length;e++)i.addItem(i.items[e],e);return i},i.save=function(){for(var e=i.getBaseObjectKeys(),t=[],n=0;n<i.items.length;n++)if(void 0!==i.items[n]){for(var a={},r=0;r<e.length;r++){var s=e[r],o=i.elem.find("."+s+"_"+n).val();a[s]=void 0!==o&&o}t.push(a)}return i.items=t,i.callbacks.save(i,t),i},i.getBaseObjectKeys=function(){for(var e=[],t=0<i.items.length?i.items:[i._baseObj],n=0;n<t.length;n++)if(void 0!==t[n]){for(var a in t[n])t[n].hasOwnProperty(a)&&e.push(a);break}return e},i.getBaseObject=function(){for(var e={},t=i.getBaseObjectKeys(),n=0;n<t.length;n++)e[t[n]]="";return e},i.getNamespacedEvents=function(e){for(var e=e.split(" "),t=[],n=0;n<e.length;n++)t.push(e[n]+".repeater");return t.join(" ")},i.isElement=function(t){try{return t instanceof HTMLElement}catch(e){return"object"==typeof t&&1===t.nodeType&&"object"==typeof t.style&&"object"==typeof t.ownerDocument}},i.init()};
@@ -0,0 +1,302 @@
<?php
/**
* Handles AJAX services such as validation and submission.
*
* @package Gravity_Forms\Gravity_Forms\Ajax
*/
namespace Gravity_Forms\Gravity_Forms\Ajax;
use GFCommon;
/**
* Class GF_Ajax_Handler
*
* @since 2.9.0
*
* Provides functionality for handling AJAX validation and submission.
*/
class GF_Ajax_Handler {
/**
* Handles the form validation AJAX requests. Uses the global $_POST array and sends the form validation result as a JSON response.
*
* @since 2.9.0
*
* @deprecated 2.9.9 Use GFAPI::validate_form() instead.
* @remove-in 4.0
*/
public function validate_form() {
_deprecated_function( __METHOD__, '2.9.9', 'GFAPI::validate_form()' );
// Check nonce.
$nonce_result = check_ajax_referer( 'gform_ajax_submission', 'gform_ajax_nonce', false );
if ( ! $nonce_result ) {
wp_send_json_error( $this->nonce_validation_message() );
}
$form_id = absint( rgpost( 'form_id' ) );
$target_page = absint( rgpost( 'gform_target_page_number_' . $form_id ) );
$source_page = absint( rgpost( 'gform_source_page_number_' . $form_id ) );
$this->hydrate_get_from_current_page_url();
$result = \GFAPI::validate_form( $form_id, array(), rgpost( 'gform_field_values' ), $target_page, $source_page );
if ( is_wp_error( $result ) ) {
wp_send_json_error( $result->get_error_message() );
}
$form = $result['form'];
if ( ! $result['is_valid'] ) {
$result = $this->add_validation_summary( $form, $result );
}
/**
* Filters the form validation result.
*
* @since 2.9.0
*
* @param array $result The form validation result to be filtered.
*
* @return array The filtered form validation result.
*/
$result = gf_apply_filters( array( 'gform_ajax_validation_result', $form['id'] ), $result );
// Remove form from result.
unset( $result['form'] );
wp_send_json_success( $result );
}
/**
* Handles the form submission AJAX requests. Uses the global $_POST array and sends the form submission result as a JSON response.
*
* @since 2.9.0
*/
public function submit_form() {
// Check nonce.
$nonce_result = check_ajax_referer( 'gform_ajax_submission', 'gform_ajax_nonce', false );
if ( ! $nonce_result ) {
GFCommon::send_json_error( $this->nonce_validation_message() );
}
$this->hydrate_get_from_current_page_url();
$form_id = absint( rgpost( 'form_id' ) );
/**
* Allows actions to be performed right before an AJAX form submission.
*
* @since 2.9.0
*
* @param int $form_id The form ID.
*/
gf_do_action( array( 'gform_ajax_pre_submit_form', $form_id ), $form_id );
// Handling the save link submission.
if ( isset( $_POST['gform_send_resume_link'] ) ) {
$this->submit_save_link();
return;
}
// Getting posted values.
$target_page = absint( rgpost( 'gform_target_page_number_' . $form_id ) );
$source_page = absint( rgpost( 'gform_source_page_number_' . $form_id ) );
$field_values = rgpost( 'gform_field_values' );
$theme = rgpost( 'gform_theme' );
$style = rgpost( 'gform_style_settings' );
$submission_method = rgpost( 'gform_submission_method' );
require_once \GFCommon::get_base_path() . '/form_display.php';
$result = \GFAPI::submit_form( $form_id, array(), $field_values, $target_page, $source_page, \GFFormDisplay::SUBMISSION_INITIATED_BY_WEBFORM );
if ( is_wp_error( $result ) ) {
if ( $result->get_error_code() === 'button_logic_error' ) {
$message = esc_html__( 'There was a problem with your submission.', 'gravityforms' ) . ' ' . $result->get_error_message();
} else {
$message = $result->get_error_message();
}
GFCommon::send_json_error( $message );
}
$form = $result['form'];
// Adding confirmation markup if there is a confirmation message to be displayed.
if ( rgar( $result, 'confirmation_type' ) == 'message' && ! empty( rgar( $result, 'confirmation_message' ) ) ) {
// Get confirmation markup from get_form(). This is necessary to ensure that confirmation markup is properly formatted.
$result['confirmation_markup'] = \GFFormDisplay::get_form( $form_id, false, false, false, $field_values, false, 0, $theme, $style );
} elseif ( ! $result['is_valid'] ) {
$result = $this->add_validation_summary( $form, $result );
// Refresh the form markup if single page or multipage forms have validation errors.
$result['form_markup'] = \GFFormDisplay::get_form( $form_id, (bool) rgpost( 'display_title' ), (bool) rgpost( 'display_description' ), false, $field_values, false, 0, $theme, $style );
} elseif ( $target_page > 0 ) {
// Getting the target page number taking page conditional logic into account.
$page_number = \GFFormDisplay::get_target_page( $form, $source_page, $field_values );
$result = $this->add_validation_summary( $form, $result );
// Getting the field markup for the target page if the form is a multipage form.
$result['page_markup'] = \GFFormDisplay::get_page( $form_id, $page_number, $field_values, $theme, $style, $submission_method );
// Ensure the form UUID is maintained on page changes.
$result['form_unique_id'] = \GFFormsModel::get_form_unique_id( $form_id );
}
$result['submission_type'] = $this->get_submission_type( $target_page, $source_page );
/**
* Filters the ajax form submission result.
*
* @since 2.9.0
*
* @param array $result The form submission result to be filtered.
*
* @return array The filtered form submission result.
*/
$result = gf_apply_filters( array( 'gform_ajax_submission_result', $form_id ), $result );
// Remove form from result.
unset( $result['form'] );
GFCommon::send_json_success( $result );
}
/**
* Returns the submission type based on the target and source page numbers.
*
* @since 2.9.7
*
* @param int $target_page The target page number.
* @param int $source_page The source page number.
*
* @return string The submission type. Possible values are SUBMISSION_TYPE_SUBMIT, SUBMISSION_TYPE_NEXT, SUBMISSION_TYPE_PREVIOUS, and SUBMISSION_TYPE_SAVE_AND_CONTINUE.
*/
public function get_submission_type( $target_page, $source_page ) {
if ( isset( $_POST['gform_send_resume_link'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
return \GFFormDisplay::SUBMISSION_TYPE_SEND_LINK;
} elseif ( rgpost( 'gform_save') ) {
return \GFFormDisplay::SUBMISSION_TYPE_SAVE_AND_CONTINUE;
} elseif ( $target_page === 0 ) {
return \GFFormDisplay::SUBMISSION_TYPE_SUBMIT;
} elseif ( $target_page > $source_page ) {
return \GFFormDisplay::SUBMISSION_TYPE_NEXT;
} else {
return \GFFormDisplay::SUBMISSION_TYPE_PREVIOUS;
}
}
/**
* Handles the save link submission. Uses the $_POST array and sends the save link result as a JSON response.
*
* @since 2.9.0
*
* @return void
*/
public function submit_save_link() {
$form_id = absint( rgpost( 'form_id' ) );
\GFFormDisplay::process_send_resume_link();
$confirmation = \GFFormDisplay::get_form( $form_id, false, false, false, rgpost( 'gform_field_values' ) );
GFCommon::send_json_success(
array(
'is_valid' => true,
'confirmation_type' => 'message',
'confirmation_message' => $confirmation,
'confirmation_markup' => $confirmation,
'submission_type' => \GFFormDisplay::SUBMISSION_TYPE_SEND_LINK,
)
);
}
/**
* Filters the lifespan of the nonce used for AJAX submissions and validation.
*
* @since 2.9.0
*
* @param int $lifespan_in_seconds The lifespan of the nonce in seconds. Defaults to 3 days
* @param string $action The nonce action (gform_ajax_submission or gform_ajax_validation).
*
* @return int The filtered lifespan of the nonce in seconds.
*/
public function nonce_life( $lifespan_in_seconds, $action = '' ) {
if ( in_array( $action, array( 'gform_ajax_submission', 'gform_ajax_validation' ) ) ) {
/**
* Filters the lifespan of the nonce used for AJAX submissions and validation.
*
* @since 2.9.0
*
* @param int $lifespan_in_seconds The lifespan of the nonce in seconds (defaults to 3 days).
* @param string $action The nonce action (gform_ajax_submission or gform_ajax_validation).
*
* @return int The lifespan of the nonce in seconds.
*/
$lifespan_in_seconds = apply_filters( 'gform_nonce_life', 3 * DAY_IN_SECONDS, $action );
}
return $lifespan_in_seconds;
}
/**
* Returns the nonce validation message.
*
* @since 2.9.0
*
* @return string The nonce validation message.
*/
private function nonce_validation_message() {
return esc_html__( 'Your session has expired. Please refresh the page and try again.', 'gravityforms' );
}
/**
* Adds the validation summary properties to the form validation result.
*
* @since 2.9.0
*
* @param array $form The form being validated.
* @param array $result The form validation result.
*
* @return mixed Returns the form validation result with the validation summary properties added.
*/
private function add_validation_summary( $form, $result ) {
$summary = \GFFormDisplay::get_validation_errors_markup( $form, array(), rgar( $form, 'validationSummary' ) );
$result['validation_summary'] = wp_kses( $summary, wp_kses_allowed_html( 'post' ) );
return $result;
}
/**
* Making the current page query string available for use with form filters.
*
* @since 2.9.5
*
* @return void
*/
private function hydrate_get_from_current_page_url() {
$url = rgpost( 'current_page_url' );
if ( empty( $url ) ) {
return;
}
$query_string = parse_url( rawurldecode( $url ), PHP_URL_QUERY );
if ( empty( $query_string ) || ! is_string( $query_string ) ) {
return;
}
parse_str( $query_string, $query );
unset( $query['gf_page'] ); // Removing so it doesn't conflict with gf_ajax_page=preview.
$_GET = array_merge( $_GET, $query ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
}
@@ -0,0 +1,71 @@
<?php
/**
* Service Provider for AJAX Service
*
* @package Gravity_Forms\Gravity_Forms\Ajax
*/
namespace Gravity_Forms\Gravity_Forms\Ajax;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Service_Provider;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
use Gravity_Forms\Gravity_Forms\Ajax\Config\GF_Ajax_Config;
/**
* Class GF_Ajax_Service_Provider
*
* Service provider for the Ajax Service.
*/
class GF_Ajax_Service_Provider extends GF_Service_Provider {
const GF_AJAX_HANDLER = 'gf_ajax_handler';
const GF_AJAX_CONFIG = 'gf_ajax_config';
/**
* Includes all related files and adds all containers.
*
* @param GF_Service_Container $container Container singleton object.
*/
public function register( GF_Service_Container $container ) {
require_once plugin_dir_path( __FILE__ ) . 'class-gf-ajax-handler.php';
require_once plugin_dir_path( __FILE__ ) . 'config/class-gf-ajax-config.php';
// Registering handler
$container->add(
self::GF_AJAX_HANDLER,
function () {
return new GF_Ajax_Handler();
}
);
// Registering config
$container->add(
self::GF_AJAX_CONFIG,
function () use ( $container ) {
return new GF_Ajax_Config( $container->get( GF_Config_Service_Provider::DATA_PARSER ) );
}
);
$container->get( GF_Config_Service_Provider::CONFIG_COLLECTION )->add_config( $container->get( self::GF_AJAX_CONFIG ) );
}
/**
* Initializes service.
*
* @param GF_Service_Container $container Service Container.
*/
public function init( GF_Service_Container $container ) {
parent::init( $container );
$ajax_handler = $container->get( self::GF_AJAX_HANDLER );
// Register nonce lifespan hook.
add_filter( 'nonce_life', array( $ajax_handler, 'nonce_life' ), 10, 2 );
// Register AJAX submission.
add_action( 'wp_ajax_gform_submit_form', array( $ajax_handler, 'submit_form' ) );
add_action( 'wp_ajax_nopriv_gform_submit_form', array( $ajax_handler, 'submit_form' ) );
}
}
@@ -0,0 +1,42 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Ajax\Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use GFForms;
/**
* Config items for Ajax operations
*
* @since 2.9.0
*/
class GF_Ajax_Config extends GF_Config {
protected $name = 'gform_theme_config';
protected $script_to_localize = 'gform_gravityforms_theme';
/**
* Config data.
*
* @return array[]
*/
public function data() {
$preview_query_string = \GFCommon::is_preview() ? '?gf_ajax_page=preview' : '';
return array(
'common' => array(
'form' => array(
'ajax' => array(
'ajaxurl' => admin_url( 'admin-ajax.php' ) . $preview_query_string,
'ajax_submission_nonce' => wp_create_nonce( 'gform_ajax_submission' ),
'i18n' => array(
/* Translators: This is used to announce the current step of a multipage form, 1. first step number, 2. total steps number, example: Step 1 of 5 */
'step_announcement' => esc_html__( 'Step %1$s of %2$s, %3$s', 'gravityforms' ),
'unknown_error' => esc_html__( 'There was an unknown error processing your request. Please try again.', 'gravityforms' ),
'error_403' => esc_html__( 'The request was blocked (403 error) for unknown security reasons. Remove any code-like text (scripts or DB queries) and try again.', 'gravityforms' ),
),
),
),
),
);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Assets\Admin_Dependencies;
use Gravity_Forms\Gravity_Forms\Assets\GF_Dependencies;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
/**
* Class GF_Admin_Script_Dependencies
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Assets\Admin_Dependencies;
*/
class GF_Admin_Script_Dependencies extends GF_Dependencies {
/**
* Items to enqueue globally in admin.
*
* @since 2.6
*
* @var string[]
*/
protected $items = array(
'gform_gravityforms_admin',
);
/**
* Enqueue the item by handle.
*
* @since 2.6
*
* @param $handle
*
* @return void
*/
protected function do_enqueue( $handle ) {
wp_enqueue_script( $handle );
}
/**
* Whether the global scripts should enqueue.
*
* @since 2.6
*
* @return bool
*/
protected function should_enqueue() {
/***
* The newer JavaScript added in 2.5 is now enqueued globally in the admin.
* We implemented this as we are now using code splitting to only inject JavaScript
* dynamically as it is needed, and to also allow our addons easy access to the core libraries
* we use.
*
* This filter allows users to make our admin scripts only load on Gravity Forms admin screens.
* Setting it to false may cause unexpected behavior/feature loss in some addons or core.
*
* @since 2.6.0
*
* @param bool true Load admin scripts globally?
*
* @return bool
*/
return apply_filters( 'gform_load_admin_scripts_globally', true );
}
}
@@ -0,0 +1,46 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Assets\Admin_Dependencies;
use Gravity_Forms\Gravity_Forms\Assets\GF_Dependencies;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
/**
* Class GF_Admin_Style_Dependencies
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Assets\Admin_Dependencies;
*/
class GF_Admin_Style_Dependencies extends GF_Dependencies {
/**
* Items to enqueue globally in admin.
*
* @since 2.6
*
* @var string[]
*/
protected $items = array(
'gform_common_css_utilities',
'gform_common_icons',
'gform_admin_icons',
'gform_admin_components',
'gform_admin_css_utilities',
);
/**
* Enqueue the item by handle.
*
* @since 2.6
*
* @param $handle
*
* @return void
*/
protected function do_enqueue( $handle ) {
wp_enqueue_style( $handle );
}
}
@@ -0,0 +1,84 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Assets;
class GF_Asset_Processor {
/**
* @var array $map - The Hash Map generated by our node scripts.
*/
private $map;
/**
* @var string $asset_path - The path to the js dist directory.
*/
private $asset_path;
/**
* Constructor
*
* @since 2.6
*
* @param array $map
* @param string $asset_path
*
* @return void
*/
public function __construct( $map, $asset_path ) {
$this->map = $map;
$this->asset_path = $asset_path;
}
/**
* Perform processing actions on assets.
*
* @since 2.6
*
* @return void
*/
public function process_assets() {
$this->process_versions();
}
/**
* Process the ver values for all of the registered scripts in order to append a
* file hash (if it exists) or the filemtime (if required).
*
* @since 2.6
*
* @return void
*/
private function process_versions() {
global $wp_scripts;
$registered = $wp_scripts->registered;
foreach( $registered as &$asset ) {
// Bail if not one of our assets or asset is empty.
if ( $asset->src && strpos( $asset->src, 'gravityforms/assets/js/dist' ) === false || is_null( $asset->src ) ) {
continue;
}
$basename = basename( $asset->src );
$path = sprintf( '%s/%s', $this->asset_path, $basename );
// Asset doesn't exist in hash_map, skip.
if ( ! array_key_exists( $basename, $this->map ) ) {
continue;
}
// The hash is either the value from our map, or the filemtime for dev.
$hash = defined( 'GF_DEV_TIME_AS_VER' ) && GF_DEV_TIME_AS_VER ?
filemtime( $path ) :
$this->map[ $basename ]['version'];
$asset->ver = $hash;
}
$wp_scripts->registered = $registered;
return;
}
}
@@ -0,0 +1,138 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Assets;
use Gravity_Forms\Gravity_Forms\Assets\Theme_Dependencies\GF_Theme_Script_Dependencies;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
use Gravity_Forms\Gravity_Forms\Assets\Admin_Dependencies\GF_Admin_Script_Dependencies;
use Gravity_Forms\Gravity_Forms\Assets\Admin_Dependencies\GF_Admin_Style_Dependencies;
/**
* Class GF_Asset_Service_Provider
*
* Service provider for assets.
*
* @package Gravity_Forms\Gravity_Forms\Merge_Tags;
*/
class GF_Asset_Service_Provider extends GF_Service_Provider {
const HASH_MAP = 'hash_map';
const ASSET_PROCESSOR = 'asset_processor';
const STYLE_DEPS = 'gf_global_style_deps';
const SCRIPT_DEPS = 'gf_global_script_deps';
const SCRIPT_DEPS_THEME = 'gf_global_script_deps_theme';
const SVG_OPTIONS = 'gf_svg_options';
private $plugin_dir;
public function __construct( $plugin_dir ) {
$this->plugin_dir = $plugin_dir;
}
/**
* Register services to the container.
*
* @since 2.6
*
* @param GF_Service_Container $container
*/
public function register( GF_Service_Container $container ) {
require_once( plugin_dir_path( __FILE__ ) . '/class-gf-asset-processor.php' );
require_once( plugin_dir_path( __FILE__ ) . '/class-gf-dependencies.php' );
require_once( plugin_dir_path( __FILE__ ) . '/admin-dependencies/class-gf-admin-script-dependencies.php' );
require_once( plugin_dir_path( __FILE__ ) . '/admin-dependencies/class-gf-admin-style-dependencies.php' );
require_once( plugin_dir_path( __FILE__ ) . '/theme-dependencies/class-gf-theme-script-dependencies.php' );
$container->add( self::HASH_MAP, function () {
if ( ! file_exists( \GFCommon::get_base_path() . '/assets/js/dist/assets.php' ) ) {
return array();
}
$map = require( \GFCommon::get_base_path() . '/assets/js/dist/assets.php' );
return rgar( $map, 'hash_map', array() );
} );
$container->add( self::ASSET_PROCESSOR, function () use ( $container ) {
$basepath = \GFCommon::get_base_path();
$asset_path = sprintf( '%s/assets/js/dist/', $basepath );
return new GF_Asset_Processor( $container->get( self::HASH_MAP ), $asset_path );
} );
$container->add( self::STYLE_DEPS, function () {
return new GF_Admin_Style_Dependencies();
} );
$container->add( self::SCRIPT_DEPS, function () {
return new GF_Admin_Script_Dependencies();
} );
$container->add( self::SCRIPT_DEPS_THEME, function () {
return new GF_Theme_Script_Dependencies();
} );
$this->svg_delivery( $container );
}
public function init( GF_Service_Container $container ) {
add_action( 'init', function () use ( $container ) {
$container->get( self::ASSET_PROCESSOR )->process_assets();
}, 9999 );
add_action( 'admin_enqueue_scripts', function () use ( $container ) {
$container->get( self::STYLE_DEPS )->enqueue();
$container->get( self::SCRIPT_DEPS )->enqueue();
// Styles and scripts required for the tooltips.
wp_enqueue_style( 'gform_font_awesome' );
wp_enqueue_script( 'gform_tooltip_init' );
} );
add_action( 'gform_enqueue_scripts', function () use ( $container ) {
$container->get( self::SCRIPT_DEPS_THEME )->enqueue();
} );
add_filter( 'gform_noconflict_styles', function ( $styles ) use ( $container ) {
return array_merge( $styles, $container->get( self::STYLE_DEPS )->get_items() );
}, 1 );
}
private function svg_delivery( GF_Service_Container $container ) {
$default_path = sprintf( '%s/assets/img/base', untrailingslashit( $this->plugin_dir ) );
/**
* Allows users to filter the path used to glob the available SVGs to use for display in themes.
*
* @since 2.7
*
* @param string $path The default orbital theme svg path within our plugin.
*
* @return string
*/
$svg_path = apply_filters( 'gform_svg_theme_path', $default_path );
$svgs = array();
foreach ( \GFCommon::glob( '*.svg', trailingslashit( $svg_path ) ) as $filename ) {
$key = pathinfo( $filename, PATHINFO_FILENAME );
$svgs[ $key ] = file_get_contents( $filename );
}
/**
* Allows users to filter the SVG options available to output in themes.
*
* @since 2.7
*
* @param array $svgs The current SVG options.
*
* @return array
*/
$svgs = apply_filters( 'gform_svg_theme_options', $svgs );
$container->add( self::SVG_OPTIONS, $svgs );
}
}
@@ -0,0 +1,55 @@
<?php
/**
* Gravity Forms Abstract Asset
*
* Provides base functionality for enqueueable/printable assets.
*
* @since 2.5
* @package gravityforms
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class GF_Asset
*/
abstract class GF_Asset {
/**
* @var string $handle
*/
protected $handle;
/**
* @var string $url
*/
protected $url;
/**
* GF_Asset constructor.
*
* @param string $handle
* @param string $url
*/
public function __construct( $handle, $url = '' ) {
$this->handle = $handle;
$this->url = $url;
}
/**
* Handle enqueueing the asset this class represents (e.g., using wp_enqueue_script() or wp_enqueue_style())
*
* @return void
*/
abstract public function enqueue_asset();
/**
* Handle printing the asset this class represents (e.g., using wp_print_scripts() or wp_print_styles())
*
* @return void
*/
abstract public function print_asset();
}
@@ -0,0 +1,66 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Assets;
abstract class GF_Dependencies {
/**
* Items to enqueue
*
* @since 2.6
*
* @var array
*/
protected $items = array();
/**
* The method for actually enqueueing the items.
*
* @since 2.6
*
* @param $handle
*
* @return mixed
*/
abstract protected function do_enqueue( $handle );
/**
* Get the dependency items.
*
* @since 2.6
*
* @return array
*/
public function get_items() {
return $this->items;
}
/**
* Enqueue the items.
*
* @since 2.6
*
* @param $items
*/
public function enqueue() {
if ( ! $this->should_enqueue() ) {
return;
}
foreach ( $this->items as $handle ) {
$this->do_enqueue( $handle );
}
}
/**
* Override to determine whether the assets outlined should be enqueued.
*
* @since 2.6
*
* @return bool
*/
protected function should_enqueue() {
return true;
}
}
@@ -0,0 +1,72 @@
<?php
/**
* Gravity Forms Script Asset
*
* @since 2.5
* @package gravityforms
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class GF_Script_Asset
*/
class GF_Script_Asset extends GF_Asset {
/**
* The data for any localized information.
*
* @since 2.5
*
* @var array $localize_data
*/
private $localize_data = array();
/**
* Localize data to this script.
*
* @since 2.5
*
* @param string $object_name
* @param array $data
*
* @return void
*/
public function add_localize_data( $object_name, $data ) {
$this->localize_data[ $object_name ] = $data;
}
/**
* Enqueue the asset.
*
* @since 2.5
*
* @return void
*/
public function enqueue_asset() {
wp_enqueue_script( $this->handle, $this->url );
if ( empty( $this->localize_data ) ) {
return;
}
foreach ( $this->localize_data as $object_name => $data ) {
wp_localize_script( $this->handle, $object_name, $data );
}
}
/**
* Print the asset.
*
* @since 2.5
*
* @return void
*/
public function print_asset() {
$this->enqueue_asset();
wp_print_scripts( $this->handle );
}
}
@@ -0,0 +1,40 @@
<?php
/**
* Gravity Forms Style Asset
*
* @since 2.5
* @package gravityforms
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class GF_Style_Asset
*/
class GF_Style_Asset extends GF_Asset {
/**
* Enqueue the asset.
*
* @since 2.5
*
* @return void
*/
public function enqueue_asset() {
wp_enqueue_style( $this->handle, $this->url );
}
/**
* Print the asset.
*
* @since 2.5
*
* @return void
*/
public function print_asset() {
$this->enqueue_asset();
wp_print_styles( $this->handle );
}
}
@@ -0,0 +1,2 @@
<?php
//Nothing to see here
@@ -0,0 +1,42 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Assets\Theme_Dependencies;
use Gravity_Forms\Gravity_Forms\Assets\GF_Dependencies;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
/**
* Class GF_Theme_Script_Dependencies
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Assets\Theme_Dependencies;
*/
class GF_Theme_Script_Dependencies extends GF_Dependencies {
/**
* Items to enqueue globally in admin.
*
* @since 2.6
*
* @var string[]
*/
protected $items = array(
'gform_gravityforms_theme',
);
/**
* Enqueue the item by handle.
*
* @since 2.6
*
* @param $handle
*
* @return void
*/
protected function do_enqueue( $handle ) {
wp_enqueue_script( $handle );
}
}
@@ -0,0 +1,115 @@
<?php
/**
* Service provider for async (background) processors.
*
* @package Gravity_Forms\Gravity_Forms
*/
namespace Gravity_Forms\Gravity_Forms\Async;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
use Gravity_Forms\Gravity_Forms\Telemetry\GF_Telemetry_Processor;
use Gravity_Forms\Gravity_Forms\Bulk_Actions\GF_Entry_Bulk_Action_Processor;
use Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints\GF_Bulk_Action_Endpoint_Start;
use Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints\GF_Bulk_Action_Endpoint_Status;
use Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints\GF_Bulk_Action_Endpoint_Cancel;
use GFForms;
use GF_Background_Upgrader;
use GF_Feed_Processor;
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class GF_Background_Processing_Service_Provider
*
* @since 2.6.9
*/
class GF_Background_Process_Service_Provider extends GF_Service_Provider {
const UPGRADER = 'upgrade_processor';
const FEEDS = 'feeds_processor';
const NOTIFICATIONS = 'notifications_processor';
const TELEMETRY = 'telemetry_processor';
const BULK_ACTION = 'bulk_action_processor';
const BULK_ACTION_ENDPOINT_START = 'bulk_action_endpoint_start';
const BULK_ACTION_ENDPOINT_STATUS = 'bulk_action_endpoint_status';
const BULK_ACTION_ENDPOINT_CANCEL = 'bulk_action_endpoint_cancel';
/**
* The names and classes of the async (background) processors.
*
* @since 2.6.9
*
* @var string[]
*/
protected $processors = array(
self::UPGRADER => GF_Background_Upgrader::class,
self::FEEDS => GF_Feed_Processor::class,
self::NOTIFICATIONS => GF_Notifications_Processor::class,
self::TELEMETRY => GF_Telemetry_Processor::class,
self::BULK_ACTION => GF_Entry_Bulk_Action_Processor::class,
);
/**
* Initializing the processors and adding them to the container as services.
*
* @since 2.6.9
*
* @param GF_Service_Container $container
*/
public function register( GF_Service_Container $container ) {
GFForms::init_background_upgrader();
require_once GF_PLUGIN_DIR_PATH . 'includes/addon/class-gf-feed-processor.php';
require_once GF_PLUGIN_DIR_PATH . 'includes/async/class-gf-notifications-processor.php';
require_once GF_PLUGIN_DIR_PATH . 'includes/telemetry/class-gf-telemetry-processor.php';
require_once GF_PLUGIN_DIR_PATH . 'includes/bulk-actions/class-gf-entry-bulk-action-processor.php';
require_once GF_PLUGIN_DIR_PATH . 'includes/bulk-actions/endpoints/class-gf-bulk-action-endpoint-start.php';
require_once GF_PLUGIN_DIR_PATH . 'includes/bulk-actions/endpoints/class-gf-bulk-action-endpoint-status.php';
require_once GF_PLUGIN_DIR_PATH . 'includes/bulk-actions/endpoints/class-gf-bulk-action-endpoint-cancel.php';
foreach ( $this->processors as $name => $class ) {
$container->add( $name, function () use ( $name, $class ) {
if ( $name === self::UPGRADER ) {
return GFForms::$background_upgrader;
}
$callback = array( $class, 'get_instance' );
if ( is_callable( $callback ) ) {
return call_user_func( $callback );
}
return new $class();
} );
}
$container->add( self::BULK_ACTION_ENDPOINT_START, function() {
return new GF_Bulk_Action_Endpoint_Start();
} );
$container->add( self::BULK_ACTION_ENDPOINT_STATUS, function() {
return new GF_Bulk_Action_Endpoint_Status();
} );
$container->add( self::BULK_ACTION_ENDPOINT_CANCEL, function() {
return new GF_Bulk_Action_Endpoint_Cancel();
} );
}
public function init( GF_Service_Container $container ) {
add_action( 'wp_ajax_' . GF_Bulk_Action_Endpoint_Start::ACTION_NAME, function() use ( $container ) {
$container->get( self::BULK_ACTION_ENDPOINT_START )->handle();
} );
add_action( 'wp_ajax_' . GF_Bulk_Action_Endpoint_Status::ACTION_NAME, function() use ( $container ) {
$container->get( self::BULK_ACTION_ENDPOINT_STATUS )->handle();
} );
add_action( 'wp_ajax_' . GF_Bulk_Action_Endpoint_Cancel::ACTION_NAME, function() use ( $container ) {
$container->get( self::BULK_ACTION_ENDPOINT_CANCEL )->handle();
} );
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,160 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Async;
use GFCommon;
use GFAPI;
if ( ! class_exists( 'GFForms' ) ) {
die();
}
if ( ! class_exists( 'Gravity_Forms\Gravity_Forms\Async\GF_Background_Process' ) ) {
require_once GF_PLUGIN_DIR_PATH . 'includes/async/class-gf-background-process.php';
}
/**
* GF_Notifications_Processor Class.
*
* @since 2.6.9
*/
class GF_Notifications_Processor extends GF_Background_Process {
/**
* The action name.
*
* @since 2.6.9
*
* @var string
*/
protected $action = 'gf_notifications_processor';
/**
* Indicates if the task uses an array that supports the attempts key.
*
* @since 2.9.9
*
* @var bool
*/
protected $supports_attempts = true;
/**
* Processes the task.
*
* @since 2.6.9
*
* @param array $item The task arguments.
*
* @return bool
*/
protected function task( $item ) {
$key = isset( $item['notification'] ) ? 'notification' : 'notifications';
$notification_or_ids = rgar( $item, $key );
if ( empty( $notification_or_ids ) || ! is_array( $notification_or_ids ) ) {
return false;
}
if ( ! empty( $item['entry'] ) ) {
$entry = $item['entry'];
} else {
$entry = GFAPI::get_entry( rgar( $item, 'entry_id' ) );
if ( is_wp_error( $entry ) ) {
GFCommon::log_debug( __METHOD__ . sprintf( '(): Aborting; Entry #%d not found.', rgar( $item, 'entry_id' ) ) );
return false;
}
}
$form = GFAPI::get_form( rgar( $item, 'form_id' ) );
if ( empty( $form ) ) {
GFCommon::log_debug( __METHOD__ . sprintf( '(): Aborting; Form #%d not found.', rgar( $item, 'form_id' ) ) );
return false;
}
$form = $this->filter_form( $form, $entry );
$event = rgar( $item, 'event', 'form_submission' );
$data = rgar( $item, 'data' );
if ( ! is_array( $data ) ) {
$data = array();
}
if ( $key === 'notification' ) {
$notifications = array( rgar( $notification_or_ids, 'id', 'custom' ) );
} else {
$notifications = $notification_or_ids;
}
/**
* Allows custom actions to be performed before notifications are sent asynchronously.
*
* @since 2.7.1
*
* @param string $event The event being processed.
* @param array $notifications An array containing the IDs of the notifications being processed.
* @param array $form The form being processed.
* @param array $entry The entry being processed.
* @param array $data An array of data which can be used in the notifications via the generic {object:property} merge tag. Defaults to empty array.
*/
do_action( 'gform_pre_process_async_notifications', $event, $notifications, $form, $entry, $data );
if ( $key === 'notification' ) {
GFCommon::send_notification( $notification_or_ids, $form, $entry, $data );
} else {
GFCommon::send_notifications( $notifications, $form, $entry, true, $event, $data );
}
/**
* Allows custom actions to be performed after notifications are sent asynchronously.
*
* @since 2.7.1
*
* @param string $event The event being processed.
* @param array $notifications An array containing the IDs of the notifications being processed.
* @param array $form The form being processed.
* @param array $entry The entry being processed.
* @param array $data An array of data which can be used in the notifications via the generic {object:property} merge tag. Defaults to empty array.
*/
do_action( 'gform_post_process_async_notifications', $event, $notifications, $form, $entry, $data );
return false;
}
/**
* Determines if async (background) processing of notifications is enabled.
*
* @since 2.7.1
* @since 2.10.0 Background notifications enabled by default for new installs.
*
* @param array $notifications An array containing the IDs of the notifications to be sent.
* @param array $form The form being processed.
* @param array $entry The entry being processed.
* @param string $event The event being processed.
* @param array $data An array of data which can be used in the notifications via the generic {object:property} merge tag. Defaults to empty array.
*
* @return bool
*/
public function is_enabled( $notifications, $form, $entry, $event = 'form_submission', $data = array() ) {
$form_id = absint( rgar( $form, 'id' ) );
$is_enabled = (bool) get_option( 'gform_enable_async_notifications' );
/**
* Allows async (background) processing of notifications to be enabled or disabled.
*
* @since 2.6.9
* @since 2.10.0 Updated to use the gform_enable_async_notifications option value as the default.
*
* @param bool $is_enabled Is async (background) processing of notifications enabled? Defaults to the value of the gform_enable_async_notifications option.
* @param string $event The event the notifications are to be sent for.
* @param array $notifications An array containing the IDs of the notifications to be sent.
* @param array $form The form currently being processed.
* @param array $entry The entry currently being processed.
* @param array $data An array of data which can be used in the notifications via the generic {object:property} merge tag. Defaults to empty array.
*/
return gf_apply_filters( array(
'gform_is_asynchronous_notifications_enabled',
$form_id,
), $is_enabled, $event, $notifications, $form, $entry, $data );
}
}
@@ -0,0 +1,216 @@
<?php
/**
* WP Async Request
*
* @package WP-Background-Processing
*/
namespace Gravity_Forms\Gravity_Forms\Async;
/**
* Abstract WP_Async_Request class.
*
* @since 2.2
* @since 2.9.8 Namespaced.
*
* @abstract
*/
abstract class WP_Async_Request {
/**
* Prefix
*
* (default value: 'wp')
*
* @var string
* @access protected
*/
protected $prefix = 'wp';
/**
* Action
*
* (default value: 'async_request')
*
* @var string
* @access protected
*/
protected $action = 'async_request';
/**
* Identifier
*
* @var mixed
* @access protected
*/
protected $identifier;
/**
* Data
*
* (default value: array())
*
* @var array
* @access protected
*/
protected $data = array();
/**
* Initiate new async request
*/
public function __construct() {
$this->identifier = $this->prefix . '_' . $this->action;
add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); // @phpstan-ignore-line
add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) ); // @phpstan-ignore-line
}
/**
* Set data used during the request
*
* @param array $data Data.
*
* @return $this
*/
public function data( $data ) {
$this->data = $data;
return $this;
}
/**
* Dispatch the async request
*
* @return array|\WP_Error
*/
public function dispatch() {
$url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
$args = $this->get_post_args();
return wp_remote_post( esc_url_raw( $url ), $args );
}
/**
* Get query args
*
* @return array
*/
protected function get_query_args() {
if ( property_exists( $this, 'query_args' ) ) {
return $this->query_args;
}
$args = array(
'action' => $this->identifier,
'nonce' => wp_create_nonce( $this->identifier ),
);
/**
* Filters the query arguments used during an async request.
*
* @since 2.9.7
*
* @param array $args
*/
return apply_filters( $this->identifier . '_query_args', $args );
}
/**
* Get query URL
*
* @return string
*/
protected function get_query_url() {
if ( property_exists( $this, 'query_url' ) ) {
return $this->query_url;
}
$url = admin_url( 'admin-ajax.php' );
/**
* Filters the query URL used during an async request.
*
* @since 2.9.7
*
* @param string $url
*/
return apply_filters( $this->identifier . '_query_url', $url );
}
/**
* Get post args
*
* @return array
*/
protected function get_post_args() {
if ( property_exists( $this, 'post_args' ) ) {
return $this->post_args;
}
$args = array(
'timeout' => 5,
'blocking' => false,
'body' => $this->data,
'cookies' => $_COOKIE, // Passing cookies ensures request is performed as initiating user.
'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // Local requests, fine to pass false.
);
/**
* Filters the post arguments used during an async request.
*
* @since 2.9.7
*
* @param array $args
*/
return apply_filters( $this->identifier . '_post_args', $args );
}
/**
* Maybe handle
*
* Check for correct nonce and pass to handler.
*
* @return void|mixed
*/
public function maybe_handle() {
// Don't lock up other requests while processing
session_write_close();
check_ajax_referer( $this->identifier, 'nonce' );
$this->handle();
return $this->maybe_wp_die();
}
/**
* Should the process exit with wp_die?
*
* @since 2.9.7
*
* @param mixed $return What to return if filter says don't die, default is null.
*
* @return void|mixed
*/
protected function maybe_wp_die( $return = null ) {
/**
* Should wp_die be used?
*
* @return bool
*/
if ( apply_filters( $this->identifier . '_wp_die', true ) ) {
wp_die();
}
return $return;
}
/**
* Handle
*
* Override this method to perform any actions required
* during the async request.
*/
abstract protected function handle();
}
@@ -0,0 +1,120 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Author_Select;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Data_Parser;
defined( 'ABSPATH' ) || die();
/**
* Config items for the Author Select dropdown
*
* @since 2.9.20
*/
class GF_Author_Select_Config extends GF_Config {
protected $name = 'gform_admin_config';
protected $script_to_localize = 'gform_gravityforms_admin_vendors';
/**
* Constructor
*
* @since 2.9.20
*
* @param GF_Config_Data_Parser $data_parser
*/
public function __construct( GF_Config_Data_Parser $data_parser ) {
parent::__construct( $data_parser );
}
/**
* Only enqueue in the form editor
*
* @since 2.9.20
*
* @return bool
*/
public function should_enqueue() {
return \GFForms::get_page() === 'form_editor';
}
/**
* Config data for the author select dropdown
*
* @since 2.9.20
*
* @return array
*/
public function data() {
$form_id = isset( $_GET['id'] ) ? intval( $_GET['id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$form = \GFFormsModel::get_form_meta( $form_id );
$current_author = rgar( $form, 'postAuthor' );
$selected_label = '';
if ( $current_author ) {
$current_user = get_user_by( 'id', $current_author );
if ( $current_user ) {
$selected_label = esc_html( $current_user->display_name );
}
}
return array(
'components' => array(
'author_select' => array(
'endpoints' => array(
'get' => array(
'action' => 'gf_get_users',
'nonce' => wp_create_nonce( 'gf_get_users' ),
),
),
'data' => $this->get_initial_users( $form_id ),
'form_id' => $form_id,
'selected_value' => $current_author,
'selected_label' => $selected_label,
'strings' => array(
'search_placeholder' => esc_html__( 'Search users...', 'gravityforms' ),
'trigger_placeholder' => esc_html__( 'Select an author', 'gravityforms' ),
'trigger_aria_text' => esc_html__( 'Default Post Author', 'gravityforms' ),
'search_aria_text' => esc_html__( 'Search for users', 'gravityforms' ),
),
),
),
);
}
/**
* Get the initial list of users for the dropdown
*
* @since 2.9.20
*
* @param int $form_id The form ID
* @return array
*/
private function get_initial_users( $form_id ) {
$args = array(
'number' => 10,
'fields' => array( 'ID', 'display_name' ),
);
// Apply existing filter for backward compatibility
$args = gf_apply_filters( array( 'gform_author_dropdown_args', $form_id ), $args );
$users = get_users( $args );
if ( ! is_array( $users ) || empty( $users ) ) {
return array();
}
$initial_users = array();
foreach ( $users as $user ) {
$initial_users[] = array(
'value' => $user->ID,
'label' => esc_html( $user->display_name ),
);
}
return $initial_users;
}
}
@@ -0,0 +1,59 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Author_Select;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Service_Provider;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
defined( 'ABSPATH' ) || die();
/**
* Class GF_Author_Select_Service_Provider
*
* Service provider for the Author Select functionality
*
* @since 2.9.20
*/
class GF_Author_Select_Service_Provider extends GF_Service_Provider {
const AUTHOR_SELECT = 'author_select';
const AUTHOR_SELECT_CONFIG = 'author_select_config';
/**
* Register services to the container
*
* @since 2.9.20
*
* @param GF_Service_Container $container
*/
public function register( GF_Service_Container $container ) {
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-author-select.php' );
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-author-select-config.php' );
$container->add( self::AUTHOR_SELECT, function() {
return new GF_Author_Select();
} );
$container->add( self::AUTHOR_SELECT_CONFIG, function() use ( $container ) {
$data_parser = $container->get( GF_Config_Service_Provider::DATA_PARSER );
return new GF_Author_Select_Config( $data_parser );
} );
}
/**
* Initialize any actions or hooks
*
* @since 2.9.20
*
* @param GF_Service_Container $container
*/
public function init( GF_Service_Container $container ) {
$container->get( self::AUTHOR_SELECT )->init();
if ( \GFForms::get_page() === 'form_editor' ) {
$config_collection = $container->get( GF_Config_Service_Provider::CONFIG_COLLECTION );
$config_collection->add_config( $container->get( self::AUTHOR_SELECT_CONFIG ) );
}
}
}
@@ -0,0 +1,75 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Author_Select;
defined( 'ABSPATH' ) || die();
use GFCommon;
/**
* Class GF_Author_Select
*
* Handles AJAX requests for the author select dropdown in the form editor
*
* @since 2.9.20
*/
class GF_Author_Select {
/**
* Initialize the author select functionality
*
* @since 2.9.20
*/
public function init() {
add_action( 'wp_ajax_gf_get_users', array( $this, 'ajax_get_users' ) );
}
/**
* Handle AJAX request to get users
*
* @since 2.9.20
*/
public function ajax_get_users() {
if ( ! GFCommon::current_user_can_any( 'gravityforms_edit_forms' ) ) {
wp_send_json_error( array( 'message' => __( 'Access denied', 'gravityforms' ) ) );
}
check_ajax_referer( 'gf_get_users', 'nonce' );
$search = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '';
$form_id = isset( $_POST['form_id'] ) ? intval( $_POST['form_id'] ) : 0;
$args = array(
'number' => 10,
'fields' => array( 'ID', 'display_name' ),
);
/**
* Filter the arguments used to query users for the author dropdown.
* Originally added to limit users for performance.
* Now with AJAX and 10-result limit by default, useful for role restrictions or custom filtering.
*
* @since 1.3.10
*
* @param array $args WP_User_Query arguments for get_users()
*/
$args = gf_apply_filters( array( 'gform_author_dropdown_args', $form_id ), $args );
if ( ! empty( $search ) ) {
$args['search'] = '*' . $search . '*';
$args['search_columns'] = array( 'user_login', 'user_email', 'display_name' );
}
$users = get_users( $args );
$response = array();
foreach ( $users as $user ) {
$response[] = array(
'value' => $user->ID,
'label' => esc_html( $user->display_name ),
);
}
wp_send_json_success( $response );
}
}
@@ -0,0 +1,28 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Blocks;
/**
* Gravity Forms Block Attributes class.
*
* @since 2.7.4
*
* Class GF_Block_Attributes
*/
class GF_Block_Attributes {
public function store( $attributes ) {
add_filter( 'gform_form_block_attribute_values', function( $attr ) use ( $attributes ) {
$form_id = rgar( $attributes, 'formId', 0 );
if ( ! array_key_exists( $form_id, $attr ) ) {
$attr[ $form_id ] = array();
}
$attr[ $form_id ][] = $attributes;
return $attr;
} );
}
}
@@ -0,0 +1,320 @@
<?php
// If Gravity Forms Block Manager is not available, do not run.
if ( ! class_exists( 'GF_Blocks' ) || ! defined( 'ABSPATH' ) ) {
exit;
}
class GF_Block_Form extends GF_Block {
/**
* Contains an instance of this block, if available.
*
* @since 2.4.10
* @var GF_Block $_instance If available, contains an instance of this block.
*/
private static $_instance = null;
/**
* Block type.
*
* @since 2.4.10
* @var string
*/
public $type = 'gravityforms/form';
/**
* Handle of primary block script.
*
* @since 2.4.10
* @var string
*/
public $script_handle = 'gform_editor_block_form';
/**
* Handle of primary block style.
*
* @since 2.5.6
* @var string
*/
public $style_handle = 'gform_editor_block_form';
public function __construct() {
$this->assign_attributes();
}
/**
* Register block type and add filters specific to the form block.
*
* @since 2.10.5
*
* @return void
*/
public function init() {
parent::init();
// Move WordPress's custom CSS classes from the <script> to the <div class="gform_wrapper">, so that the form is styled correctly.
add_filter( 'render_block_gravityforms/form', array( $this, 'fix_custom_css_class_placement' ), 10, 2 );
}
private function assign_attributes() {
$default_attributes = GFForms::get_service_container()->get( \Gravity_Forms\Gravity_Forms\Blocks\GF_Blocks_Service_Provider::FORM_BLOCK_ATTRIBUTES );
$attributes = apply_filters( 'gform_form_block_attributes', $default_attributes );
array_walk( $attributes, function ( &$value ) {
$value = array( 'type' => $value['type'] );
} );
$this->attributes = $attributes;
}
/**
* Get instance of this class.
*
* @since 2.4.10
*
* @return GF_Block_Form
*/
public static function get_instance() {
if ( null === self::$_instance ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Move the WordPress "Custom CSS" block-support classes from a leading <script> to the gform_wrapper <div>.
*
* The block "Custom CSS" setting introduced in WP 7.0 adds custom CSS classes to the first HTML element in a block,
* which might be the <script> tag added by GFForms::maybe_prepend_hooks_js_script() instead of the <div class="gform_wrapper">.
* This filter corrects that.
*
* @since 2.10.5
*
* @param string $block_content The rendered block HTML.
* @param array $block The parsed block data.
*
* @return string The (possibly corrected) block HTML.
*/
public function fix_custom_css_class_placement( $block_content, $block ) {
if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) {
return $block_content;
}
$tags = new WP_HTML_Tag_Processor( $block_content );
// Advance to the first tag. If the content is empty or has no tags, bail.
if ( ! $tags->next_tag() ) {
return $block_content;
}
// Only act when the first element is a <script> — i.e. the hooks JS was prepended.
if ( 'SCRIPT' !== $tags->get_tag() ) {
return $block_content;
}
// Only act when WordPress has placed the custom-CSS marker classes on this <script>.
$class_attr = $tags->get_attribute( 'class' );
if ( ! $class_attr || false === strpos( $class_attr, 'has-custom-css' ) ) {
return $block_content;
}
// Extract the unique `wp-custom-css-*` class generated for this block instance.
$custom_css_class = null;
foreach ( preg_split( '/\s+/', $class_attr ) as $class ) {
if ( 0 === strpos( $class, 'wp-custom-css-' ) ) {
$custom_css_class = $class;
break;
}
}
// Remove the custom-CSS classes from the <script> tag.
$tags->remove_class( 'has-custom-css' );
if ( $custom_css_class ) {
$tags->remove_class( $custom_css_class );
}
// Find the form wrapper <div> and add the classes to it instead.
// next_tag() searches forward from the current cursor position.
if ( $tags->next_tag( array( 'class_name' => 'gform_wrapper' ) ) ) {
$tags->add_class( 'has-custom-css' );
if ( $custom_css_class ) {
$tags->add_class( $custom_css_class );
}
}
return $tags->get_updated_html();
}
// # SCRIPT / STYLES -----------------------------------------------------------------------------------------------
public function register_block_assets() {
parent::register_block_assets();
if ( function_exists( 'wp_enqueue_block_style' ) && is_admin() ) {
wp_enqueue_block_style( $this->type, array( 'handle' => 'gravity_forms_theme_reset' ) );
wp_enqueue_block_style( $this->type, array( 'handle' => 'gravity_forms_theme_foundation' ) );
wp_enqueue_block_style( $this->type, array( 'handle' => 'gravity_forms_theme_framework' ) );
wp_enqueue_block_style( $this->type, array( 'handle' => 'gravity_forms_orbital_theme' ) );
}
}
/**
* Register scripts for block.
*
* @since 2.4.10
*
* @return array
*/
public function scripts() {
return array();
}
/**
* Localize Form block script.
*
* @since 2.4.10
*
* @param array $script Script arguments.
*/
public function localize_script( $script = array() ) {
wp_localize_script(
$script['handle'],
'gform_block_form',
array(
'adminURL' => admin_url( 'admin.php' ),
'forms' => $this->get_forms(),
'preview' => GFCommon::get_base_url() . '/images/gf_block_preview.svg',
)
);
if ( function_exists( 'wp_set_script_translations' ) ) {
wp_set_script_translations( $script['handle'], 'gravityforms', GFCommon::get_base_path() . '/languages' );
}
}
/**
* Register styles for block.
*
* @since 2.4.10
*
* @return array
*/
public function styles() {
// Prepare styling dependencies.
$deps = array( 'wp-edit-blocks' );
// Add Gravity Forms styling if CSS is enabled.
if ( ! GFCommon::is_frontend_default_css_disabled() ) {
$deps = array_merge( $deps, array( 'gforms_reset_css', 'gform_basic', 'gforms_formsmain_css', 'gforms_ready_class_css', 'gforms_browsers_css', 'gform_theme' ) );
/**
* Allows users to disable the main theme.css file from being loaded on the Front End.
*
* @since 2.5-beta-3
*
* @param boolean Whether to disable the theme css.
*/
$disable_theme_css = apply_filters( 'gform_disable_form_theme_css', false );
if ( ! $disable_theme_css ) {
$deps[] = 'gform_theme';
}
}
$dev_min = defined( 'GF_SCRIPT_DEBUG' ) && GF_SCRIPT_DEBUG ? '' : '.min';
return array(
array(
'handle' => $this->style_handle,
'src' => GFCommon::get_base_url() . "/assets/css/dist/blocks{$dev_min}.css",
'deps' => $deps,
'version' => defined( 'GF_SCRIPT_DEBUG' ) && GF_SCRIPT_DEBUG ? filemtime( GFCommon::get_base_path() . "/assets/css/dist/blocks{$dev_min}.css" ) : GFForms::$version,
),
);
}
// # BLOCK RENDER -------------------------------------------------------------------------------------------------
/**
* Display block contents on frontend.
*
* @since 2.4.10
*
* @param array $attributes Block attributes.
*
* @return string
*/
public function render_block( $attributes = array() ) {
GFForms::get_service_container()->get( 'block_attributes' )->store( $attributes );
// Prepare variables.
$form_id = rgar( $attributes, 'formId' ) ? $attributes['formId'] : false;
$title = isset( $attributes['title'] ) ? $attributes['title'] : true;
$description = isset( $attributes['description'] ) ? $attributes['description'] : true;
$ajax = isset( $attributes['ajax'] ) ? $attributes['ajax'] : false;
$tabindex = isset( $attributes['tabindex'] ) ? intval( $attributes['tabindex'] ) : 0;
$field_values = isset( $attributes['fieldValues'] ) ? $attributes['fieldValues'] : '';
// If form ID was not provided or form does not exist, return.
if ( ! $form_id || ( $form_id && ! GFAPI::get_form( $form_id ) ) ) {
return '';
}
// Use Gravity Forms function for REST API requests.
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
// Start output buffering.
ob_start();
// Prepare field values.
if ( ! empty( $field_values ) ) {
$field_values = str_replace( '&#038;', '&', $field_values );
parse_str( $field_values, $field_value_array );
$field_values = stripslashes_deep( $field_value_array );
}
// Get form output string.
$form_string = gravity_form( $form_id, $title, $description, false, $field_values, $ajax, $tabindex, false, rgar( $attributes, 'theme' ), json_encode( $attributes ) );
// Get output buffer contents.
$buffer_contents = ob_get_contents();
ob_end_clean();
// Return buffer contents with form string.
return $buffer_contents . $form_string; // nosemgrep audit.php.wp.security.xss.block-attr
}
// Encode field values.
$field_values = htmlspecialchars_decode( $field_values );
$field_values = str_replace( array( '&#038;', '&#091;', '&#093;' ), array( '&', '[', ']' ), $field_values );
parse_str( $field_values, $field_value_array ); //parsing query string like string for field values and placing them into an associative array
$field_values = stripslashes_deep( $field_value_array );
// If no field values are set, set field values to an empty string
if ( empty( $field_values ) ) {
$field_values = '';
}
return gravity_form( $form_id, $title, $description, false, $field_values, $ajax, $tabindex, false, rgar( $attributes, 'theme' ), json_encode( $attributes ) ); // nosemgrep audit.php.wp.security.xss.block-attr
}
}
// Register block.
if ( true !== ( $registered = GF_Blocks::register( GF_Block_Form::get_instance() ) ) && is_wp_error( $registered ) ) {
// Log that block could not be registered.
GFCommon::log_error( 'Unable to register block; ' . $registered->get_error_message() );
}
@@ -0,0 +1,375 @@
<?php
// If Gravity Forms Block Manager is not available, do not run.
if ( ! class_exists( 'GF_Blocks' ) || ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Base Gravity Forms Block class.
*
* @since 2.4.10
*
* Class GF_Block
*/
class GF_Block {
/**
* Contains an instance of this block, if available.
*
* @since 2.4.10
* @var GF_Block $_instance If available, contains an instance of this block.
*/
private static $_instance = null;
/**
* Block type.
*
* @since 2.4.10
* @var string
*/
public $type = '';
/**
* Handle of primary block editor script.
*
* @since 2.4.10
* @var string
*/
public $script_handle = '';
/**
* Handle of primary block editor style.
*
* @since 2.5.6
* @var string
*/
public $style_handle = '';
/**
* Handle of primary block FE script.
*
* @since 2.4.10
* @var string
*/
public $fe_script_handle = '';
/**
* Handle of primary block FE style.
*
* @since 2.5.6
* @var string
*/
public $fe_style_handle = '';
/**
* Block attributes.
*
* @since 2.4.10
* @var array
*/
public $attributes = array();
/**
* Register block type.
* Enqueue editor assets.
*
* @since 2.4.10
*
* @uses GF_Block::register_block_type()
*/
public function init() {
$this->register_block_type();
$this->register_block_assets();
add_action( 'gform_post_enqueue_scripts', array( $this, 'post_enqueue_scripts' ), 10, 3 );
}
// # BLOCK REGISTRATION --------------------------------------------------------------------------------------------
/**
* Get block type.
*
* @since 2.4.10
*
* @return string
*/
public function get_type() {
return $this->type;
}
/**
* Register block with WordPress.
*
* @since 2.4.10
*/
public function register_block_type() {
register_block_type( $this->get_type(), $this->get_block_properties() );
}
/**
* Get an array representing the properties for this block. Can be overriden by inheriting
* classes in order to provide more/fewer/different properties.
*
* @since 2.5.6
*
* @return array
*/
protected function get_block_properties() {
return array(
'render_callback' => array( $this, 'render_block' ),
'editor_script' => $this->script_handle,
'editor_style' => $this->style_handle,
'attributes' => $this->attributes,
'script' => $this->fe_script_handle,
'style' => $this->fe_style_handle,
);
}
/**
* Enqueue/register the block's assets upon init
*
* @since 2.5.6
*
* @return void
*/
public function register_block_assets() {
add_action( 'enqueue_block_assets', array( $this, 'register_scripts' ) );
add_action( 'enqueue_block_assets', array( $this, 'register_styles' ) );
}
/**
* Checks allowed blocks for Gravity forms blocks to only enqueue block editor assets when necessary.
*
* @since 2.4.18
*
* @deprecated since 2.5.6. See GF_Block::register_block_assets()
*
* @param bool|array $allowed_block_types Array of block type slugs, or boolean to enable/disable all.
*
* @return bool|array
*/
public function check_allowed_blocks( $allowed_block_types ) {
// Only enqueue block editor assets if all blocks are allowed or if the current block type is an allowed block.
if ( $allowed_block_types === true || ( is_array( $allowed_block_types ) && in_array( $this->get_type(), $allowed_block_types ) ) ) {
add_action( 'enqueue_block_assets', array( $this, 'register_scripts' ) );
add_action( 'enqueue_block_assets', array( $this, 'register_styles' ) );
}
return $allowed_block_types;
}
// # SCRIPT ENQUEUEING ---------------------------------------------------------------------------------------------
/**
* Enqueue block scripts.
*
* @since 2.4.10
*
* @uses GF_Block::scripts()
*/
public function register_scripts() {
if ( ! is_admin() ) {
return;
}
// Get registered scripts.
$scripts = $this->scripts();
// If no scripts are registered, return.
if ( empty( $scripts ) ) {
return;
}
// Loop through scripts.
foreach ( $scripts as $script ) {
// Prepare parameters.
$src = isset( $script['src'] ) ? $script['src'] : false;
$deps = isset( $script['deps'] ) ? $script['deps'] : array();
$version = isset( $script['version'] ) ? $script['version'] : false;
$in_footer = isset( $script['in_footer'] ) ? $script['in_footer'] : false;
// Enqueue script.
if ( $this->script_handle === $script['handle'] ) {
// Support for the editor_style property, if a style_handle is defined. No need to enqueue.
wp_register_script( $script['handle'], $src, $deps, $version, $in_footer );
} else {
// style_handle isn't defined, or this is an additional style. Enqueue it manually.
wp_enqueue_script( $script['handle'], $src, $deps, $version, $in_footer );
}
// Localize script.
if ( rgar( $script, 'strings' ) ) {
wp_localize_script( $script['handle'], $script['handle'] . '_strings', $script['strings'] );
}
// Run script callback.
if ( rgar( $script, 'callback' ) && is_callable( $script['callback'] ) ) {
call_user_func( $script['callback'], $script );
}
}
}
/**
* Enqueue scripts
*
* @depecated since 2.5.6. Use ::register_scripts() instead.
*
* @return void
*/
public function enqueue_scripts() {
$this->register_scripts();
}
/**
* Override this function to provide a list of scripts to be enqueued.
* Following is an example of the array that is expected to be returned by this function:
* <pre>
* <code>
*
* array(
* array(
* 'handle' => 'super_signature_script',
* 'src' => $this->get_base_url() . '/super_signature/ss.js',
* 'version' => $this->_version,
* 'deps' => array( 'jquery'),
* 'callback' => array( $this, 'localize_scripts' ),
* 'strings' => array(
* // Accessible in JavaScript using the global variable "[script handle]_strings"
* 'stringKey1' => __( 'The string', 'gravityforms' ),
* 'stringKey2' => __( 'Another string.', 'gravityforms' )
* )
* )
* );
*
* </code>
* </pre>
*
* @since 2.4.10
*
* @return array
*/
public function scripts() {
return array();
}
// # STYLE ENQUEUEING ----------------------------------------------------------------------------------------------
/**
* Enqueue block styles.
*
* @since 2.4.10
*/
public function register_styles() {
if ( ! is_admin() ) {
return;
}
// Get registered styles.
$styles = $this->styles();
// If no styles are registered, return.
if ( empty( $styles ) ) {
return;
}
// Loop through styles.
foreach ( $styles as $style ) {
// Prepare parameters.
$src = isset( $style['src'] ) ? $style['src'] : false;
$deps = isset( $style['deps'] ) ? $style['deps'] : array();
$version = isset( $style['version'] ) ? $style['version'] : false;
$media = isset( $style['media'] ) ? $style['media'] : 'all';
if ( $this->style_handle === $style['handle'] ) {
// Support for the editor_style property, if a style_handle is defined. No need to enqueue.
wp_register_style( $style['handle'], $src, $deps, $version, $media );
} else {
// style_handle isn't defined, or this is an additional style. Enqueue it manually.
wp_enqueue_style( $style['handle'], $src, $deps, $version, $media );
}
}
}
/**
* Enqueue styles
*
* @depecated since 2.5.6. Use ::register_styles() instead.
*
* @return void
*/
public function enqueue_styles() {
$this->register_styles();
}
/**
* Override this function to provide a list of styles to be enqueued.
* See scripts() for an example of the format expected to be returned.
*
* @since 2.4.10
*
* @return array
*/
public function styles() {
return array();
}
// # BLOCK RENDER -------------------------------------------------------------------------------------------------
/**
* Display block contents on frontend.
*
* @since 2.4.10
*
* @param array $attributes Block attributes.
*
* @return string
*/
public function render_block( $attributes = array() ) {
return '';
}
/**
* Override to perform additional actions when scripts/styles are enqueued on the wp_enqueue_scripts hook.
*
* @since 2.4.18
*
* @param array $found_forms An array of found forms using the form ID as the key to the ajax status.
* @param array $found_blocks An array of found GF blocks.
* @param WP_Post $post The post which was processed.
*/
public function post_enqueue_scripts( $found_forms, $found_blocks, $post ) {
}
}
@@ -0,0 +1,245 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Blocks;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Service_Provider;
use Gravity_Forms\Gravity_Forms\Blocks\Config\GF_Blocks_Config;
use Gravity_Forms\Gravity_Forms\Blocks\GF_Block_Attributes;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
use GFFormDisplay;
use GFCommon;
/**
* Class GF_Blocks_Service_Provider
*
* Service provider for the Blocks Service.
*
* @package Gravity_Forms\Gravity_Forms\Blocks;
*/
class GF_Blocks_Service_Provider extends GF_Service_Provider {
// Configs
const BLOCKS_CONFIG = 'blocks_config';
// Attributes
const FORM_BLOCK_ATTRIBUTES = 'form_block_attributes';
const BLOCK_ATTRIBUTES = 'block_attributes';
/**
* Array mapping config class names to their container ID.
*
* @since
*
* @var string[]
*/
protected $configs = array(
self::BLOCKS_CONFIG => GF_Blocks_Config::class,
);
/**
* Register services to the container.
*
* @since
*
* @param GF_Service_Container $container
*/
public function register( GF_Service_Container $container ) {
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-block-attributes.php' );
require_once( plugin_dir_path( __FILE__ ) . '/config/class-gf-blocks-config.php' );
$container->add( self::FORM_BLOCK_ATTRIBUTES, function () {
require_once( GFCommon::get_base_path() . '/form_display.php' );
$global_styles = GFFormDisplay::validate_form_styles( apply_filters( 'gform_default_styles', false ) );
return array(
'formId' =>
array(
'type' => 'string',
),
'title' =>
array(
'type' => 'boolean',
'default' => true,
),
'description' =>
array(
'type' => 'boolean',
'default' => true,
),
'ajax' =>
array(
'type' => 'boolean',
'default' => false,
),
'tabindex' =>
array(
'type' => 'string',
),
'fieldValues' =>
array(
'type' => 'string',
),
'formPreview' =>
array(
'type' => 'boolean',
'default' => true,
),
'imgPreview' =>
array(
'type' => 'boolean',
'default' => false,
),
'theme' =>
array(
'type' => 'string',
'default' => '',
),
'inputSize' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'inputSize' ) ? $global_styles['inputSize'] : 'md',
),
'inputBorderRadius' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'inputBorderRadius' ) ? $global_styles['inputBorderRadius'] : 3,
),
'inputBorderColor' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'inputBorderColor' ) ? $global_styles['inputBorderColor'] : '#686e77',
),
'inputBackgroundColor' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'inputBackgroundColor' ) ? $global_styles['inputBackgroundColor'] : '#fff',
),
'inputColor' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'inputColor' ) ? $global_styles['inputColor'] : '#112337',
),
'inputPrimaryColor' =>
array(
'type' => 'string',
// Setting this to empty allows us to set this to what the appropriate default
// should be from within the block. When empty, it defaults to:
// buttonPrimaryBackgroundColor
'default' => rgar( $global_styles, 'inputPrimaryColor' ) ? $global_styles['inputPrimaryColor'] : '', // #204ce5
),
'inputImageChoiceAppearance' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'inputImageChoiceAppearance' ) ? $global_styles['inputImageChoiceAppearance'] : 'card',
),
'inputImageChoiceStyle' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'inputImageChoiceStyle' ) ? $global_styles['inputImageChoiceStyle'] : 'square',
),
'inputImageChoiceSize' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'inputImageChoiceSize' ) ? $global_styles['inputImageChoiceSize'] : 'md',
),
'labelFontSize' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'labelFontSize' ) ? $global_styles['labelFontSize'] : 14,
),
'labelColor' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'labelColor' ) ? $global_styles['labelColor'] : '#112337',
),
'descriptionFontSize' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'descriptionFontSize' ) ? $global_styles['descriptionFontSize'] : 13,
),
'descriptionColor' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'descriptionColor' ) ? $global_styles['descriptionColor'] : '#585e6a',
),
'buttonPrimaryBackgroundColor' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'buttonPrimaryBackgroundColor' ) ? $global_styles['buttonPrimaryBackgroundColor'] : '#204ce5',
),
'buttonPrimaryColor' =>
array(
'type' => 'string',
'default' => rgar( $global_styles, 'buttonPrimaryColor' ) ? $global_styles['buttonPrimaryColor'] : '#fff',
),
);
} );
$this->add_configs( $container );
$this->block_attributes( $container );
}
/**
* Initialize any actions or hooks.
*
* @since
*
* @param GF_Service_Container $container
*
* @return void
*/
public function init( GF_Service_Container $container ) {
add_action( 'gform_post_enqueue_scripts', function( $found_forms, $found_blocks, $post ) use ( $container ) {
foreach( $found_blocks as $block ) {
$attributes = $block['attrs'];
$container->get( self::BLOCK_ATTRIBUTES )->store( $attributes );
}
}, -10, 3 );
}
/**
* For each config defined in $configs, instantiate and add to container.
*
* @since
*
* @param GF_Service_Container $container
*
* @return void
*/
private function add_configs( GF_Service_Container $container ) {
foreach ( $this->configs as $name => $class ) {
$container->add( $name, function () use ( $container, $class ) {
if ( $class == GF_Blocks_Config::class ) {
return new $class( $container->get( GF_Config_Service_Provider::DATA_PARSER ), $container->get( self::FORM_BLOCK_ATTRIBUTES ) );
}
return new $class( $container->get( GF_Config_Service_Provider::DATA_PARSER ) );
} );
$container->get( GF_Config_Service_Provider::CONFIG_COLLECTION )->add_config( $container->get( $name ) );
}
}
/**
* Register Block services.
*
* @since 2.7.4
*
* @param GF_Service_Container $container
*
* @return void
*/
private function block_attributes( GF_Service_Container $container ) {
$container->add( self::BLOCK_ATTRIBUTES, function () use ( $container ) {
return new GF_Block_Attributes();
} );
}
}
@@ -0,0 +1,89 @@
<?php
defined( 'ABSPATH' ) or die();
// Load core Block class.
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-block.php' );
/**
* Handles management of Gravity Forms editor blocks.
*
* @since 2.4.10
*
* Class GF_Blocks
*/
class GF_Blocks {
/**
* Registered Gravity Forms editor blocks.
*
* @since 2.4.10
* @var GF_Block[]
*/
private static $_blocks = array();
/**
* Register a block type.
*
* @since 2.4.10
*
* @param GF_Block $block Block class.
*
* @return bool|WP_Error
*/
public static function register( $block ) {
if ( ! is_subclass_of( $block, 'GF_Block' ) ) {
return new WP_Error( 'block_not_subclass', 'Must be a subclass of GF_Block' );
}
// Get block type.
$block_type = $block->get_type();
if ( empty( $block_type ) ) {
return new WP_Error( 'block_type_undefined', 'The type must be set' );
}
if ( isset( self::$_blocks[ $block_type ] ) ) {
return new WP_Error( 'block_already_registered', 'Block type already registered: ' . $block_type );
}
// Register block.
self::$_blocks[ $block_type ] = $block;
// Initialize block.
call_user_func( array( $block, 'init' ) );
return true;
}
/**
* Get instance of block.
*
* @since 2.4.10
*
* @param string $block_type Block type.
*
* @return GF_Block|bool
*/
public static function get( $block_type ) {
return isset( self::$_blocks[ $block_type ] ) ? self::$_blocks[ $block_type ] : false;
}
/**
* Returns an array of registered block types.
*
* @since 2.4.18
*
* @return array
*/
public static function get_all_types() {
return array_keys( self::$_blocks );
}
}
new GF_Blocks();
@@ -0,0 +1,174 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Blocks\Config;
use GFSettings;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Data_Parser;
use \GFCommon;
use \GFAPI;
use \GFFormDisplay;
/**
* Config items for Blocks.
*
* @since
*/
class GF_Blocks_Config extends GF_Config {
protected $name = 'gform_admin_config';
protected $script_to_localize = 'gform_gravityforms_admin_vendors';
protected $attributes = array();
public function __construct( GF_Config_Data_Parser $parser, array $attributes ) {
parent::__construct( $parser );
$this->attributes = $attributes;
}
public function should_enqueue() {
return GFCommon::is_block_editor_page();
}
/**
* Get list of forms for Block control.
*
* @since 2.4.10
*
* @return array
*/
public function get_forms() {
// Initialize forms array.
$forms = array();
// Load GFFormDisplay class.
if ( ! class_exists( 'GFFormDisplay' ) ) {
require_once GFCommon::get_base_path() . '/form_display.php';
}
// Get form objects.
$form_objects = GFAPI::get_forms( true, false, 'title', 'ASC' );
// Loop through forms, add conditional logic check.
foreach ( $form_objects as $form ) {
$forms[] = array(
'id' => $form['id'],
'title' => $form['title'],
'hasConditionalLogic' => GFFormDisplay::has_conditional_logic( $form ),
'isLegacyMarkup' => GFCommon::is_legacy_markup_enabled( $form ),
'hasImageChoices' => GFFormDisplay::has_image_choices( $form ),
);
}
/**
* Modify the list of available forms displayed in the Form block.
*
* @since 2.4.23
*
* @param array $forms A collection of active forms on site.
*/
return apply_filters( 'gform_block_form_forms', $forms );
}
/**
* Config data.
*
* @return array[]
*/
public function data() {
$attributes = apply_filters( 'gform_form_block_attributes', $this->attributes );
$orbital_default = GFSettings::is_orbital_default();
return array(
'block_editor' => array(
'gravityforms/form' => array(
'data' => array(
'attributes' => $attributes,
'adminURL' => admin_url( 'admin.php' ),
'forms' => $this->get_forms(),
'preview' => GFCommon::get_base_url() . '/images/gf_block_preview.svg',
'orbitalDefault' => $orbital_default,
'block_docs_url' => 'https://docs.gravityforms.com/gravity-forms-gutenberg-block/',
'styles' => array(
'defaults' => \GFForms::get_service_container()->get( \Gravity_Forms\Gravity_Forms\Form_Display\GF_Form_Display_Service_Provider::BLOCK_STYLES_DEFAULTS ),
),
),
'i18n' => array(
'accent' => esc_html__( 'Accent', 'gravityforms' ),
'advanced' => esc_html__( 'Advanced', 'gravityforms' ),
'ajax' => esc_html__( 'AJAX', 'gravityforms' ),
'appearance' => esc_html__( 'Appearance', 'gravityforms' ),
'background' => esc_html__( 'Background', 'gravityforms' ),
'border' => esc_html__( 'Border', 'gravityforms' ),
'border_radius' => esc_html__( 'Border Radius', 'gravityforms' ),
'button_styles' => esc_html__( 'Button Styles', 'gravityforms' ),
'cancel' => esc_html__( 'Cancel', 'gravityforms' ),
'card' => esc_html__( 'Card', 'gravityforms' ),
'circle' => esc_html__( 'Circle', 'gravityforms' ),
'close' => esc_html__( 'Close', 'gravityforms' ),
'colors' => esc_html__( 'Colors', 'gravityforms' ),
'copy_and_paste_not_available' => esc_html__( 'Copy / Paste Not Available', 'gravityforms' ),
'copy_and_paste_requires_secure_connection' => esc_html__( 'Copy and paste functionality requires a secure connection. Reload this page using an HTTPS URL and try again.', 'gravityforms' ),
'copy_form_styles' => esc_html__( 'Copy Form Styles', 'gravityforms' ),
'custom_colors' => esc_html__( 'Custom Colors', 'gravityforms' ),
'default_colors' => esc_html__( 'Default Colors', 'gravityforms' ),
'description_styles' => esc_html__( 'Description Styles', 'gravityforms' ),
'edit_form' => esc_html__( 'Edit Form', 'gravityforms' ),
'field_values' => esc_html__( 'Field Values', 'gravityforms' ),
'font_size' => esc_html__( 'Font Size', 'gravityforms' ),
'form' => esc_html__( 'Form', 'gravityforms' ),
'form_id' => esc_html__( 'Form ID: %s', 'gravityforms' ),
'form_settings' => esc_html__( 'Form Settings', 'gravityforms' ),
'form_styles' => esc_html__( 'Form Styles', 'gravityforms' ),
'form_theme' => esc_html__( 'Form Theme', 'gravityforms' ),
'form_style_options_not_available' => esc_html__( 'Form style options are not available for forms that use %1$slegacy mode%2$s.', 'gravityforms' ),
'gravity_forms' => esc_html__( 'Gravity Forms', 'gravityforms' ),
'gravity_forms_25_theme' => esc_html__( 'Gravity Forms 2.5 Theme', 'gravityforms' ),
'image_choice_styles' => esc_html__( 'Image Choice Styles', 'gravityforms' ),
'inherit_from_default' => esc_html__( 'Inherit from default (%s)', 'gravityforms' ),
'input_styles' => esc_html__( 'Input Styles', 'gravityforms' ),
'insert_gform_block_title' => esc_html__( 'Add Block To Page', 'gravityforms' ),
'insert_gform_block_content' => esc_html__( 'Click or drag the Gravity Forms Block into the page to insert the form you selected. %1$sLearn More.%2$s', 'gravityforms' ),
'in_pixels' => esc_html__( 'In pixels.', 'gravityforms' ),
'invalid_form_styles' => esc_html__( 'Invalid Form Styles', 'gravityforms' ),
'learn_more_orbital' => esc_html__( 'Learn more about configuring your form to use Orbital.', 'gravityforms' ),
'label_styles' => esc_html__( 'Label Styles', 'gravityforms' ),
'large' => esc_html__( 'Large', 'gravityforms' ),
'medium' => esc_html__( 'Medium', 'gravityforms' ),
'no_card' => esc_html__( 'No Card', 'gravityforms' ),
'ok' => esc_html__( 'OK', 'gravityforms' ),
'orbital_theme' => esc_html__( 'Orbital Theme', 'gravityforms' ),
'paste_form_styles' => esc_html__( 'Paste Form Styles', 'gravityforms' ),
'paste_not_available' => esc_html__( 'Paste Not Available', 'gravityforms' ),
'please_ensure_correct_format' => esc_html__( 'Please ensure the form styles you are trying to paste are in the correct format.', 'gravityforms' ),
'preview' => esc_html__( 'Preview', 'gravityforms' ),
'reset_defaults' => esc_html__( 'Reset Defaults', 'gravityforms' ),
'restore_defaults' => esc_html__( 'Restore Defaults', 'gravityforms' ),
'restore_default_styles' => esc_html__( 'Restore Default Styles', 'gravityforms' ),
'select_a_form' => esc_html__( 'Select a Form', 'gravityforms' ),
'select_and_display_form' => esc_html__( 'Select and display one of your forms.', 'gravityforms' ),
'show_form_description' => esc_html__( 'Show Form Description', 'gravityforms' ),
'show_form_title' => esc_html__( 'Show Form Title', 'gravityforms' ),
'size' => esc_html__( 'Size', 'gravityforms' ),
'small' => esc_html__( 'Small', 'gravityforms' ),
'style' => esc_html__( 'Style', 'gravityforms' ),
'square' => esc_html__( 'Square', 'gravityforms' ),
'tabindex' => esc_html__( 'Tabindex', 'gravityforms' ),
'text' => esc_html__( 'Text', 'gravityforms' ),
'theme_colors' => esc_html__( 'Theme Colors', 'gravityforms' ),
'the_accent_color_is_used' => esc_html__( 'The accent color is used for aspects such as checkmarks and dropdown choices.', 'gravityforms' ),
'the_background_color_is_used' => esc_html__( 'The background color is used for various form elements, such as buttons and progress bars.', 'gravityforms' ),
'the_selected_form_deleted' => esc_html__( 'The selected form has been deleted or trashed. Please select a new form.', 'gravityforms' ),
'this_will_restore_defaults' => esc_html__( 'This will restore your form styles back to their default values and cannot be undone. Are you sure you want to continue?', 'gravityforms' ),
'you_must_have_one_form' => esc_html__( 'You must have at least one form to use the block.', 'gravityforms' ),
'your_browser_no_permission_to_paste' => __( 'Your browser does not have permission to paste from the clipboard. <p>Please navigate to <strong>about:config</strong> and change the preference <strong>dom.events.asyncClipboard.readText</strong> to <strong>true</strong>.', 'gravityforms' ),
'external_link_opens_in_new_tab' => esc_html__( '(opens in a new tab)', 'gravityforms' ),
),
),
)
);
}
}
@@ -0,0 +1,167 @@
<?php
/**
* Background processor for bulk entry actions.
*
* @package Gravity_Forms\Gravity_Forms\Bulk_Actions
* @since 2.10.3
*/
namespace Gravity_Forms\Gravity_Forms\Bulk_Actions;
use Gravity_Forms\Gravity_Forms\Async\GF_Background_Process;
use GFCommon;
use GFFormsModel;
if ( ! class_exists( 'GFForms' ) ) {
die();
}
if ( ! class_exists( 'Gravity_Forms\Gravity_Forms\Async\GF_Background_Process' ) ) {
require_once GF_PLUGIN_DIR_PATH . 'includes/async/class-gf-background-process.php';
}
/**
* Class GF_Entry_Bulk_Action_Processor
*
* Handles background processing of bulk entry actions.
*
* @since 2.10.2
*/
class GF_Entry_Bulk_Action_Processor extends GF_Background_Process {
protected $action = 'gf_entry_bulk_action';
private static $instance = null;
public static function get_instance() {
if ( self::$instance === null ) {
self::$instance = new self();
}
return self::$instance;
}
protected function task( $item ) {
$entry_id = rgar( $item, 'entry_id' );
$form_id = rgar( $item, 'form_id' );
$bulk_action = rgar( $item, 'bulk_action' );
if ( empty( $bulk_action ) ) {
GFCommon::log_debug( __METHOD__ . '(): Skipping task with missing bulk_action.' );
$this->increment_processed();
return false;
}
if ( $bulk_action === 'delete_form' ) {
return $this->process_delete_form( $form_id );
}
if ( empty( $entry_id ) ) {
GFCommon::log_debug( __METHOD__ . '(): Skipping task with missing entry_id.' );
$this->increment_processed();
return false;
}
$entry = \GFAPI::get_entry( $entry_id );
if ( is_wp_error( $entry ) ) {
GFCommon::log_debug( __METHOD__ . sprintf( '(): Entry #%d not found, skipping.', $entry_id ) );
$this->increment_processed();
return false;
}
switch ( $bulk_action ) {
case 'delete':
GFFormsModel::delete_entry( $entry_id );
GFCommon::log_debug( __METHOD__ . sprintf( '(): Deleted entry #%d.', $entry_id ) );
break;
case 'trash':
\GFAPI::update_entry_property( $entry_id, 'status', 'trash' );
GFCommon::log_debug( __METHOD__ . sprintf( '(): Trashed entry #%d.', $entry_id ) );
break;
case 'restore':
\GFAPI::update_entry_property( $entry_id, 'status', 'active' );
GFCommon::log_debug( __METHOD__ . sprintf( '(): Restored entry #%d.', $entry_id ) );
break;
case 'spam':
\GFAPI::update_entry_property( $entry_id, 'status', 'spam' );
GFCommon::log_debug( __METHOD__ . sprintf( '(): Marked entry #%d as spam.', $entry_id ) );
break;
case 'unspam':
\GFAPI::update_entry_property( $entry_id, 'status', 'active' );
GFCommon::log_debug( __METHOD__ . sprintf( '(): Unmarked entry #%d as spam.', $entry_id ) );
break;
case 'mark_read':
\GFAPI::update_entry_property( $entry_id, 'is_read', 1 );
GFCommon::log_debug( __METHOD__ . sprintf( '(): Marked entry #%d as read.', $entry_id ) );
break;
case 'mark_unread':
\GFAPI::update_entry_property( $entry_id, 'is_read', 0 );
GFCommon::log_debug( __METHOD__ . sprintf( '(): Marked entry #%d as unread.', $entry_id ) );
break;
default:
GFCommon::log_debug( __METHOD__ . sprintf( '(): Unknown action "%s" for entry #%d.', $bulk_action, $entry_id ) );
break;
}
$this->increment_processed();
return false;
}
private function process_delete_form( $form_id ) {
if ( empty( $form_id ) ) {
GFCommon::log_debug( __METHOD__ . '(): Skipping task with missing form_id.' );
$this->increment_processed();
return false;
}
$form = \GFAPI::get_form( $form_id );
if ( ! $form ) {
GFCommon::log_debug( __METHOD__ . sprintf( '(): Form #%d not found, skipping.', $form_id ) );
$this->increment_processed();
return false;
}
GFFormsModel::delete_form( $form_id );
GFCommon::log_debug( __METHOD__ . sprintf( '(): Permanently deleted form #%d.', $form_id ) );
$this->increment_processed();
return false;
}
private function increment_processed() {
$status = get_option( 'gf_bulk_action_status', array() );
if ( ! empty( $status ) && rgar( $status, 'status' ) === 'processing' ) {
$status['processed'] = rgar( $status, 'processed', 0 ) + 1;
update_option( 'gf_bulk_action_status', $status );
}
}
protected function complete() {
parent::complete();
$status = get_option( 'gf_bulk_action_status', array() );
if ( ! empty( $status ) ) {
$status['status'] = 'completed';
$status['completed_at'] = time();
update_option( 'gf_bulk_action_status', $status );
}
GFCommon::log_debug( __METHOD__ . '(): Bulk action process completed.' );
}
protected function get_action_for_log() {
return ' for bulk entry action';
}
}
@@ -0,0 +1,59 @@
<?php
/**
* AJAX endpoint for cancelling bulk actions.
*
* @package Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints
* @since 2.10.3
*/
namespace Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints;
use GFCommon;
use Gravity_Forms\Gravity_Forms\Bulk_Actions\GF_Entry_Bulk_Action_Processor;
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class GF_Bulk_Action_Endpoint_Cancel
*
* Handles AJAX requests to cancel bulk actions.
*
* @since 2.10.2
*/
class GF_Bulk_Action_Endpoint_Cancel {
const ACTION_NAME = 'gf_bulk_action_cancel';
public function handle() {
check_ajax_referer( 'gf_bulk_action', 'nonce' );
if ( ! GFCommon::current_user_can_any( 'gravityforms_delete_entries' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'gravityforms' ) ) );
}
$status = get_option( 'gf_bulk_action_status', array() );
if ( empty( $status ) || rgar( $status, 'status' ) !== 'processing' ) {
wp_send_json_error( array( 'message' => __( 'No active bulk operation to cancel.', 'gravityforms' ) ) );
}
$bulk_action = rgar( $status, 'bulk_action' );
require_once GF_PLUGIN_DIR_PATH . 'includes/bulk-actions/class-gf-entry-bulk-action-processor.php';
$processor = GF_Entry_Bulk_Action_Processor::get_instance();
$processor->cancel();
$processor->clear_queue();
delete_option( 'gf_bulk_action_status' );
GFCommon::log_debug( __METHOD__ . sprintf( '(): Cancelled bulk %s operation.', $bulk_action ) );
wp_send_json_success( array(
'message' => __( 'Bulk operation cancelled.', 'gravityforms' ),
'processed' => rgar( $status, 'processed', 0 ),
'total' => rgar( $status, 'total', 0 ),
) );
}
}
@@ -0,0 +1,343 @@
<?php
/**
* AJAX endpoint for starting bulk actions.
*
* @package Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints
* @since 2.10.3
*/
namespace Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints;
use GFCommon;
use GFAPI;
use Gravity_Forms\Gravity_Forms\Bulk_Actions\GF_Entry_Bulk_Action_Processor;
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class GF_Bulk_Action_Endpoint_Start
*
* Handles AJAX requests to start bulk actions.
*
* @since 2.10.2
*/
class GF_Bulk_Action_Endpoint_Start {
const ACTION_NAME = 'gf_start_bulk_action';
const BACKGROUND_THRESHOLD = 100;
const BACKGROUND_ACTIONS_ENTRY = array( 'delete', 'trash', 'spam', 'restore', 'unspam', 'mark_read', 'mark_unread' );
const BACKGROUND_ACTIONS_FORM = array( 'delete_entries', 'delete' );
const BACKGROUND_ACTIONS = array( 'delete', 'trash', 'spam', 'restore', 'unspam', 'delete_entries', 'mark_read', 'mark_unread' );
/**
* Gets the background processing threshold.
*
* @since 2.10.2
*
* @return int The threshold for background processing.
*/
public static function get_background_threshold() {
/**
* Filters the threshold for triggering background processing of bulk actions.
*
* When the number of entries exceeds this threshold, bulk actions will be
* processed in the background instead of synchronously.
*
* @since 2.10.2
*
* @param int $threshold The threshold value. Default 100.
*/
return apply_filters( 'gform_bulk_action_threshold', self::BACKGROUND_THRESHOLD );
}
public function handle() {
check_ajax_referer( 'gf_bulk_action', 'nonce' );
if ( ! GFCommon::current_user_can_any( 'gravityforms_delete_entries' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'gravityforms' ) ) );
}
$bulk_action = sanitize_text_field( rgpost( 'bulk_action' ) );
$action_type = sanitize_text_field( rgpost( 'action_type' ) );
$origin_page = sanitize_text_field( rgpost( 'origin_page' ) );
if ( ! in_array( $bulk_action, self::BACKGROUND_ACTIONS, true ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid action.', 'gravityforms' ) ) );
}
$status = get_option( 'gf_bulk_action_status', array() );
if ( ! empty( $status ) && rgar( $status, 'status' ) === 'processing' ) {
wp_send_json_error( array( 'message' => __( 'A bulk operation is already in progress. Please wait for it to complete.', 'gravityforms' ) ) );
}
if ( $action_type === 'form' ) {
$this->handle_form_action( $bulk_action, $origin_page );
} else {
$this->handle_entry_action( $bulk_action, $origin_page );
}
}
private function handle_entry_action( $bulk_action, $origin_page ) {
$form_id = absint( rgpost( 'form_id' ) );
$entry_ids = rgpost( 'entry_ids' );
$select_all = rgpost( 'select_all' ) === 'true';
if ( empty( $form_id ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid form ID.', 'gravityforms' ) ) );
}
if ( $select_all ) {
$search_criteria = rgpost( 'search_criteria' );
if ( is_string( $search_criteria ) ) {
$search_criteria = json_decode( stripslashes( $search_criteria ), true );
}
$entry_ids = GFAPI::get_entry_ids( $form_id, $search_criteria );
} elseif ( is_string( $entry_ids ) ) {
$entry_ids = json_decode( stripslashes( $entry_ids ), true );
}
if ( empty( $entry_ids ) || ! is_array( $entry_ids ) ) {
wp_send_json_error( array( 'message' => __( 'No entries selected.', 'gravityforms' ) ) );
}
$total = count( $entry_ids );
$status = array(
'form_id' => $form_id,
'bulk_action' => $bulk_action,
'action_type' => 'entry',
'total' => $total,
'processed' => 0,
'status' => 'processing',
'started_at' => time(),
'completed_at' => null,
'origin_url' => 'admin.php?page=gf_entries&id=' . $form_id,
'origin_page' => $origin_page ?: 'entry_list',
);
update_option( 'gf_bulk_action_status', $status );
require_once GF_PLUGIN_DIR_PATH . 'includes/bulk-actions/class-gf-entry-bulk-action-processor.php';
$processor = GF_Entry_Bulk_Action_Processor::get_instance();
$this->reset_processor_if_cancelled( $processor );
$chunks = array_chunk( $entry_ids, self::get_background_threshold() );
foreach ( $chunks as $chunk ) {
foreach ( $chunk as $entry_id ) {
$processor->push_to_queue( array(
'entry_id' => absint( $entry_id ),
'form_id' => $form_id,
'bulk_action' => $bulk_action,
) );
}
$processor->save();
}
$processor->dispatch();
GFCommon::log_debug( __METHOD__ . sprintf( '(): Started bulk %s for form #%d with %d entries.', $bulk_action, $form_id, $total ) );
wp_send_json_success( array(
'total' => $total,
'bulk_action' => $bulk_action,
'action_type' => 'entry',
) );
}
private function handle_form_action( $bulk_action, $origin_page ) {
$form_ids = rgpost( 'form_ids' );
if ( is_string( $form_ids ) ) {
$form_ids = json_decode( stripslashes( $form_ids ), true );
}
if ( empty( $form_ids ) || ! is_array( $form_ids ) ) {
wp_send_json_error( array( 'message' => __( 'No forms selected.', 'gravityforms' ) ) );
}
switch ( $bulk_action ) {
case 'delete_entries':
$this->handle_delete_entries_action( $form_ids, $origin_page );
break;
case 'delete':
$this->handle_delete_forms_action( $form_ids, $origin_page );
break;
default:
wp_send_json_error( array( 'message' => __( 'Invalid action.', 'gravityforms' ) ) );
}
}
private function handle_delete_entries_action( $form_ids, $origin_page ) {
$all_entry_ids = array();
foreach ( $form_ids as $form_id ) {
$entry_ids = GFAPI::get_entry_ids( absint( $form_id ) );
if ( ! empty( $entry_ids ) ) {
foreach ( $entry_ids as $entry_id ) {
$all_entry_ids[] = array(
'entry_id' => absint( $entry_id ),
'form_id' => absint( $form_id ),
);
}
}
}
if ( empty( $all_entry_ids ) ) {
wp_send_json_error( array( 'message' => __( 'No entries found in the selected forms.', 'gravityforms' ) ) );
}
$total = count( $all_entry_ids );
if ( $total <= self::get_background_threshold() ) {
wp_send_json_success( array(
'below_threshold' => true,
'total' => $total,
) );
}
$status = array(
'form_id' => null,
'form_ids' => $form_ids,
'bulk_action' => 'delete_entries',
'action_type' => 'form',
'total' => $total,
'processed' => 0,
'status' => 'processing',
'started_at' => time(),
'completed_at' => null,
'origin_url' => 'admin.php?page=gf_edit_forms',
'origin_page' => $origin_page ?: 'form_list',
);
update_option( 'gf_bulk_action_status', $status );
require_once GF_PLUGIN_DIR_PATH . 'includes/bulk-actions/class-gf-entry-bulk-action-processor.php';
$processor = GF_Entry_Bulk_Action_Processor::get_instance();
$this->reset_processor_if_cancelled( $processor );
$chunks = array_chunk( $all_entry_ids, self::get_background_threshold() );
foreach ( $chunks as $chunk ) {
foreach ( $chunk as $entry_data ) {
$processor->push_to_queue( array(
'entry_id' => $entry_data['entry_id'],
'form_id' => $entry_data['form_id'],
'bulk_action' => 'delete',
) );
}
$processor->save();
}
$processor->dispatch();
GFCommon::log_debug( __METHOD__ . sprintf( '(): Started bulk delete_entries for %d forms (%d entries).', count( $form_ids ), $total ) );
wp_send_json_success( array(
'total' => $total,
'bulk_action' => 'delete_entries',
'action_type' => 'form',
) );
}
private function handle_delete_forms_action( $form_ids, $origin_page ) {
$all_entry_ids = array();
$form_ids_int = array();
foreach ( $form_ids as $form_id ) {
$form_id_int = absint( $form_id );
$form_ids_int[] = $form_id_int;
$entry_ids = GFAPI::get_entry_ids( $form_id_int );
if ( ! empty( $entry_ids ) ) {
foreach ( $entry_ids as $entry_id ) {
$all_entry_ids[] = array(
'entry_id' => absint( $entry_id ),
'form_id' => $form_id_int,
);
}
}
}
$entry_count = count( $all_entry_ids );
$form_count = count( $form_ids_int );
$total = $entry_count + $form_count;
if ( $entry_count <= self::get_background_threshold() ) {
wp_send_json_success( array(
'below_threshold' => true,
'total' => $entry_count,
) );
}
$status = array(
'form_id' => null,
'form_ids' => $form_ids_int,
'bulk_action' => 'delete',
'action_type' => 'form',
'total' => $total,
'processed' => 0,
'status' => 'processing',
'started_at' => time(),
'completed_at' => null,
'origin_url' => 'admin.php?page=gf_edit_forms',
'origin_page' => $origin_page ?: 'form_list',
);
update_option( 'gf_bulk_action_status', $status );
require_once GF_PLUGIN_DIR_PATH . 'includes/bulk-actions/class-gf-entry-bulk-action-processor.php';
$processor = GF_Entry_Bulk_Action_Processor::get_instance();
$this->reset_processor_if_cancelled( $processor );
$chunks = array_chunk( $all_entry_ids, self::get_background_threshold() );
foreach ( $chunks as $chunk ) {
foreach ( $chunk as $entry_data ) {
$processor->push_to_queue( array(
'entry_id' => $entry_data['entry_id'],
'form_id' => $entry_data['form_id'],
'bulk_action' => 'delete',
) );
}
$processor->save();
}
foreach ( $form_ids_int as $form_id ) {
$processor->push_to_queue( array(
'form_id' => $form_id,
'bulk_action' => 'delete_form',
) );
}
$processor->save();
$processor->dispatch();
GFCommon::log_debug( __METHOD__ . sprintf( '(): Started bulk form delete for %d forms (%d entries).', $form_count, $entry_count ) );
wp_send_json_success( array(
'total' => $total,
'bulk_action' => 'delete',
'action_type' => 'form',
) );
}
/**
* Resets the processor if it was previously cancelled.
*
* @since 2.10.2
*
* @param GF_Entry_Bulk_Action_Processor $processor The processor instance.
*/
private function reset_processor_if_cancelled( $processor ) {
if ( $processor->is_cancelled() ) {
$processor->clear_queue();
$processor->resume( false );
}
}
}
@@ -0,0 +1,101 @@
<?php
/**
* AJAX endpoint for checking bulk action status.
*
* @package Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints
* @since 2.10.3
*/
namespace Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints;
use GFCommon;
use Gravity_Forms\Gravity_Forms\Bulk_Actions\GF_Entry_Bulk_Action_Processor;
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class GF_Bulk_Action_Endpoint_Status
*
* Handles AJAX requests to check bulk action progress.
*
* @since 2.10.2
*/
class GF_Bulk_Action_Endpoint_Status {
const ACTION_NAME = 'gf_bulk_action_status';
const STUCK_TIMEOUT = 1800; // 30 minutes
public function handle() {
check_ajax_referer( 'gf_bulk_action', 'nonce' );
if ( ! GFCommon::current_user_can_any( 'gravityforms_delete_entries' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'gravityforms' ) ) );
}
$status = get_option( 'gf_bulk_action_status', array() );
require_once GF_PLUGIN_DIR_PATH . 'includes/bulk-actions/class-gf-entry-bulk-action-processor.php';
$processor = GF_Entry_Bulk_Action_Processor::get_instance();
$has_queue = $processor->is_queued();
if ( $this->is_stuck( $status, $processor ) ) {
$processor->clear_queue();
delete_option( 'gf_bulk_action_status' );
wp_send_json_success( array( 'active' => false ) );
}
if ( empty( $status ) ) {
if ( $has_queue ) {
$processor->clear_queue();
}
wp_send_json_success( array(
'active' => false,
) );
}
$is_processing = rgar( $status, 'status' ) === 'processing';
wp_send_json_success( array(
'active' => $is_processing || $has_queue,
'pending' => $has_queue && ! $processor->is_processing(),
'cancelled' => rgar( $status, 'status' ) === 'cancelled',
'form_id' => rgar( $status, 'form_id' ),
'bulk_action' => rgar( $status, 'bulk_action' ),
'action_type' => rgar( $status, 'action_type', 'entry' ),
'total' => rgar( $status, 'total', 0 ),
'processed' => rgar( $status, 'processed', 0 ),
'status' => rgar( $status, 'status' ),
'origin_url' => rgar( $status, 'origin_url' ),
'origin_page' => rgar( $status, 'origin_page' ),
) );
}
/**
* Checks if a bulk action is stuck.
*
* @since 2.10.2
*
* @param array $status The bulk action status.
* @param GF_Entry_Bulk_Action_Processor $processor The processor instance.
*
* @return bool
*/
private function is_stuck( $status, $processor ) {
if ( empty( $status ) || rgar( $status, 'status' ) !== 'processing' ) {
return false;
}
if ( $processor->is_processing() ) {
return false;
}
$started_at = rgar( $status, 'started_at', 0 );
$elapsed = time() - $started_at;
return $elapsed > self::STUCK_TIMEOUT;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,106 @@
<?php
/**
* Background Upgrader
*
* Uses https://github.com/A5hleyRich/wp-background-processing to handle DB
* updates in the background.
*
* @class GF_Background_Upgrader
* @version 2.3
* @category Class
* @author Rocketgenius
*/
use Gravity_Forms\Gravity_Forms\Async\GF_Background_Process;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'Gravity_Forms\Gravity_Forms\Async\GF_Background_Process' ) ) {
require_once GF_PLUGIN_DIR_PATH . 'includes/async/class-gf-background-process.php';
}
/**
* GF_Background_Upgrader Class.
*/
class GF_Background_Upgrader extends GF_Background_Process {
/**
* @var string
*/
protected $action = 'gf_upgrader';
/**
* Returns the data for the background upgrader.
*
* @since 2.3
*
* @return array
*/
public function get_data() {
return $this->data;
}
/**
* Is the queue empty for all blogs?
*
* @since 2.3
*
* @return bool
*/
public function is_queue_empty() {
return parent::is_queue_empty();
}
/**
* Is the updater running?
* @return boolean
*/
public function is_updating() {
return false === $this->is_queue_empty();
}
/**
* Task
*
* Override this method to perform any actions required on each
* queue item. Return the modified item for further processing
* in the next pass through. Or, return false to remove the
* item from the queue.
*
* @param string $callback Update callback function
* @return mixed
*/
protected function task( $callback ) {
if ( ! defined( 'GF_UPGRADING' ) ) {
define( 'GF_UPGRADING', true );
}
if ( is_callable( $callback ) ) {
GFCommon::log_debug( sprintf( '%s(): Running callback: %s', __METHOD__, print_r( $callback, 1 ) ) );
$needs_more_time = call_user_func( $callback );
if ( $needs_more_time ) {
GFCommon::log_debug( sprintf( '%s(): Callback needs another run: %s', __METHOD__, print_r( $callback, 1 ) ) );
return $callback;
} else {
GFCommon::log_debug( sprintf( '%s(): Finished callback: %s', __METHOD__, print_r( $callback, 1 ) ) );
}
} else {
GFCommon::log_debug( sprintf( '%s(): Could not find callback: %s', __METHOD__, print_r( $callback, 1 ) ) );
}
return false;
}
/**
* Complete
*
* Override if applicable, but ensure that the below actions are
* performed, or, call parent::complete().
*/
protected function complete() {
parent::complete();
}
}
@@ -0,0 +1,280 @@
<?php
/**
* Handles download requests for files stored by File Upload fields.
*
* Class GF_Download
*/
class GF_Download {
/**
* If the request is for a Gravity Forms file download then validate and deliver.
*
* @since 2.0
*/
public static function maybe_process() {
if ( isset( $_GET['gf-download'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$file = $_GET['gf-download']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$form_id = rgget( 'form-id' );
$field_id = rgget( 'field-id' );
if ( empty( $file ) || empty( $form_id ) ) {
return;
}
$hash = rgget( 'hash' );
$entry_id = rgget( 'entry-id' );
GFCommon::log_debug( __METHOD__ . "(): Starting file download process. file: {$file}, hash: {$hash}." );
$file_validation = self::validate_file_path( $file );
if ( is_wp_error( $file_validation ) ) {
GFCommon::log_debug( __METHOD__ . sprintf( '(): Not downloading file (%s): %s', $file_validation->get_error_code(), $file ) );
self::die_401();
}
$permission_granted = self::validate_download( $form_id, $field_id, $file, $hash, $entry_id );
if ( has_filter( 'gform_permission_granted_pre_download' ) ) {
GFCommon::log_debug( __METHOD__ . '(): Executing functions hooked to gform_permission_granted_pre_download.' );
}
/**
* Allow custom logic to be used to determine if the file can be accessed.
*
* @since 2.4.3.2
*
* @param bool $permission_granted Indicates if access to the file has been granted. Default is the result of the hash validation.
* @param string $form_id The ID of the form used to upload the requested file.
* @param string $field_id The ID of the field used to upload the requested file.
*/
$permission_granted = apply_filters( 'gform_permission_granted_pre_download', $permission_granted, $form_id, $field_id );
if ( $permission_granted ) {
GFCommon::log_debug( __METHOD__ . '(): Download validated. Proceeding.' );
self::deliver( $form_id, $file, $entry_id );
} else {
GFCommon::log_debug( __METHOD__ . '(): Download validation failed. Aborting with 401.' );
self::die_401();
}
}
}
/**
* Verifies the hash for the download.
*
* @since 2.0
* @since 2.9.29 Added the $entry_id param.
*
* @param string $form_id The form ID from the query string.
* @param string $field_id The field ID from the query string.
* @param string $file The file name from the query string.
* @param string $hash The hash from the query string.
* @param string $entry_id The entry ID from the query string.
*
* @return bool
*/
private static function validate_download( $form_id, $field_id, $file, $hash, $entry_id ) {
if ( empty( $hash ) ) {
return false;
}
if ( has_filter( 'gform_require_login_pre_download' ) ) {
GFCommon::log_debug( __METHOD__ . '(): Executing functions hooked to gform_require_login_pre_download.' );
}
/**
* Allows login to be required to access the file.
*
* @since 2.2.3.16
*
* @param bool $require_login Does the user need to be logged in to access the file? Default false.
* @param string $form_id The ID of the form used to upload the requested file.
* @param string $field_id The ID of the field used to upload the requested file.
*/
$require_login = apply_filters( 'gform_require_login_pre_download', false, $form_id, $field_id );
if ( $require_login && ! is_user_logged_in() ) {
GFCommon::log_debug( __METHOD__ . '(): Login required to access file.' );
return false;
}
$hash_check = GFCommon::generate_download_hash( $form_id, $field_id, $file, $entry_id );
$valid = hash_equals( $hash, $hash_check );
if ( ! $valid ) {
GFCommon::log_debug( __METHOD__ . '(): Hash failed validation.' );
return false;
}
return true;
}
/**
* Send the file.
*
* @since 2.0
* @since 2.9.29 Added the $entry_id param.
*
* @param string $form_id The form ID from the query string.
* @param string $file The file name from the query string.
* @param string $entry_id The entry ID from the query string.
*/
private static function deliver( $form_id, $file, $entry_id ) {
if ( $entry_id && ! GFAPI::entry_exists( $entry_id ) ) {
GFCommon::log_debug( __METHOD__ . sprintf( '(): Entry ID %d has been deleted. Aborting with 404', $entry_id ) );
self::die_404();
}
$path = GFFormsModel::get_upload_path( $form_id );
$file_path = trailingslashit( $path ) . $file;
GFCommon::log_debug( __METHOD__ . "(): Checking if file exists: {$file_path}." );
if ( file_exists( $file_path ) ) {
GFCommon::log_debug( __METHOD__ . '(): File exists. Starting delivery.' );
$content_type = self::get_content_type( $file_path );
$content_disposition = rgget( 'dl' ) ? 'attachment' : 'inline';
/**
* Allows the nocache_headers() to be overridden.
*
* @since 2.9.15
*
* @param bool $enable If the nocache_headers() should be called. Default true.
* @param string $form_id The ID of the form used to download files.
* @param string $file The file to be downloaded.
*/
if ( apply_filters( 'gform_enable_download_nocache_headers', true, $form_id, $file ) ) {
nocache_headers();
}
header( 'X-Robots-Tag: noindex', true );
header( 'Content-Type: ' . $content_type );
header( 'Content-Description: File Transfer' );
header( 'Content-Disposition: ' . $content_disposition . '; filename="' . wp_basename( $file ) . '"' );
header( 'Content-Transfer-Encoding: binary' );
// Clear buffer AND turn off output buffering before starting delivery of files requested for download to prevent third-parties to corrupt the file content.
if ( ob_get_contents() ) {
ob_end_clean();
}
self::readfile_chunked( $file_path );
die();
} else {
GFCommon::log_debug( __METHOD__ . '(): File does not exist. Aborting with 404.' );
self::die_404();
}
}
/**
* Returns the appropriate mime type for the file extension.
*
* @param $file_path
*
* @return mixed|null|string
*/
private static function get_content_type( $file_path ) {
$info = wp_check_filetype( $file_path );
$type = rgar( $info, 'type' );
return $type;
}
/**
* Reads file in chunks so big downloads are possible without changing PHP.INI
* See https://github.com/bcit-ci/CodeIgniter/wiki/Download-helper-for-large-files
*
* @access public
* @param string $file The file
* @param boolean $retbytes Return the bytes of file
* @return bool|string If string, $status || $cnt
*/
private static function readfile_chunked( $file, $retbytes = true ) {
$chunksize = 1024 * 1024;
$buffer = '';
$cnt = 0;
$handle = @fopen( $file, 'r' );
if ( $size = @filesize( $file ) ) {
header( 'Content-Length: ' . $size );
}
if ( false === $handle ) {
return false;
}
while ( ! @feof( $handle ) ) {
$buffer = @fread( $handle, $chunksize );
echo $buffer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( $retbytes ) {
$cnt += strlen( $buffer );
}
}
$status = @fclose( $handle );
if ( $retbytes && $status ) {
return $cnt;
}
return $status;
}
/**
* Ends the request with a 404 (Not Found) HTTP status code. Loads the 404 template if it exists.
*/
private static function die_404() {
global $wp_query;
status_header( 404 );
$wp_query->set_404();
$template_path = get_404_template();
if ( file_exists( $template_path ) ) {
require_once( $template_path );
}
die();
}
/**
* Validates a file path from a download request for security concerns.
*
* Checks for null bytes and directory traversal characters (including encoded variants).
*
* @since 2.10.2
*
* @param string $file The file path to validate.
*
* @return true|WP_Error True if the file path is safe, WP_Error otherwise.
*/
public static function validate_file_path( $file ) {
if ( empty( $file ) ) {
return new WP_Error( 'empty_file', __( 'The file path is empty.', 'gravityforms' ) );
}
// Null byte injection check.
if ( str_contains( $file, '%00' ) || str_contains( $file, "\0" ) ) {
return new WP_Error( 'null_byte', __( 'The file path contains a null byte.', 'gravityforms' ) );
}
// Directory traversal check on decoded path to catch encoded variants.
$decoded_file = rawurldecode( rawurldecode( rawurldecode( $file ) ) );
if ( str_contains( $decoded_file, '..' ) ) {
return new WP_Error( 'directory_traversal', __( 'The file path contains directory traversal characters.', 'gravityforms' ) );
}
return true;
}
/**
* Ends the request with a 401 (Unauthorized) HTTP status code.
*/
private static function die_401() {
status_header( 401 );
die();
}
}
@@ -0,0 +1,351 @@
<?php
/**
* Adds integration with osDXP.
*
* @since 2.4.15
*/
class GF_OSDXP {
/**
* @var GF_OSDXP
*/
private static $instance;
/**
* @var array The array of page slugs.
*/
private $slugs = array();
/**
* Class constructor.
*
* @since 2.4.15
*/
private function __construct() {
$this->slugs = [
'gf_settings' => esc_html__( 'Settings', 'gravityforms' ),
'gf_export' => esc_html__( 'Import/Export', 'gravityforms' ),
'gf_addons' => esc_html__( 'Add-Ons', 'gravityforms' ),
'gf_help' => esc_html__( 'Help', 'gravityforms' ),
'gf_system_status' => esc_html__( 'System Status', 'gravityforms' ),
];
add_filter( 'osdxp_license_key_gravityforms', array( $this, 'license_key' ) );
add_filter( 'osdxp_dashboard_license_submit_response', array( $this, 'process_license_key_submit' ), 10, 3 );
add_filter( 'osdxp_dashboard_license_deletion_response', array(
$this,
'process_license_key_deletion',
), 10, 2 );
add_filter( 'osdxp_add_module_settings_page', array( $this, 'settings_page' ) );
add_filter( 'parent_file', array( $this, 'settings_page_highlight' ) );
add_action( 'in_admin_header', array( $this, 'nav_tabs' ) );
}
/**
* Get a class instance.
*
* @since 2.4.15
*
* @return GF_OSDXP instance of GF_OSDXP.
*/
public static function get_instance() {
if ( ! self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Handles the license key display.
*
* @since 2.4.15
*
* @param $license_key
*
* @return string|null Return a string or null to display the text box.
*/
public static function license_key( $license_key ) {
$version_info = GFCommon::get_version_info( false );
$key = GFCommon::get_key();
if ( ! rgempty( 'is_error', $version_info ) ) {
return esc_attr__( 'There was an error while validating your license key. Gravity Forms will continue to work, but automatic upgrades will not be available. Please contact support to resolve this issue.', 'gravityforms' );
} elseif ( rgar( $version_info, 'is_valid_key' ) ) {
return esc_html__( 'Valid', 'gravityforms' );
} elseif ( ! empty( $key ) ) {
return esc_html__( 'Invalid or Expired.', 'gravityforms' );
}
}
/**
* Process license key submit.
*
* Callback for the 'osdxp_dashboard_license_submit_response' filter.
*
* @since 2.4.15
*
* @param array $response Response.
* @param string $plugin_slug Plugin slug.
* @param string $license_key License key.
*
* @return array Response array.
*/
public static function process_license_key_submit( $response, $plugin_slug, $license_key ) {
if ( 'gravityforms' !== $plugin_slug ) {
return $response;
}
if ( ! is_array( $response ) ) {
$response = array();
}
// Sanitize license key.
$license_key = sanitize_text_field( $license_key );
GFFormsModel::save_key( $license_key );
// Updating message because key could have been changed.
GFCommon::cache_remote_message();
// Re-caching version info.
$version_info = GFCommon::get_version_info( false );
$error_message = '';
$key = GFCommon::get_key();
if ( ! rgempty( 'is_error', $version_info ) ) {
$error_message = esc_attr__( 'There was an error while validating your license key. Gravity Forms will continue to work, but automatic upgrades will not be available. Please contact support to resolve this issue.', 'gravityforms' );
} elseif ( rgar( $version_info, 'is_valid_key' ) ) {
update_option( 'gform_pending_installation', 0 );
if ( ! get_option( 'rg_gforms_currency' ) ) {
update_option( 'rg_gforms_currency', 'USD' );
}
$success_message = esc_html__( 'Valid Key : Your license key has been successfully validated.', 'gravityforms' );
if ( empty( $response['success_messages'] ) || ! is_array( $response['success_messages'] ) ) {
$response['success_messages'] = array();
}
$response['success_messages'][] = $success_message;
} elseif ( rgar( $version_info, 'reason' ) == 'requires_enterprise_license' ) {
$error_message = esc_html__( 'Invalid Key - an Enterprise license is required.', 'gravityforms' );
} elseif ( ! empty( $key ) ) {
$error_message = esc_html__( 'Invalid or Expired Key - Please make sure you have entered the correct value and that your key is not expired.', 'gravityforms' );
}
if ( ! empty( $error_message ) ) {
if ( empty( $response['error_messages'] ) || ! is_array( $response['error_messages'] ) ) {
$response['error_messages'] = array();
}
$response['error_messages'][] = $error_message;
}
return $response;
}
/**
* Process the license key deletion.
*
* @since 2.4.15
*
* @param $response
* @param $plugin_slug
*
* @return array
*/
public static function process_license_key_deletion( $response, $plugin_slug ) {
if ( $plugin_slug != 'gravityforms' ) {
return $response;
}
if ( ! is_array( $response ) ) {
$response = array();
}
GFFormsModel::save_key( '' );
$response['success'] = 1;
if ( empty( $response['success_messages'] ) || ! is_array( $response['success_messages'] ) ) {
$response['success_messages'] = array();
}
$response['success_messages'][] = esc_html__( 'License successfully removed.', 'gravityforms' );
return $response;
}
/**
* Registers plugin pages with osDXP.
*
* @since 2.4.15
*
* @param $pages
*
* @return array
*/
public function settings_page( $pages ) {
/**
* Setup.
*/
$has_full_access = current_user_can( 'gform_full_access' );
$min_cap = GFCommon::current_user_can_which( GFCommon::all_caps() );
if ( empty( $min_cap ) ) {
$min_cap = 'gform_full_access';
}
$parent_menu = array(
'name' => 'gf_edit_forms',
'callback' => array('GFForms', 'forms')
);
/**
* Remove Classic WP-Admin menu items.
*/
global $admin_page_hooks;
static $removed = false;
if ( ! $removed && is_array( $admin_page_hooks ) ) {
$removed = true;
remove_menu_page( $parent_menu['name'] );
remove_submenu_page( $parent_menu['name'], 'gf_settings' );
remove_submenu_page( $parent_menu['name'], 'gf_export' );
remove_submenu_page( $parent_menu['name'], 'gf_addons' );
remove_submenu_page( $parent_menu['name'], 'gf_system_status' );
remove_submenu_page( $parent_menu['name'], 'gf_help' );
}
/**
* Add osDXP specific pages.
*/
// Top-level action page.
$pages[] = array(
'function' => $parent_menu['callback'],
'type' => 'menu',
'menu_slug' => $parent_menu['name'],
'page_title' => esc_html__( 'Forms', 'gravityforms' ),
'menu_title' => esc_html__( 'Forms', 'gravityforms' ),
'capability' => $has_full_access ? 'gform_full_access' : $min_cap,
'icon_url' => GFForms::get_admin_icon_b64( '#FFF' ),
);
// Settings page.
$pages[] = array(
'function' => array( 'GFForms', 'settings_page' ),
'menu_slug' => 'gf_settings',
'page_title' => esc_html__( 'Form Settings', 'gravityforms' ),
'menu_title' => esc_html__( 'Form Settings', 'gravityforms' ),
'capability' => $has_full_access ? 'gform_full_access' : 'gravityforms_view_settings',
);
// Export page.
$pages[] = array(
'function' => array( 'GFForms', 'export_page' ),
'menu_slug' => 'gf_export',
'type' => 'endpoint',
'page_title' => esc_html__( 'Import/Export', 'gravityforms' ),
'menu_title' => esc_html__( 'Import/Export', 'gravityforms' ),
'capability' => $has_full_access ? 'gform_full_access' : ( current_user_can( 'gravityforms_export_entries' ) ? 'gravityforms_export_entries' : 'gravityforms_edit_forms' ),
);
if ( current_user_can( 'install_plugins' ) ) {
// Add-ons page.
$pages[] = array(
'function' => array( 'GFForms', 'addons_page' ),
'menu_slug' => 'gf_addons',
'type' => 'endpoint',
'page_title' => esc_html__( 'Add-Ons', 'gravityforms' ),
'menu_title' => esc_html__( 'Add-Ons', 'gravityforms' ),
'capability' => $has_full_access ? 'gform_full_access' : 'gravityforms_view_addons',
);
}
// System status page.
$pages[] = array(
'function' => array( 'GFForms', 'system_status' ),
'menu_slug' => 'gf_system_status',
'type' => 'endpoint',
'page_title' => esc_html__( 'System Status', 'gravityforms' ),
'menu_title' => esc_html__( 'System Status', 'gravityforms' ),
'capability' => $has_full_access ? 'gform_full_access' : 'gravityforms_system_status',
);
// Help page.
$pages[] = array(
'function' => array( 'GFForms', 'help_page' ),
'menu_slug' => 'gf_help',
'type' => 'endpoint',
'page_title' => esc_html__( 'Help', 'gravityforms' ),
'menu_title' => esc_html__( 'Help', 'gravityforms' ),
'capability' => $has_full_access ? 'gform_full_access' : $min_cap,
);
return $pages;
}
/**
* Helper function to check if a page slug is a setting/misc page.
*
* @since 2.4.15
*
* @param $plugin_page
*
* @return bool
*/
private function is_setting_page( $plugin_page ) {
return array_key_exists( $plugin_page, $this->slugs );
}
/**
* Highlights appropriate menu item for misc pages.
*
* @since 2.4.15
*
* @param $file
*
* @return $file
*/
public function settings_page_highlight( $file ) {
global $plugin_page, $submenu_file;
if ( $this->is_setting_page( $plugin_page ) ) {
$file = 'dxp-module-settings';
$submenu_file = 'gf_settings';
}
return $file;
}
/**
* Outputs Nav Tabs for settings&misc pages.
*
* @since 2.4.15
*/
public function nav_tabs() {
global $plugin_page;
if ( $this->is_setting_page( $plugin_page ) ) {
?>
<ul class="osdxp-nav-tabs">
<?php
foreach ( $this->slugs as $path => $name ) {
?>
<li <?php echo ( $path === $plugin_page ) ? 'class="active"' : '' ?>>
<a href="<?php echo esc_url_raw( admin_url( 'admin.php?page=' . $path ) ); ?>">
<?php echo esc_html( $name ); ?>
</a>
</li>
<?php
} ?>
</ul>
<?php
}
}
}
GF_OSDXP::get_instance();
@@ -0,0 +1,94 @@
<?php
namespace Gravity_Forms\Gravity_Forms;
/**
* Class GF_Service_Container
*
* A simple Service Container used to collect and organize Services used by the application and its modules.
*
* @since 2.5
*
* @package Gravity_Forms\Gravity_Forms
*/
class GF_Service_Container {
private $services = array();
private $providers = array();
/**
* Add a service to the container.
*
* @since 2.5
*
* @param string $name The service Name
* @param mixed $service The service to add
*/
public function add( $name, $service, $defer = false ) {
if ( empty( $name ) ) {
$name = get_class( $service );
}
if ( ! $defer && is_callable( $service ) ) {
$service = $service();
}
$this->services[ $name ] = $service;
}
/**
* Remove a service from the container.
*
* @since 2.5
*
* @param string $name The service name.
*/
public function remove( $name ) {
unset( $this->services[ $name ] );
}
/**
* Get a service from the container by name.
*
* @since 2.5
*
* @param string $name The service name.
*
* @return mixed|null
*/
public function get( $name ) {
if ( ! isset( $this->services[ $name ] ) ) {
return null;
}
if ( is_callable( $this->services[ $name ] ) ) {
$called = $this->services[ $name ]();
$this->services[ $name ] = $called;
}
return $this->services[ $name ];
}
/**
* Add a service provider to the container and register each of its services.
*
* @since 2.5
*
* @param GF_Service_Provider $provider
*/
public function add_provider( GF_Service_Provider $provider ) {
$provider_name = get_class( $provider );
// Only add providers a single time.
if ( isset( $this->providers[ $provider_name ] ) ) {
return;
}
$this->providers[ $provider_name ] = $provider;
$provider->set_container( $this );
$provider->register( $this );
$provider->init( $this );
}
}
@@ -0,0 +1,107 @@
<?php
namespace Gravity_Forms\Gravity_Forms;
use Gravity_Forms\Gravity_Forms\Config\GF_App_Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Service_Provider;
/**
* Class GF_Service_Provider
*
* An abstraction which provides a contract for defining Service Providers. Service Providers facilitate
* organizing Services into discreet modules, as opposed to having to register each service in a single location.
*
* @since 2.5
*
* @package Gravity_Forms\Gravity_Forms
*/
abstract class GF_Service_Provider {
/**
* @var GF_Service_Container $container
*/
protected $container;
public function set_container( GF_Service_Container $container ) {
$this->container = $container;
}
/**
* Register new services to the Service Container.
*
* @param GF_Service_Container $container
*
* @return void
*/
abstract public function register( GF_Service_Container $container );
/**
* Noop by default - used to initialize hooks and filters for the given module.
*/
public function init( GF_Service_Container $container ) {}
//----------------------------------------
//---------- App Registration ------------
//----------------------------------------
/**
* Register a JS app with the given arguments.
*
* @since 2.7.1
*
* @param array $args
*/
public function register_app( $args ) {
$config = new GF_App_Config( $this->container->get( GF_Config_Service_Provider::DATA_PARSER ) );
$config->set_data( $args );
$this->container->get( GF_Config_Service_Provider::CONFIG_COLLECTION )->add_config( $config );
$should_display = is_callable( $args['enqueue'] ) ? call_user_func( $args['enqueue'] ) : $args['enqueue'];
if ( ! $should_display ) {
return;
}
if ( ! empty( $args['css'] ) ) {
$this->enqueue_app_css( $args );
}
if ( ! empty( $args['root_element'] ) ) {
$this->add_root_element( $args['root_element'] );
}
}
/**
* Enqueue the CSS assets for the app.
*
* @since 2.7.1
*
* @param $args
*/
protected function enqueue_app_css( $args ) {
$css_asset = $args['css'];
add_action( 'wp_enqueue_scripts', function () use ( $css_asset ) {
call_user_func_array( 'wp_enqueue_style', $css_asset );
} );
add_action( 'admin_enqueue_scripts', function () use ( $css_asset ) {
call_user_func_array( 'wp_enqueue_style', $css_asset );
} );
}
/**
* Add the root element to the footer output for bootstrapping.
*
* @since 2.7.1
*
* @param string $root
*/
protected function add_root_element( $root ) {
add_action( 'admin_footer', function() use ( $root ) {
echo '<div data-js="' . esc_attr( $root ) . '"></div>';
}, 10, 0 );
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,588 @@
<?php
if ( ! class_exists( 'GFForms' ) ) {
die();
}
if ( ! defined( 'GRAVITY_API_URL' ) ) {
define( 'GRAVITY_API_URL', 'https://gravityapi.com/wp-json/gravityapi/v1' );
}
if ( ! class_exists( 'Gravity_Api' ) ) {
/**
* Client-side API wrapper for interacting with the Gravity APIs.
*
* @package Gravity Forms
* @subpackage Gravity_Api
* @since 1.9
* @access public
*/
class Gravity_Api {
private static $instance = null;
private static $raw_response = null;
public static function get_instance() {
if ( null == self::$instance ) {
self::$instance = new self;
}
return self::$instance;
}
/**
* Retrieves site key and site secret key from remote API and stores them as WP options. Returns false if license key is invalid; otherwise, returns true.
*
* @since 2.3
*
* @param string $license_key License key to be registered.
* @param boolean $is_md5 Specifies if $license_key provided is an MD5 or unhashed license key.
*
* @return bool|WP_Error
*/
public function register_current_site( $license_key, $is_md5 = false ) {
$body = array();
$body['site_name'] = get_bloginfo( 'name' );
$body['site_url'] = get_bloginfo( 'url' );
if ( $is_md5 ) {
$body['license_key_md5'] = $license_key;
} else {
$body['license_key'] = $license_key;
}
GFCommon::log_debug( __METHOD__ . '(): registering site' );
$result = $this->request( 'sites', $body, 'POST', array( 'headers' => $this->get_license_auth_header( $license_key ) ) );
$result = $this->prepare_response_body( $result, true );
if ( is_wp_error( $result ) ) {
GFCommon::log_error( __METHOD__ . '(): error registering site. ' . $result->get_error_message() );
return $result;
}
update_option( 'gf_site_key', $result['key'] );
update_option( 'gf_site_secret', $result['secret'] );
GFCommon::log_debug( __METHOD__ . '(): site registration successful. Site Key: ' . $result['key'] );
return true;
}
/**
* Updates license key for a site that has already been registered.
*
* @since 2.3
* @since 2.5 Returns License Response on success.
*
* @access public
*
* @param string $new_license_key_md5 Hash license key to be updated
*
* @return \Gravity_Forms\Gravity_Forms\License\GF_License_API_Response|WP_Error
*/
public function update_current_site( $new_license_key_md5 ) {
$site_key = $this->get_site_key();
$site_secret = $this->get_site_secret();
if ( empty( $site_key ) || empty( $site_secret ) ) {
return false;
}
$body = GFCommon::get_remote_post_params();
$body['site_name'] = get_bloginfo( 'name' );
$body['site_url'] = get_bloginfo( 'url' );
$body['site_key'] = $site_key;
$body['site_secret'] = $site_secret;
$body['license_key_md5'] = $new_license_key_md5;
GFCommon::log_debug( __METHOD__ . '(): refreshing license info' );
$result = $this->request( 'sites/' . $site_key, $body, 'PUT', array( 'headers' => $this->get_site_auth_header( $site_key, $site_secret ) ) );
$result = $this->prepare_response_body( $result, true );
if ( is_wp_error( $result ) ) {
GFCommon::log_debug( __METHOD__ . '(): error updating site registration. ' . print_r( $result, true ) );
return $result;
}
return $result;
}
/***
* Removes a license key from a registered site. NOTE: It doesn't actually deregister the site.
*
* @deprecated Use gapi()->update_current_site('') instead.
*
* @return bool|WP_Error
*/
public function deregister_current_site() {
$site_key = $this->get_site_key();
$site_secret = $this->get_site_secret();
if ( empty( $site_key ) ) {
return false;
}
GFCommon::log_debug( __METHOD__ . '(): deregistering' );
$body = array(
'license_key_md5' => '',
);
$result = $this->request( 'sites/' . $site_key, $body, 'PUT', array( 'headers' => $this->get_site_auth_header( $site_key, $site_secret ) ) );
$result = $this->prepare_response_body( $result, true );
if ( is_wp_error( $result ) ) {
GFCommon::log_debug( __METHOD__ . '(): error updating site registration. ' . print_r( $result, true ) );
return $result;
}
return true;
}
/**
* Check the given license key to get its information from the API.
*
* @since 2.5
*
* @param string $key The license key.
*
* @return array|false|WP_Error
*/
public function check_license( $key ) {
GFCommon::log_debug( __METHOD__ . '(): getting site and license info' );
$params = array(
'site_url' => get_option( 'home' ),
'is_multisite' => is_multisite(),
);
$resource = 'licenses/' . $key . '/check?' . build_query( $params );
$result = $this->request( $resource, null );
$result = $this->prepare_response_body( $result, true );
if ( is_wp_error( $result ) ) {
GFCommon::log_debug( __METHOD__ . '(): error getting site and license information. ' . $result->get_error_message() );
return $result;
}
$response = $result;
if ( rgar( $result, 'license' ) ) {
$response = rgar( $result, 'license' );
}
// Set the license object to the transient.
set_transient( 'rg_gforms_license', $response, DAY_IN_SECONDS );
return $response;
}
/**
* Get GF core and add-on family information.
*
* @since 2.5
*
* @return false|array
*/
public function get_plugins_info() {
$version_info = $this->get_version_info();
if ( empty( $version_info['offerings'] ) ) {
return false;
}
return $version_info['offerings'];
}
/**
* Get version information from the Gravity Manager API.
*
* @since 2.5
*
* @param false $cache
*
* @return array
*/
private function get_version_info( $cache = false ) {
$version_info = null;
if ( $cache ) {
$cached_info = get_option( 'gform_version_info' );
// Checking cache expiration
$cache_duration = DAY_IN_SECONDS; // 24 hours.
$cache_timestamp = $cached_info && isset( $cached_info['timestamp'] ) ? $cached_info['timestamp'] : 0;
// Is cache expired? If not, set $version_info to the cached data.
if ( $cache_timestamp + $cache_duration >= time() ) {
$version_info = $cached_info;
}
}
if ( is_wp_error( $version_info ) || isset( $version_info['headers'] ) ) {
// Legacy ( < 2.1.1.14 ) version info contained the whole raw response.
$version_info = null;
}
// If we reach this point with a $version_info array, it's from cache, and we can return it.
if ( $version_info ) {
return $version_info;
}
//Getting version number
$options = array(
'method' => 'POST',
'timeout' => 20,
);
$options['headers'] = array(
'Content-Type' => 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' ),
'User-Agent' => 'WordPress/' . get_bloginfo( 'version' ),
);
$options['body'] = GFCommon::get_remote_post_params();
$options['timeout'] = 15;
$nocache = $cache ? '' : 'nocache=1'; //disabling server side caching
// Check if API is currently blocked due to previous 500 errors.
$api_blocked = get_transient( 'gf_api_version_blocked' );
if ( $api_blocked ) {
GFCommon::log_debug( __METHOD__ . '(): API calls to version.php are temporarily blocked due to previous server errors.' );
// Use a WP_Error to indicate API is temporarily blocked
self::$raw_response = new WP_Error( 'api_blocked', 'API temporarily blocked due to previous server errors' );
} else {
// Store the raw_response for this page load. This will keep us from hitting the api multiple times per pageload.
if ( is_null( self::$raw_response ) ) {
self::$raw_response = GFCommon::post_to_manager( 'version.php', $nocache, $options );
}
}
$raw_response = self::$raw_response;;
$version_info = array(
'is_valid_key' => '1',
'version' => '',
'url' => '',
'is_error' => '1',
);
if ( is_wp_error( $raw_response ) || rgars( $raw_response, 'response/code' ) != 200 ) {
// Check if this is a 5xx server error and set a transient to block future requests for 30 minutes.
$response_code = rgars( $raw_response, 'response/code' );
if ( $response_code >= 500 && $response_code < 600 ) {
set_transient( 'gf_api_version_blocked', true, 30 * MINUTE_IN_SECONDS );
GFCommon::log_error( __METHOD__ . "(): {$response_code} error received from version.php API. Blocking further calls for 30 minutes." );
}
$version_info['timestamp'] = time();
return $version_info;
}
$decoded = json_decode( $raw_response['body'], true );
if ( empty( $decoded ) ) {
$version_info['timestamp'] = time();
return $version_info;
}
$decoded['timestamp'] = time();
// Caching response.
update_option( 'gform_version_info', $decoded, false ); //caching version info
return $decoded;
}
/**
* Update the usage data (call version.php in Gravity Manager). We will replace it once we have statistics API endpoints.
*
* @since 2.5
*/
public function update_site_data() {
// Whenever we update the plugins info, we call the versions.php to update usage data.
$options = array( 'method' => 'POST' );
$options['headers'] = array(
'Content-Type' => 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' ),
'User-Agent' => 'WordPress/' . get_bloginfo( 'version' ),
'Referer' => get_bloginfo( 'url' ),
);
$options['body'] = GFCommon::get_remote_post_params();
// Set the version to 3 which lightens the burden of version.php, it won't return anything to us anymore.
$options['body']['version'] = '3';
$options['timeout'] = 15;
$nocache = 'nocache=1'; //disabling server side caching
// Check if API is currently blocked due to previous 500 errors.
$api_blocked = get_transient( 'gf_api_version_blocked' );
if ( $api_blocked ) {
GFCommon::log_debug( __METHOD__ . '(): API calls to version.php are temporarily blocked due to previous server errors.' );
return;
}
$response = GFCommon::post_to_manager( 'version.php', $nocache, $options );
// Check if this is a 5xx server error
$response_code = rgars( $response, 'response/code' );
if ( $response_code >= 500 && $response_code < 600 ) {
set_transient( 'gf_api_version_blocked', true, 30 * MINUTE_IN_SECONDS );
GFCommon::log_error( __METHOD__ . "(): {$response_code} error received from version.php API. Blocking further calls for 30 minutes." );
}
}
public function send_email_to_hubspot( $email ) {
GFCommon::log_debug( __METHOD__ . '(): Sending installation wizard to hubspot.' );
$body = array(
'email' => $email,
);
$result = $this->request( 'emails/installation/add-to-list', $body, 'POST', array( 'headers' => $this->get_license_info_header( $site_secret ) ) );
$result = $this->prepare_response_body( $result, true );
if ( is_wp_error( $result ) ) {
GFCommon::log_debug( __METHOD__ . '(): error sending installation wizard to hubspot. ' . print_r( $result, true ) );
return $result;
}
return true;
}
// # HELPERS
/**
* @return false|mixed|void
*/
public function get_key() {
return GFCommon::get_key();
}
/**
* @param $site_key
* @param $site_secret
*
* @return string[]
*/
private function get_site_auth_header( $site_key, $site_secret ) {
$auth = base64_encode( "{$site_key}:{$site_secret}" );
return array( 'Authorization' => 'GravityAPI ' . $auth );
}
/**
* @param $site_secret
*
* @return string[]
*/
private function get_license_info_header( $site_secret ) {
$auth = base64_encode( "gravityforms.com:{$site_secret}" );
return array( 'Authorization' => 'GravityAPI ' . $auth );
}
/**
* @param $license_key_md5
*
* @return string[]
*/
private function get_license_auth_header( $license_key_md5 ) {
$auth = base64_encode( "license:{$license_key_md5}" );
return array( 'Authorization' => 'GravityAPI ' . $auth );
}
/**
* Prepare response body.
*
* @since unknown
* @since 2.5 Support a WP_Error being returned.
* @since 2.5 Allow results to be returned as array with second param.
*
* @param WP_Error|WP_REST_Response $raw_response The API response.
* @param bool $as_array Whether to return the response as an array or object.
*
* @return array|object|WP_Error
*/
public function prepare_response_body( $raw_response, $as_array = false ) {
if ( is_wp_error( $raw_response ) ) {
return $raw_response;
}
$response_body = json_decode( wp_remote_retrieve_body( $raw_response ), $as_array );
$response_code = wp_remote_retrieve_response_code( $raw_response );
$response_message = wp_remote_retrieve_response_message( $raw_response );
if ( $response_code > 200 ) {
// If a WP_Error was returned in the body.
if ( rgar( $response_body, 'code' ) ) {
// Restore the WP_Error.
$error = new WP_Error( $response_body['code'], $response_body['message'], $response_body['data'] );
} else {
$error_code = $response_code == 429 ? 'http_request_blocked' : 'http_request_failed';
$error = new WP_Error( $error_code, 'Error from server: ' . $response_message );
}
return $error;
}
return $response_body;
}
/**
* Purge the site credentials.
*
* @since unknown
* @since 2.5 Added the deletion of the gf_site_registered option.
*/
public function purge_site_credentials() {
delete_option( 'gf_site_key' );
delete_option( 'gf_site_secret' );
delete_option( 'gf_site_registered' );
}
/**
* Making API requests.
*
* @since unknown
* @since 2.5 Purge the registration data on site if certain errors matched.
*
* @param string $resource The API route.
* @param array $body The request body.
* @param string $method The method.
* @param array $options The options.
*
* @return array|WP_Error
*/
public function request( $resource, $body, $method = 'POST', $options = array() ) {
$body['timestamp'] = time();
// set default options
$options = wp_parse_args( $options, array(
'method' => $method,
'timeout' => 10,
'body' => in_array( $method, array( 'GET', 'DELETE' ) ) ? null : json_encode( $body ),
'headers' => array(),
'sslverify' => true,
) );
// set default header options
$options['headers'] = wp_parse_args( $options['headers'], array(
'Content-Type' => 'application/json; charset=' . get_option( 'blog_charset' ),
'User-Agent' => 'WordPress/' . get_bloginfo( 'version' ),
'Referer' => get_bloginfo( 'url' ),
) );
// WP docs say method should be uppercase
$options['method'] = strtoupper( $options['method'] );
$request_url = $this->get_gravity_api_url() . $resource;
return wp_remote_request( $request_url, $options );
}
/**
* @return false|mixed|void
*/
public function get_site_key() {
if ( defined( 'GRAVITY_API_SITE_KEY' ) ) {
return GRAVITY_API_SITE_KEY;
}
$site_key = get_option( 'gf_site_key' );
if ( empty( $site_key ) ) {
return false;
}
return $site_key;
}
/**
* @return false|mixed|void
*/
public function get_site_secret() {
if ( defined( 'GRAVITY_API_SITE_SECRET' ) ) {
return GRAVITY_API_SITE_SECRET;
}
$site_secret = get_option( 'gf_site_secret' );
if ( empty( $site_secret ) ) {
return false;
}
return $site_secret;
}
/**
* @return string
*/
public function get_gravity_api_url() {
return trailingslashit( GRAVITY_API_URL );
}
/**
* Check if the site has the gf_site_key and gf_site_secret options.
*
* @since unknown
*
* @return bool
*/
public function is_site_registered() {
return $this->get_site_key() && $this->get_site_secret();
}
/**
* Check if the site has the gf_site_key, gf_site_secret and also the gf_site_registered options.
*
* @since 2.5
*
* @return bool
*/
public function is_legacy_registration() {
return $this->is_site_registered() && ! get_option( 'gf_site_registered' );
}
}
function gapi() {
return Gravity_Api::get_instance();
}
gapi();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,483 @@
<?php
namespace Gravity_Forms\Gravity_Forms;
use GFCommon;
/**
* Allows to download translations from TranslationsPress
* This is a modified version of the library available at https://github.com/WP-Translations/t15s-registry
* This version aims to be compatible with PHP 5.2, and supports only plugins.
*
* @since 2.5
*/
class TranslationsPress_Updater {
const T15S_TRANSIENT_KEY = 't15s-registry-gforms';
const T15S_API_URL = 'https://packages.translationspress.com/rocketgenius/packages.json';
/**
* The plugin slug.
*
* @since 2.5
*
* @var string
*/
private $slug = '';
/**
* The locales installed during the current request.
*
* @since 2.5.6
*
* @var array
*/
private $installed = array();
/**
* Cached TranslationsPress data for all Rocketgenius plugins.
*
* @since 2.5.6
*
* @var null|object
*/
private static $all_translations;
/**
* The current instances of this class with the slugs as the keys.
*
* @since 2.5.6
*
* @var TranslationsPress_Updater[]
*/
private static $_instances = array();
/**
* Returns an instance of this class for the given slug.
*
* @since 2.5.6
*
* @param string $slug The plugin slug.
*
* @return TranslationsPress_Updater
*/
public static function get_instance( $slug ) {
if ( empty( self::$_instances[ $slug ] ) ) {
self::$_instances[ $slug ] = new self( $slug );
}
return self::$_instances[ $slug ];
}
/**
* Adds a new project to load translations for.
*
* @since 2.5
*
* @param string $slug The plugin slug.
* @param string $deprecated Not used. Previously, the locale to be installed.
*/
public function __construct( $slug, $deprecated = '' ) {
$this->slug = $slug;
if ( 'gravityforms' === $slug ) {
// Translations data for all Rocketgenius plugins is stored together so we only need to add this hook once.
add_action( 'delete_site_transient_update_plugins', array( __CLASS__, 'refresh_all_translations' ) );
}
add_action( 'gform_post_install', array( $this, 'install' ), 10, 0 );
add_action( 'gform_post_upgrade', array( $this, 'install' ), 10, 0 );
add_action( 'upgrader_process_complete', array( $this, 'upgrader_process_complete' ), 10, 2 );
add_filter( 'translations_api', array( $this, 'translations_api' ), 10, 3 );
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'site_transient_update_plugins' ) );
}
/**
* Short-circuits translations API requests for private projects.
*
* @since 2.5
*
* @param bool|array $result The result object. Default false.
* @param string $requested_type The type of translations being requested.
* @param object $args Translation API arguments.
*
* @return bool|array
*/
public function translations_api( $result, $requested_type, $args ) {
if ( 'plugins' !== $requested_type || $this->slug !== $args['slug'] ) {
return $result;
}
return $this->get_plugin_translations();
}
/**
* Filters the translations transients to include the current plugin.
*
* @see wp_get_translation_updates()
*
* @since 2.5
*
* @param mixed $value The transient value.
*
* @return object
*/
public function site_transient_update_plugins( $value ) {
if ( ! $value ) {
$value = new \stdClass();
}
if ( ! isset( $value->translations ) ) {
$value->translations = array();
}
$translations = $this->get_plugin_translations();
if ( empty( $translations['translations'] ) ) {
return $value;
}
foreach ( $translations['translations'] as $translation ) {
if ( ! $this->should_install( $translation ) ) {
continue;
}
$translation['type'] = 'plugin';
$translation['slug'] = $this->slug;
$value->translations[] = $translation;
}
return $value;
}
/**
* Gets the TranslationsPress data for the current plugin.
*
* @since 2.5
*
* @return array
*/
private function get_plugin_translations() {
self::set_all_translations();
return (array) rgar( self::$all_translations->projects, $this->slug );
}
/**
* Refreshes the cached TranslationsPress data, if expired.
*
* @since 2.5.6
*/
public static function refresh_all_translations() {
static $done;
if ( $done ) {
return;
}
self::$all_translations = null;
self::set_all_translations();
$done = true;
}
/**
* Determines if the cached TranslationsPress data needs refreshing.
*
* @since 2.5.6
*
* @return bool
*/
private static function is_transient_expired() {
$cache_lifespan = 12 * HOUR_IN_SECONDS;
return ! isset( self::$all_translations->_last_checked ) || ( time() - self::$all_translations->_last_checked ) > $cache_lifespan;
}
/**
* Gets the translations data from the TranslationsPress API.
*
* @since 2.5.6
*
* @return array
*/
private static function get_remote_translations_data() {
$result = json_decode( wp_remote_retrieve_body( wp_remote_get( self::T15S_API_URL, array( 'timeout' => 3 ) ) ), true );
return is_array( $result ) ? $result : array();
}
/**
* Caches the TranslationsPress data, if not already cached.
*
* @since 2.5.6
*/
private static function set_all_translations() {
if ( is_object( self::$all_translations ) ) {
return;
}
self::$all_translations = get_site_transient( self::T15S_TRANSIENT_KEY );
if ( is_object( self::$all_translations ) && ! self::is_transient_expired() ) {
return;
}
self::$all_translations = new \stdClass();
self::$all_translations->projects = self::get_remote_translations_data();
self::$all_translations->_last_checked = time();
set_site_transient( self::T15S_TRANSIENT_KEY, self::$all_translations );
}
/**
* Gets the translations for a given project.
*
* @since 2.5
*
* @deprecated 2.5.6
*
* @param string $url Full GlotPress API URL for the project.
*
* @return array Translation data.
*/
public static function get_translations( $url ) {
_deprecated_function( __METHOD__, '2.5.6', '\Gravity_Forms\Gravity_Forms\TranslationsPress_Updater::get_plugin_translations' );
self::set_all_translations();
return self::$all_translations->projects;
}
/**
* Downloads and installs the translations for the specified plugin.
*
* @since 2.5
*
* @param string $slug The plugin slug.
* @param string $locale The locale when the site locale is changed or an empty string to install all the user available locales.
*/
public static function download_package( $slug, $locale = '' ) {
self::get_instance( $slug )->install( $locale );
}
/**
* Triggers translation installation, if required.
*
* @since 2.5.6
*
* @param string $locale The locale when the site locale is changed or an empty string to install all the user available locales.
*/
public function install( $locale = '' ) {
if ( $locale && in_array( $locale, $this->installed ) ) {
return;
}
$translations = $this->get_plugin_translations();
if ( empty( $translations['translations'] ) ) {
GFCommon::log_error( __METHOD__ . sprintf( '(): Aborting; No translations list for %s.', $this->slug ) );
return;
}
foreach ( $translations['translations'] as $translation ) {
if ( ! $this->should_install( $translation, $locale ) ) {
continue;
}
$this->install_translation( $translation );
if ( $locale ) {
return;
}
}
}
/**
* Downloads and installs the given translation.
*
* @since 2.5.6
*
* @param array $translation The translation data.
*/
private function install_translation( $translation ) {
global $wp_filesystem;
if ( ! $wp_filesystem ) {
require_once ABSPATH . '/wp-admin/includes/admin.php';
// Same approach as in WP Core: https://github.com/WordPress/wordpress-develop/blob/6.2/src/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php#L853-L869.
ob_start();
$filesystem_credentials_available = request_filesystem_credentials( self_admin_url() );
ob_end_clean();
if ( ! $filesystem_credentials_available ) {
GFCommon::log_error( __METHOD__ . '(): Aborting; filesystem credentials required.' );
return;
}
if ( ! \WP_Filesystem() ) {
GFCommon::log_error( __METHOD__ . '(): Aborting; unable to init WP_Filesystem.' );
return;
}
}
$lang_dir = $this->get_path();
if ( ! $wp_filesystem->is_dir( $lang_dir ) ) {
$wp_filesystem->mkdir( $lang_dir, FS_CHMOD_DIR );
}
GFCommon::log_debug( __METHOD__ . '(): Downloading: ' . $translation['package'] );
$temp_file = download_url( $translation['package'] );
if ( is_wp_error( $temp_file ) ) {
GFCommon::log_error( __METHOD__ . '(): Error downloading package. Code: ' . $temp_file->get_error_code() . '; Message: ' . $temp_file->get_error_message() );
return;
}
$zip_path = $lang_dir . $this->slug . '-' . $translation['language'] . '.zip';
$copy_result = $wp_filesystem->copy( $temp_file, $zip_path, true, FS_CHMOD_FILE );
$wp_filesystem->delete( $temp_file );
if ( ! $copy_result ) {
GFCommon::log_error( __METHOD__ . '(): Unable to move package to: ' . $lang_dir );
return;
}
$result = unzip_file( $zip_path, $lang_dir );
@unlink( $zip_path );
if ( is_wp_error( $result ) ) {
GFCommon::log_error( __METHOD__ . '(): Error extracting package. Code: ' . $result->get_error_code() . '; Message: ' . $result->get_error_message() );
return;
}
GFCommon::log_debug( __METHOD__ . sprintf( '(): Installed %s translation for %s.', $translation['language'], $this->slug ) );
$this->installed[] = $translation['language'];
}
/**
* Logs which locales WordPress installs translations for.
*
* @since 2.5.6
*
* @param object $upgrader_object WP_Upgrader Instance.
* @param array $hook_extra Item update data.
*/
public function upgrader_process_complete( $upgrader_object, $hook_extra ) {
if ( rgar( $hook_extra, 'type' ) !== 'translation' || empty( $hook_extra['translations'] ) || empty( $upgrader_object->result ) || is_wp_error( $upgrader_object->result ) ) {
return;
}
$locales = array();
foreach ( $hook_extra['translations'] as $translation ) {
if ( rgar( $translation, 'type' ) !== 'plugin' || rgar( $translation, 'slug' ) !== $this->slug ) {
continue;
}
$locales[] = $translation['language'];
}
if ( empty( $locales ) ) {
return;
}
$this->installed = $locales;
GFCommon::log_debug( __METHOD__ . sprintf( '(): WordPress installed %s translation(s) for %s.', implode( ', ', $locales ), $this->slug ) );
}
/**
* Returns an array of locales the site has installed.
*
* @since 2.5.6
*
* @return array
*/
private function get_available_languages() {
static $languages = array();
if ( empty( $languages ) ) {
$languages = get_available_languages();
}
return $languages;
}
/**
* Returns the header data from the installed translations for the current plugin.
*
* @since 2.5.6
*
* @return array
*/
private function get_installed_translations_data() {
static $data = array();
if ( isset( $data[ $this->slug ] ) ) {
return $data[ $this->slug ];
}
$data[ $this->slug ] = array();
$translations = GFCommon::get_installed_translations( $this->slug, true );
foreach ( $translations as $locale => $mo_file ) {
$po_file = str_replace( '.mo', '.po', $mo_file );
if ( ! file_exists( $po_file ) ) {
continue;
}
$data[ $this->slug ][ $locale ] = wp_get_pomo_file_data( $po_file );
}
return $data[ $this->slug ];
}
/**
* Returns the path to where plugin translations are stored.
*
* @since 2.5.6
*
* @return string
*/
private function get_path() {
return WP_LANG_DIR . '/plugins/';
}
/**
* Determines if a translation should be installed.
*
* @since 2.5.6
*
* @param array $translation The translation data.
* @param string $locale The locale when the site locale is changed or an empty string to check all the user available locales.
*
* @return bool
*/
private function should_install( $translation, $locale = '' ) {
if ( ( $locale && $locale !== $translation['language'] ) || ! in_array( $translation['language'], $this->get_available_languages() ) ) {
return false;
}
if ( empty( $translation['updated'] ) ) {
return true;
}
$installed = $this->get_installed_translations_data();
if ( ! isset( $installed[ $translation['language'] ] ) ) {
return true;
}
$local = date_create( $installed[ $translation['language'] ]['PO-Revision-Date'] );
$remote = date_create( $translation['updated'] );
return $remote > $local;
}
}
TranslationsPress_Updater::get_instance( 'gravityforms' );
@@ -0,0 +1,95 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Collection;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Configurator;
/**
* Config items for app registration, including helper methods.
*
* @since 2.7.1
*/
class GF_App_Config extends GF_Config {
/**
* The name of the app, in slug form.
*
* @var string $app_name
*/
private $app_name;
/**
* The condition for enqueuing the app data.
*
* @var bool|callable $display_condition
*/
private $display_condition;
/**
* The CSS assets to enqueue.
*
* @var array $css_asset
*/
private $css_asset;
/**
* The relative path to the chunk in JS.
*
* @var string $chunk
*/
private $chunk;
/**
* The root element to use to load the app.
*
* @var string $root_element
*/
private $root_element;
/**
* Set the data for this app config.
*
* @since 2.7.1
*
* @param $data
*/
public function set_data( $data ) {
$this->name = $data['object_name'];
$this->script_to_localize = $data['script_name'];
$this->app_name = $data['app_name'];
$this->display_condition = $data['enqueue'];
$this->chunk = $data['chunk'];
$this->root_element = $data['root_element'];
}
/**
* Whether we should enqueue this data.
*
* @since 2.6
*
* @return bool|mixed
*/
public function should_enqueue() {
return is_admin();
}
/**
* Config data.
*
* @return array[]
*/
public function data() {
return array(
'apps' => array(
$this->app_name => array(
'should_display' => is_callable( $this->display_condition ) ? call_user_func( $this->display_condition ) : $this->display_condition,
'chunk_path' => $this->chunk,
'root_element' => $this->root_element,
),
),
);
}
}
@@ -0,0 +1,224 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use GFCommon;
/**
* Collection to hold GF_Config items and provide their structured data when needed.
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Config
*/
class GF_Config_Collection {
/**
* @var GF_Config[] $configs
*/
private $configs = array();
/**
* Add a config to the collection.
*
* @param GF_Config $config
*/
public function add_config( GF_Config $config ) {
$this->configs[] = $config;
}
/**
* Handle outputting the config data.
*
* If $localize is true, data is actually localized via `wp_localize_script`, otherwise
* data is simply returned as an array.
*
* @since 2.6
*
* @param bool $localize Whether to localize the data, or simply return it.
*
* @return array
*/
public function handle( $localize = true, $args = null ) {
$scripts = $this->get_configs_by_script();
$data_to_localize = array();
foreach ( $scripts as $script => $items ) {
$item_data = $this->localize_data_for_script( $script, $items, $localize, $args );
$data_to_localize = array_merge( $data_to_localize, $item_data );
}
return $data_to_localize;
}
public function handle_ajax() {
// Check nonce.
$nonce_result = check_ajax_referer( 'gform_config_ajax', 'gform_ajax_nonce', false );
if ( ! $nonce_result ) {
GFCommon::send_json_error( esc_html__( 'Unable to verify nonce. Please refresh the page and try again.', 'gravityforms' ) );
}
$args = json_decode( rgpost( 'args' ), true );
$config_path = wp_strip_all_tags( rgpost( 'config_path' ) );
$query_string = rgpost( 'query_string' );
// Making the query string available for use with form filters.
if ( ! empty( $query_string ) && is_string( $query_string ) ) {
parse_str( $query_string, $query );
unset( $query['gf_page'] ); // Removing so it doesn't conflict with gf_ajax_page=preview.
$_GET = array_merge( $_GET, $query );
}
$configs = $this->get_configs_by_path( $config_path, $args );
if ( ! $configs ) {
GFCommon::send_json_error( sprintf( esc_html__( 'Unable to find config: %s', 'gravityforms' ), esc_html( $config_path ) ) );
}
$data = $this->get_merged_data_for_object( $configs, $args );
GFCommon::send_json_success( $data );
}
/**
* Localize the data for the given script.
*
* @since 2.6
*
* @param string $script The script to localize.
* @param array $items The associative array of configs to process for this script.
* @param bool $localize Whether to actually localize the data, or simply return it.
* @param array $args The arguments to pass to the config objects.
*
* @return array Returns the localized data.
*/
private function localize_data_for_script( $script, $items, $localize = true, $args = null ) {
$data = array();
foreach ( $items as $name => $configs ) {
$localized_data = $this->get_merged_data_for_object( $configs, $args );
/**
* Allows users to filter the data localized for a given script/resource.
*
* @since 2.6
*
* @param array $localized_data The current localize data
* @param string $script The script being localized
* @param array $configs An array of $configs being applied to this script
*
* @return array
*/
$localized_data = apply_filters( 'gform_localized_script_data_' . $name, $localized_data, $script, $configs );
$data[ $name ] = $localized_data;
if ( $localize ) {
wp_localize_script( $script, $name, $localized_data );
}
}
return $data;
}
/**
* Get the merged data object for the applicable configs. Will process each config by its
* $priority property, overriding or merging values as needed.
*
* @since 2.6
*
* @param GF_Config[] $configs
*/
private function get_merged_data_for_object( $configs, $args ) {
// Squash warnings for PHP < 7.0 when running tests.
@usort( $configs, array( $this, 'sort_by_priority' ) );
$data = array();
foreach ( $configs as $config ) {
$config->set_args( $args );
// Config is set to overwrite data - simply return its value without attempting to merge.
if ( $config->should_overwrite() ) {
$data = $config->get_data();
continue;
}
// Config should be merged - loop through each key and attempt to recursively merge the values.
foreach ( $config->get_data() as $key => $value ) {
$existing = isset( $data[ $key ] ) ? $data[ $key ] : null;
if ( is_null( $existing ) || ! is_array( $existing ) || ! is_array( $value ) ) {
$data[ $key ] = $value;
continue;
}
$data[ $key ] = array_merge_recursive( $existing, $value );
}
}
return $data;
}
/**
* Get the appropriate configs, organized by the script they belong to.
*
* @since 2.6
*
* @return array
*/
private function get_configs_by_script() {
$data_to_localize = array();
foreach ( $this->configs as $config ) {
if ( ( ! defined( 'GFORMS_DOING_MOCK' ) || ! GFORMS_DOING_MOCK ) && ! $config->should_enqueue() ) {
continue;
}
$data_to_localize[ $config->script_to_localize() ][ $config->name() ][] = $config;
}
return $data_to_localize;
}
/**
* Gets a list of configs that match the specified config path.
*
* @since 2.9.0
*
* @param string $config_path The path of the config to be returned.
* @param array $args The arguments to pass to the config objects.
*
* @return array Returns an array of configs that are associated with the specified config path.
*/
private function get_configs_by_path( $config_path, $args )
{
$configs = array();
foreach ( $this->configs as $config ) {
if ( $config->enable_ajax( $config_path, $args ) ) {
$configs[] = $config;
}
}
return $configs;
}
/**
* usort() callback to sort the configs by their $priority.
*
* @param GF_Config $a
* @param GF_Config $b
*
* @return int
*/
public function sort_by_priority( GF_Config $a, GF_Config $b ) {
if ( $a->priority() === $b->priority() ) {
return 0;
}
return $a->priority() < $b->priority() ? - 1 : 1;
}
}
@@ -0,0 +1,77 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config;
/**
* Parses a given data array to return either Live or Mock values, depending on the
* environment and context.
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Config
*/
class GF_Config_Data_Parser {
/**
* Parse the given $data array and get the correct values for the context.
*
* @since 2.6
*
* @param $data
*
* @return array
*/
public function parse( $data ) {
$return = array();
foreach( $data as $key => $value ) {
$return[ $key ] = $this->get_correct_value( $value );
}
return $return;
}
/**
* Loop through each array key and get the correct value. Is called recursively for
* nested arrays.
*
* @since 2.6
*
* @param mixed $value
*
* @return array|mixed
*/
private function get_correct_value( $value ) {
// Value isn't array - we've reached the final level for this branch.
if ( ! is_array( $value ) ) {
return $value;
}
// Value is an array with our defined value and default keys. Return either live or mock data.
if ( array_key_exists( 'default', $value ) && array_key_exists( 'value', $value ) ) {
return $this->is_mock() ? $value['default'] : $value['value'];
}
$data = array();
// Value is an array - recursively call this method to dig into each level and return the correct value.
foreach( $value as $key => $value ) {
$data[ $key ] = $this->get_correct_value( $value );
}
return $data;
}
/**
* Determine whether the current environmental context is a Mock context.
*
* @since 2.6
*
* @return bool
*/
private function is_mock() {
return defined( 'GFORMS_DOING_MOCK' ) && GFORMS_DOING_MOCK;
}
}
@@ -0,0 +1,245 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config;
use Gravity_Forms\Gravity_Forms\Config\Items\GF_Config_Admin;
use Gravity_Forms\Gravity_Forms\Config\Items\GF_Config_Block_Editor;
use Gravity_Forms\Gravity_Forms\Config\Items\GF_Config_Global;
use Gravity_Forms\Gravity_Forms\Config\Items\GF_Config_I18n;
use Gravity_Forms\Gravity_Forms\Config\Items\GF_Config_Legacy_Check;
use Gravity_Forms\Gravity_Forms\Config\Items\GF_Config_Legacy_Check_Multi;
use Gravity_Forms\Gravity_Forms\Config\Items\GF_Config_Multifile;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
/**
* Class GF_Config_Service_Provider
*
* Service provider for the Config Service.
*
* @package Gravity_Forms\Gravity_Forms\Config
*/
class GF_Config_Service_Provider extends GF_Service_Provider {
// Organizational services
const CONFIG_COLLECTION = 'config_collection';
const DATA_PARSER = 'data_parser';
// Config services
const I18N_CONFIG = 'i18n_config';
const ADMIN_CONFIG = 'admin_config';
const LEGACY_CONFIG = 'legacy_config';
const LEGACY_MULTI_CONFIG = 'legacy_multi_config';
const MULTIFILE_CONFIG = 'multifile_config';
const GLOBAL_CONFIG = 'global_config';
/**
* Array mapping config class names to their container ID.
*
* @since 2.6
*
* @var string[]
*/
protected $configs = array(
self::I18N_CONFIG => GF_Config_I18n::class,
self::ADMIN_CONFIG => GF_Config_Admin::class,
self::LEGACY_CONFIG => GF_Config_Legacy_Check::class,
self::LEGACY_MULTI_CONFIG => GF_Config_Legacy_Check_Multi::class,
self::MULTIFILE_CONFIG => GF_Config_Multifile::class,
);
/**
* Register services to the container.
*
* @since 2.6
*
* @param GF_Service_Container $container
*/
public function register( GF_Service_Container $container ) {
// Include required files.
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-config-collection.php' );
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-config.php' );
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-config-data-parser.php' );
require_once( plugin_dir_path( __FILE__ ) . 'class-gf-app-config.php' );
require_once( plugin_dir_path( __FILE__ ) . 'items/class-gf-config-global.php' );
// Add to container
$container->add( self::CONFIG_COLLECTION, function () {
return new GF_Config_Collection();
} );
$container->add( self::DATA_PARSER, function () {
return new GF_Config_Data_Parser();
} );
$container->add( self::GLOBAL_CONFIG, function () {
return new GF_Config_Global();
} );
// Add configs to container.
$this->register_config_items( $container );
$this->register_configs_to_collection( $container );
}
/**
* Whether the config has been localized.
*
* @since 2.9.0
*
* @var bool
*/
private static $is_localized = false;
/**
* Initiailize any actions or hooks.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
public function init( GF_Service_Container $container ) {
// Need to pass $this to callbacks; save as variable.
$self = $this;
add_action( 'wp_enqueue_scripts', function () use ( $container ) {
// Only localize during wp_enqueue_scripts if none of the other more specific events have been fired.
if ( ! self::$is_localized ) {
$container->get( self::CONFIG_COLLECTION )->handle();
}
}, 9999 );
add_action( 'admin_enqueue_scripts', function () use ( $container ) {
// Only localize during admin_enqueue_scripts if none of the other more specific events have been fired.
if ( ! self::$is_localized ) {
$container->get( self::CONFIG_COLLECTION )->handle();
}
}, 9999 );
add_action( 'gform_output_config', function ( $form_ids = null ) use ( $container ) {
$container->get( self::CONFIG_COLLECTION )->handle( true, $form_ids );
self::$is_localized = true;
} );
add_action( 'gform_post_enqueue_scripts', function ( $found_forms, $found_blocks, $post ) use ( $container ) {
$form_ids = array_column( $found_forms, 'formId' );
$container->get( self::CONFIG_COLLECTION )->handle( true, array( 'form_ids' => $form_ids ) );
self::$is_localized = true;
}, 10, 3);
add_action( 'gform_preview_init', function ( $form_id ) use ( $container ) {
$form_ids = array( $form_id );
$container->get( self::CONFIG_COLLECTION )->handle( true, array( 'form_ids' => $form_ids ) );
self::$is_localized = true;
}, 10, 2);
add_action('wp_ajax_gform_get_config', function () use ( $container ) {
$container->get( self::CONFIG_COLLECTION )->handle_ajax();
});
add_action('wp_ajax_nopriv_gform_get_config', function () use ( $container ) {
$container->get( self::CONFIG_COLLECTION )->handle_ajax();
});
add_action( 'rest_api_init', function () use ( $container, $self ) {
// check if we are in a test environment, if so register the mock data endpoint.
if ( defined( 'GF_SCRIPT_DEBUG' ) && GF_SCRIPT_DEBUG ) {
register_rest_route( 'gravityforms/v2', '/tests/mock-data', array( // nosemgrep audit.php.wp.security.rest-route.permission-callback.return-true
'methods' => 'GET',
'callback' => array( $self, 'config_mocks_endpoint' ),
'permission_callback' => function () {
return true;
},
) );
}
} );
// Add global config data to admin and theme.
add_filter( 'gform_localized_script_data_gform_admin_config', function ( $data ) use ( $self ) {
return $self->add_global_config_data( $data );
} );
add_filter( 'gform_localized_script_data_gform_theme_config', function ( $data ) use ( $self ) {
return $self->add_global_config_data( $data );
} );
}
/**
* For each config defined in $configs, instantiate and add to container.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
private function register_config_items( GF_Service_Container $container ) {
require_once( plugin_dir_path( __FILE__ ) . '/items/class-gf-config-i18n.php' );
require_once( plugin_dir_path( __FILE__ ) . '/items/class-gf-config-admin.php' );
require_once( plugin_dir_path( __FILE__ ) . '/items/class-gf-config-legacy-check.php' );
require_once( plugin_dir_path( __FILE__ ) . '/items/class-gf-config-legacy-check-multi.php' );
require_once( plugin_dir_path( __FILE__ ) . '/items/class-gf-config-multifile.php' );
$parser = $container->get( self::DATA_PARSER );
foreach ( $this->configs as $name => $class ) {
$container->add( $name, function () use ( $class, $parser ) {
return new $class( $parser );
} );
}
}
/**
* Register each config defined in $configs to the GF_Config_Collection.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
public function register_configs_to_collection( GF_Service_Container $container ) {
$collection = $container->get( self::CONFIG_COLLECTION );
foreach ( $this->configs as $name => $config ) {
$config_class = $container->get( $name );
$collection->add_config( $config_class );
}
}
/**
* Callback for the Config Mocks REST endpoint.
*
* @since 2.6
*
* @return array
*/
public function config_mocks_endpoint() {
define( 'GFORMS_DOING_MOCK', true );
$container = \GFForms::get_service_container();
$data = $container->get( self::CONFIG_COLLECTION )->handle( false );
return $data;
}
/**
* Add global data to both admin and theme configs so that it is available everywhere
* within the system.
*
* @since 2.7
*
* @param $data
*
* @return array
*/
public function add_global_config_data( $data ) {
$container = \GFForms::get_service_container();
$global = $container->get( self::GLOBAL_CONFIG )->data();
return array_merge( $data, $global );
}
}
@@ -0,0 +1,297 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config;
/**
* Base class for providing advanced functionality when localizing Config Data
* for usage in Javascript.
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Config
*/
abstract class GF_Config {
/**
* The Data Parser
*
* @since 2.6
*
* @var GF_Config_Data_Parser
*/
protected $parser;
/**
* The data for this config object.
*
* @since 2.6
*
* @var array
*/
protected $data;
/**
* The object name for this config.
*
* @since 2.6
*
* @var string
*/
protected $name;
/**
* The ID of the script to localize the data to.
*
* @since 2.6
*
* @var string
*/
protected $script_to_localize;
/**
* The priority of this config - can be used to control the order in
* which configs are processed in the Collection.
*
* @since 2.6
*
* @var int
*/
protected $priority = 0;
/**
* Whether the config should enqueue it's data. Can also be handled by overriding the
* ::should_enqueue() method.
*
* @since 2.6
*
* @var bool
*/
protected $should_enqueue = true;
/**
* Whether this config should overwrite previous values in the object.
*
* If set to "true", the object will be overwritten by the values provided here.
* If set to "false", the object will have its values merged with those defined here, recursively.
*
* @since 2.6
*
* @var bool
*/
protected $overwrite = false;
/**
* An args array. Use in the data() method to retrieve data specific to the specified args. For example, form specific configs will have an array of form ids specified in args.
*
* @var array
*/
protected $args = array();
/**
* Constructor
*
* @param GF_Config_Data_Parser $parser
*/
public function __construct( GF_Config_Data_Parser $parser ) {
$this->parser = $parser;
}
/**
* Method to handle defining the data array for this config.
*
* @since 2.6
*
* @return array
*/
abstract protected function data();
/**
* Override this method to add enable ajax loading for a specific config path.
* To enable loading data() via ajax, check if $config_path is one of the paths that are provided by the config. If so, return true.
*
* Example:
* public function enable_ajax( $config_path, $args ) {
* return str_starts_with( $config_path, 'gform_theme_config/common/form/product_meta' );
* }
*
* @since 2.9.0
*
* @param string $config_path The full path to the config item when stored in the browser's window object, for example: "gform_theme_config/common/form/product_meta"
* @param array $args The args used to load the config data. This will be empty for generic config items. For form specific items will be in the format: array( 'form_ids' => array(123,222) ).
*
* @return bool Return true to load the config data associated with the provided $config_path. Return false otherwise.
*/
public function enable_ajax( $config_path, $args ) {
return false;
}
/**
* Determine if the config should enqueue its data. If should_enqueue() is a method,
* call it and return the result. If not, simply return the (boolean) value of the property.
*
* @since 2.6
*
* @return bool
*/
public function should_enqueue() {
if ( is_callable( $this->should_enqueue ) ) {
return call_user_func( $this->should_enqueue );
}
return $this->should_enqueue;
}
/**
* Get the data for the config, passing it through a filter.
*
* @since 2.6
*
* @return array
*/
public function get_data() {
if ( ( ! defined( 'GFORMS_DOING_MOCK' ) || ! GFORMS_DOING_MOCK ) && ! $this->should_enqueue() ) {
return false;
}
/**
* Allows developers to modify the raw config data being sent to the Config Parser. Useful for
* adding in custom default/mock values for a given entry in the data, as well as modifying
* things like callbacks for dynamic data before it's parsed and localized.
*
* @since 2.6
*
* @param array $data
* @param string $script_to_localize
*
* @return array
*/
$data = apply_filters( 'gform_config_data_' . $this->name(), $this->data(), $this->script_to_localize() );
return $this->parser->parse( $data );
}
/**
* Get the name of the config's object.
*
* @since 2.6
*
* @return string
*/
public function name() {
return $this->name;
}
/**
* Get the $priority for the config.
*
* @since 2.6
*
* @return int
*/
public function priority() {
return $this->priority;
}
/**
* Get the script to localize.
*
* @since 2.6
*
* @return string
*/
public function script_to_localize() {
return $this->script_to_localize;
}
/**
* Get whether the config should override previous values.
*
* @since 2.6
*
* @return bool
*/
public function should_overwrite() {
return $this->overwrite;
}
/**
* Sets the $form_ids arrays.
*
* @since 2.9.0
*
* @param array $args Args array to be set
*
* @return void
*/
public function set_args( $args ) {
$this->args = $args;
}
/**
* Returns a sanitized array of form ids from the args.
*
* @since 2.9.31
*
* @return array
*/
public function get_form_ids_arg() {
$form_ids = rgar( $this->args, 'form_ids' );
if ( empty( $form_ids ) || ! is_array( $form_ids ) ) {
return array();
}
return array_filter( array_map( 'absint', $form_ids ) );
}
/**
* Validates the config data against a hash to ensure it has not been tampered with.
* This method is called via AJAX, initiated by the gform.config.isValid() JS method.
*
* @since 2.9.0
*
* @return void
*/
public static function validate_ajax() {
// Check nonce
$nonce_result = check_ajax_referer( 'gform_config_ajax', 'gform_ajax_nonce', false );
if ( ! $nonce_result ) {
wp_send_json_error( esc_html__( 'Unable to verify nonce. Please refresh the page and try again.', 'gravityforms' ) );
}
$config = json_decode( rgpost( 'config' ), true );
$hash = rgar( $config, 'hash' );
if ( ! $hash ) {
wp_send_json_error( esc_html__( 'Invalid config.', 'gravityforms' ) );
}
// Remove hash from config before validating.
unset( $config['hash'] );
// Compare hash to config.
if ( $hash !== self::hash( $config ) ) {
wp_send_json_error( esc_html__( 'Config validation failed. Hash does not match', 'gravityforms' ) );
}
// Send success response.
wp_send_json_success();
}
/**
* Hashes the config data.
*
* @since 2.9.0
*
* @param array $config
*
* @return string Returns the hash of the config data.
*/
public static function hash( $config ) {
return wp_hash( json_encode( $config ) );
}
}
// AJAX hash validation for config data.
add_action('wp_ajax_gform_validate_config', array( 'Gravity_Forms\Gravity_Forms\Config\GF_Config', 'validate_ajax' ) );
add_action('wp_ajax_nopriv_gform_validate_config', array( 'Gravity_Forms\Gravity_Forms\Config\GF_Config', 'validate_ajax' ) );
@@ -0,0 +1,188 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config\Items;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Collection;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Configurator;
/**
* Config items for Admin I18N
*
* @since 2.6
*/
class GF_Config_Admin extends GF_Config {
protected $name = 'gform_admin_config';
protected $script_to_localize = 'gform_gravityforms_admin_vendors';
/**
* Whether we should enqueue this data.
*
* @since 2.6
*
* @return bool|mixed
*/
public function should_enqueue() {
return is_admin();
}
/**
* Config data.
*
* @return array[]
*/
public function data() {
$data = array(
'data' => array(
'is_block_editor' => \GFCommon::is_block_editor_page(),
'gf_page' => \GFForms::get_page(),
),
'i18n' => array(
'form_admin' => array(
'toggle_feed_inactive' => esc_html__( 'Inactive', 'gravityforms' ),
'toggle_feed_active' => esc_html__( 'Active', 'gravityforms' ),
),
'shortcode_ui' => array(
'edit_form' => esc_html__( 'Edit Form', 'gravityforms' ),
'insert_form' => esc_html__( 'Insert Form', 'gravityforms' ),
),
'dialog' => array(
'cancel' => esc_html__( 'Cancel', 'gravityforms' ),
'close' => esc_html__( 'Close', 'gravityforms' ),
'ok' => esc_html__( 'OK', 'gravityforms' ),
),
),
'bulk_actions' => $this->get_bulk_action_config(),
);
$data = $this->add_entry_list_data( $data );
return $data;
}
private function get_bulk_action_config() {
return array(
'nonce' => wp_create_nonce( 'gf_bulk_action' ),
'threshold' => \Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints\GF_Bulk_Action_Endpoint_Start::get_background_threshold(),
'backgroundActions' => \Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints\GF_Bulk_Action_Endpoint_Start::BACKGROUND_ACTIONS,
'pollInterval' => 1500,
'labels' => array(
'delete' => array(
'title' => esc_html__( 'Bulk Delete', 'gravityforms' ),
'starting' => esc_html__( 'Starting bulk delete...', 'gravityforms' ),
'progress' => esc_html__( 'Deleting Gravity Forms entries...', 'gravityforms' ),
'complete' => esc_html__( 'Completed deleting entries!', 'gravityforms' ),
'error' => esc_html__( 'Error starting bulk delete.', 'gravityforms' ),
),
'trash' => array(
'title' => esc_html__( 'Bulk Trash', 'gravityforms' ),
'starting' => esc_html__( 'Starting bulk trash...', 'gravityforms' ),
'progress' => esc_html__( 'Trashing Gravity Forms entries...', 'gravityforms' ),
'complete' => esc_html__( 'Completed moving entries to trash!', 'gravityforms' ),
'error' => esc_html__( 'Error starting bulk trash.', 'gravityforms' ),
),
'spam' => array(
'title' => esc_html__( 'Bulk Mark as Spam', 'gravityforms' ),
'starting' => esc_html__( 'Starting bulk spam marking...', 'gravityforms' ),
'progress' => esc_html__( 'Marking Gravity Forms entries as spam...', 'gravityforms' ),
'complete' => esc_html__( 'Completed marking entries as spam!', 'gravityforms' ),
'error' => esc_html__( 'Error starting bulk spam.', 'gravityforms' ),
),
'restore' => array(
'title' => esc_html__( 'Bulk Restore', 'gravityforms' ),
'starting' => esc_html__( 'Starting bulk restore...', 'gravityforms' ),
'progress' => esc_html__( 'Restoring Gravity Forms entries...', 'gravityforms' ),
'complete' => esc_html__( 'Completed restoring entries!', 'gravityforms' ),
'error' => esc_html__( 'Error starting bulk restore.', 'gravityforms' ),
),
'unspam' => array(
'title' => esc_html__( 'Bulk Unmark as Spam', 'gravityforms' ),
'starting' => esc_html__( 'Starting bulk unspam...', 'gravityforms' ),
'progress' => esc_html__( 'Restoring Gravity Forms entries from spam...', 'gravityforms' ),
'complete' => esc_html__( 'Completed unmarking entries as spam!', 'gravityforms' ),
'error' => esc_html__( 'Error starting bulk unspam.', 'gravityforms' ),
),
'delete_entries' => array(
'title' => esc_html__( 'Bulk Delete Entries', 'gravityforms' ),
'starting' => esc_html__( 'Starting bulk entry deletion...', 'gravityforms' ),
'progress' => esc_html__( 'Deleting Gravity Forms entries...', 'gravityforms' ),
'complete' => esc_html__( 'Completed deleting entries!', 'gravityforms' ),
'error' => esc_html__( 'Error starting bulk entry deletion.', 'gravityforms' ),
),
'mark_read' => array(
'title' => esc_html__( 'Bulk Mark as Read', 'gravityforms' ),
'starting' => esc_html__( 'Starting bulk mark as read...', 'gravityforms' ),
'progress' => esc_html__( 'Marking Gravity Forms entries as read...', 'gravityforms' ),
'complete' => esc_html__( 'Completed marking entries as read!', 'gravityforms' ),
'error' => esc_html__( 'Error starting bulk mark as read.', 'gravityforms' ),
),
'mark_unread' => array(
'title' => esc_html__( 'Bulk Mark as Unread', 'gravityforms' ),
'starting' => esc_html__( 'Starting bulk mark as unread...', 'gravityforms' ),
'progress' => esc_html__( 'Marking Gravity Forms entries as unread...', 'gravityforms' ),
'complete' => esc_html__( 'Completed marking entries as unread!', 'gravityforms' ),
'error' => esc_html__( 'Error starting bulk mark as unread.', 'gravityforms' ),
),
),
'messages' => array(
'inProgress' => esc_html__( 'Bulk action in progress...', 'gravityforms' ),
'continueInBackground' => esc_html__( 'Continue in Background', 'gravityforms' ),
'cancel' => esc_html__( 'Cancel', 'gravityforms' ),
'cancelling' => esc_html__( 'Cancelling...', 'gravityforms' ),
'operationCancelled' => esc_html__( 'Operation cancelled.', 'gravityforms' ),
'viewDetails' => esc_html__( 'View Details', 'gravityforms' ),
'close' => esc_html__( 'Close', 'gravityforms' ),
'processedCount' => esc_html__( '%1$d of %2$d items processed.', 'gravityforms' ),
'goToPage' => esc_html__( 'Go to %s', 'gravityforms' ),
),
'pageLabels' => array(
'entry_list' => esc_html__( 'Entries', 'gravityforms' ),
'form_list' => esc_html__( 'Forms', 'gravityforms' ),
),
'entryActions' => \Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints\GF_Bulk_Action_Endpoint_Start::BACKGROUND_ACTIONS_ENTRY,
'formActions' => \Gravity_Forms\Gravity_Forms\Bulk_Actions\Endpoints\GF_Bulk_Action_Endpoint_Start::BACKGROUND_ACTIONS_FORM,
);
}
private function add_entry_list_data( $data ) {
if ( \GFForms::get_page() !== 'entry_list' ) {
return $data;
}
$form_id = absint( rgget( 'id' ) );
if ( ! $form_id ) {
return $data;
}
$data['form_id'] = $form_id;
$filter = rgget( 'filter' );
$search = rgget( 's' );
$field_id = rgget( 'field_id' ) === 'entry_id' ? 'id' : rgget( 'field_id' );
$operator = rgget( 'operator' );
$search_criteria = array();
if ( $filter === 'trash' || $filter === 'spam' ) {
$search_criteria['status'] = $filter;
} elseif ( $filter === 'star' ) {
$search_criteria['field_filters'][] = array( 'key' => 'is_starred', 'value' => true );
} elseif ( $filter === 'unread' ) {
$search_criteria['field_filters'][] = array( 'key' => 'is_read', 'value' => false );
} else {
$search_criteria['status'] = 'active';
}
if ( $search && $field_id ) {
$search_criteria['field_filters'][] = array(
'key' => $field_id,
'value' => $search,
'operator' => $operator ? $operator : 'contains',
);
}
$data['search_criteria'] = $search_criteria;
return $data;
}
}
@@ -0,0 +1,26 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config\Items;
/**
* Acts as a container for any Global Config data we need to send to both
* the admin and theme side of the ecosystem.
*
* @since 2.7
*/
class GF_Config_Global {
/**
* The data to send to both configs.
*
* @return array
*/
public function data() {
return array(
'hmr_dev' => defined( 'GF_ENABLE_HMR' ) && GF_ENABLE_HMR,
'public_path' => trailingslashit( \GFCommon::get_base_url() ) . 'assets/js/dist/',
'config_nonce' => wp_create_nonce( 'gform_config_ajax' ),
);
}
}
@@ -0,0 +1,59 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config\Items;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Collection;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Configurator;
/**
* Config items for Theme I18N
*
* @since 2.6
*/
class GF_Config_I18n extends GF_Config {
protected $name = 'gform_i18n';
protected $script_to_localize = 'gform_gravityforms';
/**
* Config data.
*
* @return array[]
*/
public function data() {
return array(
'datepicker' => array(
'days' => array(
'monday' => esc_html__( 'Mo', 'gravityforms' ),
'tuesday' => esc_html__( 'Tu', 'gravityforms' ),
'wednesday' => esc_html__( 'We', 'gravityforms' ),
'thursday' => esc_html__( 'Th', 'gravityforms' ),
'friday' => esc_html__( 'Fr', 'gravityforms' ),
'saturday' => esc_html__( 'Sa', 'gravityforms' ),
'sunday' => esc_html__( 'Su', 'gravityforms' ),
),
'months' => array(
'january' => esc_html__( 'January', 'gravityforms' ),
'february' => esc_html__( 'February', 'gravityforms' ),
'march' => esc_html__( 'March', 'gravityforms' ),
'april' => esc_html__( 'April', 'gravityforms' ),
'may' => esc_html_x('May', 'Full month name', 'gravityforms'),
'june' => esc_html__( 'June', 'gravityforms' ),
'july' => esc_html__( 'July', 'gravityforms' ),
'august' => esc_html__( 'August', 'gravityforms' ),
'september' => esc_html__( 'September', 'gravityforms' ),
'october' => esc_html__( 'October', 'gravityforms' ),
'november' => esc_html__( 'November', 'gravityforms' ),
'december' => esc_html__( 'December', 'gravityforms' ),
),
'firstDay' => array(
'value' => absint( get_option( 'start_of_week' ) ),
'default' => 1,
),
'iconText' => esc_html__( 'Select date', 'gravityforms' ),
),
);
}
}
@@ -0,0 +1,34 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config\Items;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Collection;
use Gravity_Forms\Gravity_Forms\Config\GF_Configurator;
/**
* Config items for Multi Legacy Check (mostly just data from a filter).
*
* @since 2.6
*/
class GF_Config_Legacy_Check_Multi extends GF_Config {
protected $name = 'gf_legacy_multi';
protected $script_to_localize = 'gform_gravityforms';
/**
* Config data.
*
* @return array[]
*/
public function data() {
/**
* Allows users to filter the legacy checks for any form on the page.
*
* @since 2.5
*
* @param array
*/
return apply_filters( 'gform_gf_legacy_multi', array() );
}
}
@@ -0,0 +1,45 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config\Items;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Collection;
use Gravity_Forms\Gravity_Forms\Config\GF_Configurator;
/**
* Config items for Theme Legacy Checks.
*
* @since 2.6
*/
class GF_Config_Legacy_Check extends GF_Config {
protected $name = 'gf_legacy';
protected $script_to_localize = 'gform_layout_editor';
/**
* Determine if the config should enqueue its data.
*
* @since 2.7
*
* @return bool
*/
public function should_enqueue() {
return \GFCommon::is_form_editor();
}
/**
* Config data.
*
* @return array[]
*/
public function data() {
$form = \RGFormsModel::get_form_meta( rgget( 'id' ) );
return array(
'is_legacy' => array(
'value' => \GFCommon::is_legacy_markup_enabled( $form ),
'default' => 0,
),
);
}
}
@@ -0,0 +1,47 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Config\Items;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Configurator;
/**
* Config items for Multifile Strings
*
* @since 2.6
*/
class GF_Config_Multifile extends GF_Config {
protected $script_to_localize = 'gform_gravityforms';
protected $name = 'gform_gravityforms';
/**
* Config data.
*
* @return array[]
*/
public function data() {
return array(
'strings' => array(
'invalid_file_extension' => wp_strip_all_tags( __( 'This type of file is not allowed. Must be one of the following: ', 'gravityforms' ) ),
'file_uploaded' => wp_strip_all_tags( __( 'File uploaded', 'gravityforms' ) ),
'delete_file' => wp_strip_all_tags( __( 'Delete this file', 'gravityforms' ) ),
'in_progress' => wp_strip_all_tags( __( 'in progress', 'gravityforms' ) ),
'file_exceeds_limit' => wp_strip_all_tags( __( 'File exceeds size limit', 'gravityforms' ) ),
'illegal_extension' => wp_strip_all_tags( __( 'This type of file is not allowed.', 'gravityforms' ) ),
'max_reached' => wp_strip_all_tags( __( 'Maximum number of files reached', 'gravityforms' ) ),
'unknown_error' => wp_strip_all_tags( __( 'There was a problem while saving the file on the server', 'gravityforms' ) ),
'currently_uploading' => wp_strip_all_tags( __( 'Please wait for the uploading to complete', 'gravityforms' ) ),
'cancel' => wp_strip_all_tags( __( 'Cancel', 'gravityforms' ) ),
'cancel_upload' => wp_strip_all_tags( __( 'Cancel this upload', 'gravityforms' ) ),
'cancelled' => wp_strip_all_tags( __( 'Cancelled', 'gravityforms' ) ),
'error' => wp_strip_all_tags( __( 'Error', 'gravityforms' ) ),
'message' => wp_strip_all_tags( __( 'Message', 'gravityforms' ) ),
),
'vars' => array(
'images_url' => \GFCommon::get_base_url() . '/images',
),
);
}
}
@@ -0,0 +1,132 @@
<?php
/**
* Handles logic for duplicate submission prevention services.
*
* @package Gravity_Forms\Gravity_Forms\Duplicate_Submissions
*/
namespace Gravity_Forms\Gravity_Forms\Duplicate_Submissions;
/**
* Class GF_Duplicate_Submissions_Handler
*
* @since 2.9.1
*
* Provides functionality for handling duplicate submissions while avoiding multiple
* entries being submitted.
*/
class GF_Duplicate_Submissions_Handler {
/**
* The URL parameter used for redirect protection in Safari.
*/
const SAFARI_REDIRECT_PARAM = 'gf_protect_submission';
/**
* The base URL for this plugin
*
* @var string $base_url.
*/
private $base_url;
/**
* GF_Duplicate_Submissions_Handler constructor.
*
* @param string $base_url The Base URL for this Plugin.
*/
public function __construct( $base_url ) {
$this->base_url = $base_url;
}
/**
* Returns true if duplicate submission protection is enabled. false otherwise.
*
* @returns bool $is_enabled true if duplicate protection is active/enabled. false otherwise
*/
public function is_enabled() {
$form_id = filter_input( INPUT_POST, 'gform_submit', FILTER_SANITIZE_NUMBER_INT );
if ( empty( $form_id ) ) {
return false;
}
/**
* Allows users to disable duplicate submissions protection, either globally
* or on a form-by-form basis.
*
* @since 2.5.15
*
* @param bool Passes a false value by default.
* @param int|string Passes the current form ID.
*/
$is_disabled = gf_apply_filters( array( 'gform_is_disabled_duplicate_submissions_protection', $form_id ), false, $form_id );
return ! $is_disabled;
}
/**
* Enqueue the JS file if this is a form submission configured for duplicate protection.
*/
public function maybe_enqueue_scripts() {
if ( $this->is_enabled() ) {
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || rgget( 'gform_debug' ) ? '' : '.min';
wp_enqueue_script( 'gform_duplicate_submissions', $this->base_url . "/js/duplicate-submissions{$min}.js", array(), true );
wp_localize_script( 'gform_duplicate_submissions', 'gf_duplicate_submissions', $this->get_localized_script_data() );
}
}
/**
* Get the correct data to localize to the JS file.
*
* @return array
*/
private function get_localized_script_data() {
return array(
'is_gf_submission' => (int) $this->is_valid_submission(),
'safari_redirect_param' => self::SAFARI_REDIRECT_PARAM,
);
}
/**
* Check if the current submission exists, and is valid.
*
* @return bool
*/
private function is_valid_submission() {
$form_id = filter_input( INPUT_POST, 'gform_submit', FILTER_SANITIZE_NUMBER_INT );
if ( empty( $form_id ) || ! class_exists( '\GFFormDisplay' ) ) {
return false;
}
$entry_id = rgars( \GFFormDisplay::$submission, $form_id . '/lead/id' );
if ( empty( $entry_id ) ) {
return false;
}
\GFCommon::log_debug( __METHOD__ . sprintf( '(): form #%d. entry #%d.', $form_id, $entry_id ) );
return true;
}
/**
* Redirect to a $_GET request if we detect a dupe submission from Safari.
*/
public function maybe_handle_safari_redirect() {
if ( rgget( self::SAFARI_REDIRECT_PARAM ) != '1' || ! $this->is_enabled() ) {
return;
}
// Get the submission URL from the $_SERVER, and strip out our redirect param.
$submission_url = filter_input( INPUT_SERVER, 'HTTP_REFERER', FILTER_SANITIZE_URL );
$base_url = esc_url( remove_query_arg( self::SAFARI_REDIRECT_PARAM, $submission_url ) );
// Redirect to the form's page URL as a GET request.
wp_safe_redirect( $base_url, 303 );
exit;
}
}
@@ -0,0 +1,54 @@
<?php
/**
* Service Provider for Duplicate Submission Service
*
* @package Gravity_Forms\Gravity_Forms\Duplicate_Submissions
*/
namespace Gravity_Forms\Gravity_Forms\Duplicate_Submissions;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
use Gravity_Forms\Gravity_Forms\Util\GF_Util_Service_Provider;
/**
* Class GF_License_Service_Provider
*
* Service provider for the Duplicate Submission Service.
*/
class GF_Duplicate_Submissions_Service_Provider extends GF_Service_Provider {
const GF_DUPLICATE_SUBMISSION_HANDLER = 'gf_duplicate_submission_handler';
/**
* Includes all related files and adds all containers.
*
* @param GF_Service_Container $container Container singleton object.
*/
public function register( GF_Service_Container $container ) {
\GFForms::include_gravity_api();
require_once plugin_dir_path( __FILE__ ) . 'class-gf-duplicate-submissions-handler.php';
$container->add(
self::GF_DUPLICATE_SUBMISSION_HANDLER,
function () {
return new GF_Duplicate_Submissions_Handler( \GFCommon::get_base_url() );
}
);
}
/**
* Initializes service.
*
* @param GF_Service_Container $container Service Container.
*/
public function init( GF_Service_Container $container ) {
parent::init( $container );
$duplicate_submission_handler = $container->get( self::GF_DUPLICATE_SUBMISSION_HANDLER );
add_action( 'gform_enqueue_scripts', array( $duplicate_submission_handler, 'maybe_enqueue_scripts' ) );
add_action( 'wp_loaded', array( $duplicate_submission_handler, 'maybe_handle_safari_redirect' ), 8, 0 );
}
}
@@ -0,0 +1,147 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Editor_Button;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Service_Provider;
use Gravity_Forms\Gravity_Forms\Editor_Button\Config\GF_Editor_Config;
use Gravity_Forms\Gravity_Forms\Editor_Button\Dom\GF_Editor_Button;
use Gravity_Forms\Gravity_Forms\Editor_Button\Endpoints\GF_Editor_Save_Editor_Settings;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
/**
* Class GF_Editor_Service_Provider
*
* Service provider for the Embed Form Service.
*
* @package Gravity_Forms\Gravity_Forms\Editor_Button;
*/
class GF_Editor_Service_Provider extends GF_Service_Provider {
// Configs
const EDITOR_CONFIG = 'editor_config';
const ENDPOINTS_CONFIG = 'editor_endpoints_config';
// DOM
const DOM_EDITOR_BUTTON = 'dom_editor_button';
/**
* Array mapping config class names to their container ID.
*
* @since 2.8
*
* @var string[]
*/
protected $configs = array(
self::EDITOR_CONFIG => GF_Editor_Config::class,
);
/**
* Register services to the container.
*
* @since 2.6
*
* @param GF_Service_Container $container
*/
public function register( GF_Service_Container $container ) {
// Configs
require_once( plugin_dir_path( __FILE__ ) . '/config/class-gf-editor-config.php' );
// Dom
require_once( plugin_dir_path( __FILE__ ) . '/dom/class-gf-editor-button.php' );
// Endpoints
require_once( plugin_dir_path( __FILE__ ) . '/endpoints/class-gf-editor-save-editor-settings.php' );
$container->add( self::ENDPOINTS_CONFIG, function () {
return new GF_Editor_Save_Editor_Settings();
} );
$this->add_configs( $container );
$this->dom( $container );
}
/**
* Initialize any actions or hooks.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
public function init( GF_Service_Container $container ) {
add_action( 'gform_after_toolbar_buttons', function () use ( $container ) {
$container->get( self::DOM_EDITOR_BUTTON )->output_button();
} );
add_action( 'wp_ajax_' . GF_Editor_Save_Editor_Settings::ACTION_NAME, function () use ( $container ) {
$container->get( self::ENDPOINTS_CONFIG )->handle();
} );
}
/**
* For each config defined in $configs, instantiate and add to container.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
private function add_configs( GF_Service_Container $container ) {
foreach ( $this->configs as $name => $class ) {
$container->add( $name, function () use ( $container, $class ) {
return new $class( $container->get( GF_Config_Service_Provider::DATA_PARSER ) );
} );
$container->get( GF_Config_Service_Provider::CONFIG_COLLECTION )->add_config( $container->get( $name ) );
}
}
/**
* Register DOM-related services.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
private function dom( GF_Service_Container $container ) {
$container->add( self::DOM_EDITOR_BUTTON, function() {
return new GF_Editor_Button();
});
}
/**
* Determine if the compact view is enabled for the given form and user.
*
* @since 2.8
*
* @param int $user_id The user ID.
* @param int $form_id The form ID.
*
* @return bool
*/
public static function is_compact_view_enabled( $user_id, $form_id ) {
$compact_view = get_user_meta( $user_id, 'gform_compact_view_' . $form_id, true );
return $compact_view === 'enable';
}
/**
* Determine if the field ID view in compact view is enabled for the given form and user.
*
* @since 2.8
*
* @param int $user_id The user ID.
* @param int $form_id The form ID.
*
* @return bool
*/
public static function is_field_id_enabled( $user_id, $form_id ) {
$field_id = get_user_meta( $user_id, 'gform_compact_view_show_id_' . $form_id, true );
return $field_id === 'enable';
}
}
@@ -0,0 +1,85 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Editor_Button\Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Editor_Button\GF_Editor_Service_Provider;
use Gravity_Forms\Gravity_Forms\Editor_Button\Endpoints\GF_Editor_Save_Editor_Settings;
/**
* Config items for the Editor Settings Button
*
* @since 2.8
*/
class GF_Editor_Config extends GF_Config {
protected $name = 'gform_admin_config';
protected $script_to_localize = 'gform_gravityforms_admin_vendors';
/**
* Determine if the config should enqueue its data.
*
* @since 2.8
*
* @return bool
*/
public function should_enqueue() {
return \GFCommon::is_form_editor();
}
/**
* Config data.
*
* @return array[]
*/
public function data() {
return array(
'components' => array(
'editor_button' => array(
'i18n' => array(
'title' => esc_html__( 'Editor Preferences', 'gravityforms' ),
'closeButtonAriaLabel' => esc_html__( 'Close button', 'gravityforms' ),
'description' => esc_html__( 'Change options related to the form editor.', 'gravityforms' ),
'compactToggleLabel' => esc_html__( 'Compact View', 'gravityforms' ),
'compactToggleText' => esc_html__( 'Simplify the preview of form fields for a more streamlined editing experience.', 'gravityforms' ),
'idToggleLabel' => esc_html__( 'Show Field IDs', 'gravityforms' ),
'idToggleText' => esc_html__( 'Show the ID of each field in Compact View.', 'gravityforms' ),
),
'endpoints' => $this->get_endpoints(),
'compactViewEnabled' => GF_Editor_Service_Provider::is_compact_view_enabled( get_current_user_id(), rgget( 'id' ) ),
'fieldIdEnabled' => GF_Editor_Service_Provider::is_field_id_enabled( get_current_user_id(), rgget( 'id' ) ),
'form' => rgget( 'id' ),
),
'dropdown_menu' => array(
'i18n' => array(
'duplicateButtonLabel' => esc_html__( 'Duplicate', 'gravityforms' ),
'deleteButtonLabel' => esc_html__( 'Delete', 'gravityforms' ),
'dropdownButtonLabel' => esc_html__( 'Dropdown menu button', 'gravityforms' ),
),
),
),
);
}
/**
* Gets the endpoints for saving the compact view settings.
*
* @since 2.8
*
* @return \array[][]
*/
private function get_endpoints() {
return array(
'save_editor_settings' => array(
'action' => array(
'value' => GF_Editor_Save_Editor_Settings::ACTION_NAME,
'default' => 'mock_endpoint',
),
'nonce' => array(
'value' => wp_create_nonce( GF_Editor_Save_Editor_Settings::ACTION_NAME ),
'default' => 'nonce',
),
),
);
}
}
@@ -0,0 +1,30 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Editor_Button\Dom;
/**
* Handle outputting the Embed Button in the UI.
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Embed_Form\Dom
*/
class GF_Editor_Button {
/**
* Output the HTML for the Embed Button.
*/
public function output_button() {
?>
<button
data-js="editor-flyout-trigger"
class="gform-button gform-button--icon-white gform-button--icon-editor"
aria-label="<?php esc_attr_e( 'Open editor preferences', 'gravityforms' ); ?>"
title="<?php esc_attr_e( 'Open editor preferences', 'gravityforms' ); ?>"
>
<i class="gform-icon gform-icon--cog gform-button__icon" aria-hidden="true"></i>
</button>
<?php
}
}
@@ -0,0 +1,38 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Editor_Button\Endpoints;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
/**
* AJAX Endpoint for saving the compact view settings.
*
* @since 2.8
*
* @package Gravity_Forms\Gravity_Forms\Editor_Button\Endpoints
*/
class GF_Editor_Save_Editor_Settings {
// Strings
const ACTION_NAME = 'gf_save_editor_settings';
/**
* Handle the AJAX request.
*
* @since 2.8
*
* @return void
*/
public function handle() {
check_ajax_referer( self::ACTION_NAME );
$form = intval( rgpost( 'form' ) );
$user = get_current_user_id();
$name = rgpost( 'name' );
$value = rgpost( 'value' );
$value = ( $value == 'enable' ) ? 'enable' : 'disable';
update_user_meta( $user, 'gform_' . $name . '_' . $form, $value );
}
}
@@ -0,0 +1,153 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Embed_Form;
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Service_Provider;
use Gravity_Forms\Gravity_Forms\Embed_Form\Config\GF_Embed_Config;
use Gravity_Forms\Gravity_Forms\Embed_Form\Config\GF_Embed_Config_I18N;
use Gravity_Forms\Gravity_Forms\Embed_Form\Config\GF_Embed_Endpoints_Config;
use Gravity_Forms\Gravity_Forms\Embed_Form\Dom\GF_Embed_Button;
use Gravity_Forms\Gravity_Forms\Embed_Form\Endpoints\GF_Embed_Endpoint_Create_With_Block;
use Gravity_Forms\Gravity_Forms\Embed_Form\Endpoints\GF_Embed_Endpoint_Get_Posts;
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
/**
* Class GF_Embed_Service_Provider
*
* Service provider for the Embed Form Service.
*
* @package Gravity_Forms\Gravity_Forms\Embed_Form;
*/
class GF_Embed_Service_Provider extends GF_Service_Provider {
// Configs
const EMBED_CONFIG = 'embed_config';
const EMBED_CONFIG_I18N = 'embed_config_i18n';
const EMBED_CONFIG_ENDPOINTS = 'embed_config_endpoints';
// Endpoints
const ENDPOINT_GET_POSTS = 'endpoint_get_posts';
const ENDPOINT_CREATE_WITH_BLOCK = 'endpoint_create_with_block';
// DOM
const DOM_EMBED_BUTTON = 'dom_embed_button';
// Strings
const ADD_BLOCK_PARAM = 'gfAddBlock';
/**
* Array mapping config class names to their container ID.
*
* @since 2.6
*
* @var string[]
*/
protected $configs = array(
self::EMBED_CONFIG => GF_Embed_Config::class,
self::EMBED_CONFIG_I18N => GF_Embed_Config_I18N::class,
self::EMBED_CONFIG_ENDPOINTS => GF_Embed_Endpoints_Config::class,
);
/**
* Register services to the container.
*
* @since 2.6
*
* @param GF_Service_Container $container
*/
public function register( GF_Service_Container $container ) {
// Configs
require_once( plugin_dir_path( __FILE__ ) . '/config/class-gf-embed-config.php' );
require_once( plugin_dir_path( __FILE__ ) . '/config/class-gf-embed-config-i18n.php' );
require_once( plugin_dir_path( __FILE__ ) . '/config/class-gf-embed-endpoints-config.php' );
// Endpoints
require_once( plugin_dir_path( __FILE__ ) . '/endpoints/class-gf-embed-endpoint-get-posts.php' );
require_once( plugin_dir_path( __FILE__ ) . '/endpoints/class-gf-embed-endpoint-create-with-block.php' );
// Dom
require_once( plugin_dir_path( __FILE__ ) . '/dom/class-gf-embed-button.php' );
$this->add_configs( $container );
$this->add_endpoints( $container );
$this->dom( $container );
}
/**
* Initiailize any actions or hooks.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
public function init( GF_Service_Container $container ) {
add_action( 'wp_ajax_' . GF_Embed_Endpoint_Get_Posts::ACTION_NAME, function () use ( $container ) {
$container->get( self::ENDPOINT_GET_POSTS )->handle();
} );
add_action( 'wp_ajax_' . GF_Embed_Endpoint_Create_With_Block::ACTION_NAME, function () use ( $container ) {
$container->get( self::ENDPOINT_CREATE_WITH_BLOCK )->handle();
} );
add_action( 'gform_before_toolbar_buttons', function () use ( $container ) {
$container->get( self::DOM_EMBED_BUTTON )->output_button();
} );
}
/**
* For each config defined in $configs, instantiate and add to container.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
private function add_configs( GF_Service_Container $container ) {
foreach ( $this->configs as $name => $class ) {
$container->add( $name, function () use ( $container, $class ) {
return new $class( $container->get( GF_Config_Service_Provider::DATA_PARSER ) );
} );
$container->get( GF_Config_Service_Provider::CONFIG_COLLECTION )->add_config( $container->get( $name ) );
}
}
/**
* Register AJAX endpoints for the Embed UI.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
private function add_endpoints( GF_Service_Container $container ) {
$container->add( self::ENDPOINT_GET_POSTS, function () use ( $container ) {
return new GF_Embed_Endpoint_Get_Posts();
} );
$container->add( self::ENDPOINT_CREATE_WITH_BLOCK, function () use ( $container ) {
return new GF_Embed_Endpoint_Create_With_Block();
} );
}
/**
* Register DOM-related services.
*
* @since 2.6
*
* @param GF_Service_Container $container
*
* @return void
*/
private function dom( GF_Service_Container $container ) {
$container->add( self::DOM_EMBED_BUTTON, function() {
return new GF_Embed_Button();
});
}
}
@@ -0,0 +1,67 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Embed_Form\Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
/**
* Config items for the Embed Form I18N
*
* @since 2.6
*/
class GF_Embed_Config_I18N extends GF_Config {
protected $name = 'gform_admin_config';
protected $script_to_localize = 'gform_gravityforms_admin_vendors';
/**
* Determine if the config should enqueue its data.
*
* @since 2.6.2
*
* @return bool
*/
public function should_enqueue() {
return \GFCommon::is_form_editor();
}
/**
* Config data.
*
* @return array[]
*/
public function data() {
return array(
'components' => array(
'embed_form' => array(
'i18n' => array(
'title' => esc_html__( 'Embed Form', 'gravityforms' ),
'id' => esc_html__( 'Form ID: %s', 'gravityforms' ),
'add_title' => esc_html__( 'Add to Existing Content', 'gravityforms' ),
'add_post_type_choice_label' => esc_html__( '%1$sAdd to Existing Content:%2$s %3$s', 'gravityforms' ),
'add_dropdown_placeholder' => esc_html__( 'Select a %s', 'gravityforms' ),
'add_trigger_aria_text' => esc_html__( 'Select a post', 'gravityforms' ),
'add_search_aria_text' => esc_html__( 'Search all %ss', 'gravityforms' ),
'add_button_label' => esc_html__( 'Insert Form', 'gravityforms' ),
'create_title' => esc_html__( 'Create New', 'gravityforms' ),
'create_post_type_choice_label' => esc_html__( '%1$sCreate New:%2$s %3$s', 'gravityforms' ),
'create_placeholder' => esc_html__( 'Enter %s Name', 'gravityforms' ),
'create_button_label' => esc_html__( 'Create', 'gravityforms' ),
'dialog_title' => esc_html__( 'Unsaved Changes', 'gravityforms' ),
'dialog_content' => esc_html__( 'Oops! You have unsaved changes in the form, before you can continue with embedding it please save your changes.', 'gravityforms' ),
'dialog_confirm_text' => esc_html__( 'Save Changes', 'gravityforms' ),
'dialog_confirm_saving' => esc_html__( 'Saving', 'gravityforms' ),
'dialog_cancel_text' => esc_html__( 'Cancel', 'gravityforms' ),
'dialog_close_title' => esc_html__( 'Close this dialog and return to form editor.', 'gravityforms' ),
'shortcode_title' => esc_html__( 'Not Using the Block Editor?', 'gravityforms' ),
'shortcode_description' => esc_html__( 'Copy and paste the shortcode within your page builder.', 'gravityforms' ),
'shortcode_button_label' => esc_html__( 'Copy Shortcode', 'gravityforms' ),
'shortcode_button_copied' => esc_html__( 'Copied', 'gravityforms' ),
'shortcode_helper' => esc_html__( '%1$sLearn more%2$s about the shortcode.', 'gravityforms' ),
'shortcode_external_link_screen_reader_text' => esc_html__( 'Opens in a new tab', 'gravityforms' ),
),
),
),
);
}
}
@@ -0,0 +1,234 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Embed_Form\Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
/**
* Config items for the Embed Form UI.
*
* @since 2.6
*/
class GF_Embed_Config extends GF_Config {
protected $name = 'gform_admin_config';
protected $script_to_localize = 'gform_gravityforms_admin_vendors';
/**
* Determine if the config should enqueue its data.
*
* @since 2.6.2
*
* @return bool
*/
public function should_enqueue() {
return \GFCommon::is_form_editor();
}
/**
* Config data.
*
* @return array[]
*/
public function data() {
return array(
'components' => array(
'embed_form' => array(
'urls' => $this->get_urls(),
'data' => array(
'form_id' => array(
'value' => rgget( 'id' ),
'default' => 1,
),
'post_types' => array(
'value' => $this->get_available_post_types(),
'default' => $this->placeholder_post_types(),
),
'items' => array(
'value' => $this->get_items_by_type(),
'default' => $this->placeholder_items(),
),
),
),
),
);
}
/**
* Get the various URLs for the Embed UI.
*
* @since 2.6
*
* @return array
*/
private function get_urls() {
$edit_link = '';
$post_type_object = get_post_type_object( 'page' );
if ( ! empty( $post_type_object->_edit_link ) ) {
$edit_link = admin_url( str_replace( '%d', '%1$s', $post_type_object->_edit_link ) . '&action=edit' );
}
/**
* Allows the edit post link to be customized.
*
* @since 2.6.2
*
* @param string $link The edit link. Use %1$s as the placeholder for the ID.
*/
$edit_link = apply_filters( 'gform_embed_edit_post_link', $edit_link );
return [
'edit_post' => [
'value' => $edit_link,
'default' => 'https://gravity.loc/wp-admin/post.php?post=%1$s&action=edit',
],
'shortcode_docs' => 'https://docs.gravityforms.com/shortcodes/',
];
}
/**
* Get the Post Types data for the Embed UI.
*
* @since 2.6
*
* @return array
*/
private function get_available_post_types() {
$types = array(
array(
'slug' => 'page',
'label' => get_post_type_object( 'page' )->labels->singular_name,
),
array(
'slug' => 'post',
'label' => get_post_type_object( 'post' )->labels->singular_name,
),
);
/**
* Allows users to modify the post types sent as selectable options in the Embed UI.
*
* @since 2.6
*
* @param array $types
*
* @return array
*/
return apply_filters( 'gform_embed_post_types', $types );
}
/**
* Get the items to localize for each post type.
*
* @since 2.6
*
* @return array
*/
private function get_items_by_type() {
$types = $this->get_available_post_types();
$data = array();
foreach ( $types as $type ) {
$slug = $type['slug'];
$label = $type['label'];
$items = get_posts( array( 'post_type' => $slug, 'posts_per_page' => 5 ) );
array_walk( $items, function ( &$item ) {
$item = array(
'value' => $item->ID,
'label' => $item->post_title,
);
} );
$data[ $slug ]['entries'] = $items;
$data[ $slug ]['count'] = $this->get_total_posts_by_type( $slug );
}
return $data;
}
/**
* Get the totals for the given post type.
*
* @since 2.6
*
* @param string $type - The Post Type to query for.
*
* @return array
*/
private function get_total_posts_by_type( $type ) {
$args = array(
'post_type' => $type,
'post_status' => 'publish',
);
$query = new \WP_Query( $args );
return $query->found_posts;
}
/**
* Get the placeholder post type values for use in Mocks.
*
* @since 2.6
*
* @return array
*/
private function placeholder_post_types() {
return array(
array( 'slug' => 'page', 'label' => __( 'Page', 'gravityforms' ) ),
array( 'slug' => 'post', 'label' => __( 'Post', 'gravityforms' ) ),
);
}
/**
* Get the placeholder post items for use in Mocks.
*
* @since 2.6
*
* @return array
*/
private function placeholder_items() {
return array(
'post' => array(
'count' => 2,
'entries' => array(
array(
'value' => 1,
'label' => 'Post One',
),
array(
'value' => 2,
'label' => 'Post Two',
),
),
),
'page' => array(
'count' => 25,
'entries' => array(
array(
'value' => 3,
'label' => 'Page Three',
),
array(
'value' => 4,
'label' => 'Page Four',
),
array(
'value' => 5,
'label' => 'Page Five',
),
array(
'value' => 6,
'label' => 'Page Six',
),
array(
'value' => 7,
'label' => 'Page Seven',
),
),
)
);
}
}
@@ -0,0 +1,82 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Embed_Form\Config;
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
use Gravity_Forms\Gravity_Forms\Embed_Form\Endpoints\GF_Embed_Endpoint_Create_With_Block;
use Gravity_Forms\Gravity_Forms\Embed_Form\Endpoints\GF_Embed_Endpoint_Get_Posts;
/**
* Config items for the Embed Forms REST Endpoints.
*
* @since 2.6
*/
class GF_Embed_Endpoints_Config extends GF_Config {
protected $script_to_localize = 'gform_gravityforms_admin_vendors';
protected $name = 'gform_admin_config';
protected $overwrite = false;
/**
* Determine if the config should enqueue its data.
*
* @since 2.6.2
*
* @return bool
*/
public function should_enqueue() {
return \GFCommon::is_form_editor();
}
/**
* Config data.
*
* @return array[]
*/
public function data() {
return array(
'components' => array(
'embed_form' => array(
'endpoints' => $this->get_endpoints(),
),
),
);
}
/**
* Get the various endpoints for the Embed UI.
*
* @since 2.6
*
* @return array
*/
private function get_endpoints() {
return array(
// Endpoint to get posts for typeahead
'get_posts' => array(
'action' => array(
'value' => 'gf_embed_query_posts',
'default' => 'mock_endpoint',
),
'nonce' => array(
'value' => wp_create_nonce( GF_Embed_Endpoint_Get_Posts::ACTION_NAME ),
'default' => 'nonce',
)
),
// Endpoint to create a new page with our block inserted.
'create_post_with_block' => array(
'action' => array(
'value' => GF_Embed_Endpoint_Create_With_Block::ACTION_NAME,
'default' => 'mock_endpoint',
),
'nonce' => array(
'value' => wp_create_nonce( GF_Embed_Endpoint_Create_With_Block::ACTION_NAME ),
'default' => 'nonce',
)
)
);
}
}
@@ -0,0 +1,26 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Embed_Form\Dom;
/**
* Handle outputting the Embed Button in the UI.
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Embed_Form\Dom
*/
class GF_Embed_Button {
/**
* Output the HTML for the Embed Button.
*/
public function output_button() {
?>
<button data-js="embed-flyout-trigger" class="gform-button gform-button--white gform-button--icon-leading">
<i class="gform-button__icon gform-icon gform-icon--embed-alt" aria-hidden="true"></i>
<?php esc_html_e( 'Embed', 'gravityforms' ); ?>
</button>
<?php
}
}
@@ -0,0 +1,68 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Embed_Form\Endpoints;
/**
* AJAX Endpoint for creating a new post with a specific block already added.
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Embed_Form\Endpoints
*/
class GF_Embed_Endpoint_Create_With_Block {
// Strings
const ACTION_NAME = 'gf_embed_create_post_with_block';
// Request Params
const PARAM_FORM_ID = 'form_id';
const PARAM_POST_TYPE = 'post_type';
const PARAM_POST_TITLE = 'post_title';
/**
* Handle the AJAX request.
*
* @since 2.6
*
* @return void
*/
public function handle() {
check_ajax_referer( self::ACTION_NAME );
$post_type = rgpost( self::PARAM_POST_TYPE );
$form_id = rgpost( self::PARAM_FORM_ID );
$post_title = rgpost( self::PARAM_POST_TITLE );
if ( empty( $post_type ) || empty( $form_id ) ) {
wp_send_json_error( 'Request must include a post_type and form_id.', 400 );
}
$post_data = array(
'post_title' => $post_title,
'post_type' => $post_type,
'post_content' => $this->get_content_for_form( $form_id ),
);
$new_id = wp_insert_post( $post_data );
wp_send_json_success( array( 'ID' => $new_id ) );
}
/**
* Get the properly-formatted comment string for the block we're inserting.
*
* @since 2.6
*
* @param $form_id
*
* @return string
*/
private function get_content_for_form( $form_id ) {
$attrs = array(
'formId' => $form_id
);
return get_comment_delimited_block_content( 'gravityforms/form', $attrs, '' );
}
}
@@ -0,0 +1,69 @@
<?php
namespace Gravity_Forms\Gravity_Forms\Embed_Form\Endpoints;
/**
* AJAX Endpoint for getting posts based on search params.
*
* @since 2.6
*
* @package Gravity_Forms\Gravity_Forms\Embed_Form\Endpoints
*/
class GF_Embed_Endpoint_Get_Posts {
// Strings
const ACTION_NAME = 'gf_embed_query_posts';
// Parameters
const PARAM_OFFSET = 'offset';
const PARAM_COUNT = 'count';
const PARAM_STATUS = 'status';
const PARAM_SEARCH = 'search';
const PARAM_POST_TYPE = 'post_type';
// Defaults
const DEFAULT_OFFSET = 0;
const DEFAULT_COUNT = 10;
const DEFAULT_STATUS = 'publish';
const DEFAULT_SEARCH = '';
const DEFAULT_POST_TYPE = 'post';
/**
* Handle the AJAX request.
*
* @since 2.6
*
* @return void
*/
public function handle() {
check_ajax_referer( self::ACTION_NAME );
$offset = rgpost( self::PARAM_OFFSET ) ? rgpost( self::PARAM_OFFSET ) : self::DEFAULT_OFFSET;
$count = rgpost( self::PARAM_COUNT ) ? rgpost( self::PARAM_COUNT ) : self::DEFAULT_COUNT;
$status = rgpost( self::PARAM_STATUS ) ? rgpost( self::PARAM_STATUS ) : self::DEFAULT_STATUS;
$search = rgpost( self::PARAM_SEARCH ) ? rgpost( self::PARAM_SEARCH ) : self::DEFAULT_SEARCH;
$post_type = rgpost( self::PARAM_POST_TYPE ) ? rgpost( self::PARAM_POST_TYPE ) : self::DEFAULT_POST_TYPE;
$args = array(
'post_type' => $post_type,
'post_status' => $status,
'posts_per_page' => $count,
'offset' => $offset,
's' => $search,
);
$query = new \WP_Query( $args );
$posts = $query->get_posts();
array_walk( $posts, function ( &$post ) {
$post = array(
'value' => $post->ID,
'label' => $post->post_title,
);
} );
wp_send_json_success( $posts );
}
}
@@ -0,0 +1,224 @@
<?php
/**
* Handles logic for adjusting Gravity Forms to different environment configurations.
*
* @package Gravity_Forms\Gravity_Forms\Environment_Config
*/
namespace Gravity_Forms\Gravity_Forms\Environment_Config;
/**
* Class GF_Environment_Config_Handler
*
* @since 2.6.7
*
* Provides functionality for handling environment configuration options.
*/
class GF_Environment_Config_Handler {
/**
* Provides caching service.
*
* @var \GFCache $cache
*/
protected $cache;
/**
* GF_Environment_Config_Handler constructor.
*
* @since 2.6.7
*
* @param \GFCache $cache Provides caching service.
*/
public function __construct( $cache ) {
$this->cache = $cache;
}
/**
* Gets an environment setting from wp_options.
*
* @since 2.6.7
*
* @param string $name The environment setting name. Don't include the "gf_env_" prefix.
* @param mixed $default Default value to be returned if option is not set.
*
* @return mixed
*/
public function get_environment_setting( $name, $default = false ) {
$option_name = "gf_env_{$name}";
$setting = $this->cache->get( $option_name, $found );
if ( ! $found ) {
$setting = get_option( $option_name, $default );
$this->cache->set( $option_name, $setting );
}
return $setting;
}
/**
* Gets the license_key config value.
*
* @since 2.6.7
*
* @return string The license key config.
*/
public function get_license_key() {
return $this->get_environment_setting( 'license_key', false );
}
/**
* Gets the hide_license config value.
*
* @since 2.6.7
*
* @return bool Returns true if license is supposed to be hidden from the UI, false otherwise.
*/
public function get_hide_license() {
return (bool) $this->get_environment_setting( 'hide_license', false );
}
/**
* Gets the hide_background_updates config value.
*
* @since 2.6.9
*
* @return bool Returns true if the background updates setting is supposed to be hidden from the UI. Returns false otherwise.
*/
public function get_hide_background_updates() {
return (bool) $this->get_environment_setting( 'hide_background_updates', false );
}
/**
* Gets the hide_install_wizard config value.
*
* @since 2.6.7
*
* @return bool Returns true if install wizard is supposed to be hidden. Returns false otherwise.
*/
public function get_hide_install_wizard() {
return (bool) $this->get_environment_setting( 'hide_setup_wizard', false );
}
/**
* Gets the hide_update_message config value.
*
* @since 2.6.7
*
* @return bool Returns true if install wizard is supposed to be hidden. Returns false otherwise.
*/
public function get_hide_update_message() {
return (bool) $this->get_environment_setting( 'hide_update_message', false );
}
/**
* Gets the support_url config value.
*
* @since 2.6.7
*
* @return string The support link config value.
*/
public function get_support_url() {
return $this->get_environment_setting( 'support_url', 'https://www.gravityforms.com/open-support-ticket/' );
}
/**
* Gets the unregistered_license_message config value.
*
* @since 2.6.7
*
* @return string The message to be displayed when the license is unregistered.
*/
public function get_unregistered_license_message() {
return $this->get_environment_setting( 'unregistered_license_message' );
}
/**
* Target of the pre_option_gform_pending_installation filter. Bypasses the installation wizard by returning 0 for the gform_pending_installation option.
*
* @hook pre_option_gform_pending_installation 10, 1
*
* @return int Returns 0 if the install wizard is set to be hidden by environment settings. Otherwise return false so that option is not overridden.
*/
public function maybe_override_gform_pending_installation() {
// If environment config is set to hide install wizard, override gform_pending_intallation option with 0. Otherwise, use existing option.
$hide_install_wizard = $this->get_hide_install_wizard();
return $hide_install_wizard ? 0 : false;
}
/**
* Maybe hides the automatic update message on the plugin's page.
*
* @since 2.6.8
*/
public function maybe_hide_plugin_page_message() {
if ( $this->get_hide_update_message() ) {
remove_filter( 'transient_update_plugins', array( 'GFForms', 'check_update' ) );
remove_filter( 'site_transient_update_plugins', array( 'GFForms', 'check_update' ) );
}
}
/**
* Maybe hides the automatic update message on the Update page.
*
* @since 2.6.8
*
* @hook gform_updates_list, 20
*
* @param array $updates Updates array being filtered.
*/
public function maybe_hide_update_page_message( $updates ) {
if ( ! $this->get_hide_update_message() ) {
return $updates;
}
foreach ( $updates as & $update ) {
if ( $update['slug'] == 'gravityforms' ) {
$update['latest_version'] = '';
}
}
return $updates;
}
/**
* Target of the pre_option_rg_gforms_key filter. Uses the license key configured by the environment settings if one is set.
*
* @hook pre_option_rg_gforms_key 10, 1
*
* @since 2.6.7
*
* @return string Returns the environment license key if one is set. If not set, return false so that value is not overridden.
*/
public function maybe_override_rg_gforms_key() {
// Use environment license key if one is set. Otherwise, use rg_gforms_key option.
$env_license_key = $this->get_license_key();
return $env_license_key !== false ? $env_license_key : false;
}
/**
* Target of the gform_plugin_settings_fields filter. Removes sections from the settings page that are configured to be hidden.
*
* @since 2.6.9
*
* @param array $fields The settings fields to be filtered.
*
* @return array Returns the filtered $fields array.
*/
public function maybe_hide_setting( $fields ) {
if ( $this->get_hide_license() ) {
unset( $fields['license_key'] );
unset( $fields['license_key_details'] );
}
if ( $this->get_hide_background_updates() ) {
unset( $fields['background_updates'] );
}
return $fields;
}
}

Some files were not shown because too many files have changed in this diff Show More