mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Added editing of ACL rules #157
This commit is contained in:
parent
5987ccdbc9
commit
e5a176f5ea
7 changed files with 263 additions and 40 deletions
|
|
@ -10,6 +10,47 @@ import { folderListOptionsBuilder, sortFolders } from 'Common/Folders';
|
||||||
import { initOnStartOrLangChange, i18n, getNotification } from 'Common/Translator';
|
import { initOnStartOrLangChange, i18n, getNotification } from 'Common/Translator';
|
||||||
import { defaultOptionsAfterRender } from 'Common/Utils';
|
import { defaultOptionsAfterRender } from 'Common/Utils';
|
||||||
|
|
||||||
|
import { FolderACLPopupView } from 'View/Popup/FolderAcl';
|
||||||
|
import { showScreenPopup } from 'Knoin/Knoin';
|
||||||
|
|
||||||
|
import { AbstractCollectionModel } from 'Model/AbstractCollection';
|
||||||
|
export class FolderACLModel extends AbstractCollectionModel
|
||||||
|
{
|
||||||
|
static reviveFromJson(json) {
|
||||||
|
return super.reviveFromJson(json, rights => FolderACLRightsModel.reviveFromJson(rights));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
|
export class FolderACLRightsModel extends AbstractModel {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
addObservablesTo(this, {
|
||||||
|
identifier: '',
|
||||||
|
mine: false
|
||||||
|
});
|
||||||
|
// The "RIGHTS=" capability MUST NOT include any of the rights defined in RFC 2086.
|
||||||
|
// That way we know it supports RFC 4314
|
||||||
|
// this.rights = ko.observableArray(/*'alrswipcd'.split('')*/);
|
||||||
|
this.rights = ko.observableArray(/*'alrswipkxte'.split('')*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
static reviveFromJson(json) {
|
||||||
|
json.rights = json.rights.split('');
|
||||||
|
return super.reviveFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
get mayReadItems() { return this.rights.includes('l') && this.rights.includes('r'); }
|
||||||
|
get mayAddItems() { return this.rights.includes('i'); }
|
||||||
|
get mayRemoveItems() { return this.rights.includes('t') && this.rights.includes('e'); }
|
||||||
|
get maySetSeen() { return this.rights.includes('s'); }
|
||||||
|
get maySetKeywords() { return this.rights.includes('w'); }
|
||||||
|
get mayCreateChild() { return this.rights.includes('k'); }
|
||||||
|
get mayRename() { return this.rights.includes('x'); }
|
||||||
|
get mayDelete() { return this.rights.includes('x'); }
|
||||||
|
get maySubmit() { return this.rights.includes('p'); }
|
||||||
|
}
|
||||||
|
|
||||||
export class FolderPopupView extends AbstractViewPopup {
|
export class FolderPopupView extends AbstractViewPopup {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('Folder');
|
super('Folder');
|
||||||
|
|
@ -17,10 +58,10 @@ export class FolderPopupView extends AbstractViewPopup {
|
||||||
folder: null, // FolderModel
|
folder: null, // FolderModel
|
||||||
parentFolder: '',
|
parentFolder: '',
|
||||||
name: '',
|
name: '',
|
||||||
editing: false
|
editing: false,
|
||||||
|
adminACL: false
|
||||||
});
|
});
|
||||||
this.ACLAllowed = FolderUserStore.hasCapability('ACL');
|
this.ACLAllowed = FolderUserStore.hasCapability('ACL');
|
||||||
this.ACL = ko.observableArray();
|
|
||||||
|
|
||||||
this.parentFolderSelectList = koComputable(() =>
|
this.parentFolderSelectList = koComputable(() =>
|
||||||
folderListOptionsBuilder(
|
folderListOptionsBuilder(
|
||||||
|
|
@ -51,6 +92,8 @@ export class FolderPopupView extends AbstractViewPopup {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
|
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
|
||||||
|
this.editACL = this.editACL.bind(this);
|
||||||
|
this.deleteACL = this.deleteACL.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
afterHide() {
|
afterHide() {
|
||||||
|
|
@ -135,11 +178,35 @@ export class FolderPopupView extends AbstractViewPopup {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createACL()
|
||||||
|
{
|
||||||
|
showScreenPopup(FolderACLPopupView, [this.folder(), new FolderACLRightsModel]);
|
||||||
|
}
|
||||||
|
|
||||||
|
editACL(acl)
|
||||||
|
{
|
||||||
|
showScreenPopup(FolderACLPopupView, [this.folder(), acl]);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteACL(acl)
|
||||||
|
{
|
||||||
|
Remote.request('FolderDeleteACL',
|
||||||
|
(iError, data) => !iError && data.Result && this.folder().ACL.remove(acl),
|
||||||
|
{
|
||||||
|
folder: this.folder().fullName,
|
||||||
|
identifier: acl.identifier
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
beforeShow(folder) {
|
beforeShow(folder) {
|
||||||
this.ACL([]);
|
folder.ACL || (folder.ACL = ko.observableArray());
|
||||||
|
this.adminACL(false);
|
||||||
this.ACLAllowed && Remote.request('FolderACL', (iError, data) => {
|
this.ACLAllowed && Remote.request('FolderACL', (iError, data) => {
|
||||||
if (!iError && data.Result) {
|
if (!iError && data.Result) {
|
||||||
this.ACL(Object.values(data.Result));
|
folder.ACL(FolderACLModel.reviveFromJson(data.Result));
|
||||||
|
// data.Result.map(aItem => FolderACLRightsModel.reviveFromJson(aItem)).filter(v => v)
|
||||||
|
this.adminACL(folder.ACL()[0].rights.includes('a'));
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
folder: folder.fullName
|
folder: folder.fullName
|
||||||
|
|
|
||||||
58
dev/View/Popup/FolderAcl.js
Normal file
58
dev/View/Popup/FolderAcl.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||||
|
import { addObservablesTo } from 'External/ko';
|
||||||
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
|
export class FolderACLPopupView extends AbstractViewPopup {
|
||||||
|
constructor() {
|
||||||
|
super('FolderACL');
|
||||||
|
addObservablesTo(this, {
|
||||||
|
create: false,
|
||||||
|
mine: false,
|
||||||
|
folderName: '',
|
||||||
|
identifier: ''
|
||||||
|
});
|
||||||
|
this.rights = ko.observableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
submitForm(/*form*/) {
|
||||||
|
if (!this.mine()) {
|
||||||
|
const rights = this.rights();
|
||||||
|
Remote.request('FolderSetACL',
|
||||||
|
(iError, data) => {
|
||||||
|
if (!iError && data.Result) {
|
||||||
|
const acl = this.acl;
|
||||||
|
if (!acl.identifier) {
|
||||||
|
this.folder.ACL.push(acl);
|
||||||
|
}
|
||||||
|
acl.rights = rights;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
folder: this.folderName(),
|
||||||
|
identifier: this.identifier(),
|
||||||
|
rights: rights.join('')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeShow(folder, acl) {
|
||||||
|
this.folder = folder;
|
||||||
|
this.create(!acl.identifier());
|
||||||
|
this.mine(acl.mine());
|
||||||
|
this.acl = acl;
|
||||||
|
/*
|
||||||
|
this.ACLAllowed && Remote.request('FolderIdentifierRights', (iError, data) => {
|
||||||
|
if (!iError && data.Result) {
|
||||||
|
this.rights(data.Result.rights.split(''));
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
folder: folder.fullName,
|
||||||
|
identifier: acl.identifier
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
this.folderName(folder.fullName);
|
||||||
|
this.identifier(acl.identifier());
|
||||||
|
this.rights(acl.rights());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -91,6 +91,7 @@ trait ACL
|
||||||
private function FolderACLRequest(string $sFolderName, string $sCommand, array $aParams) : \MailSo\Imap\ResponseCollection
|
private function FolderACLRequest(string $sFolderName, string $sCommand, array $aParams) : \MailSo\Imap\ResponseCollection
|
||||||
{
|
{
|
||||||
if ($this->ACLAllow($sFolderName, $sCommand)) try {
|
if ($this->ACLAllow($sFolderName, $sCommand)) try {
|
||||||
|
// \array_unshift($aParams, $this->EscapeFolderName($sFolderName));
|
||||||
return $this->SendRequestGetResponse($sCommand, $aParams);
|
return $this->SendRequestGetResponse($sCommand, $aParams);
|
||||||
} catch (\Throwable $oException) {
|
} catch (\Throwable $oException) {
|
||||||
// Error in IMAP command $sCommand: ACLs disabled
|
// Error in IMAP command $sCommand: ACLs disabled
|
||||||
|
|
@ -119,9 +120,13 @@ trait ACL
|
||||||
public function FolderGetACL(string $sFolderName) : array
|
public function FolderGetACL(string $sFolderName) : array
|
||||||
{
|
{
|
||||||
$aResult = array();
|
$aResult = array();
|
||||||
|
$response = $this->FolderMyRights($sFolderName);
|
||||||
|
$aResult[] = $response;
|
||||||
|
if ($response->hasRight('a')) {
|
||||||
|
// Else error: "NOPERM You lack administrator privileges on this mailbox."
|
||||||
$oResponses = $this->FolderACLRequest($sFolderName, 'GETACL', array($this->EscapeFolderName($sFolderName)));
|
$oResponses = $this->FolderACLRequest($sFolderName, 'GETACL', array($this->EscapeFolderName($sFolderName)));
|
||||||
foreach ($oResponses as $oResponse) {
|
foreach ($oResponses as $oResponse) {
|
||||||
// * ACL INBOX.shared demo@snappymail.eu akxeilprwtscd foobar@snappymail.eu akxeilprwtscd demo2@snappymail.eu lrwstipekxacd
|
// * ACL INBOX.shared demo@snappymail.eu akxeilprwtscd demo2@snappymail.eu lrwstipekxacd
|
||||||
if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType
|
if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType
|
||||||
&& isset($oResponse->ResponseList[4])
|
&& isset($oResponse->ResponseList[4])
|
||||||
&& 'ACL' === $oResponse->ResponseList[1]
|
&& 'ACL' === $oResponse->ResponseList[1]
|
||||||
|
|
@ -130,10 +135,14 @@ trait ACL
|
||||||
{
|
{
|
||||||
$c = \count($oResponse->ResponseList);
|
$c = \count($oResponse->ResponseList);
|
||||||
for ($i = 3; $i < $c; $i += 2) {
|
for ($i = 3; $i < $c; $i += 2) {
|
||||||
$aResult[] = new ACLResponse(
|
$response = new ACLResponse(
|
||||||
$oResponse->ResponseList[$i],
|
$oResponse->ResponseList[$i],
|
||||||
$oResponse->ResponseList[$i+1]
|
$oResponse->ResponseList[$i+1]
|
||||||
);
|
);
|
||||||
|
if ($response->identifier() != $this->Settings->username) {
|
||||||
|
$aResult[] = $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,9 +172,10 @@ trait ACL
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function FolderMyRights(string $sFolderName) : ?ACLResponse
|
public function FolderMyRights(string $sFolderName) : ACLResponse
|
||||||
{
|
{
|
||||||
$oResponses = $this->FolderACLRequest($sFolderName, 'MYRIGHTS', array($this->EscapeFolderName($sFolderName)));
|
$oResponses = $this->FolderACLRequest($sFolderName, 'MYRIGHTS', array($this->EscapeFolderName($sFolderName)));
|
||||||
|
$rights = '';
|
||||||
foreach ($oResponses as $oResponse) {
|
foreach ($oResponses as $oResponse) {
|
||||||
if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType
|
if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType
|
||||||
&& isset($oResponse->ResponseList[3])
|
&& isset($oResponse->ResponseList[3])
|
||||||
|
|
@ -173,9 +183,12 @@ trait ACL
|
||||||
&& $sFolderName === $oResponse->ResponseList[2]
|
&& $sFolderName === $oResponse->ResponseList[2]
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return new ACLResponse('', $oResponse->ResponseList[3]);
|
$rights = $oResponse->ResponseList[3];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
$response = new ACLResponse($this->Settings->username, $rights);
|
||||||
|
$response->mine = true;
|
||||||
|
return $response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ namespace MailSo\Imap\Responses;
|
||||||
*/
|
*/
|
||||||
class ACL implements \JsonSerializable
|
class ACL implements \JsonSerializable
|
||||||
{
|
{
|
||||||
|
public bool $mine = false;
|
||||||
private string
|
private string
|
||||||
$identifier,
|
$identifier,
|
||||||
$rights;
|
$rights;
|
||||||
|
|
@ -52,8 +53,11 @@ class ACL implements \JsonSerializable
|
||||||
public function jsonSerialize()
|
public function jsonSerialize()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'@Object' => 'Object/FolderACLRights',
|
||||||
'identifier' => $this->identifier,
|
'identifier' => $this->identifier,
|
||||||
'rights' => $this->rights,
|
'rights' => $this->rights,
|
||||||
|
'mine' => $this->mine,
|
||||||
|
/*
|
||||||
'mayReadItems' => ($this->hasRight('l') && $this->hasRight('r')),
|
'mayReadItems' => ($this->hasRight('l') && $this->hasRight('r')),
|
||||||
'mayAddItems' => $this->hasRight('i'),
|
'mayAddItems' => $this->hasRight('i'),
|
||||||
'mayRemoveItems' => ($this->hasRight('t') && $this->hasRight('e')),
|
'mayRemoveItems' => ($this->hasRight('t') && $this->hasRight('e')),
|
||||||
|
|
@ -63,6 +67,7 @@ class ACL implements \JsonSerializable
|
||||||
'mayRename' => $this->hasRight('x'),
|
'mayRename' => $this->hasRight('x'),
|
||||||
'mayDelete' => $this->hasRight('x'),
|
'mayDelete' => $this->hasRight('x'),
|
||||||
'maySubmit' => $this->hasRight('p')
|
'maySubmit' => $this->hasRight('p')
|
||||||
|
*/
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -413,11 +413,12 @@ trait Folders
|
||||||
public function DoFolderACL() : array
|
public function DoFolderACL() : array
|
||||||
{
|
{
|
||||||
$this->initMailClientConnection();
|
$this->initMailClientConnection();
|
||||||
return $this->DefaultResponse(
|
return $this->DefaultResponse([
|
||||||
$this->ImapClient()->FolderGetACL(
|
'@Object' => 'Collection/FolderACL',
|
||||||
|
'@Collection' => $this->ImapClient()->FolderGetACL(
|
||||||
$this->GetActionParam('folder', '')
|
$this->GetActionParam('folder', '')
|
||||||
)
|
)
|
||||||
);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function DoFolderDeleteACL() : array
|
public function DoFolderDeleteACL() : array
|
||||||
|
|
|
||||||
|
|
@ -60,21 +60,22 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group" data-bind="if: ACLAllowed">
|
<div class="control-group" data-bind="if: ACLAllowed">
|
||||||
<label>ACL</label>
|
<label>ACL</label>
|
||||||
<div data-bind="foreach: ACL">
|
<div>
|
||||||
<details>
|
<table class="table table-hover" style="width:auto"><tbody>
|
||||||
<summary data-bind="text: identifier"></summary>
|
<!-- ko foreach: folder()?.ACL -->
|
||||||
<table class="table table-hover table-bordered" style="width:auto"><tbody>
|
<tr>
|
||||||
<tr><td>mayAddItems</td><td class="fontastic" data-bind="text: mayAddItems ? '☑' : '☐'"></td></tr>
|
<td data-bind="text: identifier"></td>
|
||||||
<tr><td>mayCreateChild</td><td class="fontastic" data-bind="text: mayCreateChild ? '☑' : '☐'"></td></tr>
|
<td>
|
||||||
<tr><td>mayDelete</td><td class="fontastic" data-bind="text: mayDelete ? '☑' : '☐'"></td></tr>
|
<button class="btn fontastic" data-bind="visible: mine, click: $root.editACL">👁</button>
|
||||||
<tr><td>mayReadItems</td><td class="fontastic" data-bind="text: mayReadItems ? '☑' : '☐'"></td></tr>
|
<button class="btn fontastic" data-bind="visible: !mine(), click: $root.editACL">🖉</button>
|
||||||
<tr><td>mayRemoveItems</td><td class="fontastic" data-bind="text: mayRemoveItems ? '☑' : '☐'"></td></tr>
|
<button class="btn fontastic" data-bind="visible: !mine(), click: $root.deleteACL">🗑</button>
|
||||||
<tr><td>mayRename</td><td class="fontastic" data-bind="text: mayRename ? '☑' : '☐'"></td></tr>
|
</td>
|
||||||
<tr><td>maySetKeywords</td><td class="fontastic" data-bind="text: maySetKeywords ? '☑' : '☐'"></td></tr>
|
</tr>
|
||||||
<tr><td>maySetSeen</td><td class="fontastic" data-bind="text: maySetSeen ? '☑' : '☐'"></td></tr>
|
<!-- /ko -->
|
||||||
<tr><td>maySubmit</td><td class="fontastic" data-bind="text: maySubmit ? '☑' : '☐'"></td></tr>
|
<tr><td></td><td>
|
||||||
|
<button class="btn fontastic" data-bind="visible: adminACL, click: createACL">✚</button>
|
||||||
|
</td></tr>
|
||||||
</tbody></table>
|
</tbody></table>
|
||||||
</details>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<header>
|
||||||
|
<a href="#" class="close" data-bind="click: close">×</a>
|
||||||
|
<h3>ACL: <span data-bind="text: folderName"></span></h3>
|
||||||
|
</header>
|
||||||
|
<form id="folderaclform" class="modal-body form-horizontal" autocomplete="off" spellcheck="false" data-bind="submit: submitForm">
|
||||||
|
<div class="control-group">
|
||||||
|
<label data-i18n="GLOBAL/EMAIL"></label>
|
||||||
|
<input name="identifier" class="input-xlarge" autofocus=""
|
||||||
|
autocomplete="off" autocorrect="off" autocapitalize="off"
|
||||||
|
data-bind="visible: create, value: identifier" required="">
|
||||||
|
<span data-bind="visible: !create(), text: identifier"></span>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label>Rights</label>
|
||||||
|
<table class="table table-hover table-bordered" style="width:auto"><tbody>
|
||||||
|
<tr title="SETACL/DELETEACL/GETACL/LISTRIGHTS">
|
||||||
|
<td>Administer rights</td>
|
||||||
|
<td><input type="checkbox" value="a" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr title="SELECT the mailbox, perform STATUS">
|
||||||
|
<td>Select folder</td>
|
||||||
|
<td><input type="checkbox" value="r" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr title="Mailbox is visible to LIST/LSUB commands, SUBSCRIBE mailbox">
|
||||||
|
<td>Show messages</td>
|
||||||
|
<td><input type="checkbox" value="l" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr title="perform APPEND, COPY into mailbox">
|
||||||
|
<td>Insert messages</td>
|
||||||
|
<td><input type="checkbox" value="i" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr title="set or clear \SEEN flag via STORE, also set \SEEN during APPEND/COPY/FETCH BODY[...]">
|
||||||
|
<td>\SEEN flag</td>
|
||||||
|
<td><input type="checkbox" value="s" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr title="set or clear \DELETED flag via STORE, set \DELETED flag during APPEND/COPY">
|
||||||
|
<td>\DELETED flag</td>
|
||||||
|
<td><input type="checkbox" value="t" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<!--
|
||||||
|
<tr title="STORE DELETED flag, perform EXPUNGE">
|
||||||
|
<td>Old \DELETED</td>
|
||||||
|
<td><input type="checkbox" value="d" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
-->
|
||||||
|
<tr title="set or clear flags other than \SEEN and \DELETED via STORE, also set them during APPEND/COPY">
|
||||||
|
<td>Other flags</td>
|
||||||
|
<td><input type="checkbox" value="w" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr title="send mail to submission address for mailbox, not enforced by IMAP4 itself">
|
||||||
|
<td>Send mail</td>
|
||||||
|
<td><input type="checkbox" value="p" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr title="CREATE new sub-mailboxes in any implementation-defined hierarchy, parent mailbox for the new mailbox name in RENAME">
|
||||||
|
<td>Create sub-folders</td>
|
||||||
|
<td><input type="checkbox" value="k" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<!--
|
||||||
|
<tr title="CREATE new sub-mailboxes in any implementation-defined hierarchy">
|
||||||
|
<td>CREATE_OLD</td>
|
||||||
|
<td><input type="checkbox" value="c" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
-->
|
||||||
|
<tr title="DELETE mailbox, old mailbox name in RENAME">
|
||||||
|
<td>Delete/Rename folder</td>
|
||||||
|
<td><input type="checkbox" value="x" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr title="perform EXPUNGE and expunge as a part of CLOSE">
|
||||||
|
<td>Expunge</td>
|
||||||
|
<td><input type="checkbox" value="e" data-bind="checked: rights, disable: mine" /></td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<footer>
|
||||||
|
<button data-bind="visible: mine" form="folderaclform" class="btn" data-i18n="GLOBAL/CLOSE"></button>
|
||||||
|
<button data-bind="visible: !mine()" form="folderaclform" class="btn" data-i18n="GLOBAL/SAVE"></button>
|
||||||
|
</footer>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue