Änderungen von Dokument ClassEditSheet
Zuletzt geändert von Holger Engels am 2026/09/22 10:06
Von Version 2.1
bearbeitet von Holger Engels
am 2026/09/22 10:06
am 2026/09/22 10:06
Änderungskommentar:
Install extension [org.xwiki.platform:xwiki-platform-appwithinminutes-ui/18.7.0]
Auf Version 1.1
bearbeitet von Holger Engels
am 2025/08/05 10:26
am 2025/08/05 10:26
Änderungskommentar:
Install extension [org.xwiki.platform:xwiki-platform-appwithinminutes-ui/17.6.0]
Zusammenfassung
-
Seiteneigenschaften (2 geändert, 0 hinzugefügt, 0 gelöscht)
-
Objekte (1 geändert, 0 hinzugefügt, 0 gelöscht)
Details
- Seiteneigenschaften
-
- Dokument-Autor
-
... ... @@ -1,1 +1,1 @@ 1 -XWiki.h olgerengels1 +XWiki.hengels - Inhalt
-
... ... @@ -329,9 +329,6 @@ 329 329 #set ($xclass = $doc.getxWikiClass().getXWikiClass()) 330 330 #set ($discard = $xclass.addField($field.name, $field)) 331 331 #set ($discard = $field.setObject($xclass)) 332 - ## Set the class document author to the current user so that customDisplay code is rendered with the current user's 333 - ## rights, not the class document author's rights. 334 - #set ($discard = $doc.document.authors.setEffectiveMetadataAuthor($services.user.currentUserReference)) 335 335 #displayField($doc.getxWikiClass().get($field.name) $formFieldDoc) 336 336 #else 337 337 Unsupported form field. ... ... @@ -384,9 +384,6 @@ 384 384 ## 385 385 ## Display the field. 386 386 #set ($field = $doc.getxWikiClass().get($fieldName)) 387 - ## Set the class document author to the current user so that customDisplay code is rendered with the current user's 388 - ## rights, not the class document author's rights. 389 - #set ($discard = $doc.document.authors.setEffectiveMetadataAuthor($services.user.currentUserReference)) 390 390 #displayPropertyEditInput($field, "${doc.fullName}_0_", $object) 391 391 #end 392 392 ... ... @@ -395,9 +395,6 @@ 395 395 *# 396 396 #macro (displayEditForm) 397 397 #set ($discard = $xwiki.jsx.use('AppWithinMinutes.ClassEditSheet')) 398 - ## Reusable helper used to recover gracefully when a preview or field-add request is rejected because the CSRF 399 - ## token expired (e.g. after a server restart). 400 - #set ($discard = $xwiki.jsx.use('AppWithinMinutes.Code.CSRFTokenRefresh')) 401 401 #set ($discard = $xwiki.ssx.use('AppWithinMinutes.ClassEditSheet')) 402 402 #set ($discard = $xwiki.ssx.use('AppWithinMinutes.ClassSheetGenerator')) 403 403 #if ("$!request.wizard" == 'true') ... ... @@ -416,21 +416,10 @@ 416 416 * Displays either the edit class form or a new form field. The later is used when adding a new form field via AJAX. 417 417 *# 418 418 #macro (doEdit) 419 - #if ("$!request.field" != '' || "$!request.preview" == 'true') 420 - ## CSRF protection. 421 - #if (!$services.csrf.isTokenValid($request.form_token)) 422 - $response.setStatus(403) 423 - ## Return a fresh token (using the same JSON shape as XWikiAction#csrfTokenCheck) so that the client can recover 424 - ## from an expired token (e.g. after a server restart) by refreshing it, after an explicit user confirmation, 425 - ## and retrying the request. Returning the token in the response is safe because the same-origin policy prevents 426 - ## a CSRF attacker from reading it. 427 - $response.setContentType('application/json') 428 - $jsontool.serialize({'errorType': 'CSRF', 'newToken': $services.csrf.getToken()}) 429 - #elseif ("$!request.field" != '') 430 - #displayNewField() 431 - #else 432 - #previewField() 433 - #end 410 + #if ("$!request.field" != '') 411 + #displayNewField() 412 + #elseif ("$!request.preview" == 'true') 413 + #previewField() 434 434 #else 435 435 ## Make sure that only the sheet content is rendered when the class is saved using AJAX. 436 436 <div class="hidden"> ... ... @@ -438,9 +438,6 @@ 438 438 #if ($request.wizard == 'true') 439 439 ## Preserve the wizard mode. 440 440 <input type="hidden" name="wizard" value="true" /> 441 - ## Provide the URL of the next wizard step as xredirect value (used by the AJAX Save & View implementation). 442 - #set ($appWizardNextStepURL = "#getNextStepURL()") 443 - <input type="hidden" id="xredirect" name="xredirect" value="$escapetool.xml($appWizardNextStepURL)" /> 444 444 #end 445 445 ## Compute the application title to be used as the wizard step title. 446 446 #getAppTitle ... ... @@ -678,26 +678,6 @@ 678 678 #end 679 679 680 680 #** 681 - * Prints the URL of the next wizard step. 682 - *# 683 -#macro (getNextStepURL) 684 - #set ($className = $stringtool.removeEnd($doc.fullName, 'Class')) 685 - #set ($templateProviderReference = $services.model.resolveDocument("${className}TemplateProvider")) 686 - #set ($queryString = { 687 - 'wizard': true, 688 - 'sheet': 'AppWithinMinutes.TemplateProviderEditSheet' 689 - }) 690 - #if (!$xwiki.exists($templateProviderReference)) 691 - #set ($discard = $queryString.putAll({ 692 - 'form_token': $services.csrf.getToken(), 693 - 'template': 'XWiki.TemplateProviderTemplate', 694 - 'parent': $doc.fullName 695 - })) 696 - #end 697 -$xwiki.getURL($templateProviderReference, 'edit', $escapetool.url($queryString))## 698 -#end 699 - 700 -#** 701 701 * Updates and saves the class definition, the class sheet and the class template. 702 702 *# 703 703 #macro (doSave) ... ... @@ -708,19 +708,24 @@ 708 708 #updateAndSaveSheet 709 709 #updateAndSaveTranslations 710 710 #if ($action == 'save') 711 - #if ($request.getHeader('X-Requested-With') == 'XMLHttpRequest') 712 - ## When saving through AJAX, don't redirect and don't render an error page. Let the client-side handle both. 713 - #if ($errorMessage) 714 - $response.setStatus(400) 715 - #rawResponse($errorMessage, 'text/plain') 716 - #else 717 - $response.setStatus(204) 718 - #end 719 - #elseif ($errorMessage) 668 + #if ($errorMessage) 720 720 #error($errorMessage) 721 721 #elseif ("$!request.wizard" == 'true') 722 722 ## Redirect to next wizard step. 723 - $response.sendRedirect("#getNextStepURL()") 672 + #set ($className = $stringtool.removeEnd($doc.fullName, 'Class')) 673 + #set ($templateProviderReference = $services.model.resolveDocument("${className}TemplateProvider")) 674 + #set ($queryString = { 675 + 'wizard': true, 676 + 'sheet': 'AppWithinMinutes.TemplateProviderEditSheet' 677 + }) 678 + #if (!$xwiki.exists($templateProviderReference)) 679 + #set ($discard = $queryString.putAll({ 680 + 'form_token': $services.csrf.getToken(), 681 + 'template': 'XWiki.TemplateProviderTemplate', 682 + 'parent': $doc.fullName 683 + })) 684 + #end 685 + $response.sendRedirect($xwiki.getURL($templateProviderReference, 'edit', $escapetool.url($queryString))) 724 724 #else 725 725 ## Redirect to view mode. 726 726 $response.sendRedirect($doc.getURL()) ... ... @@ -727,8 +727,7 @@ 727 727 #end 728 728 #else 729 729 #if ($errorMessage) 730 - $response.setStatus(400) 731 - #rawResponse($errorMessage, 'text/plain') 692 + $response.sendError(400, $errorMessage) 732 732 #else 733 733 $response.setStatus(204) 734 734 #end
- XWiki.JavaScriptExtension[2]
-
- Code
-
... ... @@ -10,42 +10,15 @@ 10 10 enhance : function(data) { 11 11 this.container.insert({top: this._createToolBox()}); 12 12 if (data) { 13 - this._loadField(data); 13 + this.container.addClassName('loading'); 14 + new Ajax.Request(data, { 15 + method: 'get', 16 + onSuccess: this._display.bind(this) 17 + }); 14 14 } else { 15 15 this._display(null); 16 16 } 17 17 }, 18 - _loadField : function (url) { 19 - // The field URL embeds the CSRF token that was valid when the page was rendered. Always refresh it from the 20 - // current form_token input (the single source of truth, updated when the token is refreshed after an expired 21 - // token recovery) so that, once the token has been refreshed, adding further fields does not prompt again. 22 - const formToken = this.container.up('form')?.down('input[name="form_token"]'); 23 - if (formToken) { 24 - const fieldURL = new URL(url, window.location.href); 25 - fieldURL.searchParams.set('form_token', formToken.value); 26 - url = fieldURL.toString(); 27 - } 28 - this.container.addClassName('loading'); 29 - const self = this; 30 - new Ajax.Request(url, { 31 - method: 'get', 32 - onSuccess: this._display.bind(this), 33 - // A 403 means the CSRF token expired (e.g. the server was restarted while the user was editing). Offer to 34 - // refresh the token and retry, instead of leaving the loading spinner spinning forever. 35 - on403: function (response) { 36 - this.container.removeClassName('loading'); 37 - require(['xwiki-appwithinminutes-csrf'], function (csrf) { 38 - csrf.recoverFromExpiredToken(response, function () { 39 - // The token has been refreshed in the form: retry, _loadField will pick up the fresh token. 40 - self._loadField(url); 41 - }); 42 - }); 43 - }.bind(this), 44 - onFailure: function () { 45 - this.container.removeClassName('loading'); 46 - }.bind(this) 47 - }); 48 - }, 49 49 getContainer : function() { 50 50 return this.container; 51 51 }, ... ... @@ -165,12 +165,6 @@ 165 165 // Add parameters required by the AJAX preview request. 166 166 this.previewData.set('preview', true); 167 167 this.previewData.set('xpage', 'plain'); 168 - // Find and add the mandatory CSRF token. 169 - const editForm = this.container.up('form'); 170 - const formToken = editForm && editForm.down('input[name="form_token"]'); 171 - if (formToken) { 172 - this.previewData.set('form_token', formToken.value); 173 - } 174 174 }, 175 175 _maybePreview : function() { 176 176 if(!this.previewData) { ... ... @@ -202,7 +202,6 @@ 202 202 var preview = this.getViewer().down('dd'); 203 203 if (preview) { 204 204 preview.addClassName('loading'); 205 - const self = this; 206 206 // Firefox 3.6 doesn't resolve the empty string to the current page URL so we have to explicitly specify it. 207 207 new Ajax.Request(window.location.href, { 208 208 parameters: data.toQueryString(), ... ... @@ -212,20 +212,6 @@ 212 212 this.previewData = data; 213 213 document.fire('xwiki:class:previewField', {field: this}); 214 214 document.fire('xwiki:dom:updated', {elements: [preview]}); 215 - }.bind(this), 216 - // A 403 means the CSRF token expired (e.g. the server was restarted while the user was editing). Offer to 217 - // refresh the token and retry the preview, instead of leaving the loading spinner spinning forever. 218 - on403: function (response) { 219 - preview.removeClassName('loading'); 220 - require(['xwiki-appwithinminutes-csrf'], function (csrf) { 221 - csrf.recoverFromExpiredToken(response, function (token) { 222 - data.set('form_token', token); 223 - self._preview(data); 224 - }); 225 - }); 226 - }.bind(this), 227 - onFailure: function () { 228 - preview.removeClassName('loading'); 229 229 }.bind(this) 230 230 }); 231 231 } ... ... @@ -351,7 +351,7 @@ 351 351 prettyNameInput.ariaLabel = '$escapetool.javascript($services.localization.render('platform.appwithinminutes.classEditorFieldPrettyNameToolTip'))'; 352 352 // Replace the preview label with the pretty name input. 353 353 var previewLabel = field.getViewer().down('label'); 354 - // We unset the for attribute, so that this label will not be set implicitly OR explicitly anymore. 306 + // We unset the for attribute, so that this label will not be set implicitely OR explicitely anymore. 355 355 previewLabel.writeAttribute('for', ''); 356 356 previewLabel.removeChild(previewLabel.lastChild); 357 357 previewLabel.insert(prettyNameInput); ... ... @@ -580,14 +580,21 @@ 580 580 // Remove the preview button. 581 581 form.down("input[name='action_preview']").remove(); 582 582 583 - // Let actionButtons.js submit a custom value for the submit buttons to avoid triggering the standard save action 584 - // on the server side. 585 - form.down("input[name='action_save']").dataset.submitValue = 'xaction_save'; 586 - form.down("input[name='action_saveandcontinue']").dataset.submitValue = 'xaction_saveandcontinue'; 535 + // Rename the save buttons. 536 + form.down("input[name='action_save']").name = 'xaction_save'; 537 + form.down("input[name='action_saveandcontinue']").name = 'xaction_saveandcontinue'; 587 587 588 - // actionButtons.js detects this as a custom form due to the submit URL that is not the standard one. 589 - // Force the standard behavior by setting the customForm attribute to false. 590 - form.dataset.customForm = 'false'; 539 + // Make sure the 'Save & Continue' button is submitted when clicked. 540 + document.observe('xwiki:class:save', function(event) { 541 + event = event.memo.originalEvent; 542 + if (event.memo['continue']) { 543 + var submitButton = event.memo.originalEvent.element(); 544 + submitButton.insert({after: new Element('input', {type: 'hidden', 'name': submitButton.name})}); 545 + (function () { 546 + submitButton.next().remove(); 547 + }).defer(); 548 + } 549 + }.bindAsEventListener(window)); 591 591 592 592 // Be prepared to enhance some of the field properties. 593 593 new XWiki.FormFieldNameManager();