Passed
Push — master ( 02a4a5...734a7e )
by Mihail
05:36
created

Content   C

Complexity

Total Complexity 38

Size/Duplication

Total Lines 242
Duplicated Lines 3.72 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 9
Bugs 4 Features 1
Metric Value
c 9
b 4
f 1
dl 9
loc 242
rs 5.4352
wmc 38
lcom 1
cbo 20

5 Methods

Rating   Name   Duplication   Size   Complexity  
A before() 0 13 3
D actionChangerate() 3 44 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
        // set ignored content id to rate in session
68
        $ignored = App::$Session->get('content.rate.ignore');
69
        $ignored[] = $id;
70
        App::$Session->set('content.rate.ignore', $ignored);
71
        
72
        // find content record
73
        $record = ContentRecord::find($id);
74
        if ($record === null || $record->count() < 1) {
75
            throw new NotFoundException(__('Content item is not founded'));
76
        }
77
78
        // check if author rate him-self content
79
        if ($record->author_id === $user->getId()) {
80
            throw new ForbiddenException(__('You can not rate your own content'));
81
        }
82
        
83
        // initialize model
84
        $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...
85
        // check if content items is already rated by this user
86
        if ($model->isAlreadyRated()) {;
87
            throw new ForbiddenException(__('You have already rate this!'));            
88
        }
89
        
90
        // make rate - add +1 to content rating and author rating
91
        $model->make();
92
        
93
        return json_encode([
94
            'status' => 1,
95
            'rating' => $model->getRating()
96
        ]);
97
    }
98
99
    /**
100
     * Upload new files to content item gallery
101
     * @param int $id
102
     * @return string
103
     * @throws ForbiddenException
104
     * @throws NativeException
105
     * @throws \Exception
106
     */
107
    public function actionGalleryupload($id)
108
    {
109
        // check if id is passed
110
        if (Str::likeEmpty($id)) {
111
            throw new NativeException('Wrong input data');
112
        }
113
114
        // check if user have permission to access there
115 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...
116
            throw new NativeException(__('Permissions to upload is denied'));
117
        }
118
119
        // check if directory exist
120
        if (!Directory::exist('/upload/gallery/' . $id)) {
121
            Directory::create('/upload/gallery/' . $id);
122
        }
123
124
        // get file object
125
        /** @var $file \Symfony\Component\HttpFoundation\File\UploadedFile */
126
        $file = $this->request->files->get('gallery-files');
0 ignored issues
show
Documentation introduced by
The property request does not exist on object<Apps\Controller\Api\Content>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
127
        if ($file === null || $file->getError() !== 0) {
128
            throw new NativeException(__('Unexpected error in upload process'));
129
        }
130
131
        // check file size
132
        if ($file->getSize() < 1 || $file->getSize() > $this->maxSize) {
133
            throw new ForbiddenException(__('File size is too big. Max size: %size%kb', ['size' => (int)($this->maxSize/1024)]));
134
        }
135
136
        // check file extension
137
        if (!Arr::in($file->guessExtension(), $this->allowedExt)) {
138
            throw new ForbiddenException(__('File extension is not allowed to upload. Allowed: %s%', ['s' => implode(', ', $this->allowedExt)]));
139
        }
140
141
        // create origin directory
142
        $originPath = '/upload/gallery/' . $id . '/orig/';
143
        if (!Directory::exist($originPath)) {
144
            Directory::create($originPath);
145
        }
146
147
        // lets make a new file name
148
        $fileName = App::$Security->simpleHash($file->getClientOriginalName() . $file->getSize());
149
        $fileNewName = $fileName . '.' . $file->guessExtension();
150
        // save file from tmp to gallery origin directory
151
        $file->move(Normalize::diskFullPath($originPath), $fileNewName);
152
153
        // lets resize preview image for it
154
        $thumbPath = '/upload/gallery/' . $id . '/thumb/';
155
        if (!Directory::exist($thumbPath)) {
156
            Directory::create($thumbPath);
157
        }
158
159
        $thumb = new Image();
160
        $thumb->setCacheDir(root . '/Private/Cache/images');
161
162
        // open original file, resize it and save
163
        $thumbSaveName = Normalize::diskFullPath($thumbPath) . '/' . $fileName . '.jpg';
164
        $thumb->open(Normalize::diskFullPath($originPath) . DIRECTORY_SEPARATOR . $fileNewName)
165
            ->cropResize($this->maxResize)
166
            ->save($thumbSaveName, 'jpg', 90);
167
        $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...
168
169
        // dont ask me why there is 2nd lvl array (can contains multiply items to frontend response)
170
        $output = [
171
            'thumbnailUrl' => '/upload/gallery/' . $id . '/thumb/' . $fileName . '.jpg',
172
            'url' => '/upload/gallery/' . $id . '/orig/' . $fileNewName,
173
            'name' => $fileNewName
174
        ];
175
176
        $this->setJsonHeader();
177
178
        // generate success response
179
        return json_encode(['status' => 1, 'message' => 'ok', 'files' => [$output]]);
180
    }
181
182
    /**
183
     * Show gallery images from upload directory
184
     * @param int $id
185
     * @return string
186
     * @throws NotFoundException
187
     * @throws NativeException
188
     */
189
    public function actionGallerylist($id)
190
    {
191
        // check if id is passed
192
        if (Str::likeEmpty($id)) {
193
            throw new NativeException('Wrong input data');
194
        }
195
196
        // check if user have permission to access there
197 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...
198
            throw new NativeException('Permission denied');
199
        }
200
201
        $thumbDir = Normalize::diskFullPath('/upload/gallery/' . $id . '/orig/');
202
        if (!Directory::exist($thumbDir)) {
203
            throw new NotFoundException('Nothing found');
204
        }
205
206
        $files = Directory::scan($thumbDir, null, true);
207
        if (!Obj::isArray($files) || count($files) < 1) {
208
            throw new NotFoundException('Nothing found');
209
        }
210
211
        $output = [];
212
        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...
213
            $fileExt = Str::lastIn($file, '.');
214
            $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 213 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...
215
            $output[] = [
216
                'thumbnailUrl' => '/upload/gallery/' . $id . '/thumb/' . $fileName . '.jpg',
217
                'url' => '/upload/gallery/' . $id . '/orig/' . $file,
218
                'name' => $file
219
            ];
220
        }
221
222
        $this->setJsonHeader();
223
        return json_encode(['files' => $output]);
224
    }
225
226
    /**
227
     * Remove items from gallery (preview+full)
228
     * @param int $id
229
     * @param string $file
230
     * @throws ForbiddenException
231
     * @throws NativeException
232
     * @return string
233
     */
234
    public function actionGallerydelete($id, $file)
235
    {
236
        // check passed data
237
        if (Str::likeEmpty($file) || !Obj::isLikeInt($id)) {
238
            throw new NativeException('Wrong input data');
239
        }
240
241
        // check passed file extension
242
        $fileExt = Str::lastIn($file, '.', true);
243
        $fileName = Str::firstIn($file, '.');
244
        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 242 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...
245
            throw new ForbiddenException('Wrong file extension');
246
        }
247
248
        // generate path
249
        $thumb = '/upload/gallery/' . $id . '/thumb/' . $fileName . '.jpg';
250
        $full = '/upload/gallery/' . $id . '/orig/' . $file;
251
252
        // check if file exists and remove
253
        if (File::exist($thumb) || File::exist($full)) {
254
            File::remove($thumb);
255
            File::remove($full);
256
        } else {
257
            throw new NativeException('Image is not founded');
258
        }
259
260
        return json_encode(['status' => 1, 'msg' => 'Image is removed']);
261
    }
262
}