Completed
Push — develop ( 73f182...380088 )
by Zack
18:24
created

gvlogic   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 333
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 93.69%

Importance

Changes 0
Metric Value
dl 0
loc 333
ccs 104
cts 111
cp 0.9369
rs 4.08
c 0
b 0
f 0
wmc 59
lcom 1
cbo 5

8 Methods

Rating   Name   Duplication   Size   Complexity  
A add() 0 10 1
D callback() 0 76 20
A authorized() 0 10 2
A get_operator() 0 15 4
A get_value() 0 15 4
D get_output() 0 84 20
A get_operators() 0 18 2
B parse_atts() 0 38 6

How to fix   Complexity   

Complex Class

Complex classes like gvlogic 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 gvlogic, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace GV\Shortcodes;
3
4
/** If this file is called directly, abort. */
5
if ( ! defined( 'GRAVITYVIEW_DIR' ) ) {
6
	die();
7
}
8
9
/**
10
 * The [gvlogic] shortcode.
11
 */
12
class gvlogic extends \GV\Shortcode {
13
	/**
14
	 * {@inheritDoc}
15
	 */
16
	public $name = 'gvlogic';
17
18
	/**
19
	 * {@inheritDoc}
20
	 */
21
	public static function add( $name = null ) {
22
		parent::add(); // Me, myself and...
23
24
		/**
25
		 * ...some aliases.
26
		 */
27
		parent::add( 'gvlogic2' );
28
		parent::add( 'gvlogic3' ); // This level of nesting is not supported by GravityView support...but go for it!
29
		parent::add( 'gvlogicelse' );
30
	}
31
32
	/**
33
	 * Process and output the [gvfield] shortcode.
34
	 *
35
	 * @param array $atts The attributes passed.
36
	 * @param string $content The content inside the shortcode.
37
	 * @param string $tag The tag.
38
	 *
39
	 * @return string The output.
40
	 */
41 25
	public function callback( $atts, $content = '', $tag = '' ) {
42 25
		$request = gravityview()->request;
43
44 25
		if ( $request->is_admin() ) {
45
			return apply_filters( 'gravityview/shortcodes/gvlogic/output', '', $atts );
46
		}
47
48 25
		$atts = $this->parse_atts( $atts, $content, $tag );
49
50
		$content = \GravityView_Merge_Tags::replace_get_variables( $content );
51 25
		$atts = gv_map_deep( $atts, array( '\GravityView_Merge_Tags', 'replace_get_variables' ) );
52 3
53 3
		// An invalid operation
54
		if ( is_null( \GV\Utils::get( $atts, 'logged_in', null ) ) && false === \GV\Utils::get( $atts, 'if', false ) ) {
55
			gravityview()->log->error( '$atts->if/logged_in is empty.', array( 'data' => $atts ) );
56 24
			return apply_filters( 'gravityview/shortcodes/gvlogic/output', '', $atts );
57 24
		}
58 24
59
		$authed   = $this->authorized( $atts );
60 24
		$operator = $this->get_operator( $atts );
61 5
		$value    = $this->get_value( $atts );
62 4
63
		if ( false === $operator && is_null( $value ) ) {
64 5
			if ( false !== $atts['if'] ) { // Only-if test
65
				$match = $authed && ! in_array( strtolower( $atts['if'] ), array( '', '0', 'false', 'no' ) );
66
			} else {
67 24
				$match = $authed; // Just login test
68
			}
69
70 24
			$output = $this->get_output( $match, $atts, $content );
71
		} else { // Regular test
72
73 24
			$output = $content;
74 24
75
			// Allow checking against multiple values at once
76 24
			$and_values = explode( '&&', $value );
77
			$or_values = explode( '||', $value );
78
79
			// Cannot combine AND and OR
80
			if ( sizeof( $and_values ) > 1 ) {
81
82
				// Need to match all AND
83
				foreach ( $and_values as $and_value ) {
84
					$match = $authed && \GVCommon::matches_operation( $atts['if'], $and_value, $operator );
85
					if ( ! $match ) {
86 24
						break;
87
					}
88 24
				}
89
90 24
			} elseif ( sizeof( $or_values ) > 1 ) {
91 24
92
				// Only need to match a single OR
93
				foreach ( $or_values as $or_value ) {
94 1
95
					$match = \GVCommon::matches_operation( $atts['if'], $or_value, $operator );
96
97
					// Negate the negative operators
98
					if ( ( $authed && $match ) || ( $authed && ( ! $match && in_array( $operator, array( 'isnot', 'not_contains', 'not_in' ) ) ) ) ) {
99
						break;
100
					}
101
				}
102
103
			} else {
104 24
				$match = $authed && \GVCommon::matches_operation( $atts['if'], $value, $operator );
105 24
			}
106
107 24
			$output = $this->get_output( $match, $atts, $output );
0 ignored issues
show
Bug introduced by
The variable $match 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...
108 24
		}
109 24
110
111
		// Output and get recursive!
112 24
		$output = do_shortcode( $output );
113 24
		$output = \GFCommon::replace_variables( $output, array(), array(), false, true, false );
114
115
		return apply_filters( 'gravityview/shortcodes/gvlogic/output', $output, $atts );
116
	}
117 5
118
	/**
119
	 * Are we authorized to follow the if path?
120
	 *
121
	 * @param array $atts The attributes.
122
	 *
123
	 * @return bool Yes, or no.
124
	 */
125
	private function authorized( $atts ) {
126
127 24
		$needs_login = \GV\Utils::get( $atts, 'logged_in', null );
128 24
129
		if ( is_null( $needs_login ) ) {
130 24
			return true; // No auth requirements have been set
131 24
		}
132 24
133
		return ! $needs_login ^ is_user_logged_in(); // XNOR
134
	}
135 24
136 24
	/**
137
	 * Fetch the operator.
138
	 *
139
	 * @param array $atts The attributes.
140 5
	 *
141
	 * @return bool|string The operator.
142
	 */
143
	private function get_operator( $atts ) {
144
		$valid_ops = $this->get_operators( false );
145
146
		foreach ( $atts as $op => $value ) {
147
			if ( in_array( $op, array( 'if', 'else' ) ) ) {
148
				continue;
149
			}
150
151
			if ( in_array( $op, $valid_ops, true ) ) {
152 24
				return $op;
153 24
			}
154 4
		}
155
156
		return false;
157 24
	}
158 24
159
	/**
160 24
	 * Fetch the value.
161 24
	 *
162
	 * @param array $atts The attributes.
163 24
	 *
164
	 * @return null|string The value.
165 24
	 */
166 24
	private function get_value( $atts ) {
167 20
		$valid_ops = $this->get_operators( false );
168
169 8
		foreach ( $atts as $op => $value ) {
170
			if ( in_array( $op, array( 'if', 'else' ) ) ) {
171 24
				continue;
172
			}
173
174 8
			if ( in_array( $op, $valid_ops, true ) ) {
175
				return $value;
176 8
			}
177 8
		}
178
179 1
		return null;
180
	}
181
182 8
	/**
183
	 * Get the output content.
184 8
	 *
185 8
	 * @param bool $match if or else?
186 8
	 * @param array $atts The attributes.
187
	 * @param string $content The content.
188 2
	 *
189 1
	 * @return string The output.
190 1
	 */
191
	private function get_output( $match, $atts, $content ) {
192
		if ( ! $match && ! empty( $atts['else'] ) ) {
193 2
			return $atts['else']; // Attributized else is easy :)
194 1
		}
195
196
		$if = '';
197 2
		$else = '';
198 1
199
		$opens = 0; // inner opens
200
		$found = false; // found split position
201
202 2
		while ( $content ) { // scan
203 2
204
			if ( ! preg_match( '#(.*?)(\[\/?(gvlogic|else).*?])(.*)#s', $content, $matches ) ) {
205 1
				if ( ! $found ) { // We're still iffing.
206
					$if .= $content;
207
				} else { // We are elsing
208
					$else .= $content;
209 8
				}
210
				break; // No more shortcodes
211
			}
212 24
213
			list( $_, $before_shortcode, $shortcode, $_, $after_shortcode ) = $matches;
214 24
215 24
			if ( ! $found ) { // We're still iffing.
216
				$if .= $before_shortcode;
217
			} else { // We are elsing
218
				$else .= $before_shortcode;
219 24
			}
220 16
221
			if ( 0 === strpos( $shortcode, '[else]' ) && 0 === $opens ) {
222 1
				// This is the else we need!
223 1
				$found = true;
224
				if ( $match ) {
225 1
					break; // We just need the if on a match, no need to analyze further
226 1
				}
227
			} else if ( $match && 0 === strpos( $shortcode, '[else if' ) && 0 === $opens ) {
228 1
				$found = true; // We found a match, do not process further
229
				break;
230 1
			} else {
231
				// Increment inner tracking counters
232
				if ( 0 === strpos( $shortcode, '[gvlogic' ) ) {
233
					$opens++;
234 24
				}
235
236
				if ( 0 === strpos( $shortcode, '[/gvlogic' ) ) {
237
					$opens--;
238
				}
239
240
				// Tack on the shortcode
241
				if ( ! $found ) { // We're still iffing.
242
					$if .= $shortcode;
243 25
				} else { // We are elsing
244
					$else .= $shortcode;
245
				}
246 25
			}
247
248
			$content = $after_shortcode;
249
		}
250
251
		gravityview()->log->debug( '[gvlogic] output parsing:', array(
252 25
			'data' => array(
253 25
				'if'   => $if,
254 25
				'else' => $else,
255 25
			),
256
		) );
257
258
		if ( ! $match ) {
259 24
			while ( ( $position = strpos( $if, '[else if=' ) ) !== false ) {
260
				// Try to match one of the elseif's
261
				$sentinel = wp_generate_password( 32, false );
262
				$if = substr( $if, $position ); // ...by replacing it with a gvlogic shortcode
263
				// ..and executing it!
264
				$result = do_shortcode( preg_replace( '#\[else if#', '[gvlogic if', $if, 1 ) . "[else]{$sentinel}[/gvlogic]" );
265
				if ( $result !== $sentinel ) {
266
					// We have an elseif match!
267 25
					return $result;
268
				}
269 25
				$if = substr( $if, 1 ); // Move over to get the next elseif match.. and repeat
270
			}
271 25
		}
272
273
		return $match ? $if : $else;
274
	}
275 25
276
	/**
277
	 * Get array of supported operators
278 25
	 * @param bool $with_values
279
	 *
280
	 * @return array
281 25
	 */
282 24
	private function get_operators( $with_values = false ) {
283
284 3
		$operators = array(
285
			'is', 'isnot', 'contains', 'starts_with', 'ends_with',
286
			'greater_than', 'less_than', 'in', 'not_in',
287 25
			'contains', 'equals', 'greater_than_or_is', 'greater_than_or_equals',
288
			'less_than_or_is', 'less_than_or_equals', 'not_contains',
289 1
		);
290 1
291
		if ( $with_values ) {
292 1
			return array_combine(
293
				$operators,
294
				array_fill( 0, count( $operators ), '' )
295
			);
296
		}
297
298
		return $operators;
299
	}
300
301
	/**
302
	 * Process the attributes passed to the shortcode. Make sure they're valid
303 25
	 *
304
	 * @return array Array of attributes parsed for the shortcode
305
	 */
306
	private function parse_atts( $atts, $content, $tag ) {
0 ignored issues
show
Unused Code introduced by
The parameter $content is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
307
308
		$supplied_atts = ! empty( $atts ) ? $atts : array();
309
310
		$atts = shortcode_atts( array(
311
			'if'        => null,
312
			'else'      => null,
313
			'logged_in' => null,
314
		) + $this->get_operators( true ), $atts, $tag );
315
316
		// Only keep the passed attributes after making sure that they're valid pairs
317
		$atts = array_intersect_key( $supplied_atts, $atts );
318
319
		// Strip whitespace if it's not default false
320
		if ( isset( $atts['if'] ) && is_string( $atts['if'] ) ) {
321
			$atts['if'] = trim( $atts['if'] );
322
		} else {
323
			$atts['if'] = false;
324
		}
325
326
		if ( isset( $atts['logged_in'] ) ) {
327
			// Truthy
328
			if ( in_array( strtolower( $atts['logged_in'] ), array( '0', 'false', 'no' ) ) ) {
329
				$atts['logged_in'] = false;
330
			} else {
331
				$atts['logged_in'] = true;
332
			}
333
		}
334
335
		/**
336
		 * @filter `gravityview/gvlogic/atts` The logic attributes.
337
		 *
338
		 * @since 2.5
339
		 *
340
		 * @param[in,out] array $atts The logic attributes.
341
		 */
342
		return apply_filters( 'gravityview/gvlogic/atts', $atts );
343
	}
344
}
345