Completed
Push — 2.2 ( 8a877d )
by Damian
09:45 queued 06:58
created

Blog_Controller   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 450
Duplicated Lines 11.33 %

Coupling/Cohesion

Components 1
Dependencies 9
Metric Value
wmc 55
lcom 1
cbo 9
dl 51
loc 450
rs 6.8

18 Methods

Rating   Name   Duplication   Size   Complexity  
A getArchiveYear() 0 13 4
A getRSSLink() 0 3 1
A index() 0 10 1
A profile() 0 11 2
A getCurrentProfile() 0 11 2
A getCurrentProfilePosts() 0 9 2
C archive() 0 28 7
B getArchiveMonth() 0 13 5
A getArchiveDay() 0 11 3
A tag() 12 12 2
A getCurrentTag() 13 13 2
A category() 13 13 2
A getCurrentCategory() 13 13 2
A getMetaTitle() 0 12 2
C getFilterDescription() 0 79 9
A PaginatedList() 0 21 4
A rss() 0 14 1
A getArchiveDate() 0 21 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
3
/**
4
 * Blog Holder
5
 *
6
 * @package silverstripe
7
 * @subpackage blog
8
 *
9
 * @method HasManyList Tags() List of tags in this blog
10
 * @method HasManyList Categories() List of categories in this blog
11
 * @method ManyManyList Editors() List of editors
12
 * @method ManyManyList Writers() List of writers
13
 * @method ManyManyList Contributors() List of contributors
14
 */
15
class Blog extends Page implements PermissionProvider {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
16
	/**
17
	 * Permission for user management.
18
	 *
19
	 * @var string
20
	 */
21
	const MANAGE_USERS = 'BLOG_MANAGE_USERS';
22
23
	/**
24
	 * If true, users assigned as editor, writer, or contributor will be automatically granted
25
	 * CMS_ACCESS_CMSMain permission. If false, only users with this permission already may be
26
	 * assigned.
27
	 *
28
	 * @config
29
	 *
30
	 * @var boolean
31
	 */
32
	private static $grant_user_access = true;
0 ignored issues
show
Unused Code introduced by
The property $grant_user_access is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
33
34
	/**
35
	 * Permission to either require, or grant to users assigned to work on this blog.
36
	 *
37
	 * @config
38
	 *
39
	 * @var string
40
	 */
41
	private static $grant_user_permission = 'CMS_ACCESS_CMSMain';
0 ignored issues
show
Unused Code introduced by
The property $grant_user_permission is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
42
43
	/**
44
	 * Group code to assign newly granted users to.
45
	 *
46
	 * @config
47
	 *
48
	 * @var string
49
	 */
50
	private static $grant_user_group = 'blog-users';
0 ignored issues
show
Unused Code introduced by
The property $grant_user_group is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
51
52
	/**
53
	 * @var array
54
	 */
55
	private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
56
		'PostsPerPage' => 'Int',
57
	);
58
59
	/**
60
	 * @var array
61
	 */
62
	private static $has_many = array(
0 ignored issues
show
Unused Code introduced by
The property $has_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
63
		'Tags' => 'BlogTag',
64
		'Categories' => 'BlogCategory',
65
	);
66
67
	/**
68
	 * @var array
69
	 */
70
	private static $many_many = array(
0 ignored issues
show
Unused Code introduced by
The property $many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
71
		'Editors' => 'Member',
72
		'Writers' => 'Member',
73
		'Contributors' => 'Member',
74
	);
75
76
	/**
77
	 * @var array
78
	 */
79
	private static $allowed_children = array(
0 ignored issues
show
Unused Code introduced by
The property $allowed_children is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
80
		'BlogPost',
81
	);
82
83
	/**
84
	 * @var array
85
	 */
86
	private static $extensions = array(
0 ignored issues
show
Unused Code introduced by
The property $extensions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
87
		'BlogFilter',
88
	);
89
90
	/**
91
	 * @var array
92
	 */
93
	private static $defaults = array(
0 ignored issues
show
Unused Code introduced by
The property $defaults is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
94
		'ProvideComments' => false,
95
		'PostsPerPage' => 10,
96
	);
