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

Content::buildSitemapSchedule()   C

Complexity

Conditions 7
Paths 19

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 21
c 1
b 0
f 0
nc 19
nop 0
dl 0
loc 36
rs 6.7272
1
<?php
2
3
namespace Apps\Controller\Front;
4
5
use Apps\ActiveRecord\ContentCategory;
6
use Apps\ActiveRecord\Content as ContentEntity;
7
use Apps\Model\Front\Content\EntityContentSearch;
8
use Apps\Model\Front\Sitemap\EntityBuildMap;
9
use Extend\Core\Arch\FrontAppController;
10
use Ffcms\Core\App;
11
use Apps\Model\Front\Content\EntityCategoryList;
12
use Ffcms\Core\Exception\ForbiddenException;
13
use Ffcms\Core\Exception\NativeException;
14
use Ffcms\Core\Exception\NotFoundException;
15
use Apps\Model\Front\Content\EntityContentRead;
16
use Ffcms\Core\Helper\HTML\SimplePagination;
17
use Ffcms\Core\Helper\Type\Str;
18
use Suin\RSSWriter\Channel;
19
use Suin\RSSWriter\Feed;
20
use Suin\RSSWriter\Item;
21
use Ffcms\Core\Helper\Type\Arr;
22
use Apps\Model\Front\Content\FormNarrowContentUpdate;
23
use Apps\ActiveRecord\Content as ContentRecord;
24
25
/**
26
 * Class Content. Controller of content app - content and categories.
27
 * @package Apps\Controller\Front
28
 */
