Test Failed
Push — master ( 8c47c2...3acf9f )
by Steve
12:37
created

mod/discussions/start.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Discussion plugin
4
 */
5
6
elgg_register_event_handler('init', 'system', 'discussion_init');
7
8
/**
9
 * Initialize the discussion component
10
 */
11
function discussion_init() {
12
13
	elgg_register_page_handler('discussion', 'discussion_page_handler');
14
15
	elgg_register_plugin_hook_handler('entity:url', 'object', 'discussion_set_topic_url');
16
17
	// commenting not allowed on discussion topics (use a different annotation)
18
	elgg_register_plugin_hook_handler('permissions_check:comment', 'object', 'discussion_comment_override');
19
	elgg_register_plugin_hook_handler('permissions_check', 'object', 'discussion_can_edit_reply');
20
21
	// discussion reply menu
22
	elgg_register_plugin_hook_handler('register', 'menu:entity', 'discussion_reply_menu_setup');
23
24
	// allow non-owners to add replies to discussion
25
	elgg_register_plugin_hook_handler('container_logic_check', 'object', 'discussion_reply_container_logic_override');
26
	elgg_register_plugin_hook_handler('container_permissions_check', 'object', 'discussion_reply_container_permissions_override');
27
28
	elgg_register_event_handler('update:after', 'object', 'discussion_update_reply_access_ids');
29
30
	// add link to owner block
31
	elgg_register_plugin_hook_handler('register', 'menu:owner_block', 'discussion_owner_block_menu');
32
33
	// Register for search.
34
	elgg_register_plugin_hook_handler('search', 'object:discussion', 'discussion_search_discussion');
35
36
	// because replies are not comments, need of our menu item
37
	elgg_register_plugin_hook_handler('register', 'menu:river', 'discussion_add_to_river_menu');
38
39
	// add the forum tool option
40
	add_group_tool_option('forum', elgg_echo('groups:enableforum'), true);
41
	elgg_extend_view('groups/tool_latest', 'discussion/group_module');
42
43
	// TODO remove in 3.0
44
	elgg_register_js('elgg.discussion', elgg_get_simplecache_url('discussion/discussion.js'));
45
46
	elgg_register_ajax_view('ajax/discussion/reply/edit');
47
48
	// notifications
49
	elgg_register_plugin_hook_handler('get', 'subscriptions', 'discussion_get_subscriptions');
50
	elgg_register_notification_event('object', 'discussion');
51
	elgg_register_plugin_hook_handler('prepare', 'notification:create:object:discussion', 'discussion_prepare_notification');
52
	elgg_register_notification_event('object', 'discussion_reply');
53
	elgg_register_plugin_hook_handler('prepare', 'notification:create:object:discussion_reply', 'discussion_prepare_reply_notification');
54
55
	// allow ecml in discussion
56
	elgg_register_plugin_hook_handler('get_views', 'ecml', 'discussion_ecml_views_hook');
57
58
	// allow to be liked
59
	elgg_register_plugin_hook_handler('likes:is_likable', 'object:discussion', 'Elgg\Values::getTrue');
60
	elgg_register_plugin_hook_handler('likes:is_likable', 'object:discussion_reply', 'Elgg\Values::getTrue');
61
62
	// Add latest discussions tab to /groups/all page
63
	elgg_register_plugin_hook_handler('register', 'menu:filter:groups/all', 'discussion_setup_groups_filter_tabs');
64
}
65
66
/**
67
 * Discussion page handler
68
 *
69
 * URLs take the form of
70
 *  All topics in site:    discussion/all
71
 *  List topics in forum:  discussion/owner/<guid>
72
 *  View discussion topic: discussion/view/<guid>
73
 *  Add discussion topic:  discussion/add/<guid>
74
 *  Edit discussion topic: discussion/edit/<guid>
75
 *
76
 * @param array $page Array of url segments for routing
77
 * @return bool
78
 */
