Completed
Push — master ( 7710e6...19a091 )
by Nazar
04:14
created

Posts::get_latest_posts()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 11
rs 9.4285
cc 2
eloc 7
nc 2
nop 2
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,
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,
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
	protected $table_sections              = '[prefix]blogs_posts_sections';
54
	protected $table_tags                  = '[prefix]blogs_posts_tags';
55
	/**
56
	 * @var Cache_prefix
57
	 */
58
	protected $cache;
59
60
	protected function construct () {
61
		$this->cache = new Cache_prefix('Blogs');
62
		if (Config::instance()->module('Blogs')->allow_iframes_without_content) {
63
			$this->data_model['content'] = 'ml:html_iframe';
64
		}
65
	}
66
	/**
67
	 * Returns database index
68
	 *
69
	 * @return int
70
	 */
71
	protected function cdb () {
72
		return Config::instance()->module('Blogs')->db('posts');
73
	}
74
	/**
75
	 * Get data of specified post
76
	 *
77
	 * @param int|int[] $id
78
	 *
79
	 * @return array|false
80
	 */
81
	function get ($id) {
82
		if (is_array($id)) {
83
			foreach ($id as &$i) {
84
				$i = $this->get($i);
85
			}
86
			return $id;
87
		}
88
		$L        = Language::instance();
89
		$id       = (int)$id;
90
		$data     = $this->cache->get(
91
			"posts/$id/$L->clang",
92
			function () use ($id) {
93
				$data = $this->read($id);
94
				if ($data) {
95
					$data['short_content'] = truncate(explode('<!-- pagebreak -->', $data['content'])[0]);
96
					$data['tags']          = $this->read_tags_processing($data['tags']);
97
				}
98
				return $data;
99
			}
100
		);
101
		$Comments = null;
102
		Event::instance()->fire(
103
			'Comments/instance',
104
			[
105
				'Comments' => &$Comments
106
			]
107
		);
108
		/**
109
		 * @var \cs\modules\Comments\Comments $Comments
110
		 */
111
		$data['comments_count'] =
112
			Config::instance()->module('Blogs')->enable_comments && $Comments
113
				? $Comments->count($data['id'])
114
				: 0;
115
		return $data;
116
	}
117
	/**
118
	 * Transform tags ids back into array of strings
119
	 *
120
	 * @param int[] $tags
121
	 *
122
	 * @return string[]
123
	 */
124
	protected function read_tags_processing ($tags) {
125
		return array_column(Tags::instance()->get($tags) ?: [], 'text');
126
	}
127
	/**
128
	 * Get data of specified post
129
	 *
130
	 * @param int|int[] $id
131
	 *
132
	 * @return array|false
133
	 */
134
	function get_as_json_ld ($id) {
135
		$post = $this->get($id);
136
		if (!$post) {
137
			return false;
138
		}
139
		return $this->post_to_jsonld($post);
140
	}
141
	/**
142
	 * @param array|array[] $post
143
	 *
144
	 * @return array
145
	 */
146
	function post_to_jsonld ($post) {
147
		$base_structure = [
148
			'@context' =>
149
				[
150
					'content'        => 'articleBody',
151
					'title'          => 'headline',
152
					'comments_count' => 'commentCount',
153
					'tags'           => 'keywords',
154
					'datetime'       => null,
155
					'sections_paths' => null,
156
					'tags_paths'     => null
157
				] + Json_ld::context_stub(isset($post[0]) ? $post[0] : $post)
158
		];
159
		if (isset($post[0])) {
160
			return
161
				$base_structure +
162
				[
163
					'@graph' => array_map(
164
						[$this, 'post_to_jsonld_single_post'],
165
						$post
166
					)
167
				];
168
		}
169
		return
170
			$base_structure +
171
			$this->post_to_jsonld_single_post($post);
172
	}
173
	protected function post_to_jsonld_single_post ($post) {
174
		if (preg_match_all('/<img[^>]src=["\'](.*)["\']/Uims', $post['content'], $images)) {
175
			$images = $images[1];
176
		}
177
		$Sections = Sections::instance();
178
		$sections = [];
179
		if ($post['sections'] != [0]) {
180
			$sections = array_column(
181
				$Sections->get($post['sections']),
182
				'title'
183
			);
184
		}
185
		$L            = new Language_prefix('blogs_');
186
		$base_url     = Config::instance()->base_url();
187
		$module_path  = path($L->Blogs);
188
		$section_path = "$base_url/$module_path/".path($L->section);
189
		$tag_path     = "$base_url/$module_path/".path($L->tag);
190
		$url          = "$base_url/$module_path/$post[path]:$post[id]";
191
		return
192
			[
193
				'@id'            => $url,
194
				'@type'          => 'BlogPosting',
195
				'articleSection' => $sections,
196
				'author'         => Json_ld::Person($post['user']),
197
				'datePublished'  => Json_ld::Date($post['date']),
198
				'image'          => $images,
199
				'inLanguage'     => $L->clang,
200
				'url'            => $url,
201
				'datetime'       => $L->to_locale(date($L->_datetime_long, $post['date'] ?: TIME)),
202
				'sections_paths' => array_map(
203
					function ($section) use ($section_path, $Sections) {
204
						$section = $Sections->get($section);
205
						return "$section_path/$section[full_path]";
206
					},
207
					$post['sections']
208
				),
209
				'tags_paths'     => array_map(
210
					function ($tag) use ($tag_path) {
211
						return "$tag_path/$tag";
212
					},
213
					$post['tags']
214
				)
215
			] + $post;
216
	}
