Add clipboard support for tags
[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-2015 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 var $actionName = '';
410 var $parts = this._actionName.split(/([A-Z][a-z0-9]+)/);
411 for (var $i = 0, $length = $parts.length; $i < $length; $i++) {
412 var $part = $parts[$i];
413 if ($part.length) {
414 if ($actionName.length) $actionName += '-';
415 $actionName += $part.toLowerCase();
416 }
417 }
418
419 this._proxy = new WCF.Action.Proxy({
420 failure: $.proxy(this._failure, this),
421 showLoadingOverlay: false,
422 success: $.proxy(this._success, this),
423 url: 'index.php?' + $actionName + '/&t=' + SECURITY_TOKEN + SID_ARG_2ND
424 });
425 },
426
427 /**
428 * Initializes the package installation.
429 */
430 _init: function() {
431 $('#submitButton').click($.proxy(this.prepareInstallation, this));
432 },
433
434 /**
435 * Handles erroneous AJAX requests.
436 */
437 _failure: function() {
438 if (this._dialog !== null) {
439 $('#packageInstallationProgress').removeAttr('value');
440 this._setIcon('remove');
441 }
442
443 if (!this._allowRollback) {
444 return;
445 }
446
447 if (this._dialog !== null) {
448 this._purgeTemplateContent($.proxy(function() {
449 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
450 $('<button class="buttonPrimary">' + WCF.Language.get('wcf.acp.package.installation.rollback') + '</button>').appendTo($form).click($.proxy(this._rollback, this));
451
452 $('#packageInstallationInnerContentContainer').show();
453
454 this._dialog.wcfDialog('render');
455 }, this));
456 }
457 },
458
459 /**
460 * Performs a rollback.
461 *
462 * @param object event
463 */
464 _rollback: function(event) {
465 this._setIcon('spinner');
466
467 if (event) {
468 $(event.currentTarget).disable();
469 }
470
471 this._executeStep('rollback');
472 },
473
474 /**
475 * Prepares installation dialog.
476 */
477 prepareInstallation: function() {
478 this._proxy.setOption('data', this._getParameters());
479 this._proxy.sendRequest();
480 },
481
482 /**
483 * Returns parameters to prepare installation.
484 *
485 * @return object
486 */
487 _getParameters: function() {
488 return {
489 queueID: this._queueID,
490 step: 'prepare'
491 };
492 },
493
494 /**
495 * Handles successful AJAX requests.
496 *
497 * @param object data
498 * @param string textStatus
499 * @param jQuery jqXHR
500 */
501 _success: function(data, textStatus, jqXHR) {
502 this._shouldRender = false;
503
504 if (this._dialog === null) {
505 this._dialog = $('<div id="packageInstallationDialog" />').hide().appendTo(document.body);
506 this._dialog.wcfDialog({
507 closable: false,
508 title: WCF.Language.get(this._dialogTitle)
509 });
510 }
511
512 this._setIcon('spinner');
513
514 if (data.step == 'rollback') {
515 this._dialog.wcfDialog('close');
516 this._dialog.remove();
517
518 new WCF.PeriodicalExecuter(function(pe) {
519 pe.stop();
520
521 var $uninstallation = new WCF.ACP.Package.Uninstallation();
522 $uninstallation.start(data.packageID);
523 }, 200);
524
525 return;
526 }
527
528 // receive new queue id
529 if (data.queueID) {
530 this._queueID = data.queueID;
531 }
532
533 // update template
534 if (data.template && !data.ignoreTemplate) {
535 this._dialog.html(data.template);
536 this._shouldRender = true;
537 }
538
539 // update progress
540 if (data.progress) {
541 $('#packageInstallationProgress').attr('value', data.progress).text(data.progress + '%');
542 $('#packageInstallationProgressLabel').text(data.progress + '%');
543 }
544
545 // update action
546 if (data.currentAction) {
547 $('#packageInstallationAction').html(data.currentAction);
548 }
549
550 // handle success
551 if (data.step === 'success') {
552 this._setIcon('ok');
553
554 this._purgeTemplateContent($.proxy(function() {
555 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
556 var $button = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($form).click(function() {
557 $(this).disable();
558 window.location = data.redirectLocation;
559 });
560
561 $('#packageInstallationInnerContentContainer').show();
562
563 $(document).keydown(function(event) {
564 if (event.which === $.ui.keyCode.ENTER) {
565 $button.trigger('click');
566 }
567 });
568
569 this._dialog.wcfDialog('render');
570 }, this));
571
572 return;
573 }
574
575 // handle inner template
576 if (data.innerTemplate) {
577 var self = this;
578 $('#packageInstallationInnerContent').html(data.innerTemplate).find('input').keyup(function(event) {
579 if (event.keyCode === $.ui.keyCode.ENTER) {
580 self._submit(data);
581 }
582 });
583
584 // create button to handle next step
585 if (data.step && data.node) {
586 $('#packageInstallationProgress').removeAttr('value');
587 this._setIcon('question');
588
589 var $form = $('<div class="formSubmit" />').appendTo($('#packageInstallationInnerContent'));
590 $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($form).click($.proxy(function(event) {
591 $(event.currentTarget).disable();
592
593 this._submit(data);
594 }, this));
595 }
596
597 $('#packageInstallationInnerContentContainer').show();
598
599 this._dialog.wcfDialog('render');
600 return;
601 }
602
603 // purge content
604 this._purgeTemplateContent($.proxy(function() {
605 // render container
606 if (this._shouldRender) {
607 this._dialog.wcfDialog('render');
608 }
609
610 // execute next step
611 if (data.step && data.node) {
612 this._executeStep(data.step, data.node);
613 }
614 }, this));
615 },
616
617 /**
618 * Submits the dialog content.
619 *
620 * @param object data
621 */
622 _submit: function(data) {
623 this._setIcon('spinner');
624
625 // collect form values
626 var $additionalData = { };
627 $('#packageInstallationInnerContent input').each(function(index, inputElement) {
628 var $inputElement = $(inputElement);
629 var $type = $inputElement.attr('type');
630
631 if (($type != 'checkbox' && $type != 'radio') || $inputElement.prop('checked')) {
632 var $name = $inputElement.attr('name');
633 if ($name.match(/(.*)\[([^[]*)\]$/)) {
634 $name = RegExp.$1;
635 $key = RegExp.$2;
636
637 if ($additionalData[$name] === undefined) {
638 if ($key) {
639 $additionalData[$name] = { };
640 }
641 else {
642 $additionalData[$name] = [ ];
643 }
644 }
645
646 if ($key) {
647 $additionalData[$name][$key] = $inputElement.val();
648 }
649 else {
650 $additionalData[$name].push($inputElement.val());
651 }
652 }
653 else {
654 $additionalData[$name] = $inputElement.val();
655 }
656 }
657 });
658
659 this._executeStep(data.step, data.node, $additionalData);
660 },
661
662 /**
663 * Purges template content.
664 *
665 * @param function callback
666 */
667 _purgeTemplateContent: function(callback) {
668 if ($('#packageInstallationInnerContent').children().length > 1) {
669 $('#packageInstallationInnerContentContainer').wcfBlindOut('vertical', $.proxy(function() {
670 $('#packageInstallationInnerContent').empty();
671 this._shouldRender = true;
672
673 // execute callback
674 callback();
675 }, this));
676 }
677 else {
678 callback();
679 }
680 },
681
682 /**
683 * Executes the next installation step.
684 *
685 * @param string step
686 * @param string node
687 * @param object additionalData
688 */
689 _executeStep: function(step, node, additionalData) {
690 if (!additionalData) additionalData = { };
691
692 var $data = $.extend({
693 node: node,
694 queueID: this._queueID,
695 step: step
696 }, additionalData);
697
698 this._proxy.setOption('data', $data);
699 this._proxy.sendRequest();
700 },
701
702 /**
703 * Sets the icon with the given name as the current installation status icon.
704 *
705 * @param string iconName
706 */
707 _setIcon: function(iconName) {
708 this._dialog.find('.jsPackageInstallationStatus').removeClass('icon-ok icon-question icon-remove icon-spinner').addClass('icon-' + iconName);
709 }
710 });
711
712 /**
713 * Handles canceling the package installation at the package installation
714 * confirm page.
715 */
716 WCF.ACP.Package.Installation.Cancel = Class.extend({
717 /**
718 * Creates a new instance of WCF.ACP.Package.Installation.Cancel.
719 *
720 * @param integer queueID
721 */
722 init: function(queueID) {
723 $('#backButton').click(function() {
724 new WCF.Action.Proxy({
725 autoSend: true,
726 data: {
727 actionName: 'cancelInstallation',
728 className: 'wcf\\data\\package\\installation\\queue\\PackageInstallationQueueAction',
729 objectIDs: [ queueID ]
730 },
731 success: function(data) {
732 window.location = data.returnValues.url;
733 }
734 });
735 });
736 }
737 });
738
739 /**
740 * Provides the package uninstallation.
741 *
742 * @param jQuery elements
743 * @param string wcfPackageListURL
744 */
745 WCF.ACP.Package.Uninstallation = WCF.ACP.Package.Installation.extend({
746 /**
747 * list of uninstallation buttons
748 * @var jQuery
749 */
750 _elements: null,
751
752 /**
753 * current package id
754 * @var integer
755 */
756 _packageID: 0,
757
758 /**
759 * URL of WCF package list
760 * @var string
761 */
762 _wcfPackageListURL: '',
763
764 /**
765 * Initializes the WCF.ACP.Package.Uninstallation class.
766 *
767 * @param jQuery elements
768 * @param string wcfPackageListURL
769 */
770 init: function(elements, wcfPackageListURL) {
771 this._elements = elements;
772 this._packageID = 0;
773 this._wcfPackageListURL = wcfPackageListURL;
774
775 if (this._elements !== undefined && this._elements.length) {
776 this._super(0, 'UninstallPackage');
777 }
778 },
779
780 /**
781 * Begins a package uninstallation without user action.
782 *
783 * @param integer packageID
784 */
785 start: function(packageID) {
786 this._actionName = 'UninstallPackage';
787 this._packageID = packageID;
788 this._queueID = 0;
789 this._dialogTitle = 'wcf.acp.package.uninstallation.title';
790
791 this._initProxy();
792 this.prepareInstallation();
793 },
794
795 /**
796 * @see WCF.ACP.Package.Installation.init()
797 */
798 _init: function() {
799 this._elements.click($.proxy(this._showConfirmationDialog, this));
800 },
801
802 /**
803 * Displays a confirmation dialog prior to package uninstallation.
804 *
805 * @param object event
806 */
807 _showConfirmationDialog: function(event) {
808 var $element = $(event.currentTarget);
809
810 if ($element.data('isApplication') && this._wcfPackageListURL) {
811 window.location = WCF.String.unescapeHTML(this._wcfPackageListURL.replace(/{packageID}/, $element.data('objectID')));
812 return;
813 }
814
815 var self = this;
816 WCF.System.Confirmation.show($element.data('confirmMessage'), function(action) {
817 if (action === 'confirm') {
818 self._packageID = $element.data('objectID');
819 self.prepareInstallation();
820 }
821 });
822 },
823
824 /**
825 * @see WCF.ACP.Package.Installation._getParameters()
826 */
827 _getParameters: function() {
828 return {
829 packageID: this._packageID,
830 step: 'prepare'
831 };
832 }
833 });
834
835 /**
836 * Manages package search.
837 */
838 WCF.ACP.Package.Search = Class.extend({
839 /**
840 * search button
841 * @var jQuery
842 */
843 _button: null,
844
845 /**
846 * list of cached pages
847 * @var object
848 */
849 _cache: { },
850
851 /**
852 * search container
853 * @var jQuery
854 */
855 _container: null,
856
857 /**
858 * dialog overlay
859 * @var jQuery
860 */
861 _dialog: null,
862
863 /**
864 * package input field
865 * @var jQuery
866 */
867 _package: null,
868
869 /**
870 * package description input field
871 * @var jQuery
872 */
873 _packageDescription: null,
874
875 /**
876 * package name input field
877 * @var jQuery
878 */
879 _packageName: null,
880
881 /**
882 * package search result container
883 * @var jQuery
884 */
885 _packageSearchResultContainer: null,
886
887 /**
888 * package search result list
889 * @var jQuery
890 */
891 _packageSearchResultList: null,
892
893 /**
894 * number of pages
895 * @var integer
896 */
897 _pageCount: 0,
898
899 /**
900 * current page
901 * @var integer
902 */
903 _pageNo: 1,
904
905 /**
906 * action proxy
907 * @var WCF.Action:proxy
908 */
909 _proxy: null,
910
911 /**
912 * search id
913 * @var integer
914 */
915 _searchID: 0,
916
917 /**
918 * currently selected package
919 * @var string
920 */
921 _selectedPackage: '',
922
923 /**
924 * currently selected package's version
925 */
926 _selectedPackageVersion: '',
927
928 /**
929 * Initializes the WCF.ACP.Package.Seach class.
930 */
931 init: function() {
932 this._button = null;
933 this._cache = { };
934 this._container = $('#packageSearch');
935 this._dialog = null;
936 this._package = null;
937 this._packageName = null;
938 this._packageSearchResultContainer = $('#packageSearchResultContainer');
939 this._packageSearchResultList = $('#packageSearchResultList');
940 this._pageCount = 0;
941 this._pageNo = 1;
942 this._searchDescription = null;
943 this._searchID = 0;
944 this._selectedPackage = '';
945 this._selectedPackageVersion = '';
946
947 this._proxy = new WCF.Action.Proxy({
948 success: $.proxy(this._success, this)
949 });
950
951 this._initElements();
952 },
953
954 /**
955 * Initializes search elements.
956 */
957 _initElements: function() {
958 this._button = this._container.find('.formSubmit > button.jsButtonPackageSearch').disable().click($.proxy(this._search, this));
959
960 this._package = $('#package').keyup($.proxy(this._keyUp, this));
961 this._packageDescription = $('#packageDescription').keyup($.proxy(this._keyUp, this));
962 this._packageName = $('#packageName').keyup($.proxy(this._keyUp, this));
963 },
964
965 /**
966 * Handles the 'keyup' event.
967 */
968 _keyUp: function(event) {
969 if (this._package.val() === '' && this._packageDescription.val() === '' && this._packageName.val() === '') {
970 this._button.disable();
971 }
972 else {
973 this._button.enable();
974
975 // submit on [Enter]
976 if (event.which === 13) {
977 this._button.trigger('click');
978 }
979 }
980 },
981
982 /**
983 * Performs a new search.
984 */
985 _search: function() {
986 var $values = this._getSearchValues();
987 if (!this._validate($values)) {
988 return false;
989 }
990
991 $values.pageNo = this._pageNo;
992 this._proxy.setOption('data', {
993 actionName: 'search',
994 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
995 parameters: $values
996 });
997 this._proxy.sendRequest();
998 },
999
1000 /**
1001 * Returns search values.
1002 *
1003 * @return object
1004 */
1005 _getSearchValues: function() {
1006 return {
1007 'package': $.trim(this._package.val()),
1008 packageDescription: $.trim(this._packageDescription.val()),
1009 packageName: $.trim(this._packageName.val())
1010 };
1011 },
1012
1013 /**
1014 * Validates search values.
1015 *
1016 * @param object values
1017 * @return boolean
1018 */
1019 _validate: function(values) {
1020 if (values['package'] === '' && values['packageDescription'] === '' && values['packageName'] === '') {
1021 return false;
1022 }
1023
1024 return true;
1025 },
1026
1027 /**
1028 * Handles successful AJAX requests.
1029 *
1030 * @param object data
1031 * @param string textStatus
1032 * @param jQuery jqXHR
1033 */
1034 _success: function(data, textStatus, jqXHR) {
1035 switch (data.actionName) {
1036 case 'getResultList':
1037 this._insertTemplate(data.returnValues.template);
1038 break;
1039
1040 case 'prepareInstallation':
1041 if (data.returnValues.queueID) {
1042 if (this._dialog !== null) {
1043 this._dialog.wcfDialog('close');
1044 }
1045
1046 var $installation = new WCF.ACP.Package.Installation(data.returnValues.queueID, undefined, false);
1047 $installation.prepareInstallation();
1048 }
1049 else if (data.returnValues.template) {
1050 if (this._dialog === null) {
1051 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
1052 this._dialog.wcfDialog({
1053 title: WCF.Language.get('wcf.acp.package.update.unauthorized')
1054 });
1055 }
1056 else {
1057 this._dialog.html(data.returnValues.template).wcfDialog('open');
1058 }
1059
1060 this._dialog.find('.formSubmit > button').click($.proxy(this._submitAuthentication, this));
1061 }
1062 break;
1063
1064 case 'search':
1065 this._pageCount = data.returnValues.pageCount;
1066 this._searchID = data.returnValues.searchID;
1067
1068 this._insertTemplate(data.returnValues.template, (data.returnValues.count === undefined ? undefined : data.returnValues.count));
1069 this._setupPagination();
1070 break;
1071 }
1072 },
1073
1074 /**
1075 * Submits authentication data for current update server.
1076 *
1077 * @param object event
1078 */
1079 _submitAuthentication: function(event) {
1080 var $usernameField = $('#packageUpdateServerUsername');
1081 var $passwordField = $('#packageUpdateServerPassword');
1082
1083 // remove error messages if any
1084 $usernameField.next('small.innerError').remove();
1085 $passwordField.next('small.innerError').remove();
1086
1087 var $continue = true;
1088 if ($.trim($usernameField.val()) === '') {
1089 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField);
1090 $continue = false;
1091 }
1092
1093 if ($.trim($passwordField.val()) === '') {
1094 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField);
1095 $continue = false;
1096 }
1097
1098 if ($continue) {
1099 this._prepareInstallation($(event.currentTarget).data('packageUpdateServerID'));
1100 }
1101 },
1102
1103 /**
1104 * Inserts search result list template.
1105 *
1106 * @param string template
1107 * @param integer count
1108 */
1109 _insertTemplate: function(template, count) {
1110 this._packageSearchResultContainer.show();
1111
1112 this._packageSearchResultList.html(template);
1113 if (count === undefined) {
1114 this._content[this._pageNo] = template;
1115 }
1116
1117 // update badge count
1118 if (count !== undefined) {
1119 this._content = { 1: template };
1120 this._packageSearchResultContainer.find('> header > h2 > .badge').html(count);
1121 }
1122
1123 // bind listener
1124 this._packageSearchResultList.find('.jsInstallPackage').click($.proxy(this._click, this));
1125 },
1126
1127 /**
1128 * Prepares a package installation.
1129 *
1130 * @param object event
1131 */
1132 _click: function(event) {
1133 var $button = $(event.currentTarget);
1134 WCF.System.Confirmation.show($button.data('confirmMessage'), $.proxy(function(action) {
1135 if (action === 'confirm') {
1136 this._selectedPackage = $button.data('package');
1137 this._selectedPackageVersion = $button.data('packageVersion');
1138 this._prepareInstallation();
1139 }
1140 }, this));
1141 },
1142
1143 /**
1144 * Prepares package installation.
1145 *
1146 * @param integer packageUpdateServerID
1147 */
1148 _prepareInstallation: function(packageUpdateServerID) {
1149 var $parameters = {
1150 'packages': { }
1151 };
1152 $parameters['packages'][this._selectedPackage] = this._selectedPackageVersion;
1153
1154 if (packageUpdateServerID) {
1155 $parameters.authData = {
1156 packageUpdateServerID: packageUpdateServerID,
1157 password: $.trim($('#packageUpdateServerPassword').val()),
1158 saveCredentials: ($('#packageUpdateServerSaveCredentials:checked').length ? true : false),
1159 username: $.trim($('#packageUpdateServerUsername').val())
1160 };
1161 }
1162
1163 this._proxy.setOption('data', {
1164 actionName: 'prepareInstallation',
1165 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1166 parameters: $parameters
1167 });
1168 this._proxy.sendRequest();
1169 },
1170
1171 /**
1172 * Setups pagination for current search.
1173 */
1174 _setupPagination: function() {
1175 // remove previous instances
1176 this._content = { 1: this._packageSearchResultList.html() };
1177 this._packageSearchResultContainer.find('.pageNavigation').wcfPages('destroy').remove();
1178
1179 if (this._pageCount > 1) {
1180 // TODO: Fix ui.wcfPages to properly synchronize multiple instances without triggering events
1181 /*$('<div class="contentNavigation" />').insertBefore(this._packageSearchResultList).wcfPages({
1182 activePage: this._pageNo,
1183 maxPage: this._pageCount
1184 }).on('wcfpagesswitched', $.proxy(this._showPage, this));*/
1185
1186 $('<div class="contentNavigation" />').insertAfter(this._packageSearchResultList).wcfPages({
1187 activePage: this._pageNo,
1188 maxPage: this._pageCount
1189 }).on('wcfpagesswitched', $.proxy(this._showPage, this));
1190 }
1191 },
1192
1193 /**
1194 * Displays requested pages or loads it.
1195 *
1196 * @param object event
1197 * @param object data
1198 */
1199 _showPage: function(event, data) {
1200 if (data && data.activePage) {
1201 this._pageNo = data.activePage;
1202 }
1203
1204 // validate page no
1205 if (this._pageNo < 1 || this._pageNo > this._pageCount) {
1206 console.debug("[WCF.ACP.Package.Search] Cannot access page " + this._pageNo + " of " + this._pageCount);
1207 return;
1208 }
1209
1210 // load content
1211 if (this._content[this._pageNo] === undefined) {
1212 this._proxy.setOption('data', {
1213 actionName: 'getResultList',
1214 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1215 parameters: {
1216 pageNo: this._pageNo,
1217 searchID: this._searchID
1218 }
1219 });
1220 this._proxy.sendRequest();
1221 }
1222 else {
1223 // show cached content
1224 this._packageSearchResultList.html(this._content[this._pageNo]);
1225
1226 WCF.DOMNodeInsertedHandler.execute();
1227 }
1228 }
1229 });
1230
1231 WCF.ACP.Package.Server = { };
1232
1233 WCF.ACP.Package.Server.Installation = Class.extend({
1234 _proxy: null,
1235 _selectedPackage: '',
1236
1237 init: function() {
1238 this._dialog = null;
1239 this._selectedPackage = null;
1240
1241 this._proxy = new WCF.Action.Proxy({
1242 success: $.proxy(this._success, this)
1243 });
1244 },
1245
1246 bind: function() {
1247 $('.jsButtonPackageInstall').removeClass('jsButtonPackageInstall').click($.proxy(this._click, this));
1248 },
1249
1250 /**
1251 * Prepares a package installation.
1252 *
1253 * @param object event
1254 */
1255 _click: function(event) {
1256 var $button = $(event.currentTarget);
1257 WCF.System.Confirmation.show($button.data('confirmMessage'), $.proxy(function(action) {
1258 if (action === 'confirm') {
1259 this._selectedPackage = $button.data('package');
1260 this._selectedPackageVersion = $button.data('packageVersion');
1261 this._prepareInstallation();
1262 }
1263 }, this));
1264 },
1265
1266 /**
1267 * Handles successful AJAX requests.
1268 *
1269 * @param object data
1270 */
1271 _success: function(data) {
1272 if (data.returnValues.queueID) {
1273 if (this._dialog !== null) {
1274 this._dialog.wcfDialog('close');
1275 }
1276
1277 var $installation = new WCF.ACP.Package.Installation(data.returnValues.queueID, undefined, false);
1278 $installation.prepareInstallation();
1279 }
1280 else if (data.returnValues.template) {
1281 if (this._dialog === null) {
1282 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
1283 this._dialog.wcfDialog({
1284 title: WCF.Language.get('wcf.acp.package.update.unauthorized')
1285 });
1286 }
1287 else {
1288 this._dialog.html(data.returnValues.template).wcfDialog('open');
1289 }
1290
1291 this._dialog.find('.formSubmit > button').click($.proxy(this._submitAuthentication, this));
1292 }
1293 },
1294
1295 /**
1296 * Submits authentication data for current update server.
1297 *
1298 * @param object event
1299 */
1300 _submitAuthentication: function(event) {
1301 var $usernameField = $('#packageUpdateServerUsername');
1302 var $passwordField = $('#packageUpdateServerPassword');
1303
1304 // remove error messages if any
1305 $usernameField.next('small.innerError').remove();
1306 $passwordField.next('small.innerError').remove();
1307
1308 var $continue = true;
1309 if ($.trim($usernameField.val()) === '') {
1310 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField);
1311 $continue = false;
1312 }
1313
1314 if ($.trim($passwordField.val()) === '') {
1315 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField);
1316 $continue = false;
1317 }
1318
1319 if ($continue) {
1320 this._prepareInstallation($(event.currentTarget).data('packageUpdateServerID'));
1321 }
1322 },
1323
1324 /**
1325 * Prepares package installation.
1326 *
1327 * @param integer packageUpdateServerID
1328 */
1329 _prepareInstallation: function(packageUpdateServerID) {
1330 var $parameters = {
1331 'packages': { }
1332 };
1333 $parameters['packages'][this._selectedPackage] = this._selectedPackageVersion;
1334
1335 if (packageUpdateServerID) {
1336 $parameters.authData = {
1337 packageUpdateServerID: packageUpdateServerID,
1338 password: $.trim($('#packageUpdateServerPassword').val()),
1339 saveCredentials: ($('#packageUpdateServerSaveCredentials:checked').length ? true : false),
1340 username: $.trim($('#packageUpdateServerUsername').val())
1341 };
1342 }
1343
1344 this._proxy.setOption('data', {
1345 actionName: 'prepareInstallation',
1346 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1347 parameters: $parameters
1348 });
1349 this._proxy.sendRequest();
1350 },
1351 });
1352
1353 /**
1354 * Namespace for package update related classes.
1355 */
1356 WCF.ACP.Package.Update = { };
1357
1358 /**
1359 * Handles the package update process.
1360 */
1361 WCF.ACP.Package.Update.Manager = Class.extend({
1362 /**
1363 * dialog overlay
1364 * @var jQuery
1365 */
1366 _dialog: null,
1367
1368 /**
1369 * action proxy
1370 * @var WCF.Action.Proxy
1371 */
1372 _proxy: null,
1373
1374 /**
1375 * submit button
1376 * @var jQuery
1377 */
1378 _submitButton: null,
1379
1380 /**
1381 * Initializes the WCF.ACP.Package.Update.Manager class.
1382 */
1383 init: function() {
1384 this._dialog = null;
1385 this._submitButton = $('.formSubmit > button').click($.proxy(this._click, this));
1386
1387 this._proxy = new WCF.Action.Proxy({
1388 success: $.proxy(this._success, this)
1389 });
1390
1391 $('.jsPackageUpdate').each($.proxy(function(index, packageUpdate) {
1392 var $packageUpdate = $(packageUpdate);
1393 $packageUpdate.find('input[type=checkbox]').data('packageUpdate', $packageUpdate).change($.proxy(this._change, this));
1394 }, this));
1395 },
1396
1397 /**
1398 * Handles toggles for a specific update.
1399 */
1400 _change: function(event) {
1401 var $checkbox = $(event.currentTarget);
1402
1403 if ($checkbox.is(':checked')) {
1404 $checkbox.data('packageUpdate').find('select').enable();
1405 $checkbox.data('packageUpdate').find('dl').removeClass('disabled');
1406
1407 this._submitButton.enable();
1408 }
1409 else {
1410 $checkbox.data('packageUpdate').find('select').disable();
1411 $checkbox.data('packageUpdate').find('dl').addClass('disabled');
1412
1413 // disable submit button
1414 if (!$('input[type=checkbox]:checked').length) {
1415 this._submitButton.disable();
1416 }
1417 else {
1418 this._submitButton.enable();
1419 }
1420 }
1421 },
1422
1423 /**
1424 * Handles clicks on the submit button.
1425 *
1426 * @param object event
1427 * @param integer packageUpdateServerID
1428 */
1429 _click: function(event, packageUpdateServerID) {
1430 var $packages = { };
1431 $('.jsPackageUpdate').each($.proxy(function(index, packageUpdate) {
1432 var $packageUpdate = $(packageUpdate);
1433 if ($packageUpdate.find('input[type=checkbox]:checked').length) {
1434 $packages[$packageUpdate.data('package')] = $packageUpdate.find('select').val();
1435 }
1436 }, this));
1437
1438 if ($.getLength($packages)) {
1439 this._submitButton.disable();
1440
1441 var $parameters = {
1442 packages: $packages
1443 };
1444 if (packageUpdateServerID) {
1445 $parameters.authData = {
1446 packageUpdateServerID: packageUpdateServerID,
1447 password: $.trim($('#packageUpdateServerPassword').val()),
1448 saveCredentials: ($('#packageUpdateServerSaveCredentials:checked').length ? true : false),
1449 username: $.trim($('#packageUpdateServerUsername').val())
1450 };
1451 }
1452
1453 this._proxy.setOption('data', {
1454 actionName: 'prepareUpdate',
1455 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1456 parameters: $parameters,
1457 });
1458 this._proxy.sendRequest();
1459 }
1460 },
1461
1462 /**
1463 * Handles successful AJAX requests.
1464 *
1465 * @param object data
1466 * @param string textStatus
1467 * @param jQuery jqXHR
1468 */
1469 _success: function(data, textStatus, jqXHR) {
1470 if (data.returnValues.queueID) {
1471 if (this._dialog !== null) {
1472 this._dialog.wcfDialog('close');
1473 }
1474
1475 var $installation = new WCF.ACP.Package.Installation(data.returnValues.queueID, undefined, false, true);
1476 $installation.prepareInstallation();
1477 }
1478 else if (data.returnValues.excludedPackages) {
1479 if (this._dialog === null) {
1480 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
1481 this._dialog.wcfDialog({
1482 title: WCF.Language.get('wcf.acp.package.update.excludedPackages')
1483 });
1484 }
1485 else {
1486 this._dialog.wcfDialog('option', 'title', WCF.Language.get('wcf.acp.package.update.excludedPackages'));
1487 this._dialog.html(data.returnValues.template).wcfDialog('open');
1488 }
1489
1490 this._submitButton.enable();
1491 }
1492 else if (data.returnValues.template) {
1493 if (this._dialog === null) {
1494 this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
1495 this._dialog.wcfDialog({
1496 title: WCF.Language.get('wcf.acp.package.update.unauthorized')
1497 });
1498 }
1499 else {
1500 this._dialog.wcfDialog('option', 'title', WCF.Language.get('wcf.acp.package.update.unauthorized'));
1501 this._dialog.html(data.returnValues.template).wcfDialog('open');
1502 }
1503
1504 this._dialog.find('.formSubmit > button').click($.proxy(this._submitAuthentication, this));
1505 }
1506 },
1507
1508 /**
1509 * Submits authentication data for current update server.
1510 *
1511 * @param object event
1512 */
1513 _submitAuthentication: function(event) {
1514 var $usernameField = $('#packageUpdateServerUsername');
1515 var $passwordField = $('#packageUpdateServerPassword');
1516
1517 // remove error messages if any
1518 $usernameField.next('small.innerError').remove();
1519 $passwordField.next('small.innerError').remove();
1520
1521 var $continue = true;
1522 if ($.trim($usernameField.val()) === '') {
1523 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField);
1524 $continue = false;
1525 }
1526
1527 if ($.trim($passwordField.val()) === '') {
1528 $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField);
1529 $continue = false;
1530 }
1531
1532 if ($continue) {
1533 this._click(undefined, $(event.currentTarget).data('packageUpdateServerID'));
1534 }
1535 }
1536 });
1537
1538 /**
1539 * Searches for available updates.
1540 *
1541 * @param boolean bindOnExistingButtons
1542 */
1543 WCF.ACP.Package.Update.Search = Class.extend({
1544 /**
1545 * dialog overlay
1546 * @var jQuery
1547 */
1548 _dialog: null,
1549
1550 /**
1551 * Initializes the WCF.ACP.Package.SearchForUpdates class.
1552 *
1553 * @param boolean bindOnExistingButtons
1554 */
1555 init: function(bindOnExistingButtons) {
1556 this._dialog = null;
1557
1558 if (bindOnExistingButtons === true) {
1559 $('.jsButtonPackageUpdate').click($.proxy(this._click, this));
1560 }
1561 else {
1562 var $button = $('<li><a class="button"><span class="icon icon16 icon-refresh"></span> <span>' + WCF.Language.get('wcf.acp.package.searchForUpdates') + '</span></a></li>');
1563 $button.click($.proxy(this._click, this)).prependTo($('.contentNavigation:eq(0) > nav:not(.pageNavigation) > ul'));
1564 }
1565 },
1566
1567 /**
1568 * Handles clicks on the search button.
1569 */
1570 _click: function() {
1571 if (this._dialog === null) {
1572 new WCF.Action.Proxy({
1573 autoSend: true,
1574 data: {
1575 actionName: 'searchForUpdates',
1576 className: 'wcf\\data\\package\\update\\PackageUpdateAction',
1577 parameters: {
1578 ignoreCache: 1
1579 }
1580 },
1581 success: $.proxy(this._success, this)
1582 });
1583 }
1584 else {
1585 this._dialog.wcfDialog('open');
1586 }
1587 },
1588
1589 /**
1590 * Handles successful AJAX requests.
1591 *
1592 * @param object data
1593 * @param string textStatus
1594 * @param jQuery jqXHR
1595 */
1596 _success: function(data, textStatus, jqXHR) {
1597 if (data.returnValues.url) {
1598 window.location = data.returnValues.url;
1599 }
1600 else {
1601 this._dialog = $('<div>' + WCF.Language.get('wcf.acp.package.searchForUpdates.noResults') + '</div>').hide().appendTo(document.body);
1602 this._dialog.wcfDialog({
1603 title: WCF.Language.get('wcf.acp.package.searchForUpdates')
1604 });
1605 }
1606 }
1607 });
1608
1609 /**
1610 * Namespace for classes related to the WoltLab Plugin-Store.
1611 */
1612 WCF.ACP.PluginStore = { };
1613
1614 /**
1615 * Namespace for classes handling items purchased in the WoltLab Plugin-Store.
1616 */
1617 WCF.ACP.PluginStore.PurchasedItems = { };
1618
1619 /**
1620 * Searches for purchased items available for install but not yet installed.
1621 */
1622 WCF.ACP.PluginStore.PurchasedItems.Search = Class.extend({
1623 /**
1624 * dialog overlay
1625 * @var jQuery
1626 */
1627 _dialog: null,
1628
1629 /**
1630 * action proxy
1631 * @var WCF.Action.Proxy
1632 */
1633 _proxy: null,
1634
1635 /**
1636 * Initializes the WCF.ACP.PluginStore.PurchasedItems.Search class.
1637 */
1638 init: function() {
1639 this._dialog = null;
1640 this._proxy = new WCF.Action.Proxy({
1641 success: $.proxy(this._success, this)
1642 });
1643
1644 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>');
1645 $button.prependTo($('.contentNavigation:eq(0) > nav:not(.pageNavigation) > ul')).click($.proxy(this._click, this));
1646 },
1647
1648 /**
1649 * Handles clicks on the search button.
1650 */
1651 _click: function() {
1652 this._proxy.setOption('data', {
1653 actionName: 'searchForPurchasedItems',
1654 className: 'wcf\\data\\package\\PackageAction'
1655 });
1656 this._proxy.sendRequest();
1657 },
1658
1659 /**
1660 * Handles successful AJAX requests.
1661 *
1662 * @param object data
1663 * @param string textStatus
1664 * @param jQuery jqXHR
1665 */
1666 _success: function(data, textStatus, jqXHR) {
1667 // prompt for credentials
1668 if (data.returnValues.template) {
1669 if (this._dialog === null) {
1670 this._dialog = $('<div />').hide().appendTo(document.body);
1671 this._dialog.html(data.returnValues.template).wcfDialog({
1672 title: WCF.Language.get('wcf.acp.pluginStore.authorization')
1673 });
1674 }
1675 else {
1676 this._dialog.html(data.returnValues.template);
1677 this._dialog.wcfDialog('open');
1678 }
1679
1680 var $button = this._dialog.find('button').click($.proxy(this._submit, this));
1681 this._dialog.find('input').keyup(function(event) {
1682 if (event.which == $.ui.keyCode.ENTER) {
1683 $button.trigger('click');
1684 return false;
1685 }
1686 });
1687 }
1688 else if (data.returnValues.noResults) {
1689 // there are no purchased products yet
1690 if (this._dialog === null) {
1691 this._dialog = $('<div />').hide().appendTo(document.body);
1692 this._dialog.html(data.returnValues.noResults).wcfDialog({
1693 title: WCF.Language.get('wcf.acp.pluginStore.purchasedItems')
1694 });
1695 } else {
1696 this._dialog.wcfDialog('option', 'title', WCF.Language.get('wcf.acp.pluginStore.purchasedItems'));
1697 this._dialog.html(data.returnValues.noResults);
1698 this._dialog.wcfDialog('open');
1699 }
1700 }
1701 else if (data.returnValues.noSSL) {
1702 // PHP was compiled w/o OpenSSL support
1703 if (this._dialog === null) {
1704 this._dialog = $('<div />').hide().appendTo(document.body);
1705 this._dialog.html(data.returnValues.noSSL).wcfDialog({
1706 title: WCF.Language.get('wcf.global.error.title')
1707 });
1708 }
1709 else {
1710 this._dialog.wcfDialog('option', 'title', WCF.Language.get('wcf.global.error.title'));
1711 this._dialog.html(data.returnValues.noSSL);
1712 this._dialog.wcfDialog('open');
1713 }
1714 }
1715 else if (data.returnValues.redirectURL) {
1716 // redirect to list of purchased products
1717 window.location = data.returnValues.redirectURL;
1718 }
1719 },
1720
1721 /**
1722 * Submits the user credentials.
1723 */
1724 _submit: function() {
1725 this._dialog.wcfDialog('close');
1726
1727 this._proxy.setOption('data', {
1728 actionName: 'searchForPurchasedItems',
1729 className: 'wcf\\data\\package\\PackageAction',
1730 parameters: {
1731 password: $('#pluginStorePassword').val(),
1732 username: $('#pluginStoreUsername').val()
1733 }
1734 });
1735 this._proxy.sendRequest();
1736 }
1737 });
1738
1739 /**
1740 * Worker support for ACP.
1741 *
1742 * @param string dialogID
1743 * @param string className
1744 * @param string title
1745 * @param object parameters
1746 * @param object callback
1747 */
1748 WCF.ACP.Worker = Class.extend({
1749 /**
1750 * worker aborted
1751 * @var boolean
1752 */
1753 _aborted: false,
1754
1755 /**
1756 * callback invoked after worker completed
1757 * @var object
1758 */
1759 _callback: null,
1760
1761 /**
1762 * dialog id
1763 * @var string
1764 */
1765 _dialogID: null,
1766
1767 /**
1768 * dialog object
1769 * @var jQuery
1770 */
1771 _dialog: null,
1772
1773 /**
1774 * action proxy
1775 * @var WCF.Action.Proxy
1776 */
1777 _proxy: null,
1778
1779 /**
1780 * dialog title
1781 * @var string
1782 */
1783 _title: '',
1784
1785 /**
1786 * Initializes a new worker instance.
1787 *
1788 * @param string dialogID
1789 * @param string className
1790 * @param string title
1791 * @param object parameters
1792 * @param object callback
1793 * @param object confirmMessage
1794 */
1795 init: function(dialogID, className, title, parameters, callback) {
1796 this._aborted = false;
1797 this._callback = callback || null;
1798 this._dialogID = dialogID + 'Worker';
1799 this._dialog = null;
1800 this._proxy = new WCF.Action.Proxy({
1801 autoSend: true,
1802 data: {
1803 className: className,
1804 parameters: parameters || { }
1805 },
1806 showLoadingOverlay: false,
1807 success: $.proxy(this._success, this),
1808 url: 'index.php?worker-proxy/&t=' + SECURITY_TOKEN + SID_ARG_2ND
1809 });
1810 this._title = title;
1811 },
1812
1813 /**
1814 * Handles response from server.
1815 *
1816 * @param object data
1817 */
1818 _success: function(data) {
1819 // init binding
1820 if (this._dialog === null) {
1821 this._dialog = $('<div id="' + this._dialogID + '" />').hide().appendTo(document.body);
1822 this._dialog.wcfDialog({
1823 closeConfirmMessage: WCF.Language.get('wcf.acp.worker.abort.confirmMessage'),
1824 closeViaModal: false,
1825 onClose: $.proxy(function() {
1826 this._aborted = true;
1827 this._proxy.abortPrevious();
1828
1829 window.location.reload();
1830 }, this),
1831 title: this._title
1832 });
1833 }
1834
1835 if (this._aborted) {
1836 return;
1837 }
1838
1839 if (data.template) {
1840 this._dialog.html(data.template);
1841 }
1842
1843 // update progress
1844 this._dialog.find('progress').attr('value', data.progress).text(data.progress + '%').next('span').text(data.progress + '%');
1845
1846 // worker is still busy with its business, carry on
1847 if (data.progress < 100) {
1848 // send request for next loop
1849 this._proxy.setOption('data', {
1850 className: data.className,
1851 loopCount: data.loopCount,
1852 parameters: data.parameters
1853 });
1854 this._proxy.sendRequest();
1855 }
1856 else if (this._callback !== null) {
1857 this._callback(this, data);
1858 }
1859 else {
1860 // display continue button
1861 var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
1862 $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($formSubmit).focus().click(function() { window.location = data.proceedURL; });
1863
1864 this._dialog.wcfDialog('render');
1865 }
1866 }
1867 });
1868
1869 /**
1870 * Namespace for category-related functions.
1871 */
1872 WCF.ACP.Category = { };
1873
1874 /**
1875 * Handles collapsing categories.
1876 *
1877 * @param string className
1878 * @param integer objectTypeID
1879 */
1880 WCF.ACP.Category.Collapsible = WCF.Collapsible.SimpleRemote.extend({
1881 /**
1882 * @see WCF.Collapsible.Remote.init()
1883 */
1884 init: function(className) {
1885 var sortButton = $('.formSubmit > button[data-type="submit"]');
1886 if (sortButton) {
1887 sortButton.click($.proxy(this._sort, this));
1888 }
1889
1890 this._super(className);
1891 },
1892
1893 /**
1894 * @see WCF.Collapsible.Remote._getButtonContainer()
1895 */
1896 _getButtonContainer: function(containerID) {
1897 return $('#' + containerID + ' > .buttons');
1898 },
1899
1900 /**
1901 * @see WCF.Collapsible.Remote._getContainers()
1902 */
1903 _getContainers: function() {
1904 return $('.jsCategory').has('ol').has('li');
1905 },
1906
1907 /**
1908 * @see WCF.Collapsible.Remote._getTarget()
1909 */
1910 _getTarget: function(containerID) {
1911 return $('#' + containerID + ' > ol');
1912 },
1913
1914 /**
1915 * Handles a click on the sort button.
1916 */
1917 _sort: function() {
1918 // remove existing collapsible buttons
1919 $('.collapsibleButton').remove();
1920
1921 // reinit containers
1922 this._containers = { };
1923 this._containerData = { };
1924
1925 var $containers = this._getContainers();
1926 if ($containers.length == 0) {
1927 console.debug('[WCF.ACP.Category.Collapsible] Empty container set given, aborting.');
1928 }
1929 $containers.each($.proxy(function(index, container) {
1930 var $container = $(container);
1931 var $containerID = $container.wcfIdentify();
1932 this._containers[$containerID] = $container;
1933
1934 this._initContainer($containerID);
1935 }, this));
1936 }
1937 });
1938
1939 /**
1940 * Provides the search dropdown for ACP
1941 *
1942 * @see WCF.Search.Base
1943 */
1944 WCF.ACP.Search = WCF.Search.Base.extend({
1945 /**
1946 * @see WCF.Search.Base.init()
1947 */
1948 init: function() {
1949 this._className = 'wcf\\data\\acp\\search\\provider\\ACPSearchProviderAction';
1950 this._super('#search input[name=q]');
1951
1952 // disable form submitting
1953 $('#search > form').on('submit', function(event) {
1954 event.preventDefault();
1955 });
1956 },
1957
1958 /**
1959 * @see WCF.Search.Base._createListItem()
1960 */
1961 _createListItem: function(resultList) {
1962 // add a divider between result lists
1963 if (this._list.children('li').length > 0) {
1964 $('<li class="dropdownDivider" />').appendTo(this._list);
1965 }
1966
1967 // add caption
1968 $('<li class="dropdownText">' + resultList.title + '</li>').appendTo(this._list);
1969
1970 // add menu items
1971 for (var $i in resultList.items) {
1972 var $item = resultList.items[$i];
1973
1974 $('<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);
1975
1976 this._itemCount++;
1977 }
1978 },
1979
1980 /**
1981 * @see WCF.Search.Base._handleEmptyResult()
1982 */
1983 _handleEmptyResult: function() {
1984 $('<li class="dropdownText">' + WCF.Language.get('wcf.acp.search.noResults') + '</li>').appendTo(this._list);
1985
1986 return true;
1987 },
1988
1989 /**
1990 * @see WCF.Search.Base._highlightSelectedElement()
1991 */
1992 _highlightSelectedElement: function() {
1993 this._list.find('li').removeClass('dropdownNavigationItem');
1994 this._list.find('li:not(.dropdownDivider):not(.dropdownText)').eq(this._itemIndex).addClass('dropdownNavigationItem');
1995 },
1996
1997 /**
1998 * @see WCF.Search.Base._selectElement()
1999 */
2000 _selectElement: function(event) {
2001 if (this._itemIndex === -1) {
2002 return false;
2003 }
2004
2005 window.location = this._list.find('li.dropdownNavigationItem > a').attr('href');
2006 }
2007 });
2008
2009 /**
2010 * Namespace for user management.
2011 */
2012 WCF.ACP.User = { };
2013
2014 /**
2015 * Generic implementation to ban users.
2016 */
2017 WCF.ACP.User.BanHandler = {
2018 /**
2019 * callback object
2020 * @var object
2021 */
2022 _callback: null,
2023
2024 /**
2025 * dialog overlay
2026 * @var jQuery
2027 */
2028 _dialog: null,
2029
2030 /**
2031 * action proxy
2032 * @var WCF.Action.Proxy
2033 */
2034 _proxy: null,
2035
2036 /**
2037 * Initializes WCF.ACP.User.BanHandler on first use.
2038 */
2039 init: function() {
2040 this._proxy = new WCF.Action.Proxy({
2041 success: $.proxy(this._success, this)
2042 });
2043
2044 $('.jsBanButton').click($.proxy(function(event) {
2045 var $button = $(event.currentTarget);
2046 if ($button.data('banned')) {
2047 this.unban([ $button.data('objectID') ]);
2048 }
2049 else {
2050 this.ban([ $button.data('objectID') ]);
2051 }
2052 }, this));
2053
2054 // bind listener
2055 $('.jsClipboardEditor').each($.proxy(function(index, container) {
2056 var $container = $(container);
2057 var $types = eval($container.data('types'));
2058 if (WCF.inArray('com.woltlab.wcf.user', $types)) {
2059 $container.on('clipboardAction', $.proxy(this._execute, this));
2060 return false;
2061 }
2062 }, this));
2063 },
2064
2065 /**
2066 * Handles clipboard actions.
2067 *
2068 * @param object event
2069 * @param string type
2070 * @param string actionName
2071 * @param object parameters
2072 */
2073 _execute: function(event, type, actionName, parameters) {
2074 if (actionName == 'com.woltlab.wcf.user.ban') {
2075 this.ban(parameters.objectIDs);
2076 }
2077 },
2078
2079 /**
2080 * Unbans users.
2081 *
2082 * @param array<integer> userIDs
2083 */
2084 unban: function(userIDs) {
2085 this._proxy.setOption('data', {
2086 actionName: 'unban',
2087 className: 'wcf\\data\\user\\UserAction',
2088 objectIDs: userIDs
2089 });
2090 this._proxy.sendRequest();
2091 },
2092
2093 /**
2094 * Bans users.
2095 *
2096 * @param array<integer> userIDs
2097 */
2098 ban: function(userIDs) {
2099 if (this._dialog === null) {
2100 // create dialog
2101 this._dialog = $('<div />').hide().appendTo(document.body);
2102 this._dialog.append($('<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><dl><dt></dt><dd><label for="userBanNeverExpires"><input type="checkbox" name="userBanNeverExpires" id="userBanNeverExpires" checked="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></fieldset>'));
2103 this._dialog.append($('<div class="formSubmit"><button class="buttonPrimary" accesskey="s">' + WCF.Language.get('wcf.global.button.submit') + '</button></div>'));
2104
2105 this._dialog.find('#userBanNeverExpires').change(function() {
2106 $('#userBanExpiresSettings').toggle();
2107 });
2108
2109 this._dialog.find('button').click($.proxy(this._submit, this));
2110 }
2111 else {
2112 // reset dialog
2113 $('#userBanReason').val('');
2114 $('#userBanNeverExpires').prop('checked', true);
2115 $('#userBanExpiresSettings').hide();
2116 $('#userBanExpiresDatePicker, #userBanExpires').val('');
2117 }
2118
2119 this._dialog.data('userIDs', userIDs);
2120 this._dialog.wcfDialog({
2121 title: WCF.Language.get('wcf.acp.user.ban.sure')
2122 });
2123 },
2124
2125 /**
2126 * Handles submitting the ban dialog.
2127 */
2128 _submit: function() {
2129 this._dialog.find('.innerError').remove();
2130
2131 var $banExpires = '';
2132 if (!$('#userBanNeverExpires').is(':checked')) {
2133 var $banExpires = $('#userBanExpiresDatePicker').val();
2134 if (!$banExpires) {
2135 this._dialog.find('#userBanExpiresSettings > dd > small').prepend($('<small class="innerError" />').text(WCF.Language.get('wcf.global.form.error.empty')));
2136 return
2137 }
2138 }
2139
2140 this._proxy.setOption('data', {
2141 actionName: 'ban',
2142 className: 'wcf\\data\\user\\UserAction',
2143 objectIDs: this._dialog.data('userIDs'),
2144 parameters: {
2145 banReason: $('#userBanReason').val(),
2146 banExpires: $banExpires
2147 }
2148 });
2149 this._proxy.sendRequest();
2150 },
2151
2152 /**
2153 * Handles successful AJAX calls.
2154 *
2155 * @param object data
2156 * @param string textStatus
2157 * @param jQuery jqXHR
2158 */
2159 _success: function(data, textStatus, jqXHR) {
2160 $('.jsBanButton').each(function(index, button) {
2161 var $button = $(button);
2162 if (WCF.inArray($button.data('objectID'), data.objectIDs)) {
2163 if (data.actionName == 'unban') {
2164 $button.data('banned', false).data('tooltip', $button.data('banMessage')).removeClass('icon-lock').addClass('icon-unlock');
2165 }
2166 else {
2167 $button.data('banned', true).data('tooltip', $button.data('unbanMessage')).removeClass('icon-unlock').addClass('icon-lock');
2168 }
2169 }
2170 });
2171
2172 var $notification = new WCF.System.Notification();
2173 $notification.show();
2174
2175 WCF.Clipboard.reload();
2176
2177 if (data.actionName == 'ban') {
2178 this._dialog.wcfDialog('close');
2179 }
2180 }
2181 };
2182
2183 /**
2184 * Namespace for user group management.
2185 */
2186 WCF.ACP.User.Group = { };
2187
2188 /**
2189 * Handles copying user groups.
2190 */
2191 WCF.ACP.User.Group.Copy = Class.extend({
2192 /**
2193 * id of the copied group
2194 * @var integer
2195 */
2196 _groupID: 0,
2197
2198 /**
2199 * Initializes a new instance of WCF.ACP.User.Group.Copy.
2200 *
2201 * @param integer groupID
2202 */
2203 init: function(groupID) {
2204 this._groupID = groupID;
2205
2206 $('.jsButtonUserGroupCopy').click($.proxy(this._click, this));
2207 },
2208
2209 /**
2210 * Handles clicking on a 'copy user group' button.
2211 */
2212 _click: function() {
2213 var $template = $('<div />');
2214 $template.append($('<dl class="wide marginTop"><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>'));
2215 $template.append($('<dl class="wide marginTopSmall"><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>'));
2216 $template.append($('<dl class="wide marginTopSmall"><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>'));
2217
2218 WCF.System.Confirmation.show(WCF.Language.get('wcf.acp.group.copy.confirmMessage'), $.proxy(function(action) {
2219 if (action === 'confirm') {
2220 new WCF.Action.Proxy({
2221 autoSend: true,
2222 data: {
2223 actionName: 'copy',
2224 className: 'wcf\\data\\user\\group\\UserGroupAction',
2225 objectIDs: [ this._groupID ],
2226 parameters: {
2227 copyACLOptions: $('#copyACLOptions').is(':checked'),
2228 copyMembers: $('#copyMembers').is(':checked'),
2229 copyUserGroupOptions: $('#copyUserGroupOptions').is(':checked')
2230 }
2231 },
2232 success: function(data) {
2233 window.location = data.returnValues.redirectURL;
2234 }
2235 });
2236 }
2237 }, this), '', $template);
2238 }
2239 });
2240
2241 /**
2242 * Generic implementation to enable users.
2243 */
2244 WCF.ACP.User.EnableHandler = {
2245 /**
2246 * action proxy
2247 * @var WCF.Action.Proxy
2248 */
2249 _proxy: null,
2250
2251 /**
2252 * Initializes WCF.ACP.User.EnableHandler on first use.
2253 */
2254 init: function() {
2255 this._proxy = new WCF.Action.Proxy({
2256 success: $.proxy(this._success, this)
2257 });
2258
2259 $('.jsEnableButton').click($.proxy(function(event) {
2260 var $button = $(event.currentTarget);
2261 if ($button.data('enabled')) {
2262 this.disable([ $button.data('objectID') ]);
2263 }
2264 else {
2265 this.enable([ $button.data('objectID') ]);
2266 }
2267 }, this));
2268
2269 // bind listener
2270 $('.jsClipboardEditor').each($.proxy(function(index, container) {
2271 var $container = $(container);
2272 var $types = eval($container.data('types'));
2273 if (WCF.inArray('com.woltlab.wcf.user', $types)) {
2274 $container.on('clipboardAction', $.proxy(this._execute, this));
2275 return false;
2276 }
2277 }, this));
2278 },
2279
2280 /**
2281 * Handles clipboard actions.
2282 *
2283 * @param object event
2284 * @param string type
2285 * @param string actionName
2286 * @param object parameters
2287 */
2288 _execute: function(event, type, actionName, parameters) {
2289 if (actionName == 'com.woltlab.wcf.user.enable') {
2290 this.enable(parameters.objectIDs);
2291 }
2292 },
2293
2294 /**
2295 * Disables users.
2296 *
2297 * @param array<integer> userIDs
2298 */
2299 disable: function(userIDs) {
2300 this._proxy.setOption('data', {
2301 actionName: 'disable',
2302 className: 'wcf\\data\\user\\UserAction',
2303 objectIDs: userIDs
2304 });
2305 this._proxy.sendRequest();
2306 },
2307
2308 /**
2309 * Enables users.
2310 *
2311 * @param array<integer> userIDs
2312 */
2313 enable: function(userIDs) {
2314 this._proxy.setOption('data', {
2315 actionName: 'enable',
2316 className: 'wcf\\data\\user\\UserAction',
2317 objectIDs: userIDs
2318 });
2319 this._proxy.sendRequest();
2320 },
2321
2322 /**
2323 * Handles successful AJAX calls.
2324 *
2325 * @param object data
2326 * @param string textStatus
2327 * @param jQuery jqXHR
2328 */
2329 _success: function(data, textStatus, jqXHR) {
2330 $('.jsEnableButton').each(function(index, button) {
2331 var $button = $(button);
2332 if (WCF.inArray($button.data('objectID'), data.objectIDs)) {
2333 if (data.actionName == 'disable') {
2334 $button.data('enabled', false).data('tooltip', $button.data('enableMessage')).removeClass('icon-check').addClass('icon-check-empty');
2335 }
2336 else {
2337 $button.data('enabled', true).data('tooltip', $button.data('disableMessage')).removeClass('icon-check-empty').addClass('icon-check');
2338 }
2339 }
2340 });
2341
2342 var $notification = new WCF.System.Notification();
2343 $notification.show(function() { window.location.reload(); });
2344 }
2345 };
2346
2347 /**
2348 * Handles the send new password clipboard action.
2349 */
2350 WCF.ACP.User.SendNewPasswordHandler = {
2351 /**
2352 * action proxy
2353 * @var WCF.Action.Proxy
2354 */
2355 _proxy: null,
2356
2357 /**
2358 * Initializes WCF.ACP.User.SendNewPasswordHandler on first use.
2359 */
2360 init: function() {
2361 this._proxy = new WCF.Action.Proxy({
2362 success: $.proxy(this._success, this)
2363 });
2364
2365 // bind clipboard event listener
2366 $('.jsClipboardEditor').each($.proxy(function(index, container) {
2367 var $container = $(container);
2368 var $types = eval($container.data('types'));
2369 if (WCF.inArray('com.woltlab.wcf.user', $types)) {
2370 $container.on('clipboardAction', $.proxy(this._execute, this));
2371 return false;
2372 }
2373 }, this));
2374 },
2375
2376 /**
2377 * Handles clipboard actions.
2378 *
2379 * @param object event
2380 * @param string type
2381 * @param string actionName
2382 * @param object parameters
2383 */
2384 _execute: function(event, type, actionName, parameters) {
2385 if (actionName == 'com.woltlab.wcf.user.sendNewPassword') {
2386 WCF.System.Confirmation.show(parameters.confirmMessage, function(action) {
2387 if (action === 'confirm') {
2388 new WCF.ACP.Worker('sendingNewPasswords', 'wcf\\system\\worker\\SendNewPasswordWorker', WCF.Language.get('wcf.acp.user.sendNewPassword.workerTitle'), {
2389 userIDs: parameters.objectIDs
2390 });
2391 }
2392 });
2393 }
2394 }
2395 };
2396
2397 /**
2398 * Namespace for import-related classes.
2399 */
2400 WCF.ACP.Import = { };
2401
2402 /**
2403 * Importer for ACP.
2404 *
2405 * @param array<string> objectTypes
2406 */
2407 WCF.ACP.Import.Manager = Class.extend({
2408 /**
2409 * current action
2410 * @var string
2411 */
2412 _currentAction: '',
2413
2414 /**
2415 * dialog overlay
2416 * @var jQuery
2417 */
2418 _dialog: null,
2419
2420 /**
2421 * current object type index
2422 * @var integer
2423 */
2424 _index: -1,
2425
2426 /**
2427 * list of object types
2428 * @var array<string>
2429 */
2430 _objectTypes: [ ],
2431
2432 /**
2433 * action proxy
2434 * @var WCF.Action.Proxy
2435 */
2436 _proxy: null,
2437
2438 /**
2439 * redirect URL
2440 * @var string
2441 */
2442 _redirectURL: '',
2443
2444 /**
2445 * Initializes the WCF.ACP.Importer object.
2446 *
2447 * @param array<string> objectTypes
2448 * @param string redirectURL
2449 */
2450 init: function(objectTypes, redirectURL) {
2451 this._currentAction = '';
2452 this._index = -1;
2453 this._objectTypes = objectTypes;
2454 this._proxy = new WCF.Action.Proxy({
2455 showLoadingOverlay: false,
2456 success: $.proxy(this._success, this),
2457 url: 'index.php?worker-proxy/&t=' + SECURITY_TOKEN + SID_ARG_2ND
2458 });
2459 this._redirectURL = redirectURL;
2460
2461 this._invoke();
2462 },
2463
2464 /**
2465 * Invokes importing of an object type.
2466 */
2467 _invoke: function() {
2468 this._index++;
2469 if (this._index >= this._objectTypes.length) {
2470 this._dialog.find('.icon-spinner').removeClass('icon-spinner').addClass('icon-ok');
2471 this._dialog.find('h1').text(WCF.Language.get('wcf.acp.dataImport.completed'));
2472
2473 var $form = $('<div class="formSubmit" />').appendTo(this._dialog.find('#workerContainer'));
2474 $('<button>' + WCF.Language.get('wcf.global.button.next') + '</button>').click($.proxy(function() {
2475 new WCF.Action.Proxy({
2476 autoSend: true,
2477 data: {
2478 noRedirect: 1
2479 },
2480 dataType: 'html',
2481 success: $.proxy(function() {
2482 window.location = this._redirectURL;
2483 }, this),
2484 url: 'index.php?cache-clear/&t=' + SECURITY_TOKEN + SID_ARG_2ND
2485 });
2486 }, this)).appendTo($form);
2487
2488 this._dialog.wcfDialog('render');
2489 }
2490 else {
2491 this._run(
2492 WCF.Language.get('wcf.acp.dataImport.data.' + this._objectTypes[this._index]),
2493 this._objectTypes[this._index]
2494 );
2495 }
2496 },
2497
2498 /**
2499 * Executes import of given object type.
2500 *
2501 * @param string currentAction
2502 * @param string objectType
2503 */
2504 _run: function(currentAction, objectType) {
2505 this._currentAction = currentAction;
2506 this._proxy.setOption('data', {
2507 className: 'wcf\\system\\worker\\ImportWorker',
2508 parameters: {
2509 objectType: objectType
2510 }
2511 });
2512 this._proxy.sendRequest();
2513 },
2514
2515 /**
2516 * Handles response from server.
2517 *
2518 * @param object data
2519 */
2520 _success: function(data) {
2521 // init binding
2522 if (this._dialog === null) {
2523 this._dialog = $('<div />').hide().appendTo(document.body);
2524 this._dialog.wcfDialog({
2525 closable: false,
2526 title: WCF.Language.get('wcf.acp.dataImport')
2527 });
2528 }
2529
2530 if (data.template) {
2531 this._dialog.html(data.template);
2532 }
2533
2534 if (this._currentAction) {
2535 this._dialog.find('h1').text(this._currentAction);
2536 }
2537
2538 // update progress
2539 this._dialog.find('progress').attr('value', data.progress).text(data.progress + '%').next('span').text(data.progress + '%');
2540
2541 // worker is still busy with it's business, carry on
2542 if (data.progress < 100) {
2543 // send request for next loop
2544 this._proxy.setOption('data', {
2545 className: data.className,
2546 loopCount: data.loopCount,
2547 parameters: data.parameters
2548 });
2549 this._proxy.sendRequest();
2550 }
2551 else {
2552 this._invoke();
2553 }
2554 }
2555 });
2556
2557 /**
2558 * Namespace for stat-related classes.
2559 */
2560 WCF.ACP.Stat = { };
2561
2562 /**
2563 * Shows the daily stat chart.
2564 */
2565 WCF.ACP.Stat.Chart = Class.extend({
2566 init: function() {
2567 this._proxy = new WCF.Action.Proxy({
2568 success: $.proxy(this._success, this)
2569 });
2570
2571 $('#statRefreshButton').click($.proxy(this._refresh, this));
2572
2573 this._refresh();
2574 },
2575
2576 _refresh: function() {
2577 var $objectTypeIDs = [ ];
2578 $('input[name=objectTypeID]:checked').each(function() {
2579 $objectTypeIDs.push($(this).val());
2580 });
2581
2582 if (!$objectTypeIDs.length) return;
2583
2584 this._proxy.setOption('data', {
2585 className: 'wcf\\data\\stat\\daily\\StatDailyAction',
2586 actionName: 'getData',
2587 parameters: {
2588 startDate: $('#startDateDatePicker').val(),
2589 endDate: $('#endDateDatePicker').val(),
2590 value: $('input[name=value]:checked').val(),
2591 dateGrouping: $('input[name=dateGrouping]:checked').val(),
2592 objectTypeIDs: $objectTypeIDs
2593 }
2594 });
2595 this._proxy.sendRequest();
2596 },
2597
2598 _success: function(data) {
2599 switch ($('input[name=dateGrouping]:checked').val()) {
2600 case 'yearly':
2601 var $minTickSize = [1, "year"];
2602 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.yearly');
2603 break;
2604 case 'monthly':
2605 var $minTickSize = [1, "month"];
2606 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.monthly');
2607 break;
2608 case 'weekly':
2609 var $minTickSize = [7, "day"];
2610 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.weekly');
2611 break;
2612 default:
2613 var $minTickSize = [1, "day"];
2614 var $timeFormat = WCF.Language.get('wcf.acp.stat.timeFormat.daily');
2615 }
2616
2617 var options = {
2618 series: {
2619 lines: {
2620 show: true
2621 },
2622 points: {
2623 show: true
2624 }
2625 },
2626 grid: {
2627 hoverable: true
2628 },
2629 xaxis: {
2630 mode: "time",
2631 minTickSize: $minTickSize,
2632 timeformat: $timeFormat,
2633 monthNames: WCF.Language.get('__monthsShort')
2634 },
2635 yaxis: {
2636 min: 0,
2637 tickDecimals: 0,
2638 tickFormatter: function(val) {
2639 return WCF.String.addThousandsSeparator(val);
2640 }
2641 },
2642 };
2643
2644 var $data = [ ];
2645 for (var $key in data.returnValues) {
2646 var $row = data.returnValues[$key];
2647 for (var $i = 0; $i < $row.data.length; $i++) {
2648 $row.data[$i][0] *= 1000;
2649 }
2650
2651 $data.push($row);
2652 }
2653
2654 $.plot("#chart", $data, options);
2655
2656 $("#chart").on("plothover", function(event, pos, item) {
2657 if (item) {
2658 $("#chartTooltip").html(item.series.xaxis.tickFormatter(item.datapoint[0], item.series.xaxis) + ', ' + WCF.String.formatNumeric(item.datapoint[1]) + ' ' + item.series.label).css({top: item.pageY + 5, left: item.pageX + 5}).wcfFadeIn();
2659 }
2660 else {
2661 $("#chartTooltip").hide();
2662 }
2663 });
2664
2665 if (!$data.length) {
2666 $('#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>');
2667 }
2668 }
2669 });
2670
2671 /**
2672 * Namespace for ACP ad management.
2673 */
2674 WCF.ACP.Ad = { };
2675
2676 /**
2677 * Handles the location of an ad during ad creation/editing.
2678 */
2679 WCF.ACP.Ad.LocationHandler = Class.extend({
2680 /**
2681 * fieldset of the page conditions
2682 * @var jQuery
2683 */
2684 _pageConditions: null,
2685
2686 /**
2687 * select element for the page controller condition
2688 * @var jQuery
2689 */
2690 _pageControllers: null,
2691
2692 /**
2693 * Initializes a new WCF.ACP.Ad.LocationHandler object.
2694 */
2695 init: function() {
2696 this._pageConditions = $('#pageConditions');
2697 this._pageControllers = $('#pageControllers');
2698
2699 var $dl = this._pageControllers.parents('dl:eq(0)');
2700
2701 // hide the page controller element
2702 $dl.hide();
2703
2704 var $fieldset = $dl.parent('fieldset');
2705 if (!$fieldset.children('dl:visible').length) {
2706 $fieldset.hide();
2707 }
2708
2709 var $nextFieldset = $fieldset.next('fieldset');
2710 if ($nextFieldset) {
2711 $nextFieldset.data('margin-top', $nextFieldset.css('margin-top'));
2712 $nextFieldset.css('margin-top', 0);
2713 }
2714
2715 // fix the margin of a potentially next page condition element
2716 $dl.next('dl').css('margin-top', 0);
2717
2718 $('#objectTypeID').on('change', $.proxy(this._setPageController, this));
2719
2720 this._setPageController();
2721
2722 $('#adForm').submit($.proxy(this._submit, this));
2723 },
2724
2725 /**
2726 * Sets the page controller based on the selected ad location.
2727 */
2728 _setPageController: function() {
2729 var $option = $('#objectTypeID').find('option:checked');
2730
2731 // check if the selected ad location is bound to a specific page
2732 if ($option.data('page')) {
2733 // select the related page
2734 this._pageControllers.val([this._pageControllers.find('option[data-object-type="' + $option.data('page') + '"]').val()]).change();
2735 }
2736 else {
2737 this._pageControllers.val([]).change();
2738 }
2739 },
2740
2741 /**
2742 * Handles submitting the ad form.
2743 */
2744 _submit: function() {
2745 if (this._pageConditions.is(':hidden')) {
2746 // remove hidden page condition form elements to avoid creation
2747 // of these conditions
2748 this._pageConditions.find('select, input').remove();
2749 }
2750 else {
2751 // reset page controller conditions to avoid creation of
2752 // unnecessary conditions
2753 this._pageControllers.val([]);
2754 }
2755 }
2756 });
2757
2758 /**
2759 * Initialize WCF.ACP.Tag namespace.
2760 */
2761 WCF.ACP.Tag = { };
2762
2763 /**
2764 * Handles setting tags as synonyms of another tag by clipboard.
2765 */
2766 WCF.ACP.Tag.SetAsSynonymsHandler = Class.extend({
2767 /**
2768 * dialog to select the "main" tag
2769 * @var jQuery
2770 */
2771 _dialog: null,
2772
2773 /**
2774 * ids of the selected tags
2775 * @var array<integer>
2776 */
2777 _objectIDs: [ ],
2778
2779 /**
2780 * Initializes the SetAsSynonymsHandler object.
2781 *
2782 * @param array<integer> objectIDs
2783 */
2784 init: function() {
2785 // bind listener
2786 $('.jsClipboardEditor').each($.proxy(function(index, container) {
2787 var $container = $(container);
2788 var $types = eval($container.data('types'));
2789 if (WCF.inArray('com.woltlab.wcf.tag', $types)) {
2790 $container.on('clipboardAction', $.proxy(this._execute, this));
2791 return false;
2792 }
2793 }, this));
2794 },
2795
2796 /**
2797 * Handles clipboard actions.
2798 *
2799 * @param object event
2800 * @param string type
2801 * @param string actionName
2802 * @param object parameters
2803 */
2804 _execute: function(event, type, actionName, parameters) {
2805 if (type !== 'com.woltlab.wcf.tag' || actionName !== 'com.woltlab.wcf.tag.setAsSynonyms') {
2806 return;
2807 }
2808
2809 this._objectIDs = parameters.objectIDs;
2810 if (this._dialog === null) {
2811 this._dialog = $('<div id="setAsSynonymsDialog" />').hide().appendTo(document.body);
2812 this._dialog.wcfDialog({
2813 closable: false,
2814 title: WCF.Language.get('wcf.acp.tag.setAsSynonyms')
2815 });
2816 }
2817
2818 this._dialog.html(parameters.template);
2819 $button = this._dialog.find('button[data-type="submit"]').disable().click($.proxy(this._submit, this));
2820 this._dialog.find('input[type=radio]').change(function() { $button.enable(); });
2821 },
2822
2823 /**
2824 * Saves the tags as synonyms.
2825 */
2826 _submit: function() {
2827 new WCF.Action.Proxy({
2828 autoSend: true,
2829 data: {
2830 actionName: 'setAsSynonyms',
2831 className: 'wcf\\data\\tag\\TagAction',
2832 objectIDs: this._objectIDs,
2833 parameters: {
2834 tagID: this._dialog.find('input[name="tagID"]:checked').val()
2835 }
2836 },
2837 success: $.proxy(function() {
2838 this._dialog.wcfDialog('close');
2839
2840 new WCF.System.Notification().show(function() {
2841 window.location.reload();
2842 });
2843 }, this)
2844 });
2845 }
2846 });