79
function discussion_page_handler($page) {
80
81
	if (!isset($page[0])) {
82
		$page[0] = 'all';
83
	}
84
85
	elgg_push_breadcrumb(elgg_echo('discussion'), 'discussion/all');
86
87
	switch ($page[0]) {
88
		case 'all':
89
			echo elgg_view_resource('discussion/all');
90
			break;
91
		case 'owner':
92
			echo elgg_view_resource('discussion/owner', [
93
				'guid' => elgg_extract(1, $page),
94
			]);
95
			break;
96
		case 'group':
97
			echo elgg_view_resource('discussion/group', [
98
				'guid' => elgg_extract(1, $page),
99
			]);
100
			break;
101
		case 'add':
102
			echo elgg_view_resource('discussion/add', [
103
				'guid' => elgg_extract(1, $page),
104
			]);
105
			break;
106
		case 'reply':
107 View Code Duplication
			switch (elgg_extract(1, $page)) {
108
				case 'edit':
109
					echo elgg_view_resource('discussion/reply/edit', [
110
						'guid' => elgg_extract(2, $page),
111
					]);
112
					break;
113
				case 'view':
114
					discussion_redirect_to_reply(elgg_extract(2, $page), elgg_extract(3, $page));
115
					break;
116
				default:
117
					return false;
118
			}
119
			break;
120
		case 'edit':
121
			echo elgg_view_resource('discussion/edit', [
122
				'guid' => elgg_extract(1, $page),
123
			]);
124
			break;
125
		case 'view':
126
			echo elgg_view_resource('discussion/view', [
127
				'guid' => elgg_extract(1, $page),
128
			]);
129
			break;
130
		default:
131
			return false;
132
	}
133
	return true;
134
}
135
136
/**
137
 * Redirect to the reply in context of the containing topic
138
 *
139
 * @param int $reply_guid    GUID of the reply
140
 * @param int $fallback_guid GUID of the topic
141
 *
142
 * @return void
143
 * @access private
144
 */
145 View Code Duplication
function discussion_redirect_to_reply($reply_guid, $fallback_guid) {
146
	$fail = function () {
147
		register_error(elgg_echo('discussion:reply:error:notfound'));
148
		forward(REFERER);
149
	};
150
151
	$reply = get_entity($reply_guid);
152
	if (!$reply) {
153
		// try fallback
154
		$fallback = get_entity($fallback_guid);
155
		if (!elgg_instanceof($fallback, 'object', 'discussion')) {
156
			$fail();
157
		}
158
159
		register_error(elgg_echo('discussion:reply:error:notfound_fallback'));
160
		forward($fallback->getURL());
161
	}
162
163
	if (!elgg_instanceof($reply, 'object', 'discussion_reply')) {
164
		$fail();
165
	}
166
167
	// start with topic URL
168
	$topic = $reply->getContainerEntity();
169
170
	// this won't work with threaded comments, but core doesn't support that yet
171
	$count = elgg_get_entities([
172
		'type' => 'object',
173
		'subtype' => $reply->getSubtype(),
174
		'container_guid' => $topic->guid,
175
		'count' => true,
176
		'wheres' => ["e.guid < " . (int) $reply->guid],
177
	]);
178
	$limit = (int) get_input('limit', 0);
179
	if (!$limit) {
180
		$limit = _elgg_config()->default_limit;
181
	}
182
	$offset = floor($count / $limit) * $limit;
183
	if (!$offset) {
184
		$offset = null;
185
	}
186
187
	$url = elgg_http_add_url_query_elements($topic->getURL(), [
188
		'offset' => $offset,
189
	]);
190
	
191
	// make sure there's only one fragment (#)
192
	$parts = parse_url($url);
193
	$parts['fragment'] = "elgg-object-{$reply->guid}";
194
	$url = elgg_http_build_url($parts, false);
195
196
	forward($url);
197
}
198
199
/**
200
 * Override the url for discussion topics and replies
201
 *
202
 * Discussion replies do not have their own page so their url is
203
 * the same as the topic url.
204
 *
205
 * @param string $hook
206
 * @param string $type
207
 * @param string $url
208
 * @param array  $params
209
 * @return string
210
 */
