Server : Apache System : Linux pod-100823:apache2_74:v0.5.7 5.4.0-1138-gcp #147~18.04.1-Ubuntu SMP Mon Oct 7 21:46:26 UTC 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.33.7 Disable Function : apache_child_terminate,apache_get_modules,apache_get_version,apache_getenv,apache_note,apache_setenv,disk_free_space,disk_total_space,diskfreespace,dl,exec,fastcgi_finish_request,link,opcache_compile_file,opcache_get_configuration,opcache_invalidate,opcache_is_script_cached,opcache_reset,passthru,pclose,pcntl_exec,popen,posix_getpid,posix_getppid,posix_getpwuid,posix_kill,posix_mkfifo,posix_setegid,posix_seteuid,posix_setgid,posix_setpgid,posix_setsid,posix_setuid,posix_uname,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,realpath_cache_get,shell_exec,show_source,symlink,system Directory : /nas/content/live/attorneyexperi/wp-content/plugins/gravityforms/includes/webapi/ |
<?php if ( ! class_exists( 'GFForms' ) ) { die(); } if ( ! defined( 'GFWEBAPI_REQUIRE_SIGNATURE' ) ) { define( 'GFWEBAPI_REQUIRE_SIGNATURE', true ); } if ( ! defined( 'GFWEBAPI_SLUG' ) ) { define( 'GFWEBAPI_SLUG', 'gravityformsapi' ); } if ( ! defined( 'GFWEBAPI_ROUTE_VAR' ) ) { define( 'GFWEBAPI_ROUTE_VAR', 'gfapi_route' ); } if ( ! defined( 'GFWEBAPI_API_BASE_URL' ) ) { define( 'GFWEBAPI_API_BASE_URL', site_url( GFWEBAPI_SLUG ) ); } if ( class_exists( 'GFForms' ) ) { GFForms::include_addon_framework(); class GFWebAPI extends GFAddOn { protected $_version = '1.0'; protected $_min_gravityforms_version = '1.7.9999'; protected $_slug = 'gravityformswebapi'; protected $_path = 'gravityformswebapi/webapi.php'; protected $_full_path = __FILE__; protected $_url = 'https://www.gravityforms.com'; protected $_title = 'Gravity Forms REST API'; protected $_short_title = 'REST API'; private $_enabled_v1; private $_enabled_v2; private $_private_key; private $_public_key; // Members plugin integration protected $_capabilities = array( 'gravityforms_api', 'gravityforms_api_settings' ); // Permissions protected $_capabilities_settings_page = 'gravityforms_api_settings'; protected $_capabilities_uninstall = 'gravityforms_webapi_uninstall'; /** * Contains an instance of this class, if available. * * @since 2.4.24 * * @var null|GFWebAPI $_instance If available, contains an instance of this class. */ private static $_instance = null; /** * Returns the current instance of this class. * * @since 2.4.24 * * @return null|GFWebAPI */ public static function get_instance() { if ( null === self::$_instance ) { self::$_instance = new self; } return self::$_instance; } public function bootstrap() { add_action( 'after_setup_theme', array( $this, 'init' ), 15 ); } public function init() { parent::init(); global $_gaddon_posted_settings; if ( defined( 'DOING_CRON' ) && DOING_CRON ) { add_action( 'gravityforms_results_cron_' . $this->get_slug(), array( $this, 'results_cron' ), 10, 3 ); return; } $is_v2_enabled = $this->is_v2_enabled( $this->get_plugin_settings() ) || $this->is_v2_enabled(); if ( $is_v2_enabled ) { $this->maybe_upgrade_schema(); if ( ! is_admin() ) { require_once( plugin_dir_path( __FILE__ ) . 'v2/class-gf-rest-authentication.php' ); } } // Clear the settings cache because it was checked very early before other add-ons have a chance to make adjustments. $_gaddon_posted_settings = null; } /** * Triggers the db upgrade following an install, version update, or when forced from the system status page. * * @since 2.4.24 * * @param string $db_version Current Gravity Forms database version. * @param string $previous_db_version Previous Gravity Forms database version. * @param bool $force_upgrade True if this is a request to force an upgrade. False if this is a standard upgrade (due to version change). */ public function post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade ) { $this->maybe_upgrade_schema( $force_upgrade ); } /** * Updates REST API related schema when GF version changes * * @since 2.4 * @since 2.4.24 Added the $force_upgrade param. * * @param bool $force_upgrade True if this is a request to force an upgrade. False if this is a standard upgrade (due to version change). */ public function maybe_upgrade_schema( $force_upgrade = false ) { global $wpdb; if ( $force_upgrade || $this->requires_schema_upgrade() ) { $collate = $wpdb->has_cap( 'collation' ) ? $wpdb->get_charset_collate() : ''; $table_name = GFFormsModel::get_rest_api_keys_table_name(); $table = "CREATE TABLE {$table_name} ( key_id BIGINT UNSIGNED NOT NULL auto_increment, user_id BIGINT UNSIGNED NOT NULL, description varchar(200) NULL, permissions varchar(10) NOT NULL, consumer_key char(64) NOT NULL, consumer_secret char(43) NOT NULL, nonces longtext NULL, truncated_key char(7) NOT NULL, last_access datetime NULL default null, PRIMARY KEY (key_id), KEY consumer_key (consumer_key), KEY consumer_secret (consumer_secret) ) $collate;"; gf_upgrade()->dbDelta( $table ); update_option( 'gf_rest_api_db_version', GFForms::$version ); } } /** * Returns true if REST API schema needs to be upgraded. False otherwise. * * @since 2.4-beta-1 * * @return bool */ public function requires_schema_upgrade() { global $wpdb; $table_exists = $wpdb->query( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( GFFormsModel::get_rest_api_keys_table_name() ) ) ); if ( ! $table_exists ) { return true; } $rest_api_db_version = get_option( 'gf_rest_api_db_version' ); $upgrade_required = version_compare( GFForms::$version, $rest_api_db_version, '>' ); if ( $upgrade_required ) { // Making sure version has really changed. Gets around aggressive caching issue on some sites that cause setup to run multiple times. $rest_api_db_version = gf_upgrade()->get_wp_option( 'gf_rest_api_db_version' ); $upgrade_required = version_compare( GFForms::$version, $rest_api_db_version, '>' ); } return $upgrade_required; } public function init_ajax() { parent::init_ajax(); add_action( 'wp_ajax_gfwebapi_qrcode', array( $this, 'ajax_qrcode' ) ); add_action( 'wp_ajax_delete_key', array( $this, 'ajax_delete_key' ) ); add_action( 'wp_ajax_gfwebapi_edit_key', array( $this, 'ajax_edit_key' ) ); } /** * Determines if the API is enabled in the database. * * @since 2.4.23 * * @return boolean True if the API is enabled, false otherwise. */ public function is_api_enabled() { $web_api_settings = get_option( 'gravityformsaddon_gravityformswebapi_settings' ); return ( ! empty( $web_api_settings ) && $web_api_settings['enabled'] ) ? 1 : 0; } /** * Adds admin hooks. * * @since unknown * @since 2.4.18 Removed caps integrations to prevent them being added to the Add-Ons group. */ public function init_admin() { parent::init_admin(); if( GFForms::get_page() == 'settings' && rgget( 'subview' ) == $this->get_slug() ) { require_once( plugin_dir_path( __FILE__ ) . 'includes/class-gf-api-keys-table.php' ); } // update the results cache meta add_action( 'gform_after_update_entry', array( $this, 'entry_updated' ), 10, 2 ); add_action( 'gform_update_status', array( $this, 'update_entry_status' ), 10, 2 ); add_action( 'gform_after_save_form', array( $this, 'after_save_form' ), 10, 2 ); remove_action( 'members_register_cap_groups', array( $this, 'members_register_cap_group' ), 11 ); remove_action( 'members_register_caps', array( $this, 'members_register_caps' ), 11 ); remove_filter( 'ure_capabilities_groups_tree', array( $this, 'filter_ure_capabilities_groups_tree' ), 11 ); remove_filter( 'ure_custom_capability_groups', array( $this, 'filter_ure_custom_capability_groups' ), 10 ); } public function init_frontend() { parent::init_frontend(); $settings = $this->get_plugin_settings(); $this->_enabled_v1 = $this->is_v1_enabled( $settings ); $this->_enabled_v2 = $this->is_v2_enabled( $settings ); $this->_public_key = rgar( $settings, 'public_key' ); $this->_private_key = rgar( $settings, 'private_key' ); if ( $this->_enabled_v1 ) { $this->init_v1(); } if ( $this->_enabled_v2 ) { $this->init_v2(); } } public function init_v1() { add_rewrite_rule( GFWEBAPI_SLUG . '/(.*)', 'index.php?' . GFWEBAPI_ROUTE_VAR . '=$matches[1]', $after = 'top' ); $rules = get_option( 'rewrite_rules' ); if ( ! isset( $rules[ GFWEBAPI_SLUG . '/(.*)' ] ) ) { flush_rewrite_rules(); } add_filter( 'query_vars', array( $this, 'query_vars' ) ); add_action( 'template_redirect', array( $this, 'handle_page_request' ) ); // update the cache add_action( 'gform_entry_created', array( $this, 'entry_created' ), 10, 2 ); } public function init_v2() { require_once( plugin_dir_path( __FILE__ ) . 'v2/restapi.php' ); } public function load_text_domain() { GFCommon::load_gf_text_domain(); } // Scripts public function scripts() { $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min'; $scripts = array( array( 'handle' => 'wp-lists', 'enqueue' => array( array( 'admin_page' => array( 'plugin_settings' ) ), ), ), array( 'handle' => 'gfwebapi_hmac_sha1', 'src' => GFCommon::get_base_url() . '/includes/webapi/js/hmac-sha1.min.js', 'enqueue' => array( array( 'admin_page' => array( 'plugin_settings' ) ), ) ), array( 'handle' => 'gfwebapi_enc_base64', 'src' => GFCommon::get_base_url() . '/includes/webapi/js/enc-base64-min.js', 'deps' => array( 'gfwebapi_hmac_sha1' ), 'callback' => array( $this, 'localize_form_settings_scripts' ), 'enqueue' => array( array( 'admin_page' => array( 'plugin_settings' ) ), ) ), array( 'handle' => 'gfwebapi_settings.js', 'src' => GFCommon::get_base_url() . "/includes/webapi/js/gfwebapi_settings{$min}.js", 'version' => $this->_version, 'deps' => array( 'jquery', 'thickbox' ), 'enqueue' => array( array( 'admin_page' => array( 'plugin_settings' ) ), ) ), ); add_action( 'admin_footer', array( $this, 'output_webapi_json' ) ); return array_merge( parent::scripts(), $scripts ); } public function styles() { $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min'; $styles = array( array( 'handle' => 'gfwebap_settings', 'src' => GFCommon::get_base_url() . "/includes/webapi/css/gfwebapi_settings{$min}.css", 'version' => $this->_version, 'deps' => array( 'thickbox' ), 'enqueue' => array( array( 'admin_page' => array( 'plugin_settings' ) ), ) ), ); return array_merge( parent::styles(), $styles ); } /** * Echo json containing webapi variables for use in the handle unsaved changes function * * @since 2.5 * */ public function output_webapi_json() { if ( !empty( $_GET['subview'] ) && $_GET['subview'] === 'gravityformswebapi' ) { echo '<script> var gf_webapi_vars = { "api_enabled": ' . $this->is_api_enabled() . ', "enable_api_checkbox_checked": ' . $this->get_setting( "enabled" ) . ', };</script>'; } } public function render_uninstall() { } /** * Return the plugin's icon for the plugin/form settings menu. * * @since 2.5 * * @return string */ public function get_menu_icon() { return 'gform-icon--api'; } // ------- Plugin settings ------- /** * Plugin Settings page. * * @since 2.5 */ public function plugin_settings_page() { parent::plugin_settings_page(); ?> <div id="gform-webapi-edit-container" style="display: none;"> <form id="gform-webapi-edit" class="gform-settings__wrapper"> <fieldset class="gform-settings-panel__content"> <!-- Nonce --> <?php wp_nonce_field( 'gf_restapi_edit_key' ); ?> <!-- Key ID --> <input id="gform-webapi-key" type="hidden" /> <!-- Description --> <div class="gform-settings-field gform-settings-field__text"> <label class="gform-settings-label" for="gform-webapi-description"><?php esc_html_e( 'Description', 'gravityforms' ); ?></label> <input id="gform-webapi-description" type="text" value="" /> </div> <!-- User --> <div class="gform-settings-field gform-settings-field__select"> <label class="gform-settings-label" for="gform-webapi-user"><?php esc_html_e( 'User', 'gravityforms' ); ?></label> <select id="gform-webapi-user"> <?php $users = $this->get_users(); foreach ( $users as $user ) { printf( '<option value="%s">%s</option>', $user['value'], $user['label'] ); } ?> </select> </div> <!-- Permissions --> <div class="gform-settings-field gform-settings-field__select"> <label class="gform-settings-label" for="gform-webapi-permissions"><?php esc_html_e( 'Permissions', 'gravityforms' ); ?></label> <select id="gform-webapi-permissions"> <option value="read"><?php esc_html_e( 'Read', 'gravityforms' ); ?></option> <option value="write"><?php esc_html_e( 'Write', 'gravityforms' ); ?></option> <option value="read_write"><?php esc_html_e( 'Read/Write', 'gravityforms' ); ?></option> </select> </div> <!-- Last Updated --> <div class="gform-settings-field gform-settings-field__html"> <label class="gform-settings-label"><?php esc_html_e( 'Last Access', 'gravityforms' ); ?></label> <span id="gform-webapi-last-access"></span> </div> <!-- Consumer Key --> <div class="gform-settings-field gform-settings-field__text"> <label class="gform-settings-label" for="gform-webapi-description"><?php esc_html_e( 'Consumer Key', 'gravityforms' ); ?></label> <input id="gform-webapi-consumer-key" type="text" value="" /> </div> <!-- Consumer Secret --> <div class="gform-settings-field gform-settings-field__text"> <label class="gform-settings-label" for="gform-webapi-description"><?php esc_html_e( 'Consumer Secret', 'gravityforms' ); ?></label> <input id="gform-webapi-consumer-secret" type="text" value="" /> </div> </fieldset> <button type="submit" class="button" data-add="<?php esc_html_e( 'Add', 'gravityforms' ); ?>" data-edit="<?php esc_html_e( 'Update', 'gravityforms' ); ?>"><?php esc_html_e( 'Update', 'gravityforms' ); ?></button> </form> </div> <?php } public function plugin_settings_title() { return esc_html__( 'Gravity Forms API Settings', 'gravityforms' ); } public function get_users() { $args = apply_filters( 'gform_webapi_get_users_settings_page', array( 'number' => 3000 ) ); $accounts = get_users( $args ); $account_choices = array(); foreach ( $accounts as $account ) { if ( ! $this->user_can_access_api( $account ) ) { continue; } $account_choices[] = array( 'label' => $account->user_login, 'value' => $account->ID, ); } return $account_choices; } /** * Checks if a user has one or more capabilities to access Gravity Forms REST API endpoints. * * @since 2.4.24 * * @param WP_User $user WP User object. * * @return bool */ private function user_can_access_api( $user ) { /** * Filters the available capabilities used to check if a user can be added to a REST API key. * * A user only needs one capability to access the API. * * @since 2.4.24 * * @param array $capabilities Array of capabilities. */ $capabilities = (array) apply_filters( 'gform_webapi_key_user_capabilities', array( 'gform_full_access', 'gravityforms_create_form', 'gravityforms_edit_forms', 'gravityforms_delete_forms', 'gravityforms_view_entries', 'gravityforms_edit_entries', 'gravityforms_delete_entries', 'gravityforms_view_entry_notes', 'gravityforms_edit_entry_notes', ) ); foreach ( $capabilities as $capability ) { if ( $user->has_cap( $capability ) ) { return true; } } return false; } public function plugin_settings_fields() { $permalink_structure = get_option( 'permalink_structure' ); if ( ! $permalink_structure ) { return array( array( 'description' => esc_html__( 'The Gravity Forms API allows developers to interact with this install via a JSON REST API.', 'gravityforms' ), 'fields' => array( array( 'name' => 'requirements_check', 'label' => esc_html__( 'Requirements check', 'gravityforms' ), 'type' => 'requirements_check', ), array( 'id' => 'save_button', 'type' => 'save', 'value' => esc_attr__( 'Update', 'gravityforms' ), 'style' => 'display:none;', ), ) ), ); } return array( array( 'description' => esc_html__( 'The Gravity Forms API allows developers to interact with this install via a JSON REST API.', 'gravityforms' ), 'fields' => array( array( 'type' => 'checkbox', 'label' => esc_html__( 'Enable access to the API', 'gravityforms' ), 'name' => 'activate', 'onclick' => 'jQuery(this).parents("form").submit();', 'onkeypress' => 'jQuery(this).parents("form").submit();', 'choices' => array( array( 'label' => esc_html__( 'Enabled', 'gravityforms' ), 'name' => 'enabled', 'data-js-button' => 'enable-api' ), ), ), ), ), array( 'title' => esc_html__( 'Authentication ( API version 2 )', 'gravityforms' ), 'id' => 'gform_section_authentication_v2', 'description' => sprintf( __( 'Create an API Key below to use the REST API version 2. Alternatively, you can use cookie authentication which is supported for logged in users. %sVisit our documentation pages%s for more information.', 'gravityforms' ), '<a href="https://docs.gravityforms.com/rest-api-v2/" target="_blank">', '</a>' ), 'dependency' => array( $this, 'is_v2_enabled' ), 'fields' => array( array( 'type' => 'api_keys', 'label' => esc_html__( 'API Keys', 'gravityforms' ), 'name' => 'api_keys', ), ), ), array( 'title' => esc_html__( 'Authentication ( API version 1 )', 'gravityforms' ), 'id' => 'gform_section_authentication', 'description' => sprintf( __( 'Configure your API Key below to use the REST API version 1. Alternatively, you can use cookie authentication which is supported for logged in users. %sVisit our documentation pages%s for more information.', 'gravityforms' ), '<a href="https://docs.gravityforms.com/web-api/" target="_blank">', '</a>' ), 'dependency' => array( $this, 'is_v1_enabled' ), 'fields' => array( array( 'name' => 'public_key', 'label' => esc_html__( 'Public API Key', 'gravityforms' ), 'type' => 'text', 'default_value' => substr( wp_hash( site_url() ), 0, 10 ), 'class' => 'medium', 'feedback_callback' => array( $this, 'is_valid_public_key' ), ), array( 'name' => 'private_key', 'label' => esc_html__( 'Private API Key', 'gravityforms' ), 'type' => 'text', 'default_value' => substr( wp_hash( get_bloginfo( 'admin_email' ) ), 0, 15 ), 'class' => 'medium', 'feedback_callback' => array( $this, 'is_valid_private_key' ) ), array( 'name' => 'qrcode', 'label' => esc_html__( 'QR Code', 'gravityforms' ), 'type' => 'qrcode', 'dependency' => array( 'field' => 'private_key', 'values' => array( '_notempty_' ) ) ), array( 'name' => 'impersonate_account', 'label' => esc_html__( 'Impersonate account', 'gravityforms' ), 'type' => 'select', 'choices' => $this->get_users(), ), ) ), array( 'fields' => array( array( 'id' => 'save_button', 'type' => 'save', 'value' => esc_attr__( 'Update', 'gravityforms' ), ), ) ), ); } /*** * Determines if REST API V1 is enabled. * * @since Unknown * @since 2.5 Deprecated $settings parameter. * * @param array|null $settings Current settings array (optional). * * @return bool */ public function is_v1_enabled( $settings = null ) { $is_api_enabled = $this->get_setting( 'enabled', '', $settings ); /*** * Allows for disabling the REST API V1. * * @since 2.4 * * @param bool is_enabled Whether or not REST API V1 is allowed/enabled. Defaults to true. */ $is_v1_enabled = apply_filters( 'gform_is_rest_api_v1_enabled', true ); return $is_api_enabled && $is_v1_enabled; } /*** * Determines if REST API V2 is enabled. * * @since Unknown * @since 2.5 Deprecated $settings parameter. * * @param array|null $settings Current settings array (optional). * * @return bool */ public function is_v2_enabled( $settings = null ) { return $this->get_setting( 'enabled', '', $settings ) && ! is_callable( 'gf_rest_api' ); } public function settings_api_keys( $section, $is_first = false ) { if ( ! $this->is_v2_enabled() ) { return; } $table = new GF_API_Keys_Table(); $table->process_action(); $table->prepare_items(); $table->output_styles(); $table->output_scripts(); $table->display(); } public function settings_requirements_check() { $permalinks_url = admin_url( 'options-permalink.php' ); ?> <i class="fa fa-exclamation-triangle gf_invalid"></i> <span class="gf_invalid"> <?php esc_html_e( 'Permalinks are not in the correct format.', 'gravityforms' ); ?> </span> <br/> <span class='gf_settings_description'> <?php printf( esc_html__( 'Change the %sWordPress Permalink Settings%s from default to any of the other options to get started.', 'gravityforms' ), '<a href="' . esc_url( $permalinks_url ) . '">', '</a>' ); ?> </span> <?php } public function settings_qrcode() { ?> <button class="button" id="gfwebapi-qrbutton"><?php esc_html_e( 'Show/hide QR Code', 'gravityforms' ); ?></button> <div id="gfwebapi-qrcode-container" style="display:none;"> <img id="gfwebapi-qrcode" src="<?php echo GFCommon::get_base_url() ?>/images/spinner.svg"/> </div> <?php } /** * Removes the REST API from the logging page. * * @since 2.4.11 * * @param array $plugins The plugins which support logging. * * @return array */ public function set_logging_supported( $plugins ) { return $plugins; } /** * Write an error message to the Gravity Forms API log. * * @since 2.4.11 * * @param string $message The message to be logged. */ public function log_error( $message ) { GFAPI::log_error( $message ); } /** * Write a debug message to the Gravity Forms API log. * * @since 2.4.11 * * @param string $message The message to be logged. */ public function log_debug( $message ) { GFAPI::log_debug( $message ); } public function query_vars( $query_vars ) { $query_vars[] = GFWEBAPI_ROUTE_VAR; return $query_vars; } public function handle_page_request() { global $HTTP_RAW_POST_DATA; $route = get_query_var( GFWEBAPI_ROUTE_VAR ); if ( false == $route ) { return; } send_origin_headers(); $settings = get_option( 'gravityformsaddon_gravityformswebapi_settings' ); if ( empty( $settings ) || ! $settings['enabled'] ) { $this->log_debug( __METHOD__ . '(): API not enabled, permission denied.' ); $this->die_permission_denied(); } $route_parts = pathinfo( $route ); $format = rgar( $route_parts, 'extension' ); if ( $format ) { $route = str_replace( '.' . $format, '', $route ); } $path_array = explode( '/', $route ); $collection = strtolower( rgar( $path_array, 0 ) ); $id = rgar( $path_array, 1 ); if ( strpos( $id, ';' ) !== false ) { $id = explode( ';', $id ); } $collection2 = strtolower( rgar( $path_array, 2 ) ); $id2 = rgar( $path_array, 3 ); if ( strpos( $id2, ';' ) !== false ) { $id2 = explode( ';', $id2 ); } if ( empty( $format ) ) { $format = 'json'; } $schema = strtolower( ( rgget( 'schema' ) ) ); $offset = isset( $_GET['paging']['offset'] ) ? strtolower( $_GET['paging']['offset'] ) : 0; $page_size = isset( $_GET['paging']['page_size'] ) ? strtolower( $_GET['paging']['page_size'] ) : 10; $method = strtoupper( $_SERVER['REQUEST_METHOD'] ); $args = compact( 'offset', 'page_size', 'schema' ); $endpoint = empty( $collection2 ) ? strtolower( $method ) . '_' . $collection : strtolower( $method ) . '_' . $collection . '_' . $collection2; // The POST forms/[ID]/submissions endpoint is public and does not require authentication. $authentication_required = $endpoint !== 'post_forms_submissions'; /** * Allows overriding of authentication for all the endpoints of the Web API. * gform_webapi_authentication_required_[end point] * e.g. * gform_webapi_authentication_required_post_form_submissions * * @param bool $authentication_required Whether authentication is required for this endpoint. */ $authentication_required = apply_filters( 'gform_webapi_authentication_required_' . $endpoint, $authentication_required ); if ( $authentication_required ) { $this->authenticate(); } else { $this->log_debug( __METHOD__ . '(): Authentication not required.' ); } $test_mode = rgget( 'test' ); if ( $test_mode ) { die( 'test mode' ); } if ( empty( $collection2 ) ) { do_action( 'gform_webapi_' . $endpoint, $id, $format, $args ); } else { do_action( 'gform_webapi_' . $endpoint, $id, $id2, $format, $args ); } if ( ! isset( $HTTP_RAW_POST_DATA ) ) { $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' ); } $this->log_debug( __METHOD__ . '(): HTTP_RAW_POST_DATA = ' . $HTTP_RAW_POST_DATA ); $data = json_decode( $HTTP_RAW_POST_DATA, true ); switch ( $collection ) { case 'forms' : switch ( $collection2 ) { case 'results' : switch ( $method ) { case 'GET' : $this->get_results( $id ); break; case 'DELETE': case 'PUT': case 'POST': default: $this->die_bad_request(); } break; case 'properties' : switch ( $method ) { case 'PUT' : $this->put_forms_properties( $data, $id ); break; default: $this->die_bad_request(); } break; case 'feeds' : if ( false == empty( $id2 ) ) { $this->die_bad_request(); } switch ( $method ) { case 'GET' : $this->get_feeds( null, $id ); break; case 'DELETE' : $this->delete_feeds( null, $id ); break; case 'PUT' : $this->die_not_implemented(); break; case 'POST' : $this->post_feeds( $data, $id ); break; default : $this->die_bad_request(); } break; case 'entries' : if ( false == empty( $id2 ) ) { $this->die_bad_request(); } switch ( $method ) { case 'GET' : $this->get_entries( null, $id, $schema ); break; case 'POST' : $this->post_entries( $data, $id ); break; case 'PUT' : case 'DELETE' : $this->die_not_implemented(); break; default: $this->die_bad_request(); } break; case 'submissions' : if ( false == empty( $id2 ) ) { $this->die_bad_request(); } switch ( $method ) { case 'POST' : $this->submit_form( $data, $id ); break; case 'GET' : case 'PUT' : case 'DELETE' : $this->die_not_implemented(); break; default: $this->die_bad_request(); } break; case '' : switch ( $method ) { case 'GET': $this->get_forms( $id, $schema ); break; case 'DELETE': $this->delete_forms( $id ); break; case 'PUT': $this->put_forms( $data, $id, $id2 ); break; case 'POST': if ( false === empty( $id ) ) { $this->die_bad_request(); } $this->post_forms( $data, $id ); break; default: $this->die_bad_request(); } break; default : $this->die_bad_request(); break; } break; case 'entries' : // route = /entries/{id} switch ( $method ) { case 'GET': switch ( $collection2 ) { case 'fields' : // route = /entries/{id}/fields/{id2} $this->get_entries( $id, null, $schema, $id2 ); break; case '' : $this->get_entries( $id, null, $schema ); break; default : $this->die_bad_request(); } break; case 'DELETE' : $this->delete_entries( $id ); break; case 'PUT' : switch ( $collection2 ) { case 'properties' : // route = /entries/{id}/properties/{id2} $this->put_entry_properties( $data, $id ); break; case '' : $this->put_entries( $data, $id ); break; } break; case 'POST' : if ( false === empty( $id ) ) { $this->die_bad_request(); } $this->post_entries( $data ); break; default: $this->die_bad_request(); } break; case 'feeds' : switch ( $method ) { case 'GET' : $this->get_feeds( $id ); break; case 'DELETE' : if ( empty( $id ) ) { $this->die_bad_request(); } $this->delete_feeds( $id ); break; case 'PUT' : $this->put_feeds( $data, $id ); break; case 'POST' : if ( false === empty( $id ) ) { $this->die_bad_request(); } $this->post_feeds( $data ); break; default : $this->die_bad_request(); } break; default : $this->die_bad_request(); break; } $this->die_bad_request(); } public function authorize( $caps = array() ) { if ( GFCommon::current_user_can_any( $caps ) ) { GFCommon::add_api_call(); return true; } $this->die_forbidden(); } /** * Deletes a REST API key from an AJAX request. * * @since Unknown */ public function ajax_delete_key() { // Verify nonce. check_ajax_referer( 'gf_revoke_key' ); // Verify capabilities. if ( ! GFCommon::current_user_can_any( $this->_capabilities_settings_page ) ) { die(); } $key_id = rgpost( 'key' ); $this->delete_api_key( $key_id ); die( 0 ); } /** * Handle edit key related AJAX requests. * * @since 2.5 */ public function ajax_edit_key() { // If user does not have access to REST API settings, exit. if ( ! GFCommon::current_user_can_any( $this->_capabilities_settings_page ) ) { wp_send_json_error( array( 'message' => __( 'Access denied.', 'gravityforms' ) ) ); } // Handle get key requests. if ( rgget( 'key_id' ) ) { // Verify nonce. if ( ! wp_verify_nonce( rgget( 'nonce' ), 'gf_restapi_edit_key' ) ) { wp_send_json_error( array( 'message' => __( 'Access denied.', 'gravityforms' ) ) ); } // Get key. $key = $this->get_api_key( absint( rgget( 'key_id' ) ) ); // If key was not found, exit. if ( ! $key ) { wp_send_json_error( array( 'message' => __( 'Unable to retrieve key.', 'gravityforms' ) ) ); } // Return key, with truncated data. wp_send_json_success( array( 'key' => array( 'key_id' => rgobj( $key, 'key_id' ), 'description' => rgobj( $key, 'description' ), 'user_id' => rgobj( $key, 'user_id' ), 'permissions' => rgobj( $key, 'permissions' ), 'consumer_key' => substr( rgobj( $key, 'consumer_key' ), -7 ), 'last_access' => rgobj( $key, 'last_access' ) ? GFCommon::format_date( $key->last_access ) : __( 'Never Accessed', 'gravityforms' ), ), ) ); } // Handle save key requests. if ( isset( $_POST['key_id'] ) ) { // Verify nonce. if ( ! wp_verify_nonce( rgpost( 'nonce' ), 'gf_restapi_edit_key' ) ) { wp_send_json_error( array( 'message' => __( 'Access denied.', 'gravityforms' ) ) ); } // Prepare key data. $key = array( 'description' => sanitize_title( rgpost( 'description' ) ), 'user_id' => absint( rgpost( 'user_id' ) ), 'permissions' => GFCommon::whitelist( rgpost( 'permissions' ), array( 'read', 'write', 'read_write', ) ), ); // If description is empty, exit. if ( rgempty( 'description', $key ) ) { wp_send_json_error( array( 'message' => __( 'You must provide a description.', 'gravityforms' ) ) ); } // Save key. $saved = $this->update_api_key( absint( $_POST['key_id'] ), $key ); if ( rgempty( 'consumer_key', $saved ) ) { wp_send_json_success( array( 'message' => __( 'API Key successfully updated.', 'gravityforms' ) ) ); } else if ( rgar( $saved, 'consumer_key' ) ) { wp_send_json_success( array( 'message' => __( 'Make sure you have copied the consumer key and secret below. They will not be available once you leave this page.', 'gravityforms' ), 'key' => $saved ) ); } else { wp_send_json_error( array( 'message' => __( 'Unable to save API key.', 'gravityforms' ) ) ); } } wp_send_json_error( array( 'message' => __( 'Unable to process request.', 'gravityforms' ) ) ); } public static function get_api_keys() { global $wpdb; $table_name = GFFormsModel::get_rest_api_keys_table_name(); // If on a multi-site installation use the base database prefix so the query below uses the correct users table. if ( is_multisite() ) { $wpdb_prefix = $wpdb->base_prefix; } else { $wpdb_prefix = $wpdb->prefix; } $keys = $wpdb->get_results(" SELECT key_id, user_id, description, permissions, concat('...', substring( consumer_key, -7, 7 )) as 'key', u.user_login as user, last_access FROM {$table_name} k INNER JOIN {$wpdb_prefix}users u ON k.user_id = u.id ", ARRAY_A ); return $keys; } public function get_api_key( $key_id ) { global $wpdb; $table_name = GFFormsModel::get_rest_api_keys_table_name(); $key = $wpdb->get_row( $wpdb->prepare(" SELECT * FROM {$table_name} WHERE key_id=%d", $key_id ) ); return $key; } public function delete_api_key( $key_id ) { global $wpdb; $table_name = GFFormsModel::get_rest_api_keys_table_name(); $wpdb->query( $wpdb->prepare(" DELETE FROM {$table_name} WHERE key_id=%d ", $key_id ) ); } public function update_api_key( $key_id, $key ) { global $wpdb; if ( $key_id == 0 ) { $consumer_key = 'ck_' . $this->rand_hash(); $consumer_secret = 'cs_' . $this->rand_hash(); $key['consumer_key'] = self::api_hash( $consumer_key ); $key['consumer_secret'] = $consumer_secret; $key['truncated_key'] = substr( $consumer_key, -7 ); $wpdb->insert( GFFormsModel::get_rest_api_keys_table_name(), $key ); return array( 'consumer_key' => $consumer_key, 'consumer_secret' => $consumer_secret ); } else { unset( $key['last_access'] ); unset( $key['consumer_key'] ); unset( $key['consumer_secret'] ); unset( $key['truncated_key'] ); $wpdb->update( GFFormsModel::get_rest_api_keys_table_name(), $key, array( 'key_id' => $key_id ) ); return array( 'consumer_key' => '', 'consumer_secret' => '' ); } } //----- Feeds ------ public function get_feeds( $feed_ids, $form_id = null ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to get feeds via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_get_feeds', 'gravityforms_edit_forms' ); $this->authorize( $capability ); $addon_slug = rgget( 'addon' ); $output = GFAPI::get_feeds( $feed_ids, $form_id, $addon_slug ); if ( is_wp_error( $output ) ) { $this->die_not_found(); } $response = false === empty( $feed_ids ) && false === is_array( $feed_ids ) && is_array( $output ) ? array_shift( $output ) : ''; $this->end( 200, $response ); } public function delete_feeds( $feed_ids, $form_id = null ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to delete feeds via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_delete_feeds', 'gravityforms_edit_forms' ); $this->authorize( $capability ); $count = 0; if ( empty( $feed_ids ) ) { $feeds = GFAPI::get_feeds( null, $form_id ); foreach ( $feeds as $feed ) { $result = GFAPI::delete_feed( $feed['id'] ); if ( is_wp_error( $result ) ) { break; } $count ++; } } else { if ( is_array( $feed_ids ) ) { foreach ( $feed_ids as $feed_id ) { $result = GFAPI::delete_feed( $feed_id ); if ( is_wp_error( $result ) ) { break; } $count ++; } } else { $result = GFAPI::delete_feed( $feed_ids ); $count ++; } } if ( isset( $result ) && is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 200; $response = sprintf( __( 'Feeds deleted successfully: %d', 'gravityforms' ), $count ); } $this->end( $status, $response ); } public function put_feeds( $feed_data, $feed_id = null ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to update feeds via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_put_feeds', 'gravityforms_edit_forms' ); $this->authorize( $capability ); $count = 0; $result = array(); if ( empty( $feed_id ) ) { foreach ( $feed_data as $feed ) { //todo: validate feed id and form id $result = GFAPI::update_feed( $feed['id'], $feed['meta'], $feed['form_id'] ); if ( is_wp_error( $result ) ) { break; } $count ++; } } else { $result = GFAPI::update_feed( $feed_id, $feed_data['meta'], $feed_data['form_id'] ); $count ++; } if ( isset( $results ) && is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 200; $response = sprintf( __( 'Feeds updated: %d', 'gravityforms' ), $count ); } $this->end( $status, $response ); } public function post_feeds( $feeds, $form_id = null ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to create feeds via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_post_feeds', 'gravityforms_edit_forms' ); $this->authorize( $capability ); $feed_ids = array(); $result = array(); foreach ( $feeds as $feed ) { $addon_slug = isset( $feed['addon_slug'] ) ? $feed['addon_slug'] : rgget( 'addon' ); $f_id = empty( $form_id ) ? $feed['form_id'] : $form_id; if ( empty( $f_id ) ) { $result = new WP_Error( 'missing_form_id', __( 'Missing form id', 'gravityforms' ) ); break; } $result = GFAPI::add_feed( $f_id, $feed['meta'], $addon_slug ); if ( is_wp_error( $result ) ) { break; } $feed_ids[] = $result; } if ( is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 201; $response = $feed_ids; } $this->end( $status, $response ); } //----- Form Submissions ---- public function submit_form( $data, $id ) { $this->log_debug( __METHOD__ . '(): Running.' ); $form_id = absint( $id ); if ( $form_id < 1 ) { $this->die_bad_request(); } if ( empty( $data['input_values'] ) ) { $this->die_bad_request(); } $field_values = isset( $data['field_values'] ) ? $data['field_values'] : array(); $target_page = isset( $data['target_page'] ) ? $data['target_page'] : 0; $source_page = isset( $data['source_page'] ) ? $data['source_page'] : 1; add_filter( 'gform_require_login', '__return_false' ); $result = GFAPI::submit_form( $form_id, $data['input_values'], $field_values, $target_page, $source_page ); if ( is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { if ( ! $this->current_user_can_any( array( 'gravityforms_view_entries', 'gravityforms_edit_entries', ) ) ) { unset( $result['entry_id'] ); } $status = 200; $response = $result; } $this->end( $status, $response ); } //----- Forms ------ public function delete_forms( $form_ids ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to delete forms via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_delete_forms', 'gravityforms_delete_forms' ); $this->authorize( $capability ); $count = 0; if ( is_array( $form_ids ) ) { foreach ( $form_ids as $form_id ) { $result = GFAPI::delete_form( $form_id ); if ( is_wp_error( $result ) ) { break; } $count ++; } } else { $result = GFAPI::delete_form( $form_ids ); $count ++; } if ( isset( $result ) && is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 200; $response = sprintf( __( 'Forms deleted successfully: %d', 'gravityforms' ), $count ); } $this->end( $status, $response ); } public function post_entries( $data, $form_id = null ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to create entries via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_post_entries', 'gravityforms_edit_entries' ); $this->authorize( $capability ); $entries = array(); foreach ( $data as $entry ) { $entries[] = $this->maybe_serialize_list_fields( $entry, $form_id ); } $result = GFAPI::add_entries( $entries, $form_id ); if ( is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 201; $response = $result; } $this->end( $status, $response ); } public function put_entries( $data, $entry_id = null ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to update entries via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_put_entries', 'gravityforms_edit_entries' ); $this->authorize( $capability ); $entries = array(); if ( empty( $entry_id ) ) { foreach ( $data as $entry ) { $entries[] = $this->maybe_serialize_list_fields( $entry ); } $result = GFAPI::update_entries( $entries ); } else { $entry = $this->maybe_serialize_list_fields( $data ); $result = GFAPI::update_entry( $entry, $entry_id ); } if ( is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 200; $response = empty( $entry_id ) ? __( 'Entries updated successfully', 'gravityforms' ) : __( 'Entry updated successfully', 'gravityforms' ); } $this->end( $status, $response ); } public function put_forms_properties( $property_values, $form_id ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to update form properties via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_put_forms_properties', 'gravityforms_edit_forms' ); $this->authorize( $capability ); foreach ( $property_values as $key => $property_value ) { $result = GFAPI::update_form_property( $form_id, $key, $property_value ); if ( is_wp_error( $result ) ) { break; } } if ( is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 200; $response = __( 'Success', 'gravityforms' ); } $this->end( $status, $response ); } public function put_entry_properties( $property_values, $entry_id ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to update entry properties via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_put_entries_properties', 'gravityforms_edit_entries' ); $this->authorize( $capability ); if ( is_array( $property_values ) ) { foreach ( $property_values as $key => $property_value ) { $result = GFAPI::update_entry_property( $entry_id, $key, $property_value ); if ( is_wp_error( $result ) ) { break; } } if ( is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 200; $response = __( 'Success', 'gravityforms' ); } } else { $status = 400; if ( empty( $property_values ) ) { $response = __( 'No property values were found in the request body', 'gravityforms' ); } else { $response = __( 'Property values should be sent as an array', 'gravityforms' ); } } $this->end( $status, $response ); } public function post_forms( $data ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to create forms via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_post_forms', 'gravityforms_create_form' ); $this->authorize( $capability ); $form_ids = GFAPI::add_forms( $data ); if ( is_wp_error( $form_ids ) || count( $form_ids ) == 0 ) { $response = $this->get_error_response( $form_ids ); $status = $this->get_error_status( $form_ids ); } else { $status = 201; $response = $form_ids; } $this->end( $status, $response ); } public function put_forms( $data, $form_id = null ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to update forms via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_put_forms', 'gravityforms_create_form' ); $this->authorize( $capability ); if ( empty( $form_id ) ) { $result = GFAPI::update_forms( $data ); } else { $result = GFAPI::update_form( $data, $form_id ); } if ( is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 200; $response = empty( $form_id ) ? __( 'Forms updated successfully', 'gravityforms' ) : __( 'Form updated successfully', 'gravityforms' ); } $this->end( $status, $response ); } public function delete_entries( $entry_ids ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to delete entries via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_delete_entries', 'gravityforms_delete_entries' ); $this->authorize( $capability ); $count = 0; if ( is_array( $entry_ids ) ) { foreach ( $entry_ids as $entry_id ) { $this->log_debug( __METHOD__ . '(): Deleting entry id ' . $entry_id ); $result = GFAPI::delete_entry( $entry_id ); if ( is_wp_error( $result ) ) { break; } $count ++; } } else { $result = GFAPI::delete_entry( $entry_ids ); $count ++; } if ( isset( $result ) && is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } else { $status = 200; $response = sprintf( __( 'Entries deleted successfully: %d', 'gravityforms' ), $count ); } $this->end( $status, $response ); } public function get_entries( $entry_ids, $form_ids = null, $schema = '', $field_ids = array() ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to get entries via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_get_entries', 'gravityforms_view_entries' ); $this->authorize( $capability ); $status = 200; $response = array(); $result = array(); if ( $entry_ids ) { if ( is_array( $entry_ids ) ) { foreach ( $entry_ids as $entry_id ) { $result = GFAPI::get_entry( $entry_id ); if ( ! is_wp_error( $result ) ) { $result = $this->maybe_json_encode_list_fields( $result ); $response[ $entry_id ] = $result; if ( ! empty( $field_ids ) && ( ! empty( $response[ $entry_id ] ) ) ) { $response[ $entry_id ] = $this->filter_entry_object( $response[ $entry_id ], $field_ids ); } } } } else { $result = GFAPI::get_entry( $entry_ids ); if ( ! is_wp_error( $result ) ) { $result = $this->maybe_json_encode_list_fields( $result ); $response = $result; if ( ! empty( $field_ids ) && ( ! empty( $response ) ) ) { $response = $this->filter_entry_object( $response, $field_ids ); } } } if ( $schema == 'mtd' ) { $response = self::mtd_transform_entry_data( $response ); } } else { // Sorting parameters $sort_key = isset( $_GET['sorting']['key'] ) && ! empty( $_GET['sorting']['key'] ) ? $_GET['sorting']['key'] : 'id'; $sort_dir = isset( $_GET['sorting']['direction'] ) && ! empty( $_GET['sorting']['direction'] ) ? $_GET['sorting']['direction'] : 'DESC'; $sorting = array( 'key' => $sort_key, 'direction' => $sort_dir ); if ( isset( $_GET['sorting']['is_numeric'] ) ) { $sorting['is_numeric'] = $_GET['sorting']['is_numeric']; } // Paging parameters $page_size = isset( $_GET['paging']['page_size'] ) ? intval( $_GET['paging']['page_size'] ) : 10; if ( isset( $_GET['paging']['current_page'] ) ) { $current_page = intval( $_GET['paging']['current_page'] ); $offset = $page_size * ( $current_page - 1 ); } else { $offset = isset( $_GET['paging']['offset'] ) ? intval( $_GET['paging']['offset'] ) : 0; } $paging = array( 'offset' => $offset, 'page_size' => $page_size ); if ( isset( $_GET['search'] ) ) { $search = $_GET['search']; if ( ! is_array( $search ) ) { $search = urldecode( ( stripslashes( $search ) ) ); $search = json_decode( $search, true ); } } else { $search = array(); } if ( empty( $form_ids ) ) { $form_ids = 0; } // all forms $entry_count = GFAPI::count_entries( $form_ids, $search ); $result = $entry_count > 0 ? GFAPI::get_entries( $form_ids, $search, $sorting, $paging ) : array(); if ( ! is_wp_error( $result ) ) { foreach ( $result as &$entry ) { $entry = $this->maybe_json_encode_list_fields( $entry ); } $response = array( 'total_count' => $entry_count, 'entries' => $result ); if ( $schema == 'mtd' ) { $response = $this->mtd_transform_entries_data( $response, $form_ids ); } } } if ( is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } $this->end( $status, $response ); } public static function filter_entry_object( $entry, $field_ids ) { if ( ! is_array( $field_ids ) ) { $field_ids = array( $field_ids ); } $new_entry = array(); foreach ( $entry as $key => $val ) { if ( in_array( $key, $field_ids ) || ( is_numeric( $key ) && in_array( intval( $key ), $field_ids ) ) ) { $new_entry[ $key ] = $val; } } return $new_entry; } public function get_forms( $form_ids = null, $schema = '' ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to get form details via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_get_forms', 'gravityforms_edit_forms' ); $this->authorize( $capability ); $status = 200; $response = array(); if ( empty( $form_ids ) ) { $forms = RGFormsModel::get_forms( true ); foreach ( $forms as $form ) { /** * Allows third-party code to omit form totals from the API response. This is useful for increasing * the performance of the endpoint when totals aren't required. * * @since 2.5 * * @var bool $include_totals Whether to include totals; defaults to true. * @var object $form The current form object. */ $include_totals = gf_apply_filters( array( 'gform_web_api_retrieve_form_totals', $form->id ), true, $form ); $form_id = $form->id; $form_info = array( 'id' => $form_id, 'title' => $form->title, ); if ( $include_totals ) { $totals = GFFormsModel::get_form_counts( $form_id ); $form_info['entries'] = rgar( $totals, 'total' ); } $response[ $form_id ] = $form_info; } if ( $schema == 'mtd' ) { $response = $this->mtd_transform_forms_data( $response ); } } else { if ( is_array( $form_ids ) ) { foreach ( $form_ids as $form_id ) { $response[ $form_id ] = GFAPI::get_form( $form_id ); } } else { $result = GFAPI::get_form( $form_ids ); if ( is_wp_error( $result ) ) { $response = $this->get_error_response( $result ); $status = $this->get_error_status( $result ); } elseif ( ! $result ) { $this->die_not_found(); } else { $response = $result; } } } $this->end( $status, $response ); } public function maybe_json_encode_list_fields( $entry ) { $form_id = $entry['form_id']; $form = GFAPI::get_form( $form_id ); if ( ! empty ( $form['fields'] ) && is_array( $form['fields'] ) ) { foreach ( $form['fields'] as $field ) { /* @var GF_Field $field */ if ( $field->get_input_type() == 'list' ) { $new_value = maybe_unserialize( $entry[ $field->id ] ); if ( ! $this->is_json( $new_value ) ) { $new_value = json_encode( $new_value ); } $entry[ $field->id ] = $new_value; } } } return $entry; } public function maybe_serialize_list_fields( $entry, $form_id = null ) { if ( empty( $form_id ) ) { $form_id = $entry['form_id']; } $form = GFAPI::get_form( $form_id ); if ( ! empty ( $form['fields'] ) && is_array( $form['fields'] ) ) { foreach ( $form['fields'] as $field ) { /* @var GF_Field $field */ if ( $field->get_input_type() == 'list' ) { $new_list_value = $this->maybe_decode_json( $entry[ $field->id ] ); if ( ! is_serialized( $new_list_value ) ) { $new_list_value = serialize( $new_list_value ); } $entry[ $field->id ] = $new_list_value; } } } return $entry; } // RESULTS public function get_results_cache_key( $form_id, $fields, $search_criteria ) { $key = $this->get_results_cache_key_prefix( $form_id ); $key .= wp_hash( json_encode( $fields ) . json_encode( $search_criteria ) ); return $key; } public function get_results_cache_key_prefix( $form_id ) { global $blog_id; $key = is_multisite() ? $blog_id . '-' : ''; $key .= sprintf( '%s-cache-%s-', $this->get_slug(), $form_id ); // The option_name column in the options table has a max length of 64 chars. // Truncate the key if it's too long for column and allow space for the 'tmp' prefix $key = substr( $key, 0, 60 ); return $key; } public function update_entry_status( $lead_id ) { $lead = RGFormsModel::get_lead( $lead_id ); $form_id = $lead['form_id']; $form = GFFormsModel::get_form_meta( $form_id ); $this->maybe_update_results_cache_meta( $form ); } public function entry_updated( $form, $lead_id ) { $this->maybe_update_results_cache_meta( $form ); } public function entry_created( $entry, $form ) { $this->maybe_update_results_cache_meta( $form ); } public function after_save_form( $form, $is_new ) { if ( $is_new ) { return; } $form_id = $form['id']; // only need cache meta when a cache exists if ( false === $this->results_cache_exists( $form_id ) ) { return; } $fields = rgar( $form, 'fields' ); $current_fields_hash = wp_hash( json_encode( $fields ) ); $cache_meta = $this->get_results_cache_meta( $form_id ); $cached_fields_hash = rgar( $cache_meta, 'fields_hash' ); if ( $current_fields_hash !== $cached_fields_hash ) { // delete the meta for this form $this->delete_results_cache_meta( $form_id ); // delete all cached results for this form $this->delete_cached_results( $form_id ); } } public function results_cache_exists( $form_id ) { global $wpdb; $key = $this->get_results_cache_key_prefix( $form_id ); $key = '%' . GFCommon::esc_like( $key ) . '%'; $sql = $wpdb->prepare( "SELECT count(option_id) FROM $wpdb->options WHERE option_name LIKE %s", $key ); $result = $wpdb->get_var( $sql ); return $result > 0; } public function delete_cached_results( $form_id ) { global $wpdb; $form = GFAPI::get_form( $form_id ); if ( ! ( $form ) || ! is_array( $form ) ) { return; } $key = $this->get_results_cache_key_prefix( $form_id ); $key = '%' . GFCommon::esc_like( $key ) . '%'; $sql = $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name LIKE %s", $key ); $result = $wpdb->query( $sql ); return $result; } // When entries are added or updated the cache needs to be expired and rebuilt. // This cache meta records the last updated time for each form and a hash of the fields array. // Each time results are requested this value is checked to make sure the cache is still valid. public function maybe_update_results_cache_meta( $form ) { $form_id = $form['id']; // only need to expire the cache when a cache already exists if ( false === $this->results_cache_exists( $form_id ) ) { return; } $this->update_results_cache_meta( $form_id, rgar( $form, 'fields' ) ); } public function update_results_cache_meta( $form_id, $fields, $expiry = null ) { if ( empty( $expiry ) ) { $expiry = time(); } $data = array( 'fields_hash' => wp_hash( json_encode( $fields ) ), 'timestamp' => $expiry, ); $key = $this->get_results_cache_meta_key( $form_id ); $this->update_results_cache( $key, $data ); } public function delete_results_cache_meta( $form_id ) { $key = $this->get_results_cache_meta_key( $form_id ); delete_option( $key ); } public function get_results_cache_meta_key( $form_id ) { global $blog_id; $key = is_multisite() ? $blog_id . '-' : ''; $key .= 'gfresults-cache-meta-form-' . $form_id; return $key; } public function get_results_cache_meta( $form_id ) { $key = $this->get_results_cache_meta_key( $form_id ); $cache_meta = get_option( $key ); return $cache_meta; } public function update_results_cache( $key, $data ) { delete_option( $key ); $result = add_option( $key, $data, '', 'no' ); return $result; } // Recursive wp_cron task to continue the calculation of results public function results_cron( $form, $fields, $search_criteria ) { $form_id = $form['id']; $key = $this->get_results_cache_key( $form_id, $fields, $search_criteria ); $key_tmp = 'tmp' . $key; $state = get_option( $key_tmp, array() ); if ( ! empty( $state ) ) { if ( ! class_exists( 'GFResults' ) ) { require_once( GFCommon::get_base_path() . '/includes/addon/class-gf-results.php' ); } $gf_results = new GFResults( $this->get_slug(), array() ); $results = $gf_results->get_results_data( $form, $fields, $search_criteria, $state ); if ( 'complete' == $results['status'] ) { if ( isset( $results['progress'] ) ) { unset( $results['progress'] ); } $this->update_results_cache( $key, $results ); if ( false == empty( $state ) ) { delete_option( $key_tmp ); } } else { $this->update_results_cache( $key_tmp, $results ); $data = get_option( $key ); if ( $data ) { $data['progress'] = $results['progress']; $this->update_results_cache( $key, $data ); } $this->schedule_results_cron( $form, $fields, $search_criteria ); } } } // Returns an array with the results for all the fields in the form. // If the results can be calculated within the time allowed in GFResults then the results are returned and nothing is cached. // If the calculation has not finished then a single recursive wp_cron task will be scheduled for immediate execution. // While the cache is being built by the wp_cron task this function will return the expired cache results if available or the latest step in the cache build. // Add-On-specific results are not included e.g. grade frequencies in the Quiz Add-On. public function get_results( $form_id ) { $this->log_debug( __METHOD__ . '(): Running.' ); /** * Filters the capability required to get form results via the web API. * * @since 1.9.2 */ $capability = apply_filters( 'gform_web_api_capability_get_results', 'gravityforms_view_entries' ); $this->authorize( $capability ); $s = rgget( 's' ); // search criteria $search_criteria = false === empty( $s ) && is_array( $s ) ? $s : array(); $form = GFAPI::get_form( $form_id ); if ( ! $form ) { self::die_not_found(); } // for the Web API return all fields $fields = rgar( $form, 'fields' ); $form_id = $form['id']; $key = $this->get_results_cache_key( $form_id, $fields, $search_criteria ); $key_tmp = 'tmp' . $key; $data = get_option( $key, array() ); $cache_meta = $this->get_results_cache_meta( $form_id ); // add the cache meta early so form editor updates can test for valid field hash if ( empty( $cache_meta ) ) { $this->update_results_cache_meta( $form_id, $fields, 0 ); } $cache_expiry = rgar( $cache_meta, 'timestamp' ); $cache_timestamp = isset( $data['timestamp'] ) ? $data['timestamp'] : 0; $cache_expired = $cache_expiry ? $cache_expiry > $cache_timestamp : false; // check for valid cached results first if ( ! empty( $data ) && 'complete' == rgar( $data, 'status' ) && ! $cache_expired ) { $results = $data; $status = 200; if ( isset( $results['progress'] ) ) { unset( $results['progress'] ); } } else { $state = get_option( $key_tmp ); if ( empty( $state ) || ( 'complete' == rgar( $data, 'status' ) && $cache_expired ) ) { if ( ! class_exists( 'GFResults' ) ) { require_once( GFCommon::get_base_path() . '/includes/addon/class-gf-results.php' ); } $gf_results = new GFResults( $this->get_slug(), array() ); $max_execution_time = 5; $results = $gf_results->get_results_data( $form, $fields, $search_criteria, $state, $max_execution_time ); if ( 'complete' == rgar( $data, 'status' ) ) { $status = 200; if ( false == empty( $state ) ) { delete_option( $key_tmp ); } } else { if ( false === empty( $data ) && 'complete' == rgar( $data, 'status' ) && $cache_expired ) { $data['status'] = 'expired'; $data['progress'] = $results['progress']; $this->update_results_cache( $key, $data ); } $this->update_results_cache( $key_tmp, $results ); $this->schedule_results_cron( $form, $fields, $search_criteria ); if ( $data ) { $results = $data; } $status = 202; } } else { // The cron task is recursive, not periodic, so system restarts, script timeouts and memory issues can prevent the cron from restarting. // Check timestamp and kick off the cron again if it appears to have stopped $state_timestamp = rgar( $state, 'timestamp' ); $state_age = time() - $state_timestamp; if ( $state_age > 180 && ! $this->results_cron_is_scheduled( $form, $fields, $search_criteria ) ) { $this->schedule_results_cron( $form, $fields, $search_criteria ); } if ( false === empty( $data ) && 'expired' == rgar( $data, 'status' ) ) { $results = $data; } else { $results = $state; } $status = 202; } } $fields = rgar( $results, 'field_data' ); if ( ! empty( $fields ) ) { // add choice labels to the results so the client doesn't need to cross-reference with the form object $results['field_data'] = $this->results_data_add_labels( $form, $fields ); } $this->end( $status, $results ); } public function schedule_results_cron( $form, $fields, $search_criteria, $delay_in_seconds = 10 ) { // reduces problems with concurrency wp_cache_delete( 'alloptions', 'options' ); $args = array( $form, $fields, $search_criteria ); wp_schedule_single_event( time() + $delay_in_seconds, $this->get_results_cron_hook(), $args ); } public function results_cron_is_scheduled( $form, $fields, $search_criteria ) { $args = array( $form, $fields, $search_criteria ); return wp_next_scheduled( $this->get_results_cron_hook(), $args ); } public function get_results_cron_hook() { return 'gravityforms_results_cron_' . $this->get_slug(); } public function results_data_add_labels( $form, $fields ) { // replace the values/ids with text labels foreach ( $fields as $field_id => $choice_counts ) { $field = GFFormsModel::get_field( $form, $field_id ); $type = $field->get_input_type(); if ( is_array( $choice_counts ) ) { $i = 0; foreach ( $choice_counts as $choice_value => $choice_count ) { if ( class_exists( 'GFSurvey' ) && 'likert' == $type && rgar( $field, 'gsurveyLikertEnableMultipleRows' ) ) { $row_text = GFSurvey::get_likert_row_text( $field, $i ++ ); $counts_for_row = array(); foreach ( $choice_count as $col_val => $col_count ) { $text = GFSurvey::get_likert_column_text( $field, $choice_value . ':' . $col_val ); $counts_for_row[ $col_val ] = array( 'text' => $text, 'data' => $col_count ); } $counts_for_row[ $choice_value ]['data'] = $counts_for_row; $fields[ $field_id ][ $choice_value ] = array( 'text' => $row_text, 'value' => "$choice_value", 'count' => $counts_for_row ); } else { $text = GFFormsModel::get_choice_text( $field, $choice_value ); $fields[ $field_id ][ $choice_value ] = array( 'text' => $text, 'value' => "$choice_value", 'count' => $choice_count ); } } } } return $fields; } // ----- end RESULTS private function authenticate() { $this->log_debug( __METHOD__ . '(): Running.' ); if ( isset( $_REQUEST['_gf_json_nonce'] ) && is_user_logged_in() ) { $this->log_debug( __METHOD__ . '(): Using WP cookie authentication.' ); // WordPress cookie authentication for plugins and themes on this server. check_admin_referer( 'gf_api', '_gf_json_nonce' ); return true; } $authenticated = false; if ( isset( $_GET['api_key'] ) ) { $this->log_debug( __METHOD__ . '(): API Key found in request.' ); // Signatures required for external requests if ( rgget( 'api_key' ) == $this->_public_key ) { if ( self::check_signature() ) { $authenticated = true; } } } if ( $authenticated ) { $settings = get_option( 'gravityformsaddon_gravityformswebapi_settings' ); if ( empty( $settings ) || ! $settings['enabled'] ) { $authenticated = false; } else { $this->log_debug( __METHOD__ . '(): Switching to impersonation account.' ); $account_id = $settings['impersonate_account']; wp_set_current_user( $account_id ); } } if ( ! $authenticated ) { $this->log_debug( __METHOD__ . '(): Could not authenticate, permission denied.' ); $this->die_permission_denied(); } } private function check_signature() { if ( false === GFWEBAPI_REQUIRE_SIGNATURE ) { return true; } $this->log_debug( __METHOD__ . '(): Running.' ); $expires = (int) rgget( 'expires' ); $api_key = rgget( 'api_key' ); $path = strtolower( get_query_var( GFWEBAPI_ROUTE_VAR ) ); $method = strtoupper( $_SERVER['REQUEST_METHOD'] ); $signature = rgget( 'signature' ); $string_to_check = sprintf( '%s:%s:%s:%s', $api_key, $method, $path, $expires ); $calculated_sig = $this->calculate_signature( $string_to_check ); if ( time() >= $expires ) { $this->log_debug( __METHOD__ . '(): result = expired.' ); return false; } $is_valid = $signature == $calculated_sig || $signature == rawurlencode( $calculated_sig ); $this->log_debug( __METHOD__ . '(): result = ' . var_export( $is_valid, 1 ) ); return $is_valid; } private function calculate_signature( $string ) { $hash = hash_hmac( 'sha1', $string, $this->_private_key, true ); $sig = base64_encode( $hash ); return $sig; } public static function end( $status, $response ) { $output['status'] = $status; $output['response'] = $response; // PHP > 5.3 if ( function_exists( 'header_remove' ) && ! headers_sent() ) { header_remove( 'X-Pingback' ); } header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ), true ); $output_json = json_encode( $output ); echo $output_json; die(); } public function die_not_authorized() { $this->log_debug( __METHOD__ . '(): Running.' ); $this->end( 401, __( 'Not authorized', 'gravityforms' ) ); } public function die_permission_denied() { $this->log_debug( __METHOD__ . '(): Running.' ); $this->end( 401, __( 'Permission denied', 'gravityforms' ) ); } public function die_forbidden() { $this->log_debug( __METHOD__ . '(): Running.' ); $this->end( 403, __( 'Forbidden', 'gravityforms' ) ); } public function die_bad_request() { $this->log_debug( __METHOD__ . '(): Running.' ); $this->end( 400, __( 'Bad request', 'gravityforms' ) ); } public function die_not_found() { $this->log_debug( __METHOD__ . '(): Running.' ); $this->end( 404, __( 'Not found', 'gravityforms' ) ); } public function die_not_implemented() { $this->log_debug( __METHOD__ . '(): Running.' ); $this->end( 501, __( 'Not implemented', 'gravityforms' ) ); } public function die_error() { $this->log_debug( __METHOD__ . '(): Running.' ); $this->end( 500, __( 'Internal Error', 'gravityforms' ) ); } public function get_error_response( $wp_error ) { $response['code'] = $wp_error->get_error_code(); $response['message'] = $wp_error->get_error_message(); $data = $wp_error->get_error_data(); if ( $data ) { $response['data'] = $data; } return $response; } public function get_error_status( $wp_error ) { $error_code = $wp_error->get_error_code(); $mappings = array( 'not_found' => 404, 'not_allowed' => 401, ); $http_code = isset( $mappings[ $error_code ] ) ? $mappings[ $error_code ] : 400; return $http_code; } public static function get_form_metas() { $form_ids = array(); $forms = RGFormsModel::get_forms( true ); foreach ( $forms as $form ) { $form_ids[] = $form->id; } $form_metas = GFFormsModel::get_form_meta_by_id( $form_ids ); return $form_metas; } public static function ajax_qrcode() { require_once GFCommon::get_base_path() . '/includes/phpqrcode/phpqrcode.php'; $settings = get_option( 'gravityformsaddon_gravityformswebapi_settings' ); if ( empty( $settings ) ) { die(); } if ( ! GFAPI::current_user_can_any( 'gravityforms_api_settings' ) ) { die(); } $data['url'] = site_url(); $data['name'] = get_bloginfo(); $data['public_key'] = rgar( $settings, 'public_key' ); $data['private_key'] = rgar( $settings, 'private_key' ); QRcode::png( json_encode( $data ), false, QR_ECLEVEL_L, 4, 1, false ); die(); } /** * Support for MonoTouch.Dialog */ // todo: support array of form ids public function mtd_transform_entries_data( $output, $form_id ) { $form = GFFormsModel::get_form_meta( $form_id ); $form_element = array(); $form_element['title'] = $form['title']; $form_element['type'] = 'root'; $form_element['id'] = 'id-form-' . $form_id; $form_element['count'] = rgar( $output, 'total_count' ); $entries = rgar( $output, 'entries' ); $section['header'] = 'Entries'; $entry_elements = array(); if ( is_array( $entries ) ) { foreach ( $entries as $entry ) { $entry_element['type'] = 'root'; $entry_element['title'] = $entry['id'] . ': ' . $entry['date_created']; $entry_element['id'] = $entry['id']; $entry_element['url'] = GFWEBAPI_API_BASE_URL . '/entries/' . rgar( $entry, 'id' ) . '?schema=mtd'; $entry_elements[] = $entry_element; } } $section['elements'] = $entry_elements; $form_element['sections'][] = $section; return $form_element; } public function mtd_transform_forms_data( $forms ) { $data = array(); $data['title'] = 'Forms'; $data['type'] = 'root'; $data['id'] = 'forms'; foreach ( $forms as $form ) { $element = array(); $element['title'] = $form['title']; $element['type'] = 'root'; $element['id'] = 'id-form-' . $form['id']; $element['url'] = GFWEBAPI_API_BASE_URL . '/forms/' . $form['id'] . '/entries.json?schema=mtd'; $section = array(); $section['elements'][] = $element; $data['sections'][] = $section; } return $data; } public static function mtd_transform_entry_data( $entry ) { $data = array(); $root_element['type'] = 'root'; $root_element['title'] = $entry['id'] . ': ' . $entry['date_created']; $root_element['id'] = 'id-entry-' . $entry['id']; $form_id = rgar( $entry, 'form_id' ); $form = RGFormsModel::get_form_meta( $form_id ); $fields = $form['fields']; foreach ( $fields as $field ) { $field_data = array(); $field_data['header'] = $field->label; $elements = array(); $value = RGFormsModel::get_lead_field_value( $entry, $field ); if ( is_array( $value ) && isset( $field->choices ) ) { $choices = $field->choices; foreach ( $choices as $choice ) { $found = false; foreach ( $value as $item ) { if ( $item == rgar( $choice, 'value' ) ) { $found = true; break; } } $element = array(); $element['type'] = 'checkbox'; $element['caption'] = $choice['text']; $element['value'] = $found; $elements[] = $element; } } else { $element = array(); $element['type'] = 'string'; $element['caption'] = GFFormsModel::get_choice_text( $field, $value ); $elements[] = $element; } $field_data['elements'] = $elements; $data[] = $field_data; } $root_element['sections'] = $data; return $root_element; } /** * Generate a rand hash. * * @since 2.4-beta-1 * @since 2.5 add a fallback generation method in case openssl_random_pseudo_bytes() returns empty. * * @return string */ public function rand_hash() { $hash = ''; if ( function_exists( 'openssl_random_pseudo_bytes' ) ) { $hash = bin2hex( openssl_random_pseudo_bytes( 20 ) ); } if ( empty( $hash ) ) { $hash = sha1( wp_rand() ); } return $hash; } /** * Hashes specified text. * * @since 2.4-beta-1 * * @param string $data Message to be hashed. * @return string Hashed data */ public static function api_hash( $data ) { return hash_hmac( 'sha256', $data, 'gf-api' ); } } GFWebAPI::get_instance(); }