diff --git a/class-gf-zapier.php b/class-gf-zapier.php new file mode 100644 index 0000000..bbb4de7 --- /dev/null +++ b/class-gf-zapier.php @@ -0,0 +1,1668 @@ +add_delayed_payment_support( + array( + 'option_label' => esc_html__( 'Send feed to Zapier only when payment is received.', 'gravityformszapier' ), + ) + ); + } + + /** + * Includes the GF REST API, if not already included. + * + * @since 4.1 + */ + public function init_rest_api() { + if ( class_exists( 'GF_REST_Controller' ) ) { + return; + } + + if ( is_callable( array( 'GFWebAPI', 'get_instance' ) ) ) { + GFWebAPI::get_instance()->init_v2(); + } else { + ( new GFWebAPI() )->init_v2(); + } + } + + /** + * Registers the REST API endpoints. + * + * @since 4.0 + */ + public function register_rest_routes() { + $this->init_rest_api(); + + require_once plugin_dir_path( __FILE__ ) . 'includes/rest/class-zapier-controller.php'; + require_once plugin_dir_path( __FILE__ ) . 'includes/rest/class-requirements-controller.php'; + require_once plugin_dir_path( __FILE__ ) . 'includes/rest/class-sample-entry-controller.php'; + require_once plugin_dir_path( __FILE__ ) . 'includes/rest/class-sample-entries-controller.php'; + require_once plugin_dir_path( __FILE__ ) . 'includes/rest/class-transfer-entries-controller.php'; + require_once plugin_dir_path( __FILE__ ) . 'includes/rest/class-feeds-controller.php'; + + $controllers = array( + REST\Requirements_Controller::class, + REST\Sample_Entry_Controller::class, + REST\Sample_Entries_Controller::class, + REST\Transfer_Entries_Controller::class, + REST\Feeds_Controller::class, + ); + + foreach ( $controllers as $controller ) { + $controller_obj = new $controller(); + $controller_obj->register_routes(); + } + } + + /** + * For form submissions made by Zapier, populates the product name and product price inputs of single product and + * hidden product fields. + * + * @since 4.0 + * + * @param array $form Current form object. + * + * @return array + */ + public function populate_product_inputs_submission( $form ) { + + // Ignore requests that did not come from Zapier. + if ( ! $this->is_zapier_request() ) { + return $form; + } + + $product_inputs = $this->get_product_inputs( $form ); + + foreach ( $product_inputs as $field_id => $product ) { + + $_POST["input_{$field_id}_1"] = $product['name']; + $_POST["input_{$field_id}_2"] = $product['price']; + } + + return $form; + } + + + /** + * For request made by Zapier, populates the product name and product price inputs of single product and hidden product fields, and updates the entry with those new values. + * + * @since 4.0 + * + * @param array $entry Current entry object. + * @param array $form Current form object. + */ + public function populate_product_inputs_add_entry( $entry, $form ) { + + // Ignore requests that did not come from Zapier. + if ( ! $this->is_zapier_request() ) { + return; + } + + $product_inputs = $this->get_product_inputs( $form, $entry ); + if ( empty( $product_inputs ) ) { + return; + } + + foreach ( $product_inputs as $field_id => $product ) { + + $entry["{$field_id}.1"] = $product['name']; + $entry["{$field_id}.2"] = $product['price']; + } + + GFAPI::update_entry( $entry ); + } + + /** + * Determines whether the current request was made by the Zapier App. + * + * @since 4.0 + * + * @return bool Returns true if the current request was made by the Gravity Forms Zapier App. Returns false otherwise + */ + public function is_zapier_request() { + + return rgar( $_SERVER, 'HTTP_X_APPLICATION_SOURCE' ) == 'Zapier Integration'; + } + + /** + * Check to see if there are legacy feeds. + * Used to filter the feed list, control menu links. + * + * @since 4.0 + * + * @param null|int $form_id The form ID. + * + * @return bool + */ + public function has_legacy_feeds( $form_id = null ) { + $feeds = $this->get_feeds( $form_id ); + foreach ( $feeds as $feed ) { + $is_legacy = $this->is_legacy_feed( $feed ); + $this->log_debug( 'legacy check ' . $is_legacy ); + if ( $is_legacy ) { + $this->log_debug( 'there are legacy feeds for feed id ' . $feed['id'] . ', returning true' ); + return true; + } + } + $this->log_debug( 'no legacy found' ); + + return false; + } + + /** + * Check if a feed (or current feed) is legacy. + * + * @since 4.0 + * + * @param array $feed GF Feed Array. + * + * @return bool + */ + public function is_legacy_feed( $feed = null ) { + if ( null === $feed ) { + $feed = $this->get_current_feed(); + } + + return (bool) rgar( $feed['meta'], 'legacy' ); + } + + /** + * Remove items from Feed Table when Feeds are hidden. + * + * @since 4.0 + * + * @param array $form GF Form array. + * + * @return GFAddOnFeedsTable + */ + public function get_feed_table( $form ) { + $feeds = $this->get_feeds( rgar( $form, 'id' ) ); + + // Disable rendering of feeds unless toggled true. + if ( ! $this->should_display_feeds() ) { + $feeds = array(); + } + + $columns = $this->feed_list_columns(); + $column_value_callback = array( $this, 'get_column_value' ); + $bulk_actions = $this->get_bulk_actions(); + $action_links = $this->get_action_links(); + $no_item_callback = array( $this, 'feed_list_no_item_message' ); + $message_callback = '__return_false'; + + require_once $this->get_base_path() . '/includes/class-feeds-list-table.php'; + + return new GF_Zapier_Feeds_List_Table( $feeds, $this->_slug, $columns, $bulk_actions, $action_links, $column_value_callback, $no_item_callback, $message_callback, $this ); + } + + // # FEED SETTINGS ------------------------------------------------------------------------------------------------- + + /** + * Restores the previous value of the given field. + * + * @since 4.1 + * + * @param array $field The current field. + * + * @return string|null + */ + public function restore_previous_value( $field ) { + $name = rgar( $field, 'name' ); + $value = rgar( $this->get_previous_settings(), $name ); + + if ( ! $this->is_gravityforms_supported( '2.5-rc-1' ) ) { + global $_gaddon_posted_settings; + $_gaddon_posted_settings[ $name ] = $value; + } + + return $value; + } + + /** + * Setup fields for feed settings. + * + * @since 4.0 + * + * @return array + */ + public function feed_settings_fields() { + $feed_name = array( + 'label' => esc_html__( 'Name', 'gravityformszapier' ), + 'name' => 'feedName', + 'type' => 'text', + 'class' => 'medium', + 'required' => true, + 'tooltip' => sprintf( + '
%s
%s', + esc_html__( 'Name', 'gravityformszapier' ), + esc_html__( 'This is a friendly name so you know what Zap is run when this form is submitted.', 'gravityformszapier' ) + ), + ); + + $zap_url = array( + 'label' => esc_html__( 'URL', 'gravityformszapier' ), + 'name' => 'zapURL', + 'type' => 'text', + 'class' => 'large', + 'required' => true, + 'tooltip' => sprintf( + '
%s
%s', + esc_html__( 'URL', 'gravityformszapier' ), + esc_html__( 'This is the URL provided by Zapier when you created your Zap on their website. This is the location to which your form data will be submitted to Zapier for additional processing.', 'gravityformszapier' ) + ), + ); + + $admin_labels = array( + 'label' => esc_html__( 'Use Admin Labels', 'gravityformszapier' ), + 'name' => 'adminLabels', + 'type' => 'radio', + 'choices' => array( + array( + 'label' => 'Yes', + 'value' => '1', + ), + array( + 'label' => 'No', + 'value' => '0', + ), + ), + 'horizontal' => true, + 'default_value' => '0', + 'tooltip' => sprintf( + '
%s
%s', + esc_html__( 'Use Admin Labels', 'gravityformszapier' ), + esc_html__( 'By default the field labels will be sent to Zapier. Enable this option to send the field admin labels when available.', 'gravityformszapier' ) + ), + ); + + if ( ! $this->is_legacy_feed() ) { + $save_callback = array( $this, 'restore_previous_value' ); + $feed_name['readonly'] = 'readonly'; + $feed_name['save_callback'] = $save_callback; + $zap_url['readonly'] = 'readonly'; + $zap_url['save_callback'] = $save_callback; + $admin_labels['disabled'] = 'disabled'; + $admin_labels['save_callback'] = $save_callback; + } + + return array( + array( + 'fields' => array( + $feed_name, + $zap_url, + $admin_labels, + array( + 'name' => 'zapier_conditional_enabled', + 'label' => __( 'Conditional Logic', 'gravityformszapier' ), + 'type' => 'feed_condition', + 'tooltip' => sprintf( + '
%s
%s', + esc_html__( 'Conditional Logic', 'gravityformszapier' ), + esc_html__( 'When Conditional Logic is enabled, submissions for this form will only be sent to Zapier when the condition is met. When disabled, all submissions for this form will be sent to Zapier.', 'gravityformszapier' ) + ), + ), + array( + 'name' => 'legacy', + 'type' => 'hidden', + ), + array( + 'name' => 'legacy_id', + 'type' => 'hidden', + ), + array( + 'name' => 'zapID', + 'type' => 'hidden', + ), + ), + ), + ); + + } + + /** + * Setup columns for feed list table. + * + * @since 4.0 + * + * @return array + */ + public function feed_list_columns() { + + return array( + 'feedName' => esc_html__( 'Name', 'gravityformszapier' ), + 'zapURL' => esc_html__( 'Zap URL', 'gravityformszapier' ), + ); + + } + + + // # FEED PROCESSING ------------------------------------------------------------------------------------------------- + + /** + * Send trigger request to Zapier. + * + * @since 4.0 + * + * @param array $feed The current Feed object. + * @param array $entry The current Entry object. + * @param array $form The current Form object. + * + * @return bool + */ + public function process_feed( $feed, $entry, $form ) { + $body = $this->get_body( $entry, $form, $feed ); + $headers = array(); + if ( empty( $entry ) ) { + $headers['X-Hook-Test'] = 'true'; + } + + $json_body = json_encode( $body ); + if ( empty( $body ) ) { + $this->log_debug( 'There is no field data to send to Zapier.' ); + + return false; + } + + $this->log_debug( 'Posting to url: ' . $feed['meta']['zapURL'] . ' data: ' . print_r( $body, true ) ); + + $form_data = array( 'sslverify' => false, 'ssl' => true, 'body' => $json_body, 'headers' => $headers ); + $response = wp_remote_post( $feed['meta']['zapURL'], $form_data ); + + if ( is_wp_error( $response ) ) { + $this->log_error( 'The following error occurred: ' . print_r( $response, true ) ); + + return false; + } else { + $this->log_debug( 'Successful response from Zap: ' . print_r( $response, true ) ); + + if ( ! empty( $entry ) ) { + $this->log_debug( 'Marking entry #'.$entry['id'].' as fulfilled.' ); + gform_update_meta( $entry['id'], $this->_slug.'_is_fulfilled', true ); + } + + return true; + } + } + + /** + * Returns the body of the request to be sent to zapier. + * + * @since 4.0 + * + * @param array $entry The current Entry array. + * @param array $form The current Form array. + * @param bool|array $feed The current Feed array. + * + * @return array Returns the request body to be sent to Zapier as an associative array. + */ + public function get_body( $entry, $form, $feed = false ) { + $admin_labels = is_array( $feed ) ? rgars( $feed, 'meta/adminLabels' ) : false; + $cache_key = get_current_blog_id() . '_' . rgar( $form, 'id' ) . '_' . rgar( $entry, 'id', 'sample' ) . '_' . $admin_labels; + + /** + * Determines if the Zapier add-on should use the body already stored. + * + * @since 2.1.1 + * + * @param bool true If the current body should be used. Defaults to true. + * @param array $entry The Entry array. + * @param array $form The Form array. + * @param array $feed The Feed array. + */ + if ( apply_filters( 'gform_zapier_use_stored_body', true, $entry, $form, $feed ) ) { + $current_body = rgar( self::$_current_body, $cache_key ); + + if ( ! empty( $current_body ) ) { + $this->log_debug( __METHOD__ . "(): Using cached request body ({$cache_key})." ); + + return $current_body; + } + } + + $use_sample_value = empty( $entry ); + $body = array(); + + $body[ esc_html__( 'Form ID', 'gravityformszapier' ) ] = rgar( $form, 'id' ); + $body[ esc_html__( 'Form Title', 'gravityformszapier' ) ] = rgar( $form, 'title' ); + + $entry_properties = $this->get_entry_properties(); + foreach ( $entry_properties as $property_key => $property_config ) { + $key = $this->get_body_key( $body, $property_config['label'] ); + + if ( $use_sample_value ) { + $value = $property_config['sample_value']; + } else { + $value = rgar( $entry, $property_key ); + } + + $body[ $key ] = $value; + } + + $entry_meta = GFFormsModel::get_entry_meta( $form['id'] ); + foreach ( $entry_meta as $meta_key => $meta_config ) { + $key = $this->get_body_key( $body, $meta_config['label'] ); + + if ( $use_sample_value ) { + $body[ $key ] = rgar( $meta_config, 'is_numeric' ) ? rand( 0, 10 ) : 'Sample value'; + } else { + $body[ $key ] = rgar( $entry, $meta_key ); + } + } + + foreach ( $form['fields'] as $field ) { + $input_type = GFFormsModel::get_input_type( $field ); + if ( $input_type == 'honeypot' || $field->displayOnly ) { + // Skip the honeypot and displayOnly fields. + continue; + } + + if ( ! $use_sample_value ) { + $field_value = GFFormsModel::get_lead_field_value( $entry, $field ); + $field_value = apply_filters( 'gform_zapier_field_value', $field_value, $form['id'], $field->id, $entry ); + } else { + $field_value = $this->get_sample_value( $field ); + $field_value = apply_filters( 'gform_zapier_sample_field_value', $field_value, $form['id'], $field->id ); + } + + $field_label = $this->get_body_label( $admin_labels, $field ); + + $inputs = $field instanceof GF_Field ? $field->get_entry_inputs() : rgar( $field, 'inputs' ); + + if ( is_array( $inputs ) && ( is_array( $field_value ) || $use_sample_value ) ) { + // Handling multi-input fields. + + $non_blank_items = array(); + + // Field has inputs, complex field like name, address and checkboxes. Get individual inputs. + foreach ( $inputs as $input ) { + $input_label = $this->get_body_label( $admin_labels, $field, $input['id'] ); + $key = $this->get_body_key( $body, $input_label ); + + $field_id = (string) $input['id']; + $input_value = rgar( $field_value, $field_id ); + $body[ $key ] = $input_value; + + if ( ! rgblank( $input_value ) ) { + $non_blank_items[] = $input_value; + } + } + + // Also adding an item for the "whole" field, which will be a concatenation of the individual inputs. + switch ( $input_type ) { + case 'checkbox' : + // Checkboxes will create a comma separated list of values. + $key = $this->get_body_key( $body, $field_label ); + $body[ $key ] = implode( ', ', $non_blank_items ); + break; + + case 'name' : + case 'address' : + // Name and address will separate inputs by a single blank space. + $key = $this->get_body_key( $body, $field_label ); + $body[ $key ] = implode( ' ', $non_blank_items ); + break; + + case 'calculation': + case 'hiddenproduct': + case 'singleproduct': + if ( $use_sample_value ) { + $name = rgar( $field_value, $field->id.'.1' ); + $price = rgar( $field_value, $field->id.'.2' ); + $quantity = rgar( $field_value, $field->id.'.3' ); + + $body['Products /'][] = array( + 'product_id' => $field->id, + 'product_name' => $name, + 'product_quantity' => $quantity, + 'product_price' => $price, + 'product_price_with_options' => $price + 10 + 20, + 'product_subtotal' => ( $price + 10 + 20 ) * $quantity, + 'product_options' => 'Option 1, Option 2', + ); + } else { + // We get all product fields at once, so skipped if products has been set. + if ( isset( $body['Products /'] ) ) { + break; + } + + $body['Products /'] = $this->get_products_array( $form, $entry ); + } + break; + } + } else { + $key = $this->get_body_key( $body, $field_label ); + + switch ( $input_type ) { + case 'list' : + + if ( $field->enableColumns ) { + + // Keep for backwards compatibility. + $body[ $key ] = $field_value; + + // Add line-item support to list. + $body[ $key.' /' ] = maybe_unserialize( $field_value ); + + } else { + + $body[ $key ] = maybe_unserialize( $field_value ); + + } + + break; + + default : + if ( $field->type == 'product' ) { + // Keep for backwards compatibility. + $body[ $key ] = $field_value; + + if ( $use_sample_value ) { + list( $name, $price ) = explode( '|', $field_value ); + $quantity = rand( 1, 10 ); + + $body['Products /'][] = array( + 'product_id' => $field->id, + 'product_name' => $name, + 'product_quantity' => $quantity, + 'product_price' => $price, + 'product_price_with_options' => $price + 10 + 20, + 'product_subtotal' => ( $price + 10 + 20 ) * $quantity, + 'product_options' => 'Option 1, Option 2' + ); + } else { + // We get all product fields at once, so skipped if products has been set. + if ( isset( $body['Products /'] ) ) { + break; + } + + $body['Products /'] = $this->get_products_array( $form, $entry ); + } + } elseif ( $field->type == 'shipping' ) { + // Keep old shipping value for backward compatibility. + $body[ $key ] = rgblank( $field_value ) ? '' : $field_value; + + // Set shipping as a faux product. + if ( $use_sample_value ) { + if ( $field->get_input_type() !== 'singleshipping' ) { + list( $name, $price ) = explode( '|', $field_value ); + $name = 'Shipping ('.$name.')'; + } else { + $name = 'Shipping'; + $price = $field_value; + } + $body['Products /'][] = array( + 'product_id' => $field->id, + 'product_name' => $name, + 'product_quantity' => 1, + 'product_price' => $price, + 'product_price_with_options' => $price, + 'product_subtotal' => $price, + 'product_options' => '', + ); + } + } else { + $body[ $key ] = rgblank( $field_value ) ? '' : $field_value; + } + } + } + } + + /** + * Allows the request body sent to zapier to be filtered + * + * @param array $body An associative array containing the request body that will be sent to Zapier. + * @param array $feed The Feed Object currently being processed. + * @param array $entry The Entry Object currently being processed. + * @param array $form The Form Object currently being processed. + * + * @since 3.1.1 + */ + self::$_current_body[ $cache_key ] = gf_apply_filters( + array( 'gform_zapier_request_body', rgar( $form, 'id' ) ), + $body, + $feed, + $entry, + $form + ); + + $this->log_debug( __METHOD__ . "(): Request body cached ({$cache_key})." ); + + return rgar( self::$_current_body, $cache_key ); + } + + + /** + * Retrieve a sample value for the current field. + * + * @since 4.0 + * + * @param GF_Field $field The field properties. + * + * @return array|string + */ + public function get_sample_value( $field ) { + + $default_value = 'Sample value'; + $always_text = array( 'survey', 'quiz', 'poll' ); + $field_id = absint( $field->id ); + $choice_type = in_array( $field->type, $always_text ) || ! $field->enableChoiceValue ? 'text' : 'value'; + + switch ( $field->get_input_type() ) { + case 'address' : + $value[ $field_id.'.1' ] = 'Bag End'; + $value[ $field_id.'.2' ] = 'Bagshot Row'; + $value[ $field_id.'.3' ] = 'Hobbiton'; + $value[ $field_id.'.4' ] = 'Shire'; + $value[ $field_id.'.5' ] = '1234'; + $value[ $field_id.'.6' ] = 'Middle Earth'; + break; + + case 'name' : + $value[ $field_id.'.2' ] = 'Mr.'; + $value[ $field_id.'.3' ] = 'Bilbo'; + $value[ $field_id.'.4' ] = 'L.'; + $value[ $field_id.'.6' ] = 'Baggins'; + $value[ $field_id.'.8' ] = 'Ring-bearer'; + + $inputs = $field->get_entry_inputs(); + if ( ! is_array( $inputs ) ) { + $value = implode( ' ', $value ); + } + + break; + + case 'calculation' : + $value[ $field_id.'.1' ] = $field->label; + $value[ $field_id.'.2' ] = 10; + $value[ $field_id.'.3' ] = 2; + break; + + case 'checkbox' : + $value = array(); + if ( is_array( $field->choices ) ) { + $choice_number = 1; + foreach ( $field->choices as $choice ) { + if ( $choice_number % 10 == 0 ) { + $choice_number ++; + } + + $choice_value = rgar( $choice, $choice_type ); + if ( $field->enablePrice ) { + $price = rgempty( 'price', $choice ) ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) ); + $choice_value .= '|'.$price; + } + + $input_id = $field_id.'.'.$choice_number ++; + $value[ $input_id ] = $choice_value; + } + } + break; + + case 'creditcard' : + $value[ $field_id.'.1' ] = str_repeat( 'X', 16 ); + $value[ $field_id.'.4' ] = 'Visa'; + break; + + case 'date' : + $value = date( 'Y-m-d' ); + break; + + case 'email' : + $value = 'test@domain.dev'; + break; + + case 'fileupload' : + case 'signature' : + $value = 'http://domain.dev/some_location/file.png'; + break; + + case 'list' : + if ( ! $field->enableColumns ) { + $max = 2; + } else { + $max = count( $field->choices ) * 2; + } + + $value = array_fill( 0, $max, $default_value ); + $value = serialize( $field->create_list_array( $value ) ); + break; + + case 'multiselect' : + $value = rgars( $field->choices, '0/'.$choice_type ); + if ( isset( $field->choices[1] ) ) { + $value .= ','.rgar( $field->choices[1], $choice_type ); + } + break; + + case 'number' : + case 'total' : + $value = 100; + break; + + case 'price' : + $value = $field->label.'|10'; + break; + + case 'phone' : + $value = '(999) 999-9999'; + break; + + case 'post_image' : + $title = $field->displayTitle ? 'The title' : ''; + $caption = $field->displayCaption ? 'The caption' : ''; + $description = $field->displayDescription ? 'The description' : ''; + $value = 'http://domain.dev/some_location/image.img|:|'.$title.'|:|'.$caption.'|:|'.$description; + break; + + case 'hiddenproduct' : + case 'singleproduct' : + $value[ $field_id.'.1' ] = $field->label; + $value[ $field_id.'.2' ] = empty( $field->basePrice ) ? 10 : GFCommon::to_number( $field->basePrice ); + $value[ $field_id.'.3' ] = 2; + break; + + case 'singleshipping' : + $value = empty( $field->basePrice ) ? 10 : GFCommon::to_number( $field->basePrice ); + break; + + case 'time' : + $value = '10:30 am'; + break; + + case 'website' : + $value = 'http://domain.dev'; + break; + + case 'likert' : + if ( $field->gsurveyLikertEnableMultipleRows ) { + $value = array(); + foreach ( $field->inputs as $input ) { + $value[ $input['id'] ] = $this->get_random_choice( $field->choices, $choice_type ); + } + } else { + $value = $this->get_random_choice( $field->choices, $choice_type ); + } + break; + + case 'rank' : + $c = 1; + $value = array(); + $choices = $field->choices; + shuffle( $choices ); + foreach ( $choices as $choice ) { + $value[] = $c ++.'. '.rgar( $choice, $choice_type ); + } + $value = implode( ', ', $value ); + break; + + default : + $inputs = $field->get_entry_inputs(); + + if ( $inputs ) { + $value = array(); + foreach ( $inputs as $input ) { + $choices = rgar( $input, 'choices' ); + if ( is_array( $choices ) ) { + $value[ $input['id'] ] = $this->get_random_choice( $choices, $choice_type ); + } else { + $value[ $input['id'] ] = $default_value; + } + } + } elseif ( is_array( $field->choices ) && count( $field->choices ) > 0 ) { + $value = $this->get_random_choice( $field->choices, $choice_type, $field->enablePrice ); + } else { + $value = $default_value; + } + } + + return $value; + } + + /** + * Return a random choice. + * + * @since 4.0 + * + * @param array $choices The choices. + * @param string $choice_type The choice property to return; text or value. + * @param bool $price_enabled Is the enablePrice property enabled for the field being processed. + * + * @return string + */ + public function get_random_choice( $choices, $choice_type, $price_enabled = false ) { + $key = array_rand( $choices ); + $choice = $choices[ $key ]; + $value = rgar( $choice, $choice_type ); + + if ( $price_enabled ) { + $price = rgempty( 'price', $choice ) ? 0 : GFCommon::to_number( rgar( $choice, 'price' ) ); + $value .= '|'.$price; + } + + return $value; + } + + /** + * Return the product fields in the entry as an array. + * + * @since 4.0 + * + * @param array $form The Form Object. + * @param array $entry The Entry Object. + * + * @return array + */ + public function get_products_array( $form, $entry ) { + $product_info = GFCommon::get_product_fields( $form, $entry ); + $products = array_values( $product_info['products'] ); + $product_ids = array_keys( $product_info['products'] ); + foreach ( $products as $key => $product ) { + $products[ $key ]['product_id'] = $product_ids[ $key ]; + $products[ $key ]['product_name'] = $product['name']; + unset( $products[ $key ]['name'] ); + $products[ $key ]['product_quantity'] = intval( $product['quantity'] ); + unset( $products[ $key ]['quantity'] ); + + // Change price to "product price" to be more clear when displaying in Zapier. + $products[ $key ]['product_price'] = GFCommon::to_number( $product['price'], $entry['currency'] ); + unset( $products[ $key ]['price'] ); + + $options = rgar( $product, 'options' ); + // Add unit price. + $products[ $key ]['product_price_with_options'] = GFCommon::to_number( $product['price'], $entry['currency'] ); + if ( is_array( $options ) && ! empty( $options ) ) { + foreach ( $options as $option ) { + $products[ $key ]['product_price_with_options'] += GFCommon::to_number( $option['price'], $entry['currency'] ); + } + } + + // Add subtotal to product array. + $products[ $key ]['product_subtotal'] = $products[ $key ]['product_price_with_options'] * $products[ $key ]['product_quantity']; + + // Turn options into product_options. + unset( $products[ $key ]['options'] ); + $products[ $key ]['product_options'] = ( empty( $options ) ) ? '' : implode( ', ', wp_list_pluck( $options, 'option_name' ) ); + } + + $shipping_field = GFAPI::get_fields_by_type( $form, array( 'shipping' ) ); + if ( ! empty( $shipping_field ) ) { + // Set shipping as a faux product. + $products[] = array( + 'product_id' => $product_info['shipping']['id'], + 'product_name' => $product_info['shipping']['name'], + 'product_quantity' => 1, + 'product_price' => $product_info['shipping']['price'], + 'product_price_with_options' => $product_info['shipping']['price'], + 'product_subtotal' => $product_info['shipping']['price'], + 'product_options' => '', + ); + } + + return apply_filters( 'gform_zapier_products', $products, $form, $entry ); + } + + /** + * Retrieve label to be sent to Zapier. + * + * @since 4.0 + * + * @param bool $admin_labels Should the field adminLabel be used. + * @param GF_Field $field The field currently being processed. + * @param bool|int $input_id False or the input ID. + * + * @return string + */ + public function get_body_label( $admin_labels, $field, $input_id = false ) { + + $label = $admin_labels && ! empty( $field->adminLabel ) ? $field->adminLabel : $field->label; + + if ( $input_id ) { + $input = GFFormsModel::get_input( $field, $input_id ); + + if ( ! is_null( $input ) ) { + if ( $field->get_input_type() == 'checkbox' ) { + $label = $input['label']; + } else { + $label .= ' ('.$input['label'].')'; + } + } + } + + if ( empty( $label ) ) { + return $field->get_form_editor_field_title(); + } + + return $label; + } + + /** + * Ensure the label (array key) is unique. + * + * @since 4.0 + * + * @param array $body The data to be sent to Zapier. + * @param string $label The field or entry meta label. + * + * @return string + */ + public function get_body_key( $body, $label ) { + + $count = 1; + $key = $label; + + while ( array_key_exists( $key, $body ) ) { + $key = $label.' - '.$count; + $count ++; + } + + return $key; + } + + /** + * Return the entry properties to be sent to Zapier. + * + * @since 4.0 + * + * @return array + */ + public function get_entry_properties() { + return array( + 'id' => array( + 'label' => esc_html__( 'Entry ID', 'gravityforms' ), + 'sample_value' => 0, + ), + 'date_created' => array( + 'label' => esc_html__( 'Entry Date', 'gravityforms' ), + 'sample_value' => gmdate( 'Y-m-d H:i:s' ), + ), + 'ip' => array( + 'label' => esc_html__( 'User IP', 'gravityforms' ), + 'sample_value' => GFFormsModel::get_ip(), + ), + 'source_url' => array( + 'label' => esc_html__( 'Source Url', 'gravityforms' ), + 'sample_value' => RGFormsModel::get_current_page_url(), + ), + 'created_by' => array( + 'label' => esc_html__( 'Created By', 'gravityforms' ), + 'sample_value' => 1, + ), + 'transaction_id' => array( + 'label' => esc_html__( 'Transaction Id', 'gravityforms' ), + 'sample_value' => '1234567890', + ), + 'payment_amount' => array( + 'label' => esc_html__( 'Payment Amount', 'gravityforms' ), + 'sample_value' => 100, + ), + 'payment_date' => array( + 'label' => esc_html__( 'Payment Date', 'gravityforms' ), + 'sample_value' => gmdate( 'Y-m-d H:i:s' ), + ), + 'payment_status' => array( + 'label' => esc_html__( 'Payment Status', 'gravityforms' ), + 'sample_value' => 'Paid', + ), + 'post_id' => array( + 'label' => esc_html__( 'Post Id', 'gravityforms' ), + 'sample_value' => 1, + ), + 'user_agent' => array( + 'label' => esc_html__( 'User Agent', 'gravityforms' ), + 'sample_value' => sanitize_text_field( substr( $_SERVER['HTTP_USER_AGENT'], 0, 250 ) ), + ), + ); + } + + /** + * Upgrades delayed feed settings for the specified payment addon + * + * @since 4.0 + * + * @param string $payment_addon_slug + */ + public function upgrade_delayed_feed_settings( $payment_addon_slug ) { + + $payment_feeds = $this->get_feeds_by_slug( $payment_addon_slug ); + if ( ! empty( $payment_feeds ) ) { + + $this->log_debug( __METHOD__.'(): New feeds found for '.$this->_slug.' - copying over delay settings.' ); + + foreach ( $payment_feeds as $feed ) { + $meta = $feed['meta']; + if ( ! rgempty( 'delay_zapier_subscription', $meta ) ) { + $meta[ "delay_{$this->_slug}" ] = $meta[ 'delay_zapier_subscription' ]; + $this->update_feed_meta( $feed['id'], $meta ); + } + } + } + } + + + private function __clone() { + } /* do nothing */ + + + // # FEED UPGRADE TO FRAMEWORK ------------------------------------------------------------- + + /** + * Part of the add-on framework upgrade process. This function gets called if an upgrade is required (i.e. version changed). + * Upgrade feeds to new addon framework table if previously installed version used the legacy feeds table + * + * @since 4.0 + * + * @param string $previous_version Previously installed version. + */ + public function upgrade( $previous_version ) { + + $this->log_debug( 'Starting upgrade routine' ); + + if ( $this->is_pre_addon_framework( $previous_version ) ) { + + // DB table has already been created at this point by add-on framework. + // Only need to move feeds over. + $this->upgrade_feeds(); + } else { + $this->log_debug( 'Previous version on framework, no need to migrate' ); + } + } + + /** + * Migrates feeds from legacy db table to new addon framework feed table. + * + * @since 4.0 + */ + public function upgrade_feeds() { + + if ( $this->is_upgrading() ) { + return; + } + + $old_feeds = $this->get_old_feeds(); + + if ( ! $old_feeds ) { + $this->log_debug( 'There are no old feeds to migrate.' ); + return; + } + + // Get new feeds, and only add legacy if it isn't already in the new feed array. + $current_feeds = $this->get_feeds(); + $this->log_debug( 'Current feed count: ' . count( $current_feeds ) ); + + foreach ( $old_feeds as $old_feed ) { + + $feed_name = $old_feed['name']; + $form_id = $old_feed['form_id']; + $is_active = $old_feed['is_active']; + $zap_url = $old_feed['url']; + + // If feed has already been migrated, skip it. + if ( $this->is_feed_upgraded( $old_feed, $current_feeds ) ) { + $this->log_debug( 'Feed ' . $zap_url . ' already migrated. Skipping.' ); + continue; + } + + $new_meta = array( + 'feedName' => $feed_name, + 'zapURL' => $zap_url, + 'adminLabels' => rgar( $old_feed['meta'], 'adminLabels' ), + 'legacy' => '1', + 'legacy_id' => $old_feed['id'], + ); + + // Add conditional logic, legacy only allowed one condition. + $conditional_enabled = rgar( $old_feed['meta'], 'zapier_conditional_enabled' ); + if ( $conditional_enabled ) { + $new_meta['feed_condition_conditional_logic'] = 1; + + $logic = array( + 'actionType' => 'show', + 'logicType' => 'all', + 'rules' => array( + array( + 'fieldId' => rgar( $old_feed['meta'], 'zapier_conditional_field_id' ), + 'operator' => rgar( $old_feed['meta'], 'zapier_conditional_operator' ), + 'value' => rgar( $old_feed['meta'], 'zapier_conditional_value' ), + ), + ), + ); + + $logic = apply_filters( 'gform_zapier_feed_conditional_logic', $logic, GFAPI::get_form( $form_id ), $old_feed['meta'] ); + + $new_meta['feed_condition_conditional_logic_object'] = array( 'conditionalLogic' => $logic ); + + } else { + $new_meta['feed_condition_conditional_logic'] = 0; + } + + $this->log_debug( 'Migrating feed ' . $zap_url ); + $this->insert_feed( $form_id, $is_active, $new_meta ); + } + + // Upgrade paypal delayed setting. + $this->upgrade_paypal_delay_settings(); + + //TODO: may need to update Stripe delayed setting as well + + $this->end_upgrade(); + + $migrated_feeds = $this->get_feeds(); + + /** + * Allows custom actions to be performed once the feeds have been migrated to the add-on framework. + * + * @since 4.0.0 + * + * @param array $migrated_feeds An array of migrated Zapier feeds. + * @param array $old_feeds An array of legacy Zapier feeds from before the migration. + */ + do_action( 'gform_zapier_post_migrate_feeds', $migrated_feeds, $old_feeds ); + } + + /** + * Returns true if the legacy feed has already been upgraded (i.e. moved to new feed table). + * + * @since 4.0 + * + * @param array $legacy_feed The feed to be processed. + * @param array $current_feeds An array of all feeds that have been upgraded. + * + * @return bool + */ + public function is_feed_upgraded( $legacy_feed, $current_feeds ) { + + if ( count( $current_feeds ) == 0 ) { + //no new feeds, not migrated + return false; + } + + foreach ( $current_feeds as $new_feed ) { + if ( isset( $new_feed['meta']['legacy_id'] ) ) { + if ( $new_feed['meta']['legacy_id'] == $legacy_feed['id'] ) { + return true; + } + continue; + } + + // Feed was migrated by the first beta; checking the form ID and Zap URL instead. + if ( ( $new_feed['form_id'] == $legacy_feed['form_id'] ) && ( rgars( $new_feed, 'meta/zapURL' ) == $legacy_feed['url'] ) ) { + return true; + } + } + + return false; + } + + + /** + * Determines if the upgrade process is running + * + * @since 4.0 + * + * @return bool Returns true if the database upgrade is currently running. Returns false otherwise. + */ + public function is_upgrading() { + + $option_name = 'gform_zapier_feed_upgrading'; + + $timestamp = get_option( $option_name ); + if ( empty( $timestamp ) ) { + $timestamp = 0; + } + // Expires in 15 seconds. + $is_upgrading = ( time() - $timestamp ) < 15; + + // Marking as upgrading + if ( ! $is_upgrading ) { + update_option( $option_name, time(), false ); + } + + return $is_upgrading; + } + + /** + * Marks the upgrade process as completed + * + * @since 4.0 + */ + public function end_upgrade() { + + delete_option( 'gform_zapier_feed_upgrading' ); + + } + + /** + * Migrate the delayed payment setting for the PayPal add-on integration. + * + * @since 4.0 + * + */ + public function upgrade_paypal_delay_settings() { + + global $wpdb; + + // Log that we are checking for delay settings for migration. + $this->log_debug( __METHOD__ . '(): Checking to see if there are any delay settings that need to be migrated for PayPal Standard.' ); + + + //**** test this ******* + + // Upgrade PayPal delayed feed settings + $this->upgrade_delayed_feed_settings( 'gravityformspaypal' ); + + // Upgrade Stripe delayed feed settings + $this->upgrade_delayed_feed_settings( 'gravityformsstripe' ); + + } + + /** + * Returns the old feeds. + * + * For some additional context: + * Old Feeds: Stored in wp_rg_zapier + * Legacy Feeds: Stored in wp_gf_addon_feed + * + * @since 4.0 + * + * @return null|array|bool|object + */ + public function get_old_feeds() { + global $wpdb; + $table_name = $wpdb->prefix . 'rg_zapier'; + + $this->log_debug( 'Looking for table ' . $table_name ); + + if ( ! $this->table_exists( $table_name ) ) { + $this->log_debug( 'Table did NOT exist.' ); + + return false; + } + + $form_table_name = RGFormsModel::get_form_table_name(); + $sql = "SELECT s.id, s.is_active, s.form_id, s.name, s.url, s.meta, f.title as form_title + FROM $table_name s + INNER JOIN $form_table_name f ON s.form_id = f.id"; + + $results = $wpdb->get_results( $sql, ARRAY_A ); + + $count = sizeof( $results ); + for ( $i = 0; $i < $count; $i ++ ) { + $results[ $i ]['meta'] = maybe_unserialize( $results[ $i ]['meta'] ); + } + + return $results; + } + + /** + * Returns true if the specified version is a legacy (i.e. pre addon-framework, pre 4.0) version. Returns false otherwise. + * + * @since 4.0 + * + * @param string $version The version to be checked. + * + * @return bool Returns true if the specified version is a legacy (i.e. pre addon-framework, pre 4.0) version. Returns false otherwise + */ + public function is_pre_addon_framework( $version = null ) { + + if ( empty( $version ) ) { + $version = get_option( 'gf_zapier_version' ); + } + $is_pre_addon_framework = empty( $version ) || version_compare( $version, '4.0', '<' ); + + return $is_pre_addon_framework; + } + + //------------------------------------------------------------- + + /** + * Returns the plugin settings fields. + * + * @since 4.0 + * + * @return array + */ + public function plugin_settings_fields() { + // Get Setup instructions view. + ob_start(); + require $this->get_base_path() . '/includes/setup-instructions.php'; + $instructions = ob_get_clean(); + + $fields = array(); + $instructions_section = array( + 'fields' => array(), + ); + + if ( $this->is_gravityforms_supported( '2.5-beta' ) ) { + $instructions_section['fields'][] = array( + 'type' => 'html', + 'name' => 'zapier_instructions', + 'html' => $instructions, + ); + } else { + $instructions_section['description'] = $instructions; + } + + return array( + $instructions_section, + array( + 'title' => esc_html__( 'Advanced Settings', 'gravityformszapier' ), + 'fields' => array( + array( + 'type' => 'checkbox', + 'name' => 'toggle_feeds', + 'label' => esc_html__( 'Zapier Feeds', 'gravityformszapier' ), + 'tooltip' => esc_html__( 'Show or hide the Zapier feed(s) on the Form > Settings > Zapier Feeds screen. This is intended for advanced users and should only be enabled when instructed by support.', 'gravityformszapier' ), + 'choices' => array( + array( + 'label' => esc_html__( 'Display Zapier feeds in the form settings', 'gravityformszapier' ), + 'name' => 'display_feeds', + 'default_value' => intval( $this->should_display_feeds() ), + ), + ), + ), + ), + ), + ); + } + + /** + * Overridden so that only forms with legacy zapier feeds have the link to the feeds. + * + * @since 4.0 + * + * @param array $tabs Array of Form Settings Tabs. + * @param int $form_id GF Form ID. + * + * @return array + */ + public function add_form_settings_menu( $tabs, $form_id ) { + if ( $this->should_display_feeds() ) { + $tabs[] = array( + 'name' => $this->_slug, + 'label' => $this->get_short_title(), + 'query' => array( 'fid' => null ), + 'capabilities' => $this->_capabilities_form_settings, + 'icon' => $this->get_menu_icon(), + ); + } + + return $tabs; + } + + /** + * Disables manual creation of feeds. + * + * @since 4.1 + * + * @return bool + */ + public function can_create_feed() { + return ! empty( $_GET['fid'] ); + } + + /** + * Should Zapier feeds be displayed? + * + * @since 4.0 + * + * @return bool + */ + private function should_display_feeds() { + // Get Addon Settings field value. + $state = $this->get_plugin_settings(); + $value = isset( $state['display_feeds'] ) ? (bool) $state['display_feeds'] : null; + + return null === $value ? (bool) $this->has_legacy_feeds() : $value; + } + + /** + * The empty feeds list table message. + * + * @since 4.0 + * + * @return string + */ + public function feed_list_no_item_message() { + return sprintf( + // Translators: 1. Opening tag for link to Zapier, 2. Closing tag. + esc_html__( 'Simply %1$screate a zap%2$s on zapier.com.', 'gravityformszapier' ), + '', + '' + ); + } + + /** + * Gets all product fields that have been submitted in the form (i.e. non-blank and non-zero quantities) and returns an array containing their product names and prices as configured in the form object + * + * @since 4.0 + * + * @param array $form Current form array. + * + * @return array Returns an associative array containing all submitted products with their respecitve product names and prices, keyed by the field id. + */ + private function get_product_inputs( $form, $entry = null ) { + + if ( empty( $entry ) ) { + $entry = GFFormsModel::create_lead( $form ); + } + + $products = array(); + foreach ( $form['fields'] as $field ) { + + // Ignore any field that is not a single product or hidden product. + if ( ! in_array( $field->inputType, array( 'singleproduct', 'hiddenproduct' ) ) ) { + continue; + } + + $quantity_field = GFCommon::get_product_fields_by_type( $form, array( 'quantity' ), $field->id ); + + + if ( sizeof( $quantity_field ) > 0 ) { + $quantity = ! RGFormsModel::is_field_hidden( $form, $quantity_field[0], array(), $entry ) ? RGFormsModel::get_lead_field_value( $entry, $quantity_field[0] ) : 0; + } else { + $entry_value = RGFormsModel::get_lead_field_value( $entry, $field ); + $quantity = ! $field->disableQuantity ? rgget( "{$field->id}.3", $entry_value ) : 0; + } + + // Ignore product fields that didn't have a quantity sent in. + if ( empty( $quantity ) ) { + continue; + } + + $products[ $field->id ] = array( + 'name' => $field->label, + 'price' => $field->basePrice, + ); + } + + return $products; + } + + /** + * Return the Zapier icon for the plugin/form settings menu. + * + * @since 4.0 + * + * @return string + */ + public function get_menu_icon() { + + return $this->is_gravityforms_supported( '2.5-beta-4' ) ? 'gform-icon--zapier' : 'dashicons-admin-generic'; + + } + +} diff --git a/data.php b/data.php deleted file mode 100644 index 1de89a0..0000000 --- a/data.php +++ /dev/null @@ -1,123 +0,0 @@ -charset) ) - $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset"; - if ( ! empty($wpdb->collate) ) - $charset_collate .= " COLLATE $wpdb->collate"; - - $sql = "CREATE TABLE $table_name ( - id mediumint(8) unsigned not null auto_increment, - form_id mediumint(8) unsigned not null, - is_active tinyint(1) not null default 1, - name varchar(150) not null, - url varchar(150) not null, - meta longtext, - PRIMARY KEY (id), - KEY form_id (form_id) - )$charset_collate;"; - - require_once(ABSPATH . '/wp-admin/includes/upgrade.php'); - dbDelta($sql); - } - - public static function get_zapier_table_name(){ - global $wpdb; - return $wpdb->prefix . "rg_zapier"; - } - - public static function get_feeds(){ - global $wpdb; - $table_name = self::get_zapier_table_name(); - $form_table_name = RGFormsModel::get_form_table_name(); - $sql = "SELECT s.id, s.is_active, s.form_id, s.name, s.url, s.meta, f.title as form_title - FROM $table_name s - INNER JOIN $form_table_name f ON s.form_id = f.id"; - - $results = $wpdb->get_results($sql, ARRAY_A); - - $count = sizeof($results); - for($i=0; $i<$count; $i++){ - $results[$i]["meta"] = maybe_unserialize($results[$i]["meta"]); - } - - return $results; - } - - public static function delete_feed($id){ - global $wpdb; - $table_name = self::get_zapier_table_name(); - $wpdb->query($wpdb->prepare("DELETE FROM $table_name WHERE id=%s", $id)); - } - - public static function get_feed_by_form($form_id, $only_active = false){ - global $wpdb; - $table_name = self::get_zapier_table_name(); - $active_clause = $only_active ? " AND is_active=1" : ""; - $sql = $wpdb->prepare("SELECT id, form_id, is_active, name, url, meta FROM $table_name WHERE form_id=%d $active_clause", $form_id); - $results = $wpdb->get_results($sql, ARRAY_A); - if(empty($results)) - return array(); - - //Deserializing meta - $count = sizeof($results); - for($i=0; $i<$count; $i++){ - $results[$i]["meta"] = maybe_unserialize($results[$i]["meta"]); - } - return $results; - } - - public static function get_feed($id){ - global $wpdb; - $table_name = self::get_zapier_table_name(); - $sql = $wpdb->prepare("SELECT id, form_id, is_active, name, url, meta FROM $table_name WHERE id=%d", $id); - $results = $wpdb->get_results($sql, ARRAY_A); - if(empty($results)) - return array(); - - $result = $results[0]; - $result["meta"] = maybe_unserialize($result["meta"]); - return $result; - } - - public static function update_feed($id, $form_id, $is_active, $name, $url, $meta){ - global $wpdb; - $table_name = self::get_zapier_table_name(); - $meta = maybe_serialize($meta); - if($id == 0){ - //insert - $wpdb->insert($table_name, array("form_id" => $form_id, "is_active"=> $is_active, "name" => $name, "url" => $url, "meta" => $meta), array("%d", "%d", "%s", "%s", "%s", "%s")); - $id = $wpdb->get_var("SELECT LAST_INSERT_ID()"); - } - else{ - //update - $wpdb->update($table_name, array("form_id" => $form_id, "is_active"=> $is_active, "name" => $name, "url" => $url, "meta" => $meta), array("id" => $id), array("%d", "%d", "%s", "%s", "%s", "%s")); - } - - return $id; - } - - public static function drop_tables(){ - global $wpdb; - $wpdb->query("DROP TABLE IF EXISTS " . self::get_zapier_table_name()); - } -} \ No newline at end of file diff --git a/includes/class-feeds-list-table.php b/includes/class-feeds-list-table.php new file mode 100644 index 0000000..2dddc8a --- /dev/null +++ b/includes/class-feeds-list-table.php @@ -0,0 +1,44 @@ +is_gravityforms_supported( '2.5-rc-1' ) ) { + $open = '
'; + $close = '
'; + } else { + $open = '

'; + $close = '

'; + } + + echo $open . sprintf( + // Translators: 1. Opening tag for link to Zapier, 2. Closing tag. 3. Opening tag for link to Gravity Forms Zapier documentation. 4. Closing tag. + esc_html__( 'Zapier feeds are created automatically when %1$szaps are configured%2$s on zapier.com. %3$sLearn more%4$s.', 'gravityformszapier' ), + '', + '', + '', + '' + ) . $close; + + parent::display(); + } + +} \ No newline at end of file diff --git a/includes/rest/class-feeds-controller.php b/includes/rest/class-feeds-controller.php new file mode 100644 index 0000000..d647ba6 --- /dev/null +++ b/includes/rest/class-feeds-controller.php @@ -0,0 +1,87 @@ +[\d]+)/zapier-feeds/(?P[\d]+)'; + + /** + * Register the routes for the objects of the controller. + * + * @since 4.1 + */ + public function register_routes() { + register_rest_route( $this->namespace, '/' . $this->rest_base, array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + ), + ) ); + } + + /** + * Gets a Zapier feed by the zapID stored in the feed meta. + * + * @since 4.1 + * + * @param WP_REST_Request $request Full data about the request. + * + * @return WP_Error|WP_REST_Response + */ + public function get_item( $request ) { + $form_id_valid = $this->is_url_form_id_valid( $request ); + if ( is_wp_error( $form_id_valid ) ) { + return $form_id_valid; + } + + $result = $this->query_item( $request ); + + if ( empty( $result ) ) { + return new WP_Error( 'feed_not_found', __( 'Feed not found.', 'gravityformszapier' ), array( 'status' => 404 ) ); + } + + $result['meta'] = json_decode( $result['meta'], true ); + + return new WP_REST_Response( $result, 200 ); + } + + /** + * Performs the database query to retrieve the feed. + * + * @since 4.1 + * + * @param WP_REST_Request $request Full data about the request. + * + * @return array|null + */ + public function query_item( $request ) { + global $wpdb; + + $table = $wpdb->prefix . 'gf_addon_feed'; + $like = '%' . $wpdb->esc_like( sprintf( '"zapID":"%d"', $request->get_param( 'zap_id' ) ) ) . '%'; + + return $wpdb->get_row( $wpdb->prepare( + "SELECT * FROM {$table} WHERE (form_id=%d) AND (addon_slug=%s) AND (meta LIKE %s) ORDER BY id DESC", + $request->get_param( 'form_id' ), + 'gravityformszapier', + $like + ), ARRAY_A ); + } + +} diff --git a/includes/rest/class-requirements-controller.php b/includes/rest/class-requirements-controller.php new file mode 100644 index 0000000..96ac5f2 --- /dev/null +++ b/includes/rest/class-requirements-controller.php @@ -0,0 +1,52 @@ +namespace, '/' . $this->rest_base, array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + ), + ) ); + } + + /** + * Returns the Zapier app requirements for this version of the Zapier Add-On. + * + * @since 4.0 + * + * @param WP_REST_Request $request Full data about the request. + * + * @return WP_REST_Response + */ + public function get_items( $request ) { + $response = array( + 'zapier-version' => GF_ZAPIER_TARGET_ZAPIER_APP_VERSION, + ); + + return new WP_REST_Response( $response, 200 ); + } + +} diff --git a/includes/rest/class-sample-entries-controller.php b/includes/rest/class-sample-entries-controller.php new file mode 100644 index 0000000..a628ec0 --- /dev/null +++ b/includes/rest/class-sample-entries-controller.php @@ -0,0 +1,84 @@ +[\d]+)/zapier-sample-entries'; + + /** + * Register the routes for the objects of the controller. + * + * @since 4.1 + */ + public function register_routes() { + register_rest_route( $this->namespace, '/' . $this->rest_base, array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_sample_entries' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + '_admin_labels' => array( + 'description' => __( 'Indicates if admin labels should be used instead of frontend labels.', 'gravityformszapier' ), + 'type' => 'boolean', + ) + ), + ), + ) ); + } + + /** + * Get a collection of the latest 3 entries for the requested from. + * + * If the there are no entries, or the user doesn't have the gravityforms_view_entries capability, it will return the sample data returned by the '/sample-entry' endpoint. + * + * @since 4.1 + * + * @param WP_REST_Request $request Full data about the request. + * + * @return WP_Error|WP_REST_Response + */ + public function get_sample_entries( $request ) { + $form_id_valid = $this->is_url_form_id_valid( $request ); + if ( is_wp_error( $form_id_valid ) ) { + return $form_id_valid; + } + + $form_id = $request->get_param( 'form_id' ); + $form = GFAPI::get_form( $form_id ); + $admin_labels = ! empty( $request->get_param( '_admin_labels' ) ); + + if ( ! $this->current_user_can_any( 'gravityforms_view_entries', $request ) ) { + return new WP_REST_Response( $this->get_sample_data( $form, $admin_labels ), 200 ); + } + + $entries = GFAPI::get_entries( $form_id, array( 'status' => 'active' ), null, array( 'offset' => 0, 'page_size' => 3 ) ); + if ( is_wp_error( $entries ) ) { + return $entries; + } + + foreach ( $entries as $entry ) { + $sample_data[] = $this->get_sample_data( $form, $admin_labels, $entry ); + } + + if ( empty( $sample_data ) ) { + $sample_data[] = $this->get_sample_data( $form, $admin_labels ); + } + + return new WP_REST_Response( $sample_data, 200 ); + } + +} diff --git a/includes/rest/class-sample-entry-controller.php b/includes/rest/class-sample-entry-controller.php new file mode 100644 index 0000000..d4ac511 --- /dev/null +++ b/includes/rest/class-sample-entry-controller.php @@ -0,0 +1,56 @@ +[\d]+)/sample-entry'; + + /** + * Register the routes for the objects of the controller. + * + * @since 2.4 + */ + public function register_routes() { + register_rest_route( $this->namespace, '/' . $this->rest_base, array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_sample_entry' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + ), + ) ); + } + + /** + * Get a collection of feeds for the form. + * + * @since 2.4 + * + * @param WP_REST_Request $request Full data about the request. + * + * @return WP_REST_Response|\WP_Error + */ + public function get_sample_entry( $request ) { + $form_id_valid = $this->is_url_form_id_valid( $request ); + if ( is_wp_error( $form_id_valid ) ) { + return $form_id_valid; + } + + $form = GFAPI::get_form( $request->get_param( 'form_id' ) ); + + return new WP_REST_Response( $this->get_sample_data( $form ), 200 ); + } + +} diff --git a/includes/rest/class-transfer-entries-controller.php b/includes/rest/class-transfer-entries-controller.php new file mode 100644 index 0000000..57e773b --- /dev/null +++ b/includes/rest/class-transfer-entries-controller.php @@ -0,0 +1,91 @@ +[\d]+)/zapier-transfer-entries'; + + /** + * Register the routes for the objects of the controller. + * + * This route is for internal use, and may be subject to change. + * + * @since 4.2 + */ + public function register_routes() { + register_rest_route( $this->namespace, '/' . $this->rest_base, array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + ), + ) ); + } + + /** + * Gets all the entries for a form. + * + * @since 4.2 + * + * @param WP_REST_Request $request Full data about the request. + * + * @return WP_Error|WP_REST_Response + */ + public function get_items( $request ) { + $form_id_valid = $this->is_url_form_id_valid( $request ); + if ( is_wp_error( $form_id_valid ) ) { + return $form_id_valid; + } + + $form_id = $request->get_param( 'form_id' ); + $search_params = $this->parse_entry_search_params( $request ); + $total_count = 0; + $entries = GFAPI::get_entries( $form_id, $search_params['search_criteria'], $search_params['sorting'], $search_params['paging'], $total_count ); + $form = GFAPI::get_form( $form_id ); + $admin_labels = ! empty( $request->get_param( '_admin_labels' ) ); + $data = array(); + foreach ( $entries as $entry ) { + $data[] = $this->get_sample_data( $form, $admin_labels, $entry ); + } + + $per_page = isset( $search_params['paging']['page_size'] ) ? $search_params['paging']['page_size'] : 10; + $max_pages = ceil( $total_count / (int) $per_page ); + + $response = rest_ensure_response( $data ); + $response->header( 'X-GF-Total', (int) $total_count ); + $response->header( 'X-GF-TotalPages', (int) $max_pages ); + + return $response; + + } + + /** + * Determines if the user has the required capability to get the item. + * + * @since 4.2 + * + * @param WP_REST_Request $request The full data for the request. + * + * @return bool + */ + public function get_item_permissions_check( $request ) { + return $this->current_user_can_any( 'gravityforms_view_entries', $request ); + } + +} diff --git a/includes/rest/class-zapier-controller.php b/includes/rest/class-zapier-controller.php new file mode 100644 index 0000000..2244c60 --- /dev/null +++ b/includes/rest/class-zapier-controller.php @@ -0,0 +1,59 @@ +get_url_params(), 'form_id' ) ) ) { + return new WP_Error( 'form_not_found', __( 'Form not found.', 'gravityformszapier' ), array( 'status' => 404 ) ); + } + + return true; + } + + /** + * Determines if the user has the required capability to get the item. + * + * @since 4.1 + * + * @param WP_REST_Request $request The full data for the request. + * + * @return bool + */ + public function get_item_permissions_check( $request ) { + return $this->current_user_can_any( 'gravityforms_edit_forms', $request ); + } + + /** + * Prepares the data for the sample entry/entries response. + * + * @since 4.1 + * + * @param array $form The form the Zapier feed is assigned to. + * @param bool $admin_labels Should admin labels be used instead of front end labels or not? + * @param null|array $entry The entry to use when preparing the data or null to use the default sample values. + * + * @return array + */ + protected function get_sample_data( $form, $admin_labels = false, $entry = null ) { + return gf_zapier()->get_body( $entry, $form, array( 'meta' => array( 'adminLabels' => $admin_labels ) ) ); + } + +} diff --git a/includes/setup-instructions.php b/includes/setup-instructions.php new file mode 100644 index 0000000..01ae58e --- /dev/null +++ b/includes/setup-instructions.php @@ -0,0 +1,105 @@ + + +

