Completed
Pull Request — develop (#1425)
by Gennady
07:12
created

GF_Form   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 672
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 72.89%

Importance

Changes 0
Metric Value
dl 0
loc 672
ccs 207
cts 284
cp 0.7289
rs 1.928
c 0
b 0
f 0
wmc 92
lcom 1
cbo 15

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A by_id() 0 21 3
A from_form() 0 11 2
A get_query_class() 0 9 1
F get_entries() 0 465 70
A get_field() 0 14 3
A get_fields() 0 14 5
A __get() 0 8 2
A offsetExists() 0 3 1
A offsetGet() 0 3 1
A offsetSet() 0 3 1
A offsetUnset() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like GF_Form often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GF_Form, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace GV;
3
4
/** If this file is called directly, abort. */
5
if ( ! defined( 'GRAVITYVIEW_DIR' ) ) {
6
	die();
7
}
8
9
/**
10
 * The Gravity Forms Form class implementation.
11
 *
12
 * Accessible as an array for back-compatibility.
13
 */
14
class GF_Form extends Form implements \ArrayAccess {
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
15
16
	/**
17
	 * @var string The identifier of the backend used for this form.
18
	 * @api
19
	 * @since 2.0
20
	 */
21
	public static $backend = self::BACKEND_GRAVITYFORMS;
22
23
	/**
24
	 * Initialization.
25
	 */
26 181
	private function __construct() {
27 181
		if ( ! class_exists( 'GFAPI' ) ) {
28
			gravityview()->log->error( 'Gravity Forms plugin is not active.' );
29
		}
30 181
	}
31
32
	/**
33
	 * Construct a \GV\GF_Form instance by ID.
34
	 *
35
	 * @param int|string $form_id The internal form ID.
36
	 *
37
	 * @api
38
	 * @since 2.0
39
	 * @return \GV\GF_Form|null An instance of this form or null if not found.
40
	 */
41 182
	public static function by_id( $form_id ) {
42
43 182
		$form = wp_cache_get( 'gf_form_' . $form_id, 'gravityview' );
44
45 182
		if ( ! $form ) {
46 182
			$form = \GFAPI::get_form( $form_id );
47
		}
48
49 182
		if ( ! $form ) {
50 9
			return null;
51
		}
52
53 182
		wp_cache_set( 'gf_form_' . $form_id, $form, 'gravityview' );
54
55 182
		$self = new self();
56 182
		$self->form = $form;
0 ignored issues
show
Bug introduced by
The property form cannot be accessed from this context as it is declared private in class GV\Form.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
57
58 182
		$self->ID = intval( $self->form['id'] );
0 ignored issues
show
Documentation introduced by
The property $form is declared private in GV\Form. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
59
60 182
		return $self;
61
	}
62
63
	/**
64
	 * Construct a \GV\Form instance from a Gravity Forms form array.
65
	 *
66
	 * @since 2.0.7
67
	 *
68
	 * @param array $form The form array
69
	 *
70
	 * @return \GV\GF_Form|null An instance of this form or null if not found.
71
	 */
72
	public static function from_form( $form ) {
73
		if ( empty( $form['id'] ) ) {
74
			return null;
75
		}
76
77
		$self = new self();
78
		$self->form = $form;
0 ignored issues
show
Bug introduced by
The property form cannot be accessed from this context as it is declared private in class GV\Form.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
79
		$self->ID = $self->form['id'];
0 ignored issues
show
Documentation introduced by
The property $form is declared private in GV\Form. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
80
81
		return $self;
82
	}
83
84
	/**
85
	 * Return the query class for this View.
86
	 *
87
	 * @return string The class name.
88
	 */
89 72
	public function get_query_class( $view ) {
90
		/**
91
		 * @filter `gravityview/query/class`
92
		 * @param[in,out] string The query class. Default: GF_Query.
93
		 * @param \GV\View $this The View.
94
		 */
95 72
		$query_class = apply_filters( 'gravityview/query/class', '\GF_Query', $view );
96 72
		return $query_class;
97
	}
98
99
	/**
100
	 * Get all entries for this form.
101
	 *
102
	 * @api
103
	 * @since 2.0
104
	 *
105
	 * @param \GV\View $view The View context.
106
	 *
107
	 * @return \GV\Entry_Collection The \GV\Entry_Collection
108
	 */
109 72
	public function get_entries( $view ) {
110 72
		$entries = new \GV\Entry_Collection();
111
112 72
		$form = &$this;
113
114 72
		if ( gravityview()->plugin->supports( Plugin::FEATURE_GFQUERY ) ) { // @todo switch to GFAPI:: once they start supporting nested and joins stuff
115 72
			$entries->add_fetch_callback( function( $filters, $sorts, $offset ) use ( $view, &$form ) {
116 72
				$atts = $view->settings->as_atts();
0 ignored issues
show
Deprecated Code introduced by
The method GV\View_Settings::as_atts() has been deprecated.

This method has been deprecated.

Loading history...
117
118 72
				$entries = new \GV\Entry_Collection();
119
120 72
				$search_criteria = array();
121 72
				$sorting = array();
122 72
				$paging = array();
123
124
				/** Apply the filters */
125 72
				foreach ( $filters as $filter ) {
126 72
					$search_criteria = $filter::merge_search_criteria( $search_criteria, $filter->as_search_criteria() );
127
				}
128
129
				/** Apply the sorts */
130 72
				foreach ( $sorts as $sort ) {
131
					/** Gravity Forms does not have multi-sorting, so just overwrite. */
132
					$sorting = array(
133 9
						'key' => $sort->field->ID,
134 9
						'direction' => $sort->direction,
135 9
						'is_numeric' => $sort->mode == Entry_Sort::NUMERIC,
136
					);
137
				}
138
139
				/** The offset and limit */
140 72
				if ( ! empty( $offset->limit ) ) {
141 72
					$paging['page_size'] = $offset->limit;
142
				}
143
144 72
				if ( ! empty( $offset->offset ) ) {
145 2
					$paging['offset'] = $offset->offset;
146
				}
147
				
148 72
				$query_class = $form->get_query_class( $view );
149
150
				/** @var \GF_Query $query */
151 72
				$query = new $query_class( $form->ID, $search_criteria, $sorting, $paging );
152
153
				/**
154
				 * Apply multisort.
155
				 */
156 72
				if ( is_array( $sort_fields = \GV\Utils::get( $atts, 'sort_field' ) ) && ! empty( $sort_fields ) ) {
157 2
					$view_setting_sort_field_ids = \GV\Utils::get( $atts, 'sort_field', array() );
158 2
					$view_setting_sort_directions = \GV\Utils::get( $atts, 'sort_direction', array() );
159
160 2
					$has_sort_query_param = ! empty( $_GET['sort'] ) && is_array( $_GET['sort'] );
161
162 2
					if( $has_sort_query_param ) {
163
						$has_sort_query_param = array_filter( array_values( $_GET['sort'] ) );
164
					}
165
166 2
					if ( $view->settings->get( 'sort_columns' ) && $has_sort_query_param ) {
167
						$sort_field_ids = array_keys( $_GET['sort'] );
168
						$sort_directions = array_values( $_GET['sort'] );
169
					} else {
170 2
						$sort_field_ids = $view_setting_sort_field_ids;
171 2
						$sort_directions = $view_setting_sort_directions;
172
					}
173
174 2
					$skip_first = false;
175
176 2
					foreach ( (array) $sort_field_ids as $key => $sort_field_id ) {
177
178 2
						if ( ! $skip_first && ! $has_sort_query_param ) {
179 2
							$skip_first = true; // Skip the first one, it's already in the query
180 2
							continue;
181
						}
182
183 1
						$sort_field_id = \GravityView_frontend::_override_sorting_id_by_field_type( $sort_field_id, $form->ID );
184 1
						$sort_direction = strtoupper( \GV\Utils::get( $sort_directions, $key, 'ASC' ) );
185
186 1
						if ( ! empty( $sort_field_id ) ) {
187 1
							$order = new \GF_Query_Column( $sort_field_id, $form->ID );
188 1
							if ( \GVCommon::is_field_numeric( $form->ID, $sort_field_id ) ) {
189
								$order = \GF_Query_Call::CAST( $order, defined( 'GF_Query::TYPE_DECIMAL' ) ? \GF_Query::TYPE_DECIMAL : \GF_Query::TYPE_SIGNED );
190
							}
191
192 1
							$query->order( $order, $sort_direction );
193
						}
194
					}
195
				}
196
197
				/**
198
				 * Merge time subfield sorts.
199
				 */
200 72
				add_filter( 'gform_gf_query_sql', $gf_query_timesort_sql_callback = function( $sql ) use ( &$query ) {
201 72
					$q = $query->_introspect();
202 72
					$orders = array();
203
204 72
					$merged_time = false;
205
206 72
					foreach ( $q['order'] as $oid => $order ) {
207 72
						if ( $order[0] instanceof \GF_Query_Column ) {
0 ignored issues
show
Bug introduced by
The class GF_Query_Column does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
208 72
							$column = $order[0];
209
						} else if ( $order[0] instanceof \GF_Query_Call ) {
0 ignored issues
show
Bug introduced by
The class GF_Query_Call does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
210
							if ( count( $order[0]->columns ) != 1 || ! $order[0]->columns[0] instanceof \GF_Query_Column ) {
0 ignored issues
show
Bug introduced by
The class GF_Query_Column does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
211
								$orders[ $oid ] = $order;
212
								continue; // Need something that resembles a single sort
213
							}
214
							$column = $order[0]->columns[0];
215
						}
216
217 72
						if ( ( ! $field = \GFAPI::get_field( $column->source, $column->field_id ) ) || $field->type !== 'time' ) {
0 ignored issues
show
Bug introduced by
The variable $column does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
218 71
							$orders[ $oid ] = $order;
219 71
							continue; // Not a time field
220
						}
221
222 1
						if ( ! class_exists( '\GV\Mocks\GF_Query_Call_TIMESORT' ) ) {
223 1
							require_once gravityview()->plugin->dir( 'future/_mocks.timesort.php' );
224
						}
225
226 1
						$orders[ $oid ] = array(
227 1
							new \GV\Mocks\GF_Query_Call_TIMESORT( 'timesort', array( $column, $sql ) ),
228 1
							$order[1] // Mock it!
229
						);
230
231 1
						$merged_time = true;
232
					}
233
234 72
					if ( $merged_time ) {
235
						/**
236
						 * ORDER again.
237
						 */
238 1
						if ( ! empty( $orders ) && $_orders = $query->_order_generate( $orders ) ) {
239 1
							$sql['order'] = 'ORDER BY ' . implode( ', ', $_orders );
240
						}
241
					}
242
243 72
					return $sql;
244 72
				} );
245
246
				/**
247
				 * Any joins?
248
				 */
249 72
				if ( gravityview()->plugin->supports( Plugin::FEATURE_JOINS ) && count( $view->joins ) ) {
250
251 12
					$is_admin_and_can_view = $view->settings->get( 'admin_show_all_statuses' ) && \GVCommon::has_cap( 'gravityview_moderate_entries', $view->ID );
0 ignored issues
show
Documentation introduced by
The property ID does not exist on object<GV\View>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
252
253 12
					foreach ( $view->joins as $join ) {
254 12
						$query = $join->as_query_join( $query );
255
256 12
						if ( $view->settings->get( 'multiple_forms_disable_null_joins' ) ) {
257
258
							// Disable NULL outputs
259
							$condition = new \GF_Query_Condition(
260
								new \GF_Query_Column( $join->join_on_column->ID, $join->join_on->ID ),
0 ignored issues
show
Bug introduced by
The property ID does not seem to exist in GV\Source.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
261
								\GF_Query_Condition::NEQ,
262
								new \GF_Query_Literal( '' )
263
							);
264
265
							$query_parameters = $query->_introspect();
266
267
							$query->where( \GF_Query_Condition::_and( $query_parameters['where'], $condition ) );
268
						}
269
270
						/**
271
						 * This is a temporary stub filter, until GF_Query supports NULL conditions.
272
						 * Do not use! This filter will be removed.
273
						 */
274 12
						if ( defined( 'GF_Query_Condition::NULL' ) ) {
275 12
							$is_null_condition_native = true;
276
						} else {
277
							$is_null_condition_class = apply_filters( 'gravityview/query/is_null_condition', null );
278
							$is_null_condition_native = false;
279
						}
280
281
						// Filter to active entries only
282 12
						$condition = new \GF_Query_Condition(
283 12
							new \GF_Query_Column( 'status', $join->join_on->ID ),
284 12
							\GF_Query_Condition::EQ,
285 12
							new \GF_Query_Literal( 'active' )
286
						);
287
288 12
						if ( $is_null_condition_native ) {
289 12
							$condition = \GF_Query_Condition::_or( $condition, new \GF_Query_Condition(
290 12
								new \GF_Query_Column( 'status', $join->join_on->ID ),
291 12
								\GF_Query_Condition::IS,
292 12
								\GF_Query_Condition::NULL
293
							) );
294
						} else if ( ! is_null( $is_null_condition_class ) ) {
295
							$condition = \GF_Query_Condition::_or( $condition, new $is_null_condition_class(
0 ignored issues
show
Bug introduced by
The variable $is_null_condition_class does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
296
								new \GF_Query_Column( 'status', $join->join_on->ID )
297
							) );
298
						}
299
300 12
						$q = $query->_introspect();
301 12
						$query->where( \GF_Query_Condition::_and( $q['where'], $condition ) );
302
303 12
						if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
304
305
							// Show only approved joined entries
306 1
							$condition = new \GF_Query_Condition(
307 1
								new \GF_Query_Column( \GravityView_Entry_Approval::meta_key, $join->join_on->ID ),
308 1
								\GF_Query_Condition::EQ,
309 1
								new \GF_Query_Literal( \GravityView_Entry_Approval_Status::APPROVED )
310
							);
311
312 1
							if ( $is_null_condition_native ) {
313 1
								$condition = \GF_Query_Condition::_or( $condition, new \GF_Query_Condition(
314 1
									new \GF_Query_Column( \GravityView_Entry_Approval::meta_key, $join->join_on->ID ),
315 1
									\GF_Query_Condition::IS,
316 1
									\GF_Query_Condition::NULL
317
								) );
318
							} else if ( ! is_null( $is_null_condition_class ) ) {
319
								$condition = \GF_Query_Condition::_or( $condition, new $is_null_condition_class(
320
									new \GF_Query_Column( \GravityView_Entry_Approval::meta_key, $join->join_on->ID )
321
								) );
322
							}
323
324 1
							$query_parameters = $query->_introspect();
325
326 1
							$query->where( \GF_Query_Condition::_and( $query_parameters['where'], $condition ) );
327
						}
328
					}
329
				/**
330
				 * Unions?
331
				 */
332 60
				} else if ( gravityview()->plugin->supports( Plugin::FEATURE_UNIONS ) && count( $view->unions ) ) {
333 1
					$query_parameters = $query->_introspect();
334
335 1
					$unions_sql = array();
336
337
					/**
338
					 * @param \GF_Query_Condition $condition
339
					 * @param array $fields
340
					 * @param $recurse
341
					 *
342
					 * @return \GF_Query_Condition
343
					 */
344 1
					$where_union_substitute = function( $condition, $fields, $recurse ) {
345 1
						if ( $condition->expressions ) {
346 1
							$conditions = array();
347
348 1
							foreach ( $condition->expressions as $_condition ) {
349 1
								$conditions[] = $recurse( $_condition, $fields, $recurse );
350
							}
351
352 1
							return call_user_func_array(
353 1
								array( '\GF_Query_Condition', $condition->operator == 'AND' ? '_and' : '_or' ),
354 1
								$conditions
355
							);
356
						}
357
358 1
						if ( ! ( $condition->left && $condition->left instanceof \GF_Query_Column ) || ( ! $condition->left->is_entry_column() && ! $condition->left->is_meta_column() ) ) {
0 ignored issues
show
Bug introduced by
The class GF_Query_Column does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
359 1
							return new \GF_Query_Condition(
360 1
								new \GF_Query_Column( $fields[ $condition->left->field_id ]->ID ),
361 1
								$condition->operator,
362 1
								$condition->right
363
							);
364
						}
365
366 1
						return $condition;
367 1
					};
368
369 1
					foreach ( $view->unions as $form_id => $fields ) {
370
371
						// Build a new query for every unioned form
372
373
						/** @var \GF_Query|\GF_Patched_Query $q */
374 1
						$q = new $query_class( $form_id );
375
376
						// Copy the WHERE clauses but substitute the field_ids to the respective ones
377 1
						$q->where( $where_union_substitute( $query_parameters['where'], $fields, $where_union_substitute ) );
378
379
						// Copy the ORDER clause and substitute the field_ids to the respective ones
380 1
						foreach ( $query_parameters['order'] as $order ) {
381 1
							list( $column, $_order ) = $order;
382
383 1
							if ( $column && $column instanceof \GF_Query_Column ) {
0 ignored issues
show
Bug introduced by
The class GF_Query_Column does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
384 1
								if ( ! $column->is_entry_column() && ! $column->is_meta_column() ) {
385 1
									$column = new \GF_Query_Column( $fields[ $column->field_id ]->ID );
386
								}
387
388 1
								$q->order( $column, $_order );
389
							}
390
						}
391
392 1
						add_filter( 'gform_gf_query_sql', $gf_query_sql_callback = function( $sql ) use ( &$unions_sql ) {
393
							// Remove SQL_CALC_FOUND_ROWS as it's not needed in UNION clauses
394 1
							$select = 'UNION ALL ' . str_replace( 'SQL_CALC_FOUND_ROWS ', '', $sql['select'] );
395
396
							// Record the SQL
397 1
							$unions_sql[] = array(
398
								// Remove columns, we'll rebuild them
399 1
								'select'  => preg_replace( '#DISTINCT (.*)#', 'DISTINCT ', $select ),
400 1
								'from'    => $sql['from'],
401 1
								'join'    => $sql['join'],
402 1
								'where'   => $sql['where'],
403
								// Remove order and limit
404
							);
405
406
							// Return empty query, no need to call the database
407 1
							return array();
408 1
						} );
409
410 1
						do_action_ref_array( 'gravityview/view/query', array( &$q, $view, gravityview()->request ) );
411
412 1
						$q->get(); // Launch
413
414 1
						remove_filter( 'gform_gf_query_sql', $gf_query_sql_callback );
415
					}
416
417 1
					add_filter( 'gform_gf_query_sql', $gf_query_sql_callback = function( $sql ) use ( $unions_sql ) {
418
						// Remove SQL_CALC_FOUND_ROWS as it's not needed in UNION clauses
419 1
						$sql['select'] = str_replace( 'SQL_CALC_FOUND_ROWS ', '', $sql['select'] );
420
421
						// Remove columns, we'll rebuild them
422 1
						preg_match( '#DISTINCT (`[motc]\d+`.`.*?`)#', $sql['select'], $select_match );
423 1
						$sql['select'] = preg_replace( '#DISTINCT (.*)#', 'DISTINCT ', $sql['select'] );
424
425 1
						$unions = array();
426
427
						// Transform selected columns to shared alias names
428 1
						$column_to_alias = function( $column ) {
429 1
							$column = str_replace( '`', '', $column );
430 1
							return '`' . str_replace( '.', '_', $column ) . '`';
431 1
						};
432
433
						// Add all the order columns into the selects, so we can order by the whole union group
434 1
						preg_match_all( '#(`[motc]\d+`.`.*?`)#', $sql['order'], $order_matches );
435
						
436
						$columns = array(
437 1
							sprintf( '%s AS %s', $select_match[1], $column_to_alias( $select_match[1] ) )
438
						);
439
440 1
						foreach ( array_slice( $order_matches, 1 ) as $match ) {
441 1
							$columns[] = sprintf( '%s AS %s', $match[0], $column_to_alias( $match[0] ) );
442
443
							// Rewrite the order columns to the shared aliases
444 1
							$sql['order'] = str_replace( $match[0], $column_to_alias( $match[0] ), $sql['order'] );
445
						}
446
447 1
						$columns = array_unique( $columns );
448
449
						// Add the columns to every UNION
450 1
						foreach ( $unions_sql as $union_sql ) {
451 1
							$union_sql['select'] .= implode( ', ', $columns );
452 1
							$unions []= implode( ' ', $union_sql );
453
						}
454
455
						// Add the columns to the main SELECT, but only grab the entry id column
456 1
						$sql['select'] = 'SELECT SQL_CALC_FOUND_ROWS t1_id FROM (' . $sql['select'] . implode( ', ', $columns );
457 1
						$sql['order'] = implode( ' ', $unions ) . ') AS u ' . $sql['order'];
458
459 1
						return $sql;
460 1
					} );
461
				}
462
463
				/**
464
				 * @action `gravityview/view/query` Override the \GF_Query before the get() call.
465
				 * @param \GF_Query $query The current query object reference
466
				 * @param \GV\View $view The current view object
467
				 * @param \GV\Request $request The request object
468
				 */
469 72
				do_action_ref_array( 'gravityview/view/query', array( &$query, $view, gravityview()->request ) );
470
471 72
				gravityview()->log->debug( 'GF_Query parameters: ', array( 'data' => Utils::gf_query_debug( $query ) ) );
472
473
				/**
474
				 * Map from Gravity Forms entries arrays to an Entry_Collection.
475
				 */
476 72
				if ( count( $view->joins ) ) {
477 12
					foreach ( $query->get() as $entry ) {
478 12
						$entries->add(
479 12
							Multi_Entry::from_entries( array_map( '\GV\GF_Entry::from_entry', $entry ) )
480
						);
481
					}
482
				} else {
483 60
					array_map( array( $entries, 'add' ), array_map( '\GV\GF_Entry::from_entry', $query->get() ) );
484
				}
485
486 72
				if ( isset( $gf_query_sql_callback ) ) {
487 1
					remove_action( 'gform_gf_query_sql', $gf_query_sql_callback );
488
				}
489
490 72
				if ( isset( $gf_query_timesort_sql_callback ) ) {
491 72
					remove_action( 'gform_gf_query_sql', $gf_query_timesort_sql_callback );
492
				}
493
494 72
				return $entries;
495 72
			} );
496
497 72
			$entries->add_count_callback( function( $filters ) use ( $entries ) {
498 32
				$query = null;
499
500 32
				add_action( 'gravityview/view/query', $q_callback = function( &$q ) use ( &$query ) {
501 32
					$query = $q;
502 32
				} );
503
504 32
				foreach ( $filters as $filter ) {
505 32
					$entries = $entries->filter( $filter );
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $entries, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
506
				}
507 32
				$entries->fetch(); // Gravity Forms requires a fetch before total can be had
508
509 32
				remove_action( 'gravityview/view/query', $q_callback );
510
511 32
				if ( ! is_object( $query ) ) {
512
					return 0;
513
				}
514
515 32
				return $query->total_found;
516 72
			} );
517
		} else {
518
			/** Add the fetcher lazy callback. */
519
			$entries->add_fetch_callback( function( $filters, $sorts, $offset ) use ( $form ) {
520
				$entries = new \GV\Entry_Collection();
521
522
				$search_criteria = array();
523
				$sorting = array();
524
				$paging = array();
525
526
				/** Apply the filters */
527
				foreach ( $filters as $filter ) {
528
					$search_criteria = $filter::merge_search_criteria( $search_criteria, $filter->as_search_criteria() );
529
				}
530
531
				/** Apply the sorts */
532
				foreach ( $sorts as $sort ) {
533
					/** Gravity Forms does not have multi-sorting, so just overwrite. */
534
					$sorting = array(
535
						'key' => $sort->field->ID,
536
						'direction' => $sort->direction,
537
						'is_numeric' => $sort->mode == Entry_Sort::NUMERIC,
538
					);
539
				}
540
541
				/** The offset and limit */
542
				if ( ! empty( $offset->limit ) ) {
543
					$paging['page_size'] = $offset->limit;
544
				}
545
546
				if ( ! empty( $offset->offset ) ) {
547
					$paging['offset'] = $offset->offset;
548
				}
549
550
				foreach ( \GFAPI::get_entries( $form->ID, $search_criteria, $sorting, $paging ) as $entry ) {
551
					$entries->add( \GV\GF_Entry::from_entry( $entry ) );
552
				}
553
554
				return $entries;
555
			} );
556
557
			/** Add the counter lazy callback. */
558
			$entries->add_count_callback( function( $filters ) use ( $form ) {
559
				$search_criteria = array();
560
				$sorting = array();
561
562
				/** Apply the filters */
563
				/** @var \GV\GF_Entry_Filter|\GV\Entry_Filter $filter */
564
				foreach ( $filters as $filter ) {
565
					$search_criteria = $filter::merge_search_criteria( $search_criteria, $filter->as_search_criteria() );
566
				}
567
568
				return \GFAPI::count_entries( $form->ID, $search_criteria );
569
			} );
570
		}
571
572 72
		return $entries;
573
	}
574
575
	/**
576
	 * Get a \GV\Field by Form and Field ID for this data source.
577
	 *
578
	 * @param \GV\GF_Form $form The Gravity Form form ID.
0 ignored issues
show
Bug introduced by
There is no parameter named $form. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
579
	 * @param int $field_id The Gravity Form field ID for the $form_id.
0 ignored issues
show
Bug introduced by
There is no parameter named $field_id. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
580
	 *
581
	 * @return \GV\Field|null The requested field or null if not found.
582
	 */
583 3
	public static function get_field( /** varargs */ ) {
584 3
		$args = func_get_args();
585
586 3
		if ( ! is_array( $args ) || count( $args ) != 2 ) {
587 1
			gravityview()->log->error( '{source} expects 2 arguments for ::get_field ($form, $field_id)', array( 'source' => __CLASS__ ) );
588 1
			return null;
589
		}
590
591
		/** Unwrap the arguments. */
592 3
		list( $form, $field_id ) = $args;
593
594
		/** Wrap it up into a \GV\Field. */
595 3
		return GF_Field::by_id( $form, $field_id );
596
	}
597
598
	/**
599
	 * Get an array of GV Fields for this data source
600
	 *
601
	 * @return \GV\Field[]|array Empty array if no fields
602
	 */
603
	public function get_fields() {
604
		$fields = array();
605
		foreach ( $this['fields'] as $field ) {
606
			foreach ( empty( $field['inputs'] ) ? array( $field['id'] ) : wp_list_pluck( $field['inputs'], 'id' ) as $id ) {
607
				if ( is_numeric( $id ) ) {
608
					$fields[ $id ] = self::get_field( $this, $id );
609
				} else {
610
					$fields[ $id ] = Internal_Field::by_id( $id );
611
				}
612
			}
613
		}
614
615
		return array_filter( $fields );
616
	}
617
618
	/**
619
	 * Proxies.
620
	 *
621
	 * @param string $key The property to get.
622
	 *
623
	 * @return mixed
624
	 */
625
	public function __get( $key ) {
626
		switch ( $key ) {
627
			case 'fields':
628
				return $this->get_fields();
629
			default:
630
				return parent::__get( $key );
631
		}
632
	}
633
634
	/**
635
	 * ArrayAccess compatibility layer with a Gravity Forms form array.
636
	 *
637
	 * @internal
638
	 * @deprecated
639
	 * @since 2.0
640
	 * @return bool Whether the offset exists or not.
641
	 */
642 28
	public function offsetExists( $offset ) {
643 28
		return isset( $this->form[$offset] );
0 ignored issues
show
Documentation introduced by
The property $form is declared private in GV\Form. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
644
	}
645
646
	/**
647
	 * ArrayAccess compatibility layer with a Gravity Forms form array.
648
	 *
649
	 * Maps the old keys to the new data;
650
	 *
651
	 * @internal
652
	 * @deprecated
653
	 * @since 2.0
654
	 *
655
	 * @return mixed The value of the requested form data.
656
	 */
657 21
	public function offsetGet( $offset ) {
658 21
		return $this->form[$offset];
0 ignored issues
show
Documentation introduced by
The property $form is declared private in GV\Form. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
659
	}
660
661
	/**
662
	 * ArrayAccess compatibility layer with a Gravity Forms form array.
663
	 *
664
	 * @internal
665
	 * @deprecated
666
	 * @since 2.0
667
	 *
668
	 * @return void
669
	 */
670
	public function offsetSet( $offset, $value ) {
671
		gravityview()->log->error( 'The underlying Gravity Forms form is immutable. This is a \GV\Form object and should not be accessed as an array.' );
672
	}
673
674
	/**
675
	 * ArrayAccess compatibility layer with a Gravity Forms form array.
676
	 *
677
	 * @internal
678
	 * @deprecated
679
	 * @since 2.0
680
	 * @return void
681
	 */
682
	public function offsetUnset( $offset ) {
683
		gravityview()->log->error( 'The underlying Gravity Forms form is immutable. This is a \GV\Form object and should not be accessed as an array.' );
684
	}
685
}
686