Merge branch '3.0'
[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
c839bd49 5 * @copyright 2001-2018 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
0acbb6be
AE
170 /**
171 * true, if rollbacks are supported
172 * @var boolean
173 */
174 _allowRollback: false,
175
e5b88a7f 176 /**
cb9e9c12
AE
177 * dialog object
178 * @var jQuery
158bd3ca
TD
179 */
180 _dialog: null,
181
6207cc21
MS
182 /**
183 * name of the language item with the title of the dialog
184 * @var string
185 */
186 _dialogTitle: '',
187
e5b88a7f
AE
188 /**
189 * action proxy
190 * @var WCF.Action.Proxy
191 */
192 _proxy: null,
9f959ced 193
158bd3ca 194 /**
cb9e9c12 195 * package installation queue id
158bd3ca
TD
196 * @var integer
197 */
198 _queueID: 0,
9f959ced 199
ba133309
AE
200 /**
201 * true, if dialog should be rendered again
202 * @var boolean
203 */
204 _shouldRender: false,
205
0a3949d4 206 /**
cb9e9c12 207 * Initializes the WCF.ACP.Package.Installation class.
158bd3ca 208 *
158bd3ca 209 * @param integer queueID
2ef79140 210 * @param string actionName
0acbb6be 211 * @param boolean allowRollback
adf9791d 212 * @param boolean isUpdate
158bd3ca 213 */
adf9791d 214 init: function(queueID, actionName, allowRollback, isUpdate) {
2ef79140 215 this._actionName = (actionName) ? actionName : 'InstallPackage';
9d9aede4 216 this._allowRollback = (allowRollback === true);
158bd3ca 217 this._queueID = queueID;
cb9e9c12 218
3484b0ce 219 switch (this._actionName) {
824d9e90 220 case 'InstallPackage':
328111b8 221 this._dialogTitle = 'wcf.acp.package.' + (isUpdate ? 'update' : 'install') + '.title';
824d9e90
AE
222 break;
223
224 case 'UninstallPackage':
225 this._dialogTitle = 'wcf.acp.package.uninstallation.title';
226 break;
6207cc21
MS
227 }
228
0acbb6be
AE
229 this._initProxy();
230 this._init();
231 },
232
233 /**
234 * Initializes the WCF.Action.Proxy object.
235 */
236 _initProxy: function() {
e31cf611
AE
237 var $actionName = '';
238 var $parts = this._actionName.split(/([A-Z][a-z0-9]+)/);
239 for (var $i = 0, $length = $parts.length; $i < $length; $i++) {
240 var $part = $parts[$i];
241 if ($part.length) {
242 if ($actionName.length) $actionName += '-';
243 $actionName += $part.toLowerCase();
244 }
245 }
246
e5b88a7f 247 this._proxy = new WCF.Action.Proxy({
cb9e9c12 248 failure: $.proxy(this._failure, this),
324e8301 249 showLoadingOverlay: false,
cb9e9c12 250 success: $.proxy(this._success, this),
772f616f 251 url: 'index.php?' + $actionName + '/&t=' + SECURITY_TOKEN
e5b88a7f 252 });
cb9e9c12
AE
253 },
254
255 /**
256 * Initializes the package installation.
257 */
258 _init: function() {
b32a906e 259 $('#submitButton').click($.proxy(this.prepareInstallation, this));
cb9e9c12
AE
260 },
261
262 /**
263 * Handles erroneous AJAX requests.
cb9e9c12 264 */
014819c4 265 _failure: function() {
e0a0fe54 266 if (this._dialog !== null) {
5df9b2ef 267 $('#packageInstallationProgress').removeAttr('value');
962f1164 268 this._setIcon('times');
e0a0fe54 269 }
e098d212 270
0acbb6be
AE
271 if (!this._allowRollback) {
272 return;
273 }
274
7289c8d3
MS
275 if (this._dialog !== null) {
276 this._purgeTemplateContent($.proxy(function() {
277 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
278 $('<button class="buttonPrimary">' + WCF.Language.get('wcf.acp.package.installation.rollback') + '</button>').appendTo($form).click($.proxy(this._rollback, this));
279
280 $('#packageInstallationInnerContentContainer').show();
281
282 this._dialog.wcfDialog('render');
283 }, this));
284 }
0acbb6be
AE
285 },
286
287 /**
288 * Performs a rollback.
7fcd3ad5
AE
289 *
290 * @param object event
0acbb6be 291 */
7fcd3ad5 292 _rollback: function(event) {
e098d212
AE
293 this._setIcon('spinner');
294
7fcd3ad5
AE
295 if (event) {
296 $(event.currentTarget).disable();
297 }
298
0acbb6be 299 this._executeStep('rollback');
158bd3ca
TD
300 },
301
302 /**
303 * Prepares installation dialog.
304 */
b32a906e 305 prepareInstallation: function() {
639e46d6
AE
306 if (document.activeElement) {
307 document.activeElement.blur();
308 }
309
4da866ad
AE
310 this._proxy.setOption('data', this._getParameters());
311 this._proxy.sendRequest();
158bd3ca
TD
312 },
313
314 /**
cb9e9c12
AE
315 * Returns parameters to prepare installation.
316 *
317 * @return object
318 */
319 _getParameters: function() {
320 return {
321 queueID: this._queueID,
322 step: 'prepare'
323 };
324 },
325
326 /**
327 * Handles successful AJAX requests.
e5b88a7f
AE
328 *
329 * @param object data
330 * @param string textStatus
331 * @param jQuery jqXHR
158bd3ca 332 */
cb9e9c12 333 _success: function(data, textStatus, jqXHR) {
ba133309 334 this._shouldRender = false;
cb9e9c12 335
4da866ad 336 if (this._dialog === null) {
ac86f5f7 337 this._dialog = $('<div id="package' + (this._actionName === 'UninstallPackage' ? 'Uni' : 'I') + 'nstallationDialog" />').hide().appendTo(document.body);
4da866ad
AE
338 this._dialog.wcfDialog({
339 closable: false,
6207cc21 340 title: WCF.Language.get(this._dialogTitle)
4da866ad
AE
341 });
342 }
343
e098d212
AE
344 this._setIcon('spinner');
345
0acbb6be
AE
346 if (data.step == 'rollback') {
347 this._dialog.wcfDialog('close');
e9add3b2 348 this._dialog.remove();
0acbb6be
AE
349
350 new WCF.PeriodicalExecuter(function(pe) {
351 pe.stop();
352
353 var $uninstallation = new WCF.ACP.Package.Uninstallation();
354 $uninstallation.start(data.packageID);
355 }, 200);
356
357 return;
358 }
359
158bd3ca 360 // receive new queue id
e5b88a7f
AE
361 if (data.queueID) {
362 this._queueID = data.queueID;
158bd3ca
TD
363 }
364
47d85da7
AE
365 // update template
366 if (data.template && !data.ignoreTemplate) {
367 this._dialog.html(data.template);
368 this._shouldRender = true;
369 }
370
158bd3ca 371 // update progress
e5b88a7f
AE
372 if (data.progress) {
373 $('#packageInstallationProgress').attr('value', data.progress).text(data.progress + '%');
374 $('#packageInstallationProgressLabel').text(data.progress + '%');
e70175d6 375 }
9f959ced 376
e70175d6 377 // update action
e5b88a7f
AE
378 if (data.currentAction) {
379 $('#packageInstallationAction').html(data.currentAction);
158bd3ca
TD
380 }
381
382 // handle success
cb9e9c12 383 if (data.step === 'success') {
962f1164 384 this._setIcon('check');
e098d212 385
0a3949d4 386 this._purgeTemplateContent($.proxy(function() {
cb9e9c12 387 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
45f1e6f9 388 var $button = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($form).click(function() {
7fcd3ad5
AE
389 $(this).disable();
390 window.location = data.redirectLocation;
391 });
3c87a1b1 392
0a3949d4 393 $('#packageInstallationInnerContentContainer').show();
cb9e9c12 394
45f1e6f9
AE
395 $(document).keydown(function(event) {
396 if (event.which === $.ui.keyCode.ENTER) {
397 $button.trigger('click');
398 }
399 });
400
cb9e9c12 401 this._dialog.wcfDialog('render');
0a3949d4 402 }, this));
158bd3ca
TD
403
404 return;
405 }
406
158bd3ca 407 // handle inner template
e5b88a7f 408 if (data.innerTemplate) {
599a39ba 409 var self = this;
599a39ba 410 $('#packageInstallationInnerContent').html(data.innerTemplate).find('input').keyup(function(event) {
45f1e6f9 411 if (event.keyCode === $.ui.keyCode.ENTER) {
b32a906e 412 self._submit(data);
599a39ba
AE
413 }
414 });
158bd3ca
TD
415
416 // create button to handle next step
e5b88a7f 417 if (data.step && data.node) {
5df9b2ef 418 $('#packageInstallationProgress').removeAttr('value');
e098d212
AE
419 this._setIcon('question');
420
cb9e9c12 421 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
45f1e6f9 422 $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($form).click($.proxy(function(event) {
7fcd3ad5
AE
423 $(event.currentTarget).disable();
424
425 this._submit(data);
9db0c330 426 }, this));
158bd3ca
TD
427 }
428
0a3949d4 429 $('#packageInstallationInnerContentContainer').show();
158bd3ca 430
cb9e9c12 431 this._dialog.wcfDialog('render');
158bd3ca
TD
432 return;
433 }
434
435 // purge content
3c87a1b1 436 this._purgeTemplateContent($.proxy(function() {
e5b88a7f 437 // render container
ba133309 438 if (this._shouldRender) {
cb9e9c12 439 this._dialog.wcfDialog('render');
0a3949d4 440 }
5e6179f1 441
3c87a1b1 442 // execute next step
e5b88a7f
AE
443 if (data.step && data.node) {
444 this._executeStep(data.step, data.node);
3c87a1b1
AE
445 }
446 }, this));
447 },
448
599a39ba
AE
449 /**
450 * Submits the dialog content.
451 *
452 * @param object data
453 */
b32a906e 454 _submit: function(data) {
e098d212
AE
455 this._setIcon('spinner');
456
599a39ba 457 // collect form values
cb6e5946 458 var $additionalData = { };
599a39ba
AE
459 $('#packageInstallationInnerContent input').each(function(index, inputElement) {
460 var $inputElement = $(inputElement);
461 var $type = $inputElement.attr('type');
462
3bbe83f0
MS
463 if (($type != 'checkbox' && $type != 'radio') || $inputElement.prop('checked')) {
464 var $name = $inputElement.attr('name');
465 if ($name.match(/(.*)\[([^[]*)\]$/)) {
466 $name = RegExp.$1;
467 $key = RegExp.$2;
468
469 if ($additionalData[$name] === undefined) {
470 if ($key) {
471 $additionalData[$name] = { };
472 }
473 else {
474 $additionalData[$name] = [ ];
475 }
476 }
477
c001b457 478 if ($key) {
3bbe83f0 479 $additionalData[$name][$key] = $inputElement.val();
c001b457
AE
480 }
481 else {
3bbe83f0 482 $additionalData[$name].push($inputElement.val());
c001b457
AE
483 }
484 }
c001b457 485 else {
3bbe83f0 486 $additionalData[$name] = $inputElement.val();
c001b457
AE
487 }
488 }
599a39ba
AE
489 });
490
491 this._executeStep(data.step, data.node, $additionalData);
492 },
493
3c87a1b1
AE
494 /**
495 * Purges template content.
496 *
497 * @param function callback
498 */
499 _purgeTemplateContent: function(callback) {
f027ba61
AE
500 if ($('#packageInstallationInnerContent').children().length) {
501 $('#packageInstallationInnerContentContainer').hide();
502 $('#packageInstallationInnerContent').empty();
503
504 this._shouldRender = true;
158bd3ca 505 }
f027ba61
AE
506
507 callback();
158bd3ca
TD
508 },
509
510 /**
511 * Executes the next installation step.
512 *
513 * @param string step
514 * @param string node
515 * @param object additionalData
516 */
517 _executeStep: function(step, node, additionalData) {
cb6e5946 518 if (!additionalData) additionalData = { };
158bd3ca
TD
519
520 var $data = $.extend({
a698c064
AE
521 node: node,
522 queueID: this._queueID,
523 step: step
158bd3ca 524 }, additionalData);
9f959ced 525
e5b88a7f
AE
526 this._proxy.setOption('data', $data);
527 this._proxy.sendRequest();
e098d212
AE
528 },
529
e0a0fe54
MS
530 /**
531 * Sets the icon with the given name as the current installation status icon.
532 *
533 * @param string iconName
534 */
e098d212 535 _setIcon: function(iconName) {
47e76c45 536 this._dialog.find('.jsPackageInstallationStatus').removeClass('fa-check fa-question fa-times fa-spinner').addClass('fa-' + iconName);
158bd3ca 537 }
cb9e9c12 538});
158bd3ca 539
a45fa84d
MS
540/**
541 * Handles canceling the package installation at the package installation
542 * confirm page.
543 */
544WCF.ACP.Package.Installation.Cancel = Class.extend({
545 /**
546 * Creates a new instance of WCF.ACP.Package.Installation.Cancel.
547 *
548 * @param integer queueID
549 */
550 init: function(queueID) {
551 $('#backButton').click(function() {
552 new WCF.Action.Proxy({
553 autoSend: true,
554 data: {
555 actionName: 'cancelInstallation',
556 className: 'wcf\\data\\package\\installation\\queue\\PackageInstallationQueueAction',
557 objectIDs: [ queueID ]
558 },
559 success: function(data) {
560 window.location = data.returnValues.url;
561 }
562 });
563 });
564 }
565});
566
158bd3ca 567/**
cb9e9c12 568 * Provides the package uninstallation.
158bd3ca
TD
569 *
570 * @param jQuery elements
74622428 571 * @param string wcfPackageListURL
158bd3ca 572 */
cb9e9c12 573WCF.ACP.Package.Uninstallation = WCF.ACP.Package.Installation.extend({
158bd3ca 574 /**
cb9e9c12
AE
575 * list of uninstallation buttons
576 * @var jQuery
158bd3ca 577 */
cb9e9c12 578 _elements: null,
158bd3ca
TD
579
580 /**
cb9e9c12
AE
581 * current package id
582 * @var integer
583 */
584 _packageID: 0,
585
74622428
AE
586 /**
587 * URL of WCF package list
588 * @var string
589 */
590 _wcfPackageListURL: '',
591
cb9e9c12
AE
592 /**
593 * Initializes the WCF.ACP.Package.Uninstallation class.
158bd3ca
TD
594 *
595 * @param jQuery elements
74622428 596 * @param string wcfPackageListURL
158bd3ca 597 */
74622428 598 init: function(elements, wcfPackageListURL) {
cb9e9c12
AE
599 this._elements = elements;
600 this._packageID = 0;
74622428 601 this._wcfPackageListURL = wcfPackageListURL;
158bd3ca 602
0acbb6be 603 if (this._elements !== undefined && this._elements.length) {
2ef79140 604 this._super(0, 'UninstallPackage');
cb9e9c12 605 }
158bd3ca
TD
606 },
607
0acbb6be
AE
608 /**
609 * Begins a package uninstallation without user action.
610 *
611 * @param integer packageID
612 */
613 start: function(packageID) {
614 this._actionName = 'UninstallPackage';
615 this._packageID = packageID;
616 this._queueID = 0;
6207cc21 617 this._dialogTitle = 'wcf.acp.package.uninstallation.title';
0acbb6be
AE
618
619 this._initProxy();
620 this.prepareInstallation();
621 },
622
158bd3ca 623 /**
cb9e9c12
AE
624 * @see WCF.ACP.Package.Installation.init()
625 */
626 _init: function() {
e3be0700 627 this._elements.click($.proxy(this._showConfirmationDialog, this));
e852aa82
AE
628 },
629
630 /**
631 * Displays a confirmation dialog prior to package uninstallation.
632 *
cec91f01 633 * @param object event
e852aa82 634 */
cec91f01 635 _showConfirmationDialog: function(event) {
e3be0700
AE
636 var $element = $(event.currentTarget);
637
638 if ($element.data('isApplication') && this._wcfPackageListURL) {
639 window.location = WCF.String.unescapeHTML(this._wcfPackageListURL.replace(/{packageID}/, $element.data('objectID')));
74622428
AE
640 return;
641 }
642
cb9e9c12 643 var self = this;
e3be0700 644 WCF.System.Confirmation.show($element.data('confirmMessage'), function(action) {
cb9e9c12 645 if (action === 'confirm') {
e3be0700 646 self._packageID = $element.data('objectID');
b32a906e 647 self.prepareInstallation();
cb9e9c12 648 }
23e43ac5 649 }, undefined, undefined, true);
29b533c8
AE
650 },
651
652 /**
cb9e9c12 653 * @see WCF.ACP.Package.Installation._getParameters()
29b533c8 654 */
cb9e9c12
AE
655 _getParameters: function() {
656 return {
657 packageID: this._packageID,
658 step: 'prepare'
659 };
158bd3ca 660 }
cb9e9c12 661});
158bd3ca 662
25c2c382
AE
663/**
664 * Manages package search.
665 */
666WCF.ACP.Package.Search = Class.extend({
667 /**
668 * search button
669 * @var jQuery
670 */
671 _button: null,
672
673 /**
674 * list of cached pages
675 * @var object
676 */
677 _cache: { },
678
679 /**
680 * search container
681 * @var jQuery
682 */
683 _container: null,
684
996eb650
AE
685 /**
686 * dialog overlay
687 * @var jQuery
688 */
689 _dialog: null,
690
25c2c382
AE
691 /**
692 * package input field
693 * @var jQuery
694 */
695 _package: null,
696
697 /**
698 * package description input field
699 * @var jQuery
700 */
701 _packageDescription: null,
702
703 /**
704 * package name input field
705 * @var jQuery
706 */
707 _packageName: null,
708
709 /**
710 * package search result container
711 * @var jQuery
712 */
713 _packageSearchResultContainer: null,
714
715 /**
716 * package search result list
717 * @var jQuery
718 */
719 _packageSearchResultList: null,
720
721 /**
722 * number of pages
723 * @var integer
724 */
725 _pageCount: 0,
726
727 /**
728 * current page
729 * @var integer
730 */
731 _pageNo: 1,
732
733 /**
734 * action proxy
735 * @var WCF.Action:proxy
736 */
737 _proxy: null,
738
739 /**
740 * search id
741 * @var integer
742 */
743 _searchID: 0,
744
996eb650
AE
745 /**
746 * currently selected package
747 * @var string
748 */
749 _selectedPackage: '',
750
751 /**
752 * currently selected package's version
753 */
754 _selectedPackageVersion: '',
755
25c2c382 756 /**
1615fc2e 757 * Initializes the WCF.ACP.Package.Search class.
25c2c382
AE
758 */
759 init: function() {
760 this._button = null;
761 this._cache = { };
762 this._container = $('#packageSearch');
996eb650 763 this._dialog = null;
25c2c382
AE
764 this._package = null;
765 this._packageName = null;
766 this._packageSearchResultContainer = $('#packageSearchResultContainer');
767 this._packageSearchResultList = $('#packageSearchResultList');
768 this._pageCount = 0;
769 this._pageNo = 1;
770 this._searchDescription = null;
771 this._searchID = 0;
996eb650
AE
772 this._selectedPackage = '';
773 this._selectedPackageVersion = '';
25c2c382
AE
774
775 this._proxy = new WCF.Action.Proxy({
776 success: $.proxy(this._success, this)
777 });
778
779 this._initElements();
780 },
781
782 /**
783 * Initializes search elements.
784 */
785 _initElements: function() {
786 this._button = this._container.find('.formSubmit > button.jsButtonPackageSearch').disable().click($.proxy(this._search, this));
787
788 this._package = $('#package').keyup($.proxy(this._keyUp, this));
789 this._packageDescription = $('#packageDescription').keyup($.proxy(this._keyUp, this));
790 this._packageName = $('#packageName').keyup($.proxy(this._keyUp, this));
791 },
792
793 /**
794 * Handles the 'keyup' event.
795 */
796 _keyUp: function(event) {
797 if (this._package.val() === '' && this._packageDescription.val() === '' && this._packageName.val() === '') {
798 this._button.disable();
799 }
800 else {
801 this._button.enable();
802
803 // submit on [Enter]
804 if (event.which === 13) {
805 this._button.trigger('click');
806 }
807 }
808 },
809
810 /**
811 * Performs a new search.
812 */
813 _search: function() {
814 var $values = this._getSearchValues();
815 if (!this._validate($values)) {
816 return false;
817 }
818
819 $values.pageNo = this._pageNo;
820 this._proxy.setOption('data', {
821 actionName: 'search',
822 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
823 parameters: $values
824 });
825 this._proxy.sendRequest();
826 },
827
828 /**
829 * Returns search values.
830 *
831 * @return object
832 */
833 _getSearchValues: function() {
834 return {
835 'package': $.trim(this._package.val()),
836 packageDescription: $.trim(this._packageDescription.val()),
837 packageName: $.trim(this._packageName.val())
838 };
839 },
840
841 /**
842 * Validates search values.
843 *
844 * @param object values
845 * @return boolean
846 */
847 _validate: function(values) {
848 if (values['package'] === '' && values['packageDescription'] === '' && values['packageName'] === '') {
849 return false;
850 }
851
852 return true;
853 },
854
855 /**
856 * Handles successful AJAX requests.
857 *
858 * @param object data
859 * @param string textStatus
860 * @param jQuery jqXHR
861 */
862 _success: function(data, textStatus, jqXHR) {
863 switch (data.actionName) {
864 case 'getResultList':
865 this._insertTemplate(data.returnValues.template);
866 break;
867
996eb650
AE
868 case 'prepareInstallation':
869 if (data.returnValues.queueID) {
870 if (this._dialog !== null) {
871 this._dialog.wcfDialog('close');
872 }
873
874 var $installation = new WCF.ACP.Package.Installation(data.returnValues.queueID, undefined, false);
875 $installation.prepareInstallation();
876 }
877 else if (data.returnValues.template) {
878 if (this._dialog === null) {
879 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
880 this._dialog.wcfDialog({
881 title: WCF.Language.get('wcf.acp.package.update.unauthorized')
882 });
883 }
884 else {
885 this._dialog.html(data.returnValues.template).wcfDialog('open');
886 }
887
888 this._dialog.find('.formSubmit > button').click($.proxy(this._submitAuthentication, this));
889 }
890 break;
891
25c2c382
AE
892 case 'search':
893 this._pageCount = data.returnValues.pageCount;
894 this._searchID = data.returnValues.searchID;
895
896 this._insertTemplate(data.returnValues.template, (data.returnValues.count === undefined ? undefined : data.returnValues.count));
897 this._setupPagination();
898 break;
899 }
900 },
901
996eb650
AE
902 /**
903 * Submits authentication data for current update server.
904 *
905 * @param object event
906 */
907 _submitAuthentication: function(event) {
908 var $usernameField = $('#packageUpdateServerUsername');
909 var $passwordField = $('#packageUpdateServerPassword');
910
911 // remove error messages if any
912 $usernameField.next('small.innerError').remove();
913 $passwordField.next('small.innerError').remove();
914
915 var $continue = true;
916 if ($.trim($usernameField.val()) === '') {
917 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField);
918 $continue = false;
919 }
920
921 if ($.trim($passwordField.val()) === '') {
922 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField);
923 $continue = false;
924 }
925
926 if ($continue) {
927 this._prepareInstallation($(event.currentTarget).data('packageUpdateServerID'));
928 }
929 },
930
25c2c382
AE
931 /**
932 * Inserts search result list template.
933 *
934 * @param string template
935 * @param integer count
936 */
937 _insertTemplate: function(template, count) {
938 this._packageSearchResultContainer.show();
939
940 this._packageSearchResultList.html(template);
941 if (count === undefined) {
942 this._content[this._pageNo] = template;
943 }
944
945 // update badge count
946 if (count !== undefined) {
947 this._content = { 1: template };
939fce69 948 this._packageSearchResultContainer.find('h2.sectionTitle > .badge').html(count);
996eb650
AE
949 }
950
951 // bind listener
952 this._packageSearchResultList.find('.jsInstallPackage').click($.proxy(this._click, this));
953 },
954
955 /**
956 * Prepares a package installation.
957 *
958 * @param object event
959 */
960 _click: function(event) {
961 var $button = $(event.currentTarget);
962 WCF.System.Confirmation.show($button.data('confirmMessage'), $.proxy(function(action) {
963 if (action === 'confirm') {
964 this._selectedPackage = $button.data('package');
965 this._selectedPackageVersion = $button.data('packageVersion');
966 this._prepareInstallation();
967 }
23e43ac5 968 }, this), undefined, undefined, true);
996eb650
AE
969 },
970
971 /**
972 * Prepares package installation.
973 *
974 * @param integer packageUpdateServerID
975 */
976 _prepareInstallation: function(packageUpdateServerID) {
977 var $parameters = {
02662193 978 'packages': { }
996eb650 979 };
02662193 980 $parameters['packages'][this._selectedPackage] = this._selectedPackageVersion;
996eb650
AE
981
982 if (packageUpdateServerID) {
983 $parameters.authData = {
984 packageUpdateServerID: packageUpdateServerID,
985 password: $.trim($('#packageUpdateServerPassword').val()),
986 saveCredentials: ($('#packageUpdateServerSaveCredentials:checked').length ? true : false),
987 username: $.trim($('#packageUpdateServerUsername').val())
988 };
25c2c382 989 }
996eb650
AE
990
991 this._proxy.setOption('data', {
992 actionName: 'prepareInstallation',
993 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
994 parameters: $parameters
995 });
996 this._proxy.sendRequest();
25c2c382
AE
997 },
998
999 /**
1000 * Setups pagination for current search.
1001 */
1002 _setupPagination: function() {
1003 // remove previous instances
1004 this._content = { 1: this._packageSearchResultList.html() };
abe07e47 1005 this._packageSearchResultContainer.find('.pagination').wcfPages('destroy').remove();
25c2c382
AE
1006
1007 if (this._pageCount > 1) {
126c2518
MW
1008 var $contentFooter = $('<footer class="contentFooter"><div class="paginationBottom"><nav /></div></footer>').insertAfter(this._packageSearchResultList);
1009 $contentFooter.find('nav').wcfPages({
25c2c382
AE
1010 activePage: this._pageNo,
1011 maxPage: this._pageCount
d62d0d94 1012 }).on('wcfpagesswitched', $.proxy(this._showPage, this));
25c2c382
AE
1013 }
1014 },
1015
1016 /**
1017 * Displays requested pages or loads it.
1018 *
1019 * @param object event
1020 * @param object data
1021 */
1022 _showPage: function(event, data) {
1023 if (data && data.activePage) {
1024 this._pageNo = data.activePage;
1025 }
1026
1027 // validate page no
1028 if (this._pageNo < 1 || this._pageNo > this._pageCount) {
1029 console.debug("[WCF.ACP.Package.Search] Cannot access page " + this._pageNo + " of " + this._pageCount);
1030 return;
1031 }
1032
1033 // load content
1034 if (this._content[this._pageNo] === undefined) {
1035 this._proxy.setOption('data', {
1036 actionName: 'getResultList',
1037 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1038 parameters: {
1039 pageNo: this._pageNo,
1040 searchID: this._searchID
1041 }
1042 });
1043 this._proxy.sendRequest();
1044 }
1045 else {
25c2c382
AE
1046 // show cached content
1047 this._packageSearchResultList.html(this._content[this._pageNo]);
1048
42d7d2cc 1049 WCF.DOMNodeInsertedHandler.execute();
25c2c382
AE
1050 }
1051 }
1052});
1053
66ed9925 1054WCF.ACP.Package.Server = { };
298a7cd8
AE
1055
1056WCF.ACP.Package.Server.Installation = Class.extend({
1057 _proxy: null,
1058 _selectedPackage: '',
1059
1060 init: function() {
1061 this._dialog = null;
1062 this._selectedPackage = null;
1063
1064 this._proxy = new WCF.Action.Proxy({
1065 success: $.proxy(this._success, this)
1066 });
1067 },
1068
1069 bind: function() {
1070 $('.jsButtonPackageInstall').removeClass('jsButtonPackageInstall').click($.proxy(this._click, this));
1071 },
1072
1073 /**
1074 * Prepares a package installation.
1075 *
1076 * @param object event
1077 */
1078 _click: function(event) {
1079 var $button = $(event.currentTarget);
1080 WCF.System.Confirmation.show($button.data('confirmMessage'), $.proxy(function(action) {
1081 if (action === 'confirm') {
1082 this._selectedPackage = $button.data('package');
1083 this._selectedPackageVersion = $button.data('packageVersion');
1084 this._prepareInstallation();
1085 }
23e43ac5 1086 }, this), undefined, undefined, true);
298a7cd8
AE
1087 },
1088
1089 /**
1090 * Handles successful AJAX requests.
1091 *
1092 * @param object data
1093 */
1094 _success: function(data) {
1095 if (data.returnValues.queueID) {
1096 if (this._dialog !== null) {
1097 this._dialog.wcfDialog('close');
1098 }
1099
1100 var $installation = new WCF.ACP.Package.Installation(data.returnValues.queueID, undefined, false);
1101 $installation.prepareInstallation();
1102 }
1103 else if (data.returnValues.template) {
1104 if (this._dialog === null) {
1105 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
1106 this._dialog.wcfDialog({
1107 title: WCF.Language.get('wcf.acp.package.update.unauthorized')
1108 });
1109 }
1110 else {
1111 this._dialog.html(data.returnValues.template).wcfDialog('open');
1112 }
1113
1114 this._dialog.find('.formSubmit > button').click($.proxy(this._submitAuthentication, this));
1115 }
1116 },
1117
1118 /**
1119 * Submits authentication data for current update server.
1120 *
1121 * @param object event
1122 */
1123 _submitAuthentication: function(event) {
1124 var $usernameField = $('#packageUpdateServerUsername');
1125 var $passwordField = $('#packageUpdateServerPassword');
1126
1127 // remove error messages if any
1128 $usernameField.next('small.innerError').remove();
1129 $passwordField.next('small.innerError').remove();
1130
1131 var $continue = true;
1132 if ($.trim($usernameField.val()) === '') {
1133 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField);
1134 $continue = false;
1135 }
1136
1137 if ($.trim($passwordField.val()) === '') {
1138 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField);
1139 $continue = false;
1140 }
1141
1142 if ($continue) {
1143 this._prepareInstallation($(event.currentTarget).data('packageUpdateServerID'));
1144 }
1145 },
1146
1147 /**
1148 * Prepares package installation.
1149 *
1150 * @param integer packageUpdateServerID
1151 */
1152 _prepareInstallation: function(packageUpdateServerID) {
1153 var $parameters = {
1154 'packages': { }
1155 };
1156 $parameters['packages'][this._selectedPackage] = this._selectedPackageVersion;
1157
1158 if (packageUpdateServerID) {
1159 $parameters.authData = {
1160 packageUpdateServerID: packageUpdateServerID,
1161 password: $.trim($('#packageUpdateServerPassword').val()),
1162 saveCredentials: ($('#packageUpdateServerSaveCredentials:checked').length ? true : false),
1163 username: $.trim($('#packageUpdateServerUsername').val())
1164 };
1165 }
1166
1167 this._proxy.setOption('data', {
1168 actionName: 'prepareInstallation',
1169 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1170 parameters: $parameters
1171 });
1172 this._proxy.sendRequest();
1173 },
2061c822 1174});
298a7cd8 1175
3536d2fe
AE
1176/**
1177 * Namespace for package update related classes.
1178 */
1179WCF.ACP.Package.Update = { };
1180
1181/**
1182 * Handles the package update process.
1183 */
1184WCF.ACP.Package.Update.Manager = Class.extend({
1185 /**
1186 * dialog overlay
1187 * @var jQuery
1188 */
1189 _dialog: null,
1190
1191 /**
1192 * action proxy
1193 * @var WCF.Action.Proxy
1194 */
1195 _proxy: null,
1196
1197 /**
1198 * submit button
1199 * @var jQuery
1200 */
1201 _submitButton: null,
1202
1203 /**
1204 * Initializes the WCF.ACP.Package.Update.Manager class.
1205 */
1206 init: function() {
1207 this._dialog = null;
1208 this._submitButton = $('.formSubmit > button').click($.proxy(this._click, this));
1209
1210 this._proxy = new WCF.Action.Proxy({
1211 success: $.proxy(this._success, this)
1212 });
1213
1214 $('.jsPackageUpdate').each($.proxy(function(index, packageUpdate) {
1215 var $packageUpdate = $(packageUpdate);
1216 $packageUpdate.find('input[type=checkbox]').data('packageUpdate', $packageUpdate).change($.proxy(this._change, this));
1217 }, this));
1218 },
1219
1220 /**
1221 * Handles toggles for a specific update.
1222 */
1223 _change: function(event) {
1224 var $checkbox = $(event.currentTarget);
1225
1226 if ($checkbox.is(':checked')) {
1227 $checkbox.data('packageUpdate').find('select').enable();
1228 $checkbox.data('packageUpdate').find('dl').removeClass('disabled');
1229
1230 this._submitButton.enable();
1231 }
1232 else {
1233 $checkbox.data('packageUpdate').find('select').disable();
1234 $checkbox.data('packageUpdate').find('dl').addClass('disabled');
1235
1236 // disable submit button
1237 if (!$('input[type=checkbox]:checked').length) {
1238 this._submitButton.disable();
1239 }
2061c822
AE
1240 else {
1241 this._submitButton.enable();
1242 }
3536d2fe
AE
1243 }
1244 },
1245
1246 /**
1247 * Handles clicks on the submit button.
1248 *
1249 * @param object event
1250 * @param integer packageUpdateServerID
1251 */
1252 _click: function(event, packageUpdateServerID) {
1253 var $packages = { };
1254 $('.jsPackageUpdate').each($.proxy(function(index, packageUpdate) {
1255 var $packageUpdate = $(packageUpdate);
1256 if ($packageUpdate.find('input[type=checkbox]:checked').length) {
1257 $packages[$packageUpdate.data('package')] = $packageUpdate.find('select').val();
1258 }
1259 }, this));
1260
1261 if ($.getLength($packages)) {
839984a4
MS
1262 this._submitButton.disable();
1263
3536d2fe
AE
1264 var $parameters = {
1265 packages: $packages
1266 };
1267 if (packageUpdateServerID) {
1268 $parameters.authData = {
1269 packageUpdateServerID: packageUpdateServerID,
1270 password: $.trim($('#packageUpdateServerPassword').val()),
1271 saveCredentials: ($('#packageUpdateServerSaveCredentials:checked').length ? true : false),
1272 username: $.trim($('#packageUpdateServerUsername').val())
996eb650 1273 };
3536d2fe
AE
1274 }
1275
1276 this._proxy.setOption('data', {
1277 actionName: 'prepareUpdate',
1278 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1279 parameters: $parameters,
1280 });
1281 this._proxy.sendRequest();
1282 }
1283 },
1284
1285 /**
1286 * Handles successful AJAX requests.
1287 *
1288 * @param object data
1289 * @param string textStatus
1290 * @param jQuery jqXHR
1291 */
1292 _success: function(data, textStatus, jqXHR) {
1293 if (data.returnValues.queueID) {
1294 if (this._dialog !== null) {
1295 this._dialog.wcfDialog('close');
1296 }
1297
adf9791d 1298 var $installation = new WCF.ACP.Package.Installation(data.returnValues.queueID, undefined, false, true);
3536d2fe
AE
1299 $installation.prepareInstallation();
1300 }
1d7f1d20
AE
1301 else if (data.returnValues.excludedPackages) {
1302 if (this._dialog === null) {
1303 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
1304 this._dialog.wcfDialog({
1305 title: WCF.Language.get('wcf.acp.package.update.excludedPackages')
1306 });
1307 }
1308 else {
1309 this._dialog.wcfDialog('option', 'title', WCF.Language.get('wcf.acp.package.update.excludedPackages'));
1310 this._dialog.html(data.returnValues.template).wcfDialog('open');
1311 }
1312
1313 this._submitButton.enable();
1314 }
3536d2fe
AE
1315 else if (data.returnValues.template) {
1316 if (this._dialog === null) {
1317 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
1318 this._dialog.wcfDialog({
1319 title: WCF.Language.get('wcf.acp.package.update.unauthorized')
1320 });
1321 }
1322 else {
1d7f1d20 1323 this._dialog.wcfDialog('option', 'title', WCF.Language.get('wcf.acp.package.update.unauthorized'));
3536d2fe
AE
1324 this._dialog.html(data.returnValues.template).wcfDialog('open');
1325 }
1326
1327 this._dialog.find('.formSubmit > button').click($.proxy(this._submitAuthentication, this));
1328 }
1329 },
1330
1331 /**
1332 * Submits authentication data for current update server.
1333 *
1334 * @param object event
1335 */
1336 _submitAuthentication: function(event) {
1337 var $usernameField = $('#packageUpdateServerUsername');
1338 var $passwordField = $('#packageUpdateServerPassword');
1339
1340 // remove error messages if any
1341 $usernameField.next('small.innerError').remove();
1342 $passwordField.next('small.innerError').remove();
1343
1344 var $continue = true;
1345 if ($.trim($usernameField.val()) === '') {
1346 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField);
1347 $continue = false;
1348 }
1349
1350 if ($.trim($passwordField.val()) === '') {
1351 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField);
1352 $continue = false;
1353 }
1354
1355 if ($continue) {
22508797 1356 this._click(undefined, $(event.currentTarget).data('packageUpdateServerID'));
3536d2fe
AE
1357 }
1358 }
1359});
1360
1361/**
1362 * Searches for available updates.
60fe64f6
AE
1363 *
1364 * @param boolean bindOnExistingButtons
3536d2fe
AE
1365 */
1366WCF.ACP.Package.Update.Search = Class.extend({
1367 /**
1368 * dialog overlay
1369 * @var jQuery
1370 */
1371 _dialog: null,
1372
1373 /**
60fe64f6
AE
1374 * Initializes the WCF.ACP.Package.SearchForUpdates class.
1375 *
1376 * @param boolean bindOnExistingButtons
3536d2fe 1377 */
60fe64f6 1378 init: function(bindOnExistingButtons) {
3536d2fe
AE
1379 this._dialog = null;
1380
60fe64f6
AE
1381 if (bindOnExistingButtons === true) {
1382 $('.jsButtonPackageUpdate').click($.proxy(this._click, this));
1383 }
1384 else {
f2b50825 1385 var $button = $('<li><a class="button"><span class="icon icon16 fa-refresh"></span> <span>' + WCF.Language.get('wcf.acp.package.searchForUpdates') + '</span></a></li>');
549b92e7 1386 $button.click(this._click.bind(this)).prependTo($('.contentHeaderNavigation > ul'));
60fe64f6 1387 }
3536d2fe
AE
1388 },
1389
1390 /**
1391 * Handles clicks on the search button.
1392 */
1393 _click: function() {
1394 if (this._dialog === null) {
1395 new WCF.Action.Proxy({
1396 autoSend: true,
1397 data: {
1398 actionName: 'searchForUpdates',
70e6f4e6
AE
1399 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1400 parameters: {
1401 ignoreCache: 1
1402 }
3536d2fe
AE
1403 },
1404 success: $.proxy(this._success, this)
1405 });
1406 }
1407 else {
1408 this._dialog.wcfDialog('open');
1409 }
1410 },
1411
1412 /**
1413 * Handles successful AJAX requests.
1414 *
1415 * @param object data
1416 * @param string textStatus
1417 * @param jQuery jqXHR
1418 */
1419 _success: function(data, textStatus, jqXHR) {
1420 if (data.returnValues.url) {
1421 window.location = data.returnValues.url;
1422 }
1423 else {
1424 this._dialog = $('<div>' + WCF.Language.get('wcf.acp.package.searchForUpdates.noResults') + '</div>').hide().appendTo(document.body);
1425 this._dialog.wcfDialog({
1426 title: WCF.Language.get('wcf.acp.package.searchForUpdates')
1427 });
1428 }
1429 }
1430});
1431
317c8af5
AE
1432/**
1433 * Namespace for classes related to the WoltLab Plugin-Store.
1434 */
1435WCF.ACP.PluginStore = { };
1436
1437/**
1438 * Namespace for classes handling items purchased in the WoltLab Plugin-Store.
1439 */
1440WCF.ACP.PluginStore.PurchasedItems = { };
1441
1442/**
1443 * Searches for purchased items available for install but not yet installed.
1444 */
1445WCF.ACP.PluginStore.PurchasedItems.Search = Class.extend({
4087dcf3
AE
1446 /**
1447 * dialog overlay
1448 * @var jQuery
1449 */
317c8af5 1450 _dialog: null,
4087dcf3
AE
1451
1452 /**
1453 * action proxy
1454 * @var WCF.Action.Proxy
1455 */
317c8af5 1456 _proxy: null,
317c8af5 1457
4087dcf3
AE
1458 /**
1459 * Initializes the WCF.ACP.PluginStore.PurchasedItems.Search class.
1460 */
d1813e77 1461 init: function() {
317c8af5
AE
1462 this._dialog = null;
1463 this._proxy = new WCF.Action.Proxy({
1464 success: $.proxy(this._success, this)
1465 });
317c8af5 1466
d528766d 1467 var $button = $('<li><a class="button"><span class="icon icon16 fa-shopping-cart" /> <span>' + WCF.Language.get('wcf.acp.pluginStore.purchasedItems.button.search') + '</span></a></li>');
8e61f344 1468 $button.prependTo($('.contentHeaderNavigation > ul')).click($.proxy(this._click, this));
317c8af5
AE
1469 },
1470
4087dcf3
AE
1471 /**
1472 * Handles clicks on the search button.
1473 */
317c8af5
AE
1474 _click: function() {
1475 this._proxy.setOption('data', {
1476 actionName: 'searchForPurchasedItems',
d1813e77 1477 className: 'wcf\\data\\package\\PackageAction'
317c8af5
AE
1478 });
1479 this._proxy.sendRequest();
1480 },
1481
4087dcf3
AE
1482 /**
1483 * Handles successful AJAX requests.
1484 *
1485 * @param object data
1486 * @param string textStatus
1487 * @param jQuery jqXHR
1488 */
317c8af5 1489 _success: function(data, textStatus, jqXHR) {
4087dcf3 1490 // prompt for credentials
317c8af5
AE
1491 if (data.returnValues.template) {
1492 if (this._dialog === null) {
1493 this._dialog = $('<div />').hide().appendTo(document.body);
1494 this._dialog.html(data.returnValues.template).wcfDialog({
d528766d 1495 title: WCF.Language.get('wcf.acp.pluginStore.authorization')
317c8af5
AE
1496 });
1497 }
1498 else {
1499 this._dialog.html(data.returnValues.template);
1500 this._dialog.wcfDialog('open');
1501 }
1502
298a7cd8
AE
1503 var $button = this._dialog.find('button').click($.proxy(this._submit, this));
1504 this._dialog.find('input').keyup(function(event) {
1505 if (event.which == $.ui.keyCode.ENTER) {
1506 $button.trigger('click');
1507 return false;
1508 }
1509 });
317c8af5
AE
1510 }
1511 else if (data.returnValues.noResults) {
4087dcf3 1512 // there are no purchased products yet
74cde074 1513 if (this._dialog === null) {
1514 this._dialog = $('<div />').hide().appendTo(document.body);
1515 this._dialog.html(data.returnValues.noResults).wcfDialog({
1516 title: WCF.Language.get('wcf.acp.pluginStore.purchasedItems')
1517 });
1518 } else {
1519 this._dialog.wcfDialog('option', 'title', WCF.Language.get('wcf.acp.pluginStore.purchasedItems'));
1520 this._dialog.html(data.returnValues.noResults);
1521 this._dialog.wcfDialog('open');
1522 }
317c8af5 1523 }
93c9c717
AE
1524 else if (data.returnValues.noSSL) {
1525 // PHP was compiled w/o OpenSSL support
1526 if (this._dialog === null) {
1527 this._dialog = $('<div />').hide().appendTo(document.body);
1528 this._dialog.html(data.returnValues.noSSL).wcfDialog({
1529 title: WCF.Language.get('wcf.global.error.title')
1530 });
1531 }
1532 else {
1533 this._dialog.wcfDialog('option', 'title', WCF.Language.get('wcf.global.error.title'));
1534 this._dialog.html(data.returnValues.noSSL);
1535 this._dialog.wcfDialog('open');
1536 }
1537 }
298a7cd8 1538 else if (data.returnValues.redirectURL) {
4087dcf3 1539 // redirect to list of purchased products
298a7cd8
AE
1540 window.location = data.returnValues.redirectURL;
1541 }
317c8af5
AE
1542 },
1543
4087dcf3
AE
1544 /**
1545 * Submits the user credentials.
1546 */
317c8af5
AE
1547 _submit: function() {
1548 this._dialog.wcfDialog('close');
1549
1550 this._proxy.setOption('data', {
1551 actionName: 'searchForPurchasedItems',
1552 className: 'wcf\\data\\package\\PackageAction',
1553 parameters: {
1554 password: $('#pluginStorePassword').val(),
d1813e77 1555 username: $('#pluginStoreUsername').val()
317c8af5
AE
1556 }
1557 });
1558 this._proxy.sendRequest();
1559 }
1560});
1561
532f1173
AE
1562/**
1563 * Worker support for ACP.
1564 *
1565 * @param string dialogID
1566 * @param string className
0cb0c522
AE
1567 * @param string title
1568 * @param object parameters
1569 * @param object callback
835354e5
AE
1570 *
1571 * @deprecated 3.1 - please use `WoltLabSuite/Core/Acp/Ui/Worker` instead
532f1173 1572 */
1f9100ba 1573WCF.ACP.Worker = Class.extend({
532f1173
AE
1574 /**
1575 * Initializes a new worker instance.
1576 *
1577 * @param string dialogID
1578 * @param string className
2cea2b5b
AE
1579 * @param string title
1580 * @param object parameters
0cb0c522 1581 * @param object callback
532f1173 1582 */
0cb0c522 1583 init: function(dialogID, className, title, parameters, callback) {
835354e5
AE
1584 if (typeof callback === 'function') {
1585 throw new Error("The callback parameter is no longer supported, please migrate to 'WoltLabSuite/Core/Acp/Ui/Worker'.");
532f1173
AE
1586 }
1587
835354e5
AE
1588 require(['WoltLabSuite/Core/Acp/Ui/Worker'], function(AcpUiWorker) {
1589 new AcpUiWorker({
1590 // dialog
1591 dialogId: dialogID,
1592 dialogTitle: title,
1593
1594 // ajax
1595 className: className,
1596 parameters: parameters
532f1173 1597 });
835354e5 1598 });
532f1173 1599 }
1f9100ba 1600});
13d8b49b
MS
1601
1602/**
1603 * Namespace for category-related functions.
1604 */
cb6e5946 1605WCF.ACP.Category = { };
13d8b49b
MS
1606
1607/**
1608 * Handles collapsing categories.
1609 *
1610 * @param string className
1611 * @param integer objectTypeID
1612 */
1613WCF.ACP.Category.Collapsible = WCF.Collapsible.SimpleRemote.extend({
1614 /**
1615 * @see WCF.Collapsible.Remote.init()
1616 */
fc3d134b 1617 init: function(className) {
13d8b49b
MS
1618 var sortButton = $('.formSubmit > button[data-type="submit"]');
1619 if (sortButton) {
1620 sortButton.click($.proxy(this._sort, this));
1621 }
1622
1623 this._super(className);
1624 },
9f959ced 1625
13d8b49b
MS
1626 /**
1627 * @see WCF.Collapsible.Remote._getButtonContainer()
1628 */
1629 _getButtonContainer: function(containerID) {
1630 return $('#' + containerID + ' > .buttons');
1631 },
9f959ced 1632
13d8b49b
MS
1633 /**
1634 * @see WCF.Collapsible.Remote._getContainers()
1635 */
1636 _getContainers: function() {
1637 return $('.jsCategory').has('ol').has('li');
1638 },
9f959ced 1639
13d8b49b
MS
1640 /**
1641 * @see WCF.Collapsible.Remote._getTarget()
1642 */
1643 _getTarget: function(containerID) {
1644 return $('#' + containerID + ' > ol');
1645 },
1646
1647 /**
1648 * Handles a click on the sort button.
1649 */
1650 _sort: function() {
1651 // remove existing collapsible buttons
1652 $('.collapsibleButton').remove();
1653
1654 // reinit containers
cb6e5946
MS
1655 this._containers = { };
1656 this._containerData = { };
13d8b49b
MS
1657
1658 var $containers = this._getContainers();
1659 if ($containers.length == 0) {
a0b60d23 1660 console.debug('[WCF.ACP.Category.Collapsible] Empty container set given, aborting.');
13d8b49b
MS
1661 }
1662 $containers.each($.proxy(function(index, container) {
1663 var $container = $(container);
1664 var $containerID = $container.wcfIdentify();
1665 this._containers[$containerID] = $container;
1666
1667 this._initContainer($containerID);
1668 }, this));
1669 }
1670});
1671
71662ae8
AE
1672/**
1673 * Provides the search dropdown for ACP
1674 *
1675 * @see WCF.Search.Base
1676 */
1677WCF.ACP.Search = WCF.Search.Base.extend({
c8f7b2a1
MW
1678 /**
1679 * name of the selected search provider
1680 * @var string
1681 */
1682 _providerName: '',
1683
71662ae8
AE
1684 /**
1685 * @see WCF.Search.Base.init()
1686 */
1687 init: function() {
1688 this._className = 'wcf\\data\\acp\\search\\provider\\ACPSearchProviderAction';
6476e7a1 1689 this._super('#pageHeaderSearch input[name=q]');
d6760827
MS
1690
1691 // disable form submitting
6476e7a1 1692 $('#pageHeaderSearch > form').on('submit', function(event) {
d6760827
MS
1693 event.preventDefault();
1694 });
c8f7b2a1
MW
1695
1696 var $dropdown = WCF.Dropdown.getDropdownMenu('pageHeaderSearchType');
1697 $dropdown.find('a[data-provider-name]').on('click', $.proxy(function(event) {
1698 event.preventDefault();
1699 var $button = $(event.target);
1700 $('.pageHeaderSearchType > .button').text($button.text());
1701
1702 var $oldProviderName = this._providerName;
1703 this._providerName = ($button.data('providerName') != 'everywhere' ? $button.data('providerName') : '');
1704
1705 if ($oldProviderName != this._providerName) {
1706 var $searchString = $.trim(this._searchInput.val());
1707 if ($searchString) {
1708 var $parameters = {
1709 data: {
1710 excludedSearchValues: this._excludedSearchValues,
1711 searchString: $searchString
1712 }
1713 };
1714 this._queryServer($parameters);
1715 }
c5dd767e 1716 }
c8f7b2a1 1717 }, this));
71662ae8
AE
1718 },
1719
1720 /**
1721 * @see WCF.Search.Base._createListItem()
1722 */
1723 _createListItem: function(resultList) {
1724 // add a divider between result lists
1725 if (this._list.children('li').length > 0) {
1726 $('<li class="dropdownDivider" />').appendTo(this._list);
1727 }
1728
1729 // add caption
1730 $('<li class="dropdownText">' + resultList.title + '</li>').appendTo(this._list);
1731
1732 // add menu items
1733 for (var $i in resultList.items) {
1734 var $item = resultList.items[$i];
1735
790aadd9 1736 $('<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
1737
1738 this._itemCount++;
71662ae8 1739 }
9a130ab4
AE
1740 },
1741
71c70823
MS
1742 /**
1743 * @see WCF.Search.Base._openDropdown()
1744 */
1745 _openDropdown: function() {
1746 this._list.find('small').each(function(index, element) {
1747 while (element.scrollWidth > element.clientWidth) {
1748 element.innerText = '\u2026 ' + element.innerText.substr(3);
1749 }
1750 });
1751 },
1752
c138555b
AE
1753 /**
1754 * @see WCF.Search.Base._handleEmptyResult()
1755 */
1756 _handleEmptyResult: function() {
1757 $('<li class="dropdownText">' + WCF.Language.get('wcf.acp.search.noResults') + '</li>').appendTo(this._list);
1758
1759 return true;
1760 },
1761
9a130ab4
AE
1762 /**
1763 * @see WCF.Search.Base._highlightSelectedElement()
1764 */
1765 _highlightSelectedElement: function() {
1766 this._list.find('li').removeClass('dropdownNavigationItem');
1767 this._list.find('li:not(.dropdownDivider):not(.dropdownText)').eq(this._itemIndex).addClass('dropdownNavigationItem');
1768 },
1769
1770 /**
1771 * @see WCF.Search.Base._selectElement()
1772 */
1773 _selectElement: function(event) {
c138555b
AE
1774 if (this._itemIndex === -1) {
1775 return false;
1776 }
1777
9a130ab4 1778 window.location = this._list.find('li.dropdownNavigationItem > a').attr('href');
6476e7a1
AE
1779 },
1780
1781 _success: function(data) {
1782 this._super(data);
1783
1784 this._list.addClass('acpSearchDropdown');
c8f7b2a1
MW
1785 },
1786
1787 /**
1788 * @see WCF.Search.Base._getParameters()
1789 */
1790 _getParameters: function(parameters) {
1791 parameters.data.providerName = this._providerName;
1792
1793 return parameters;
71662ae8
AE
1794 }
1795});
11cf19be
MW
1796
1797/**
1798 * Namespace for user management.
1799 */
1800WCF.ACP.User = { };
1801
1802/**
1803 * Generic implementation to ban users.
1804 */
1805WCF.ACP.User.BanHandler = {
1806 /**
1807 * callback object
1808 * @var object
1809 */
1810 _callback: null,
1811
1812 /**
1813 * dialog overlay
1814 * @var jQuery
1815 */
1816 _dialog: null,
1817
1818 /**
1819 * action proxy
1820 * @var WCF.Action.Proxy
1821 */
1822 _proxy: null,
1823
1824 /**
1825 * Initializes WCF.ACP.User.BanHandler on first use.
1826 */
1827 init: function() {
11cf19be
MW
1828 this._proxy = new WCF.Action.Proxy({
1829 success: $.proxy(this._success, this)
1830 });
1831
1832 $('.jsBanButton').click($.proxy(function(event) {
1833 var $button = $(event.currentTarget);
1834 if ($button.data('banned')) {
1835 this.unban([ $button.data('objectID') ]);
1836 }
1837 else {
1838 this.ban([ $button.data('objectID') ]);
1839 }
1840 }, this));
bbef7ed8 1841
efcd047d
MS
1842 require(['EventHandler'], function(EventHandler) {
1843 EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.user', this._clipboardAction.bind(this));
1844 }.bind(this));
bbef7ed8
MW
1845 },
1846
1847 /**
efcd047d
MS
1848 * Reacts to executed clipboard actions.
1849 *
1850 * @param {object<string, *>} actionData data of the executed clipboard action
bbef7ed8 1851 */
efcd047d
MS
1852 _clipboardAction: function(actionData) {
1853 if (actionData.data.actionName === 'com.woltlab.wcf.user.ban') {
1854 this.ban(actionData.data.parameters.objectIDs);
bbef7ed8 1855 }
11cf19be
MW
1856 },
1857
1858 /**
1859 * Unbans users.
1860 *
1861 * @param array<integer> userIDs
1862 */
1863 unban: function(userIDs) {
1864 this._proxy.setOption('data', {
1865 actionName: 'unban',
1866 className: 'wcf\\data\\user\\UserAction',
1867 objectIDs: userIDs
1868 });
1869 this._proxy.sendRequest();
1870 },
1871
1872 /**
1873 * Bans users.
1874 *
1875 * @param array<integer> userIDs
1876 */
1877 ban: function(userIDs) {
f034d0ec
MS
1878 if (this._dialog === null) {
1879 // create dialog
1880 this._dialog = $('<div />').hide().appendTo(document.body);
6f2bc235 1881 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>'));
f034d0ec
MS
1882 this._dialog.append($('<div class="formSubmit"><button class="buttonPrimary" accesskey="s">' + WCF.Language.get('wcf.global.button.submit') + '</button></div>'));
1883
1884 this._dialog.find('#userBanNeverExpires').change(function() {
1885 $('#userBanExpiresSettings').toggle();
1886 });
1887
1888 this._dialog.find('button').click($.proxy(this._submit, this));
1889 }
1890 else {
1891 // reset dialog
1892 $('#userBanReason').val('');
1893 $('#userBanNeverExpires').prop('checked', true);
1894 $('#userBanExpiresSettings').hide();
1895 $('#userBanExpiresDatePicker, #userBanExpires').val('');
1896 }
1897
1898 this._dialog.data('userIDs', userIDs);
1899 this._dialog.wcfDialog({
1900 title: WCF.Language.get('wcf.acp.user.ban.sure')
1901 });
1902 },
1903
1904 /**
1905 * Handles submitting the ban dialog.
1906 */
1907 _submit: function() {
1908 this._dialog.find('.innerError').remove();
1909
1910 var $banExpires = '';
1911 if (!$('#userBanNeverExpires').is(':checked')) {
1912 var $banExpires = $('#userBanExpiresDatePicker').val();
1913 if (!$banExpires) {
1914 this._dialog.find('#userBanExpiresSettings > dd > small').prepend($('<small class="innerError" />').text(WCF.Language.get('wcf.global.form.error.empty')));
1915 return
1916 }
1917 }
1918
1919 this._proxy.setOption('data', {
1920 actionName: 'ban',
1921 className: 'wcf\\data\\user\\UserAction',
1922 objectIDs: this._dialog.data('userIDs'),
1923 parameters: {
1924 banReason: $('#userBanReason').val(),
1925 banExpires: $banExpires
11cf19be 1926 }
f034d0ec
MS
1927 });
1928 this._proxy.sendRequest();
11cf19be
MW
1929 },
1930
1931 /**
1932 * Handles successful AJAX calls.
1933 *
1934 * @param object data
1935 * @param string textStatus
1936 * @param jQuery jqXHR
1937 */
1938 _success: function(data, textStatus, jqXHR) {
1939 $('.jsBanButton').each(function(index, button) {
1940 var $button = $(button);
1941 if (WCF.inArray($button.data('objectID'), data.objectIDs)) {
1942 if (data.actionName == 'unban') {
6783e56b 1943 $button.data('banned', false).attr('data-tooltip', $button.data('banMessage')).removeClass('fa-lock').addClass('fa-unlock');
11cf19be
MW
1944 }
1945 else {
6783e56b 1946 $button.data('banned', true).attr('data-tooltip', $button.data('unbanMessage')).removeClass('fa-unlock').addClass('fa-lock');
11cf19be
MW
1947 }
1948 }
1949 });
1950
1951 var $notification = new WCF.System.Notification();
1952 $notification.show();
bbef7ed8
MW
1953
1954 WCF.Clipboard.reload();
f034d0ec
MS
1955
1956 if (data.actionName == 'ban') {
1957 this._dialog.wcfDialog('close');
1958 }
11cf19be 1959 }
bc3b71fd 1960};
ef012ee1 1961
2a5d6b57
MS
1962/**
1963 * Namespace for user group management.
1964 */
1965WCF.ACP.User.Group = { };
1966
1967/**
1968 * Handles copying user groups.
1969 */
1970WCF.ACP.User.Group.Copy = Class.extend({
1971 /**
1972 * id of the copied group
1973 * @var integer
1974 */
1975 _groupID: 0,
1976
1977 /**
1978 * Initializes a new instance of WCF.ACP.User.Group.Copy.
1979 *
1980 * @param integer groupID
1981 */
1982 init: function(groupID) {
1983 this._groupID = groupID;
1984
1985 $('.jsButtonUserGroupCopy').click($.proxy(this._click, this));
1986 },
1987
1988 /**
1989 * Handles clicking on a 'copy user group' button.
1990 */
1991 _click: function() {
7730f067 1992 var $template = $('<div class="section" />');
95961bdf
MW
1993 $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>'));
1994 $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>'));
1995 $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
1996
1997 WCF.System.Confirmation.show(WCF.Language.get('wcf.acp.group.copy.confirmMessage'), $.proxy(function(action) {
1998 if (action === 'confirm') {
1999 new WCF.Action.Proxy({
2000 autoSend: true,
2001 data: {
2002 actionName: 'copy',
2003 className: 'wcf\\data\\user\\group\\UserGroupAction',
2004 objectIDs: [ this._groupID ],
2005 parameters: {
2006 copyACLOptions: $('#copyACLOptions').is(':checked'),
2007 copyMembers: $('#copyMembers').is(':checked'),
2008 copyUserGroupOptions: $('#copyUserGroupOptions').is(':checked')
2009 }
2010 },
2011 success: function(data) {
2012 window.location = data.returnValues.redirectURL;
2013 }
2014 });
2015 }
23e43ac5 2016 }, this), '', $template, true);
2a5d6b57
MS
2017 }
2018});
2019
ef012ee1
MW
2020/**
2021 * Generic implementation to enable users.
2022 */
2023WCF.ACP.User.EnableHandler = {
2024 /**
2025 * action proxy
2026 * @var WCF.Action.Proxy
2027 */
2028 _proxy: null,
2029
2030 /**
2031 * Initializes WCF.ACP.User.EnableHandler on first use.
2032 */
2033 init: function() {
2034 this._proxy = new WCF.Action.Proxy({
2035 success: $.proxy(this._success, this)
2036 });
2037
2038 $('.jsEnableButton').click($.proxy(function(event) {
2039 var $button = $(event.currentTarget);
2040 if ($button.data('enabled')) {
2041 this.disable([ $button.data('objectID') ]);
2042 }
2043 else {
2044 this.enable([ $button.data('objectID') ]);
2045 }
2046 }, this));
2047
efcd047d
MS
2048 require(['EventHandler'], function(EventHandler) {
2049 EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.user', this._clipboardAction.bind(this));
2050 }.bind(this));
ef012ee1
MW
2051 },
2052
2053 /**
efcd047d
MS
2054 * Reacts to executed clipboard actions.
2055 *
2056 * @param {object<string, *>} actionData data of the executed clipboard action
ef012ee1 2057 */
efcd047d
MS
2058 _clipboardAction: function(actionData) {
2059 if (actionData.data.actionName === 'com.woltlab.wcf.user.enable') {
2060 this.enable(actionData.data.parameters.objectIDs);
ef012ee1
MW
2061 }
2062 },
2063
2064 /**
2065 * Disables users.
2066 *
2067 * @param array<integer> userIDs
2068 */
2069 disable: function(userIDs) {
2070 this._proxy.setOption('data', {
2071 actionName: 'disable',
2072 className: 'wcf\\data\\user\\UserAction',
2073 objectIDs: userIDs
2074 });
2075 this._proxy.sendRequest();
2076 },
2077
2078 /**
2079 * Enables users.
2080 *
2081 * @param array<integer> userIDs
2082 */
2083 enable: function(userIDs) {
2084 this._proxy.setOption('data', {
2085 actionName: 'enable',
2086 className: 'wcf\\data\\user\\UserAction',
2087 objectIDs: userIDs
2088 });
2089 this._proxy.sendRequest();
2090 },
2091
2092 /**
2093 * Handles successful AJAX calls.
2094 *
2095 * @param object data
2096 * @param string textStatus
2097 * @param jQuery jqXHR
2098 */
2099 _success: function(data, textStatus, jqXHR) {
2100 $('.jsEnableButton').each(function(index, button) {
2101 var $button = $(button);
2102 if (WCF.inArray($button.data('objectID'), data.objectIDs)) {
2103 if (data.actionName == 'disable') {
6783e56b 2104 $button.data('enabled', false).attr('data-tooltip', $button.data('enableMessage')).removeClass('fa-check-square-o').addClass('fa-square-o');
ef012ee1
MW
2105 }
2106 else {
6783e56b 2107 $button.data('enabled', true).attr('data-tooltip', $button.data('disableMessage')).removeClass('fa-square-o').addClass('fa-check-square-o');
ef012ee1
MW
2108 }
2109 }
2110 });
2111
2112 var $notification = new WCF.System.Notification();
00ce5cf8 2113 $notification.show(function() { window.location.reload(); });
ef012ee1
MW
2114 }
2115};
2116
cb6e5946
MS
2117/**
2118 * Handles the send new password clipboard action.
2119 */
2120WCF.ACP.User.SendNewPasswordHandler = {
cb6e5946
MS
2121 /**
2122 * Initializes WCF.ACP.User.SendNewPasswordHandler on first use.
2123 */
2124 init: function() {
efcd047d
MS
2125 require(['EventHandler'], function(EventHandler) {
2126 EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.user', this._clipboardAction.bind(this));
2127 }.bind(this));
cb6e5946
MS
2128 },
2129
2130 /**
efcd047d
MS
2131 * Reacts to executed clipboard actions.
2132 *
2133 * @param {object<string, *>} actionData data of the executed clipboard action
cb6e5946 2134 */
efcd047d
MS
2135 _clipboardAction: function(actionData) {
2136 if (actionData.data.actionName === 'com.woltlab.wcf.user.sendNewPassword') {
2137 WCF.System.Confirmation.show(actionData.data.parameters.confirmMessage, function(action) {
cb6e5946
MS
2138 if (action === 'confirm') {
2139 new WCF.ACP.Worker('sendingNewPasswords', 'wcf\\system\\worker\\SendNewPasswordWorker', WCF.Language.get('wcf.acp.user.sendNewPassword.workerTitle'), {
efcd047d 2140 userIDs: actionData.data.parameters.objectIDs
cb6e5946
MS
2141 });
2142 }
2143 });
2144 }
2145 }
2146};
2147
971fbab8
AE
2148/**
2149 * Namespace for import-related classes.
2150 */
2151WCF.ACP.Import = { };
2152
24d15c69
AE
2153/**
2154 * Importer for ACP.
2155 *
971fbab8 2156 * @param array<string> objectTypes
24d15c69 2157 */
971fbab8 2158WCF.ACP.Import.Manager = Class.extend({
24d15c69 2159 /**
971fbab8
AE
2160 * current action
2161 * @var string
24d15c69 2162 */
971fbab8 2163 _currentAction: '',
24d15c69
AE
2164
2165 /**
2166 * dialog overlay
2167 * @var jQuery
2168 */
2169 _dialog: null,
2170
971fbab8
AE
2171 /**
2172 * current object type index
67c2bb1d 2173 * @var int
971fbab8
AE
2174 */
2175 _index: -1,
2176
2177 /**
2178 * list of object types
67c2bb1d 2179 * @var string[]
971fbab8 2180 */
67c2bb1d 2181 _objectTypes: [],
971fbab8 2182
24d15c69
AE
2183 /**
2184 * action proxy
2185 * @var WCF.Action.Proxy
2186 */
2187 _proxy: null,
2188
fed8bce0
AE
2189 /**
2190 * redirect URL
2191 * @var string
2192 */
2193 _redirectURL: '',
2194
24d15c69
AE
2195 /**
2196 * Initializes the WCF.ACP.Importer object.
2197 *
67c2bb1d
AE
2198 * @param {string[]} objectTypes
2199 * @param {string} redirectURL
24d15c69 2200 */
fed8bce0 2201 init: function(objectTypes, redirectURL) {
971fbab8
AE
2202 this._currentAction = '';
2203 this._index = -1;
2204 this._objectTypes = objectTypes;
24d15c69
AE
2205 this._proxy = new WCF.Action.Proxy({
2206 showLoadingOverlay: false,
2207 success: $.proxy(this._success, this),
772f616f 2208 url: 'index.php?worker-proxy/&t=' + SECURITY_TOKEN
24d15c69 2209 });
fed8bce0 2210 this._redirectURL = redirectURL;
971fbab8
AE
2211
2212 this._invoke();
24d15c69
AE
2213 },
2214
2215 /**
2216 * Invokes importing of an object type.
971fbab8
AE
2217 */
2218 _invoke: function() {
2219 this._index++;
2220 if (this._index >= this._objectTypes.length) {
962f1164 2221 this._dialog.find('.fa-spinner').removeClass('fa-spinner').addClass('fa-check');
3bbdfdb0
AE
2222 this._dialog.find('h1').text(WCF.Language.get('wcf.acp.dataImport.completed'));
2223
2224 var $form = $('<div class="formSubmit" />').appendTo(this._dialog.find('#workerContainer'));
2225 $('<button>' + WCF.Language.get('wcf.global.button.next') + '</button>').click($.proxy(function() {
127ee67e
AE
2226 new WCF.Action.Proxy({
2227 autoSend: true,
2228 data: {
2229 noRedirect: 1
2230 },
1d997070 2231 dataType: 'html',
127ee67e
AE
2232 success: $.proxy(function() {
2233 window.location = this._redirectURL;
2234 }, this),
772f616f 2235 url: 'index.php?cache-clear/&t=' + SECURITY_TOKEN
127ee67e 2236 });
3bbdfdb0
AE
2237 }, this)).appendTo($form);
2238
2239 this._dialog.wcfDialog('render');
971fbab8
AE
2240 }
2241 else {
2242 this._run(
607e3941 2243 WCF.Language.get('wcf.acp.dataImport.data.' + this._objectTypes[this._index]),
971fbab8
AE
2244 this._objectTypes[this._index]
2245 );
2246 }
2247 },
2248
2249 /**
2250 * Executes import of given object type.
24d15c69 2251 *
971fbab8 2252 * @param string currentAction
24d15c69
AE
2253 * @param string objectType
2254 */
971fbab8
AE
2255 _run: function(currentAction, objectType) {
2256 this._currentAction = currentAction;
24d15c69
AE
2257 this._proxy.setOption('data', {
2258 className: 'wcf\\system\\worker\\ImportWorker',
2259 parameters: {
2260 objectType: objectType
2261 }
2262 });
2263 this._proxy.sendRequest();
2264 },
2265
2266 /**
2267 * Handles response from server.
2268 *
2269 * @param object data
2270 */
2271 _success: function(data) {
2272 // init binding
2273 if (this._dialog === null) {
2274 this._dialog = $('<div />').hide().appendTo(document.body);
2275 this._dialog.wcfDialog({
2276 closable: false,
2277 title: WCF.Language.get('wcf.acp.dataImport')
2278 });
2279 }
2280
2281 if (data.template) {
2282 this._dialog.html(data.template);
2283 }
2284
971fbab8
AE
2285 if (this._currentAction) {
2286 this._dialog.find('h1').text(this._currentAction);
2287 }
2288
24d15c69
AE
2289 // update progress
2290 this._dialog.find('progress').attr('value', data.progress).text(data.progress + '%').next('span').text(data.progress + '%');
2291
2292 // worker is still busy with it's business, carry on
2293 if (data.progress < 100) {
2294 // send request for next loop
2295 this._proxy.setOption('data', {
2296 className: data.className,
2297 loopCount: data.loopCount,
2298 parameters: data.parameters
2299 });
2300 this._proxy.sendRequest();
2301 }
2302 else {
971fbab8 2303 this._invoke();
24d15c69
AE
2304 }
2305 }
2306});
e1fe9453
MW
2307
2308/**
2309 * Namespace for stat-related classes.
2310 */
2311WCF.ACP.Stat = { };
2312
2313/**
2314 * Shows the daily stat chart.
2315 */
2316WCF.ACP.Stat.Chart = Class.extend({
2317 init: function() {
2318 this._proxy = new WCF.Action.Proxy({
2319 success: $.proxy(this._success, this)
2320 });
2321
49ec07c5 2322 $('#statRefreshButton').click($.proxy(this._refresh, this));
e1fe9453 2323
49ec07c5 2324 this._refresh();
e1fe9453
MW
2325 },
2326
49ec07c5 2327 _refresh: function() {
e1fe9453
MW
2328 var $objectTypeIDs = [ ];
2329 $('input[name=objectTypeID]:checked').each(function() {
2330 $objectTypeIDs.push($(this).val());
2331 });
2332
49ec07c5
MW
2333 if (!$objectTypeIDs.length) return;
2334
e1fe9453
MW
2335 this._proxy.setOption('data', {
2336 className: 'wcf\\data\\stat\\daily\\StatDailyAction',
2337 actionName: 'getData',
2338 parameters: {
2339 startDate: $('#startDateDatePicker').val(),
2340 endDate: $('#endDateDatePicker').val(),
2341 value: $('input[name=value]:checked').val(),
49ec07c5 2342 dateGrouping: $('input[name=dateGrouping]:checked').val(),
e1fe9453
MW
2343 objectTypeIDs: $objectTypeIDs
2344 }
2345 });
2346 this._proxy.sendRequest();
2347 },
2348
2349 _success: function(data) {
49ec07c5
MW
2350 switch ($('input[name=dateGrouping]:checked').val()) {
2351 case 'yearly':
2352 var $minTickSize = [1, "year"];
2353 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.yearly');
2354 break;
2355 case 'monthly':
2356 var $minTickSize = [1, "month"];
2357 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.monthly');
2358 break;
2359 case 'weekly':
2360 var $minTickSize = [7, "day"];
2361 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.weekly');
2362 break;
2363 default:
2364 var $minTickSize = [1, "day"];
2365 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.daily');
2366 }
2367
e1fe9453
MW
2368 var options = {
2369 series: {
2370 lines: {
2371 show: true
2372 },
2373 points: {
2374 show: true
2375 }
2376 },
2377 grid: {
2378 hoverable: true
2379 },
2380 xaxis: {
2381 mode: "time",
49ec07c5
MW
2382 minTickSize: $minTickSize,
2383 timeformat: $timeFormat,
2384 monthNames: WCF.Language.get('__monthsShort')
e1fe9453
MW
2385 },
2386 yaxis: {
2387 min: 0,
2388 tickDecimals: 0,
2389 tickFormatter: function(val) {
2390 return WCF.String.addThousandsSeparator(val);
2391 }
a16e5980 2392 }
e1fe9453
MW
2393 };
2394
2395 var $data = [ ];
2396 for (var $key in data.returnValues) {
2397 var $row = data.returnValues[$key];
2398 for (var $i = 0; $i < $row.data.length; $i++) {
2399 $row.data[$i][0] *= 1000;
2400 }
2401
2402 $data.push($row);
2403 }
2404
2405 $.plot("#chart", $data, options);
2406
a16e5980
MW
2407 require(['Ui/Alignment'], function (UiAlignment) {
2408 var span = elCreate('span');
2409 span.style.setProperty('position', 'absolute', '');
2410 document.body.appendChild(span);
2411 $("#chart").on("plothover", function(event, pos, item) {
2412 if (item) {
2413 span.style.setProperty('top', item.pageY + 'px', '');
2414 span.style.setProperty('left', item.pageX + 'px', '');
2415 $("#chartTooltip").html(item.series.xaxis.tickFormatter(item.datapoint[0], item.series.xaxis) + ', ' + WCF.String.formatNumeric(item.datapoint[1]) + ' ' + item.series.label).show();
2416 UiAlignment.set($("#chartTooltip")[0], span, {
2417 verticalOffset: 5,
2418 horizontal: 'center'
2419 });
2420 }
2421 else {
2422 $("#chartTooltip").hide();
2423 }
2424 });
e1fe9453 2425 });
7652dbb0
MW
2426
2427 if (!$data.length) {
2428 $('#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>');
2429 }
a59f3c51
MW
2430
2431 elBySel('.contentHeader > .contentTitle').scrollIntoView({ behavior: 'smooth' });
e1fe9453 2432 }
e72efaf9 2433});
e866b80e
MS
2434
2435/**
2436 * Namespace for ACP ad management.
2437 */
2438WCF.ACP.Ad = { };
2439
2440/**
2441 * Handles the location of an ad during ad creation/editing.
2442 */
2443WCF.ACP.Ad.LocationHandler = Class.extend({
2444 /**
2445 * fieldset of the page conditions
2446 * @var jQuery
2447 */
2448 _pageConditions: null,
2449
2450 /**
2451 * select element for the page controller condition
0360f61b 2452 * @var jQuery[]
e866b80e 2453 */
0360f61b 2454 _pageInputs: [],
e866b80e
MS
2455
2456 /**
2457 * Initializes a new WCF.ACP.Ad.LocationHandler object.
2458 */
2459 init: function() {
2460 this._pageConditions = $('#pageConditions');
0360f61b 2461 this._pageInputs = $('input[name="pageIDs[]"]');
e866b80e 2462
0360f61b 2463 var dl = $(this._pageInputs[0]).parents('dl:eq(0)');
fff8c61a 2464
e866b80e 2465 // hide the page controller element
0360f61b 2466 dl.hide();
fff8c61a 2467
0360f61b
MS
2468 var section = dl.parent('section');
2469 if (!section.children('dl:visible').length) {
2470 section.hide();
fff8c61a
MS
2471 }
2472
0360f61b
MS
2473 var nextSection = section.next('section');
2474 if (nextSection) {
2475 var marginTop = nextSection.css('margin-top');
2476 nextSection.css('margin-top', 0);
2477
2478 require(['EventHandler'], function(EventHandler) {
2479 EventHandler.add('com.woltlab.wcf.pageConditionDependence', 'checkVisivility', function() {
2480 if (section.is(':visible')) {
2481 nextSection.css('margin-top', marginTop);
2482 }
2483 else {
2484 nextSection.css('margin-top', 0);
2485 }
2486 });
2487 });
fff8c61a 2488 }
e866b80e
MS
2489
2490 // fix the margin of a potentially next page condition element
0360f61b 2491 dl.next('dl').css('margin-top', 0);
e866b80e
MS
2492
2493 $('#objectTypeID').on('change', $.proxy(this._setPageController, this));
2494
2495 this._setPageController();
2496
2497 $('#adForm').submit($.proxy(this._submit, this));
2498 },
2499
2500 /**
2501 * Sets the page controller based on the selected ad location.
2502 */
2503 _setPageController: function() {
0360f61b 2504 var option = $('#objectTypeID').find('option:checked');
e866b80e 2505
0360f61b
MS
2506 require(['Core'], function(Core) {
2507 var input, triggerEvent;
2508
e866b80e 2509 // select the related page
0360f61b
MS
2510 for (var i = 0, length = this._pageInputs.length; i < length; i++) {
2511 input = this._pageInputs[i];
2512 triggerEvent = false;
2513
2514 if (option.data('page') && elData(input, 'identifier') === option.data('page')) {
2515 if (!input.checked) triggerEvent = true;
2516
2517 input.checked = true;
2518 }
2519 else {
2520 if (input.checked) triggerEvent = true;
2521
2522 input.checked = false;
2523 }
2524
2525 if (triggerEvent) Core.triggerEvent(this._pageInputs[i], 'change');
2526 }
2527 }.bind(this));
e866b80e
MS
2528 },
2529
2530 /**
2531 * Handles submitting the ad form.
2532 */
2533 _submit: function() {
2534 if (this._pageConditions.is(':hidden')) {
2535 // remove hidden page condition form elements to avoid creation
2536 // of these conditions
2537 this._pageConditions.find('select, input').remove();
2538 }
2539 else {
2540 // reset page controller conditions to avoid creation of
2541 // unnecessary conditions
0360f61b
MS
2542 for (var i = 0, length = this._pageInputs.length; i < length; i++) {
2543 this._pageInputs[i].checked = false;
2544 }
e866b80e
MS
2545 }
2546 }
2547});
ea3185a0
MS
2548
2549/**
2550 * Initialize WCF.ACP.Tag namespace.
2551 */
2552WCF.ACP.Tag = { };
2553
2554/**
2555 * Handles setting tags as synonyms of another tag by clipboard.
2556 */
2557WCF.ACP.Tag.SetAsSynonymsHandler = Class.extend({
2558 /**
2559 * dialog to select the "main" tag
2560 * @var jQuery
2561 */
2562 _dialog: null,
2563
2564 /**
2565 * ids of the selected tags
2566 * @var array<integer>
2567 */
2568 _objectIDs: [ ],
2569
2570 /**
2571 * Initializes the SetAsSynonymsHandler object.
ea3185a0
MS
2572 */
2573 init: function() {
f8d9e485
MS
2574 require(['EventHandler'], function(EventHandler) {
2575 EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.tag', this._clipboardAction.bind(this));
2576 }.bind(this));
ea3185a0
MS
2577 },
2578
2579 /**
f8d9e485
MS
2580 * Reacts to executed clipboard actions.
2581 *
2582 * @param {object<string, *>} actionData data of the executed clipboard action
ea3185a0 2583 */
f8d9e485
MS
2584 _clipboardAction: function(actionData) {
2585 if (actionData.data.actionName === 'com.woltlab.wcf.tag.setAsSynonyms') {
2586 this._objectIDs = actionData.data.parameters.objectIDs;
2587 if (this._dialog === null) {
2588 this._dialog = $('<div id="setAsSynonymsDialog" />').hide().appendTo(document.body);
2589 this._dialog.wcfDialog({
2590 closable: false,
2591 title: WCF.Language.get('wcf.acp.tag.setAsSynonyms')
2592 });
2593 }
2594
2595 this._dialog.html(actionData.data.parameters.template);
2596 $button = this._dialog.find('button[data-type="submit"]').disable().click($.proxy(this._submit, this));
2597 this._dialog.find('input[type=radio]').change(function() { $button.enable(); });
ea3185a0 2598 }
ea3185a0
MS
2599 },
2600
2601 /**
2602 * Saves the tags as synonyms.
2603 */
2604 _submit: function() {
2605 new WCF.Action.Proxy({
2606 autoSend: true,
2607 data: {
2608 actionName: 'setAsSynonyms',
2609 className: 'wcf\\data\\tag\\TagAction',
2610 objectIDs: this._objectIDs,
2611 parameters: {
2612 tagID: this._dialog.find('input[name="tagID"]:checked').val()
2613 }
2614 },
2615 success: $.proxy(function() {
2616 this._dialog.wcfDialog('close');
2617
2618 new WCF.System.Notification().show(function() {
2619 window.location.reload();
2620 });
2621 }, this)
2622 });
2623 }
2624});