ContentsFileTrait   C
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 431
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 11
dl 0
loc 431
rs 5.5199
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A contentsFileSettings() 0 7 1
A getContentsFileSettings() 0 7 2
B getContentsFile() 0 38 6
A setContentsFile() 0 13 4
B normalSetContentsFile() 0 42 5
B ddSetContentsFile() 0 50 5
A getExt() 0 9 2
A tmpUpload() 0 23 4
F orientationFixedImage() 0 80 20
A imageFlop() 0 18 3
A imageFlip() 0 18 3
A imageRotate() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ContentsFileTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ContentsFileTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ContentsFile\Model\Entity;
4
5
use Cake\Core\Configure;
6
use Cake\Filesystem\File;
7
use Cake\Http\Exception\InternalErrorException;
8
use Cake\I18n\Time;
9
use Cake\ORM\TableRegistry;
10
use Cake\Utility\Security;
11
use ContentsFile\Aws\S3;
12
use Laminas\Diactoros\UploadedFile;
13
14
trait ContentsFileTrait
15
{
16
    private $contentsFileSettings = [];
17
18
    /**
19
     * contentsFileSettings
20
     * 設定値のセッティング
21
     *
22
     * @author hagiwara
23
     * @return void
24
     */
25
    private function contentsFileSettings(): void
26
    {
27
        $default = [];
28
        //設定値はまとめる
29
        $settings = $this->contentsFileConfig;
0 ignored issues
show
Bug introduced by
The property contentsFileConfig does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
30
        $this->contentsFileSettings = array_merge($default, $settings);
31
    }
32
33
    /**
34
     * getContentsFileSettings
35
     * 設定値のセッティングの取得
36
     *
37
     * @author hagiwara
38
     * @return array
39
     */
40
    public function getContentsFileSettings(): array
41
    {
42
        if (empty($this->contentsFileSettings)) {
43
            $this->contentsFileSettings();
44
        }
45
        return $this->contentsFileSettings;
46
    }
47
48
    /**
49
     * getContentsFile
50
     * ファイルのgetterのセッティング
51
     *
52
     * @author hagiwara
53
     * @param string $property
54
     * @param mixed $value
55
     * @return mixed
56
     */
57
    public function getContentsFile(string $property, $value)
58
    {
59
        $this->contentsFileSettings();
60
        if (
61
            //attachmentにデータが登録時のみ
62
            !empty($this->id) &&
63
            //設定値に設定されているとき
64
            preg_match('/^contents_file_(.*)$/', $property, $match) &&
65
            array_key_exists($match[1], $this->contentsFileSettings['fields'])
66
        ) {
67
            //何もセットされていないとき
68
            if (empty($this->_fields[$property])) {
69
                //attachmentからデータを探しに行く
70
                $attachmentModel = TableRegistry::getTableLocator()->get('Attachments');
71
                $attachmentData = $attachmentModel->find('all')
72
                    ->where(['model' => $this->getSource()])
0 ignored issues
show
Bug introduced by
It seems like getSource() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
73
                    ->where(['model_id' => $this->id])
0 ignored issues
show
Bug introduced by
The property id does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
74
                    ->where(['field_name' => $match[1]])
75
                    ->first()
76
                ;
77
                if (!empty($attachmentData)) {
78
                    $value = [
79
                        'model' => $attachmentData->model,
80
                        'model_id' => $attachmentData->model_id,
81
                        'field_name' => $attachmentData->field_name,
82
                        'file_name' => $attachmentData->file_name,
83
                        'file_content_type' => $attachmentData->file_content_type,
84
                        'file_size' => $attachmentData->file_size,
85
                        'file_random_path' => $attachmentData->file_random_path,
86
                    ];
87
                }
88
            } else {
89
                //それ以外はpropertiesの値を取得(setterで値を編集している場合はそれを反映するために必要)
90
                $value = $this->_fields[$property];
0 ignored issues
show
Bug introduced by
The property _fields does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
91
            }
92
        }
93
        return $value;
94
    }
95
96
    /**
97
     * setContentsFile
98
     * ファイルのsetterのセッティング
99
     *
100
     * @author hagiwara
101
     */
102
    public function setContentsFile()
103
    {
104
        $this->contentsFileSettings();
105
        foreach ($this->contentsFileSettings['fields'] as $field => $fieldSetting) {
106
            // 通常のパターン
107
            if (!array_key_exists('type', $fieldSetting) || $fieldSetting['type'] == 'normal') {
108
                $this->normalSetContentsFile($field, $fieldSetting);
109
            } else {
110
                $this->ddSetContentsFile($field, $fieldSetting);
111
            }
112
        }
113
        return $this;
114
    }
115
116
    /**
117
     * normalSetContentsFile
118
     * ファイルのsetterのセッティング
119
     *
120
     * @param string $field
121
     * @param array $fieldSetting
122
     * @return void
123
     * @author hagiwara
124
     */
125
    private function normalSetContentsFile(string $field, array $fieldSetting): void
126
    {
127
        $fileInfo = $this->{$field};
128
        if (
129
            //ファイルの情報がある
130
            is_object($fileInfo) &&
131
            //空アップロード時は通さない(もともとのデータを活かす)
132
            $fileInfo->getError() != UPLOAD_ERR_NO_FILE
133
        ) {
134
            $fileSet = [
135
                'model' => $this->getSource(),
0 ignored issues
show
Bug introduced by
It seems like getSource() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
136
                'model_id' => $this->id,
137
                'field_name' => $field,
138
                'file_name' => $fileInfo->getClientFilename(),
139
                'file_content_type' => Configure::read('ContentsFile.Setting.type'),
140
                'file_size' => $fileInfo->getSize(),
141
                'file_error' => $fileInfo->getError(),
142
            ];
143
144
            //$fileInfoにtmp_nameがいるときはtmpディレクトリへのファイルのコピーを行う
145
            // if (!empty($fileInfo['tmp_name'])) {
146
            $tmpFileName = Security::hash(rand() . Time::now()->i18nFormat('YYYY/MM/dd HH:ii:ss') . $fileInfo->getClientFilename());
147
148
            if ($this->getExt($fileInfo->getClientFilename()) !== null) {
149
                $tmpFileName .= '.' . $this->getExt($fileInfo->getClientFilename());
150
            }
151
152
            // tmpディレクトリへのアップロードのエラー(パーミッションなど)
153
            if (!$this->tmpUpload($fileInfo, $fieldSetting, $tmpFileName)) {
154
                throw new InternalErrorException('tmp upload error');
155
            }
156
            $fileSet['tmp_file_name'] = $tmpFileName;
157
            // }
158
            //これを残して次に引き渡したくないので
159
            unset($this->{$field});
160
161
            $nowNew = $this->isNew();
0 ignored issues
show
Bug introduced by
It seems like isNew() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
Unused Code introduced by
$nowNew 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...
162
163
            $this->set('contents_file_' . $field, $fileSet);
0 ignored issues
show
Bug introduced by
It seems like set() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
164
            $this->set('contents_file_' . $field . '_filename', $fileInfo->getClientFilename());
0 ignored issues
show
Bug introduced by
It seems like set() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
165
        }
166
    }
167
168
    /**
169
     * ddSetContentsFile
170
     * ファイルのsetterのセッティング
171
     *
172
     * @param string $field
173
     * @param array $fieldSetting
174
     * @return void
175
     * @author hagiwara
176
     */
177
    private function ddSetContentsFile(string $field, array $fieldSetting): void
178
    {
179
180
        $fileInfo = $this->{$field};
181
        if (!empty($fileInfo)) {
182
            if (!preg_match('/^data:([^;]+);base64,(.+)$/', $fileInfo, $fileMatch)) {
183
                // ちゃんとファイルアップがないのでエラー
184
                throw new InternalErrorException('tmp upload erroar');
185
            }
186
            $filename = $this->{'contents_file_' . $field . '_filename'};
187
188
            $filebody = base64_decode($fileMatch[2]);
189
            $filesize = strlen($filebody);
190
191
            $tmpFileName = Security::hash(rand() . Time::now()->i18nFormat('YYYY/MM/dd HH:ii:ss') . $filename);
192
193
            if ($this->getExt($filename) !== null) {
194
                $tmpFileName .= '.' . $this->getExt($filename);
195
            }
196
            // まずは一時的にファイルを書き出す
197
            $ddTmpFileName = TMP . Security::hash(rand() . Time::now()->i18nFormat('YYYY/MM/dd HH:ii:ss') . $filename);
198
            $fp = new File($ddTmpFileName);
0 ignored issues
show
Deprecated Code introduced by
The class Cake\Filesystem\File has been deprecated with message: 4.0.0 Will be removed in 5.0.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
199
            $fp->write($filebody);
200
201
            // tmpディレクトリへのアップロードのエラー(パーミッションなど)
202
            if (!$this->tmpUpload($ddTmpFileName, $fieldSetting, $tmpFileName)) {
0 ignored issues
show
Documentation introduced by
$ddTmpFileName is of type string, but the function expects a object<Laminas\Diactoros\UploadedFile>.

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...
203
                throw new InternalErrorException('tmp upload error');
204
            }
205
            $fp->delete();
206
            $fp->close();
207
208
            $fileSet = [
209
                'model' => $this->getSource(),
0 ignored issues
show
Bug introduced by
It seems like getSource() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
210
                'model_id' => $this->id,
211
                'field_name' => $field,
212
                'file_name' => $filename,
213
                'file_content_type' => Configure::read('ContentsFile.Setting.type'),
214
                'file_size' => $filesize,
215
                'file_error' => 0,
216
            ];
217
218
            $fileSet['tmp_file_name'] = $tmpFileName;
219
220
            //これを残して次に引き渡したくないので
221
            unset($this->{$field});
222
223
            $this->{'contents_file_' . $field} = $fileSet;
224
            $this->{'contents_file_' . $field . '_filename'} = $filename;
225
        }
226
    }
227
228
    /**
229
     * getExt
230
     * 拡張子の取得
231
     *
232
     * @author hagiwara
233
     * @param string $file
234
     * @return string|null
235
     */
236
    private function getExt(string $file): ?string
237
    {
238
        $fileExplode = explode('.', $file);
239
        //この場合拡張子なし
240
        if (count($fileExplode) == 1) {
241
            return null;
242
        }
243
        return $fileExplode[(count($fileExplode) - 1)];
244
    }
245
246
    /**
247
     * tmpUpload
248
     * tmpディレクトリへのアップロード
249
     *
250
     * @author hagiwara
251
     * @param \Laminas\Diactoros\UploadedFile $fileInfo
252
     * @param array $fieldSetting
253
     * @param string $tmpFileName
254
     * @return mixed
255
     */
256
    private function tmpUpload(UploadedFile $fileInfo, array $fieldSetting, string $tmpFileName)
0 ignored issues
show
Unused Code introduced by
The parameter $fieldSetting is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
257
    {
258
        // すでにtraitのため、ここはif文での分岐処理
259
        if (Configure::read('ContentsFile.Setting.type') == 'normal') {
260
            $fileInfo->moveTo(Configure::read('ContentsFile.Setting.Normal.tmpDir') . $tmpFileName);
261
            // 向きの調整をする場合
262
            if (Configure::read('ContentsFile.Setting.exifRotate') == true) {
263
                $this->orientationFixedImage($tmpFileName, $tmpFileName);
264
            }
265
266
            return true;
267
268
        } elseif (Configure::read('ContentsFile.Setting.type') == 's3') {
269
            $tmpName = Configure::read('ContentsFile.Setting.S3.workingDir') . $tmpFileName;
270
            $fileInfo->moveTo($tmpName);
271
            $uploadFileName = Configure::read('ContentsFile.Setting.S3.tmpDir') . $tmpFileName;
272
273
            $S3 = new S3();
274
            return $S3->upload($tmpName, $uploadFileName);
275
        } else {
276
            throw new InternalErrorException('contentsFileConfig type illegal');
277
        }
278
    }
279
280
    /**
281
     * orientationFixedImage
282
     * http://www.glic.co.jp/blog/archives/88 よりコピペ
283
     * 画像の方向を正す
284
     * 向きだけロジックが逆そうなので調整
285
     *
286
     * @param string $input
287
     * @param string $output
288
     * @return void
289
     * @author hagiwara
290
     */
291
    private function orientationFixedImage(string $input, string $output): void
292
    {
293
        $imagetype = exif_imagetype($input);
294
        // 何も取れない場合何もしない
295
        if ($imagetype === false) {
296
            return;
297
        }
298
        // exif情報の取得
299
        $exif_datas = [];
300
        // 画像読み込み
301
        switch ($imagetype) {
302
            case IMAGETYPE_GIF:
303
                $image = ImageCreateFromGIF($input);
304
                break;
305
            case IMAGETYPE_JPEG:
306
                $image = ImageCreateFromJPEG($input);
307
                // exif情報の取得(jpegのみ
308
                $exif_datas = @exif_read_data($input);
309
                break;
310
            case IMAGETYPE_PNG:
311
                $image = ImageCreateFromPNG($input);
312
                break;
313
            default:
314
                $image = false;
315
        }
316
317
        // 画像以外は何もしない
318
        if (!$image) {
319
            return;
320
        }
321
322
        // 向き補正
323
        if(isset($exif_datas['Orientation'])){
324
            $orientation = $exif_datas['Orientation'];
325
            if($image){
326
                // 未定義
327
                if($orientation == 0) {
328
                    // 通常
329
                }else if($orientation == 1) {
330
                    // 左右反転
331
                }else if($orientation == 2) {
332
                    $image = $this->imageFlop($image);
333
                    // 180°回転
334
                }else if($orientation == 3) {
335
                    $image = $this->imageRotate($image,180, 0);
336
                    // 上下反転
337
                }else if($orientation == 4) {
338
                    $image = $this->imageFlip($image);
339
                    // 反時計回りに90°回転 上下反転
340
                }else if($orientation == 5) {
341
                    $image = $this->imageRotate($image,90, 0);
342
                    $image = $this->imageFlip($image);
343
                    // 時計回りに90°回転
344
                }else if($orientation == 6) {
345
                    $image = $this->imageRotate($image,-90, 0);
346
                    // 時計回りに90°回転 上下反転
347
                }else if($orientation == 7) {
348
                    $image = $this->imageRotate($image,-90, 0);
349
                    $image = $this->imageFlip($image);
350
                // 反時計回りに90°回転
351
                }else if($orientation == 8) {
352
                    $image = $this->imageRotate($image,90, 0);
353
                }
354
            }
355
        }
356
357
        switch ($imagetype) {
358
            case IMAGETYPE_GIF:
359
                ImageGIF($image ,$output);
360
                break;
361
            case IMAGETYPE_JPEG:
362
                ImageJPEG($image ,$output, 100);
363
                break;
364
            case IMAGETYPE_PNG:
365
                ImagePNG($image ,$output);
366
                break;
367
            default:
368
                return;
369
        }
370
    }
371
372
    /**
373
     * imageFlop
374
     * http://www.glic.co.jp/blog/archives/88 よりコピペ
375
     * 画像の左右反転
376
     *
377
     * @param resource $image
378
     * @return resource
379
     * @author hagiwara
380
     */
381
    private function imageFlop($image)
382
    {
383
        // 画像の幅を取得
384
        $w = imagesx($image);
385
        // 画像の高さを取得
386
        $h = imagesy($image);
387
        // 変換後の画像の生成(元の画像と同じサイズ)
388
        $destImage = @imagecreatetruecolor($w,$h);
389
        // 逆側から色を取得
390
        for($i=($w-1);$i>=0;$i--){
391
            for($j=0;$j<$h;$j++){
392
                $color_index = imagecolorat($image,$i,$j);
393
                $colors = imagecolorsforindex($image,$color_index);
394
                imagesetpixel($destImage,abs($i-$w+1),$j,imagecolorallocate($destImage,$colors["red"],$colors["green"],$colors["blue"]));
395
            }
396
        }
397
        return $destImage;
398
    }
399
400
    /**
401
     * imageFlip
402
     * http://www.glic.co.jp/blog/archives/88 よりコピペ
403
     * 上下反転
404
     * @param resource $image
405
     * @return resource
406
     *
407
     * @author hagiwara
408
     */
409
    private function imageFlip($image)
410
    {
411
        // 画像の幅を取得
412
        $w = imagesx($image);
413
        // 画像の高さを取得
414
        $h = imagesy($image);
415
        // 変換後の画像の生成(元の画像と同じサイズ)
416
        $destImage = @imagecreatetruecolor($w,$h);
417
        // 逆側から色を取得
418
        for($i=0;$i<$w;$i++){
419
            for($j=($h-1);$j>=0;$j--){
420
                $color_index = imagecolorat($image,$i,$j);
421
                $colors = imagecolorsforindex($image,$color_index);
422
                imagesetpixel($destImage,$i,abs($j-$h+1),imagecolorallocate($destImage,$colors["red"],$colors["green"],$colors["blue"]));
423
            }
424
        }
425
        return $destImage;
426
    }
427
428
429
    /**
430
     * imageRotate
431
     * http://www.glic.co.jp/blog/archives/88 よりコピペ
432
     * 画像を回転
433
     * @param resouce $image
434
     * @param integer $angle
435
     * @param integer $bgd_color
436
     * @return resource
437
     *
438
     * @author hagiwara
439
     */
440
    private function imageRotate($image, int $angle, int $bgd_color)
441
    {
442
        return imagerotate($image, $angle, $bgd_color, 0);
443
    }
444
}
445