217
	/**
218
	 * Get latest posts
219
	 *
220
	 * @param int $page
221
	 * @param int $number
222
	 *
223
	 * @return int[]
0 ignored issues
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...
224
	 */
225
	function get_latest_posts ($page, $number) {
226
		$number = (int)$number;
227
		$from   = ($page - 1) * $number;
228
		return $this->db()->qfas(
229
			"SELECT `id`
230
			FROM `$this->table`
231
			WHERE `draft` = 0
232
			ORDER BY `date` DESC
233
			LIMIT $from, $number"
234
		) ?: [];
235
	}
236
	/**
237
	 * Get posts for section
238
	 *
239
	 * @param int $section
240
	 * @param int $page
241
	 * @param int $number
242
	 *
243
	 * @return int[]
0 ignored issues
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...
244
	 */
245
	function get_for_section ($section, $page, $number) {
246
		$section = (int)$section;
247
		$number  = (int)$number;
248
		$from    = ($page - 1) * $number;
249
		return $this->db()->qfas(
250
			"SELECT `s`.`id`
251
			FROM `$this->table_sections` AS `s`
252
				LEFT JOIN `$this->table` AS `p`
253
			ON `s`.`id` = `p`.`id`
254
			WHERE
255
				`s`.`section`	= $section AND
256
				`p`.`draft`		= 0
257
			ORDER BY `p`.`date` DESC
258
			LIMIT $from, $number"
259
		) ?: [];
260
	}
261
	/**
262
	 * Get number of posts for section
263
	 *
264
	 * @param int $section
265
	 *
266
	 * @return int
0 ignored issues
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...
267
	 */
268
	function get_for_section_count ($section) {
269
		$section = (int)$section;
270
		return $this->db()->qfs(
271
			"SELECT COUNT(`s`.`id`)
272
			FROM `$this->table_sections` AS `s`
273
				LEFT JOIN `$this->table` AS `p`
274
			ON `s`.`id` = `p`.`id`
275
			WHERE
276
				`s`.`section`	= $section AND
277
				`p`.`draft`		= 0"
278
		) ?: [];
279
	}
280
	/**
281
	 * Get posts for tag
282
	 *
283
	 * @param int    $tag
284
	 * @param string $lang
285
	 * @param int    $page
286
	 * @param int    $number
287
	 *
288
	 * @return int[]
0 ignored issues
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...
289
	 */
290
	function get_for_tag ($tag, $lang, $page, $number) {
291
		$number = (int)$number;
292
		$from   = ($page - 1) * $number;
293
		return $this->db()->qfas(
294
			[
295
				"SELECT `t`.`id`
296
				FROM `$this->table_tags` AS `t`
297
					LEFT JOIN `$this->table` AS `p`
298
				ON `t`.`id` = `p`.`id`
299
				WHERE
300
					`t`.`tag`	= '%s' AND
301
					`p`.`draft`	= 0 AND
302
					`t`.`lang`	= '%s'
303
				ORDER BY `p`.`date` DESC
304
				LIMIT $from, $number",
305
				$tag,
306
				$lang
307
			]
308
		) ?: [];
309
	}
310
	/**
311
	 * Get number of posts for tag
312
	 *
313
	 * @param int    $tag
314
	 * @param string $lang
315
	 *
316
	 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be array[]|integer|integer[]|string|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...
317
	 */
318
	function get_for_tag_count ($tag, $lang) {
319
		return $this->db()->qfs(
320
			[
321
				"SELECT COUNT(`t`.`id`)
322
				FROM `$this->table_tags` AS `t`
323
					LEFT JOIN `$this->table` AS `p`
324
				ON `t`.`id` = `p`.`id`
325
				WHERE
326
					`t`.`tag`	= '%s' AND
327
					`p`.`draft`	= 0 AND
328
					`t`.`lang`	= '%s'",
329
				$tag,
330
				$lang
331
			]
332
		) ?: 0;
333
	}
334
	/**
335
	 * Get drafts
336
	 *
337
	 * @param int $user
338
	 * @param int $page
339
	 * @param int $number
340
	 *
341
	 * @return int[]
0 ignored issues
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...
342
	 */
