Issues (1065)

Sources/Likes.php (1 issue)

1
<?php
2
3
/**
4
 * This file contains liking posts and displaying the list of who liked a post.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2025 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1.5
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Class Likes
21
 */
22
class Likes
23
{
24
	/**
25
	 * @var boolean Know if a request comes from an ajax call or not, depends on $_GET['js'] been set.
26
	 */
27
	protected $_js = false;
28
29
	/**
30
	 * @var string The sub action sent in $_GET['sa'].
31
	 */
32
	protected $_sa = null;
33
34
	/**
35
	 * @var string If filled, its value will contain a string matching a key on a language var $txt[$this->_error]
36
	 */
37
	protected $_error = false;
38
39
	/**
40
	 * @var string The unique type to like, needs to be unique and it needs to be no longer than 6 characters, only numbers and letters are allowed.
41
	 */
42
	protected $_type = '';
43
44
	/**
45
	 * @var string A generic string used if you need to pass any extra info. It gets set via $_GET['extra'].
46
	 */
47
	protected $_extra = false;
48
49
	/**
50
	 * @var integer a valid ID to identify your like content.
51
	 */
52
	protected $_content = 0;
53
54
	/**
55
	 * @var integer The number of times your content has been liked.
56
	 */
57
	protected $_numLikes = 0;
58
59
	/**
60
	 * @var boolean If the current user has already liked this content.
61
	 */
62
	protected $_alreadyLiked = false;
63
64
	/**
65
	 * @var array $_validLikes mostly used for external integration, needs to be filled as an array with the following keys:
66
	 * => 'can_like' boolean|string whether or not the current user can actually like your content.
67
	 * for can_like: Return a boolean true if the user can, otherwise return a string, the string will be used as key in a regular $txt language error var. The code assumes you already loaded your language file. If no value is returned or the $txt var isn't set, the code will use a generic error message.
68
	 * => 'redirect' string To add support for non JS users, It is highly encouraged to set a valid URL to redirect the user to, if you don't provide any, the code will redirect the user to the main page. The code only performs a light check to see if the redirect is valid so be extra careful while building it.
69
	 * => 'type' string 6 letters or numbers. The unique identifier for your content, the code doesn't check for duplicate entries, if there are 2 or more exact hook calls, the code will take the first registered one so make sure you provide a unique identifier. Must match with what you sent in $_GET['ltype'].
70
	 * => 'flush_cache' boolean this is optional, it tells the code to reset your like content's cache entry after a new entry has been inserted.
71
	 * => 'callback' callable optional, useful if you don't want to issue a separate hook for updating your data, it is called immediately after the data was inserted or deleted and before the actual hook. Uses call_helper(); so the same format for your function/method can be applied here.
72
	 * => 'json' boolean optional defaults to false, if true the Like class will return a json object as response instead of HTML.
73
	 */
74
	protected $_validLikes = array(
75
		'can_like' => false,
76
		'redirect' => '',
77
		'type' => '',
78
		'flush_cache' => '',
79
		'callback' => false,
80
		'json' => false,
81
	);
82
83
	/**
84
	 * @var array The current user info ($user_info).
85
	 */
86
	protected $_user;
87
88
	/**
89
	 * @var integer The topic ID, used for liking messages.
90
	 */
91
	protected $_idTopic = 0;
92
93
	/**
94
	 * @var boolean to know if response(); will be executed as normal. If this is set to false it indicates the method already solved its own way to send back a response.
95
	 */
96
	protected $_setResponse = true;
97
98
	/**
99
	 * Likes::__construct()
100
	 *
101
	 * Sets the basic data needed for the rest of the process.
102
	 */
103
	public function __construct()
104
	{
105
		global $db_show_debug;
106
107
		$this->_type = isset($_GET['ltype']) ? $_GET['ltype'] : '';
108
		$this->_content = isset($_GET['like']) ? (int) $_GET['like'] : 0;
109
		$this->_js = isset($_GET['js']) ? true : false;
110
		$this->_sa = isset($_GET['sa']) ? $_GET['sa'] : 'like';
111
		$this->_extra = isset($_GET['extra']) ? $_GET['extra'] : false;
0 ignored issues
show
Documentation Bug introduced by
It seems like IssetNode ? $_GET['extra'] : false can also be of type false. However, the property $_extra is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
112
113
		// We do not want to output debug information here.
114
		if ($this->_js)
115
			$db_show_debug = false;
116
	}
117
118
	/**
119
	 * Likes::call()
120
	 *
121
	 * The main handler. Verifies permissions (whether the user can see the content in question), dispatch different method for different sub-actions.
122
	 * Accessed from index.php?action=likes
123
	 */
124
	public function call()
125
	{
126
		global $context;
127
128
		$this->_user = $context['user'];
129
130
		// Make sure the user can see and like your content.
131
		$this->check();
132
133
		$subActions = array(
134
			'like',
135
			'view',
136
			'delete',
137
			'insert',
138
			'_count',
139
		);
140
141
		// So at this point, whatever type of like the user supplied and the item of content in question,
142
		// we know it exists, now we need to figure out what we're doing with that.
143
		if (in_array($this->_sa, $subActions) && !is_string($this->_error))
144
		{
145
			// To avoid ambiguity, turn the property to a normal var.
146
			$call = $this->_sa;
147
148
			// Guest can only view likes.
149
			if ($call != 'view')
150
				is_not_guest();
151
152
			checkSession('get');
153
154
			// Call the appropriate method.
155
			$this->$call();
156
		}
157
158
		// else An error message.
159
		$this->response();
160
	}
161
162
	/**
163
	 * Likes::get()
164
	 *
165
	 * A simple getter for all protected properties.
166
	 * Accessed from index.php?action=likes
167
	 *
168
	 * @param string $property The name of the property to get.
169
	 * @return mixed Either return the property or false if there isn't a property with that name.
170
	 */
171
	public function get($property = '')
172
	{
173
		// All properties inside Likes are protected, thus, an underscore is used.
174
		$property = '_' . $property;
175
		return property_exists($this, $property) ? $this->$property : false;
176
	}
177
178
	/**
179
	 * Likes::check()
180
	 *
181
	 * Performs basic checks on the data provided, checks for a valid msg like.
182
	 * Calls integrate_valid_likes hook for retrieving all the data needed and apply checks based on the data provided.
183
	 */
184
	protected function check()
185
	{
186
		global $smcFunc, $modSettings;
187
188
		// This feature is currently disable.
189
		if (empty($modSettings['enable_likes']))
190
			return $this->_error = 'like_disable';
191
192
		// Zerothly, they did indicate some kind of content to like, right?
193
		preg_match('~^([a-z0-9\-\_]{1,6})~i', $this->_type, $matches);
194
		$this->_type = isset($matches[1]) ? $matches[1] : '';
195
196
		if ($this->_type == '' || $this->_content <= 0)
197
			return $this->_error = 'cannot_';
198
199
		// First we need to verify if the user can see the type of content or not. This is set up to be extensible,
200
		// so we'll check for the one type we do know about, and if it's not that, we'll defer to any hooks.
201
		if ($this->_type == 'msg')
202
		{
203
			// So we're doing something off a like. We need to verify that it exists, and that the current user can see it.
204
			// Fortunately for messages, this is quite easy to do - and we'll get the topic id while we're at it, because
205
			// we need this later for other things.
206
			$request = $smcFunc['db_query']('', '
207
				SELECT m.id_topic, m.id_member
208
				FROM {db_prefix}messages AS m
209
				WHERE {query_see_message_board}
210
					AND m.id_msg = {int:msg}',
211
				array(
212
					'msg' => $this->_content,
213
				)
214
			);
215
			if ($smcFunc['db_num_rows']($request) == 1)
216
				list ($this->_idTopic, $topicOwner) = $smcFunc['db_fetch_row']($request);
217
218
			$smcFunc['db_free_result']($request);
219
			if (empty($this->_idTopic))
220
				return $this->_error = 'cannot_';
221
222
			// So we know what topic it's in and more importantly we know the user can see it.
223
			// If we're not viewing, we need some info set up.
224
			$this->_validLikes['type'] = 'msg';
225
			$this->_validLikes['flush_cache'] = 'likes_topic_' . $this->_idTopic . '_' . $this->_user['id'];
226
			$this->_validLikes['redirect'] = 'topic=' . $this->_idTopic . '.msg' . $this->_content . '#msg' . $this->_content;
227
228
			$this->_validLikes['can_like'] = ($this->_user['id'] == $topicOwner ? 'cannot_like_content' : (allowedTo('likes_like') ? true : 'cannot_like_content'));
229
		}
230
231
		else
232
		{
233
			// Modders: This will give you whatever the user offers up in terms of liking, e.g. $this->_type=msg, $this->_content=1
234
			// When you hook this, check $this->_type first. If it is not something your mod worries about, return false.
235
			// Otherwise, fill an array according to the docs for $this->_validLikes. Determine (however you need to) that the user can see and can_like the relevant liked content (and it exists) Remember that users can't like their own content.
236
			// If the user can like it, you MUST return your type in the 'type' key back.
237
			// See also issueLike() for further notes.
238
			$can_like = call_integration_hook('integrate_valid_likes', array($this->_type, $this->_content, $this->_sa, $this->_js, $this->_extra));
239
240
			$found = false;
241
			if (!empty($can_like))
242
			{
243
				$can_like = (array) $can_like;
244
				foreach ($can_like as $result)
245
				{
246
					if ($result !== false)
247
					{
248
						// Match the type with what we already have.
249
						if (!isset($result['type']) || $result['type'] != $this->_type)
250
							return $this->_error = 'not_valid_like_type';
251
252
						// Fill out the rest.
253
						$this->_type = $result['type'];
254
						$this->_validLikes = array_merge($this->_validLikes, $result);
255
						$found = true;
256
						break;
257
					}
258
				}
259
			}
260
261
			if (!$found)
262
				return $this->_error = 'cannot_';
263
		}
264
265
		// Does the user can like this? Viewing a list of likes doesn't require this permission.
266
		if ($this->_sa != 'view' && isset($this->_validLikes['can_like']) && is_string($this->_validLikes['can_like']))
267
			return $this->_error = $this->_validLikes['can_like'];
268
	}
269
270
	/**
271
	 * Likes::delete()
272
	 *
273
	 * Deletes an entry from user_likes table, needs 3 properties: $_content, $_type and $_user['id'].
274
	 */
275
	protected function delete()
276
	{
277
		global $smcFunc;
278
279
		$smcFunc['db_query']('', '
280
			DELETE FROM {db_prefix}user_likes
281
			WHERE content_id = {int:like_content}
282
				AND content_type = {string:like_type}
283
				AND id_member = {int:id_member}',
284
			array(
285
				'like_content' => $this->_content,
286
				'like_type' => $this->_type,
287
				'id_member' => $this->_user['id'],
288
			)
289
		);
290
291
		// Are we calling this directly? if so, set a proper data for the response. Do note that __METHOD__ returns both the class name and the function name.
292
		if ($this->_sa == __FUNCTION__)
293
			$this->_data = __FUNCTION__;
294
295
		// Check to see if there is an unread alert to delete as well...
296
		$result = $smcFunc['db_query']('', '
297
			SELECT id_alert, id_member FROM {db_prefix}user_alerts
298
			WHERE content_id = {int:like_content}
299
				AND content_type = {string:like_type}
300
				AND id_member_started = {int:id_member_started}
301
				AND content_action = {string:content_action}
302
				AND is_read = {int:unread}',
303
			array(
304
				'like_content' => $this->_content,
305
				'like_type' => $this->_type,
306
				'id_member_started' => $this->_user['id'],
307
				'content_action' => 'like',
308
				'unread' => 0,
309
			)
310
		);
311
		// Found one?
312
		if ($smcFunc['db_num_rows']($result) == 1)
313
		{
314
			list($alert, $member) = $smcFunc['db_fetch_row']($result);
315
316
			// Delete it
317
			$smcFunc['db_query']('', '
318
				DELETE FROM {db_prefix}user_alerts
319
				WHERE id_alert = {int:alert}',
320
				array(
321
					'alert' => $alert,
322
				)
323
			);
324
			// Decrement counter for member who received the like
325
			updateMemberData($member, array('alerts' => '-'));
326
		}
327
	}
328
329
	/**
330
	 * Likes::insert()
331
	 *
332
	 * Inserts a new entry on user_likes table. Creates a background task for the inserted entry.
333
	 */
334
	protected function insert()
335
	{
336
		global $smcFunc;
337
338
		// Any last minute changes? Temporarily turn the passed properties to normal vars to prevent unexpected behaviour with other methods using these properties.
339
		$type = $this->_type;
340
		$content = $this->_content;
341
		$user = $this->_user;
342
		$time = time();
343
344
		call_integration_hook('integrate_issue_like_before', array(&$type, &$content, &$user, &$time));
345
346
		// Insert the like.
347
		$smcFunc['db_insert']('ignore',
348
			'{db_prefix}user_likes',
349
			array('content_id' => 'int', 'content_type' => 'string-6', 'id_member' => 'int', 'like_time' => 'int'),
350
			array($content, $type, $user['id'], $time),
351
			array('content_id', 'content_type', 'id_member')
352
		);
353
354
		// Add a background task to process sending alerts.
355
		// Mod author, you can add your own background task for your own custom like event using the "integrate_issue_like" hook or your callback, both are immediately called after this.
356
		if ($this->_type == 'msg')
357
			$smcFunc['db_insert']('insert',
358
				'{db_prefix}background_tasks',
359
				array('task_file' => 'string', 'task_class' => 'string', 'task_data' => 'string', 'claimed_time' => 'int'),
360
				array('$sourcedir/tasks/Likes-Notify.php', 'Likes_Notify_Background', $smcFunc['json_encode'](array(
361
					'content_id' => $content,
362
					'content_type' => $type,
363
					'sender_id' => $user['id'],
364
					'sender_name' => $user['name'],
365
					'time' => $time,
366
				)), 0),
367
				array('id_task')
368
			);
369
370
		// Are we calling this directly? if so, set a proper data for the response. Do note that __METHOD__ returns both the class name and the function name.
371
		if ($this->_sa == __FUNCTION__)
372
			$this->_data = __FUNCTION__;
373
	}
374
375
	/**
376
	 * Likes::_count()
377
	 *
378
	 * Sets $_numLikes with the actual number of likes your content has, needs two properties: $_content and $_view. When called directly it will return the number of likes as response.
379
	 */
380
	protected function _count()
381
	{
382
		global $smcFunc;
383
384
		$request = $smcFunc['db_query']('', '
385
			SELECT COUNT(*)
386
			FROM {db_prefix}user_likes
387
			WHERE content_id = {int:like_content}
388
				AND content_type = {string:like_type}',
389
			array(
390
				'like_content' => $this->_content,
391
				'like_type' => $this->_type,
392
			)
393
		);
394
		list ($this->_numLikes) = $smcFunc['db_fetch_row']($request);
395
		$smcFunc['db_free_result']($request);
396
397
		// If you want to call this directly, fill out _data property too.
398
		if ($this->_sa == __FUNCTION__)
399
			$this->_data = $this->_numLikes;
400
	}
401
402
	/**
403
	 * Likes::like()
404
	 *
405
	 * Performs a like action, either like or unlike. Counts the total of likes and calls a hook after the event.
406
	 */
407
	protected function like()
408
	{
409
		global $smcFunc;
410
411
		// Safety first!
412
		if (empty($this->_type) || empty($this->_content))
413
			return $this->_error = 'cannot_';
414
415
		// Do we already like this?
416
		$request = $smcFunc['db_query']('', '
417
			SELECT content_id, content_type, id_member
418
			FROM {db_prefix}user_likes
419
			WHERE content_id = {int:like_content}
420
				AND content_type = {string:like_type}
421
				AND id_member = {int:id_member}',
422
			array(
423
				'like_content' => $this->_content,
424
				'like_type' => $this->_type,
425
				'id_member' => $this->_user['id'],
426
			)
427
		);
428
		$this->_alreadyLiked = (bool) $smcFunc['db_num_rows']($request) != 0;
429
		$smcFunc['db_free_result']($request);
430
431
		if ($this->_alreadyLiked)
432
			$this->delete();
433
434
		else
435
			$this->insert();
436
437
		// Now, how many people like this content now? We *could* just +1 / -1 the relevant container but that has proven to become unstable.
438
		$this->_count();
439
440
		// Update the likes count for messages.
441
		if ($this->_type == 'msg')
442
			$this->msgIssueLike();
443
444
		// Any callbacks?
445
		elseif (!empty($this->_validLikes['callback']))
446
		{
447
			$call = call_helper($this->_validLikes['callback'], true);
448
449
			if (!empty($call))
450
				call_user_func_array($call, array($this));
451
		}
452
453
		// Sometimes there might be other things that need updating after we do this like.
454
		call_integration_hook('integrate_issue_like', array($this));
455
456
		// Now some clean up. This is provided here for any like handlers that want to do any cache flushing.
457
		// This way a like handler doesn't need to explicitly declare anything in integrate_issue_like, but do so
458
		// in integrate_valid_likes where it absolutely has to exist.
459
		if (!empty($this->_validLikes['flush_cache']))
460
			cache_put_data($this->_validLikes['flush_cache'], null);
461
462
		// All done, start building the data to pass as response.
463
		$this->_data = array(
464
			'id_topic' => !empty($this->_idTopic) ? $this->_idTopic : 0,
465
			'id_content' => $this->_content,
466
			'count' => $this->_numLikes,
467
			'can_like' => $this->_validLikes['can_like'],
468
			'already_liked' => empty($this->_alreadyLiked),
469
			'type' => $this->_type,
470
		);
471
	}
472
473
	/**
474
	 * Likes::msgIssueLike()
475
	 *
476
	 * Partly it indicates how it's supposed to work and partly it deals with updating the count of likes
477
	 * attached to this message now.
478
	 */
479
	function msgIssueLike()
480
	{
481
		global $smcFunc;
482
483
		if ($this->_type !== 'msg')
484
			return;
485
486
		$smcFunc['db_query']('', '
487
			UPDATE {db_prefix}messages
488
			SET likes = {int:num_likes}
489
			WHERE id_msg = {int:id_msg}',
490
			array(
491
				'id_msg' => $this->_content,
492
				'num_likes' => $this->_numLikes,
493
			)
494
		);
495
496
		// Note that we could just as easily have cleared the cache here, or set up the redirection address
497
		// but if your liked content doesn't need to do anything other than have the record in smf_user_likes,
498
		// there's no point in creating another function unnecessarily.
499
	}
500
501
	/**
502
	 * Likes::view()
503
	 *
504
	 * This is for viewing the people who liked a thing.
505
	 * Accessed from index.php?action=likes;view and should generally load in a popup.
506
	 * We use a template for this in case themers want to style it.
507
	 */
508
	function view()
509
	{
510
		global $smcFunc, $txt, $context, $memberContext;
511
512
		// Firstly, load what we need. We already know we can see this, so that's something.
513
		$context['likers'] = array();
514
		$request = $smcFunc['db_query']('', '
515
			SELECT id_member, like_time
516
			FROM {db_prefix}user_likes
517
			WHERE content_id = {int:like_content}
518
				AND content_type = {string:like_type}
519
			ORDER BY like_time DESC',
520
			array(
521
				'like_content' => $this->_content,
522
				'like_type' => $this->_type,
523
			)
524
		);
525
		while ($row = $smcFunc['db_fetch_assoc']($request))
526
			$context['likers'][$row['id_member']] = array('timestamp' => $row['like_time']);
527
528
		// Now to get member data, including avatars and so on.
529
		$members = array_keys($context['likers']);
530
		$loaded = loadMemberData($members);
531
		if (count($loaded) != count($members))
532
		{
533
			$members = array_diff($members, $loaded);
534
			foreach ($members as $not_loaded)
535
				unset ($context['likers'][$not_loaded]);
536
		}
537
538
		foreach ($context['likers'] as $liker => $dummy)
539
		{
540
			$loaded = loadMemberContext($liker);
541
			if (!$loaded)
542
			{
543
				unset ($context['likers'][$liker]);
544
				continue;
545
			}
546
547
			$context['likers'][$liker]['profile'] = &$memberContext[$liker];
548
			$context['likers'][$liker]['time'] = !empty($dummy['timestamp']) ? timeformat($dummy['timestamp']) : '';
549
		}
550
551
		$count = count($context['likers']);
552
		$title_base = isset($txt['likes_' . $count]) ? 'likes_' . $count : 'likes_n';
553
		$context['page_title'] = strip_tags(sprintf($txt[$title_base], '', comma_format($count)));
554
555
		// Lastly, setting up for display.
556
		loadTemplate('Likes');
557
		loadLanguage('Help'); // For the close window button.
558
		$context['template_layers'] = array();
559
		$context['sub_template'] = 'popup';
560
561
		// We already took care of our response so there is no need to bother with respond();
562
		$this->_setResponse = false;
563
	}
564
565
	/**
566
	 * Likes::response()
567
	 *
568
	 * Checks if the user can use JavaScript and acts accordingly.
569
	 * Calls the appropriate sub-template for each method
570
	 * Handles error messages.
571
	 */
572
	protected function response()
573
	{
574
		global $context, $txt;
575
576
		// Don't do anything if someone else has already take care of the response.
577
		if (!$this->_setResponse)
578
			return;
579
580
		// Want a json response huh?
581
		if ($this->_validLikes['json'])
582
			return $this->jsonResponse();
583
584
		// Set everything up for display.
585
		loadTemplate('Likes');
586
		$context['template_layers'] = array();
587
588
		// If there are any errors, process them first.
589
		if ($this->_error)
590
		{
591
			// If this is a generic error, set it up good.
592
			if ($this->_error == 'cannot_')
593
				$this->_error = $this->_sa == 'view' ? 'cannot_view_likes' : 'cannot_like_content';
594
595
			// Is this request coming from an ajax call?
596
			if ($this->_js)
597
			{
598
				$context['sub_template'] = 'generic';
599
				$context['data'] = isset($txt[$this->_error]) ? $txt[$this->_error] : $txt['like_error'];
600
			}
601
602
			// Nope?  then just do a redirect to whatever URL was provided.
603
			else
604
				redirectexit(!empty($this->_validLikes['redirect']) ? $this->_validLikes['redirect'] . ';error=' . $this->_error : '');
605
606
			return;
607
		}
608
609
		// A like operation.
610
		else
611
		{
612
			// Not an ajax request so send the user back to the previous location or the main page.
613
			if (!$this->_js)
614
				redirectexit(!empty($this->_validLikes['redirect']) ? $this->_validLikes['redirect'] : '');
615
616
			// These fine gentlemen all share the same template.
617
			$generic = array('delete', 'insert', '_count');
618
			if (in_array($this->_sa, $generic))
619
			{
620
				$context['sub_template'] = 'generic';
621
				$context['data'] = isset($txt['like_' . $this->_data]) ? $txt['like_' . $this->_data] : $this->_data;
622
			}
623
624
			// Directly pass the current called sub-action and the data generated by its associated Method.
625
			else
626
			{
627
				$context['sub_template'] = $this->_sa;
628
				$context['data'] = $this->_data;
629
			}
630
		}
631
	}
632
633
	/**
634
	 * Outputs a JSON-encoded response
635
	 */
636
	protected function jsonResponse()
637
	{
638
		global $smcFunc;
639
640
		$print = array(
641
			'data' => $this->_data,
642
		);
643
644
		// If there is an error, send it.
645
		if ($this->_error)
646
		{
647
			if ($this->_error == 'cannot_')
648
				$this->_error = $this->_sa == 'view' ? 'cannot_view_likes' : 'cannot_like_content';
649
650
			$print['error'] = $this->_error;
651
		}
652
653
		// Do you want to add something at the very last minute?
654
		call_integration_hook('integrate_likes_json_response', array(&$print));
655
656
		// Print the data.
657
		smf_serverResponse($smcFunc['json_encode']($print));
658
		die;
659
	}
660
}
661
662
/**
663
 * What's this?  I dunno, what are you talking about?  Never seen this before, nope.  No sir.
664
 */