29
class Content extends FrontAppController
30
{
31
    const TAG_PER_PAGE = 50;
32
33
    const EVENT_CONTENT_READ = 'content.read';
34
    const EVENT_RSS_READ = 'content.rss.read';
35
    const EVENT_CONTENT_LIST = 'content.list';
36
    const EVENT_TAG_LIST = 'content.tags';
37
38
    /**
39
     * Index is forbidden
40
     * @throws NotFoundException
41
     */
42
    public function actionIndex()
43
    {
44
        throw new NotFoundException();
45
    }
46
47
    /**
48
     * List category content
49
     * @throws NotFoundException
50
     * @throws \Ffcms\Core\Exception\SyntaxException
51
     * @throws \Ffcms\Core\Exception\NativeException
52
     * @return string
53
     */
54
    public function actionList()
55
    {
56
        $path = App::$Request->getPathWithoutControllerAction();
57
        $configs = $this->getConfigs();
58
        $page = (int)App::$Request->query->get('page', 0);
59
        $sort = (string)App::$Request->query->get('sort', 'newest');
60
        $itemCount = (int)$configs['itemPerCategory'];
61
62
        // build special model with content list and category list information
63
        $model = new EntityCategoryList($path, $configs, $page, $sort);
64
65
        // prepare query string (?a=b) for pagination if sort is defined
66
        $sortQuery = null;
67
        if (Arr::in($sort, ['rating', 'views'])) {
68
            $sortQuery = ['sort' => $sort];
69
        }
70
71
        // build pagination
72
        $pagination = new SimplePagination([
73
            'url' => ['content/list', $path, null, $sortQuery],
74
            'page' => $page,
75
            'step' => $itemCount,
76
            'total' => $model->getContentCount()
77
        ]);
78
79
        // define list event
80
        App::$Event->run(static::EVENT_CONTENT_LIST, [
81
            'model' => $model
82
        ]);
83
84
        // draw response view
85
        return App::$View->render('list', [
86
            'model' => $model,
87
            'pagination' => $pagination,
88
            'configs' => $configs,
89
        ]);
90
    }
91
92
    /**
93
     * Show content item
94
     * @throws NotFoundException
95
     * @throws \Ffcms\Core\Exception\SyntaxException
96
     * @throws \Ffcms\Core\Exception\NativeException
97
     * @return string
98
     */
99
    public function actionRead()
100
    {
101
        // get raw path without controller-action
102
        $rawPath = App::$Request->getPathWithoutControllerAction();
103
        $arrayPath = explode('/', $rawPath);
104
        // get category and content item path as string
105
        $contentPath = array_pop($arrayPath);
106
        $categoryPath = implode('/', $arrayPath);
107
108
        // try to find category object by string pathway
109
        $categoryRecord = ContentCategory::getByPath($categoryPath);
110
111
        // if no categories are available for this path - throw exception
112
        if ($categoryRecord === null || $categoryRecord->count() < 1) {
113
            throw new NotFoundException();
114
        }
115
116
        // try to find content entity record
117
        $contentRecord = ContentEntity::where('path', '=', $contentPath)->where('category_id', '=', $categoryRecord->id);
118
        $trash = false;
119
120
        // if no entity is founded for this path lets try to find on trashed
121
        if ($contentRecord === null || $contentRecord->count() !== 1) {
122
            // check if user can access to content list on admin panel
123 View Code Duplication
            if (!App::$User->isAuth() || !App::$User->identity()->getRole()->can('Admin/Content/Index')) {
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...
124
                throw new NotFoundException();
125
            }
126
            // lets try to find in trashed
127
            $contentRecord->withTrashed();
128
            // no way, lets throw exception
129
            if ($contentRecord->count() !== 1) {
130
                throw new NotFoundException();
131
            }
132
            // set trashed marker for this item
133
            $trash = true;
134
        }
135
136
        // lets init entity model for content transfer to view
137
        $model = new EntityContentRead($categoryRecord, $contentRecord->first());
0 ignored issues
show
Documentation introduced by
$contentRecord->first() is of type object<Ffcms\Core\Arch\ActiveModel>|null, but the function expects a object<Apps\ActiveRecord\Content>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
138
        $search = null;
139
        // check if similar search is enabled for item category
140
        if ((int)$model->getCategory()->getProperty('showSimilar') === 1 && $trash === false) {
141
            $search = new EntityContentSearch($model->title, $model->id);
142
        }
143
144
        // define read event
145
        App::$Event->run(static::EVENT_CONTENT_READ, [
146
            'model' => $model
147
        ]);
148
149
        // render view output
150
        return App::$View->render('read', [
151
            'model' => $model,
152
            'search' => $search,
153
            'trash' => $trash,
154
            'configs' => $this->getConfigs()
155
        ]);
156
    }
157
158
    /**
159
     * List latest by created_at content items contains tag name
160
     * @param string $tagName
161
     * @return string
162
     * @throws NotFoundException
163
     * @throws \Ffcms\Core\Exception\SyntaxException
164
     * @throws \Ffcms\Core\Exception\NativeException
165
     */
166
    public function actionTag($tagName)
167
    {
168
        $configs = $this->getConfigs();
169
        // check if tags is enabled
170
        if ((int)$configs['keywordsAsTags'] !== 1) {
171
            throw new NotFoundException(__('Tag system is disabled'));
172
        }
173
174
        // remove spaces and other shits
175
        $tagName = trim($tagName);
176
177
        // check if tag is not empty
178 View Code Duplication
        if (Str::likeEmpty($tagName) || Str::length($tagName) < 2) {
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...
179
            throw new NotFoundException(__('Tag is empty or is too short!'));
180
        }
181
182
        // get equal rows order by creation date
183
        $records = ContentEntity::where('meta_keywords', 'like', '%' . $tagName . '%')->orderBy('created_at', 'DESC')->take(self::TAG_PER_PAGE);
184
        // check if result is not empty
185
        if ($records->count() < 1) {
186
            throw new NotFoundException(__('Nothing founded'));
187
        }
188
189
        // define tag list event
190
        App::$Event->run(static::EVENT_TAG_LIST, [
191
            'records' => $records
192
        ]);
193
194
        // render response
195
        return App::$View->render('tag', [
196
            'records' => $records->get(),
197
            'tag' => App::$Security->strip_tags($tagName)
198
        ]);
199
    }
200
201
    /**
202
     * Display rss feeds from content category
203
     * @return string
204
     * @throws ForbiddenException
205
     */
206
    public function actionRss()
207
    {
208
        $path = App::$Request->getPathWithoutControllerAction();
209
        $configs = $this->getConfigs();
210
211
        // build model data
212
        $model = new EntityCategoryList($path, $configs, 0);
213
        // remove global layout
214
        $this->layout = null;
215
216
        // check if rss display allowed for this category
217 View Code Duplication
        if ((int)$model->category['configs']['showRss'] !== 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...
218
            throw new ForbiddenException(__('Rss feed is disabled for this category'));
219
        }
220
221
        // initialize rss feed objects
222
        $feed = new Feed();
223
        $channel = new Channel();
224
225
        // set channel data
226
        $channel->title($model->category['title'])
0 ignored issues
show
Bug introduced by
It seems like $model->category['title'] can also be of type array; however, Suin\RSSWriter\Channel::title() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
227
            ->description($model->category['description'])
0 ignored issues
show
Bug introduced by
It seems like $model->category['description'] can also be of type array; however, Suin\RSSWriter\Channel::description() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
228
            ->url(App::$Alias->baseUrl . '/content/list/' . $model->category['path'])
229
            ->appendTo($feed);
230
231
        // add content data
232
        if ($model->getContentCount() > 0) {
233
            foreach ($model->items as $row) {
234
                $item = new Item();
235
                // add title, short text, url
236
                $item->title($row['title'])
0 ignored issues
show
Bug introduced by
It seems like $row['title'] can also be of type array; however, Suin\RSSWriter\Item::title() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
237
                    ->description($row['text'])
238
                    ->url(App::$Alias->baseUrl . $row['uri']);
239
                // add poster
240
                if ($row['thumb'] !== null) {
241
                    $item->enclosure(App::$Alias->scriptUrl . $row['thumb'], $row['thumbSize'], 'image/jpeg');
242
                }
243
244
                // append response to channel
245
                $item->appendTo($channel);
246
            }
247
        }
248
        // define rss read event
249
        App::$Event->run(static::EVENT_RSS_READ, [
250
            'model' => $model,
251
            'feed' => $feed,
252
            'channel' => $channel
253
        ]);
254
255
        // render response from feed object
256
        return $feed->render();
257
    }
258
259
    /**
260
     * Show user added content list
261
     * @return string
262
     * @throws ForbiddenException
263
     * @throws NotFoundException
264
     * @throws \Ffcms\Core\Exception\SyntaxException
265
     * @throws NativeException
266
     */
267
    public function actionMy()
268
    {
269
        // check if user is auth
270
        if (!App::$User->isAuth()) {
271
            throw new ForbiddenException(__('Only authorized users can manage content'));
272
        }
273
274
        // check if user add enabled
275
        $configs = $this->getConfigs();
276
        if (!(bool)$configs['userAdd']) {
277
            throw new NotFoundException(__('User add is disabled'));
278
        }
279
280
        // prepare query
281
        $page = (int)App::$Request->query->get('page', 0);
282
        $offset = $page * 10;
283
        $query = ContentRecord::where('author_id', '=', App::$User->identity()->getId());
284
285
        // build pagination
286
        $pagination = new SimplePagination([
287
            'url' => ['content/my'],
288
            'page' => $page,
289
            'step' => 10,
290
            'total' => $query->count()
291
        ]);
292
293
        // build records object
294
        $records = $query->skip($offset)->take(10)->orderBy('id', 'DESC')->get();
295
296
        // render output view
297
        return App::$View->render('my', [
298
            'records' => $records,
299
            'pagination' => $pagination
300
        ]);
301
    }
302
303
    /**
304
     * Update personal content items or add new content item
305
     * @param null|int $id
306
     * @return string
307
     * @throws ForbiddenException
308
     * @throws NotFoundException
309
     * @throws NativeException
310
     * @throws \Ffcms\Core\Exception\SyntaxException
311
     */
312
    public function actionUpdate($id = null)
313
    {
314
        // check if user is auth
315
        if (!App::$User->isAuth()) {
316
            throw new ForbiddenException(__('Only authorized users can add content'));
317
        }
318
319
        // check if user add enabled
320
        $configs = $this->getConfigs();
321
        if (!(bool)$configs['userAdd']) {
322
            throw new NotFoundException(__('User add is disabled'));
323
        }
324
325
        // find record in db
326
        $record = ContentRecord::findOrNew($id);
327
        $new = $record->id === null;
328
329
        // reject edit published items and items from other authors
330 View Code Duplication
        if (($new === false && (int)$record->author_id !== App::$User->identity()->getId()) || (int)$record->display === 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...
331
            throw new ForbiddenException(__('You have no permissions to edit this content'));
332
        }
333
334
        // initialize model
335
        $model = new FormNarrowContentUpdate($record, $configs);
336
        if ($model->send() && $model->validate()) {
337
            $model->make();
338
            // if is new - make redirect to listing & add notify
339 View Code Duplication
            if ($new === true) {
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...
340
                App::$Session->getFlashBag()->add('success', __('Content successfully added'));
341
                App::$Response->redirect('content/my');
342
            } else {
343
                App::$Session->getFlashBag()->add('success', __('Content successfully updated'));
344
            }
345
        }
346
347
        // render view output
348
        return App::$View->render('update', [
349
            'model' => $model->filter(['text' => 'html']),
350
            'configs' => $configs
351
        ]);
352
    }
353
354
    /**
355
     * Cron schedule action - build content sitemap
356
     * @throws \Ffcms\Core\Exception\NativeException
357
     * @throws \Ffcms\Core\Exception\SyntaxException
358
     */
359
    public static function buildSitemapSchedule()
360
    {
361
        // get records from database as activerecord object
362
        $contents = \Apps\ActiveRecord\Content::where('display', '=', 1);
363
        if ($contents->count() < 1) {
364
            return;
365
        }
366
367
        // get languages if multilanguage enabled
368
        $langs = null;
369
        if (App::$Properties->get('multiLanguage')) {
370
            $langs = App::$Properties->get('languages');
371
        }
372
373
        // build sitemap from content items via business model
374
        $sitemap = new EntityBuildMap($langs);
375
        foreach ($contents->get() as $content) {
376
            $category = $content->getCategory();
377
            $uri = '/content/read/';
378
            if (!Str::likeEmpty($category->path)) {
379
                $uri .= $category->path . '/';
380
            }
381
            $uri .= $content->path;
382
            $sitemap->add($uri, $content->created_at, 'weekly', 0.7);
383
        }
384
        // add categories
385
        $categories = ContentCategory::getAll();
386
        foreach ($categories as $item) {
387
            if ((bool)$item->getProperty('showCategory')) {
388
                $uri = '/content/list/' . $item->path;
389
                $sitemap->add($uri, date('c'), 'daily', 0.9);
390
            }
391
        }
392
        // save data to xml file
393
        $sitemap->save('content');
394
    }
395
}
396