32 const BEGIN_MARKER =
'/* BEGIN MODULEBUILDER PERMISSIONS */';
33 const END_MARKER =
'/* END MODULEBUILDER PERMISSIONS */';
41 private const ALLOWED_VARIABLES = array(
'$this',
'$r',
'$o');
50 private const ALLOWED_IDENTIFIERS = array(
'rights',
'numero',
'sprintf',
'null',
'true',
'false');
57 private const ALLOWED_PUNCTUATION = array(
'[',
']',
'(',
')',
'=',
';',
',',
'.',
'+',
'-',
'*');
60 private const CRUD_OFFSETS = array(
'read' => 0,
'write' => 1,
'delete' => 2);
63 private const OBJECT_ID_STRIDE = 10;
66 private const INDEX_ID = 0;
69 private const INDEX_LABEL = 1;
72 private const INDEX_OBJECT = 4;
75 private const INDEX_CRUD = 5;
78 private const SUPPORTED_INDEXES = array(0, 1, 4, 5);
90 private function __construct(
string $file,
string $innerBlock)
93 $this->innerBlock = $innerBlock;
103 public static function fromFile(
string $file): self
105 if (strpos($file,
'..') !==
false) {
106 throw new \RuntimeException(
'Descriptor path must not contain a parent directory reference: '.$file);
109 throw new \RuntimeException(
'Descriptor file not found: '.$file);
111 $content = file_get_contents($file);
112 if ($content ===
false) {
113 throw new \RuntimeException(
'Descriptor file is unreadable: '.$file);
116 $posBegin = strpos($content, self::BEGIN_MARKER);
117 $posEnd = strpos($content, self::END_MARKER);
118 if ($posBegin ===
false || $posEnd ===
false || $posEnd < $posBegin) {
119 throw new \RuntimeException(
'Cannot find the start and/or end comments of the permissions section in '.$file);
122 $start = $posBegin + strlen(self::BEGIN_MARKER);
123 $innerBlock = substr($content, $start, $posEnd - $start);
125 return new self($file, $innerBlock);
135 return $this->innerBlock;
150 $conflicts = array();
151 $tokens = token_get_all(
'<?php '.$this->innerBlock);
153 foreach ($tokens as $token) {
154 if (is_string($token)) {
155 if (!in_array($token, self::ALLOWED_PUNCTUATION,
true)) {
156 $conflicts[] =
'unexpected "'.$token.
'" in the permissions block';
161 list(
$id, $text, $line) = $token;
163 if (in_array(
$id, array(T_OPEN_TAG, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_INLINE_HTML),
true)) {
166 if (in_array(
$id, array(T_LNUMBER, T_DNUMBER, T_CONSTANT_ENCAPSED_STRING, T_OBJECT_OPERATOR, T_INC),
true)) {
169 if (
$id === T_VARIABLE) {
170 if (!in_array($text, self::ALLOWED_VARIABLES,
true)) {
171 $conflicts[] =
'line '.$line.
': unexpected variable '.$text.
' — the permissions block builds its rights dynamically and cannot be rewritten safely';
175 if (
$id === T_STRING) {
176 if (!in_array($text, self::ALLOWED_IDENTIFIERS,
true)) {
177 $conflicts[] =
'line '.$line.
': unexpected identifier "'.$text.
'" in the permissions block';
182 $conflicts[] =
'line '.$line.
': unexpected "'.trim($text).
'" in the permissions block — the section cannot be rewritten safely';
185 return array_values(array_unique($conflicts));
196 $conflicts = array();
197 foreach ($permissions as $i => $right) {
198 if (!is_array($right)) {
199 $conflicts[] =
'right #'.$i.
' is not an array';
202 if (!isset($right[self::INDEX_OBJECT]) || (
string) $right[self::INDEX_OBJECT] ===
'') {
203 $conflicts[] =
'right #'.$i.
' declares no object name at index '.self::INDEX_OBJECT;
205 if (!isset($right[self::INDEX_CRUD]) || (
string) $right[self::INDEX_CRUD] ===
'') {
206 $conflicts[] =
'right #'.$i.
' declares no crud code at index '.self::INDEX_CRUD;
225 foreach ($permissions as $i => $right) {
226 if (!is_array($right)) {
229 $unsupported = array_diff(array_keys($right), self::SUPPORTED_INDEXES);
230 if (!empty($unsupported)) {
231 $warnings[] =
'right #'.$i.
' carries unsupported index '.implode(
', ', $unsupported).
', dropped on rewrite';
251 foreach ($permissions as $right) {
252 if (!is_array($right) || !isset($right[self::INDEX_OBJECT], $right[self::INDEX_CRUD])) {
255 $grouped[(
string) $right[self::INDEX_OBJECT]][] = $right;
260 foreach ($grouped as $group) {
262 $right = $entry[
'right'];
263 $id =
"\$this->numero . sprintf('%02d', (".$objectIndex.
" * ".self::OBJECT_ID_STRIDE.
") + ".$entry[
'offset'].
" + 1)";
265 $lines[] =
"\t\t\$this->rights[\$r][".self::INDEX_ID.
"] = ".
$id.
";";
266 $lines[] =
"\t\t\$this->rights[\$r][".self::INDEX_LABEL.
"] = '".$this->
escapeForPhpSingleQuotedString((
string) ($right[self::INDEX_LABEL] ??
'')).
"';";
269 $lines[] =
"\t\t\$r++;";
274 return empty($lines) ?
'' : implode(
"\n", $lines).
"\n";
290 $usedOffsets = array();
291 $next = count(self::CRUD_OFFSETS);
293 foreach ($group as $right) {
294 $crud = (
string) $right[self::INDEX_CRUD];
295 if (isset(self::CRUD_OFFSETS[$crud]) && !isset($usedOffsets[self::CRUD_OFFSETS[$crud]])) {
296 $offset = self::CRUD_OFFSETS[$crud];
298 while (isset($usedOffsets[$next])) {
303 $usedOffsets[$offset] =
true;
304 $assigned[] = array(
'offset' => $offset,
'right' => $right);
314 static function (array $a, array $b):
int {
315 return $a[
'offset'] <=> $b[
'offset'];
333 return str_replace(array(
'\\',
"'"), array(
'\\\\',
"\\'"), $value);
351 public function write(
string $newInnerBlock): int
353 $pattern =
'/'.preg_quote(self::BEGIN_MARKER,
'/').
'.*?'.preg_quote(self::END_MARKER,
'/').
'/s';
354 $replacement = self::BEGIN_MARKER.
"\n".$newInnerBlock.
"\t\t".self::END_MARKER;
358 $replacement = str_replace(array(
'\\',
'$'), array(
'\\\\',
'\\$'), $replacement);
360 $result =
dolReplaceInFile($this->file, array($pattern => $replacement),
'',
'0', 0, 1);
362 dol_syslog(
'PermissionsBlock::write failed on '.$this->file.
' with code '.$result, LOG_ERR);
363 return $result < 0 ? $result : -1;
366 $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_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string