ILIAS  release_8 Revision v8.23
class.ilFileXMLParser.php
Go to the documentation of this file.
1 <?php
2 
31 
33 {
34  public static int $CONTENT_NOT_COMPRESSED = 0;
35  public static int $CONTENT_GZ_COMPRESSED = 1;
36  public static int $CONTENT_ZLIB_COMPRESSED = 2;
37  public static int $CONTENT_COPY = 4;
38  // begin-patch fm
39  public static int $CONTENT_REST = 5;
40  // end-patch fm
44  public \ilObjFile $file;
49  public int $obj_id;
53  public bool $result;
57  public int $mode;
61  public ?string $tmpFilename = null;
62 
63  protected ?int $version = null;
64  protected ?string $action = null;
65  protected ?int $max_version = null;
66  protected ?int $date = null;
67  protected ?int $usr_id = null;
68  protected array $versions = [];
69  protected ?string $import_directory = null;
70  protected ?string $cdata = null;
71 
75  public function __construct(ilObjFile $file, string $a_xml_data, int $obj_id = -1, int $mode = 0)
76  {
78  $this->file = $file;
79  $this->setXMLContent($a_xml_data);
80  $this->obj_id = $obj_id;
81  $this->result = false;
82  $this->mode = $mode;
83  }
84 
90  public function setImportDirectory(?string $a_val): void
91  {
92  $this->import_directory = $a_val;
93  }
94 
100  public function getImportDirectory(): ?string
101  {
103  }
104 
112  public function setHandlers($a_xml_parser): void
113  {
114  xml_set_object($a_xml_parser, $this);
115  xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag');
116  xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData');
117  }
118 
129  public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs): void
130  {
131  global $DIC;
132 
133  global $DIC;
134 
135  switch ($a_name) {
136  case 'File':
137  if (isset($a_attribs["obj_id"])) {
138  $read_obj_id = ilUtil::__extractId($a_attribs["obj_id"], IL_INST_ID);
139  if ($this->obj_id != -1 && (int) $read_obj_id != -1 && $this->obj_id != (int) $read_obj_id) {
140  throw new ilFileException(
141  "Object IDs (xml $read_obj_id and argument " . $this->obj_id . ") do not match!",
143  );
144  }
145  }
146 
147  break;
148  case 'Content': // Old import files
149  case 'Version':
150  if ($a_name === "Version" && !isset($a_attribs["mode"])) {
151  // Old import files
152  $this->version = null;
153  if ($this->date === null) {
154  // Version tag comes after Content tag. Take only first (= Should be latest)
155  $this->date = $a_attribs["date"];
156  $this->usr_id = (int) $a_attribs["usr_id"];
157  $this->versions[0]["date"] = $this->date;
158  $this->versions[0]["usr_id"] = $this->usr_id;
159  }
160  break;
161  }
162 
164  #echo $a_attribs["mode"];
165  if (isset($a_attribs["mode"])) {
166  if ($a_attribs["mode"] == "GZIP") {
167  if (!function_exists("gzread")) {
168  throw new ilFileException(
169  "Deflating with gzip is not supported",
171  );
172  }
173 
175  } elseif ($a_attribs["mode"] == "ZLIB") {
176  if (!function_exists("gzuncompress")) {
177  throw new ilFileException(
178  "Deflating with zlib (compress/uncompress) is not supported",
180  );
181  }
182 
184  } elseif ($a_attribs["mode"] == "COPY") {
185  $this->mode = ilFileXMLParser::$CONTENT_COPY;
186  } // begin-patch fm
187  elseif ($a_attribs['mode'] == 'REST') {
188  $this->mode = ilFileXMLParser::$CONTENT_REST;
189  }
190  // end-patch fm
191  }
192 
193  if ($a_name === "Version") {
194  $this->version = (int) $a_attribs["version"];
195  $this->max_version = (int) $a_attribs["max_version"];
196  $this->date = (int) $a_attribs["date"];
197  $this->usr_id = (int) $a_attribs["usr_id"];
198  $this->action = (string) $a_attribs["action"];
199  }
200  }
201  }
202 
209  public function handlerEndTag($a_xml_parser, string $a_name): void
210  {
211  $this->cdata = trim($this->cdata);
212 
213  $GLOBALS['DIC']['ilLog']->write(__METHOD__ . ': ' . $this->cdata);
214 
215  switch ($a_name) {
216  case 'File':
217  $this->result = true;
218  break;
219  case 'Filename':
220  if ($this->cdata === '') {
221  throw new ilFileException("Filename ist missing!");
222  }
223 
224  $this->file->setFilename($this->cdata);
225  $this->file->setTitle($this->cdata);
226 
227  break;
228  case 'Title':
229  $this->file->setTitle(trim($this->cdata));
230  break;
231  case 'Description':
232  $this->file->setDescription(trim($this->cdata));
233  break;
234  case 'Rating':
235  $this->file->setRating((bool) $this->cdata);
236  break;
237  case 'Content': // Old import files
238  case 'Version':
239  if ($a_name === "Version" && $this->version === null) {
240  // Old import files
241  break;
242  }
243 
244  $baseDecodedFilename = ilFileUtils::ilTempnam();
245  if ($this->mode == ilFileXMLParser::$CONTENT_COPY) {
246  $this->tmpFilename = $this->getImportDirectory() . "/" . self::normalizeRelativePath($this->cdata);
247  } // begin-patch fm
248  elseif ($this->mode == ilFileXMLParser::$CONTENT_REST) {
249  $storage = new ilRestFileStorage();
250  $this->tmpFilename = $storage->getStoredFilePath(self::normalizeRelativePath($this->cdata));
251  if (!$this->fastBase64Decode($this->tmpFilename, $baseDecodedFilename)) {
252  throw new ilFileException("Base64-Decoding failed", ilFileException::$DECOMPRESSION_FAILED);
253  }
254  $this->tmpFilename = $baseDecodedFilename;
255  } // end-patch fm
256  else {
257  if (!$this->fastBase64Decode($this->tmpFilename, $baseDecodedFilename)) {
258  throw new ilFileException("Base64-Decoding failed", ilFileException::$DECOMPRESSION_FAILED);
259  }
260  if ($this->mode == ilFileXMLParser::$CONTENT_GZ_COMPRESSED) {
261  if (!$this->fastGunzip($baseDecodedFilename, $this->tmpFilename)) {
262  throw new ilFileException(
263  "Deflating with fastzunzip failed",
265  );
266  }
267  unlink($baseDecodedFilename);
268  } elseif ($this->mode == ilFileXMLParser::$CONTENT_ZLIB_COMPRESSED) {
269  if (!$this->fastGunzip($baseDecodedFilename, $this->tmpFilename)) {
270  throw new ilFileException(
271  "Deflating with fastDecompress failed",
273  );
274  }
275  unlink($baseDecodedFilename);
276  } else {
277  $this->tmpFilename = $baseDecodedFilename;
278  }
279  }
280 
281  //$this->content = $content;
282  // see #17211
283 
284  if ($this->version == $this->file->getVersion()) {
285  if (is_file($this->tmpFilename)) {
286  $this->file->setFileSize(filesize($this->tmpFilename)); // strlen($this->content));
287  }
288 
289  // if no file type is given => lookup mime type
290  if (!$this->file->getFileType()) {
291  global $DIC;
292  $this->file->setFileType(MimeType::getMimeType($this->tmpFilename));
293  }
294  }
295 
296  $this->versions[] = [
297  "version" => $this->version,
298  "max_version" => $this->max_version,
299  "tmpFilename" => $this->tmpFilename,
300  "date" => $this->date,
301  "usr_id" => $this->usr_id,
302  "action" => $this->action,
303  ];
304  $this->version = null;
305  $this->date = null;
306  $this->usr_id = null;
307  break;
308  }
309 
310  $this->cdata = '';
311  }
312 
319  public function handlerCharacterData($a_xml_parser, string $a_data): void
320  {
321  if ($a_data != "\n") {
322  // begin-patch fm
323  if ($this->mode != ilFileXMLParser::$CONTENT_COPY
324  && $this->mode != ilFileXMLParser::$CONTENT_REST
325  ) { // begin-patch fm
326  $this->cdata .= $a_data;
327  } else {
328  $this->cdata .= $a_data;
329  }
330  }
331  }
332 
338  public function setFileContents(): void
339  {
340  // Delete exists version 1 history
341  ilHistory::_removeEntriesForObject($this->file->getId());
342 
343  foreach ($this->versions as $version) {
344  if (!file_exists($version["tmpFilename"])) {
345  if (!isset($version["tmpFilename"])) {
346  continue;
347  }
348  // try to get first file of directory
349  $files = scandir(dirname($version["tmpFilename"]));
350  $version["tmpFilename"] = rtrim(
351  dirname($version["tmpFilename"]),
352  "/"
353  ) . "/" . $files[2];// because [0] = "." [1] = ".."
354  if (!file_exists($version["tmpFilename"])) {
355  ilLoggerFactory::getLogger('file')->error(__METHOD__ . ' "' . ($version["tmpFilename"]) . '" file not found.');
356 
357  continue;
358  }
359  }
360 
361  if (filesize($version["tmpFilename"]) == 0) {
362  continue;
363  }
364 
365  // imported file version
366  $import_file_version_path = $version["tmpFilename"];
367 
368  $stream = Streams::ofResource(fopen($import_file_version_path, 'rb'));
369  $this->file->appendStream($stream, $this->file->getTitle());
370  }
371  }
372 
378  public function updateFileContents(): void
379  {
380  // removed
381  }
382 
390  public function start(): bool
391  {
392  $this->startParsing();
393 
394  return $this->result > 0;
395  }
396 
407  public static function normalizeRelativePath(string $path): string
408  {
409  $path = str_replace('\\', '/', $path);
410 
411  while (preg_match('#\p{C}+|^\./#u', $path)) {
412  $path = preg_replace('#\p{C}+|^\./#u', '', $path);
413  }
414 
415  $parts = [];
416  foreach (explode('/', $path) as $part) {
417  switch ($part) {
418  case '':
419  case '.':
420  break;
421  case '..':
422  array_pop($parts);
423  break;
424  default:
425  $parts[] = $part;
426  break;
427  }
428  }
429 
430  return implode('/', $parts);
431  }
432 
433  private function fastBase64Decode(string $filein, string $fileout): bool
434  {
435  $fh = fopen($filein, 'rb');
436  $fh2 = fopen($fileout, 'wb');
437  stream_filter_append($fh2, 'convert.base64-decode');
438 
439  while (!feof($fh)) {
440  $chunk = fgets($fh);
441  if ($chunk === false) {
442  break;
443  }
444  fwrite($fh2, $chunk);
445  }
446  fclose($fh);
447  fclose($fh2);
448 
449  return true;
450  }
451 
452  private function fastGunzip(string $in, string $out): bool
453  {
454  if (!file_exists($in) || !is_readable($in)) {
455  return false;
456  }
457  if ((!file_exists($out) && !is_writable(dirname($out)) || (file_exists($out) && !is_writable($out)))) {
458  return false;
459  }
460 
461  $in_file = gzopen($in, "rb");
462  $out_file = fopen($out, "wb");
463 
464  while (!gzeof($in_file)) {
465  $buffer = gzread($in_file, 4096);
466  fwrite($out_file, $buffer, 4096);
467  }
468 
469  gzclose($in_file);
470  fclose($out_file);
471 
472  return true;
473  }
474 }
fastGunzip(string $in, string $out)
handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs)
handler for begin of element
int $mode
Content compression mode, defaults to no compression.
const IL_INST_ID
Definition: constants.php:40
static getLogger(string $a_component_id)
Get component logger.
startParsing()
stores xml data in array
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
fastBase64Decode(string $filein, string $fileout)
$path
Definition: ltiservices.php:32
handlerEndTag($a_xml_parser, string $a_name)
handler for end of element
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
start()
starts parsing an changes object by side effect.
global $DIC
Definition: feed.php:28
static int $DECOMPRESSION_FAILED
static _removeEntriesForObject(int $a_obj_id)
remove all history entries for an object
string $tmpFilename
file of temporary file where we store the file content instead of in memory
__construct(ilObjFile $file, string $a_xml_data, int $obj_id=-1, int $mode=0)
Constructor.
static int $CONTENT_GZ_COMPRESSED
Class ilObjFile.
$out
Definition: buildRTE.php:24
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
handlerCharacterData($a_xml_parser, string $a_data)
handler for character data
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static int $CONTENT_NOT_COMPRESSED
static normalizeRelativePath(string $path)
Normalize relative directories in a path.
static int $CONTENT_ZLIB_COMPRESSED
setHandlers($a_xml_parser)
set event handlers
static ilTempnam(?string $a_temp_path=null)
Returns a unique and non existing Path for e temporary file or directory.
setImportDirectory(?string $a_val)
Set import directory.
File storage handling.
updateFileContents()
update file according to filename and version and create history entry has to be called after (!) fil...
static int $ID_DEFLATE_METHOD_MISMATCH
static __extractId(string $ilias_id, int $inst_id)
extract ref id from role title, e.g.
__construct(Container $dic, ilPlugin $plugin)
ilObjFile $file
Exercise object which has been parsed.
int $obj_id
this will be matched against the id in the xml in case we want to update an exercise ...
getImportDirectory()
Get import directory.
bool $result
result of parsing and updating
setXMLContent(string $a_xml_content)
setFileContents()
update file according to filename and version, does not update history has to be called after (!) fil...