343
	function get_drafts ($user, $page, $number) {
344
		$number = (int)$number;
345
		$from   = ($page - 1) * $number;
346
		return $this->db()->qfas(
347
			[
348
				"SELECT `id`
349
				FROM `$this->table`
350
				WHERE
351
					`draft` = 1 AND
352
					`user`	= '%s'
353
				ORDER BY `date` DESC
354
				LIMIT $from, $number",
355
				$user
356
			]
357
		) ?: [];
358
	}
359
	/**
360
	 * Get number of drafts
361
	 *
362
	 * @param int $user
363
	 *
364
	 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be array[]|integer|integer[]|string|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...
365
	 */
366
	function get_drafts_count ($user) {
367
		return $this->db()->qfs(
368
			[
369
				"SELECT COUNT(`id`)
370
				FROM `$this->table`
371
				WHERE
372
					`draft` = 1 AND
373
					`user`	= '%s'",
374
				$user
375
			]
376
		) ?: 0;
377
	}
378
	/**
379
	 * Add new post
380
	 *
381
	 * @param string   $title
382
	 * @param string   $path
383
	 * @param string   $content
384
	 * @param int[]    $sections
385
	 * @param string[] $tags
386
	 * @param bool     $draft
387
	 *
388
	 * @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...
389
	 */
390
	function add ($title, $path, $content, $sections, $tags, $draft) {
391
		if (!$this->check_arguments($content, $sections, $tags)) {
392
			return false;
393
		}
394
		$id = $this->create(
395
			[
396
				User::instance()->id,
397
				$draft ? 0 : time(),
398
				$title,
399
				path($path ?: $title),
400
				$content,
401
				(int)(bool)$draft,
402
				$sections,
403
				$this->prepare_tags($tags)
404
			]
405
		);
406
		if ($id) {
407
			$this->cache_cleanups($id);
408
		}
409
		return $id;
410
	}
411
	/**
412
	 * Transform array of string tags into array of their ids
413
	 *
414
	 * @param string[] $tags
415
	 *
416
	 * @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...
417
	 */
418
	protected function prepare_tags ($tags) {
419
		return Tags::instance()->add($tags) ?: [];
420
	}
421
	/**
422
	 * @param string   $content
423
	 * @param int[]    $sections
424
	 * @param string[] $tags
425
	 *
426
	 * @return bool
427
	 */
428
	protected function check_arguments ($content, &$sections, $tags) {
429
		if (empty($tags) || empty($content)) {
430
			return false;
431
		}
432
		$sections = array_intersect(
433
			array_keys(Sections::instance()->get_list()),
434
			$sections
435
		);
436
		return
437
			$sections && count($sections) <= Config::instance()->module('Blogs')->max_sections;
438
	}
439
	/**
440
	 * @param int $id
441
	 */
442
	protected function cache_cleanups ($id) {
443
		$Cache = $this->cache;
444
		unset(
445
			$Cache->{"posts/$id"},
446
			$Cache->sections,
447
			$Cache->total_count
448
		);
449
	}
450
	/**
451
	 * Set data of specified post
452
	 *
453
	 * @param int      $id
454
	 * @param string   $title
455
	 * @param string   $path
456
	 * @param string   $content
457
	 * @param int[]    $sections
458
	 * @param string[] $tags
459
	 * @param bool     $draft
460
	 *
461
	 * @return bool
462
	 */
463
	function set ($id, $title, $path, $content, $sections, $tags, $draft) {
464
		if (!$this->check_arguments($content, $sections, $tags)) {
465
			return false;
466
		}
467
		$old_data = $this->get($id);
468
		$result   = $this->update(
469
			[
470
				$id,
471
				$old_data['user'],
472
				$old_data['draft'] == 1 && $old_data['date'] == 0 && !$draft ? time() : $old_data['date'],
473
				$title,
474
				path($path ?: $title),
475
				$content,
476
				(int)(bool)$draft,
477
				$sections,
478
				$this->prepare_tags($tags)
479
			]
480
		);
481
		$this->cache_cleanups($id);
482
		return $result;
483
	}
484
	/**
485
	 * Delete specified post
486
	 *
487
	 * @param int $id
488
	 *
489
	 * @return bool
490
	 */
491
	function del ($id) {
492
		$id     = (int)$id;
493
		$result = $this->delete($id);
494
		if ($result) {
495
			$Comments = null;
496
			Event::instance()->fire(
497
				'Comments/instance',
498
				[
499
					'Comments' => &$Comments
500
				]
501
			);
502
			/**
503
			 * @var \cs\modules\Comments\Comments $Comments
504
			 */
505
			if ($Comments) {
506
				$Comments->del_all($id);
507
			}
508
			$this->cache_cleanups($id);
509
		}
510
		return $result;
511
	}
512
	/**
513
	 * Get total count of posts
514
	 *
515
	 * @return int
516
	 */
517
	function get_total_count () {
518
		return $this->cache->get(
519
			'total_count',
520
			function () {
521
				return $this->db()->qfs(
522
					"SELECT COUNT(`id`)
523
					FROM `$this->table`
524
					WHERE `draft` = 0"
525
				);
526
			}
527
		);
528
	}
529
}
530