Completed
Pull Request — master (#166)
by Gordon
02:16
created

CommentingController::approve()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 22
Code Lines 16

Duplication

Lines 22
Ratio 100 %
Metric Value
dl 22
loc 22
rs 6.9811
cc 7
eloc 16
nc 7
nop 0
1
<?php
2
3
/**
4
 * @package comments
5
 */
6
7
class CommentingController extends Controller {
8
9
	private static $allowed_actions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
10
		'delete',
11
		'spam',
12
		'ham',
13
		'approve',
14
		'rss',
15
		'CommentsForm',
16
		'reply',
17
		'doPostComment',
18
		'doPreviewComment'
19
	);
20
21
	private static $url_handlers = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
22
		'reply/$ParentCommentID//$ID/$OtherID' => 'reply',
23
	);
24
25
	/**
26
	 * Fields required for this form
27
	 *
28
	 * @var array
29
	 * @config
30
	 */
31
	private static $required_fields = array(
32
		'Name',
33
		'Email',
34
		'Comment'
35
	);
36
37
	/**
38
	 * Base class this commenting form is for
39
	 *
40
	 * @var string
41
	 */
42
	private $baseClass = "";
43
44
	/**
45
	 * The record this commenting form is for
46
	 *
47
	 * @var DataObject
48
	 */
49
	private $ownerRecord = null;
50
51
	/**
52
	 * Parent controller record
53
	 *
54
	 * @var Controller
55
	 */
56
	private $ownerController = null;
57
58
	/**
59
	 * Backup url to return to
60
	 *
61
	 * @var string
62
	 */
63
	protected $fallbackReturnURL = null;
64
65
	/**
66
	 * Set the base class to use
67
	 *
68
	 * @param string $class
69
	 */
70
	public function setBaseClass($class) {
71
		$this->baseClass = $class;
72
	}
73
74
	/**
75
	 * Get the base class used
76
	 *
77
	 * @return string
78
	 */
79
	public function getBaseClass() {
80
		return $this->baseClass;
81
	}
82
83
	/**
84
	 * Set the record this controller is working on
85
	 *
86
	 * @param DataObject $record
87
	 */
88
	public function setOwnerRecord($record) {
89
		$this->ownerRecord = $record;
90
	}
91
92
	/**
93
	 * Get the record
94
	 *
95
	 * @return DataObject
96
	 */
97
	public function getOwnerRecord() {
98
		return $this->ownerRecord;
99
	}
100
101
	/**
102
	 * Set the parent controller
103
	 *
104
	 * @param Controller $controller
105
	 */
106
	public function setOwnerController($controller) {
107
		$this->ownerController = $controller;
108
	}
109
110
	/**
111
	 * Get the parent controller
112
	 *
113
	 * @return Controller
114
	 */
115
	public function getOwnerController() {
116
		return $this->ownerController;
117
	}
118
119
	/**
120
	 * Get the commenting option for the current state
121
	 *
122
	 * @param string $key
123
	 * @return mixed Result if the setting is available, or null otherwise
124
	 */
125
	public function getOption($key) {
126
		// If possible use the current record
127
		if($record = $this->getOwnerRecord()) {
128
			return $record->getCommentsOption($key);
129
		}
130
131
		// Otherwise a singleton of that record
132
		if($class = $this->getBaseClass()) {
133
			return singleton($class)->getCommentsOption($key);
134
		}
135
136
		// Otherwise just use the default options
137
		return singleton('CommentsExtension')->getCommentsOption($key);
138
	}
139
140
	/**
141
	 * Workaround for generating the link to this controller
142
	 *
143
	 * @return string
144
	 */
145
	public function Link($action = '', $id = '', $other = '') {
146
		return Controller::join_links(Director::baseURL(), __CLASS__ , $action, $id, $other);
147
	}
148
149
	/**
150
	 * Outputs the RSS feed of comments
151
	 *
152
	 * @return HTMLText
153
	 */
154
	public function rss() {
155
		return $this->getFeed($this->request)->outputToBrowser();
156
	}
157
158
	/**
159
	 * Return an RSSFeed of comments for a given set of comments or all
160
	 * comments on the website.
161
	 *
162
	 * To maintain backwards compatibility with 2.4 this supports mapping
163
	 * of PageComment/rss?pageid= as well as the new RSS format for comments
164
	 * of CommentingController/rss/{classname}/{id}
165
	 *
166
	 * @param SS_HTTPRequest
167
	 *
168
	 * @return RSSFeed
169
	 */
