Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added better support for handling pages with no fields after randomization. #334

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
150 changes: 88 additions & 62 deletions gravity-forms/gw-random-fields.php
Expand Up @@ -5,7 +5,7 @@
*
* Randomly display a specified number of fields on your form.
*
* @version 1.3
* @version 1.3.x
* @author David Smith <david@gravitywiz.com>
* @license GPL-2.0+
* @link https://gravitywiz.com/random-fields-with-gravity-forms/
Expand All @@ -14,30 +14,28 @@
* Plugin URI: https://gravitywiz.com/random-fields-with-gravity-forms/
* Description: Randomly display a specified number of fields on your form.
* Author: Gravity Wiz
* Version: 1.3
* Version: 1.3.x
* Author URI: http://gravitywiz.com
*/
class GFRandomFields {

public $all_random_field_ids;
public $display_count;
public $selected_field_ids = array();
public $preserve_order;

public function __construct( $form_id, $display_count = 5, $random_field_ids = false, $preserve_order = false ) {
public $restructure_pages;
public function __construct( $form_id, $display_count = 5, $random_field_ids = false, $preserve_order = false, $restructure_pages = true ) {

$this->_form_id = (int) $form_id;
$this->all_random_field_ids = array_map( 'intval', array_filter( (array) $random_field_ids ) );
$this->display_count = (int) $display_count;
$this->preserve_order = (bool) $preserve_order;
$this->restructure_pages = (bool) $restructure_pages;

add_filter( "gform_pre_render_$form_id", array( $this, 'pre_render' ) );
add_filter( "gform_pre_process_$form_id", array( $this, 'pre_render' ) );

add_filter( "gform_form_tag_$form_id", array( $this, 'store_selected_field_ids' ), 10, 2 );
add_filter( "gform_validation_$form_id", array( $this, 'validate' ) );
add_filter( "gform_pre_submission_filter_$form_id", array( $this, 'pre_render' ) );
add_filter( "gform_target_page_{$form_id}", array( $this, 'modify_target_page' ), 10, 3 );

//add_filter( "gform_admin_pre_render_$form_id", array( $this, 'admin_pre_render' ) );
add_action( 'gform_entry_created', array( $this, 'save_selected_field_ids_meta' ), 10, 2 );

}
Expand All @@ -46,15 +44,6 @@ public function pre_render( $form ) {
return $this->filter_form_fields( $form, $this->get_selected_field_ids( false, $form ) );
}

public function admin_pre_render( $form ) {

if ( (int) $form['id'] !== $this->_form_id ) {
return $form;
}

return $form;
}

public function store_selected_field_ids( $form_tag, $form ) {
$hash = $this->get_selected_field_ids_hash( $form );
$value = implode( ',', $this->get_selected_field_ids( false, $form ) );
Expand All @@ -78,12 +67,21 @@ public function validate( $validation_result ) {

public function filter_form_fields( $form, $selected_fields ) {

// Prevent the form from being randomized multiple times. Once per runtime is enough. 😅
if ( rgar( $form, 'gwrfRandomized' ) ) {
return $form;
}

$filtered_fields = array();
$selected_fields = array_map( 'intval', $selected_fields );
$random_indexes = array();
$selected_fields = array_map( 'intval', $selected_fields );

foreach ( $form['fields'] as $field ) {

if ( $this->restructure_pages && $field->type === 'page' ) {
continue;
}

if ( in_array( (int) $field['id'], $this->get_random_field_ids( $form['fields'] ), true ) ) {
if ( in_array( (int) $field['id'], $selected_fields, true ) ) {
$filtered_fields[] = $field;
Expand All @@ -94,7 +92,13 @@ public function filter_form_fields( $form, $selected_fields ) {
}
}

if ( ! $this->preserve_order ) {
if ( $this->restructure_pages && GFCommon::has_pages( $form ) ) {
$filtered_fields = $this->handle_pagination( $filtered_fields, $form );
}

// @todo Fix issue where randomized order is not preserved on subsequent form renders (e.g. pages, validation errors).
// For now, let's preserve field order for paginated forms.
if ( ! $this->preserve_order && ( ! $this->restructure_pages || ! GFCommon::has_pages( $form ) ) ) {

$reordered_fields = array();
$random_index_key = $random_indexes;
Expand All @@ -114,11 +118,69 @@ public function filter_form_fields( $form, $selected_fields ) {

}

$form['fields'] = $filtered_fields;
$form['gwrfRandomized'] = true;
$form['fields'] = $filtered_fields;

return $form;
}

public function handle_pagination( $filtered_fields, $form ) {

// Remove Section fields if they are the only fields left on a page after randomization.
foreach ( $filtered_fields as $index => $field ) {
if ( $field->type === 'section' && $this->is_only_field_on_page( $filtered_fields, $field ) ) {
unset( $filtered_fields[ $index ] );
}
}

$filtered_fields = array_filter( $filtered_fields );

// Get all the page numbers that exist now that the fields have been filtered.
$page_numbers = array_values( array_unique( wp_list_pluck( $filtered_fields, 'pageNumber' ) ) );

// Updates 0-based index to a 1-based index so the array key represents the new page number exactly (e.g. Page 1 vs Page 0).
array_unshift( $page_numbers, false );
unset( $page_numbers[0] );

// Loop through the page numbers and assign fields to their new page number based on their old page number.
foreach ( $page_numbers as $new_page_number => $old_page_number ) {

// $pages is still a 0-based indexed array; subtract 2 from the old page number to find the right page.
$page = $this->get_page_by_page_number( $form, $old_page_number );
if ( ! $page ) {
continue;
}

foreach ( $filtered_fields as $index => &$field ) {
if ( $field->pageNumber == $old_page_number && $field->type !== 'page' ) {

$field->pageNumber = $new_page_number;

// Only inject the page once.
if ( isset( $page ) ) {
// Update the page to its new page number and inject it into the filtered fields ahead.
$page->pageNumber = $new_page_number;
array_splice( $filtered_fields, $index, 0, array( $page ) );
unset( $page );
}
}
}
}

unset( $field );

return $filtered_fields;
}

public function get_page_by_page_number( $form, $page_number ) {
foreach ( $form['fields'] as $field ) {
if ( $field->type === 'page' && $field->pageNumber == $page_number ) {
return $field;
}
}
return false;
}

public function get_random_field_ids( $fields ) {

if ( ! empty( $this->all_random_field_ids ) ) {
Expand Down Expand Up @@ -179,49 +241,13 @@ public function save_selected_field_ids_meta( $entry, $form ) {
}
}

/**
* When the form is submitted modify the target page to avoid navigating to a page that has no visible fields.
*
* Known limitation: If the first page of a form has no visible fields, this will not avoid that as this only works
* on submission.
*
* @param $page_number
* @param $form
* @param $current_page
* @param $field_values
*
* @return int|mixed
*/
public function modify_target_page( $page_number, $form, $current_page ) {

$page_number = intval( $page_number );
$form = $this->pre_render( $form );

$page_has_visible_fields = false;
foreach ( $form['fields'] as $field ) {
if ( (int) $field->pageNumber === (int) $page_number && GFFormDisplay::is_field_validation_supported( $field ) ) {
$page_has_visible_fields = true;
}
}

if ( ! $page_has_visible_fields ) {
// Are we moving to the next or previous page?
$is_next = $page_number === 0 || $page_number > $current_page;
$max_page = GFFormDisplay::get_max_page_number( $form );
if ( $page_number !== 0 && $page_number < $max_page ) {
$page_number += $is_next ? 1 : -1;
// When moving to a previous page, always stop at the first page. Otherwise, we'll submit the form.
if ( ! $is_next && $page_number === 0 ) {
return 1;
}
$page_number = $this->modify_target_page( $page_number, $form, $current_page );
} elseif ( $page_number >= $max_page ) {
// Target page number 0 to submit the form.
$page_number = 0;
public function is_only_field_on_page( $fields, $source_field ) {
foreach ( $fields as $_field ) {
if ( $_field->id != $source_field->id && $_field->pageNumber == $source_field->pageNumber && $_field->type !== 'section' ) {
return false;
}
}

return $page_number;
return true;
}

}