211
function discussion_set_topic_url($hook, $type, $url, $params) {
212
	$entity = $params['entity'];
213
214
	if (!$entity instanceof ElggObject) {
215
		return;
216
	}
217
218
	if ($entity->getSubtype() === 'discussion') {
219
		$title = elgg_get_friendly_title($entity->title);
220
		return "discussion/view/{$entity->guid}/{$title}";
221
	}
222
223
	if ($entity->getSubtype() === 'discussion_reply') {
224
		$topic = $entity->getContainerEntity();
225
		return "discussion/reply/view/{$entity->guid}/{$topic->guid}";
226
	}
227
}
228
229
/**
230
 * We don't want people commenting on topics in the river
231
 *
232
 * @param string $hook
233
 * @param string $type
234
 * @param string $return
235
 * @param array  $params
236
 * @return bool
237
 */
238
function discussion_comment_override($hook, $type, $return, $params) {
239
	if (elgg_instanceof($params['entity'], 'object', 'discussion')) {
240
		return false;
241
	}
242
}
243
244
/**
245
 * Add owner block link for groups
246
 *
247
 * @param string         $hook   'register'
248
 * @param string         $type   'menu:owner_block'
249
 * @param ElggMenuItem[] $return
250
 * @param array          $params
251
 * @return ElggMenuItem[] $return
252
 */
253 View Code Duplication
function discussion_owner_block_menu($hook, $type, $return, $params) {
254
	if (elgg_instanceof($params['entity'], 'group')) {
255
		if ($params['entity']->forum_enable != "no") {
256
			$url = "discussion/group/{$params['entity']->guid}";
257
			$item = new ElggMenuItem('discussion', elgg_echo('discussion:group'), $url);
258
			$return[] = $item;
259
		}
260
	}
261
262
	return $return;
263
}
264
265
/**
266
 * Set up menu items for river items
267
 *
268
 * Add reply button for discussion topic. Remove the possibility
269
 * to comment on a discussion reply.
270
 *
271
 * @param string         $hook   'register'
272
 * @param string         $type   'menu:river'
273
 * @param ElggMenuItem[] $return
274
 * @param array          $params
275
 * @return ElggMenuItem[] $return
276
 */
277
function discussion_add_to_river_menu($hook, $type, $return, $params) {
278
	if (!elgg_is_logged_in() || elgg_in_context('widgets')) {
279
		return $return;
280
	}
281
282
	$item = $params['item'];
283
	$object = $item->getObjectEntity();
284
285
	if (elgg_instanceof($object, 'object', 'discussion')) {
286
		/* @var $object ElggObject */
287 View Code Duplication
		if ($object->canWriteToContainer(0, 'object', 'discussion_reply')) {
288
				$options = [
289
				'name' => 'reply',
290
				'href' => "#discussion-reply-{$object->guid}",
291
				'text' => elgg_view_icon('speech-bubble'),
292
				'title' => elgg_echo('reply:this'),
293
				'rel' => 'toggle',
294
				'priority' => 50,
295
				];
296
				$return[] = ElggMenuItem::factory($options);
297
		}
298
	} else if (elgg_instanceof($object, 'object', 'discussion_reply')) {
299
		/* @var $object ElggDiscussionReply */
300
		if (!$object->canComment()) {
301
			// Discussion replies cannot be commented
302
			foreach ($return as $key => $item) {
303
				if ($item->getName() === 'comment') {
304
					unset($return[$key]);
305
				}
306
			}
307
		}
308
	}
309
310
	return $return;
311
}
312
313
/**
314
 * Prepare a notification message about a new discussion topic
315
 *
316
 * @param string                          $hook         Hook name
317
 * @param string                          $type         Hook type
318
 * @param Elgg\Notifications\Notification $notification The notification to prepare
319
 * @param array                           $params       Hook parameters
320
 * @return Elgg\Notifications\Notification
321
 */
