Passed
Push — master ( a9f488...61c2c6 )
by Mihail
07:48
created

Content   D

Complexity

Total Complexity 38

Size/Duplication

Total Lines 241
Duplicated Lines 3.73 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 7
Bugs 3 Features 0
Metric Value
wmc 38
c 7
b 3
f 0
lcom 1
cbo 22
dl 9
loc 241
rs 4.8631

5 Methods

Rating   Name   Duplication   Size   Complexity  
A before() 0 13 3
D actionChangerate() 3 43 9
C actionGalleryupload() 3 74 12
C actionGallerylist() 3 36 8
B actionGallerydelete() 0 28 6

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Apps\Controller\Api;
4
5
use Extend\Core\Arch\ApiController;
6
use Apps\ActiveRecord\App as AppRecord;
7
use Ffcms\Core\App;
8
use Ffcms\Core\Exception\NativeException;
9
use Ffcms\Core\Helper\FileSystem\Directory;
10
use Ffcms\Core\Helper\FileSystem\File;
11
use Ffcms\Core\Helper\FileSystem\Normalize;
12
use Ffcms\Core\Helper\Type\Arr;
13
use Ffcms\Core\Helper\Type\Obj;
14
use Ffcms\Core\Helper\Type\Str;
15
use Gregwar\Image\Image;
16
use Apps\ActiveRecord\Content as ContentRecord;
17
use Apps\Model\Api\Content\ContentRatingChange;
18
use Ffcms\Core\Exception\ForbiddenException;
19
use Ffcms\Core\Exception\NotFoundException;
20
21
class Content extends ApiController
22
{
23
    public $maxSize = 512000; // in bytes, 500 * 1024
24
    public $maxResize = 150;
25
26
    public $allowedExt = ['jpg', 'png', 'gif', 'jpeg', 'bmp', 'webp'];
27
28
    /**
29
     * Prepare configuratins before initialization
30
     */
31
    public function before()
32
    {
33
        parent::before();
34
        $configs = AppRecord::getConfigs('app', 'Content');
35
        // prevent null-type config data
36
        if ((int)$configs['gallerySize'] > 0) {
37
            $this->maxSize = (int)$configs['gallerySize'] * 1024;
38
        }
39
40
        if ((int)$configs['galleryResize'] > 0) {
41
            $this->maxResize = (int)$configs['galleryResize'];
42
        }
43
    }
44
    
45
    /**
46
     * Change content item rating action
47
     * @param string $type
48
     * @param int $id
49
     * @throws NativeException
50
     * @throws ForbiddenException
51
     * @throws NotFoundException
52
     * @return string
53
     */
54
    public function actionChangerate($type, $id)
55
    {
56
        // check input params
57 View Code Duplication
        if (!Arr::in($type, ['plus', 'minus']) || !Obj::isLikeInt($id)) {
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...
58
            throw new NativeException('Bad conditions');
59
        }
60
        
61
        // get current user and check is authed
62
        $user = App::$User->identity();
63
        if ($user === null || !App::$User->isAuth()) {
64
            throw new ForbiddenException(__('Authorization is required!'));
65
        }
66
        
67
        // find content record
68
        $record = ContentRecord::find($id);
69
        if ($record === null || $record->count() < 1) {
70
            throw new NotFoundException(__('Content item is not founded'));
71
        }
72
        
73
        // initialize model
74
        $model = new ContentRatingChange($record, $type, $user);
0 ignored issues
show
Compatibility introduced by
$record of type object<Ffcms\Core\Arch\ActiveModel> is not a sub-type of object<Apps\ActiveRecord\Content>. It seems like you assume a child class of the class Ffcms\Core\Arch\ActiveModel to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
75
        // check if content items is already rated by this user
76
        if ($model->isAlreadyRated()) {
77
            // set ignored content id to rate in session
78
            $ignored = App::$Session->get('content.rate.ignore');
79
            $ignored[] = $id;
80
            App::$Session->set('content.rate.ignore', $ignored);
81
            throw new ForbiddenException(__('You have already rate this!'));            
82
        }
83
        
84
        // make rate - add +1 to content rating and author rating
85
        if ($model->make()) {
86
            // set ignored content id to rate in session
87
            $ignored = App::$Session->get('content.rate.ignore');
88
            $ignored[] = $id;
89
            App::$Session->set('content.rate.ignore', $ignored);            
90
        }
91
        
92
        return json_encode([
93
            'status' => 1,
94
            'rating' => $model->getRating() // @todo this
95
        ]);
96
    }
97
98
    /**
99
     * Upload new files to content item gallery
100
     * @param int $id
101
     * @return string
102
     * @throws ForbiddenException
103
     * @throws NativeException
104
     * @throws \Exception
105
     */
106
    public function actionGalleryupload($id)
107
    {
108
        // check if id is passed
109
        if (Str::likeEmpty($id)) {
110
            throw new NativeException('Wrong input data');
111
        }
112
113
        // check if user have permission to access there
114 View Code Duplication
        if (!App::$User->isAuth() || !App::$User->identity()->getRole()->can('global/file')) {
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...
115
            throw new NativeException(__('Permissions to upload is denied'));
116
        }
117
118
        // check if directory exist
119
        if (!Directory::exist('/upload/gallery/' . $id)) {
120
            Directory::create('/upload/gallery/' . $id);
121
        }
122
123
        // get file object
124
        /** @var $file \Symfony\Component\HttpFoundation\File\UploadedFile */
125
        $file = App::$Request->files->get('gallery-files');
126
        if ($file === null || $file->getError() !== 0) {
127
            throw new NativeException(__('Unexpected error in upload process'));
128
        }
129
130
        // check file size
131
        if ($file->getSize() < 1 || $file->getSize() > $this->maxSize) {
132
            throw new ForbiddenException(__('File size is too big. Max size: %size%kb', ['size' => intval($this->maxSize/1024)]));
133
        }
134
135
        // check file extension
136
        if (!Arr::in($file->guessExtension(), $this->allowedExt)) {
137
            throw new ForbiddenException(__('File extension is not allowed to upload. Allowed: %s%', ['s' => implode(', ', $this->allowedExt)]));
138
        }
139
140
        // create origin directory
141
        $originPath = '/upload/gallery/' . $id . '/orig/';
142
        if (!Directory::exist($originPath)) {
143
            Directory::create($originPath);
144
        }
145
146
        // lets make a new file name
147
        $fileName = App::$Security->simpleHash($file->getFilename() . $file->getSize());
148
        $fileNewName = $fileName . '.' . $file->guessExtension();
149
        // save file from tmp to gallery origin directory
150
        $file->move(Normalize::diskFullPath($originPath), $fileNewName);
151
152
        // lets resize preview image for it
153
        $thumbPath = '/upload/gallery/' . $id . '/thumb/';
154
        if (!Directory::exist($thumbPath)) {
155
            Directory::create($thumbPath);
156
        }
157
158
        $thumb = new Image();
159
        $thumb->setCacheDir(root . '/Private/Cache/images');
160
161
        // open original file, resize it and save
162
        $thumbSaveName = Normalize::diskFullPath($thumbPath) . '/' . $fileName . '.jpg';
163
        $thumb->open(Normalize::diskFullPath($originPath) . DIRECTORY_SEPARATOR . $fileNewName)
164
            ->cropResize($this->maxResize)
165
            ->save($thumbSaveName, 'jpg', 90);
166
        $thumb = null;
0 ignored issues
show
Unused Code introduced by
$thumb is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
167
168
        // dont ask me why there is 2nd lvl array (can contains multiply items to frontend response)
169
        $output = [
170
            'thumbnailUrl' => '/upload/gallery/' . $id . '/thumb/' . $fileName . '.jpg',
171
            'url' => '/upload/gallery/' . $id . '/orig/' . $fileNewName,
172
            'name' => $fileNewName
173
        ];
174
175
        $this->setJsonHeader();
176
177
        // generate success response
178
        return json_encode(['status' => 1, 'message' => 'ok', 'files' => [$output]]);
179
    }
180
181
    /**
182
     * Show gallery images from upload directory
183
     * @param int $id
184
     * @return string
185
     * @throws NotFoundException
186
     * @throws NativeException
187
     */
188
    public function actionGallerylist($id)
189
    {
190
        // check if id is passed
191
        if (Str::likeEmpty($id)) {
192
            throw new NativeException('Wrong input data');
193
        }
194
195
        // check if user have permission to access there
196 View Code Duplication
        if (!App::$User->isAuth() || !App::$User->identity()->getRole()->can('global/file')) {
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...
197
            throw new NativeException('Permission denied');
198
        }
199
200
        $thumbDir = Normalize::diskFullPath('/upload/gallery/' . $id . '/orig/');
201
        if (!Directory::exist($thumbDir)) {
202
            throw new NotFoundException('Nothing found');
203
        }
204
205
        $files = Directory::scan($thumbDir, null, true);
206
        if (!Obj::isArray($files) || count($files) < 1) {
207
            throw new NotFoundException('Nothing found');
208
        }
209
210
        $output = [];
211
        foreach ($files as $file) {
0 ignored issues
show
Bug introduced by
The expression $files of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
212
            $fileExt = Str::lastIn($file, '.');
213
            $fileName = Str::sub($file, 0, -Str::length($fileExt));
0 ignored issues
show
Security Bug introduced by
It seems like $fileExt defined by \Ffcms\Core\Helper\Type\Str::lastIn($file, '.') on line 212 can also be of type false; however, Ffcms\Core\Helper\Type\Str::length() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
214
            $output[] = [
215
                'thumbnailUrl' => '/upload/gallery/' . $id . '/thumb/' . $fileName . '.jpg',
216
                'url' => '/upload/gallery/' . $id . '/orig/' . $file,
217
                'name' => $file
218
            ];
219
        }
220
221
        $this->setJsonHeader();
222
        return json_encode(['files' => $output]);
223
    }
224
225
    /**
226
     * Remove items from gallery (preview+full)
227
     * @param int $id
228
     * @param string $file
229
     * @throws ForbiddenException
230
     * @throws NativeException
231
     * @return string
232
     */
233
    public function actionGallerydelete($id, $file)
234
    {
235
        // check passed data
236
        if (Str::likeEmpty($file) || !Obj::isLikeInt($id)) {
237
            throw new NativeException('Wrong input data');
238
        }
239
240
        // check passed file extension
241
        $fileExt = Str::lastIn($file, '.', true);
242
        $fileName = Str::firstIn($file, '.');
243
        if (!Arr::in($fileExt, $this->allowedExt)) {
0 ignored issues
show
Security Bug introduced by
It seems like $fileExt defined by \Ffcms\Core\Helper\Type\...astIn($file, '.', true) on line 241 can also be of type false; however, Ffcms\Core\Helper\Type\Arr::in() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
244
            throw new ForbiddenException('Wrong file extension');
245
        }
246
247
        // generate path
248
        $thumb = '/upload/gallery/' . $id . '/thumb/' . $fileName . '.jpg';
249
        $full = '/upload/gallery/' . $id . '/orig/' . $file;
250
251
        // check if file exists and remove
252
        if (File::exist($thumb) || File::exist($full)) {
253
            File::remove($thumb);
254
            File::remove($full);
255
        } else {
256
            throw new NativeException('Image is not founded');
257
        }
258
259
        return json_encode(['status' => 1, 'msg' => 'Image is removed']);
260
    }
261
}