97
98
	/**
99
	 * @var string
100
	 */
101
	private static $description = 'Adds a blog to your website.';
0 ignored issues
show
Unused Code introduced by
The property $description is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
102
103
	/**
104
	 * {@inheritdoc}
105
	 */
106
	public function getCMSFields() {
107
		Requirements::css(BLOGGER_DIR . '/css/cms.css');
108
		Requirements::javascript(BLOGGER_DIR . '/js/cms.js');
109
110
		$self =& $this;
111
112
		$this->beforeUpdateCMSFields(function ($fields) use ($self) {
113
			if(!$self->canEdit()) {
114
				return;
115
			}
116
117
			$categories = GridField::create(
118
				'Categories',
119
				_t('Blog.Categories', 'Categories'),
120
				$self->Categories(),
121
				GridFieldCategorisationConfig::create(15, $self->Categories()->sort('Title'), 'BlogCategory', 'Categories', 'BlogPosts')
122
			);
123
124
			$tags = GridField::create(
125
				'Tags',
126
				_t('Blog.Tags', 'Tags'),
127
				$self->Tags(),
128
				GridFieldCategorisationConfig::create(15, $self->Tags()->sort('Title'), 'BlogTag', 'Tags', 'BlogPosts')
129
			);
130
131
			/**
132
			 * @var FieldList $fields
133
			 */
134
			$fields->addFieldsToTab('Root.Categorisation', array(
135
				$categories,
136
				$tags
137
			));
138
139
			$fields->findOrMakeTab('Root.Categorisation')->addExtraClass('blog-cms-categorisation');
140
		});
141
142
		return parent::getCMSFields();
143
	}
144
145
	/**
146
	 * {@inheritdoc}
147
	 */
148
	public function canEdit($member = null) {
149
		$member = $this->getMember($member);
150
151
		if($this->isEditor($member)) {
152
			return true;
153
		}
154
155
		return parent::canEdit($member);
156
	}
157
158
	/**
159
	 * @param null|int|Member $member
160
	 *
161
	 * @return null|Member
162
	 */
163 View Code Duplication
	protected function getMember($member = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164
		if(!$member) {
165
			$member = Member::currentUser();
166
		}
167
168
		if(is_numeric($member)) {
169
			$member = Member::get()->byID($member);
170
		}
171
172
		return $member;
173
	}
174
175
	/**
176
	 * Check if this member is an editor of the blog.
177
	 *
178
	 * @param Member $member
179
	 *
180
	 * @return bool
181
	 */
182
	public function isEditor($member) {
183
		$isEditor = $this->isMemberOf($member, $this->Editors());
184
		$this->extend('updateIsEditor', $isEditor, $member);
185
186
		return $isEditor;
187
	}
188
189
	/**
190
	 * Determine if the given member belongs to the given relation.
191
	 *
192
	 * @param Member $member
193
	 * @param DataList $relation
194
	 *
195
	 * @return bool
196
	 */
197 View Code Duplication
	protected function isMemberOf($member, $relation) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
198
		if(!$member || !$member->exists()) {
199
			return false;
200
		}
201
202
		if($relation instanceof UnsavedRelationList) {
203
			return in_array($member->ID, $relation->getIDList());
204
		}
205
206
		return $relation->byID($member->ID) !== null;
207
	}
208
209
	/**
210
	 * Determine the role of the given member.
211
	 *
212
	 * Call be called via template to determine the current user.
213
	 *
214
	 * @example "Hello $RoleOf($CurrentMember.ID)"
215
	 *
216
	 * @param int|Member $member
217
	 *
218
	 * @return null|string
219
	 */
220
	public function RoleOf($member) {
221
		if(is_numeric($member)) {
222
			$member = DataObject::get_by_id('Member', $member);
223
		}
224
225
		if(!$member) {
226
			return null;
227
		}
228
229
		if($this->isEditor($member)) {
230
			return _t('Blog.EDITOR', 'Editor');
231
		}
232
233
		if($this->isWriter($member)) {
234
			return _t('Blog.WRITER', 'Writer');
235
		}
236
237
		if($this->isContributor($member)) {
238
			return _t('Blog.CONTRIBUTOR', 'Contributor');
239
		}
240
241
		return null;
242
	}
