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[] |
|
|
|
|
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[] |
|
|
|
|
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 |
|
|
|
|
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[] |
|
|
|
|
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 |
|
|
|
|
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[] |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
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[] |
|
|
|
|
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
|
|
|
|
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[]
orarray<String>
.