Element.class.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. <?php
  2. /**
  3. * Define a fileor directory element.
  4. * @author Valentin CARRUESCO
  5. * @category Plugin
  6. * @license MIT
  7. */
  8. class Element extends Entity{
  9. public $id,$path,$label,$type,$extension,$size,$creatorSite,$icon,$thumbnail,$childNumber,$link;
  10. protected $TABLE_NAME = 'document_element';
  11. public $fields =
  12. array(
  13. 'id' => 'key',
  14. 'path' => 'longstring',
  15. 'label' => 'string',
  16. 'type' => 'string',
  17. 'extension' => 'string',
  18. 'size' => 'string',
  19. 'creatorSite' => 'int'
  20. );
  21. //Récupération de la racine de la GED
  22. public static function root(){
  23. return File::dir().'documents'.SLASH;
  24. }
  25. public function save(){
  26. $this->path = str_replace('\\','/',$this->path);
  27. parent::save();
  28. }
  29. //Récuperation du chemin ou du flux de thumbnail associé à l'élement
  30. public function thumbnail(){
  31. if(!in_array($this->extension, array('jpg','png','jpeg','gif','bmp','jfif'))) return $this->icon();
  32. if(!file_exists(self::root().'.thumbnails')) mkdir(self::root().'.thumbnails',755,true);
  33. $thumbname = str_replace(array('\\','./'),array('/',''),$this->path);
  34. $thumbnail = self::root().'.thumbnails'.SLASH.base64_encode($thumbname).'.'.$this->extension;
  35. if(!file_exists($thumbnail)){
  36. $osPath = File::convert_decoding($this->path);
  37. copy(self::root().$osPath,$thumbnail);
  38. Image::resize($thumbnail,200,200);
  39. }
  40. $path ='data:image/'.$this->extension.';base64,'.base64_encode(file_get_contents($thumbnail));
  41. return $path;
  42. }
  43. //Récuperation du chemin di'one à l'élement
  44. public function icon(){
  45. $path = 'plugin/document/img/file-types/';
  46. $icon = $this->type == 'directory' ? 'folder.svg' : 'document.svg';
  47. switch($this->extension){
  48. case 'docx':
  49. case 'doc':
  50. case 'dot':
  51. case 'dotx':
  52. $icon = 'word.svg';
  53. break;
  54. case 'xls':
  55. case 'xlsx':
  56. case 'csv':
  57. $icon = 'excel.svg';
  58. break;
  59. case 'ppt':
  60. case 'pptx':
  61. $icon = 'powerpoint.svg';
  62. break;
  63. case 'pdf':
  64. $icon = 'pdf.svg';
  65. break;
  66. case 'mp3':
  67. case 'wav':
  68. case 'mp4':
  69. case 'flac':
  70. $icon = 'music.svg';
  71. break;
  72. case 'html':
  73. case 'htm':
  74. $icon = 'url.svg';
  75. break;
  76. case 'msg':
  77. $icon = 'mail.svg';
  78. break;
  79. case 'psd':
  80. $icon = 'photoshop.svg';
  81. break;
  82. case 'rss':
  83. $icon = 'feed.svg';
  84. break;
  85. case 'mdb':
  86. $icon = 'access.svg';
  87. break;
  88. case 'bat':
  89. case 'sh':
  90. case 'cmd':
  91. $icon = 'gear.svg';
  92. break;
  93. case 'php':
  94. case 'js':
  95. case 'java':
  96. case 'class':
  97. case 'css':
  98. case 'c':
  99. case 'cpp':
  100. case 'py':
  101. $icon = 'code.svg';
  102. break;
  103. case 'jpg':
  104. case 'jpeg':
  105. case 'png':
  106. case 'bmp':
  107. case 'gif':
  108. case 'svg':
  109. case 'jfif':
  110. $icon = 'image.svg';
  111. break;
  112. }
  113. $path .= $icon;
  114. return $path;
  115. }
  116. //Récuperation d'un element base depuis un chemin physique, ou d'un objet element rensiegné a partir du path si non existant en base (prend du utf8 en entré)
  117. public static function fromPath($path){
  118. $element = new self();
  119. //convertion utf8 ISO-8859-1
  120. $osPath = File::convert_decoding($path);
  121. $infos = pathinfo($path);
  122. //Gestion label
  123. $element->label = mt_basename($path);
  124. $element->path = str_replace(array(self::root(),'\\'),array('','/'),$path);
  125. $element->type = is_dir($osPath) ? 'directory' : 'file';
  126. $element->link = is_link($osPath);
  127. $exists = file_exists($osPath);
  128. if($element->type == 'directory'){
  129. $path .= SLASH;
  130. $fi = new FilesystemIterator($osPath, FilesystemIterator::SKIP_DOTS);
  131. $element->childNumber = iterator_count($fi);
  132. $element->size = 0;
  133. } else {
  134. if(isset($infos['extension'])) $element->extension = $infos['extension'];
  135. $element->size = !$exists ? 0 : filesize($osPath);
  136. }
  137. $element->updated = !$exists ? 0 : filemtime($osPath);
  138. $element->created = !$exists ? 0 : filectime($osPath);
  139. $relativePath = trim(str_replace(Element::root(),'',$path),SLASH);
  140. if ($baseElement = Element::load(array('path'=>str_replace('\\','/',$relativePath) ))) {
  141. $element->creator = $baseElement->creator;
  142. $element->id = $baseElement->id;
  143. }
  144. $element->path = trim($element->path,'/');
  145. return $element;
  146. }
  147. //Récuperation de la liste des élements d'un repertoire (tiens compte des droits utilisateur) (attends de l'utf8)
  148. public static function browse($scanned,$folderOnly = false){
  149. global $myUser,$myFirm;
  150. require_once(__DIR__.SLASH.'ElementRight.class.php');
  151. $osScanned = File::convert_decoding($scanned);
  152. $elements = array();
  153. User::check_access('document','read');
  154. //Récuperation du tableau des rang utilisateur sous forme d'id.
  155. $ranks = array();
  156. if(!$myUser->superadmin){
  157. foreach($myUser->ranks[$myFirm->id] as $rank)
  158. $ranks[] = $rank->id;
  159. }
  160. $existingPathes = array();
  161. //Fix glob et utilisation d'un dossier avec des [] (glob utilise ce char pour de la regex, on doit escape)
  162. $osScanned = str_replace(array('[',']'),array('\[','\]'), $osScanned);
  163. $osScanned = str_replace(array('\[','\]'), array('[[]','[]]'), $osScanned);
  164. //Pour chaque fichier physique existant
  165. foreach(glob($osScanned) as $osPath){
  166. $osPath = str_replace(SLASH.'.'.SLASH, SLASH, $osPath);
  167. $basePath = File::convert_encoding($osPath);
  168. if((substr(mt_basename($osPath), 0,1)=='.') || ($folderOnly && !is_dir($osPath))) continue;
  169. //Récuperation/création de l'element objet correspondant (frompath n'accepte que l'utf8, on lui donne le basepath)
  170. $line = Element::fromPath($basePath);
  171. $existingPathes[] = $line->path;
  172. //Si l'element correspondant n'existe pas en base, on le créé
  173. if($line->id == ''){
  174. $line->creator = 'anonymous';
  175. $line->save();
  176. }
  177. //Si l'utilisateur n'est pas le créateur, pas un super admin et n'a pas le droit configure sur les documents on check les partages de droits
  178. if($myUser->login != $line->creator && !$myUser->can('document','configure') && !$myUser->superadmin){
  179. $can = false;
  180. $pathes = array();
  181. $path = '';
  182. //Récuperation des chemins dont on doit récuperer les droits :
  183. // ex : pour le fichier a/b/c les chemins sont a, a/b, et a/b/c
  184. foreach(explode('/',$line->path) as $i=>$pathCrumb){
  185. if($i!=0) $path.= '/';
  186. $path.= $pathCrumb;
  187. $pathes[] = $path;
  188. }
  189. //On récupere les droits pour chaques chemin
  190. foreach(ElementRight::staticQuery('SELECT er.*,el.path as '.Element::tableName().'_join_path
  191. FROM {{table}} er
  192. LEFT JOIN '.Element::tableName().' el ON el.id=er.element
  193. WHERE (er.entity=? OR (er.entity=? AND er.uid=?) OR (er.entity=? AND er.uid IN ('.str_repeat('?,',count($ranks)-1).'?)))
  194. AND element IN(SELECT id from '.Element::tableName().' WHERE path IN ('.str_repeat('?,',count($pathes)-1).'?)) ',array_merge(array("all","user",$myUser->login,"rank"),$ranks,$pathes),true,1) as $right){
  195. $element = $right->join('element');
  196. //Si les droits sont sur un dossier parent et sont recursifs OU si les droits sont sur le fichier concerné on continue la verification
  197. if($right->element == $line->id || ($right->recursive && strpos($line->path, $element->path)!==false ) )
  198. $can = true;
  199. }
  200. if(!$can) continue;
  201. }
  202. $elements[] = $line;
  203. }
  204. return $elements;
  205. }
  206. //Téléchargement d'un fichier ou d'un repertoire (prends de l'utf 8 en entrée)
  207. public static function download($path){
  208. $osPath = File::convert_decoding($path);
  209. if(!file_exists($osPath)) return '';
  210. $element = Element::fromPath($path);
  211. if(!self::hasRight($element,'read')) throw new Exception("Permissions insuffisantes",403);
  212. if(is_dir($osPath)){
  213. $filename = rand().'-'.time().'.zip';
  214. $filepath = sys_get_temp_dir().SLASH.$filename;
  215. $zip = new ZipArchive;
  216. $res = $zip->open($filepath, ZipArchive::CREATE);
  217. if ($res === TRUE) {
  218. //Fix glob et utilisation d'un dossier avec des [] (glob utilise ce char pour de la regex, on doit escape)
  219. $osPath = str_replace(array('[',']'),array('\[','\]'), $osPath);
  220. $osPath = str_replace(array('\[','\]'), array('[[]','[]]'), $osPath);
  221. $ressources = glob_recursive($osPath.SLASH.'*') ;
  222. foreach($ressources as $resource){
  223. if(is_dir($resource)) continue;
  224. $filename = $resource;
  225. $filename = File::convert_encoding($filename);
  226. $filename = str_replace($path,mt_basename($path),$filename);
  227. $filename=iconv("UTF-8", "IBM850", $filename);
  228. if(!$zip->addFromString($filename, file_get_contents($resource))) throw new Exception("Impossible de compresser le fichier ".$path);
  229. }
  230. if(count($ressources)==0)
  231. $zip->addEmptyDir('.');
  232. $zip->close();
  233. }
  234. $stream = file_get_contents($filepath);
  235. unlink($filepath);
  236. }else{
  237. $stream = file_get_contents($osPath);
  238. }
  239. return $stream;
  240. }
  241. //Creation d'un raccourcis (prends de l'utf 8 en entrée)
  242. public static function shortcut($path,$destination,$readonly = false){
  243. $osDestination = File::convert_decoding($destination);
  244. if(file_exists($osDestination)) return false;
  245. $element = Element::fromPath($destination);
  246. if($readonly){
  247. require_once(__DIR__.SLASH.'ElementRight.class.php');
  248. $right = new ElementRight();
  249. $right->element = $element->id;
  250. $right->read = true;
  251. $right->edit = false;
  252. $right->delete = false;
  253. $right->recursive = false;
  254. $right->save();
  255. }
  256. return symlink($path,$osDestination);
  257. }
  258. //Supression d'un répértoire ou d'un fichier en base et en physique (prends de l'utf 8 en entrée)
  259. public static function remove($path){
  260. require_once(__DIR__.SLASH.'ElementRight.class.php');
  261. $osPath = File::convert_decoding($path);
  262. if(!file_exists($osPath)) return;
  263. $element = Element::fromPath($path);
  264. $dbPath = str_replace('\\','/',$element->path);
  265. if(!self::hasRight($element,'edit')) throw new Exception("Permissions insuffisantes",403);
  266. if(is_dir($osPath) && !is_link($osPath)) {
  267. self::remove_dir_recursive($osPath);
  268. Element::staticQuery('DELETE FROM {{table}} WHERE `path` = ? OR `path` LIKE ?',array($dbPath,$dbPath.'/%'));
  269. } else {
  270. unlink($osPath);
  271. Element::delete(array('path'=>$dbPath));
  272. }
  273. ElementRight::delete(array('element'=>$element->id));
  274. }
  275. //Copie d'un répértoire ou d'un fichier en base et en physique (prends de l'utf 8 en entrée)
  276. public static function copy($path,$to, $checkExist = true){
  277. $osPath = File::convert_decoding($path);
  278. $osTo = File::convert_decoding($to);
  279. if(!file_exists($osPath)) return;
  280. $element = Element::fromPath($path);
  281. if($checkExist && file_exists($osTo)) return false;
  282. $parentPathTo = dirname($to);
  283. $parentOsPathTo = dirname($osTo);
  284. if(!file_exists($parentOsPathTo)) throw new Exception("Dossier de destination inexistant",404);
  285. $parentTo = Element::fromPath($parentPathTo);
  286. if(!self::hasRight($parentTo,'read')) throw new Exception("Permissions insuffisantes",403);
  287. if(!self::hasRight($element,'read')) throw new Exception("Permissions insuffisantes",403);
  288. $dbPath = str_replace('\\','/',$element->path);
  289. if($element->type == 'directory'){
  290. //Si c'est un dossier, on récupere tous les élements commendant par ce nom afin de gerer leurs chemins
  291. $baseElement = Element::staticQuery('SELECT * FROM {{table}} WHERE `path` = ? OR `path` LIKE ?',array($dbPath,$dbPath.'/%'), true);
  292. } else {
  293. $baseElement = Element::load(array('path'=>$dbPath));
  294. if(is_object($baseElement) && $baseElement->id != 0) $element->id = $baseElement->id;
  295. }
  296. if(!File::copy($osPath,$osTo)) throw new Exception('Erreur lors de la copie...');
  297. $newelement = Element::fromPath($to);
  298. if(is_array($baseElement)){
  299. foreach ($baseElement as $elem) {
  300. if($elem->path == $dbPath) $elem->label = $element->label;
  301. $elem->path = str_replace($dbPath, $newelement->path, $elem->path);
  302. $elem->id = 0;
  303. $elem->save();
  304. }
  305. } else {
  306. $baseElement->path = $newelement->path;
  307. $baseElement->label = $newelement->label;
  308. $baseElement->id = 0;
  309. $baseElement->save();
  310. }
  311. }
  312. //Déplacement d'un répertoire ou d'un fichier en base et en physique
  313. public static function move($path,$to, $label=''){
  314. $osPath = File::convert_decoding($path);
  315. $osTo = File::convert_decoding($to);
  316. if(!file_exists($osPath)) return;
  317. $element = Element::fromPath($path);
  318. //Si le dossier se termine par un ., on supprime ce . (non pris en charge par l'os)
  319. if($element->type=="directory" && substr($to, -1,1) =='.')
  320. $to = substr($to, 0,strlen($to)-1);
  321. if($to == $path) return $element;
  322. if(!self::hasRight($element,'edit')) throw new Exception("Permissions insuffisantes",403);
  323. $parentPathTo = dirname($to);
  324. $parentPathOsTo = dirname($osTo);
  325. if(!file_exists($parentPathOsTo)) throw new Exception("Dossier de destination inexistant",404);
  326. $parentTo = Element::fromPath($parentPathTo);
  327. if(!self::hasRight($parentTo,'edit')) throw new Exception("Permissions insuffisantes",403);
  328. $dbPath = str_replace('\\','/',$element->path);
  329. if(file_exists($osTo)) return false;
  330. if($element->type == 'directory'){
  331. $element->path = $oldPath = $element->path;
  332. //Si c'est un dossier, on récupere tous les élements commencant par ce nom afin de modifier leurs chemins
  333. $baseElement = Element::staticQuery('SELECT * FROM {{table}} WHERE `path` = ? OR `path` LIKE ?',array($dbPath,$dbPath.'/%'), true);
  334. } else {
  335. $baseElement = Element::load(array('path'=>$dbPath));
  336. if(is_object($baseElement) && $baseElement->id != 0) $element->id = $baseElement->id;
  337. }
  338. try {
  339. if(!rename($osPath,$osTo))
  340. throw new Exception('Un élément du même nom existe déjà.');
  341. } catch (Exception $e) {
  342. return false;
  343. }
  344. $toElement = Element::fromPath($to);
  345. //Si type est image, on supprime l'ancienne miniature
  346. if(in_array($element->extension, array('jpg','png','jpeg','gif','bmp')) && file_exists(self::root().'.thumbnails'.SLASH.base64_encode($element->path).'.'.$element->extension))
  347. unlink(self::root().'.thumbnails'.SLASH.base64_encode($element->path).'.'.$element->extension);
  348. if(is_array($baseElement)){
  349. foreach ($baseElement as $elem) {
  350. if($elem->path == $oldPath)
  351. $elem->label = $toElement->label;
  352. $elem->path = str_replace($dbPath, $toElement->path, $elem->path);
  353. $elem->save();
  354. }
  355. } else {
  356. $baseElement->path = $toElement->path;
  357. $baseElement->label = $toElement->label;
  358. $baseElement->save();
  359. }
  360. return $toElement;
  361. }
  362. //Supprime les caractères interdits ou non utf8 (ex le "’" de word )
  363. public static function convertName($name){
  364. $name = iconv("UTF-8","UTF-8//IGNORE",$name);
  365. $name = str_replace(array('’','»','«'), array("'",'"','"'), $name);
  366. return $name;
  367. }
  368. //Ajout d'un fichier en base et en physique (prend de l'utf8 en entré pour le path)
  369. public static function addFile($path,$stream){
  370. //on filtre les noms non utf8 ou interdits
  371. $path = dirname($path).SLASH.self::convertName(basename($path));
  372. //on enregistre le fichier dans l'encodage du système
  373. $osPath = File::convert_decoding($path);
  374. $parentPath = dirname($path);
  375. $parentOsPath = File::convert_decoding($parentPath);
  376. if(!file_exists($parentOsPath)) throw new Exception("Dossier de destination inexistant",404);
  377. $parent = Element::fromPath($parentPath);
  378. if(!self::hasRight($parent,'edit')) throw new Exception("Permissions insuffisantes",403);
  379. file_put_contents($osPath, $stream);
  380. $element = Element::fromPath($path, array('preview'=>false));
  381. if(!self::hasRight($element,'edit')) throw new Exception("Permissions insuffisantes",403);
  382. $element->save();
  383. return $element;
  384. }
  385. //Ajout d'un dossier en base et en physique (prend de l'utf8 en entré pour le path)
  386. public static function addFolder($path, $recursive = false, $checkRight=true, $creator = null){
  387. //on filtre les noms non utf8 ou interdits
  388. $path = dirname($path).SLASH.self::convertName(basename($path));
  389. $osPath = File::convert_decoding($path);
  390. $parentPath = dirname($path);
  391. $osParentPath = dirname($osPath);
  392. if(!file_exists($osParentPath)) {
  393. if(!$recursive){
  394. throw new Exception("Dossier parent ".$parentPath." inexistant",404);
  395. } else {
  396. self::addFolder($parentPath, $recursive, $checkRight, $creator);
  397. }
  398. }
  399. $parent = Element::fromPath($parentPath);
  400. if($checkRight && !self::hasRight($parent,'edit')) throw new Exception("Permissions insuffisantes",403);
  401. if(file_exists($osPath)) return;
  402. mkdir($osPath,0755,$recursive);
  403. $element = Element::fromPath($path);
  404. if(isset($creator)) $element->creator = $creator;
  405. $element->save();
  406. return $element;
  407. }
  408. public static function hasRight($element,$type){
  409. global $myUser,$myFirm;
  410. require_once(__DIR__.SLASH.'ElementRight.class.php');
  411. $documentRight = '';
  412. switch($type){
  413. case 'read':
  414. $documentRight = 'read';
  415. break;
  416. case 'delete':
  417. case 'edit':
  418. $documentRight = 'edit';
  419. break;
  420. }
  421. if(!$myUser->can('document',$type)) return false;
  422. if($myUser->login == $element->creator || $myUser->can('document','configure') || $myUser->superadmin == 1) return true;
  423. $allPathes = array();
  424. $rootPath = '';
  425. foreach (explode('/',$element->path) as $i=>$crumb) {
  426. $rootPath .= ($i==0?'':'/').$crumb;
  427. $allPathes[] = $rootPath;
  428. }
  429. $userRanks = array();
  430. foreach ($myUser->ranks[$myFirm->id] as $rank)
  431. $userRanks[] = $rank->id;
  432. $data = array();
  433. $query = 'SELECT dr.recursive,dr.edit,dr.read,de.path
  434. FROM {{table}} dr
  435. LEFT JOIN '.Element::tableName().' de ON de.id = dr.element
  436. WHERE dr.element IN(
  437. SELECT id
  438. FROM '.Element::tableName().'
  439. WHERE path IN ('.str_repeat('?,',count($allPathes)-1).'?)
  440. ) AND (dr.entity=? OR (dr.entity=? AND dr.uid=?) ';
  441. $data = array_merge($allPathes, array("all","user",$myUser->login));
  442. if(count($userRanks)!=0){
  443. $query .= ' OR (dr.entity=? AND dr.uid IN ('.str_repeat('?,',count($userRanks)-1).'?)) ';
  444. $data = array_merge($data,array("rank"),$userRanks);
  445. }
  446. $query .= ') ORDER BY CHAR_LENGTH(de.path)';
  447. $result = ElementRight::staticQuery($query,$data);
  448. $rights = array(
  449. 'edit'=> false,
  450. 'read'=> false
  451. );
  452. foreach($result->fetchAll() as $line){
  453. //si le droit n'est pas récursif et que le chemin associé n'est pas exactement cleui ciblé on ignore ce droit
  454. if($line['recursive'] != 1 && $line['path']!=$element->path) continue;
  455. if($line['edit'] == 1) $rights['edit'] = true;
  456. if($line['read'] == 1) $rights['read'] = true;
  457. }
  458. if(!$rights[$type]) return false;
  459. return true;
  460. }
  461. public static function remove_dir_recursive($path) {
  462. //Fix glob et utilisation d'un dossier avec des [] (glob utilise ce char pour de la regex, on doit escape)
  463. $globPath = $path.SLASH.'{,.}*';
  464. $globPath = str_replace(array('[',']'),array('\[','\]'), $globPath);
  465. $globPath = str_replace(array('\[','\]'), array('[[]','[]]'), $globPath);
  466. foreach(glob($globPath,GLOB_BRACE) as $element){
  467. if(mt_basename($element) == '.' || mt_basename($element) == '..') continue;
  468. if(is_dir($element)) {
  469. self::remove_dir_recursive($element);
  470. } else {
  471. unlink($element);
  472. }
  473. }
  474. rmdir($path);
  475. }
  476. }
  477. ?>