From eff60b9cd9a3d9453f5a0490b6099b80fe1bd2bb Mon Sep 17 00:00:00 2001
From: jhaller <jan.haller@math.uzh.ch>
Date: Thu, 28 Dec 2023 10:07:04 +0100
Subject: [PATCH] Refs #8702 - Added functionality to check if the record is
 already locked after loading the form. If the record is locked by another
 user, an alert is displayed with the message "The record has been locked by
 user 'user1' until 28.12.2023 10:16:51.".

---
 extension/Classes/Core/Constants.php  |  1 +
 extension/Classes/Core/Form/Dirty.php | 12 +++--
 javascript/src/Dirty.js               | 74 +++++++++++++++++++++++++--
 javascript/src/QfqForm.js             | 25 ++++++++-
 4 files changed, 104 insertions(+), 8 deletions(-)

diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php
index 41fa8778e..127ce39b3 100644
--- a/extension/Classes/Core/Constants.php
+++ b/extension/Classes/Core/Constants.php
@@ -955,6 +955,7 @@ const API_SUBMIT_REASON_SAVE_FORCE = 'save,force';
 const API_LOCK_ACTION_LOCK = 'lock';
 const API_LOCK_ACTION_EXTEND = 'extend';
 const API_LOCK_ACTION_RELEASE = 'release';
+const API_LOCK_ACTION_CHECK = 'check';
 
 const API_ANSWER_STATUS_SUCCESS = 'success';
 const API_ANSWER_STATUS_CONFLICT = 'conflict';
