_id = absint( $feed['id'] ); $this->_is_active = (bool) $feed['is_active']; $this->_form_id = absint( $feed['form_id'] ); $this->_step_type = $feed['meta']['step_type']; $this->_meta = $feed['meta']; $this->_entry = $entry; } /** * Magic method to allow direct access to the settings as properties. * Returns an empty string for undefined properties allowing for graceful backward compatibility where new settings may not have been defined in stored settings. * * @param string $name The property key. * * @return mixed */ public function &__get( $name ) { if ( ! isset( $this->_meta[ $name ] ) ) { $this->_meta[ $name ] = ''; } return $this->_meta[ $name ]; } /** * Sets the value for the specified property. * * @param string $key The property key. * @param mixed $value The property value. */ public function __set( $key, $value ) { $this->_meta[ $key ] = $value; $this->$key = $value; } /** * Determines if the specified property has been defined. * * @param string $key The property key. * * @return bool */ public function __isset( $key ) { return isset( $this->_meta[ $key ] ); } /** * Deletes the specified property. * * @param string $key The property key. */ public function __unset( $key ) { unset( $this->$key ); } /** * Returns an array of the configuration of the status options for this step. * These options will appear in the step settings. * Override this method to add status options. * * For example, a status configuration may look like this: * array( * 'status' => 'complete', * 'status_label' => __( 'Complete', 'gravityflow' ), * 'destination_setting_label' => __( 'Next Step', 'gravityflow' ), * 'default_destination' => 'next', * ) * * @return array An array of arrays */ public function get_status_config() { return array( array( 'status' => 'complete', 'status_label' => __( 'Complete', 'gravityflow' ), 'destination_setting_label' => __( 'Next Step', 'gravityflow' ), 'default_destination' => 'next', ), ); } /** * Returns an array of the configuration of the status options for this step. * * @deprecated * * @return array */ public function get_final_status_config() { return $this->get_status_config(); } /** * Returns an array of quick actions to be displayed on the inbox. * * Example: * * array( * array( * 'key' => 'approve', * 'icon' => $this->get_approve_icon(), * 'label' => __( 'Approve', 'gravityflow' ), * 'show_note_field' => true * ), * array( * 'key' => 'reject', * 'icon' => $this->get_reject_icon(), * 'label' => __( 'Reject', 'gravityflow' ), * 'show_note_field' => false * ), * ); * * @return array */ public function get_actions() { return array(); } /** * Returns the resource slug for the REST API. * * @return string */ public function get_rest_base() { return $this->_rest_base; } /** * Process the REST request for an entry. * * @deprecated 1.7.1 * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response|mixed If response generated an error, WP_Error, if response * is already an instance, WP_HTTP_Response, otherwise * returns a new WP_REST_Response instance. */ public function handle_rest_request( $request ) { return new WP_Error( 'not_implemented', __( ' Not implemented', 'gravityflow' ) ); } /** * Check if a REST request has permission. * * @since 1.4.3 * @access public * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|boolean */ public function rest_permission_callback( $request ) { if ( ! is_user_logged_in() ) { // Email assignee authentication & nonce check. $nonce = $request->get_header( 'x_wp_nonce' ); if ( empty( $nonce ) ) { if ( isset( $request['_wpnonce'] ) ) { $nonce = $request['_wpnonce']; } elseif ( isset( $request['HTTP_X_WP_NONCE'] ) ) { $nonce = $request['HTTP_X_WP_NONCE']; } } if ( empty( $nonce ) ) { return false; } // Check the nonce. $result = wp_verify_nonce( $nonce, 'wp_rest' ); if ( ! $result ) { return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) ); } } $assignees = $this->get_assignees(); foreach ( $assignees as $assignee ) { if ( $assignee->is_current_user() ) { return true; } } return false; } /** * Process the REST request for an entry. * * @param WP_REST_Request $request Full data about the request. * * @return WP_REST_Response|mixed If response generated an error, WP_Error, if response * is already an instance, WP_HTTP_Response, otherwise * returns a new WP_REST_Response instance. */ public function rest_callback( $request ) { return new WP_Error( 'not_implemented', __( ' Not implemented', 'gravityflow' ) ); } /** * Returns the translated label for a status key. * * @param string $status The status key. * * @return string */ public function get_status_label( $status ) { if ( $status == 'pending' ) { return __( 'Pending', 'gravityflow' ); } $status_configs = $this->get_status_config(); foreach ( $status_configs as $status_config ) { if ( strtolower( $status ) == rgar( $status_config, 'status' ) ) { return isset( $status_config['status_label'] ) ? $status_config['status_label'] : $status; } } return $status; } /** * Returns the label for the step. * * Override this method to return a custom label. * * @return string */ public function get_label() { return $this->get_type(); } /** * If set, returns the entry for this step. * * @return array|null */ public function get_entry() { if ( empty( $this->_entry ) ) { $this->refresh_entry(); } return $this->_entry; } /** * Flushes and reloads the cached entry for this step. * * @return array|mixed|null */ public function refresh_entry() { $entry_id = $this->get_entry_id(); if ( ! empty( $entry_id ) ) { $this->_entry = GFAPI::get_entry( $entry_id ); } return $this->_entry; } /** * Returns the Form object for this step. * * @return mixed */ public function get_form() { $entry = $this->get_entry(); if ( $entry ) { $form_id = $entry['form_id']; } else { $form_id = $this->get_form_id(); } $form = GFAPI::get_form( $form_id ); return $form; } /** * Returns the ID for the current entry object. If not set the lid query arg is returned. * * @return int */ public function get_entry_id() { if ( empty( $this->_entry ) ) { return rgget( 'lid' ); } $id = absint( $this->_entry['id'] ); return $id; } /** * Returns the step type. * * @return string */ public function get_type() { return $this->_step_type; } /** * Returns the Step ID. * * @return int */ public function get_id() { return $this->_id; } /** * Is the step active? The step may have been deactivated by the user in the list of steps. * * @return bool */ public function is_active() { return $this->_is_active; } /** * Is this step supported on this server? Override to hide this step in the list of step types if the requirements are not met. * * @return bool */ public function is_supported() { return true; } /** * Returns the ID of the Form object for the step. * * @return int */ public function get_form_id() { if ( empty( $this->_form_id ) ) { $this->_form_id = absint( rgget( 'id' ) ); } return $this->_form_id; } /** * Returns the user-defined name of the step. * * @return string */ public function get_name() { return $this->step_name; } /** * Get the API for preparing common settings such as those which appear on notification tabs. * * @since 1.5.1-dev * * @return Gravity_Flow_Common_Step_Settings */ public function get_common_settings_api() { require_once( 'class-common-step-settings.php' ); return new Gravity_Flow_Common_Step_Settings(); } /** * Override this method to add settings to the step. Use the Gravity Forms Add-On Framework Settings API. * * @return array */ public function get_settings() { return array(); } /** * Override this method to set a custom icon in the step settings. * 32px x 32px * * @return string */ public function get_icon_url() { return $this->get_base_url() . '/images/gravityflow-icon-blue.svg'; } /** * Returns the Gravity Flow base URL. * * @return string */ public function get_base_url() { return gravity_flow()->get_base_url(); } /** * Returns the Gravity Flow base path. * * @return string */ public function get_base_path() { return gravity_flow()->get_base_path(); } /** * Returns the ID of the next step. * * @return int|string */ public function get_next_step_id() { if ( isset( $this->_next_step_id ) ) { return $this->_next_step_id; } $status = $this->evaluate_status(); $destination_status_key = 'destination_' . $status; if ( isset( $this->{$destination_status_key} ) ) { $next_step_id = $this->{$destination_status_key}; } else { $next_step_id = 'next'; } $this->set_next_step_id( $next_step_id ); return $next_step_id; } /** * Sets the next step. * * @param int|string $id The ID of the next step. */ public function set_next_step_id( $id ) { $this->_next_step_id = $id; } /** * Attempts to start this step for the current entry. If the step is scheduled then the entry will be queued. * * @return bool Is the step complete? */ public function start() { $entry_id = $this->get_entry_id(); $this->log_debug( __METHOD__ . '() - triggered step: ' . $this->get_name() . ' for entry id ' . $entry_id ); $step_id = $this->get_id(); gform_update_meta( $entry_id, 'workflow_step', $step_id ); $step_timestamp = $this->get_step_timestamp(); if ( empty( $step_timestamp ) ) { $this->log_debug( __METHOD__ . '() - No timestamp, adding one' ); gform_update_meta( $entry_id, 'workflow_step_' . $this->get_id() . '_timestamp', time() ); $this->refresh_entry(); } $status = $this->evaluate_status(); $this->log_debug( __METHOD__ . '() - Step status before processing: ' . $status ); if ( $this->scheduled && ! $this->validate_schedule() ) { if ( $status == 'queued' ) { $this->log_debug( __METHOD__ . '() - Step still queued: ' . $this->get_name() ); } else { $this->update_step_status( 'queued' ); $this->refresh_entry(); $this->log_event( 'queued' ); $this->log_debug( __METHOD__ . '() - Step queued: ' . $this->get_name() ); } $complete = false; } else { $this->log_debug( __METHOD__ . '() - Starting step: ' . $this->get_name() ); gform_update_meta( $entry_id, 'workflow_step_' . $this->get_id() . '_timestamp', time() ); $this->update_step_status(); $this->refresh_entry(); $this->log_event( 'started' ); $complete = $this->process(); $log_is_complete = $complete ? 'yes' : 'no'; $this->log_debug( __METHOD__ . '() - step complete: ' . $log_is_complete ); } return $complete; } /** * Is the step currently in the queue waiting for the scheduled start time? * * @return bool */ public function is_queued() { $entry = $this->get_entry(); return rgar( $entry, 'workflow_step_status_' . $this->get_id() ) == 'queued'; } /** * Validates the step schedule. * * @return bool Returns true if step is ready to proceed. */ public function validate_schedule() { if ( ! $this->scheduled ) { return true; } $this->log_debug( __METHOD__ . '() step is scheduled' ); $schedule_timestamp = $this->get_schedule_timestamp(); $this->log_debug( __METHOD__ . '() schedule_timestamp: ' . $schedule_timestamp ); $this->log_debug( __METHOD__ . '() schedule_timestamp formatted: ' . date( 'Y-m-d H:i:s', $schedule_timestamp ) ); $current_time = time(); $this->log_debug( __METHOD__ . '() current_time: ' . $current_time ); $this->log_debug( __METHOD__ . '() current_time formatted: ' . date( 'Y-m-d H:i:s', $current_time ) ); return $current_time >= $schedule_timestamp; } /** * Returns the schedule timestamp (UTC) calculated from the schedule settings. * * @return int */ public function get_schedule_timestamp() { if ( $this->schedule_type == 'date' ) { $this->log_debug( __METHOD__ . '() schedule_date: ' . $this->schedule_date ); $schedule_datetime = strtotime( $this->schedule_date ); $schedule_date = date( 'Y-m-d H:i:s', $schedule_datetime ); $schedule_date_gmt = get_gmt_from_date( $schedule_date ); $schedule_datetime = strtotime( $schedule_date_gmt ); /** * Allows the scheduled date/timestamp to be custom defined. * * @since 2.0.2-dev * * @param int $schedule_timestamp The current scheduled timestamp (UTC) * @param string $schedule_type The type of schedule defined in step settings. * @param Gravity_Flow_Step $this The current step. * * @return int */ $schedule_datetime = apply_filters( 'gravityflow_step_schedule_timestamp', $schedule_datetime, $this->schedule_type, $this ); return $schedule_datetime; } $entry = $this->get_entry(); if ( $this->schedule_type == 'date_field' ) { $this->log_debug( __METHOD__ . '() schedule_date_field: ' . $this->schedule_date_field ); $schedule_date = $entry[ (string) $this->schedule_date_field ]; $this->log_debug( __METHOD__ . '() schedule_date: ' . $schedule_date ); $schedule_datetime = strtotime( $schedule_date ); $schedule_date = date( 'Y-m-d H:i:s', $schedule_datetime ); $schedule_date_gmt = get_gmt_from_date( $schedule_date ); $schedule_datetime = strtotime( $schedule_date_gmt ); // Calculate offset. if ( $this->schedule_date_field_offset ) { $offset = 0; switch ( $this->schedule_date_field_offset_unit ) { case 'minutes' : $offset = ( MINUTE_IN_SECONDS * $this->schedule_date_field_offset ); break; case 'hours' : $offset = ( HOUR_IN_SECONDS * $this->schedule_date_field_offset ); break; case 'days' : $offset = ( DAY_IN_SECONDS * $this->schedule_date_field_offset ); break; case 'weeks' : $offset = ( WEEK_IN_SECONDS * $this->schedule_date_field_offset ); break; } if ( $this->schedule_date_field_before_after == 'before' ) { $schedule_datetime = $schedule_datetime - $offset; } else { $schedule_datetime += $offset; } } /** * Allows the scheduled date/timestamp to be custom defined. * * @since 2.0.2-dev * * @param int $schedule_timestamp The current scheduled timestamp (UTC) * @param string $schedule_type The type of schedule defined in step settings. * @param Gravity_Flow_Step $this The current step. * * @return int */ $schedule_datetime = apply_filters( 'gravityflow_step_schedule_timestamp', $schedule_datetime, $this->schedule_type, $this ); return $schedule_datetime; } $entry_timestamp = $this->get_step_timestamp(); $schedule_timestamp = $entry_timestamp; if ( $this->schedule_delay_offset ) { switch ( $this->schedule_delay_unit ) { case 'minutes' : $schedule_timestamp += ( MINUTE_IN_SECONDS * $this->schedule_delay_offset ); break; case 'hours' : $schedule_timestamp += ( HOUR_IN_SECONDS * $this->schedule_delay_offset ); break; case 'days' : $schedule_timestamp += ( DAY_IN_SECONDS * $this->schedule_delay_offset ); break; case 'weeks' : $schedule_timestamp += ( WEEK_IN_SECONDS * $this->schedule_delay_offset ); break; } } /** * Allows the scheduled date/timestamp to be custom defined. * * @since 2.0.2-dev * * @param int $schedule_timestamp The current scheduled timestamp (UTC) * @param string $schedule_type The type of schedule defined in step settings. * @param Gravity_Flow_Step $this The current step. * * @return int */ $schedule_timestamp = apply_filters( 'gravityflow_step_schedule_timestamp', $schedule_timestamp, $this->schedule_type, $this ); return $schedule_timestamp; } /** * Determines if the step has expired. * * @return bool */ public function is_expired() { if ( ! $this->supports_expiration() ) { return false; } if ( ! $this->expiration ) { return false; } $this->log_debug( __METHOD__ . '() step is scheduled for expiration' ); $expiration_timestamp = $this->get_expiration_timestamp(); $this->log_debug( __METHOD__ . '() expiration_timestamp UTC: ' . $expiration_timestamp ); $this->log_debug( __METHOD__ . '() expiration_timestamp formatted UTC: ' . date( 'Y-m-d H:i:s', $expiration_timestamp ) ); // Schedule delay is relative to UTC. Schedule date is relative to timezone of the site. $current_time = time(); $this->log_debug( __METHOD__ . '() current_time UTC: ' . $current_time ); $this->log_debug( __METHOD__ . '() current_time formatted UTC: ' . date( 'Y-m-d H:i:s', $current_time ) ); $is_expired = $current_time >= $expiration_timestamp; $this->log_debug( __METHOD__ . '() is expired? ' . ( $is_expired ? 'yes' : 'no' ) ); return $is_expired; } /** * Returns the schedule timestamp calculated from the schedule settings. * * @return bool|int */ public function get_expiration_timestamp() { if ( ! $this->expiration ) { return false; } if ( $this->expiration_type == 'date' ) { $this->log_debug( __METHOD__ . '() expiration_date: ' . $this->expiration_date ); $expiration_datetime = strtotime( $this->expiration_date ); $expiration_date = date( 'Y-m-d H:i:s', $expiration_datetime ); $expiration_date_gmt = get_gmt_from_date( $expiration_date ); $expiration_datetime = strtotime( $expiration_date_gmt ); return $expiration_datetime; } $entry = $this->get_entry(); if ( $this->expiration_type == 'date_field' ) { $this->log_debug( __METHOD__ . '() expiration_date_field: ' . $this->expiration_date_field ); $expiration_date = $entry[ (string) $this->expiration_date_field ]; $this->log_debug( __METHOD__ . '() expiration_date: ' . $expiration_date ); $expiration_datetime = strtotime( $expiration_date ); $expiration_date = date( 'Y-m-d H:i:s', $expiration_datetime ); $schedule_date_gmt = get_gmt_from_date( $expiration_date ); $expiration_datetime = strtotime( $schedule_date_gmt ); // Calculate offset. if ( $this->expiration_date_field_offset ) { $offset = 0; switch ( $this->expiration_date_field_offset_unit ) { case 'minutes' : $offset = ( MINUTE_IN_SECONDS * $this->expiration_date_field_offset ); break; case 'hours' : $offset = ( HOUR_IN_SECONDS * $this->expiration_date_field_offset ); break; case 'days' : $offset = ( DAY_IN_SECONDS * $this->expiration_date_field_offset ); break; case 'weeks' : $offset = ( WEEK_IN_SECONDS * $this->sexpiration_date_field_offset ); break; } if ( $this->expiration_date_field_before_after == 'before' ) { $expiration_datetime = $expiration_datetime - $offset; } else { $expiration_datetime += $offset; } } return $expiration_datetime; } $entry_timestamp = $this->get_step_timestamp(); $expiration_timestamp = $entry_timestamp; switch ( $this->expiration_delay_unit ) { case 'minutes' : $expiration_timestamp += ( MINUTE_IN_SECONDS * $this->expiration_delay_offset ); break; case 'hours' : $expiration_timestamp += ( HOUR_IN_SECONDS * $this->expiration_delay_offset ); break; case 'days' : $expiration_timestamp += ( DAY_IN_SECONDS * $this->expiration_delay_offset ); break; case 'weeks' : $expiration_timestamp += ( WEEK_IN_SECONDS * $this->expiration_delay_offset ); break; } return $expiration_timestamp; } /** * Returns the value of the entries workflow_timestamp property. * * @return string|int */ public function get_entry_timestamp() { $entry = $this->get_entry(); return $entry['workflow_timestamp']; } /** * Returns the step timestamp from the entry meta. * * @return bool|int */ public function get_step_timestamp() { $timestamp = gform_get_meta( $this->get_entry_id(), 'workflow_step_' . $this->get_id() . '_timestamp' ); return $timestamp; } /** * Process the step. For example, assign to a user, send to a service, send a notification or do nothing. Return (bool) $complete. * * @return bool Is the step complete? */ public function process() { return true; } /** * Set the assignee status to pending and trigger sending of the assignee notification if enabled. * * @return bool */ public function assign() { $complete = $this->is_complete(); $assignees = $this->get_assignees(); if ( empty( $assignees ) ) { $this->add_note( sprintf( __( '%s: No assignees', 'gravityflow' ), $this->get_name() ) ); } else { foreach ( $assignees as $assignee ) { $assignee->update_status( 'pending' ); // Send notification. $this->maybe_send_assignee_notification( $assignee ); $complete = false; } } return $complete; } /** * Sends the assignee email if the assignee_notification_setting is enabled. * * @param Gravity_Flow_Assignee $assignee The assignee properties. * @param bool $is_reminder Indicates if this is a reminder notification. Default is false. */ public function maybe_send_assignee_notification( $assignee, $is_reminder = false ) { if ( $this->assignee_notification_enabled ) { $this->send_assignee_notification( $assignee, $is_reminder ); } } /** * Retrieves the properties for the specified notification type; building an array using the keys required by Gravity Forms. * * @param string $type The type of notification currently being processed e.g. assignee, approval, or rejection. * * @return array */ public function get_notification( $type ) { $notification = array( 'workflow_notification_type' => $type ); $type .= '_notification_'; $from_name = $type . 'from_name'; $from_email = $type . 'from_email'; $subject = $type . 'subject'; $notification['fromName'] = empty( $this->{$from_name} ) ? get_bloginfo() : $this->{$from_name}; $notification['from'] = empty( $this->{$from_email} ) ? get_bloginfo( 'admin_email' ) : $this->{$from_email}; $notification['replyTo'] = $this->{$type . 'reply_to'}; $notification['bcc'] = $this->{$type . 'bcc'}; $notification['message'] = $this->{$type . 'message'}; $notification['disableAutoformat'] = $this->{$type . 'disable_autoformat'}; if ( empty( $this->{$subject} ) ) { $form = $this->get_form(); $notification['subject'] = $form['title'] . ': ' . $this->get_name(); } else { $notification['subject'] = $this->{$subject}; } if ( defined( 'PDF_EXTENDED_VERSION' ) && version_compare( PDF_EXTENDED_VERSION, '4.0-RC2', '>=' ) ) { if ( $this->{$type . 'gpdfEnable'} ) { $gpdf_id = $this->{$type . 'gpdfValue'}; $notification = $this->gpdf_add_notification_attachment( $notification, $gpdf_id ); } } return $notification; } /** * Retrieve the assignees for the current * * @param string $type The type of notification currently being processed e.g. assignee, approval, or rejection. * * @return array */ public function get_notification_assignees( $type ) { $type .= '_notification_'; $notification_type = $this->{$type . 'type'}; $assignees = array(); switch ( $notification_type ) { case 'select' : $users = $this->{$type . 'users'}; if ( is_array( $users ) ) { foreach ( $users as $assignee_key ) { $assignees[] = $this->get_assignee( $assignee_key ); } } break; case 'routing' : $routings = $this->{$type . 'routing'}; if ( is_array( $routings ) ) { foreach ( $routings as $routing ) { if ( $this->evaluate_routing_rule( $routing ) ) { $assignees[] = $this->get_assignee( rgar( $routing, 'assignee' ) ); } } } break; } return $assignees; } /** * Sends the assignee email. * * @param Gravity_Flow_Assignee $assignee The assignee properties. * @param bool $is_reminder Indicates if this is a reminder notification. Default is false. */ public function send_assignee_notification( $assignee, $is_reminder = false ) { $this->log_debug( __METHOD__ . '() starting. assignee: ' . $assignee->get_key() ); $notification = $this->get_notification( 'assignee' ); if ( $is_reminder ) { $notification['subject'] = esc_html__( 'Reminder', 'gravityflow' ) . ': ' . $notification['subject']; } $assignee->send_notification( $notification ); } /** * Override this method to replace merge tags. * Important: call the parent method first. * $text = parent::replace_variables( $text, $assignee ); * * @param string $text The text containing merge tags to be processed. * @param Gravity_Flow_Assignee $assignee The assignee properties. * * @return string */ public function replace_variables( $text, $assignee ) { return $text; } /** * Replace the {workflow_entry_link}, {workflow_entry_url}, {workflow_inbox_link}, and {workflow_inbox_url} merge tags. * * @param string $text The text being processed. * @param Gravity_Flow_Assignee $assignee The assignee properties. * * @return string */ public function replace_workflow_url_variables( $text, $assignee ) { _deprecated_function( 'replace_workflow_url_variables', '1.7.2', 'Gravity_Flow_Merge_Tags::get( \'workflow_url\', $args )->replace()' ); $args = array( 'assignee' => $assignee, 'step' => $this, ); $text = Gravity_Flow_Merge_Tags::get( 'workflow_url', $args )->replace( $text ); return $text; } /** * Get the access token for the workflow_entry_ and workflow_inbox_ merge tags. * * @param array $a The merge tag attributes. * @param Gravity_Flow_Assignee $assignee The assignee properties. * * @return string */ public function get_workflow_url_access_token( $a, $assignee ) { _deprecated_function( 'get_workflow_url_access_token', '1.7.2', 'gravity_flow()->generate_access_token' ); $force_token = $a['token']; $token = ''; if ( $assignee && $force_token ) { $token_lifetime_days = apply_filters( 'gravityflow_entry_token_expiration_days', 30, $assignee ); $token_expiration_timestamp = strtotime( '+' . (int) $token_lifetime_days . ' days' ); $token = gravity_flow()->generate_access_token( $assignee, null, $token_expiration_timestamp ); } return $token; } /** * Replace the {workflow_cancel_link} and {workflow_cancel_url} merge tags. * * @param string $text The text being processed. * @param Gravity_Flow_Assignee $assignee The assignee properties. * * @return string */ public function replace_workflow_cancel_variables( $text, $assignee ) { _deprecated_function( 'replace_workflow_cancel_variables', '1.7.2', 'Gravity_Flow_Merge_Tags::get( \'workflow_cancel\', $args )->replace()' ); if ( $assignee ) { $args = array( 'assignee' => $assignee, 'step' => $this, ); $text = Gravity_Flow_Merge_Tags::get( 'workflow_cancel', $args )->replace( $text ); } return $text; } /** * Returns the entry URL. * * @param int|null $page_id The ID of the WordPress Page where the shortcode is located. * @param Gravity_Flow_Assignee $assignee The assignee properties. * @param string $access_token The access token for the current assignee. * * @return string */ public function get_entry_url( $page_id = null, $assignee = null, $access_token = '' ) { _deprecated_function( 'get_entry_url', '1.7.2', 'Gravity_Flow_Common::get_workflow_url' ); $query_args = array( 'page' => 'gravityflow-inbox', 'view' => 'entry', 'id' => $this->get_form_id(), 'lid' => $this->get_entry_id(), ); return Gravity_Flow_Common::get_workflow_url( $query_args, $page_id, $assignee, $access_token ); } /** * Returns the inbox URL. * * @param int|null $page_id The ID of the WordPress Page where the shortcode is located. * @param Gravity_Flow_Assignee $assignee The assignee properties. * @param string $access_token The access token for the current assignee. * * @return string */ public function get_inbox_url( $page_id = null, $assignee = null, $access_token = '' ) { _deprecated_function( 'get_inbox_url', '1.7.2', 'Gravity_Flow_Common::get_workflow_url' ); $query_args = array( 'page' => 'gravityflow-inbox', ); return Gravity_Flow_Common::get_workflow_url( $query_args, $page_id, $assignee, $access_token ); } /** * Updates the status for this step. * * @param string|bool $status The step status. */ public function update_step_status( $status = false ) { if ( empty( $status ) ) { $status = 'pending'; } $entry_id = $this->get_entry_id(); $step_id = $this->get_id(); gform_update_meta( $entry_id, 'workflow_step_status_' . $step_id, $status ); gform_update_meta( $entry_id, 'workflow_step_status_' . $step_id . '_timestamp', time() ); } /** * Ends the step if it's complete. * * @return bool Is the step complete? */ public function end_if_complete() { $id = $this->get_next_step_id(); $this->set_next_step_id( $id ); $complete = $this->is_complete(); if ( $complete ) { $this->end(); } return $complete; } /** * Optionally override this method to add additional entry meta. See the Gravity Forms Add-On Framework for details on the return array. * * @param array $entry_meta The entry meta properties. * @param int $form_id The current form ID. * * @return array */ public function get_entry_meta( $entry_meta, $form_id ) { return array(); } /** * Returns the status key * * @param string $assignee The assignee key. * @param bool|string $type The assignee type. * * @return string */ public function get_status_key( $assignee, $type = false ) { if ( $type === false ) { list( $type, $value ) = rgexplode( '|', $assignee, 2 ); } else { $value = $assignee; } $key = 'workflow_' . $type . '_' . $value; return $key; } /** * Returns the status timestamp key * * @param string $assignee_key The assignee key. * @param bool|string $type The assignee type. * * @return string */ public function get_status_timestamp_key( $assignee_key, $type = false ) { if ( $type === false ) { list( $type, $value ) = rgexplode( '|', $assignee_key, 2 ); } else { $value = $assignee_key; } $key = 'workflow_' . $type . '_' . $value . '_timestamp'; return $key; } /** * Retrieves the step status from the entry meta. * * @return bool|string */ public function get_status() { $status_key = 'workflow_step_status_' . $this->get_id(); $status = gform_get_meta( $this->get_entry_id(), $status_key ); return $status; } /** * Evaluates the status for the step. * * @return string 'queued' or 'complete' */ public function evaluate_status() { if ( $this->is_queued() ) { return 'queued'; } if ( $this->is_expired() ) { return $this->get_expiration_status_key(); } $status = $this->get_status(); if ( empty( $status ) ) { return 'pending'; } return $this->status_evaluation(); } /** * Override this to perform custom evaluation of the step status. * * @return string */ public function status_evaluation() { return 'complete'; } /** * Return the value of the status expiration setting. * * @return string */ public function get_expiration_status_key() { $status_expiration = $this->status_expiration ? $this->status_expiration : 'complete'; return $status_expiration; } /** * Processes the conditional logic for the entry in this step. * * @param array $form The current form. * * @return bool */ public function is_condition_met( $form ) { $feed_meta = $this->_meta; $is_condition_enabled = rgar( $feed_meta, 'feed_condition_conditional_logic' ) == true; $logic = rgars( $feed_meta, 'feed_condition_conditional_logic_object/conditionalLogic' ); if ( ! $is_condition_enabled || empty( $logic ) ) { return true; } $entry = $this->get_entry(); return gravity_flow()->evaluate_conditional_logic( $logic, $form, $entry ); } /** * Returns the status for a user. Defaults to current WordPress user or authenticated email address. * * @param int|bool $user_id The user ID. * * @return bool|string */ public function get_user_status( $user_id = false ) { global $current_user; $type = 'user_id'; if ( empty( $user_id ) ) { if ( $token = gravity_flow()->decode_access_token() ) { $assignee_key = sanitize_text_field( $token['sub'] ); list( $type, $user_id ) = rgexplode( '|', $assignee_key, 2 ); } else { $user_id = $current_user->ID; } } $key = $this->get_status_key( $user_id, $type ); return gform_get_meta( $this->get_entry_id(), $key ); } /** * Get the current role and status. * * @return array */ public function get_current_role_status() { $current_role_status = false; $role = false; foreach ( gravity_flow()->get_user_roles() as $role ) { $current_role_status = $this->get_role_status( $role ); if ( $current_role_status == 'pending' ) { break; } } return array( $role, $current_role_status ); } /** * Returns the status for the given role. * * @param string $role The user role. * * @return bool|string */ public function get_role_status( $role ) { if ( empty( $role ) ) { return false; } $key = $this->get_status_key( $role, 'role' ); return gform_get_meta( $this->get_entry_id(), $key ); } /** * Updates the status for the given user. * * @param bool|int $user_id The user ID. * @param bool|string $new_assignee_status The assignee status. */ public function update_user_status( $user_id = false, $new_assignee_status = false ) { if ( $user_id === false ) { global $current_user; $user_id = $current_user->ID; } $key = $this->get_status_key( $user_id, 'user_id' ); gform_update_meta( $this->get_entry_id(), $key, $new_assignee_status ); } /** * Updates the status for the given role. * * @param bool|string $role The user role. * @param bool|string $new_assignee_status The assignee status. */ public function update_role_status( $role = false, $new_assignee_status = false ) { if ( $role == false ) { $roles = gravity_flow()->get_user_roles( $role ); $role = current( $roles ); } $entry_id = $this->get_entry_id(); $key = $this->get_status_key( $role, 'role' ); $timestamp = gform_get_meta( $entry_id, $key . '_timestamp' ); $duration = $timestamp ? time() - $timestamp : 0; gform_update_meta( $entry_id, $key, $new_assignee_status ); gform_update_meta( $entry_id, $key . '_timestamp', time() ); gravity_flow()->log_event( 'assignee', 'status', $this->get_form_id(), $entry_id, $new_assignee_status, $this->get_id(), $duration, $role, 'role', $role ); } /** * Returns an array of assignees for this step. * * @return Gravity_Flow_Assignee[] */ public function get_assignees() { if ( ! empty( $this->_assignees ) ) { return $this->_assignees; } if ( ! empty( $this->type ) ) { $this->maybe_add_select_assignees(); $this->maybe_add_routing_assignees(); $this->log_debug( __METHOD__ . '(): assignees: ' . print_r( $this->get_assignee_keys(), true ) ); /** * Allows the assignees to be modified for the step. * * @since 1.8.1 * * @param Gravity_Flow_Assignee[] $this->_assignees The array of Assignees. * @param Gravity_Flow_Step $this The current step. */ $this->_assignees = apply_filters( 'gravityflow_step_assignees', $this->_assignees, $this ); return $this->_assignees; } return array(); } /** * Retrieve an array containing this steps assignee details. * * @deprecated 1.8.1 * * @return Gravity_Flow_Assignee[] */ public function get_assignee_details() { _deprecated_function( 'get_assignee_details', '1.8.1', '$this->_assignees or get_assignees' ); return $this->_assignees; } /** * Flush assignee details. */ public function flush_assignees() { $this->_assignees = array(); } /** * Retrieve an array containing the assignee keys for this step. * * @return array */ public function get_assignee_keys() { $assignees = $this->_assignees; $assignee_keys = array(); foreach( $assignees as $assignee ) { $assignee_keys[] = $assignee->get_key(); } return $assignee_keys; } /** * Retrieve the assignee object for the given arguments. * * @param string|array $args An assignee key or array containing the id, type and editable_fields (if applicable). * * @return Gravity_Flow_Assignee */ public function get_assignee( $args ) { $assignee = Gravity_Flow_Assignees::create( $args, $this ); return $assignee; } /** * Get the assignee key for the current access token or user. * * @return string|bool */ public function get_current_assignee_key() { return gravity_flow()->get_current_user_assignee_key(); } /** * Get the status for the current assignee. * * @return bool|string */ public function get_current_assignee_status() { $assignee_key = $this->get_current_assignee_key(); $assignee = $this->get_assignee( $assignee_key ); return $assignee->get_status(); } /** * Adds the assignees when the 'assign to' setting is set to 'select'. */ public function maybe_add_select_assignees() { if ( $this->type != 'select' || ! is_array( $this->assignees ) ) { return; } $has_editable_fields = ! empty( $this->editable_fields ); foreach ( $this->assignees as $assignee_key ) { $args = $this->get_assignee_args( $assignee_key ); if ( $has_editable_fields ) { $args['editable_fields'] = $this->editable_fields; } $this->maybe_add_assignee( $args ); } } /** * Adds the assignees when the 'assign to' setting is set to 'routing'. */ public function maybe_add_routing_assignees() { if ( $this->type != 'routing' || ! is_array( $this->routing ) ) { return; } $entry = $this->get_entry(); foreach ( $this->routing as $routing ) { $args = $this->get_assignee_args( rgar( $routing, 'assignee' ) ); $args['editable_fields'] = rgar( $routing, 'editable_fields' ); if ( $entry ) { if ( $this->evaluate_routing_rule( $routing ) ) { $this->maybe_add_assignee( $args ); } } else { $this->maybe_add_assignee( $args ); } } } /** * Creates an array containing the assignees id and type from the supplied key. * * @param string $assignee_key The assignee key. * * @return array */ public function get_assignee_args( $assignee_key ) { list( $assignee_type, $assignee_id ) = explode( '|', $assignee_key ); $args = array( 'id' => $assignee_id, 'type' => $assignee_type, ); return $args; } /** * Adds the assignee to the step if certain conditions are met. * * @param string|array $args An assignee key or array containing the id, type and editable_fields (if applicable). */ public function maybe_add_assignee( $args ) { $assignee = $this->get_assignee( $args ); $id = $assignee->get_id(); $key = $assignee->get_key(); if ( ! empty( $id ) && ! in_array( $key, $this->get_assignee_keys() ) ) { $type = $assignee->get_type(); switch ( $type ) { case 'user_id' : $object = get_userdata( $id ); break; case 'assignee_multi_user_field' : $entry = $this->get_entry(); $json_value = $entry[ $id ]; $user_ids = json_decode( $json_value ); if ( $user_ids && is_array( $user_ids ) ) { $args['type'] = 'user_id'; foreach ( $user_ids as $user_id ) { $user = get_userdata( $user_id ); if ( $user ) { $args['id'] = $user_id; $user_assignee = $this->get_assignee( $args ); $this->_assignees[] = $user_assignee; } } } $object = false; break; case 'role' : $object = get_role( $id ); break; default : $object = true; } if ( $object ) { $this->_assignees[] = $assignee; } } } /** * Removes assignee from the step. This is only used for maintenance when the assignee settings change. * * @param Gravity_Flow_Assignee|bool $assignee The assignee properties. */ public function remove_assignee( $assignee = false ) { if ( $assignee === false ) { global $current_user; $assignee = $this->get_assignee( 'user_id|' . $current_user->ID ); } $assignee->remove(); } /** * Handles POSTed values from the workflow detail page. * * @param array $form The current form. * @param array $entry The current entry. * * @return string|bool|WP_Error Return a success feedback message safe for page output or a WP_Error instance with an error. */ public function maybe_process_status_update( $form, $entry ) { return false; } /** * Displays content inside the Workflow metabox on the workflow detail page. * * @deprecated since 1.3.2 * * @param array $form The Form array which may contain validation details. */ public function workflow_detail_status_box( $form ) { _deprecated_function( 'workflow_detail_status_box', '1.3.2', 'workflow_detail_box' ); $default_args = array( 'display_empty_fields' => true, 'check_permissions' => true, 'show_header' => true, 'timeline' => true, 'display_instructions' => true, 'sidebar' => true, 'step_status' => true, 'workflow_info' => true, ); $this->workflow_detail_box( $form, $default_args ); } /** * Displays content inside the Workflow metabox on the workflow detail page. * * @param array $form The Form array which may contain validation details. * @param array $args Additional args which may affect the display. */ public function workflow_detail_box( $form, $args ) { } /** * Displays content inside the Workflow metabox on the Gravity Forms Entry Detail page. * * @param array $form The current form. */ public function entry_detail_status_box( $form ) { } /** * Override to return an array of editable fields for the current user. * * @return array */ public function get_editable_fields() { return array(); } /** * Send the applicable notification if it is enabled and has assignees. * * @param string $type The type of notification currently being processed; approval or rejection. */ public function maybe_send_notification( $type ) { if ( ! $this->{$type . '_notification_enabled'} ) { return; } $assignees = $this->get_notification_assignees( $type ); if ( empty( $assignees ) ) { return; } $notification = $this->get_notification( $type ); $this->send_notifications( $assignees, $notification ); } /** * Sends an email. * * @param array $notification The notification properties. */ public function send_notification( $notification ) { $entry = $this->get_entry(); $form = $this->get_form(); $notification = apply_filters( 'gravityflow_notification', $notification, $form, $entry, $this ); $to = rgar( $notification, 'to' ); if ( in_array( $to, $this->_assignees_emailed ) ) { $this->log_debug( __METHOD__ . '() - aborting. assignee has already been sent a notification.' ); return; } $this->_assignees_emailed[] = $to; $this->log_debug( __METHOD__ . '() - sending notification: ' . print_r( $notification, true ) ); GFCommon::send_notification( $notification, $form, $entry ); } /** * If Gravity PDF is enabled we'll generate the appropriate PDF and attach it to the current notification * * @param array $notification The notification array currently being sent. * @param string $gpdf_id The Gravity PDF ID. * * @return array */ public function gpdf_add_notification_attachment( $notification, $gpdf_id ) { if ( ! class_exists( 'GPDFAPI' ) ) { return $notification; } /* Check if our PDF is active (might have been deactivated by users after saving Workflow) */ $form_id = $this->get_form_id(); $entry_id = $this->get_entry_id(); $pdf = GPDFAPI::get_pdf( $form_id, $gpdf_id ); if ( ! is_wp_error( $pdf ) && true === $pdf['active'] ) { /* Generate and save the PDF */ $pdf_path = GPDFAPI::create_pdf( $entry_id, $gpdf_id ); if ( ! is_wp_error( $pdf_path ) ) { /* Ensure our notification has an array setup for the attachments key */ $notification['attachments'] = ( isset( $notification['attachments'] ) ) ? $notification['attachments'] : array(); $notification['attachments'][] = $pdf_path; } } return $notification; } /** * Ends the step cleanly and wraps up loose ends. * Sets the next step. Deletes assignee status entry meta. */ public function end() { $next_step_id = $this->get_next_step_id(); $this->set_next_step_id( $next_step_id ); $status = $this->evaluate_status(); $started = $this->get_step_timestamp(); $duration = time() - $started; $this->update_step_status( $status ); $assignees = $this->get_assignees(); foreach ( $assignees as $assignee ) { $assignee->remove(); } $entry_id = $this->get_entry_id(); $step_id = $this->get_id(); if ( $this->can_set_workflow_status() ) { gform_update_meta( $entry_id, 'workflow_current_status', $status ); gform_update_meta( $entry_id, 'workflow_current_status_timestamp', time() ); } do_action( 'gravityflow_step_complete', $step_id, $entry_id, $this->get_form_id(), $status, $this ); $this->log_debug( __METHOD__ . '() - ending step ' . $step_id ); $this->log_event( 'ended', $status, $duration ); } /** * Returns TRUE if this step can alter the current and final status. * If the only status option available for this step is 'complete' then, by default, the step will not set the status. * The default final status for the workflow is 'complete'. * * @return bool */ public function can_set_workflow_status() { $status_config = $this->get_status_config(); return ! ( count( $status_config ) === 1 && $status_config[0]['status'] = 'complete' ); } /** * Override this method to check whether the step is complete in interactive and long running steps. * * @return bool */ public function is_complete() { $status = $this->evaluate_status(); return $status == 'complete' || $status == 'expired'; } /** * Adds a note to the timeline. The timeline is a filtered subset of the Gravity Forms Entry notes. * * @since 1.7.1-dev Updated to store notes in the entry meta. * @since unknown * * @param string $note The note to be added. * @param bool $is_user_event Formerly $user_id; as of 1.7.1-dev indicates if the current note is the result of an assignee action. * @param bool $deprecated Formerly $user_name; no longer used as of 1.7.1-dev. */ public function add_note( $note, $is_user_event = false, $deprecated = false ) { $user_id = false; $user_name = $this->get_type(); if ( $is_user_event ) { $assignee_key = $this->get_current_assignee_key(); if ( $assignee_key ) { $assignee = $this->get_assignee( $assignee_key ); if ( $assignee instanceof Gravity_Flow_Assignee && $assignee->get_type() === 'user_id' ) { $user_id = $assignee->get_id(); $user_name = $assignee->get_display_name(); } } } GFFormsModel::add_note( $this->get_entry_id(), $user_id, $user_name, $note, 'gravityflow' ); } /** * Adds a user submitted note. * * @since 1.7.1-dev * * @return string The user note which was added or an empty string. */ public function maybe_add_user_note() { $note = trim( rgpost( 'gravityflow_note' ) ); if ( $note ) { Gravity_Flow_Common::add_workflow_note( $note, $this->get_entry_id(), $this->get_id() ); $note = sprintf( "\n%s: %s", __( 'Note', 'gravityflow' ), $note ); } return $note; } /** * Evaluates a routing rule. * * @param array $routing_rule The routing rule properties. * * @return bool Is the routing rule a match? */ public function evaluate_routing_rule( $routing_rule ) { $this->log_debug( __METHOD__ . '(): rule: ' . print_r( $routing_rule, true ) ); $entry = $this->get_entry(); $form_id = $this->get_form_id(); $form = GFAPI::get_form( $form_id ); $entry_meta_keys = array_keys( GFFormsModel::get_entry_meta( $form_id ) ); $entry_properties = array( 'created_by', 'date_created', 'currency', 'id', 'status', 'source_url', 'ip', 'is_starred' ); $field_id = $routing_rule['fieldId'] == 'entry_id' ? 'id' : $routing_rule['fieldId']; if ( in_array( $field_id, $entry_meta_keys ) || in_array( $field_id, $entry_properties ) ) { $is_value_match = GFFormsModel::is_value_match( rgar( $entry, $field_id ), $routing_rule['value'], $routing_rule['operator'], null, $routing_rule, $form ); } else { $source_field = GFFormsModel::get_field( $form, $field_id ); $field_value = empty( $entry ) ? GFFormsModel::get_field_value( $source_field, array() ) : GFFormsModel::get_lead_field_value( $entry, $source_field ); $is_value_match = GFFormsModel::is_value_match( $field_value, $routing_rule['value'], $routing_rule['operator'], $source_field, $routing_rule, $form ); } $this->log_debug( __METHOD__ . '(): is_match: ' . var_export( $is_value_match, true ) ); return $is_value_match; } /** * Sends a notification to an array of assignees. * * @param Gravity_Flow_Assignee[] $assignees The assignee properties. * @param array $notification The notification properties. */ public function send_notifications( $assignees, $notification ) { if ( empty( $assignees ) ) { return; } $form = $this->get_form(); if ( empty( $notification['subject'] ) ) { $notification['subject'] = $form['title'] . ': ' . $this->get_name(); } else { $notification['subject'] = $this->replace_variables( $notification['subject'], null ); } foreach ( $assignees as $assignee ) { /* @var Gravity_Flow_Assignee $assignee */ $assignee->send_notification( $notification ); } } /** * Returns the number of entries on this step. * * @return int|mixed */ public function entry_count() { if ( isset( $this->_entry_count ) ) { return $this->_entry_count; } $form_id = $this->get_form_id(); $search_criteria = array( 'status' => 'active', 'field_filters' => array( array( 'key' => 'workflow_step', 'value' => $this->get_id(), ), ), ); $this->_entry_count = GFAPI::count_entries( $form_id, $search_criteria ); return $this->_entry_count; } /** * Logs debug messages to the Gravity Flow log file generated by the Gravity Forms Logging Add-On. * * @param string $message The message to be logged. */ public function log_debug( $message ) { gravity_flow()->log_debug( $message ); } /** * Retrieves the feed meta for the current step. * * @return array */ public function get_feed_meta() { return $this->_meta; } /** * Process token action if conditions are satisfied. * * @param array $action The action properties. * @param array $token The assignee token properties. * @param array $form The current form. * @param array $entry The current entry. * * @return bool|WP_Error Return a success feedback message safe for page output or false. */ public function maybe_process_token_action( $action, $token, $form, $entry ) { return false; } /** * Add a new event to the activity log. * * @param string $step_event The event name. * @param string $step_status The step status. * @param int $duration The duration in seconds, if any. */ public function log_event( $step_event, $step_status = '', $duration = 0 ) { gravity_flow()->log_event( 'step', $step_event, $this->get_form_id(), $this->get_entry_id(), $step_status, $this->get_id(), $duration ); } /** * Override to indicate if the current step supports expiration. * * @return bool */ public function supports_expiration() { return false; } /** * Returns the correct value for the step setting for the current context - either step settings or step processing. * * @param string $setting The setting key. * * @return array|mixed|string */ public function get_setting( $setting ) { $meta = $this->get_feed_meta(); if ( empty( $meta ) ) { $value = gravity_flow()->get_setting( $setting ); } else { $value = $this->{$setting}; } return $value; } /** * Process a status change for an assignee. * * @param Gravity_Flow_Assignee $assignee The assignee properties. * @param string $new_status The assignee status. * @param array $form The current form. * * @return string|bool Return a success feedback message safe for page output or false. */ public function process_assignee_status( $assignee, $new_status, $form ) { $assignee->update_status( $new_status ); $note = $this->get_name() . ': ' . esc_html__( 'Processed', 'gravityflow' ); $this->add_note( $note ); return $note; } /** * Determines if the supplied assignee key belongs to one of the steps assignees. * * @param string $assignee_key The assignee key. * * @return bool */ public function is_assignee( $assignee_key ) { $assignees = $this->get_assignees(); $current_user = wp_get_current_user(); foreach ( $assignees as $assignee ) { $key = $assignee->get_key(); if ( $key == $assignee_key ) { return true; } if ( $assignee->get_type() == 'role' && in_array( $assignee->get_id(), (array) $current_user->roles ) ) { return true; } } return false; } /** * Removes assignees from and/or adds assignees to a step. Call after updating entry values. * Make sure you call get_assignees() to get the assignees before you update the entry before you update the entry or the previous assignees may not get removed. * * @param Gravity_Flow_Assignee[] $previous_assignees The previous assignees. */ public function maybe_adjust_assignment( $previous_assignees ) { gravity_flow()->log_debug( __METHOD__ . '(): Starting' ); $this->flush_assignees(); $new_assignees = $this->get_assignees(); $new_assignees_keys = array(); foreach ( $new_assignees as $new_assignee ) { $new_assignees_keys[] = $new_assignee->get_key(); } $previous_assignees_keys = array(); foreach ( $previous_assignees as $previous_assignee ) { $previous_assignees_keys[] = $previous_assignee->get_key(); } $assignee_keys_to_add = array_diff( $new_assignees_keys, $previous_assignees_keys ); $assignee_keys_to_remove = array_diff( $previous_assignees_keys, $new_assignees_keys ); foreach ( $assignee_keys_to_add as $assignee_key_to_add ) { $assignee_to_add = $this->get_assignee( $assignee_key_to_add ); $assignee_to_add->update_status( 'pending' ); } foreach ( $assignee_keys_to_remove as $assignee_key_to_remove ) { $assignee_to_remove = $this->get_assignee( $assignee_key_to_remove ); $assignee_to_remove->remove(); } } /** * Override this to perform any tasks for the current step when restarting the workflow or step, such as cleaning up custom entry meta. */ public function restart_action() { } /** * Determine if the note is valid and update the form with the result. * * @param string $new_status The new status for the current step. * @param array $form The form currently being processed. * * @return bool */ public function validate_note( $new_status, &$form ) { $note = rgpost( 'gravityflow_note' ); $valid = $this->validate_note_mode( $new_status, $note ); if ( ! $valid ) { $form['workflow_note'] = array( 'failed_validation' => true, 'validation_message' => esc_html__( 'A note is required', 'gravityflow' ) ); } return $valid; } /** * Override this with the validation logic to determine if the submitted note for this step is valid. * * @param string $new_status The new status for the current step. * @param string $note The submitted note. * * @return bool */ public function validate_note_mode( $new_status, $note ) { return true; } /** * Get the validation result for this step. * * @param bool $valid The steps current validation state. * @param array $form The form currently being processed. * @param string $new_status The new status for the current step. * * @return array|bool|WP_Error */ public function get_validation_result( $valid, $form, $new_status ) { if ( ! $valid ) { $form['failed_validation'] = true; } $validation_result = array( 'is_valid' => $valid, 'form' => $form, ); $validation_result = $this->maybe_filter_validation_result( $validation_result, $new_status ); if ( is_wp_error( $validation_result ) ) { return $validation_result; } if ( ! $validation_result['is_valid'] ) { return new WP_Error( 'validation_result', esc_html__( 'There was a problem while updating your form.', 'gravityflow' ), $validation_result ); } return true; } /** * Override this to implement a custom filter for this steps validation result. * * @param array $validation_result The validation result and form currently being processed. * @param string $new_status The new status for the current step. * * @return array */ public function maybe_filter_validation_result( $validation_result, $new_status ) { return $validation_result; } /** * Purges assignees from the database. * * @since 2.1.2 */ public function purge_assignees() { global $wpdb; $entry_id = $this->get_entry_id(); $entry_meta_table = Gravity_Flow_Common::get_entry_meta_table_name(); $entry_id_column = Gravity_Flow_Common::get_entry_id_column_name(); $assignee_types = array( '^workflow_user_id_', '^workflow_role_', '^workflow_email_', '^workflow_api_key_', ); $assignee_names = Gravity_Flow_Assignees::get_names(); foreach ( $assignee_names as $assignee_name ) { if ( $assignee_name == 'generic' ) { continue; } $assignee_types[] = "^workflow_{$assignee_name}_"; } $assignee_types_str = join( '|', $assignee_types ); $sql = $wpdb->prepare( "DELETE FROM {$entry_meta_table} WHERE {$entry_id_column}=%d AND meta_key REGEXP %s", $entry_id, $assignee_types_str ); $result = $wpdb->query( $sql ); $this->log_debug( 'Assignees purged. number of rows deleted: ' . $result ); } }