Passed
Pull Request — master (#103)
by Salim
19:58 queued 15:58
created

delete_tus_file()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 9
rs 10
c 1
b 0
f 0
1
<?php
2
/**
3
 Copyright (C) 2018-2020 KANOUN Salim
4
 This program is free software; you can redistribute it and/or modify
5
 it under the terms of the Affero GNU General Public v.3 License as published by
6
 the Free Software Foundation;
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
 Affero GNU General Public Public for more details.
11
 You should have received a copy of the Affero GNU General Public Public along
12
 with this program; if not, write to the Free Software Foundation, Inc.,
13
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
14
 */
15
16
/**
17
 * Validate the uploaded dicom, this script act as follow : 
18
 * Unzip the recieved ZIPs
19
 * Send each dicom to Orthanc Exposed
20
 * Produce the Anonymize query in Orthanc Exposed
21
 * Delete the original import in Orthanc Exposed
22
 * Send the anonymized study in Orthanc PACS
23
 * Delete the anonymied dicom in Orthanc Exposed
24
 * Analyze the Orthanc PACS dicom and write DICOM details in database
25
 * Update Visit status and send email notifications
26
 * 
27
 * Warning : The execution time of this script is long due to Orthanc heavy operations
28
 * Double check the timeout value that are set in Orthanc class and that database connexion is not timed up
29
 * Check also the max execution time (which is reset in Orthanc class)
30
 * 
31
 */
32
33
require_once($_SERVER['DOCUMENT_ROOT'].'/vendor/autoload.php');
34
35
use GuzzleHttp\Client;
0 ignored issues
show
Bug introduced by
The type GuzzleHttp\Client was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
37
Session::checkSession();
38
$linkpdo=Session::getLinkpdo();
39
40
$timeStamp = $_POST['timeStamp'];
41
$id_visit = $_POST['id_visit'];
42
$nbOfInstances = $_POST['totalDicomFiles'];
43
$anonFromOrthancId=$_POST['originalOrthancStudyID'];
44
$username=$_SESSION['username'];
45
$study=$_SESSION['study'];
46
$role=$_SESSION['role'];
47
$tusFilesID = json_decode($_POST['sucessIDsUploaded']);
48
49
$unzipedPath = $_SERVER['DOCUMENT_ROOT'].'/data/upload/temp/'.$timeStamp.'_'.$id_visit;
50
51
$visitObject=new Visit($id_visit, $linkpdo);
52
$userObject=new User($username, $linkpdo);
53
54
$accessCheck=$userObject->isVisitAllowed($id_visit, User::INVESTIGATOR);
55
56
if ($accessCheck && $role == User::INVESTIGATOR && $visitObject->uploadStatus == Visit::NOT_DONE) {
57
	
58
	//Run as a background task even if the user leave the website
59
	ignore_user_abort(true);
60
	//Set Visit as upload processing status
61
	$visitObject->changeUploadStatus(Visit::UPLOAD_PROCESSING);
62
    $start_time=microtime(true);
63
	/**
64
	 * Try block as each interruption of the proccess must make visit return as upload not done
65
	 * To allow new upload
66
	 */
67
	try {
68
69
		if (!is_dir($unzipedPath)) {
70
			mkdir($unzipedPath, 0755);
71
		}
72
73
		//Unzip each uploaded file and remove them from tus
74
		foreach($tusFilesID as $fileName){
75
			$tempZipPath = get_tus_file($fileName);
76
77
			$zipSize=filesize($tempZipPath);
78
			$uncompressedzipSize=get_zip_originalsize($tempZipPath);
79
			if ($uncompressedzipSize/$zipSize > 50) {
80
				throw new Exception("Bomb Zip");
81
			}
82
83
			$zip=new ZipArchive;
84
			$zip->open($tempZipPath);
85
			$zip->extractTo($unzipedPath);
86
			$zip->close();
87
			
88
			//Remove file from TUS and downloaded temporary zip
89
			delete_tus_file($fileName);
90
			unlink($tempZipPath);
91
92
		}
93
		
94
95
96
		//Send unziped files to Orthanc
97
		$orthancExposedObject=new Orthanc(true);
98
		$importedMap=sendFolderToOrthanc($unzipedPath, $orthancExposedObject);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $importedMap is correct as sendFolderToOrthanc($unz... $orthancExposedObject) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
99
100
		//Anonymize, remove original and send anonymized to Orthanc PACS
101
		//Read imported map, it only have only one study
102
		foreach ($importedMap as $studyID=>$seriesIDs) {
0 ignored issues
show
Bug introduced by
The expression $importedMap of type null is not traversable.
Loading history...
103
			//Anonymize and store new anonymized study Orthanc ID
104
			$anonymizedIDArray[]=$orthancExposedObject->Anonymize($studyID, $visitObject->getVisitCharacteristics()->anonProfile, $visitObject->patientCode, $visitObject->visitType, $visitObject->study);
105
			//error_log("Anonymization done at ".(microtime(true)-$start_time));
106
			//Delete original import
107
			$orthancExposedObject->deleteFromOrthanc("studies", $studyID);
108
			
109
		}
110
		//Send to Orthanc Pacs and fill the database
111
		$orthancExposedObject->sendToPeer("OrthancPacs", $anonymizedIDArray);
112
		//error_log("Peer sent at ".(microtime(true)-$start_time));
113
		//erase transfered anonymized study from orthanc exposed
114
		$orthancExposedObject->deleteFromOrthanc("studies", $anonymizedIDArray[0]);
115
	
116
		//Fill the Orthanc study / series table
117
		//Reset the PDO object as the database connexion is likely to be timed out
118
		$linkpdo=Session::getLinkpdo();
119
		$fillTable=new Fill_Orthanc_Table($visitObject->id_visit, $username, $linkpdo);
120
		$studyDetails=$fillTable->parseData($anonymizedIDArray[0]);
121
122
		//Check that nb on instances in Orthanc PACS still match the original number of sent instances
123
		if ($studyDetails['countInstances'] != $nbOfInstances) {
124
			throw new Exception("Error during Peer transfers"); 
125
		}
126
    		
127
		//Fill Orthanc Tables in Database and update visit status
128
		$fillTable->fillDB($anonFromOrthancId);
129
		$answer['receivedConfirmation']=true;
130
		$logDetails['uploadedSeries']=$studyDetails['seriesInStudy'];
131
		$logDetails['patientNumber']=$visitObject->patientCode;
132
		$logDetails['visitType']=$visitObject->visitType;
133
		$logDetails['modality_visit']=$visitObject->visitGroupObject->groupModality;
134
		//Log import
135
		Tracker::logActivity($username, $role, $study, $visitObject->id_visit, "Upload Series", $logDetails);
136
	
137
	}catch (Throwable $e1) {
138
		error_log($e1->getMessage());
139
		handleException($e1);
140
	}
141
		
142
143
}else {
144
	header('HTTP/1.0 403 Forbidden');
145
	die('You are not allowed to access this file.');
146
}
147
148
149
/**
150
 * Send all folder content to Orthanc (recursively)
151
 * @param string $unzipedPath
152
 * @param Orthanc $orthancExposedObject
153
 * @throws Exception
154
 * @return mixed
155
 */
156
function sendFolderToOrthanc(string $unzipedPath, Orthanc $orthancExposedObject) {
157
	
158
	global $nbOfInstances;
159
	//Recursive scann of the unzipped folder
160
	$rii=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($unzipedPath));
161
	
162
	$files=array();
163
	foreach ($rii as $file) {
164
		if ($file->isDir()) {
165
			continue;
166
		}
167
		$files[]=$file->getPathname();
168
    }
169
    
170
    if(sizeof($files) != $nbOfInstances){
171
        throw new Exception("Number Of Uploaded Files dosen't match expected instance number");
172
    }
173
	
174
	$importedMap=null;
175
	$importedInstances=0;
176
	//$start_time=microtime(true);
177
	
178
	//Import dicom file one by one
179
	foreach ($files as $file) {
180
		$importAnswer=$orthancExposedObject->importFile($file);
181
		if (!empty($importAnswer)) {
182
			$answerdetails=json_decode($importAnswer, true);
183
			$importedMap[$answerdetails['ParentStudy']][$answerdetails['ParentSeries']][]=$answerdetails['ID'];
184
			$importedInstances++;
185
		}
186
		
187
	}
188
	
189
	//Delete original file after import
190
	recursive_directory_delete($unzipedPath);
191
	
192
	//error_log("Imported ".$importedInstances." files in ".(microtime(true)-$start_time));
193
	error_log('Imported Instances :'.$importedInstances);
194
	error_log('Announced number of Instances :'.$nbOfInstances);
195
	
196
	if (count($importedMap) == 1 && $importedInstances == $nbOfInstances) {
197
		return $importedMap;
198
	}else {
199
		//These error shall never occur
200
		if (count($importedMap) > 1) {
201
			throw new Exception("More than one study in Zip");
202
		}else if ($importedInstances != $nbOfInstances) {
203
			throw new Exception("Imported DICOM not matching announced number of Instances");
204
			
205
		}
206
	}
207
	
208
}
209
210
/**
211
 * Recursively delete unziped folder
212
 * @param string $directory
213
 */
