Completed
Push — ezp30878_cant_add_image_with_p... ( 61f9a2...e19ea7 )
by
unknown
21:20
created

LegacyDFSCluster::getFileInsertQuery()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 0
dl 0
loc 27
rs 9.488
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of the eZ Publish Legacy package.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributd with this source code.
8
 */
9
namespace eZ\Publish\Core\IO\IOMetadataHandler;
10
11
use DateTime;
12
use eZ\Publish\Core\IO\IOMetadataHandler;
13
use Doctrine\DBAL\Connection;
14
use Doctrine\DBAL\DBALException;
15
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
16
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
17
use eZ\Publish\Core\IO\Exception\BinaryFileNotFoundException;
18
use eZ\Publish\Core\IO\Exception\InvalidBinaryFileIdException;
19
use eZ\Publish\Core\IO\UrlDecorator;
20
use eZ\Publish\SPI\IO\BinaryFileCreateStruct as SPIBinaryFileCreateStruct;
21
use eZ\Publish\SPI\IO\BinaryFile as SPIBinaryFile;
22
use RuntimeException;
23
24
/**
25
 * Manages IO metadata in a mysql table, ezdfsfile.
26
 *
27
 * It will prevent simultaneous writes to the same file.
28
 */
29
class LegacyDFSCluster implements IOMetadataHandler
30
{
31
    /** @var Connection */
32
    private $db;
33
34
    /** @var UrlDecorator */
35
    private $urlDecorator;
36
37
    /**
38
     * @param Connection $connection Doctrine DBAL connection
39
     * @param UrlDecorator $urlDecorator The URL decorator used to add a prefix to files path
40
     */
41
    public function __construct(Connection $connection, UrlDecorator $urlDecorator = null)
42
    {
43
        $this->db = $connection;
44
        $this->urlDecorator = $urlDecorator;
45
    }
46
47
    /**
48
     * Inserts a new reference to file $spiBinaryFileId.
49
     *
50
     * @since 6.10 The mtime of the $binaryFileCreateStruct must be a DateTime, as specified in the struct doc.
51
     *
52
     * @param SPIBinaryFileCreateStruct $binaryFileCreateStruct
53
     *
54
     * @throws InvalidArgumentException if the $binaryFileCreateStruct is invalid
55
     * @throws RuntimeException if a DBAL error occurs
56
     *
57
     * @return \eZ\Publish\SPI\IO\BinaryFile
58
     */
59
    public function create(SPIBinaryFileCreateStruct $binaryFileCreateStruct)
60
    {
61
        if (!($binaryFileCreateStruct->mtime instanceof DateTime)) {
62
            throw new InvalidArgumentException('$binaryFileCreateStruct', 'Property \'mtime\' must be a DateTime');
63
        }
64
65
        $path = $this->addPrefix($binaryFileCreateStruct->id);
66
67
        try {
68
            /*
69
             * @todo what might go wrong here ? Can another process be trying to insert the same image ?
70
             *       what happens if somebody did ?
71
             **/
72
            $stmt = $this->db->prepare($this->getFileInsertQuery());
73
            $stmt->bindValue('name', $path);
74
            $stmt->bindValue('name_hash', md5($path));
75
            $stmt->bindValue('name_trunk', $this->getNameTrunk($binaryFileCreateStruct));
76
            $stmt->bindValue('mtime', $binaryFileCreateStruct->mtime->getTimestamp());
77
            $stmt->bindValue('size', $binaryFileCreateStruct->size);
78
            $stmt->bindValue('scope', $this->getScope($binaryFileCreateStruct));
79
            $stmt->bindValue('datatype', $binaryFileCreateStruct->mimeType);
80
            $stmt->execute();
81
        } catch (DBALException $e) {
82
            throw new RuntimeException("A DBAL error occured while writing $path", 0, $e);
83
        }
84
85
        return $this->mapSPIBinaryFileCreateStructToSPIBinaryFile($binaryFileCreateStruct);
86
    }
87
88
    /**
89
     * Deletes file $spiBinaryFileId.
90
     *
91
     * @throws BinaryFileNotFoundException If $spiBinaryFileId is not found
92
     *
93
     * @param string $spiBinaryFileId
94
     */
95
    public function delete($spiBinaryFileId)
96
    {
97
        $path = $this->addPrefix($spiBinaryFileId);
98
99
        // Unlike the legacy cluster, the file is directly deleted. It was inherited from the DB cluster anyway
100
        $stmt = $this->db->prepare('DELETE FROM ezdfsfile WHERE name_hash LIKE :name_hash');
101
        $stmt->bindValue('name_hash', md5($path));
102
        $stmt->execute();
103
104
        if ($stmt->rowCount() != true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $stmt->rowCount() of type integer to the boolean true. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
105
            // Is this really necessary ?
106
            throw new BinaryFileNotFoundException($path);
107
        }
108
    }
109
110
    /**
111
     * Loads and returns metadata for $spiBinaryFileId.
112
     *
113
     * @param string $spiBinaryFileId
114
     *
115
     * @return SPIBinaryFile
116
     *
117
     * @throws BinaryFileNotFoundException if no row is found for $spiBinaryFileId
118
     * @throws DBALException Any unhandled DBAL exception
119
     */
120
    public function load($spiBinaryFileId)
121
    {
122
        $path = $this->addPrefix($spiBinaryFileId);
123
124
        $stmt = $this->db->prepare('SELECT * FROM ezdfsfile WHERE name_hash LIKE ? AND expired != true AND mtime > 0');
125
        $stmt->bindValue(1, md5($path));
126
        $stmt->execute();
127
128
        if ($stmt->rowCount() == 0) {
129
            throw new BinaryFileNotFoundException($path);
130
        }
131
132
        $row = $stmt->fetch(\PDO::FETCH_ASSOC) + ['id' => $spiBinaryFileId];
133
134
        return $this->mapArrayToSPIBinaryFile($row);
135
    }
136
137
    /**
138
     * Checks if a file $spiBinaryFileId exists.
139
     *
140
     * @param string $spiBinaryFileId
141
     *
142
     * @throws NotFoundException
143
     * @throws DBALException Any unhandled DBAL exception
144
     *
145
     * @return bool
146
     */
147 View Code Duplication
    public function exists($spiBinaryFileId)
148
    {
149
        $path = $this->addPrefix($spiBinaryFileId);
150
151
        $stmt = $this->db->prepare('SELECT name FROM ezdfsfile WHERE name_hash LIKE ? and mtime > 0 and expired != true');
152
        $stmt->bindValue(1, md5($path));
153
        $stmt->execute();
154
155
        return $stmt->rowCount() == 1;
156
    }
157
158
    /**
159
     * @param SPIBinaryFileCreateStruct $binaryFileCreateStruct
160
     *
161
     * @return mixed
162
     */
163
    protected function getNameTrunk(SPIBinaryFileCreateStruct $binaryFileCreateStruct)
164
    {
165
        return $this->addPrefix($binaryFileCreateStruct->id);
166
    }
167
168
    /**
169
     * Returns the value for the scope meta field, based on the created file's path.
170
     *
171
     * Note that this is slightly incorrect, as it will return binaryfile for media files as well. It is a bit
172
     * of an issue, but shouldn't be a blocker given that this meta field isn't used that much.
173
     *
174
     * @param SPIBinaryFileCreateStruct $binaryFileCreateStruct
175
     *
176
     * @return string
177
     */
178
    protected function getScope(SPIBinaryFileCreateStruct $binaryFileCreateStruct)
179
    {
180
        list($filePrefix) = explode('/', $binaryFileCreateStruct->id);
181
182
        switch ($filePrefix) {
183
            case 'images':
184
                return 'image';
185
186
            case 'original':
187
                return 'binaryfile';
188
        }
189
190
        return 'UNKNOWN_SCOPE';
191
    }
192
193
    /**
194
     * Adds the internal prefix string to $id.
195
     *
196
     * @param string $id
197
     *
198
     * @return string prefixed id
199
     */
200
    protected function addPrefix($id)
201
    {
202
        return isset($this->urlDecorator) ? $this->urlDecorator->decorate($id) : $id;
203
    }
204
205
    /**
206
     * Removes the internal prefix string from $prefixedId.
207
     *
208
     * @param string $prefixedId
209
     *
210
     * @return string the id without the prefix
211
     *
212
     * @throws InvalidBinaryFileIdException if the prefix isn't found in $prefixedId
213
     */
214
    protected function removePrefix($prefixedId)
215
    {
216
        return isset($this->urlDecorator) ? $this->urlDecorator->undecorate($prefixedId) : $prefixedId;
217
    }
218
219 View Code Duplication
    public function getMimeType($spiBinaryFileId)
220
    {
221
        $stmt = $this->db->prepare('SELECT * FROM ezdfsfile WHERE name_hash LIKE ? AND expired != true AND mtime > 0');
222
        $stmt->bindValue(1, md5($this->addPrefix($spiBinaryFileId)));
223
        $stmt->execute();
224
225
        if ($stmt->rowCount() == 0) {
226
            throw new BinaryFileNotFoundException($spiBinaryFileId);
227
        }
228
229
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
230
231
        return $row['datatype'];
232
    }
233
234
    /**
235
     * Delete directory and all the content under specified directory.
236
     *
237
     * @param string $spiPath SPI Path, not prefixed by URL decoration
238
     */
239
    public function deleteDirectory($spiPath)
240
    {
241
        $query = $this->db->createQueryBuilder();
242
        $query
243
            ->delete('ezdfsfile')
244
            ->where('name LIKE :spiPath ESCAPE :esc')
245
            ->setParameter(':esc', '\\')
246
            ->setParameter(
247
                ':spiPath',
248
                addcslashes($this->addPrefix(rtrim($spiPath, '/')), '%_') . '/%'
249
            );
250
        $query->execute();
251
    }
252
253
    /**
254
     * Maps an array of data base properties (id, size, mtime, datatype, md5_path, path...) to an SPIBinaryFile object.
255
     *
256
     * @param array $properties database properties array
257
     *
258
     * @return SPIBinaryFile
259
     */
260
    protected function mapArrayToSPIBinaryFile(array $properties)
261
    {
262
        $spiBinaryFile = new SPIBinaryFile();
263
        $spiBinaryFile->id = $properties['id'];
264
        $spiBinaryFile->size = $properties['size'];
265
        $spiBinaryFile->mtime = new DateTime('@' . $properties['mtime']);
266
        $spiBinaryFile->mimeType = $properties['datatype'];
267
268
        return $spiBinaryFile;
269
    }
270
271
    /**
272
     * @param SPIBinaryFileCreateStruct $binaryFileCreateStruct
273
     *
274
     * @return SPIBinaryFile
275
     */
276
    protected function mapSPIBinaryFileCreateStructToSPIBinaryFile(SPIBinaryFileCreateStruct $binaryFileCreateStruct)
277
    {
278
        $spiBinaryFile = new SPIBinaryFile();
279
        $spiBinaryFile->id = $binaryFileCreateStruct->id;
280
        $spiBinaryFile->mtime = $binaryFileCreateStruct->mtime;
281
        $spiBinaryFile->size = $binaryFileCreateStruct->size;
282
        $spiBinaryFile->mimeType = $binaryFileCreateStruct->mimeType;
283
284
        return $spiBinaryFile;
285
    }
286
287
    /**
288
     * Generate the correct SQL query to insert an entry.
289
     *
290
     * @return string
291
     */
292
    protected function getFileInsertQuery()
293
    {
294
        $dbDriver = $this->db->getDriver();
295
296
        if ($dbDriver instanceof Connection && $dbDriver->getName() === 'pdo_mysql') {
297
            return <<<SQL
298
INSERT INTO ezdfsfile
299
(name, name_hash, name_trunk, mtime, size, scope, datatype)
300
VALUES (:name, :name_hash, :name_trunk, :mtime, :size, :scope, :datatype)
301
ON DUPLICATE KEY UPDATE
302
datatype=VALUES(datatype), scope=VALUES(scope), size=VALUES(size),
303
mtime=VALUES(mtime)
304
SQL;
305
        }
306
307
        // ANSI compatible: Postgres and others
308
        return <<<SQL
309
MERGE INTO ezdfsfile
310
USING ( (name, name_hash, name_trunk, mtime, size, scope, datatype)
311
        VALUES (:name, :name_hash, :name_trunk, :mtime, :size, :scope, :datatype) ) v
312
ON v.name_hash = ezdfsfile.name_hash
313
WHEN NOT MATCHED
314
    INSERT VALUES(name, name_hash, name_trunk, mtime, size, scope, datatype)
315
WHEN MATCHED
316
    UPDATE SET datatype=v.datatype, scope=v.scope, size=v.size, mtime=v.mtime
317
SQL;
318
    }
319
}
320