243
244
	/**
245
	 * Check if this member is a writer of the blog.
246
	 *
247
	 * @param Member $member
248
	 *
249
	 * @return bool
250
	 */
251
	public function isWriter($member) {
252
		$isWriter = $this->isMemberOf($member, $this->Writers());
253
		$this->extend('updateIsWriter', $isWriter, $member);
254
255
		return $isWriter;
256
	}
257
258
	/**
259
	 * Check if this member is a contributor of the blog.
260
	 *
261
	 * @param Member $member
262
	 *
263
	 * @return bool
264
	 */
265
	public function isContributor($member) {
266
		$isContributor = $this->isMemberOf($member, $this->Contributors());
267
		$this->extend('updateIsContributor', $isContributor, $member);
268
269
		return $isContributor;
270
	}
271
272
	/**
273
	 * {@inheritdoc}
274
	 */
275
	public function canAddChildren($member = null) {
276
		$member = $this->getMember($member);
277
278
		if($this->isEditor($member) || $this->isWriter($member) || $this->isContributor($member)) {
279
			return true;
280
		}
281
282
		return parent::canAddChildren($member);
283
	}
284
285
	/**
286
	 * {@inheritdoc}
287
	 */
288
	public function getSettingsFields() {
289
		$fields = parent::getSettingsFields();
290
291
		$fields->addFieldToTab('Root.Settings',
292
			NumericField::create('PostsPerPage', _t('Blog.PostsPerPage', 'Posts Per Page'))
293
		);
294
295
		$members = $this->getCandidateUsers()->map()->toArray();
296
297
		$editorField = ListboxField::create('Editors', 'Editors', $members)
298
			->setMultiple(true)
299
			->setRightTitle('<a class="toggle-description">help</a>')
300
			->setDescription('
301
				An editor has control over specific Blogs, and all posts included within it. Short of being able to assign other editors to a blog, they are able to handle most changes to their assigned blog.<br />
302
				<br />
303
				Editors have these permissions:<br />
304
				<br />
305
				Update or publish any BlogPost in their Blog<br />
306
				Update or publish their Blog<br />
307
				Assign/unassign writers to their Blog<br />
308
				Assign/unassign contributors to their Blog<br />
309
				Assign/unassign any member as an author of a particular BlogPost
310
			');
311
312
		if(!$this->canEditEditors()) {
313
			$editorField = $editorField->performDisabledTransformation();
314
		}
315
316
		$writerField = ListboxField::create('Writers', 'Writers', $members)
317
			->setMultiple(true)
318
			->setRightTitle('<a class="toggle-description">help</a>')
319
			->setDescription('
320
				A writer has full control over creating, editing and publishing BlogPosts they have authored or have been assigned to. Writers are unable to edit BlogPosts to which they are not assigned.<br />
321
				<br />
322
				Writers have these permissions:<br />
323
				<br />
324
				Update or publish any BlogPost they have authored or have been assigned to<br />
325
				Assign/unassign any member as an author of a particular BlogPost they have authored or have been assigned to
326
			');
327
328
		if(!$this->canEditWriters()) {
329
			$writerField = $writerField->performDisabledTransformation();
330
		}
331
332
		$contributorField = ListboxField::create('Contributors', 'Contributors', $members)
333
			->setMultiple(true)
334
			->setRightTitle('<a class="toggle-description">help</a>')
335
			->setDescription('
336
				Contributors have the ability to create or edit BlogPosts, but are unable to publish without authorisation of an editor. They are also unable to assign other contributing authors to any of their BlogPosts.<br />
337
				<br />
338
				Contributors have these permissions:<br />
339
				<br />
340
				Update any BlogPost they have authored or have been assigned to
341
			');
342
343
		if(!$this->canEditContributors()) {
344
			$contributorField = $contributorField->performDisabledTransformation();
345
		}
346
347
		$fields->addFieldsToTab('Root.Users', array(
348
			$editorField,
349
			$writerField,
350
			$contributorField
351
		));
352
353
		return $fields;
354
	}
355
356
	/**
357
	 * Gets the list of user candidates to be assigned to assist with this blog.
358
	 *
359
	 * @return SS_List
360
	 */
361
	protected function getCandidateUsers() {
362
		if($this->config()->grant_user_access) {
363
			$list = Member::get();
364
			$this->extend('updateCandidateUsers', $list);
365
			return $list;
366
		} else {
367
			return Permission::get_members_by_permission(
368
				$this->config()->grant_user_permission
369
			);
370
		}
371
	}
372
373
	/**
374
	 * Determine if this user can edit the editors list.
375
	 *
376
	 * @param int|Member $member
377
	 *
378
	 * @return bool
379
	 */
380
	public function canEditEditors($member = null) {
381
		$member = $this->getMember($member);
382
383
		$extended = $this->extendedCan('canEditEditors', $member);
384
385
		if($extended !== null) {
386
			return $extended;
387
		}
388
389
		return Permission::checkMember($member, self::MANAGE_USERS);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Permission::checkMember...r, self::MANAGE_USERS); of type boolean|string|null adds the type string to the return on line 389 which is incompatible with the return type documented by Blog::canEditEditors of type boolean.
Loading history...
390
	}
391
392
	/**
393
	 * Determine if this user can edit writers list.
394
	 *
395
	 * @param int|Member $member
396
	 *
397
	 * @return boolean
398
	 */
399 View Code Duplication
	public function canEditWriters($member = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
400
		$member = $this->getMember($member);
401
402
		$extended = $this->extendedCan('canEditWriters', $member);
403
404
		if($extended !== null) {
405
			return $extended;
406
		}
407
408
		if($this->isEditor($member)) {
409
			return true;
410
		}
411
412
		return Permission::checkMember($member, self::MANAGE_USERS);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Permission::checkMember...r, self::MANAGE_USERS); of type boolean|string|null adds the type string to the return on line 412 which is incompatible with the return type documented by Blog::canEditWriters of type boolean.
Loading history...
413
	}
414
415
	/**
416
	 * Determines if this user can edit the contributors list.
417
	 *
418
	 * @param int|Member $member
419
	 *
420
	 * @return boolean
421
	 */
422 View Code Duplication
	public function canEditContributors($member = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
423
		$member = $this->getMember($member);
424
425
		$extended = $this->extendedCan('canEditContributors', $member);
426
427
		if($extended !== null) {
428
			return $extended;
429
		}
430
431
		if($this->isEditor($member)) {
432
			return true;
433
		}
434
435
		return Permission::checkMember($member, self::MANAGE_USERS);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Permission::checkMember...r, self::MANAGE_USERS); of type boolean|string|null adds the type string to the return on line 435 which is incompatible with the return type documented by Blog::canEditContributors of type boolean.
Loading history...
436
	}