214
function recursive_directory_delete(string $directory) {
215
	$it=new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS);
216
	$it=new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
217
	foreach ($it as $file) {
218
		if ($file->isDir()) rmdir($file->getPathname());
219
		else unlink($file->getPathname());
220
	}
221
	rmdir($directory);
222
}
223
224
/**
225
 * In case of a thrown exception, warn administrator and uploader and set upload to not done
226
 * @param Exception $e1
227
 */
228
function handleException(Throwable $e1) {
229
	global $visitObject;
230
	global $linkpdo;
231
	global $answer;
232
	//If more than own study uploaded or difference of instance number an exception is thrown
233
	$answer['receivedConfirmation']=false;
234
	$answer['errorDetails']=$e1->getMessage();
235
	$visitObject->changeUploadStatus(Visit::NOT_DONE);
236
	warningAdminError($e1->getMessage(), $linkpdo);
237
	die($e1->getMessage());
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
238
}
239
/**
240
 * Warn supervisors and uploader that validation of uploaded DICOM has failed
241
 * @param string $errorMessage
242
 * @param PDO $linkpdo
243
 */
244
function warningAdminError(string $errorMessage, PDO $linkpdo) {
245
	$sendEmails=new Send_Email($linkpdo);
246
	global $visitObject;
247
	global $unzipedPath;
248
	global $study;
249
	global $username;
250
251
	$sendEmails->addGroupEmails($study, User::SUPERVISOR)->addEmail($sendEmails->getUserEmails($username));
252
	$sendEmails->sendUploadValidationFailure($visitObject->id_visit, $visitObject->patientCode, $visitObject->visitType,
253
			$study, $unzipedPath, $username, $errorMessage);
254
}
255
256
/**
257
 * Get uncompressed Size
258
 * @param string $filename
259
 * @return number
260
 */
