Completed
Push — master ( 7ec011...e103de )
by Nazar
04:16
created

Posts::cache_cleanups()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 8
rs 9.4285
c 1
b 0
f 0
cc 1
eloc 6
nc 1
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,
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, $L) {
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 posts for tag
263
	 *
264
	 * @param int    $tag
265
	 * @param string $lang
266
	 * @param int    $page
267
	 * @param int    $number
268
	 *
269
	 * @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...
270
	 */
271
	function get_for_tag ($tag, $lang, $page, $number) {
272
		$number = (int)$number;
273
		$from   = ($page - 1) * $number;
274
		return $this->db()->qfas(
275
			[
276
				"SELECT `t`.`id`
277
				FROM `$this->table_tags` AS `t`
278
					LEFT JOIN `$this->table` AS `p`
279
				ON `t`.`id` = `p`.`id`
280
				WHERE
281
					`t`.`tag`	= '%s' AND
282
					`p`.`draft`	= 0 AND
283
					`t`.`lang`	= '%s'
284
				ORDER BY `p`.`date` DESC
285
				LIMIT $from, $number",
286
				$tag,
287
				$lang
288
			]
289
		) ?: [];
290
	}
291
	/**
292
	 * Get number of posts for tag
293
	 *
294
	 * @param int    $tag
295
	 * @param string $lang
296
	 *
297
	 * @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...
298
	 */
299
	function get_for_tag_count ($tag, $lang) {
300
		return $this->db()->qfs(
301
			[
302
				"SELECT COUNT(`t`.`id`)
303
				FROM `$this->table_tags` AS `t`
304
					LEFT JOIN `$this->table` AS `p`
305
				ON `t`.`id` = `p`.`id`
306
				WHERE
307
					`t`.`tag`	= '%s' AND
308
					`p`.`draft`	= 0 AND
309
					`t`.`lang`	= '%s'",
310
				$tag,
311
				$lang
312
			]
313
		) ?: 0;
314
	}
315
	/**
316
	 * Get drafts
317
	 *
318
	 * @param int $user
319
	 * @param int $page
320
	 * @param int $number
321
	 *
322
	 * @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...
323
	 */
324
	function get_drafts ($user, $page, $number) {
325
		$number = (int)$number;
326
		$from   = ($page - 1) * $number;
327
		return $this->db()->qfas(
328
			[
329
				"SELECT `id`
330
				FROM `$this->table`
331
				WHERE
332
					`draft` = 1 AND
333
					`user`	= '%s'
334
				ORDER BY `date` DESC
335
				LIMIT $from, $number",
336
				$user
337
			]
338
		) ?: [];
339
	}
340
	/**
341
	 * Get number of drafts
342
	 *
343
	 * @param int $user
344
	 *
345
	 * @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...
346
	 */
347
	function get_drafts_count ($user) {
348
		return $this->db()->qfs(
349
			[
350
				"SELECT COUNT(`id`)
351
				FROM `$this->table`
352
				WHERE
353
					`draft` = 1 AND
354
					`user`	= '%s'",
355
				$user
356
			]
357
		) ?: 0;
358
	}
359
	/**
360
	 * Add new post
361
	 *
362
	 * @param string   $title
363
	 * @param string   $path
364
	 * @param string   $content
365
	 * @param int[]    $sections
366
	 * @param string[] $tags
367
	 * @param bool     $draft
368
	 *
369
	 * @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...
370
	 */
371
	function add ($title, $path, $content, $sections, $tags, $draft) {
372
		if (!$this->check_arguments($content, $sections, $tags)) {
373
			return false;
374
		}
375
		$id = $this->create(
376
			[
377
				User::instance()->id,
378
				$draft ? 0 : time(),
379
				$title,
380
				path($path ?: $title),
381
				$content,
382
				(int)(bool)$draft,
383
				$sections,
384
				$this->prepare_tags($tags)
385
			]
386
		);
387
		if ($id) {
388
			$this->cache_cleanups($id);
389
		}
390
		return $id;
391
	}
392
	/**
393
	 * Transform array of string tags into array of their ids
394
	 *
395
	 * @param string[] $tags
396
	 *
397
	 * @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...
398
	 */
399
	protected function prepare_tags ($tags) {
400
		return Tags::instance()->add($tags) ?: [];
401
	}
402
	/**
403
	 * @param string   $content
404
	 * @param int[]    $sections
405
	 * @param string[] $tags
406
	 *
407
	 * @return bool
408
	 */
409
	protected function check_arguments ($content, &$sections, $tags) {
410
		if (empty($tags) || empty($content)) {
411
			return false;
412
		}
413
		$sections = array_intersect(
414
			array_keys(Sections::instance()->get_list()),
415
			$sections
416
		);
417
		return
418
			$sections && count($sections) <= Config::instance()->module('Blogs')->max_sections;
419
	}
420
	/**
421
	 * @param int $id
422
	 */
423
	protected function cache_cleanups ($id) {
424
		$Cache = $this->cache;
425
		unset(
426
			$Cache->{"posts/$id"},
427
			$Cache->sections,
428
			$Cache->total_count
429
		);
430
	}
431
	/**
432
	 * Set data of specified post
433
	 *
434
	 * @param int      $id
435
	 * @param string   $title
436
	 * @param string   $path
437
	 * @param string   $content
438
	 * @param int[]    $sections
439
	 * @param string[] $tags
440
	 * @param bool     $draft
441
	 *
442
	 * @return bool
443
	 */
444
	function set ($id, $title, $path, $content, $sections, $tags, $draft) {
445
		if (!$this->check_arguments($content, $sections, $tags)) {
446
			return false;
447
		}
448
		$old_data = $this->get($id);
449
		$result   = $this->update(
450
			[
451
				$id,
452
				$old_data['user'],
453
				$old_data['draft'] == 1 && $old_data['date'] == 0 && !$draft ? time() : $old_data['date'],
454
				$title,
455
				path($path ?: $title),
456
				$content,
457
				(int)(bool)$draft,
458
				$sections,
459
				$this->prepare_tags($tags)
460
			]
461
		);
462
		$this->cache_cleanups($id);
463
		return $result;
464
	}
465
	/**
466
	 * Delete specified post
467
	 *
468
	 * @param int $id
469
	 *
470
	 * @return bool
471
	 */
472
	function del ($id) {
473
		$id     = (int)$id;
474
		$result = $this->delete($id);
475
		if ($result) {
476
			$Comments = null;
477
			Event::instance()->fire(
478
				'Comments/instance',
479
				[
480
					'Comments' => &$Comments
481
				]
482
			);
483
			/**
484
			 * @var \cs\modules\Comments\Comments $Comments
485
			 */
486
			if ($Comments) {
487
				$Comments->del_all($id);
488
			}
489
			$this->cache_cleanups($id);
490
		}
491
		return $result;
492
	}
493
	/**
494
	 * Get total count of posts
495
	 *
496
	 * @return int
497
	 */
498
	function get_total_count () {
499
		return $this->cache->get(
500
			'total_count',
501
			function () {
502
				return $this->db()->qfs(
503
					"SELECT COUNT(`id`)
504
					FROM `$this->table`
505
					WHERE `draft` = 0"
506
				);
507
			}
508
		);
509
	}
510
}
511