Passed
Push — patch_1-1-9 ( 33104f...4749cf )
by Spuds
18:43 queued 12:51
created

Metadata_Integrate::getPostSchema()   B

Complexity

Conditions 7
Paths 33

Size

Total Lines 57
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 39
dl 0
loc 57
rs 8.3626
c 1
b 0
f 0
nc 33
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Implementing this interface will make controllers usable as a front page
5
 * replacing the classic board index.
6
 *
7
 * @name      ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
10
 *
11
 * @version 1.1.9
12
 *
13
 */
14
15
/**
16
 * Class Metadata_Integrate
17
 *
18
 * OG and Schema functions for creation of microdata
19
 */
20
class Metadata_Integrate
21
{
22
	/** @var array data from the post renderer */
23
	public $data;
24
25
	/** @var array attachment data from AttachmentsDisplay Controller */
26
	public $attachments;
27
28
	/** @var array ila data from AttachmentsDisplay Controller */
29
	public $ila;
30
31
	/**
32
	 * Register Metadata hooks to the system.
33
	 *
34
	 * @return array
35
	 */
36
	public static function register()
37
	{
38
		global $modSettings;
39
40
		if (empty($modSettings['metadata_enabled']))
41
		{
42
			return array();
43
		}
44
45
		// Simply load context with our data which will be consumed by the theme's index.template (if supported)
46
		return array(
47
			// Display
48
			array('integrate_action_display_after', 'Metadata_Integrate::prepare_topic_metadata'),
49
			// Board
50
			array('integrate_action_boardindex_after', 'Metadata_Integrate::prepare_basic_metadata'),
51
			// MessageIndex
52
			array('integrate_action_messageindex_after', 'Metadata_Integrate::prepare_basic_metadata'),
53
		);
54
	}
55
56
	/**
57
	 * Prepares Open Graph and Schema data for use in templates when viewing a specific topic
58
	 *
59
	 * - It will only generate full schema data when the pageindex of the topic is on page 1
60
	 *
61
	 * @param int $start
62
	 */
63
	public static function prepare_topic_metadata($start = -1)
64
	{
65
		global $context;
66
67
		$meta = new self();
68
		$start = isset($context['start']) ? $context['start'] : $start;
69
70
		// Load in the post data if available
71
		$meta->data = $meta->initPostData($start);
72
		$meta->attachments = isset($meta->data['attachments']) ? $meta->data['attachments'] : array();
73
		$meta->ila = !empty($context['ila_dont_show_attach_below']) ? $context['ila_dont_show_attach_below'] : array();
74
75
		// Set the data into context for template use
76
		$meta->setContext();
77
	}
78
79
	/**
80
	 * Prepares Open Graph and Schema data when viewing a message listing or the board index.
81
	 * Currently, this consists of a simple organizational card and OG with description
82
	 */
83
	public static function prepare_basic_metadata()
84
	{
85
		$meta = new self();
86
87
		// Set the data into context for template use
88
		$meta->setContext();
89
	}
90
91
	/*
92
	 * Set what we have created into context for template consumption.
93
	 *
94
	 * The schema data should be output in a template as
95
	 * <script type="application/ld+json">
96
	 * json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
97
	 * </script>
98
	 * OG data is an array of <meta> tags for implosion.
99
	 */
100
	private function setContext()
101
	{
102
		global $context;
103
104
		// Set the data into context for template use
105
		$context['smd_site'] = $this->getSiteSchema();
106
		$context['smd_article'] = $this->getPostSchema();
107
		$context['open_graph'] = $this->getOgData();
108
	}
109
110
	/**
111
	 * When viewing the first page of a topic, will return data from the first post
112
	 * to be used in creating microdata.
113
	 *
114
	 * Requires that display renderer, $context['get_message'], has been set via the Display Controller
115
	 *
116
	 * @param int $start
117
	 * @return array
118
	 */
119
	private function initPostData($start)
120
	{
121
		global $context, $topic;
122
123
		$smd = array();
124
125
		// If this is a topic, and we are on the first page (so we can get first post data)
126
		if (!empty($topic)
127
			&& $start === 0
128
			&& (!empty($context['get_message'][0]) && is_object($context['get_message'][0])))
129
		{
130
			// Grab the first post of the thread to get proper thread author data
131
			$controller = $context['get_message'][0];
132
			$smd = $controller->{$context['get_message'][1]}();
133
134
			// Create a short body, leaving some very basic html
135
			$smd['raw_body'] = trim(strip_tags($smd['body']));
136
			$smd['html_body'] = trim(strip_tags($smd['body'], '<br><strong><em><blockquote>'));
137
			$smd['html_body'] = str_replace(array("\n", "\t"), '', $smd['html_body']);
138
139
			// Strip attributes from any remaining tags
140
			$smd['html_body'] = preg_replace('~<([bse][a-z0-9]*)[^>]*?(/?)>~i', '<$1$2>', $smd['html_body']);
141
			$smd['html_body'] = Util::shorten_html($smd['html_body'], 375);
142
143
			// Create a short plain text description // $context['page_description']
144
			$description = empty($context['description'])
145
				? preg_replace('~\s\s+|&nbsp;|&quot;|&#039;~', ' ', $smd['raw_body'])
146
				: $context['description'];
147
			$smd['description'] = Util::shorten_text($description, 110, true);
148
149
			// Reset the pointer so the template does not miss the first post
150
			$controller->{$context['get_message'][1]}(true);
151
		}
152
153
		return $smd;
154
	}
155
156
	/**
157
	 * Build and return the schema business card
158
	 *
159
	 * @return array
160
	 */
161
	public function getSiteSchema()
162
	{
163
		global $context, $boardurl, $mbname, $settings;
164
165
		// Snag us a site logo
166
		$logo = $this->getLogo();
167
168
		$slogan = !empty($settings['site_slogan']) ? $settings['site_slogan'] : un_htmlspecialchars($mbname);
169
170
		// The sites organizational card
171
		return array(
172
			'@context' => 'https://schema.org',
173
			'@type' => 'Organization',
174
			'url' => !empty($context['canonical_url']) ? $context['canonical_url'] : $boardurl,
175
			'logo' => array(
176
				'@type' => 'ImageObject',
177
				'url' => $logo[2],
178
				'width' => $logo[0],
179
				'height' => $logo[1],
180
			),
181
			'name' => un_htmlspecialchars($context['forum_name']),
182
			'slogan' => $slogan,
183
		);
184
	}
185
186
	/**
187
	 * Function to return the sites logo url
188
	 *
189
	 * @return array width, height and html safe logo url
190
	 */
191
	private function getLogo()
192
	{
193
		global $context, $boardurl;
194
195
		// Set in ThemeLoader
196
		if (!empty($context['header_logo_url_html_safe']))
197
		{
198
			$logo = $context['header_logo_url_html_safe'];
199
		}
200
		else
201
		{
202
			$logo = $boardurl . '/mobile.png';
203
		}
204
205
		// This will also cache these values for us
206
		require_once(SUBSDIR . '/Attachments.subs.php');
207
		list($width, $height) = url_image_size(un_htmlspecialchars($logo));
208
209
		return array($width, $height, $logo);
210
	}
211
212
	/**
213
	 * Build and return the article schema.  This is intended for use when displaying
214
	 * a topic.
215
	 *
216
	 * @return array
217
	 */
218
	public function getPostSchema()
219
	{
220
		global $context, $boardurl, $mbname, $board_info;
221
222
		$smd = array();
223
224
		if (empty($this->data))
225
		{
226
			return $smd;
227
		}
228
229
		$logo = $this->getLogo();
230
		$smd = array(
231
			'@context' => 'https://schema.org',
232
			'@type' => 'DiscussionForumPosting',
233
			'@id' => $this->data['href'],
234
			'headline' => $this->getPageTitle(),
235
			'author' => array(
236
				'@type' => 'Person',
237
				'name' => $this->data['member']['name'],
238
				'url' => $this->data['member']['href']
239
			),
240
			'url' => $this->data['href'],
241
			'articleBody' => $this->data['html_body'],
242
			'articleSection' => isset($board_info['name']) ? $board_info['name'] : '',
243
			'datePublished' => $this->data['time'],
244
			'dateModified' => !empty($this->data['modified']['name']) ? $this->data['modified']['time'] : $this->data['time'],
245
			'interactionStatistic' => array(
246
				'@type' => 'InteractionCounter',
247
				'interactionType' => 'https://schema.org/ReplyAction',
248
				'userInteractionCount' => !empty($context['real_num_replies']) ? $context['real_num_replies'] : 0,
249
			),
250
			'wordCount' => str_word_count($this->data['raw_body']),
251
			'publisher' => array(
252
				'@type' => 'Organization',
253
				'name' => un_htmlspecialchars($mbname),
254
				'logo' => array(
255
					'@type' => 'ImageObject',
256
					'url' => $logo[2],
257
					'width' => $logo[0],
258
					'height' => $logo[1],
259
				),
260
			),
261
			'mainEntityOfPage' => array(
262
				'@type' => 'WebPage',
263
				'@id' => !empty($context['canonical_url']) ? $context['canonical_url'] : $boardurl,
264
			),
265
		);
266
267
		// If the post has any attachments, set an ImageObject
268
		$image = $this->getAttachment();
269
		if (!empty($image))
270
		{
271
			$smd['image'] = $image;
272
		}
273
274
		return $smd;
275
	}
276
277
	/**
278
	 * Checks the post for any attachments to use as an image.  Will use the
279
	 * first below post attachment, failing that the first ILA, failing that nothing
280
	 *
281
	 * @return array
282
	 */
283
	private function getAttachment()
284
	{
285
		global $boardurl, $context;
286
287
		if (empty($this->data))
288
		{
289
			return array();
290
		}
291
292
		// If there are below post attachments, use the first one that is an image
293
		if (!empty($this->data['attachment']))
294
		{
295
			foreach ($this->data['attachment'] as $attachment)
296
			{
297
				if (isset($attachment['is_image']) && !empty($attachment['is_approved']))
298
				{
299
					return array(
300
						'@type' => 'ImageObject',
301
						'url' => $attachment['href'],
302
						'width' => isset($attachment['real_width']) ? $attachment['real_width'] : 0,
303
						'height' => isset($attachment['real_height']) ? $attachment['real_height'] : 0
304
					);
305
				}
306
			}
307
		}
308
309
		// Maybe it has an inline image?
310
		if (!empty($context['ila']))
311
		{
312
			foreach ($context['ila'] as $ila)
313
			{
314
				if (!empty($ila['width']) && !empty($ila['height']) && !empty($ila['approved']))
315
				{
316
					return array(
317
						'@type' => 'ImageObject',
318
						'url' => $boardurl . '/index.php?action=dlattach;attach=' . $ila['id_attach'] . ';image',
319
						'width' => isset($ila['width']) ? $ila['width'] : 0,
320
						'height' => isset($ila['height']) ? $ila['height'] : 0
321
					);
322
				}
323
			}
324
		}
325
326
		return array();
327
	}
328
329
	/**
330
	 * Function to provide backup page name if none is defined
331
	 *
332
	 * @param string $description
333
	 * @return string html safe title
334
	 */
335
	private function getPageTitle($description = '')
336
	{
337
		global $context;
338
339
		// As long as you are calling this class from the right area, this will be set
340
		if (!empty($context['page_title']))
341
		{
342
			return Util::shorten_text(Util::htmlspecialchars(un_htmlspecialchars($context['page_title'])), 110, true);
343
		}
344
345
		// Otherwise, do the best we can
346
		$description = empty($description) ? $this->getDescription() : $description;
347
348
		return Util::shorten_text(Util::htmlspecialchars(un_htmlspecialchars($description)), 110, true);
349
	}
350
351
	/**
352
	 * Prepares the description for use in Metadata
353
	 *
354
	 * This is typically already generated and is one of
355
	 * - The board description, set in MessageIndex Controller
356
	 * - The topic description, set in Display Controller
357
	 *
358
	 * Failing that will use one of
359
	 * - The page title
360
	 * - The site slogan
361
	 * - The site name
362
	 *
363
	 * @return string html safe description
364
	 */
365
	private function getDescription()
366
	{
367
		global $context, $settings, $mbname;
368
369
		// Supplied one, simply use it.
370
		if (!empty($context['page_description']) || !empty(!empty($context['description'])))
371
		{
372
			return isset($context['page_description']) ? $context['page_description'] : $context['description'];
373
		}
374
375
		// Build out a default that makes some sense
376
		if (!empty($this->data['description']))
377
		{
378
			$description = $this->data['description'];
379
		}
380
		else
381
		{
382
			$sitename = un_htmlspecialchars($mbname);
383
384
			// Avoid if possible a description like sitename - Index
385
			if (isset($context['page_title']) && strpos($context['page_title'], $sitename) === 0)
386
			{
387
				$description = isset($settings['site_slogan']) ? $settings['site_slogan'] : $context['page_title'];
388
			}
389
			else
390
			{
391
				$description = isset($context['page_title']) ? $context['page_title'] : (isset($settings['site_slogan']) ? $settings['site_slogan'] : $sitename);
392
			}
393
		}
394
395
		return Util::htmlspecialchars($description);
396
	}
397
398
	/**
399
	 * Basic OG Metadata to insert in to the <head></head> element.  See https://ogp.me
400
	 *
401
	 * This will generate *basic* og metadata, suitable for FB/Meta website/post sharing.
402
	 *
403
	 * og:title - The title of your article without any branding (site name)
404
	 * og:type - The type of your object, e.g., "website".
405
	 * og:image - The URL of the image that appears when someone shares the content
406
	 * og:url - The canonical URL of your page.
407
	 * og:site_name - The name which should be displayed for the overall site.
408
	 * og:description - A brief description of the content, usually between 2 and 4 sentences.
409
	 *
410
	 * @return array
411
	 */
412
	public function getOgData()
413
	{
414
		global $context, $boardurl, $mbname, $topic;
415
416
		$description = strip_tags($this->getDescription());
417
		$page_title = $this->getPageTitle();
418
		$logo = $this->getLogo();
419
		$attach = $this->getAttachment();
420
421
		// If on a post page, with attachments, use it vs a site logo
422
		if (isset($attach['url']))
423
		{
424
			$logo[2] = $attach['url'];
425
			$logo[1] = $attach['height'];
426
			$logo[0] = $attach['width'];
427
		}
428
429
		$metaOg = array();
430
		$metaOg['title'] = '<meta property="og:title" content="' . $page_title . '" />';
431
		$metaOg['type'] = '<meta property="og:type" content="' . (!empty($topic) ? 'article' : 'website') . '" />';
432
		$metaOg['url'] = '<meta property="og:url" content="' . (!empty($context['canonical_url']) ? $context['canonical_url'] : $boardurl) . '" />';
433
		$metaOg['image'] = '<meta property="og:image" content="' . $logo[2] . '" />';
434
		$metaOg['image_width'] = '<meta property="og:image:width" content="' . $logo[0] . '" />';
435
		$metaOg['image_height'] = '<meta property="og:image:height" content="' . $logo[1] . '" />';
436
		$metaOg['sitename'] = '<meta property="og:site_name" content="' . Util::htmlspecialchars($mbname) . '" />';
437
		$metaOg['description'] = '<meta property="og:description" content="' . $description . '" />';
438
439
		return $metaOg;
440
	}
441
}
442