322 View Code Duplication
function discussion_prepare_notification($hook, $type, $notification, $params) {
323
	$entity = $params['event']->getObject();
324
	$owner = $params['event']->getActor();
325
	$language = $params['language'];
326
327
	$descr = $entity->description;
328
	$title = $entity->title;
329
330
	$notification->subject = elgg_echo('discussion:topic:notify:subject', [$title], $language);
331
	$notification->body = elgg_echo('discussion:topic:notify:body', [
332
		$owner->name,
333
		$title,
334
		$descr,
335
		$entity->getURL()
336
	], $language);
337
	$notification->summary = elgg_echo('discussion:topic:notify:summary', [$entity->title], $language);
338
	$notification->url = $entity->getURL();
339
	return $notification;
340
}
341
342
/**
343
 * Prepare a notification message about a new discussion reply
344
 *
345
 * @param string                          $hook         Hook name
346
 * @param string                          $type         Hook type
347
 * @param Elgg\Notifications\Notification $notification The notification to prepare
348
 * @param array                           $params       Hook parameters
349
 * @return Elgg\Notifications\Notification
350
 */
351
function discussion_prepare_reply_notification($hook, $type, $notification, $params) {
352
	$reply = $params['event']->getObject();
353
	$topic = $reply->getContainerEntity();
354
	$poster = $reply->getOwnerEntity();
355
	$language = elgg_extract('language', $params);
356
357
	$notification->subject = elgg_echo('discussion:reply:notify:subject', [$topic->title], $language);
358
	$notification->body = elgg_echo('discussion:reply:notify:body', [
359
		$poster->name,
360
		$topic->title,
361
		$reply->description,
362
		$reply->getURL(),
363
	], $language);
364
	$notification->summary = elgg_echo('discussion:reply:notify:summary', [$topic->title], $language);
365
	$notification->url = $reply->getURL();
366
	
367
	return $notification;
368
}
369
370
/**
371
 * Get subscriptions for notifications
372
 *
373
 * @param string $hook          'get'
374
 * @param string $type          'subscriptions'
375
 * @param array  $subscriptions Array containing subscriptions in the form
376
 *                              <user guid> => array('email', 'site', etc.)
377
 * @param array  $params        Hook parameters
378
 * @return array
379
 */
380
function discussion_get_subscriptions($hook, $type, $subscriptions, $params) {
381
	$reply = $params['event']->getObject();
382
383
	if (!elgg_instanceof($reply, 'object', 'discussion_reply')) {
384
		return $subscriptions;
385
	}
386
387
	$container_guid = $reply->getContainerEntity()->container_guid;
388
	$container_subscriptions = elgg_get_subscriptions_for_container($container_guid);
389
390
	return ($subscriptions + $container_subscriptions);
391
}
392
393
/**
394
 * Parse ECML on discussion views
395
 */
396
function discussion_ecml_views_hook($hook, $type, $return_value, $params) {
397
	$return_value['forum/viewposts'] = elgg_echo('discussion:ecml:discussion');
398
399
	return $return_value;
400
}
401
402
403
/**
404
 * Allow group owner and discussion owner to edit discussion replies.
405
 *
406
 * @param string  $hook   'permissions_check'
407
 * @param string  $type   'object'
408
 * @param boolean $return
409
 * @param array   $params Array('entity' => ElggEntity, 'user' => ElggUser)
410
 * @return boolean True if user is discussion or group owner
411
 */
412
function discussion_can_edit_reply($hook, $type, $return, $params) {
413
	/** @var $reply ElggEntity */
414
	$reply = $params['entity'];
415
	$user = $params['user'];
416
417
	if (!elgg_instanceof($reply, 'object', 'discussion_reply')) {
418
		return $return;
419
	}
420
421
	if ($reply->owner_guid == $user->guid) {
422
	    return true;
423
	}
424
425
	$discussion = $reply->getContainerEntity();
426
	if ($discussion->owner_guid == $user->guid) {
427
		return true;
428
	}
429
430
	$container = $discussion->getContainerEntity();
431
	if (elgg_instanceof($container, 'group') && $container->owner_guid == $user->guid) {
432
		return true;
433
	}
434
435
	return false;
436
}
437
438
/**
439
 * Make sure that discussion replies are only contained by discussions
440
 * Make sure discussion replies can not be written to a discussion after it has been closed
441
 *
442
 * @param string $hook   'container_logic_check'
443
 * @param string $type   'object'
444
 * @param array  $return Allowed or not
445
 * @param array  $params Hook params
446
 * @return bool
447
 */