437
438
	/**
439
	 * Returns BlogPosts for a given date period.
440
	 *
441
	 * @param int $year
442
	 * @param null|int $month
443
	 * @param null|int $day
444
	 *
445
	 * @return DataList
446
	 */
447
	public function getArchivedBlogPosts($year, $month = null, $day = null) {
448
		$query = $this->getBlogPosts()->dataQuery();
449
450
		$stage = $query->getQueryParam('Versioned.stage');
451
452
		if($stage) {
453
			$stage = '_' . $stage;
454
		}
455
456
		$query->innerJoin('BlogPost', sprintf('"SiteTree%s"."ID" = "BlogPost%s"."ID"', $stage, $stage));
457
458
		$query->where(sprintf('YEAR("PublishDate") = \'%s\'', Convert::raw2sql($year)));
459
460
		if($month) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
461
			$query->where(sprintf('MONTH("PublishDate") = \'%s\'', Convert::raw2sql($month)));
462
463
			if($day) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $day of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
464
				$query->where(sprintf('DAY("PublishDate") = \'%s\'', Convert::raw2sql($day)));
465
			}
466
		}
467
468
		return $this->getBlogPosts()->setDataQuery($query);
469
	}
470
471
	/**
472
	 * Return blog posts.
473
	 *
474
	 * @return DataList of BlogPost objects
475
	 */
