Merge pull request #5987 from WoltLab/acp-dahsboard-box-hight
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / acp / js / WCF.ACP.js
CommitLineData
158bd3ca
TD
1/**
2 * Class and function collection for WCF ACP
3 *
13d8b49b 4 * @author Alexander Ebert, Matthias Schmidt
7b7b9764 5 * @copyright 2001-2019 WoltLab GmbH
158bd3ca
TD
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7 */
8
9/**
10 * Initialize WCF.ACP namespace
11 */
cb6e5946 12WCF.ACP = { };
158bd3ca 13
1a8d655f
AE
14/**
15 * Namespace for ACP application management.
16 */
17WCF.ACP.Application = { };
18
29873759
MS
19/**
20 * Namespace for ACP cronjob management.
21 */
22WCF.ACP.Cronjob = { };
23
02b148ad
MS
24/**
25 * Handles the manual execution of cronjobs.
26 */
27WCF.ACP.Cronjob.ExecutionHandler = Class.extend({
28 /**
29 * notification object
30 * @var WCF.System.Notification
31 */
32 _notification: null,
33
34 /**
35 * action proxy
36 * @var WCF.Action.Proxy
37 */
38 _proxy: null,
39
40 /**
41 * Initializes WCF.ACP.Cronjob.ExecutionHandler object.
42 */
43 init: function() {
44 this._proxy = new WCF.Action.Proxy({
45 success: $.proxy(this._success, this)
46 });
47
48 $('.jsCronjobRow .jsExecuteButton').click($.proxy(this._click, this));
49
50 this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success');
51 },
52
53 /**
54 * Handles a click on an execute button.
55 *
56 * @param object event
57 */
58 _click: function(event) {
59 this._proxy.setOption('data', {
60 actionName: 'execute',
61 className: 'wcf\\data\\cronjob\\CronjobAction',
62 objectIDs: [ $(event.target).data('objectID') ]
63 });
64
65 this._proxy.sendRequest();
66 },
67
68 /**
69 * Handles successful cronjob execution.
70 *
71 * @param object data
72 * @param string textStatus
73 * @param jQuery jqXHR
74 */
75 _success: function(data, textStatus, jqXHR) {
76 $('.jsCronjobRow').each($.proxy(function(index, row) {
77 var $button = $(row).find('.jsExecuteButton');
78 var $objectID = ($button).data('objectID');
79
80 if (WCF.inArray($objectID, data.objectIDs)) {
81 if (data.returnValues[$objectID]) {
82 // insert feedback here
83 $(row).find('td.columnNextExec').html(data.returnValues[$objectID].formatted);
84 $(row).wcfHighlight();
85 }
86
87 this._notification.show();
88
89 return false;
90 }
91 }, this));
92 }
93});
94
29873759
MS
95/**
96 * Handles the cronjob log list.
97 */
98WCF.ACP.Cronjob.LogList = Class.extend({
99 /**
100 * error message dialog
101 * @var jQuery
102 */
103 _dialog: null,
104
105 /**
106 * Initializes WCF.ACP.Cronjob.LogList object.
107 */
108 init: function() {
109 // bind event listener to delete cronjob log button
110 $('.jsCronjobLogDelete').click(function() {
111 WCF.System.Confirmation.show(WCF.Language.get('wcf.acp.cronjob.log.clear.confirm'), function(action) {
112 if (action == 'confirm') {
113 new WCF.Action.Proxy({
114 autoSend: true,
115 data: {
116 actionName: 'clearAll',
117 className: 'wcf\\data\\cronjob\\log\\CronjobLogAction'
118 },
119 success: function() {
120 window.location.reload();
121 }
122 });
123 }
124 });
125 });
126
127 // bind event listeners to error badges
128 $('.jsCronjobError').click($.proxy(this._showError, this));
129 },
130
131 /**
132 * Shows certain error message
133 *
134 * @param object event
135 */
136 _showError: function(event) {
137 var $errorBadge = $(event.currentTarget);
138
139 if (this._dialog === null) {
cafc0b69 140 this._dialog = $('<div style="overflow: auto"><pre>' + $errorBadge.next().html() + '</pre></div>').hide().appendTo(document.body);
29873759
MS
141 this._dialog.wcfDialog({
142 title: WCF.Language.get('wcf.acp.cronjob.log.error.details')
143 });
144 }
145 else {
cafc0b69 146 this._dialog.html('<pre>' + $errorBadge.next().html() + '</pre>');
29873759
MS
147 this._dialog.wcfDialog('open');
148 }
149 }
150});
151
e5b88a7f
AE
152/**
153 * Namespace for ACP package management.
154 */
3536d2fe 155WCF.ACP.Package = { };
d71e5a29 156
158bd3ca 157/**
cb9e9c12 158 * Provides the package installation.
158bd3ca 159 *
158bd3ca 160 * @param integer queueID
2ef79140 161 * @param string actionName
158bd3ca 162 */
cb9e9c12 163WCF.ACP.Package.Installation = Class.extend({
158bd3ca
TD
164 /**
165 * package installation type
158bd3ca
TD
166 * @var string
167 */
cb9e9c12 168 _actionName: 'InstallPackage',
9f959ced 169
89aa80c1
MS
170 /**
171 * additional parameters send in all requests
172 * @var object
173 */
174 _additionalRequestParameters: {},
175
0acbb6be
AE
176 /**
177 * true, if rollbacks are supported
178 * @var boolean
179 */
180 _allowRollback: false,
181
e5b88a7f 182 /**
cb9e9c12
AE
183 * dialog object
184 * @var jQuery
158bd3ca
TD
185 */
186 _dialog: null,
187
6207cc21
MS
188 /**
189 * name of the language item with the title of the dialog
190 * @var string
191 */
192 _dialogTitle: '',
193
e5b88a7f
AE
194 /**
195 * action proxy
196 * @var WCF.Action.Proxy
197 */
198 _proxy: null,
9f959ced 199
158bd3ca 200 /**
cb9e9c12 201 * package installation queue id
158bd3ca
TD
202 * @var integer
203 */
204 _queueID: 0,
9f959ced 205
ba133309
AE
206 /**
207 * true, if dialog should be rendered again
208 * @var boolean
209 */
210 _shouldRender: false,
211
0a3949d4 212 /**
cb9e9c12 213 * Initializes the WCF.ACP.Package.Installation class.
158bd3ca 214 *
158bd3ca 215 * @param integer queueID
2ef79140 216 * @param string actionName
0acbb6be 217 * @param boolean allowRollback
adf9791d 218 * @param boolean isUpdate
89aa80c1 219 * @param object additionalRequestParameters
158bd3ca 220 */
89aa80c1 221 init: function(queueID, actionName, allowRollback, isUpdate, additionalRequestParameters) {
2ef79140 222 this._actionName = (actionName) ? actionName : 'InstallPackage';
9d9aede4 223 this._allowRollback = (allowRollback === true);
158bd3ca 224 this._queueID = queueID;
89aa80c1 225 this._additionalRequestParameters = additionalRequestParameters || {};
cb9e9c12 226
89aa80c1
MS
227 this._dialogTitle = 'wcf.acp.package.' + (isUpdate ? 'update' : 'install') + '.title';
228 if (this._actionName === 'UninstallPackage') {
229 this._dialogTitle = 'wcf.acp.package.uninstallation.title';
6207cc21
MS
230 }
231
0acbb6be
AE
232 this._initProxy();
233 this._init();
234 },
235
236 /**
237 * Initializes the WCF.Action.Proxy object.
238 */
239 _initProxy: function() {
e31cf611
AE
240 var $actionName = '';
241 var $parts = this._actionName.split(/([A-Z][a-z0-9]+)/);
242 for (var $i = 0, $length = $parts.length; $i < $length; $i++) {
243 var $part = $parts[$i];
244 if ($part.length) {
245 if ($actionName.length) $actionName += '-';
246 $actionName += $part.toLowerCase();
247 }
248 }
249
e5b88a7f 250 this._proxy = new WCF.Action.Proxy({
cb9e9c12 251 failure: $.proxy(this._failure, this),
324e8301 252 showLoadingOverlay: false,
cb9e9c12 253 success: $.proxy(this._success, this),
772f616f 254 url: 'index.php?' + $actionName + '/&t=' + SECURITY_TOKEN
e5b88a7f 255 });
cb9e9c12
AE
256 },
257
258 /**
259 * Initializes the package installation.
260 */
261 _init: function() {
5bae7259 262 const button = document.getElementById('submitButton');
99930e4a 263 button?.addEventListener(
5bae7259
TD
264 'click',
265 () => {
266 button.disabled = true;
267 this.prepareInstallation();
268 }
269 );
cb9e9c12
AE
270 },
271
272 /**
273 * Handles erroneous AJAX requests.
cb9e9c12 274 */
014819c4 275 _failure: function() {
e0a0fe54 276 if (this._dialog !== null) {
5df9b2ef 277 $('#packageInstallationProgress').removeAttr('value');
0caf2e88 278 this._setIcon('xmark');
e0a0fe54 279 }
e098d212 280
0acbb6be
AE
281 if (!this._allowRollback) {
282 return;
283 }
284
7289c8d3
MS
285 if (this._dialog !== null) {
286 this._purgeTemplateContent($.proxy(function() {
287 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
58d5a1b5 288 $('<button type="button" class="button buttonPrimary">' + WCF.Language.get('wcf.acp.package.installation.rollback') + '</button>').appendTo($form).click($.proxy(this._rollback, this));
7289c8d3
MS
289
290 $('#packageInstallationInnerContentContainer').show();
291
292 this._dialog.wcfDialog('render');
293 }, this));
294 }
0acbb6be
AE
295 },
296
297 /**
298 * Performs a rollback.
7fcd3ad5
AE
299 *
300 * @param object event
0acbb6be 301 */
7fcd3ad5 302 _rollback: function(event) {
e098d212
AE
303 this._setIcon('spinner');
304
7fcd3ad5
AE
305 if (event) {
306 $(event.currentTarget).disable();
307 }
308
0acbb6be 309 this._executeStep('rollback');
158bd3ca
TD
310 },
311
312 /**
313 * Prepares installation dialog.
314 */
b32a906e 315 prepareInstallation: function() {
639e46d6
AE
316 if (document.activeElement) {
317 document.activeElement.blur();
318 }
319
872db512 320 require(['WoltLabSuite/Core/Ajax/Status'], ({show}) => show());
4da866ad
AE
321 this._proxy.setOption('data', this._getParameters());
322 this._proxy.sendRequest();
158bd3ca
TD
323 },
324
325 /**
cb9e9c12
AE
326 * Returns parameters to prepare installation.
327 *
328 * @return object
329 */
330 _getParameters: function() {
89aa80c1 331 return $.extend({}, this._additionalRequestParameters, {
cb9e9c12
AE
332 queueID: this._queueID,
333 step: 'prepare'
89aa80c1 334 });
cb9e9c12
AE
335 },
336
337 /**
338 * Handles successful AJAX requests.
e5b88a7f
AE
339 *
340 * @param object data
341 * @param string textStatus
342 * @param jQuery jqXHR
158bd3ca 343 */
cb9e9c12 344 _success: function(data, textStatus, jqXHR) {
ba133309 345 this._shouldRender = false;
cb9e9c12 346
a7f9deff
TD
347 if (typeof window._trackPackageStep === 'function') window._trackPackageStep(this._actionName, data);
348
4da866ad 349 if (this._dialog === null) {
ac86f5f7 350 this._dialog = $('<div id="package' + (this._actionName === 'UninstallPackage' ? 'Uni' : 'I') + 'nstallationDialog" />').hide().appendTo(document.body);
4da866ad
AE
351 this._dialog.wcfDialog({
352 closable: false,
6207cc21 353 title: WCF.Language.get(this._dialogTitle)
4da866ad 354 });
872db512 355 require(['WoltLabSuite/Core/Ajax/Status'], ({hide}) => hide());
4da866ad
AE
356 }
357
e098d212
AE
358 this._setIcon('spinner');
359
0acbb6be
AE
360 if (data.step == 'rollback') {
361 this._dialog.wcfDialog('close');
e9add3b2 362 this._dialog.remove();
0acbb6be 363
81fe3118 364 setTimeout(function () {
0acbb6be
AE
365 var $uninstallation = new WCF.ACP.Package.Uninstallation();
366 $uninstallation.start(data.packageID);
367 }, 200);
368
369 return;
370 }
371
158bd3ca 372 // receive new queue id
e5b88a7f
AE
373 if (data.queueID) {
374 this._queueID = data.queueID;
158bd3ca
TD
375 }
376
47d85da7
AE
377 // update template
378 if (data.template && !data.ignoreTemplate) {
379 this._dialog.html(data.template);
380 this._shouldRender = true;
381 }
382
158bd3ca 383 // update progress
e5b88a7f
AE
384 if (data.progress) {
385 $('#packageInstallationProgress').attr('value', data.progress).text(data.progress + '%');
386 $('#packageInstallationProgressLabel').text(data.progress + '%');
e70175d6 387 }
9f959ced 388
e70175d6 389 // update action
e5b88a7f
AE
390 if (data.currentAction) {
391 $('#packageInstallationAction').html(data.currentAction);
158bd3ca
TD
392 }
393
394 // handle success
cb9e9c12 395 if (data.step === 'success') {
962f1164 396 this._setIcon('check');
e098d212 397
0a3949d4 398 this._purgeTemplateContent($.proxy(function() {
cb9e9c12 399 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
58d5a1b5 400 var $button = $('<button type="button" class="button buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($form).click(function() {
7fcd3ad5
AE
401 $(this).disable();
402 window.location = data.redirectLocation;
403 });
3c87a1b1 404
0a3949d4 405 $('#packageInstallationInnerContentContainer').show();
cb9e9c12 406
45f1e6f9
AE
407 $(document).keydown(function(event) {
408 if (event.which === $.ui.keyCode.ENTER) {
409 $button.trigger('click');
410 }
411 });
412
cb9e9c12 413 this._dialog.wcfDialog('render');
0a3949d4 414 }, this));
158bd3ca
TD
415
416 return;
417 }
418
158bd3ca 419 // handle inner template
e5b88a7f 420 if (data.innerTemplate) {
599a39ba 421 var self = this;
599a39ba 422 $('#packageInstallationInnerContent').html(data.innerTemplate).find('input').keyup(function(event) {
45f1e6f9 423 if (event.keyCode === $.ui.keyCode.ENTER) {
b32a906e 424 self._submit(data);
599a39ba
AE
425 }
426 });
158bd3ca
TD
427
428 // create button to handle next step
e5b88a7f 429 if (data.step && data.node) {
5df9b2ef 430 $('#packageInstallationProgress').removeAttr('value');
e098d212
AE
431 this._setIcon('question');
432
cb9e9c12 433 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
58d5a1b5 434 $('<button type="button" class="button buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($form).click($.proxy(function(event) {
7fcd3ad5
AE
435 $(event.currentTarget).disable();
436
437 this._submit(data);
9db0c330 438 }, this));
158bd3ca
TD
439 }
440
0a3949d4 441 $('#packageInstallationInnerContentContainer').show();
158bd3ca 442
cb9e9c12 443 this._dialog.wcfDialog('render');
158bd3ca
TD
444 return;
445 }
446
447 // purge content
3c87a1b1 448 this._purgeTemplateContent($.proxy(function() {
e5b88a7f 449 // render container
ba133309 450 if (this._shouldRender) {
cb9e9c12 451 this._dialog.wcfDialog('render');
0a3949d4 452 }
5e6179f1 453
3c87a1b1 454 // execute next step
e5b88a7f
AE
455 if (data.step && data.node) {
456 this._executeStep(data.step, data.node);
3c87a1b1
AE
457 }
458 }, this));
459 },
460
599a39ba
AE
461 /**
462 * Submits the dialog content.
463 *
464 * @param object data
465 */
b32a906e 466 _submit: function(data) {
e098d212
AE
467 this._setIcon('spinner');
468
599a39ba 469 // collect form values
cb6e5946 470 var $additionalData = { };
599a39ba
AE
471 $('#packageInstallationInnerContent input').each(function(index, inputElement) {
472 var $inputElement = $(inputElement);
473 var $type = $inputElement.attr('type');
474
3bbe83f0
MS
475 if (($type != 'checkbox' && $type != 'radio') || $inputElement.prop('checked')) {
476 var $name = $inputElement.attr('name');
477 if ($name.match(/(.*)\[([^[]*)\]$/)) {
478 $name = RegExp.$1;
479 $key = RegExp.$2;
480
481 if ($additionalData[$name] === undefined) {
482 if ($key) {
483 $additionalData[$name] = { };
484 }
485 else {
486 $additionalData[$name] = [ ];
487 }
488 }
489
c001b457 490 if ($key) {
3bbe83f0 491 $additionalData[$name][$key] = $inputElement.val();
c001b457
AE
492 }
493 else {
3bbe83f0 494 $additionalData[$name].push($inputElement.val());
c001b457
AE
495 }
496 }
c001b457 497 else {
3bbe83f0 498 $additionalData[$name] = $inputElement.val();
c001b457
AE
499 }
500 }
599a39ba
AE
501 });
502
503 this._executeStep(data.step, data.node, $additionalData);
504 },
505
3c87a1b1
AE
506 /**
507 * Purges template content.
508 *
509 * @param function callback
510 */
511 _purgeTemplateContent: function(callback) {
f027ba61
AE
512 if ($('#packageInstallationInnerContent').children().length) {
513 $('#packageInstallationInnerContentContainer').hide();
514 $('#packageInstallationInnerContent').empty();
515
516 this._shouldRender = true;
158bd3ca 517 }
f027ba61
AE
518
519 callback();
158bd3ca
TD
520 },
521
522 /**
523 * Executes the next installation step.
524 *
525 * @param string step
526 * @param string node
527 * @param object additionalData
528 */
529 _executeStep: function(step, node, additionalData) {
cb6e5946 530 if (!additionalData) additionalData = { };
158bd3ca 531
89aa80c1 532 var $data = $.extend({}, this._additionalRequestParameters, {
a698c064
AE
533 node: node,
534 queueID: this._queueID,
535 step: step
158bd3ca 536 }, additionalData);
9f959ced 537
e5b88a7f
AE
538 this._proxy.setOption('data', $data);
539 this._proxy.sendRequest();
e098d212
AE
540 },
541
e0a0fe54
MS
542 /**
543 * Sets the icon with the given name as the current installation status icon.
544 *
545 * @param string iconName
546 */
e098d212 547 _setIcon: function(iconName) {
e6286f54
AE
548 const icon = this._dialog.find('.jsPackageInstallationStatus fa-icon');
549 if (icon.length === 1) {
550 icon[0].setIcon(iconName);
551 }
158bd3ca 552 }
cb9e9c12 553});
158bd3ca 554
a45fa84d
MS
555/**
556 * Handles canceling the package installation at the package installation
557 * confirm page.
558 */
559WCF.ACP.Package.Installation.Cancel = Class.extend({
560 /**
561 * Creates a new instance of WCF.ACP.Package.Installation.Cancel.
562 *
563 * @param integer queueID
564 */
565 init: function(queueID) {
566 $('#backButton').click(function() {
567 new WCF.Action.Proxy({
568 autoSend: true,
569 data: {
570 actionName: 'cancelInstallation',
571 className: 'wcf\\data\\package\\installation\\queue\\PackageInstallationQueueAction',
572 objectIDs: [ queueID ]
573 },
574 success: function(data) {
575 window.location = data.returnValues.url;
576 }
577 });
578 });
579 }
580});
581
158bd3ca 582/**
cb9e9c12 583 * Provides the package uninstallation.
158bd3ca
TD
584 *
585 * @param jQuery elements
74622428 586 * @param string wcfPackageListURL
158bd3ca 587 */
cb9e9c12 588WCF.ACP.Package.Uninstallation = WCF.ACP.Package.Installation.extend({
158bd3ca 589 /**
cb9e9c12
AE
590 * list of uninstallation buttons
591 * @var jQuery
158bd3ca 592 */
cb9e9c12 593 _elements: null,
158bd3ca
TD
594
595 /**
cb9e9c12
AE
596 * current package id
597 * @var integer
598 */
599 _packageID: 0,
600
601 /**
602 * Initializes the WCF.ACP.Package.Uninstallation class.
158bd3ca
TD
603 *
604 * @param jQuery elements
605 */
f35150dd 606 init: function(elements) {
cb9e9c12
AE
607 this._elements = elements;
608 this._packageID = 0;
158bd3ca 609
0acbb6be 610 if (this._elements !== undefined && this._elements.length) {
2ef79140 611 this._super(0, 'UninstallPackage');
cb9e9c12 612 }
158bd3ca
TD
613 },
614
0acbb6be
AE
615 /**
616 * Begins a package uninstallation without user action.
617 *
618 * @param integer packageID
619 */
620 start: function(packageID) {
621 this._actionName = 'UninstallPackage';
622 this._packageID = packageID;
623 this._queueID = 0;
6207cc21 624 this._dialogTitle = 'wcf.acp.package.uninstallation.title';
0acbb6be
AE
625
626 this._initProxy();
627 this.prepareInstallation();
628 },
629
158bd3ca 630 /**
cb9e9c12
AE
631 * @see WCF.ACP.Package.Installation.init()
632 */
633 _init: function() {
e3be0700 634 this._elements.click($.proxy(this._showConfirmationDialog, this));
e852aa82
AE
635 },
636
637 /**
638 * Displays a confirmation dialog prior to package uninstallation.
639 *
cec91f01 640 * @param object event
e852aa82 641 */
cec91f01 642 _showConfirmationDialog: function(event) {
e3be0700
AE
643 var $element = $(event.currentTarget);
644
cb9e9c12 645 var self = this;
e3be0700 646 WCF.System.Confirmation.show($element.data('confirmMessage'), function(action) {
cb9e9c12 647 if (action === 'confirm') {
e3be0700 648 self._packageID = $element.data('objectID');
b32a906e 649 self.prepareInstallation();
cb9e9c12 650 }
23e43ac5 651 }, undefined, undefined, true);
29b533c8
AE
652 },
653
654 /**
cb9e9c12 655 * @see WCF.ACP.Package.Installation._getParameters()
29b533c8 656 */
cb9e9c12
AE
657 _getParameters: function() {
658 return {
659 packageID: this._packageID,
660 step: 'prepare'
661 };
158bd3ca 662 }
cb9e9c12 663});
158bd3ca 664
66ed9925 665WCF.ACP.Package.Server = { };
298a7cd8
AE
666
667WCF.ACP.Package.Server.Installation = Class.extend({
668 _proxy: null,
669 _selectedPackage: '',
670
671 init: function() {
672 this._dialog = null;
673 this._selectedPackage = null;
674
675 this._proxy = new WCF.Action.Proxy({
676 success: $.proxy(this._success, this)
677 });
678 },
679
680 bind: function() {
681 $('.jsButtonPackageInstall').removeClass('jsButtonPackageInstall').click($.proxy(this._click, this));
682 },
683
684 /**
685 * Prepares a package installation.
686 *
687 * @param object event
688 */
689 _click: function(event) {
690 var $button = $(event.currentTarget);
691 WCF.System.Confirmation.show($button.data('confirmMessage'), $.proxy(function(action) {
692 if (action === 'confirm') {
693 this._selectedPackage = $button.data('package');
694 this._selectedPackageVersion = $button.data('packageVersion');
695 this._prepareInstallation();
696 }
23e43ac5 697 }, this), undefined, undefined, true);
298a7cd8
AE
698 },
699
700 /**
701 * Handles successful AJAX requests.
702 *
703 * @param object data
704 */
705 _success: function(data) {
706 if (data.returnValues.queueID) {
707 if (this._dialog !== null) {
708 this._dialog.wcfDialog('close');
709 }
710
711 var $installation = new WCF.ACP.Package.Installation(data.returnValues.queueID, undefined, false);
712 $installation.prepareInstallation();
713 }
714 else if (data.returnValues.template) {
715 if (this._dialog === null) {
716 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
717 this._dialog.wcfDialog({
718 title: WCF.Language.get('wcf.acp.package.update.unauthorized')
719 });
720 }
721 else {
722 this._dialog.html(data.returnValues.template).wcfDialog('open');
723 }
724
725 this._dialog.find('.formSubmit > button').click($.proxy(this._submitAuthentication, this));
726 }
727 },
728
729 /**
730 * Submits authentication data for current update server.
731 *
732 * @param object event
733 */
734 _submitAuthentication: function(event) {
735 var $usernameField = $('#packageUpdateServerUsername');
736 var $passwordField = $('#packageUpdateServerPassword');
737
738 // remove error messages if any
739 $usernameField.next('small.innerError').remove();
740 $passwordField.next('small.innerError').remove();
741
742 var $continue = true;
743 if ($.trim($usernameField.val()) === '') {
744 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField);
745 $continue = false;
746 }
747
748 if ($.trim($passwordField.val()) === '') {
749 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField);
750 $continue = false;
751 }
752
753 if ($continue) {
754 this._prepareInstallation($(event.currentTarget).data('packageUpdateServerID'));
755 }
756 },
757
758 /**
759 * Prepares package installation.
760 *
761 * @param integer packageUpdateServerID
762 */
763 _prepareInstallation: function(packageUpdateServerID) {
764 var $parameters = {
765 'packages': { }
766 };
767 $parameters['packages'][this._selectedPackage] = this._selectedPackageVersion;
768
769 if (packageUpdateServerID) {
770 $parameters.authData = {
771 packageUpdateServerID: packageUpdateServerID,
772 password: $.trim($('#packageUpdateServerPassword').val()),
773 saveCredentials: ($('#packageUpdateServerSaveCredentials:checked').length ? true : false),
774 username: $.trim($('#packageUpdateServerUsername').val())
775 };
776 }
777
778 this._proxy.setOption('data', {
779 actionName: 'prepareInstallation',
780 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
781 parameters: $parameters
782 });
783 this._proxy.sendRequest();
784 },
2061c822 785});
298a7cd8 786
3536d2fe
AE
787/**
788 * Namespace for package update related classes.
789 */
790WCF.ACP.Package.Update = { };
791
3536d2fe
AE
792/**
793 * Searches for available updates.
60fe64f6
AE
794 *
795 * @param boolean bindOnExistingButtons
3536d2fe
AE
796 */
797WCF.ACP.Package.Update.Search = Class.extend({
be005df2
AE
798 /** @var {Element} */
799 _button: null,
800
3536d2fe
AE
801 /**
802 * dialog overlay
803 * @var jQuery
804 */
805 _dialog: null,
806
807 /**
60fe64f6
AE
808 * Initializes the WCF.ACP.Package.SearchForUpdates class.
809 *
be005df2 810 * @param {boolean} bindOnExistingButtons
3536d2fe 811 */
60fe64f6 812 init: function(bindOnExistingButtons) {
3536d2fe
AE
813 this._dialog = null;
814
be005df2 815 if (!bindOnExistingButtons === true) {
925dad9f 816 $(`<li>
58d5a1b5 817 <button type="button" class="button jsButtonSearchForUpdates">
925dad9f
AE
818 <fa-icon size="16" name="arrows-rotate"></fa-icon>
819 <span>${WCF.Language.get('wcf.acp.package.searchForUpdates')}</span>
820 </button>
821 </li>`).prependTo($('.contentHeaderNavigation > ul'));
60fe64f6 822 }
be005df2
AE
823
824 this._button = elBySel('.jsButtonSearchForUpdates');
490ef421
AE
825 if (this._button) {
826 this._button.addEventListener('click', this._click.bind(this));
827
828 const url = new URL(window.location.href);
829 if (url.searchParams.has("searchForUpdates")) {
830 this._click();
831 }
832 }
3536d2fe
AE
833 },
834
835 /**
836 * Handles clicks on the search button.
c93ced9f
AE
837 *
838 * @param {Event} event
3536d2fe 839 */
c93ced9f 840 _click: function(event) {
490ef421 841 event?.preventDefault();
c93ced9f 842
be005df2
AE
843 if (this._button.classList.contains('disabled')) {
844 return;
845 }
846
847 this._button.classList.add('disabled');
848
3536d2fe
AE
849 if (this._dialog === null) {
850 new WCF.Action.Proxy({
851 autoSend: true,
852 data: {
853 actionName: 'searchForUpdates',
70e6f4e6
AE
854 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
855 parameters: {
856 ignoreCache: 1
857 }
3536d2fe
AE
858 },
859 success: $.proxy(this._success, this)
860 });
861 }
862 else {
863 this._dialog.wcfDialog('open');
864 }
865 },
866
867 /**
868 * Handles successful AJAX requests.
869 *
870 * @param object data
871 * @param string textStatus
872 * @param jQuery jqXHR
873 */
874 _success: function(data, textStatus, jqXHR) {
cdfed2c3
AE
875 if (typeof window._trackSearchForUpdates === 'function') {
876 window._trackSearchForUpdates(data);
877 return;
878 }
879
3536d2fe
AE
880 if (data.returnValues.url) {
881 window.location = data.returnValues.url;
882 }
883 else {
884 this._dialog = $('<div>' + WCF.Language.get('wcf.acp.package.searchForUpdates.noResults') + '</div>').hide().appendTo(document.body);
885 this._dialog.wcfDialog({
886 title: WCF.Language.get('wcf.acp.package.searchForUpdates')
887 });
be005df2
AE
888
889 this._button.classList.remove('disabled');
3536d2fe
AE
890 }
891 }
892});
893
532f1173
AE
894/**
895 * Worker support for ACP.
896 *
897 * @param string dialogID
898 * @param string className
0cb0c522
AE
899 * @param string title
900 * @param object parameters
901 * @param object callback
835354e5
AE
902 *
903 * @deprecated 3.1 - please use `WoltLabSuite/Core/Acp/Ui/Worker` instead
532f1173 904 */
1f9100ba 905WCF.ACP.Worker = Class.extend({
532f1173
AE
906 /**
907 * Initializes a new worker instance.
908 *
909 * @param string dialogID
910 * @param string className
2cea2b5b
AE
911 * @param string title
912 * @param object parameters
0cb0c522 913 * @param object callback
532f1173 914 */
0cb0c522 915 init: function(dialogID, className, title, parameters, callback) {
835354e5
AE
916 if (typeof callback === 'function') {
917 throw new Error("The callback parameter is no longer supported, please migrate to 'WoltLabSuite/Core/Acp/Ui/Worker'.");
532f1173
AE
918 }
919
835354e5
AE
920 require(['WoltLabSuite/Core/Acp/Ui/Worker'], function(AcpUiWorker) {
921 new AcpUiWorker({
922 // dialog
923 dialogId: dialogID,
924 dialogTitle: title,
925
926 // ajax
927 className: className,
928 parameters: parameters
532f1173 929 });
835354e5 930 });
532f1173 931 }
1f9100ba 932});
13d8b49b
MS
933
934/**
935 * Namespace for category-related functions.
936 */
cb6e5946 937WCF.ACP.Category = { };
13d8b49b
MS
938
939/**
940 * Handles collapsing categories.
941 *
942 * @param string className
943 * @param integer objectTypeID
944 */
945WCF.ACP.Category.Collapsible = WCF.Collapsible.SimpleRemote.extend({
946 /**
947 * @see WCF.Collapsible.Remote.init()
948 */
fc3d134b 949 init: function(className) {
13d8b49b
MS
950 var sortButton = $('.formSubmit > button[data-type="submit"]');
951 if (sortButton) {
952 sortButton.click($.proxy(this._sort, this));
953 }
954
955 this._super(className);
956 },
9f959ced 957
13d8b49b
MS
958 /**
959 * @see WCF.Collapsible.Remote._getButtonContainer()
960 */
961 _getButtonContainer: function(containerID) {
962 return $('#' + containerID + ' > .buttons');
963 },
9f959ced 964
13d8b49b
MS
965 /**
966 * @see WCF.Collapsible.Remote._getContainers()
967 */
968 _getContainers: function() {
969 return $('.jsCategory').has('ol').has('li');
970 },
9f959ced 971
13d8b49b
MS
972 /**
973 * @see WCF.Collapsible.Remote._getTarget()
974 */
975 _getTarget: function(containerID) {
976 return $('#' + containerID + ' > ol');
977 },
978
979 /**
980 * Handles a click on the sort button.
981 */
982 _sort: function() {
983 // remove existing collapsible buttons
984 $('.collapsibleButton').remove();
985
986 // reinit containers
cb6e5946
MS
987 this._containers = { };
988 this._containerData = { };
13d8b49b
MS
989
990 var $containers = this._getContainers();
991 if ($containers.length == 0) {
a0b60d23 992 console.debug('[WCF.ACP.Category.Collapsible] Empty container set given, aborting.');
13d8b49b
MS
993 }
994 $containers.each($.proxy(function(index, container) {
995 var $container = $(container);
996 var $containerID = $container.wcfIdentify();
997 this._containers[$containerID] = $container;
998
999 this._initContainer($containerID);
1000 }, this));
1001 }
1002});
1003
71662ae8
AE
1004/**
1005 * Provides the search dropdown for ACP
1006 *
1007 * @see WCF.Search.Base
1008 */
1009WCF.ACP.Search = WCF.Search.Base.extend({
001740db
AE
1010 _delay: 250,
1011
c8f7b2a1
MW
1012 /**
1013 * name of the selected search provider
1014 * @var string
1015 */
1016 _providerName: '',
1017
71662ae8
AE
1018 /**
1019 * @see WCF.Search.Base.init()
1020 */
1021 init: function() {
1022 this._className = 'wcf\\data\\acp\\search\\provider\\ACPSearchProviderAction';
6476e7a1 1023 this._super('#pageHeaderSearch input[name=q]');
d6760827
MS
1024
1025 // disable form submitting
6476e7a1 1026 $('#pageHeaderSearch > form').on('submit', function(event) {
d6760827
MS
1027 event.preventDefault();
1028 });
c8f7b2a1
MW
1029
1030 var $dropdown = WCF.Dropdown.getDropdownMenu('pageHeaderSearchType');
1031 $dropdown.find('a[data-provider-name]').on('click', $.proxy(function(event) {
1032 event.preventDefault();
1033 var $button = $(event.target);
f776bbc0 1034 $('.pageHeaderSearchType > .button > .pageHeaderSearchTypeLabel').text($button.text());
c8f7b2a1
MW
1035
1036 var $oldProviderName = this._providerName;
1037 this._providerName = ($button.data('providerName') != 'everywhere' ? $button.data('providerName') : '');
1038
1039 if ($oldProviderName != this._providerName) {
1040 var $searchString = $.trim(this._searchInput.val());
1041 if ($searchString) {
1042 var $parameters = {
1043 data: {
1044 excludedSearchValues: this._excludedSearchValues,
1045 searchString: $searchString
1046 }
1047 };
1048 this._queryServer($parameters);
1049 }
c5dd767e 1050 }
c8f7b2a1 1051 }, this));
666e4579
AE
1052
1053 const searchInput = document.querySelector("#pageHeaderSearch input[name=q]");
1054 document.addEventListener("keydown", (event) => {
1055 if (event.key !== "s") {
1056 return;
1057 }
1058
1059 if (!event.defaultPrevented && document.activeElement === document.body) {
1060 searchInput.focus();
1061
1062 event.preventDefault();
1063 }
1064 }, {
1065 passive: false,
1066 });
1067
1068 searchInput.addEventListener("keydown", (event) => {
1069 if (event.key !== "Escape") {
1070 return;
1071 }
1072
1073 if (!event.defaultPrevented && searchInput.value.trim() === "") {
1074 event.preventDefault();
1075
1076 searchInput.blur();
1077 }
1078 }, {
1079 passive: false,
1080 });
71662ae8
AE
1081 },
1082
1083 /**
1084 * @see WCF.Search.Base._createListItem()
1085 */
1086 _createListItem: function(resultList) {
1087 // add a divider between result lists
1088 if (this._list.children('li').length > 0) {
1089 $('<li class="dropdownDivider" />').appendTo(this._list);
1090 }
1091
1092 // add caption
1093 $('<li class="dropdownText">' + resultList.title + '</li>').appendTo(this._list);
1094
1095 // add menu items
1096 for (var $i in resultList.items) {
1097 var $item = resultList.items[$i];
1098
790aadd9 1099 $('<li><a href="' + $item.link + '"><span>' + WCF.String.escapeHTML($item.title) + '</span>' + ($item.subtitle ? '<small>' + WCF.String.escapeHTML($item.subtitle) + '</small>' : '') + '</a></li>').appendTo(this._list);
9a130ab4
AE
1100
1101 this._itemCount++;
71662ae8 1102 }
9a130ab4
AE
1103 },
1104
71c70823
MS
1105 /**
1106 * @see WCF.Search.Base._openDropdown()
1107 */
1108 _openDropdown: function() {
1109 this._list.find('small').each(function(index, element) {
1110 while (element.scrollWidth > element.clientWidth) {
1111 element.innerText = '\u2026 ' + element.innerText.substr(3);
1112 }
1113 });
1114 },
1115
c138555b
AE
1116 /**
1117 * @see WCF.Search.Base._handleEmptyResult()
1118 */
1119 _handleEmptyResult: function() {
1120 $('<li class="dropdownText">' + WCF.Language.get('wcf.acp.search.noResults') + '</li>').appendTo(this._list);
1121
1122 return true;
1123 },
1124
9a130ab4
AE
1125 /**
1126 * @see WCF.Search.Base._highlightSelectedElement()
1127 */
1128 _highlightSelectedElement: function() {
1129 this._list.find('li').removeClass('dropdownNavigationItem');
1130 this._list.find('li:not(.dropdownDivider):not(.dropdownText)').eq(this._itemIndex).addClass('dropdownNavigationItem');
1131 },
1132
1133 /**
1134 * @see WCF.Search.Base._selectElement()
1135 */
1136 _selectElement: function(event) {
c138555b
AE
1137 if (this._itemIndex === -1) {
1138 return false;
1139 }
1140
9a130ab4 1141 window.location = this._list.find('li.dropdownNavigationItem > a').attr('href');
6476e7a1
AE
1142 },
1143
1144 _success: function(data) {
1145 this._super(data);
1146
7a6f4991
AE
1147 const container = document.getElementById("pageHeaderSearch").querySelector(".pageHeaderSearchInputContainer");
1148 const { bottom } = container.getBoundingClientRect();
1149 this._list[0].style.setProperty("top", `${Math.trunc(bottom)}px`, "important");
1150 this._list[0].classList.add("acpSearchDropdown");
1151 this._list[0].dataset.dropdownIgnorePageScroll = "true";
c8f7b2a1
MW
1152 },
1153
1154 /**
1155 * @see WCF.Search.Base._getParameters()
1156 */
1157 _getParameters: function(parameters) {
1158 parameters.data.providerName = this._providerName;
1159
1160 return parameters;
71662ae8
AE
1161 }
1162});
11cf19be
MW
1163
1164/**
1165 * Namespace for user management.
1166 */
1167WCF.ACP.User = { };
1168
1169/**
1170 * Generic implementation to ban users.
1171 */
1172WCF.ACP.User.BanHandler = {
1173 /**
1174 * callback object
1175 * @var object
1176 */
1177 _callback: null,
1178
1179 /**
1180 * dialog overlay
1181 * @var jQuery
1182 */
1183 _dialog: null,
1184
1185 /**
1186 * action proxy
1187 * @var WCF.Action.Proxy
1188 */
1189 _proxy: null,
1190
1191 /**
1192 * Initializes WCF.ACP.User.BanHandler on first use.
1193 */
1194 init: function() {
11cf19be
MW
1195 this._proxy = new WCF.Action.Proxy({
1196 success: $.proxy(this._success, this)
1197 });
1198
1199 $('.jsBanButton').click($.proxy(function(event) {
1200 var $button = $(event.currentTarget);
1201 if ($button.data('banned')) {
1202 this.unban([ $button.data('objectID') ]);
1203 }
1204 else {
1205 this.ban([ $button.data('objectID') ]);
1206 }
1207 }, this));
bbef7ed8 1208
efcd047d
MS
1209 require(['EventHandler'], function(EventHandler) {
1210 EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.user', this._clipboardAction.bind(this));
1211 }.bind(this));
bbef7ed8
MW
1212 },
1213
1214 /**
efcd047d
MS
1215 * Reacts to executed clipboard actions.
1216 *
1217 * @param {object<string, *>} actionData data of the executed clipboard action
bbef7ed8 1218 */
efcd047d
MS
1219 _clipboardAction: function(actionData) {
1220 if (actionData.data.actionName === 'com.woltlab.wcf.user.ban') {
1221 this.ban(actionData.data.parameters.objectIDs);
bbef7ed8 1222 }
11cf19be
MW
1223 },
1224
1225 /**
1226 * Unbans users.
1227 *
1228 * @param array<integer> userIDs
1229 */
1230 unban: function(userIDs) {
1231 this._proxy.setOption('data', {
1232 actionName: 'unban',
1233 className: 'wcf\\data\\user\\UserAction',
1234 objectIDs: userIDs
1235 });
1236 this._proxy.sendRequest();
1237 },
1238
1239 /**
1240 * Bans users.
1241 *
1242 * @param array<integer> userIDs
1243 */
1244 ban: function(userIDs) {
f034d0ec
MS
1245 if (this._dialog === null) {
1246 // create dialog
1247 this._dialog = $('<div />').hide().appendTo(document.body);
6f2bc235 1248 this._dialog.append($('<div class="section"><dl><dt><label for="userBanReason">' + WCF.Language.get('wcf.acp.user.banReason') + '</label></dt><dd><textarea id="userBanReason" cols="40" rows="3" /><small>' + WCF.Language.get('wcf.acp.user.banReason.description') + '</small></dd></dl><dl><dt></dt><dd><label for="userBanNeverExpires"><input type="checkbox" name="userBanNeverExpires" id="userBanNeverExpires" checked> ' + WCF.Language.get('wcf.acp.user.ban.neverExpires') + '</label></dd></dl><dl id="userBanExpiresSettings" style="display: none;"><dt><label for="userBanExpires">' + WCF.Language.get('wcf.acp.user.ban.expires') + '</label></dt><dd><input type="date" name="userBanExpires" id="userBanExpires" class="medium" min="' + new Date(TIME_NOW * 1000).toISOString() + '" data-ignore-timezone="true" /><small>' + WCF.Language.get('wcf.acp.user.ban.expires.description') + '</small></dd></dl></div>'));
58d5a1b5 1249 this._dialog.append($('<div class="formSubmit"><button type="button" class="button buttonPrimary" accesskey="s">' + WCF.Language.get('wcf.global.button.submit') + '</button></div>'));
f034d0ec
MS
1250
1251 this._dialog.find('#userBanNeverExpires').change(function() {
1252 $('#userBanExpiresSettings').toggle();
1253 });
1254
1255 this._dialog.find('button').click($.proxy(this._submit, this));
1256 }
1257 else {
1258 // reset dialog
1259 $('#userBanReason').val('');
1260 $('#userBanNeverExpires').prop('checked', true);
1261 $('#userBanExpiresSettings').hide();
1262 $('#userBanExpiresDatePicker, #userBanExpires').val('');
1263 }
1264
1265 this._dialog.data('userIDs', userIDs);
1266 this._dialog.wcfDialog({
1267 title: WCF.Language.get('wcf.acp.user.ban.sure')
1268 });
1269 },
1270
1271 /**
1272 * Handles submitting the ban dialog.
1273 */
1274 _submit: function() {
1275 this._dialog.find('.innerError').remove();
1276
1277 var $banExpires = '';
1278 if (!$('#userBanNeverExpires').is(':checked')) {
1279 var $banExpires = $('#userBanExpiresDatePicker').val();
1280 if (!$banExpires) {
1281 this._dialog.find('#userBanExpiresSettings > dd > small').prepend($('<small class="innerError" />').text(WCF.Language.get('wcf.global.form.error.empty')));
1282 return
1283 }
1284 }
1285
1286 this._proxy.setOption('data', {
1287 actionName: 'ban',
1288 className: 'wcf\\data\\user\\UserAction',
1289 objectIDs: this._dialog.data('userIDs'),
1290 parameters: {
1291 banReason: $('#userBanReason').val(),
1292 banExpires: $banExpires
11cf19be 1293 }
f034d0ec
MS
1294 });
1295 this._proxy.sendRequest();
11cf19be
MW
1296 },
1297
1298 /**
1299 * Handles successful AJAX calls.
1300 *
1301 * @param object data
1302 * @param string textStatus
1303 * @param jQuery jqXHR
1304 */
1305 _success: function(data, textStatus, jqXHR) {
47619e6c
AE
1306 elBySelAll('.jsUserRow', undefined, function(userRow) {
1307 var userId = parseInt(elData(userRow, 'object-id'), 10);
1308 if (data.objectIDs.indexOf(userId) !== -1) {
1309 elData(userRow, 'banned', data.actionName === 'ban');
1310 }
1311 });
1312
11cf19be
MW
1313 $('.jsBanButton').each(function(index, button) {
1314 var $button = $(button);
1315 if (WCF.inArray($button.data('objectID'), data.objectIDs)) {
1316 if (data.actionName == 'unban') {
0caf2e88
AE
1317 $button.data('banned', false).attr('data-tooltip', $button.data('banMessage'));
1318 $button[0].querySelector("fa-icon").setIcon("unlock");
11cf19be
MW
1319 }
1320 else {
0caf2e88
AE
1321 $button.data('banned', true).attr('data-tooltip', $button.data('unbanMessage'));
1322 $button[0].querySelector("fa-icon").setIcon("lock");
11cf19be
MW
1323 }
1324 }
1325 });
1326
1327 var $notification = new WCF.System.Notification();
1328 $notification.show();
bbef7ed8
MW
1329
1330 WCF.Clipboard.reload();
f034d0ec
MS
1331
1332 if (data.actionName == 'ban') {
1333 this._dialog.wcfDialog('close');
1334 }
47619e6c
AE
1335
1336 WCF.System.Event.fireEvent('com.woltlab.wcf.acp.user', 'refresh', {userIds: data.objectIDs});
11cf19be 1337 }
bc3b71fd 1338};
ef012ee1 1339
2a5d6b57
MS
1340/**
1341 * Namespace for user group management.
1342 */
1343WCF.ACP.User.Group = { };
1344
1345/**
1346 * Handles copying user groups.
1347 */
1348WCF.ACP.User.Group.Copy = Class.extend({
1349 /**
1350 * id of the copied group
1351 * @var integer
1352 */
1353 _groupID: 0,
1354
1355 /**
1356 * Initializes a new instance of WCF.ACP.User.Group.Copy.
1357 *
1358 * @param integer groupID
1359 */
1360 init: function(groupID) {
1361 this._groupID = groupID;
1362
1363 $('.jsButtonUserGroupCopy').click($.proxy(this._click, this));
1364 },
1365
1366 /**
1367 * Handles clicking on a 'copy user group' button.
1368 */
1369 _click: function() {
7730f067 1370 var $template = $('<div class="section" />');
95961bdf
MW
1371 $template.append($('<dl class="wide"><dt /><dd><label><input type="checkbox" id="copyMembers" value="1" /> ' + WCF.Language.get('wcf.acp.group.copy.copyMembers') + '</label><small>' + WCF.Language.get('wcf.acp.group.copy.copyMembers.description') + '</small></dd></dl>'));
1372 $template.append($('<dl class="wide"><dt /><dd><label><input type="checkbox" id="copyUserGroupOptions" value="1" /> ' + WCF.Language.get('wcf.acp.group.copy.copyUserGroupOptions') + '</label><small>' + WCF.Language.get('wcf.acp.group.copy.copyUserGroupOptions.description') + '</small></dd></dl>'));
1373 $template.append($('<dl class="wide"><dt /><dd><label><input type="checkbox" id="copyACLOptions" value="1" /> ' + WCF.Language.get('wcf.acp.group.copy.copyACLOptions') + '</label><small>' + WCF.Language.get('wcf.acp.group.copy.copyACLOptions.description') + '</small></dd></dl>'));
2a5d6b57
MS
1374
1375 WCF.System.Confirmation.show(WCF.Language.get('wcf.acp.group.copy.confirmMessage'), $.proxy(function(action) {
1376 if (action === 'confirm') {
1377 new WCF.Action.Proxy({
1378 autoSend: true,
1379 data: {
1380 actionName: 'copy',
1381 className: 'wcf\\data\\user\\group\\UserGroupAction',
1382 objectIDs: [ this._groupID ],
1383 parameters: {
1384 copyACLOptions: $('#copyACLOptions').is(':checked'),
1385 copyMembers: $('#copyMembers').is(':checked'),
1386 copyUserGroupOptions: $('#copyUserGroupOptions').is(':checked')
1387 }
1388 },
1389 success: function(data) {
1390 window.location = data.returnValues.redirectURL;
1391 }
1392 });
1393 }
23e43ac5 1394 }, this), '', $template, true);
2a5d6b57
MS
1395 }
1396});
1397
ef012ee1
MW
1398/**
1399 * Generic implementation to enable users.
1400 */
1401WCF.ACP.User.EnableHandler = {
1402 /**
1403 * action proxy
1404 * @var WCF.Action.Proxy
1405 */
1406 _proxy: null,
1407
1408 /**
1409 * Initializes WCF.ACP.User.EnableHandler on first use.
1410 */
1411 init: function() {
1412 this._proxy = new WCF.Action.Proxy({
1413 success: $.proxy(this._success, this)
1414 });
1415
1416 $('.jsEnableButton').click($.proxy(function(event) {
1417 var $button = $(event.currentTarget);
1418 if ($button.data('enabled')) {
1419 this.disable([ $button.data('objectID') ]);
1420 }
1421 else {
1422 this.enable([ $button.data('objectID') ]);
1423 }
1424 }, this));
1425
efcd047d
MS
1426 require(['EventHandler'], function(EventHandler) {
1427 EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.user', this._clipboardAction.bind(this));
1428 }.bind(this));
ef012ee1
MW
1429 },
1430
1431 /**
efcd047d
MS
1432 * Reacts to executed clipboard actions.
1433 *
1434 * @param {object<string, *>} actionData data of the executed clipboard action
ef012ee1 1435 */
efcd047d
MS
1436 _clipboardAction: function(actionData) {
1437 if (actionData.data.actionName === 'com.woltlab.wcf.user.enable') {
1438 this.enable(actionData.data.parameters.objectIDs);
ef012ee1
MW
1439 }
1440 },
1441
1442 /**
1443 * Disables users.
1444 *
1445 * @param array<integer> userIDs
1446 */
1447 disable: function(userIDs) {
1448 this._proxy.setOption('data', {
1449 actionName: 'disable',
1450 className: 'wcf\\data\\user\\UserAction',
1451 objectIDs: userIDs
1452 });
1453 this._proxy.sendRequest();
1454 },
1455
1456 /**
1457 * Enables users.
1458 *
1459 * @param array<integer> userIDs
1460 */
1461 enable: function(userIDs) {
1462 this._proxy.setOption('data', {
1463 actionName: 'enable',
1464 className: 'wcf\\data\\user\\UserAction',
1465 objectIDs: userIDs
1466 });
1467 this._proxy.sendRequest();
1468 },
1469
1470 /**
1471 * Handles successful AJAX calls.
1472 *
1473 * @param object data
1474 * @param string textStatus
1475 * @param jQuery jqXHR
1476 */
1477 _success: function(data, textStatus, jqXHR) {
47619e6c
AE
1478 elBySelAll('.jsUserRow', undefined, function(userRow) {
1479 var userId = parseInt(elData(userRow, 'object-id'), 10);
1480 if (data.objectIDs.indexOf(userId) !== -1) {
1481 elData(userRow, 'enabled', data.actionName === 'enable');
1482 }
1483 });
1484
ef012ee1
MW
1485 $('.jsEnableButton').each(function(index, button) {
1486 var $button = $(button);
1487 if (WCF.inArray($button.data('objectID'), data.objectIDs)) {
1488 if (data.actionName == 'disable') {
0caf2e88
AE
1489 $button.data('enabled', false).attr('data-tooltip', $button.data('enableMessage'));
1490 $button[0].querySelector("fa-icon").setIcon("square");
ef012ee1
MW
1491 }
1492 else {
0caf2e88
AE
1493 $button.data('enabled', true).attr('data-tooltip', $button.data('disableMessage'));
1494 $button[0].querySelector("fa-icon").setIcon("square-check");
ef012ee1
MW
1495 }
1496 }
1497 });
1498
1499 var $notification = new WCF.System.Notification();
00ce5cf8 1500 $notification.show(function() { window.location.reload(); });
47619e6c
AE
1501
1502 WCF.System.Event.fireEvent('com.woltlab.wcf.acp.user', 'refresh', {userIds: data.objectIDs});
ef012ee1
MW
1503 }
1504};
1505
cb6e5946
MS
1506/**
1507 * Handles the send new password clipboard action.
1508 */
1509WCF.ACP.User.SendNewPasswordHandler = {
cb6e5946
MS
1510 /**
1511 * Initializes WCF.ACP.User.SendNewPasswordHandler on first use.
1512 */
1513 init: function() {
efcd047d
MS
1514 require(['EventHandler'], function(EventHandler) {
1515 EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.user', this._clipboardAction.bind(this));
1516 }.bind(this));
cb6e5946
MS
1517 },
1518
1519 /**
efcd047d
MS
1520 * Reacts to executed clipboard actions.
1521 *
1522 * @param {object<string, *>} actionData data of the executed clipboard action
cb6e5946 1523 */
efcd047d
MS
1524 _clipboardAction: function(actionData) {
1525 if (actionData.data.actionName === 'com.woltlab.wcf.user.sendNewPassword') {
5a1e558d
MS
1526 require(['Language', 'Ui/Confirmation', 'WoltLabSuite/Core/Acp/Ui/Worker'], function(Language, UiConfirmation, AcpUiWorker) {
1527 UiConfirmation.show({
1528 confirm: () => {
1529 new AcpUiWorker({
1530 dialogId: 'sendingNewPasswords',
1531 dialogTitle: Language.get('wcf.acp.user.sendNewPassword.workerTitle'),
1532 className: 'wcf\\system\\worker\\SendNewPasswordWorker',
1533 parameters: {
1534 userIDs: actionData.data.parameters.objectIDs
1535 },
1536 });
1537 },
1538 message: actionData.data.parameters.confirmMessage,
1539 })
cb6e5946
MS
1540 });
1541 }
1542 }
1543};
1544
e1fe9453
MW
1545/**
1546 * Namespace for stat-related classes.
1547 */
1548WCF.ACP.Stat = { };
1549
1550/**
1551 * Shows the daily stat chart.
1552 */
1553WCF.ACP.Stat.Chart = Class.extend({
1554 init: function() {
1555 this._proxy = new WCF.Action.Proxy({
1556 success: $.proxy(this._success, this)
1557 });
1558
49ec07c5 1559 $('#statRefreshButton').click($.proxy(this._refresh, this));
e1fe9453 1560
49ec07c5 1561 this._refresh();
e1fe9453
MW
1562 },
1563
49ec07c5 1564 _refresh: function() {
e1fe9453
MW
1565 var $objectTypeIDs = [ ];
1566 $('input[name=objectTypeID]:checked').each(function() {
1567 $objectTypeIDs.push($(this).val());
1568 });
1569
49ec07c5
MW
1570 if (!$objectTypeIDs.length) return;
1571
e1fe9453
MW
1572 this._proxy.setOption('data', {
1573 className: 'wcf\\data\\stat\\daily\\StatDailyAction',
1574 actionName: 'getData',
1575 parameters: {
1576 startDate: $('#startDateDatePicker').val(),
1577 endDate: $('#endDateDatePicker').val(),
1578 value: $('input[name=value]:checked').val(),
49ec07c5 1579 dateGrouping: $('input[name=dateGrouping]:checked').val(),
e1fe9453
MW
1580 objectTypeIDs: $objectTypeIDs
1581 }
1582 });
1583 this._proxy.sendRequest();
1584 },
1585
1586 _success: function(data) {
49ec07c5
MW
1587 switch ($('input[name=dateGrouping]:checked').val()) {
1588 case 'yearly':
1589 var $minTickSize = [1, "year"];
1590 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.yearly');
1591 break;
1592 case 'monthly':
1593 var $minTickSize = [1, "month"];
1594 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.monthly');
1595 break;
1596 case 'weekly':
1597 var $minTickSize = [7, "day"];
1598 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.weekly');
1599 break;
1600 default:
1601 var $minTickSize = [1, "day"];
1602 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.daily');
1603 }
1604
e1fe9453
MW
1605 var options = {
1606 series: {
1607 lines: {
1608 show: true
1609 },
1610 points: {
1611 show: true
1612 }
1613 },
1614 grid: {
1615 hoverable: true
1616 },
1617 xaxis: {
1618 mode: "time",
49ec07c5
MW
1619 minTickSize: $minTickSize,
1620 timeformat: $timeFormat,
1621 monthNames: WCF.Language.get('__monthsShort')
e1fe9453
MW
1622 },
1623 yaxis: {
1624 min: 0,
1625 tickDecimals: 0,
1626 tickFormatter: function(val) {
1627 return WCF.String.addThousandsSeparator(val);
1628 }
a16e5980 1629 }
e1fe9453
MW
1630 };
1631
1632 var $data = [ ];
1633 for (var $key in data.returnValues) {
1634 var $row = data.returnValues[$key];
1635 for (var $i = 0; $i < $row.data.length; $i++) {
1636 $row.data[$i][0] *= 1000;
1637 }
1638
1639 $data.push($row);
1640 }
1641
1642 $.plot("#chart", $data, options);
1643
a16e5980
MW
1644 require(['Ui/Alignment'], function (UiAlignment) {
1645 var span = elCreate('span');
1646 span.style.setProperty('position', 'absolute', '');
1647 document.body.appendChild(span);
1648 $("#chart").on("plothover", function(event, pos, item) {
1649 if (item) {
1650 span.style.setProperty('top', item.pageY + 'px', '');
1651 span.style.setProperty('left', item.pageX + 'px', '');
1652 $("#chartTooltip").html(item.series.xaxis.tickFormatter(item.datapoint[0], item.series.xaxis) + ', ' + WCF.String.formatNumeric(item.datapoint[1]) + ' ' + item.series.label).show();
1653 UiAlignment.set($("#chartTooltip")[0], span, {
1654 verticalOffset: 5,
b984a2b2
F
1655 horizontal: 'center',
1656 vertical: 'top'
a16e5980
MW
1657 });
1658 }
1659 else {
1660 $("#chartTooltip").hide();
1661 }
1662 });
e1fe9453 1663 });
7652dbb0
MW
1664
1665 if (!$data.length) {
1666 $('#chart').append('<p style="position: absolute; font-size: 1.2rem; text-align: center; top: 50%; margin-top: -20px; width: 100%">' + WCF.Language.get('wcf.acp.stat.noData') + '</p>');
1667 }
a59f3c51
MW
1668
1669 elBySel('.contentHeader > .contentTitle').scrollIntoView({ behavior: 'smooth' });
e1fe9453 1670 }
e72efaf9 1671});
e866b80e
MS
1672
1673/**
1674 * Namespace for ACP ad management.
1675 */
1676WCF.ACP.Ad = { };
1677
1678/**
1679 * Handles the location of an ad during ad creation/editing.
1680 */
1681WCF.ACP.Ad.LocationHandler = Class.extend({
1682 /**
1683 * fieldset of the page conditions
1684 * @var jQuery
1685 */
1686 _pageConditions: null,
1687
1688 /**
098aa5ee 1689 * select elements for the page controller condition
0360f61b 1690 * @var jQuery[]
e866b80e 1691 */
0360f61b 1692 _pageInputs: [],
e866b80e 1693
098aa5ee
MS
1694 /**
1695 * page controller condition container
1696 * @var jQuery[]
1697 */
1698 _pageSelectionContainer: null,
1699
e866b80e
MS
1700 /**
1701 * Initializes a new WCF.ACP.Ad.LocationHandler object.
1949f2e3
MS
1702 *
1703 * @param {object} variablesDescriptions
e866b80e 1704 */
1949f2e3
MS
1705 init: function(variablesDescriptions) {
1706 this._variablesDescriptions = variablesDescriptions;
1707
e866b80e 1708 this._pageConditions = $('#pageConditions');
0360f61b 1709 this._pageInputs = $('input[name="pageIDs[]"]');
e866b80e 1710
1949f2e3
MS
1711 this._variablesDescriptionsList = $('#ad').next('small').children('ul');
1712
098aa5ee 1713 this._pageSelectionContainer = $(this._pageInputs[0]).parents('dl:eq(0)');
fff8c61a 1714
1c2e3dd9 1715 // hide the page controller elements
098aa5ee
MS
1716 this._hidePageSelection(true);
1717
1718 $('#objectTypeID').on('change', $.proxy(this._setPageController, this));
1719
1720 this._setPageController();
1721
1722 $('#adForm').submit($.proxy(this._submit, this));
1723 },
1724
1725 /**
1726 * Hides the page selection form field.
1727 *
1728 * @since 5.2
1729 */
1730 _hidePageSelection: function(addEventListeners) {
1731 this._pageSelectionContainer.prev('dl').hide();
1732 this._pageSelectionContainer.hide();
1733
1734 // fix the margin of a potentially next page condition element
1735 this._pageSelectionContainer.next('dl').css('margin-top', 0);
fff8c61a 1736
098aa5ee 1737 var section = this._pageSelectionContainer.parent('section');
0360f61b
MS
1738 if (!section.children('dl:visible').length) {
1739 section.hide();
098aa5ee
MS
1740
1741 var nextSection = section.next('section');
1742 if (nextSection) {
1743 nextSection.css('margin-top', 0);
1744
1745 if (addEventListeners) {
1746 require(['EventHandler'], function(EventHandler) {
1747 EventHandler.add('com.woltlab.wcf.pageConditionDependence', 'checkVisivility', function() {
1748 if (section.is(':visible')) {
1749 nextSection.css('margin-top', '40px');
1750 }
1751 else {
1752 nextSection.css('margin-top', 0);
1753 }
1754 });
1755 });
1756 }
1757 }
fff8c61a 1758 }
098aa5ee
MS
1759 },
1760
1761 /**
1762 * Shows the page selection form field.
1763 *
1764 * @since 5.2
1765 */
1766 _showPageSelection: function() {
1767 this._pageSelectionContainer.prev('dl').show();
1768 this._pageSelectionContainer.show();
1769 this._pageSelectionContainer.next('dl').css('margin-top', '40px');
1770
1771 var section = this._pageSelectionContainer.parent('section');
1772 section.show();
fff8c61a 1773
0360f61b
MS
1774 var nextSection = section.next('section');
1775 if (nextSection) {
098aa5ee 1776 nextSection.css('margin-top', '40px');
fff8c61a 1777 }
e866b80e
MS
1778 },
1779
1780 /**
1781 * Sets the page controller based on the selected ad location.
1782 */
1783 _setPageController: function() {
0360f61b 1784 var option = $('#objectTypeID').find('option:checked');
098aa5ee 1785 var parent = option.parent();
e866b80e 1786
098aa5ee
MS
1787 // the page controller can be explicitly set for global positions
1788 if (parent.is('optgroup') && parent.data('categoryName') === 'com.woltlab.wcf.global') {
1789 this._showPageSelection();
1790 }
1791 else {
1792 this._hidePageSelection();
0360f61b 1793
098aa5ee
MS
1794 require(['Core'], function(Core) {
1795 var input, triggerEvent;
0360f61b 1796
098aa5ee
MS
1797 // select the related page
1798 for (var i = 0, length = this._pageInputs.length; i < length; i++) {
1799 input = this._pageInputs[i];
1800 triggerEvent = false;
0360f61b 1801
098aa5ee
MS
1802 if (option.data('page') && elData(input, 'identifier') === option.data('page')) {
1803 if (!input.checked) triggerEvent = true;
1804
1805 input.checked = true;
1806 }
1807 else {
1808 if (input.checked) triggerEvent = true;
1809
1810 input.checked = false;
1811 }
0360f61b 1812
098aa5ee 1813 if (triggerEvent) Core.triggerEvent(this._pageInputs[i], 'change');
0360f61b 1814 }
098aa5ee
MS
1815 }.bind(this));
1816 }
1949f2e3
MS
1817
1818 this._variablesDescriptionsList.children(':not(.jsDefaultItem)').remove();
1819
1820 var objectTypeId = $('#objectTypeID').val();
1821 if (objectTypeId in this._variablesDescriptions) {
1822 this._variablesDescriptionsList[0].innerHTML += this._variablesDescriptions[objectTypeId];
1823 }
e866b80e
MS
1824 },
1825
1826 /**
1827 * Handles submitting the ad form.
1828 */
1829 _submit: function() {
1830 if (this._pageConditions.is(':hidden')) {
1831 // remove hidden page condition form elements to avoid creation
1832 // of these conditions
1833 this._pageConditions.find('select, input').remove();
1834 }
098aa5ee 1835 else if (this._pageSelectionContainer.is(':hidden')) {
e866b80e
MS
1836 // reset page controller conditions to avoid creation of
1837 // unnecessary conditions
0360f61b
MS
1838 for (var i = 0, length = this._pageInputs.length; i < length; i++) {
1839 this._pageInputs[i].checked = false;
1840 }
e866b80e
MS
1841 }
1842 }
1843});
ea3185a0
MS
1844
1845/**
1846 * Initialize WCF.ACP.Tag namespace.
1847 */
1848WCF.ACP.Tag = { };
1849
1850/**
1851 * Handles setting tags as synonyms of another tag by clipboard.
1852 */
1853WCF.ACP.Tag.SetAsSynonymsHandler = Class.extend({
1854 /**
1855 * dialog to select the "main" tag
1856 * @var jQuery
1857 */
1858 _dialog: null,
1859
1860 /**
1861 * ids of the selected tags
1862 * @var array<integer>
1863 */
1864 _objectIDs: [ ],
1865
1866 /**
1867 * Initializes the SetAsSynonymsHandler object.
ea3185a0
MS
1868 */
1869 init: function() {
f8d9e485
MS
1870 require(['EventHandler'], function(EventHandler) {
1871 EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.tag', this._clipboardAction.bind(this));
1872 }.bind(this));
ea3185a0
MS
1873 },
1874
1875 /**
f8d9e485
MS
1876 * Reacts to executed clipboard actions.
1877 *
1878 * @param {object<string, *>} actionData data of the executed clipboard action
ea3185a0 1879 */
f8d9e485
MS
1880 _clipboardAction: function(actionData) {
1881 if (actionData.data.actionName === 'com.woltlab.wcf.tag.setAsSynonyms') {
1882 this._objectIDs = actionData.data.parameters.objectIDs;
1883 if (this._dialog === null) {
1884 this._dialog = $('<div id="setAsSynonymsDialog" />').hide().appendTo(document.body);
1885 this._dialog.wcfDialog({
1886 closable: false,
1887 title: WCF.Language.get('wcf.acp.tag.setAsSynonyms')
1888 });
1889 }
1890
1891 this._dialog.html(actionData.data.parameters.template);
1892 $button = this._dialog.find('button[data-type="submit"]').disable().click($.proxy(this._submit, this));
1893 this._dialog.find('input[type=radio]').change(function() { $button.enable(); });
ea3185a0 1894 }
ea3185a0
MS
1895 },
1896
1897 /**
1898 * Saves the tags as synonyms.
1899 */
1900 _submit: function() {
1901 new WCF.Action.Proxy({
1902 autoSend: true,
1903 data: {
1904 actionName: 'setAsSynonyms',
1905 className: 'wcf\\data\\tag\\TagAction',
1906 objectIDs: this._objectIDs,
1907 parameters: {
1908 tagID: this._dialog.find('input[name="tagID"]:checked').val()
1909 }
1910 },
1911 success: $.proxy(function() {
1912 this._dialog.wcfDialog('close');
1913
1914 new WCF.System.Notification().show(function() {
1915 window.location.reload();
1916 });
1917 }, this)
1918 });
1919 }
1920});