170
	public function getFeed(SS_HTTPRequest $request) {
171
		$link = $this->Link('rss');
172
		$class = $request->param('ID');
173
		$id = $request->param('OtherID');
174
175
		// Support old pageid param
176
		if(!$id && !$class && ($id = $request->getVar('pageid'))) {
177
			$class = 'SiteTree';
178
		}
179
180
		$comments = Comment::get()->filter(array(
181
			'Moderated' => 1,
182
			'IsSpam' => 0,
183
		));
184
185
		// Check if class filter
186
		if($class) {
187
			if(!is_subclass_of($class, 'DataObject') || !$class::has_extension('CommentsExtension')) {
188
				return $this->httpError(404);
189
			}
190
			$this->setBaseClass($class);
191
			$comments = $comments->filter('BaseClass', $class);
192
			$link = Controller::join_links($link, $class);
193
194
			// Check if id filter
195
			if($id) {
196
				$comments = $comments->filter('ParentID', $id);
197
				$link = Controller::join_links($link, $id);
198
				$this->setOwnerRecord(DataObject::get_by_id($class, $id));
199
			}
200
		}
201
202
		$title = _t('CommentingController.RSSTITLE', "Comments RSS Feed");
203
204
		$comments = new PaginatedList($comments, $request);
0 ignored issues
show
Documentation introduced by
$request is of type object<SS_HTTPRequest>, but the function expects a array.

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...
205
		$comments->setPageLength($this->getOption('comments_per_page'));
206
207
		return new RSSFeed(
208
			$comments,
209
			$link,
210
			$title,
211
			$link,
212
			'Title', 'EscapedComment', 'AuthorName'
213
		);
214
	}
215
216
	/**
217
	 * Deletes a given {@link Comment} via the URL.
218
	 */
219
	public function delete() {
220
		$comment = $this->getComment();
221
		if(!$comment) return $this->httpError(404);
222
		if(!$comment->canDelete()) {
223
			return Security::permissionFailure($this, 'You do not have permission to delete this comment');
224
		}
225
		if(!$comment->getSecurityToken()->checkRequest($this->request)) return $this->httpError(400);
226
227
		$comment->delete();
228
229
		return $this->request->isAjax()
230
			? true
231
			: $this->redirectBack();
232
	}
233
234
	/**
235
	 * Marks a given {@link Comment} as spam. Removes the comment from display
236
	 */
237 View Code Duplication
	public function spam() {
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...
238
		$comment = $this->getComment();
239
		if(!$comment) return $this->httpError(404);
240
		if(!$comment->canEdit()) {
241
			return Security::permissionFailure($this, 'You do not have permission to edit this comment');
242
		}
243
		if(!$comment->getSecurityToken()->checkRequest($this->request)) return $this->httpError(400);
244
245
		$comment->markSpam();
246
        if (empty($this->getRequest()->getHeader('Referer'))) {
247
            return $this->request->isAjax()
248
                ? $comment->renderWith('CommentsInterface_singlecomment')
249
                : $this->redirectBack();
250
        } else {
251
            $url = $this->getRequest()->getHeader('Referer') . '#comment-' . $comment->ID;
252
            return $this->request->isAjax()
253
                ? $comment->renderWith('CommentsInterface_singlecomment')
254
                : $this->redirect($url);
255
        }
256
	}
257
258
	/**
259
	 * Marks a given {@link Comment} as ham (not spam).
260
	 */
261 View Code Duplication
	public function ham() {
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...
262
		$comment = $this->getComment();
263
		if(!$comment) return $this->httpError(404);
264
		if(!$comment->canEdit()) {
265
			return Security::permissionFailure($this, 'You do not have permission to edit this comment');
266
		}
267
		if(!$comment->getSecurityToken()->checkRequest($this->request)) return $this->httpError(400);
268
269
		$comment->markApproved();
270
        if (empty($this->getRequest()->getHeader('Referer'))) {
271
            return $this->request->isAjax()
272
                ? $comment->renderWith('CommentsInterface_singlecomment')
273
                : $this->redirectBack();
274
        } else {
275
            $url = $this->getRequest()->getHeader('Referer') . '#comment-' . $comment->ID;
276
            return $this->request->isAjax()
277
                ? $comment->renderWith('CommentsInterface_singlecomment')
278
                : $this->redirect($url);
279
        }
280
	}
281
282
	/**
283
	 * Marks a given {@link Comment} as approved.
284
	 */