476
	public function getBlogPosts() {
477
		$blogPosts = BlogPost::get()->filter('ParentID', $this->ID);
478
479
		$this->extend('updateGetBlogPosts', $blogPosts);
480
481
		return $blogPosts;
482
	}
483
484
	/**
485
	 * Get a link to a Member profile.
486
	 *
487
	 * @param string $urlSegment
488
	 *
489
	 * @return string
490
	 */
491
	public function ProfileLink($urlSegment) {
492
		return Controller::join_links($this->Link(), 'profile', $urlSegment);
493
	}
494
495
	/**
496
	 * This sets the title for our gridfield.
497
	 *
498
	 * @return string
499
	 */
500
	public function getLumberjackTitle() {
501
		return _t('Blog.LumberjackTitle', 'Blog Posts');
502
	}
503
504
	/**
505
	 * This overwrites lumberjacks default gridfield config.
506
	 *
507
	 * @return GridFieldConfig
508
	 */
509
	public function getLumberjackGridFieldConfig() {
510
		return GridFieldConfig_BlogPost::create();
511
	}
512
513
	/**
514
	 * {@inheritdoc}
515
	 */
516
	public function providePermissions() {
517
		return array(
518
			Blog::MANAGE_USERS => array(
519
				'name' => _t(
520
					'Blog.PERMISSION_MANAGE_USERS_DESCRIPTION',
521
					'Manage users for individual blogs'
522
				),
523
				'help' => _t(
524
					'Blog.PERMISSION_MANAGE_USERS_HELP',
525
					'Allow assignment of Editors, Writers, or Contributors to blogs'
526
				),
527
				'category' => _t('Blog.PERMISSIONS_CATEGORY', 'Blog permissions'),
528
				'sort' => 100
529
			)
530
		);
531
	}
532
533
	/**
534
	 * {@inheritdoc}
535
	 */
536
	protected function onBeforeWrite() {
537
		parent::onBeforeWrite();
538
		$this->assignGroup();
539
	}
540
541
	/**
542
	 * Assign users as necessary to the blog group.
543
	 */
544
	protected function assignGroup() {
545
		if(!$this->config()->grant_user_access) {
546
			return;
547
		}
548
549
		$group = $this->getUserGroup();
550
551
		// Must check if the method exists or else an error occurs when changing page type
552
		if ($this->hasMethod('Editors')) {
553
			foreach(array($this->Editors(), $this->Writers(), $this->Contributors()) as $levels) {
554
				foreach($levels as $user) {
555
					if(!$user->inGroup($group)) {
556
						$user->Groups()->add($group);
557
					}
558
				}
559
			}
560
		}
561
	}
562
563
	/**
564
	 * Gets or creates the group used to assign CMS access.
565
	 *
566
	 * @return Group
567
	 */
568
	protected function getUserGroup() {
569
		$code = $this->config()->grant_user_group;
570
571
		$group = Group::get()->filter('Code', $code)->first();
572
573
		if($group) {
574
			return $group;
575
		}
576
577
		$group = new Group();
578
		$group->Title = 'Blog users';
579
		$group->Code = $code;
580
581
		$group->write();
582
583
		$permission = new Permission();
584
		$permission->Code = $this->config()->grant_user_permission;
585
586
		$group->Permissions()->add($permission);
587
588
		return $group;
589
	}
590
}
591
592
/**
593
 * @package silverstripe
594
 * @subpackage blog
595
 */
