Test Failed
Push — master ( e3c39f...fe570d )
by Mihail
07:20
created

EntityCategoryList   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 298
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 35
eloc 135
dl 0
loc 298
rs 9.6
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getContentCount() 0 3 1
A setItemLimit() 0 3 1
B buildCategory() 0 40 6
B findItems() 0 42 7
A findCategory() 0 12 2
B buildContent() 0 71 11
A before() 0 14 2
A findCategories() 0 22 4
A __construct() 0 7 1
1
<?php
2
3
namespace Apps\Model\Front\Content;
4
5
use Apps\ActiveRecord\Content;
6
use Apps\ActiveRecord\Content as ContentRecord;
7
use Apps\ActiveRecord\ContentCategory;
8
use Apps\ActiveRecord\User;
9
use Ffcms\Core\App;
10
use Ffcms\Core\Arch\Model;
11
use Ffcms\Core\Exception\ForbiddenException;
12
use Ffcms\Core\Exception\NotFoundException;
13
use Ffcms\Core\Helper\Date;
14
use Ffcms\Core\Helper\FileSystem\File;
15
use Ffcms\Core\Helper\Text;
16
use Ffcms\Core\Helper\Type\Any;
17
use Ffcms\Core\Helper\Type\Arr;
18
use Ffcms\Core\Helper\Type\Str;
19
use Ffcms\Templex\Url\Url;
20
21
/**
22
 * Class EntityCategoryList. Build content and category data to display in views based on pathway.
23
 * @package Apps\Model\Front\Content
24
 */
