2 * Class and function collection for WCF ACP
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>
10 * Initialize WCF.ACP namespace
15 * Namespace for ACP application management.
17 WCF
.ACP
.Application
= { };
20 * Provides the ability to set an application as primary.
22 * @param integer packageID
24 WCF
.ACP
.Application
.SetAsPrimary
= Class
.extend({
26 * application package id
32 * Initializes the WCF.ACP.Application.SetAsPrimary class.
34 * @param integer packageID
36 init: function(packageID
) {
37 this._packageID
= packageID
;
39 $('#setAsPrimary').click($.proxy(this._click
, this));
43 * Shows a confirmation dialog to set current application as primary.
46 WCF
.System
.Confirmation
.show(WCF
.Language
.get('wcf.acp.application.setAsPrimary.confirmMessage'), $.proxy(function(action
) {
47 if (action
=== 'confirm') {
54 * Sets an application as primary.
56 _setAsPrimary: function() {
57 new WCF
.Action
.Proxy({
60 actionName
: 'setAsPrimary',
61 className
: 'wcf\\data\\application\\ApplicationAction',
62 objectIDs
: [ this._packageID
]
64 success
: $.proxy(function(data
, textStatus
, jqXHR
) {
65 var $notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.global.success'));
69 $('#setAsPrimary').parent().remove();
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
);
76 WCF
.DOMNodeInsertedHandler
.execute();
83 * Namespace for ACP cronjob management.
85 WCF
.ACP
.Cronjob
= { };
88 * Handles the manual execution of cronjobs.
90 WCF
.ACP
.Cronjob
.ExecutionHandler
= Class
.extend({
93 * @var WCF.System.Notification
99 * @var WCF.Action.Proxy
104 * Initializes WCF.ACP.Cronjob.ExecutionHandler object.
107 this._proxy
= new WCF
.Action
.Proxy({
108 success
: $.proxy(this._success
, this)
111 $('.jsCronjobRow .jsExecuteButton').click($.proxy(this._click
, this));
113 this._notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.global.success'), 'success');
117 * Handles a click on an execute button.
119 * @param object event
121 _click: function(event
) {
122 this._proxy
.setOption('data', {
123 actionName
: 'execute',
124 className
: 'wcf\\data\\cronjob\\CronjobAction',
125 objectIDs
: [ $(event
.target
).data('objectID') ]
128 this._proxy
.sendRequest();
132 * Handles successful cronjob execution.
135 * @param string textStatus
136 * @param jQuery jqXHR
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');
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();
150 this._notification
.show();
159 * Handles the cronjob log list.
161 WCF
.ACP
.Cronjob
.LogList
= Class
.extend({
163 * error message dialog
169 * Initializes WCF.ACP.Cronjob.LogList object.
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({
179 actionName
: 'clearAll',
180 className
: 'wcf\\data\\cronjob\\log\\CronjobLogAction'
182 success: function() {
183 window
.location
.reload();
190 // bind event listeners to error badges
191 $('.jsCronjobError').click($.proxy(this._showError
, this));
195 * Shows certain error message
197 * @param object event
199 _showError: function(event
) {
200 var $errorBadge
= $(event
.currentTarget
);
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')
209 this._dialog
.html('<pre>' + $errorBadge
.next().html() + '</pre>');
210 this._dialog
.wcfDialog('open');
218 * @param array<string> activeMenuItems
220 WCF
.ACP
.Menu
= Class
.extend({
222 * Initializes ACPMenu.
224 * @param array activeMenuItems
226 init: function(activeMenuItems
) {
227 this._headerNavigation
= $('nav#mainMenu');
228 this._sidebarNavigation
= $('aside.collapsibleMenu > div');
230 this._prepareElements(activeMenuItems
);
234 * Resets all elements and binds event listeners.
236 _prepareElements: function(activeMenuItems
) {
237 this._headerNavigation
.find('li').removeClass('active');
239 this._sidebarNavigation
.find('legend').each($.proxy(function(index
, menuHeader
) {
240 $(menuHeader
).click($.proxy(this._toggleItem
, this));
243 // close all navigation groups
244 this._sidebarNavigation
.find('nav ul').each(function() {
248 this._headerNavigation
.find('li').click($.proxy(this._toggleSidebar
, this));
250 if (activeMenuItems
.length
=== 0) {
251 this._renderSidebar(this._headerNavigation
.find('li:first').data('menuItem'), []);
254 this._renderSidebar('', activeMenuItems
);
259 * Toggles a navigation group entry.
261 _toggleItem: function(event
) {
262 var $menuItem
= $(event
.currentTarget
);
264 $menuItem
.parent().find('nav ul').stop(true, true).toggle('blind', { }, 200).end();
265 $menuItem
.toggleClass('active');
269 * Handles clicks on main menu.
271 * @param object event
273 _toggleSidebar: function(event
) {
274 var $target
= $(event
.currentTarget
);
276 if ($target
.hasClass('active')) {
280 this._renderSidebar($target
.data('menuItem'), []);
284 * Renders sidebar including highlighting of currently active menu items.
286 * @param string menuItem
287 * @param array activeMenuItems
289 _renderSidebar: function(menuItem
, activeMenuItems
) {
290 // reset visible and active items
291 this._headerNavigation
.find('li').removeClass('active');
292 this._sidebarNavigation
.find('> div').hide();
294 if (activeMenuItems
.length
=== 0) {
296 this._headerNavigation
.find('li[data-menu-item="' + menuItem
+ '"]').addClass('active');
297 this._sidebarNavigation
.find('div[data-parent-menu-item="' + menuItem
+ '"]').show();
300 // open menu by active menu items, first element is always a head navigation item
301 menuItem
= activeMenuItems
.shift();
303 this._headerNavigation
.find('li[data-menu-item="' + menuItem
+ '"]').addClass('active');
304 this._sidebarNavigation
.find('div[data-parent-menu-item="' + menuItem
+ '"]').show();
306 for (var $i
= 0, $size
= activeMenuItems
.length
; $i
< $size
; $i
++) {
307 var $item
= activeMenuItems
[$i
];
309 if ($.wcfIsset($item
)) {
310 var $menuItem
= $('#' + $.wcfEscapeID($item
));
312 if ($menuItem
.getTagName() === 'ul') {
313 $menuItem
.show().parents('fieldset').children('legend').addClass('active');
316 $menuItem
.addClass('active');
325 * Namespace for ACP package management.
327 WCF
.ACP
.Package
= { };
330 * Provides the package installation.
332 * @param integer queueID
333 * @param string actionName
335 WCF
.ACP
.Package
.Installation
= Class
.extend({
337 * package installation type
340 _actionName
: 'InstallPackage',
343 * true, if rollbacks are supported
346 _allowRollback
: false,
355 * name of the language item with the title of the dialog
362 * @var WCF.Action.Proxy
367 * package installation queue id
373 * true, if dialog should be rendered again
376 _shouldRender
: false,
379 * Initializes the WCF.ACP.Package.Installation class.
381 * @param integer queueID
382 * @param string actionName
383 * @param boolean allowRollback
384 * @param boolean isUpdate
386 init: function(queueID
, actionName
, allowRollback
, isUpdate
) {
387 this._actionName
= (actionName
) ? actionName
: 'InstallPackage';
388 this._allowRollback
= (allowRollback
=== true) ? true : false;
389 this._queueID
= queueID
;
391 switch (this._actionName
) {
392 case 'InstallPackage':
393 this._dialogTitle
= 'wcf.acp.package.' + (isUpdate
? 'update' : 'install') + '.title';
396 case 'UninstallPackage':
397 this._dialogTitle
= 'wcf.acp.package.uninstallation.title';
406 * Initializes the WCF.Action.Proxy object.
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
];
414 if ($actionName
.length
) $actionName
+= '-';
415 $actionName
+= $part
.toLowerCase();
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
428 * Initializes the package installation.
431 $('#submitButton').click($.proxy(this.prepareInstallation
, this));
435 * Handles erroneous AJAX requests.
437 _failure: function() {
438 if (this._dialog
!== null) {
439 $('#packageInstallationProgress').removeAttr('value');
440 this._setIcon('remove');
443 if (!this._allowRollback
) {
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));
452 $('#packageInstallationInnerContentContainer').show();
454 this._dialog
.wcfDialog('render');
460 * Performs a rollback.
462 * @param object event
464 _rollback: function(event
) {
465 this._setIcon('spinner');
468 $(event
.currentTarget
).disable();
471 this._executeStep('rollback');
475 * Prepares installation dialog.
477 prepareInstallation: function() {
478 this._proxy
.setOption('data', this._getParameters());
479 this._proxy
.sendRequest();
483 * Returns parameters to prepare installation.
487 _getParameters: function() {
489 queueID
: this._queueID
,
495 * Handles successful AJAX requests.
498 * @param string textStatus
499 * @param jQuery jqXHR
501 _success: function(data
, textStatus
, jqXHR
) {
502 this._shouldRender
= false;
504 if (this._dialog
=== null) {
505 this._dialog
= $('<div id="packageInstallationDialog" />').hide().appendTo(document
.body
);
506 this._dialog
.wcfDialog({
508 title
: WCF
.Language
.get(this._dialogTitle
)
512 this._setIcon('spinner');
514 if (data
.step
== 'rollback') {
515 this._dialog
.wcfDialog('close');
516 this._dialog
.remove();
518 new WCF
.PeriodicalExecuter(function(pe
) {
521 var $uninstallation
= new WCF
.ACP
.Package
.Uninstallation();
522 $uninstallation
.start(data
.packageID
);
528 // receive new queue id
530 this._queueID
= data
.queueID
;
534 if (data
.template
&& !data
.ignoreTemplate
) {
535 this._dialog
.html(data
.template
);
536 this._shouldRender
= true;
541 $('#packageInstallationProgress').attr('value', data
.progress
).text(data
.progress
+ '%');
542 $('#packageInstallationProgressLabel').text(data
.progress
+ '%');
546 if (data
.currentAction
) {
547 $('#packageInstallationAction').html(data
.currentAction
);
551 if (data
.step
=== 'success') {
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() {
558 window
.location
= data
.redirectLocation
;
561 $('#packageInstallationInnerContentContainer').show();
563 $(document
).keydown(function(event
) {
564 if (event
.which
=== $.ui
.keyCode
.ENTER
) {
565 $button
.trigger('click');
569 this._dialog
.wcfDialog('render');
575 // handle inner template
576 if (data
.innerTemplate
) {
578 $('#packageInstallationInnerContent').html(data
.innerTemplate
).find('input').keyup(function(event
) {
579 if (event
.keyCode
=== $.ui
.keyCode
.ENTER
) {
584 // create button to handle next step
585 if (data
.step
&& data
.node
) {
586 $('#packageInstallationProgress').removeAttr('value');
587 this._setIcon('question');
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();
597 $('#packageInstallationInnerContentContainer').show();
599 this._dialog
.wcfDialog('render');
604 this._purgeTemplateContent($.proxy(function() {
606 if (this._shouldRender
) {
607 this._dialog
.wcfDialog('render');
611 if (data
.step
&& data
.node
) {
612 this._executeStep(data
.step
, data
.node
);
618 * Submits the dialog content.
622 _submit: function(data
) {
623 this._setIcon('spinner');
625 // collect form values
626 var $additionalData
= { };
627 $('#packageInstallationInnerContent input').each(function(index
, inputElement
) {
628 var $inputElement
= $(inputElement
);
629 var $type
= $inputElement
.attr('type');
631 if (($type
!= 'checkbox' && $type
!= 'radio') || $inputElement
.prop('checked')) {
632 var $name
= $inputElement
.attr('name');
633 if ($name
.match(/(.*)\[([^[]*)\]$/)) {
637 if ($additionalData
[$name
] === undefined) {
639 $additionalData
[$name
] = { };
642 $additionalData
[$name
] = [ ];
647 $additionalData
[$name
][$key
] = $inputElement
.val();
650 $additionalData
[$name
].push($inputElement
.val());
654 $additionalData
[$name
] = $inputElement
.val();
659 this._executeStep(data
.step
, data
.node
, $additionalData
);
663 * Purges template content.
665 * @param function callback
667 _purgeTemplateContent: function(callback
) {
668 if ($('#packageInstallationInnerContent').children().length
> 1) {
669 $('#packageInstallationInnerContentContainer').wcfBlindOut('vertical', $.proxy(function() {
670 $('#packageInstallationInnerContent').empty();
671 this._shouldRender
= true;
683 * Executes the next installation step.
687 * @param object additionalData
689 _executeStep: function(step
, node
, additionalData
) {
690 if (!additionalData
) additionalData
= { };
692 var $data
= $.extend({
694 queueID
: this._queueID
,
698 this._proxy
.setOption('data', $data
);
699 this._proxy
.sendRequest();
703 * Sets the icon with the given name as the current installation status icon.
705 * @param string iconName
707 _setIcon: function(iconName
) {
708 this._dialog
.find('.jsPackageInstallationStatus').removeClass('icon-ok icon-question icon-remove icon-spinner').addClass('icon-' + iconName
);
713 * Handles canceling the package installation at the package installation
716 WCF
.ACP
.Package
.Installation
.Cancel
= Class
.extend({
718 * Creates a new instance of WCF.ACP.Package.Installation.Cancel.
720 * @param integer queueID
722 init: function(queueID
) {
723 $('#backButton').click(function() {
724 new WCF
.Action
.Proxy({
727 actionName
: 'cancelInstallation',
728 className
: 'wcf\\data\\package\\installation\\queue\\PackageInstallationQueueAction',
729 objectIDs
: [ queueID
]
731 success: function(data
) {
732 window
.location
= data
.returnValues
.url
;
740 * Provides the package uninstallation.
742 * @param jQuery elements
743 * @param string wcfPackageListURL
745 WCF
.ACP
.Package
.Uninstallation
= WCF
.ACP
.Package
.Installation
.extend({
747 * list of uninstallation buttons
759 * URL of WCF package list
762 _wcfPackageListURL
: '',
765 * Initializes the WCF.ACP.Package.Uninstallation class.
767 * @param jQuery elements
768 * @param string wcfPackageListURL
770 init: function(elements
, wcfPackageListURL
) {
771 this._elements
= elements
;
773 this._wcfPackageListURL
= wcfPackageListURL
;
775 if (this._elements
!== undefined && this._elements
.length
) {
776 this._super(0, 'UninstallPackage');
781 * Begins a package uninstallation without user action.
783 * @param integer packageID
785 start: function(packageID
) {
786 this._actionName
= 'UninstallPackage';
787 this._packageID
= packageID
;
789 this._dialogTitle
= 'wcf.acp.package.uninstallation.title';
792 this.prepareInstallation();
796 * @see WCF.ACP.Package.Installation.init()
799 this._elements
.click($.proxy(this._showConfirmationDialog
, this));
803 * Displays a confirmation dialog prior to package uninstallation.
805 * @param object event
807 _showConfirmationDialog: function(event
) {
808 var $element
= $(event
.currentTarget
);
810 if ($element
.data('isApplication') && this._wcfPackageListURL
) {
811 window
.location
= WCF
.String
.unescapeHTML(this._wcfPackageListURL
.replace(/{packageID}/, $element
.data('objectID')));
816 WCF
.System
.Confirmation
.show($element
.data('confirmMessage'), function(action
) {
817 if (action
=== 'confirm') {
818 self
._packageID
= $element
.data('objectID');
819 self
.prepareInstallation();
825 * @see WCF.ACP.Package.Installation._getParameters()
827 _getParameters: function() {
829 packageID
: this._packageID
,
836 * Manages package search.
838 WCF
.ACP
.Package
.Search
= Class
.extend({
846 * list of cached pages
864 * package input field
870 * package description input field
873 _packageDescription
: null,
876 * package name input field
882 * package search result container
885 _packageSearchResultContainer
: null,
888 * package search result list
891 _packageSearchResultList
: null,
907 * @var WCF.Action:proxy
918 * currently selected package
921 _selectedPackage
: '',
924 * currently selected package's version
926 _selectedPackageVersion
: '',
929 * Initializes the WCF.ACP.Package.Seach class.
934 this._container
= $('#packageSearch');
936 this._package
= null;
937 this._packageName
= null;
938 this._packageSearchResultContainer
= $('#packageSearchResultContainer');
939 this._packageSearchResultList
= $('#packageSearchResultList');
942 this._searchDescription
= null;
944 this._selectedPackage
= '';
945 this._selectedPackageVersion
= '';
947 this._proxy
= new WCF
.Action
.Proxy({
948 success
: $.proxy(this._success
, this)
951 this._initElements();
955 * Initializes search elements.
957 _initElements: function() {
958 this._button
= this._container
.find('.formSubmit > button.jsButtonPackageSearch').disable().click($.proxy(this._search
, this));
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));
966 * Handles the 'keyup' event.
968 _keyUp: function(event
) {
969 if (this._package
.val() === '' && this._packageDescription
.val() === '' && this._packageName
.val() === '') {
970 this._button
.disable();
973 this._button
.enable();
976 if (event
.which
=== 13) {
977 this._button
.trigger('click');
983 * Performs a new search.
985 _search: function() {
986 var $values
= this._getSearchValues();
987 if (!this._validate($values
)) {
991 $values
.pageNo
= this._pageNo
;
992 this._proxy
.setOption('data', {
993 actionName
: 'search',
994 className
: 'wcf\\data\\package\\update\\PackageUpdateAction',
997 this._proxy
.sendRequest();
1001 * Returns search values.
1005 _getSearchValues: function() {
1007 'package': $.trim(this._package
.val()),
1008 packageDescription
: $.trim(this._packageDescription
.val()),
1009 packageName
: $.trim(this._packageName
.val())
1014 * Validates search values.
1016 * @param object values
1019 _validate: function(values
) {
1020 if (values
['package'] === '' && values
['packageDescription'] === '' && values
['packageName'] === '') {
1028 * Handles successful AJAX requests.
1030 * @param object data
1031 * @param string textStatus
1032 * @param jQuery jqXHR
1034 _success: function(data
, textStatus
, jqXHR
) {
1035 switch (data
.actionName
) {
1036 case 'getResultList':
1037 this._insertTemplate(data
.returnValues
.template
);
1040 case 'prepareInstallation':
1041 if (data
.returnValues
.queueID
) {
1042 if (this._dialog
!== null) {
1043 this._dialog
.wcfDialog('close');
1046 var $installation
= new WCF
.ACP
.Package
.Installation(data
.returnValues
.queueID
, undefined, false);
1047 $installation
.prepareInstallation();
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')
1057 this._dialog
.html(data
.returnValues
.template
).wcfDialog('open');
1060 this._dialog
.find('.formSubmit > button').click($.proxy(this._submitAuthentication
, this));
1065 this._pageCount
= data
.returnValues
.pageCount
;
1066 this._searchID
= data
.returnValues
.searchID
;
1068 this._insertTemplate(data
.returnValues
.template
, (data
.returnValues
.count
=== undefined ? undefined : data
.returnValues
.count
));
1069 this._setupPagination();
1075 * Submits authentication data for current update server.
1077 * @param object event
1079 _submitAuthentication: function(event
) {
1080 var $usernameField
= $('#packageUpdateServerUsername');
1081 var $passwordField
= $('#packageUpdateServerPassword');
1083 // remove error messages if any
1084 $usernameField
.next('small.innerError').remove();
1085 $passwordField
.next('small.innerError').remove();
1087 var $continue = true;
1088 if ($.trim($usernameField
.val()) === '') {
1089 $('<small class="innerError">' + WCF
.Language
.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField
);
1093 if ($.trim($passwordField
.val()) === '') {
1094 $('<small class="innerError">' + WCF
.Language
.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField
);
1099 this._prepareInstallation($(event
.currentTarget
).data('packageUpdateServerID'));
1104 * Inserts search result list template.
1106 * @param string template
1107 * @param integer count
1109 _insertTemplate: function(template
, count
) {
1110 this._packageSearchResultContainer
.show();
1112 this._packageSearchResultList
.html(template
);
1113 if (count
=== undefined) {
1114 this._content
[this._pageNo
] = template
;
1117 // update badge count
1118 if (count
!== undefined) {
1119 this._content
= { 1: template
};
1120 this._packageSearchResultContainer
.find('> header > h2 > .badge').html(count
);
1124 this._packageSearchResultList
.find('.jsInstallPackage').click($.proxy(this._click
, this));
1128 * Prepares a package installation.
1130 * @param object event
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();
1144 * Prepares package installation.
1146 * @param integer packageUpdateServerID
1148 _prepareInstallation: function(packageUpdateServerID
) {
1152 $parameters
['packages'][this._selectedPackage
] = this._selectedPackageVersion
;
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())
1163 this._proxy
.setOption('data', {
1164 actionName
: 'prepareInstallation',
1165 className
: 'wcf\\data\\package\\update\\PackageUpdateAction',
1166 parameters
: $parameters
1168 this._proxy
.sendRequest();
1172 * Setups pagination for current search.
1174 _setupPagination: function() {
1175 // remove previous instances
1176 this._content
= { 1: this._packageSearchResultList
.html() };
1177 this._packageSearchResultContainer
.find('.pageNavigation').wcfPages('destroy').remove();
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));*/
1186 $('<div class="contentNavigation" />').insertAfter(this._packageSearchResultList
).wcfPages({
1187 activePage
: this._pageNo
,
1188 maxPage
: this._pageCount
1189 }).on('wcfpagesswitched', $.proxy(this._showPage
, this));
1194 * Displays requested pages or loads it.
1196 * @param object event
1197 * @param object data
1199 _showPage: function(event
, data
) {
1200 if (data
&& data
.activePage
) {
1201 this._pageNo
= data
.activePage
;
1205 if (this._pageNo
< 1 || this._pageNo
> this._pageCount
) {
1206 console
.debug("[WCF.ACP.Package.Search] Cannot access page " + this._pageNo
+ " of " + this._pageCount
);
1211 if (this._content
[this._pageNo
] === undefined) {
1212 this._proxy
.setOption('data', {
1213 actionName
: 'getResultList',
1214 className
: 'wcf\\data\\package\\update\\PackageUpdateAction',
1216 pageNo
: this._pageNo
,
1217 searchID
: this._searchID
1220 this._proxy
.sendRequest();
1223 // show cached content
1224 this._packageSearchResultList
.html(this._content
[this._pageNo
]);
1226 WCF
.DOMNodeInsertedHandler
.execute();
1231 WCF
.ACP
.Package
.Server
= { };
1233 WCF
.ACP
.Package
.Server
.Installation
= Class
.extend({
1235 _selectedPackage
: '',
1238 this._dialog
= null;
1239 this._selectedPackage
= null;
1241 this._proxy
= new WCF
.Action
.Proxy({
1242 success
: $.proxy(this._success
, this)
1247 $('.jsButtonPackageInstall').removeClass('jsButtonPackageInstall').click($.proxy(this._click
, this));
1251 * Prepares a package installation.
1253 * @param object event
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();
1267 * Handles successful AJAX requests.
1269 * @param object data
1271 _success: function(data
) {
1272 if (data
.returnValues
.queueID
) {
1273 if (this._dialog
!== null) {
1274 this._dialog
.wcfDialog('close');
1277 var $installation
= new WCF
.ACP
.Package
.Installation(data
.returnValues
.queueID
, undefined, false);
1278 $installation
.prepareInstallation();
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')
1288 this._dialog
.html(data
.returnValues
.template
).wcfDialog('open');
1291 this._dialog
.find('.formSubmit > button').click($.proxy(this._submitAuthentication
, this));
1296 * Submits authentication data for current update server.
1298 * @param object event
1300 _submitAuthentication: function(event
) {
1301 var $usernameField
= $('#packageUpdateServerUsername');
1302 var $passwordField
= $('#packageUpdateServerPassword');
1304 // remove error messages if any
1305 $usernameField
.next('small.innerError').remove();
1306 $passwordField
.next('small.innerError').remove();
1308 var $continue = true;
1309 if ($.trim($usernameField
.val()) === '') {
1310 $('<small class="innerError">' + WCF
.Language
.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField
);
1314 if ($.trim($passwordField
.val()) === '') {
1315 $('<small class="innerError">' + WCF
.Language
.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField
);
1320 this._prepareInstallation($(event
.currentTarget
).data('packageUpdateServerID'));
1325 * Prepares package installation.
1327 * @param integer packageUpdateServerID
1329 _prepareInstallation: function(packageUpdateServerID
) {
1333 $parameters
['packages'][this._selectedPackage
] = this._selectedPackageVersion
;
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())
1344 this._proxy
.setOption('data', {
1345 actionName
: 'prepareInstallation',
1346 className
: 'wcf\\data\\package\\update\\PackageUpdateAction',
1347 parameters
: $parameters
1349 this._proxy
.sendRequest();
1354 * Namespace for package update related classes.
1356 WCF
.ACP
.Package
.Update
= { };
1359 * Handles the package update process.
1361 WCF
.ACP
.Package
.Update
.Manager
= Class
.extend({
1370 * @var WCF.Action.Proxy
1378 _submitButton
: null,
1381 * Initializes the WCF.ACP.Package.Update.Manager class.
1384 this._dialog
= null;
1385 this._submitButton
= $('.formSubmit > button').click($.proxy(this._click
, this));
1387 this._proxy
= new WCF
.Action
.Proxy({
1388 success
: $.proxy(this._success
, this)
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));
1398 * Handles toggles for a specific update.
1400 _change: function(event
) {
1401 var $checkbox
= $(event
.currentTarget
);
1403 if ($checkbox
.is(':checked')) {
1404 $checkbox
.data('packageUpdate').find('select').enable();
1405 $checkbox
.data('packageUpdate').find('dl').removeClass('disabled');
1407 this._submitButton
.enable();
1410 $checkbox
.data('packageUpdate').find('select').disable();
1411 $checkbox
.data('packageUpdate').find('dl').addClass('disabled');
1413 // disable submit button
1414 if (!$('input[type=checkbox]:checked').length
) {
1415 this._submitButton
.disable();
1418 this._submitButton
.enable();
1424 * Handles clicks on the submit button.
1426 * @param object event
1427 * @param integer packageUpdateServerID
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();
1438 if ($.getLength($packages
)) {
1439 this._submitButton
.disable();
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())
1453 this._proxy
.setOption('data', {
1454 actionName
: 'prepareUpdate',
1455 className
: 'wcf\\data\\package\\update\\PackageUpdateAction',
1456 parameters
: $parameters
,
1458 this._proxy
.sendRequest();
1463 * Handles successful AJAX requests.
1465 * @param object data
1466 * @param string textStatus
1467 * @param jQuery jqXHR
1469 _success: function(data
, textStatus
, jqXHR
) {
1470 if (data
.returnValues
.queueID
) {
1471 if (this._dialog
!== null) {
1472 this._dialog
.wcfDialog('close');
1475 var $installation
= new WCF
.ACP
.Package
.Installation(data
.returnValues
.queueID
, undefined, false, true);
1476 $installation
.prepareInstallation();
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')
1486 this._dialog
.wcfDialog('option', 'title', WCF
.Language
.get('wcf.acp.package.update.excludedPackages'));
1487 this._dialog
.html(data
.returnValues
.template
).wcfDialog('open');
1490 this._submitButton
.enable();
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')
1500 this._dialog
.wcfDialog('option', 'title', WCF
.Language
.get('wcf.acp.package.update.unauthorized'));
1501 this._dialog
.html(data
.returnValues
.template
).wcfDialog('open');
1504 this._dialog
.find('.formSubmit > button').click($.proxy(this._submitAuthentication
, this));
1509 * Submits authentication data for current update server.
1511 * @param object event
1513 _submitAuthentication: function(event
) {
1514 var $usernameField
= $('#packageUpdateServerUsername');
1515 var $passwordField
= $('#packageUpdateServerPassword');
1517 // remove error messages if any
1518 $usernameField
.next('small.innerError').remove();
1519 $passwordField
.next('small.innerError').remove();
1521 var $continue = true;
1522 if ($.trim($usernameField
.val()) === '') {
1523 $('<small class="innerError">' + WCF
.Language
.get('wcf.global.form.error.empty') + '</small>').insertAfter($usernameField
);
1527 if ($.trim($passwordField
.val()) === '') {
1528 $('<small class="innerError">' + WCF
.Language
.get('wcf.global.form.error.empty') + '</small>').insertAfter($passwordField
);
1533 this._click(undefined, $(event
.currentTarget
).data('packageUpdateServerID'));
1539 * Searches for available updates.
1541 * @param boolean bindOnExistingButtons
1543 WCF
.ACP
.Package
.Update
.Search
= Class
.extend({
1551 * Initializes the WCF.ACP.Package.SearchForUpdates class.
1553 * @param boolean bindOnExistingButtons
1555 init: function(bindOnExistingButtons
) {
1556 this._dialog
= null;
1558 if (bindOnExistingButtons
=== true) {
1559 $('.jsButtonPackageUpdate').click($.proxy(this._click
, this));
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'));
1568 * Handles clicks on the search button.
1570 _click: function() {
1571 if (this._dialog
=== null) {
1572 new WCF
.Action
.Proxy({
1575 actionName
: 'searchForUpdates',
1576 className
: 'wcf\\data\\package\\update\\PackageUpdateAction',
1581 success
: $.proxy(this._success
, this)
1585 this._dialog
.wcfDialog('open');
1590 * Handles successful AJAX requests.
1592 * @param object data
1593 * @param string textStatus
1594 * @param jQuery jqXHR
1596 _success: function(data
, textStatus
, jqXHR
) {
1597 if (data
.returnValues
.url
) {
1598 window
.location
= data
.returnValues
.url
;
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')
1610 * Namespace for classes related to the WoltLab Plugin-Store.
1612 WCF
.ACP
.PluginStore
= { };
1615 * Namespace for classes handling items purchased in the WoltLab Plugin-Store.
1617 WCF
.ACP
.PluginStore
.PurchasedItems
= { };
1620 * Searches for purchased items available for install but not yet installed.
1622 WCF
.ACP
.PluginStore
.PurchasedItems
.Search
= Class
.extend({
1631 * @var WCF.Action.Proxy
1636 * Initializes the WCF.ACP.PluginStore.PurchasedItems.Search class.
1639 this._dialog
= null;
1640 this._proxy
= new WCF
.Action
.Proxy({
1641 success
: $.proxy(this._success
, this)
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));
1649 * Handles clicks on the search button.
1651 _click: function() {
1652 this._proxy
.setOption('data', {
1653 actionName
: 'searchForPurchasedItems',
1654 className
: 'wcf\\data\\package\\PackageAction'
1656 this._proxy
.sendRequest();
1660 * Handles successful AJAX requests.
1662 * @param object data
1663 * @param string textStatus
1664 * @param jQuery jqXHR
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')
1676 this._dialog
.html(data
.returnValues
.template
);
1677 this._dialog
.wcfDialog('open');
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');
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')
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');
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')
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');
1715 else if (data
.returnValues
.redirectURL
) {
1716 // redirect to list of purchased products
1717 window
.location
= data
.returnValues
.redirectURL
;
1722 * Submits the user credentials.
1724 _submit: function() {
1725 this._dialog
.wcfDialog('close');
1727 this._proxy
.setOption('data', {
1728 actionName
: 'searchForPurchasedItems',
1729 className
: 'wcf\\data\\package\\PackageAction',
1731 password
: $('#pluginStorePassword').val(),
1732 username
: $('#pluginStoreUsername').val()
1735 this._proxy
.sendRequest();
1740 * Worker support for ACP.
1742 * @param string dialogID
1743 * @param string className
1744 * @param string title
1745 * @param object parameters
1746 * @param object callback
1748 WCF
.ACP
.Worker
= Class
.extend({
1756 * callback invoked after worker completed
1775 * @var WCF.Action.Proxy
1786 * Initializes a new worker instance.
1788 * @param string dialogID
1789 * @param string className
1790 * @param string title
1791 * @param object parameters
1792 * @param object callback
1793 * @param object confirmMessage
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({
1803 className
: className
,
1804 parameters
: parameters
|| { }
1806 showLoadingOverlay
: false,
1807 success
: $.proxy(this._success
, this),
1808 url
: 'index.php?worker-proxy/&t=' + SECURITY_TOKEN
+ SID_ARG_2ND
1810 this._title
= title
;
1814 * Handles response from server.
1816 * @param object data
1818 _success: function(data
) {
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();
1829 window
.location
.reload();
1835 if (this._aborted
) {
1839 if (data
.template
) {
1840 this._dialog
.html(data
.template
);
1844 this._dialog
.find('progress').attr('value', data
.progress
).text(data
.progress
+ '%').next('span').text(data
.progress
+ '%');
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
1854 this._proxy
.sendRequest();
1856 else if (this._callback
!== null) {
1857 this._callback(this, data
);
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
; });
1864 this._dialog
.wcfDialog('render');
1870 * Namespace for category-related functions.
1872 WCF
.ACP
.Category
= { };
1875 * Handles collapsing categories.
1877 * @param string className
1878 * @param integer objectTypeID
1880 WCF
.ACP
.Category
.Collapsible
= WCF
.Collapsible
.SimpleRemote
.extend({
1882 * @see WCF.Collapsible.Remote.init()
1884 init: function(className
) {
1885 var sortButton
= $('.formSubmit > button[data-type="submit"]');
1887 sortButton
.click($.proxy(this._sort
, this));
1890 this._super(className
);
1894 * @see WCF.Collapsible.Remote._getButtonContainer()
1896 _getButtonContainer: function(containerID
) {
1897 return $('#' + containerID
+ ' > .buttons');
1901 * @see WCF.Collapsible.Remote._getContainers()
1903 _getContainers: function() {
1904 return $('.jsCategory').has('ol').has('li');
1908 * @see WCF.Collapsible.Remote._getTarget()
1910 _getTarget: function(containerID
) {
1911 return $('#' + containerID
+ ' > ol');
1915 * Handles a click on the sort button.
1918 // remove existing collapsible buttons
1919 $('.collapsibleButton').remove();
1921 // reinit containers
1922 this._containers
= { };
1923 this._containerData
= { };
1925 var $containers
= this._getContainers();
1926 if ($containers
.length
== 0) {
1927 console
.debug('[WCF.ACP.Category.Collapsible] Empty container set given, aborting.');
1929 $containers
.each($.proxy(function(index
, container
) {
1930 var $container
= $(container
);
1931 var $containerID
= $container
.wcfIdentify();
1932 this._containers
[$containerID
] = $container
;
1934 this._initContainer($containerID
);
1940 * Provides the search dropdown for ACP
1942 * @see WCF.Search.Base
1944 WCF
.ACP
.Search
= WCF
.Search
.Base
.extend({
1946 * @see WCF.Search.Base.init()
1949 this._className
= 'wcf\\data\\acp\\search\\provider\\ACPSearchProviderAction';
1950 this._super('#search input[name=q]');
1952 // disable form submitting
1953 $('#search > form').on('submit', function(event
) {
1954 event
.preventDefault();
1959 * @see WCF.Search.Base._createListItem()
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
);
1968 $('<li class="dropdownText">' + resultList
.title
+ '</li>').appendTo(this._list
);
1971 for (var $i
in resultList
.items
) {
1972 var $item
= resultList
.items
[$i
];
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
);
1981 * @see WCF.Search.Base._handleEmptyResult()
1983 _handleEmptyResult: function() {
1984 $('<li class="dropdownText">' + WCF
.Language
.get('wcf.acp.search.noResults') + '</li>').appendTo(this._list
);
1990 * @see WCF.Search.Base._highlightSelectedElement()
1992 _highlightSelectedElement: function() {
1993 this._list
.find('li').removeClass('dropdownNavigationItem');
1994 this._list
.find('li:not(.dropdownDivider):not(.dropdownText)').eq(this._itemIndex
).addClass('dropdownNavigationItem');
1998 * @see WCF.Search.Base._selectElement()
2000 _selectElement: function(event
) {
2001 if (this._itemIndex
=== -1) {
2005 window
.location
= this._list
.find('li.dropdownNavigationItem > a').attr('href');
2010 * Namespace for user management.
2015 * Generic implementation to ban users.
2017 WCF
.ACP
.User
.BanHandler
= {
2032 * @var WCF.Action.Proxy
2037 * Initializes WCF.ACP.User.BanHandler on first use.
2040 this._proxy
= new WCF
.Action
.Proxy({
2041 success
: $.proxy(this._success
, this)
2044 $('.jsBanButton').click($.proxy(function(event
) {
2045 var $button
= $(event
.currentTarget
);
2046 if ($button
.data('banned')) {
2047 this.unban([ $button
.data('objectID') ]);
2050 this.ban([ $button
.data('objectID') ]);
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));
2066 * Handles clipboard actions.
2068 * @param object event
2069 * @param string type
2070 * @param string actionName
2071 * @param object parameters
2073 _execute: function(event
, type
, actionName
, parameters
) {
2074 if (actionName
== 'com.woltlab.wcf.user.ban') {
2075 this.ban(parameters
.objectIDs
);
2082 * @param array<integer> userIDs
2084 unban: function(userIDs
) {
2085 this._proxy
.setOption('data', {
2086 actionName
: 'unban',
2087 className
: 'wcf\\data\\user\\UserAction',
2090 this._proxy
.sendRequest();
2096 * @param array<integer> userIDs
2098 ban: function(userIDs
) {
2099 if (this._dialog
=== null) {
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>'));
2105 this._dialog
.find('#userBanNeverExpires').change(function() {
2106 $('#userBanExpiresSettings').toggle();
2109 this._dialog
.find('button').click($.proxy(this._submit
, this));
2113 $('#userBanReason').val('');
2114 $('#userBanNeverExpires').prop('checked', true);
2115 $('#userBanExpiresSettings').hide();
2116 $('#userBanExpiresDatePicker, #userBanExpires').val('');
2119 this._dialog
.data('userIDs', userIDs
);
2120 this._dialog
.wcfDialog({
2121 title
: WCF
.Language
.get('wcf.acp.user.ban.sure')
2126 * Handles submitting the ban dialog.
2128 _submit: function() {
2129 this._dialog
.find('.innerError').remove();
2131 var $banExpires
= '';
2132 if (!$('#userBanNeverExpires').is(':checked')) {
2133 var $banExpires
= $('#userBanExpiresDatePicker').val();
2135 this._dialog
.find('#userBanExpiresSettings > dd > small').prepend($('<small class="innerError" />').text(WCF
.Language
.get('wcf.global.form.error.empty')));
2140 this._proxy
.setOption('data', {
2142 className
: 'wcf\\data\\user\\UserAction',
2143 objectIDs
: this._dialog
.data('userIDs'),
2145 banReason
: $('#userBanReason').val(),
2146 banExpires
: $banExpires
2149 this._proxy
.sendRequest();
2153 * Handles successful AJAX calls.
2155 * @param object data
2156 * @param string textStatus
2157 * @param jQuery jqXHR
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');
2167 $button
.data('banned', true).data('tooltip', $button
.data('unbanMessage')).removeClass('icon-unlock').addClass('icon-lock');
2172 var $notification
= new WCF
.System
.Notification();
2173 $notification
.show();
2175 WCF
.Clipboard
.reload();
2177 if (data
.actionName
== 'ban') {
2178 this._dialog
.wcfDialog('close');
2184 * Namespace for user group management.
2186 WCF
.ACP
.User
.Group
= { };
2189 * Handles copying user groups.
2191 WCF
.ACP
.User
.Group
.Copy
= Class
.extend({
2193 * id of the copied group
2199 * Initializes a new instance of WCF.ACP.User.Group.Copy.
2201 * @param integer groupID
2203 init: function(groupID
) {
2204 this._groupID
= groupID
;
2206 $('.jsButtonUserGroupCopy').click($.proxy(this._click
, this));
2210 * Handles clicking on a 'copy user group' button.
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>'));
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({
2224 className
: 'wcf\\data\\user\\group\\UserGroupAction',
2225 objectIDs
: [ this._groupID
],
2227 copyACLOptions
: $('#copyACLOptions').is(':checked'),
2228 copyMembers
: $('#copyMembers').is(':checked'),
2229 copyUserGroupOptions
: $('#copyUserGroupOptions').is(':checked')
2232 success: function(data
) {
2233 window
.location
= data
.returnValues
.redirectURL
;
2237 }, this), '', $template
);
2242 * Generic implementation to enable users.
2244 WCF
.ACP
.User
.EnableHandler
= {
2247 * @var WCF.Action.Proxy
2252 * Initializes WCF.ACP.User.EnableHandler on first use.
2255 this._proxy
= new WCF
.Action
.Proxy({
2256 success
: $.proxy(this._success
, this)
2259 $('.jsEnableButton').click($.proxy(function(event
) {
2260 var $button
= $(event
.currentTarget
);
2261 if ($button
.data('enabled')) {
2262 this.disable([ $button
.data('objectID') ]);
2265 this.enable([ $button
.data('objectID') ]);
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));
2281 * Handles clipboard actions.
2283 * @param object event
2284 * @param string type
2285 * @param string actionName
2286 * @param object parameters
2288 _execute: function(event
, type
, actionName
, parameters
) {
2289 if (actionName
== 'com.woltlab.wcf.user.enable') {
2290 this.enable(parameters
.objectIDs
);
2297 * @param array<integer> userIDs
2299 disable: function(userIDs
) {
2300 this._proxy
.setOption('data', {
2301 actionName
: 'disable',
2302 className
: 'wcf\\data\\user\\UserAction',
2305 this._proxy
.sendRequest();
2311 * @param array<integer> userIDs
2313 enable: function(userIDs
) {
2314 this._proxy
.setOption('data', {
2315 actionName
: 'enable',
2316 className
: 'wcf\\data\\user\\UserAction',
2319 this._proxy
.sendRequest();
2323 * Handles successful AJAX calls.
2325 * @param object data
2326 * @param string textStatus
2327 * @param jQuery jqXHR
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');
2337 $button
.data('enabled', true).data('tooltip', $button
.data('disableMessage')).removeClass('icon-check-empty').addClass('icon-check');
2342 var $notification
= new WCF
.System
.Notification();
2343 $notification
.show(function() { window
.location
.reload(); });
2348 * Handles the send new password clipboard action.
2350 WCF
.ACP
.User
.SendNewPasswordHandler
= {
2353 * @var WCF.Action.Proxy
2358 * Initializes WCF.ACP.User.SendNewPasswordHandler on first use.
2361 this._proxy
= new WCF
.Action
.Proxy({
2362 success
: $.proxy(this._success
, this)
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));
2377 * Handles clipboard actions.
2379 * @param object event
2380 * @param string type
2381 * @param string actionName
2382 * @param object parameters
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
2398 * Namespace for import-related classes.
2400 WCF
.ACP
.Import
= { };
2405 * @param array<string> objectTypes
2407 WCF
.ACP
.Import
.Manager
= Class
.extend({
2421 * current object type index
2427 * list of object types
2428 * @var array<string>
2434 * @var WCF.Action.Proxy
2445 * Initializes the WCF.ACP.Importer object.
2447 * @param array<string> objectTypes
2448 * @param string redirectURL
2450 init: function(objectTypes
, redirectURL
) {
2451 this._currentAction
= '';
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
2459 this._redirectURL
= redirectURL
;
2465 * Invokes importing of an object type.
2467 _invoke: function() {
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'));
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({
2481 success
: $.proxy(function() {
2482 window
.location
= this._redirectURL
;
2484 url
: 'index.php?cache-clear/&t=' + SECURITY_TOKEN
+ SID_ARG_2ND
2486 }, this)).appendTo($form
);
2488 this._dialog
.wcfDialog('render');
2492 WCF
.Language
.get('wcf.acp.dataImport.data.' + this._objectTypes
[this._index
]),
2493 this._objectTypes
[this._index
]
2499 * Executes import of given object type.
2501 * @param string currentAction
2502 * @param string objectType
2504 _run: function(currentAction
, objectType
) {
2505 this._currentAction
= currentAction
;
2506 this._proxy
.setOption('data', {
2507 className
: 'wcf\\system\\worker\\ImportWorker',
2509 objectType
: objectType
2512 this._proxy
.sendRequest();
2516 * Handles response from server.
2518 * @param object data
2520 _success: function(data
) {
2522 if (this._dialog
=== null) {
2523 this._dialog
= $('<div />').hide().appendTo(document
.body
);
2524 this._dialog
.wcfDialog({
2526 title
: WCF
.Language
.get('wcf.acp.dataImport')
2530 if (data
.template
) {
2531 this._dialog
.html(data
.template
);
2534 if (this._currentAction
) {
2535 this._dialog
.find('h1').text(this._currentAction
);
2539 this._dialog
.find('progress').attr('value', data
.progress
).text(data
.progress
+ '%').next('span').text(data
.progress
+ '%');
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
2549 this._proxy
.sendRequest();
2558 * Namespace for stat-related classes.
2563 * Shows the daily stat chart.
2565 WCF
.ACP
.Stat
.Chart
= Class
.extend({
2567 this._proxy
= new WCF
.Action
.Proxy({
2568 success
: $.proxy(this._success
, this)
2571 $('#statRefreshButton').click($.proxy(this._refresh
, this));
2576 _refresh: function() {
2577 var $objectTypeIDs
= [ ];
2578 $('input[name=objectTypeID]:checked').each(function() {
2579 $objectTypeIDs
.push($(this).val());
2582 if (!$objectTypeIDs
.length
) return;
2584 this._proxy
.setOption('data', {
2585 className
: 'wcf\\data\\stat\\daily\\StatDailyAction',
2586 actionName
: 'getData',
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
2595 this._proxy
.sendRequest();
2598 _success: function(data
) {
2599 switch ($('input[name=dateGrouping]:checked').val()) {
2601 var $minTickSize
= [1, "year"];
2602 var $timeFormat
= WCF
.Language
.get('wcf.acp.stat.timeFormat.yearly');
2605 var $minTickSize
= [1, "month"];
2606 var $timeFormat
= WCF
.Language
.get('wcf.acp.stat.timeFormat.monthly');
2609 var $minTickSize
= [7, "day"];
2610 var $timeFormat
= WCF
.Language
.get('wcf.acp.stat.timeFormat.weekly');
2613 var $minTickSize
= [1, "day"];
2614 var $timeFormat
= WCF
.Language
.get('wcf.acp.stat.timeFormat.daily');
2631 minTickSize
: $minTickSize
,
2632 timeformat
: $timeFormat
,
2633 monthNames
: WCF
.Language
.get('__monthsShort')
2638 tickFormatter: function(val
) {
2639 return WCF
.String
.addThousandsSeparator(val
);
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;
2654 $.plot("#chart", $data
, options
);
2656 $("#chart").on("plothover", function(event
, pos
, 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();
2661 $("#chartTooltip").hide();
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>');
2672 * Namespace for ACP ad management.
2677 * Handles the location of an ad during ad creation/editing.
2679 WCF
.ACP
.Ad
.LocationHandler
= Class
.extend({
2681 * fieldset of the page conditions
2684 _pageConditions
: null,
2687 * select element for the page controller condition
2690 _pageControllers
: null,
2693 * Initializes a new WCF.ACP.Ad.LocationHandler object.
2696 this._pageConditions
= $('#pageConditions');
2697 this._pageControllers
= $('#pageControllers');
2699 var $dl
= this._pageControllers
.parents('dl:eq(0)');
2701 // hide the page controller element
2704 var $fieldset
= $dl
.parent('fieldset');
2705 if (!$fieldset
.children('dl:visible').length
) {
2709 var $nextFieldset
= $fieldset
.next('fieldset');
2710 if ($nextFieldset
) {
2711 $nextFieldset
.data('margin-top', $nextFieldset
.css('margin-top'));
2712 $nextFieldset
.css('margin-top', 0);
2715 // fix the margin of a potentially next page condition element
2716 $dl
.next('dl').css('margin-top', 0);
2718 $('#objectTypeID').on('change', $.proxy(this._setPageController
, this));
2720 this._setPageController();
2722 $('#adForm').submit($.proxy(this._submit
, this));
2726 * Sets the page controller based on the selected ad location.
2728 _setPageController: function() {
2729 var $option
= $('#objectTypeID').find('option:checked');
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();
2737 this._pageControllers
.val([]).change();
2742 * Handles submitting the ad form.
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();
2751 // reset page controller conditions to avoid creation of
2752 // unnecessary conditions
2753 this._pageControllers
.val([]);
2759 * Initialize WCF.ACP.Tag namespace.
2764 * Handles setting tags as synonyms of another tag by clipboard.
2766 WCF
.ACP
.Tag
.SetAsSynonymsHandler
= Class
.extend({
2768 * dialog to select the "main" tag
2774 * ids of the selected tags
2775 * @var array<integer>
2780 * Initializes the SetAsSynonymsHandler object.
2782 * @param array<integer> objectIDs
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));
2797 * Handles clipboard actions.
2799 * @param object event
2800 * @param string type
2801 * @param string actionName
2802 * @param object parameters
2804 _execute: function(event
, type
, actionName
, parameters
) {
2805 if (type
!== 'com.woltlab.wcf.tag' || actionName
!== 'com.woltlab.wcf.tag.setAsSynonyms') {
2809 this._objectIDs
= parameters
.objectIDs
;
2810 if (this._dialog
=== null) {
2811 this._dialog
= $('<div id="setAsSynonymsDialog" />').hide().appendTo(document
.body
);
2812 this._dialog
.wcfDialog({
2814 title
: WCF
.Language
.get('wcf.acp.tag.setAsSynonyms')
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(); });
2824 * Saves the tags as synonyms.
2826 _submit: function() {
2827 new WCF
.Action
.Proxy({
2830 actionName
: 'setAsSynonyms',
2831 className
: 'wcf\\data\\tag\\TagAction',
2832 objectIDs
: this._objectIDs
,
2834 tagID
: this._dialog
.find('input[name="tagID"]:checked').val()
2837 success
: $.proxy(function() {
2838 this._dialog
.wcfDialog('close');
2840 new WCF
.System
.Notification().show(function() {
2841 window
.location
.reload();