596
class Blog_Controller extends Page_Controller {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
597
	/**
598
	 * @var array
599
	 */
600
	private static $allowed_actions = array(
0 ignored issues
show
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
601
		'archive',
602
		'tag',
603
		'category',
604
		'rss',
605
		'profile',
606
	);
607
608
	/**
609
	 * @var array
610
	 */
611
	private static $url_handlers = array(
0 ignored issues
show
Unused Code introduced by
The property $url_handlers is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
612
		'tag/$Tag!' => 'tag',
613
		'category/$Category!' => 'category',
614
		'archive/$Year!/$Month/$Day' => 'archive',
615
		'profile/$URLSegment!' => 'profile',
616
	);
617
618
	/**
619
	 * @var array
620
	 */
621
	private static $casting = array(
0 ignored issues
show
Unused Code introduced by
The property $casting is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
622
		'MetaTitle' => 'Text',
623
		'FilterDescription' => 'Text',
624
	);
625
626
	/**
627
	 * The current Blog Post DataList query.
628
	 *
629
	 * @var DataList
630
	 */
631
	protected $blogPosts;
632
633
	/**
634
	 * @return string
635
	 */
636
	public function index() {
637
		/**
638
		 * @var Blog $dataRecord
639
		 */
640
		$dataRecord = $this->dataRecord;
641
642
		$this->blogPosts = $dataRecord->getBlogPosts();
643
644
		return $this->render();
645
	}
646
647
	/**
648
	 * Renders a Blog Member's profile.
649
	 *
650
	 * @return SS_HTTPResponse
651
	 */
652
	public function profile() {
653
		$profile = $this->getCurrentProfile();
654
655
		if(!$profile) {
656
			return $this->httpError(404, 'Not Found');
657
		}
658
659
		$this->blogPosts = $this->getCurrentProfilePosts();
660
661
		return $this->render();
662
	}
663
664
	/**
665
	 * Get the Member associated with the current URL segment.
666
	 *
667
	 * @return null|Member
668
	 */
669
	public function getCurrentProfile() {
670
		$urlSegment = $this->request->param('URLSegment');
671
672
		if($urlSegment) {
673
			return Member::get()
674
				->filter('URLSegment', $urlSegment)
675
				->first();
676
		}
677
678
		return null;
679
	}
680
681
	/**
682
	 * Get posts related to the current Member profile.
683
	 *
684
	 * @return null|DataList
685
	 */
686
	public function getCurrentProfilePosts() {
687
		$profile = $this->getCurrentProfile();
688
689
		if($profile) {
690
			return $profile->BlogPosts()->filter('ParentID', $this->ID);
691
		}
692
693
		return null;
694
	}
695
696
	/**
697
	 * Renders an archive for a specified date. This can be by year or year/month.
698
	 *
699
	 * @return null|SS_HTTPResponse
700
	 */
701
	public function archive() {
702
		/**
703
		 * @var Blog $dataRecord
704
		 */
705
		$dataRecord = $this->dataRecord;
706
707
		$year = $this->getArchiveYear();
708
		$month = $this->getArchiveMonth();
709
		$day = $this->getArchiveDay();
710
711
		if($this->request->param('Month') && !$month) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
712
			$this->httpError(404, 'Not Found');
713
		}
714
715
		if($month && $this->request->param('Day') && !$day) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $day of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
716
			$this->httpError(404, 'Not Found');
717
		}
718
719
		if($year) {
720
			$this->blogPosts = $dataRecord->getArchivedBlogPosts($year, $month, $day);
721
722
			return $this->render();
723
		}
724
725
		$this->httpError(404, 'Not Found');
726
727
		return null;
728
	}
729
730
	/**
731
	 * Fetches the archive year from the url.
732
	 *
733
	 * @return int
734
	 */
735
	public function getArchiveYear() {
736
737
		if($this->request->param('Year')){
738
739
			if(preg_match('/^[0-9]{4}$/', $year = $this->request->param('Year'))) {
740
				return (int) $year;
741
			}
742
		} elseif($this->request->param('Action') == 'archive') {
743
			return SS_Datetime::now()->Year();
744
		}
745
746
		return null;
747
	}
748
749
	/**
750
	 * Fetches the archive money from the url.
751
	 *
752
	 * @return null|int
753
	 */
754
	public function getArchiveMonth() {
755
		$month = $this->request->param('Month');
756
757
		if(preg_match('/^[0-9]{1,2}$/', $month)) {
758
			if($month > 0 && $month < 13) {
759
				if(checkdate($month, 01, $this->getArchiveYear())) {
760
					return (int) $month;
761
				}
762
			}
763
		}
764
765
		return null;
766
	}
767
768
	/**
769
	 * Fetches the archive day from the url.
770
	 *
771
	 * @return null|int
772
	 */
