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; |
|
|
|
|
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()]) |
|
|
|
|
73
|
|
|
->where(['model_id' => $this->id]) |
|
|
|
|
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]; |
|
|
|
|
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(), |
|
|
|
|
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(); |
|
|
|
|
162
|
|
|
|
163
|
|
|
$this->set('contents_file_' . $field, $fileSet); |
|
|
|
|
164
|
|
|
$this->set('contents_file_' . $field . '_filename', $fileInfo->getClientFilename()); |
|
|
|
|
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); |
|
|
|
|
199
|
|
|
$fp->write($filebody); |
200
|
|
|
|
201
|
|
|
// tmpディレクトリへのアップロードのエラー(パーミッションなど) |
202
|
|
|
if (!$this->tmpUpload($ddTmpFileName, $fieldSetting, $tmpFileName)) { |
|
|
|
|
203
|
|
|
throw new InternalErrorException('tmp upload error'); |
204
|
|
|
} |
205
|
|
|
$fp->delete(); |
206
|
|
|
$fp->close(); |
207
|
|
|
|
208
|
|
|
$fileSet = [ |
209
|
|
|
'model' => $this->getSource(), |
|
|
|
|
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) |
|
|
|
|
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
|
|
|
|
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: