diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php index 41fa8778eeee7c48027f380095980e8c65eb2c95..127ce39b3c9ba3e2755d3f1341347e31e067fb72 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 6c4c42dc6f0eaeac9720e34da74dda66873ea60e..261476060ced81a85325ce36abf419f0eb987f4f 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 333332e11516c3a379982fbf3b04605529df55af..1246223810ffc1065931250cb84af81e5e890c6a 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 e292a30b5a4819d9700216d32e6bb5cb0992d3a9..994f8a20dc72d87032607d4b3fba4f4ca42fd73f 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; };