Completed
Push — develop ( ddf816...398a4f )
by Zack
24:58 queued 11:44
created

GravityView_Field_Sequence   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 8

Test Coverage

Coverage 66.22%

Importance

Changes 0
Metric Value
dl 0
loc 232
ccs 49
cts 74
cp 0.6622
rs 10
c 0
b 0
f 0
wmc 22
lcom 0
cbo 8

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A field_tooltips() 0 11 1
A field_options() 0 22 1
C replace_merge_tag() 0 84 10
B get_sequence() 0 50 9
1
<?php
2
/**
3
 * @file class-gravityview-field-sequence.php
4
 * @package GravityView
5
 * @subpackage includes\fields
6
 */
7
8
/**
9
 * Add a sequence field.
10
 * @since 2.3.3
11
 */
12
class GravityView_Field_Sequence extends GravityView_Field {
13
14
	var $name = 'sequence';
15
16
	var $contexts = array( 'single', 'multiple' );
17
18
	/**
19
	 * @var bool
20
	 */
21
	var $is_sortable = false;
22
23
	/**
24
	 * @var bool
25
	 */
26
	var $is_searchable = false;
27
28
	/**
29
	 * @var bool
30
	 */
31
	var $is_numeric = true;
32
33
	var $_custom_merge_tag = 'sequence';
34
35
	var $group = 'gravityview';
36
37
	public function __construct() {
38
39
		$this->label = esc_html__( 'Number Sequence', 'gravityview' );
40
41
		add_filter( 'gravityview/metaboxes/tooltips', array( $this, 'field_tooltips') );
42
43
		parent::__construct();
44
	}
45
46
	/**
47
	 * Add tooltips
48
	 * @param  array $tooltips Existing tooltips
49
	 * @return array           Modified tooltips
50
	 */
51
	public function field_tooltips( $tooltips ) {
52
53
		$return = $tooltips;
54
55
		$return['reverse_sequence'] = array(
56
			'title' => __('Reverse the order of the result numbers', 'gravityview'),
57
			'value' => __('Output the number sequence in descending order. If enabled, numbers will count down from high to low.', 'gravityview'),
58
		);
59
60
		return $return;
61
	}
62
63
	public function field_options( $field_options, $template_id, $field_id, $context, $input_type ) {
64
65
		unset ( $field_options['search_filter'] );
66
67
		$new_fields = array(
68
			'start' => array(
69
				'type' => 'number',
70
				'label' => __( 'First Number in the Sequence', 'gravityview' ),
71
				'desc' => __('For each entry, the displayed number will increase by one. When displaying ten entries, the first entry will display "1", and the last entry will show "10".', 'gravityview'),
72
				'value' => '1',
73
				'merge_tags' => false,
74
			),
75
			'reverse' => array(
76
				'type' => 'checkbox',
77
				'label' => __( 'Reverse the order of the number sequence (high to low)', 'gravityview' ),
78
				'tooltip' => 'reverse_sequence',
79
				'value' => '',
80
			),
81
		);
82
83
		return $new_fields + $field_options;
84
	}
85
86
	/**
87
	 * Replace {sequence} Merge Tags inside Custom Content fields
88
	 *
89
	 * TODO:
90
	 * - Find a better way to infer current View data (without using legacy code)
91
	 *
92
	 * @param array $matches
93
	 * @param string $text
94
	 * @param array $form
95
	 * @param array $entry
96
	 * @param bool $url_encode
97
	 * @param bool $esc_html
98
	 *
99
	 * @return string
100
	 */
101 1
	public function replace_merge_tag( $matches = array(), $text = '', $form = array(), $entry = array(), $url_encode = false, $esc_html = false ) {
102
		/**
103
		 * An internal cache for sequence tag reuse within one field.
104
		 * Avoids calling get_sequence over and over again, off-by-many increments, etc.
105
		 */
106 1
		static $merge_tag_sequences = array();
107
108 1
		$view_data = gravityview_get_current_view_data(); // TODO: Don't use legacy code...
109
110
		// If we're not in a View or embed, don't replace the merge tag
111 1
		if ( empty( $view_data ) ) {
112
			gravityview()->log->error( '{sequence} Merge Tag used outside of a GravityView View.', array( 'data' => $matches ) );
113
			return $text;
114
		}
115
116 1
		$legacy_field = \GravityView_View::getInstance()->getCurrentField(); // TODO: Don't use legacy code...
117
118
		// If we're outside field context (like a GV widget), don't replace the merge tag
119 1
		if ( ! $legacy_field ) {
120 1
			gravityview()->log->error( '{sequence} Merge Tag was used without outside of the GravityView entry loop.', array( 'data' => $matches ) );
121 1
			return $text;
122
		}
123
124 1
		$return = $text;
125
126 1
		$context = new \GV\Template_Context();
127 1
		$context->view = \GV\View::by_id( $view_data['view_id'] );
128 1
		$context->entry = \GV\GF_Entry::from_entry( $entry );
129
130 1
		$gv_field = \GV\Internal_Field::by_id( 'sequence' );
131 1
		$merge_tag_context = \GV\Utils::get( $legacy_field, 'UID' );
132
133 1
		$merge_tag_context = uniqid( 'sequence_', true ) . "/{$merge_tag_context}";
134
135 1
		foreach ( $matches as $match ) {
136
137 1
			$full_tag = $match[0];
138 1
			$property = $match[1];
139
140 1
			$gv_field->reverse = false;
0 ignored issues
show
Bug introduced by
The property reverse does not seem to exist in GV\Internal_Field.

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...
141 1
			$gv_field->start = 1;
0 ignored issues
show
Bug introduced by
The property start does not seem to exist in GV\Internal_Field.

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...
142
143 1
			$modifiers = explode( ',', trim( $property ) );
144
145 1
			foreach ( $modifiers as $modifier ) {
146
147 1
				$modifier = trim( $modifier );
148
149 1
				if ( 'reverse' === $modifier ) {
150 1
					$gv_field->reverse = true;
151
				}
152
153 1
				$maybe_start = explode( ':', $modifier );
154
155
				// If there is a field with the ID of the start number, the merge tag won't work.
156
				// In that case, you can use "=" instead: `{sequence start=10}`
157 1
				if( 1 === sizeof( $maybe_start ) ) {
158 1
					$maybe_start = explode( '=', $modifier );
159
				}
160
161 1
				if ( 'start' === rgar( $maybe_start, 0 ) && is_numeric( rgar( $maybe_start, 1 ) ) ) {
162 1
					$gv_field->start = (int) rgar( $maybe_start, 1 );
163
				}
164
			}
165
166
			/**
167
			 * We make sure that distinct sequence modifiers have their own
168
			 * output counters.
169
			 */
170 1
			$merge_tag_context_modifiers = $merge_tag_context . '/' . var_export( $gv_field->reverse, true ) . '/' . $gv_field->start;
0 ignored issues
show
Documentation introduced by
The property reverse does not exist on object<GV\Internal_Field>. 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...
Documentation introduced by
The property start does not exist on object<GV\Internal_Field>. 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...
171
172 1
			if ( ! isset( $merge_tag_sequences[ $merge_tag_context_modifiers ] ) ) {
173 1
				$gv_field->UID = $legacy_field['UID'] . '/' . var_export( $gv_field->reverse, true ) . '/' . $gv_field->start;
0 ignored issues
show
Documentation introduced by
The property reverse does not exist on object<GV\Internal_Field>. 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...
Documentation introduced by
The property start does not exist on object<GV\Internal_Field>. 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...
174 1
				$context->field = $gv_field;
175 1
				$sequence = $merge_tag_sequences[ $merge_tag_context_modifiers ] = $this->get_sequence( $context );
176
			} else {
177 1
				$sequence = $merge_tag_sequences[ $merge_tag_context_modifiers ];
178
			}
179
180 1
			$return = str_replace( $full_tag, $sequence, $return );
181
		}
182
183 1
		return $return;
184
	}
185
186
	/**
187
	 * Calculate the current sequence number for the context.
188
	 *
189
	 * @param  \GV\Template_Context $context The context.
190
	 *
191
	 * @return int The sequence number for the field/entry within the view results.
192
	 */
193 2
	public function get_sequence( $context ) {
194 2
		static $startlines = array();
195
196 2
		$context_key = md5( json_encode(
197
			array(
198 2
				$context->view->ID,
199 2
				\GV\Utils::get( $context, 'field/UID' ),
200
			)
201
		) );
202
203
		/**
204
		 * Figure out the starting number.
205
		 */
206 2
		if ( $context->request && $entry = $context->request->is_entry() ) {
207
208 1
			$sql_query = array();
209
210 1
			add_filter( 'gform_gf_query_sql', $callback = function( $sql ) use ( &$sql_query ) {
211 1
				$sql_query = $sql;
212 1
				return $sql;
213 1
			} );
214
215
			$total = $context->view->get_entries()->total();
216
			remove_filter( 'gform_gf_query_sql', $callback );
217
218
			unset( $sql_query['paginate'] );
219
220
			global $wpdb;
221
222
			foreach ( $wpdb->get_results( implode( ' ', $sql_query ), ARRAY_A ) as $n => $result ) {
223
				if ( in_array( $entry->ID, $result ) ) {
224
					return $context->field->reverse ? ( $total - $n ) : ( $n + 1 );
0 ignored issues
show
Documentation introduced by
The property reverse does not exist on object<GV\Field>. 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...
225
				}
226
			}
227
228
			return 0;
229
		} elseif ( ! isset( $startlines[ $context_key ] ) ) {
230
			$pagenum  = max( 0, \GV\Utils::_GET( 'pagenum', 1 ) - 1 );
231
			$pagesize = $context->view->settings->get( 'page_size', 25 );
232
233
			if ( $context->field->reverse ) {
0 ignored issues
show
Documentation introduced by
The property reverse does not exist on object<GV\Field>. 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...
234
				$startlines[ $context_key ] = $context->view->get_entries()->total() - ( $pagenum * $pagesize );
235
				$startlines[ $context_key ] += $context->field->start - 1;
0 ignored issues
show
Documentation introduced by
The property start does not exist on object<GV\Field>. 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...
236
			} else {
237
				$startlines[ $context_key ] = ( $pagenum * $pagesize ) + $context->field->start;
0 ignored issues
show
Documentation introduced by
The property start does not exist on object<GV\Field>. 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...
238
			}
239
		}
240
241
		return $context->field->reverse ? $startlines[ $context_key ]-- : $startlines[ $context_key ]++;
0 ignored issues
show
Documentation introduced by
The property reverse does not exist on object<GV\Field>. 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...
242
	}
243
}
244
245
new GravityView_Field_Sequence;
246