1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Apps\Model\Front\Content; |
4
|
|
|
|
5
|
|
|
|
6
|
|
|
use Apps\ActiveRecord\Content; |
7
|
|
|
use Apps\ActiveRecord\ContentCategory; |
8
|
|
|
use Apps\ActiveRecord\Content as ContentRecord; |
9
|
|
|
use Apps\ActiveRecord\User; |
10
|
|
|
use Ffcms\Core\App; |
11
|
|
|
use Ffcms\Core\Arch\Model; |
12
|
|
|
use Ffcms\Core\Exception\ForbiddenException; |
13
|
|
|
use Ffcms\Core\Exception\NotFoundException; |
14
|
|
|
use Ffcms\Core\Helper\Date; |
15
|
|
|
use Ffcms\Core\Helper\FileSystem\File; |
16
|
|
|
use Ffcms\Core\Helper\Serialize; |
17
|
|
|
use Ffcms\Core\Helper\Text; |
18
|
|
|
use Ffcms\Core\Helper\Type\Arr; |
19
|
|
|
use Ffcms\Core\Helper\Type\Obj; |
20
|
|
|
use Ffcms\Core\Helper\Type\Str; |
21
|
|
|
use Ffcms\Core\Helper\Url; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Class EntityCategoryList. Build content and category data to display in views based on pathway. |
25
|
|
|
* @package Apps\Model\Front\Content |
26
|
|
|
*/ |
27
|
|
|
class EntityCategoryList extends Model |
28
|
|
|
{ |
29
|
|
|
// page breaker to split short and full content |
30
|
|
|
const PAGE_BREAK = '<div style="page-break-after: always">'; |
31
|
|
|
|
32
|
|
|
// properties to display: content item collection, category data, etc |
33
|
|
|
public $items; |
34
|
|
|
public $category; |
35
|
|
|
public $categories; |
36
|
|
|
|
37
|
|
|
// private items used on model building |
38
|
|
|
private $_path; |
39
|
|
|
private $_configs; |
40
|
|
|
private $_page = 0; |
41
|
|
|
private $_sort; |
42
|
|
|
private $_contentCount = 0; |
43
|
|
|
|
44
|
|
|
private $_currentCategory; |
45
|
|
|
private $_allCategories; |
46
|
|
|
private $_catIds; |
47
|
|
|
|
48
|
|
|
/** @var bool|int */ |
49
|
|
|
private $_customItemLimit = false; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* EntityCategoryList constructor. Pass pathway as string and data of multi-category system |
53
|
|
|
* @param string $path |
54
|
|
|
* @param array $configs |
55
|
|
|
* @param int $offset |
56
|
|
|
* @param string $sort |
57
|
|
|
*/ |
58
|
|
|
public function __construct($path, array $configs, $offset = 0, $sort = 'newest') |
59
|
|
|
{ |
60
|
|
|
$this->_path = $path; |
61
|
|
|
$this->_configs = $configs; |
62
|
|
|
$this->_page = (int)$offset; |
63
|
|
|
$this->_sort = $sort; |
64
|
|
|
parent::__construct(); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Build model properties |
69
|
|
|
* @throws ForbiddenException |
70
|
|
|
* @throws NotFoundException |
71
|
|
|
*/ |
72
|
|
|
public function before() |
73
|
|
|
{ |
74
|
|
|
// find one or more categories where we must looking for content items |
75
|
|
|
if ((int)$this->_configs['multiCategories'] === 1) { |
76
|
|
|
$this->findCategories(); |
77
|
|
|
} else { |
78
|
|
|
$this->findCategory(); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
// try to find content items depend of founded category(ies) |
82
|
|
|
$records = $this->findItems(); |
83
|
|
|
// build output information |
84
|
|
|
$this->buildCategory(); |
85
|
|
|
$this->buildContent($records); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Set select items limit count |
90
|
|
|
* @param int $limit |
91
|
|
|
*/ |
92
|
|
|
public function setItemLimit($limit) |
93
|
|
|
{ |
94
|
|
|
$this->_customItemLimit = (int)$limit; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Find current category data |
99
|
|
|
* @throws NotFoundException |
100
|
|
|
*/ |
101
|
|
|
private function findCategory() |
102
|
|
|
{ |
103
|
|
|
// get current category |
104
|
|
|
$query = ContentCategory::where('path', '=', $this->_path); |
105
|
|
|
if ($query->count() !== 1) { |
106
|
|
|
throw new NotFoundException(__('Category is not founded')); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
// set properties from query |
110
|
|
|
$this->_allCategories = $query->get(); |
111
|
|
|
$this->_currentCategory = $query->first(); |
112
|
|
|
$this->_catIds[] = $this->_currentCategory['id']; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Find multiple categories child of current |
117
|
|
|
* @throws NotFoundException |
118
|
|
|
*/ |
119
|
|
|
private function findCategories() |
120
|
|
|
{ |
121
|
|
|
// get all categories for current path and child of it |
122
|
|
|
$query = ContentCategory::where('path', 'like', $this->_path . '%'); |
123
|
|
|
if ($query->count() < 1) { |
124
|
|
|
throw new NotFoundException(__('Category is not founded')); |
125
|
|
|
} |
126
|
|
|
// get result as object |
127
|
|
|
$result = $query->get(); |
128
|
|
|
|
129
|
|
|
// extract ids from result as array by key id |
130
|
|
|
$this->_catIds = Arr::ploke('id', $result->toArray()); |
131
|
|
|
|
132
|
|
|
// get current category matching |
133
|
|
|
foreach ($result as $row) { |
134
|
|
|
if ($row->path === $this->_path) { |
135
|
|
|
$this->_currentCategory = $row; |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
// set result to property |
140
|
|
|
$this->_allCategories = $result; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Find content items on database and return rows as object |
145
|
|
|
* @return mixed |
146
|
|
|
* @throws NotFoundException |
147
|
|
|
*/ |
148
|
|
|
private function findItems() |
149
|
|
|
{ |
150
|
|
|
if (!Obj::isArray($this->_catIds) || count($this->_catIds) < 1) { |
151
|
|
|
throw new NotFoundException(__('Category is not founded')); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
// calculate selection offset |
155
|
|
|
$itemPerPage = (int)$this->_configs['itemPerCategory']; |
156
|
|
|
// check if custom itemlimit defined over model api |
157
|
|
|
if ($this->_customItemLimit !== false) { |
158
|
|
|
$itemPerPage = (int)$this->_customItemLimit; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
$offset = $this->_page * $itemPerPage; |
162
|
|
|
|
163
|
|
|
// get all items from categories |
164
|
|
|
$query = ContentRecord::whereIn('category_id', $this->_catIds) |
165
|
|
|
->where('display', '=', 1); |
166
|
|
|
// save count |
167
|
|
|
$this->_contentCount = $query->count(); |
168
|
|
|
|
169
|
|
|
// apply sort by |
170
|
|
|
switch ($this->_sort) { |
171
|
|
|
case 'rating': |
172
|
|
|
$query = $query->orderBy('rating', 'DESC'); |
173
|
|
|
break; |
174
|
|
|
case 'views': |
175
|
|
|
$query = $query->orderBy('views', 'DESC'); |
176
|
|
|
break; |
177
|
|
|
default: |
178
|
|
|
$query = $query->orderBy('created_at', 'DESC'); |
179
|
|
|
break; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
// get all items if offset is negative |
183
|
|
|
if ($itemPerPage < 0) { |
184
|
|
|
return $query->get(); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
// make select based on offset |
188
|
|
|
return $query->skip($offset)->take($itemPerPage)->get(); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Prepare category data to display |
193
|
|
|
* @throws ForbiddenException |
194
|
|
|
*/ |
195
|
|
|
private function buildCategory() |
196
|
|
|
{ |
197
|
|
|
$catConfigs = Serialize::decode($this->_currentCategory->configs); |
198
|
|
|
// prepare rss url link for current category if enabled |
199
|
|
|
$rssUrl = false; |
200
|
|
|
if ((int)$this->_configs['rss'] === 1 && (int)$catConfigs['showRss'] === 1) { |
201
|
|
|
$rssUrl = App::$Alias->baseUrl . '/content/rss/' . $this->_currentCategory->path; |
202
|
|
|
$rssUrl = rtrim($rssUrl, '/'); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
// prepare sorting urls |
206
|
|
|
$catSortParams = []; |
207
|
|
|
if (App::$Request->query->get('page') !== null) { |
208
|
|
|
$catSortParams['page'] = (int)App::$Request->query->get('page'); |
209
|
|
|
} |
210
|
|
|
$catSortUrls = [ |
211
|
|
|
'views' => Url::to('content/list', $this->_currentCategory->path, null, Arr::merge($catSortParams, ['sort' => 'views']), false), |
212
|
|
|
'rating' => Url::to('content/list', $this->_currentCategory->path, null, Arr::merge($catSortParams, ['sort' => 'rating']), false), |
213
|
|
|
'newest' => Url::to('content/list', $this->_currentCategory->path, null, $catSortParams, false) |
214
|
|
|
]; |
215
|
|
|
|
216
|
|
|
// prepare current category data to output (unserialize locales and strip tags) |
217
|
|
|
$this->category = [ |
218
|
|
|
'title' => App::$Security->strip_tags($this->_currentCategory->getLocaled('title')), |
|
|
|
|
219
|
|
|
'description' => App::$Security->strip_tags($this->_currentCategory->getLocaled('description')), |
|
|
|
|
220
|
|
|
'configs' => $catConfigs, |
221
|
|
|
'path' => $this->_currentCategory->path, |
222
|
|
|
'rss' => $rssUrl, |
223
|
|
|
'sort' => $catSortUrls |
224
|
|
|
]; |
225
|
|
|
|
226
|
|
|
// check if this category is hidden |
227
|
|
View Code Duplication |
if ((int)$this->category['configs']['showCategory'] !== 1) { |
|
|
|
|
228
|
|
|
throw new ForbiddenException(__('This category is not available to view')); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
// make sorted tree of categories to display in breadcrumbs |
232
|
|
|
foreach ($this->_allCategories as $cat) { |
233
|
|
|
$this->categories[$cat->id] = $cat; |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Build content data to model properties |
239
|
|
|
* @param $records |
240
|
|
|
* @throws ForbiddenException |
241
|
|
|
* @throws NotFoundException |
242
|
|
|
*/ |
243
|
|
|
private function buildContent($records) |
244
|
|
|
{ |
245
|
|
|
$nullItems = 0; |
246
|
|
|
foreach ($records as $row) { |
247
|
|
|
/** @var Content $row */ |
248
|
|
|
|
249
|
|
|
// check title length on current language locale |
250
|
|
|
$localeTitle = App::$Security->strip_tags($row->getLocaled('title')); |
251
|
|
|
if (Str::likeEmpty($localeTitle)) { |
252
|
|
|
++$nullItems; |
253
|
|
|
continue; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
// get snippet from full text for current locale |
257
|
|
|
$text = Text::snippet($row->getLocaled('text')); |
258
|
|
|
|
259
|
|
|
$itemPath = $this->categories[$row->category_id]->path; |
260
|
|
|
if (!Str::likeEmpty($itemPath)) { |
261
|
|
|
$itemPath .= '/'; |
262
|
|
|
} |
263
|
|
|
$itemPath .= $row->path; |
264
|
|
|
|
265
|
|
|
// prepare tags data |
266
|
|
|
$tags = $row->getLocaled('meta_keywords'); |
267
|
|
|
if (!Str::likeEmpty($tags)) { |
268
|
|
|
$tags = explode(',', $tags); |
269
|
|
|
} else { |
270
|
|
|
$tags = null; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
$owner = App::$User->identity($row->author_id); |
274
|
|
|
// make a fake if user is not exist over id |
275
|
|
|
if ($owner === null) { |
276
|
|
|
$owner = new User(); |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
// check if current user can rate item |
280
|
|
|
$ignoredRate = App::$Session->get('content.rate.ignore'); |
281
|
|
|
$canRate = true; |
282
|
|
|
if (Obj::isArray($ignoredRate) && Arr::in((string)$row->id, $ignoredRate)) { |
283
|
|
|
$canRate = false; |
284
|
|
|
} |
285
|
|
|
if (!App::$User->isAuth()) { |
286
|
|
|
$canRate = false; |
287
|
|
|
} elseif ($owner->getId() === App::$User->identity()->getId()) { // own item |
288
|
|
|
$canRate = false; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
// build result array |
292
|
|
|
$this->items[] = [ |
293
|
|
|
'id' => $row->id, |
294
|
|
|
'title' => $localeTitle, |
295
|
|
|
'text' => $text, |
296
|
|
|
'date' => Date::humanize($row->created_at), |
297
|
|
|
'updated' => $row->updated_at, |
298
|
|
|
'author' => $owner, |
299
|
|
|
'poster' => $row->getPosterUri(), |
300
|
|
|
'thumb' => $row->getPosterThumbUri(), |
301
|
|
|
'thumbSize' => File::size($row->getPosterThumbUri()), |
302
|
|
|
'views' => (int)$row->views, |
303
|
|
|
'rating' => (int)$row->rating, |
304
|
|
|
'canRate' => $canRate, |
305
|
|
|
'category' => $this->categories[$row->category_id], |
306
|
|
|
'uri' => '/content/read/' . $itemPath, |
307
|
|
|
'tags' => $tags |
308
|
|
|
]; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
if ($nullItems === $this->_contentCount) { |
312
|
|
|
throw new NotFoundException(__('Content is not founded')); |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Get content items count |
318
|
|
|
* @return int |
319
|
|
|
*/ |
320
|
|
|
public function getContentCount() |
321
|
|
|
{ |
322
|
|
|
return $this->_contentCount; |
323
|
|
|
} |
324
|
|
|
} |
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.