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

FormContentUpdate::labels()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 15
nc 1
nop 0
1
<?php
2
3
namespace Apps\Model\Admin\Content;
4
5
use Apps\ActiveRecord\Content;
6
use Apps\ActiveRecord\ContentCategory;
7
use Apps\ActiveRecord\ContentTag;
8
use Ffcms\Core\App;
9
use Ffcms\Core\Arch\Model;
10
use Ffcms\Core\Helper\Date;
11
use Ffcms\Core\Helper\FileSystem\Directory;
12
use Ffcms\Core\Helper\FileSystem\File;
13
use Ffcms\Core\Helper\Type\Any;
14
use Ffcms\Core\Helper\Type\Obj;
15
use Ffcms\Core\Helper\Type\Str;
16
17
/**
18
 * Class FormContentUpdate. Create and update content items business model
19
 * @package Apps\Model\Admin\Content
20
 */
21
class FormContentUpdate extends Model
22
{
23
    public $title = [];
24
    public $text = [];
25
    public $path;
26
    public $poster;
27
    public $categoryId;
28
    public $authorId;
29
    public $metaTitle;
30
    public $metaKeywords = [];
31
    public $metaDescription = [];
32
    public $display = '1';
33
    public $source;
34
    public $addRating = 0;
35
    public $createdAt;
36
    public $important;
37
38
    public $galleryFreeId;
39
40
    private $_content;
41
    private $_new = false;
42
43
    /**
44
     * FormContentUpdate constructor. Pass content active record inside
45
     * @param Content $content
46
     */
47
    public function __construct(Content $content)
48
    {
49
        $this->_content = $content;
50
        parent::__construct();
51
    }
52
53
    /**
54
     * Set model properties from active record data
55
     */
56
    public function before()
57
    {
58
        // is new item?
59
        if ($this->_content->id === null) {
60
            $this->_new = true;
61
            if (!$this->galleryFreeId) {
62
                $this->galleryFreeId = '_tmp_' . Str::randomLatin(mt_rand(16, 32));
63
            }
64
65
            if (!$this->authorId) {
66
                $this->authorId = App::$User->identity()->getId();
67
            }
68
69
            if (!$this->categoryId) {
70
                $this->categoryId = 1;
71
            }
72
        } else { // is edit of exist item? define available data
73
            $this->title = $this->_content->title;
74
            $this->text = $this->_content->text;
75
            $this->path = $this->_content->path;
76
            $this->poster = $this->_content->poster;
77
            $this->categoryId = $this->_content->category_id;
78
            $this->authorId = $this->_content->author_id;
79
            $this->metaTitle = $this->_content->meta_title;
80
            $this->metaKeywords = $this->_content->meta_keywords;
81
            $this->metaDescription = $this->_content->meta_description;
82
            $this->display = $this->_content->display;
83
            $this->source = $this->_content->source;
84
            $this->createdAt = Date::convertToDatetime($this->_content->created_at, Date::FORMAT_TO_HOUR);
85
            $this->galleryFreeId = $this->_content->id;
86
            $this->important = $this->_content->important;
87
        }
88
    }
89
90
    /**
91
     * Validation rules
92
     * @return array
93
     */
94
    public function rules(): array
95
    {
96
        $res = [
97
            ['title.' . App::$Request->getLanguage(), 'required'],
98
            ['text.' . App::$Request->getLanguage(), 'required'],
99
            ['text', 'used'],
100
            ['path', 'reverse_match', '/[\/\'~`\!@#\$%\^&\*\(\)+=\{\}\[\]\|;:"\<\>,\?\\\]/'],
101
            [['path', 'categoryId', 'authorId', 'display', 'galleryFreeId', 'title', 'important'], 'required'],
102
            [['metaTitle', 'metaKeywords', 'metaDescription', 'poster', 'source', 'addRating', 'createdAt'], 'used'],
103
            [['addRating', 'authorId', 'display'], 'int'],
104
            [['important', 'display'], 'in', [0, 1]],
105
            ['categoryId', 'in', $this->categoryIds()],
106
            ['path', '\Apps\Model\Admin\Content\FormContentUpdate::validatePath'],
107
            ['authorId', '\App::$User::isExist']
108
        ];
109
110
        foreach (App::$Properties->get('languages') as $lang) {
111
            $res[] = ['title.' . $lang, 'length_max', 120, null, true, true];
112
            $res[] = ['keywords.' . $lang, 'length_max', 150];
113
            $res[] = ['description.' . $lang, 'length_max', 250];
114
        }
115
116
        return $res;
117
    }
118
119
    /**
120
     * Filtering attribute types
121
     * @return array
122
     */
123
    public function types(): array
124
    {
125
        return [
126
            'text' => '!secure'
127
        ];
128
    }
129
130
    /**
131
     * Form display labels
132
     * @return array
133
     */
134
    public function labels(): array
135
    {
136
        return [
137
            'title' => __('Content title'),
138
            'text' => __('Content text'),
139
            'path' => __('Path slug'),
140
            'categoryId' => __('Category'),
141
            'metaTitle' => __('Meta title'),
142
            'metaKeywords' => __('Meta keywords'),
143
            'metaDescription' => __('Meta description'),
144
            'display' => __('Public display'),
145
            'important' => __('Make important'),
146
            'createdAt' => __('Publish date'),
147
            'authorId' => __('Author identity'),
148
            'source' => __('Source URL'),
149
            'addRating' => __('Change rating'),
150
            'poster' => __('Poster')
151
        ];
152
    }
153
154
    /**
155
     * Save changes in database
156
     */
157
    public function save()
158
    {
159
        $this->_content->title = $this->title;
160
        $this->_content->text = $this->text;
161
        $this->_content->path = $this->path;
162
        $this->_content->category_id = $this->categoryId;
163
        $this->_content->author_id = $this->authorId;
164
        $this->_content->display = $this->display;
0 ignored issues
show
Documentation Bug introduced by
The property $display was declared of type boolean, but $this->display is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
165
        $this->_content->meta_title = $this->metaTitle;
166
        $this->_content->meta_keywords = $this->metaKeywords;
167
        $this->_content->meta_description = $this->metaDescription;
168
        $this->_content->source = $this->source;
169
        $this->_content->important = (int)$this->important;
0 ignored issues
show
Documentation Bug introduced by
The property $important was declared of type boolean, but (int)$this->important is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
170
        // check if rating is changed
171
        if ((int)$this->addRating !== 0) {
172
            $this->_content->rating += (int)$this->addRating;
173
        }
174
        // check if special comment hash is exist
175
        if ($this->_new || Str::length($this->_content->comment_hash) < 32) {
176
            $this->_content->comment_hash = $this->generateCommentHash();
177
        }
178
179
        // check if date is updated
180
        if (!Str::likeEmpty($this->createdAt) && !Str::startsWith('0000', Date::convertToDatetime($this->createdAt, Date::FORMAT_SQL_TIMESTAMP))) {
0 ignored issues
show
Bug introduced by
It seems like Ffcms\Core\Helper\Date::...::FORMAT_SQL_TIMESTAMP) can also be of type false; however, parameter $where of Ffcms\Core\Helper\Type\Str::startsWith() 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

180
        if (!Str::likeEmpty($this->createdAt) && !Str::startsWith('0000', /** @scrutinizer ignore-type */ Date::convertToDatetime($this->createdAt, Date::FORMAT_SQL_TIMESTAMP))) {
Loading history...
181
            $this->_content->created_at = Date::convertToDatetime($this->createdAt, Date::FORMAT_SQL_TIMESTAMP);
0 ignored issues
show
Documentation Bug introduced by
It seems like Ffcms\Core\Helper\Date::...::FORMAT_SQL_TIMESTAMP) can also be of type false. However, the property $created_at is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
182
        }
183
184
        // save poster data
185
        $posterPath = '/upload/gallery/' . $this->galleryFreeId . '/orig/' . $this->poster;
186
        if (File::exist($posterPath)) {
187
            $this->_content->poster = $this->poster;
188
        }
189
190
        // get temporary gallery id
191
        $tmpGalleryId = $this->galleryFreeId;
192
193
        // save row
194
        $this->_content->save();
195
        
196
        // update tags data in special table (relation: content->content_tags = oneToMany)
197
        ContentTag::where('content_id', '=', $this->_content->id)->delete();
198
        $insertData = [];
199
        foreach ($this->metaKeywords as $lang => $keys) {
200
            // split keywords to tag array
201
            $tags = explode(',', $keys);
202
            foreach ($tags as $tag) {
203
                // cleanup tag from white spaces
204
                $tag = trim($tag);
205
                // prepare data to insert
206
                if (Str::length($tag) > 0) {
207
                    $insertData[] = [
208
                        'content_id' => $this->_content->id,
209
                        'lang' => $lang,
210
                        'tag' => $tag
211
                    ];
212
                }
213
            }
214
        }
215
        // insert tags
216
        ContentTag::insert($insertData);
217
218
        // move files
219
        if ($tmpGalleryId !== $this->_content->id) {
220
            Directory::rename('/upload/gallery/' . $tmpGalleryId, $this->_content->id);
221
        }
222
    }
