ILIAS  release_7 Revision v7.30-3-g800a261c036
class.ilCtrlStructureReader.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2020 ILIAS open source, Extended GPL, see docs/LICENSE */
3
14{
15 public $executed;
16 public $db = null;
17 protected $read_plugins = false;
18
19 public function __construct($a_ini_file = null)
20 {
21 $this->class_script = array();
22 $this->class_childs = array();
23 $this->executed = false;
24 $this->ini = $a_ini_file;
25 }
26
27 public function setIniFile($a_ini_file)
28 {
29 $this->ini = $a_ini_file;
30 }
31
35 public function getStructure()
36 {
37 $ilDB = $this->getDB();
38
39 $this->ini->setVariable("db", "structure_reload", "1");
40 $this->ini->write();
41 if ($this->ini->readVariable("db", "structure_reload") != "1") {
42 echo "Error Cannot write client.ini.file.";
43 }
44 //$this->get_structure = true;
45 }
46
47
48 // ----------------------
49 // READING CTRL STRUCTURE
50 // ----------------------
51
52 public function readStructure(
53 $a_force = false,
54 $a_dir = "",
55 $a_comp_prefix = "",
56 $a_plugin_path = ""
57 ) : void {
58 $ilDB = $this->getDB();
59
60 if (!$a_force && $this->ini->readVariable("db", "structure_reload") != "1") {
61 return;
62 }
63
64 // only run one time per db_update request
65 if ($this->executed) {
66 return;
67 }
68
69 $this->flushCaches();
70
71 // prefix for component
72 $this->comp_prefix = $a_comp_prefix;
73
74 // plugin path
75 $this->plugin_path = $a_plugin_path;
76
77 if ($this->plugin_path != "" && $this->comp_prefix != "") {
78 $this->read_plugins = true;
79 }
80
81 if ($a_dir == "") {
82 $a_dir = $this->getILIASAbsolutePath();
83 }
84
85 $ctrl_structure = $this->readDirTo($a_dir, new \ilCtrlStructure());
86 $this->storeToDB($ctrl_structure, $a_dir);
87 $this->setClassFileIdsInDB();
88
89 $this->executed = true;
90 if (!$a_force) {
91 $this->ini->setVariable("db", "structure_reload", "0");
92 $this->ini->write();
93 }
94 }
95
96 protected function flushCaches() : void
97 {
99 ilGlobalCache::flushAll();
100 }
101
102 protected function readDirTo(string $a_cdir, \ilCtrlStructure $cs) : \ilCtrlStructure
103 {
104 // check wether $a_cdir is a directory
105 if (!@is_dir($a_cdir)) {
106 throw new \LogicException("'$a_cdir' is not a directory.");
107 }
108
109 foreach ($this->getFilesIn($a_cdir) as list($file, $full_path)) {
110 if (!$this->isInterestingFile($file)) {
111 continue;
112 }
113
114 $content = file_get_contents($full_path);
115 try {
116 $cs = $this->parseFileTo($cs, $full_path, $content);
117 } catch (\LogicException $e) {
118 throw new \LogicException("In file \"$full_path\": " . $e->getMessage(), $e->getCode(), $e);
119 } catch (\RuntimeException $e) {
120 if (!isset($e->class) || !isset($e->file_path)) {
121 throw $e;
122 }
123 $this->panicOnDuplicateClass(
124 $e->file_path,
125 $cs->getClassScriptOf($e->class),
126 $e->class
127 );
128 }
129 }
130
131 return $cs;
132 }
133
134
135 // ----------------------
136 // DIRECTORY TRAVERSAL
137 // ----------------------
138
139 protected function getFilesIn(string $dir) : \Generator
140 {
141 foreach (scandir($dir) as $e) {
142 if ($e == "." || $e == "..") {
143 continue;
144 }
145 $f = $this->normalizePath("$dir/$e");
146 if (@is_dir($f)) {
147 if (!$this->shouldDescendToDirectory($dir)) {
148 continue;
149 }
150 foreach ($this->getFilesIn($f) as $s) {
151 yield $s;
152 }
153 }
154 if (@is_file($f)) {
155 yield [$e, $f];
156 }
157 }
158 }
159
160 protected function shouldDescendToDirectory(string $dir) : bool
161 {
162 $il_absolute_path = $this->getILIASAbsolutePath();
163 $data_dir = $this->normalizePath($il_absolute_path . "/data");
164 $customizing_dir = $this->normalizePath($il_absolute_path . "/Customizing");
165
166 $dir = $this->normalizePath($dir);
167 if ($this->read_plugins) {
168 return $dir != $data_dir;
169 }
170 return $dir != $customizing_dir && $dir != $data_dir;
171 }
172
173 protected function normalizePath(string $path) : string
174 {
175 return realpath(str_replace(['//'], ['/'], $path));
176 }
177
178 const INTERESTING_FILES_REGEXP = "~^(class\..*\.php)$~i";
179
180 protected function isInterestingFile(string $file) : bool
181 {
182 return preg_match(self::INTERESTING_FILES_REGEXP, $file);
183 }
184
185
186 // ----------------------
187 // RESULT STORAGE
188 // ----------------------
189
190 protected function panicOnDuplicateClass(string $full_path, string $other_path, string $parent) : void
191 {
192 $ilDB = $this->getDB();
193
194 // delete all class to file assignments
195 $ilDB->manipulate("DELETE FROM ctrl_classfile WHERE comp_prefix = " .
196 $ilDB->quote($this->comp_prefix, "text"));
197 if ($this->comp_prefix == "") {
198 $ilDB->manipulate($q = "DELETE FROM ctrl_classfile WHERE " .
199 $ilDB->equals("comp_prefix", "", "text", true));
200 }
201
202 // delete all call entries
203 $ilDB->manipulate("DELETE FROM ctrl_calls WHERE comp_prefix = " .
204 $ilDB->quote($this->comp_prefix, "text"));
205 if ($this->comp_prefix == "") {
206 $ilDB->manipulate("DELETE FROM ctrl_calls WHERE comp_prefix IS NULL");
207 }
208
209 $msg = implode("\n", [
210 "Error: Duplicate call structure definition found (Class %s) in files:",
211 "- %s",
212 "- %s",
213 "",
214 "Please remove the file, that does not belong to the official ILIAS distribution.",
215 "After that invoke 'Tools' -> 'Reload Control Structure' in the ILIAS Setup."
216 ]);
217
218 throw new \Exception(
219 sprintf(
220 $msg,
221 $parent,
222 $other_path,
223 $full_path
224 )
225 );
226 }
227
228 protected function storeToDB(\ilCtrlStructure $ctrl_structure, string $start_dir) : void
229 {
230 $ilDB = $this->getDB();
231
232 // delete all class to file assignments
233 $ilDB->manipulate("DELETE FROM ctrl_classfile WHERE comp_prefix = " .
234 $ilDB->quote($this->comp_prefix, "text"));
235 if ($this->comp_prefix == "") {
236 $ilDB->manipulate($q = "DELETE FROM ctrl_classfile WHERE " .
237 $ilDB->equals("comp_prefix", "", "text", true));
238 }
239
240 // delete all call entries
241 $ilDB->manipulate("DELETE FROM ctrl_calls WHERE comp_prefix = " .
242 $ilDB->quote($this->comp_prefix, "text"));
243 if ($this->comp_prefix == "") {
244 $ilDB->manipulate("DELETE FROM ctrl_calls WHERE " .
245 $ilDB->equals("comp_prefix", "", "text", true));
246 }
247
248 foreach ($ctrl_structure->getClassScripts() as $class => $script) {
249 $file = substr(realpath($script), strlen(realpath($start_dir)) + 1);
250 // store class to file assignment
251 $ilDB->manipulate(sprintf(
252 "INSERT IGNORE INTO ctrl_classfile (class, filename, comp_prefix, plugin_path) " .
253 " VALUES (%s,%s,%s,%s)",
254 $ilDB->quote($class, "text"),
255 $ilDB->quote($file, "text"),
256 $ilDB->quote($this->comp_prefix, "text"),
257 $ilDB->quote($this->plugin_path, "text")
258 ));
259 }
260 //$this->class_childs[$parent][] = $child;
261 foreach ($ctrl_structure->getClassChildren() as $parent => $children) {
262 if (!strlen($parent)) {
263 continue;
264 }
265 foreach ($children as $child) {
266 if (!strlen(trim($child))) {
267 continue;
268 }
269 // store call entry
270 $ilDB->manipulate(sprintf(
271 "INSERT IGNORE INTO ctrl_calls (parent, child, comp_prefix) " .
272 "VALUES (%s,%s,%s)",
273 $ilDB->quote($parent, "text"),
274 $ilDB->quote($child, "text"),
275 $ilDB->quote($this->comp_prefix, "text")
276 ));
277 }
278 }
279 }
280
281 protected function setClassFileIdsInDB() : void
282 {
283 $ilDB = $this->getDB();
284
285 $ilDB->manipulate(
286 "UPDATE ctrl_classfile SET " .
287 " cid = " . $ilDB->quote("", "text")
288 );
289 $set = $ilDB->query("SELECT * FROM ctrl_classfile ");
290 $cnt = 1;
291 while ($rec = $ilDB->fetchAssoc($set)) {
292 $cid = base_convert((string) $cnt, 10, 36);
293 $ilDB->manipulate(
294 "UPDATE ctrl_classfile SET " .
295 " cid = " . $ilDB->quote($cid, "text") .
296 " WHERE class = " . $ilDB->quote($rec["class"], "text")
297 );
298 $cnt++;
299 }
300 }
301
302
303 // ----------------------
304 // PARSING
305 // ----------------------
306
311 protected function parseFileTo(\ilCtrlStructure $cs, string $full_path, string $content) : \ilCtrlStructure
312 {
313 list($parent, $children) = $this->getIlCtrlCalls($content);
314 if ($parent) {
315 $cs = $cs->withClassScript($parent, $full_path);
316 }
317 if ($children) {
318 foreach ($children as $child) {
319 $cs = $cs->withClassChild($parent, $child);
320 }
321 }
322
323 list($child, $parents) = $this->getIlCtrlIsCalledBy($content);
324 if ($child) {
325 $cs = $cs->withClassScript($child, $full_path);
326 }
327 if ($parents) {
328 foreach ($parents as $parent) {
329 $cs = $cs->withClassChild($parent, $child);
330 }
331 }
332
333 $cl = $this->getGUIClassNameFromClassPath($full_path);
334 if ($cl && $this->containsClassDefinitionFor($cl, $content)) {
335 $cs = $cs->withClassScript($cl, $full_path);
336 }
337
338 return $cs;
339 }
340
341 // ----------------------
342 // GUI CLASS FINDING
343 // ----------------------
344
345 const GUI_CLASS_FILE_REGEXP = "~^.*[/\\\\]class\.(.*GUI)\.php$~i";
346
347 protected function getGUIClassNameFromClassPath(string $path) : ?string
348 {
349 $res = [];
350 if (preg_match(self::GUI_CLASS_FILE_REGEXP, $path, $res)) {
351 return strtolower($res[1]);
352 }
353 return null;
354 }
355
356 protected function containsClassDefinitionFor(string $class, string $content) : bool
357 {
358 $regexp = "~.*class\s+$class~mi";
359 return preg_match($regexp, $content) != 0;
360 }
361
362
363 // ----------------------
364 // ILCTRL DECLARATION FINDING
365 // ----------------------
366
367 const IL_CTRL_DECLARATION_REGEXP = '~^.*@{WHICH}\s+([\w\\\\]+)\s*:\s*([\w\\\\]+(\s*,\s*[\w\\\\]+)*)\s*$~mi';
368
372 protected function getIlCtrlCalls(string $content) : ?array
373 {
374 return $this->getIlCtrlDeclarations($content, "ilctrl_calls");
375 }
376
380 protected function getIlCtrlIsCalledBy(string $content) : ?array
381 {
382 return $this->getIlCtrlDeclarations($content, "ilctrl_iscalledby");
383 }
384
388 protected function getIlCtrlDeclarations(string $content, string $which) : ?array
389 {
390 $regexp = str_replace("{WHICH}", $which, self::IL_CTRL_DECLARATION_REGEXP);
391 $res = [];
392 if (!preg_match_all($regexp, $content, $res)) {
393 return null;
394 }
395
396 $class_names = array_unique($res[1]);
397 if (count($class_names) != 1) {
398 throw new \LogicException(
399 "Found different class names in ilctrl_calls: " . join(",", $class_names)
400 );
401 }
402
403 $declaration = [];
404 foreach ($res[2] as $ls) {
405 foreach (explode(",", $ls) as $l) {
406 $declaration[] = strtolower(trim($l));
407 }
408 }
409
410 return [strtolower(trim($class_names[0])), $declaration];
411 }
412
413
414 // ----------------------
415 // DEPENDENCIES
416 // ----------------------
417
418 public function withDB(\ilDBInterface $db)
419 {
420 $clone = clone $this;
421 $clone->db = $db;
422 return $clone;
423 }
424
425 protected function getDB() : \ilDBInterface
426 {
427 if (!is_null($this->db)) {
428 return $this->db;
429 }
430 //return ilDB in any case - backward compat.
431 global $ilDB;
432 return $ilDB;
433 }
434
435 protected function getILIASAbsolutePath() : string
436 {
437 if (defined("ILIAS_ABSOLUTE_PATH")) {
438 return $this->normalizePath(ILIAS_ABSOLUTE_PATH);
439 } else {
440 return dirname(__FILE__, 5);
441 }
442 }
443}
An exception for terminatinating execution or to throw for unit testing.
Class ilCtrlStructureReader.
parseFileTo(\ilCtrlStructure $cs, string $full_path, string $content)
readDirTo(string $a_cdir, \ilCtrlStructure $cs)
panicOnDuplicateClass(string $full_path, string $other_path, string $parent)
readStructure( $a_force=false, $a_dir="", $a_comp_prefix="", $a_plugin_path="")
storeToDB(\ilCtrlStructure $ctrl_structure, string $start_dir)
containsClassDefinitionFor(string $class, string $content)
getIlCtrlDeclarations(string $content, string $which)
getStructure()
parse code files and store call structure in db
The CtrlStructure knows how GUIs call each other and where the code of the guis is located.
withClassChild(string $parent, string $child)
withClassScript(string $class, string $file_path)
getClassScriptOf(string $class)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
foreach($_POST as $key=> $value) $res
global $ilDB