Completed
Push — master ( 998007...d6e392 )
by Nazar
04:34
created

Controller   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 521
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 4
Bugs 1 Features 0
Metric Value
wmc 56
c 4
b 1
f 0
lcom 1
cbo 14
dl 0
loc 521
rs 6.5957

10 Methods

Rating   Name   Duplication   Size   Complexity  
B latest_posts() 0 44 2
A get_page_and_set_title() 0 13 3
B section() 0 62 4
C post() 0 66 14
B tag() 0 69 6
B new_post() 0 22 5
D edit_post() 0 43 10
B drafts() 0 34 2
C atom_xml() 0 112 7
A get_favicon_path() 0 9 3

How to fix   Complexity   

Complex Class

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

1
<?php
2
/**
3
 * @package   Blogs
4
 * @category  modules
5
 * @author    Nazar Mokrynskyi <[email protected]>
6
 * @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi
7
 * @license   MIT License, see license.txt
8
 */
9
namespace cs\modules\Blogs;
10
use
11
	cs\Config,
12
	cs\Event,
13
	cs\ExitException,
14
	cs\Language\Prefix,
15
	cs\Page\Meta,
16
	cs\Page,
17
	cs\User,
18
	h;
19
20
class Controller {
21
	/**
22
	 * @param \cs\Request $Request
23
	 */
24
	static function latest_posts ($Request) {
25
		if (!Event::instance()->fire('Blogs/latest_posts')) {
26
			return;
27
		}
28
		$Config = Config::instance();
29
		$L      = new Prefix('blogs_');
30
		$Meta   = Meta::instance();
31
		$Page   = Page::instance();
32
		$Posts  = Posts::instance();
33
		/**
34
		 * Page title
35
		 */
36
		$Page->title($L->latest_posts);
37
		/**
38
		 * Now add link to Atom feed for latest posts
39
		 */
40
		$Page->atom('Blogs/atom.xml', $L->latest_posts);
41
		/**
42
		 * Set page of blog type (Open Graph protocol)
43
		 */
44
		$Meta->blog();
45
		/**
46
		 * Determine current page
47
		 */
48
		$page = static::get_page_and_set_title($Request, $Page, $L);
49
		/**
50
		 * Get posts for current page in JSON-LD structure format
51
		 */
52
		$posts_per_page = $Config->module('Blogs')->posts_per_page;
53
		$posts          = $Posts->get_latest_posts($page, $posts_per_page);
54
		/**
55
		 * Base url (without page number)
56
		 */
57
		$base_url = $Config->base_url().'/'.path($L->Blogs).'/'.path($L->latest_posts);
58
		/**
59
		 * Render posts page
60
		 */
61
		Helpers::show_posts_list(
62
			$posts,
63
			$Posts->get_total_count(),
64
			$page,
65
			$base_url
66
		);
67
	}
68
	/**
69
	 * @param \cs\Request $Request
70
	 * @param Page        $Page
71
	 * @param Prefix      $L
72
	 *
73
	 * @return int
74
	 */
75
	protected static function get_page_and_set_title ($Request, $Page, $L) {
76
		$page = max(
77
			isset($Request->route_ids[0]) ? array_slice($Request->route_ids, -1)[0] : 1,
78
			1
79
		);
80
		/**
81
		 * If this is not first page - show that in page title
82
		 */
83
		if ($page > 1) {
84
			$Page->title($L->page_number($page));
85
		}
86
		return $page;
87
	}
88
	/**
89
	 * @param \cs\Request $Request
90
	 *
91
	 * @throws ExitException
92
	 */
93
	static function section ($Request) {
94
		if (!Event::instance()->fire('Blogs/section')) {
95
			return;
96
		}
97
		$Config   = Config::instance();
98
		$L        = new Prefix('blogs_');
99
		$Meta     = Meta::instance();
100
		$Page     = Page::instance();
101
		$Posts    = Posts::instance();
102
		$Sections = Sections::instance();
103
		/**
104
		 * At first - determine part of url and get sections list based on that path
105
		 */
106
		$sections = $Sections->get_by_path(
107
			array_slice($Request->route_path, 1)
108
		);
109
		if (!$sections) {
110
			throw new ExitException(400);
111
		}
112
		$sections = $Sections->get($sections);
113
		/**
114
		 * Now lets set page title using sections names from page path
115
		 * We will not remove `$section` variable after, since it will be direct parent of each shown post
116
		 */
117
		foreach ($sections as $section) {
0 ignored issues
show
Bug introduced by
The expression $sections of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
118
			$Page->title($section['title']);
119
		}
120
		/**
121
		 * Now add link to Atom feed for posts from current section only
122
		 */
123
		/** @noinspection PhpUndefinedVariableInspection */
124
		$Page->atom(
125
			"Blogs/atom.xml/?section=$section[id]",
126
			implode($Config->core['title_delimiter'], [$L->latest_posts, $L->section, $section['title']])
127
		);
128
		/**
129
		 * Set page of blog type (Open Graph protocol)
130
		 */
131
		$Meta->blog();
132
		/**
133
		 * Determine current page
134
		 */
135
		$page = static::get_page_and_set_title($Request, $Page, $L);
136
		/**
137
		 * Get posts for current page in JSON-LD structure format
138
		 */
139
		$posts_per_page = $Config->module('Blogs')->posts_per_page;
140
		$posts          = $Posts->get_for_section($section['id'], $page, $posts_per_page);
141
		/**
142
		 * Base url (without page number)
143
		 */
144
		$base_url = $Config->base_url().'/'.path($L->Blogs).'/'.path($L->section)."/$section[full_path]";
145
		/**
146
		 * Render posts page
147
		 */
148
		Helpers::show_posts_list(
149
			$posts,
150
			$section['posts'],
151
			$page,
152
			$base_url
153
		);
154
	}
155
	/**
156
	 * @param \cs\Request  $Request
157
	 * @param \cs\Response $Response
158
	 *
159
	 * @throws ExitException
160
	 */
161
	static function post ($Request, $Response) {
162
		if (!Event::instance()->fire('Blogs/post')) {
163
			return;
164
		}
165
166
		$Config   = Config::instance();
167
		$Page     = Page::instance();
168
		$User     = User::instance();
169
		$Comments = null;
170
		Event::instance()->fire(
171
			'Comments/instance',
172
			[
173
				'Comments' => &$Comments
174
			]
175
		);
176
		/**
177
		 * @var \cs\modules\Comments\Comments $Comments
178
		 */
179
		$Posts   = Posts::instance();
180
		$rc      = $Request->route;
181
		$post_id = (int)mb_substr($rc[1], mb_strrpos($rc[1], ':') + 1);
182
		if (!$post_id) {
183
			throw new ExitException(404);
184
		}
185
		$post = $Posts->get_as_json_ld($post_id);
186
		if (
187
			!$post ||
188
			(
189
				$post['draft'] && $post['user'] != $User->id
190
			)
191
		) {
192
			throw new ExitException(404);
193
		}
194
		if ($post['path'] != mb_substr($rc[1], 0, mb_strrpos($rc[1], ':'))) {
195
			$Response->redirect($post['url'], 303);
196
			return;
197
		}
198
		$Page->title($post['title']);
0 ignored issues
show
Documentation introduced by
$post['title'] is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
199
		$Page->Description = description($post['short_content']);
200
		$Page->canonical_url($post['url']);
0 ignored issues
show
Documentation introduced by
$post['url'] is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
201
		$Meta = Meta::instance();
202
		$Meta
203
			->article()
204
			->article('published_time', date('Y-m-d', $post['date'] ?: TIME))
205
			->article('section', $post['articleSection'] ? $post['articleSection'][0] : false)
206
			->article('tag', $post['tags']);
207
		array_map([$Meta, 'image'], $post['image']);
208
		$comments_enabled = $Config->module('Blogs')->enable_comments && $Comments;
209
		$is_admin         =
210
			$User->admin() &&
211
			$User->get_permission('admin/Blogs', 'index') &&
212
			$User->get_permission('admin/Blogs', 'edit_post');
213
		$Page->content(
214
			h::{'article[is=cs-blogs-post]'}(
215
				h::{'script[type=application/ld+json]'}(
216
					json_encode($post, JSON_UNESCAPED_UNICODE)
217
				),
218
				[
219
					'comments_enabled' => $comments_enabled,
220
					'can_edit'         => $is_admin || $User->id == $post['user'],
221
					'can_delete'       => $is_admin
222
				]
223
			).
224
			($comments_enabled ? $Comments->block($post['id']) : '')
0 ignored issues
show
Documentation introduced by
$post['id'] is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
225
		);
226
	}
227
	/**
228
	 * @param \cs\Request $Request
229
	 *
230
	 * @throws ExitException
231
	 */
232
	static function tag ($Request) {
233
		if (!Event::instance()->fire('Blogs/tag')) {
234
			return;
235
		}
236
		$Config = Config::instance();
237
		$L      = new Prefix('blogs_');
238
		$Meta   = Meta::instance();
239
		$Page   = Page::instance();
240
		$Posts  = Posts::instance();
241
		$Tags   = Tags::instance();
242
		/**
243
		 * If no tag specified
244
		 */
245
		if (!isset($Request->route[1])) {
246
			throw new ExitException(404);
247
		}
248
		/**
249
		 * Find tag
250
		 */
251
		$tag = $Tags->get_by_text($Request->route[1]);
252
		if (!$tag) {
253
			throw new ExitException(404);
254
		}
255
		$tag = $Tags->get($tag);
256
		/**
257
		 * Add tag to page title
258
		 */
259
		$Page->title($tag['text']);
260
		/**
261
		 * Now add link to Atom feed for posts with current tag only
262
		 */
263
		$Page->atom(
264
			"Blogs/atom.xml/?tag=$tag[id]",
265
			implode($Config->core['title_delimiter'], [$L->latest_posts, $L->tag, $tag['text']])
266
		);
267
		/**
268
		 * Set page of blog type (Open Graph protocol)
269
		 */
270
		$Meta->blog();
271
		/**
272
		 * Determine current page
273
		 */
274
		$page = max($Request->route(2) ?: 1, 1);
275
		/**
276
		 * If this is not first page - show that in page title
277
		 */
278
		if ($page > 1) {
279
			$Page->title($L->blogs_nav_page($page));
280
		}
281
		/**
282
		 * Get posts for current page in JSON-LD structure format
283
		 */
284
		$posts_per_page = $Config->module('Blogs')->posts_per_page;
285
		$posts          = $Posts->get_for_tag($tag['id'], $L->clang, $page, $posts_per_page);
286
		$posts_count    = $Posts->get_for_tag_count($tag['id'], $L->clang);
287
		/**
288
		 * Base url (without page number)
289
		 */
290
		$base_url = $Config->base_url().'/'.path($L->Blogs).'/'.path($L->tag).'/'.$Request->route[1];
291
		/**
292
		 * Render posts page
293
		 */
294
		Helpers::show_posts_list(
295
			$posts,
296
			$posts_count,
297
			$page,
298
			$base_url
299
		);
300
	}
301
	static function new_post () {
302
		if (!Event::instance()->fire('Blogs/new_post')) {
303
			return;
304
		}
305
306
		$Config      = Config::instance();
307
		$module_data = $Config->module('Blogs');
308
		$L           = new Prefix('blogs_');
309
		$Page        = Page::instance();
310
		$User        = User::instance();
311
		$Page->title($L->new_post);
312
		if (!$User->admin() && $module_data->new_posts_only_from_admins) {
313
			throw new ExitException(403);
314
		}
315
		if (!$User->user()) {
316
			$Page->warning($L->for_registered_users_only);
317
			return;
318
		}
319
		$Page->content(
320
			h::cs_blogs_add_edit_post()
321
		);
322
	}
323
	/**
324
	 * @param \cs\Request $Request
325
	 *
326
	 * @throws ExitException
327
	 */
328
	static function edit_post ($Request) {
329
		if (!Event::instance()->fire('Blogs/edit_post')) {
330
			return;
331
		}
332
333
		$Posts       = Posts::instance();
334
		$Config      = Config::instance();
335
		$module_data = $Config->module('Blogs');
336
		$L           = new Prefix('blogs_');
337
		$Page        = Page::instance();
338
		$User        = User::instance();
339
		if ($module_data->new_posts_only_from_admins && !$User->admin()) {
340
			throw new ExitException(403);
341
		}
342
		$id = $Request->route(1);
343
		if (!$id) {
344
			throw new ExitException(400);
345
		}
346
		$post = $Posts->get($id);
347
		if (!$post) {
348
			throw new ExitException(404);
349
		}
350
		if (
351
			$post['user'] != $User->id &&
352
			!(
353
				$User->admin() &&
354
				$User->get_permission('admin/Blogs', 'index') &&
355
				$User->get_permission('admin/Blogs', 'edit_post')
356
			)
357
		) {
358
			throw new ExitException(403);
359
		}
360
		$Page->title(
361
			$L->editing_of_post($post['title'])
362
		);
363
		$Page->content(
364
			h::cs_blogs_add_edit_post(
365
				[
366
					'id' => $post['id']
367
				]
368
			)
369
		);
370
	}
371
	/**
372
	 * @param \cs\Request $Request
373
	 */
374
	static function drafts ($Request) {
375
		if (!Event::instance()->fire('Blogs/drafts')) {
376
			return;
377
		}
378
		$Config = Config::instance();
379
		$L      = new Prefix('blogs_');
380
		$Page   = Page::instance();
381
		$Posts  = Posts::instance();
382
		$User   = User::instance();
383
		$Page->title($L->drafts);
384
		/**
385
		 * Determine current page
386
		 */
387
		$page = static::get_page_and_set_title($Request, $Page, $L);
388
		/**
389
		 * Get posts for current page in JSON-LD structure format
390
		 */
391
		$posts_per_page = $Config->module('Blogs')->posts_per_page;
392
		$posts          = $Posts->get_drafts($User->id, $page, $posts_per_page);
393
		$posts_count    = $Posts->get_drafts_count($User->id);
394
		/**
395
		 * Base url (without page number)
396
		 */
397
		$base_url = $Config->base_url().'/'.path($L->Blogs).'/'.path($L->drafts);
398
		/**
399
		 * Render posts page
400
		 */
401
		Helpers::show_posts_list(
402
			$posts,
403
			$posts_count,
404
			$page,
405
			$base_url
406
		);
407
	}
408
	/**
409
	 * @param \cs\Request  $Request
410
	 * @param \cs\Response $Response
411
	 *
412
	 * @throws ExitException
413
	 */
414
	static function atom_xml ($Request, $Response) {
415
		$Config   = Config::instance();
416
		$L        = new Prefix('blogs_');
417
		$Page     = Page::instance();
418
		$User     = User::instance();
419
		$title    = [
420
			get_core_ml_text('name'),
421
			$L->Blogs
422
		];
423
		$Posts    = Posts::instance();
424
		$Sections = Sections::instance();
425
		$Tags     = Tags::instance();
426
		$number   = $Config->module('Blogs')->posts_per_page;
427
		$section  = $Request->query('section');
428
		$tag      = $Request->query('tag');
429
		if ($section) {
430
			$section = $Sections->get($section);
431
			if (!$section) {
432
				throw new ExitException(404);
433
			}
434
			$title[] = $L->section;
435
			$title[] = $section['title'];
436
			$posts   = $Posts->get_for_section($section['id'], 1, $number);
437
		} elseif ($tag) {
438
			$tag = $Tags->get($tag);
439
			if (!$tag) {
440
				throw new ExitException(404);
441
			}
442
			$title[] = $L->tag;
443
			$title[] = $tag['text'];
444
			$posts   = $Posts->get_for_tag($tag['id'], $L->clang, 1, $number);
445
		} else {
446
			$posts = $Posts->get_latest_posts(1, $number);
447
		}
448
		$title[]  = $L->latest_posts;
449
		$title    = implode($Config->core['title_delimiter'], $title);
450
		$base_url = $Config->base_url();
451
		$Response->header('content-type', 'application/atom+xml');
452
		$Page->interface = false;
453
454
		$url = $Config->core_url().$Request->uri;
455
		$Page->content(
456
			"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
457
			h::feed(
458
				h::title($title).
459
				h::id($url).
460
				str_replace(
461
					'>',
462
					'/>',
463
					h::link(
464
						[
465
							'href' => $url,
466
							'rel'  => 'self'
467
						]
468
					)
469
				).
470
				h::updated(date('c')).
471
				'<icon>'.static::get_favicon_path($Config->core['theme'])."</icon>\n".
472
				h::entry(
473
					array_map(
474
						function ($post) use ($Posts, $Sections, $User, $base_url) {
475
							$post = $Posts->get($post);
476
							return
477
								h::title($post['title']).
478
								h::id("$base_url/Blogs/:$post[id]").
479
								h::updated(date('c', $post['date'])).
480
								h::published(date('c', $post['date'])).
481
								str_replace(
482
									'>',
483
									'/>',
484
									h::link(
485
										[
486
											'href' => "$base_url/Blogs/:$post[id]"
487
										]
488
									)
489
								).
490
								h::{'author name'}($User->username($post['user'])).
491
								h::category(
492
									$post['sections'] == ['0'] ? false : array_map(
493
										function ($category) {
494
											return [
495
												'term'  => $category['title'],
496
												'label' => $category['title']
497
											];
498
										},
499
										$Sections->get($post['sections'])
500
									)
501
								).
502
								h::summary(
503
									htmlentities($post['short_content']),
504
									[
505
										'type' => 'html'
506
									]
507
								).
508
								h::content(
509
									htmlentities($post['content']),
510
									[
511
										'type' => 'html'
512
									]
513
								);
514
						},
515
						$posts ?: []
516
					)
517
				),
518
				[
519
					'xmlns'    => 'http://www.w3.org/2005/Atom',
520
					'xml:lang' => $L->clang,
521
					'xml:base' => "$base_url/"
522
				]
523
			)
524
		);
525
	}
526
	/**
527
	 * @param string $theme
528
	 *
529
	 * @return string
530
	 */
531
	protected static function get_favicon_path ($theme) {
532
		$theme_favicon = "$theme/img/favicon";
533
		if (file_exists(THEMES."/$theme_favicon.png")) {
534
			return "$theme_favicon.png";
535
		} elseif (file_exists(THEMES."/$theme_favicon.ico")) {
536
			return "$theme_favicon.ico";
537
		}
538
		return 'favicon.ico';
539
	}
540
}
541