285 View Code Duplication
	public function approve() {
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...
286
		$comment = $this->getComment();
287
		if(!$comment) return $this->httpError(404);
288
		if(!$comment->canEdit()) {
289
			return Security::permissionFailure($this, 'You do not have permission to approve this comment');
290
		}
291
		if(!$comment->getSecurityToken()->checkRequest($this->request)) return $this->httpError(400);
292
293
		$comment->markApproved();
294
295
        if (empty($this->getRequest()->getHeader('Referer'))) {
296
            return $this->request->isAjax()
297
                ? $comment->renderWith('CommentsInterface_singlecomment')
298
                : $this->redirectBack();
299
        } else {
300
            $url = $this->getRequest()->getHeader('Referer') . '#comment-' . $comment->ID;
301
            return $this->request->isAjax()
302
                ? $comment->renderWith('CommentsInterface_singlecomment')
303
                : $this->redirect($url);
304
        }
305
306
	}
307
308
	/**
309
	 * Returns the comment referenced in the URL (by ID). Permission checking
310
	 * should be done in the callee.
311
	 *
312
	 * @return Comment|false
313
	 */
314
	public function getComment() {
315
		$id = isset($this->urlParams['ID']) ? $this->urlParams['ID'] : false;
316
317
		if($id) {
318
			$comment = DataObject::get_by_id('Comment', $id);
319
320
			if($comment) {
321
				$this->fallbackReturnURL = $comment->Link();
322
				return $comment;
323
			}
324
		}
325
326
		return false;
327
	}
328
329
	/**
330
	 * Create a reply form for a specified comment
331
	 *
332
	 * @param Comment $comment
333
	 */
334
	public function ReplyForm($comment) {
335
		// Enables multiple forms with different names to use the same handler
336
		$form = $this->CommentsForm();
337
		$form->setName('ReplyForm_'.$comment->ID);
338
		$form->addExtraClass('reply-form');
339
340
		// Load parent into reply form
341
		$form->loadDataFrom(array(
342
			'ParentCommentID' => $comment->ID
343
		));
344
345
		// Customise action
346
		$form->setFormAction($this->Link('reply', $comment->ID));
347
348
		$this->extend('updateReplyForm', $form);
349
		return $form;
350
	}
351
352
353
	/**
354
	 * Request handler for reply form.
355
	 * This method will disambiguate multiple reply forms in the same method
356
	 *
357
	 * @param SS_HTTPRequest $request
358
	 */
359
	public function reply(SS_HTTPRequest $request) {
360
		// Extract parent comment from reply and build this way
361
		if($parentID = $request->param('ParentCommentID')) {
362
			$comment = DataObject::get_by_id('Comment', $parentID, true);
363
			if($comment) {
364
				return $this->ReplyForm($comment);
0 ignored issues
show
Compatibility introduced by
$comment of type object<DataObject> is not a sub-type of object<Comment>. It seems like you assume a child class of the class DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
365
			}
366
		}
367
		return $this->httpError(404);
368
	}
369
370
	/**
371
	 * Post a comment form
372
	 *
373
	 * @return Form
374
	 */
