Dirty.php 14.6 KB
Newer Older
Carsten  Rose's avatar
Carsten Rose committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 3/13/17
 * Time: 9:29 PM
 */

namespace qfq;

use qfq;

require_once(__DIR__ . '/../store/Session.php');
require_once(__DIR__ . '/../Constants.php');
require_once(__DIR__ . '/../database/Database.php');
require_once(__DIR__ . '/../../qfq/store/Client.php');
17
require_once(__DIR__ . '/../exceptions/UserFormException.php');
Carsten  Rose's avatar
Carsten Rose committed
18

Carsten  Rose's avatar
Carsten Rose committed
19
20
21
22
23
24
25
26
/**
 * Class Dirty
 * Process Record locking in mode DIRTY_MODE_ADVISORY, DIRTY_MODE_EXCLUSIVE or DIRTY_MODE_NONE.
 * Two entry points: process() and checkDirtyAndRelease().
 * Check doc/diagram/*.png for detailed workflow.
 *
 * @package qfq
 */
Carsten  Rose's avatar
Carsten Rose committed
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Dirty {

    /**
     * @var Database instantiated class
     */
    protected $db = null;

    /**
     * @var array
     */
    protected $client = array();

    /**
     * @var Session
     */
    private $session = null;

    /**
Carsten  Rose's avatar
Carsten Rose committed
45
     * Init class
Carsten  Rose's avatar
Carsten Rose committed
46
     */
Carsten  Rose's avatar
Carsten Rose committed
47
    public function __construct($phpUnit = false) {
Carsten  Rose's avatar
Carsten Rose committed
48
49
50

        $this->session = Session::getInstance($phpUnit);
        $this->client = Client::getParam();
Carsten  Rose's avatar
Carsten Rose committed
51
52
53
        if (!isset($this->client[DIRTY_RECORD_HASH_MD5])) {
            $this->client[DIRTY_RECORD_HASH_MD5] = '';
        }
Carsten  Rose's avatar
Carsten Rose committed
54
55
56
57
        $this->db = new Database();
    }

    /**
Carsten  Rose's avatar
Carsten Rose committed
58
59
     * Handle any lock requests submitted via api/dirty.php.
     *
Carsten  Rose's avatar
Carsten Rose committed
60
61
62
63
64
65
     * @return array|int
     * @throws CodeException
     * @throws DbException
     * @throws UserFormException
     */
    public function process() {
Carsten  Rose's avatar
Carsten Rose committed
66
        $tableVars = array();
Carsten  Rose's avatar
Carsten Rose committed
67
68
69
70
71

        $sipClass = new Sip();

        $sipVars = $sipClass->getVarsFromSip($this->client[SIP_SIP]);

Carsten  Rose's avatar
Carsten Rose committed
72
        if (empty($sipVars[SIP_FORM])) {
Carsten  Rose's avatar
Carsten Rose committed
73
            throw new CodeException("Missing 'form' in SIP. There might be something broken.", ERROR_DIRTY_MISSING_FORM_IN_SIP);
Carsten  Rose's avatar
Carsten Rose committed
74
75
76
        }

        $recordId = empty($sipVars[SIP_RECORD_ID]) ? 0 : $sipVars[SIP_RECORD_ID];
77
78
79
80
        if ($recordId == 0) {
            // For r=0 (new) , 'dirty' will always succeed.
            return [API_STATUS => 'success', API_MESSAGE => ''];
        } else {
Carsten  Rose's avatar
Carsten Rose committed
81
            $tableVars = $this->db->sql("SELECT tableName, dirtyMode, recordLockTimeoutSeconds FROM Form AS f WHERE f.name=?", ROW_EXPECT_1, [$sipVars[SIP_FORM]], "Form not found: '" . $sipVars[SIP_FORM] . "'");
Carsten  Rose's avatar
Carsten Rose committed
82
83
        }

84
        switch ($this->client[API_LOCK_ACTION]) {
Carsten  Rose's avatar
Carsten Rose committed
85
            case API_LOCK_ACTION_LOCK:
86
            case API_LOCK_ACTION_EXTEND:
Carsten  Rose's avatar
Carsten Rose committed
87
                $answer = $this->acquireDirty($recordId, $tableVars, $this->client[DIRTY_RECORD_HASH_MD5]);
Carsten  Rose's avatar
Carsten Rose committed
88
89
                break;
            case API_LOCK_ACTION_RELEASE:
Carsten  Rose's avatar
Carsten Rose committed
90
                $answer = $this->checkDirtyAndRelease(FORM_SAVE, $tableVars[F_RECORD_LOCK_TIMEOUT_SECONDS], $tableVars[F_DIRTY_MODE], $tableVars[F_TABLE_NAME], $recordId);
Carsten  Rose's avatar
Carsten Rose committed
91
92
                break;
            default;
Carsten  Rose's avatar
Carsten Rose committed
93
                throw new CodeException("Unknown action: " . $this->client[API_LOCK_ACTION], ERROR_DIRTY_UNKNOWN_ACTION);
Carsten  Rose's avatar
Carsten Rose committed
94
95
96
        }

        return $answer;
Carsten  Rose's avatar
Carsten Rose committed
97
98
99
    }

    /**
100
     * Tries to get a 'DirtyRecord'. Returns an array (becomes JSON) about success or failure.
Carsten  Rose's avatar
Carsten Rose committed
101
     *
Carsten  Rose's avatar
Carsten Rose committed
102
103
104
     * @param int    $recordId
     * @param array  $tableVars
     * @param string $recordHashMd5
Carsten  Rose's avatar
Carsten Rose committed
105
     * @return array
Carsten  Rose's avatar
Carsten Rose committed
106
     * @throws \qfq\CodeException
Carsten  Rose's avatar
Carsten Rose committed
107
     */
Carsten  Rose's avatar
Carsten Rose committed
108
    private function acquireDirty($recordId, array $tableVars, $recordHashMd5) {
Carsten  Rose's avatar
Carsten Rose committed
109
110
111

        $tableName = $tableVars[F_TABLE_NAME];
        $formDirtyMode = $tableVars[F_DIRTY_MODE];
Carsten  Rose's avatar
Carsten Rose committed
112
113
114
115
116
117
118
        $rcMd5 = '';

        // Check for changed record. Compute $rcMd5
        $flagModified = $this->isRecordModified($tableName, $recordId, $recordHashMd5, $rcMd5);
        if (($recordHashMd5 != '') && $flagModified) {
            return [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => 'The record has been modified in the meantime. Please reload the form, edit and save again.'];
        }
Carsten  Rose's avatar
Carsten Rose committed
119
120
121
122

        $feUser = $this->session->get(SESSION_FE_USER);

        // Look for already existing dirty record.
Carsten  Rose's avatar
Carsten Rose committed
123
        $recordDirty = $this->getRecordDirty($tableName, $recordId);
Carsten  Rose's avatar
Carsten Rose committed
124

125
126
127
128
        // Check if the record is timed out - owner doesn't matter.
        if (count($recordDirty) != 0 && $recordDirty[DIRTY_EXPIRE] < date('Y-m-d H:i:s')) {
            $this->deleteDirtyRecord($recordDirty[COLUMN_ID]);
            $recordDirty = array();
Carsten  Rose's avatar
Carsten Rose committed
129
130
        }

Carsten  Rose's avatar
Carsten Rose committed
131
        if (count($recordDirty) == 0) {
132
133
134
135
            if ($formDirtyMode == DIRTY_MODE_NONE) {
                $answer = [API_STATUS => 'success', API_MESSAGE => ''];
            } else {
                // No dirty record found.
Carsten  Rose's avatar
Carsten Rose committed
136
                $answer = $this->writeDirty($this->client[SIP_SIP], $recordId, $tableVars, $feUser, $rcMd5);
137
            }
Carsten  Rose's avatar
Carsten Rose committed
138
        } else {
Carsten  Rose's avatar
Carsten Rose committed
139
            $answer = $this->conflict($recordDirty, $formDirtyMode);
Carsten  Rose's avatar
Carsten Rose committed
140
141
142
143
144
        }

        return $answer;
    }

Carsten  Rose's avatar
Carsten Rose committed
145
146
147
    /**
     * Load (if exist) a DirtyRecord (lock).
     *
Carsten  Rose's avatar
Carsten Rose committed
148
149
     * @param string $tableName
     * @param int    $recordId
Carsten  Rose's avatar
Carsten Rose committed
150
151
152
153
154
155
     * @return array   DirtyRecord or empty array.
     * @throws CodeException
     * @throws DbException
     */
    private function getRecordDirty($tableName, $recordId) {

Carsten  Rose's avatar
Carsten Rose committed
156
        $recordDirty = $this->db->sql("SELECT * FROM Dirty AS d WHERE d.tableName LIKE ? AND recordId=? ",
Carsten  Rose's avatar
Carsten Rose committed
157
158
159
160
161
            ROW_EXPECT_0_1, [$tableName, $recordId]);

        return $recordDirty;
    }

Carsten  Rose's avatar
Carsten Rose committed
162
    /**
Carsten  Rose's avatar
Carsten Rose committed
163
     *
Carsten  Rose's avatar
Carsten Rose committed
164
165
     * @param array  $recordDirty
     * @param string $currentFormDirtyMode
Carsten  Rose's avatar
Carsten Rose committed
166
167
     * @return array
     */
Carsten  Rose's avatar
Carsten Rose committed
168
    private function conflict(array $recordDirty, $currentFormDirtyMode) {
Carsten  Rose's avatar
Carsten Rose committed
169
        $status = API_ANSWER_STATUS_CONFLICT;
Carsten  Rose's avatar
Carsten Rose committed
170
        $at = "at " . $recordDirty[COLUMN_CREATED] . " from " . $recordDirty[DIRTY_REMOTE_ADDRESS];
Carsten  Rose's avatar
Carsten Rose committed
171

172
        // Compare modified timestamp
Carsten  Rose's avatar
Carsten Rose committed
173
        if ($this->isRecordModified($recordDirty[DIRTY_TABLE_NAME], $recordDirty[DIRTY_RECORD_ID], $recordDirty[DIRTY_RECORD_HASH_MD5], $dummy)) {
174
175
176
            return [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => 'The record has been modified in the meantime. Please reload the form, edit and save again.'];
        }

Carsten  Rose's avatar
Carsten Rose committed
177
        if ($this->client[CLIENT_COOKIE_QFQ] == $recordDirty[DIRTY_QFQ_USER_SESSION_COOKIE]) {
Carsten  Rose's avatar
Carsten Rose committed
178
            $msg = "The record has already been locked by you (maybe in another browser tab) $at!";
179
            $status = ($recordDirty[F_DIRTY_MODE] == DIRTY_MODE_EXCLUSIVE) ? API_ANSWER_STATUS_CONFLICT : API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE;
Carsten  Rose's avatar
Carsten Rose committed
180
181
182

        } else {

Carsten  Rose's avatar
Carsten Rose committed
183
            if (empty($recordDirty[DIRTY_FE_USER])) {
Carsten  Rose's avatar
Carsten Rose committed
184
185
                $msgUser = "another user";
            } else {
Carsten  Rose's avatar
Carsten Rose committed
186
                $msgUser = "user '$recordDirty[DIRTY_FE_USER]'";
Carsten  Rose's avatar
Carsten Rose committed
187
188
            }

Carsten  Rose's avatar
Carsten Rose committed
189
            $msg = "The record has already been locked by $msgUser at $at.";
Carsten  Rose's avatar
Carsten Rose committed
190
191

            // Mandatory lock on Record or current Form?
Carsten  Rose's avatar
Carsten Rose committed
192
            if ($recordDirty[F_DIRTY_MODE] == DIRTY_MODE_EXCLUSIVE || $currentFormDirtyMode == DIRTY_MODE_EXCLUSIVE) {
Carsten  Rose's avatar
Carsten Rose committed
193
194
195
196
197
198
199
200
201
202
203
204
                $status = API_ANSWER_STATUS_CONFLICT;
            } else {
                $status = API_ANSWER_STATUS_CONFLICT_ALLOW_FORCE;
            }
        }

        return [API_STATUS => $status, API_MESSAGE => $msg];
    }

    /**
     * Write a 'Dirty'-Record.
     *
Carsten  Rose's avatar
Carsten Rose committed
205
     * @param string $s SIP given by URL GET
Carsten  Rose's avatar
Carsten Rose committed
206
207
     * @param int    $recordId extracted from SIP
     * @param array  $tableVars columns: F_TABLE_NAME, F_DIRTY_MODE, F_RECORD_LOCK_TIMEOUT_SECONDS
Carsten  Rose's avatar
Carsten Rose committed
208
     * @param string $feUser
Carsten  Rose's avatar
Carsten Rose committed
209
     * @param string $recordHashMd5
Carsten  Rose's avatar
Carsten Rose committed
210
     * @return array
Carsten  Rose's avatar
Carsten Rose committed
211
212
     * @throws \qfq\CodeException
     * @throws \qfq\DbException
Carsten  Rose's avatar
Carsten Rose committed
213
     */
Carsten  Rose's avatar
Carsten Rose committed
214
    private function writeDirty($s, $recordId, array $tableVars, $feUser, $recordHashMd5) {
Carsten  Rose's avatar
Carsten Rose committed
215
216
217

        $tableName = $tableVars[F_TABLE_NAME];
        $formDirtyMode = $tableVars[F_DIRTY_MODE];
Carsten  Rose's avatar
Carsten Rose committed
218

219
        $record = $this->db->sql("SELECT * FROM $tableName WHERE id=?", ROW_EXPECT_1, [$recordId], "Record to lock not found.");
Carsten  Rose's avatar
Carsten Rose committed
220

221
        $expire = date('Y-m-d H:i:s', strtotime("+" . $tableVars[F_RECORD_LOCK_TIMEOUT_SECONDS] . " seconds"));
Carsten  Rose's avatar
Carsten Rose committed
222
        // Write 'dirty' record
Carsten  Rose's avatar
Carsten Rose committed
223
        $this->db->sql("INSERT INTO Dirty (`sip`, `tableName`, `recordId`, `expire`, `recordHashMd5`, `feUser`, `qfqUserSessionCookie`, `dirtyMode`, `remoteAddress`, `created`) " .
Carsten  Rose's avatar
Carsten Rose committed
224
            "VALUES ( ?,?,?,?,?,?,?,?,?,? )", ROW_REGULAR,
Carsten  Rose's avatar
Carsten Rose committed
225
            [$s, $tableName, $recordId, $expire, $recordHashMd5, $feUser, $this->client[CLIENT_COOKIE_QFQ], $formDirtyMode,
Carsten  Rose's avatar
Carsten Rose committed
226
                $this->client[CLIENT_REMOTE_ADDRESS], date('YmdHis')]);
Carsten  Rose's avatar
Carsten Rose committed
227

228
        return [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => '',
229
            API_LOCK_TIMEOUT => $tableVars[F_RECORD_LOCK_TIMEOUT_SECONDS]];
Carsten  Rose's avatar
Carsten Rose committed
230
231

    }
Carsten  Rose's avatar
Carsten Rose committed
232

233
    /**
Carsten  Rose's avatar
Carsten Rose committed
234
     * Get MD5 from tableName/recordId and compare with $recordHashMd5.
235
236
     *
     * @param string $tableName
Carsten  Rose's avatar
Carsten Rose committed
237
238
239
     * @param int    $recordId
     * @param string $recordHashMd5 - timestamp e.g. '2017-07-27 14:06:56'
     * @return bool true if $recordHashMd5 is different from current record md5 hash.
240
241
242
     * @throws CodeException
     * @throws DbException
     */
Carsten  Rose's avatar
Carsten Rose committed
243
    private function isRecordModified($tableName, $recordId, $recordHashMd5, &$rcMd5) {
244

245
246
247
248
        if($recordHashMd5 =='') {
            return false; // If there is no recordHashMd5, the check is not possible. Always return 'not modified' (=ok)
        }

249
250
        $record = $this->db->sql("SELECT * FROM $tableName WHERE id=?", ROW_EXPECT_1, [$recordId], "Record to lock not found.");

Carsten  Rose's avatar
Carsten Rose committed
251
        $rcMd5 = OnArray::getMd5($record);
252

Carsten  Rose's avatar
Carsten Rose committed
253
        return ($recordHashMd5 != $rcMd5);
254
255
    }

256
257
258
259
    /**
     * Check if a lock exist for the current table, recordId and session.
     *
     * @param string $tableName
Carsten  Rose's avatar
Carsten Rose committed
260
261
     * @param int    $recordId
     * @param array  $recordDirty - return dirty record if one exist.
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
     * @param string $msg - return preformatted message in case of conflict
     * @return int LOCK_NOT_FOUND | LOCK_FOUND_OWNER | LOCK_FOUND_CONFLICT,
     */
    public function getCheckDirty($tableName, $recordId, array &$recordDirty, &$msg) {

        $msg = '';

        if ($recordId == 0) {
            return LOCK_NOT_FOUND; // New records never have a recordDirty nor a conflict.
        }

        $recordDirty = $this->getRecordDirty($tableName, $recordId);

        if (empty($recordDirty)) {
            return LOCK_NOT_FOUND;
        }

        if ($recordDirty[DIRTY_QFQ_USER_SESSION_COOKIE] == $this->client[CLIENT_COOKIE_QFQ]) {
280
            $msgUser = "you";
281
282
        } else {
            $msgUser = (empty($recordDirty[DIRTY_FE_USER])) ? "another user" : "user '$recordDirty[DIRTY_FE_USER]'";
283
284
285
        }
        $msgAt = "at " . $recordDirty[COLUMN_CREATED] . " from " . $recordDirty[DIRTY_REMOTE_ADDRESS];
        $msg = "The record has been locked by $msgUser $msgAt";
286

287
288
289
290
        // Is the dirtyRecord mine?
        if ($recordDirty[DIRTY_QFQ_USER_SESSION_COOKIE] == $this->client[CLIENT_COOKIE_QFQ]) {
            return LOCK_FOUND_OWNER;
        } else {
291
292
293
294
            return LOCK_FOUND_CONFLICT;
        }
    }

Carsten  Rose's avatar
Carsten Rose committed
295
296
297
298
    /**
     * Release a dirtyRecord. This is only possible if the current user owns the dirtyRecord.
     * In case of not owner, throws an exception and the save should break.
     *
Carsten  Rose's avatar
Carsten Rose committed
299
     * @param string $formMode FORM_DELETE, FORM_SAVE
Carsten  Rose's avatar
Carsten Rose committed
300
     * @param int    $lockTimeout
Carsten  Rose's avatar
Carsten Rose committed
301
     * @param string $dirtyMode DIRTY_MODE_EXCLUSIVE, DIRTY_MODE_ADVISORY, DIRTY_MODE_NONE
Carsten  Rose's avatar
Carsten Rose committed
302
     * @param string $tableName
Carsten  Rose's avatar
Carsten Rose committed
303
     * @param int    $recordId
Carsten  Rose's avatar
Carsten Rose committed
304
     * @return array
Carsten  Rose's avatar
Carsten Rose committed
305
306
     * @throws \qfq\CodeException
     * @throws \qfq\UserFormException
Carsten  Rose's avatar
Carsten Rose committed
307
     */
Carsten  Rose's avatar
Carsten Rose committed
308
    public function checkDirtyAndRelease($formMode, $lockTimeout, $dirtyMode, $tableName, $recordId, $flagCheckModifiedFirst = false) {
Carsten  Rose's avatar
Carsten Rose committed
309

Carsten  Rose's avatar
Carsten Rose committed
310
311
        $rcRecordDirty = array();
        $rcMsg = '';
312

313
        $answer = [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
Carsten  Rose's avatar
Carsten Rose committed
314

Carsten  Rose's avatar
Carsten Rose committed
315
        if ($recordId == 0) {
Carsten  Rose's avatar
Carsten Rose committed
316
            return $answer; // New records never have a recordDirty nor a conflict.
Carsten  Rose's avatar
Carsten Rose committed
317
318
        }

Carsten  Rose's avatar
Carsten Rose committed
319
320
321
322
323
        // Check if the record has changed in the meantime.
        if ($flagCheckModifiedFirst && $this->isRecordModified($tableName, $recordId, $this->client[DIRTY_RECORD_HASH_MD5], $dummy)) {
            throw new UserFormException ('The record has been modified in the meantime. Please reload the form, edit and save again.', ERROR_DIRTY_RECORD_MODIFIED);
//            return [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => 'The record has been modified in the meantime. Please reload the form, edit and save again.'];
        }
Carsten  Rose's avatar
Carsten Rose committed
324

Carsten  Rose's avatar
Carsten Rose committed
325
326
327
        $lockStatus = $this->getCheckDirty($tableName, $recordId, $rcRecordDirty, $rcMsg);

        if (empty($rcRecordDirty)) {
328
329
330
            if ($dirtyMode == DIRTY_MODE_NONE) {
                return $answer; // only situation where it's ok that there is no dirtyRecord.
            }
Carsten  Rose's avatar
Carsten Rose committed
331
            if ($formMode == FORM_DELETE) {
Carsten  Rose's avatar
Carsten Rose committed
332
                return $answer;
Carsten  Rose's avatar
Carsten Rose committed
333
334
            }

335
            // This is pessimistic, but secure.
336
            throw new UserFormException("Missing record lock: please reload the form, edit and save again.", ERROR_DIRTY_MISSING_LOCK);
Carsten  Rose's avatar
Carsten Rose committed
337
338
        }

Carsten  Rose's avatar
Carsten Rose committed
339
        if ($formMode == FORM_DELETE) {
340
            // Check if the record is timed out
Carsten  Rose's avatar
Carsten Rose committed
341
342
            if ($lockTimeout > 0 && $rcRecordDirty[DIRTY_EXPIRE] < date('Y-m-d H:i:s')) {
                $this->deleteDirtyRecord($rcRecordDirty[COLUMN_ID]);
343
344
345
346

                return $answer;
            }

Carsten  Rose's avatar
Carsten Rose committed
347
            $answer = [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => $rcMsg];
348
            return $answer;
Carsten  Rose's avatar
Carsten Rose committed
349
350
        }

Carsten  Rose's avatar
Carsten Rose committed
351
        // Is the dirtyRecord mine?
352
        if ($lockStatus == LOCK_FOUND_OWNER) {
Carsten  Rose's avatar
Carsten Rose committed
353
354
            // Check if the record has changed in the meantime.
            if ($this->isRecordModified($tableName, $recordId, $rcRecordDirty[DIRTY_RECORD_HASH_MD5], $dummy)) {
355
356
                return [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => 'The record has been modified in the meantime. Please reload the form, edit and save again.'];
            }
357

Carsten  Rose's avatar
Carsten Rose committed
358
            // Clear the lock
Carsten  Rose's avatar
Carsten Rose committed
359
            $this->deleteDirtyRecord($rcRecordDirty[COLUMN_ID]);
Carsten  Rose's avatar
Carsten Rose committed
360
            return $answer;
Carsten  Rose's avatar
Carsten Rose committed
361
362
363
364
365
366
        }

        //----------------------------------------
        // From here: there is a foreign lock!

        // Check if overwrite is allowed
Carsten  Rose's avatar
Carsten Rose committed
367
        if ($dirtyMode == DIRTY_MODE_ADVISORY && $rcRecordDirty[F_DIRTY_MODE] == DIRTY_MODE_ADVISORY) {
Carsten  Rose's avatar
Carsten Rose committed
368
            return $answer;
Carsten  Rose's avatar
Carsten Rose committed
369
370
371
        }

        // Check if the record is timed out
Carsten  Rose's avatar
Carsten Rose committed
372
373
        if ($lockTimeout > 0 && $rcRecordDirty[DIRTY_EXPIRE] < date('Y-m-d H:i:s')) {
            $this->deleteDirtyRecord($rcRecordDirty[COLUMN_ID]);
374
            return $answer;
Carsten  Rose's avatar
Carsten Rose committed
375
376
        }

Carsten  Rose's avatar
Carsten Rose committed
377
        throw new UserFormException($rcMsg, ERROR_DIRTY_ALREADY_LOCKED);
Carsten  Rose's avatar
Carsten Rose committed
378
379
380
381
382
    }

    /**
     * Delete the dirtyRecord with $recordDirtyId. Throw an exception if the record has not been deleted.
     *
Carsten  Rose's avatar
Carsten Rose committed
383
     * @param int $recordDirtyId
Carsten  Rose's avatar
Carsten Rose committed
384
385
386
387
     * @throws CodeException
     * @throws DbException
     */
    private function deleteDirtyRecord($recordDirtyId) {
388

Carsten  Rose's avatar
Carsten Rose committed
389
390
        $cnt = $this->db->sql('DELETE FROM Dirty WHERE id=? LIMIT 1', ROW_REGULAR, [$recordDirtyId]);
        if ($cnt != 1) {
Carsten  Rose's avatar
Carsten Rose committed
391
            throw new CodeException("Failed to delete dirty record id=" . $recordDirtyId, ERROR_DIRTY_DELETE_RECORD);
Carsten  Rose's avatar
Carsten Rose committed
392
393
        }
    }
Carsten  Rose's avatar
Carsten Rose committed
394
}