665
function BookOfUnknown()
666
{
667
	global $context, $scripturl;
668
669
	echo '<!DOCTYPE html>
670
<html', $context['right_to_left'] ? ' dir="rtl"' : '', '>
671
	<head>
672
		<title>The Book of Unknown, ', @$_GET['verse'] == '2:18' ? '2:18' : '4:16', '</title>
673
		<style>
674
			em
675
			{
676
				font-size: 1.3em;
677
				line-height: 0;
678
			}
679
		</style>
680
	</head>
681
	<body style="background-color: #444455; color: white; font-style: italic; font-family: serif;">
682
		<div style="margin-top: 12%; font-size: 1.1em; line-height: 1.4; text-align: center;">';
683
684
	if (!isset($_GET['verse']) || ($_GET['verse'] != '2:18' && $_GET['verse'] != '22:1-2'))
685
		$_GET['verse'] = '4:16';
686
687
	if ($_GET['verse'] == '2:18')
688
		echo '
689
			Woe, it was that his name wasn\'t <em>known</em>, that he came in mystery, and was recognized by none.&nbsp;And it became to be in those days <em>something</em>.&nbsp; Something not yet <em id="unknown" name="[Unknown]">unknown</em> to mankind.&nbsp; And thus what was to be known the <em>secret project</em> began into its existence.&nbsp; Henceforth the opposition was only <em>weary</em> and <em>fearful</em>, for now their match was at arms against them.';
690
	elseif ($_GET['verse'] == '4:16')
691
		echo '
692
			And it came to pass that the <em>unbelievers</em> dwindled in number and saw rise of many <em>proselytizers</em>, and the opposition found fear in the face of the <em>x</em> and the <em>j</em> while those who stood with the <em>something</em> grew stronger and came together.&nbsp; Still, this was only the <em>beginning</em>, and what lay in the future was <em id="unknown" name="[Unknown]">unknown</em> to all, even those on the right side.';
693
	elseif ($_GET['verse'] == '22:1-2')
694
		echo '
695
			<p>Now <em>behold</em>, that which was once the secret project was <em id="unknown" name="[Unknown]">unknown</em> no longer.&nbsp; Alas, it needed more than <em>only one</em>, but yet even thought otherwise.&nbsp; It became that the opposition <em>rumored</em> and lied, but still to no avail.&nbsp; Their match, though not <em>perfect</em>, had them outdone.</p>
696
			<p style="margin: 2ex 1ex 0 1ex; font-size: 1.05em; line-height: 1.5; text-align: center;">Let it continue.&nbsp; <em>The end</em>.</p>';
697
698
	echo '
699
		</div>
700
		<div style="margin-top: 2ex; font-size: 2em; text-align: right;">';
701
702
	if ($_GET['verse'] == '2:18')
703
		echo '
704
			from <span style="font-family: Georgia, serif;"><strong><a href="', $scripturl, '?action=about:unknown;verse=4:16" style="color: white; text-decoration: none; cursor: text;">The Book of Unknown</a></strong>, 2:18</span>';
705
	elseif ($_GET['verse'] == '4:16')
706
		echo '
707
			from <span style="font-family: Georgia, serif;"><strong><a href="', $scripturl, '?action=about:unknown;verse=22:1-2" style="color: white; text-decoration: none; cursor: text;">The Book of Unknown</a></strong>, 4:16</span>';
708
	elseif ($_GET['verse'] == '22:1-2')
709
		echo '
710
			from <span style="font-family: Georgia, serif;"><strong>The Book of Unknown</strong>, 22:1-2</span>';
711
712
	echo '
713
		</div>
714
	</body>
715
</html>';
716
717
	obExit(false);
718
}
719
720
?>