25
class EntityCategoryList extends Model
26
{
27
    // page breaker to split short and full content
28
    const PAGE_BREAK = '<div style="page-break-after: always">';
29
30
    // properties to display: content item collection, category data, etc
31
    public $items;
32
    public $category;
33
    public $categories;
34
35
    // private items used on model building
36
    private $_path;
37
    private $_configs;
38
    private $_page = 0;
39
    private $_sort;
40
    private $_contentCount = 0;
41
42
    private $_currentCategory;
43
    private $_allCategories;
44
    private $_catIds;
45
46
    /** @var bool|int */
47
    private $_customItemLimit = false;
48
49
    /**
50
     * EntityCategoryList constructor. Pass pathway as string and data of multi-category system
51
     * @param string $path
52
     * @param array $configs
53
     * @param int $offset
54
     * @param string $sort
55
     */
56
    public function __construct($path, array $configs, $offset = 0, $sort = 'newest')
57
    {
58
        $this->_path = $path;
59
        $this->_configs = $configs;
60
        $this->_page = (int)$offset;
61
        $this->_sort = $sort;
62
        parent::__construct();
63
    }
64
65
    /**
66
     * Build model properties
67
     * @throws ForbiddenException
68
     * @throws NotFoundException
69
     */
70
    public function before()
71
    {
72
        // find one or more categories where we must looking for content items
73
        if ((int)$this->_configs['multiCategories'] === 1) {
74
            $this->findCategories();
75
        } else {
76
            $this->findCategory();
77
        }
78
79
        // try to find content items depend of founded category(ies)
80
        $records = $this->findItems();
81
        // build output information
82
        $this->buildCategory();
83
        $this->buildContent($records);
84
    }
85
86
    /**
87
     * Set select items limit count
88
     * @param int $limit
89
     */
90
    public function setItemLimit($limit)
91
    {
92
        $this->_customItemLimit = (int)$limit;
93
    }
94
95
    /**
96
     * Find current category data
97
     * @throws NotFoundException
98
     */
99
    private function findCategory()
100
    {
101
        // get current category
102
        $query = ContentCategory::where('path', '=', $this->_path);
103
        if ($query->count() !== 1) {
104
            throw new NotFoundException(__('Category is not founded'));
105
        }
106
107
        // set properties from query
108
        $this->_allCategories = $query->get();
109
        $this->_currentCategory = $query->first();
110
        $this->_catIds[] = $this->_currentCategory['id'];
111
    }
112
113
    /**
114
     * Find multiple categories child of current
115
     * @throws NotFoundException
116
     */
117
    private function findCategories()
118
    {
119
        // get all categories for current path and child of it
120
        $query = ContentCategory::where('path', 'like', $this->_path . '%');
121
        if ($query->count() < 1) {
122
            throw new NotFoundException(__('Category is not founded'));
123
        }
124
        // get result as object
125
        $result = $query->get();
126
127
        // extract ids from result as array by key id
128
        $this->_catIds = Arr::pluck('id', $result->toArray());
129
130
        // get current category matching
131
        foreach ($result as $row) {
132
            if ($row->path === $this->_path) {
133
                $this->_currentCategory = $row;
134
            }
135
        }
136
137
        // set result to property
138
        $this->_allCategories = $result;
139
    }
140
141
    /**
142
     * Find content items on database and return rows as object
143
     * @return mixed
144
     * @throws NotFoundException
145
     */
146
    private function findItems()
147
    {
148
        if (!Any::isArray($this->_catIds) || count($this->_catIds) < 1) {
149
            throw new NotFoundException(__('Category is not founded'));
150
        }
151
152
        // calculate selection offset
153
        $itemPerPage = (int)$this->_configs['itemPerCategory'];
154
        // check if custom itemlimit defined over model api
155
        if ($this->_customItemLimit !== false) {
156
            $itemPerPage = (int)$this->_customItemLimit;
157
        }
158
159
        $offset = $this->_page * $itemPerPage;
160
161
        // get all items from categories
162
        $query = ContentRecord::with(['user', 'user.profile'])
163
            ->whereIn('category_id', $this->_catIds)
164
            ->where('display', '=', 1);
165
        // save count
166
        $this->_contentCount = $query->count();
167
168
        // apply sort by
169
        switch ($this->_sort) {
170
            case 'rating':
171
                $query = $query->orderBy('rating', 'DESC');
172
                break;
173
            case 'views':
174
                $query = $query->orderBy('views', 'DESC');
175
                break;
176
            default:
177
                $query = $query->orderBy('important', 'DESC')->orderBy('created_at', 'DESC');
178
                break;
179
        }
180
181
        // get all items if offset is negative
182
        if ($itemPerPage < 0) {
183
            return $query->get();
184
        }
185
186
        // make select based on offset
187
        return $query->skip($offset)->take($itemPerPage)->get();
188
    }
189
190
    /**
191
     * Prepare category data to display
192
     * @throws ForbiddenException
193
     */
194
    private function buildCategory()
195
    {
196
        $catConfigs = $this->_currentCategory->configs;
197
        // prepare rss url link for current category if enabled
198
        $rssUrl = false;
199
        if ((int)$this->_configs['rss'] === 1 && (int)$catConfigs['showRss'] === 1) {
200
            $rssUrl = App::$Alias->baseUrl . '/content/rss/' . $this->_currentCategory->path;
201
            $rssUrl = rtrim($rssUrl, '/');
202
        }
203
204
        // prepare sorting urls
205
        $catSortParams = [];
206
        if (App::$Request->query->get('page')) {
207
            $catSortParams['page'] = (int)App::$Request->query->get('page');
208
        }
209
210
        $catSortUrls = [
211
            'views' => Url::to('content/list', [$this->_currentCategory->path], Arr::merge($catSortParams, ['sort' => 'views'])),
212
            'rating' => Url::to('content/list', [$this->_currentCategory->path], Arr::merge($catSortParams, ['sort' => 'rating'])),
213
            'newest' => Url::to('content/list', [$this->_currentCategory->path], $catSortParams)
214
        ];
215
216
        // prepare current category data to output (unserialize locales and strip tags)
217
        $this->category = [
218
            'title' => $this->_currentCategory->getLocaled('title'),
219
            'description' => $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
        if (!(bool)$this->category['configs']['showCategory']) {
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 NotFoundException
241
     */
242
    private function buildContent($records)
243
    {
244
        $nullItems = 0;
245
        foreach ($records as $row) {
246
            /** @var Content $row */
247
            // check title length on current language locale
248
            $localeTitle = $row->getLocaled('title');
249
            if (Str::likeEmpty($localeTitle)) {
0 ignored issues
show
Bug introduced by zenn
It seems like $localeTitle can also be of type array; however, parameter $string of Ffcms\Core\Helper\Type\Str::likeEmpty() does only seem to accept null|string, 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

249
            if (Str::likeEmpty(/** @scrutinizer ignore-type */ $localeTitle)) {
Loading history...
250
                ++$nullItems;
251
                continue;
252
            }
253
254
            // get snippet from full text for current locale
255
            $text = Text::snippet($row->getLocaled('text'));
0 ignored issues
show
Bug introduced by zenn
It seems like $row->getLocaled('text') can also be of type array and null; however, parameter $text of Ffcms\Core\Helper\Text::snippet() does only seem to accept string, 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

255
            $text = Text::snippet(/** @scrutinizer ignore-type */ $row->getLocaled('text'));
Loading history...
256
257
            $itemPath = $this->categories[$row->category_id]->path;
258
            if (!Str::likeEmpty($itemPath)) {
259
                $itemPath .= '/';
260
            }
261
            $itemPath .= $row->path;
262
263
            // prepare tags data
264
            $tags = $row->getLocaled('meta_keywords');
265
            if (!Str::likeEmpty($tags)) {
266
                $tags = explode(',', $tags);
0 ignored issues
show
Bug introduced by zenn
It seems like $tags can also be of type array; however, parameter $string of explode() does only seem to accept string, 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

266
                $tags = explode(',', /** @scrutinizer ignore-type */ $tags);
Loading history...
267
            } else {
268
                $tags = null;
269
            }
270
271
            $owner = $row->user;
272
            // make a fake if user is not exist over id
273
            if (!$owner) {
274
                $owner = new User();
275
            }
276
277
            // check if current user can rate item
278
            $ignoredRate = App::$Session->get('content.rate.ignore');
279
            $canRate = true;
280
            if (Any::isArray($ignoredRate) && Arr::in((string)$row->id, $ignoredRate)) {
281
                $canRate = false;
282
            }
283
284
            if (!App::$User->isAuth()) {
285
                $canRate = false;
286
            } elseif ($owner->getId() === App::$User->identity()->getId()) { // own item
287
                $canRate = false;
288
            }
289
290
            // build result array
291
            $this->items[] = [
292
                'id' => $row->id,
293
                'title' => $localeTitle,
294
                'text' => $text,
295
                'date' => Date::humanize($row->created_at),
296
                'updated' => $row->updated_at,
297
                'author' => $owner,
298
                'poster' => $row->getPosterUri(),
299
                'thumb' => $row->getPosterThumbUri(),
300
                'thumbSize' => File::size($row->getPosterThumbUri()),
301
                'views' => (int)$row->views,
302
                'rating' => (int)$row->rating,
303
                'canRate' => $canRate,
304
                'category' => $this->categories[$row->category_id],
305
                'uri' => '/content/read/' . $itemPath,
306
                'tags' => $tags,
307
                'important' => (bool)$row->important
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
}
325