773
	public function getArchiveDay() {
774
		$day = $this->request->param('Day');
775
776
		if(preg_match('/^[0-9]{1,2}$/', $day)) {
777
			if(checkdate($this->getArchiveMonth(), $day, $this->getArchiveYear())) {
778
				return (int) $day;
779
			}
780
		}
781
782
		return null;
783
	}
784
785
	/**
786
	 * Renders the blog posts for a given tag.
787
	 *
788
	 * @return null|SS_HTTPResponse
789
	 */
790 View Code Duplication
	public function tag() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
791
		$tag = $this->getCurrentTag();
792
793
		if($tag) {
794
			$this->blogPosts = $tag->BlogPosts();
795
			return $this->render();
796
		}
797
798
		$this->httpError(404, 'Not Found');
799
800
		return null;
801
	}
802
803
	/**
804
	 * Tag Getter for use in templates.
805
	 *
806
	 * @return null|BlogTag
807
	 */
808 View Code Duplication
	public function getCurrentTag() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
809
		/**
810
		 * @var Blog $dataRecord
811
		 */
812
		$dataRecord = $this->dataRecord;
813
		$tag = $this->request->param('Tag');
814
		if($tag) {
815
			return $dataRecord->Tags()
816
				->filter('URLSegment', array($tag, rawurlencode($tag)))
817
				->first();
818
		}
819
		return null;
820
	}
821
822
	/**
823
	 * Renders the blog posts for a given category.
824
	 *
825
	 * @return null|SS_HTTPResponse
826
	 */
827 View Code Duplication
	public function category() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
828
		$category = $this->getCurrentCategory();
829
830
		if($category) {
831
			$this->blogPosts = $category->BlogPosts();
832
833
			return $this->render();
834
		}
835
836
		$this->httpError(404, 'Not Found');
837
838
		return null;
839
	}
840
841
	/**
842
	 * Category Getter for use in templates.
843
	 *
844
	 * @return null|BlogCategory
845
	 */
846 View Code Duplication
	public function getCurrentCategory() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
847
		/**
848
		 * @var Blog $dataRecord
849
		 */
850
		$dataRecord = $this->dataRecord;
851
		$category = $this->request->param('Category');
852
		if($category) {
853
			return $dataRecord->Categories()
854
				->filter('URLSegment', array($category, rawurlencode($category)))
855
				->first();
856
		}
857
		return null;
858
	}
859
860
	/**
861
	 * Get the meta title for the current action.
862
	 *
863
	 * @return string
864
	 */
865
	public function getMetaTitle() {
866
		$title = $this->data()->getTitle();
867
		$filter = $this->getFilterDescription();
868
869
		if($filter) {
870
			$title = sprintf('%s - %s', $title, $filter);
871
		}
872
873
		$this->extend('updateMetaTitle', $title);
874
875
		return $title;
876
	}
877
878
	/**
879
	 * Returns a description of the current filter.
880
	 *
881
	 * @return string
882
	 */
