Passed
Push — master ( ef4849...3d6fe1 )
by Mihail
05:23
created

EntityCategoryList::setItemLimit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
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')),
0 ignored issues
show
Bug introduced by
It seems like $this->_currentCategory->getLocaled('title') targeting Ffcms\Core\Arch\ActiveModel::getLocaled() can also be of type null; however, Ffcms\Core\Helper\Security::strip_tags() does only seem to accept string|array, maybe add an additional type check?

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.

Loading history...
219
            'description' => App::$Security->strip_tags($this->_currentCategory->getLocaled('description')),
0 ignored issues
show
Bug introduced by
It seems like $this->_currentCategory-...tLocaled('description') targeting Ffcms\Core\Arch\ActiveModel::getLocaled() can also be of type null; however, Ffcms\Core\Helper\Security::strip_tags() does only seem to accept string|array, maybe add an additional type check?

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.

Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
}