document.plugin.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. <?php
  2. //Déclaration d'un item de menu dans le menu principal
  3. function document_menu(&$menuItems){
  4. global $myUser;
  5. if(!$myUser->can('document','read')) return;
  6. $menuItems[] = array(
  7. 'sort'=>4,
  8. 'url'=>'index.php?module=document',
  9. 'label'=>'Documents',
  10. 'icon'=> 'far fa-copy',
  11. 'color'=> '#FFBA00'
  12. );
  13. }
  14. //Cette fonction va generer une page quand on clique sur document dans menu
  15. function document_page(){
  16. global $_;
  17. if(!isset($_['module']) || $_['module'] !='document') return;
  18. $page = !isset($_['page']) ? 'list' : $_['page'];
  19. $file = __DIR__.SLASH.'page.'.$page.'.php';
  20. if(!file_exists($file)) throw new Exception("Page ".$page." inexistante");
  21. require_once($file);
  22. }
  23. //Fonction executée lors de l'activation du plugin
  24. function document_install($id){
  25. if($id != 'fr.core.document') return;
  26. Entity::install(__DIR__);
  27. if(!file_exists(Element::root()))
  28. mkdir(Element::root(),0755,true);
  29. if(!file_exists(__DIR__.SLASH.'logs'))
  30. mkdir(__DIR__.SLASH.'logs',0755,true);
  31. global $conf;
  32. $conf->put('document_allowed_extensions','csv,xls,xlsx,doc,docx,dotm,dotx,pdf,png,jpg,jpeg,gif,tif,svg,bmp,txt,zip,gzip,msg');
  33. $conf->put('document_allowed_size',10485760);
  34. }
  35. //Fonction executée lors de la désactivation du plugin
  36. function document_uninstall($id){
  37. if($id != 'fr.core.document') return;
  38. Entity::uninstall(__DIR__);
  39. }
  40. //Déclaration des sections de droits du plugin
  41. //Déclaration des sections de droits du plugin
  42. Right::register('document',array('label'=>'Gestion des droits sur le plugin document'));
  43. //Déclaration du menu de réglages
  44. function document_menu_setting(&$settingMenu){
  45. global $myUser;
  46. if($myUser->can('document','configure')) {
  47. $settingMenu[]= array(
  48. 'sort' =>4,
  49. 'url' => 'setting.php?section=document',
  50. 'icon' => 'fas fa-angle-right',
  51. 'label' => 'Documents'
  52. );
  53. }
  54. }
  55. //Déclaration des pages de réglages
  56. function document_content_setting(){
  57. global $_;
  58. if(file_exists(__DIR__.SLASH.'setting.'.$_['section'].'.php'))
  59. require_once(__DIR__.SLASH.'setting.'.$_['section'].'.php');
  60. }
  61. //Vérifie qu'un nom de fichier ou de dossier ne contient pas des caractères interdits par l'os (?:*|<>/\)
  62. function document_check_element_name($element){
  63. preg_match('/[?:*|<>\/\\\\]/', $element, $match);
  64. return !empty($match) ? $match[0] : '';
  65. }
  66. function document_dav_document($requested){
  67. global $conf,$myUser;
  68. $requested = trim($requested, '/');
  69. if(substr($requested, 0,13)!='dav/documents') return;
  70. if(empty($requested)) throw new Exception("Unspecified DAV path");
  71. if($conf->get('document_enable_dav') != "1"){
  72. header('HTTP/1.1 501 Method not implemented');
  73. header('Content-Type: text/html; charset=utf-8');
  74. throw new Exception('Mode Webdav désactivé, veuillez débloquer le Webdav dans les paramétrages pour accéder à cette fonctionnalité');
  75. }
  76. require_once(__ROOT__.'common.php');
  77. require_once(__DIR__.SLASH.'Element.class.php');
  78. require_once(__DIR__.SLASH.'WebDav.class.php');
  79. $projectPath = preg_replace('|https?\:\/\/'.$_SERVER['HTTP_HOST'].'|i', '', ROOT_URL);
  80. $server = new WebDav();
  81. $server->logs = WebDav::logPath().SLASH.'dav-logs.txt';
  82. $server->lockfile = WebDav::logPath().SLASH.'dav-lock.json';
  83. $server->root = str_replace('//','/',$projectPath.'/dav/documents/');
  84. $server->folder = Element::root();
  85. //Windows cherche desktop.ini a chaque ouverture de dossier pour le custom
  86. $server->ignoreFiles[] = 'desktop.ini';
  87. //Tortoise et autre client git d'explorer cherchent les .git a chaques ouverture
  88. $server->ignoreFiles[] = '.git';
  89. if($conf->get('document_enable_dav_logs') == "1"){
  90. $server->on['log'] = function($message){
  91. Log::put(nl2br($message),'DAV');
  92. };
  93. }
  94. $server->do['login'] = function($login,$password){
  95. global $myUser;
  96. if($myUser->login !=''){
  97. return $myUser;
  98. }
  99. $myUser = User::check($login,$password);
  100. if(!$myUser)
  101. throw new Exception("Problème lors de la connexion, veuillez contacter l'administrateur",401);
  102. if(file_exists('enabled.maintenance') && $myUser->superadmin != 1)
  103. throw new Exception('Seul un compte Super Admin peut se connecter en mode maintenance',401);
  104. if(!$myUser->connected())
  105. throw new Exception('Identifiant ou mot de passe incorrect',401);
  106. if(is_numeric($myUser->preference('default_firm')) && $myUser->haveFirm($myUser->preference('default_firm'))){
  107. $_SESSION['firm'] = serialize(Firm::getById($myUser->preference('default_firm')));
  108. if(!$_SESSION['firm'])
  109. throw new Exception("Problème lors de la connexion, veuillez contacter l'administrateur",401);
  110. }else if(count($myUser->firms)!=0){
  111. $_SESSION['firm'] = serialize(reset($myUser->firms));
  112. if(!$_SESSION['firm'])
  113. throw new Exception(" Problème lors de la connexion, veuillez contacter l'administrateur",401);
  114. }else{
  115. throw new Exception('Ce compte n\'est actif sur aucune firm',401);
  116. }
  117. $myFirm = isset($_SESSION['firm']) ? unserialize($_SESSION['firm']) : new Firm();
  118. if(!$myFirm)
  119. throw new Exception("Problème lors de la connexion, veuillez contacter l'administrateur",401);
  120. $_SESSION['currentUser'] = serialize($myUser);
  121. if(!$_SESSION['currentUser'])
  122. throw new Exception("Problème lors de la connexion, veuillez contacter l'administrateur",401);
  123. };
  124. $server->do['delete'] = function($isoPath){
  125. global $myUser,$conf;
  126. $utf8Path = utf8_encode($isoPath);
  127. Element::remove($utf8Path);
  128. if($conf->get('document_enable_logs')) Log::put('Suppression de '.$utf8Path,'document');
  129. };
  130. $server->do['download'] = function($isoPath){
  131. global $myUser,$_,$conf;
  132. $utf8Path = utf8_encode($isoPath);
  133. //pour verfiier si le fichier existe, on récupere son chemin système avec le bon encodage
  134. $osPath = get_OS() === 'WIN' ? $isoPath: $utf8Path;
  135. if(!file_exists($osPath)) throw new Exception("Fichier inexistant",404);
  136. if(!is_file($osPath)) throw new Exception("Méthode non autorisée sur autre chose qu'un fichier",501);
  137. $stream = Element::download($utf8Path);
  138. if($conf->get('document_enable_logs_verbose')) Log::put('Téléchargement de '.$utf8Path,'document');
  139. return $stream;
  140. };
  141. $server->do['edit'] = function($isoPath,$body,$type){
  142. global $myUser,$conf;
  143. User::check_access('document','edit');
  144. $utf8Path = utf8_encode($isoPath);
  145. if($type=='file'){
  146. $maxSize = $conf->get('document_allowed_size');
  147. $extension = getExt($utf8Path);
  148. $extensions = explode(',',str_replace(' ', '', $conf->get('document_allowed_extensions')));
  149. if(strlen($body) > $maxSize) throw new Exception("Taille du fichier trop grande, taille maximum :".readable_size($maxSize).' ('.$maxSize.' octets)',406);
  150. if(!in_array($extension , $extensions)) throw new Exception("Extension '".$extension."' du fichier ".$path." non permise, autorisé :".implode(', ',$extensions),415);
  151. $element = Element::addFile($utf8Path, $body);
  152. if($conf->get('document_enable_logs') || $conf->get('document_enable_logs_verbose')) Log::put('Enregistrement fichier '.$utf8Path,'document');
  153. }else{
  154. Element::addFolder($utf8Path);
  155. if($conf->get('document_enable_logs') || $conf->get('document_enable_logs_verbose')) Log::put('Enregistrement dossier '.$utf8Path,'document');
  156. }
  157. $element = Element::fromPath($utf8Path);
  158. $baseElement = Element::load(array('path'=>$element->path));
  159. if(is_object($baseElement) && $baseElement->id!=0) $element->id = $baseElement->id;
  160. $element->save();
  161. };
  162. $server->do['move'] = function($isoPath,$isoTo){
  163. global $myUser,$conf;
  164. $utf8Path = utf8_encode($isoPath);
  165. $utf8To = utf8_encode($isoTo);
  166. User::check_access('document','edit');
  167. $char = document_check_element_name(mt_basename($utf8To));
  168. if(!empty($char)) throw new Exception("Caractères interdits : ".$char);
  169. Element::move($utf8Path,$utf8To);
  170. if($conf->get('document_enable_logs') || $conf->get('document_enable_logs_verbose')) Log::put('Déplacement de '.$utf8Path.' dans '.$utf8To,'document');
  171. };
  172. $server->do['copy'] = function($isoPath,$isoTo){
  173. global $myUser,$conf;
  174. $utf8Path = utf8_encode($isoPath);
  175. $utf8To = utf8_encode($isoTo);
  176. User::check_access('document','edit');
  177. $char = document_check_element_name(mt_basename($utf8To));
  178. if(!empty($char)) throw new Exception("Caractères interdits : ".$char);
  179. Element::copy($utf8Path,$utf8To);
  180. if($conf->get('document_enable_logs') || $conf->get('document_enable_logs_verbose')) Log::put('Copie de '.$utf8Path.' dans '.$utf8To,'document');
  181. };
  182. $server->do['list'] = function($isoPath,$depth =0){
  183. global $myUser,$conf;
  184. $utf8Path = utf8_encode($isoPath);
  185. User::check_access('document','read');
  186. //pour verfier si le fichier existe, on récupere son chemin système avec le bon encodage
  187. $osPath = get_OS() === 'WIN' ? $isoPath: $utf8Path;
  188. if(!file_exists($osPath)) throw new Exception("Not found", 404);
  189. $files = array();
  190. //Si la cible est la racine dav, on la retourne sous forme d'element fictif, sinon on utilise le systeme d'element standard
  191. if($utf8Path == Element::root()){
  192. $file = new Element();
  193. $file->label = '';
  194. $file->type = 'directory';
  195. $file->path = '';
  196. $toScan = array($file);
  197. }else{
  198. $toScan = array(Element::fromPath($utf8Path));
  199. }
  200. if($conf->get('document_enable_logs_verbose')) Logs::put('visualisation de '.$utf8Path,'document');
  201. //Si le dav demande un scan en profondeur du dossier, on scan les enfants du dossier ciblé
  202. if($depth>0)
  203. $toScan = array_merge($toScan,Element::browse($utf8Path.SLASH.'*'));
  204. foreach($toScan as $element){
  205. //on convertis l'utf8 de l'element pour passer en iso webdav windows si le serveur est sous windows
  206. $path = Element::root().(get_OS() === 'WIN' ? utf8_decode( $element->path) : $element->path );
  207. $file = array(
  208. 'type' => $element->type,
  209. 'label' => $element->label
  210. );
  211. if($element->type == 'directory'){
  212. $stat = stat($path);
  213. $file['create_time'] = $stat['ctime'];
  214. $file['update_time'] = $stat['mtime'];
  215. $file['length'] = $stat['size'];
  216. }else{
  217. $file['create_time'] = filectime($path);
  218. $file['update_time'] = filemtime($path);
  219. $file['length'] = filesize($path);
  220. }
  221. $files[] = $file;
  222. }
  223. return $files;
  224. };
  225. $server->start();
  226. }
  227. //Déclaration des settings de base
  228. //Types possibles : text,select ( + "values"=> array('1'=>'Val 1'),password,checkbox. Un simple string définit une catégorie.
  229. Configuration::setting('document',array(
  230. "Général",
  231. 'document_allowed_extensions' => array("label"=>"Extensions autorisées","type"=>"text","legend"=>"(séparés par virgules)","placeholder"=>"csv,pdf,jpg,..."),
  232. 'document_allowed_size' => array("label"=>"Taille maximum autorisée","type"=>"text","legend"=>"(en octets)","placeholder"=>"28060000","type"=>"number"),
  233. 'document_enable_logs' => array("label"=>"Activer les logs standards","legend"=>"(ajout, supression, modifification d'un fichier ou dossier)","type"=>"boolean"),
  234. 'document_enable_logs_verbose' => array("label"=>"Activer les logs avancés","legend"=>"(lecture ou téléchargement d'un fichier ou dossier)","type"=>"boolean"),
  235. "Options DAV <br><small style='font-size:65%;'>Adresse WebDAV : <code>".
  236. ( substr(ROOT_URL,-1) == '/' ? substr(ROOT_URL,0,(strlen(ROOT_URL)-1)) : ROOT_URL )
  237. ."/dav/documents</code><br>
  238. Les Identifiants sont identiques à ceux de l'erp<br>
  239. Si vous souhaitez vous connecter avec un lecteur réseau windows, vous devez installer un certificat https</small>",
  240. 'document_enable_dav' => array("label"=>"Activer le WebDav","type"=>"boolean"),
  241. 'document_enable_dav_logs' => array("label"=>"Activer les logs WebDav ","type"=>"boolean"),
  242. 'document_client_directory' => array("label"=>"Chemin vers le dossier document des clients","placeholder"=>"documents/Affaires/{{slug}}","type"=>"text"),
  243. ));
  244. function document_widget(&$widgets){
  245. global $myUser;
  246. require_once(__DIR__.SLASH.'..'.SLASH.'dashboard'.SLASH.'DashboardWidget.class.php');
  247. $modelWidget = new DashboardWidget();
  248. $modelWidget->model = 'document';
  249. $modelWidget->title = 'Documents';
  250. $modelWidget->icon = 'fas fa-file';
  251. $modelWidget->background = '#A3CB38';
  252. $modelWidget->callback = 'init_components';
  253. $modelWidget->load = 'action.php?action=document_widget_load';
  254. if($myUser->can('document','configure')){
  255. $modelWidget->configure = 'action.php?action=document_widget_configure';
  256. $modelWidget->configure_callback = 'document_widget_configure_save';
  257. }
  258. $modelWidget->js = [Plugin::url().'/js/widget.js?v='.time()];
  259. $modelWidget->css = [Plugin::url().'/css/widget.css?v=1'.time()];
  260. $modelWidget->description = "Affiche un espace document";
  261. $widgets[] = $modelWidget;
  262. }
  263. function document_client_menu(&$clientMenu,$client){
  264. global $myUser;
  265. if(!$myUser->can('document','read')) return;
  266. $menu = new MenuItem();
  267. $menu->label = 'Documents';
  268. $menu->url = '#tab=document';
  269. $menu->slug = 'document';
  270. $menu->sort = 10;
  271. $clientMenu[] = $menu;
  272. }
  273. function document_client_page($slug){
  274. if($slug != 'document') return;
  275. require_once(__DIR__.SLASH.'tab.client.php');
  276. }
  277. function document_client_merge($clientBase,$clientToMerge,&$logs){
  278. global $_,$myUser,$conf;
  279. Plugin::need('client/Client');
  280. require_once(__DIR__.SLASH.'Element.class.php');
  281. $logs[] = ' Migration des documents attaches ';
  282. $clientDirectoryTemplate = empty($conf->get('document_client_directory')) ? 'Clients'.SLASH.'{{slug}}' : $conf->get('document_client_directory');
  283. $toMergeRelativeDirectory = str_replace('{{slug}}',$clientToMerge->slug,$clientDirectoryTemplate);
  284. $toMergeDirectory = Element::root().$toMergeRelativeDirectory;
  285. if(!file_exists($toMergeDirectory)) return;
  286. $baseRelativeDirectory = str_replace('{{slug}}',$clientBase->slug,$clientDirectoryTemplate);
  287. $baseDirectory = Element::root().$baseRelativeDirectory;
  288. if(!file_exists($baseDirectory)) mkdir($baseDirectory,0755,true);
  289. File::merge($baseDirectory,$toMergeDirectory);
  290. }
  291. $api = new Api("document", "Api de gestion des documents");
  292. $api->route('element/search','retourne une liste de documents','GET',function($request,&$response){
  293. global $myUser,$_;
  294. $_ = $request['parameters'];
  295. if(!$myUser->connected()) throw new Exception("Credentials are missing",401);
  296. Action::run('document_element_search',$response);
  297. });
  298. $api->route('element/upload','Upload un fichier dans la racine spécifiée','POST',function($request,&$response){
  299. global $myUser,$_;
  300. if(isset($_SERVER['CONTENT_TYPE'])){
  301. if(preg_match('/multipart\/(related|form); boundary=(.*)/is', $_SERVER['CONTENT_TYPE'],$match)){
  302. $boundary = $match[2];
  303. $parts = explode('--'.$boundary, $request['body']);
  304. $parts = array_filter($parts);
  305. $contents = array();
  306. foreach ($parts as $part) {
  307. if(empty(trim($part))) continue;
  308. list($header,$body) = explode("\n\n",$part,2);
  309. $content = array(
  310. 'header' => array(),
  311. 'body'=>substr($body,0,-2)
  312. );
  313. foreach(explode("\n",substr($header,1)) as $header){
  314. $headerInfos = explode(':',$header);
  315. $content['header'][$headerInfos[0]] = $headerInfos[1];
  316. }
  317. $contents[] = $content;
  318. }
  319. }
  320. }
  321. $i = 0;
  322. foreach($contents as $content){
  323. //fichier
  324. if(isset($content['header']['Content-Disposition']) && strpos($content['header']['Content-Disposition'], 'filename="') !==-1){
  325. preg_match('/filename="(.*)"/', $content['header']['Content-Disposition'],$matches);
  326. $_FILES['file']['name'][$i] = $matches[1];
  327. $_FILES['file']['size'][$i] = strlen($content['body']);
  328. $tmpfname = tempnam(sys_get_temp_dir(), 'up_');
  329. file_put_contents($tmpfname , $content['body']);
  330. $_FILES['file']['tmp_name'][$i] = $tmpfname;
  331. $i++;
  332. }
  333. if(strpos($content['header']['Content-Type'], 'application/json')!==false){
  334. $_ = json_decode($content['body'],true);
  335. }
  336. }
  337. if(!isset($contents)){
  338. $_ = json_decode($request['body'],true);
  339. }
  340. if(!$_) throw new Exception("Chaine json incorrecte ".$request['body']);
  341. if(!$myUser->connected()) throw new Exception("Credentials are missing",401);
  342. Action::run('document_element_upload',$response);
  343. });
  344. $api->register();
  345. //Cette fonction comprend toutes les actions du plugin qui ne nécessitent pas de vue html
  346. require_once(__DIR__.SLASH.'action.php');
  347. function document_cron($time){
  348. global $_;
  349. if(date('H:i', $time)!='03:00' && !isset($_['force-document'])) return;
  350. require_once(__DIR__.SLASH.'Element.class.php');
  351. require_once(__DIR__.SLASH.'ElementRight.class.php');
  352. $toDelete = array();
  353. $i = 0;
  354. $ids = array();
  355. foreach(Element::staticQuery('SELECT `id`,`path` FROM {{table}}')->fetchAll() as $row){
  356. $osPath = Element::root().str_replace('/',SLASH, $row['path']);
  357. if(file_exists($osPath)) continue;
  358. echo $row['path'].PHP_EOL;
  359. $ids[] = $row['id'];
  360. if($i>500){
  361. echo 'Delete pack'.PHP_EOL;
  362. Element::delete(array('id:IN'=>$ids));
  363. ElementRight::delete(array('element:IN'=>$ids));
  364. $ids = array();
  365. $i = 0;
  366. }
  367. $i++;
  368. }
  369. }
  370. //Déclation des assets
  371. Plugin::addCss("/css/main.css");
  372. Plugin::addCss("/css/document.api.css");
  373. Plugin::addJs("/js/component.js",true);
  374. Plugin::addJs("/js/document.api.js");
  375. Plugin::addJs("/js/main.js");
  376. //Mapping hook / fonctions
  377. Plugin::addHook("widget", "document_widget");
  378. Plugin::addHook("install", "document_install");
  379. Plugin::addHook("uninstall", "document_uninstall");
  380. Plugin::addHook("menu_main", "document_menu");
  381. Plugin::addHook("page", "document_page");
  382. Plugin::addHook("menu_setting", "document_menu_setting");
  383. Plugin::addHook("content_setting", "document_content_setting");
  384. Plugin::addHook("rewrite", "document_dav_document");
  385. Plugin::addHook("cron", "document_cron");
  386. Plugin::addHook("client_menu", "document_client_menu");
  387. Plugin::addHook("client_page", "document_client_page");
  388. Plugin::addHook("client_merge", "document_client_merge");
  389. ?>