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 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
24require_once DOL_DOCUMENT_ROOT.'/modulebuilder/class/RightsSyncCommand.class.php';
25require_once DOL_DOCUMENT_ROOT.'/modulebuilder/class/SyncReport.class.php';
26require_once DOL_DOCUMENT_ROOT.'/modulebuilder/class/PermissionsBlock.class.php';
27
32{
39 public function sync(RightsSyncCommand $cmd): SyncReport;
40}
41
50{
52 private const CRUD_LABELS = array(
53 'read' => 'Read %s object of %s',
54 'write' => 'Create/Update %s object of %s',
55 'delete' => 'Delete %s object of %s',
56 );
57
59 private const INDEX_LABEL = 1;
60
62 private const INDEX_OBJECT = 4;
63
65 private const INDEX_CRUD = 5;
66
73 public function sync(RightsSyncCommand $cmd): SyncReport
74 {
75 try {
76 $block = PermissionsBlock::fromFile($cmd->descriptorFile);
77 } catch (\RuntimeException $e) {
78 dol_syslog('DescriptorRightsSyncService::sync '.$e->getMessage(), LOG_WARNING);
79 return new SyncReport(0, 0, array($e->getMessage()));
80 }
81
82 $conflicts = array_merge(
83 $block->detectTextConflicts(),
84 $block->detectRightsShapeConflicts($cmd->permissions)
85 );
86 if (!empty($conflicts)) {
88 'DescriptorRightsSyncService::sync refused to rewrite '.$cmd->descriptorFile.': '
89 .implode('; ', array_slice($conflicts, 0, 3)),
90 LOG_WARNING
91 );
92 return new SyncReport(0, 0, $conflicts);
93 }
94
95 $warnings = $block->detectRightsShapeWarnings($cmd->permissions);
96
97 try {
98 $permissions = $this->applyCommand($cmd);
99 } catch (\InvalidArgumentException $e) {
100 dol_syslog('DescriptorRightsSyncService::sync '.$e->getMessage(), LOG_WARNING);
101 return new SyncReport(0, 0, array($e->getMessage()), $warnings);
102 }
103 if ($permissions === null) {
104 return new SyncReport(0, 1, array(), $warnings);
105 }
106
107 // The shape check above ran on the incoming rights; the command may itself produce an
108 // unusable one, e.g. a right attached to no object, which hasRight() could never match.
109 $producedConflicts = $block->detectRightsShapeConflicts($permissions);
110 if (!empty($producedConflicts)) {
112 'DescriptorRightsSyncService::sync refused to write an unusable right into '.$cmd->descriptorFile.': '
113 .implode('; ', array_slice($producedConflicts, 0, 3)),
114 LOG_WARNING
115 );
116 return new SyncReport(0, 0, $producedConflicts, $warnings);
117 }
118
119 $newBlock = $block->render($permissions);
120 if ($block->write($newBlock) < 0) {
121 return new SyncReport(0, 0, array('Failed to write the permissions section of '.$cmd->descriptorFile), $warnings);
122 }
123
124 return new SyncReport(substr_count($newBlock, "\n"), 0, array(), $warnings);
125 }
126
134 private function applyCommand(RightsSyncCommand $cmd): ?array
135 {
136 $permissions = array_values($cmd->permissions);
137
138 if ($cmd->scope === RightsSyncCommand::SCOPE_OBJECT) {
139 if ($cmd->actionType === RightsSyncCommand::ACTION_ADD) {
140 return $this->addObjectRights($permissions, $cmd->module, $cmd->objectName);
141 }
142 return $this->removeObjectRights($permissions, $cmd->objectName);
143 }
144
145 if ($cmd->actionType === RightsSyncCommand::ACTION_ADD) {
146 return $this->addRight($permissions, $cmd->objectName, (string) $cmd->rightLabel, (string) $cmd->rightCrud);
147 }
148
149 $key = (int) $cmd->rightKey;
150 if (!array_key_exists($key, $permissions)) {
151 throw new \InvalidArgumentException('No permission found at index '.$key.' of the descriptor rights array');
152 }
153
154 if ($cmd->actionType === RightsSyncCommand::ACTION_UPDATE) {
155 // Addressed by key, never by value: two rights may carry the very same label.
156 $permissions[$key] = array(
157 self::INDEX_LABEL => (string) $cmd->rightLabel,
158 self::INDEX_OBJECT => strtolower($cmd->objectName),
159 self::INDEX_CRUD => (string) $cmd->rightCrud,
160 );
161 return $permissions;
162 }
163
164 unset($permissions[$key]);
165 return array_values($permissions);
166 }
167
176 private function addObjectRights(array $permissions, string $module, string $objectName): ?array
177 {
178 $target = strtolower($objectName);
179 foreach ($permissions as $right) {
180 if (isset($right[self::INDEX_OBJECT]) && strtolower((string) $right[self::INDEX_OBJECT]) === $target) {
181 return null;
182 }
183 }
184
185 foreach (self::CRUD_LABELS as $crud => $template) {
186 $permissions[] = array(
187 self::INDEX_LABEL => sprintf($template, $objectName, ucfirst($module)),
188 self::INDEX_OBJECT => $target,
189 self::INDEX_CRUD => $crud,
190 );
191 }
192
193 return $permissions;
194 }
195
205 private function removeObjectRights(array $permissions, string $objectName): array
206 {
207 $target = strtolower($objectName);
208
209 return array_values(array_filter(
210 $permissions,
215 static function ($right) use ($target) {
216 return !isset($right[self::INDEX_OBJECT]) || strtolower((string) $right[self::INDEX_OBJECT]) !== $target;
217 }
218 ));
219 }
220
231 private function addRight(array $permissions, string $objectName, string $label, string $crud): array
232 {
233 $target = strtolower($objectName);
234 foreach ($permissions as $right) {
235 if (isset($right[self::INDEX_OBJECT], $right[self::INDEX_CRUD])
236 && strtolower((string) $right[self::INDEX_OBJECT]) === $target
237 && (string) $right[self::INDEX_CRUD] === $crud) {
238 throw new \InvalidArgumentException('Permission "'.$crud.'" is already declared for object "'.$objectName.'"');
239 }
240 }
241
242 $permissions[] = array(
243 self::INDEX_LABEL => $label,
244 self::INDEX_OBJECT => $target,
245 self::INDEX_CRUD => $crud,
246 );
247
248 return $permissions;
249 }
250}
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.
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.