448
function discussion_reply_container_logic_override($hook, $type, $return, $params) {
449
450
	$container = elgg_extract('container', $params);
451
	$subtype = elgg_extract('subtype', $params);
452
453
	if ($type !== 'object' || $subtype !== 'discussion_reply') {
454
		return;
455
	}
456
457
	if (!elgg_instanceof($container, 'object', 'discussion')) {
458
		// only discussions can contain discussion replies
459
		return false;
460
	}
461
462
	if ($container->status == 'closed') {
463
		// do not allow new replies in closed discussions
464
		return false;
465
	}
466
}
467
468
/**
469
 * Make sure that only group members can post to a group discussion
470
 *
471
 * @param string $hook   'container_permissions_check'
472
 * @param string $type   'object'
473
 * @param array  $return
474
 * @param array  $params Array with container, user and subtype
475
 * @return boolean $return
476
 */
477
function discussion_reply_container_permissions_override($hook, $type, $return, $params) {
478
	if ($params['subtype'] !== 'discussion_reply') {
479
		return $return;
480
	}
481
482
	/** @var $discussion ElggEntity */
483
	$discussion = $params['container'];
484
	$user = $params['user'];
485
	
486
	$container = $discussion->getContainerEntity();
487
488
	if (elgg_instanceof($container, 'group')) {
489
		// Only group members are allowed to reply
490
		// to a discussion within a group
491
		if (!$container->canWriteToContainer($user->guid)) {
492
			return false;
493
		}
494
	}
495
496
	return true;
497
}
498
499
/**
500
 * Update access_id of discussion replies when topic access_id is updated.
501
 *
502
 * @param string     $event  'update'
503
 * @param string     $type   'object'
504
 * @param ElggObject $object ElggObject
505
 */
506
function discussion_update_reply_access_ids($event, $type, $object) {
507
	if (!elgg_instanceof($object, 'object', 'discussion')) {
508
		return;
509
	}
510
511
	$ia = elgg_set_ignore_access(true);
512
	$options = [
513
		'type' => 'object',
514
		'subtype' => 'discussion_reply',
515
		'container_guid' => $object->getGUID(),
516
		'limit' => 0,
517
	];
518
	$batch = new ElggBatch('elgg_get_entities', $options);
519
	foreach ($batch as $reply) {
520
		if ($reply->access_id == $object->access_id) {
521
			// Assume access_id of the replies is up-to-date
522
			break;
523
		}
524
525
		// Update reply access_id
526
		$reply->access_id = $object->access_id;
527
		$reply->save();
528
	}
529
530
	elgg_set_ignore_access($ia);
531
}
532
533
/**
534
 * Set up discussion reply entity menu
535
 *
536
 * @param string          $hook   'register'
537
 * @param string          $type   'menu:entity'
538
 * @param ElggMenuItem[]  $return
539
 * @param array           $params
540
 * @return ElggMenuItem[] $return
541
 */
