ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
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;
43  public bool $result = false;
47  public ?string $tmpFilename = null;
48 
49  protected ?int $version = null;
50  protected ?string $action = null;
51  protected ?int $max_version = null;
52  protected ?int $date = null;
53  protected ?int $usr_id = null;
54  protected array $versions = [];
55  protected ?string $import_directory = null;
56  protected ?string $cdata = null;
57 
61  public function __construct(// end-patch fm
65  public ilObjFile $file,
66  string $a_xml_data,
71  public int $obj_id = -1,
75  public int $mode = 0
76  ) {
78  $this->setXMLContent($a_xml_data);
79  }
80 
86  public function setImportDirectory(?string $a_val): void
87  {
88  $this->import_directory = $a_val;
89  }
90 
96  public function getImportDirectory(): ?string
97  {
99  }
100 
108  public function setHandlers($a_xml_parser): void
109  {
110  xml_set_object($a_xml_parser, $this);
111  xml_set_element_handler($a_xml_parser, 'handlerBeginTag', 'handlerEndTag');
112  xml_set_character_data_handler($a_xml_parser, 'handlerCharacterData');
113  }
114 
125  public function handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs): void
126  {
127  global $DIC;
128 
129  global $DIC;
130 
131  switch ($a_name) {
132  case 'File':
133  if (isset($a_attribs["obj_id"])) {
134  $read_obj_id = ilUtil::__extractId($a_attribs["obj_id"], IL_INST_ID);
135  if ($this->obj_id != -1 && (int) $read_obj_id != -1 && $this->obj_id != (int) $read_obj_id) {
136  throw new ilFileException(
137  "Object IDs (xml $read_obj_id and argument " . $this->obj_id . ") do not match!",
139  );
140  }
141  }
142 
143  break;
144  case 'Content': // Old import files
145  case 'Version':
146  if ($a_name === "Version" && !isset($a_attribs["mode"])) {
147  // Old import files
148  $this->version = null;
149  if ($this->date === null) {
150  // Version tag comes after Content tag. Take only first (= Should be latest)
151  $this->date = $a_attribs["date"];
152  $this->usr_id = (int) $a_attribs["usr_id"];
153  $this->versions[0]["date"] = $this->date;
154  $this->versions[0]["usr_id"] = $this->usr_id;
155  }
156  break;
157  }
158 
160  #echo $a_attribs["mode"];
161  if (isset($a_attribs["mode"])) {
162  if ($a_attribs["mode"] == "GZIP") {
163  if (!function_exists("gzread")) {
164  throw new ilFileException(
165  "Deflating with gzip is not supported",
167  );
168  }
169 
171  } elseif ($a_attribs["mode"] == "ZLIB") {
172  if (!function_exists("gzuncompress")) {
173  throw new ilFileException(
174  "Deflating with zlib (compress/uncompress) is not supported",
176  );
177  }
178 
180  } elseif ($a_attribs["mode"] == "COPY") {
181  $this->mode = ilFileXMLParser::$CONTENT_COPY;
182  } // begin-patch fm
183  elseif ($a_attribs['mode'] == 'REST') {
184  $this->mode = ilFileXMLParser::$CONTENT_REST;
185  }
186  // end-patch fm
187  }
188 
189  if ($a_name === "Version") {
190  $this->version = (int) $a_attribs["version"];
191  $this->max_version = (int) $a_attribs["max_version"];
192  $this->date = (int) $a_attribs["date"];
193  $this->usr_id = (int) $a_attribs["usr_id"];
194  $this->action = (string) $a_attribs["action"];
195  }
196  }
197  }
198 
205  public function handlerEndTag($a_xml_parser, string $a_name): void
206  {
207  $this->cdata = trim($this->cdata ?? '');
208 
209  $GLOBALS['DIC']['ilLog']->write(__METHOD__ . ': ' . $this->cdata);
210 
211  switch ($a_name) {
212  case 'File':
213  $this->result = true;
214  break;
215  case 'Filename':
216  if ($this->cdata === '') {
217  throw new ilFileException("Filename ist missing!");
218  }
219 
220  $this->file->setFilename($this->cdata);
221  $this->file->setTitle($this->cdata);
222 
223  break;
224  case 'Title':
225  $this->file->setTitle(trim($this->cdata));
226  break;
227  case 'Description':
228  $this->file->setDescription(trim($this->cdata));
229  break;
230  case 'Rating':
231  $this->file->setRating((bool) $this->cdata);
232  break;
233  case 'Content': // Old import files
234  case 'Version':
235  if ($a_name === "Version" && $this->version === null) {
236  // Old import files
237  break;
238  }
239 
240  $baseDecodedFilename = ilFileUtils::ilTempnam();
241  if ($this->mode === ilFileXMLParser::$CONTENT_COPY) {
242  $this->tmpFilename = $this->getImportDirectory() . "/" . self::normalizeRelativePath($this->cdata);
243  } // begin-patch fm
244  elseif ($this->mode === ilFileXMLParser::$CONTENT_REST) {
245  $storage = new ilRestFileStorage();
246  $this->tmpFilename = $storage->getStoredFilePath(self::normalizeRelativePath($this->cdata));
247  if (!$this->fastBase64Decode($this->tmpFilename, $baseDecodedFilename)) {
248  throw new ilFileException("Base64-Decoding failed", ilFileException::$DECOMPRESSION_FAILED);
249  }
250  $this->tmpFilename = $baseDecodedFilename;
251  } // end-patch fm
252  else {
253  if (!$this->fastBase64Decode($this->tmpFilename, $baseDecodedFilename)) {
254  throw new ilFileException("Base64-Decoding failed", ilFileException::$DECOMPRESSION_FAILED);
255  }
256  if ($this->mode === ilFileXMLParser::$CONTENT_GZ_COMPRESSED) {
257  if (!$this->fastGunzip($baseDecodedFilename, $this->tmpFilename)) {
258  throw new ilFileException(
259  "Deflating with fastzunzip failed",
261  );
262  }
263  unlink($baseDecodedFilename);
264  } elseif ($this->mode === ilFileXMLParser::$CONTENT_ZLIB_COMPRESSED) {
265  if (!$this->fastGunzip($baseDecodedFilename, $this->tmpFilename)) {
266  throw new ilFileException(
267  "Deflating with fastDecompress failed",
269  );
270  }
271  unlink($baseDecodedFilename);
272  } else {
273  $this->tmpFilename = $baseDecodedFilename;
274  }
275  }
276 
277  //$this->content = $content;
278  // see #17211
279 
280  if ($this->version == $this->file->getVersion()) {
281  if (is_file($this->tmpFilename)) {
282  $this->file->setFileSize(filesize($this->tmpFilename)); // strlen($this->content));
283  }
284 
285  // if no file type is given => lookup mime type
286  if (!$this->file->getFileType()) {
287  global $DIC;
288  $this->file->setFileType(MimeType::getMimeType($this->tmpFilename));
289  }
290  }
291 
292  $this->versions[] = [
293  "version" => $this->version,
294  "max_version" => $this->max_version,
295  "tmpFilename" => $this->tmpFilename,
296  "date" => $this->date,
297  "usr_id" => $this->usr_id,
298  "action" => $this->action,
299  ];
300  $this->version = null;
301  $this->date = null;
302  $this->usr_id = null;
303  break;
304  }
305 
306  $this->cdata = '';
307  }
308 
315  public function handlerCharacterData($a_xml_parser, string $a_data): void
316  {
317  if ($a_data != "\n") {
318  // begin-patch fm
319  if ($this->mode !== ilFileXMLParser::$CONTENT_COPY
320  && $this->mode !== ilFileXMLParser::$CONTENT_REST
321  ) { // begin-patch fm
322  $this->cdata .= $a_data;
323  } else {
324  $this->cdata .= $a_data;
325  }
326  }
327  }
328 
334  public function setFileContents(): void
335  {
336  // Delete exists version 1 history
337  ilHistory::_removeEntriesForObject($this->file->getId());
338 
339  foreach ($this->versions as $version) {
340  if (!file_exists($version["tmpFilename"])) {
341  if (!isset($version["tmpFilename"])) {
342  continue;
343  }
344  // try to get first file of directory
345  $files = scandir(dirname($version["tmpFilename"]));
346  $version["tmpFilename"] = rtrim(
347  dirname($version["tmpFilename"]),
348  "/"
349  ) . "/" . $files[2];// because [0] = "." [1] = ".."
350  if (!file_exists($version["tmpFilename"])) {
351  ilLoggerFactory::getLogger('file')->error(
352  __METHOD__ . ' "' . ($version["tmpFilename"]) . '" file not found.'
353  );
354 
355  continue;
356  }
357  }
358 
359  if (filesize($version["tmpFilename"]) == 0) {
360  continue;
361  }
362 
363  // imported file version
364  $import_file_version_path = $version["tmpFilename"];
365 
366  $stream = Streams::ofResource(fopen($import_file_version_path, 'rb'));
367  $this->file->appendStream($stream, $this->file->getTitle());
368  }
369  }
370 
376  public function updateFileContents(): void
377  {
378  // removed
379  }
380 
388  public function start(): bool
389  {
390  $this->startParsing();
391 
392  return $this->result > 0;
393  }
394 
405  public static function normalizeRelativePath(string $path): string
406  {
407  $path = str_replace('\\', '/', $path);
408 
409  while (preg_match('#\p{C}+|^\./#u', $path)) {
410  $path = preg_replace('#\p{C}+|^\./#u', '', $path);
411  }
412 
413  $parts = [];
414  foreach (explode('/', $path) as $part) {
415  switch ($part) {
416  case '':
417  case '.':
418  break;
419  case '..':
420  array_pop($parts);
421  break;
422  default:
423  $parts[] = $part;
424  break;
425  }
426  }
427 
428  return implode('/', $parts);
429  }
430 
431  private function fastBase64Decode(string $filein, string $fileout): bool
432  {
433  $fh = fopen($filein, 'rb');
434  $fh2 = fopen($fileout, 'wb');
435  stream_filter_append($fh2, 'convert.base64-decode');
436 
437  while (!feof($fh)) {
438  $chunk = fgets($fh);
439  if ($chunk === false) {
440  break;
441  }
442  fwrite($fh2, $chunk);
443  }
444  fclose($fh);
445  fclose($fh2);
446 
447  return true;
448  }
449 
450  private function fastGunzip(string $in, string $out): bool
451  {
452  if (!file_exists($in)) {
453  return false;
454  }
455  if (!is_readable($in)) {
456  return false;
457  }
458  if (!file_exists($out) && !is_writable(dirname($out))) {
459  return false;
460  }
461  if (file_exists($out) && !is_writable($out)) {
462  return false;
463  }
464  $in_file = gzopen($in, "rb");
465  $out_file = fopen($out, "wb");
466 
467  while (!gzeof($in_file)) {
468  $buffer = gzread($in_file, 4096);
469  fwrite($out_file, $buffer, 4096);
470  }
471 
472  gzclose($in_file);
473  fclose($out_file);
474 
475  return true;
476  }
477 }
fastGunzip(string $in, string $out)
handlerBeginTag($a_xml_parser, string $a_name, array $a_attribs)
handler for begin of element
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
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
__construct(public ilObjFile $file, string $a_xml_data, public int $obj_id=-1, public int $mode=0)
Constructor.
static _removeEntriesForObject(int $a_obj_id)
remove all history entries for an object
__construct(VocabulariesInterface $vocabularies)
string $tmpFilename
file of temporary file where we store the file content instead of in memory
$GLOBALS["DIC"]
Definition: wac.php:31
static int $CONTENT_GZ_COMPRESSED
Class ilObjFile.
$out
Definition: buildRTE.php:24
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.
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...