Fixed time zone calculation issue
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / acp / js / WCF.ACP.js
1 /**
2 * Class and function collection for WCF ACP
3 *
4 * @author Alexander Ebert, Matthias Schmidt
5 * @copyright 2001-2014 WoltLab GmbH
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7 */
8
9 /**
10 * Initialize WCF.ACP namespace
11 */
12 WCF.ACP = {};
13
14 /**
15 * Namespace for ACP application management.
16 */
17 WCF.ACP.Application = { };
18
19 /**
20 * Provides the ability to set an application as primary.
21 *
22 * @param integer packageID
23 */
24 WCF.ACP.Application.SetAsPrimary = Class.extend({
25 /**
26 * application package id
27 * @var integer
28 */
29 _packageID: 0,
30
31 /**
32 * Initializes the WCF.ACP.Application.SetAsPrimary class.
33 *
34 * @param integer packageID
35 */
36 init: function(packageID) {
37 this._packageID = packageID;
38
39 $('#setAsPrimary').click($.proxy(this._click, this));
40 },
41
42 /**
43 * Shows a confirmation dialog to set current application as primary.
44 */
45 _click: function() {
46 WCF.System.Confirmation.show(WCF.Language.get('wcf.acp.application.setAsPrimary.confirmMessage'), $.proxy(function(action) {
47 if (action === 'confirm') {
48 this._setAsPrimary();
49 }
50 }, this));
51 },
52
53 /**
54 * Sets an application as primary.
55 */
56 _setAsPrimary: function() {
57 new WCF.Action.Proxy({
58 autoSend: true,
59 data: {
60 actionName: 'setAsPrimary',
61 className: 'wcf\\data\\application\\ApplicationAction',
62 objectIDs: [ this._packageID ]
63 },
64 success: $.proxy(function(data, textStatus, jqXHR) {
65 var $notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'));
66 $notification.show();
67
68 // remove button
69 $('#setAsPrimary').parent().remove();
70
71 // insert icon
72 $headline = $('.boxHeadline > h1');
73 $headline.html($headline.html() + ' ');
74 $('<span class="icon icon16 icon-ok-sign jsTooltip" title="' + WCF.Language.get('wcf.acp.application.primaryApplication') + '" />').appendTo($headline);
75
76 WCF.DOMNodeInsertedHandler.execute();
77 }, this)
78 });
79 }
80 });
81
82 /**
83 * Namespace for ACP cronjob management.
84 */
85 WCF.ACP.Cronjob = { };
86
87 /**
88 * Handles the manual execution of cronjobs.
89 */
90 WCF.ACP.Cronjob.ExecutionHandler = Class.extend({
91 /**
92 * notification object
93 * @var WCF.System.Notification
94 */
95 _notification: null,
96
97 /**
98 * action proxy
99 * @var WCF.Action.Proxy
100 */
101 _proxy: null,
102
103 /**
104 * Initializes WCF.ACP.Cronjob.ExecutionHandler object.
105 */
106 init: function() {
107 this._proxy = new WCF.Action.Proxy({
108 success: $.proxy(this._success, this)
109 });
110
111 $('.jsCronjobRow .jsExecuteButton').click($.proxy(this._click, this));
112
113 this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success');
114 },
115
116 /**
117 * Handles a click on an execute button.
118 *
119 * @param object event
120 */
121 _click: function(event) {
122 this._proxy.setOption('data', {
123 actionName: 'execute',
124 className: 'wcf\\data\\cronjob\\CronjobAction',
125 objectIDs: [ $(event.target).data('objectID') ]
126 });
127
128 this._proxy.sendRequest();
129 },
130
131 /**
132 * Handles successful cronjob execution.
133 *
134 * @param object data
135 * @param string textStatus
136 * @param jQuery jqXHR
137 */
138 _success: function(data, textStatus, jqXHR) {
139 $('.jsCronjobRow').each($.proxy(function(index, row) {
140 var $button = $(row).find('.jsExecuteButton');
141 var $objectID = ($button).data('objectID');
142
143 if (WCF.inArray($objectID, data.objectIDs)) {
144 if (data.returnValues[$objectID]) {
145 // insert feedback here
146 $(row).find('td.columnNextExec').html(data.returnValues[$objectID].formatted);
147 $(row).wcfHighlight();
148 }
149
150 this._notification.show();
151
152 return false;
153 }
154 }, this));
155 }
156 });
157
158 /**
159 * Handles the cronjob log list.
160 */
161 WCF.ACP.Cronjob.LogList = Class.extend({
162 /**
163 * error message dialog
164 * @var jQuery
165 */
166 _dialog: null,
167
168 /**
169 * Initializes WCF.ACP.Cronjob.LogList object.
170 */
171 init: function() {
172 // bind event listener to delete cronjob log button
173 $('.jsCronjobLogDelete').click(function() {
174 WCF.System.Confirmation.show(WCF.Language.get('wcf.acp.cronjob.log.clear.confirm'), function(action) {
175 if (action == 'confirm') {
176 new WCF.Action.Proxy({
177 autoSend: true,
178 data: {
179 actionName: 'clearAll',
180 className: 'wcf\\data\\cronjob\\log\\CronjobLogAction'
181 },
182 success: function() {
183 window.location.reload();
184 }
185 });
186 }
187 });
188 });
189
190 // bind event listeners to error badges
191 $('.jsCronjobError').click($.proxy(this._showError, this));
192 },
193
194 /**
195 * Shows certain error message
196 *
197 * @param object event
198 */
199 _showError: function(event) {
200 var $errorBadge = $(event.currentTarget);
201
202 if (this._dialog === null) {
203 this._dialog = $('<div style="overflow: auto"><pre>' + $errorBadge.next().html() + '</pre></div>').hide().appendTo(document.body);
204 this._dialog.wcfDialog({
205 title: WCF.Language.get('wcf.acp.cronjob.log.error.details')
206 });
207 }
208 else {
209 this._dialog.html('<pre>' + $errorBadge.next().html() + '</pre>');
210 this._dialog.wcfDialog('open');
211 }
212 }
213 });
214
215 /**
216 * Handles ACPMenu.
217 *
218 * @param array<string> activeMenuItems
219 */
220 WCF.ACP.Menu = Class.extend({
221 /**
222 * Initializes ACPMenu.
223 *
224 * @param array activeMenuItems
225 */
226 init: function(activeMenuItems) {
227 this._headerNavigation = $('nav#mainMenu');
228 this._sidebarNavigation = $('aside.collapsibleMenu > div');
229
230 this._prepareElements(activeMenuItems);
231 },
232
233 /**
234 * Resets all elements and binds event listeners.
235 */
236 _prepareElements: function(activeMenuItems) {
237 this._headerNavigation.find('li').removeClass('active');
238
239 this._sidebarNavigation.find('legend').each($.proxy(function(index, menuHeader) {
240 $(menuHeader).click($.proxy(this._toggleItem, this));
241 }, this));
242
243 // close all navigation groups
244 this._sidebarNavigation.find('nav ul').each(function() {
245 $(this).hide();
246 });
247
248 this._headerNavigation.find('li').click($.proxy(this._toggleSidebar, this));
249
250 if (activeMenuItems.length === 0) {
251 this._renderSidebar(this._headerNavigation.find('li:first').data('menuItem'), []);
252 }
253 else {
254 this._renderSidebar('', activeMenuItems);
255 }
256 },
257
258 /**
259 * Toggles a navigation group entry.
260 */
261 _toggleItem: function(event) {
262 var $menuItem = $(event.currentTarget);
263
264 $menuItem.parent().find('nav ul').stop(true, true).toggle('blind', { }, 200).end();
265 $menuItem.toggleClass('active');
266 },
267
268 /**
269 * Handles clicks on main menu.
270 *
271 * @param object event
272 */
273 _toggleSidebar: function(event) {
274 var $target = $(event.currentTarget);
275
276 if ($target.hasClass('active')) {
277 return;
278 }
279
280 this._renderSidebar($target.data('menuItem'), []);
281 },
282
283 /**
284 * Renders sidebar including highlighting of currently active menu items.
285 *
286 * @param string menuItem
287 * @param array activeMenuItems
288 */
289 _renderSidebar: function(menuItem, activeMenuItems) {
290 // reset visible and active items
291 this._headerNavigation.find('li').removeClass('active');
292 this._sidebarNavigation.find('> div').hide();
293
294 if (activeMenuItems.length === 0) {
295 // show active menu
296 this._headerNavigation.find('li[data-menu-item="' + menuItem + '"]').addClass('active');
297 this._sidebarNavigation.find('div[data-parent-menu-item="' + menuItem + '"]').show();
298 }
299 else {
300 // open menu by active menu items, first element is always a head navigation item
301 menuItem = activeMenuItems.shift();
302
303 this._headerNavigation.find('li[data-menu-item="' + menuItem + '"]').addClass('active');
304 this._sidebarNavigation.find('div[data-parent-menu-item="' + menuItem + '"]').show();
305
306 for (var $i = 0, $size = activeMenuItems.length; $i < $size; $i++) {
307 var $item = activeMenuItems[$i];
308
309 if ($.wcfIsset($item)) {
310 var $menuItem = $('#' + $.wcfEscapeID($item));
311
312 if ($menuItem.getTagName() === 'ul') {
313 $menuItem.show().parents('fieldset').children('legend').addClass('active');
314 }
315 else {
316 $menuItem.addClass('active');
317 }
318 }
319 }
320 }
321 }
322 });
323
324 /**
325 * Namespace for ACP package management.
326 */
327 WCF.ACP.Package = { };
328
329 /**
330 * Provides the package installation.
331 *
332 * @param integer queueID
333 * @param string actionName
334 */
335 WCF.ACP.Package.Installation = Class.extend({
336 /**
337 * package installation type
338 * @var string
339 */
340 _actionName: 'InstallPackage',
341
342 /**
343 * true, if rollbacks are supported
344 * @var boolean
345 */
346 _allowRollback: false,
347
348 /**
349 * dialog object
350 * @var jQuery
351 */
352 _dialog: null,
353
354 /**
355 * name of the language item with the title of the dialog
356 * @var string
357 */
358 _dialogTitle: '',
359
360 /**
361 * action proxy
362 * @var WCF.Action.Proxy
363 */
364 _proxy: null,
365
366 /**
367 * package installation queue id
368 * @var integer
369 */
370 _queueID: 0,
371
372 /**
373 * true, if dialog should be rendered again
374 * @var boolean
375 */
376 _shouldRender: false,
377
378 /**
379 * Initializes the WCF.ACP.Package.Installation class.
380 *
381 * @param integer queueID
382 * @param string actionName
383 * @param boolean allowRollback
384 * @param boolean isUpdate
385 */
386 init: function(queueID, actionName, allowRollback, isUpdate) {
387 this._actionName = (actionName) ? actionName : 'InstallPackage';
388 this._allowRollback = (allowRollback === true) ? true : false;
389 this._queueID = queueID;
390
391 switch (this._actionName) {
392 case 'InstallPackage':
393 this._dialogTitle = 'wcf.acp.package.' + (isUpdate ? 'update' : 'install') + '.title';
394 break;
395
396 case 'UninstallPackage':
397 this._dialogTitle = 'wcf.acp.package.uninstallation.title';
398 break;
399 }
400
401 this._initProxy();
402 this._init();
403 },
404
405 /**
406 * Initializes the WCF.Action.Proxy object.
407 */
408 _initProxy: function() {
409 this._proxy = new WCF.Action.Proxy({
410 failure: $.proxy(this._failure, this),
411 showLoadingOverlay: false,
412 success: $.proxy(this._success, this),
413 url: 'index.php/' + this._actionName + '/?t=' + SECURITY_TOKEN + SID_ARG_2ND
414 });
415 },
416
417 /**
418 * Initializes the package installation.
419 */
420 _init: function() {
421 $('#submitButton').click($.proxy(this.prepareInstallation, this));
422 },
423
424 /**
425 * Handles erroneous AJAX requests.
426 */
427 _failure: function() {
428 if (this._dialog !== null) {
429 $('#packageInstallationProgress').removeAttr('value');
430 this._setIcon('remove');
431 }
432
433 if (!this._allowRollback) {
434 return;
435 }
436
437 if (this._dialog !== null) {
438 this._purgeTemplateContent($.proxy(function() {
439 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
440 $('<button class="buttonPrimary">' + WCF.Language.get('wcf.acp.package.installation.rollback') + '</button>').appendTo($form).click($.proxy(this._rollback, this));
441
442 $('#packageInstallationInnerContentContainer').show();
443
444 this._dialog.wcfDialog('render');
445 }, this));
446 }
447 },
448
449 /**
450 * Performs a rollback.
451 *
452 * @param object event
453 */
454 _rollback: function(event) {
455 this._setIcon('spinner');
456
457 if (event) {
458 $(event.currentTarget).disable();
459 }
460
461 this._executeStep('rollback');
462 },
463
464 /**
465 * Prepares installation dialog.
466 */
467 prepareInstallation: function() {
468 this._proxy.setOption('data', this._getParameters());
469 this._proxy.sendRequest();
470 },
471
472 /**
473 * Returns parameters to prepare installation.
474 *
475 * @return object
476 */
477 _getParameters: function() {
478 return {
479 queueID: this._queueID,
480 step: 'prepare'
481 };
482 },
483
484 /**
485 * Handles successful AJAX requests.
486 *
487 * @param object data
488 * @param string textStatus
489 * @param jQuery jqXHR
490 */
491 _success: function(data, textStatus, jqXHR) {
492 this._shouldRender = false;
493
494 if (this._dialog === null) {
495 this._dialog = $('<div id="packageInstallationDialog" />').hide().appendTo(document.body);
496 this._dialog.wcfDialog({
497 closable: false,
498 title: WCF.Language.get(this._dialogTitle)
499 });
500 }
501
502 this._setIcon('spinner');
503
504 if (data.step == 'rollback') {
505 this._dialog.wcfDialog('close');
506 this._dialog.remove();
507
508 new WCF.PeriodicalExecuter(function(pe) {
509 pe.stop();
510
511 var $uninstallation = new WCF.ACP.Package.Uninstallation();
512 $uninstallation.start(data.packageID);
513 }, 200);
514
515 return;
516 }
517
518 // receive new queue id
519 if (data.queueID) {
520 this._queueID = data.queueID;
521 }
522
523 // update template
524 if (data.template && !data.ignoreTemplate) {
525 this._dialog.html(data.template);
526 this._shouldRender = true;
527 }
528
529 // update progress
530 if (data.progress) {
531 $('#packageInstallationProgress').attr('value', data.progress).text(data.progress + '%');
532 $('#packageInstallationProgressLabel').text(data.progress + '%');
533 }
534
535 // update action
536 if (data.currentAction) {
537 $('#packageInstallationAction').html(data.currentAction);
538 }
539
540 // handle success
541 if (data.step === 'success') {
542 this._setIcon('ok');
543
544 this._purgeTemplateContent($.proxy(function() {
545 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
546 var $button = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($form).click(function() {
547 $(this).disable();
548 window.location = data.redirectLocation;
549 });
550
551 $('#packageInstallationInnerContentContainer').show();
552
553 $(document).keydown(function(event) {
554 if (event.which === $.ui.keyCode.ENTER) {
555 $button.trigger('click');
556 }
557 });
558
559 this._dialog.wcfDialog('render');
560 }, this));
561
562 return;
563 }
564
565 // handle inner template
566 if (data.innerTemplate) {
567 var self = this;
568 $('#packageInstallationInnerContent').html(data.innerTemplate).find('input').keyup(function(event) {
569 if (event.keyCode === $.ui.keyCode.ENTER) {
570 self._submit(data);
571 }
572 });
573
574 // create button to handle next step
575 if (data.step && data.node) {
576 $('#packageInstallationProgress').removeAttr('value');
577 this._setIcon('question');
578
579 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
580 $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($form).click($.proxy(function(event) {
581 $(event.currentTarget).disable();
582
583 this._submit(data);
584 }, this));
585 }
586
587 $('#packageInstallationInnerContentContainer').show();
588
589 this._dialog.wcfDialog('render');
590 return;
591 }
592
593 // purge content
594 this._purgeTemplateContent($.proxy(function() {
595 // render container
596 if (this._shouldRender) {
597 this._dialog.wcfDialog('render');
598 }
599
600 // execute next step
601 if (data.step && data.node) {
602 this._executeStep(data.step, data.node);
603 }
604 }, this));
605 },
606
607 /**
608 * Submits the dialog content.
609 *
610 * @param object data
611 */
612 _submit: function(data) {
613 this._setIcon('spinner');
614
615 // collect form values
616 var $additionalData = {};
617 $('#packageInstallationInnerContent input').each(function(index, inputElement) {
618 var $inputElement = $(inputElement);
619 var $type = $inputElement.attr('type');
620
621 if (($type != 'checkbox' && $type != 'radio') || $inputElement.prop('checked')) {
622 var $name = $inputElement.attr('name');
623 if ($name.match(/(.*)\[([^[]*)\]$/)) {
624 $name = RegExp.$1;
625 $key = RegExp.$2;
626
627 if ($additionalData[$name] === undefined) {
628 if ($key) {
629 $additionalData[$name] = { };
630 }
631 else {
632 $additionalData[$name] = [ ];
633 }
634 }
635
636 if ($key) {
637 $additionalData[$name][$key] = $inputElement.val();
638 }
639 else {
640 $additionalData[$name].push($inputElement.val());
641 }
642 }
643 else {
644 $additionalData[$name] = $inputElement.val();
645 }
646 }
647 });
648
649 this._executeStep(data.step, data.node, $additionalData);
650 },
651
652 /**
653 * Purges template content.
654 *
655 * @param function callback
656 */
657 _purgeTemplateContent: function(callback) {
658 if ($('#packageInstallationInnerContent').children().length > 1) {
659 $('#packageInstallationInnerContentContainer').wcfBlindOut('vertical', $.proxy(function() {
660 $('#packageInstallationInnerContent').empty();
661 this._shouldRender = true;
662
663 // execute callback
664 callback();
665 }, this));
666 }
667 else {
668 callback();
669 }
670 },
671
672 /**
673 * Executes the next installation step.
674 *
675 * @param string step
676 * @param string node
677 * @param object additionalData
678 */
679 _executeStep: function(step, node, additionalData) {
680 if (!additionalData) additionalData = {};
681
682 var $data = $.extend({
683 node: node,
684 queueID: this._queueID,
685 step: step
686 }, additionalData);
687
688 this._proxy.setOption('data', $data);
689 this._proxy.sendRequest();
690 },
691
692 /**
693 * Sets the icon with the given name as the current installation status icon.
694 *
695 * @param string iconName
696 */
697 _setIcon: function(iconName) {
698 this._dialog.find('.jsPackageInstallationStatus').removeClass('icon-ok icon-question icon-remove icon-spinner').addClass('icon-' + iconName);
699 }
700 });
701
702 /**
703 * Handles canceling the package installation at the package installation
704 * confirm page.
705 */
706 WCF.ACP.Package.Installation.Cancel = Class.extend({
707 /**
708 * Creates a new instance of WCF.ACP.Package.Installation.Cancel.
709 *
710 * @param integer queueID
711 */
712 init: function(queueID) {
713 $('#backButton').click(function() {
714 new WCF.Action.Proxy({
715 autoSend: true,
716 data: {
717 actionName: 'cancelInstallation',
718 className: 'wcf\\data\\package\\installation\\queue\\PackageInstallationQueueAction',
719 objectIDs: [ queueID ]
720 },
721 success: function(data) {
722 window.location = data.returnValues.url;
723 }
724 });
725 });
726 }
727 });
728
729 /**
730 * Provides the package uninstallation.
731 *
732 * @param jQuery elements
733 * @param string wcfPackageListURL
734 */
735 WCF.ACP.Package.Uninstallation = WCF.ACP.Package.Installation.extend({
736 /**
737 * list of uninstallation buttons
738 * @var jQuery
739 */
740 _elements: null,
741
742 /**
743 * current package id
744 * @var integer
745 */
746 _packageID: 0,
747
748 /**
749 * URL of WCF package list
750 * @var string
751 */
752 _wcfPackageListURL: '',
753
754 /**
755 * Initializes the WCF.ACP.Package.Uninstallation class.
756 *
757 * @param jQuery elements
758 * @param string wcfPackageListURL
759 */
760 init: function(elements, wcfPackageListURL) {
761 this._elements = elements;
762 this._packageID = 0;
763 this._wcfPackageListURL = wcfPackageListURL;
764
765 if (this._elements !== undefined && this._elements.length) {
766 this._super(0, 'UninstallPackage');
767 }
768 },
769
770 /**
771 * Begins a package uninstallation without user action.
772 *
773 * @param integer packageID
774 */
775 start: function(packageID) {
776 this._actionName = 'UninstallPackage';
777 this._packageID = packageID;
778 this._queueID = 0;
779 this._dialogTitle = 'wcf.acp.package.uninstallation.title';
780
781 this._initProxy();
782 this.prepareInstallation();
783 },
784
785 /**
786 * @see WCF.ACP.Package.Installation.init()
787 */
788 _init: function() {
789 this._elements.click($.proxy(this._showConfirmationDialog, this));
790 },
791
792 /**
793 * Displays a confirmation dialog prior to package uninstallation.
794 *
795 * @param object event
796 */
797 _showConfirmationDialog: function(event) {
798 var $element = $(event.currentTarget);
799
800 if ($element.data('isApplication') && this._wcfPackageListURL) {
801 window.location = WCF.String.unescapeHTML(this._wcfPackageListURL.replace(/{packageID}/, $element.data('objectID')));
802 return;
803 }
804
805 var self = this;
806 WCF.System.Confirmation.show($element.data('confirmMessage'), function(action) {
807 if (action === 'confirm') {
808 self._packageID = $element.data('objectID');
809 self.prepareInstallation();
810 }
811 });
812 },
813
814 /**
815 * @see WCF.ACP.Package.Installation._getParameters()
816 */
817 _getParameters: function() {
818 return {
819 packageID: this._packageID,
820 step: 'prepare'
821 };
822 }
823 });
824
825 /**
826 * Manages package search.
827 */
828 WCF.ACP.Package.Search = Class.extend({
829 /**
830 * search button
831 * @var jQuery
832 */
833 _button: null,
834
835 /**
836 * list of cached pages
837 * @var object
838 */
839 _cache: { },
840
841 /**
842 * search container
843 * @var jQuery
844 */
845 _container: null,
846
847 /**
848 * dialog overlay
849 * @var jQuery
850 */
851 _dialog: null,
852
853 /**
854 * package input field
855 * @var jQuery
856 */
857 _package: null,
858
859 /**
860 * package description input field
861 * @var jQuery
862 */
863 _packageDescription: null,
864
865 /**
866 * package name input field
867 * @var jQuery
868 */
869 _packageName: null,
870
871 /**
872 * package search result container
873 * @var jQuery
874 */
875 _packageSearchResultContainer: null,
876
877 /**
878 * package search result list
879 * @var jQuery
880 */
881 _packageSearchResultList: null,
882
883 /**
884 * number of pages
885 * @var integer
886 */
887 _pageCount: 0,
888
889 /**
890 * current page
891 * @var integer
892 */
893 _pageNo: 1,
894
895 /**
896 * action proxy
897 * @var WCF.Action:proxy
898 */
899 _proxy: null,
900
901 /**
902 * search id
903 * @var integer
904 */
905 _searchID: 0,
906
907 /**
908 * currently selected package
909 * @var string
910 */
911 _selectedPackage: '',
912
913 /**
914 * currently selected package's version
915 */
916 _selectedPackageVersion: '',
917
918 /**
919 * Initializes the WCF.ACP.Package.Seach class.
920 */
921 init: function() {
922 this._button = null;
923 this._cache = { };
924 this._container = $('#packageSearch');
925 this._dialog = null;
926 this._package = null;
927 this._packageName = null;
928 this._packageSearchResultContainer = $('#packageSearchResultContainer');
929 this._packageSearchResultList = $('#packageSearchResultList');
930 this._pageCount = 0;
931 this._pageNo = 1;
932 this._searchDescription = null;
933 this._searchID = 0;
934 this._selectedPackage = '';
935 this._selectedPackageVersion = '';
936
937 this._proxy = new WCF.Action.Proxy({
938 success: $.proxy(this._success, this)
939 });
940
941 this._initElements();
942 },
943
944 /**
945 * Initializes search elements.
946 */
947 _initElements: function() {
948 this._button = this._container.find('.formSubmit > button.jsButtonPackageSearch').disable().click($.proxy(this._search, this));
949
950 this._package = $('#package').keyup($.proxy(this._keyUp, this));
951 this._packageDescription = $('#packageDescription').keyup($.proxy(this._keyUp, this));
952 this._packageName = $('#packageName').keyup($.proxy(this._keyUp, this));
953 },
954
955 /**
956 * Handles the 'keyup' event.
957 */
958 _keyUp: function(event) {
959 if (this._package.val() === '' && this._packageDescription.val() === '' && this._packageName.val() === '') {
960 this._button.disable();
961 }
962 else {
963 this._button.enable();
964
965 // submit on [Enter]
966 if (event.which === 13) {
967 this._button.trigger('click');
968 }
969 }
970 },
971
972 /**
973 * Performs a new search.
974 */
975 _search: function() {
976 var $values = this._getSearchValues();
977 if (!this._validate($values)) {
978 return false;
979 }
980
981 $values.pageNo = this._pageNo;
982 this._proxy.setOption('data', {
983 actionName: 'search',
984 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
985 parameters: $values
986 });
987 this._proxy.sendRequest();
988 },
989
990 /**
991 * Returns search values.
992 *
993 * @return object
994 */
995 _getSearchValues: function() {
996 return {
997 'package': $.trim(this._package.val()),
998 packageDescription: $.trim(this._packageDescription.val()),
999 packageName: $.trim(this._packageName.val())
1000 };
1001 },
1002
1003 /**
1004 * Validates search values.
1005 *
1006 * @param object values
1007 * @return boolean
1008 */
1009 _validate: function(values) {
1010 if (values['package'] === '' && values['packageDescription'] === '' && values['packageName'] === '') {
1011 return false;
1012 }
1013
1014 return true;
1015 },
1016
1017 /**
1018 * Handles successful AJAX requests.
1019 *
1020 * @param object data
1021 * @param string textStatus
1022 * @param jQuery jqXHR
1023 */
1024 _success: function(data, textStatus, jqXHR) {
1025 switch (data.actionName) {
1026 case 'getResultList':
1027 this._insertTemplate(data.returnValues.template);
1028 break;
1029
1030 case 'prepareInstallation':
1031 if (data.returnValues.queueID) {
1032 if (this._dialog !== null) {
1033 this._dialog.wcfDialog('close');
1034 }
1035
1036 var $installation = new WCF.ACP.Package.Installation(data.returnValues.queueID, undefined, false);
1037 $installation.prepareInstallation();
1038 }
1039 else if (data.returnValues.template) {
1040 if (this._dialog === null) {
1041 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
1042 this._dialog.wcfDialog({
1043 title: WCF.Language.get('wcf.acp.package.update.unauthorized')
1044 });
1045 }
1046 else {
1047 this._dialog.html(data.returnValues.template).wcfDialog('open');
1048 }
1049
1050 this._dialog.find('.formSubmit > button').click($.proxy(this._submitAuthentication, this));
1051 }
1052 break;
1053
1054 case 'search':
1055 this._pageCount = data.returnValues.pageCount;
1056 this._searchID = data.returnValues.searchID;
1057
1058 this._insertTemplate(data.returnValues.template, (data.returnValues.count === undefined ? undefined : data.returnValues.count));
1059 this._setupPagination();
1060 break;
1061 }
1062 },
1063
1064 /**
1065 * Submits authentication data for current update server.
1066 *
1067 * @param object event
1068 */
1069 _submitAuthentication: function(event) {
1070 var $usernameField = $('#packageUpdateServerUsername');
1071 var $passwordField = $('#packageUpdateServerPassword');
1072
1073 // remove error messages if any
1074 $usernameField.next('small.innerError').remove();
1075 $passwordField.next('small.innerError').remove();
1076
1077 var $continue = true;
1078 if ($.trim($usernameField.val()) === '') {
1079 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField);
1080 $continue = false;
1081 }
1082
1083 if ($.trim($passwordField.val()) === '') {
1084 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField);
1085 $continue = false;
1086 }
1087
1088 if ($continue) {
1089 this._prepareInstallation($(event.currentTarget).data('packageUpdateServerID'));
1090 }
1091 },
1092
1093 /**
1094 * Inserts search result list template.
1095 *
1096 * @param string template
1097 * @param integer count
1098 */
1099 _insertTemplate: function(template, count) {
1100 this._packageSearchResultContainer.show();
1101
1102 this._packageSearchResultList.html(template);
1103 if (count === undefined) {
1104 this._content[this._pageNo] = template;
1105 }
1106
1107 // update badge count
1108 if (count !== undefined) {
1109 this._content = { 1: template };
1110 this._packageSearchResultContainer.find('> header > h2 > .badge').html(count);
1111 }
1112
1113 // bind listener
1114 this._packageSearchResultList.find('.jsInstallPackage').click($.proxy(this._click, this));
1115 },
1116
1117 /**
1118 * Prepares a package installation.
1119 *
1120 * @param object event
1121 */
1122 _click: function(event) {
1123 var $button = $(event.currentTarget);
1124 WCF.System.Confirmation.show($button.data('confirmMessage'), $.proxy(function(action) {
1125 if (action === 'confirm') {
1126 this._selectedPackage = $button.data('package');
1127 this._selectedPackageVersion = $button.data('packageVersion');
1128 this._prepareInstallation();
1129 }
1130 }, this));
1131 },
1132
1133 /**
1134 * Prepares package installation.
1135 *
1136 * @param integer packageUpdateServerID
1137 */
1138 _prepareInstallation: function(packageUpdateServerID) {
1139 var $parameters = {
1140 'packages': { }
1141 };
1142 $parameters['packages'][this._selectedPackage] = this._selectedPackageVersion;
1143
1144 if (packageUpdateServerID) {
1145 $parameters.authData = {
1146 packageUpdateServerID: packageUpdateServerID,
1147 password: $.trim($('#packageUpdateServerPassword').val()),
1148 saveCredentials: ($('#packageUpdateServerSaveCredentials:checked').length ? true : false),
1149 username: $.trim($('#packageUpdateServerUsername').val())
1150 };
1151 }
1152
1153 this._proxy.setOption('data', {
1154 actionName: 'prepareInstallation',
1155 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1156 parameters: $parameters
1157 });
1158 this._proxy.sendRequest();
1159 },
1160
1161 /**
1162 * Setups pagination for current search.
1163 */
1164 _setupPagination: function() {
1165 // remove previous instances
1166 this._content = { 1: this._packageSearchResultList.html() };
1167 this._packageSearchResultContainer.find('.pageNavigation').wcfPages('destroy').remove();
1168
1169 if (this._pageCount > 1) {
1170 // TODO: Fix ui.wcfPages to properly synchronize multiple instances without triggering events
1171 /*$('<div class="contentNavigation" />').insertBefore(this._packageSearchResultList).wcfPages({
1172 activePage: this._pageNo,
1173 maxPage: this._pageCount
1174 }).on('wcfpagesswitched', $.proxy(this._showPage, this));*/
1175
1176 $('<div class="contentNavigation" />').insertAfter(this._packageSearchResultList).wcfPages({
1177 activePage: this._pageNo,
1178 maxPage: this._pageCount
1179 }).on('wcfpagesswitched', $.proxy(this._showPage, this));
1180 }
1181 },
1182
1183 /**
1184 * Displays requested pages or loads it.
1185 *
1186 * @param object event
1187 * @param object data
1188 */
1189 _showPage: function(event, data) {
1190 if (data && data.activePage) {
1191 this._pageNo = data.activePage;
1192 }
1193
1194 // validate page no
1195 if (this._pageNo < 1 || this._pageNo > this._pageCount) {
1196 console.debug("[WCF.ACP.Package.Search] Cannot access page " + this._pageNo + " of " + this._pageCount);
1197 return;
1198 }
1199
1200 // load content
1201 if (this._content[this._pageNo] === undefined) {
1202 this._proxy.setOption('data', {
1203 actionName: 'getResultList',
1204 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1205 parameters: {
1206 pageNo: this._pageNo,
1207 searchID: this._searchID
1208 }
1209 });
1210 this._proxy.sendRequest();
1211 }
1212 else {
1213 // show cached content
1214 this._packageSearchResultList.html(this._content[this._pageNo]);
1215
1216 WCF.DOMNodeInsertedHandler.execute();
1217 }
1218 }
1219 });
1220
1221 /**
1222 * Namespace for package update related classes.
1223 */
1224 WCF.ACP.Package.Update = { };
1225
1226 /**
1227 * Handles the package update process.
1228 */
1229 WCF.ACP.Package.Update.Manager = Class.extend({
1230 /**
1231 * dialog overlay
1232 * @var jQuery
1233 */
1234 _dialog: null,
1235
1236 /**
1237 * action proxy
1238 * @var WCF.Action.Proxy
1239 */
1240 _proxy: null,
1241
1242 /**
1243 * submit button
1244 * @var jQuery
1245 */
1246 _submitButton: null,
1247
1248 /**
1249 * Initializes the WCF.ACP.Package.Update.Manager class.
1250 */
1251 init: function() {
1252 this._dialog = null;
1253 this._submitButton = $('.formSubmit > button').click($.proxy(this._click, this));
1254
1255 this._proxy = new WCF.Action.Proxy({
1256 success: $.proxy(this._success, this)
1257 });
1258
1259 $('.jsPackageUpdate').each($.proxy(function(index, packageUpdate) {
1260 var $packageUpdate = $(packageUpdate);
1261 $packageUpdate.find('input[type=checkbox]').data('packageUpdate', $packageUpdate).change($.proxy(this._change, this));
1262 }, this));
1263 },
1264
1265 /**
1266 * Handles toggles for a specific update.
1267 */
1268 _change: function(event) {
1269 var $checkbox = $(event.currentTarget);
1270
1271 if ($checkbox.is(':checked')) {
1272 $checkbox.data('packageUpdate').find('select').enable();
1273 $checkbox.data('packageUpdate').find('dl').removeClass('disabled');
1274
1275 this._submitButton.enable();
1276 }
1277 else {
1278 $checkbox.data('packageUpdate').find('select').disable();
1279 $checkbox.data('packageUpdate').find('dl').addClass('disabled');
1280
1281 // disable submit button
1282 if (!$('input[type=checkbox]:checked').length) {
1283 this._submitButton.disable();
1284 }
1285 }
1286 },
1287
1288 /**
1289 * Handles clicks on the submit button.
1290 *
1291 * @param object event
1292 * @param integer packageUpdateServerID
1293 */
1294 _click: function(event, packageUpdateServerID) {
1295 var $packages = { };
1296 $('.jsPackageUpdate').each($.proxy(function(index, packageUpdate) {
1297 var $packageUpdate = $(packageUpdate);
1298 if ($packageUpdate.find('input[type=checkbox]:checked').length) {
1299 $packages[$packageUpdate.data('package')] = $packageUpdate.find('select').val();
1300 }
1301 }, this));
1302
1303 if ($.getLength($packages)) {
1304 this._submitButton.disable();
1305
1306 var $parameters = {
1307 packages: $packages
1308 };
1309 if (packageUpdateServerID) {
1310 $parameters.authData = {
1311 packageUpdateServerID: packageUpdateServerID,
1312 password: $.trim($('#packageUpdateServerPassword').val()),
1313 saveCredentials: ($('#packageUpdateServerSaveCredentials:checked').length ? true : false),
1314 username: $.trim($('#packageUpdateServerUsername').val())
1315 };
1316 }
1317
1318 this._proxy.setOption('data', {
1319 actionName: 'prepareUpdate',
1320 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1321 parameters: $parameters,
1322 });
1323 this._proxy.sendRequest();
1324 }
1325 },
1326
1327 /**
1328 * Handles successful AJAX requests.
1329 *
1330 * @param object data
1331 * @param string textStatus
1332 * @param jQuery jqXHR
1333 */
1334 _success: function(data, textStatus, jqXHR) {
1335 if (data.returnValues.queueID) {
1336 if (this._dialog !== null) {
1337 this._dialog.wcfDialog('close');
1338 }
1339
1340 var $installation = new WCF.ACP.Package.Installation(data.returnValues.queueID, undefined, false, true);
1341 $installation.prepareInstallation();
1342 }
1343 else if (data.returnValues.template) {
1344 if (this._dialog === null) {
1345 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
1346 this._dialog.wcfDialog({
1347 title: WCF.Language.get('wcf.acp.package.update.unauthorized')
1348 });
1349 }
1350 else {
1351 this._dialog.html(data.returnValues.template).wcfDialog('open');
1352 }
1353
1354 this._dialog.find('.formSubmit > button').click($.proxy(this._submitAuthentication, this));
1355 }
1356 },
1357
1358 /**
1359 * Submits authentication data for current update server.
1360 *
1361 * @param object event
1362 */
1363 _submitAuthentication: function(event) {
1364 var $usernameField = $('#packageUpdateServerUsername');
1365 var $passwordField = $('#packageUpdateServerPassword');
1366
1367 // remove error messages if any
1368 $usernameField.next('small.innerError').remove();
1369 $passwordField.next('small.innerError').remove();
1370
1371 var $continue = true;
1372 if ($.trim($usernameField.val()) === '') {
1373 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField);
1374 $continue = false;
1375 }
1376
1377 if ($.trim($passwordField.val()) === '') {
1378 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField);
1379 $continue = false;
1380 }
1381
1382 if ($continue) {
1383 this._click(undefined, $(event.currentTarget).data('packageUpdateServerID'));
1384 }
1385 }
1386 });
1387
1388 /**
1389 * Searches for available updates.
1390 */
1391 WCF.ACP.Package.Update.Search = Class.extend({
1392 /**
1393 * dialog overlay
1394 * @var jQuery
1395 */
1396 _dialog: null,
1397
1398 /**
1399 * initializes the WCF.ACP.Package.SearchForUpdates class.
1400 */
1401 init: function() {
1402 this._dialog = null;
1403
1404 var $button = $('<li><a class="button"><span class="icon icon16 icon-refresh"></span> <span>' + WCF.Language.get('wcf.acp.package.searchForUpdates') + '</span></a></li>');
1405 $button.click($.proxy(this._click, this)).prependTo($('.contentNavigation:eq(0) > nav:not(.pageNavigation) > ul'));
1406 },
1407
1408 /**
1409 * Handles clicks on the search button.
1410 */
1411 _click: function() {
1412 if (this._dialog === null) {
1413 new WCF.Action.Proxy({
1414 autoSend: true,
1415 data: {
1416 actionName: 'searchForUpdates',
1417 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1418 parameters: {
1419 ignoreCache: 1
1420 }
1421 },
1422 success: $.proxy(this._success, this)
1423 });
1424 }
1425 else {
1426 this._dialog.wcfDialog('open');
1427 }
1428 },
1429
1430 /**
1431 * Handles successful AJAX requests.
1432 *
1433 * @param object data
1434 * @param string textStatus
1435 * @param jQuery jqXHR
1436 */
1437 _success: function(data, textStatus, jqXHR) {
1438 if (data.returnValues.url) {
1439 window.location = data.returnValues.url;
1440 }
1441 else {
1442 this._dialog = $('<div>' + WCF.Language.get('wcf.acp.package.searchForUpdates.noResults') + '</div>').hide().appendTo(document.body);
1443 this._dialog.wcfDialog({
1444 title: WCF.Language.get('wcf.acp.package.searchForUpdates')
1445 });
1446 }
1447 }
1448 });
1449
1450 /**
1451 * Handles option selection.
1452 */
1453 WCF.ACP.Options = Class.extend({
1454 /**
1455 * Initializes options.
1456 */
1457 init: function() {
1458 $('.jsEnablesOptions').each($.proxy(this._initOption, this));
1459 },
1460
1461 /**
1462 * Initializes an option.
1463 *
1464 * @param integer index
1465 * @param object option
1466 */
1467 _initOption: function(index, option) {
1468 // execute action on init
1469 this._change(option);
1470
1471 // bind event listener
1472 $(option).change($.proxy(this._handleChange, this));
1473 },
1474
1475 /**
1476 * Applies whenever an option is changed.
1477 *
1478 * @param object event
1479 */
1480 _handleChange: function(event) {
1481 this._change($(event.target));
1482 },
1483
1484 /**
1485 * Enables or disables options on option value change.
1486 *
1487 * @param object option
1488 */
1489 _change: function(option) {
1490 option = $(option);
1491
1492 var $disableOptions = eval(option.data('disableOptions'));
1493 var $enableOptions = eval(option.data('enableOptions'));
1494
1495 // determine action by type
1496 switch(option.getTagName()) {
1497 case 'input':
1498 switch(option.attr('type')) {
1499 case 'checkbox':
1500 this._execute(option.prop('checked'), $disableOptions, $enableOptions);
1501 break;
1502
1503 case 'radio':
1504 if (option.prop('checked')) {
1505 this._execute(true, $disableOptions, $enableOptions);
1506 }
1507 break;
1508 }
1509 break;
1510
1511 case 'select':
1512 var $value = option.val();
1513 var $disableOptions = $enableOptions = [];
1514
1515 if (option.data('disableOptions').length > 0) {
1516 for (var $index in option.data('disableOptions')) {
1517 var $item = option.data('disableOptions')[$index];
1518
1519 if ($item.value == $value) {
1520 $disableOptions.push($item.option);
1521 }
1522 }
1523 }
1524
1525 if (option.data('enableOptions').length > 0) {
1526 for (var $index in option.data('enableOptions')) {
1527 var $item = option.data('enableOptions')[$index];
1528
1529 if ($item.value == $value) {
1530 $enableOptions.push($item.option);
1531 }
1532 }
1533 }
1534
1535 this._execute(true, $disableOptions, $enableOptions);
1536 break;
1537 }
1538 },
1539
1540 /**
1541 * Enables or disables options.
1542 *
1543 * @param boolean isActive
1544 * @param array disableOptions
1545 * @param array enableOptions
1546 */
1547 _execute: function(isActive, disableOptions, enableOptions) {
1548 if (disableOptions.length > 0) {
1549 for (var $i = 0, $size = disableOptions.length; $i < $size; $i++) {
1550 var $target = disableOptions[$i];
1551 if ($.wcfIsset($target)) {
1552 this._enableOption($target, !isActive);
1553 }
1554 }
1555 }
1556
1557 if (enableOptions.length > 0) {
1558 for (var $i = 0, $size = enableOptions.length; $i < $size; $i++) {
1559 var $target = enableOptions[$i];
1560 if ($.wcfIsset($target)) {
1561 this._enableOption($target, isActive);
1562 }
1563 }
1564 }
1565 },
1566
1567 /**
1568 * Enables an option.
1569 *
1570 * @param string target
1571 * @param boolean enable
1572 */
1573 _enableOption: function(target, enable) {
1574 var $targetElement = $('#' + $.wcfEscapeID(target));
1575 var $tagName = $targetElement.getTagName();
1576
1577 if ($tagName == 'select' || ($tagName == 'input' && ($targetElement.attr('type') == 'checkbox' || $targetElement.attr('type') == 'radio'))) {
1578 if (enable) $targetElement.enable();
1579 else $targetElement.disable();
1580 }
1581 else {
1582 if (enable) $targetElement.removeAttr('readonly');
1583 else $targetElement.attr('readonly', true);
1584 }
1585
1586 if (enable) {
1587 $targetElement.closest('dl').removeClass('disabled');
1588 }
1589 else {
1590 $targetElement.closest('dl').addClass('disabled');
1591 }
1592 }
1593 });
1594
1595 /**
1596 * Worker support for ACP.
1597 *
1598 * @param string dialogID
1599 * @param string className
1600 * @param string title
1601 * @param object parameters
1602 * @param object callback
1603 */
1604 WCF.ACP.Worker = Class.extend({
1605 /**
1606 * worker aborted
1607 * @var boolean
1608 */
1609 _aborted: false,
1610
1611 /**
1612 * callback invoked after worker completed
1613 * @var object
1614 */
1615 _callback: null,
1616
1617 /**
1618 * dialog id
1619 * @var string
1620 */
1621 _dialogID: null,
1622
1623 /**
1624 * dialog object
1625 * @var jQuery
1626 */
1627 _dialog: null,
1628
1629 /**
1630 * action proxy
1631 * @var WCF.Action.Proxy
1632 */
1633 _proxy: null,
1634
1635 /**
1636 * dialog title
1637 * @var string
1638 */
1639 _title: '',
1640
1641 /**
1642 * Initializes a new worker instance.
1643 *
1644 * @param string dialogID
1645 * @param string className
1646 * @param string title
1647 * @param object parameters
1648 * @param object callback
1649 * @param object confirmMessage
1650 */
1651 init: function(dialogID, className, title, parameters, callback) {
1652 this._aborted = false;
1653 this._callback = callback || null;
1654 this._dialogID = dialogID + 'Worker';
1655 this._dialog = null;
1656 this._proxy = new WCF.Action.Proxy({
1657 autoSend: true,
1658 data: {
1659 className: className,
1660 parameters: parameters || { }
1661 },
1662 showLoadingOverlay: false,
1663 success: $.proxy(this._success, this),
1664 url: 'index.php/WorkerProxy/?t=' + SECURITY_TOKEN + SID_ARG_2ND
1665 });
1666 this._title = title;
1667 },
1668
1669 /**
1670 * Handles response from server.
1671 *
1672 * @param object data
1673 */
1674 _success: function(data) {
1675 // init binding
1676 if (this._dialog === null) {
1677 this._dialog = $('<div id="' + this._dialogID + '" />').hide().appendTo(document.body);
1678 this._dialog.wcfDialog({
1679 closeConfirmMessage: WCF.Language.get('wcf.acp.worker.abort.confirmMessage'),
1680 closeViaModal: false,
1681 onClose: $.proxy(function() {
1682 this._aborted = true;
1683 this._proxy.abortPrevious();
1684
1685 window.location.reload();
1686 }, this),
1687 title: this._title
1688 });
1689 }
1690
1691 if (this._aborted) {
1692 return;
1693 }
1694
1695 if (data.template) {
1696 this._dialog.html(data.template);
1697 }
1698
1699 // update progress
1700 this._dialog.find('progress').attr('value', data.progress).text(data.progress + '%').next('span').text(data.progress + '%');
1701
1702 // worker is still busy with it's business, carry on
1703 if (data.progress < 100) {
1704 // send request for next loop
1705 this._proxy.setOption('data', {
1706 className: data.className,
1707 loopCount: data.loopCount,
1708 parameters: data.parameters
1709 });
1710 this._proxy.sendRequest();
1711 }
1712 else if (this._callback !== null) {
1713 this._callback(this, data);
1714 }
1715 else {
1716 // display continue button
1717 var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
1718 $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($formSubmit).click(function() { window.location = data.proceedURL; });
1719
1720 this._dialog.wcfDialog('render');
1721 }
1722 }
1723 });
1724
1725 /**
1726 * Namespace for category-related functions.
1727 */
1728 WCF.ACP.Category = {};
1729
1730 /**
1731 * Handles collapsing categories.
1732 *
1733 * @param string className
1734 * @param integer objectTypeID
1735 */
1736 WCF.ACP.Category.Collapsible = WCF.Collapsible.SimpleRemote.extend({
1737 /**
1738 * @see WCF.Collapsible.Remote.init()
1739 */
1740 init: function(className) {
1741 var sortButton = $('.formSubmit > button[data-type="submit"]');
1742 if (sortButton) {
1743 sortButton.click($.proxy(this._sort, this));
1744 }
1745
1746 this._super(className);
1747 },
1748
1749 /**
1750 * @see WCF.Collapsible.Remote._getButtonContainer()
1751 */
1752 _getButtonContainer: function(containerID) {
1753 return $('#' + containerID + ' > .buttons');
1754 },
1755
1756 /**
1757 * @see WCF.Collapsible.Remote._getContainers()
1758 */
1759 _getContainers: function() {
1760 return $('.jsCategory').has('ol').has('li');
1761 },
1762
1763 /**
1764 * @see WCF.Collapsible.Remote._getTarget()
1765 */
1766 _getTarget: function(containerID) {
1767 return $('#' + containerID + ' > ol');
1768 },
1769
1770 /**
1771 * Handles a click on the sort button.
1772 */
1773 _sort: function() {
1774 // remove existing collapsible buttons
1775 $('.collapsibleButton').remove();
1776
1777 // reinit containers
1778 this._containers = {};
1779 this._containerData = {};
1780
1781 var $containers = this._getContainers();
1782 if ($containers.length == 0) {
1783 console.debug('[WCF.ACP.Category.Collapsible] Empty container set given, aborting.');
1784 }
1785 $containers.each($.proxy(function(index, container) {
1786 var $container = $(container);
1787 var $containerID = $container.wcfIdentify();
1788 this._containers[$containerID] = $container;
1789
1790 this._initContainer($containerID);
1791 }, this));
1792 }
1793 });
1794
1795 /**
1796 * Provides the search dropdown for ACP
1797 *
1798 * @see WCF.Search.Base
1799 */
1800 WCF.ACP.Search = WCF.Search.Base.extend({
1801 /**
1802 * @see WCF.Search.Base.init()
1803 */
1804 init: function() {
1805 this._className = 'wcf\\data\\acp\\search\\provider\\ACPSearchProviderAction';
1806 this._super('#search input[name=q]');
1807
1808 // disable form submitting
1809 $('#search > form').on('submit', function(event) {
1810 event.preventDefault();
1811 });
1812 },
1813
1814 /**
1815 * @see WCF.Search.Base._createListItem()
1816 */
1817 _createListItem: function(resultList) {
1818 // add a divider between result lists
1819 if (this._list.children('li').length > 0) {
1820 $('<li class="dropdownDivider" />').appendTo(this._list);
1821 }
1822
1823 // add caption
1824 $('<li class="dropdownText">' + resultList.title + '</li>').appendTo(this._list);
1825
1826 // add menu items
1827 for (var $i in resultList.items) {
1828 var $item = resultList.items[$i];
1829
1830 $('<li><a href="' + $item.link + '">' + WCF.String.escapeHTML($item.title) + '</a></li>').appendTo(this._list);
1831
1832 this._itemCount++;
1833 }
1834 },
1835
1836 /**
1837 * @see WCF.Search.Base._handleEmptyResult()
1838 */
1839 _handleEmptyResult: function() {
1840 $('<li class="dropdownText">' + WCF.Language.get('wcf.acp.search.noResults') + '</li>').appendTo(this._list);
1841
1842 return true;
1843 },
1844
1845 /**
1846 * @see WCF.Search.Base._highlightSelectedElement()
1847 */
1848 _highlightSelectedElement: function() {
1849 this._list.find('li').removeClass('dropdownNavigationItem');
1850 this._list.find('li:not(.dropdownDivider):not(.dropdownText)').eq(this._itemIndex).addClass('dropdownNavigationItem');
1851 },
1852
1853 /**
1854 * @see WCF.Search.Base._selectElement()
1855 */
1856 _selectElement: function(event) {
1857 if (this._itemIndex === -1) {
1858 return false;
1859 }
1860
1861 window.location = this._list.find('li.dropdownNavigationItem > a').attr('href');
1862 }
1863 });
1864
1865 /**
1866 * Namespace for user management.
1867 */
1868 WCF.ACP.User = { };
1869
1870 /**
1871 * Generic implementation to ban users.
1872 */
1873 WCF.ACP.User.BanHandler = {
1874 /**
1875 * callback object
1876 * @var object
1877 */
1878 _callback: null,
1879
1880 /**
1881 * dialog overlay
1882 * @var jQuery
1883 */
1884 _dialog: null,
1885
1886 /**
1887 * action proxy
1888 * @var WCF.Action.Proxy
1889 */
1890 _proxy: null,
1891
1892 /**
1893 * Initializes WCF.ACP.User.BanHandler on first use.
1894 */
1895 init: function() {
1896 this._dialog = $('<div />').hide().appendTo(document.body);
1897 this._proxy = new WCF.Action.Proxy({
1898 success: $.proxy(this._success, this)
1899 });
1900
1901 $('.jsBanButton').click($.proxy(function(event) {
1902 var $button = $(event.currentTarget);
1903 if ($button.data('banned')) {
1904 this.unban([ $button.data('objectID') ]);
1905 }
1906 else {
1907 this.ban([ $button.data('objectID') ]);
1908 }
1909 }, this));
1910
1911 // bind listener
1912 $('.jsClipboardEditor').each($.proxy(function(index, container) {
1913 var $container = $(container);
1914 var $types = eval($container.data('types'));
1915 if (WCF.inArray('com.woltlab.wcf.user', $types)) {
1916 $container.on('clipboardAction', $.proxy(this._execute, this));
1917 return false;
1918 }
1919 }, this));
1920 },
1921
1922 /**
1923 * Handles clipboard actions.
1924 *
1925 * @param object event
1926 * @param string type
1927 * @param string actionName
1928 * @param object parameters
1929 */
1930 _execute: function(event, type, actionName, parameters) {
1931 if (actionName == 'com.woltlab.wcf.user.ban') {
1932 this.ban(parameters.objectIDs);
1933 }
1934 },
1935
1936 /**
1937 * Unbans users.
1938 *
1939 * @param array<integer> userIDs
1940 */
1941 unban: function(userIDs) {
1942 this._proxy.setOption('data', {
1943 actionName: 'unban',
1944 className: 'wcf\\data\\user\\UserAction',
1945 objectIDs: userIDs
1946 });
1947 this._proxy.sendRequest();
1948 },
1949
1950 /**
1951 * Bans users.
1952 *
1953 * @param array<integer> userIDs
1954 */
1955 ban: function(userIDs) {
1956 WCF.System.Confirmation.show(WCF.Language.get('wcf.acp.user.ban.sure'), $.proxy(function(action) {
1957 if (action === 'confirm') {
1958 this._proxy.setOption('data', {
1959 actionName: 'ban',
1960 className: 'wcf\\data\\user\\UserAction',
1961 objectIDs: userIDs,
1962 parameters: {
1963 banReason: $('#userBanReason').val()
1964 }
1965 });
1966 this._proxy.sendRequest();
1967 }
1968 }, this), '', $('<fieldset><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></fieldset>'));
1969 },
1970
1971 /**
1972 * Handles successful AJAX calls.
1973 *
1974 * @param object data
1975 * @param string textStatus
1976 * @param jQuery jqXHR
1977 */
1978 _success: function(data, textStatus, jqXHR) {
1979 $('.jsBanButton').each(function(index, button) {
1980 var $button = $(button);
1981 if (WCF.inArray($button.data('objectID'), data.objectIDs)) {
1982 if (data.actionName == 'unban') {
1983 $button.data('banned', false).data('tooltip', $button.data('banMessage')).removeClass('icon-lock').addClass('icon-unlock');
1984 }
1985 else {
1986 $button.data('banned', true).data('tooltip', $button.data('unbanMessage')).removeClass('icon-unlock').addClass('icon-lock');
1987 }
1988 }
1989 });
1990
1991 var $notification = new WCF.System.Notification();
1992 $notification.show();
1993
1994 WCF.Clipboard.reload();
1995 }
1996 };
1997
1998 /**
1999 * Generic implementation to enable users.
2000 */
2001 WCF.ACP.User.EnableHandler = {
2002 /**
2003 * action proxy
2004 * @var WCF.Action.Proxy
2005 */
2006 _proxy: null,
2007
2008 /**
2009 * Initializes WCF.ACP.User.EnableHandler on first use.
2010 */
2011 init: function() {
2012 this._proxy = new WCF.Action.Proxy({
2013 success: $.proxy(this._success, this)
2014 });
2015
2016 $('.jsEnableButton').click($.proxy(function(event) {
2017 var $button = $(event.currentTarget);
2018 if ($button.data('enabled')) {
2019 this.disable([ $button.data('objectID') ]);
2020 }
2021 else {
2022 this.enable([ $button.data('objectID') ]);
2023 }
2024 }, this));
2025
2026 // bind listener
2027 $('.jsClipboardEditor').each($.proxy(function(index, container) {
2028 var $container = $(container);
2029 var $types = eval($container.data('types'));
2030 if (WCF.inArray('com.woltlab.wcf.user', $types)) {
2031 $container.on('clipboardAction', $.proxy(this._execute, this));
2032 return false;
2033 }
2034 }, this));
2035 },
2036
2037 /**
2038 * Handles clipboard actions.
2039 *
2040 * @param object event
2041 * @param string type
2042 * @param string actionName
2043 * @param object parameters
2044 */
2045 _execute: function(event, type, actionName, parameters) {
2046 if (actionName == 'com.woltlab.wcf.user.enable') {
2047 this.enable(parameters.objectIDs);
2048 }
2049 },
2050
2051 /**
2052 * Disables users.
2053 *
2054 * @param array<integer> userIDs
2055 */
2056 disable: function(userIDs) {
2057 this._proxy.setOption('data', {
2058 actionName: 'disable',
2059 className: 'wcf\\data\\user\\UserAction',
2060 objectIDs: userIDs
2061 });
2062 this._proxy.sendRequest();
2063 },
2064
2065 /**
2066 * Enables users.
2067 *
2068 * @param array<integer> userIDs
2069 */
2070 enable: function(userIDs) {
2071 this._proxy.setOption('data', {
2072 actionName: 'enable',
2073 className: 'wcf\\data\\user\\UserAction',
2074 objectIDs: userIDs
2075 });
2076 this._proxy.sendRequest();
2077 },
2078
2079 /**
2080 * Handles successful AJAX calls.
2081 *
2082 * @param object data
2083 * @param string textStatus
2084 * @param jQuery jqXHR
2085 */
2086 _success: function(data, textStatus, jqXHR) {
2087 $('.jsEnableButton').each(function(index, button) {
2088 var $button = $(button);
2089 if (WCF.inArray($button.data('objectID'), data.objectIDs)) {
2090 if (data.actionName == 'disable') {
2091 $button.data('enabled', false).data('tooltip', $button.data('enableMessage')).removeClass('icon-check').addClass('icon-check-empty');
2092 }
2093 else {
2094 $button.data('enabled', true).data('tooltip', $button.data('disableMessage')).removeClass('icon-check-empty').addClass('icon-check');
2095 }
2096 }
2097 });
2098
2099 var $notification = new WCF.System.Notification();
2100 $notification.show(function() { window.location.reload(); });
2101 }
2102 };
2103
2104 /**
2105 * Namespace for import-related classes.
2106 */
2107 WCF.ACP.Import = { };
2108
2109 /**
2110 * Importer for ACP.
2111 *
2112 * @param array<string> objectTypes
2113 */
2114 WCF.ACP.Import.Manager = Class.extend({
2115 /**
2116 * current action
2117 * @var string
2118 */
2119 _currentAction: '',
2120
2121 /**
2122 * dialog overlay
2123 * @var jQuery
2124 */
2125 _dialog: null,
2126
2127 /**
2128 * current object type index
2129 * @var integer
2130 */
2131 _index: -1,
2132
2133 /**
2134 * list of object types
2135 * @var array<string>
2136 */
2137 _objectTypes: [ ],
2138
2139 /**
2140 * action proxy
2141 * @var WCF.Action.Proxy
2142 */
2143 _proxy: null,
2144
2145 /**
2146 * redirect URL
2147 * @var string
2148 */
2149 _redirectURL: '',
2150
2151 /**
2152 * Initializes the WCF.ACP.Importer object.
2153 *
2154 * @param array<string> objectTypes
2155 * @param string redirectURL
2156 */
2157 init: function(objectTypes, redirectURL) {
2158 this._currentAction = '';
2159 this._index = -1;
2160 this._objectTypes = objectTypes;
2161 this._proxy = new WCF.Action.Proxy({
2162 showLoadingOverlay: false,
2163 success: $.proxy(this._success, this),
2164 url: 'index.php/WorkerProxy/?t=' + SECURITY_TOKEN + SID_ARG_2ND
2165 });
2166 this._redirectURL = redirectURL;
2167
2168 this._invoke();
2169 },
2170
2171 /**
2172 * Invokes importing of an object type.
2173 */
2174 _invoke: function() {
2175 this._index++;
2176 if (this._index >= this._objectTypes.length) {
2177 this._dialog.find('.icon-spinner').removeClass('icon-spinner').addClass('icon-ok');
2178 this._dialog.find('h1').text(WCF.Language.get('wcf.acp.dataImport.completed'));
2179
2180 var $form = $('<div class="formSubmit" />').appendTo(this._dialog.find('#workerContainer'));
2181 $('<button>' + WCF.Language.get('wcf.global.button.next') + '</button>').click($.proxy(function() {
2182 new WCF.Action.Proxy({
2183 autoSend: true,
2184 data: {
2185 noRedirect: 1
2186 },
2187 dataType: 'html',
2188 success: $.proxy(function() {
2189 window.location = this._redirectURL;
2190 }, this),
2191 url: 'index.php/CacheClear/?t=' + SECURITY_TOKEN + SID_ARG_2ND
2192 });
2193 }, this)).appendTo($form);
2194
2195 this._dialog.wcfDialog('render');
2196 }
2197 else {
2198 this._run(
2199 WCF.Language.get('wcf.acp.dataImport.data.' + this._objectTypes[this._index]),
2200 this._objectTypes[this._index]
2201 );
2202 }
2203 },
2204
2205 /**
2206 * Executes import of given object type.
2207 *
2208 * @param string currentAction
2209 * @param string objectType
2210 */
2211 _run: function(currentAction, objectType) {
2212 this._currentAction = currentAction;
2213 this._proxy.setOption('data', {
2214 className: 'wcf\\system\\worker\\ImportWorker',
2215 parameters: {
2216 objectType: objectType
2217 }
2218 });
2219 this._proxy.sendRequest();
2220 },
2221
2222 /**
2223 * Handles response from server.
2224 *
2225 * @param object data
2226 */
2227 _success: function(data) {
2228 // init binding
2229 if (this._dialog === null) {
2230 this._dialog = $('<div />').hide().appendTo(document.body);
2231 this._dialog.wcfDialog({
2232 closable: false,
2233 title: WCF.Language.get('wcf.acp.dataImport')
2234 });
2235 }
2236
2237 if (data.template) {
2238 this._dialog.html(data.template);
2239 }
2240
2241 if (this._currentAction) {
2242 this._dialog.find('h1').text(this._currentAction);
2243 }
2244
2245 // update progress
2246 this._dialog.find('progress').attr('value', data.progress).text(data.progress + '%').next('span').text(data.progress + '%');
2247
2248 // worker is still busy with it's business, carry on
2249 if (data.progress < 100) {
2250 // send request for next loop
2251 this._proxy.setOption('data', {
2252 className: data.className,
2253 loopCount: data.loopCount,
2254 parameters: data.parameters
2255 });
2256 this._proxy.sendRequest();
2257 }
2258 else {
2259 this._invoke();
2260 }
2261 }
2262 });