542
function discussion_reply_menu_setup($hook, $type, $return, $params) {
543
	/** @var $reply ElggEntity */
544
	$reply = elgg_extract('entity', $params);
545
546
	if (empty($reply) || !elgg_instanceof($reply, 'object', 'discussion_reply')) {
547
		return $return;
548
	}
549
550
	if (!elgg_is_logged_in()) {
551
		return $return;
552
	}
553
554
	if (elgg_in_context('widgets')) {
555
		return $return;
556
	}
557
558
	$remove = [];
559
560
	$user = elgg_get_logged_in_user_entity();
561
562
	// Allow discussion topic owner, group owner and admins to edit and delete
563
	if ($reply->canEdit() && !elgg_in_context('activity')) {
564
		$return[] = ElggMenuItem::factory([
565
			'name' => 'edit',
566
			'text' => elgg_echo('edit'),
567
			'href' => "discussion/reply/edit/{$reply->guid}",
568
			'priority' => 150,
569
		]);
570
571
		$return[] = ElggMenuItem::factory([
572
			'name' => 'delete',
573
			'text' => elgg_view_icon('delete'),
574
			'href' => "action/discussion/reply/delete?guid={$reply->guid}",
575
			'priority' => 150,
576
			'is_action' => true,
577
			'confirm' => elgg_echo('deleteconfirm'),
578
		]);
579
	} else {
580
		// Edit and delete links can be removed from all other users
581
		$remove[] = 'edit';
582
		$remove[] = 'delete';
583
	}
584
585
	// Remove unneeded menu items
586
	foreach ($return as $key => $item) {
587
		if (in_array($item->getName(), $remove)) {
588
			unset($return[$key]);
589
		}
590
	}
591
592
	return $return;
593
}
594
595
/**
596
 * Search in both discussion topics and replies
597
 *
598
 * @param string $hook   the name of the hook
599
 * @param string $type   the type of the hook
600
 * @param mixed  $value  the current return value
601
 * @param array  $params supplied params
602
 */
603 View Code Duplication
function discussion_search_discussion($hook, $type, $value, $params) {
604
605
	if (empty($params) || !is_array($params)) {
606
		return $value;
607
	}
608
609
	$subtype = elgg_extract("subtype", $params);
610
	if (empty($subtype) || ($subtype !== "discussion")) {
611
		return $value;
612
	}
613
614
	unset($params["subtype"]);
615
	$params["subtypes"] = ["discussion", "discussion_reply"];
616
617
	// trigger the 'normal' object search as it can handle the added options
618
	return elgg_trigger_plugin_hook('search', 'object', $params, []);
619
}
620
621
/**
622
 * Prepare discussion topic form variables
623
 *
624
 * @param ElggObject $topic Topic object if editing
625
 * @return array
626
 */
627
function discussion_prepare_form_vars($topic = null) {
628
	// input names => defaults
629
	$values = [
630
		'title' => '',
631
		'description' => '',
632
		'status' => '',
633
		'access_id' => ACCESS_DEFAULT,
634
		'tags' => '',
635
		'container_guid' => elgg_get_page_owner_guid(),
636
		'guid' => null,
637
		'topic' => $topic,
638
		'entity' => $topic,
639
	];
640
641
	if ($topic) {
642
		foreach (array_keys($values) as $field) {
643
			if (isset($topic->$field)) {
644
				$values[$field] = $topic->$field;
645
			}
646
		}
647
	}
648
649
	if (elgg_is_sticky_form('topic')) {
650
		$sticky_values = elgg_get_sticky_values('topic');
651
		foreach ($sticky_values as $key => $value) {
652
			$values[$key] = $value;
653
		}
654
	}
655
656
	elgg_clear_sticky_form('topic');
657
658
	return $values;
659
}
660
661
/**
662
 * Add latest discussions tab to /groups/all page
663
 *
664
 * @param string         $hook   "register"
665
 * @param string         $type   "menu:filter:groups/all"
666
 * @param ElggMenuItem[] $return Menu
667
 * @param array          $params Hook params
668
 * @return ElggMenuItem[]
669
 */
670 View Code Duplication
function discussion_setup_groups_filter_tabs($hook, $type, $return, $params) {
0 ignored issues
show
This function 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...
671
672
	$filter_value = elgg_extract('filter_value', $params);
673
674
	$return[] = ElggMenuItem::factory([
675
		'name' => 'discussion',
676
		'text' => elgg_echo('discussion:latest'),
677
		'href' => 'groups/all?filter=discussion',
678
		'priority' => 500,
679
		'selected' => $filter_value == 'discussion',
680
	]);
681
682
	return $return;
683
}
684