dolibarr 25.0.0-alpha
RightsSyncService.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2026 ATM Consulting <support@atm-consulting.fr>
3 * Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
25require_once DOL_DOCUMENT_ROOT . '/core/lib/functions.lib.php';
26require_once DOL_DOCUMENT_ROOT.'/modulebuilder/class/RightsSyncCommand.class.php';
27require_once DOL_DOCUMENT_ROOT.'/modulebuilder/class/SyncReport.class.php';
28require_once DOL_DOCUMENT_ROOT.'/modulebuilder/class/PermissionsBlock.class.php';
29
34{
41 public function sync(RightsSyncCommand $cmd): SyncReport;
42}
43
52{
54 private const CRUD_LABELS = array(
55 'read' => 'Read %s object of %s',
56 'write' => 'Create/Update %s object of %s',
57 'delete' => 'Delete %s object of %s',
58 );
59
61 private const INDEX_LABEL = 1;
62
64 private const INDEX_OBJECT = 4;
65
67 private const INDEX_CRUD = 5;
68
75 public function sync(RightsSyncCommand $cmd): SyncReport
76 {
77 try {
78 $block = PermissionsBlock::fromFile($cmd->descriptorFile);
79 } catch (\RuntimeException $e) {
80 dol_syslog('DescriptorRightsSyncService::sync '.$e->getMessage(), LOG_WARNING);
81 return new SyncReport(0, 0, array($e->getMessage()));
82 }
83
84 $conflicts = array_merge(
85 $block->detectTextConflicts(),
86 $block->detectRightsShapeConflicts($cmd->permissions)
87 );
88 if (!empty($conflicts)) {
90 'DescriptorRightsSyncService::sync refused to rewrite '.$cmd->descriptorFile.': '
91 .implode('; ', array_slice($conflicts, 0, 3)),
92 LOG_WARNING
93 );
94 return new SyncReport(0, 0, $conflicts);
95 }
96
97 $warnings = $block->detectRightsShapeWarnings($cmd->permissions);
98
99 try {
100 $permissions = $this->applyCommand($cmd);
101 } catch (\InvalidArgumentException $e) {
102 dol_syslog('DescriptorRightsSyncService::sync '.$e->getMessage(), LOG_WARNING);
103 return new SyncReport(0, 0, array($e->getMessage()), $warnings);
104 }
105 if ($permissions === null) {
106 return new SyncReport(0, 1, array(), $warnings);
107 }
108
109 // The shape check above ran on the incoming rights; the command may itself produce an
110 // unusable one, e.g. a right attached to no object, which hasRight() could never match.
111 $producedConflicts = $block->detectRightsShapeConflicts($permissions);
112 if (!empty($producedConflicts)) {
114 'DescriptorRightsSyncService::sync refused to write an unusable right into '.$cmd->descriptorFile.': '
115 .implode('; ', array_slice($producedConflicts, 0, 3)),
116 LOG_WARNING
117 );
118 return new SyncReport(0, 0, $producedConflicts, $warnings);
119 }
120
121 $newBlock = $block->render($permissions);
122 if ($block->write($newBlock) < 0) {
123 return new SyncReport(0, 0, array('Failed to write the permissions section of '.$cmd->descriptorFile), $warnings);
124 }
125
126 return new SyncReport(substr_count($newBlock, "\n"), 0, array(), $warnings);
127 }
128
136 private function applyCommand(RightsSyncCommand $cmd): ?array
137 {
138 $permissions = array_values($cmd->permissions);
139
140 if ($cmd->scope === RightsSyncCommand::SCOPE_OBJECT) {
141 if ($cmd->actionType === RightsSyncCommand::ACTION_ADD) {
142 return $this->addObjectRights($permissions, $cmd->module, $cmd->objectName);
143 }
144 return $this->removeObjectRights($permissions, $cmd->objectName);
145 }
146
147 if ($cmd->actionType === RightsSyncCommand::ACTION_ADD) {
148 return $this->addRight($permissions, $cmd->objectName, (string) $cmd->rightLabel, (string) $cmd->rightCrud);
149 }
150
151 $key = (int) $cmd->rightKey;
152 if (!array_key_exists($key, $permissions)) {
153 throw new \InvalidArgumentException('No permission found at index '.$key.' of the descriptor rights array');
154 }
155
156 if ($cmd->actionType === RightsSyncCommand::ACTION_UPDATE) {
157 // Addressed by key, never by value: two rights may carry the very same label.
158 $permissions[$key] = array(
159 self::INDEX_LABEL => (string) $cmd->rightLabel,
160 self::INDEX_OBJECT => dol_strtolower($cmd->objectName),
161 self::INDEX_CRUD => (string) $cmd->rightCrud,
162 );
163 return $permissions;
164 }
165
166 unset($permissions[$key]);
167 return array_values($permissions);
168 }
169
178 private function addObjectRights(array $permissions, string $module, string $objectName): ?array
179 {
180 $target = dol_strtolower($objectName);
181 foreach ($permissions as $right) {
182 if (isset($right[self::INDEX_OBJECT]) && dol_strtolower((string) $right[self::INDEX_OBJECT]) === $target) {
183 return null;
184 }
185 }
186
187 foreach (self::CRUD_LABELS as $crud => $template) {
188 $permissions[] = array(
189 self::INDEX_LABEL => sprintf($template, $objectName, dol_ucfirst($module)),
190 self::INDEX_OBJECT => $target,
191 self::INDEX_CRUD => $crud,
192 );
193 }
194
195 return $permissions;
196 }
197
207 private function removeObjectRights(array $permissions, string $objectName): array
208 {
209 $target = dol_strtolower($objectName);
210
211 return array_values(array_filter(
212 $permissions,
217 static function ($right) use ($target) {
218 return !isset($right[self::INDEX_OBJECT]) || dol_strtolower((string) $right[self::INDEX_OBJECT]) !== $target;
219 }
220 ));
221 }
222
233 private function addRight(array $permissions, string $objectName, string $label, string $crud): array
234 {
235 $target = dol_strtolower($objectName);
236 foreach ($permissions as $right) {
237 if (isset($right[self::INDEX_OBJECT], $right[self::INDEX_CRUD])
238 && dol_strtolower((string) $right[self::INDEX_OBJECT]) === $target
239 && (string) $right[self::INDEX_CRUD] === $crud) {
240 throw new \InvalidArgumentException('Permission "'.$crud.'" is already declared for object "'.$objectName.'"');
241 }
242 }
243
244 $permissions[] = array(
245 self::INDEX_LABEL => $label,
246 self::INDEX_OBJECT => $target,
247 self::INDEX_CRUD => $crud,
248 );
249
250 return $permissions;
251 }
252}
Syncs rights straight into the mod<Module>.class.php descriptor file.
sync(RightsSyncCommand $cmd)
Apply one sync command to a module descriptor.
addRight(array $permissions, string $objectName, string $label, string $crud)
Append one right, refusing a crud code the object already declares.
applyCommand(RightsSyncCommand $cmd)
Produce the rights array the descriptor should now declare.
removeObjectRights(array $permissions, string $objectName)
Drop every right attached to an object.
addObjectRights(array $permissions, string $module, string $objectName)
Append the three CRUD rights of a freshly generated object.
static fromFile(string $file)
Read a descriptor and locate its permissions block.
Immutable request describing one permissions sync to perform on a module descriptor.
Immutable outcome of a permissions sync run.
if(! $sortfield) if(! $sortorder) $module
Definition list.php:193
dol_ucfirst($string, $encoding="UTF-8")
Convert first character of the first word of a string to upper.
dol_strtolower($string, $encoding="UTF-8")
Convert a string to lower.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
Keeps the permissions section of a module descriptor in sync with ModuleBuilder actions.
sync(RightsSyncCommand $cmd)
Apply one sync command to a module descriptor.