Passed
Push — master ( a29a7e...12a432 )
by Mihail
08:06
created

EntityCategoryList::getContentCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
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\Obj;
19
use Ffcms\Core\Helper\Type\Str;
20
use Ffcms\Core\Helper\Url;
21
22
/**
23
 * Class EntityCategoryList. Build content and category data to display in views based on pathway.
24
 * @package Apps\Model\Front\Content
25
 */
26
class EntityCategoryList extends Model
27
{
28
    // page breaker to split short and full content
29
    const PAGE_BREAK = '<div style="page-break-after: always">';
30
31
    // properties to display: content item collection, category data, etc
32
    public $items;
33
    public $category;
34
    public $categories;
35
36
    // private items used on model building
37
    private $_path;
38
    private $_configs;
39
    private $_page = 0;
40
    private $_sort;
41
    private $_contentCount = 0;
42
43
    private $_currentCategory;
44
    private $_allCategories;
45
    private $_catIds;
46
47
    /** @var bool|int */
48
    private $_customItemLimit = false;
49
50
    /**
51
     * EntityCategoryList constructor. Pass pathway as string and data of multi-category system
52
     * @param string $path
53
     * @param array $configs
54
     * @param int $offset
55
     * @param string $sort
56
     */
57
    public function __construct($path, array $configs, $offset = 0, $sort = 'newest')
58
    {
59
        $this->_path = $path;
60
        $this->_configs = $configs;
61
        $this->_page = (int)$offset;
62
        $this->_sort = $sort;
63
        parent::__construct();
64
    }
65
66
    /**
67
     * Build model properties
68
     * @throws ForbiddenException
69
     * @throws NotFoundException
70
     */
71
    public function before()
72
    {
73
        // find one or more categories where we must looking for content items
74
        if ((int)$this->_configs['multiCategories'] === 1) {
75
            $this->findCategories();
76
        } else {
77
            $this->findCategory();
78
        }
79
80
        // try to find content items depend of founded category(ies)
81
        $records = $this->findItems();
82
        // build output information
83
        $this->buildCategory();
84
        $this->buildContent($records);
85
    }
86
87
    /**
88
     * Set select items limit count
89
     * @param int $limit
90
     */
91
    public function setItemLimit($limit)
92
    {
93
        $this->_customItemLimit = (int)$limit;
94
    }
95
96
    /**
97
     * Find current category data
98
     * @throws NotFoundException
99
     */
100
    private function findCategory()
101
    {
102
        // get current category
103
        $query = ContentCategory::where('path', '=', $this->_path);
104
        if ($query->count() !== 1) {
105
            throw new NotFoundException(__('Category is not founded'));
106
        }
107
108
        // set properties from query
109
        $this->_allCategories = $query->get();
110
        $this->_currentCategory = $query->first();
111
        $this->_catIds[] = $this->_currentCategory['id'];
112
    }
113
114
    /**
115
     * Find multiple categories child of current
116
     * @throws NotFoundException
117
     */
118
    private function findCategories()
119
    {
120
        // get all categories for current path and child of it
121
        $query = ContentCategory::where('path', 'like', $this->_path . '%');
122
        if ($query->count() < 1) {
123
            throw new NotFoundException(__('Category is not founded'));
124
        }
125
        // get result as object
126
        $result = $query->get();
127
128
        // extract ids from result as array by key id
129
        $this->_catIds = Arr::pluck('id', $result->toArray());
130
131
        // get current category matching
132
        foreach ($result as $row) {
133
            if ($row->path === $this->_path) {
0 ignored issues
show
Bug introduced by
The property path does not seem to exist on Ffcms\Core\Arch\ActiveModel. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
134
                $this->_currentCategory = $row;
135
            }
136
        }
137
138
        // set result to property
139
        $this->_allCategories = $result;
140
    }
141
142
    /**
143
     * Find content items on database and return rows as object
144
     * @return mixed
145
     * @throws NotFoundException
146
     */
147
    private function findItems()
148
    {
149
        if (!Any::isArray($this->_catIds) || count($this->_catIds) < 1) {
150
            throw new NotFoundException(__('Category is not founded'));
151
        }
152
153
        // calculate selection offset
154
        $itemPerPage = (int)$this->_configs['itemPerCategory'];
155
        // check if custom itemlimit defined over model api
156
        if ($this->_customItemLimit !== false) {
157
            $itemPerPage = (int)$this->_customItemLimit;
158
        }
159
160
        $offset = $this->_page * $itemPerPage;
161
162
        // get all items from categories
163
        $query = ContentRecord::with(['user', 'user.profile'])
164
            ->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('important', 'DESC')->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 = $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')) {
208
            $catSortParams['page'] = (int)App::$Request->query->get('page');
209
        }
210
211
        $catSortUrls = [
212
            'views' => Url::to('content/list', $this->_currentCategory->path, null, Arr::merge($catSortParams, ['sort' => 'views']), false),
213
            'rating' => Url::to('content/list', $this->_currentCategory->path, null, Arr::merge($catSortParams, ['sort' => 'rating']), false),
214
            'newest' => Url::to('content/list', $this->_currentCategory->path, null, $catSortParams, false)
215
        ];
216
217
        // prepare current category data to output (unserialize locales and strip tags)
218
        $this->category = [
219
            'title' => $this->_currentCategory->getLocaled('title'),
220
            'description' => $this->_currentCategory->getLocaled('description'),
221
            'configs' => $catConfigs,
222
            'path' => $this->_currentCategory->path,
223
            'rss' => $rssUrl,
224
            'sort' => $catSortUrls
225
        ];
226
227
        // check if this category is hidden
228
        if (!(bool)$this->category['configs']['showCategory']) {
229
            throw new ForbiddenException(__('This category is not available to view'));
230
        }
231
232
        // make sorted tree of categories to display in breadcrumbs
233
        foreach ($this->_allCategories as $cat) {
234
            $this->categories[$cat->id] = $cat;
235
        }
236
    }