261
function get_zip_originalsize(string $filename) {
262
	$size=0;
263
	$resource=zip_open($filename);
264
	while ($dir_resource=zip_read($resource)) {
265
		$size+=zip_entry_filesize($dir_resource);
266
	}
267
	zip_close($resource);
268
    
269
	return $size;
270
}
271
272
function get_tus_file($fileName) {
273
    
274
    $client = new Client([
275
        // Base URI is used with relative requests
276
		'base_uri' => TUS_SERVER.'/tus/',
277
		'headers' => ['Tus-Resumable' => '1.0.0']
278
    ]);
279
	$downloadedFileName = tempnam(sys_get_temp_dir(), 'dicom');
280
281
	$resource  = fopen( $downloadedFileName, 'r+');
282
	
283
	$client->request('GET', $fileName, ['sink' => $resource]);
284
285
    return $downloadedFileName;
286
}
287
288
function delete_tus_file($fileName){
289
290
    $client = new Client([
291
        // Base URI is used with relative requests
292
		'base_uri' => TUS_SERVER.'/tus/',
293
		'headers' => ['Tus-Resumable' => '1.0.0']
294
    ]);
295
296
	$client->request('DELETE', $fileName);
297
	
298
    //$code = $response->getStatusCode(); // 200
299
	//$reason = $response->getReasonPhrase(); // OK
300
301
}
302
303
304
305