375
	public function CommentsForm() {
376
		$usePreview = $this->getOption('use_preview');
377
378
		$nameRequired = _t('CommentInterface.YOURNAME_MESSAGE_REQUIRED', 'Please enter your name');
379
		$emailRequired = _t('CommentInterface.EMAILADDRESS_MESSAGE_REQUIRED', 'Please enter your email address');
380
		$emailInvalid = _t('CommentInterface.EMAILADDRESS_MESSAGE_EMAIL', 'Please enter a valid email address');
381
		$urlInvalid = _t('CommentInterface.COMMENT_MESSAGE_URL', 'Please enter a valid URL');
382
		$commentRequired = _t('CommentInterface.COMMENT_MESSAGE_REQUIRED', 'Please enter your comment');
383
384
		$fields = new FieldList(
385
			$dataFields = new CompositeField(
386
				// Name
387
				TextField::create("Name", _t('CommentInterface.YOURNAME', 'Your name'))
388
					->setCustomValidationMessage($nameRequired)
389
					->setAttribute('data-msg-required', $nameRequired),
390
391
				// Email
392
				EmailField::create(
393
					"Email",
394
					_t('CommentingController.EMAILADDRESS', "Your email address (will not be published)")
395
				)
396
					->setCustomValidationMessage($emailRequired)
397
					->setAttribute('data-msg-required', $emailRequired)
398
					->setAttribute('data-msg-email', $emailInvalid)
399
					->setAttribute('data-rule-email', true),
0 ignored issues
show
Documentation introduced by
true is of type boolean, 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...
400
401
				// Url
402
				TextField::create("URL", _t('CommentingController.WEBSITEURL', "Your website URL"))
403
					->setAttribute('data-msg-url', $urlInvalid)
404
					->setAttribute('data-rule-url', true),
0 ignored issues
show
Documentation introduced by
true is of type boolean, 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...
405
406
				// Comment
407
				TextareaField::create("Comment", _t('CommentingController.COMMENTS', "Comments"))
408
					->setCustomValidationMessage($commentRequired)
409
					->setAttribute('data-msg-required', $commentRequired)
410
			),
411
			HiddenField::create("ParentID"),
412
			HiddenField::create("ReturnURL"),
413
			HiddenField::create("ParentCommentID"),
414
			HiddenField::create("BaseClass")
415
		);
416
417
		// Preview formatted comment. Makes most sense when shortcodes or
418
		// limited HTML is allowed. Populated by JS/Ajax.
419
		if($usePreview) {
420
			$fields->insertAfter(
421
				ReadonlyField::create('PreviewComment', _t('CommentInterface.PREVIEWLABEL', 'Preview'))
422
					->setAttribute('style', 'display: none'), // enable through JS
423
				'Comment'
0 ignored issues
show
Documentation introduced by
'Comment' is of type string, but the function expects a object<FormField>.

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...
424
			);
425
		}
426
427
		$dataFields->addExtraClass('data-fields');
428
429
		// save actions
430
		$actions = new FieldList(
431
			new FormAction("doPostComment", _t('CommentInterface.POST', 'Post'))
432
		);
433
		if($usePreview) {
434
			$actions->push(
435
				FormAction::create('doPreviewComment', _t('CommentInterface.PREVIEW', 'Preview'))
436
					->addExtraClass('action-minor')
437
					->setAttribute('style', 'display: none') // enable through JS
438
			);
439
		}
440
441
		// required fields for server side
442
		$required = new RequiredFields($this->config()->required_fields);
443
444
		// create the comment form
445
		$form = new Form($this, 'CommentsForm', $fields, $actions, $required);
446
447
		// if the record exists load the extra required data
448
		if($record = $this->getOwnerRecord()) {
449
450
			// Load member data
451
			$member = Member::currentUser();
452
			if(($record->CommentsRequireLogin || $record->PostingRequiredPermission) && $member) {
453
				$fields = $form->Fields();
454
455
				$fields->removeByName('Name');
456
				$fields->removeByName('Email');
457
				$fields->insertBefore(new ReadonlyField("NameView", _t('CommentInterface.YOURNAME', 'Your name'), $member->getName()), 'URL');
0 ignored issues
show
Documentation introduced by
'URL' is of type string, but the function expects a object<FormField>.

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...
458
				$fields->push(new HiddenField("Name", "", $member->getName()));
459
				$fields->push(new HiddenField("Email", "", $member->Email));
460
			}
461
462
			// we do not want to read a new URL when the form has already been submitted
463
			// which in here, it hasn't been.
464
			$form->loadDataFrom(array(
465
				'ParentID'		=> $record->ID,
466
				'ReturnURL'		=> $this->request->getURL(),
467
				'BaseClass'		=> $this->getBaseClass()
468
			));
469
		}
470
471
		// Set it so the user gets redirected back down to the form upon form fail
472
		$form->setRedirectToFormOnValidationError(true);
473
474
		// load any data from the cookies
475
		if($data = Cookie::get('CommentsForm_UserData')) {
476
			$data = Convert::json2array($data);
477
478
			$form->loadDataFrom(array(
479
				"Name"		=> isset($data['Name']) ? $data['Name'] : '',
480
				"URL"		=> isset($data['URL']) ? $data['URL'] : '',
481
				"Email"		=> isset($data['Email']) ? $data['Email'] : ''
482
			));
483
			// allow previous value to fill if comment not stored in cookie (i.e. validation error)
484
			$prevComment = Cookie::get('CommentsForm_Comment');
485
			if($prevComment && $prevComment != ''){
0 ignored issues
show
Bug Best Practice introduced by
The expression $prevComment of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
486
				$form->loadDataFrom(array("Comment" => $prevComment));
487
			}
488
		}
489
490
		if(!empty($member)) {
491
			$form->loadDataFrom($member);
492
		}
493
494
		// hook to allow further extensions to alter the comments form
495
		$this->extend('alterCommentForm', $form);
496
497
		return $form;
498
	}
499
500
	/**
501
	 * Process which creates a {@link Comment} once a user submits a comment from this form.
502
	 *
503
	 * @param array $data
504
	 * @param Form $form
505
	 */
