ContentsFileBehavior   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 219
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 30
lcom 1
cbo 11
dl 0
loc 219
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
B afterSave() 0 60 10
B fileDelete() 0 20 7
A fileValidationWhen() 0 21 4
A fileDeleteParts() 0 21 3
A mkdir() 0 10 2
A makeRandomPath() 0 13 2
1
<?php
2
3
namespace ContentsFile\Model\Behavior;
4
5
use ArrayObject;
6
use Cake\Core\Configure;
7
use Cake\Event\Event;
8
use Cake\Network\Exception\InternalErrorException;
9
use Cake\ORM\Behavior;
10
use Cake\Datasource\EntityInterface;
11
use Cake\ORM\Table;
12
use Cake\ORM\TableRegistry;
13
use ContentsFile\Model\Behavior\Traits\NormalContentsFileBehaviorTrait;
14
use ContentsFile\Model\Behavior\Traits\S3ContentsFileBehaviorTrait;
15
use ContentsFile\Model\Behavior\Traits\ImageContentsFileBehaviorTrait;
16
use Cake\Utility\Security;
17
18
class ContentsFileBehavior extends Behavior {
19
20
    use NormalContentsFileBehaviorTrait;
21
    use S3ContentsFileBehaviorTrait;
22
    use ImageContentsFileBehaviorTrait;
23
24
    /**
25
     * __construct
26
     * @author hagiwara
27
     * @param Table $table
28
     * @param array $config
29
     */
30
    public function __construct(Table $table, array $config = [])
31
    {
32
        parent::__construct($table, $config);
33
        // 指定外のものが指定されている場合はエラーとする
34
        if (!in_array(Configure::read('ContentsFile.Setting.type'), ['s3', 'normal'])) {
35
            throw new InternalErrorException('contentsFileConfig type illegal');
36
        }
37
        // Configureの設定不足をチェックする
38
        $this->{Configure::read('ContentsFile.Setting.type') . 'ParamCheck'}();
39
}
40
41
    /**
42
     * afterSave
43
     * 画像をafterSaveで保存する
44
     * @author hagiwara
45
     * @param Event $event
46
     * @param EntityInterface $entity
47
     * @param ArrayObject $options
48
     * @return bool
49
     */
50
    public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options): bool
0 ignored issues
show
Unused Code introduced by
The parameter $event 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...
Unused Code introduced by
The parameter $options 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...
51
    {
52
        //設定値をentityから取得
53
        $contentsFileConfig = $entity->getContentsFileSettings();
0 ignored issues
show
Bug introduced by
The method getContentsFileSettings() does not seem to exist on object<Cake\Datasource\EntityInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
54
        $attachmentModel = TableRegistry::getTableLocator()->get('ContentsFile.Attachments');
55
        foreach ($contentsFileConfig['fields'] as $field => $fieldSettings) {
56
            // ファイルの削除を最初に確認
57
            if ($entity->{'delete_' . $field} == true) {
58
                // 該当フィールドを削除
59
                if (!$this->fileDelete($entity, [$field])) {
60
                    return false;
61
                }
62
                // ファイルの削除に成功したら保存処理は飛ばす
63
                continue;
64
            }
65
            //contents_file_の方に入ったentityをベースに処理する
66
            $fileInfo = $entity->{'contents_file_' . $field};
67
            if (
68
                !empty($fileInfo) &&
69
                //tmp_file_nameがある=アップロードしたファイルがある
70
                array_key_exists('tmp_file_name', $fileInfo)
71
            ) {
72
                // ファイルの削除
73
                $attachmentSaveData = [
74
                    'model' => $fileInfo['model'],
75
                    'model_id' => $entity->id,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
76
                    'field_name' => $fileInfo['field_name'],
77
                    'file_name' => $fileInfo['file_name'],
78
                    'file_content_type' => $fileInfo['file_content_type'],
79
                    'file_size' => $fileInfo['file_size'],
80
                ];
81
                if (Configure::read('ContentsFile.Setting.randomFile') === true) {
82
                    $attachmentSaveData['file_random_path'] = $this->makeRandomPath();
83
                }
84
                $attachmentEntity = $attachmentModel->newEntity($attachmentSaveData);
85
                //元のデータがあるかfind(あれば元のファイルを消す)
86
                $oldAttachmentData = $attachmentModel->find('all')
87
                    ->where(['model' => $fileInfo['model']])
88
                    ->where(['model_id' => $entity->id])
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
89
                    ->where(['field_name' => $fileInfo['field_name']])
90
                    ->first();
91
92
                // 通常とS3で画像保存方法の切り替え
93
                if (!$this->{Configure::read('ContentsFile.Setting.type') . 'FileSave'}($fileInfo, $fieldSettings, $attachmentSaveData, $oldAttachmentData)) {
94
                    return false;
95
                }
96
97
                //元のデータがあれば更新にする
98
                if (!empty($oldAttachmentData)) {
99
                    $attachmentEntity->id = $oldAttachmentData->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
100
                }
101
                if (!$attachmentModel->save($attachmentEntity)) {
102
                    return false;
103
                }
104
            }
105
        }
106
107
        return true;
108
109
    }
