Passed
Push — master ( 6b8ca8...3384db )
by Paul
04:57
created

ListTableController   F

Complexity

Total Complexity 68

Size/Duplication

Total Lines 394
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 68
dl 0
loc 394
ccs 0
cts 181
cp 0
rs 2.9411
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A buildColumnReviewer() 0 3 1
A renderFilterRatings() 0 15 3
A setQueryForColumn() 0 7 2
A buildColumnType() 0 6 2
A saveBulkEditFields() 0 5 4
A filterDefaultHiddenColumns() 0 6 2
A getTranslation() 0 13 2
A renderColumnValues() 0 8 3
A buildColumnSticky() 0 8 2
B renderFilterTypes() 0 14 6
A setOrderby() 0 8 2
B filterColumnsForPostType() 0 11 5
A setMetaQuery() 0 10 3
A filterSortableColumns() 0 9 3
A renderBulkEditFields() 0 4 3
A filterStatusText() 0 17 4
A renderColumnFilters() 0 10 3
A buildColumnAssignedTo() 0 8 3
A canModifyTranslation() 0 5 3
A approve() 0 9 1
A hasPermission() 0 7 4
A filterBulkUpdateMessages() 0 10 1
A buildColumnStars() 0 4 1
A unapprove() 0 9 1
A filterRowActions() 0 21 4

How to fix   Complexity   

Complex Class

Complex classes like ListTableController 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.

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 ListTableController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Controllers;
4
5
use GeminiLabs\SiteReviews\Application;
6
use GeminiLabs\SiteReviews\Database;
7
use GeminiLabs\SiteReviews\Commands\SubmitReview;
8
use GeminiLabs\SiteReviews\Controllers\Controller;
9
use GeminiLabs\SiteReviews\Helper;
10
use GeminiLabs\SiteReviews\Modules\Html;
11
use GeminiLabs\SiteReviews\Modules\Html\Builder;
12
use WP_Post;
13
use WP_Screen;
14
use WP_Query;
15
16
class ListTableController extends Controller
17
{
18
	/**
19
	 * @return void
20
	 * @action admin_action_approve
21
	 */
22
	public function approve()
23
	{
24
		check_admin_referer( 'approve-review_'.( $postId = $this->getPostId() ));
25
		wp_update_post([
26
			'ID' => $postId,
27
			'post_status' => 'publish',
28
		]);
29
		wp_safe_redirect( wp_get_referer() );
30
		exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
31
	}
32
33
	/**
34
	 * @return array
35
	 * @filter bulk_post_updated_messages
36
	 */
37
	public function filterBulkUpdateMessages( array $messages, array $counts )
38
	{
39
		$messages[Application::POST_TYPE] = [
40
			'updated' => _n( '%s review updated.', '%s reviews updated.', $counts['updated'], 'site-reviews' ),
41
			'locked' => _n( '%s review not updated, somebody is editing it.', '%s reviews not updated, somebody is editing them.', $counts['locked'], 'site-reviews' ),
42
			'deleted' => _n( '%s review permanently deleted.', '%s reviews permanently deleted.', $counts['deleted'], 'site-reviews' ),
43
			'trashed' => _n( '%s review moved to the Trash.', '%s reviews moved to the Trash.', $counts['trashed'], 'site-reviews' ),
44
			'untrashed' => _n( '%s review restored from the Trash.', '%s reviews restored from the Trash.', $counts['untrashed'], 'site-reviews' ),
45
		];
46
		return $messages;
47
	}
48
49
	/**
50
	 * @return array
51
	 * @filter manage_.Application::POST_TYPE._posts_columns
52
	 */
53
	public function filterColumnsForPostType( array $columns )
54
	{
55
		$postTypeColumns = glsr()->postTypeColumns[Application::POST_TYPE];
56
		foreach( $postTypeColumns as $key => &$value ) {
57
			if( !array_key_exists( $key, $columns ) || !empty( $value ))continue;
58
			$value = $columns[$key];
59
		}
60
		if( count( glsr( Database::class )->getReviewsMeta( 'type' )) < 2 ) {
61
			unset( $postTypeColumns['review_type'] );
62
		}
63
		return array_filter( $postTypeColumns, 'strlen' );
64
	}
65
66
	/**
67
	 * @return array
68
	 * @filter default_hidden_columns
69
	 */
70
	public function filterDefaultHiddenColumns( array $hidden, WP_Screen $screen )
71
	{
72
		if( $screen->id == 'edit-'.Application::POST_TYPE ) {
73
			$hidden = ['reviewer'];
74
		}
75
		return $hidden;
76
	}
77
78
	/**
79
	 * @return array
80
	 * @filter post_row_actions
81
	 */
82
	public function filterRowActions( array $actions, WP_Post $post )
83
	{
84
		if( $post->post_type != Application::POST_TYPE || $post->post_status == 'trash' ) {
85
			return $actions;
86
		}
87
		unset( $actions['inline hide-if-no-js'] ); //Remove Quick-edit
88
		$rowActions = [
89
			'approve' => esc_attr__( 'Approve', 'site-reviews' ),
90
			'unapprove' => esc_attr__( 'Unapprove', 'site-reviews' ),
91
		];
92
		foreach( $rowActions as $key => $text ) {
93
			$actions[$key] = glsr( Builder::class )->a( $text, [
94
				'aria-label' => sprintf( esc_attr_x( '%s this review', 'Approve the review', 'site-reviews' ), text ),
0 ignored issues
show
Bug introduced by
The constant GeminiLabs\SiteReviews\Controllers\text was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
95
				'class' => 'change-'.Application::POST_TYPE.'-status',
96
				'href' => wp_nonce_url(
97
					admin_url( 'post.php?post='.$post->ID.'&action='.$key ),
98
					$key.'-review_'.$post->ID
99
				),
100
			]);
101
		}
102
		return $actions;
103
	}
104
105
	/**
106
	 * @return array
107
	 * @filter manage_edit-.Application::POST_TYPE._sortable_columns
108
	 */
109
	public function filterSortableColumns( array $columns )
110
	{
111
		$postTypeColumns = glsr()->postTypeColumns[Application::POST_TYPE];
112
		unset( $postTypeColumns['cb'] );
113
		foreach( $postTypeColumns as $key => $value ) {
114
			if( glsr( Helper::class )->startsWith( 'taxonomy', $key ))continue;
115
			$columns[$key] = $key;
116
		}
117
		return $columns;
118
	}
119
120
	/**
121
	 * Customize the post_type status text
122
	 * @param string $translation
123
	 * @param string $single
124
	 * @param string $plural
125
	 * @param int $number
126
	 * @param string $domain
127
	 * @return string
128
	 * @filter ngettext
129
	 */
130
	public function filterStatusText( $translation, $single, $plural, $number, $domain )
131
	{
132
		if( $this->canModifyTranslation( $domain )) {
133
			$strings = [
134
				'Published' => __( 'Approved', 'site-reviews' ),
135
				'Pending' => __( 'Unapproved', 'site-reviews' ),
136
			];
137
			foreach( $strings as $search => $replace ) {
138
				if( strpos( $single, $search ) === false )continue;
139
				$translation = $this->getTranslation([
140
					'number' => $number,
141
					'plural' => str_replace( $search, $replace, $plural ),
142
					'single' => str_replace( $search, $replace, $single ),
143
				]);
144
			}
145
		}
146
		return $translation;
147
	}
148
149
	/**
150
	 * @param string $columnName
151
	 * @param string $postType
152
	 * @return void
153
	 * @action bulk_edit_custom_box
154
	 */
155
	public function renderBulkEditFields( $columnName, $postType )
156
	{
157
		if( $columnName == 'assigned_to' && $postType == Application::POST_TYPE ) {
158
			$this->render( 'edit/bulk-edit-assigned-to' );
0 ignored issues
show
Bug introduced by
The method render() does not exist on GeminiLabs\SiteReviews\C...ers\ListTableController. Did you maybe mean renderFilterTypes()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

158
			$this->/** @scrutinizer ignore-call */ 
159
          render( 'edit/bulk-edit-assigned-to' );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
159
		};
160
	}
161
162
	/**
163
	 * @param string $post_type
164
	 * @return void
165
	 * @action restrict_manage_posts
166
	 */
167
	public function renderColumnFilters( $post_type )
168
	{
169
		if( $post_type !== Application::POST_TYPE )return;
170
		if( !( $status = filter_input( INPUT_GET, 'post_status' ))) {
171
			$status = 'publish';
172
		}
173
		$ratings = glsr( Database::class )->getReviewsMeta( 'rating', $status );
174
		$types = glsr( Database::class )->getReviewsMeta( 'type', $status );
175
		$this->renderFilterRatings( $ratings );
176
		$this->renderFilterTypes( $types );
177
	}
178
179
	/**
180
	 * @param string $column
181
	 * @return void
182
	 * @action manage_posts_custom_column
183
	 */
184
	public function renderColumnValues( $column, $postId )
185
	{
186
		if( glsr_current_screen()->id != Application::POST_TYPE )return;
187
		global $wp_version;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
188
		$method = glsr( Helper::class )->buildMethodName( $column, 'buildColumn' );
189
		echo method_exists( $this, $method )
190
			? call_user_func( [$this, $method], $postId )
191
			: apply_filters( 'site-reviews/columns/'.$column, '', $postId );
192
	}
193
194
	/**
195
	 * @param int $postId
196
	 * @return void
197
	 * @action save_post_.Application::POST_TYPE
198
	 */
199
	public function saveBulkEditFields( $postId )
200
	{
201
		if( !current_user_can( 'edit_posts' ))return;
202
		if( $assignedTo = filter_input( INPUT_GET, 'assigned_to' ) && get_post( $assignedTo )) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $assignedTo seems to be never defined.
Loading history...
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $assignedTo = (filter_in... get_post($assignedTo)), Probably Intended Meaning: ($assignedTo = filter_in...& get_post($assignedTo)
Loading history...
203
			update_post_meta( $postId, 'assigned_to', $assignedTo );
204
		}
205
	}
206
207
	/**
208
	 * @return void
209
	 * @action pre_get_posts
210
	 */
211
	public function setQueryForColumn( WP_Query $query )
212
	{
213
		if( !$this->hasPermission( $query ))return;
214
		$this->setMetaQuery( $query, [
215
			'rating', 'review_type',
216
		]);
217
		$this->setOrderby( $query );
218
	}
219
220
	/**
221
	 * @return void
222
	 * @action admin_action_unapprove
223
	 */
224
	public function unapprove()
225
	{
226
		check_admin_referer( 'unapprove-review_'.( $postId = $this->getPostId() ));
227
		wp_update_post([
228
			'ID' => $postId,
229
			'post_status' => 'pending',
230
		]);
231
		wp_safe_redirect( wp_get_referer() );
232
		exit;
1 ignored issue
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
233
	}
234
235
	/**
236
	 * @param int $postId
237
	 * @return string
238
	 */
239
	protected function buildColumnAssignedTo( $postId )
240
	{
241
		$post = get_post( glsr( Database::class )->getReviewMeta( $postId )->assigned_to );
242
		if( !( $post instanceof WP_Post ) || $post->post_status != 'publish' ) {
243
			return '&mdash;';
244
		}
245
		return glsr( Builder::class )->a( get_the_title( $post->ID ), [
246
			'href' => (string)get_the_permalink( $post->ID ),
247
		]);
248
	}
249
250
	/**
251
	 * @param int $postId
252
	 * @return string
253
	 */
254
	protected function buildColumnReviewer( $postId )
255
	{
256
		return glsr( Database::class )->getReviewMeta(  $postId  )->author;
257
	}
258
259
	/**
260
	 * @param int $postId
261
	 * @return string
262
	 */
263
	protected function buildColumnStars( $postId )
264
	{
265
		return glsr( Html::class )->buildPartial( 'star-rating', [
266
			'rating' => glsr( Database::class )->getReviewMeta( $postId )->rating,
267
		]);
268
	}
269
270
	/**
271
	 * @param int $postId
272
	 * @return string
273
	 */
274
	protected function buildColumnSticky( $postId )
275
	{
276
		$pinned = glsr( Database::class )->getReviewMeta( $postId )->pinned
277
			? ' pinned'
278
			: '';
279
		return glsr( Builder::class )->i([
280
			'class' => trim( 'dashicons dashicons-sticky '.$pinned ),
281
			'data-id' => $postId,
282
		]);
283
	}
284
285
	/**
286
	 * @param int $postId
287
	 * @return string
288
	 */
289
	protected function buildColumnType( $postId )
290
	{
291
		$reviewMeta = glsr( Database::class )->getReviewMeta( $postId );
292
		return isset( glsr()->reviewTypes[$reviewMeta->review_type] )
293
			? glsr()->reviewTypes[$reviewMeta->review_type]
294
			: $reviewMeta->review_type;
295
	}
296
297
	/**
298
	 * Check if the translation string can be modified
299
	 * @param string $domain
300
	 * @return bool
301
	 */
302
	protected function canModifyTranslation( $domain = 'default' )
303
	{
304
		return $domain == 'default'
305
			&& glsr_current_screen()->base == 'edit'
306
			&& glsr_current_screen()->post_type == Application::POST_TYPE;
307
	}
308
309
	/**
310
	 * Get the modified translation string
311
	 * @return string
312
	 */
313
	protected function getTranslation( array $args )
314
	{
315
		$defaults = [
316
			'number' => 0,
317
			'plural' => '',
318
			'single' => '',
319
			'text' => '',
320
		];
321
		$args = (object) wp_parse_args( $args, $defaults );
322
		$translations = get_translations_for_domain( Application::ID );
323
		return $args->text
324
			? $translations->translate( $args->text )
325
			: $translations->translate_plural( $args->single, $args->plural, $args->number );
326
	}
327
328
	/**
329
	 * @return bool
330
	 */
331
	protected function hasPermission( WP_Query $query )
332
	{
333
		global $pagenow;
1 ignored issue
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
334
		return is_admin()
335
			&& $query->is_main_query()
336
			&& $query->get( 'post_type' ) == Application::POST_TYPE
337
			&& $pagenow == 'edit.php';
338
	}
339
340
	/**
341
	 * @param array $ratings
342
	 * @return void
343
	 */
344
	protected function renderFilterRatings( $ratings )
345
	{
346
		if( empty( $ratings )
347
			|| apply_filters( 'site-reviews/disable/filter/ratings', false )
348
		)return;
349
		$ratings = array_flip( array_reverse( $ratings ));
350
		array_walk( $ratings, function( &$value, $key ) {
351
			$label = _n( '%s star', '%s stars', $key, 'site-reviews' );
352
			$value = sprintf( $label, $key );
353
		});
354
		$ratings = [__( 'All ratings', 'site-reviews' )] + $ratings;
355
		printf( '<label class="screen-reader-text" for="rating">%s</label>', __( 'Filter by rating', 'site-reviews' ));
356
		glsr( Html::class )->renderPartial( 'filterby', [
357
			'name' => 'rating',
358
			'values' => $ratings,
359
		]);
360
	}
361
362
	/**
363
	 * @param array $types
364
	 * @return void
365
	 */
366
	protected function renderFilterTypes( $types )
367
	{
368
		if( count( $types ) < 1
369
			|| ( count( $types ) == 1 && $types[0] == 'local' )
370
			|| apply_filters( 'site-reviews/disable/filter/types', false )
371
		)return;
372
		$reviewTypes = [__( 'All types', 'site-reviews' )];
373
		foreach( $types as $type ) {
374
			$reviewTypes[$type] = glsr( Strings::class )->review_types( $type, ucfirst( $type ));
0 ignored issues
show
Bug introduced by
The type GeminiLabs\SiteReviews\Controllers\Strings was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
375
		}
376
		printf( '<label class="screen-reader-text" for="type">%s</label>', __( 'Filter by type', 'site-reviews' ));
377
		glsr( Html::class )->renderPartial( 'filterby', [
378
			'name' => 'review_type',
379
			'values' => $reviewTypes,
380
		]);
381
	}
382
383
	/**
384
	 * @return void
385
	 */
386
	protected function setMetaQuery( WP_Query $query, array $metaKeys )
387
	{
388
		foreach( $metaKeys as $key ) {
389
			if( !( $value = filter_input( INPUT_GET, $key )))continue;
390
			$metaQuery = (array)$query->get( 'meta_query' );
391
			$metaQuery[] = [
392
				'key' => $key,
393
				'value' => $value,
394
			];
395
			$query->set( 'meta_query', $metaQuery );
396
		}
397
	}
398
399
	/**
400
	 * @return void
401
	 */
402
	protected function setOrderby( WP_Query $query )
403
	{
404
		$orderby = $query->get( 'orderby' );
405
		$columns = glsr()->postTypeColumns[Application::POST_TYPE];
406
		unset( $columns['cb'], $columns['title'], $columns['date'] );
407
		if( in_array( $orderby, array_keys( $columns ))) {
408
			$query->set( 'meta_key', $orderby );
409
			$query->set( 'orderby', 'meta_value' );
410
		}
411
	}
412
}
413