+
    +
  1. + + + + + +
  2. +
  3. +
  4. ', + '' + ); + ?>
  5. +
+ + diff --git a/plugin-upgrade.php b/plugin-upgrade.php deleted file mode 100644 index cce9718..0000000 --- a/plugin-upgrade.php +++ /dev/null @@ -1,114 +0,0 @@ -response[$plugin_path])) - $option->response[$plugin_path] = new stdClass(); - - //Empty response means that the key is invalid. Do not queue for upgrade - if(!$version_info["is_valid_key"] || version_compare($version, $version_info["version"], '>=')){ - unset($option->response[$plugin_path]); - } - else{ - $option->response[$plugin_path]->url = $plugin_url; - $option->response[$plugin_path]->slug = $plugin_slug; - $option->response[$plugin_path]->package = str_replace("{KEY}", $key, $version_info["url"]); - $option->response[$plugin_path]->new_version = $version_info["version"]; - $option->response[$plugin_path]->id = "0"; - } - - return $option; - - } - - public static function display_plugin_message($message, $is_error = false){ - $style = ""; - if($is_error) - $style = 'style="background-color: #ffebe8;"'; - - echo '
' . $message . '
'; - } - - public static function display_upgrade_message($plugin_name, $plugin_title, $version, $message, $localization_namespace){ - $upgrade_message = $message .' '. sprintf(__('View version %s Details', $localization_namespace), $version) . '. '; - self::display_plugin_message($upgrade_message); - } - - //Displays current version details on Plugin's page - public static function display_changelog($offering, $key, $version){ - - $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"), - 'Referer' => get_bloginfo("url") - ); - - $raw_response = wp_remote_request(GRAVITY_MANAGER_URL . "/changelog.php?" . self::get_remote_request_params($offering, $key, $version), $options); - - if ( is_wp_error( $raw_response ) || 200 != $raw_response['response']['code']){ - $page_text = sprintf(__("Oops!! Something went wrong.%sPlease try again or %scontact us%s.", 'gravityformszapier'), "
", "", ""); - } - else{ - $page_text = $raw_response['body']; - if(substr($page_text, 0, 10) != "") - $page_text = ""; - } - echo stripslashes($page_text); - - exit; - } - - - public static function get_version_info($offering, $key, $version){ - - $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"), - 'Referer' => get_bloginfo("url") - ); - $url = GRAVITY_MANAGER_URL . "/version.php?" . self::get_remote_request_params($offering, $key, $version); - $raw_response = wp_remote_request($url, $options); - - if ( is_wp_error( $raw_response ) || 200 != $raw_response['response']['code']) - return -1; - else - { - $ary = explode("||", $raw_response['body']); - return array("is_valid_key" => $ary[0], "version" => $ary[1], "url" => $ary[2]); - } - } - - public static 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($wpdb->db_version())); - } - -} -?> \ No newline at end of file