24require_once DOL_DOCUMENT_ROOT .
'/core/lib/functions.lib.php';
34 const BEGIN_MARKER =
'/* BEGIN MODULEBUILDER PERMISSIONS */';
35 const END_MARKER =
'/* END MODULEBUILDER PERMISSIONS */';
43 private const ALLOWED_VARIABLES = array(
'$this',
'$r',
'$o');
52 private const ALLOWED_IDENTIFIERS = array(
'rights',
'numero',
'sprintf',
'null',
'true',
'false');
59 private const ALLOWED_PUNCTUATION = array(
'[',
']',
'(',
')',
'=',
';',
',',
'.',
'+',
'-',
'*');
62 private const CRUD_OFFSETS = array(
'read' => 0,
'write' => 1,
'delete' => 2);
65 private const OBJECT_ID_STRIDE = 10;
68 private const INDEX_ID = 0;
71 private const INDEX_LABEL = 1;
74 private const INDEX_OBJECT = 4;
77 private const INDEX_CRUD = 5;
80 private const SUPPORTED_INDEXES = array(0, 1, 4, 5);
92 private function __construct(
string $file,
string $innerBlock)
95 $this->innerBlock = $innerBlock;
105 public static function fromFile(
string $file): self
107 if (strpos($file,
'..') !==
false) {
108 throw new \RuntimeException(
'Descriptor path must not contain a parent directory reference: '.$file);
111 throw new \RuntimeException(
'Descriptor file not found: '.$file);
113 $content = file_get_contents($file);
114 if ($content ===
false) {
115 throw new \RuntimeException(
'Descriptor file is unreadable: '.$file);
118 $posBegin = strpos($content, self::BEGIN_MARKER);
119 $posEnd = strpos($content, self::END_MARKER);
120 if ($posBegin ===
false || $posEnd ===
false || $posEnd < $posBegin) {
121 throw new \RuntimeException(
'Cannot find the start and/or end comments of the permissions section in '.$file);
124 $start = $posBegin +
dol_strlen(self::BEGIN_MARKER);
125 $innerBlock =
dol_substr($content, $start, $posEnd - $start);
127 return new self($file, $innerBlock);
137 return $this->innerBlock;
152 $conflicts = array();
153 $tokens = token_get_all(
'<?php '.$this->innerBlock);
155 foreach ($tokens as $token) {
156 if (is_string($token)) {
157 if (!in_array($token, self::ALLOWED_PUNCTUATION,
true)) {
158 $conflicts[] =
'unexpected "'.$token.
'" in the permissions block';
163 list(
$id, $text, $line) = $token;
165 if (in_array(
$id, array(T_OPEN_TAG, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_INLINE_HTML),
true)) {
168 if (in_array(
$id, array(T_LNUMBER, T_DNUMBER, T_CONSTANT_ENCAPSED_STRING, T_OBJECT_OPERATOR, T_INC),
true)) {
171 if (
$id === T_VARIABLE) {
172 if (!in_array($text, self::ALLOWED_VARIABLES,
true)) {
173 $conflicts[] =
'line '.$line.
': unexpected variable '.$text.
' — the permissions block builds its rights dynamically and cannot be rewritten safely';
177 if (
$id === T_STRING) {
178 if (!in_array($text, self::ALLOWED_IDENTIFIERS,
true)) {
179 $conflicts[] =
'line '.$line.
': unexpected identifier "'.$text.
'" in the permissions block';
184 $conflicts[] =
'line '.$line.
': unexpected "'.trim($text).
'" in the permissions block — the section cannot be rewritten safely';
187 return array_values(array_unique($conflicts));
198 $conflicts = array();
199 foreach ($permissions as $i => $right) {
200 if (!is_array($right)) {
201 $conflicts[] =
'right #'.$i.
' is not an array';
204 if (!isset($right[self::INDEX_OBJECT]) || (
string) $right[self::INDEX_OBJECT] ===
'') {
205 $conflicts[] =
'right #'.$i.
' declares no object name at index '.self::INDEX_OBJECT;
207 if (!isset($right[self::INDEX_CRUD]) || (
string) $right[self::INDEX_CRUD] ===
'') {
208 $conflicts[] =
'right #'.$i.
' declares no crud code at index '.self::INDEX_CRUD;
227 foreach ($permissions as $i => $right) {
228 if (!is_array($right)) {
231 $unsupported = array_diff(array_keys($right), self::SUPPORTED_INDEXES);
232 if (!empty($unsupported)) {
233 $warnings[] =
'right #'.$i.
' carries unsupported index '.implode(
', ', $unsupported).
', dropped on rewrite';
250 public function render(array $permissions): string
253 foreach ($permissions as $right) {
254 if (!is_array($right) || !isset($right[self::INDEX_OBJECT], $right[self::INDEX_CRUD])) {
257 $grouped[(string) $right[self::INDEX_OBJECT]][] = $right;
262 foreach ($grouped as $group) {
264 $right = $entry[
'right'];
265 $id =
"\$this->numero . sprintf('%02d', (".$objectIndex.
" * ".self::OBJECT_ID_STRIDE.
") + ".$entry[
'offset'].
" + 1)";
267 $lines[] =
"\t\t\$this->rights[\$r][".self::INDEX_ID.
"] = ".
$id.
";";
268 $lines[] =
"\t\t\$this->rights[\$r][".self::INDEX_LABEL.
"] = '".$this->
escapeForPhpSingleQuotedString((
string) ($right[self::INDEX_LABEL] ??
'')).
"';";
271 $lines[] =
"\t\t\$r++;";
276 return empty($lines) ?
'' : implode(
"\n", $lines).
"\n";
292 $usedOffsets = array();
293 $next = count(self::CRUD_OFFSETS);
295 foreach ($group as $right) {
296 $crud = (string) $right[self::INDEX_CRUD];
297 if (isset(self::CRUD_OFFSETS[$crud]) && !isset($usedOffsets[self::CRUD_OFFSETS[$crud]])) {
298 $offset = self::CRUD_OFFSETS[$crud];
300 while (isset($usedOffsets[$next])) {
305 $usedOffsets[$offset] =
true;
306 $assigned[] = array(
'offset' => $offset,
'right' => $right);
316 static function (array $a, array $b):
int {
317 return $a[
'offset'] <=> $b[
'offset'];
335 return str_replace(array(
'\\',
"'"), array(
'\\\\',
"\\'"), $value);
353 public function write(
string $newInnerBlock): int
355 $pattern =
'/'.preg_quote(self::BEGIN_MARKER,
'/').
'.*?'.preg_quote(self::END_MARKER,
'/').
'/s';
356 $replacement = self::BEGIN_MARKER.
"\n".$newInnerBlock.
"\t\t".self::END_MARKER;
360 $replacement = str_replace(array(
'\\',
'$'), array(
'\\\\',
'\\$'), $replacement);
362 $result =
dolReplaceInFile($this->file, array($pattern => $replacement),
'',
'0', 0, 1);
364 dol_syslog(
'PermissionsBlock::write failed on '.$this->file.
' with code '.$result, LOG_ERR);
365 return $result < 0 ? $result : -1;
368 $this->innerBlock = $newInnerBlock;
write(string $newInnerBlock)
Replace the whole permissions block, markers included, in a single write.
detectRightsShapeWarnings(array $permissions)
List the rights carrying indexes the renderer will drop.
assignOffsets(array $group)
Give each right of one object its numbering offset.
render(array $permissions)
Render a rights array as the new content of the permissions block.
escapeForPhpSingleQuotedString(string $value)
Escape a value for inclusion in a single-quoted PHP string literal.
$id
Support class for third parties, contacts, members, users or resources.
Text engine for the BEGIN/END MODULEBUILDER PERMISSIONS section of a module descriptor.
__construct(string $file, string $innerBlock)
detectRightsShapeConflicts(array $permissions)
List the rights that cannot be rendered at all.
static fromFile(string $file)
Read a descriptor and locate its permissions block.
detectTextConflicts()
List the reasons why rewriting this block would destroy something.
getInnerBlock()
Raw content between the markers, markers excluded.
dol_is_file($pathoffile)
Return if path is a file.
dolReplaceInFile($srcfile, $arrayreplacement, $destfile='', $newmask='0', $indexdatabase=0, $arrayreplacementisregex=0)
Make replacement of strings into a file.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.