Issues (1358)

modules/Blogs/Posts.php (18 issues)

1
<?php
2
/**
3
 * @package  Blogs
4
 * @category modules
5
 * @author   Nazar Mokrynskyi <[email protected]>
6
 * @license  0BSD
7
 */
8
namespace cs\modules\Blogs;
9
use
10
	cs\Cache,
11
	cs\Config,
12
	cs\Event,
13
	cs\Language,
14
	cs\User,
15
	cs\CRUD_helpers,
16
	cs\Singleton,
17
	cs\modules\Json_ld\Json_ld;
18
19
/**
20
 * @method static $this instance($check = false)
21
 */
22
class Posts {
23
	use
24
		CRUD_helpers,
25
		Singleton;
26
	protected $data_model                  = [
27
		'id'       => 'int:0',
28
		'user'     => 'int:0',
29
		'date'     => 'int:0',
30
		'title'    => 'ml:text',
31
		'path'     => 'ml:text',
32
		'content'  => 'ml:html',
33
		'draft'    => 'int:0..1',
34
		'sections' => [
35
			'data_model' => [
36
				'id'      => 'int:0',
37
				'section' => 'int:0'
38
			]
39
		],
40
		'tags'     => [
41
			'data_model'     => [
42
				'id'  => 'int:0',
43
				'tag' => 'int:0'
44
			],
45
			'language_field' => 'lang'
46
		]
47
	];
48
	protected $table                       = '[prefix]blogs_posts';
49
	protected $data_model_ml_group         = 'Blogs/posts';
50
	protected $data_model_files_tag_prefix = 'Blogs/posts';
51
	/**
52
	 * @var Cache\Prefix
53
	 */
54
	protected $cache;
55
56
	protected function construct () {
57
		$this->cache = Cache::prefix('Blogs');
58
		if (Config::instance()->module('Blogs')->allow_iframes_without_content) {
0 ignored issues
show
Bug Best Practice introduced by
The property allow_iframes_without_content does not exist on cs\Config\Module_Properties. Since you implemented __get, consider adding a @property annotation.
Loading history...
59
			$this->data_model['content'] = 'ml:html_iframe';
60
		}
61
	}
62
	/**
63
	 * Returns database index
64
	 *
65
	 * @return int
66
	 */
67
	protected function cdb () {
68
		return Config::instance()->module('Blogs')->db('posts');
69
	}
70
	/**
71
	 * Get data of specified post
72
	 *
73
	 * @param int|int[] $id
74
	 *
75
	 * @return array|false
76
	 */
77
	public function get ($id) {
78
		if (is_array($id)) {
79
			return array_map([$this, 'get'], $id);
80
		}
81
		$L    = Language::instance();
82
		$id   = (int)$id;
83
		$data = $this->cache->get(
84
			"posts/$id/$L->clang",
85
			function () use ($id) {
86
				$data = $this->read($id);
87
				if ($data) {
88
					$data['short_content'] = truncate(explode('<!-- pagebreak -->', $data['content'])[0]);
89
					$data['tags']          = $this->read_tags_processing($data['tags']);
0 ignored issues
show
It seems like $data['tags'] can also be of type string; however, parameter $tags of cs\modules\Blogs\Posts::read_tags_processing() does only seem to accept integer[], maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

89
					$data['tags']          = $this->read_tags_processing(/** @scrutinizer ignore-type */ $data['tags']);
Loading history...
90
				}
91
				return $data;
92
			}
93
		);
94
		return $data;
95
	}
96
	/**
97
	 * @param int $page
98
	 * @param int $count
99
	 *
100
	 * @return int[]
101
	 */
102
	public function get_all ($page, $count) {
103
		return $this->search([], $page, $count, 'id');
104
	}
105
	/**
106
	 * Transform tags ids back into array of strings
107
	 *
108
	 * @param int[] $tags
109
	 *
110
	 * @return string[]
111
	 */
112
	protected function read_tags_processing ($tags) {
113
		return array_column(Tags::instance()->get($tags) ?: [], 'text');
114
	}
115
	/**
116
	 * Get data of specified post
117
	 *
118
	 * @param int|int[] $id
119
	 *
120
	 * @return array|false
121
	 */
122
	public function get_as_json_ld ($id) {
123
		$post = $this->get($id);
124
		if (!$post) {
125
			return false;
126
		}
127
		return $this->post_to_jsonld($post);
128
	}
129
	/**
130
	 * @param array|array[] $post
131
	 *
132
	 * @return array
133
	 */
134
	public function post_to_jsonld ($post) {
135
		$base_structure = [
136
			'@context' =>
137
				[
138
					'content'        => 'articleBody',
139
					'title'          => 'headline',
140
					'tags'           => 'keywords',
141
					'datetime'       => null,
142
					'sections_paths' => null,
143
					'tags_paths'     => null
144
				] + Json_ld::context_stub(isset($post[0]) ? $post[0] : $post)
145
		];
146
		if (isset($post[0])) {
147
			return
148
				$base_structure +
149
				[
150
					'@graph' => array_map(
151
						[$this, 'post_to_jsonld_single_post'],
152
						$post
153
					)
154
				];
155
		}
156
		return
157
			$base_structure +
158
			$this->post_to_jsonld_single_post($post);
159
	}
160
	protected function post_to_jsonld_single_post ($post) {
161
		if (preg_match_all('/<img[^>]src=["\'](.*)["\']/Uims', $post['content'], $images)) {
162
			$images = $images[1];
163
		}
164
		$Sections = Sections::instance();
165
		$sections = [];
166
		if ($post['sections'] != [0]) {
167
			$sections = array_column(
168
				$Sections->get($post['sections']),
0 ignored issues
show
It seems like $Sections->get($post['sections']) can also be of type false; however, parameter $input of array_column() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

168
				/** @scrutinizer ignore-type */ $Sections->get($post['sections']),
Loading history...
169
				'title'
170
			);
171
		}
172
		$L            = Language::prefix('blogs_');
173
		$base_url     = Config::instance()->base_url();
174
		$module_path  = path($L->Blogs);
0 ignored issues
show
Bug Best Practice introduced by
The property Blogs does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
175
		$section_path = "$base_url/$module_path/".path($L->section);
0 ignored issues
show
Bug Best Practice introduced by
The property section does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
176
		$tag_path     = "$base_url/$module_path/".path($L->tag);
0 ignored issues
show
Bug Best Practice introduced by
The property tag does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
177
		$url          = "$base_url/$module_path/$post[path]:$post[id]";
178
		return
179
			[
180
				'@id'            => $url,
181
				'@type'          => 'BlogPosting',
182
				'articleSection' => $sections,
183
				'author'         => Json_ld::Person($post['user']),
184
				'datePublished'  => Json_ld::Date($post['date']),
185
				'image'          => $images,
186
				'inLanguage'     => $L->clang,
0 ignored issues
show
Bug Best Practice introduced by
The property clang does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
187
				'url'            => $url,
188
				'datetime'       => $L->to_locale(date($L->_datetime_long, $post['date'] ?: time())),
0 ignored issues
show
Bug Best Practice introduced by
The property _datetime_long does not exist on cs\Language\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
189
				'sections_paths' => array_map(
190
					function ($section) use ($section_path, $Sections) {
191
						$section = $Sections->get($section);
192
						return "$section_path/$section[full_path]";
193
					},
194
					$post['sections']
195
				),
196
				'tags_paths'     => array_map(
197
					function ($tag) use ($tag_path) {
198
						return "$tag_path/$tag";
199
					},
200
					$post['tags']
201
				)
202
			] + $post;
203
	}
204
	/**
205
	 * Get latest posts
206
	 *
207
	 * @param int $page
208
	 * @param int $count
209
	 *
210
	 * @return int[]
211
	 */
212
	public function get_latest_posts ($page, $count) {
213
		$search_parameters = [
214
			'draft' => 0
215
		];
216
		return $this->search($search_parameters, $page, $count, 'date', false) ?: [];
217
	}
218
	/**
219
	 * Get posts for section
220
	 *
221
	 * @param int $section
222
	 * @param int $page
223
	 * @param int $count
224
	 *
225
	 * @return int[]
226
	 */
227
	public function get_for_section ($section, $page, $count) {
228
		$search_parameters = [
229
			'draft'    => 0,
230
			'sections' => [
231
				'section' => $section
232
			]
233
		];
234
		return $this->search($search_parameters, $page, $count, 'date', false) ?: [];
235
	}
236
	/**
237
	 * Get number of posts for section
238
	 *
239
	 * @param int $section
240
	 *
241
	 * @return int
242
	 */
243
	public function get_for_section_count ($section) {
244
		$search_parameters = [
245
			'draft'       => 0,
246
			'sections'    => [
247
				'section' => $section
248
			],
249
			'total_count' => true
250
		];
251
		return $this->search($search_parameters);
252
	}
253
	/**
254
	 * Get posts for tag
255
	 *
256
	 * @param int $tag
257
	 * @param int $page
258
	 * @param int $count
259
	 *
260
	 * @return int[]
261
	 */
262
	public function get_for_tag ($tag, $page, $count) {
263
		$search_parameters = [
264
			'draft' => 0,
265
			'tags'  => [
266
				'tag' => $tag
267
			]
268
		];
269
		return $this->search($search_parameters, $page, $count, 'date', false) ?: [];
270
	}
271
	/**
272
	 * Get number of posts for tag
273
	 *
274
	 * @param int $tag
275
	 *
276
	 * @return int
277
	 */
278
	public function get_for_tag_count ($tag) {
279
		$search_parameters = [
280
			'draft'       => 0,
281
			'tags'        => [
282
				'tag' => $tag
283
			],
284
			'total_count' => true
285
		];
286
		return $this->search($search_parameters);
287
	}
288
	/**
289
	 * Get drafts
290
	 *
291
	 * @param int $user
292
	 * @param int $page
293
	 * @param int $count
294
	 *
295
	 * @return int[]
296
	 */
297
	public function get_drafts ($user, $page, $count) {
298
		$search_parameters = [
299
			'user'  => $user,
300
			'draft' => 1
301
		];
302
		return $this->search($search_parameters, $page, $count, 'date', false) ?: [];
303
	}
304
	/**
305
	 * Get number of drafts
306
	 *
307
	 * @param int $user
308
	 *
309
	 * @return int
310
	 */
311
	public function get_drafts_count ($user) {
312
		$search_parameters = [
313
			'user'        => $user,
314
			'draft'       => 1,
315
			'total_count' => true
316
		];
317
		return $this->search($search_parameters);
318
	}
319
	/**
320
	 * Add new post
321
	 *
322
	 * @param string   $title
323
	 * @param string   $path
324
	 * @param string   $content
325
	 * @param int[]    $sections
326
	 * @param string[] $tags
327
	 * @param bool     $draft
328
	 *
329
	 * @return false|int Id of created post on success of <b>false</> on failure
330
	 */
331
	public function add ($title, $path, $content, $sections, $tags, $draft) {
332
		if (!$this->check_arguments($content, $sections, $tags)) {
333
			return false;
334
		}
335
		$id = $this->create(
336
			User::instance()->id,
0 ignored issues
show
cs\User::instance()->id of type integer is incompatible with the type array expected by parameter $arguments of cs\modules\Blogs\Posts::create(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

336
			/** @scrutinizer ignore-type */ User::instance()->id,
Loading history...
337
			$draft ? 0 : time(),
338
			$title,
0 ignored issues
show
$title of type string is incompatible with the type array expected by parameter $arguments of cs\modules\Blogs\Posts::create(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

338
			/** @scrutinizer ignore-type */ $title,
Loading history...
339
			path($path ?: $title),
340
			$content,
341
			$draft,
0 ignored issues
show
$draft of type boolean is incompatible with the type array expected by parameter $arguments of cs\modules\Blogs\Posts::create(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

341
			/** @scrutinizer ignore-type */ $draft,
Loading history...
342
			$sections,
343
			$this->prepare_tags($tags)
344
		);
345
		if ($id) {
346
			$this->cache_cleanups($id);
0 ignored issues
show
It seems like $id can also be of type string; however, parameter $id of cs\modules\Blogs\Posts::cache_cleanups() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

346
			$this->cache_cleanups(/** @scrutinizer ignore-type */ $id);
Loading history...
347
		}
348
		return $id;
349
	}
350
	/**
351
	 * Transform array of string tags into array of their ids
352
	 *
353
	 * @param string[] $tags
354
	 *
355
	 * @return int[]
356
	 */
357
	protected function prepare_tags ($tags) {
358
		return Tags::instance()->add($tags) ?: [];
359
	}
360
	/**
361
	 * @param string   $content
362
	 * @param int[]    $sections
363
	 * @param string[] $tags
364
	 *
365
	 * @return bool
366
	 */
367
	protected function check_arguments ($content, &$sections, $tags) {
368
		if (empty($tags) || empty($content)) {
369
			return false;
370
		}
371
		$sections = array_intersect(
372
			array_merge([0], array_column(Sections::instance()->get_all(), 'id')),
373
			$sections
374
		);
375
		$sections = array_values($sections);
376
		return $sections && count($sections) <= Config::instance()->module('Blogs')->max_sections;
0 ignored issues
show
Bug Best Practice introduced by
The property max_sections does not exist on cs\Config\Module_Properties. Since you implemented __get, consider adding a @property annotation.
Loading history...
377
	}
378
	/**
379
	 * @param int $id
380
	 */
381
	protected function cache_cleanups ($id) {
382
		$Cache = $this->cache;
383
		unset(
384
			$Cache->{"posts/$id"},
385
			$Cache->sections,
0 ignored issues
show
Bug Best Practice introduced by
The property sections does not exist on cs\Cache\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
386
			$Cache->total_count
0 ignored issues
show
Bug Best Practice introduced by
The property total_count does not exist on cs\Cache\Prefix. Since you implemented __get, consider adding a @property annotation.
Loading history...
387
		);
388
	}
389
	/**
390
	 * Set data of specified post
391
	 *
392
	 * @param int      $id
393
	 * @param string   $title
394
	 * @param string   $path
395
	 * @param string   $content
396
	 * @param int[]    $sections
397
	 * @param string[] $tags
398
	 * @param bool     $draft
399
	 *
400
	 * @return bool
401
	 */
402
	public function set ($id, $title, $path, $content, $sections, $tags, $draft) {
403
		if (!$this->check_arguments($content, $sections, $tags)) {
404
			return false;
405
		}
406
		$old_data = $this->get($id);
407
		$result   = $this->update(
408
			$id,
0 ignored issues
show
$id of type integer is incompatible with the type array expected by parameter $arguments of cs\modules\Blogs\Posts::update(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

408
			/** @scrutinizer ignore-type */ $id,
Loading history...
409
			$old_data['user'],
410
			$old_data['draft'] == 1 && $old_data['date'] == 0 && !$draft ? time() : $old_data['date'],
411
			$title,
0 ignored issues
show
$title of type string is incompatible with the type array expected by parameter $arguments of cs\modules\Blogs\Posts::update(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

411
			/** @scrutinizer ignore-type */ $title,
Loading history...
412
			path($path ?: $title),
413
			$content,
414
			$draft,
0 ignored issues
show
$draft of type boolean is incompatible with the type array expected by parameter $arguments of cs\modules\Blogs\Posts::update(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

414
			/** @scrutinizer ignore-type */ $draft,
Loading history...
415
			$sections,
416
			$this->prepare_tags($tags)
417
		);
418
		$this->cache_cleanups($id);
419
		return $result;
420
	}
421
	/**
422
	 * Delete specified post
423
	 *
424
	 * @param int $id
425
	 *
426
	 * @return bool
427
	 */
428
	public function del ($id) {
429
		$id     = (int)$id;
430
		$result = $this->delete($id);
431
		if ($result) {
432
			Event::instance()->fire(
433
				'Comments/deleted',
434
				[
435
					'module' => 'Blogs',
436
					'item'   => $id
437
				]
438
			);
439
			$this->cache_cleanups($id);
440
		}
441
		return $result;
442
	}
443
	/**
444
	 * Get total count of posts
445
	 *
446
	 * @return int
447
	 */
448
	public function get_total_count () {
449
		return $this->cache->get(
450
			'total_count',
451
			function () {
452
				$search_parameters = [
453
					'draft'       => 0,
454
					'total_count' => true
455
				];
456
				return $this->search($search_parameters);
457
			}
458
		);
459
	}
460
}
461