Subversion Repositories oidplus

Rev

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