883
	public function getFilterDescription() {
884
		$items = array();
885
886
		$list = $this->PaginatedList();
887
		$currentPage = $list->CurrentPage();
888
889
		if($currentPage > 1) {
890
			$items[] = _t(
891
				'Blog.FILTERDESCRIPTION_PAGE',
892
				'Page {page}',
893
				null,
894
				array(
0 ignored issues
show
Documentation introduced by
array('page' => $currentPage) is of type array<string,double|inte...age":"double|integer"}>, 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...
895
					'page' => $currentPage,
896
				)
897
			);
898
		}
899
900
		if($author = $this->getCurrentProfile()) {
901
			$items[] = _t(
902
				'Blog.FILTERDESCRIPTION_AUTHOR',
903
				'By {author}',
904
				null,
905
				array(
0 ignored issues
show
Documentation introduced by
array('author' => $author->Title) is of type array<string,?,{"author":"?"}>, 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...
906
					'author' => $author->Title,
907
				)
908
			);
909
		}
910
911
		if($tag = $this->getCurrentTag()) {
912
			$items[] = _t(
913
				'Blog.FILTERDESCRIPTION_TAG',
914
				'Tagged with {tag}',
915
				null,
916
				array(
0 ignored issues
show
Documentation introduced by
array('tag' => $tag->Title) is of type array<string,?,{"tag":"?"}>, 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...
917
					'tag' => $tag->Title,
918
				)
919
			);
920
		}
921
922
		if($category = $this->getCurrentCategory()) {
923
			$items[] = _t(
924
				'Blog.FILTERDESCRIPTION_CATEGORY',
925
				'In category {category}',
926
				null,
927
				array(
0 ignored issues
show
Documentation introduced by
array('category' => $category->Title) is of type array<string,?,{"category":"?"}>, 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...
928
					'category' => $category->Title,
929
				)
930
			);
931
		}
932
933
		if($this->owner->getArchiveYear()) {
934
			if($this->owner->getArchiveDay()) {
935
				$date = $this->owner->getArchiveDate()->Nice();
936
			} elseif($this->owner->getArchiveMonth()) {
937
				$date = $this->owner->getArchiveDate()->format('F, Y');
938
			} else {
939
				$date = $this->owner->getArchiveDate()->format('Y');
940
			}
941
942
			$items[] = _t(
943
				'Blog.FILTERDESCRIPTION_DATE',
944
				'In {date}',
945
				null,
946
				array(
0 ignored issues
show
Documentation introduced by
array('date' => $date) is of type array<string,?,{"date":"?"}>, 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...
947
					'date' => $date,
948
				)
949
			);
950
		}
951
952
		$result = '';
953
954
		if($items) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $items 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...
955
			$result = implode(', ', $items);
956
		}
957
958
		$this->extend('updateFilterDescription', $result);
959
960
		return $result;
961
	}
962
963
	/**
964
	 * Returns a list of paginated blog posts based on the BlogPost dataList.
965
	 *
966
	 * @return PaginatedList
967
	 */
968
	public function PaginatedList() {
969
		$allPosts = $this->blogPosts ?: new ArrayList();
970
971
		$posts = new PaginatedList($allPosts);
972
973
		// Set appropriate page size
974
		if($this->PostsPerPage > 0) {
975
			$pageSize = $this->PostsPerPage;
976
		} elseif($count = $allPosts->count()) {
977
			$pageSize = $count;
978
		} else {
979
			$pageSize = 99999;
980
		}
981
		$posts->setPageLength($pageSize);
982
983
		// Set current page
984
		$start = $this->request->getVar($posts->getPaginationGetVar());
985
		$posts->setPageStart($start);
986
987
		return $posts;
988
	}
989
990
	/**
991
	 * Displays an RSS feed of blog posts.
992
	 *
993
	 * @return string
994
	 */
995
	public function rss() {
996
		/**
997
		 * @var Blog $dataRecord
998
		 */
999
		$dataRecord = $this->dataRecord;
1000
1001
		$this->blogPosts = $dataRecord->getBlogPosts();
1002
1003
		$rss = new RSSFeed($this->blogPosts, $this->Link(), $this->MetaTitle, $this->MetaDescription);
1004
1005
		$this->extend('updateRss', $rss);
1006
1007
		return $rss->outputToBrowser();
1008
	}
1009
1010
	/**
1011
	 * Returns the current archive date.
1012
	 *
1013
	 * @return null|Date
1014
	 */
1015
	public function getArchiveDate() {
1016
		$year = $this->getArchiveYear();
1017
		$month = $this->getArchiveMonth();
1018
		$day = $this->getArchiveDay();
1019
1020
		if($year) {
1021
			if($month) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1022
				$date = sprintf('%s-%s-01', $year, $month);
1023
1024
				if($day) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $day of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1025
					$date = sprintf('%s-%s-%s', $year, $month, $day);
1026
				}
1027
			} else {
1028
				$date = sprintf('%s-01-01', $year);
1029
			}
1030
1031
			return DBField::create_field('Date', $date);
1032
		}
1033
1034
		return null;
1035
	}
1036
1037
	/**
1038
	 * Returns a link to the RSS feed.
1039
	 *
1040
	 * @return string
1041
	 */
1042
	public function getRSSLink() {
1043
		return $this->Link('rss');
1044
	}
1045
}
1046