Completed
Push — master ( d9fcc5...35afbd )
by Nazar
04:10
created

Posts::get_as_json_ld()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 7
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
/**
3
 * @package   Blogs
4
 * @category  modules
5
 * @author    Nazar Mokrynskyi <[email protected]>
6
 * @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi
7
 * @license   MIT License, see license.txt
8
 */
9
namespace cs\modules\Blogs;
10
use
11
	cs\Event,
12
	cs\Cache\Prefix as Cache_prefix,
13
	cs\Config,
14
	cs\Language,
15
	cs\Language\Prefix as Language_prefix,
16
	cs\User,
17
	cs\CRUD_helpers,
18
	cs\Singleton,
19
	cs\plugins\Json_ld\Json_ld;
20
21
/**
22
 * @method static $this instance($check = false)
23
 */
24
class Posts {
25
	use
26
		CRUD_helpers,
27
		Singleton;
28
	protected $data_model                  = [
29
		'id'       => 'int:0',
30
		'user'     => 'int:0',
31
		'date'     => 'int:0',
32
		'title'    => 'ml:text',
33
		'path'     => 'ml:text',
34
		'content'  => 'ml:html',
35
		'draft'    => 'int:0..1',
36
		'sections' => [
37
			'data_model' => [
38
				'id'      => 'int:0',
39
				'section' => 'int:0'
40
			]
41
		],
42
		'tags'     => [
43
			'data_model'     => [
44
				'id'  => 'int:0',
45
				'tag' => 'int:0'
46
			],
47
			'language_field' => 'lang'
48
		]
49
	];
50
	protected $table                       = '[prefix]blogs_posts';
51
	protected $data_model_ml_group         = 'Blogs/posts';
52
	protected $data_model_files_tag_prefix = 'Blogs/posts';
53
	/**
54
	 * @var Cache_prefix
55
	 */
56
	protected $cache;
57
58
	protected function construct () {
59
		$this->cache = new Cache_prefix('Blogs');
60
		if (Config::instance()->module('Blogs')->allow_iframes_without_content) {
61
			$this->data_model['content'] = 'ml:html_iframe';
62
		}
63
	}
64
	/**
65
	 * Returns database index
66
	 *
67
	 * @return int
68
	 */
69
	protected function cdb () {
70
		return Config::instance()->module('Blogs')->db('posts');
71
	}
72
	/**
73
	 * Get data of specified post
74
	 *
75
	 * @param int|int[] $id
76
	 *
77
	 * @return array|false
78
	 */
79
	function get ($id) {
80
		if (is_array($id)) {
81
			foreach ($id as &$i) {
82
				$i = $this->get($i);
83
			}
84
			return $id;
85
		}
86
		$L        = Language::instance();
87
		$id       = (int)$id;
88
		$data     = $this->cache->get(
89
			"posts/$id/$L->clang",
90
			function () use ($id) {
91
				$data = $this->read($id);
92
				if ($data) {
93
					$data['short_content'] = truncate(explode('<!-- pagebreak -->', $data['content'])[0]);
94
					$data['tags']          = $this->read_tags_processing($data['tags']);
95
				}
96
				return $data;
97
			}
98
		);
99
		$Comments = null;
100
		Event::instance()->fire(
101
			'Comments/instance',
102
			[
103
				'Comments' => &$Comments
104
			]
105
		);
106
		/**
107
		 * @var \cs\modules\Comments\Comments $Comments
108
		 */
109
		$data['comments_count'] =
110
			Config::instance()->module('Blogs')->enable_comments && $Comments
111
				? $Comments->count($data['id'])
112
				: 0;
113
		return $data;
114
	}
115
	/**
116
	 * Transform tags ids back into array of strings
117
	 *
118
	 * @param int[] $tags
119
	 *
120
	 * @return string[]
121
	 */
122
	protected function read_tags_processing ($tags) {
123
		return array_column(Tags::instance()->get($tags) ?: [], 'text');
124
	}
125
	/**
126
	 * Get data of specified post
127
	 *
128
	 * @param int|int[] $id
129
	 *
130
	 * @return array|false
131
	 */
132
	function get_as_json_ld ($id) {
133
		$post = $this->get($id);
134
		if (!$post) {
135
			return false;
136
		}
137
		return $this->post_to_jsonld($post);
138
	}
139
	/**
140
	 * @param array|array[] $post
141
	 *
142
	 * @return array
143
	 */
144
	function post_to_jsonld ($post) {
145
		$base_structure = [
146
			'@context' =>
147
				[
148
					'content'        => 'articleBody',
149
					'title'          => 'headline',
150
					'comments_count' => 'commentCount',
151
					'tags'           => 'keywords',
152
					'datetime'       => null,
153
					'sections_paths' => null,
154
					'tags_paths'     => null
155
				] + Json_ld::context_stub(isset($post[0]) ? $post[0] : $post)
156
		];
157
		if (isset($post[0])) {
158
			return
159
				$base_structure +
160
				[
161
					'@graph' => array_map(
162
						[$this, 'post_to_jsonld_single_post'],
163
						$post
164
					)
165
				];
166
		}
167
		return
168
			$base_structure +
169
			$this->post_to_jsonld_single_post($post);
170
	}
171
	protected function post_to_jsonld_single_post ($post) {
172
		if (preg_match_all('/<img[^>]src=["\'](.*)["\']/Uims', $post['content'], $images)) {
173
			$images = $images[1];
174
		}
175
		$Sections = Sections::instance();
176
		$sections = [];
177
		if ($post['sections'] != [0]) {
178
			$sections = array_column(
179
				$Sections->get($post['sections']),
180
				'title'
181
			);
182
		}
183
		$L            = new Language_prefix('blogs_');
184
		$base_url     = Config::instance()->base_url();
185
		$module_path  = path($L->Blogs);
186
		$section_path = "$base_url/$module_path/".path($L->section);
187
		$tag_path     = "$base_url/$module_path/".path($L->tag);
188
		$url          = "$base_url/$module_path/$post[path]:$post[id]";
189
		return
190
			[
191
				'@id'            => $url,
192
				'@type'          => 'BlogPosting',
193
				'articleSection' => $sections,
194
				'author'         => Json_ld::Person($post['user']),
195
				'datePublished'  => Json_ld::Date($post['date']),
196
				'image'          => $images,
197
				'inLanguage'     => $L->clang,
198
				'url'            => $url,
199
				'datetime'       => $L->to_locale(date($L->_datetime_long, $post['date'] ?: TIME)),
200
				'sections_paths' => array_map(
201
					function ($section) use ($section_path, $Sections) {
202
						$section = $Sections->get($section);
203
						return "$section_path/$section[full_path]";
204
					},
205
					$post['sections']
206
				),
207
				'tags_paths'     => array_map(
208
					function ($tag) use ($tag_path) {
209
						return "$tag_path/$tag";
210
					},
211
					$post['tags']
212
				)
213
			] + $post;
214
	}
215
	/**
216
	 * Get latest posts
217
	 *
218
	 * @param int $page
219
	 * @param int $count
220
	 *
221
	 * @return int[]
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|array|double|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
222
	 */
223
	function get_latest_posts ($page, $count) {
224
		$search_parameters = [
225
			'draft' => 0
226
		];
227
		return $this->search($search_parameters, $page, $count, 'date', false) ?: [];
228
	}
229
	/**
230
	 * Get posts for section
231
	 *
232
	 * @param int $section
233
	 * @param int $page
234
	 * @param int $count
235
	 *
236
	 * @return int[]
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|array|double|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
237
	 */
238
	function get_for_section ($section, $page, $count) {
239
		$search_parameters = [
240
			'draft'    => 0,
241
			'sections' => [
242
				'section' => $section
243
			]
244
		];
245
		return $this->search($search_parameters, $page, $count, 'date', false) ?: [];
246
	}
247
	/**
248
	 * Get number of posts for section
249
	 *
250
	 * @param int $section
251
	 *
252
	 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be false|integer|array|double|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
253
	 */
254
	function get_for_section_count ($section) {
255
		$search_parameters = [
256
			'draft'       => 0,
257
			'sections'    => [
258
				'section' => $section
259
			],
260
			'total_count' => true
261
		];
262
		return $this->search($search_parameters);
263
	}
264
	/**
265
	 * Get posts for tag
266
	 *
267
	 * @param int    $tag
268
	 * @param string $lang
269
	 * @param int    $page
270
	 * @param int    $count
271
	 *
272
	 * @return int[]
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|array|double|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
273
	 */
274
	function get_for_tag ($tag, $lang, $page, $count) {
275
		$search_parameters = [
276
			'draft' => 0,
277
			'tags'  => [
278
				'tag'  => $tag,
279
				'lang' => $lang
280
			]
281
		];
282
		return $this->search($search_parameters, $page, $count, 'date', false) ?: [];
283
	}
284
	/**
285
	 * Get number of posts for tag
286
	 *
287
	 * @param int    $tag
288
	 * @param string $lang
289
	 *
290
	 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be false|integer|array|double|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
291
	 */
292
	function get_for_tag_count ($tag, $lang) {
293
		$search_parameters = [
294
			'draft'       => 0,
295
			'tags'        => [
296
				'tag'  => $tag,
297
				'lang' => $lang
298
			],
299
			'total_count' => true
300
		];
301
		return $this->search($search_parameters);
302
	}
303
	/**
304
	 * Get drafts
305
	 *
306
	 * @param int $user
307
	 * @param int $page
308
	 * @param int $count
309
	 *
310
	 * @return int[]
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|array|double|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
311
	 */
312
	function get_drafts ($user, $page, $count) {
313
		$search_parameters = [
314
			'user'  => $user,
315
			'draft' => 1
316
		];
317
		return $this->search($search_parameters, $page, $count, 'date', false) ?: [];
318
	}
319
	/**
320
	 * Get number of drafts
321
	 *
322
	 * @param int $user
323
	 *
324
	 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be false|integer|array|double|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
325
	 */
326
	function get_drafts_count ($user) {
327
		$search_parameters = [
328
			'user'        => $user,
329
			'draft'       => 1,
330
			'total_count' => true
331
		];
332
		return $this->search($search_parameters);
333
	}
334
	/**
335
	 * Add new post
336
	 *
337
	 * @param string   $title
338
	 * @param string   $path
339
	 * @param string   $content
340
	 * @param int[]    $sections
341
	 * @param string[] $tags
342
	 * @param bool     $draft
343
	 *
344
	 * @return false|int Id of created post on success of <b>false</> on failure
1 ignored issue
show
Documentation introduced by
Should the return type not be false|integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
345
	 */
346
	function add ($title, $path, $content, $sections, $tags, $draft) {
347
		if (!$this->check_arguments($content, $sections, $tags)) {
348
			return false;
349
		}
350
		$id = $this->create(
351
			User::instance()->id,
352
			$draft ? 0 : time(),
353
			$title,
354
			path($path ?: $title),
355
			$content,
356
			$draft,
357
			$sections,
358
			$this->prepare_tags($tags)
359
		);
360
		if ($id) {
361
			$this->cache_cleanups($id);
362
		}
363
		return $id;
364
	}
365
	/**
366
	 * Transform array of string tags into array of their ids
367
	 *
368
	 * @param string[] $tags
369
	 *
370
	 * @return int[]
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|string|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
371
	 */
372
	protected function prepare_tags ($tags) {
373
		return Tags::instance()->add($tags) ?: [];
374
	}
375
	/**
376
	 * @param string   $content
377
	 * @param int[]    $sections
378
	 * @param string[] $tags
379
	 *
380
	 * @return bool
381
	 */
382
	protected function check_arguments ($content, &$sections, $tags) {
383
		if (empty($tags) || empty($content)) {
384
			return false;
385
		}
386
		$sections = array_intersect(
387
			array_column(Sections::instance()->get_all(), 'id'),
388
			$sections
389
		);
390
		return $sections && count($sections) <= Config::instance()->module('Blogs')->max_sections;
391
	}
392
	/**
393
	 * @param int $id
394
	 */
395
	protected function cache_cleanups ($id) {
396
		$Cache = $this->cache;
397
		unset(
398
			$Cache->{"posts/$id"},
399
			$Cache->sections,
400
			$Cache->total_count
401
		);
402
	}
403
	/**
404
	 * Set data of specified post
405
	 *
406
	 * @param int      $id
407
	 * @param string   $title
408
	 * @param string   $path
409
	 * @param string   $content
410
	 * @param int[]    $sections
411
	 * @param string[] $tags
412
	 * @param bool     $draft
413
	 *
414
	 * @return bool
415
	 */
416
	function set ($id, $title, $path, $content, $sections, $tags, $draft) {
417
		if (!$this->check_arguments($content, $sections, $tags)) {
418
			return false;
419
		}
420
		$old_data = $this->get($id);
421
		$result   = $this->update(
422
			$id,
423
			$old_data['user'],
424
			$old_data['draft'] == 1 && $old_data['date'] == 0 && !$draft ? time() : $old_data['date'],
425
			$title,
426
			path($path ?: $title),
427
			$content,
428
			$draft,
429
			$sections,
430
			$this->prepare_tags($tags)
431
		);
432
		$this->cache_cleanups($id);
433
		return $result;
434
	}
435
	/**
436
	 * Delete specified post
437
	 *
438
	 * @param int $id
439
	 *
440
	 * @return bool
441
	 */
442
	function del ($id) {
443
		$id     = (int)$id;
444
		$result = $this->delete($id);
445
		if ($result) {
446
			$Comments = null;
447
			Event::instance()->fire(
448
				'Comments/instance',
449
				[
450
					'Comments' => &$Comments
451
				]
452
			);
453
			/**
454
			 * @var \cs\modules\Comments\Comments $Comments
455
			 */
456
			if ($Comments) {
457
				$Comments->del_all($id);
458
			}
459
			$this->cache_cleanups($id);
460
		}
461
		return $result;
462
	}
463
	/**
464
	 * Get total count of posts
465
	 *
466
	 * @return int
467
	 */
468
	function get_total_count () {
469
		return $this->cache->get(
470
			'total_count',
471
			function () {
472
				$search_parameters = [
473
					'draft'       => 0,
474
					'total_count' => true
475
				];
476
				return $this->search($search_parameters);
477
			}
478
		);
479
	}
480
}
481