s' => $this->get_class(), 'is_multi_on_global' => $this->is_allow_multi_on_global(), 'is_activable' => $this->is_activable(), 'is_settings_available' => $this->is_settings_available(), 'is_connected' => $this->is_connected(), ); if ( $is_allow_multi_on_global ) { $to_array['global_multi_ids'] = $this->get_global_multi_ids(); } return $to_array; } /** * Transform provider instance into array with module relation * * @since 4.0.0 * * @param string $module_id ID of the module to retrieve the settings from. * @return array */ final public function to_array_with_form( $module_id ) { $to_array = $this->to_array(); $is_allow_multi_on_form = $this->is_allow_multi_on_form(); $to_array['is_form_connected'] = $this->is_form_connected( $module_id ); $to_array['is_form_settings_available'] = $this->is_form_settings_available( $module_id ); $to_array['is_allow_multi_on_form'] = $is_allow_multi_on_form; // Handle multiple form setting. if ( $is_allow_multi_on_form ) { $to_array['multi_ids'] = $this->get_form_settings_multi_ids( $module_id ); } $to_array_with_form = $to_array; return $to_array_with_form; } /** * Gets activable status. * * @return bool */ final public function is_activable() { if ( is_null( $this->is_activable ) ) { $this->is_activable = $this->check_is_activable(); } return $this->is_activable; } /** * Checks if the integration meets the requirements to be activated. * Override this method if you have another logic for checking activable integrations. * Non-activable integrations are instantiated, but not listed for the users to be used. * If your integration has certain requirements that should prevent it from being * instantiated if not met, override @see Hustle_Provider_Abstract::check_is_compatible() instead. * -Optional. * * @return bool */ public function check_is_activable() { if ( ! self::check_is_compatible( $this->class ) ) { return false; } return true; } /** * Checks if the provider meets the requirements to be instantiated. * If the provider is not compatible, it won't be instantiated. * Instantiating a not compatible provider may trigger PHP errors. * By default, it will return false if: * -The installed PHP version is lower than the required by your integration. * -The installed Hustle version is lower than the required by your integration. * * Override this method if you have another logic for checking if your integration is compatible. * -Optional. * * @since 3.0.5 * * @param string $class_name Provider's class name. * @return bool */ public static function check_is_compatible( $class_name ) { // PHP 5.2 compatibility. // We can remove this now. YAI!! $reflector = new ReflectionClass( $class_name ); $min_php_version = $reflector->getStaticPropertyValue( 'min_php_version' ); $is_php_version_supported = version_compare( PHP_VERSION, $min_php_version, '>=' ); if ( ! $is_php_version_supported ) { return false; } // If it's a test version, skip Hustle version validation. if ( false !== stripos( Opt_in::VERSION, 'beta' ) || false !== stripos( Opt_in::VERSION, 'alpha' ) ) { return true; } $min_hustle_version = $reflector->getStaticPropertyValue( 'min_hustle_version' ); $is_hustle_version_supported = version_compare( Opt_In::VERSION, $min_hustle_version, '>=' ); if ( ! $is_hustle_version_supported ) { return false; } return true; } /** * Override this method to add an action when the user deactivates the addon. * * @example DROP table * return true when succes * return false on failure, it will stop the deactivate process * * @since 4.0.0 * * @param array $data Data passed during deactivation. * @return bool */ public function deactivate( $data = array() ) { return true; } /** * Override this method to add an action when the user activates the provider * * @example CREATE table * return true when succes, false on failure. Hustle will stop activation process on failure. * * @since 4.0.0 * @return bool */ public function activate() { return true; } /** * Override this method to add an action when the version of the provider changed. * * @example CREATE table * return true when succes * return false on failure, forminator will stop activation process * * @since 4.0.3 * * @param string $old_version Version of the previously installed provider. * @param string $new_version Version of the current provider. * * @return bool true on success, false on failure. This will stop the activation process */ public function version_changed( $old_version, $new_version ) { return true; } /** * Check if the version of the provider has changed. * * @since 4.0.3 * @return bool */ final public function is_version_changed() { $installed_version = $this->get_installed_version(); // New installed. if ( false === $installed_version ) { return false; } $version_is_changed = version_compare( $this->version, $installed_version, '!=' ); if ( $version_is_changed ) { return true; } return false; } /** * Get the currently installed provider version * retrieved from the wp options. * * @since 4.0.3 * @return string|bool */ final public function get_installed_version() { return get_option( $this->get_version_options_name(), false ); } /** * Get error message on activation * * @since 4.0.0 * @return string */ public function get_activation_error_message() { return $this->activation_error_message; } /** * Get Global settings wizard * This function will process @see Hustle_Provider_Abstract::settings_wizards() * Keep in mind this function will only be called when @see Hustle_Provider_Abstract::is_settings_available() returns `true` * which will call @see Hustle_Provider_Abstract::settings_wizards() to check if requirements are passed. * * @since 4.0.0 * * @param array $submitted_data Array with the submitted data. Softly sanitized by @see Opt_In_Utils::validate_and_sanitize_fields(). * @param int $module_id ID of the module to which the settings wizard belongs to if retrieved from within a module and not from global settings. * @param int $current_step Step from which the call is made. * @param int $step Step to which the user is going. * * @return array|mixed */ final public function get_settings_wizard( $submitted_data, $module_id = 0, $current_step = 0, $step = 0 ) { $steps = $this->settings_wizards(); if ( ! is_array( $steps ) ) { /* translators: provider's title */ return $this->get_empty_wizard( sprintf( __( 'No settings available for %s', 'hustle' ), $this->get_title() ) ); } $total_steps = count( $steps ); if ( $total_steps < 1 ) { /* translators: provider's title */ return $this->get_empty_wizard( sprintf( __( 'No settings available for %s', 'hustle' ), $this->get_title() ) ); } if ( ! isset( $steps[ $step ] ) ) { // Go to last step. $step = $total_steps - 1; return $this->get_settings_wizard( $submitted_data, $module_id, $current_step ); } if ( $step > 0 ) { if ( $current_step > 0 ) { // Check previous step is complete. $prev_step = $current_step - 1; $prev_step_is_completed = true; // Only call `is_completed` when its defined. if ( isset( $steps[ $prev_step ]['is_completed'] ) && is_callable( $steps[ $prev_step ]['is_completed'] ) ) { $prev_step_is_completed = call_user_func( $steps[ $prev_step ]['is_completed'], $submitted_data ); } if ( ! $prev_step_is_completed ) { $step --; return $this->get_settings_wizard( $submitted_data, $module_id, $current_step, $step ); } } // Only validation when it moves forward. if ( $step > $current_step ) { $current_step_result = $this->get_settings_wizard( $submitted_data, $module_id, $current_step, $current_step ); if ( isset( $current_step_result['has_errors'] ) && true === $current_step_result['has_errors'] ) { return $current_step_result; } else { // Set empty submitted data for next step. $submitted_data = array(); } } } return $this->get_wizard( $steps, $submitted_data, $module_id, $step ); } /** * Get Form Setting Wizard * This function will process @see Hustle_Provider_Abstract::form_settings_wizard() * Keep in mind this function will only be called when @see Hustle_Provider_Abstract::is_form_settings_available() returns `true` * which will call @see Hustle_Provider_Abstract::form_settings_wizard() to check if requirements are passed. * * @since 3.0.5 * @since 4.0.0 Add global form settings steps at the beginning if the provider is not already connected. $module_id param added. * * @param array $submitted_data Array with the submitted data. Softly sanitized by @see Opt_In_Utils::validate_and_sanitize_fields(). * @param int $module_id ID of the module to which the setting wizard belongs to. * @param int $current_step Step from which the call is made. * @param int $step Step to which the user is going. * * @return array|mixed */ final public function get_form_settings_wizard( $submitted_data, $module_id, $current_step = 0, $step = 0 ) { // Check if provider is connected, if so - go to the next step. if ( $this->is_connected() && 0 === $current_step ) { ++$step; ++$current_step; } // Check if the global account was already selected, if so - go to the next step. $form_settings_instance = $this->get_provider_form_settings( $module_id ); if ( $form_settings_instance->is_multi_global_select_step_completed() && 1 === $current_step ) { ++$step; ++$current_step; } $settings_steps = $this->settings_wizards(); $form_settings_steps = $this->get_form_settings_steps( $module_id ); $steps = array_merge( $settings_steps, $form_settings_steps ); if ( ! is_array( $steps ) ) { /* translators: provider's title */ return $this->get_empty_wizard( sprintf( __( 'No Form Settings available for %s', 'hustle' ), $this->get_title() ) ); } $total_steps = count( $steps ); if ( $total_steps < 1 ) { /* translators: provider's title */ return $this->get_empty_wizard( sprintf( __( 'No Form Settings available for %s', 'hustle' ), $this->get_title() ) ); } if ( ! isset( $steps[ $step ] ) ) { // Go to last step. $step = $total_steps - 1; return $this->get_form_settings_wizard( $submitted_data, $module_id, $current_step, $step ); } if ( $step > 0 ) { if ( $current_step > 0 ) { // Check previous step is complete. $prev_step = $current_step - 1; $prev_step_is_completed = true; // Only call `is_completed` when its defined. if ( isset( $steps[ $prev_step ]['is_completed'] ) && is_callable( $steps[ $prev_step ]['is_completed'] ) ) { $prev_step_is_completed = call_user_func( $steps[ $prev_step ]['is_completed'], $submitted_data ); } if ( ! $prev_step_is_completed ) { $step --; return $this->get_form_settings_wizard( $submitted_data, $module_id, $current_step, $step ); } } // Only validation when it moves forward. if ( $step > $current_step ) { $current_step_result = $this->get_form_settings_wizard( $submitted_data, $module_id, $current_step, $current_step ); if ( isset( $current_step_result['has_errors'] ) && true === $current_step_result['has_errors'] ) { return $current_step_result; } else { // Set empty submitted data for next step, except preserved as reference. $preserved_keys = array( 'multi_id', ); foreach ( $submitted_data as $key => $value ) { if ( ! in_array( $key, $preserved_keys, true ) ) { unset( $submitted_data[ $key ] ); } } } } } return $this->get_wizard( $steps, $submitted_data, $module_id, $step ); } /** * Gets the steps from integration's form settings wizard. * * @since 3.0.5 * @since 4.0.0 $module_id param added * * @param string $module_id ID of the module to get the settings steps for. * @param bool $check_steps_exist Check are steps available?. * @return array */ private function get_form_settings_steps( $module_id, $check_steps_exist = false ) { $form_settings_instance = $this->get_provider_form_settings( $module_id ); $form_settings_steps = array(); if ( $this->is_allow_multi_on_global() ) { $form_settings_steps = $form_settings_instance->get_form_settings_global_multi_id_step(); } if ( $check_steps_exist && ! empty( $form_settings_steps ) ) { // If we already got some steps and we're checking are steps available - just return these steps whithout additional work. return $form_settings_steps; } if ( ! is_null( $form_settings_instance ) && $form_settings_instance instanceof Hustle_Provider_Form_Settings_Abstract ) { $form_settings_steps = array_merge( $form_settings_steps, $form_settings_instance->form_settings_wizards() ); } return $form_settings_steps; } /** * Checks whether the integration has global settings available. * This function will check @see Hustle_Provider_Abstract::settings_wizards() * as a valid multi array. * * @since 3.0.5 * @return bool */ public function is_settings_available() { if ( ! is_admin() ) { return false; } $steps = $this->settings_wizards(); if ( ! is_array( $steps ) ) { return false; } if ( count( $steps ) < 1 ) { return false; } return true; } /** * Checks whether the integration has available form settings. * This function will check @see Hustle_Provider_Form_Settings_Abstract::form_settings_wizards() * as a valid multi array. * * @since 3.0.5 * @since 4.0.0 $module_id param added * * @param string $module_id ID of the module to check whether the form settings is available for. * @return bool */ final public function is_form_settings_available( $module_id ) { if ( ! is_admin() ) { return false; } $steps = $this->get_form_settings_steps( $module_id, true ); if ( ! is_array( $steps ) || count( $steps ) < 1 ) { return false; } return true; } /** * Flag to check if a provider is connected. This is true if the global settings such as the API key is completed. * * @since 4.0.0 * @return boolean */ final public function is_connected() { if ( ! $this->is_active() ) { return false; } if ( $this->is_allow_multi_on_global() ) { // Mark as active when there's at least one active connection. if ( false !== $this->find_one_global_active_connection() ) { return true; } } else { if ( $this->settings_are_completed() ) { return true; } } return false; } /** * Flag to check if the settings is completed. This is true if the global settings such as the API key is completed. * * @since 4.0.0 * * @param string $multi_id ID of the global instance of the provider. * @return boolean */ protected function settings_are_completed( $multi_id = '' ) { $settings_values = $this->get_settings_values(); $is_connected = true; foreach ( $this->completion_options as $key ) { if ( empty( $multi_id ) || ! is_string( $multi_id ) ) { $is_connected = $is_connected && ! empty( $settings_values[ $key ] ); } else { $is_connected = $is_connected && ! empty( $settings_values[ $multi_id ][ $key ] ); } } return $is_connected; } /** * Flag for check if a provider is connected to a module. * This is true when a module's setting such as list id is completed. * * @since 4.0.0 * * @param string $module_id ID of the module to check. * @return boolean */ public function is_form_connected( $module_id ) { if ( ! $this->is_connected() ) { return false; } $form_settings_instance = $this->get_provider_form_settings( $module_id ); if ( ! $form_settings_instance instanceof $this->form_settings ) { return false; } $saved_form_settings = $form_settings_instance->get_form_settings_values(); if ( empty( $saved_form_settings ) ) { return false; } $is_connected = true; $required_form_options = $form_settings_instance->get_form_completion_options( $saved_form_settings ); foreach ( $required_form_options as $option ) { $is_connected = $is_connected && ! empty( $saved_form_settings[ $option ] ); } // Disconnect the form if the settings are half-way completed. if ( ! $is_connected ) { $form_settings_instance->disconnect_form( array() ); // Check if the parent exists. Disconnect from form if it doesn't. } elseif ( $this->is_allow_multi_on_global() ) { $selected_global_multi_id = $this->get_selected_global_multi_id( $module_id ); if ( empty( $selected_global_multi_id ) ) { $form_settings_instance->disconnect_form( array() ); } } return $is_connected; } /** * Add Identifier name to the provider $data * * @param array $data Provider data. * @param string $module_id Module id. * @return array */ public function maybe_add_multi_name( $data, $module_id ) { $selected_global_multi_id = $this->get_selected_global_multi_id( $module_id ); if ( ! empty( $selected_global_multi_id ) && ! empty( $selected_global_multi_id['name'] ) ) { $data['multi_name'] = $selected_global_multi_id['name']; } return $data; } /** * Get selected global multi id (Identifier) * * @param string $module_id Module id. * @return boolean|array */ public function get_selected_global_multi_id( $module_id ) { $form_settings_instance = $this->get_provider_form_settings( $module_id ); if ( ! $form_settings_instance instanceof $this->form_settings ) { return false; } $global_settings = $this->get_settings_values(); $form_settings = $form_settings_instance->get_form_settings_values(); // Disconnect integration from form if the global instance it was connected to is gone. if ( ! empty( $form_settings['selected_global_multi_id'] ) && ! empty( $global_settings[ $form_settings['selected_global_multi_id'] ] ) ) { return $global_settings[ $form_settings['selected_global_multi_id'] ]; } return false; } /** * Return wether the provider is active. * * @since 4.0.0 * * @return boolean */ final public function is_active() { return Hustle_Provider_Utils::is_provider_active( $this->get_slug() ); } /** * Gets the class name of the integration's form settings class. * * @see Hustle_Provider_Form_Settings_Abstract * * @since 3.0.5 * @return null|string */ final public function get_form_settings_class_name() { $provider_slug = $this->get_slug(); $form_settings_class_name = $this->form_settings; /** * Filter the class name of the integration's form settings class. * * Form settings class name is a string * it will be validated by `class_exists` and must be instanceof @see Hustle_Provider_Form_Settings_Abstract * * @since 3.0.5 * @param string $form_settings_class_name */ $form_settings_class_name = apply_filters( 'hustle_provider_' . $provider_slug . '_form_settings_class_name', $form_settings_class_name ); return $form_settings_class_name; } /** * Gets Form Settings Instance. * * @since 3.0.5 * * @param string $module_id ID of the module. * @return Hustle_Provider_Form_Settings_Abstract | null * @throws Exception With the message to add to logs. */ final public function get_provider_form_settings( $module_id ) { $class_name = $this->get_form_settings_class_name(); if ( ! isset( $this->provider_form_settings_instance[ $module_id ] ) || ! $this->provider_form_settings_instance[ $module_id ] instanceof Hustle_Provider_Form_Settings_Abstract ) { if ( empty( $class_name ) ) { return null; } if ( ! class_exists( $class_name ) ) { return null; } try { $form_settings_instance = new $class_name( $this, $module_id ); if ( ! $form_settings_instance instanceof Hustle_Provider_Form_Settings_Abstract ) { throw new Exception( $class_name . ' is not instanceof Hustle_Provider_Form_Settings_Abstract' ); } $this->provider_form_settings_instance[ $module_id ] = $form_settings_instance; } catch ( Exception $e ) { Hustle_Provider_Utils::maybe_log( $this->get_slug(), 'Failed to instantiate its _form_settings_instance', $e->getMessage() ); return null; } } return $this->provider_form_settings_instance[ $module_id ]; } /** * Executor of before_get_form_settings values, to be correctly mapped with form_setting instance for module_id. * * @since 4.0.0 * * @param array $values Settings to be stored. * @param string $module_id ID of the module to store the settings into. * * @return mixed */ final public function before_get_form_settings_values( $values, $module_id ) { $form_settings = $this->get_provider_form_settings( $module_id ); if ( $form_settings instanceof Hustle_Provider_Form_Settings_Abstract ) { if ( is_callable( array( $form_settings, 'before_get_form_settings_values' ) ) ) { return $form_settings->before_get_form_settings_values( $values ); } } return $values; } /** * Executor of before_save_form_settings_ values, to be correctly mapped with form_setting instance for module_id * * @since 4.0.0 * * @param array $values Settings to be stored. * @param string $module_id ID of the module to store the settings into. * * @return mixed */ final public function before_save_form_settings_values( $values, $module_id ) { $form_settings = $this->get_provider_form_settings( $module_id ); if ( $form_settings instanceof Hustle_Provider_Form_Settings_Abstract ) { if ( is_callable( array( $form_settings, 'before_save_form_settings_values' ) ) ) { return $form_settings->before_save_form_settings_values( $values ); } } return $values; } /** * Get Form Hooks of Addons * * @since 4.0.0 * * @param string $module_id Module ID. * @return Hustle_Provider_Form_Hooks_Abstract|null */ final public function get_addon_form_hooks( $module_id ) { if ( ! isset( $this->provider_form_hooks_instances[ $module_id ] ) || ! $this->provider_form_hooks_instances[ $module_id ] instanceof Hustle_Provider_Form_Hooks_Abstract ) { if ( empty( $this->form_hooks ) ) { return null; } if ( ! class_exists( $this->form_hooks ) ) { return null; } try { $classname = $this->form_hooks; $this->provider_form_hooks_instances[ $module_id ] = new $classname( $this, $module_id ); } catch ( Exception $e ) { Hustle_Provider_Utils::maybe_log( $this->get_slug(), 'Failed to instantiate its _addon_form_hooks_instance', $e->getMessage() ); return null; } } return $this->provider_form_hooks_instances[ $module_id ]; } /** * Gets the requested wizard. * * @since 3.0.5 * @since 4.0.0 $module_id param added. $is_close, $is_submit, $data_to_save params removed. * * @param array $steps Array with all the wizard's steps from the integration. * @param array $submitted_data Array with the submitted data. Softly sanitized by @see Opt_In_Utils::validate_and_sanitize_fields(). * @param string $module_id Module ID. * @param int $step Step from which the call is made. * * @return array|mixed */ private function get_wizard( $steps, $submitted_data, $module_id, $step = 0 ) { $total_steps = count( $steps ); $is_submit = ! empty( $submitted_data['hustle_is_submit'] ); // Validate callback, when its empty or not callable, mark as no wizard. if ( ! isset( $steps[ $step ]['callback'] ) || ! is_callable( $steps[ $step ]['callback'] ) ) { /* translators: provider's title */ return $this->get_empty_wizard( sprintf( __( 'No Settings available for %s', 'hustle' ), $this->get_title() ) ); } $wizard = call_user_func( $steps[ $step ]['callback'], $submitted_data, $is_submit, $module_id ); // A wizard to be able to processed by our application need to has at least `html` // which will be rendered or `redirect` which will be the url for redirect user to go to. if ( ! isset( $wizard['html'] ) && ! isset( $wizard['redirect'] ) ) { /* translators: provider's title */ return $this->get_empty_wizard( sprintf( __( 'No Settings available for %s', 'hustle' ), $this->get_title() ) ); } // Add 'hustle_is_submit' hidden input at the end. if ( isset( $wizard['html'] ) ) { $wizard['html'] = $wizard['html'] . $this->get_step_html_common_hidden_fields( $submitted_data ); } $wizard['opt_in_provider_current_step'] = $step; $wizard['opt_in_provider_count_step'] = $total_steps; $wizard['opt_in_provider_has_next_step'] = ( ( $step + 1 ) >= $total_steps ? false : true ); $wizard['opt_in_provider_has_prev_step'] = ( $step > 0 ? true : false ); // If ['data_to_save] is set on $wizard, that would mean the provider hasn't been apdapted // to 4.0. Save the data here if it's not updated so it keeps working. if ( isset( $wizard['data_to_save'] ) ) { $form_settings_instance = $this->get_provider_form_settings( $module_id ); $form_settings_instance->save_form_settings_values( $wizard['data_to_save'] ); } // Close the modal if... $do_close = ( // It's a submission. ! empty( $submitted_data['hustle_is_submit'] ) && // We're in the last step. ! $wizard['opt_in_provider_has_next_step'] && // And there are no errors. ( ! isset( $wizard['has_errors'] ) || ! $wizard['has_errors'] ) ); if ( $do_close ) { $wizard['is_close'] = true; } $wizard_default_values = array( 'has_errors' => false, 'is_close' => false, 'notification' => array(), 'size' => 'small', 'has_back' => false, ); foreach ( $wizard_default_values as $key => $wizard_default_value ) { if ( ! isset( $wizard[ $key ] ) ) { $wizard[ $key ] = $wizard_default_value; } } $wizard = apply_filters( 'hustle_get_integration_form_wizard', $wizard, $this, $submitted_data, $module_id, $steps, $step ); return $wizard; } /** * Gets empty wizard markup. * Helper to display a user friendly step when no settings are available. * * @since 3.0.5 * @param string $notice Message to be shown. * @return array */ public function get_empty_wizard( $notice ) { $notice_markup = '