Add abstract object bulk processing (WIP)
authorMatthias Schmidt <gravatronics@live.com>
Mon, 15 Jun 2015 18:03:19 +0000 (20:03 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Mon, 15 Jun 2015 18:03:19 +0000 (20:03 +0200)
…and replace the current user bulk processing form with the new system.

42 files changed:
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/objectTypeDefinition.xml
wcfsetup/install/files/acp/templates/bulkProcessing.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/exportMailAddressUserBulkProcessing.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/sendMailUserBulkProcessing.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/userBulkProcessing.tpl [deleted file]
wcfsetup/install/files/acp/templates/userConditions.tpl
wcfsetup/install/files/acp/templates/userGroupListUserBulkProcessing.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/AbstractBulkProcessingForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/UserBulkProcessingForm.class.php
wcfsetup/install/files/lib/system/bulk/processing/AbstractBulkProcessableObjectType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/AbstractBulkProcessingAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/IBulkProcessableObjectType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/IBulkProcessingAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/user/AbstractUserBulkProcessingAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/user/AbstractUserGroupsUserBulkProcessingAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/user/AssignToUserGroupsUserBulkProcessingAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/user/DeleteUserBulkProcessingAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/user/ExportMailAddressUserBulkProcessingAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/user/RemoveFromUserGroupsUserBulkProcessingAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/user/SendMailUserBulkProcessingAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bulk/processing/user/UserBulkProcessableObjectType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/AbstractObjectTextPropertyCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/AbstractTextCondition.class.php
wcfsetup/install/files/lib/system/condition/AbstractTimestampCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/IObjectCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/IObjectListCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/TObjectListUserCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/TObjectUserCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/UserAvatarCondition.class.php
wcfsetup/install/files/lib/system/condition/UserEmailCondition.class.php
wcfsetup/install/files/lib/system/condition/UserGroupCondition.class.php
wcfsetup/install/files/lib/system/condition/UserIntegerPropertyCondition.class.php
wcfsetup/install/files/lib/system/condition/UserLanguageCondition.class.php
wcfsetup/install/files/lib/system/condition/UserOptionsCondition.class.php
wcfsetup/install/files/lib/system/condition/UserRegistrationDateCondition.class.php
wcfsetup/install/files/lib/system/condition/UserRegistrationDateIntervalCondition.class.php
wcfsetup/install/files/lib/system/condition/UserStateCondition.class.php
wcfsetup/install/files/lib/system/condition/UserTimestampPropertyCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/UserUsernameCondition.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 0b2ef40b9c1496f4a72cd0a6a91f7305e069911b..cc1534a73c22d7faa2542f92b2c6cde77fa71f21 100644 (file)
                        <definitionname>com.woltlab.wcf.payment.type</definitionname>
                        <classname><![CDATA[wcf\system\payment\type\PaidSubscriptionPaymentType]]></classname>
                </type>
+               
+               <!-- bulk processable objects -->
+               <type>
+                       <name>com.woltlab.wcf.bulkProcessing.user</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessableObject</definitionname>
+                       <classname><![CDATA[wcf\system\bulk\processing\user\UserBulkProcessableObjectType]]></classname>
+               </type>
+               <!-- /bulk processable objects -->
+               
+               <!-- user bulk processing actions -->
+               <type>
+                       <name>com.woltlab.wcf.delete</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.action</definitionname>
+                       <classname><![CDATA[wcf\system\bulk\processing\user\DeleteUserBulkProcessingAction]]></classname>
+                       <action>delete</action>
+                       <permissions>admin.user.canDeleteUser</permissions>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.assignToUserGroups</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.action</definitionname>
+                       <classname><![CDATA[wcf\system\bulk\processing\user\AssignToUserGroupsUserBulkProcessingAction]]></classname>
+                       <action>assignToUserGroups</action>
+                       <permissions>admin.user.canEditUser</permissions>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.removeFromUserGroups</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.action</definitionname>
+                       <classname><![CDATA[wcf\system\bulk\processing\user\RemoveFromUserGroupsUserBulkProcessingAction]]></classname>
+                       <action>removeFromUserGroups</action>
+                       <permissions>admin.user.canEditUser</permissions>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.sendMail</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.action</definitionname>
+                       <classname><![CDATA[wcf\system\bulk\processing\user\SendMailUserBulkProcessingAction]]></classname>
+                       <action>sendMail</action>
+                       <permissions>admin.user.canMailUser</permissions>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.exportMailAddress</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.action</definitionname>
+                       <classname><![CDATA[wcf\system\bulk\processing\user\ExportMailAddressUserBulkProcessingAction]]></classname>
+                       <action>exportMailAddress</action>
+                       <permissions>admin.user.canMailUser</permissions>
+               </type>
+               <!-- /user bulk processing actions -->
+               
+               <!-- user bulk processing conditions -->
+               <type>
+                       <name>com.woltlab.wcf.username</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserUsernameCondition]]></classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.email</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserEmailCondition]]></classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.userGroup</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserGroupCondition]]></classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.languages</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserLanguageCondition]]></classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.registrationDate</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserRegistrationDateCondition]]></classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.registrationDateInterval</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserRegistrationDateIntervalCondition]]></classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.lastActivityTime</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserTimestampPropertyCondition]]></classname>
+                       <conditiongroup>general</conditiongroup>
+                       <propertyname>lastActivityTime</propertyname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.avatar</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserAvatarCondition]]></classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.state</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserStateCondition]]></classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.activityPoints</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserIntegerPropertyCondition]]></classname>
+                       <conditiongroup>contents</conditiongroup>
+                       <propertyname>activityPoints</propertyname>
+                       <minvalue>0</minvalue>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.likesReceived</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserIntegerPropertyCondition]]></classname>
+                       <conditiongroup>contents</conditiongroup>
+                       <propertyname>likesReceived</propertyname>
+                       <minvalue>0</minvalue>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.userOptions</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname><![CDATA[wcf\system\condition\UserOptionsCondition]]></classname>
+                       <conditiongroup>userOptions</conditiongroup>
+               </type>
+               <!-- /user bulk processing conditions -->
        </import>
 </data>
index ef7e89931c0b2ee1dcbab4972a3d528da62f89ce..c2e7b7ee1043ef2c15dbd56ecf2220a890ca8e89 100644 (file)
                        <name>com.woltlab.wcf.payment.type</name>
                        <interfacename><![CDATA[wcf\system\payment\type\IPaymentType]]></interfacename>
                </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.bulkProcessableObject</name>
+                       <interfacename><![CDATA[wcf\system\bulk\processing\IBulkProcessableObjectType]]></interfacename>
+               </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.bulkProcessing.user.condition</name>
+                       <interfacename><![CDATA[wcf\system\condition\IObjectListCondition]]></interfacename>
+               </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.bulkProcessing.user.action</name>
+                       <interfacename><![CDATA[wcf\system\bulk\processing\IBulkProcessingAction]]></interfacename>
+               </definition>
        </import>
        
        <delete>
