Subversion Repositories oidplus

Rev

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