506
	public function doPostComment($data, $form) {
507
		// Load class and parent from data
508
		if(isset($data['BaseClass'])) {
509
			$this->setBaseClass($data['BaseClass']);
510
		}
511
		if(isset($data['ParentID']) && ($class = $this->getBaseClass())) {
512
			$this->setOwnerRecord($class::get()->byID($data['ParentID']));
513
		}
514
		if(!$this->getOwnerRecord()) return $this->httpError(404);
515
516
		// cache users data
517
		Cookie::set("CommentsForm_UserData", Convert::raw2json($data));
518
		Cookie::set("CommentsForm_Comment", $data['Comment']);
519
520
		// extend hook to allow extensions. Also see onAfterPostComment
521
		$this->extend('onBeforePostComment', $form);
522
523
		// If commenting can only be done by logged in users, make sure the user is logged in
524
		if(!$this->getOwnerRecord()->canPostComment()) {
525
			return Security::permissionFailure(
526
				$this,
527
				_t(
528
					'CommentingController.PERMISSIONFAILURE',
529
					"You're not able to post comments to this page. Please ensure you are logged in and have an "
530
					. "appropriate permission level."
531
				)
532
			);
533
		}
534
535
		if($member = Member::currentUser()) {
536
			$form->Fields()->push(new HiddenField("AuthorID", "Author ID", $member->ID));
537
		}
538
539
		// What kind of moderation is required?
540
		switch($this->getOwnerRecord()->ModerationRequired) {
541
			case 'Required':
542
				$requireModeration = true;
543
				break;
544
			case 'NonMembersOnly':
545
				$requireModeration = empty($member);
546
				break;
547
			case 'None':
548
			default:
549
				$requireModeration = false;
550
				break;
551
		}
552
553
		$comment = new Comment();
554
		$form->saveInto($comment);
555
556
		$comment->AllowHtml = $this->getOption('html_allowed');
557
		$comment->Moderated = !$requireModeration;
558
559
		// Save into DB, or call pre-save hooks to give accurate preview
560
		$usePreview = $this->getOption('use_preview');
561
		$isPreview = $usePreview && !empty($data['IsPreview']);
562
		if($isPreview) {
563
			$comment->extend('onBeforeWrite');
564
		} else {
565
			$comment->write();
566
567
			// extend hook to allow extensions. Also see onBeforePostComment
568
			$this->extend('onAfterPostComment', $comment);
569
		}
570
571
		// we want to show a notification if comments are moderated
572
		if ($requireModeration && !$comment->IsSpam) {
573
			Session::set('CommentsModerated', 1);
574
		}
575
576
		// clear the users comment since it passed validation
577
		Cookie::set('CommentsForm_Comment', false);
578
579
		// Find parent link
580
		if(!empty($data['ReturnURL'])) {
581
			$url = $data['ReturnURL'];
582
		} elseif($parent = $comment->getParent()) {
583
			$url = $parent->Link();
584
		} else {
585
			return $this->redirectBack();
586
		}
587
588
		// Given a redirect page exists, attempt to link to the correct anchor
589
		if($comment->IsSpam) {
590
			// Link to the form with the error message contained
591
			$hash = $form->FormName();
592
		} else if(!$comment->Moderated) {
593
			// Display the "awaiting moderation" text
594
			$holder = $this->getOption('comments_holder_id');
595
			$hash = "{$holder}_PostCommentForm_error";
596
		} else {
597
			// Link to the moderated, non-spam comment
598
			$hash = $comment->Permalink();
599
		}
600
601
		return $this->redirect(Controller::join_links($url, "#{$hash}"));
602
	}
603
604
	public function doPreviewComment($data, $form) {
605
		$data['IsPreview'] = 1;
606
607
		return $this->doPostComment($data, $form);
608
	}
609
610
	public function redirectBack() {
611
		// Don't cache the redirect back ever
612
		HTTP::set_cache_age(0);
613
614
		$url = null;
615
616
		// In edge-cases, this will be called outside of a handleRequest() context; in that case,
617
		// redirect to the homepage - don't break into the global state at this stage because we'll
618
		// be calling from a test context or something else where the global state is inappropraite
619
		if($this->request) {
620
			if($this->request->requestVar('BackURL')) {
621
				$url = $this->request->requestVar('BackURL');
622
			} else if($this->request->isAjax() && $this->request->getHeader('X-Backurl')) {
623
				$url = $this->request->getHeader('X-Backurl');
624
			} else if($this->request->getHeader('Referer')) {
625
				$url = $this->request->getHeader('Referer');
626
			}
627
		}
628
629
		if(!$url) $url = $this->fallbackReturnURL;
630
		if(!$url) $url = Director::baseURL();
631
632
		// absolute redirection URLs not located on this site may cause phishing
633
		if(Director::is_site_url($url)) {
634
			return $this->redirect($url);
635
		} else {
636
			return false;
637
		}
638
639
	}
640
}
641