223
224
    /**
225
     * Get allowed category ids as array
226
     * @return array
227
     */
228
    public function categoryIds()
229
    {
230
        $data = ContentCategory::getSortedCategories();
231
        return array_keys($data);
232
    }
233
234
    /**
235
     * Validate path filter
236
     * @return bool
237
     */
238
    public function validatePath()
239
    {
240
        // try to find this item
241
        $find = Content::where('path', '=', $this->path);
242
        // exclude self id
243
        if ($this->_content->id !== null && Any::isInt($this->_content->id)) {
244
            $find->where('id', '!=', $this->_content->id);
245
        }
246
247
        // limit only current category id
248
        $find->where('category_id', '=', $this->categoryId);
249
250
        return $find->count() < 1;
251
    }
252
253
    /**
254
     * Generate random string for comment hash value
255
     * @return string
256
     */
257
    private function generateCommentHash()
258
    {
259
        $hash = Str::randomLatinNumeric(mt_rand(32, 128));
260
        $find = Content::where('comment_hash', '=', $hash)->count();
261
        // hmmm, is always exist? Chance of it is TOOOO low, but lets recursion re-generate
262
        if ($find !== 0) {
263
            return $this->generateCommentHash();
264
        }
265
266
        return $hash;
267
    }
268
}
269