Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.72% covered (warning)
78.72%
74 / 94
88.24% covered (warning)
88.24%
15 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
File
78.72% covered (warning)
78.72%
74 / 94
88.24% covered (warning)
88.24%
15 / 17
41.86
0.00% covered (danger)
0.00%
0 / 1
 get
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 fromString
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 determineType
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 __construct
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
2
 readFileMeta
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 load
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
 validate
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 getType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersionMayorMinor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isValid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 report
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 crossValidate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 addRelation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 jsonSerialize
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 getContent
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare(strict_types=1);
3
4class File extends FileData {
5  private const type = 'file';
6  public const canBeRelationSubject = false;
7  public const canBeRelationObject = false;
8  protected string $name = '';
9  protected ?string $content = null;
10
11  static function get(string|FileData $init, string $type = null): File {
12    if (!$type and !is_a($init, FileData::class)) {
13      $type = File::determineType($init);
14    }
15
16    return match ($type) {
17      'Testtakers' => new XMLFileTesttakers($init),
18      'SysCheck' => new XMLFileSysCheck($init),
19      'Booklet' => new XMLFileBooklet($init),
20      'Unit' => new XMLFileUnit($init),
21      'Resource' => new ResourceFile($init),
22      'xml' => new XMLFile($init),
23      default => new File($init),
24    };
25  }
26
27  /** For use in testing classes */
28  static function fromString(string $fileContent, string $fileName = 'virtual_file'): File {
29    $file = new static(new FileData($fileName));
30    $file->content = $fileContent;
31    $file->validate();
32    return $file;
33  }
34
35  // TODO unit-test
36  public static function determineType(string $path): string {
37    if (strtoupper(substr($path, -4)) == '.XML') {
38      $asGenericXmlFile = new XMLFile($path);
39      if (!in_array($asGenericXmlFile->rootTagName, XMLFile::knownRootTags)) {
40        return 'xml';
41      }
42      return $asGenericXmlFile->rootTagName;
43    } else {
44      return 'Resource';
45    }
46  }
47
48  public function __construct(string|FileData $init) {
49    if (is_a($init, FileData::class)) {
50      $this->path = $init->path;
51      $this->type = $init->type;
52      $this->id = $init->id;
53      $this->label = $init->label;
54      $this->description = $init->description;
55      $this->validationReport = $init->validationReport;
56      $this->relations = $init->relations;
57      $this->modificationTime = $init->modificationTime;
58      $this->size = $init->size;
59      $this->name = basename($init->path);
60      $this->contextData = $init->contextData;
61      $this->veronaModuleType = $init->veronaModuleType;
62      $this->veronaModuleId = $init->veronaModuleId;
63      $this->versionMayor = $init->versionMayor;
64      $this->versionMinor = $init->versionMinor;
65      $this->versionPatch = $init->versionPatch;
66      $this->versionLabel = $init->versionLabel;
67      $this->veronaVersion = $init->veronaVersion;
68      return;
69    }
70
71    parent::__construct();
72
73    $this->readFileMeta($init);
74    $this->id = strtoupper($this->getName());
75
76    $this->load();
77  }
78
79  public function readFileMeta(string $path): void { // TODO can this be private / merged with load?
80
81    $this->path = $path;
82
83    if (!file_exists($path)) {
84      $this->size = 0;
85      $this->name = basename($path);
86      $this->modificationTime = 1;
87      $this->report('error', "File does not exist: `" . dirname($path) . '/'. basename($path) . "`");
88
89    } else {
90      $this->size = filesize($path);
91      $this->name = basename($path);
92      $this->modificationTime = FileTime::modification($path);
93    }
94  }
95
96  protected function load(): void {
97    if (($this->content === null) and $this->path and file_exists($this->path)) {
98      $this->content = file_get_contents($this->path);
99      $this->validate();
100    }
101  }
102
103  protected function validate(): void {
104    if (strlen($this->name) > 120) {
105      $this->report('error', "Filename too long!");
106    }
107  }
108
109  public function getType(): string {
110    return $this->type ?? $this::type;
111  }
112
113  public function getName(): string {
114    return $this->name;
115  }
116
117  public function getVersion(): string {
118    return Version::asString($this->versionMayor, $this->versionMinor, $this->versionPatch, $this->versionLabel) ?? '';
119  }
120
121  public function getVersionMayorMinor(): string {
122    return "$this->versionMayor.$this->versionMinor";
123  }
124
125  public function isValid(): bool {
126    return count($this->validationReport['error'] ?? []) == 0;
127  }
128
129  public function report(string $level, string $message): void {
130    if (isset($this->validationReport[$level]) and count($this->validationReport[$level]) == 5) {
131      $aggregated = str_ends_with($this->validationReport[$level][4], " more {$level}s.") ? (int) $this->validationReport[$level][4] : 1;
132      $this->validationReport[$level][4] = ($aggregated + 1) . " more {$level}s.";
133    } else {
134      $this->validationReport[$level][] = $message;
135    }
136  }
137
138  // TODO unit-test
139  public function crossValidate(WorkspaceCache $workspaceCache): void {
140    if ($duplicateId = $workspaceCache->getDuplicateId($this)) {
141      $origFile = $workspaceCache->getFile($this->getType(), $this->getId());
142
143      $this->report('error', "Duplicate {$this->getType()}-Id: `{$this->getId()}` ({$origFile->getName()})");
144      $this->id = $duplicateId;
145    }
146  }
147
148  public function addRelation(FileRelation $relation): void {
149    $this->relations[] = $relation;
150  }
151
152  public function jsonSerialize(): mixed {
153    $info = [
154      'label' => $this->getLabel(),
155      'description' => $this->getDescription(),
156    ];
157    if ($this->veronaModuleType) {
158      $info['veronaModuleType'] = $this->veronaModuleType;
159      $info['veronaVersion'] = $this->veronaVersion;
160      $info['version'] = $this->getVersion();
161    }
162
163    return [
164      'name' => $this->name,
165      'size' => $this->size,
166      'modificationTime' => $this->modificationTime,
167      'type' => $this->type,
168      'id' => $this->id,
169      'report' => $this->validationReport,
170      'dependencies' => $this->relations,
171      'info' => array_merge($info, $this->getContextData()
172      ),
173    ];
174  }
175
176  public function getContent(): string {
177    $this->load();
178    return $this->content;
179  }
180}
181