diff --git a/wcfsetup/install/files/acp/templates/bulkProcessing.tpl b/wcfsetup/install/files/acp/templates/bulkProcessing.tpl
new file mode 100644 (file)
index 0000000..a5760ff
--- /dev/null
@@ -0,0 +1,86 @@
+{include file='header' pageTitle=$objectType->getProcessor()->getLanguageItemPrefix()}
+
+<script data-relocate="true">
+       require(['WoltLab/WCF/UI/TabMenu'], function(UITabMenu) {
+               UITabMenu.setup();
+               
+               function toggleActionOptions(event) {
+                       var actionName = event.currentTarget.getAttribute('value');
+                       var actionSettings = document.getElementsByClassName('jsBulkProcessingActionSettings');
+                       for (var i = 0, length = actionSettings.length; i < length; i++) {
+                               var settings = actionSettings[i];
+                               
+                               if (settings.getAttribute('data-action') === actionName) {
+                                       settings.style.removeProperty('display');
+                               }
+                               else {
+                                       settings.style.setProperty('display', 'none');
+                               }
+                       }
+               };
+               
+               var actions = document.querySelectorAll('input[name=action]');
+               for (var i = 0, length = actions.length; i < length; i++) {
+                       actions[i].addEventListener('change', toggleActionOptions);
+               }
+       });
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}{$objectType->getProcessor()->getLanguageItemPrefix()}{/lang}</h1>
+</header>
+
+{include file='formError'}
+
+<p class="warning">{hascontent}{content}{lang __optional=true}{$objectType->getProcessor()->getLanguageItemPrefix()}.warning{/lang}{/content}{hascontentelse}{lang}wcf.global.bulkProcessing.warning{/lang}{/hascontent}</p>
+
+{if $success|isset}
+       <p class="success">{lang}{$objectType->getProcessor()->getLanguageItemPrefix()}.success{/lang}</p>
+{/if}
+
+<form method="post" action="{link controller=$controller}{/link}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}{$objectType->getProcessor()->getLanguageItemPrefix()}.action{/lang}</legend>
+                       
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       {foreach from=$actions item=actionObjectType}
+                                               <label><input type="radio" name="action" value="{@$actionObjectType->action}" {if $actionObjectType->action == $action}checked="checked" {/if}/> {lang}{$objectType->getProcessor()->getLanguageItemPrefix()}.{@$actionObjectType->action}{/lang}</label>
+                                       {/foreach}
+                                       
+                                       {if $errorField == 'action'}
+                                               <small class="innerError">
+                                                       {lang}wcf.global.form.error.{@$errorType}{/lang}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+               </fieldset>
+               
+               {foreach from=$actions item=actionObjectType}
+                       {if $actionObjectType->getProcessor()->getHTML()}
+                               <fieldset class="jsBulkProcessingActionSettings" data-action="{@$actionObjectType->action}" {if $actionObjectType->action != $action}style="display: none;"{/if}>
+                                       <legend>{lang}{$objectType->getProcessor()->getLanguageItemPrefix()}.{@$actionObjectType->action}{/lang}</legend>
+                                       
+                                       {@$actionObjectType->getProcessor()->getHTML()}
+                               </fieldset>
+                       {/if}
+               {/foreach}
+       </div>
+       
+       <header class="boxHeadline boxSubHeadline">
+               <h2>{lang}{$objectType->getProcessor()->getLanguageItemPrefix()}.conditions{/lang}</h2>
+               {hascontent}<small>{content}{lang __optional=true}{$objectType->getProcessor()->getLanguageItemPrefix()}.conditions.descriptions{/lang}{/content}</small>{/hascontent}
+       </header>
+       
+       {@$objectType->getProcessor()->getConditionHTML()}
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/exportMailAddressUserBulkProcessing.tpl b/wcfsetup/install/files/acp/templates/exportMailAddressUserBulkProcessing.tpl
new file mode 100644 (file)
index 0000000..7f6a311
--- /dev/null
@@ -0,0 +1,40 @@
+<dl>
+       <dt><label>{lang}wcf.acp.user.exportEmailAddress.fileType{/lang}</label></dt>
+       <dd>
+               <label><input type="radio" name="fileType" value="csv" {if $fileType == 'csv'}checked="checked" {/if}/> {lang}wcf.acp.user.exportEmailAddress.fileType.csv{/lang}</label>
+               <label><input type="radio" name="fileType" value="xml" {if $fileType == 'xml'}checked="checked" {/if}/> {lang}wcf.acp.user.exportEmailAddress.fileType.xml{/lang}</label>
+       </dd>
+</dl>
+
+<dl id="separatorDiv"{if $fileType == 'xml'} style="display: none;"{/if}>
+       <dt><label for="separator">{lang}wcf.acp.user.exportEmailAddress.separator{/lang}</label></dt>
+       <dd>
+               <input type="text" id="separator" name="separator" value="{$separator}" class="medium" />
+       </dd>
+</dl>
+
+<dl id="textSeparatorDiv"{if $fileType == 'xml'} style="display: none;"{/if}>
+       <dt><label for="textSeparator">{lang}wcf.acp.user.exportEmailAddress.textSeparator{/lang}</label></dt>
+       <dd>
+               <input type="text" id="textSeparator" name="textSeparator" value="{$textSeparator}" class="medium" />
+       </dd>
+</dl>
+
+<script data-relocate="true">
+       function toggleExportOptions(event) {
+               var fileType = event.currentTarget.getAttribute('value');
+               if (fileType === 'csv') {
+                       document.getElementById('separatorDiv').style.removeProperty('display');
+                       document.getElementById('textSeparatorDiv').style.removeProperty('display');
+               }
+               else {
+                       document.getElementById('separatorDiv').style.setProperty('display', 'none');
+                       document.getElementById('textSeparatorDiv').style.setProperty('display', 'none');
+               }
+       };
+       
+       var fileTypes = document.querySelectorAll('input[name=fileType]');
+       for (var i = 0, length = fileTypes.length; i < length; i++) {
+               fileTypes[i].addEventListener('change', toggleExportOptions);
+       }
+</script>
diff --git a/wcfsetup/install/files/acp/templates/sendMailUserBulkProcessing.tpl b/wcfsetup/install/files/acp/templates/sendMailUserBulkProcessing.tpl
new file mode 100644 (file)
index 0000000..960422c
--- /dev/null
@@ -0,0 +1,55 @@
+<dl{if $errorField == 'subject'} class="formError"{/if}>
+       <dt><label for="subject">{lang}wcf.acp.user.sendMail.subject{/lang}</label></dt>
+       <dd>
+               <input type="text" id="subject" name="subject" value="{$subject}" class="long" />
+               {if $errorField == 'subject'}
+                       <small class="innerError">
+                               {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+                       </small>
+               {/if}
+       </dd>
+</dl>
+
+<dl{if $errorField == 'from'} class="formError"{/if}>
+       <dt><label for="from">{lang}wcf.acp.user.sendMail.from{/lang}</label></dt>
+       <dd>
+               <input type="text" id="from" name="from" value="{$from}" class="medium" />
+               {if $errorField == 'from'}
+                       <small class="innerError">
+                               {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+                       </small>
+               {/if}
+               <small>{lang}wcf.acp.user.sendMail.from.description{/lang}</small>
+       </dd>
+</dl>
+
+<dl{if $errorField == 'text'} class="formError"{/if}>
+       <dt><label for="text">{lang}wcf.acp.user.sendMail.text{/lang}</label></dt>
+       <dd>
+               <textarea id="text" name="text" rows="15" cols="40">{$text}</textarea>
+               {if $errorField == 'text'}
+                       <small class="innerError" class="long">
+                               {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+                       </small>
+               {/if}
+       </dd>
+</dl>
+
+<dl>
+       <dt></dt>
+       <dd>
+               <label for="enableHTML"><input type="checkbox" id="enableHTML" name="enableHTML" value="1"{if $enableHTML == 1} checked="checked"{/if}/> {lang}wcf.acp.user.sendMail.enableHTML{/lang}</label>
+       </dd>
+</dl>
+
+{if !$mailID|empty}
+       <script data-relocate="true">
+               require(['Language'], function(Language) {
+                       Language.add('wcf.acp.worker.abort.confirmMessage', '{lang}wcf.acp.worker.abort.confirmMessage{/lang}');
+                       
+                       new WCF.ACP.Worker('mail', 'wcf\\system\\worker\\MailWorker', '', {
+                               mailID: {@$mailID}
+                       });
+               });
+       </script>
+{/if}
diff --git a/wcfsetup/install/files/acp/templates/userBulkProcessing.tpl b/wcfsetup/install/files/acp/templates/userBulkProcessing.tpl
deleted file mode 100644 (file)
index 9681e22..0000000
+++ /dev/null
@@ -1,347 +0,0 @@
-{include file='header' pageTitle='wcf.acp.user.bulkProcessing'}
-
-{if $mailID|isset}
-       <script data-relocate="true">
-               //<![CDATA[
-               $(function() {
-                       WCF.Language.add('wcf.acp.worker.abort.confirmMessage', '{lang}wcf.acp.worker.abort.confirmMessage{/lang}');
-                       
-                       new WCF.ACP.Worker('mail', 'wcf\\system\\worker\\MailWorker', '', {
-                               mailID: {@$mailID}
-                       });
-               });
-               //]]>
-       </script>
-{/if}
-
-<script data-relocate="true">
-       //<![CDATA[
-       $(function() {
-               function toggleContainer(value) {
-                       for (var $name in $targetContainers) {
-                               if ($name === value) {
-                                       $targetContainers[$name].show();
-                               }
-                               else {
-                                       $targetContainers[$name].hide();
-                               }
-                       }
-               }
-               
-               var $targetContainers = { };
-               $('input[name=action]').each(function(index, input) {
-                       var $input = $(input);
-                       var $value = $input.prop('value');
-                       
-                       if (!$targetContainers[$value]) {
-                               var $container = $('#' + $.wcfEscapeID($value + 'Div'));
-                               if ($container.length) {
-                                       $targetContainers[$value] = $container;
-                               }
-                       }
-                       
-                       $input.change(function(event) {
-                               toggleContainer($(event.currentTarget).prop('value'));
-                       });
-               });
-               
-               function setFileType(newType) {
-                       if (newType === 'csv') {
-                               $('#separatorDiv').show().next().show();
-                       }
-                       else {
-                               $('#separatorDiv').hide().next().hide();
-                       }
-               }
-               
-               $('input[name=fileType]').each(function(index, input) {
-                       var $input = $(input);
-                       
-                       $input.change(function(event) {
-                               setFileType($input.prop('value'));
-                       });
-               });
-               
-               toggleContainer('{@$action}');
-               setFileType('{@$fileType}');
-               
-               new WCF.Search.User($('#username'), function(data) {
-                       $('#username').val(data.label);
-                       return false;
-               }, false);
-               WCF.TabMenu.init();
-       });
-       //]]>
-</script>
-
-<header class="boxHeadline">
-       <h1>{lang}wcf.acp.user.bulkProcessing{/lang}</h1>
-</header>
-
-{include file='formError'}
-
-<p class="warning">{lang}wcf.acp.user.bulkProcessing.warning{/lang}</p>
-
-{if $affectedUsers|isset}
-       <p class="success">{lang}wcf.acp.user.bulkProcessing.success{/lang}</p>
-{/if}
-
-<div class="contentNavigation">
-       {hascontent}
-               <nav>
-                       <ul>
-                               {content}
-                                       {event name='contentNavigationButtons'}
-                               {/content}
-                       </ul>
-               </nav>
-       {/hascontent}
-</div>
-
-<form method="post" action="{link controller='UserBulkProcessing'}{/link}">
-       <div class="tabMenuContainer" data-active="{$activeTabMenuItem}" data-store="activeTabMenuItem">
-               <nav class="tabMenu">
-                       <ul>
-                               <li><a href="{@$__wcf->getAnchor('conditions')}">{lang}wcf.acp.user.search.conditions{/lang}</a></li>
-                               
-                               {if $options|count}
-                                       <li><a href="{@$__wcf->getAnchor('profile')}">{lang}wcf.acp.user.search.conditions.profile{/lang}</a></li>
-                               {/if}
-                               
-                               <li><a href="{@$__wcf->getAnchor('action')}">{lang}wcf.acp.user.bulkProcessing.action{/lang}</a></li>
-                               {event name='tabMenuTabs'}
-                       </ul>
-               </nav>
-               
-               <div id="conditions" class="container containerPadding tabMenuContent">
-                       <fieldset>
-                               <legend>{lang}wcf.acp.user.search.conditions{/lang}</legend>
-                               
-                               <dl>
-                                       <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
-                                       <dd>
-                                               <input type="text" id="username" name="username" value="{$username}" autofocus="autofocus" class="medium" />
-                                       </dd>
-                               </dl>
-                               
-                               {if $__wcf->session->getPermission('admin.user.canEditMailAddress')}
-                                       <dl>
-                                               <dt><label for="email">{lang}wcf.user.email{/lang}</label></dt>
-                                               <dd>
-                                                       <input type="text" id="email" name="email" value="{$email}" class="medium" />
-                                               </dd>
-                                       </dl>
-                               {/if}
-                               
-                               {if $availableGroups|count}
-                                       <dl>
-                                               <dt><label>{lang}wcf.acp.user.groups{/lang}</label></dt>
-                                               <dd>
-                                                       {htmlCheckboxes options=$availableGroups name='groupIDs' selected=$groupIDs}
-                                                       
-                                                       <label class="marginTop"><input type="checkbox" name="invertGroupIDs" value="1" {if $invertGroupIDs == 1}checked="checked" {/if}/> {lang}wcf.acp.user.groups.invertSearch{/lang}</label>
-                                               </dd>
-                                       </dl>
-                               {/if}
-                               
-                               {if $availableLanguages|count > 1}
-                                       <dl>
-                                               <dt><label>{lang}wcf.user.language{/lang}</label></dt>
-                                               <dd>
-                                                       {htmlCheckboxes options=$availableLanguages name='languageIDs' selected=$languageIDs disableEncoding=true}
-                                               </dd>
-                                       </dl>
-                               {/if}
-                               
-                               <dl>
-                                       <dt><label for="registrationDateStart">{lang}wcf.user.registrationDate{/lang}</label></dt>
-                                       <dd>
-                                               <input type="date" id="registrationDateStart" name="registrationDateStart" value="{$registrationDateStart}" placeholder="{lang}wcf.date.period.start{/lang}" />
-                                               <input type="date" id="registrationDateEnd" name="registrationDateEnd" value="{$registrationDateEnd}" placeholder="{lang}wcf.date.period.end{/lang}" />
-                                       </dd>
-                               </dl>
-                               
-                               <dl>
-                                       <dt><label for="lastActivityTimeStart">{lang}wcf.user.lastActivityTime{/lang}</label></dt>
-                                       <dd>
-                                               <input type="date" id="lastActivityTimeStart" name="lastActivityTimeStart" value="{$lastActivityTimeStart}" placeholder="{lang}wcf.date.period.start{/lang}" />
-                                               <input type="date" id="lastActivityTimeEnd" name="lastActivityTimeEnd" value="{$lastActivityTimeEnd}" placeholder="{lang}wcf.date.period.end{/lang}" />
-                                       </dd>
-                               </dl>
-                               
-                               {event name='conditionFields'}
-                       </fieldset>
-                       
-                       <fieldset>
-                               <legend>{lang}wcf.acp.user.search.conditions.states{/lang}</legend>
-                               
-                               <dl>
-                                       <dt></dt>
-                                       <dd>
-                                               <label><input type="checkbox" name="banned" value="1" {if $banned == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.banned{/lang}</label>
-                                               <label><input type="checkbox" name="notBanned" value="1" {if $notBanned == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.notBanned{/lang}</label>
-                                               <label><input type="checkbox" name="enabled" value="1" {if $enabled == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.enabled{/lang}</label>
-                                               <label><input type="checkbox" name="disabled" value="1" {if $disabled == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.disabled{/lang}</label>
-                                               
-                                               {event name='states'}
-                                       </dd>
-                               </dl>
-                               
-                               {event name='stateFields'}
-                       </fieldset>
-                       
-                       {event name='conditionFieldsets'}
-               </div>
-               
-               {if $options|count}
-                       <div id="profile" class="container containerPadding tabMenuContent">
-                               <fieldset>
-                                       <legend>{lang}wcf.acp.user.search.conditions.profile{/lang}</legend>
-                                       
-                                       {include file='optionFieldList' langPrefix='wcf.user.option.'}
-                               </fieldset>
-                               
-                               {event name='profileFieldsets'}
-                       </div>
-               {/if}
-               
-               <div id="action" class="container containerPadding tabMenuContent">
-                       <fieldset{if $errorField == 'action'} class="formError"{/if}>
-                               <legend>{lang}wcf.acp.user.bulkProcessing.action{/lang}</legend>
-                               
-                               <dl>
-                                       <dt></dt>
-                                       <dd>
-                                               {if $__wcf->session->getPermission('admin.user.canMailUser')}
-                                                       <label><input type="radio" name="action" value="sendMail" {if $action == 'sendMail'}checked="checked" {/if}/> {lang}wcf.acp.user.sendMail{/lang}</label>
-                                                       <label><input type="radio" name="action" value="exportMailAddress" {if $action == 'exportMailAddress'}checked="checked" {/if}/> {lang}wcf.acp.user.exportEmailAddress{/lang}</label>
-                                               {/if}
-                                               {if $__wcf->session->getPermission('admin.user.canEditUser')}
-                                                       <label><input type="radio" name="action" value="assignToGroup" {if $action == 'assignToGroup'}checked="checked" {/if}/> {lang}wcf.acp.user.assignToGroup{/lang}</label>
-                                               {/if}
-                                               {if $__wcf->session->getPermission('admin.user.canDeleteUser')}
-                                                       <label><input type="radio" name="action" value="delete" {if $action == 'delete'}checked="checked" {/if}/> {lang}wcf.acp.user.delete{/lang}</label>
-                                               {/if}
-                                               
-                                               {event name='actions'}
-                                               
-                                               {if $errorField == 'action'}
-                                                       <small class="innerError">
-                                                               {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
-                                                       </small>
-                                               {/if}
-                                       </dd>
-                               </dl>
-                       </fieldset>
-                       
-                       {if $__wcf->session->getPermission('admin.user.canMailUser')}
-                               <div id="sendMailDiv">
-                                       <fieldset>
-                                               <legend>{lang}wcf.acp.user.sendMail.mail{/lang}</legend>
-                                               
-                                               <dl{if $errorField == 'subject'} class="formError"{/if}>
-                                                       <dt><label for="subject">{lang}wcf.acp.user.sendMail.subject{/lang}</label></dt>
-                                                       <dd>
-                                                               <input type="text" id="subject" name="subject" value="{$subject}" class="long" />
-                                                               {if $errorField == 'subject'}
-                                                                       <small class="innerError">
-                                                                               {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
-                                                                       </small>
-                                                               {/if}
-                                                       </dd>
-                                               </dl>
-                                               
-                                               <dl{if $errorField == 'from'} class="formError"{/if}>
-                                                       <dt><label for="from">{lang}wcf.acp.user.sendMail.from{/lang}</label></dt>
-                                                       <dd>
-                                                               <input type="text" id="from" name="from" value="{$from}" class="medium" />
-                                                               {if $errorField == 'from'}
-                                                                       <small class="innerError">
-                                                                               {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
-                                                                       </small>
-                                                               {/if}
-                                                               <small>{lang}wcf.acp.user.sendMail.from.description{/lang}</small>
-                                                       </dd>
-                                               </dl>
-                                               
-                                               <dl{if $errorField == 'text'} class="formError"{/if}>
-                                                       <dt><label for="text">{lang}wcf.acp.user.sendMail.text{/lang}</label></dt>
-                                                       <dd>
-                                                               <textarea id="text" name="text" rows="15" cols="40">{$text}</textarea>
-                                                               {if $errorField == 'text'}
-                                                                       <small class="innerError" class="long">
-                                                                               {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
-                                                                       </small>
-                                                               {/if}
-                                                       </dd>
-                                               </dl>
-                                               
-                                               <dl>
-                                                       <dt></dt>
-                                                       <dd>
-                                                               <label for="enableHTML"><input type="checkbox" id="enableHTML" name="enableHTML" value="1"{if $enableHTML == 1} checked="checked"{/if}/> {lang}wcf.acp.user.sendMail.enableHTML{/lang}</label>
-                                                       </dd>
-                                               </dl>
-                                       </fieldset>
-                               </div>
-                               
-                               <div id="exportMailAddressDiv">
-                                       <fieldset>
-                                               <legend>{lang}wcf.acp.user.exportEmailAddress.format{/lang}</legend>
-                                               
-                                               <dl>
-                                                       <dt><label>{lang}wcf.acp.user.exportEmailAddress.fileType{/lang}</label></dt>
-                                                       <dd>
-                                                               <label><input type="radio" name="fileType" value="csv" {if $fileType == 'csv'}checked="checked" {/if}/> {lang}wcf.acp.user.exportEmailAddress.fileType.csv{/lang}</label>
-                                                               <label><input type="radio" name="fileType" value="xml" {if $fileType == 'xml'}checked="checked" {/if}/> {lang}wcf.acp.user.exportEmailAddress.fileType.xml{/lang}</label>
-                                                       </dd>
-                                               </dl>
-                                               
-                                               <dl id="separatorDiv">
-                                                       <dt><label for="separator">{lang}wcf.acp.user.exportEmailAddress.separator{/lang}</label></dt>
-                                                       <dd>
-                                                               <input type="text" id="separator" name="separator" value="{$separator}" class="medium" />
-                                                       </dd>
-                                               </dl>
-                                               
-                                               <dl id="textSeparatorDiv">
-                                                       <dt><label for="textSeparator">{lang}wcf.acp.user.exportEmailAddress.textSeparator{/lang}</label></dt>
-                                                       <dd>
-                                                               <input type="text" id="textSeparator" name="textSeparator" value="{$textSeparator}" class="medium" />
-                                                       </dd>
-                                               </dl>
-                                       </fieldset>
-                               </div>
-                       {/if}
-                       
-                       {if $__wcf->session->getPermission('admin.user.canEditUser')}
-                               <div id="assignToGroupDiv">
-                                       <fieldset>
-                                               <legend>{lang}wcf.acp.user.groups{/lang}</legend>
-                                               
-                                               <dl>
-                                                       <dt></dt>
-                                                       <dd{if $errorField == 'assignToGroupIDs'} class="formError"{/if}>
-                                                               {htmlCheckboxes options=$availableGroups name=assignToGroupIDs selected=$assignToGroupIDs}
-                                                               {if $errorField == 'assignToGroupIDs'}
-                                                                       <small class="innerError">
-                                                                               {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
-                                                                       </small>
-                                                               {/if}
-                                                       </dd>
-                                               </dl>
-                                       </fieldset>
-                               </div>
-                       {/if}
-                       
-                       {event name='actionFieldsets'}
-               </div>
-       </div>
-       
-       <div class="formSubmit">
-               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
-               {@SECURITY_TOKEN_INPUT_TAG}
-       </div>
-</form>
-
-{include file='footer'}
index d1382ad2468c6b872be2bd17ae21876f8ae2f670..1fdcb12201d710eda3b92f7b20e966a08608c11a 100644 (file)
@@ -1,3 +1,5 @@
+{if !$groupedObjectTypes|isset && $conditions|isset}{assign var='groupedObjectTypes' value=$conditions}{/if}
+
 <div class="tabMenuContainer">
        <nav class="tabMenu">
                <ul>
@@ -9,7 +11,7 @@
        </nav>
        
        {foreach from=$groupedObjectTypes key='conditionGroup' item='conditionObjectTypes'}
-               <div id="user_{$conditionGroup}" class="container containerPadding tabMenuContainer tabMenuContent">
+               <div id="user_{$conditionGroup}" class="container containerPadding tabMenuContent">
                        {if $conditionGroup != 'userOptions'}
                                <fieldset>
                                        <legend>{lang}wcf.user.condition.conditionGroup.{$conditionGroup}{/lang}</legend>
diff --git a/wcfsetup/install/files/acp/templates/userGroupListUserBulkProcessing.tpl b/wcfsetup/install/files/acp/templates/userGroupListUserBulkProcessing.tpl
new file mode 100644 (file)
index 0000000..bd640a3
--- /dev/null
@@ -0,0 +1,9 @@
+<dl>
+       <dt></dt>
+       <dd{if $errorField == $inputName} class="formError"{/if}>
+               {htmlCheckboxes options=$availableUserGroups name=$inputName selected=$selectedUserGroupIDs}
+               {if $errorField == $inputName}
+                       <small class="innerError">{lang}wcf.global.form.error.{$errorType}{/lang}</small>
+               {/if}
+       </dd>
+</dl>
diff --git a/wcfsetup/install/files/lib/acp/form/AbstractBulkProcessingForm.class.php b/wcfsetup/install/files/lib/acp/form/AbstractBulkProcessingForm.class.php
new file mode 100644 (file)
index 0000000..349ee1d
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\SystemException;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * Abstract implementation of a form for bulk processing objects of a certain type.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+abstract class AbstractBulkProcessingForm extends AbstractForm {
+       /**
+        * object action object type types
+        * @var array<\wcf\data\object\type\ObjectType>
+        */
+       public $actions = array();
+       
+       /**
+        * number of objects affected by bulk processing
+        * @var integer
+        */
+       public $affectedObjectCount = 0;
+       
+       /**
+        * object condition object type types
+        * @var array<\wcf\data\object\type\ObjectType>
+        */
+       public $conditions = array();
+       
+       /**
+        * list with bulk processed objects
+        * @var \wcf\data\DatabaseObjectList
+        */
+       public $objectList = null;
+       
+       /**
+        * bulk processable object type
+        * @var \wcf\data\object\type\ObjectType
+        */
+       public $objectType = null;
+       
+       /**
+        * name of the bulk processable object type
+        * @var string
+        */
+       public $objectTypeName = '';
+       
+       /**
+        * @see \wcf\page\Abstractpage::$templateName
+        */
+       public $templateName = 'bulkProcessing';
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               $classParts = explode('\\', get_class($this));
+               
+               WCF::getTPL()->assign([
+                       'actions' => $this->actions,
+                       'affectedObjectCount' => $this->affectedObjectCount,
+                       'controller' => str_replace('Form', '', array_pop($classParts)),
+                       'conditions' => $this->conditions,
+                       'objectType' => $this->objectType
+               ]);
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               // read bulk processable object type
+               $this->objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.bulkProcessableObject', $this->objectTypeName);
+               if ($this->objectType === null) {
+                       throw new SystemException("Unknown bulk processable object type '".$this->objectTypeName."'");
+               }
+               
+               // read conditions
+               if (ObjectTypeCache::getInstance()->getDefinitionByName($this->objectType->getProcessor()->getConditionObjectTypeDefinition()) === null) {
+                       throw new SystemException("Unknown condition object type definition '".$this->objectType->getProcessor()->getConditionObjectTypeDefinition()."'");
+               }
+               $conditionObjectTypes = ObjectTypeCache::getInstance()->getObjectTypes($this->objectType->getProcessor()->getConditionObjectTypeDefinition());
+               if (empty($conditionObjectTypes)) {
+                       throw new IllegalLinkException();
+               }
+               
+               foreach ($conditionObjectTypes as $objectType) {
+                       if ($objectType->conditiongroup) {
+                               if (!isset($this->conditions[$objectType->conditiongroup])) {
+                                       $this->conditions[$objectType->conditiongroup] = [ ];
+                               }
+                               
+                               $this->conditions[$objectType->conditiongroup][$objectType->objectTypeID] = $objectType;
+                       }
+                       else {
+                               $this->conditions[''][$objectType->objectTypeID] = $objectType;
+                       }
+               }
+               
+               // read actions
+               if (ObjectTypeCache::getInstance()->getDefinitionByName($this->objectType->getProcessor()->getActionObjectTypeDefinition()) === null) {
+                       throw new SystemException("Unknown action object type definition '".$this->objectType->getProcessor()->getActionObjectTypeDefinition()."'");
+               }
+               
+               $actions = ObjectTypeCache::getInstance()->getObjectTypes($this->objectType->getProcessor()->getActionObjectTypeDefinition());
+               foreach ($actions as $objectType) {
+                       if (isset($this->actions[$objectType->action])) {
+                               throw new SystemException("Duplicate action with name '".$objectType->action."'");
+                       }
+                       
+                       if ($objectType->validateOptions() && $objectType->validatePermissions()) {
+                               $this->actions[$objectType->action] = $objectType;
+                       }
+               }
+               if (empty($this->actions)) {
+                       throw new IllegalLinkException();
+               }
+               
+               parent::readData();
+       }
+       
+       /**
+        * @see \wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               foreach ($this->conditions as $groupedObjectTypes) {
+                       foreach ($groupedObjectTypes as $objectType) {
+                               $objectType->getProcessor()->readFormParameters();
+                       }
+               }
+               
+               if (isset($this->actions[$this->action])) {
+                       $this->actions[$this->action]->getProcessor()->readFormParameters();
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               $this->objectList = $this->actions[$this->action]->getProcessor()->getObjectList();
+               
+               parent::save();
+               
+               // read objects
+               foreach ($this->conditions as $groupedObjectTypes) {
+                       foreach ($groupedObjectTypes as $objectType) {
+                               $data = $objectType->getProcessor()->getData();
+                               if ($data !== null) {
+                                       $objectType->getProcessor()->addObjectListCondition($this->objectList, $data);
+                               }
+                       }
+               }
+               $this->objectList->readObjects();
+               
+               // execute action
+               $this->actions[$this->action]->getProcessor()->executeAction($this->objectList);
+               
+               $this->affectedObjectCount = count($this->objectList);
+               
+               $this->saved();
+               
+               // reset fields
+               $this->actions[$this->action]->getProcessor()->reset();
+               
+               foreach ($this->conditions as $groupedObjectTypes) {
+                       foreach ($groupedObjectTypes as $objectType) {
+                               $objectType->getProcessor()->reset();
+                       }
+               }
+               $this->action = '';
+               
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @see \wcf\form\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+               
+               // validate action
+               if (empty($this->action)) {
+                       throw new UserInputException('action');
+               }
+               
+               if (!isset($this->actions[$this->action])) {
+                       throw new UserInputException('action', 'noValidSelection');
+               }
+               
+               $this->actions[$this->action]->getProcessor()->validate();
+               
+               // validate conditions
+               foreach ($this->conditions as $groupedObjectTypes) {
+                       foreach ($groupedObjectTypes as $objectType) {
+                               $objectType->getProcessor()->validate();
+                       }
+               }
+       }
+}
index 8091c4c44d5ca0790a6ca6b4a3b21fa55fac7aff..7b15382c75cc542fa3cc060acd0dce4625435297 100644 (file)
 <?php
 namespace wcf\acp\form;
-use wcf\data\user\group\UserGroup;
-use wcf\data\user\User;
-use wcf\data\user\UserAction;
-use wcf\data\user\UserEditor;
-use wcf\form\AbstractForm;
-use wcf\system\database\util\PreparedStatementConditionBuilder;
-use wcf\system\event\EventHandler;
-use wcf\system\exception\PermissionDeniedException;
-use wcf\system\exception\UserInputException;
-use wcf\system\language\LanguageFactory;
-use wcf\system\menu\acp\ACPMenu;
-use wcf\system\user\storage\UserStorageHandler;
-use wcf\system\WCF;
-use wcf\system\WCFACP;
-use wcf\util\ArrayUtil;
-use wcf\util\StringUtil;
 
 /**
  * Shows the user bulk processing form.
  * 
- * @author     Marcel Werk
+ * @author     Matthias Schmidt
  * @copyright  2001-2015 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
  * @subpackage acp.form
  * @category   Community Framework
  */
-class UserBulkProcessingForm extends UserOptionListForm {
+class UserBulkProcessingForm extends AbstractBulkProcessingForm {
        /**
-        * @see \wcf\page\AbstractPage::$neededPermissions
+        * @see \wcf\page\AbstractPage::$activeMenuItem
         */
-       public $neededPermissions = array('admin.user.canEditUser', 'admin.user.canDeleteUser', 'admin.user.canMailUser');
+       public $activeMenuItem = 'wcf.acp.menu.link.user.bulkProcessing';
        
        /**
-        * searched username
-        * @var string
+        * @see \wcf\acp\form\AbstractBulkProcessingForm::$objectTypeName
         */
-       public $username = '';
-       
-       /**
-        * searched email adress
-        * @var string
-        */
-       public $email = '';
-       
-       /**
-        * ids of the searched user group ids
-        * @var array<integer>
-        */
-       public $groupIDs = array();
-       
-       /**
-        * ids of the users' languages
-        * @var array<integer>
-        */
-       public $languageIDs = array();
-       
-       /**
-        * indicates if the user may not be in the user groups with the selected
-        * ids
-        * @var integer
-        */
-       public $invertGroupIDs = 0;
-       
-       /**
-        * registration start date
-        * @var string
-        */
-       public $registrationDateStart = '';
-       
-       /**
-        * registration start date
-        * @var string
-        */
-       public $registrationDateEnd = '';
-       
-       /**
-        * banned state
-        * @var boolean
-        */
-       public $banned = 0;
-       
-       /**
-        * not banned state
-        * @var boolean
-        */
-       public $notBanned = 0;
-       
-       /**
-        * last activity start time
-        * @var string
-        */
-       public $lastActivityTimeStart = '';
-       
-       /**
-        * last activity end time
-        * @var string
-        */
-       public $lastActivityTimeEnd = '';
-       
-       /**
-        * enabled state
-        * @var boolean
-        */
-       public $enabled = 0;
-       
-       /**
-        * disabled state
-        * @var boolean
-        */
-       public $disabled = 0;
-       
-       // assign to group
-       public $assignToGroupIDs = array();
-       
-       // export mail address
-       public $fileType = 'csv';
-       public $separator = ',';
-       public $textSeparator = '"';
-       
-       // send mail
-       public $subject = '';
-       public $text = '';
-       public $from = '';
-       public $enableHTML = 0;
-       
-       // data
-       public $availableGroups = array();
-       public $options = array();
-       public $availableActions = array('sendMail', 'exportMailAddress', 'assignToGroup', 'delete');
-       public $affectedUsers = 0;
-       
-       /**
-        * conditions builder object
-        * @var \wcf\system\database\condition\PreparedStatementConditionBuilder
-        */
-       public $conditions = null;
-       
-       /**
-        * options of the active category
-        * @var array
-        */
-       public $activeOptions = array();
-       
-       /**
-        * @see \wcf\form\IForm::readFormParameters()
-        */
-       public function readFormParameters() {
-               parent::readFormParameters();
-               
-               if (isset($_POST['username'])) $this->username = StringUtil::trim($_POST['username']);
-               if (isset($_POST['email'])) $this->email = StringUtil::trim($_POST['email']);
-               if (isset($_POST['groupIDs']) && is_array($_POST['groupIDs'])) $this->groupIDs = ArrayUtil::toIntegerArray($_POST['groupIDs']);
-               if (isset($_POST['languageIDs']) && is_array($_POST['languageIDs'])) $this->languageIDs = ArrayUtil::toIntegerArray($_POST['languageIDs']);
-               if (isset($_POST['invertGroupIDs'])) $this->invertGroupIDs = intval($_POST['invertGroupIDs']);
-               if (isset($_POST['registrationDateStart'])) $this->registrationDateStart = $_POST['registrationDateStart'];
-               if (isset($_POST['registrationDateEnd'])) $this->registrationDateEnd = $_POST['registrationDateEnd'];
-               if (isset($_POST['banned'])) $this->banned = intval($_POST['banned']);
-               if (isset($_POST['notBanned'])) $this->notBanned = intval($_POST['notBanned']);
-               if (isset($_POST['lastActivityTimeStart'])) $this->lastActivityTimeStart = $_POST['lastActivityTimeStart'];
-               if (isset($_POST['lastActivityTimeEnd'])) $this->lastActivityTimeEnd = $_POST['lastActivityTimeEnd'];
-               if (isset($_POST['enabled'])) $this->enabled = intval($_POST['enabled']);
-               if (isset($_POST['disabled'])) $this->disabled = intval($_POST['disabled']);
-               
-               // assign to group
-               if (isset($_POST['assignToGroupIDs']) && is_array($_POST['assignToGroupIDs'])) $this->assignToGroupIDs = ArrayUtil::toIntegerArray($_POST['assignToGroupIDs']);
-               // export mail address
-               if (isset($_POST['fileType']) && $_POST['fileType'] == 'xml') $this->fileType = $_POST['fileType'];
-               if (isset($_POST['separator'])) $this->separator = $_POST['separator'];
-               if (isset($_POST['textSeparator'])) $this->textSeparator = $_POST['textSeparator'];
-               // send mail
-               if (isset($_POST['subject'])) $this->subject = StringUtil::trim($_POST['subject']);
-               if (isset($_POST['text'])) $this->text = StringUtil::trim($_POST['text']);
-               if (isset($_POST['from'])) $this->from = StringUtil::trim($_POST['from']);
-               if (isset($_POST['enableHTML'])) $this->enableHTML = intval($_POST['enableHTML']);
-       }
-       
-       /**
-        * @see \wcf\form\IForm::validate()
-        */
-       public function validate() {
-               AbstractForm::validate();
-               
-               // action
-               if (!in_array($this->action, $this->availableActions)) {
-                       throw new UserInputException('action');
-               }
-               
-               // assign to group
-               if ($this->action == 'assignToGroup') {
-                       if (empty($this->assignToGroupIDs)) {
-                               throw new UserInputException('assignToGroupIDs');
-                       }
-               }
-               
-               // send mail
-               if ($this->action == 'sendMail') {
-                       if (empty($this->subject)) {
-                               throw new UserInputException('subject');
-                       }
-                       
-                       if (empty($this->text)) {
-                               throw new UserInputException('text');
-                       }
-                       
-                       if (empty($this->from)) {
-                               throw new UserInputException('from');
-                       }
-               }
-       }
-       
-       /**
-        * @see \wcf\form\IForm::save()
-        */
-       public function save() {
-               parent::save();
-               
-               // build conditions
-               $this->conditions = new PreparedStatementConditionBuilder();
-               
-               // deny self delete
-               if ($this->action == 'delete') {
-                       $this->conditions->add("user_table.userID <> ?", array(WCF::getUser()->userID));
-               }
-               
-               // static fields
-               if (!empty($this->username)) {
-                       $this->conditions->add("user_table.username LIKE ?", array('%'.addcslashes($this->username, '_%').'%'));
-               }
-               if (!empty($this->email)) {
-                       $this->conditions->add("user_table.email LIKE ?", array('%'.addcslashes($this->email, '_%').'%'));
-               }
-               if (!empty($this->groupIDs)) {
-                       $this->conditions->add("user_table.userID ".($this->invertGroupIDs == 1 ? 'NOT ' : '')."IN (SELECT userID FROM wcf".WCF_N."_user_to_group WHERE groupID IN (?))", array($this->groupIDs));
-               }
-               if (!empty($this->languageIDs)) {
-                       $this->conditions->add("user_table.languageID IN (?)", array($this->languageIDs));
-               }
-               
-               // registration date
-               if ($startDate = @strtotime($this->registrationDateStart)) {
-                       $this->conditions->add('user_table.registrationDate >= ?', array($startDate));
-               }
-               if ($endDate = @strtotime($this->registrationDateEnd)) {
-                       $this->conditions->add('user_table.registrationDate <= ?', array($endDate));
-               }
-               
-               if ($this->banned) {
-                       $this->conditions->add('user_table.banned = ?', array(1));
-               }
-               if ($this->notBanned) {
-                       $this->conditions->add('user_table.banned = ?', array(0));
-               }
-               
-               // last activity time
-               if ($startDate = @strtotime($this->lastActivityTimeStart)) {
-                       $this->conditions->add('user_table.lastActivityTime >= ?', array($startDate));
-               }
-               if ($endDate = @strtotime($this->lastActivityTimeEnd)) {
-                       $this->conditions->add('user_table.lastActivityTime <= ?', array($endDate));
-               }
-               
-               if ($this->enabled) {
-                       $this->conditions->add('user_table.activationCode = ?', array(0));
-               }
-               if ($this->disabled) {
-                       $this->conditions->add('user_table.activationCode <> ?', array(0));
-               }
-               
-               // dynamic fields
-               foreach ($this->activeOptions as $name => $option) {
-                       $value = isset($this->values[$option['optionName']]) ? $this->values[$option['optionName']] : null;
-                       $this->getTypeObject($option['optionType'])->getCondition($this->conditions, $option, $value);
-               }
-               
-               // call buildConditions event
-               EventHandler::getInstance()->fireAction($this, 'buildConditions');
-               
-               // execute action
-               switch ($this->action) {
-                       case 'sendMail':
-                               WCF::getSession()->checkPermissions(array('admin.user.canMailUser'));
-                               // get user ids
-                               $userIDs = array();
-                               $sql = "SELECT          user_table.userID
-                                       FROM            wcf".WCF_N."_user user_table
-                                       LEFT JOIN       wcf".WCF_N."_user_option_value option_value
-                                       ON              (option_value.userID = user_table.userID)".
-                                       $this->conditions;
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute($this->conditions->getParameters());
-                               while ($row = $statement->fetchArray()) {
-                                       $userIDs[] = $row['userID'];
-                                       $this->affectedUsers++;
-                               }
-                               
-                               if (!empty($userIDs)) {
-                                       // save config in session
-                                       $userMailData = WCF::getSession()->getVar('userMailData');
-                                       if ($userMailData === null) $userMailData = array();
-                                       $mailID = count($userMailData);
-                                       $userMailData[$mailID] = array(
-                                               'action' => '',
-                                               'userIDs' => $userIDs,
-                                               'groupIDs' => '',
-                                               'subject' => $this->subject,
-                                               'text' => $this->text,
-                                               'from' => $this->from,
-                                               'enableHTML' => $this->enableHTML
-                                       );
-                                       WCF::getSession()->register('userMailData', $userMailData);
-                                       
-                                       WCF::getTPL()->assign('mailID', $mailID);
-                               }
-                       break;
-                       
-                       case 'exportMailAddress':
-                               WCF::getSession()->checkPermissions(array('admin.user.canMailUser'));
-                               // send content type
-                               header('Content-Type: text/'.$this->fileType.'; charset=UTF-8');
-                               header('Content-Disposition: attachment; filename="export.'.$this->fileType.'"');
-                               
-                               if ($this->fileType == 'xml') {
-                                       echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<addresses>\n";
-                               }
-                               
-                               // count users
-                               $sql = "SELECT          COUNT(*) AS count
-                                       FROM            wcf".WCF_N."_user user_table
-                                       LEFT JOIN       wcf".WCF_N."_user_option_value option_value
-                                       ON              (option_value.userID = user_table.userID)
-                                       ".$this->conditions;
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute($this->conditions->getParameters());
-                               $count = $statement->fetchArray();
-                               
-                               // get users
-                               $sql = "SELECT          user_table.email
-                                       FROM            wcf".WCF_N."_user user_table
-                                       LEFT JOIN       wcf".WCF_N."_user_option_value option_value
-                                       ON              (option_value.userID = user_table.userID)
-                                       ".$this->conditions."
-                                       ORDER BY        user_table.email";
-                               $statement = WCF::getDB()->prepareStatement($sql);
-                               $statement->execute($this->conditions->getParameters());
-                               
-                               $i = 0;
-                               while ($row = $statement->fetchArray()) {
-                                       if ($this->fileType == 'xml') echo "<address><![CDATA[".StringUtil::escapeCDATA($row['email'])."]]></address>\n";
-                                       else echo $this->textSeparator . $row['email'] . $this->textSeparator . ($i < $count['count'] ? $this->separator : '');
-                                       $i++;
-                                       $this->affectedUsers++;
-                               }
-                               
-                               if ($this->fileType == 'xml') {
-                                       echo "</addresses>";
-                               }
-                               $this->saved();
-                               exit;
-                       break;
-                       
-                       case 'assignToGroup':
-                               WCF::getSession()->checkPermissions(array('admin.user.canEditUser'));
-                               
-                               $_this = $this;
-                               $userIDs = $this->fetchUsers(function($userID, array $userData) use ($_this) {
-                                       $user = new UserEditor(new User(null, $userData));
-                                       $user->addToGroups($_this->assignToGroupIDs, false, false);
-                               });
-                               
-                               if (!empty($userIDs)) {
-                                       UserStorageHandler::getInstance()->reset($userIDs, 'groupIDs', 1);
-                               }
-                       break;
-                       
-                       case 'delete':
-                               WCF::getSession()->checkPermissions(array('admin.user.canDeleteUser'));
-                               
-                               $userIDs = $this->fetchUsers();
-                               
-                               if (!empty($userIDs)) {
-                                       $userAction = new UserAction($userIDs, 'delete');
-                                       $userAction->executeAction();
-                               }
-                       break;
-               }
-               $this->saved();
-               
-               WCF::getTPL()->assign('affectedUsers', $this->affectedUsers);
-       }
-       
-       /**
-        * Fetches a list of users.
-        * 
-        * @param       mixed           $loopFunction
-        * @return      array<integer>
-        */
-       public function fetchUsers($loopFunction = null) {
-               // select users
-               $sql = "SELECT          user_table.*
-                       FROM            wcf".WCF_N."_user user_table
-                       LEFT JOIN       wcf".WCF_N."_user_option_value option_value
-                       ON              (option_value.userID = user_table.userID)
-                       ".$this->conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($this->conditions->getParameters());
-               
-               $users = array();
-               while ($row = $statement->fetchArray()) {
-                       $users[$row['userID']] = $row;
-               }
-               if (empty($users)) return array();
-               
-               // select group ids
-               $conditions = new PreparedStatementConditionBuilder();
-               $conditions->add("userID IN (?)", array(array_keys($users)));
-               
-               $sql = "SELECT  userID, groupID
-                       FROM    wcf".WCF_N."_user_to_group
-                       ".$conditions;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditions->getParameters());
-               
-               $groupIDs = array();
-               while ($row = $statement->fetchArray()) {
-                       if (!isset($groupIDs[$row['userID']])) {
-                               $groupIDs[$row['userID']] = array();
-                       }
-                       
-                       $groupIDs[$row['userID']][] = $row['groupID'];
-               }
-               
-               foreach ($users as $userID => $userData) {
-                       if (!empty($groupIDs[$userID]) && !UserGroup::isAccessibleGroup($groupIDs[$userID])) {
-                               throw new PermissionDeniedException();
-                       }
-                       
-                       if ($loopFunction !== null) {
-                               $loopFunction($userID, $userData);
-                       }
-                       
-                       $userIDs[] = $userID;
-                       $this->affectedUsers++;
-               }
-               
-               return $userIDs;
-       }
-       
-       /**
-        * @see \wcf\page\IPage::readData()
-        */
-       public function readData() {
-               parent::readData();
-               
-               if (empty($_POST)) {
-                       if (MAIL_USE_FORMATTED_ADDRESS) {
-                               $this->from = MAIL_FROM_NAME.' <'.MAIL_FROM_ADDRESS.'>';
-                       }
-                       else {
-                               $this->from = MAIL_FROM_ADDRESS;
-                       }
-               }
-               
-               $this->availableGroups = $this->getAvailableGroups();
-               
-               foreach ($this->activeOptions as $name => $option) {
-                       if (isset($this->values[$name])) {
-                               $this->activeOptions[$name]['optionValue'] = $this->values[$name];
-                       }
-               }
-               
-               $this->options = $this->optionHandler->getCategoryOptions('profile');
-       }
-       
-       /**
-        * @see \wcf\page\IPage::assignVariables()
-        */
-       public function assignVariables() {
-               parent::assignVariables();
-               
-               WCF::getTPL()->assign(array(
-                       'username' => $this->username,
-                       'email' => $this->email,
-                       'groupIDs' => $this->groupIDs,
-                       'languageIDs' => $this->languageIDs,
-                       'invertGroupIDs' => $this->invertGroupIDs,
-                       'registrationDateStart' => $this->registrationDateStart,
-                       'registrationDateEnd' => $this->registrationDateEnd,
-                       'banned' => $this->banned,
-                       'notBanned' => $this->notBanned,
-                       'lastActivityTimeStart' => $this->lastActivityTimeStart,
-                       'lastActivityTimeEnd' => $this->lastActivityTimeEnd,
-                       'enabled' => $this->enabled,
-                       'disabled' => $this->disabled,
-                       
-                       'availableGroups' => $this->availableGroups,
-                       'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
-                       'options' => $this->options,
-                       'availableActions' => $this->availableActions,
-                       // assign to group
-                       'assignToGroupIDs' => $this->assignToGroupIDs,
-                       // export mail address
-                       'separator' => $this->separator,
-                       'textSeparator' => $this->textSeparator,
-                       'fileType' => $this->fileType,
-                       // send mail
-                       'subject' => $this->subject,
-                       'text' => $this->text,
-                       'from' => $this->from,
-                       'enableHTML' => $this->enableHTML
-               ));
-       }
-       
-       /**
-        * @see \wcf\form\IForm::show()
-        */
-       public function show() {
-               // set active menu item
-               ACPMenu::getInstance()->setActiveMenuItem('wcf.acp.menu.link.user.bulkProcessing');
-               
-               // check master password
-               WCFACP::checkMasterPassword();
-               
-               // show form
-               parent::show();
-       }
+       public $objectTypeName = 'com.woltlab.wcf.bulkProcessing.user';
 }
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/AbstractBulkProcessableObjectType.class.php b/wcfsetup/install/files/lib/system/bulk/processing/AbstractBulkProcessableObjectType.class.php
new file mode 100644 (file)
index 0000000..218f56d
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+namespace wcf\system\bulk\processing;
+use wcf\data\object\type\AbstractObjectTypeProcessor;
+use wcf\system\WCF;
+
+/**
+ * Abstract implementation of a bulk processable object type.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing
+ * @category   Community Framework
+ */
+class AbstractBulkProcessableObjectType extends AbstractObjectTypeProcessor implements IBulkProcessableObjectType {
+       /**
+        * name of the object type definition for the bulk actions
+        * @var string
+        */
+       protected $actionObjectTypeDefinition = '';
+       
+       /**
+        * name of the object type definition for the object conditions
+        * @var string
+        */
+       protected $conditionObjectTypeDefinition = '';
+
+       /**
+        * name of the prefix of the language items used in the interface
+        * @var string
+        */
+       protected $languageItemPrefix = '';
+       
+       /**
+        * name of the conditions template
+        * @var string
+        */
+       protected $templateName = '';
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessableObjectType::getActionObjectTypeDefinition()
+        */
+       public function getActionObjectTypeDefinition() {
+               if (empty($this->actionObjectTypeDefinition)) {
+                       $this->actionObjectTypeDefinition = $this->object->objectType.'.action';
+               }
+               
+               return $this->actionObjectTypeDefinition;
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessableObjectType::getConditionHTML()
+        */
+       public function getConditionHTML() {
+               return WCF::getTPL()->fetch($this->templateName, explode('\\', get_class($this))[0]);
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessableObjectType::getConditionObjectTypeDefinition()
+        */
+       public function getConditionObjectTypeDefinition() {
+               if (empty($this->conditionObjectTypeDefinition)) {
+                       $this->conditionObjectTypeDefinition = $this->object->objectType.'.condition';
+               }
+               
+               return $this->conditionObjectTypeDefinition;
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessableObjectType::getLanguageItemPrefix()
+        */
+       public function getLanguageItemPrefix() {
+               if (empty($this->languageItemPrefix)) {
+                       $application = explode('\\', get_class($this))[0];
+                       $objectTypePieces = explode('.', $this->object->objectType);
+                       
+                       $this->languageItemPrefix = $application.'.acp.'.end($objectTypePieces).'.bulkProcessing';
+               }
+               
+               return $this->languageItemPrefix;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/AbstractBulkProcessingAction.class.php b/wcfsetup/install/files/lib/system/bulk/processing/AbstractBulkProcessingAction.class.php
new file mode 100644 (file)
index 0000000..83e4e44
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+namespace wcf\system\bulk\processing;
+use wcf\data\object\type\AbstractObjectTypeProcessor;
+
+/**
+ * Abstract implementation of a bulk processing action.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing
+ * @category   Community Framework
+ */
+abstract class AbstractBulkProcessingAction extends AbstractObjectTypeProcessor implements IBulkProcessingAction {
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::getHTML()
+        */
+       public function getHTML() {
+               return '';
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::isAvailable()
+        */
+       public function isAvailable() {
+               return true;
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::readFormParameters()
+        */
+       public function readFormParameters() {
+               // does nothing
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::reset()
+        */
+       public function reset() {
+               // does nothing
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::validate()
+        */
+       public function validate() {
+               // does nothing
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/IBulkProcessableObjectType.class.php b/wcfsetup/install/files/lib/system/bulk/processing/IBulkProcessableObjectType.class.php
new file mode 100644 (file)
index 0000000..1382aad
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace wcf\system\bulk\processing;
+
+/**
+ * Every bulk processable object type has to implement this interface.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing
+ * @category   Community Framework
+ */
+interface IBulkProcessableObjectType {
+       /**
+        * Returns the name of the object type definition for the bulk actions.
+        * 
+        * @return      string
+        */
+       public function getActionObjectTypeDefinition();
+       
+       /**
+        * Returns the output for setting up the conditions for the bulk processable
+        * object.
+        * 
+        * @return      string
+        */
+       public function getConditionHTML();
+       
+       /**
+        * Returns the name of the object type definition for the object conditions.
+        * 
+        * @return      string
+        */
+       public function getConditionObjectTypeDefinition();
+       
+       /**
+        * Returns the name of the prefix of the language items used in the interface.
+        * 
+        * The returned prefix has not trailing dot.
+        * 
+        * @return      string
+        */
+       public function getLanguageItemPrefix();
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/IBulkProcessingAction.class.php b/wcfsetup/install/files/lib/system/bulk/processing/IBulkProcessingAction.class.php
new file mode 100644 (file)
index 0000000..1e97090
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+namespace wcf\system\bulk\processing;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Every bulk processing action has to implement this interface.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing
+ * @category   Community Framework
+ */
+interface IBulkProcessingAction {
+       /**
+        * Executes the bulk processing action on all objects in the given object
+        * list.
+        * 
+        * @param       \wcf\data\DatabaseObjectList            $objectList
+        */
+       public function executeAction(DatabaseObjectList $objectList);
+       
+       /**
+        * Returns the output for setting additional action parameters.
+        * 
+        * @return      string
+        */
+       public function getHTML();
+       
+       /**
+        * Returns an object list which will be populated with conditions to read
+        * the processed objects.
+        * 
+        * @return      \wcf\data\DatabaseObjectList
+        */
+       public function getObjectList();
+       
+       /**
+        * Returns true if the action is available for the active user.
+        * 
+        * @return      boolean
+        */
+       public function isAvailable();
+       
+       /**
+        * Reads additional parameters to execute the action.
+        */
+       public function readFormParameters();
+       
+       /**
+        * Resets the internally stored additional action parameters.
+        */
+       public function reset();
+       
+       /**
+        * Validates the additional action parameters.
+        */
+       public function validate();
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/user/AbstractUserBulkProcessingAction.class.php b/wcfsetup/install/files/lib/system/bulk/processing/user/AbstractUserBulkProcessingAction.class.php
new file mode 100644 (file)
index 0000000..1168852
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+namespace wcf\system\bulk\processing\user;
+use wcf\data\user\group\UserGroup;
+use wcf\data\user\UserList;
+use wcf\system\bulk\processing\AbstractBulkProcessingAction;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\WCF;
+
+/**
+ * Abstract implementation of a user bulk processing action.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing.user
+ * @category   Community Framework
+ */
+abstract class AbstractUserBulkProcessingAction extends AbstractBulkProcessingAction {
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::getObjectList()
+        */
+       public function getObjectList() {
+               return new UserList();
+       }
+       
+       /**
+        * Returns all users who the active user can access due to their user group
+        * assocition.
+        * 
+        * @param       \wcf\data\user\UserList         $userList
+        */
+       protected function getAccessibleUsers(UserList $userList) {
+               // fetch user group ids of all users
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add('userID IN (?)', [ $userList->getObjectIDs() ]);
+               
+               $sql = "SELECT  userID, groupID
+                       FROM    wcf".WCF_N."_user_to_group
+                       ".$conditionBuilder;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditionBuilder->getParameters());
+               
+               $groupIDs = [ ];
+               while ($row = $statement->fetchArray()) {
+                       if (!isset($groupIDs[$row['userID']])) {
+                               $groupIDs[$row['userID']] = [ ];
+                       }
+                       
+                       $groupIDs[$row['userID']][] = $row['groupID'];
+               }
+               
+               $users = [ ];
+               foreach ($userList as $user) {
+                       if (empty($groupIDs[$user->userID]) || UserGroup::isAccessibleGroup($groupIDs[$user->userID])) {
+                               $users[$user->userID] = $user;
+                       }
+               }
+               
+               return $users;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/user/AbstractUserGroupsUserBulkProcessingAction.class.php b/wcfsetup/install/files/lib/system/bulk/processing/user/AbstractUserGroupsUserBulkProcessingAction.class.php
new file mode 100644 (file)
index 0000000..49c797b
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+namespace wcf\system\bulk\processing\user;
+use wcf\data\user\group\UserGroup;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserList;
+use wcf\data\DatabaseObject;
+use wcf\data\DatabaseObjectList;
+use wcf\system\exception\UserInputException;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+
+/**
+ * Abstract implementation of a user bulk processing action related to selecting
+ * user groups.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing.user
+ * @category   Community Framework
+ */
+abstract class AbstractUserGroupsUserBulkProcessingAction extends AbstractUserBulkProcessingAction {
+       /**
+        * list of available user groups
+        * @var array<\wcf\data\user\group\UserGroup>
+        */
+       public $availableUserGroups = [ ];
+       
+       /**
+        * name of the inputs used to store the selected user group ids
+        * @var string
+        */
+       public $inputName = '';
+       
+       /**
+        * ids of selected user groups
+        * @var array<integer>
+        */
+       public $userGroupIDs = [ ];
+       
+       /**
+        * @see \wcf\data\DatabaseObjectDecorator::__construct()
+        */
+       public function __construct(DatabaseObject $object) {
+               parent::__construct($object);
+               
+               $this->availableUserGroups = UserGroup::getAccessibleGroups([ ], [ UserGroup::GUESTS, UserGroup::EVERYONE, UserGroup::USERS ]);
+               
+               uasort($this->availableUserGroups, function(UserGroup $groupA, UserGroup $groupB) {
+                       return strcmp($groupA->getName(), $groupB->getName());
+               });
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::executeAction()
+        */
+       public function executeAction(DatabaseObjectList $objectList) {
+               if (!($objectList instanceof UserList)) return;
+               
+               $users = $this->getAccessibleUsers($objectList);
+               
+               if (!empty($users)) {
+                       WCF::getDB()->beginTransaction();
+                       foreach ($users as $user) {
+                               $user = new UserEditor($user);
+                               $this->executeUserAction($user);
+                       }
+                       WCF::getDB()->commitTransaction();
+                       
+                       UserStorageHandler::getInstance()->reset(array_keys($users), 'groupIDs');
+               }
+       }
+       
+       /**
+        * Execute the action for the given user.
+        * 
+        * @param       \wcf\data\user\UserEditor       $user
+        */
+       abstract protected function executeUserAction(UserEditor $user);
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::getHTML()
+        */
+       public function getHTML() {
+               return WCF::getTPL()->fetch('userGroupListUserBulkProcessing', 'wcf', [
+                       'availableUserGroups' => $this->availableUserGroups,
+                       'inputName' => $this->inputName,
+                       'selectedUserGroupIDs' => $this->userGroupIDs
+               ]);
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::isAvailable()
+        */
+       public function isAvailable() {
+               return !empty($this->availableUserGroups);
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::readFormParameters()
+        */
+       public function readFormParameters() {
+               if (isset($_POST[$this->inputName])) $this->userGroupIDs = ArrayUtil::toIntegerArray($_POST[$this->inputName]);
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::reset()
+        */
+       public function reset() {
+               $this->userGroupIDs = [ ];
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::validate()
+        */
+       public function validate() {
+               if (empty($this->userGroupIDs)) {
+                       throw new UserInputException($this->inputName);
+               }
+               
+               foreach ($this->userGroupIDs as $groupID) {
+                       if (!isset($this->availableUserGroups[$groupID])) {
+                               throw new UserInputException($this->inputName, 'noValidSelection');
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/user/AssignToUserGroupsUserBulkProcessingAction.class.php b/wcfsetup/install/files/lib/system/bulk/processing/user/AssignToUserGroupsUserBulkProcessingAction.class.php
new file mode 100644 (file)
index 0000000..d8f25b0
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+namespace wcf\system\bulk\processing\user;
+use wcf\data\user\UserEditor;
+
+/**
+ * Bulk processing action implementation for assigning users to user groups.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing.user
+ * @category   Community Framework
+ */
+class AssignToUserGroupsUserBulkProcessingAction extends AbstractUserGroupsUserBulkProcessingAction {
+       /**
+        * @see \wcf\system\bulk\processing\user\AbstractUserGroupsUserBulkProcessingAction::$inputName
+        */
+       public $inputName = 'assignToUserGroupIDs';
+       
+       /**
+        * @see \wcf\system\bulk\processing\user\AbstractUserGroupsUserBulkProcessingAction::executeUserAction()
+        */
+       protected function executeUserAction(UserEditor $user) {
+               $user->addToGroups($this->userGroupIDs, false, false);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/user/DeleteUserBulkProcessingAction.class.php b/wcfsetup/install/files/lib/system/bulk/processing/user/DeleteUserBulkProcessingAction.class.php
new file mode 100644 (file)
index 0000000..b684ef2
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+namespace wcf\system\bulk\processing\user;
+use wcf\data\user\UserAction;
+use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
+use wcf\system\WCF;
+
+/**
+ * Bulk processing action implementation for deleting users.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing.user
+ * @category   Community Framework
+ */
+class DeleteUserBulkProcessingAction extends AbstractUserBulkProcessingAction {
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::executeAction()
+        */
+       public function executeAction(DatabaseObjectList $objectList) {
+               if (!($objectList instanceof UserList)) return;
+               
+               $users = $this->getAccessibleUsers($objectList);
+               
+               if (!empty($users)) {
+                       $userAction = new UserAction($users, 'delete');
+                       $userAction->executeAction();
+               }
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::getObjectList()
+        */
+       public function getObjectList() {
+               $userList = parent::getObjectList();
+               
+               // deny self deletion
+               $userList->getConditionBuilder()->add('user_table.userID <> ?', array(WCF::getUser()->userID));
+               
+               return $userList;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/user/ExportMailAddressUserBulkProcessingAction.class.php b/wcfsetup/install/files/lib/system/bulk/processing/user/ExportMailAddressUserBulkProcessingAction.class.php
new file mode 100644 (file)
index 0000000..7fa1f44
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace wcf\system\bulk\processing\user;
+use wcf\data\user\UserAction;
+use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Bulk processing action implementation for exporting mail addresses of users.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing.user
+ * @category   Community Framework
+ */
+class ExportMailAddressUserBulkProcessingAction extends AbstractUserBulkProcessingAction {
+       /**
+        * type of the file the email addresses will be saved in (csv or xml)
+        * @var string
+        */
+       public $fileType = 'csv';
+       
+       /**
+        * separates the exported email addresses
+        * @var string
+        */
+       public $separator = ',';
+       
+       /**
+        * encloses the exported email addresses
+        * @var string
+        */
+       public $textSeparator = '"';
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::executeAction()
+        */
+       public function executeAction(DatabaseObjectList $objectList) {
+               if (!($objectList instanceof UserList)) return;
+               
+               // send content type
+               header('Content-Type: text/'.$this->fileType.'; charset=UTF-8');
+               header('Content-Disposition: attachment; filename="export.'.$this->fileType.'"');
+               
+               if ($this->fileType == 'xml') {
+                       echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<addresses>\n";
+               }
+               
+               $userCount = count($objectList);
+               $i = 0;
+               foreach ($objectList as $user) {
+                       if ($this->fileType == 'xml') {
+                               echo "<address><![CDATA[".StringUtil::escapeCDATA($user->email)."]]></address>\n";
+                       }
+                       else {
+                               echo $this->textSeparator.$user->email.$this->textSeparator.($i < $userCount ? $this->separator : '');
+                       }
+                       
+                       $i++;
+               }
+               
+               if ($this->fileType == 'xml') {
+                       echo "</addresses>";
+               }
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::getHTML()
+        */
+       public function getHTML() {
+               return WCF::getTPL()->fetch('exportMailAddressUserBulkProcessing', 'wcf', [
+                       'fileType' => $this->fileType,
+                       'separator' => $this->separator,
+                       'textSeparator' => $this->textSeparator
+               ]);
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::getObjectList()
+        */
+       public function getObjectList() {
+               $userList = parent::getObjectList();
+               
+               $userList->sqlOrderBy = 'user_table.email';
+               
+               return $userList;
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::readFormParameters()
+        */
+       public function readFormParameters() {
+               if (isset($_POST['fileType']) && $_POST['fileType'] == 'xml') $this->fileType = $_POST['fileType'];
+               if (isset($_POST['separator'])) $this->separator = $_POST['separator'];
+               if (isset($_POST['textSeparator'])) $this->textSeparator = $_POST['textSeparator'];
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::reset()
+        */
+       public function reset() {
+               exit;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/user/RemoveFromUserGroupsUserBulkProcessingAction.class.php b/wcfsetup/install/files/lib/system/bulk/processing/user/RemoveFromUserGroupsUserBulkProcessingAction.class.php
new file mode 100644 (file)
index 0000000..cd11567
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+namespace wcf\system\bulk\processing\user;
+use wcf\data\user\UserEditor;
+use wcf\system\WCF;
+
+/**
+ * Bulk processing action implementation for removing users from user groups.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing.user
+ * @category   Community Framework
+ */
+class RemoveFromUserGroupsUserBulkProcessingAction extends AbstractUserGroupsUserBulkProcessingAction {
+       /**
+        * @see \wcf\system\bulk\processing\user\AbstractUserGroupsUserBulkProcessingAction::$inputName
+        */
+       public $inputName = 'removeFromUserGroupIDs';
+       
+       /**
+        * @see \wcf\system\bulk\processing\user\AbstractUserGroupsUserBulkProcessingAction::executeUserAction()
+        */
+       protected function executeUserAction(UserEditor $user) {
+               $user->removeFromGroups($this->userGroupIDs);
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::getObjectList()
+        */
+       public function getObjectList() {
+               $userList = parent::getObjectList();
+               
+               // the active user may not remove themselves from any user group
+               // to avoid potential permission issues
+               $userList->getConditionBuilder()->add('user_table.userID <> ?', array(WCF::getUser()->userID));
+               
+               return $userList;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/user/SendMailUserBulkProcessingAction.class.php b/wcfsetup/install/files/lib/system/bulk/processing/user/SendMailUserBulkProcessingAction.class.php
new file mode 100644 (file)
index 0000000..16d38b4
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+namespace wcf\system\bulk\processing\user;
+use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Bulk processing action implementation for sening mails to users.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing.user
+ * @category   Community Framework
+ */
+class SendMailUserBulkProcessingAction extends AbstractUserBulkProcessingAction {
+       /**
+        * email text
+        * @var string
+        */
+       public $email = '';
+       
+       /**
+        * is 1 if HTML for the email is enabled
+        * @var integer
+        */
+       public $enableHTML = 0;
+       
+       /**
+        * sender
+        * @var string
+        */
+       public $from = '';
+       
+       /**
+        * identifier for the mail worker
+        * @var string
+        */
+       public $mailID = '';
+       
+       /**
+        * email text
+        * @var string
+        */
+       public $text = '';
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::executeAction()
+        */
+       public function executeAction(DatabaseObjectList $objectList) {
+               if (!($objectList instanceof UserList)) return;
+               
+               if (count($objectList)) {
+                       // save config in session
+                       $userMailData = WCF::getSession()->getVar('userMailData');
+                       if ($userMailData === null) $userMailData = array();
+                       $this->mailID = count($userMailData);
+                       $userMailData[$this->mailID] = array(
+                               'action' => '',
+                               'enableHTML' => $this->enableHTML,
+                               'from' => $this->from,
+                               'groupIDs' => '',
+                               'subject' => $this->subject,
+                               'text' => $this->text,
+                               'userIDs' => $objectList->getObjectIDs()
+                       );
+                       WCF::getSession()->register('userMailData', $userMailData);
+               }
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::getHTML()
+        */
+       public function getHTML() {
+               return WCF::getTPL()->fetch('sendMailUserBulkProcessing', 'wcf', [
+                       'enableHTML' => $this->enableHTML,
+                       'from' => $this->from,
+                       'mailID' => $this->mailID,
+                       'subject' => $this->subject,
+                       'text' => $this->text
+               ]);
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::readFormParameters()
+        */
+       public function readFormParameters() {
+               if (isset($_POST['enableHTML'])) $this->enableHTML = intval($_POST['enableHTML']);
+               if (isset($_POST['from'])) $this->from = StringUtil::trim($_POST['from']);
+               if (isset($_POST['subject'])) $this->subject = StringUtil::trim($_POST['subject']);
+               if (isset($_POST['text'])) $this->text = StringUtil::trim($_POST['text']);
+       }
+       
+       /**
+        * @see \wcf\system\bulk\processing\IBulkProcessingAction::validate()
+        */
+       public function validate() {
+               if (empty($this->subject)) {
+                       throw new UserInputException('subject');
+               }
+               
+               if (empty($this->text)) {
+                       throw new UserInputException('text');
+               }
+               
+               if (empty($this->from)) {
+                       throw new UserInputException('from');
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bulk/processing/user/UserBulkProcessableObjectType.class.php b/wcfsetup/install/files/lib/system/bulk/processing/user/UserBulkProcessableObjectType.class.php
new file mode 100644 (file)
index 0000000..fe7e211
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+namespace wcf\system\bulk\processing\user;
+use wcf\system\bulk\processing\AbstractBulkProcessableObjectType;
+
+
+/**
+ * Bulk processable object type implementation for users.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.bulk.processing.user
+ * @category   Community Framework
+ */
+class UserBulkProcessableObjectType extends AbstractBulkProcessableObjectType {
+       /**
+        * @see \wcf\system\bulk\processing\AbstractBulkProcessableObjectType::$templateName
+        */
+       protected $templateName = 'userConditions';
+}
diff --git a/wcfsetup/install/files/lib/system/condition/AbstractObjectTextPropertyCondition.class.php b/wcfsetup/install/files/lib/system/condition/AbstractObjectTextPropertyCondition.class.php
new file mode 100644 (file)
index 0000000..a9b8c0e
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\DatabaseObject;
+use wcf\data\DatabaseObjectList;
+use wcf\util\ClassUtil;
+
+/**
+ * Abstract condition implementation for check a text-typed property of a database
+ * object.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.condition
+ * @category   Community Framework
+ */
+abstract class AbstractObjectTextPropertyCondition extends AbstractTextCondition implements IObjectCondition, IObjectListCondition {
+       /**
+        * name of the relevant database object class
+        * @var string
+        */
+       protected $className = '';
+       
+       /**
+        * is true if the entered value should be split by commas to search for
+        * multiple values
+        * @var boolean
+        */
+       protected $supportsMultipleValues = false;
+       
+       /**
+        * name of the relevant object property
+        * @var string
+        */
+       protected $propertyName = '';
+       
+       /**
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
+        */
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!ClassUtil::isInstanceOf($objectList, $this->getListClassName())) return;
+               
+               if ($this->supportsMultipleValues) {
+                       $objectList->getConditionBuilder()->add($objectList->getDatabaseTableAlias().'.'.$this->getPropertyName().' IN (?)', [ $conditionData[$this->fieldName] ]);
+               }
+               else {
+                       $objectList->getConditionBuilder()->add($objectList->getDatabaseTableAlias().'.'.$this->getPropertyName().' = ?', [ $conditionData[$this->fieldName] ]);
+               }
+       }
+       
+       /**
+        * @see \wcf\system\condition\IObjectCondition::checkObject()
+        */
+       public function checkObject(DatabaseObject $object, array $conditionData) {
+               if (!ClassUtil::isInstanceOf($object, $this->getClassName())) return;
+               
+               return in_array($object->{$this->getPropertyName()}, $conditionData[$this->fieldName]);
+       }
+       
+       /**
+        * Returns the name of the relevant database object class.
+        *
+        * @return      string
+        */
+       protected function getClassName() {
+               return $this->className;
+       }
+       
+       /**
+        * @see \wcf\system\condition\ICondition::getData()
+        */
+       public function getData() {
+               $value = parent::getData();
+               if ($value === null || !$this->supportsMultipleValues) {
+                       return $value;
+               }
+               
+               return [
+                       $this->fieldName => preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY)
+               ];
+       }
+       
+       /**
+        * Returns the name of the relevant database object list class.
+        *
+        * @return      string
+        */
+       protected function getListClassName() {
+               return $this->className.'List';
+       }
+       
+       /**
+        * Returns the name of the relevant object property.
+        * 
+        * @return      string
+        */
+       protected function getPropertyName() {
+               return $this->propertyName;
+       }
+}
index a28f3f374b9aed3b9e7741b219c4d1dc31b27e92..4caaa3acf1ed627814ed36db66eaeb2964f846ca 100644 (file)
@@ -31,9 +31,9 @@ abstract class AbstractTextCondition extends AbstractSingleFieldCondition {
         */
        public function getData() {
                if (mb_strlen($this->fieldValue)) {
-                       return array(
+                       return [
                                $this->fieldName => $this->fieldValue
-                       );
+                       ];
                }
                
                return null;
diff --git a/wcfsetup/install/files/lib/system/condition/AbstractTimestampCondition.class.php b/wcfsetup/install/files/lib/system/condition/AbstractTimestampCondition.class.php
new file mode 100644 (file)
index 0000000..7972e54
--- /dev/null
@@ -0,0 +1,209 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\condition\Condition;
+use wcf\data\user\User;
+use wcf\data\user\UserList;
+use wcf\data\DatabaseObject;
+use wcf\data\DatabaseObjectList;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\ClassUtil;
+
+/**
+ * Condition implementation for compairing a user-bound timestamp with a fixed time
+ * interval.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.condition
+ * @category   Community Framework
+ */
+abstract class AbstractTimestampCondition extends AbstractSingleFieldCondition implements IObjectCondition, IObjectListCondition {
+       /**
+        * name of the relevant database object class
+        * @var string
+        */
+       protected $className = '';
+       
+       /**
+        * registration start date
+        * @var string
+        */
+       protected $endTime = '';
+       
+       /**
+        * name of the relevant object property
+        * @var string
+        */
+       protected $propertyName = '';
+       
+       /**
+        * registration start date
+        * @var string
+        */
+       protected $startTime = '';
+       
+       /**
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
+        */
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!ClassUtil::isInstanceOf($objectList, $this->getListClassName())) return;
+               
+               $objectList->getConditionBuilder()->add($objectList->getDatabaseTableAlias().'.'.$this->getPropertyName().' <> ?', [ 0 ]);
+               if (isset($conditionData['endTime'])) {
+                       $objectList->getConditionBuilder()->add($objectList->getDatabaseTableAlias().'.'.$this->getPropertyName().' < ?', [ strtotime($conditionData['endTime']) + 86400 ]);
+               }
+               if (isset($conditionData['startTime'])) {
+                       $objectList->getConditionBuilder()->add($objectList->getDatabaseTableAlias().'.'.$this->getPropertyName().' >= ?', [ strtotime($conditionData['startTime']) ]);
+               }
+       }
+       
+       /**
+        * @see \wcf\system\condition\IObjectCondition::checkObject()
+        */
+       public function checkObject(DatabaseObject $object, array $conditionData) {
+               if (!ClassUtil::isInstanceOf($object, $this->getClassName())) return;
+               
+               if (isset($conditionData['startTime']) && $object->{$this->getPropertyName()} < strtotime($conditionData['startTime'])) {
+                       return false;
+               }
+               if (isset($conditionData['endTimeTime']) && $object->{$this->getPropertyName()} >= strtotime($conditionData['endTime']) + 86400) {
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * Returns the name of the relevant database object class.
+        * 
+        * @return      string
+        */
+       protected function getClassName() {
+               return $this->className;
+       }
+       
+       /**
+        * @see \wcf\system\condition\ICondition::getData()
+        */
+       public function getData() {
+               $data = array();
+               
+               if (strlen($this->startTime)) {
+                       $data['startTime'] = $this->startTime;
+               }
+               if (strlen($this->endTime)) {
+                       $data['endTime'] = $this->endTime;
+               }
+               
+               if (!empty($data)) {
+                       return $data;
+               }
+               
+               return null;
+       }
+       
+       /**
+        * @see \wcf\system\condition\AbstractSingleFieldCondition::getFieldElement()
+        */
+       protected function getFieldElement() {
+               $start = WCF::getLanguage()->get('wcf.date.period.start');
+               $end = WCF::getLanguage()->get('wcf.date.period.end');
+               
+               return <<<HTML
+<input type="date" id="{$this->getPropertyName()}StartTime" name="{$this->getPropertyName()}StartTime" value="{$this->startTime}" placeholder="{$start}" />
+<input type="date" id="{$this->getPropertyName()}EndTime" name="{$this->getPropertyName()}EndTime" value="{$this->endTime}" placeholder="{$end}" />
+HTML;
+       }
+       
+       /**
+        * @see \wcf\system\condition\AbstractSingleFieldCondition::getLabel()
+        */
+       protected function getLabel() {
+               return WCF::getLanguage()->get($this->getLanguageItemPrefix().'.'.$this->getPropertyName());
+       }
+       
+       /**
+        * Returns the prefix of the language items used for the condition.
+        * 
+        * @return      string
+        */
+       abstract protected function getLanguageItemPrefix();
+       
+       /**
+        * Returns the name of the relevant database object list class.
+        * 
+        * @return      string
+        */
+       protected function getListClassName() {
+               return $this->className.'List';
+       }
+       
+       /**
+        * Returns the name of the relevant object property.
+        * 
+        * @return      string
+        */
+       protected function getPropertyName() {
+               return $this->propertyName;
+       }
+       
+       /**
+        * @see \wcf\system\condition\ICondition::readFormParameters()
+        */
+       public function readFormParameters() {
+               if (isset($_POST[$this->getPropertyName().'EndTime'])) $this->endTime = $_POST[$this->getPropertyName().'EndTime'];
+               if (isset($_POST[$this->getPropertyName().'StartTime'])) $this->startTime = $_POST[$this->getPropertyName().'StartTime'];
+       }
+       
+       /**
+        * @see \wcf\system\condition\ICondition::reset()
+        */
+       public function reset() {
+               $this->endTime = '';
+               $this->startTime = '';
+       }
+       
+       /**
+        * @see \wcf\system\condition\ICondition::setData()
+        */
+       public function setData(Condition $condition) {
+               if ($condition->endTime) {
+                       $this->endTime = $condition->endTime;
+               }
+               if ($condition->startTime) {
+                       $this->startTime = $condition->startTime;
+               }
+       }
+       
+       /**
+        * @see \wcf\system\condition\ICondition::validate()
+        */
+       public function validate() {
+               $endTime = $startTime = null;
+               if (strlen($this->startTime)) {
+                       $startTime = @strtotime($this->startTime);
+                       if ($startTime === false) {
+                               $this->errorMessage = $this->getLanguageItemPrefix().'.'.$this->getPropertyName().'.error.startNotValid';
+                               
+                               throw new UserInputException($this->getPropertyName(), 'startNotValid');
+                       }
+               }
+               if (strlen($this->endTime)) {
+                       $endTime = @strtotime($this->endTime);
+                       if ($endTime === false) {
+                               $this->errorMessage = $this->getLanguageItemPrefix().'.'.$this->getPropertyName().'.error.endNotValid';
+                               
+                               throw new UserInputException($this->getPropertyName(), 'endNotValid');
+                       }
+               }
+               
+               if ($endTime !== null && $startTime !== null && $endTime < $startTime) {
+                       $this->errorMessage = $this->getLanguageItemPrefix().'.'.$this->getPropertyName().'.error.endBeforeStart';
+                       
+                       throw new UserInputException($this->getPropertyName(), 'endBeforeStart');
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/condition/IObjectCondition.class.php b/wcfsetup/install/files/lib/system/condition/IObjectCondition.class.php
new file mode 100644 (file)
index 0000000..baca519
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\DatabaseObject;
+
+/**
+ * Every implementation of database object-related conditions needs to implements
+ * this interface.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.condition
+ * @category   Community Framework
+ */
+interface IObjectCondition extends ICondition {
+       /**
+        * Returns true if the given object fulfills the condition specified by
+        * the given condition data returned by \wcf\system\condition\ICondition::getData().
+        * 
+        * @param       \wcf\data\DatabaseObject        $object
+        * @param       array                           $conditionData
+        * @return      boolean
+        */
+       public function checkObject(DatabaseObject $object, array $conditionData);
+}
diff --git a/wcfsetup/install/files/lib/system/condition/IObjectListCondition.class.php b/wcfsetup/install/files/lib/system/condition/IObjectListCondition.class.php
new file mode 100644 (file)
index 0000000..f0272c9
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Every implementation of database object list-related conditions needs to implements
+ * this interface.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.condition
+ * @category   Community Framework
+ */
+interface IObjectListCondition extends ICondition {
+       /**
+        * Adds a condition to the given object list based on the given condition
+        * data returned by \wcf\system\condition\ICondition::getData().
+        * 
+        * @param       \wcf\data\DatabaseObjectList    $objectList
+        * @param       array                           $conditionData
+        */
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData);
+}
diff --git a/wcfsetup/install/files/lib/system/condition/TObjectListUserCondition.class.php b/wcfsetup/install/files/lib/system/condition/TObjectListUserCondition.class.php
new file mode 100644 (file)
index 0000000..13dad87
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\condition\Condition;
+use wcf\data\user\UserList;
+
+/**
+ * Redirects IUserCondition::addUserCondition() calls to the more general
+ * IObjectListCondition::addObjectListCondition().
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.condition
+ * @category   Community Framework
+ */
+trait TObjectListUserCondition {
+       /**
+        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        */
+       public function addUserCondition(Condition $condition, UserList $userList) {
+               $this->addObjectListCondition($userList, $condition->conditionData);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/condition/TObjectUserCondition.class.php b/wcfsetup/install/files/lib/system/condition/TObjectUserCondition.class.php
new file mode 100644 (file)
index 0000000..6b587d4
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\condition\Condition;
+use wcf\data\user\User;
+
+/**
+ * Redirects IUserCondition::checkUser() calls to the more general IObjectCondition::checkObject().
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.condition
+ * @category   Community Framework
+ */
+trait TObjectUserCondition {
+       /**
+        * @see \wcf\system\condition\IUserCondition::checkUser()
+        */
+       public function checkUser(Condition $condition, User $user) {
+               $this->checkObject($user, $condition->conditionData);
+       }
+}
index 634fbc29a441e42dfea6988605150a052e05e58a..47c9cfded3f8477926c0367f3b372c4683a57786 100644 (file)
@@ -2,7 +2,7 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
 use wcf\system\WCF;
 
 /**
@@ -15,7 +15,9 @@ use wcf\system\WCF;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserAvatarCondition extends AbstractSelectCondition implements IContentCondition, IUserCondition {
+class UserAvatarCondition extends AbstractSelectCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
         * @see wcf\system\condition\AbstractSelectCondition::$fieldName
         */
@@ -45,21 +47,23 @@ class UserAvatarCondition extends AbstractSelectCondition implements IContentCon
        const GRAVATAR = 2;
        
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               switch ($condition->userAvatar) {
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               switch ($conditionData['userAvatar']) {
                        case self::NO_AVATAR:
-                               $userList->getConditionBuilder()->add('user_table.avatarID IS NULL');
-                               $userList->getConditionBuilder()->add('user_table.enableGravatar = ?', array(0));
+                               $objectList->getConditionBuilder()->add('user_table.avatarID IS NULL');
+                               $objectList->getConditionBuilder()->add('user_table.enableGravatar = ?', array(0));
                        break;
                        
                        case self::AVATAR:
-                               $userList->getConditionBuilder()->add('user_table.avatarID IS NOT NULL');
+                               $objectList->getConditionBuilder()->add('user_table.avatarID IS NOT NULL');
                        break;
                        
                        case self::GRAVATAR:
-                               $userList->getConditionBuilder()->add('user_table.enableGravatar = ?', array(1));
+                               $objectList->getConditionBuilder()->add('user_table.enableGravatar = ?', array(1));
                        break;
                }
        }
index be0bd018f7d9aba1728893c000f8ea91a54f5848..d30cb9ef3a11e85f1df77d84ec6cdf7dd14cdef2 100644 (file)
@@ -2,7 +2,7 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
 use wcf\system\WCF;
 
 /**
@@ -15,7 +15,9 @@ use wcf\system\WCF;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserEmailCondition extends AbstractTextCondition implements IContentCondition, IUserCondition {
+class UserEmailCondition extends AbstractTextCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
         * @see \wcf\system\condition\AbstractTextCondition::$fieldName
         */
@@ -27,10 +29,12 @@ class UserEmailCondition extends AbstractTextCondition implements IContentCondit
        protected $label = 'wcf.user.email';
        
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               $userList->getConditionBuilder()->add('user_table.email LIKE ?', array('%'.addcslashes($condition->email, '_%').'%'));
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               $objectList->getConditionBuilder()->add('user_table.email LIKE ?', array('%'.addcslashes($conditionData['email'], '_%').'%'));
        }
        
        /**
index 8a50bbddb720bf29c858ee90ccbfd496034aefdf..d190e2f36affa6be8193fd08d99c0b0800dbcdab 100644 (file)
@@ -3,7 +3,7 @@ namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\group\UserGroup;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
 use wcf\system\exception\UserInputException;
 use wcf\system\WCF;
 use wcf\util\ArrayUtil;
@@ -19,7 +19,9 @@ use wcf\util\ArrayUtil;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserGroupCondition extends AbstractMultipleFieldsCondition implements IContentCondition, IUserCondition {
+class UserGroupCondition extends AbstractMultipleFieldsCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
         * @see \wcf\system\condition\AbstractMultipleFieldsCondition::$descriptions
         */
@@ -55,14 +57,16 @@ class UserGroupCondition extends AbstractMultipleFieldsCondition implements ICon
        protected $userGroups = null;
        
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               if ($condition->groupIDs !== null) {
-                       $userList->getConditionBuilder()->add('user_table.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_to_group WHERE groupID IN (?) GROUP BY userID HAVING COUNT(userID) = ?)', array($condition->groupIDs, count($condition->groupIDs)));
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               if (isset($conditionData['groupIDs'])) {
+                       $objectList->getConditionBuilder()->add('user_table.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_to_group WHERE groupID IN (?) GROUP BY userID HAVING COUNT(userID) = ?)', array($conditionData['groupIDs'], count($conditionData['groupIDs'])));
                }
-               if ($condition->notGroupIDs !== null) {
-                       $userList->getConditionBuilder()->add('user_table.userID NOT IN (SELECT userID FROM wcf'.WCF_N.'_user_to_group WHERE groupID IN (?))', array($condition->notGroupIDs));
+               if (isset($conditionData['notGroupIDs'])) {
+                       $objectList->getConditionBuilder()->add('user_table.userID NOT IN (SELECT userID FROM wcf'.WCF_N.'_user_to_group WHERE groupID IN (?))', array($conditionData['notGroupIDs']));
                }
        }
        
index 4dcc9adbed149207139aa2cd35ca4cd52818d514..c36e797ecee00865e97bc0ac656cb62becc0338f 100644 (file)
@@ -2,7 +2,7 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
 use wcf\system\WCF;
 
 /**
@@ -15,16 +15,20 @@ use wcf\system\WCF;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserIntegerPropertyCondition extends AbstractIntegerCondition implements IContentCondition, IUserCondition {
+class UserIntegerPropertyCondition extends AbstractIntegerCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               if ($condition->greaterThan !== null) {
-                       $userList->getConditionBuilder()->add('user_table.'.$this->getDecoratedObject()->propertyname.' > ?', array($condition->greaterThan));
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               if (isset($conditionData['greaterThan'])) {
+                       $objectList->getConditionBuilder()->add('user_table.'.$this->getDecoratedObject()->propertyname.' > ?', array($conditionData['greaterThan']));
                }
-               if ($condition->lessThan !== null) {
-                       $userList->getConditionBuilder()->add('user_table.'.$this->getDecoratedObject()->propertyname.' < ?', array($condition->lessThan));
+               if (isset($conditionData['lessThan'])) {
+                       $objectList->getConditionBuilder()->add('user_table.'.$this->getDecoratedObject()->propertyname.' < ?', array($conditionData['lessThan']));
                }
        }
        
index 9bf72984ff18420c37fdc2f108d56267b6f1e419..a8ad6bc71ce9180c94ed211484175f8b19ea6a3e 100644 (file)
@@ -2,7 +2,7 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
 use wcf\system\exception\UserInputException;
 use wcf\system\language\LanguageFactory;
 use wcf\system\WCF;
@@ -18,7 +18,9 @@ use wcf\util\ArrayUtil;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserLanguageCondition extends AbstractSingleFieldCondition implements IContentCondition, IUserCondition {
+class UserLanguageCondition extends AbstractSingleFieldCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
         * @see \wcf\system\condition\AbstractSingleFieldCondition::$label
         */
@@ -31,10 +33,12 @@ class UserLanguageCondition extends AbstractSingleFieldCondition implements ICon
        protected $languageIDs = array();
        
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               $userList->getConditionBuilder()->add('user_table.languageID IN (?)', array($condition->conditionData['languageIDs']));
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               $objectList->getConditionBuilder()->add('user_table.languageID IN (?)', array($conditionData['languageIDs']));
        }
        
        /**
index 8b0821075d20956a4d822434984077ab4ae0acdc..0ba429ff8816b76cc907184f5f78bb8284530e7f 100644 (file)
@@ -2,8 +2,8 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
 use wcf\data\DatabaseObject;
+use wcf\data\DatabaseObjectList;
 use wcf\system\option\user\UserOptionHandler;
 use wcf\system\WCF;
 
@@ -17,7 +17,9 @@ use wcf\system\WCF;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserOptionsCondition extends AbstractMultipleFieldsCondition implements IContentCondition, IUserCondition {
+class UserOptionsCondition extends AbstractMultipleFieldsCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
         * user option handler object
         * @var \wcf\system\option\user\UserOptionHandler
@@ -36,16 +38,18 @@ class UserOptionsCondition extends AbstractMultipleFieldsCondition implements IC
        }
        
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               $optionValues = $condition->optionValues;
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               $optionValues = $conditionData['optionValues'];
                
                foreach ($this->optionHandler->getCategoryOptions('profile') as $option) {
                        $option = $option['object'];
                        
                        if (isset($optionValues[$option->optionName])) {
-                               $this->optionHandler->getTypeObject($option->optionType)->addCondition($userList, $option, $optionValues[$option->optionName]);
+                               $this->optionHandler->getTypeObject($option->optionType)->addCondition($objectList, $option, $optionValues[$option->optionName]);
                        }
                }
        }
index 626595ca4678a4165d687306fe60bde88dd7c9c8..0f9bb3e73d2c1dd1a65c7ffdb2abf43cfa2b2e47 100644 (file)
@@ -2,7 +2,7 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
 use wcf\system\exception\UserInputException;
 use wcf\system\WCF;
 
@@ -16,7 +16,9 @@ use wcf\system\WCF;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserRegistrationDateCondition extends AbstractSingleFieldCondition implements IContentCondition, IUserCondition {
+class UserRegistrationDateCondition extends AbstractSingleFieldCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
         * @see \wcf\system\condition\AbstractSingleFieldCondition::$label
         */
@@ -35,14 +37,16 @@ class UserRegistrationDateCondition extends AbstractSingleFieldCondition impleme
        protected $registrationDateStart = '';
        
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               if ($condition->registrationDateEnd !== null) {
-                       $userList->getConditionBuilder()->add('user_table.registrationDate < ?', array(strtotime($condition->registrationDateEnd) + 86400));
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               if (isset($conditionData['registrationDateEnd'])) {
+                       $userList->getConditionBuilder()->add('user_table.registrationDate < ?', array(strtotime($conditionData['registrationDateEnd']) + 86400));
                }
-               if ($condition->registrationDateStart !== null) {
-                       $userList->getConditionBuilder()->add('user_table.registrationDate >= ?', array(strtotime($condition->registrationDateStart)));
+               if (isset($conditionData['registrationDateStart'])) {
+                       $userList->getConditionBuilder()->add('user_table.registrationDate >= ?', array(strtotime($conditionData['registrationDateStart'])));
                }
        }
        
index 59a54f508b7b1c4057744f6f394e13001e1bddde..e78d3ea6665f15516859548e4ffea259ad068a65 100644 (file)
@@ -2,7 +2,7 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
 use wcf\system\WCF;
 
 /**
@@ -16,7 +16,9 @@ use wcf\system\WCF;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserRegistrationDateIntervalCondition extends AbstractIntegerCondition implements IContentCondition, IUserCondition {
+class UserRegistrationDateIntervalCondition extends AbstractIntegerCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
         * @see \wcf\system\condition\AbstractMultipleFieldsCondition::$languageItemPrefix
         */
@@ -28,14 +30,16 @@ class UserRegistrationDateIntervalCondition extends AbstractIntegerCondition imp
        protected $minValue = 0;
        
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               if ($condition->greaterThan !== null) {
-                       $userList->getConditionBuilder()->add('user_table.registrationDate < ?', array(TIME_NOW - $condition->greaterThan * 86400));
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               if ($conditionData['greaterThan'] !== null) {
+                       $userList->getConditionBuilder()->add('user_table.registrationDate < ?', array(TIME_NOW - $conditionData['greaterThan'] * 86400));
                }
-               if ($condition->lessThan !== null) {
-                       $userList->getConditionBuilder()->add('user_table.registrationDate > ?', array(TIME_NOW - $condition->lessThan * 86400));
+               if ($conditionData['lessThan'] !== null) {
+                       $userList->getConditionBuilder()->add('user_table.registrationDate > ?', array(TIME_NOW - $conditionData['lessThan'] * 86400));
                }
        }
        
index 4924d9649bf68099324b064f8279f99bef6ea7b8..2e5d8a263fd8d4df6dc2346a20398551ba5c166e 100644 (file)
@@ -2,7 +2,7 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
 use wcf\system\exception\UserInputException;
 use wcf\system\WCF;
 
@@ -16,7 +16,9 @@ use wcf\system\WCF;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserStateCondition extends AbstractSingleFieldCondition implements IContentCondition, IUserCondition {
+class UserStateCondition extends AbstractSingleFieldCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
         * @see \wcf\system\condition\AbstractSingleFieldCondition::$label
         */
@@ -47,15 +49,17 @@ class UserStateCondition extends AbstractSingleFieldCondition implements IConten
        protected $userIsNotBanned = 0;
        
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               if ($condition->userIsBanned !== null) {
-                       $userList->getConditionBuilder()->add('user_table.banned = ?', array($condition->userIsBanned));
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               if (isset($conditionData['userIsBanned'])) {
+                       $userList->getConditionBuilder()->add('user_table.banned = ?', array($conditionData['userIsBanned']));
                }
                
-               if ($condition->userIsEnabled !== null) {
-                       if ($condition->userIsEnabled) {
+               if ($conditionData['userIsEnabled']) {
+                       if ($conditionData['userIsEnabled']) {
                                $userList->getConditionBuilder()->add('user_table.activationCode = ?', array(0));
                        }
                        else {
diff --git a/wcfsetup/install/files/lib/system/condition/UserTimestampPropertyCondition.class.php b/wcfsetup/install/files/lib/system/condition/UserTimestampPropertyCondition.class.php
new file mode 100644 (file)
index 0000000..fc286fb
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\condition\Condition;
+use wcf\data\user\User;
+use wcf\system\WCF;
+
+/**
+ * Condition implementation for compairing a user-bound timestamp with a fixed time
+ * interval.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.condition
+ * @category   Community Framework
+ */
+class UserTimestampPropertyCondition extends AbstractTimestampCondition implements IContentCondition, IUserCondition {
+       use TObjectUserCondition;
+       use TObjectListUserCondition;
+       
+       /**
+        * @see \wcf\system\condition\AbstractTimestampCondition::$className
+        */
+       protected $className = User::class;
+       
+       /**
+        * @see \wcf\system\condition\AbstractTimestampCondition::getLanguageItemPrefix()
+        */
+       protected function getLanguageItemPrefix() {
+               return 'wcf.user.condition';
+       }
+       
+       /**
+        * @see \wcf\system\condition\AbstractTimestampCondition::getPropertyName()
+        */
+       protected function getPropertyName() {
+               return $this->getDecoratedObject()->propertyname;
+       }
+       
+       /**
+        * @see \wcf\system\condition\IContentCondition::showContent()
+        */
+       public function showContent(Condition $condition) {
+               if (!WCF::getUser()->userID) return false;
+               
+               return $this->checkUser($condition, WCF::getUser());
+       }
+}
index efe6e449c13d0f2374be1921f4b1d73639daa623..d07b7e244a2040a5af12448767867ecbd44776f0 100644 (file)
@@ -2,7 +2,7 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\User;
-use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
 use wcf\system\WCF;
 
 /**
@@ -15,7 +15,9 @@ use wcf\system\WCF;
  * @subpackage system.condition
  * @category   Community Framework
  */
-class UserUsernameCondition extends AbstractTextCondition implements IContentCondition, IUserCondition {
+class UserUsernameCondition extends AbstractTextCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
        /**
         * @see \wcf\system\condition\AbstractTextCondition::$fieldName
         */
@@ -27,10 +29,12 @@ class UserUsernameCondition extends AbstractTextCondition implements IContentCon
        protected $label = 'wcf.user.username';
        
        /**
-        * @see \wcf\system\condition\IUserCondition::addUserCondition()
+        * @see \wcf\system\condition\IObjectListCondition::addObjectListCondition()
         */
-       public function addUserCondition(Condition $condition, UserList $userList) {
-               $userList->getConditionBuilder()->add('user_table.username LIKE ?', array('%'.addcslashes($condition->username, '_%').'%'));
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) return;
+               
+               $objectList->getConditionBuilder()->add('user_table.username LIKE ?', array('%'.addcslashes($conditionData['username'], '_%').'%'));
        }
        
        /**
index 10dd23556d2d027090b77d4a268ecd065f3b4001..c838057ba90d15d3f92da60144e825ebf95ae833 100644 (file)
@@ -1592,11 +1592,6 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.user.revertChanges.timeframe"><![CDATA[Zeitraum]]></item>
                <item name="wcf.acp.user.revertChanges.timeframe.description"><![CDATA[Zeitraum in dem Änderungen des Benutzers zurückgesetzt werden sollen. Inhalte werden auf die neuste Version, welche entweder älter als der angegebene Zeitraum ist oder welche von einem anderen Nutzer stammt, zurückgesetzt. [Zeitraum in Tagen]]]></item>
                <item name="wcf.acp.user.revertChanges.markedUsers"><![CDATA[Änderungen folgender Nutzer zurücksetzen]]></item>
-               <item name="wcf.acp.user.bulkProcessing"><![CDATA[Benutzer-Massenbearbeitung]]></item>
-               <item name="wcf.acp.user.bulkProcessing.action"><![CDATA[Aktion]]></item>
-               <item name="wcf.acp.user.bulkProcessing.conditions"><![CDATA[Bedingungen]]></item>
-               <item name="wcf.acp.user.bulkProcessing.success"><![CDATA[Die gewählte Aktion wurde erfolgreich auf {#$affectedUsers} Benutzer ausgeführt.]]></item>
-               <item name="wcf.acp.user.bulkProcessing.warning"><![CDATA[Die Massenbearbeitung von Benutzern führt die unten ausgewählte Aktion <b>ohne zusätzliche Sicherheitsabfrage</b> bei allen Benutzern aus, die unter die eingestellten Bedingungen fallen.]]></item>
                <item name="wcf.acp.user.search"><![CDATA[Benutzer suchen]]></item>
                <item name="wcf.acp.user.search.conditions.general"><![CDATA[Allgemein]]></item>
                <item name="wcf.acp.user.search.conditions.profile"><![CDATA[Profil]]></item>
@@ -1735,6 +1730,17 @@ Sie können jetzt den vollen Funktionsumfang der Seite nutzen.]]></item>
                <item name="wcf.acp.user.notificationPresetSettings.description"><![CDATA[Sie können auf dieser Seite die Standardeinstellungen für neue Benutzer festlegen, dies wirkt sich nicht auf bestehende Benutzer aus. Benutzer können abweichende Einstellungen festlegen.]]></item>
                <item name="wcf.acp.user.notificationPresetSettings.applyChangesToExistingUsers"><![CDATA[Änderungen auch auf bestehende Benutzerkonten anwenden]]></item>
                <item name="wcf.acp.user.notificationPresetSettings.applyChangesToExistingUsers.description"><![CDATA[Die hier eingestellten Änderungen der Vorgabewerte für Benachrichtigungen werden auch in bestehende Benutzerkonten übernommen. Die entsprechenden Einstellungen der Benutzer werden dabei überschrieben.]]></item>
+               
+               <item name="wcf.acp.user.bulkProcessing"><![CDATA[Benutzer-Massenbearbeitung]]></item>
+               <item name="wcf.acp.user.bulkProcessing.action"><![CDATA[Aktion]]></item>
+               <item name="wcf.acp.user.bulkProcessing.assignToUserGroups"><![CDATA[Benutzergruppen zuweisen]]></item>
+               <item name="wcf.acp.user.bulkProcessing.conditions"><![CDATA[Bedingungen]]></item>
+               <item name="wcf.acp.user.bulkProcessing.delete"><![CDATA[Benutzer löschen]]></item>
+               <item name="wcf.acp.user.bulkProcessing.exportMailAddress"><![CDATA[E-Mail-Adressen exportieren]]></item>
+               <item name="wcf.acp.user.bulkProcessing.removeFromUserGroups"><![CDATA[Aus Benutzergruppen entfernen]]></item>
+               <item name="wcf.acp.user.bulkProcessing.sendMail"><![CDATA[E-Mail an Benutzer senden]]></item>
+               <item name="wcf.acp.user.bulkProcessing.success"><![CDATA[Die gewählte Aktion wurde auf {#$affectedObjectCount} Benutzer ausgeführt.]]></item>
+               <item name="wcf.acp.user.bulkProcessing.warning"><![CDATA[Die Massenbearbeitung von Benutzern führt die unten ausgewählte Aktion <b>ohne zusätzliche Sicherheitsabfrage</b> bei allen Benutzern aus, die unter die eingestellten Bedingungen fallen.]]></item>
        </category>
        
        <category name="wcf.acp.worker">
@@ -2246,6 +2252,7 @@ Fehler sind beispielsweise:
                <item name="wcf.global.button.showAll"><![CDATA[Alle anzeigen]]></item>
                <item name="wcf.global.reason"><![CDATA[Begründung]]></item>
                <item name="wcf.global.settings"><![CDATA[Einstellungen]]></item>
+               <item name="wcf.global.bulkProcessing.warning"><![CDATA[Die Massenbearbeitung führt die unten ausgewählte Aktion <b>ohne zusätzliche Sicherheitsabfrage</b> aus!]]></item>
        </category>
        
        <category name="wcf.global.form">
@@ -3118,6 +3125,7 @@ Sollten Sie sich nicht auf der Website: {@PAGE_TITLE|language} angemeldet haben,
                <item name="wcf.user.condition.groupIDs"><![CDATA[in Benutzergruppen]]></item>
                <item name="wcf.user.condition.groupIDs.description"><![CDATA[Benutzer müssen in den ausgewählten Benutzergruppen Mitglied sein.]]></item>
                <item name="wcf.user.condition.languages"><![CDATA[Sprachen]]></item>
+               <item name="wcf.user.condition.lastActivityTime"><![CDATA[Letzte Aktivität]]></item>
                <item name="wcf.user.condition.likesReceived"><![CDATA[Erhaltene Likes]]></item>
                <item name="wcf.user.condition.mobileBrowser"><![CDATA[Mobiler Browser]]></item>
                <item name="wcf.user.condition.mobileBrowser.usesMobileBrowser"><![CDATA[Verwendet mobilen Browser]]></item>
index 750a9a7a8b80b7686a62eb63fd12474ad390da52..9067773a9597d849f175ee36ad56ec493d9e9f97 100644 (file)
@@ -1591,11 +1591,6 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.user.revertChanges.timeframe"><![CDATA[Timeframe]]></item>
                <item name="wcf.acp.user.revertChanges.timeframe.description"><![CDATA[Changes made in this timeframe will be reverted to the newest version that is either older than the given timeframe or made by an unrelated user [time in days]]]></item>
                <item name="wcf.acp.user.revertChanges.markedUsers"><![CDATA[Revert changes by the following users]]></item>
-               <item name="wcf.acp.user.bulkProcessing"><![CDATA[Bulk Processing]]></item>
-               <item name="wcf.acp.user.bulkProcessing.action"><![CDATA[Actions]]></item>
-               <item name="wcf.acp.user.bulkProcessing.conditions"><![CDATA[Conditions]]></item>
-               <item name="wcf.acp.user.bulkProcessing.success"><![CDATA[Executed action affected {#$affectedUsers} user{if $affectedUsers != 1}s{/if}.]]></item>
-               <item name="wcf.acp.user.bulkProcessing.warning"><![CDATA[Heads up! The bulk processing executes all actions below without any further confirmation prompt!]]></item>
                <item name="wcf.acp.user.search"><![CDATA[Search Users]]></item>
                <item name="wcf.acp.user.search.conditions.general"><![CDATA[General]]></item>
                <item name="wcf.acp.user.search.conditions.profile"><![CDATA[Profile]]></item>
@@ -1735,6 +1730,16 @@ You can now fully access the website.]]></item>
                <item name="wcf.acp.user.notificationPresetSettings.applyChangesToExistingUsers"><![CDATA[Apply changes for existing users too]]></item>
                <item name="wcf.acp.user.notificationPresetSettings.applyChangesToExistingUsers.description"><![CDATA[Any changes to default notification settings will be applied to existing users. This overrides all affected settings previously changed by the respective user itself.]]></item>
                
+               <item name="wcf.acp.user.bulkProcessing"><![CDATA[User Bulk Processing]]></item>
+               <item name="wcf.acp.user.bulkProcessing.action"><![CDATA[Action]]></item>
+               <item name="wcf.acp.user.bulkProcessing.assignToUserGroups"><![CDATA[Assign to User Groups]]></item>
+               <item name="wcf.acp.user.bulkProcessing.conditions"><![CDATA[Conditions]]></item>
+               <item name="wcf.acp.user.bulkProcessing.delete"><![CDATA[Delete Users]]></item>
+               <item name="wcf.acp.user.bulkProcessing.exportMailAddress"><![CDATA[Export Email Addresses]]></item>
+               <item name="wcf.acp.user.bulkProcessing.removeFromUserGroups"><![CDATA[Remove from User Groups]]></item>
+               <item name="wcf.acp.user.bulkProcessing.sendMail"><![CDATA[Send Email to Users]]></item>
+               <item name="wcf.acp.user.bulkProcessing.success"><![CDATA[The selection action has been executed and affected {#$affectedObjectCount} user{if $affectedUsers != 1}s{/if}.]]></item>
+               <item name="wcf.acp.user.bulkProcessing.warning"><![CDATA[Heads up! The bulk processing executes all actions below on all users matching the seleced conditions without any further confirmation prompt!]]></item>
        </category>
        
        <category name="wcf.acp.worker">