diff --git a/extension/Classes/Core/Form/Dirty.php b/extension/Classes/Core/Form/Dirty.php
index 6c4c42dc6..261476060 100644
--- a/extension/Classes/Core/Form/Dirty.php
+++ b/extension/Classes/Core/Form/Dirty.php
@@ -117,7 +117,7 @@ class Dirty {
         $recordId = empty($sipVars[SIP_RECORD_ID]) ? 0 : $sipVars[SIP_RECORD_ID];
         if ($recordId == 0) {
             // For r=0 (new) , 'dirty' will always succeed.
-            return [API_STATUS => 'success', API_MESSAGE => ''];
+            return [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
         }
 
         $this->dbIndexQfq = $this->store->getVar(SYSTEM_DB_INDEX_QFQ, STORE_SYSTEM);
@@ -138,6 +138,12 @@ class Dirty {
             case API_LOCK_ACTION_RELEASE:
                 $answer = $this->checkDirtyAndRelease(FORM_SAVE, $tableVars[F_RECORD_LOCK_TIMEOUT_SECONDS], $tableVars[F_DIRTY_MODE], $tableVars[F_TABLE_NAME], $tableVars[F_PRIMARY_KEY], $recordId);
                 break;
+            case API_LOCK_ACTION_CHECK:
+                $rcRecordDirty = array();
+                $rcMsg = '';
+                $rc = $this->getCheckDirty($tableVars[F_TABLE_NAME], $recordId, $rcRecordDirty, $rcMsg);
+                $answer = ($rc === LOCK_FOUND_CONFLICT) ? [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => $rcMsg] : [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
+                break;
             default;
                 throw new \CodeException("Unknown action: " . $this->client[API_LOCK_ACTION], ERROR_DIRTY_UNKNOWN_ACTION);
         }
@@ -178,7 +184,7 @@ class Dirty {
 
         if (count($recordDirty) == 0) {
             if ($formDirtyMode == DIRTY_MODE_NONE) {
-                $answer = [API_STATUS => 'success', API_MESSAGE => ''];
+                $answer = [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
             } else {
                 // No dirty record found: create lock
                 $answer = $this->writeDirty($this->client[SIP_SIP], $recordId, $tableVars, $feUser, $rcMd5, $tabUniqId);
@@ -186,7 +192,7 @@ class Dirty {
         } else {
             if ($tabUniqId == $recordDirty[TAB_UNIQ_ID]) {
                 // In case it's the same tab (page reload): OK
-                $answer = [API_STATUS => 'success', API_MESSAGE => ''];
+                $answer = [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
             } else {
                 // Here is probably a conflict.
                 $answer = $this->conflict($recordDirty, $formDirtyMode, $primaryKey);
diff --git a/javascript/src/Dirty.js b/javascript/src/Dirty.js
index 333332e11..124622381 100644
--- a/javascript/src/Dirty.js
+++ b/javascript/src/Dirty.js
@@ -43,16 +43,23 @@ var QfqNS = QfqNS || {};
         RENEWAL_ENDED: 'dirty.renewal.ended',
         RENEWAL_SUCCESS: 'dirty.renewal.success',
         RENEWAL_DENIED: 'dirty.renewal.denied',
-        RENEWAL_FAILED: 'dirty.renewal.failed'
+        RENEWAL_FAILED: 'dirty.renewal.failed',
+
+        CHECK_STARTED: 'dirty.check.started',
+        CHECK_SUCCESS: 'dirty.check.success',
+        CHECK_FAILED: 'dirty.check.failed',
+        CHECK_ENDED: 'dirty.check.ended',
     };
 
     n.Dirty.ENDPOINT_OPERATIONS = {
-        /** Aquire Lock */
+        /** Acquire Lock */
         LOCK: "lock",
         /** Release Lock */
         RELEASE: "release",
         /** Renew Lock */
-        RENEW: "extend"
+        RENEW: "extend",
+        /** Check Lock */
+        CHECK: "check"
     };
 
     n.Dirty.MINIMUM_TIMER_DELAY_IN_SECONDS = 5;
@@ -330,4 +337,65 @@ var QfqNS = QfqNS || {};
         queryString = $.param(mergedQueryParameterObject);
         return this.dirtyUrl + "?" + queryString;
     };
+
+    /**
+     * Check with the server if record is already locked.
+     *
+     * @param sip {string} sip.
+     * @public
+     */
+    n.Dirty.prototype.check = function (sip, optionalQueryParameters) {
+        var eventData;
+
+        if (!this.dirtyUrl) {
+            n.Log.debug("notify: cannot contact server, no dirtyUrl.");
+            return;
+        }
+        eventData = n.EventEmitter.makePayload(this, null);
+        this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_STARTED, eventData);
+        $.ajax({
+            url: this.makeUrl(sip, n.Dirty.ENDPOINT_OPERATIONS.CHECK, optionalQueryParameters),
+            type: 'GET',
+            cache: false
+        })
+            .done(this.ajaxCheckSuccessHandler.bind(this))
+            .fail(this.ajaxCheckErrorHandler.bind(this));
+    };
+
+    /**
+     * @private
+     * @param data
+     * @param textStatus
+     * @param jqXHR
+     */
+    n.Dirty.prototype.ajaxCheckSuccessHandler = function (data, textStatus, jqXHR) {
+        var eventData = n.EventEmitter.makePayload(this, data);
+        if (data.status && data.status === "success") {
+            this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_SUCCESS, eventData);
+        } else {
+            this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_FAILED, eventData);
+        }
+
+        this.setTimeoutIfRequired(data, eventData);
+        this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_ENDED, eventData);
+    };
+
+    /**
+     * @private
+     * @param jqXHR
+     * @param textStatus
+     * @param errorThrown
+     */
+    n.Dirty.prototype.ajaxCheckErrorHandler = function (jqXHR, textStatus, errorThrown) {
+        var eventData = n.EventEmitter.makePayload(this, null, {
+            textStatus: textStatus,
+            errorThrown: errorThrown,
+            jqXHR: jqXHR
+        });
+
+        this.lockTimeoutInMilliseconds = n.Dirty.NO_LOCK_TIMEOUT;
+
+        this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_FAILED, eventData);
+        this.eventEmitter.emitEvent(n.Dirty.EVENTS.CHECK_ENDED, eventData);
+    };
 })(QfqNS);
diff --git a/javascript/src/QfqForm.js b/javascript/src/QfqForm.js
index e292a30b5..994f8a20d 100644
--- a/javascript/src/QfqForm.js
+++ b/javascript/src/QfqForm.js
@@ -22,7 +22,7 @@ var QfqNS = QfqNS || {};
     /**
      * Represents a QFQ Form.
      *
-     * QfqForm will autonomously fire an lock `extend` request when the lock expired, but the last change `t_c` has
+     * QfqForm will autonomously fire a lock `extend` request when the lock expired, but the last change `t_c` has
      * been made during the lock period `t_l`. I.e. let `t_{current}` be the current time, an `extend` request is made
      * when
      *
@@ -95,7 +95,8 @@ var QfqNS = QfqNS || {};
         this.dirty.on(n.Dirty.EVENTS.SUCCESS_TIMEOUT, this.dirtyTimeout.bind(this));
         this.dirty.on(n.Dirty.EVENTS.RENEWAL_DENIED, this.dirtyRenewalDenied.bind(this));
         this.dirty.on(n.Dirty.EVENTS.RENEWAL_SUCCESS, this.dirtyRenewalSuccess.bind(this));
-
+        this.dirty.on(n.Dirty.EVENTS.CHECK_SUCCESS, this.dirtyCheckSuccess.bind(this));
+        this.dirty.on(n.Dirty.EVENTS.CHECK_FAILED, this.dirtyCheckFailed.bind(this));
 
         this.form.on('form.changed', this.changeHandler.bind(this));
         this.form.on('form.reset', this.resetHandler.bind(this));
@@ -171,6 +172,8 @@ var QfqNS = QfqNS || {};
                 checkboxes[i].checked = $(this).is(':checked');
             }
         });
+
+        this.dirty.check(this.getSip(), this.getRecordHashMd5AsQueryParameter());
     };
 
     n.QfqForm.prototype.on = n.EventEmitter.onMixin;
@@ -180,6 +183,24 @@ var QfqNS = QfqNS || {};
         // Intentionally empty. May be used later on.
     };
 
+    n.QfqForm.prototype.dirtyCheckSuccess = function (obj) {
+        // Intentionally empty. May be used later on.
+    };
+
+    n.QfqForm.prototype.dirtyCheckFailed = function (obj) {
+        var message = new n.Alert({
+            message: obj.data.message,
+            type: "error",
+            timeout: n.Alert.constants.NO_TIMEOUT,
+            modal: true,
+            buttons: [{
+                label: "Close",
+                eventName: 'close'
+            }]
+        });
+        message.show();
+    };
+
     n.QfqForm.prototype.dirtyRenewalSuccess = function (obj) {
         this.lockAcquired = true;
     };
-- 
GitLab