Completed
Pull Request — develop (#1487)
by Gennady
07:46
created

gravityview::callback()   F

Complexity

Conditions 52
Paths 2644

Size

Total Lines 189

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 65
CRAP Score 109.97

Importance

Changes 0
Metric Value
cc 52
nc 2644
nop 3
dl 0
loc 189
ccs 65
cts 90
cp 0.7221
crap 109.97
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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