Completed
Push — develop ( 0f41f0...4939cc )
by Zack
25:11 queued 19:29
created

gravityview   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 335
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 72.99%

Importance

Changes 0
Metric Value
dl 0
loc 335
ccs 100
cts 137
cp 0.7299
rs 2.88
c 0
b 0
f 0
wmc 69
lcom 1
cbo 13

4 Methods

Rating   Name   Duplication   Size   Complexity  
F callback() 0 188 52
C parse_and_sanitize_atts() 0 59 11
A detail() 0 32 5
A _return() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like gravityview 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 gravityview, 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 [gravityview] shortcode.
11
 */
12
class gravityview extends \GV\Shortcode {
13
	/**
14
	 * {@inheritDoc}
15
	 */
16
	public $name = 'gravityview';
17
18
	/**
19
	 * A stack of calls to track nested shortcodes.
20
	 */
21
	public static $callstack = array();
22
23
	/**
24
	 * Process and output the [gravityview] shortcode.
25
	 *
26
	 * @param array $passed_atts The attributes passed.
27
	 * @param string $content The content inside the shortcode.
28
	 * @param string $tag The shortcode tag.
29
	 *
30
	 * @return string|null The output.
31
	 */
32 10
	public function callback( $passed_atts, $content = '', $tag = '' ) {
33 10
		$request = gravityview()->request;
34
35 10
		if ( $request->is_admin() ) {
36
			return '';
37
		}
38
39 10
		$atts = wp_parse_args( $passed_atts, array(
40 10
			'id' => 0,
41
			'view_id' => 0,
42
			'detail' => null,
43
		) );
44
		
45 10
		if ( ! $view_id = $atts['id'] ? : $atts['view_id'] ) {
46
			if ( $atts['detail'] && $view = $request->is_view() ) {
47
				$view_id = $view->ID;
48
			}
49
		}
50
51 10
		$view = \GV\View::by_id( $view_id );
52
53 10
		if ( ! $view ) {
54
			gravityview()->log->error( 'View does not exist #{view_id}', array( 'view_id' => $view_id ) );
55
			return '';
56
		}
57
58 10
		gravityview()->views->set( $view );
0 ignored issues
show
Documentation introduced by
The property views does not exist on object<GV\Core>. 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...
59
60
		/**
61
		 * When this shortcode is embedded inside a View we can only display it as a directory. There's no other way.
62
		 * Try to detect that we're not embedded to allow edit and single contexts.
63
		 */
64 10
		$is_reembedded = false; // Assume not embedded unless detected otherwise.
65 10
		if ( in_array( get_class( $request ), array( 'GV\Frontend_Request', 'GV\Mock_Request' ) ) ) {
66
67 10
			if ( ( $_view = $request->is_view() ) && $_view->ID !== $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...
68
				$is_reembedded = true;
69
70 10
			} elseif ( $request->is_entry( $view->form ? $view->form->ID : 0 ) && self::$callstack ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$callstack of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
71 1
				$is_reembedded = true;
72
			}
73
		}
74
75 10
		array_push( self::$callstack, true );
76
77
		/**
78
		 * Remove Widgets on a nested embedded View.
79
		 */
80 10
		if ( $is_reembedded ) {
81 1
			$view->widgets = new \GV\Widget_Collection();
82
		}
83
84 10
		$atts = $this->parse_and_sanitize_atts( $atts );
85
86 10
		$view->settings->update( array( 'shortcode_atts' => $atts ) );
87 10
		$view->settings->update( $atts );
88
89
		/**
90
		 * Check permissions.
91
		 */
92 10
		while ( $error = $view->can_render( array( 'shortcode' ), $request ) ) {
93 10
			if ( ! is_wp_error( $error ) )
94 10
				break;
95
96 1
			switch ( str_replace( 'gravityview/', '', $error->get_error_code() ) ) {
97 1
				case 'post_password_required':
98 1
					return self::_return( get_the_password_form( $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...
99 1
				case 'no_form_attached':
100
					/**
101
					 * This View has no data source. There's nothing to show really.
102
					 * ...apart from a nice message if the user can do anything about it.
103
					 */
104
					if ( \GVCommon::has_cap( array( 'edit_gravityviews', 'edit_gravityview' ), $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...
105
						return self::_return( __( sprintf( 'This View is not configured properly. Start by <a href="%s">selecting a form</a>.', esc_url( get_edit_post_link( $view->ID, false ) ) ), 'gravityview' ) );
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...
106
					}
107
					break;
108 1
				case 'no_direct_access':
109 1
				case 'embed_only':
110 1
				case 'not_public':
111
				default:
112 1
					return self::_return( __( 'You are not allowed to view this content.', 'gravityview' ) );
113
			}
114
		}
115
116 10
		$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...
117
118
		/**
119
		 * View details.
120
		 */
121 10
		if ( $atts['detail'] ) {
122 2
			$entries = $view->get_entries( $request );
123 2
			return self::_return( $this->detail( $view, $entries, $atts ) );
124
125
		/**
126
		 * Editing a single entry.
127
		 */
128 9
		} else if ( ! $is_reembedded && ( $entry = $request->is_edit_entry( $view->form ? $view->form->ID : 0 ) ) ) {
129
130
			/**
131
			 * When editing an entry don't render multiple views.
132
			 */
133
			if ( ( $selected = \GV\Utils::_GET( 'gvid' ) ) && $view->ID != $selected ) {
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...
134
				gravityview()->log->notice( 'Entry ID #{entry_id} not rendered because another View ID was passed using `?gvid`: #{selected}', array( 'entry_id' => $entry->ID, 'selected' => $selected ) );
135
				return self::_return( '' );
136
			}
137
138
			if ( $entry['status'] != 'active' ) {
139
				gravityview()->log->notice( 'Entry ID #{entry_id} is not active', array( 'entry_id' => $entry->ID ) );
140
				return self::_return( __( 'You are not allowed to view this content.', 'gravityview' ) );
141
			}
142
143
			if ( apply_filters( 'gravityview_custom_entry_slug', false ) && $entry->slug != get_query_var( \GV\Entry::get_endpoint_name() ) ) {
144
				gravityview()->log->error( 'Entry ID #{entry_id} was accessed by a bad slug', array( 'entry_id' => $entry->ID ) );
145
				return self::_return( __( 'You are not allowed to view this content.', 'gravityview' ) );
146
			}
147
148
			if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
149
				if ( ! \GravityView_Entry_Approval_Status::is_approved( gform_get_meta( $entry->ID, \GravityView_Entry_Approval::meta_key ) )  ) {
150
					gravityview()->log->error( 'Entry ID #{entry_id} is not approved for viewing', array( 'entry_id' => $entry->ID ) );
151
					return self::_return( __( 'You are not allowed to view this content.', 'gravityview' ) );
152
				}
153
			}
154
155
			$renderer = new \GV\Edit_Entry_Renderer();
156
			return self::_return( $renderer->render( $entry, $view, $request ) );
157
158
		/**
159
		 * Viewing a single entry.
160
		 */
161 9
		} else if ( ! $is_reembedded && ( $entry = $request->is_entry( $view->form ? $view->form->ID : 0 ) ) ) {
162
			/**
163
			 * When viewing an entry don't render multiple views.
164
			 */
165 4
			if ( ( $selected = \GV\Utils::_GET( 'gvid' ) ) && $view->ID != $selected ) {
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...
166
				return self::_return( '' );
167
			}
168
169 4
			$entryset = $entry->is_multi() ? $entry->entries : array( $entry );
170
171 4
			foreach ( $entryset as $e ) {
172 4
				if ( $e['status'] != 'active' ) {
173 1
					gravityview()->log->notice( 'Entry ID #{entry_id} is not active', array( 'entry_id' => $e->ID ) );
174 1
					return self::_return( __( 'You are not allowed to view this content.', 'gravityview' ) );
175
				}
176
177 4
				if ( apply_filters( 'gravityview_custom_entry_slug', false ) && $e->slug != get_query_var( \GV\Entry::get_endpoint_name() ) ) {
178 1
					gravityview()->log->error( 'Entry ID #{entry_id} was accessed by a bad slug', array( 'entry_id' => $e->ID ) );
179 1
					return self::_return( __( 'You are not allowed to view this content.', 'gravityview' ) );
180
				}
181
182 4
				if ( $view->settings->get( 'show_only_approved' ) && ! $is_admin_and_can_view ) {
183 1
					if ( ! \GravityView_Entry_Approval_Status::is_approved( gform_get_meta( $e->ID, \GravityView_Entry_Approval::meta_key ) )  ) {
184 1
						gravityview()->log->error( 'Entry ID #{entry_id} is not approved for viewing', array( 'entry_id' => $e->ID ) );
185 1
						return self::_return( __( 'You are not allowed to view this content.', 'gravityview' ) );
186
					}
187
				}
188
189 4
				$error = \GVCommon::check_entry_display( $e->as_entry(), $view );
190
191 4
				if ( is_wp_error( $error ) ) {
192 2
					gravityview()->log->error( 'Entry ID #{entry_id} is not approved for viewing: {message}', array( 'entry_id' => $e->ID, 'message' => $error->get_error_message() ) );
193 2
					return self::_return( __( 'You are not allowed to view this content.', 'gravityview' ) );
194
				}
195
			}
196
197 4
			$renderer = new \GV\Entry_Renderer();
198 4
			return self::_return( $renderer->render( $entry, $view, $request ) );
199
200
		/**
201
		 * Just this view.
202
		 */
203
		} else {
204 7
			if ( $is_reembedded ) {
205
				
206
				// Mock the request with the actual View, not the global one
207 1
				$mock_request = new \GV\Mock_Request();
208 1
				$mock_request->returns['is_view'] = $view;
209 1
				$mock_request->returns['is_entry'] = $request->is_entry( $view->form ? $view->form->ID : 0 );
210 1
				$mock_request->returns['is_edit_entry'] = $request->is_edit_entry( $view->form ? $view->form->ID : 0 );
211 1
				$mock_request->returns['is_search'] = $request->is_search();
212
213 1
				$request = $mock_request;
214
			}
215
216 7
			$renderer = new \GV\View_Renderer();
217 7
			return self::_return( $renderer->render( $view, $request ) );
218
		}
219
	}
220
221
	/**
222
	 * Validate attributes passed to the [gravityview] shortcode. Supports {get} Merge Tags values.
223
	 *
224
	 * Attributes passed to the shortcode are compared to registered attributes {@see \GV\View_Settings::defaults}
225
	 * Only attributes that are defined will be allowed through.
226
	 *
227
	 * Then, {get} merge tags are replaced with their $_GET values, if passed
228
	 *
229
	 * Then, attributes are sanitized based on the type of setting (number, checkbox, select, radio, text)
230
	 *
231
	 * @see \GV\View_Settings::defaults() Only attributes defined in default() are valid to be passed via the shortcode
232
	 *
233
	 * @param array $passed_atts Attribute pairs defined to render the View
234
	 *
235
	 * @return array Valid and sanitized attribute pairs
236
	 */
237 9
	private function parse_and_sanitize_atts( $passed_atts ) {
238
239 9
		$defaults = \GV\View_Settings::defaults( true );
240
241 9
		$supported_atts = array_fill_keys( array_keys( $defaults ), '' );
242
243
		// Whittle down the attributes to only valid pairs
244 9
		$filtered_atts = shortcode_atts( $supported_atts, $passed_atts, 'gravityview' );
245
246
		// Only keep the passed attributes after making sure that they're valid pairs
247 9
		$filtered_atts = array_intersect_key( (array) $passed_atts, $filtered_atts );
248
249 9
		$atts = array();
250
251 9
		foreach( $filtered_atts as $key => $passed_value ) {
252
253
			// Allow using GravityView merge tags in shortcode attributes, like {get} and {created_by}
254 9
			$passed_value = \GravityView_Merge_Tags::replace_variables( $passed_value );
255
256 9
			switch( $defaults[ $key ]['type'] ) {
257
258
				/**
259
				 * Make sure number fields are numeric.
260
				 * Also, convert mixed number strings to numbers
261
				 * @see http://php.net/manual/en/function.is-numeric.php#107326
262
				 */
263 9
				case 'number':
264 9
					if( is_numeric( $passed_value ) ) {
265 9
						$atts[ $key ] = ( $passed_value + 0 );
266
					}
267 9
					break;
268
269
				// Checkboxes should be 1 or 0
270 3
				case 'checkbox':
271
					$atts[ $key ] = gv_empty( $passed_value, true, false ) ? 0 : 1;
272
					break;
273
274
				/**
275
				 * Only allow values that are defined in the settings
276
				 */
277 3
				case 'select':
278 2
				case 'radio':
279 1
					$options = isset( $defaults[ $key ]['choices'] ) ? $defaults[ $key ]['choices'] : $defaults[ $key ]['options'];
280 1
					if( in_array( $passed_value, array_keys( $options ) ) ) {
281
						$atts[ $key ] = $passed_value;
282
					}
283 1
					break;
284
285 2
				case 'text':
286
				default:
287 2
					$atts[ $key ] = $passed_value;
288 2
					break;
289
			}
290
		}
291
292 9
		$atts['detail'] = \GV\Utils::get( $passed_atts, 'detail', null );
293
294 9
		return $atts;
295
	}
296
297
	/**
298
	 * Output view details.
299
	 *
300
	 * @param \GV\View $view The View.
301
	 * @param \GV\Entry_Collection $entries The calculated entries.
302
	 * @param array $atts The shortcode attributes (with defaults).
303
	 *
304
	 * @return string The output.
305
	 */
306 1
	private function detail( $view, $entries, $atts ) {
307 1
		$output = '';
308
309 1
		switch ( $key = $atts['detail'] ):
310 1
			case 'total_entries':
311 1
				$output = number_format_i18n( $entries->total() );
312 1
				break;
313
			case 'first_entry':
314
				$output = number_format_i18n( min( $entries->total(), $view->settings->get( 'offset' ) + 1 ) );
315
				break;
316
			case 'last_entry':
317
				$output = number_format_i18n( $view->settings->get( 'page_size' ) + $view->settings->get( 'offset' ) );
318
				break;
319
			case 'page_size':
320
				$output = number_format_i18n( $view->settings->get( $key ) );
321
				break;
322
		endswitch;
323
324
		/**
325
		 * @filter `gravityview/shortcode/detail/{$detail}` Filter the detail output returned from `[gravityview detail="$detail"]`
326
		 * @since 1.13
327
		 * @param string[in,out] $output Existing output
0 ignored issues
show
Documentation introduced by
The doc-type string[in,out] could not be parsed: Expected "]" at position 2, but found "in". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
328
		 *
329
		 * @since 2.0.3
330
		 * @param \GV\View $view The view.
331
		 * @param \GV\Entry_Collection $entries The entries.
332
		 * @param array $atts The shortcode atts with defaults.
333
		 */
334 1
		$output = apply_filters( "gravityview/shortcode/detail/$key", $output, $view );
335
336 1
		return $output;
337
	}
338
339
	/**
340
	 * Pop the callstack and return the value.
341
	 */
342 9
	private static function _return( $value ) {
343 9
		array_pop( self::$callstack );
344 9
		return $value;
345
	}
346
}
347