110
111
    /**
112
     * fileDelete
113
     * ファイル削除
114
     * @author hagiwara
115
     * @param EntityInterface $entity
116
     * @param array $fields
117
     * @return bool
118
     */
119
    public function fileDelete(EntityInterface $entity, array $fields = []): bool
120
    {
121
        // 新規作成データ時は何もしない
122
        if (empty($entity->id)) {
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
123
            return true;
124
        }
125
        $contentsFileConfig = $entity->getContentsFileSettings();
0 ignored issues
show
Bug introduced by
The method getContentsFileSettings() does not seem to exist on object<Cake\Datasource\EntityInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
126
        if (!empty($contentsFileConfig['fields'])) {
127
            foreach ($contentsFileConfig['fields'] as $field => $config) {
128
                // fieldsの指定がない場合は全部消す
129
                if (!empty($fields) && !in_array($field, $fields)) {
130
                    continue;
131
                }
132
                if (!$this->fileDeleteParts($entity, $field)) {
133
                    return false;
134
                }
135
            }
136
        }
137
        return true;
138
    }
139
140
    /**
141
     * fileValidationWhen
142
     * ファイルのバリデーションのwhenに使用可能なメソッド
143
     * @author hagiwara
144
     * @param array $context
145
     * @param string $field
146
     * @return bool
147
     */
148
    public function fileValidationWhen(array $context, string $field): bool
149
    {
150
        // 初期遷移時などでdataがそもそもいない場合はチェックしない
151
        if ( empty($context['data']) ) {
152
            return false;
153
        }
154
        // content_file_fileがいる場合はチェックしない
155
        if (!empty($context['data']['contents_file_' . $field])) {
156
            return false;
157
        }
158
159
        // 新規作成時はチェックする
160
        if ($context['newRecord'] == true) {
161
            return true;
162
        }
163
        $fileInfo = $this->_table->find('all')
164
            ->where([$this->_table->aliasField('id') => $context['data']['id']])
165
            ->first();
166
        // 編集時はfileがアップロードされていなければチェックする
167
        return empty($fileInfo->{'contents_file_' . $field});
168
    }
169
170
    /**
171
     * fileDeleteParts
172
     * ファイル削除
173
     * @author hagiwara
174
     * @param EntityInterface $entity
175
     * @param string $field
176
     */
177
    private function fileDeleteParts(EntityInterface $entity, string $field): bool
178
    {
179
        $attachmentModel = TableRegistry::getTableLocator()->get('ContentsFile.Attachments');
180
        $modelName = $entity->getSource();
181
        $modelId = $entity->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
182
        // 添付ファイルデータの削除
183
        $deleteAttachmentData = $attachmentModel->find('all')
184
            ->where(['Attachments.model' => $modelName])
185
            ->where(['Attachments.model_id' => $modelId])
186
            ->where(['Attachments.field_name' => $field])
187
            ->first();
188
189
        if (!empty($deleteAttachmentData->id)) {
190
            // 通常とS3でファイルの削除方法の切り替え
191
            if (!$this->{Configure::read('ContentsFile.Setting.type') . 'FileDelete'}($modelName, $modelId, $field)) {
192
                return false;
193
            }
194
            $attachmentModel->delete($deleteAttachmentData);
0 ignored issues
show
Bug introduced by
It seems like $deleteAttachmentData defined by $attachmentModel->find('...e' => $field))->first() on line 183 can also be of type array; however, Cake\ORM\Table::delete() does only seem to accept object<Cake\Datasource\EntityInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
195
        }
196
        return true;
197
    }
198
199
    /**
200
     * mkdir
201
     * ディレクトリの作成(パーミッションの設定のため
202
     * @author hagiwara
203
     * @param string $path
204
     * @param integer $permission
205
     * @param boolean $recursive
206
     */
207
    private function mkdir(string $path, $permission, bool $recursive)
208
    {
209
        if (is_dir($path)) {
210
            return true;
211
        }
212
        $oldumask = umask(0);
213
        $result = mkdir($path, $permission, $recursive);
214
        umask($oldumask);
215
        return $result;
216
    }
217
218
    /**
219
     * makeRandomKey
220
     * @author hagiwara
221
     * @return string
222
     */
223
    private function makeRandomPath(): string
224
    {
225
        $hash = Security::hash(time() . rand());
226
        $attachmentModel = TableRegistry::getTableLocator()->get('ContentsFile.Attachments');
227
        $check = $attachmentModel->find('all')
228
            ->where(['Attachments.file_random_path' => $hash])
229
            ->count();
230
        // データがある場合は再作成
231
        if ($check > 0) {
232
            return $this->makeRandomPath();
233
        }
234
        return $hash;
235
    }
236
}
237