Subversion Repositories oidplus

Rev

Rev 1138 | Rev 1175 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2023 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. namespace ViaThinkSoft\OIDplus;
  21.  
  22. // phpcs:disable PSR1.Files.SideEffects
  23. \defined('INSIDE_OIDPLUS') or die;
  24. // phpcs:enable PSR1.Files.SideEffects
  25.  
  26. class OIDplusPagePublicAttachments extends OIDplusPagePluginPublic
  27.         implements INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_2, /* modifyContent */
  28.                    INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3, /* beforeObject*, afterObject* */
  29.                    INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4  /* whois*Attributes */
  30. {
  31.  
  32.         /**
  33.          *
  34.          */
  35.         const DIR_UNLOCK_FILE = 'oidplus_upload.dir';
  36.  
  37.         /**
  38.          * @param string $dir
  39.          * @return void
  40.          * @throws OIDplusException
  41.          */
  42.         private static function checkUploadDir(string $dir) {
  43.                 if (!is_dir($dir)) {
  44.                         throw new OIDplusException(_L('The attachment directory "%1" is not existing.', $dir));
  45.                 }
  46.  
  47.                 $realdir = realpath($dir);
  48.                 if ($realdir === false) {
  49.                         throw new OIDplusException(_L('The attachment directory "%1" cannot be resolved (realpath).', $dir));
  50.                 }
  51.  
  52.                 $unlock_file = $realdir . DIRECTORY_SEPARATOR . self::DIR_UNLOCK_FILE;
  53.                 if (!file_exists($unlock_file)) {
  54.                         throw new OIDplusException(_L('Unlock file "%1" is not existing in attachment directory "%2".', self::DIR_UNLOCK_FILE, $dir));
  55.                 }
  56.  
  57.                 if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
  58.                         // Linux check 1: Check for critical directories
  59.                         if (self::isCriticalLinuxDirectory($realdir)) {
  60.                                 throw new OIDplusException(_L('The attachment directory must not be inside a critical system directory!'));
  61.                         }
  62.  
  63.                         // Linux check 2: Check file owner
  64.                         $file_owner_a = fileowner(OIDplus::localpath().'index.php');
  65.                         if ($file_owner_a === false) {
  66.                                 $file_owner_a = -1;
  67.                                 $file_owner_a_name = '???';
  68.                         } else {
  69.                                 $tmp = function_exists('posix_getpwuid') ? posix_getpwuid($file_owner_a) : false;
  70.                                 $file_owner_a_name = $tmp !== false ? $tmp['name'] : 'UID '.$file_owner_a;
  71.                         }
  72.  
  73.                         $file_owner_b = fileowner($unlock_file);
  74.                         if ($file_owner_b === false) {
  75.                                 $file_owner_b = -1;
  76.                                 $file_owner_b_name = '???';
  77.                         } else {
  78.                                 $tmp = function_exists('posix_getpwuid') ? posix_getpwuid($file_owner_b) : false;
  79.                                 $file_owner_b_name = $tmp !== false ? $tmp['name'] : 'UID '.$file_owner_b;
  80.                         }
  81.  
  82.                         if ($file_owner_a != $file_owner_b) {
  83.                                 throw new OIDplusException(_L('Owner of unlock file "%1" is wrong. It is "%2", but it should be "%3".', $unlock_file, $file_owner_b_name, $file_owner_a_name));
  84.                         }
  85.                 } else {
  86.                         // Windows check 1: Check for critical directories
  87.                         if (self::isCriticalWindowsDirectory($realdir)) {
  88.                                 throw new OIDplusException(_L('The attachment directory must not be inside a critical system directory!'));
  89.                         }
  90.  
  91.                         // Note: We will not query the file owner in Windows systems.
  92.                         // It would be possible, however, on Windows systems, the file
  93.                         // ownership is rather hidden to the user and the user needs
  94.                         // to go into several menus and windows in order to see/change
  95.                         // the owner. We don't want to over-complicate it to the Windows admin.
  96.                 }
  97.         }
  98.  
  99.         /**
  100.          * @param string $dir
  101.          * @return bool
  102.          */
  103.         private static function isCriticalWindowsDirectory(string $dir): bool {
  104.                 $dir .= '\\';
  105.                 $windir = isset($_SERVER['SystemRoot']) ? $_SERVER['SystemRoot'].'\\' : 'C:\\Windows\\';
  106.                 if (stripos($dir,$windir) === 0) return true;
  107.                 return false;
  108.         }
  109.  
  110.         /**
  111.          * @param string $dir
  112.          * @return bool
  113.          */
  114.         private static function isCriticalLinuxDirectory(string $dir): bool {
  115.                 if ($dir == '/') return true;
  116.                 $dir .= '/';
  117.                 if (strpos($dir,'/bin/') === 0) return true;
  118.                 if (strpos($dir,'/boot/') === 0) return true;
  119.                 if (strpos($dir,'/dev/') === 0) return true;
  120.                 if (strpos($dir,'/etc/') === 0) return true;
  121.                 if (strpos($dir,'/lib') === 0) return true;
  122.                 if (strpos($dir,'/opt/') === 0) return true;
  123.                 if (strpos($dir,'/proc/') === 0) return true;
  124.                 if (strpos($dir,'/root/') === 0) return true;
  125.                 if (strpos($dir,'/run/') === 0) return true;
  126.                 if (strpos($dir,'/sbin/') === 0) return true;
  127.                 if (strpos($dir,'/sys/') === 0) return true;
  128.                 if (strpos($dir,'/tmp/') === 0) return true;
  129.                 if (strpos($dir,'/usr/bin/') === 0) return true;
  130.                 if (strpos($dir,'/usr/include/') === 0) return true;
  131.                 if (strpos($dir,'/usr/lib') === 0) return true;
  132.                 if (strpos($dir,'/usr/sbin/') === 0) return true;
  133.                 if (strpos($dir,'/usr/src/') === 0) return true;
  134.                 if (strpos($dir,'/var/cache/') === 0) return true;
  135.                 if (strpos($dir,'/var/lib') === 0) return true;
  136.                 if (strpos($dir,'/var/lock/') === 0) return true;
  137.                 if (strpos($dir,'/var/log/') === 0) return true;
  138.                 if (strpos($dir,'/var/mail/') === 0) return true;
  139.                 if (strpos($dir,'/var/opt/') === 0) return true;
  140.                 if (strpos($dir,'/var/run/') === 0) return true;
  141.                 if (strpos($dir,'/var/spool/') === 0) return true;
  142.                 if (strpos($dir,'/var/tmp/') === 0) return true;
  143.                 return false;
  144.         }
  145.  
  146.         /**
  147.          * @param string|null $id
  148.          * @return string
  149.          * @throws OIDplusException
  150.          */
  151.         public static function getUploadDir(string $id=null): string {
  152.                 // Get base path
  153.                 $cfg = OIDplus::config()->getValue('attachment_upload_dir', '');
  154.                 $cfg = trim($cfg);
  155.                 if ($cfg === '') {
  156.                         $basepath = OIDplus::localpath() . 'userdata' . DIRECTORY_SEPARATOR . 'attachments';
  157.                 } else {
  158.                         $basepath = $cfg;
  159.                 }
  160.  
  161.                 try {
  162.                         self::checkUploadDir($basepath);
  163.                 } catch (\Exception $e) {
  164.                         $error = _L('This functionality is not available due to a misconfiguration');
  165.                         if (OIDplus::authUtils()->isAdminLoggedIn()) {
  166.                                 $error .= ': '.$e->getMessage();
  167.                         } else {
  168.                                 $error .= '. '._L('Please notify the system administrator. After they log-in, they can see the reason at this place.');
  169.                         }
  170.                         throw new OIDplusException($error);
  171.                 }
  172.  
  173.                 // Get object-specific path
  174.                 if (!is_null($id)) {
  175.                         $obj = OIDplusObject::parse($id);
  176.                         if (!$obj) throw new OIDplusException(_L('Invalid object "%1"',$id));
  177.  
  178.                         $path_v1 = $basepath . DIRECTORY_SEPARATOR . $obj->getLegacyDirectoryName();
  179.                         $path_v1_bug = $basepath . $obj->getLegacyDirectoryName();
  180.                         $path_v2 = $basepath . DIRECTORY_SEPARATOR . $obj->getDirectoryName();
  181.  
  182.                         if (is_dir($path_v1)) return $path_v1; // backwards compatibility
  183.                         if (is_dir($path_v1_bug)) return $path_v1_bug; // backwards compatibility
  184.                         return $path_v2;
  185.                 } else {
  186.                         return $basepath;
  187.                 }
  188.         }
  189.  
  190.         /**
  191.          * @return mixed|null
  192.          * @throws OIDplusException
  193.          */
  194.         private function raMayDelete() {
  195.                 return OIDplus::config()->getValue('attachments_allow_ra_delete', 0);
  196.         }
  197.  
  198.         /**
  199.          * @return mixed|null
  200.          * @throws OIDplusException
  201.          */
  202.         private function raMayUpload() {
  203.                 return OIDplus::config()->getValue('attachments_allow_ra_upload', 0);
  204.         }
  205.  
  206.         /**
  207.          * @param string $actionID
  208.          * @param array $params
  209.          * @return array
  210.          * @throws OIDplusException
  211.          */
  212.         public function action(string $actionID, array $params): array {
  213.  
  214.                 if ($actionID == 'deleteAttachment') {
  215.                         _CheckParamExists($params, 'id');
  216.                         $id = $params['id'];
  217.                         $obj = OIDplusObject::parse($id);
  218.                         if (!$obj) throw new OIDplusException(_L('Invalid object "%1"',$id));
  219.                         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));
  220.  
  221.                         if (!OIDplus::authUtils()->isAdminLoggedIn() && !$this->raMayDelete()) {
  222.                                 throw new OIDplusException(_L('The administrator has disabled deleting attachments by RAs.'));
  223.                         }
  224.  
  225.                         _CheckParamExists($params, 'filename');
  226.                         $req_filename = $params['filename'];
  227.                         if (strpos($req_filename, '/') !== false) throw new OIDplusException(_L('Illegal file name'));
  228.                         if (strpos($req_filename, '\\') !== false) throw new OIDplusException(_L('Illegal file name'));
  229.                         if (strpos($req_filename, '..') !== false) throw new OIDplusException(_L('Illegal file name'));
  230.                         if (strpos($req_filename, chr(0)) !== false) throw new OIDplusException(_L('Illegal file name'));
  231.  
  232.                         $uploaddir = self::getUploadDir($id);
  233.                         $uploadfile = $uploaddir . DIRECTORY_SEPARATOR . basename($req_filename);
  234.  
  235.                         if (!file_exists($uploadfile)) throw new OIDplusException(_L('File does not exist'));
  236.                         @unlink($uploadfile);
  237.                         if (file_exists($uploadfile)) {
  238.                                 OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Attachment file '".basename($uploadfile)."' could not be deleted from object '$id' (problem with permissions?)");
  239.                                 $msg = _L('Attachment file "%1" could not be deleted from object "%2" (problem with permissions?)',basename($uploadfile),$id);
  240.                                 if (OIDplus::authUtils()->isAdminLoggedIn()) {
  241.                                         throw new OIDplusException($msg);
  242.                                 } else {
  243.                                         throw new OIDplusException($msg.'. '._L('Please contact the system administrator.'));
  244.                                 }
  245.                         } else {
  246.                                 // If it was the last file, delete the empty directory
  247.                                 $ary = @glob($uploaddir . DIRECTORY_SEPARATOR . '*');
  248.                                 if (is_array($ary) && (count($ary) == 0)) @rmdir($uploaddir);
  249.                         }
  250.  
  251.                         OIDplus::logger()->log("[OK]OID($id)+[?INFO/!OK]OIDRA($id)?/[?INFO/!OK]A?", "Deleted attachment '".basename($uploadfile)."' from object '$id'");
  252.  
  253.                         return array("status" => 0);
  254.  
  255.                 } else if ($actionID == 'uploadAttachment') {
  256.                         _CheckParamExists($params, 'id');
  257.                         $id = $params['id'];
  258.                         $obj = OIDplusObject::parse($id);
  259.                         if (!$obj) throw new OIDplusException(_L('Invalid object "%1"',$id));
  260.                         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));
  261.  
  262.                         if (!OIDplus::authUtils()->isAdminLoggedIn() && !$this->raMayUpload()) {
  263.                                 throw new OIDplusException(_L('The administrator has disabled uploading attachments by RAs.'));
  264.                         }
  265.  
  266.                         if (!isset($_FILES['userfile'])) {
  267.                                 throw new OIDplusException(_L('Please choose a file.'));
  268.                         }
  269.  
  270.                         if (!OIDplus::authUtils()->isAdminLoggedIn()) {
  271.                                 $fname = basename($_FILES['userfile']['name']);
  272.  
  273.                                 // 1. If something is on the blacklist, we always block it, even if it is on the whitelist, too
  274.                                 $banned = explode(',', OIDplus::config()->getValue('attachments_block_extensions', ''));
  275.                                 foreach ($banned as $ext) {
  276.                                         $ext = trim($ext);
  277.                                         if ($ext == '') continue;
  278.                                         if (strtolower(substr($fname, -strlen($ext)-1)) == strtolower('.'.$ext)) {
  279.                                                 throw new OIDplusException(_L('The file extension "%1" is banned by the administrator (it can be uploaded by the administrator though)',$ext));
  280.                                         }
  281.                                 }
  282.  
  283.                                 // 2. Something on the whitelist is always OK
  284.                                 $allowed = explode(',', OIDplus::config()->getValue('attachments_allow_extensions', ''));
  285.                                 $is_whitelisted = false;
  286.                                 foreach ($allowed as $ext) {
  287.                                         $ext = trim($ext);
  288.                                         if ($ext == '') continue;
  289.                                         if (strtolower(substr($fname, -strlen($ext)-1)) == strtolower('.'.$ext)) {
  290.                                                 $is_whitelisted = true;
  291.                                                 break;
  292.                                         }
  293.                                 }
  294.  
  295.                                 // 3. For everything that is neither whitelisted, nor blacklisted, the admin can decide if these grey zone is allowed or blocked
  296.                                 if (!$is_whitelisted) {
  297.                                         if (!OIDplus::config()->getValue('attachments_allow_grey_extensions', '1')) {
  298.                                                 $tmp = explode('.', $fname);
  299.                                                 $ext = array_pop($tmp);
  300.                                                 throw new OIDplusException(_L('The file extension "%1" is not on the whitelist (it can be uploaded by the administrator though)',$ext));
  301.                                         }
  302.                                 }
  303.                         }
  304.  
  305.                         $req_filename = $_FILES['userfile']['name'];
  306.                         if (strpos($req_filename, '/') !== false) throw new OIDplusException(_L('Illegal file name'));
  307.                         if (strpos($req_filename, '\\') !== false) throw new OIDplusException(_L('Illegal file name'));
  308.                         if (strpos($req_filename, '..') !== false) throw new OIDplusException(_L('Illegal file name'));
  309.                         if (strpos($req_filename, chr(0)) !== false) throw new OIDplusException(_L('Illegal file name'));
  310.  
  311.                         $uploaddir = self::getUploadDir($id);
  312.                         $uploadfile = $uploaddir . DIRECTORY_SEPARATOR . basename($req_filename);
  313.  
  314.                         if (!is_dir($uploaddir)) {
  315.                                 @mkdir($uploaddir, 0777, true);
  316.                                 if (!is_dir($uploaddir)) {
  317.                                         OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Upload attachment '".basename($uploadfile)."' to object '$id' failed: Cannot create directory '".basename($uploaddir)."' (problem with permissions?)");
  318.                                         $msg = _L('Upload attachment "%1" to object "%2" failed',basename($uploadfile),$id).': '._L('Cannot create directory "%1" (problem with permissions?)',basename($uploaddir));
  319.                                         if (OIDplus::authUtils()->isAdminLoggedIn()) {
  320.                                                 throw new OIDplusException($msg);
  321.                                         } else {
  322.                                                 throw new OIDplusException($msg.'. '._L('Please contact the system administrator.'));
  323.                                         }
  324.                                 }
  325.                         }
  326.  
  327.                         if (!@move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
  328.                                 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?)");
  329.                                 $msg = _L('Upload attachment "%1" to object "%2" failed',basename($uploadfile),$id).': '._L('Cannot move uploaded file into directory (problem with permissions?)');
  330.                                 if (OIDplus::authUtils()->isAdminLoggedIn()) {
  331.                                         throw new OIDplusException($msg);
  332.                                 } else {
  333.                                         throw new OIDplusException($msg.'. '._L('Please contact the system administrator.'));
  334.                                 }
  335.                         }
  336.  
  337.                         OIDplus::logger()->log("[OK]OID($id)+[?INFO/!OK]OIDRA($id)?/[?INFO/!OK]A?", "Uploaded attachment '".basename($uploadfile)."' to object '$id'");
  338.  
  339.                         return array("status" => 0);
  340.                 } else {
  341.                         return parent::action($actionID, $params);
  342.                 }
  343.         }
  344.  
  345.         /**
  346.          * @param bool $html
  347.          * @return void
  348.          * @throws OIDplusException
  349.          */
  350.         public function init(bool $html=true) {
  351.                 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) {
  352.                         // TODO: check if a blacklist entry is also on the whitelist (which is not allowed)
  353.                 });
  354.                 OIDplus::config()->prepareConfigKey('attachments_allow_extensions', 'Allow (whitelist) file name extensions being used in file attachments (comma separated)', '', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  355.                         // TODO: check if a whitelist entry is also on the blacklist (which is not allowed)
  356.                 });
  357.                 OIDplus::config()->prepareConfigKey('attachments_allow_grey_extensions', 'Should file-extensions which are neither be on the whitelist, nor be at the blacklist, be allowed? (1=Yes, 0=No)', '1', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  358.                         if (!is_numeric($value) || ($value < 0) || ($value > 1)) {
  359.                                 throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).'));
  360.                         }
  361.                 });
  362.                 OIDplus::config()->prepareConfigKey('attachments_allow_ra_delete', 'Allow that RAs delete file attachments? (0=no, 1=yes)', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  363.                         if (!is_numeric($value) || ($value < 0) || ($value > 1)) {
  364.                                 throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).'));
  365.                         }
  366.                 });
  367.                 OIDplus::config()->prepareConfigKey('attachments_allow_ra_upload', 'Allow that RAs upload file attachments? (0=no, 1=yes)', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  368.                         if (!is_numeric($value) || ($value < 0) || ($value > 1)) {
  369.                                 throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).'));
  370.                         }
  371.                 });
  372.  
  373.                 $info_txt  = 'Alternative directory for attachments. It must contain a file named "';
  374.                 $info_txt .= self::DIR_UNLOCK_FILE;
  375.                 $info_txt .= '"';
  376.                 if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
  377.                         $info_txt .= ' with the same owner as index.php';
  378.                 }
  379.                 $info_txt .= '. If this setting is empty, then the userdata directory is used.';
  380.                 OIDplus::config()->prepareConfigKey('attachment_upload_dir', $info_txt, '', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  381.                         if (trim($value) !== '') {
  382.                                 self::checkUploadDir($value);
  383.                         }
  384.                 });
  385.         }
  386.  
  387.         /**
  388.          * @param string $id
  389.          * @param array $out
  390.          * @param bool $handled
  391.          * @return void
  392.          */
  393.         public function gui(string $id, array &$out, bool &$handled) {
  394.                 // Nothing
  395.         }
  396.  
  397.         /**
  398.          * @param array $out
  399.          * @return void
  400.          */
  401.         public function publicSitemap(array &$out) {
  402.                 // Nothing
  403.         }
  404.  
  405.         /**
  406.          * @param array $json
  407.          * @param string|null $ra_email
  408.          * @param bool $nonjs
  409.          * @param string $req_goto
  410.          * @return bool
  411.          */
  412.         public function tree(array &$json, string $ra_email=null, bool $nonjs=false, string $req_goto=''): bool {
  413.                 return false;
  414.         }
  415.  
  416.         /**
  417.          * Convert amount of bytes to human-friendly name
  418.          * @param int $bytes
  419.          * @param int $decimals
  420.          * @return string
  421.          */
  422.         private static function convert_filesize(int $bytes, int $decimals = 2): string {
  423.                 $size = array(_L('Bytes'),_L('KiB'),_L('MiB'),_L('GiB'),_L('TiB'),_L('PiB'),_L('EiB'),_L('ZiB'),_L('YiB'));
  424.                 $factor = floor((strlen("$bytes") - 1) / 3);
  425.                 return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor];
  426.         }
  427.  
  428.         /**
  429.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_2
  430.          * @param string $id
  431.          * @param string $title
  432.          * @param string $icon
  433.          * @param string $text
  434.          * @return void
  435.          */
  436.         public function modifyContent(string $id, string &$title, string &$icon, string &$text) {
  437.                 $output = '';
  438.                 $doshow = false;
  439.  
  440.                 try {
  441.                         $upload_dir = self::getUploadDir($id);
  442.                         $files = @glob($upload_dir . DIRECTORY_SEPARATOR . '*');
  443.                         $found_files = false;
  444.  
  445.                         $obj = OIDplusObject::parse($id);
  446.                         if (!$obj) throw new OIDplusException(_L('Invalid object "%1"',$id));
  447.                         $can_upload = OIDplus::authUtils()->isAdminLoggedIn() || ($this->raMayUpload() && $obj->userHasWriteRights());
  448.                         $can_delete = OIDplus::authUtils()->isAdminLoggedIn() || ($this->raMayDelete() && $obj->userHasWriteRights());
  449.  
  450.                         if (OIDplus::authUtils()->isAdminLoggedIn()) {
  451.                                 $output .= '<p>'._L('Admin info: The directory is %1','<b>'.htmlentities($upload_dir).'</b>').'</p>';
  452.                                 $doshow = true;
  453.                         }
  454.  
  455.                         $output .= '<div id="fileattachments_table" class="table-responsive">';
  456.                         $output .= '<table class="table table-bordered table-striped">';
  457.                         $output .= '<thead>';
  458.                         $output .= '<tr>';
  459.                         $output .= '<th>'._L('Filename').'</th>';
  460.                         $output .= '<th>'._L('Size').'</th>';
  461.                         $output .= '<th>'._L('File type').'</th>';
  462.                         $output .= '<th>'._L('Download').'</th>';
  463.                         if ($can_delete) $output .= '<th>'._L('Delete').'</th>';
  464.                         $output .= '</tr>';
  465.                         $output .= '</thead>';
  466.                         $output .= '<tbody>';
  467.                         if ($files) foreach ($files as $file) {
  468.                                 if (is_dir($file)) continue;
  469.  
  470.                                 $output .= '<tr>';
  471.                                 $output .= '<td>'.htmlentities(basename($file)).'</td>';
  472.                                 $output .= '<td>'.htmlentities(self::convert_filesize(filesize($file), 0)).'</td>';
  473.                                 $lookup_files = array(
  474.                                         OIDplus::localpath().'userdata/attachments/filetypes$'.OIDplus::getCurrentLang().'.conf',
  475.                                         OIDplus::localpath().'userdata/attachments/filetypes.conf',
  476.                                         OIDplus::localpath().'vendor/danielmarschall/fileformats/filetypes$'.OIDplus::getCurrentLang().'.local', // not recommended
  477.                                         OIDplus::localpath().'vendor/danielmarschall/fileformats/filetypes.local', // not recommended
  478.                                         OIDplus::localpath().'vendor/danielmarschall/fileformats/filetypes$'.OIDplus::getCurrentLang().'.conf',
  479.                                         OIDplus::localpath().'vendor/danielmarschall/fileformats/filetypes.conf'
  480.                                 );
  481.                                 $output .= '<td>'.htmlentities(\VtsFileTypeDetect::getDescription($file, $lookup_files)).'</td>';
  482.  
  483.                                 $output .= '     <td><button type="button" name="download_'.md5($file).'" id="download_'.md5($file).'" class="btn btn-success btn-xs download" onclick="OIDplusPagePublicAttachments.downloadAttachment('.js_escape(OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE)).', current_node,'.js_escape(basename($file)).')">'._L('Download').'</button></td>';
  484.                                 if ($can_delete) {
  485.                                         $output .= '     <td><button type="button" name="delete_'.md5($file).'" id="delete_'.md5($file).'" class="btn btn-danger btn-xs delete" onclick="OIDplusPagePublicAttachments.deleteAttachment(current_node,'.js_escape(basename($file)).')">'._L('Delete').'</button></td>';
  486.                                 }
  487.  
  488.                                 $output .= '</tr>';
  489.                                 $doshow = true;
  490.                                 $found_files = true;
  491.                         }
  492.                         $output .= '</tbody>';
  493.  
  494.                         if (!$found_files) {
  495.                                 $output .= '<tfoor>';
  496.                                 $output .= '<tr><td colspan="' . ($can_delete ? 5 : 4) . '"><i>' . _L('No attachments') . '</i></td></tr>';
  497.                                 $output .= '</tfoot>';
  498.                         }
  499.  
  500.                         $output .= '</table></div>';
  501.  
  502.                         if ($can_upload) {
  503.                                 $output .= '<form action="javascript:void(0);" onsubmit="return OIDplusPagePublicAttachments.uploadAttachmentOnSubmit(this);" enctype="multipart/form-data" id="uploadAttachmentForm">';
  504.                                 $output .= '<input type="hidden" name="id" value="'.htmlentities($id).'">';
  505.                                 $output .= '<div>'._L('Add a file attachment').':<input type="file" name="userfile" value="" id="fileAttachment">';
  506.                                 $output .= '<br><input type="submit" value="'._L('Upload').'"></div>';
  507.                                 $output .= '</form>';
  508.                                 $doshow = true;
  509.                         }
  510.                 } catch (\Exception $e) {
  511.                         $doshow = true;
  512.                         $output = '<p>'.$e->getMessage().'</p>';
  513.                 }
  514.  
  515.                 $output = '<h2>'._L('File attachments').'</h2>' .
  516.                           '<div class="container box">' .
  517.                           $output .
  518.                           '</div>';
  519.                 if ($doshow) $text .= $output;
  520.         }
  521.  
  522.         /**
  523.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  524.          * @param string $id
  525.          * @return void
  526.          */
  527.         public function beforeObjectDelete(string $id) {}
  528.  
  529.         /**
  530.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  531.          * @param string $id
  532.          * @return void
  533.          * @throws OIDplusException
  534.          */
  535.         public function afterObjectDelete(string $id) {
  536.                 // Delete the attachment folder including all files in it (note: Subfolders are not possible)
  537.                 $uploaddir = self::getUploadDir($id);
  538.                 if ($uploaddir != '') {
  539.                         $ary = @glob($uploaddir . DIRECTORY_SEPARATOR . '*');
  540.                         if ($ary) foreach ($ary as $a) @unlink($a);
  541.                         @rmdir($uploaddir);
  542.                         if (is_dir($uploaddir)) {
  543.                                 OIDplus::logger()->log("[WARN]OID($id)+[WARN]A!", "Attachment directory '$uploaddir' could not be deleted during the deletion of the OID");
  544.                         }
  545.                 }
  546.         }
  547.  
  548.         /**
  549.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  550.          * @param string $id
  551.          * @param array $params
  552.          * @return void
  553.          */
  554.         public function beforeObjectUpdateSuperior(string $id, array &$params) {}
  555.  
  556.         /**
  557.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  558.          * @param string $id
  559.          * @param array $params
  560.          * @return void
  561.          */
  562.         public function afterObjectUpdateSuperior(string $id, array &$params) {}
  563.  
  564.         /**
  565.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  566.          * @param string $id
  567.          * @param array $params
  568.          * @return void
  569.          */
  570.         public function beforeObjectUpdateSelf(string $id, array &$params) {}
  571.  
  572.         /**
  573.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  574.          * @param string $id
  575.          * @param array $params
  576.          * @return void
  577.          */
  578.         public function afterObjectUpdateSelf(string $id, array &$params) {}
  579.  
  580.         /**
  581.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  582.          * @param string $id
  583.          * @param array $params
  584.          * @return void
  585.          */
  586.         public function beforeObjectInsert(string $id, array &$params) {}
  587.  
  588.         /**
  589.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3
  590.          * @param string $id
  591.          * @param array $params
  592.          * @return void
  593.          */
  594.         public function afterObjectInsert(string $id, array &$params) {}
  595.  
  596.         /**
  597.          * @param string $request
  598.          * @return array|false
  599.          */
  600.         public function tree_search(string $request) {
  601.                 return false;
  602.         }
  603.  
  604.         /**
  605.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4
  606.          * @param string $id
  607.          * @param array $out
  608.          * @return void
  609.          * @throws OIDplusException
  610.          */
  611.         public function whoisObjectAttributes(string $id, array &$out) {
  612.                 $xmlns = 'oidplus-attachment-plugin';
  613.                 $xmlschema = 'urn:oid:1.3.6.1.4.1.37476.2.5.2.4.1.95.1';
  614.                 $xmlschemauri = OIDplus::webpath(__DIR__.'/attachments.xsd',OIDplus::PATH_ABSOLUTE);
  615.  
  616.                 $files = @glob(self::getUploadDir($id) . DIRECTORY_SEPARATOR . '*');
  617.                 if ($files) foreach ($files as $file) {
  618.                         $url = OIDplus::webpath(__DIR__,OIDplus::PATH_ABSOLUTE).'download.php?id='.urlencode($id).'&filename='.urlencode(basename($file));
  619.  
  620.                         $out[] = array(
  621.                                 'xmlns' => $xmlns,
  622.                                 'xmlschema' => $xmlschema,
  623.                                 'xmlschemauri' => $xmlschemauri,
  624.                                 'name' => 'attachment-name',
  625.                                 'value' => basename($file)
  626.                         );
  627.  
  628.                         $out[] = array(
  629.                                 'xmlns' => $xmlns,
  630.                                 'xmlschema' => $xmlschema,
  631.                                 'xmlschemauri' => $xmlschemauri,
  632.                                 'name' => 'attachment-url',
  633.                                 'value' => $url
  634.                         );
  635.                 }
  636.  
  637.         }
  638.  
  639.         /**
  640.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4
  641.          * @param string $email
  642.          * @param array $out
  643.          * @return void
  644.          */
  645.         public function whoisRaAttributes(string $email, array &$out) {}
  646. }
  647.