237
238
    /**
239
     * Build content data to model properties
240
     * @param $records
241
     * @throws NotFoundException
242
     */
243
    private function buildContent($records)
244
    {
245
        $nullItems = 0;
246
        foreach ($records as $row) {
247
            /** @var Content $row */
248
            // check title length on current language locale
249
            $localeTitle = $row->getLocaled('title');
250
            if (Str::likeEmpty($localeTitle)) {
0 ignored issues
show
Bug introduced by
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

250
            if (Str::likeEmpty(/** @scrutinizer ignore-type */ $localeTitle)) {
Loading history...
251
                ++$nullItems;
252
                continue;
253
            }
254
255
            // get snippet from full text for current locale
256
            $text = Text::snippet($row->getLocaled('text'));
257
258
            $itemPath = $this->categories[$row->category_id]->path;
259
            if (!Str::likeEmpty($itemPath)) {
260
                $itemPath .= '/';
261
            }
262
            $itemPath .= $row->path;
263
264
            // prepare tags data
265
            $tags = $row->getLocaled('meta_keywords');
266
            if (!Str::likeEmpty($tags)) {
267
                $tags = explode(',', $tags);
0 ignored issues
show
Bug introduced by
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

267
                $tags = explode(',', /** @scrutinizer ignore-type */ $tags);
Loading history...
268
            } else {
269
                $tags = null;
270
            }
271
272
            $owner = $row->user;
273
            // make a fake if user is not exist over id
274
            if (!$owner) {
275
                $owner = new User();
276
            }
277
278
            // check if current user can rate item
279
            $ignoredRate = App::$Session->get('content.rate.ignore');
280
            $canRate = true;
281
            if (Any::isArray($ignoredRate) && Arr::in((string)$row->id, $ignoredRate)) {
282
                $canRate = false;
283
            }
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
                'important' => (bool)$row->important
309
            ];
310
        }
311
312
        if ($nullItems === $this->_contentCount) {
313
            throw new NotFoundException(__('Content is not founded'));
314
        }
315
    }
316
317
    /**
318
     * Get content items count
319
     * @return int
320
     */
321
    public function getContentCount()
322
    {
323
        return $this->_contentCount;
324
    }
325
}
326