{if !$isGuestGroup|isset}{assign var=isGuestGroup value=false}{/if}
+{if !$groupIsOwner|isset}{assign var=groupIsOwner value=false}{/if}
{foreach from=$options item=optionData}
{assign var=option value=$optionData[object]}
{if $errorType|is_array && $errorType[$option->optionName]|isset}
{assign var=error value=''}
{/if}
<dl class="{$option->optionName}Input{if $error} formError{/if}">
- <dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}>{if $isSearchMode|empty || !$optionData[hideLabelInSearch]}<label for="{$option->optionName}">{if VISITOR_USE_TINY_BUILD && $isGuestGroup && $option->excludedInTinyBuild}<span class="icon icon16 fa-bolt red jsTooltip" title="{lang}wcf.acp.group.excludedInTinyBuild{/lang}"></span> {/if}{lang}{@$langPrefix}{$option->optionName}{/lang}</label>{/if}</dt>
+ <dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}>
+ {if $isSearchMode|empty || !$optionData[hideLabelInSearch]}
+ <label for="{$option->optionName}">
+ {if VISITOR_USE_TINY_BUILD && $isGuestGroup && $option->excludedInTinyBuild}<span class="icon icon16 fa-bolt red jsTooltip" title="{lang}wcf.acp.group.excludedInTinyBuild{/lang}"></span> {/if}
+ {if $groupIsOwner && $option->optionName|in_array:$ownerGroupPermissions}<span class="icon icon16 fa-shield jsTooltip" title="{lang}wcf.acp.group.ownerGroupPermission{/lang}"></span> {/if}
+
+ {lang}{@$langPrefix}{$option->optionName}{/lang}
+ </label>
+ {/if}
+ </dt>
<dd>{@$optionData[html]}
{if $error}
<small class="innerError">
<small>{lang __optional=true}{@$langPrefix}{$option->optionName}.description{/lang}</small>
</dd>
</dl>
-{/foreach}
\ No newline at end of file
+{/foreach}
<p class="warning">{lang}wcf.acp.group.excludedInTinyBuild.notice{/lang}</p>
{/if}
+{if $action == 'edit' && $group->isOwner()}
+ <p class="info"><span class="icon icon16 fa-shield"></span> {lang}wcf.acp.group.type.owner.description{/lang}</p>
+{/if}
+
{if $warningSelfEdit|isset}
<p class="warning">{lang}wcf.acp.group.edit.warning.selfIsMember{/lang}</p>
{/if}
</div>
</form>
+{if $action === 'edit'}
+ <script>
+ (function () {
+ {if $groupIsOwner}
+ elBySelAll('input[name="values[admin.user.accessibleGroups][]"]', undefined, function(input) {
+ var shadow = elCreate('input');
+ shadow.type = 'hidden';
+ shadow.name = input.name;
+ shadow.value = input.value;
+
+ input.parentNode.appendChild(shadow);
+
+ input.disabled = true;
+ });
+
+ var permissions = [{implode from=$ownerGroupPermissions item=$_ownerPermission}'{$_ownerPermission|encodeJS}'{/implode}];
+ permissions.forEach(function(permission) {
+ elBySelAll('input[name="values[' + permission + ']"]', undefined, function (input) {
+ if (input.value === '1') {
+ input.checked = true;
+ }
+ else {
+ input.disabled = true;
+ }
+ });
+ });
+ {elseif $ownerGroupID}
+ var input = elBySel('input[name="values[admin.user.accessibleGroups][]"][value="{$ownerGroupID}"]');
+ if (input) {
+ elRemove(input.closest('label'));
+ }
+ {/if}
+ })();
+ </script>
+{/if}
+
{include file='footer'}
{else}
{lang}{$group->groupName}{/lang}
{/if}
+ {if $group->isOwner()}
+ <span class="icon icon16 fa-shield jsTooltip" title="{lang}wcf.acp.group.type.owner{/lang}"></span>
+ {/if}
</td>
<td class="columnDigits columnMembers">
{if $group->groupType == 1 ||$group->groupType == 2}
I18nHandler::getInstance()->assignVariables(!empty($_POST));
+ $ownerGroupPermissions = [];
+ if ($this->group->isOwner()) {
+ $ownerGroupPermissions = UserGroup::getOwnerPermissions();
+ $ownerGroupPermissions[] = 'admin.user.accessibleGroups';
+ }
+
+ $ownerGroup = UserGroup::getGroupByType(UserGroup::OWNER);
+
WCF::getTPL()->assign([
'groupID' => $this->group->groupID,
'group' => $this->group,
'groupIsEveryone' => $this->group->groupType == UserGroup::EVERYONE,
'groupIsGuest' => $this->group->groupType == UserGroup::GUESTS,
'groupIsUsers' => $this->group->groupType == UserGroup::USERS,
+ 'groupIsOwner' => $this->group->isOwner(),
'isUnmentionableGroup' => $this->isUnmentionableGroup ? 1 : 0,
+ 'ownerGroupPermissions' => $ownerGroupPermissions,
+ 'ownerGroupID' => $ownerGroup ? $ownerGroup->groupID : null,
]);
// add warning when the initiator is in the group
$this->hasAdministrativePermissions = false;
if ($this->userID) {
- foreach ($this->getGroupIDs() as $groupID) {
- $group = UserGroup::getGroupByID($groupID);
+ foreach (UserGroup::getGroupsByIDs($this->getGroupIDs()) as $group) {
if ($group->isAdminGroup()) {
$this->hasAdministrativePermissions = true;
break;
return $this->hasAdministrativePermissions;
}
+ /**
+ * Returns true, if this user is a member of the owner group.
+ *
+ * @return bool
+ * @since 5.2
+ */
+ public function hasOwnerAccess() {
+ static $isOwner;
+
+ if ($isOwner === null) {
+ $isOwner = false;
+
+ foreach (UserGroup::getGroupsByIDs($this->getGroupIDs()) as $group) {
+ if ($group->isOwner()) {
+ $isOwner = true;
+ break;
+ }
+ }
+ }
+
+ return $isOwner;
+ }
+
/**
* @inheritDoc
*/
*/
const OTHER = 4;
+ /**
+ * the owner group is always an administrator group
+ * @var int
+ */
+ const OWNER = 5;
+
/**
* group cache
* @var UserGroup[]
* @throws SystemException
*/
public static function getGroupByType($type) {
- if ($type != self::EVERYONE && $type != self::GUESTS && $type != self::USERS) {
+ if ($type != self::EVERYONE && $type != self::GUESTS && $type != self::USERS && $type != self::OWNER) {
throw new SystemException('invalid value for type argument');
}
return $this->groupType == self::USERS;
}
+ /**
+ * Returns true if this is the 'Owner' group.
+ *
+ * @return bool
+ * @since 5.2
+ */
+ public function isOwner() {
+ return $this->groupType == self::OWNER;
+ }
+
/**
* Returns true if the given groups are accessible for the active user.
*
}
/**
- * Returns true if the current group is an admin-group.
- * Every group that may access EVERY group is an admin-group.
+ * Returns true if the current group is an admin-group, which requires it to fulfill
+ * one of these conditions:
+ * a) The WCFSetup is running and the group id is 4.
+ * b) This is the 'Owner' group.
+ * c) The group can access all groups (the 'Owner' group does not count).
*
* @return boolean
*/
public function isAdminGroup() {
- // workaround for WCF-Setup
- if (!PACKAGE_ID && $this->groupID == 4) return true;
+ // WCFSetup
+ if (!PACKAGE_ID && $this->groupID == 4) {
+ return true;
+ }
- $groupIDs = array_keys(self::getGroupsByType());
+ if ($this->groupType === self::OWNER) {
+ return true;
+ }
+
+ $groupIDs = array_keys(self::getGroupsByType([], [self::OWNER]));
$accessibleGroupIDs = explode(',', (string) $this->getGroupOption('admin.user.accessibleGroups'));
// no differences -> all groups are included
if (!$this->isAccessible()) return false;
// cannot delete static groups
- if ($this->groupType == self::EVERYONE || $this->groupType == self::GUESTS || $this->groupType == self::USERS) return false;
+ if ($this->groupType == self::EVERYONE || $this->groupType == self::GUESTS || $this->groupType == self::USERS || $this->groupType == self::OWNER) return false;
return true;
}
return self::$cache['groups'];
}
+
+ /**
+ * Returns the list of irrevocable permissions of the owner group.
+ *
+ * @return string[]
+ * @since 5.2
+ */
+ public static function getOwnerPermissions() {
+ return [
+ 'admin.configuration.canEditOption',
+ 'admin.configuration.canManageApplication',
+ 'admin.configuration.package.canInstallPackage',
+ 'admin.configuration.package.canUninstallPackage',
+ 'admin.configuration.package.canUpdatePackage',
+ 'admin.general.canUseAcp',
+ 'admin.general.canViewPageDuringOfflineMode',
+ 'admin.user.canEditGroup',
+ 'admin.user.canEditUser',
+ 'admin.user.canSearchUser',
+ ];
+ }
}
$data[$row['optionName']]['values'][] = $row['optionValue'];
}
+ $includesOwnerGroup = false;
+ $ownerGroup = UserGroup::getGroupByType(UserGroup::OWNER);
+ if ($ownerGroup && in_array($ownerGroup->groupID, $parameters)) {
+ $includesOwnerGroup = true;
+ }
+
+ $forceGrantPermission = [];
+ if ($includesOwnerGroup) {
+ $forceGrantPermission = UserGroup::getOwnerPermissions();
+ }
+
// merge values
$neverValues = [];
foreach ($data as $optionName => $option) {
}
}
+ if ($ownerGroup && $optionName === 'admin.user.accessibleGroups') {
+ $accessibleGroupIDs = explode(',', $result);
+ if ($includesOwnerGroup) {
+ // Regardless of the actual permissions, the owner group has access to all groups.
+
+ $accessibleGroupIDs[] = $ownerGroup->groupID;
+ }
+ else if (!$includesOwnerGroup && in_array($ownerGroup->groupID, $accessibleGroupIDs)) {
+ $accessibleGroupIDs = array_diff($accessibleGroupIDs, [$ownerGroup->groupID]);
+ }
+
+ $result = implode(',', $accessibleGroupIDs);
+ }
+ else if ($includesOwnerGroup && in_array($optionName, $forceGrantPermission)) {
+ $result = 1;
+ }
+
// handle special value 'Never' for boolean options
if ($option['type'] === 'boolean' && $result == -1) {
$neverValues[$optionName] = $optionName;
* user group object
* @var UserGroup
*/
- protected $group = null;
+ protected $group;
/**
* true if current user can edit every user group
*/
protected $isAdmin = null;
+ /**
+ * true if the user is part of the owner group
+ * @var bool
+ * @since 5.2
+ */
+ protected $isOwner = null;
+
/**
* Sets current user group.
*
*/
protected function isAdmin() {
if ($this->isAdmin === null) {
- $this->isAdmin = false;
-
- foreach (WCF::getUser()->getGroupIDs() as $groupID) {
- if (UserGroup::getGroupByID($groupID)->isAdminGroup()) {
- $this->isAdmin = true;
- break;
- }
- }
+ $this->isAdmin = WCF::getUser()->hasAdministrativeAccess();
}
return $this->isAdmin;
}
+ /**
+ * Returns true, if the current user is a member of the owner group.
+ *
+ * @return bool
+ * @since 5.2
+ */
+ protected function isOwner() {
+ if ($this->isOwner === null) {
+ $this->isOwner = WCF::getUser()->hasOwnerAccess();
+ }
+
+ return $this->isOwner;
+ }
+
/**
* @inheritDoc
*/
throw new UserInputException($option->optionName, 'exceedsOwnPermission');
}
}
- else if ($option->optionName == 'admin.user.accessibleGroups' && $this->group !== null && $this->group->isAdminGroup()) {
+ else if (!$this->isOwner() && $option->optionName == 'admin.user.accessibleGroups' && $this->group !== null && $this->group->isAdminGroup()) {
$hasOtherAdminGroup = false;
foreach (UserGroup::getGroupsByType() as $userGroup) {
if ($userGroup->groupID != $this->group->groupID && $userGroup->isAdminGroup()) {
<item name="wcf.acp.group.option.user.contactForm.attachment.maxCount"><![CDATA[Maximale Dateianhänge pro Nachricht]]></item>
<item name="wcf.acp.group.option.user.profile.canEditUserProfile"><![CDATA[Kann eigenes Profil bearbeiten]]></item>
<item name="wcf.acp.group.allowMention"><![CDATA[Benutzergruppe kann erwähnt werden]]></item>
+ <item name="wcf.acp.group.type.owner"><![CDATA[Besitzer]]></item>
+ <item name="wcf.acp.group.type.owner.description"><![CDATA[Die Besitzer-Gruppe verfügt über nicht entziehbare Berechtigungen und kann von anderen Gruppen nicht bearbeitet werden, diese Gruppe kann nur durch die Besitzer-Gruppe selbst bearbeitet werden. Mitglieder dieser Benutzer können andere Benutzer zu dieser Gruppe hinzufügen, sich aber selbst nicht daraus entfernen.]]></item>
+ <item name="wcf.acp.group.ownerGroupPermission"><![CDATA[Die Besitzer-Gruppe verfügt immer über diese Berechtigung, sie kann nicht entzogen werden.]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.acp.index.credits"><![CDATA[Über WoltLab Suite™]]></item>
<item name="wcf.acp.group.option.user.contactForm.attachment.maxCount"><![CDATA[Maximum Attachments per Message]]></item>
<item name="wcf.acp.group.option.user.profile.canEditUserProfile"><![CDATA[Can edit their profile]]></item>
<item name="wcf.acp.group.allowMention"><![CDATA[User group can be mentioned]]></item>
+ <item name="wcf.acp.group.type.owner"><![CDATA[Owner]]></item>
+ <item name="wcf.acp.group.type.owner.description"><![CDATA[The owner group features a few irrevocable permissions and is protected from edits by other groups, only the owner group can edit itself. Members of this group can add other users to this group, but cannot remove themselves.]]></item>
+ <item name="wcf.acp.group.ownerGroupPermission"><![CDATA[The owner group always has this permission, it cannot be revoked.]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.acp.index.credits"><![CDATA[About WoltLab Suite™]]></item>
INSERT INTO wcf1_user_group (groupID, groupName, groupType) VALUES (1, 'wcf.acp.group.group1', 1); -- Everyone
INSERT INTO wcf1_user_group (groupID, groupName, groupType) VALUES (2, 'wcf.acp.group.group2', 2); -- Guests
INSERT INTO wcf1_user_group (groupID, groupName, groupType) VALUES (3, 'wcf.acp.group.group3', 3); -- Registered Users
-INSERT INTO wcf1_user_group (groupID, groupName, groupType) VALUES (4, 'wcf.acp.group.group4', 4); -- Administrators
+INSERT INTO wcf1_user_group (groupID, groupName, groupType) VALUES (4, 'wcf.acp.group.group4', 5); -- Administrators
INSERT INTO wcf1_user_group (groupID, groupName, groupType) VALUES (5, 'wcf.acp.group.group5', 4); -- Moderators
-- default user group options