Rev 384 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
310 | daniel-mar | 1 | <?php |
2 | |||
3 | /* |
||
4 | * OIDplus 2.0 |
||
5 | * Copyright 2019 Daniel Marschall, ViaThinkSoft |
||
6 | * |
||
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
8 | * you may not use this file except in compliance with the License. |
||
9 | * You may obtain a copy of the License at |
||
10 | * |
||
11 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
12 | * |
||
13 | * Unless required by applicable law or agreed to in writing, software |
||
14 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
16 | * See the License for the specific language governing permissions and |
||
17 | * limitations under the License. |
||
18 | */ |
||
19 | |||
20 | class OIDplusPagePublicAttachments extends OIDplusPagePluginPublic { |
||
21 | |||
22 | public static function getUploadDir($id) { |
||
23 | $path = realpath(__DIR__.'/../../../').'/userdata/attachments/'; |
||
24 | |||
25 | $obj = OIDplusObject::parse($id); |
||
360 | daniel-mar | 26 | if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id)); |
310 | daniel-mar | 27 | if ($obj::ns() == 'oid') { |
28 | $oid = $obj->nodeId(false); |
||
29 | } else { |
||
30 | $oid = null; |
||
31 | $alt_ids = $obj->getAltIds(); |
||
32 | foreach ($alt_ids as $alt_id) { |
||
33 | if ($alt_id->getNamespace() == 'oid') { |
||
34 | $oid = $alt_id->getId(); |
||
35 | break; // we prefer the first OID (for GUIDs, the first OID is the OIDplus-OID, and the second OID is the UUID OID) |
||
36 | } |
||
37 | } |
||
38 | } |
||
39 | |||
384 | daniel-mar | 40 | if (!is_null($oid) && ($oid != '')) { |
310 | daniel-mar | 41 | // For OIDs, it is the OID, for other identifiers |
42 | // it it the OID alt ID (generated using the SystemID) |
||
43 | $path .= str_replace('.', '_', $oid); |
||
44 | |||
45 | } else { |
||
46 | // Can happen if you don't have a system ID (due to missing OpenSSL plugin) |
||
47 | $path .= md5($obj->nodeId(true)); // we don't use $id, because $obj->nodeId(true) is possibly more canonical than $id |
||
48 | } |
||
49 | |||
50 | return $path; |
||
51 | } |
||
52 | |||
53 | private function raMayDelete() { |
||
54 | return OIDplus::config()->getValue('attachments_allow_ra_delete', 0); |
||
55 | } |
||
56 | |||
57 | private function raMayUpload() { |
||
58 | return OIDplus::config()->getValue('attachments_allow_ra_upload', 0); |
||
59 | } |
||
60 | |||
321 | daniel-mar | 61 | public function action($actionID, $params) { |
317 | daniel-mar | 62 | |
321 | daniel-mar | 63 | if ($actionID == 'deleteAttachment') { |
64 | $id = $params['id']; |
||
317 | daniel-mar | 65 | $obj = OIDplusObject::parse($id); |
360 | daniel-mar | 66 | if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id)); |
67 | if (!$obj->userHasWriteRights()) throw new OIDplusException(_L('Authentication error. Please log in as admin, or as the RA of "%1" to upload an attachment.',$id)); |
||
310 | daniel-mar | 68 | |
69 | if (!OIDplus::authUtils()::isAdminLoggedIn() && !$this->raMayDelete()) { |
||
360 | daniel-mar | 70 | throw new OIDplusException(_L('The administrator has disabled deleting attachments by RAs.')); |
310 | daniel-mar | 71 | } |
72 | |||
321 | daniel-mar | 73 | $req_filename = $params['filename']; |
360 | daniel-mar | 74 | if (strpos($req_filename, '/') !== false) throw new OIDplusException(_L('Illegal file name')); |
75 | if (strpos($req_filename, '\\') !== false) throw new OIDplusException(_L('Illegal file name')); |
||
76 | if (strpos($req_filename, '..') !== false) throw new OIDplusException(_L('Illegal file name')); |
||
77 | if (strpos($req_filename, chr(0)) !== false) throw new OIDplusException(_L('Illegal file name')); |
||
310 | daniel-mar | 78 | |
79 | $uploaddir = self::getUploadDir($id); |
||
80 | $uploadfile = $uploaddir . '/' . basename($req_filename); |
||
81 | |||
360 | daniel-mar | 82 | if (!file_exists($uploadfile)) throw new OIDplusException(_L('File does not exist')); |
310 | daniel-mar | 83 | @unlink($uploadfile); |
84 | if (file_exists($uploadfile)) { |
||
386 | daniel-mar | 85 | OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Attachment file '".basename($uploadfile)."' could not be deleted from object '$id' (problem with permissions?)"); |
86 | $msg = _L('Attachment file "%1" could not be deleted from object "%2" (problem with permissions?)',basename($uploadfile),$id); |
||
310 | daniel-mar | 87 | if (OIDplus::authUtils()::isAdminLoggedIn()) { |
88 | throw new OIDplusException($msg); |
||
89 | } else { |
||
360 | daniel-mar | 90 | throw new OIDplusException($msg.'. '._L('Please contact the system administrator.')); |
310 | daniel-mar | 91 | } |
317 | daniel-mar | 92 | } else { |
93 | // If it was the last file, delete the empty directory |
||
94 | $ary = glob($uploaddir . '/' . '*'); |
||
95 | if (count($ary) == 0) @rmdir($uploaddir); |
||
310 | daniel-mar | 96 | } |
97 | |||
98 | OIDplus::logger()->log("[OK]OID($id)+[?INFO/!OK]OIDRA($id)?/[?INFO/!OK]A?", "Deleted attachment '".basename($uploadfile)."' from object '$id'"); |
||
99 | |||
328 | daniel-mar | 100 | return array("status" => 0); |
322 | daniel-mar | 101 | |
321 | daniel-mar | 102 | } else if ($actionID == 'uploadAttachment') { |
322 | daniel-mar | 103 | |
321 | daniel-mar | 104 | $id = $params['id']; |
310 | daniel-mar | 105 | $obj = OIDplusObject::parse($id); |
360 | daniel-mar | 106 | if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id)); |
107 | if (!$obj->userHasWriteRights()) throw new OIDplusException(_L('Authentication error. Please log in as admin, or as the RA of "%1" to upload an attachment.',$id)); |
||
310 | daniel-mar | 108 | |
109 | if (!OIDplus::authUtils()::isAdminLoggedIn() && !$this->raMayUpload()) { |
||
360 | daniel-mar | 110 | throw new OIDplusException(_L('The administrator has disabled uploading attachments by RAs.')); |
310 | daniel-mar | 111 | } |
112 | |||
327 | daniel-mar | 113 | if (!isset($_FILES['userfile'])) { |
360 | daniel-mar | 114 | throw new OIDplusException(_L('Please choose a file.')); |
327 | daniel-mar | 115 | } |
116 | |||
310 | daniel-mar | 117 | if (!OIDplus::authUtils()::isAdminLoggedIn()) { |
118 | $banned = explode(',', OIDplus::config()->getValue('attachments_block_extensions', '')); |
||
119 | foreach ($banned as $ext) { |
||
120 | $ext = trim($ext); |
||
121 | if ($ext == '') continue; |
||
122 | if (strtolower(substr(basename($_FILES['userfile']['name']), -strlen($ext)-1)) == strtolower('.'.$ext)) { |
||
360 | daniel-mar | 123 | throw new OIDplusException(_L('The file extension "%1" is banned by the administrator (it can be uploaded by the administrator though)',$ext)); |
310 | daniel-mar | 124 | } |
125 | } |
||
126 | } |
||
127 | |||
128 | $req_filename = $_FILES['userfile']['name']; |
||
360 | daniel-mar | 129 | if (strpos($req_filename, '/') !== false) throw new OIDplusException(_L('Illegal file name')); |
130 | if (strpos($req_filename, '\\') !== false) throw new OIDplusException(_L('Illegal file name')); |
||
131 | if (strpos($req_filename, '..') !== false) throw new OIDplusException(_L('Illegal file name')); |
||
132 | if (strpos($req_filename, chr(0)) !== false) throw new OIDplusException(_L('Illegal file name')); |
||
310 | daniel-mar | 133 | |
134 | $uploaddir = self::getUploadDir($id); |
||
135 | $uploadfile = $uploaddir . '/' . basename($req_filename); |
||
136 | |||
137 | if (!is_dir($uploaddir)) { |
||
138 | @mkdir($uploaddir, 0777, true); |
||
139 | if (!is_dir($uploaddir)) { |
||
360 | daniel-mar | 140 | OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Upload attachment '".basename($uploadfile)."' to object '$id' failed: Cannot create directory '".basename($uploaddir)."' (problem with permissions?)"); |
141 | $msg = _L('Upload attachment "%1" to object "%2" failed',basename($uploadfile),$id).': '._L('Cannot create directory "%1" (problem with permissions?)',basename($uploaddir)); |
||
310 | daniel-mar | 142 | if (OIDplus::authUtils()::isAdminLoggedIn()) { |
143 | throw new OIDplusException($msg); |
||
144 | } else { |
||
360 | daniel-mar | 145 | throw new OIDplusException($msg.'. '._L('Please contact the system administrator.')); |
310 | daniel-mar | 146 | } |
147 | } |
||
148 | } |
||
149 | |||
150 | if (!@move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) { |
||
360 | daniel-mar | 151 | OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Upload attachment '".basename($uploadfile)."' to object '$id' failed: Cannot move uploaded file into directory (problem with permissions?)"); |
152 | $msg = _L('Upload attachment "%1" to object "%2" failed',basename($uploadfile),$id).': '._L('Cannot move uploaded file into directory (problem with permissions?)'); |
||
310 | daniel-mar | 153 | if (OIDplus::authUtils()::isAdminLoggedIn()) { |
154 | throw new OIDplusException($msg); |
||
155 | } else { |
||
360 | daniel-mar | 156 | throw new OIDplusException($msg.'. '._L('Please contact the system administrator.')); |
310 | daniel-mar | 157 | } |
158 | } |
||
159 | |||
160 | OIDplus::logger()->log("[OK]OID($id)+[?INFO/!OK]OIDRA($id)?/[?INFO/!OK]A?", "Uploaded attachment '".basename($uploadfile)."' to object '$id'"); |
||
161 | |||
328 | daniel-mar | 162 | return array("status" => 0); |
321 | daniel-mar | 163 | } else { |
360 | daniel-mar | 164 | throw new OIDplusException(_L('Unknown action ID')); |
310 | daniel-mar | 165 | } |
166 | } |
||
167 | |||
168 | public function init($html=true) { |
||
169 | OIDplus::config()->prepareConfigKey('attachments_block_extensions', 'Block file name extensions being used in file attachments (comma separated)', 'exe,scr,pif,bat,com,vbs,cmd', OIDplusConfig::PROTECTION_EDITABLE, function($value) { |
||
170 | }); |
||
171 | OIDplus::config()->prepareConfigKey('attachments_allow_ra_delete', 'Allow that RAs delete file attachments? (0=no, 1=yes)', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) { |
||
172 | if (!is_numeric($value) || ($value < 0) || ($value > 1)) { |
||
360 | daniel-mar | 173 | throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).')); |
310 | daniel-mar | 174 | } |
175 | }); |
||
176 | OIDplus::config()->prepareConfigKey('attachments_allow_ra_upload', 'Allow that RAs upload file attachments? (0=no, 1=yes)', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) { |
||
177 | if (!is_numeric($value) || ($value < 0) || ($value > 1)) { |
||
360 | daniel-mar | 178 | throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).')); |
310 | daniel-mar | 179 | } |
180 | }); |
||
181 | } |
||
182 | |||
183 | public function gui($id, &$out, &$handled) { |
||
184 | // Nothing |
||
185 | } |
||
186 | |||
187 | public function publicSitemap(&$out) { |
||
188 | // Nothing |
||
189 | } |
||
190 | |||
191 | public function tree(&$json, $ra_email=null, $nonjs=false, $req_goto='') { |
||
192 | return false; |
||
193 | } |
||
194 | |||
195 | private static function convert_filesize($bytes, $decimals = 2){ |
||
360 | daniel-mar | 196 | $size = array(_L('Bytes'),_L('KiB'),_L('MiB'),_L('GiB'),_L('TiB'),_L('PiB'),_L('EiB'),_L('ZiB'),_L('YiB')); |
310 | daniel-mar | 197 | $factor = floor((strlen($bytes) - 1) / 3); |
198 | return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor]; |
||
199 | } |
||
200 | |||
201 | public function implementsFeature($id) { |
||
202 | if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.2') return true; // modifyContent |
||
321 | daniel-mar | 203 | if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.3') return true; // beforeObject*, afterObject* |
322 | daniel-mar | 204 | if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.4') return true; // whois*Attributes |
310 | daniel-mar | 205 | return false; |
206 | } |
||
207 | |||
208 | public function modifyContent($id, &$title, &$icon, &$text) { |
||
209 | // Interface 1.3.6.1.4.1.37476.2.5.2.3.2 |
||
210 | |||
211 | $files = glob(self::getUploadDir($id).'/'.'*'); |
||
212 | $doshow = false; |
||
213 | $output = ''; |
||
314 | daniel-mar | 214 | $found_files = false; |
310 | daniel-mar | 215 | |
216 | $obj = OIDplusObject::parse($id); |
||
360 | daniel-mar | 217 | if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id)); |
310 | daniel-mar | 218 | $can_upload = OIDplus::authUtils()::isAdminLoggedIn() || ($this->raMayUpload() && $obj->userHasWriteRights()); |
219 | $can_delete = OIDplus::authUtils()::isAdminLoggedIn() || ($this->raMayDelete() && $obj->userHasWriteRights()); |
||
220 | |||
360 | daniel-mar | 221 | $output .= '<h2>'._L('File attachments').'</h2>'; |
310 | daniel-mar | 222 | $output .= '<div class="container box">'; |
223 | |||
224 | if (OIDplus::authUtils()::isAdminLoggedIn()) { |
||
360 | daniel-mar | 225 | $output .= '<p>'._L('Admin info: The directory is %1','<b>'.htmlentities(self::getUploadDir($id)).'</b>').'</p>'; |
310 | daniel-mar | 226 | $doshow = true; |
227 | } |
||
228 | |||
229 | $output .= '<div id="fileattachments_table" class="table-responsive">'; |
||
230 | $output .= '<table class="table table-bordered table-striped">'; |
||
231 | $output .= '<tr>'; |
||
360 | daniel-mar | 232 | $output .= '<th>'._L('Filename').'</th>'; |
233 | $output .= '<th>'._L('Size').'</th>'; |
||
234 | $output .= '<th>'._L('File type').'</th>'; |
||
235 | $output .= '<th>'._L('Download').'</th>'; |
||
236 | if ($can_delete) $output .= '<th>'._L('Delete').'</th>'; |
||
310 | daniel-mar | 237 | $output .= '</tr>'; |
238 | foreach ($files as $file) { |
||
314 | daniel-mar | 239 | if (is_dir($file)) continue; |
240 | |||
310 | daniel-mar | 241 | $output .= '<tr>'; |
242 | $output .= '<td>'.htmlentities(basename($file)).'</td>'; |
||
243 | $output .= '<td>'.htmlentities(self::convert_filesize(filesize($file), 0)).'</td>'; |
||
373 | daniel-mar | 244 | $lookup_files = array( |
245 | __DIR__.'/../../../userdata/attachments/filetypes$'.OIDplus::getCurrentLang().'.conf', |
||
246 | __DIR__.'/../../../userdata/attachments/filetypes.conf', |
||
247 | __DIR__.'/../../../3p/vts_fileformats/filetypes$'.OIDplus::getCurrentLang().'.local', // not recommended |
||
248 | __DIR__.'/../../../3p/vts_fileformats/filetypes.local', // not recommended |
||
249 | __DIR__.'/../../../3p/vts_fileformats/filetypes$'.OIDplus::getCurrentLang().'.conf', |
||
250 | __DIR__.'/../../../3p/vts_fileformats/filetypes.conf' |
||
251 | ); |
||
310 | daniel-mar | 252 | $output .= '<td>'.htmlentities(VtsFileTypeDetect::getDescription($file, $lookup_files)).'</td>'; |
253 | |||
360 | daniel-mar | 254 | $output .= ' <td><button type="button" name="download_'.md5($file).'" id="download_'.md5($file).'" class="btn btn-success btn-xs download" onclick="downloadAttachment('.js_escape(OIDplus::webpath(__DIR__)).', current_node,'.js_escape(basename($file)).')">'._L('Download').'</button></td>'; |
310 | daniel-mar | 255 | if ($can_delete) { |
360 | daniel-mar | 256 | $output .= ' <td><button type="button" name="delete_'.md5($file).'" id="delete_'.md5($file).'" class="btn btn-danger btn-xs delete" onclick="deleteAttachment(current_node,'.js_escape(basename($file)).')">'._L('Delete').'</button></td>'; |
310 | daniel-mar | 257 | } |
258 | |||
259 | $output .= '</tr>'; |
||
260 | $doshow = true; |
||
314 | daniel-mar | 261 | $found_files = true; |
310 | daniel-mar | 262 | } |
263 | |||
360 | daniel-mar | 264 | if (!$found_files) $output .= '<tr><td colspan="'.($can_delete ? 5 : 4).'"><i>'._L('No attachments').'</i></td></tr>'; |
310 | daniel-mar | 265 | |
266 | $output .= '</table></div>'; |
||
267 | |||
268 | if ($can_upload) { |
||
269 | $output .= '<form onsubmit="return uploadAttachmentOnSubmit(this);" enctype="multipart/form-data" id="uploadAttachmentForm">'; |
||
270 | $output .= '<input type="hidden" name="id" value="'.htmlentities($id).'">'; |
||
360 | daniel-mar | 271 | $output .= '<div>'._L('Add a file attachment').':<input type="file" name="userfile" value="" id="fileAttachment">'; |
272 | $output .= '<br><input type="submit" value="'._L('Upload').'"></div>'; |
||
310 | daniel-mar | 273 | $output .= '</form>'; |
274 | $doshow = true; |
||
275 | } |
||
276 | |||
277 | $output .= '</div>'; |
||
278 | |||
279 | if ($doshow) $text .= $output; |
||
280 | } |
||
281 | |||
321 | daniel-mar | 282 | public function beforeObjectDelete($id) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
283 | public function afterObjectDelete($id) { |
||
284 | // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
285 | // Delete the attachment folder including all files in it (note: Subfolders are not possible) |
||
286 | $uploaddir = self::getUploadDir($id); |
||
287 | if ($uploaddir != '') { |
||
288 | $ary = glob($uploaddir . '/' . '*'); |
||
289 | foreach ($ary as $a) @unlink($a); |
||
290 | @rmdir($uploaddir); |
||
291 | if (is_dir($uploaddir)) { |
||
292 | OIDplus::logger()->log("[WARN]OID($id)+[WARN]A!", "Attachment directory '$uploaddir' could not be deleted during the deletion of the OID"); |
||
293 | } |
||
294 | } |
||
295 | } |
||
296 | public function beforeObjectUpdateSuperior($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
297 | public function afterObjectUpdateSuperior($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
298 | public function beforeObjectUpdateSelf($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
299 | public function afterObjectUpdateSelf($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
300 | public function beforeObjectInsert($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
301 | public function afterObjectInsert($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
302 | |||
310 | daniel-mar | 303 | public function tree_search($request) { |
304 | return false; |
||
305 | } |
||
322 | daniel-mar | 306 | |
307 | public function whoisObjectAttributes($id, &$out) { |
||
308 | // Interface 1.3.6.1.4.1.37476.2.5.2.3.4 |
||
309 | |||
310 | $files = glob(self::getUploadDir($id).'/'.'*'); |
||
311 | foreach ($files as $file) { |
||
312 | $out[] = 'attachment-name: '.basename($file); |
||
313 | $out[] = 'attachment-url: '.OIDplus::getSystemUrl().OIDplus::webpath(__DIR__).'download.php?id='.urlencode($id).'&filename='.urlencode(basename($file)); |
||
314 | } |
||
315 | |||
316 | } |
||
317 | public function whoisRaAttributes($email, &$out) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.4 |
||
373 | daniel-mar | 318 | } |