fileUpload.lib.php 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use Chamilo\CoreBundle\Framework\Container;
  4. use Chamilo\CourseBundle\Entity\CDocument;
  5. use Symfony\Component\HttpFoundation\File\UploadedFile;
  6. /**
  7. * FILE UPLOAD LIBRARY.
  8. *
  9. * This is the file upload library for Chamilo.
  10. * Include/require it in your code to use its functionality.
  11. *
  12. * @package chamilo.library
  13. *
  14. * @todo test and reorganise
  15. */
  16. /**
  17. * Changes the file name extension from .php to .phps
  18. * Useful for securing a site.
  19. *
  20. * @author Hugues Peeters <peeters@ipm.ucl.ac.be>
  21. *
  22. * @param string $file_name Name of a file
  23. *
  24. * @return string the filename phps'ized
  25. */
  26. function php2phps($file_name)
  27. {
  28. return preg_replace('/\.(php.?|phtml.?)(\.){0,1}.*$/i', '.phps', $file_name);
  29. }
  30. /**
  31. * Renames .htaccess & .HTACCESS to htaccess.txt.
  32. *
  33. * @param string $filename
  34. *
  35. * @return string
  36. */
  37. function htaccess2txt($filename)
  38. {
  39. return str_replace(['.htaccess', '.HTACCESS'], ['htaccess.txt', 'htaccess.txt'], $filename);
  40. }
  41. /**
  42. * This function executes our safety precautions
  43. * more functions can be added.
  44. *
  45. * @param string $filename
  46. *
  47. * @return string
  48. *
  49. * @see php2phps()
  50. * @see htaccess2txt()
  51. */
  52. function disable_dangerous_file($filename)
  53. {
  54. return htaccess2txt(php2phps($filename));
  55. }
  56. /**
  57. * Returns the name without extension, used for the title.
  58. *
  59. * @param string $name
  60. *
  61. * @return name without the extension
  62. */
  63. function get_document_title($name)
  64. {
  65. // If they upload .htaccess...
  66. $name = disable_dangerous_file($name);
  67. $ext = substr(strrchr($name, '.'), 0);
  68. if (empty($ext)) {
  69. return substr($name, 0, strlen($name));
  70. }
  71. return substr($name, 0, strlen($name) - strlen(strstr($name, $ext)));
  72. }
  73. /**
  74. * This function checks if the upload succeeded.
  75. *
  76. * @param array $uploaded_file ($_FILES)
  77. *
  78. * @return true if upload succeeded
  79. */
  80. function process_uploaded_file($uploaded_file, $show_output = true)
  81. {
  82. // Checking the error code sent with the file upload.
  83. if (isset($uploaded_file['error'])) {
  84. switch ($uploaded_file['error']) {
  85. case 1:
  86. // The uploaded file exceeds the upload_max_filesize directive in php.ini.
  87. if ($show_output) {
  88. Display::addFlash(
  89. Display::return_message(
  90. get_lang('The uploaded file exceeds the maximum filesize allowed by the server:').ini_get('upload_max_filesize'),
  91. 'error'
  92. )
  93. );
  94. }
  95. return false;
  96. case 2:
  97. // The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.
  98. // Not used at the moment, but could be handy if we want to limit the size of an upload
  99. // (e.g. image upload in html editor).
  100. $max_file_size = (int) $_POST['MAX_FILE_SIZE'];
  101. if ($show_output) {
  102. Display::addFlash(
  103. Display::return_message(
  104. get_lang('The file size exceeds the maximum allowed setting:').format_file_size($max_file_size),
  105. 'error'
  106. )
  107. );
  108. }
  109. return false;
  110. case 3:
  111. // The uploaded file was only partially uploaded.
  112. if ($show_output) {
  113. Display::addFlash(
  114. Display::return_message(
  115. get_lang('The uploaded file was only partially uploaded.').' '.get_lang('Please Try Again!'),
  116. 'error'
  117. )
  118. );
  119. }
  120. return false;
  121. case 4:
  122. // No file was uploaded.
  123. if ($show_output) {
  124. Display::addFlash(
  125. Display::return_message(
  126. get_lang('No file was uploaded.').' '.get_lang('Please select a file before pressing the upload button.'),
  127. 'error'
  128. )
  129. );
  130. }
  131. return false;
  132. }
  133. }
  134. if (!file_exists($uploaded_file['tmp_name'])) {
  135. // No file was uploaded.
  136. if ($show_output) {
  137. Display::addFlash(Display::return_message(get_lang('The file upload has failed.'), 'error'));
  138. }
  139. return false;
  140. }
  141. if (file_exists($uploaded_file['tmp_name'])) {
  142. $filesize = filesize($uploaded_file['tmp_name']);
  143. if (empty($filesize)) {
  144. // No file was uploaded.
  145. if ($show_output) {
  146. Display::addFlash(
  147. Display::return_message(
  148. get_lang('The file upload has failed.SizeIsZero'),
  149. 'error'
  150. )
  151. );
  152. }
  153. return false;
  154. }
  155. }
  156. $course_id = api_get_course_id();
  157. //Checking course quota if we are in a course
  158. if (!empty($course_id)) {
  159. $max_filled_space = DocumentManager::get_course_quota();
  160. // Check if there is enough space to save the file
  161. if (!DocumentManager::enough_space($uploaded_file['size'], $max_filled_space)) {
  162. if ($show_output) {
  163. Display::addFlash(
  164. Display::return_message(
  165. get_lang('There is not enough space to upload this file.'),
  166. 'error'
  167. )
  168. );
  169. }
  170. return false;
  171. }
  172. }
  173. // case 0: default: We assume there is no error, the file uploaded with success.
  174. return true;
  175. }
  176. /**
  177. * This function does the save-work for the documents.
  178. * It handles the uploaded file and adds the properties to the database
  179. * If unzip=1 and the file is a zipfile, it is extracted
  180. * If we decide to save ALL kinds of documents in one database,
  181. * we could extend this with a $type='document', 'scormdocument',...
  182. *
  183. * @param array $courseInfo
  184. * @param array $uploadedFile ($_FILES)
  185. * array(
  186. * 'name' => 'picture.jpg',
  187. * 'tmp_name' => '...', // absolute path
  188. * );
  189. * @param string $documentDir Example: /var/www/chamilo/courses/ABC/document
  190. * @param string $uploadPath Example: /folder1/folder2/
  191. * @param int $userId
  192. * @param int $groupId group.id
  193. * @param int $toUserId User ID, or NULL for everybody
  194. * @param int $unzip 1/0
  195. * @param string $whatIfFileExists overwrite, rename or warn if exists (default)
  196. * @param bool $output optional output parameter
  197. * @param bool $onlyUploadFile
  198. * @param string $comment
  199. * @param int $sessionId
  200. * @param bool $treat_spaces_as_hyphens
  201. * @param string $uploadKey
  202. * @param int $parentId
  203. * @param $content
  204. *
  205. * So far only use for unzip_uploaded_document function.
  206. * If no output wanted on success, set to false.
  207. *
  208. * @return CDocument|false
  209. */
  210. function handle_uploaded_document(
  211. $courseInfo,
  212. $uploadedFile,
  213. $documentDir,
  214. $uploadPath,
  215. $userId,
  216. $groupId = 0,
  217. $toUserId = null,
  218. $unzip = 0,
  219. $whatIfFileExists = '',
  220. $output = true,
  221. $onlyUploadFile = false,
  222. $comment = null,
  223. $sessionId = null,
  224. $treat_spaces_as_hyphens = true,
  225. $uploadKey = '',
  226. $parentId = 0,
  227. $content = null
  228. ) {
  229. if (!$userId) {
  230. return false;
  231. }
  232. $userInfo = api_get_user_info();
  233. $uploadedFile['name'] = stripslashes($uploadedFile['name']);
  234. // Add extension to files without one (if possible)
  235. $uploadedFile['name'] = add_ext_on_mime($uploadedFile['name'], $uploadedFile['type']);
  236. $sessionId = (int) $sessionId;
  237. if (empty($sessionId)) {
  238. $sessionId = api_get_session_id();
  239. }
  240. $group = api_get_group_entity($groupId);
  241. // Just in case process_uploaded_file is not called
  242. $maxSpace = DocumentManager::get_course_quota();
  243. // Check if there is enough space to save the file
  244. if (!DocumentManager::enough_space($uploadedFile['size'], $maxSpace)) {
  245. if ($output) {
  246. Display::addFlash(Display::return_message(get_lang('There is not enough space to upload this file.'), 'error'));
  247. }
  248. return false;
  249. }
  250. // If the want to unzip, check if the file has a .zip (or ZIP,Zip,ZiP,...) extension
  251. if ($unzip == 1 && preg_match('/.zip$/', strtolower($uploadedFile['name']))) {
  252. return unzip_uploaded_document(
  253. $courseInfo,
  254. $userInfo,
  255. $uploadedFile,
  256. $uploadPath,
  257. $documentDir,
  258. $maxSpace,
  259. $sessionId,
  260. $groupId,
  261. $output,
  262. $onlyUploadFile,
  263. $whatIfFileExists
  264. );
  265. } elseif ($unzip == 1 && !preg_match('/.zip$/', strtolower($uploadedFile['name']))) {
  266. // We can only unzip ZIP files (no gz, tar,...)
  267. if ($output) {
  268. Display::addFlash(
  269. Display::return_message(
  270. get_lang('The file you selected was not a zip file.')." ".get_lang('Please Try Again!'),
  271. 'error'
  272. )
  273. );
  274. }
  275. return false;
  276. } else {
  277. // Clean up the name, only ASCII characters should stay. (and strict)
  278. $cleanName = api_replace_dangerous_char($uploadedFile['name'], $treat_spaces_as_hyphens);
  279. // No "dangerous" files
  280. $cleanName = disable_dangerous_file($cleanName);
  281. // Checking file extension
  282. if (!filter_extension($cleanName)) {
  283. if ($output) {
  284. Display::addFlash(
  285. Display::return_message(
  286. get_lang('File upload failed: this file extension or file type is prohibited'),
  287. 'error'
  288. )
  289. );
  290. }
  291. return false;
  292. } else {
  293. // If the upload path differs from / (= root) it will need a slash at the end
  294. if ($uploadPath != '/') {
  295. $uploadPath = $uploadPath.'/';
  296. }
  297. // Full path to where we want to store the file with trailing slash
  298. $whereToSave = $documentDir.$uploadPath;
  299. // Just upload the file "as is"
  300. if ($onlyUploadFile) {
  301. $errorResult = moveUploadedFile($uploadedFile, $whereToSave.$cleanName);
  302. if ($errorResult) {
  303. return $whereToSave.$cleanName;
  304. }
  305. return $errorResult;
  306. }
  307. /*
  308. Based in the clean name we generate a new filesystem name
  309. Using the session_id and group_id if values are not empty
  310. */
  311. $fileSystemName = DocumentManager::fixDocumentName(
  312. $cleanName,
  313. 'file',
  314. $courseInfo,
  315. $sessionId,
  316. $groupId
  317. );
  318. // Name of the document without the extension (for the title)
  319. $documentTitle = get_document_title($uploadedFile['name']);
  320. // Size of the uploaded file (in bytes)
  321. $fileSize = $uploadedFile['size'];
  322. // Example: /folder/picture.jpg
  323. $filePath = $uploadPath.$fileSystemName;
  324. $docId = DocumentManager::get_document_id(
  325. $courseInfo,
  326. $filePath,
  327. $sessionId
  328. );
  329. $documentRepo = Container::getDocumentRepository();
  330. $document = $documentRepo->find($docId);
  331. if (!($content instanceof UploadedFile)) {
  332. $request = Container::getRequest();
  333. $content = $request->files->get($uploadKey);
  334. if (is_array($content)) {
  335. $content = $content[0];
  336. }
  337. }
  338. // What to do if the target file exists
  339. switch ($whatIfFileExists) {
  340. // Overwrite the file if it exists
  341. case 'overwrite':
  342. if ($document) {
  343. // Update file size
  344. update_existing_document(
  345. $courseInfo,
  346. $document->getIid(),
  347. $uploadedFile['size']
  348. );
  349. $document = DocumentManager::addFileToDocument(
  350. $document,
  351. $filePath,
  352. $content,
  353. null,
  354. null,
  355. $group
  356. );
  357. // Display success message with extra info to user
  358. if ($document && $output) {
  359. Display::addFlash(
  360. Display::return_message(
  361. get_lang('File upload succeeded!').'<br /> '.
  362. $document->getTitle().' '.get_lang(' was overwritten.'),
  363. 'confirmation',
  364. false
  365. )
  366. );
  367. }
  368. return $document;
  369. } else {
  370. // Put the document data in the database
  371. $document = DocumentManager::addDocument(
  372. $courseInfo,
  373. $filePath,
  374. 'file',
  375. $fileSize,
  376. $documentTitle,
  377. $comment,
  378. 0,
  379. null,
  380. $groupId,
  381. $sessionId,
  382. 0,
  383. true,
  384. $content
  385. );
  386. // Display success message to user
  387. if ($document) {
  388. Display::addFlash(
  389. Display::return_message(
  390. get_lang('File upload succeeded!').'<br /> '.$documentTitle,
  391. 'confirmation',
  392. false
  393. )
  394. );
  395. }
  396. return $document;
  397. }
  398. break;
  399. case 'rename':
  400. // Rename the file if it exists
  401. $cleanName = DocumentManager::getUniqueFileName(
  402. $uploadPath,
  403. $cleanName,
  404. $courseInfo,
  405. $sessionId,
  406. $groupId
  407. );
  408. $fileSystemName = DocumentManager::fixDocumentName(
  409. $cleanName,
  410. 'file',
  411. $courseInfo,
  412. $sessionId,
  413. $groupId
  414. );
  415. $documentTitle = disable_dangerous_file($cleanName);
  416. $filePath = $uploadPath.$fileSystemName;
  417. // Put the document data in the database
  418. $document = DocumentManager::addDocument(
  419. $courseInfo,
  420. $filePath,
  421. 'file',
  422. $fileSize,
  423. $documentTitle,
  424. $comment, // comment
  425. 0, // read only
  426. true, // save visibility
  427. $groupId,
  428. $sessionId,
  429. 0,
  430. true,
  431. $content,
  432. $parentId
  433. );
  434. // Display success message to user
  435. if ($output && $document) {
  436. Display::addFlash(
  437. Display::return_message(
  438. get_lang('File upload succeeded!').'<br />'.
  439. get_lang('File saved as').' '.$document->getTitle(),
  440. 'success',
  441. false
  442. )
  443. );
  444. }
  445. return $document;
  446. break;
  447. case 'nothing':
  448. if ($document) {
  449. if ($output) {
  450. Display::addFlash(
  451. Display::return_message(
  452. $uploadPath.$cleanName.' '.get_lang(' already exists.'),
  453. 'warning',
  454. false
  455. )
  456. );
  457. }
  458. break;
  459. }
  460. // no break
  461. default:
  462. // Only save the file if it doesn't exist or warn user if it does exist
  463. if ($document) {
  464. if ($output) {
  465. Display::addFlash(
  466. Display::return_message($cleanName.' '.get_lang(' already exists.'), 'warning', false)
  467. );
  468. }
  469. } else {
  470. // Put the document data in the database
  471. $document = DocumentManager::addDocument(
  472. $courseInfo,
  473. $filePath,
  474. 'file',
  475. $fileSize,
  476. $documentTitle,
  477. $comment,
  478. 0,
  479. true,
  480. $groupId,
  481. $sessionId,
  482. 0,
  483. true,
  484. $content
  485. );
  486. if ($document) {
  487. // Display success message to user
  488. if ($output) {
  489. Display::addFlash(
  490. Display::return_message(
  491. get_lang('File upload succeeded!').'<br /> '.$documentTitle,
  492. 'confirm',
  493. false
  494. )
  495. );
  496. }
  497. return $document;
  498. } else {
  499. if ($output) {
  500. Display::addFlash(
  501. Display::return_message(
  502. get_lang('The uploaded file could not be saved (perhaps a permission problem?)'),
  503. 'error',
  504. false
  505. )
  506. );
  507. }
  508. }
  509. }
  510. break;
  511. }
  512. }
  513. }
  514. }
  515. /**
  516. * @param string $file
  517. * @param string $storePath
  518. *
  519. * @return bool
  520. */
  521. function moveUploadedFile($file, $storePath)
  522. {
  523. $handleFromFile = isset($file['from_file']) && $file['from_file'] ? true : false;
  524. $moveFile = isset($file['move_file']) && $file['move_file'] ? true : false;
  525. if ($moveFile) {
  526. $copied = copy($file['tmp_name'], $storePath);
  527. if (!$copied) {
  528. return false;
  529. }
  530. }
  531. if ($handleFromFile) {
  532. return file_exists($file['tmp_name']);
  533. } else {
  534. return move_uploaded_file($file['tmp_name'], $storePath);
  535. }
  536. }
  537. /**
  538. * Checks if there is enough place to add a file on a directory
  539. * on the base of a maximum directory size allowed
  540. * deprecated: use enough_space instead!
  541. *
  542. * @author Hugues Peeters <peeters@ipm.ucl.ac.be>
  543. *
  544. * @param int $file_size Size of the file in byte
  545. * @param string $dir Path of the directory where the file should be added
  546. * @param int $max_dir_space Maximum size of the diretory in byte
  547. *
  548. * @return bool true if there is enough space, false otherwise
  549. *
  550. * @see enough_size() uses dir_total_space() function
  551. */
  552. function enough_size($file_size, $dir, $max_dir_space)
  553. {
  554. // If the directory is the archive directory, safely ignore the size limit
  555. if (api_get_path(SYS_ARCHIVE_PATH) == $dir) {
  556. return true;
  557. }
  558. if ($max_dir_space) {
  559. $already_filled_space = dir_total_space($dir);
  560. if (($file_size + $already_filled_space) > $max_dir_space) {
  561. return false;
  562. }
  563. }
  564. return true;
  565. }
  566. /**
  567. * Computes the size already occupied by a directory and is subdirectories.
  568. *
  569. * @author Hugues Peeters <peeters@ipm.ucl.ac.be>
  570. *
  571. * @param string $dir_path Size of the file in byte
  572. *
  573. * @return int Return the directory size in bytes
  574. */
  575. function dir_total_space($dir_path)
  576. {
  577. $save_dir = getcwd();
  578. chdir($dir_path);
  579. $handle = opendir($dir_path);
  580. $sumSize = 0;
  581. $dirList = [];
  582. while ($element = readdir($handle)) {
  583. if ($element == '.' || $element == '..') {
  584. continue; // Skip the current and parent directories
  585. }
  586. if (is_file($element)) {
  587. $sumSize += filesize($element);
  588. }
  589. if (is_dir($element)) {
  590. $dirList[] = $dir_path.'/'.$element;
  591. }
  592. }
  593. closedir($handle);
  594. if (sizeof($dirList) > 0) {
  595. foreach ($dirList as $j) {
  596. $sizeDir = dir_total_space($j); // Recursivity
  597. $sumSize += $sizeDir;
  598. }
  599. }
  600. chdir($save_dir); // Return to initial position
  601. return $sumSize;
  602. }
  603. /**
  604. * Tries to add an extension to files without extension
  605. * Some applications on Macintosh computers don't add an extension to the files.
  606. * This subroutine try to fix this on the basis of the MIME type sent
  607. * by the browser.
  608. *
  609. * Note : some browsers don't send the MIME Type (e.g. Netscape 4).
  610. * We don't have solution for this kind of situation
  611. *
  612. * @author Hugues Peeters <peeters@ipm.ucl.ac.be>
  613. * @author Bert Vanderkimpen
  614. *
  615. * @param string $file_name Name of the file
  616. * @param string $file_type Type of the file
  617. *
  618. * @return string File name
  619. */
  620. function add_ext_on_mime($file_name, $file_type)
  621. {
  622. // Check whether the file has an extension AND whether the browser has sent a MIME Type
  623. if (!preg_match('/^.*\.[a-zA-Z_0-9]+$/', $file_name) && $file_type) {
  624. // Build a "MIME-types / extensions" connection table
  625. static $mime_type = [];
  626. $mime_type[] = 'application/msword';
  627. $extension[] = '.doc';
  628. $mime_type[] = 'application/rtf';
  629. $extension[] = '.rtf';
  630. $mime_type[] = 'application/vnd.ms-powerpoint';
  631. $extension[] = '.ppt';
  632. $mime_type[] = 'application/vnd.ms-excel';
  633. $extension[] = '.xls';
  634. $mime_type[] = 'application/pdf';
  635. $extension[] = '.pdf';
  636. $mime_type[] = 'application/postscript';
  637. $extension[] = '.ps';
  638. $mime_type[] = 'application/mac-binhex40';
  639. $extension[] = '.hqx';
  640. $mime_type[] = 'application/x-gzip';
  641. $extension[] = 'tar.gz';
  642. $mime_type[] = 'application/x-shockwave-flash';
  643. $extension[] = '.swf';
  644. $mime_type[] = 'application/x-stuffit';
  645. $extension[] = '.sit';
  646. $mime_type[] = 'application/x-tar';
  647. $extension[] = '.tar';
  648. $mime_type[] = 'application/zip';
  649. $extension[] = '.zip';
  650. $mime_type[] = 'application/x-tar';
  651. $extension[] = '.tar';
  652. $mime_type[] = 'text/html';
  653. $extension[] = '.html';
  654. $mime_type[] = 'text/plain';
  655. $extension[] = '.txt';
  656. $mime_type[] = 'text/rtf';
  657. $extension[] = '.rtf';
  658. $mime_type[] = 'img/gif';
  659. $extension[] = '.gif';
  660. $mime_type[] = 'img/jpeg';
  661. $extension[] = '.jpg';
  662. $mime_type[] = 'img/png';
  663. $extension[] = '.png';
  664. $mime_type[] = 'audio/midi';
  665. $extension[] = '.mid';
  666. $mime_type[] = 'audio/mpeg';
  667. $extension[] = '.mp3';
  668. $mime_type[] = 'audio/x-aiff';
  669. $extension[] = '.aif';
  670. $mime_type[] = 'audio/x-pn-realaudio';
  671. $extension[] = '.rm';
  672. $mime_type[] = 'audio/x-pn-realaudio-plugin';
  673. $extension[] = '.rpm';
  674. $mime_type[] = 'audio/x-wav';
  675. $extension[] = '.wav';
  676. $mime_type[] = 'video/mpeg';
  677. $extension[] = '.mpg';
  678. $mime_type[] = 'video/mpeg4-generic';
  679. $extension[] = '.mp4';
  680. $mime_type[] = 'video/quicktime';
  681. $extension[] = '.mov';
  682. $mime_type[] = 'video/x-msvideo';
  683. $extension[] = '.avi';
  684. $mime_type[] = 'video/x-ms-wmv';
  685. $extension[] = '.wmv';
  686. $mime_type[] = 'video/x-flv';
  687. $extension[] = '.flv';
  688. $mime_type[] = 'image/svg+xml';
  689. $extension[] = '.svg';
  690. $mime_type[] = 'image/svg+xml';
  691. $extension[] = '.svgz';
  692. $mime_type[] = 'video/ogg';
  693. $extension[] = '.ogv';
  694. $mime_type[] = 'audio/ogg';
  695. $extension[] = '.oga';
  696. $mime_type[] = 'application/ogg';
  697. $extension[] = '.ogg';
  698. $mime_type[] = 'application/ogg';
  699. $extension[] = '.ogx';
  700. $mime_type[] = 'application/x-freemind';
  701. $extension[] = '.mm';
  702. $mime_type[] = 'application/vnd.ms-word.document.macroEnabled.12';
  703. $extension[] = '.docm';
  704. $mime_type[] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
  705. $extension[] = '.docx';
  706. $mime_type[] = 'application/vnd.ms-word.template.macroEnabled.12';
  707. $extension[] = '.dotm';
  708. $mime_type[] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.template';
  709. $extension[] = '.dotx';
  710. $mime_type[] = 'application/vnd.ms-powerpoint.template.macroEnabled.12';
  711. $extension[] = '.potm';
  712. $mime_type[] = 'application/vnd.openxmlformats-officedocument.presentationml.template';
  713. $extension[] = '.potx';
  714. $mime_type[] = 'application/vnd.ms-powerpoint.addin.macroEnabled.12';
  715. $extension[] = '.ppam';
  716. $mime_type[] = 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12';
  717. $extension[] = '.ppsm';
  718. $mime_type[] = 'application/vnd.openxmlformats-officedocument.presentationml.slideshow';
  719. $extension[] = '.ppsx';
  720. $mime_type[] = 'application/vnd.ms-powerpoint.presentation.macroEnabled.12';
  721. $extension[] = '.pptm';
  722. $mime_type[] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
  723. $extension[] = '.pptx';
  724. $mime_type[] = 'application/vnd.ms-excel.addin.macroEnabled.12';
  725. $extension[] = '.xlam';
  726. $mime_type[] = 'application/vnd.ms-excel.sheet.binary.macroEnabled.12';
  727. $extension[] = '.xlsb';
  728. $mime_type[] = 'application/vnd.ms-excel.sheet.macroEnabled.12';
  729. $extension[] = '.xlsm';
  730. $mime_type[] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
  731. $extension[] = '.xlsx';
  732. $mime_type[] = 'application/vnd.ms-excel.template.macroEnabled.12';
  733. $extension[] = '.xltm';
  734. $mime_type[] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.template';
  735. $extension[] = '.xltx';
  736. // Test on PC (files with no extension get application/octet-stream)
  737. //$mime_type[] = 'application/octet-stream'; $extension[] = '.ext';
  738. // Check whether the MIME type sent by the browser is within the table
  739. foreach ($mime_type as $key => &$type) {
  740. if ($type == $file_type) {
  741. $file_name .= $extension[$key];
  742. break;
  743. }
  744. }
  745. unset($mime_type, $extension, $type, $key); // Delete to eschew possible collisions
  746. }
  747. return $file_name;
  748. }
  749. /**
  750. * Manages all the unzipping process of an uploaded file.
  751. *
  752. * @author Hugues Peeters <hugues.peeters@claroline.net>
  753. *
  754. * @param array $uploaded_file - follows the $_FILES Structure
  755. * @param string $upload_path - destination of the upload.
  756. * This path is to append to $base_work_dir
  757. * @param string $base_work_dir - base working directory of the module
  758. * @param int $max_filled_space - amount of bytes to not exceed in the base
  759. * working directory
  760. *
  761. * @return bool true if it succeeds false otherwise
  762. */
  763. function unzip_uploaded_file($uploaded_file, $upload_path, $base_work_dir, $max_filled_space)
  764. {
  765. $zip_file = new PclZip($uploaded_file['tmp_name']);
  766. // Check the zip content (real size and file extension)
  767. if (file_exists($uploaded_file['tmp_name'])) {
  768. $zip_content_array = $zip_file->listContent();
  769. $ok_scorm = false;
  770. $realFileSize = 0;
  771. foreach ($zip_content_array as &$this_content) {
  772. if (preg_match('~.(php.*|phtml)$~i', $this_content['filename'])) {
  773. Display::addFlash(
  774. Display::return_message(get_lang('The zip file can not contain .PHP files'))
  775. );
  776. return false;
  777. } elseif (stristr($this_content['filename'], 'imsmanifest.xml')) {
  778. $ok_scorm = true;
  779. } elseif (stristr($this_content['filename'], 'LMS')) {
  780. $ok_plantyn_scorm1 = true;
  781. } elseif (stristr($this_content['filename'], 'REF')) {
  782. $ok_plantyn_scorm2 = true;
  783. } elseif (stristr($this_content['filename'], 'SCO')) {
  784. $ok_plantyn_scorm3 = true;
  785. } elseif (stristr($this_content['filename'], 'AICC')) {
  786. $ok_aicc_scorm = true;
  787. }
  788. $realFileSize += $this_content['size'];
  789. }
  790. if (($ok_plantyn_scorm1 && $ok_plantyn_scorm2 && $ok_plantyn_scorm3) || $ok_aicc_scorm) {
  791. $ok_scorm = true;
  792. }
  793. if (!$ok_scorm && defined('CHECK_FOR_SCORM') && CHECK_FOR_SCORM) {
  794. Display::addFlash(
  795. Display::return_message(get_lang('This is not a valid SCORM ZIP file !'))
  796. );
  797. return false;
  798. }
  799. if (!enough_size($realFileSize, $base_work_dir, $max_filled_space)) {
  800. Display::addFlash(
  801. Display::return_message(get_lang('The upload has failed. Either you have exceeded your maximum quota, or there is not enough disk space.'))
  802. );
  803. return false;
  804. }
  805. // It happens on Linux that $upload_path sometimes doesn't start with '/'
  806. if ($upload_path[0] != '/' && substr($base_work_dir, -1, 1) != '/') {
  807. $upload_path = '/'.$upload_path;
  808. }
  809. if ($upload_path[strlen($upload_path) - 1] == '/') {
  810. $upload_path = substr($upload_path, 0, -1);
  811. }
  812. /* Uncompressing phase */
  813. /*
  814. The first version, using OS unzip, is not used anymore
  815. because it does not return enough information.
  816. We need to process each individual file in the zip archive to
  817. - add it to the database
  818. - parse & change relative html links
  819. */
  820. if (PHP_OS == 'Linux' && !get_cfg_var('safe_mode') && false) { // *** UGent, changed by OC ***
  821. // Shell Method - if this is possible, it gains some speed
  822. exec("unzip -d \"".$base_work_dir.$upload_path."/\"".$uploaded_file['name']." ".$uploaded_file['tmp_name']);
  823. } else {
  824. // PHP method - slower...
  825. $save_dir = getcwd();
  826. chdir($base_work_dir.$upload_path);
  827. $unzippingState = $zip_file->extract();
  828. for ($j = 0; $j < count($unzippingState); $j++) {
  829. $state = $unzippingState[$j];
  830. // Fix relative links in html files
  831. $extension = strrchr($state['stored_filename'], '.');
  832. }
  833. if ($dir = @opendir($base_work_dir.$upload_path)) {
  834. while ($file = readdir($dir)) {
  835. if ($file != '.' && $file != '..') {
  836. $filetype = 'file';
  837. if (is_dir($base_work_dir.$upload_path.'/'.$file)) {
  838. $filetype = 'folder';
  839. }
  840. $safe_file = api_replace_dangerous_char($file);
  841. @rename($base_work_dir.$upload_path.'/'.$file, $base_work_dir.$upload_path.'/'.$safe_file);
  842. set_default_settings($upload_path, $safe_file, $filetype);
  843. }
  844. }
  845. closedir($dir);
  846. } else {
  847. error_log('Could not create directory '.$base_work_dir.$upload_path.' to unzip files');
  848. }
  849. chdir($save_dir); // Back to previous dir position
  850. }
  851. }
  852. return true;
  853. }
  854. /**
  855. * Manages all the unzipping process of an uploaded document
  856. * This uses the item_property table for properties of documents.
  857. *
  858. * @author Hugues Peeters <hugues.peeters@claroline.net>
  859. * @author Bert Vanderkimpen
  860. *
  861. * @param array $courseInfo
  862. * @param array $userInfo
  863. * @param array $uploaded_file - follows the $_FILES Structure
  864. * @param string $uploadPath - destination of the upload.
  865. * This path is to append to $base_work_dir
  866. * @param string $base_work_dir - base working directory of the module
  867. * @param int $maxFilledSpace - amount of bytes to not exceed in the base
  868. * working directory
  869. * @param int $sessionId
  870. * @param int $groupId group.id
  871. * @param bool $output Optional. If no output not wanted on success, set to false.
  872. * @param bool $onlyUploadFile
  873. * @param string $whatIfFileExists (only works if $onlyUploadFile is false)
  874. *
  875. * @return bool true if it succeeds false otherwise
  876. */
  877. function unzip_uploaded_document(
  878. $courseInfo,
  879. $userInfo,
  880. $uploaded_file,
  881. $uploadPath,
  882. $base_work_dir,
  883. $maxFilledSpace,
  884. $sessionId = 0,
  885. $groupId = 0,
  886. $output = true,
  887. $onlyUploadFile = false,
  888. $whatIfFileExists = 'overwrite'
  889. ) {
  890. $zip = new PclZip($uploaded_file['tmp_name']);
  891. // Check the zip content (real size and file extension)
  892. $zip_content_array = (array) $zip->listContent();
  893. $realSize = 0;
  894. foreach ($zip_content_array as &$this_content) {
  895. $realSize += $this_content['size'];
  896. }
  897. if (!DocumentManager::enough_space($realSize, $maxFilledSpace)) {
  898. echo Display::return_message(get_lang('There is not enough space to upload this file.'), 'error');
  899. return false;
  900. }
  901. $folder = api_get_unique_id();
  902. $destinationDir = api_get_path(SYS_ARCHIVE_PATH).$folder;
  903. mkdir($destinationDir, api_get_permissions_for_new_directories(), true);
  904. // Uncompress zip file
  905. // We extract using a callback function that "cleans" the path
  906. $zip->extract(
  907. PCLZIP_OPT_PATH,
  908. $destinationDir,
  909. PCLZIP_CB_PRE_EXTRACT,
  910. 'clean_up_files_in_zip',
  911. PCLZIP_OPT_REPLACE_NEWER
  912. );
  913. if ($onlyUploadFile === false) {
  914. // Add all documents in the unzipped folder to the database
  915. add_all_documents_in_folder_to_database(
  916. $courseInfo,
  917. $userInfo,
  918. $base_work_dir,
  919. $destinationDir,
  920. $sessionId,
  921. $groupId,
  922. $output,
  923. ['path' => $uploadPath],
  924. $whatIfFileExists
  925. );
  926. } else {
  927. // Copy result
  928. $fs = new \Symfony\Component\Filesystem\Filesystem();
  929. $fs->mirror($destinationDir, $base_work_dir.$uploadPath, null, ['overwrite']);
  930. }
  931. if (is_dir($destinationDir)) {
  932. rmdirr($destinationDir);
  933. }
  934. return true;
  935. }
  936. /**
  937. * This function is a callback function that is used while extracting a zipfile
  938. * http://www.phpconcept.net/pclzip/man/en/index.php?options-pclzip_cb_pre_extract.
  939. *
  940. * @param array $p_event
  941. * @param array $p_header
  942. *
  943. * @return int (If the function returns 1, then the extraction is resumed, if 0 the path was skipped)
  944. */
  945. function clean_up_files_in_zip($p_event, &$p_header)
  946. {
  947. $originalStoredFileName = $p_header['stored_filename'];
  948. $baseName = basename($originalStoredFileName);
  949. // Skip files
  950. $skipFiles = [
  951. '__MACOSX',
  952. '.Thumbs.db',
  953. 'Thumbs.db',
  954. ];
  955. if (in_array($baseName, $skipFiles)) {
  956. return 0;
  957. }
  958. $modifiedStoredFileName = clean_up_path($originalStoredFileName);
  959. $p_header['filename'] = str_replace($originalStoredFileName, $modifiedStoredFileName, $p_header['filename']);
  960. return 1;
  961. }
  962. /**
  963. * Allow .htaccess file.
  964. *
  965. * @param $p_event
  966. * @param $p_header
  967. *
  968. * @return int
  969. */
  970. function cleanZipFilesAllowHtaccess($p_event, &$p_header)
  971. {
  972. $originalStoredFileName = $p_header['stored_filename'];
  973. $baseName = basename($originalStoredFileName);
  974. $allowFiles = ['.htaccess'];
  975. if (in_array($baseName, $allowFiles)) {
  976. return 1;
  977. }
  978. // Skip files
  979. $skipFiles = [
  980. '__MACOSX',
  981. '.Thumbs.db',
  982. 'Thumbs.db',
  983. ];
  984. if (in_array($baseName, $skipFiles)) {
  985. return 0;
  986. }
  987. $modifiedStoredFileName = clean_up_path($originalStoredFileName);
  988. $p_header['filename'] = str_replace($originalStoredFileName, $modifiedStoredFileName, $p_header['filename']);
  989. return 1;
  990. }
  991. /**
  992. * This function cleans up a given path
  993. * by eliminating dangerous file names and cleaning them.
  994. *
  995. * @param string $path
  996. *
  997. * @return string
  998. *
  999. * @see disable_dangerous_file()
  1000. * @see api_replace_dangerous_char()
  1001. */
  1002. function clean_up_path($path)
  1003. {
  1004. // Split the path in folders and files
  1005. $path_array = explode('/', $path);
  1006. // Clean up every folder and filename in the path
  1007. foreach ($path_array as $key => &$val) {
  1008. // We don't want to lose the dots in ././folder/file (cfr. zipfile)
  1009. if ($val != '.') {
  1010. $val = disable_dangerous_file(api_replace_dangerous_char($val));
  1011. }
  1012. }
  1013. // Join the "cleaned" path (modified in-place as passed by reference)
  1014. $path = implode('/', $path_array);
  1015. filter_extension($path);
  1016. return $path;
  1017. }
  1018. /**
  1019. * Checks if the file is dangerous, based on extension and/or mimetype.
  1020. * The list of extensions accepted/rejected can be found from
  1021. * api_get_setting('upload_extensions_exclude') and api_get_setting('upload_extensions_include').
  1022. *
  1023. * @param string $filename passed by reference. The filename will be modified
  1024. * if filter rules say so! (you can include path but the filename should look like 'abc.html')
  1025. *
  1026. * @return int 0 to skip file, 1 to keep file
  1027. */
  1028. function filter_extension(&$filename)
  1029. {
  1030. if (substr($filename, -1) == '/') {
  1031. return 1; // Authorize directories
  1032. }
  1033. $blacklist = api_get_setting('upload_extensions_list_type');
  1034. if ($blacklist != 'whitelist') { // if = blacklist
  1035. $extensions = explode(';', strtolower(api_get_setting('upload_extensions_blacklist')));
  1036. $skip = api_get_setting('upload_extensions_skip');
  1037. $ext = strrchr($filename, '.');
  1038. $ext = substr($ext, 1);
  1039. if (empty($ext)) {
  1040. return 1; // We're in blacklist mode, so accept empty extensions
  1041. }
  1042. if (in_array(strtolower($ext), $extensions)) {
  1043. if ($skip == 'true') {
  1044. return 0;
  1045. } else {
  1046. $new_ext = api_get_setting('upload_extensions_replace_by');
  1047. $filename = str_replace('.'.$ext, '.'.$new_ext, $filename);
  1048. return 1;
  1049. }
  1050. } else {
  1051. return 1;
  1052. }
  1053. } else {
  1054. $extensions = explode(';', strtolower(api_get_setting('upload_extensions_whitelist')));
  1055. $skip = api_get_setting('upload_extensions_skip');
  1056. $ext = strrchr($filename, '.');
  1057. $ext = substr($ext, 1);
  1058. if (empty($ext)) {
  1059. return 1; // Accept empty extensions
  1060. }
  1061. if (!in_array(strtolower($ext), $extensions)) {
  1062. if ($skip == 'true') {
  1063. return 0;
  1064. } else {
  1065. $new_ext = api_get_setting('upload_extensions_replace_by');
  1066. $filename = str_replace('.'.$ext, '.'.$new_ext, $filename);
  1067. return 1;
  1068. }
  1069. } else {
  1070. return 1;
  1071. }
  1072. }
  1073. }
  1074. /**
  1075. * Updates an existing document in the database
  1076. * as the file exists, we only need to change the size.
  1077. *
  1078. * @param array $_course
  1079. * @param int $documentId
  1080. * @param int $filesize
  1081. * @param int $readonly
  1082. *
  1083. * @return bool true /false
  1084. */
  1085. function update_existing_document($_course, $documentId, $filesize, $readonly = 0)
  1086. {
  1087. $document_table = Database::get_course_table(TABLE_DOCUMENT);
  1088. $documentId = intval($documentId);
  1089. $filesize = intval($filesize);
  1090. $readonly = intval($readonly);
  1091. $course_id = $_course['real_id'];
  1092. $sql = "UPDATE $document_table SET
  1093. size = '$filesize',
  1094. readonly = '$readonly'
  1095. WHERE c_id = $course_id AND id = $documentId";
  1096. if (Database::query($sql)) {
  1097. return true;
  1098. } else {
  1099. return false;
  1100. }
  1101. }
  1102. /**
  1103. * This function updates the last_edit_date, last edit user id on all folders in a given path.
  1104. *
  1105. * @param array $_course
  1106. * @param string $path
  1107. * @param int $user_id
  1108. */
  1109. function item_property_update_on_folder($_course, $path, $user_id)
  1110. {
  1111. // If we are in the root, just return... no need to update anything
  1112. if ($path == '/') {
  1113. return;
  1114. }
  1115. $user_id = intval($user_id);
  1116. // If the given path ends with a / we remove it
  1117. $endchar = substr($path, strlen($path) - 1, 1);
  1118. if ($endchar == '/') {
  1119. $path = substr($path, 0, strlen($path) - 1);
  1120. }
  1121. $table = Database::get_course_table(TABLE_ITEM_PROPERTY);
  1122. // Get the time
  1123. $time = api_get_utc_datetime();
  1124. // Det all paths in the given path
  1125. // /folder/subfolder/subsubfolder/file
  1126. // if file is updated, subsubfolder, subfolder and folder are updated
  1127. $exploded_path = explode('/', $path);
  1128. $course_id = api_get_course_int_id();
  1129. $newpath = '';
  1130. foreach ($exploded_path as $key => &$value) {
  1131. // We don't want a slash before our first slash
  1132. if ($key != 0) {
  1133. $newpath .= '/'.$value;
  1134. // Select ID of given folder
  1135. $folder_id = DocumentManager::get_document_id($_course, $newpath);
  1136. if ($folder_id) {
  1137. $sql = "UPDATE $table SET
  1138. lastedit_date = '$time',
  1139. lastedit_type = 'DocumentInFolderUpdated',
  1140. lastedit_user_id='$user_id'
  1141. WHERE
  1142. c_id = $course_id AND
  1143. tool='".TOOL_DOCUMENT."' AND
  1144. ref = '$folder_id'";
  1145. Database::query($sql);
  1146. }
  1147. }
  1148. }
  1149. }
  1150. /**
  1151. * Adds file to document table in database
  1152. * deprecated: use file_set_default_settings instead.
  1153. *
  1154. * @author Olivier Cauberghe <olivier.cauberghe@ugent.be>
  1155. *
  1156. * @param path,filename
  1157. * action: Adds an entry to the document table with the default settings
  1158. */
  1159. function set_default_settings($upload_path, $filename, $filetype = 'file')
  1160. {
  1161. $dbTable = Database::get_course_table(TABLE_DOCUMENT);
  1162. global $default_visibility;
  1163. if (!$default_visibility) {
  1164. $default_visibility = 'v';
  1165. }
  1166. $filetype = Database::escape_string($filetype);
  1167. $upload_path = str_replace('\\', '/', $upload_path);
  1168. $upload_path = str_replace('//', '/', $upload_path);
  1169. if ($upload_path == '/') {
  1170. $upload_path = '';
  1171. } elseif (!empty($upload_path) && $upload_path[0] != '/') {
  1172. $upload_path = "/$upload_path";
  1173. }
  1174. $endchar = substr($filename, strlen($filename) - 1, 1);
  1175. if ($endchar == '/') {
  1176. $filename = substr($filename, 0, -1);
  1177. }
  1178. $filename = Database::escape_string($filename);
  1179. $query = "SELECT count(*) as bestaat FROM $dbTable
  1180. WHERE path='$upload_path/$filename'";
  1181. $result = Database::query($query);
  1182. $row = Database::fetch_array($result);
  1183. if ($row['bestaat'] > 0) {
  1184. $query = "UPDATE $dbTable SET
  1185. path='$upload_path/$filename',
  1186. visibility='$default_visibility',
  1187. filetype='$filetype'
  1188. WHERE path='$upload_path/$filename'";
  1189. } else {
  1190. $query = "INSERT INTO $dbTable (path,visibility,filetype)
  1191. VALUES('$upload_path/$filename','$default_visibility','$filetype')";
  1192. }
  1193. Database::query($query);
  1194. }
  1195. /**
  1196. * Retrieves the image path list in a html file.
  1197. *
  1198. * @author Hugues Peeters <hugues.peeters@claroline.net>
  1199. *
  1200. * @param string $html_file
  1201. *
  1202. * @return array - images path list
  1203. */
  1204. function search_img_from_html($html_file)
  1205. {
  1206. $img_path_list = [];
  1207. if (!$fp = fopen($html_file, 'r')) {
  1208. return;
  1209. }
  1210. // Aearch and store occurences of the <img> tag in an array
  1211. $size_file = (filesize($html_file) === 0) ? 1 : filesize($html_file);
  1212. if (isset($fp) && $fp !== false) {
  1213. $buffer = fread($fp, $size_file);
  1214. if (strlen($buffer) >= 0 && $buffer !== false) {
  1215. } else {
  1216. die('<center>Can not read file.</center>');
  1217. }
  1218. } else {
  1219. die('<center>Can not read file.</center>');
  1220. }
  1221. $matches = [];
  1222. if (preg_match_all('~<[[:space:]]*img[^>]*>~i', $buffer, $matches)) {
  1223. $img_tag_list = $matches[0];
  1224. }
  1225. fclose($fp);
  1226. unset($buffer);
  1227. // Search the image file path from all the <IMG> tag detected
  1228. if (sizeof($img_tag_list) > 0) {
  1229. foreach ($img_tag_list as &$this_img_tag) {
  1230. if (preg_match('~src[[:space:]]*=[[:space:]]*[\"]{1}([^\"]+)[\"]{1}~i', $this_img_tag, $matches)) {
  1231. $img_path_list[] = $matches[1];
  1232. }
  1233. }
  1234. $img_path_list = array_unique($img_path_list); // Remove duplicate entries
  1235. }
  1236. return $img_path_list;
  1237. }
  1238. /**
  1239. * Creates a new directory trying to find a directory name
  1240. * that doesn't already exist.
  1241. *
  1242. * @author Hugues Peeters <hugues.peeters@claroline.net>
  1243. * @author Bert Vanderkimpen
  1244. *
  1245. * @param array $_course current course information
  1246. * @param int $user_id current user id
  1247. * @param int $session_id
  1248. * @param int $to_group_id group.id
  1249. * @param int $to_user_id
  1250. * @param string $base_work_dir /var/www/chamilo/courses/ABC/document
  1251. * @param string $desired_dir_name complete path of the desired name
  1252. * Example: /folder1/folder2
  1253. * @param string $title "folder2"
  1254. * @param int $visibility (0 for invisible, 1 for visible, 2 for deleted)
  1255. * @param bool $generateNewNameIfExists
  1256. * @param bool $sendNotification depends in conf setting "send_notification_when_document_added"
  1257. * @param array $parentInfo
  1258. *
  1259. * @return CDocument|false
  1260. */
  1261. function create_unexisting_directory(
  1262. $_course,
  1263. $user_id,
  1264. $session_id,
  1265. $to_group_id,
  1266. $to_user_id,
  1267. $base_work_dir,
  1268. $desired_dir_name,
  1269. $title = '',
  1270. $visibility = '',
  1271. $generateNewNameIfExists = false,
  1272. $sendNotification = true,
  1273. $parentInfo = []
  1274. ) {
  1275. $course_id = $_course['real_id'];
  1276. $session_id = (int) $session_id;
  1277. $folderExists = DocumentManager::folderExists(
  1278. $desired_dir_name,
  1279. $_course,
  1280. $session_id,
  1281. $to_group_id
  1282. );
  1283. if ($folderExists === true) {
  1284. if ($generateNewNameIfExists) {
  1285. $counter = 1;
  1286. while (1) {
  1287. $folderExists = DocumentManager::folderExists(
  1288. $desired_dir_name.'_'.$counter,
  1289. $_course,
  1290. $session_id,
  1291. $to_group_id
  1292. );
  1293. if ($folderExists === false) {
  1294. break;
  1295. }
  1296. $counter++;
  1297. }
  1298. $desired_dir_name = $desired_dir_name.'_'.$counter;
  1299. }
  1300. }
  1301. $systemFolderName = $desired_dir_name;
  1302. // Adding suffix
  1303. $suffix = DocumentManager::getDocumentSuffix(
  1304. $_course,
  1305. $session_id,
  1306. $to_group_id
  1307. );
  1308. $systemFolderName .= $suffix;
  1309. if ($title == null) {
  1310. $title = basename($desired_dir_name);
  1311. }
  1312. // Check if pathname already exists inside document table
  1313. $table = Database::get_course_table(TABLE_DOCUMENT);
  1314. $sql = "SELECT id, path FROM $table
  1315. WHERE
  1316. c_id = $course_id AND
  1317. path = '".Database::escape_string($systemFolderName)."'";
  1318. $rs = Database::query($sql);
  1319. $parentId = 0;
  1320. if (!empty($parentInfo) && isset($parentInfo['iid'])) {
  1321. $parentId = $parentInfo['iid'];
  1322. }
  1323. if (Database::num_rows($rs) == 0) {
  1324. $document = DocumentManager::addDocument(
  1325. $_course,
  1326. $systemFolderName,
  1327. 'folder',
  1328. 0,
  1329. $title,
  1330. null,
  1331. 0,
  1332. true,
  1333. $to_group_id,
  1334. $session_id,
  1335. $user_id,
  1336. $sendNotification,
  1337. '',
  1338. $parentId
  1339. );
  1340. if ($document) {
  1341. return $document;
  1342. }
  1343. } else {
  1344. $document = Database::fetch_array($rs);
  1345. $documentData = DocumentManager::get_document_data_by_id(
  1346. $document['id'],
  1347. $_course['code'],
  1348. false,
  1349. $session_id
  1350. );
  1351. if ($documentData) {
  1352. $document = Container::getDocumentRepository()->find($documentData['iid']);
  1353. return $document;
  1354. }
  1355. }
  1356. return false;
  1357. }
  1358. /**
  1359. * Handles uploaded missing images.
  1360. *
  1361. * @author Hugues Peeters <hugues.peeters@claroline.net>
  1362. * @author Bert Vanderkimpen
  1363. *
  1364. * @param array $_course
  1365. * @param array $uploaded_file_collection - follows the $_FILES Structure
  1366. * @param string $base_work_dir
  1367. * @param string $missing_files_dir
  1368. * @param int $user_id
  1369. * @param int $to_group_id group.id
  1370. */
  1371. function move_uploaded_file_collection_into_directory(
  1372. $_course,
  1373. $uploaded_file_collection,
  1374. $base_work_dir,
  1375. $missing_files_dir,
  1376. $user_id,
  1377. $to_group_id,
  1378. $to_user_id,
  1379. $max_filled_space
  1380. ) {
  1381. $number_of_uploaded_images = count($uploaded_file_collection['name']);
  1382. $list = [];
  1383. for ($i = 0; $i < $number_of_uploaded_images; $i++) {
  1384. $missing_file['name'] = $uploaded_file_collection['name'][$i];
  1385. $missing_file['type'] = $uploaded_file_collection['type'][$i];
  1386. $missing_file['tmp_name'] = $uploaded_file_collection['tmp_name'][$i];
  1387. $missing_file['error'] = $uploaded_file_collection['error'][$i];
  1388. $missing_file['size'] = $uploaded_file_collection['size'][$i];
  1389. $upload_ok = process_uploaded_file($missing_file);
  1390. if ($upload_ok) {
  1391. $list[] = handle_uploaded_document(
  1392. $_course,
  1393. $missing_file,
  1394. $base_work_dir,
  1395. $missing_files_dir,
  1396. $user_id,
  1397. $to_group_id,
  1398. $to_user_id,
  1399. $max_filled_space,
  1400. 0,
  1401. 'overwrite'
  1402. );
  1403. }
  1404. unset($missing_file);
  1405. }
  1406. return $list;
  1407. }
  1408. /**
  1409. * Opens the old html file and replace the src path into the img tag
  1410. * This also works for files in subdirectories.
  1411. *
  1412. * @param $original_img_path is an array
  1413. * @param $new_img_path is an array
  1414. */
  1415. function replace_img_path_in_html_file($original_img_path, $new_img_path, $html_file)
  1416. {
  1417. // Open the file
  1418. $fp = fopen($html_file, 'r');
  1419. $buffer = fread($fp, filesize($html_file));
  1420. $new_html_content = '';
  1421. // Fix the image tags
  1422. for ($i = 0, $fileNb = count($original_img_path); $i < $fileNb; $i++) {
  1423. $replace_what = $original_img_path[$i];
  1424. // We only need the directory and the filename /path/to/file_html_files/missing_file.gif -> file_html_files/missing_file.gif
  1425. $exploded_file_path = explode('/', $new_img_path[$i]);
  1426. $replace_by = $exploded_file_path[count($exploded_file_path) - 2].'/'.$exploded_file_path[count($exploded_file_path) - 1];
  1427. $buffer = str_replace($replace_what, $replace_by, $buffer);
  1428. }
  1429. $new_html_content .= $buffer;
  1430. @fclose($fp);
  1431. // Write the resulted new file
  1432. if (!$fp = fopen($html_file, 'w')) {
  1433. return;
  1434. }
  1435. if (!fwrite($fp, $new_html_content)) {
  1436. return;
  1437. }
  1438. }
  1439. /**
  1440. * Checks the extension of a file, if it's .htm or .html
  1441. * we use search_img_from_html to get all image paths in the file.
  1442. *
  1443. * @param string $file
  1444. *
  1445. * @return array paths
  1446. *
  1447. * @see check_for_missing_files() uses search_img_from_html()
  1448. */
  1449. function check_for_missing_files($file)
  1450. {
  1451. if (strrchr($file, '.') == '.htm' || strrchr($file, '.') == '.html') {
  1452. $img_file_path = search_img_from_html($file);
  1453. return $img_file_path;
  1454. }
  1455. return false;
  1456. }
  1457. /**
  1458. * This function builds a form that asks for the missing images in a html file
  1459. * maybe we should do this another way?
  1460. *
  1461. * @param array $missing_files
  1462. * @param string $upload_path
  1463. * @param string $file_name
  1464. *
  1465. * @return string the form
  1466. */
  1467. function build_missing_files_form($missing_files, $upload_path, $file_name)
  1468. {
  1469. // Do we need a / or not?
  1470. $added_slash = ($upload_path == '/') ? '' : '/';
  1471. $folder_id = DocumentManager::get_document_id(api_get_course_info(), $upload_path);
  1472. // Build the form
  1473. $form = "<p><strong>".get_lang('Missing images detected')."</strong></p>"
  1474. ."<form method=\"post\" action=\"".api_get_self()."\" enctype=\"multipart/form-data\">"
  1475. // Related_file is the path to the file that has missing images
  1476. ."<input type=\"hidden\" name=\"related_file\" value=\"".$upload_path.$added_slash.$file_name."\" />"
  1477. ."<input type=\"hidden\" name=\"upload_path\" value=\"".$upload_path."\" />"
  1478. ."<input type=\"hidden\" name=\"id\" value=\"".$folder_id."\" />"
  1479. ."<table border=\"0\">";
  1480. foreach ($missing_files as &$this_img_file_path) {
  1481. $form .= "<tr>"
  1482. ."<td>".basename($this_img_file_path)." : </td>"
  1483. ."<td>"
  1484. ."<input type=\"file\" name=\"img_file[]\"/>"
  1485. ."<input type=\"hidden\" name=\"img_file_path[]\" value=\"".$this_img_file_path."\" />"
  1486. ."</td>"
  1487. ."</tr>";
  1488. }
  1489. $form .= "</table>"
  1490. ."<button type='submit' name=\"cancel_submit_image\" value=\"".get_lang('Cancel')."\" class=\"cancel\">".get_lang('Cancel')."</button>"
  1491. ."<button type='submit' name=\"submit_image\" value=\"".get_lang('Validate')."\" class=\"save\">".get_lang('Validate')."</button>"
  1492. ."</form>";
  1493. return $form;
  1494. }
  1495. /**
  1496. * This recursive function can be used during the upgrade process form older
  1497. * versions of Chamilo
  1498. * It crawls the given directory, checks if the file is in the DB and adds
  1499. * it if it's not.
  1500. *
  1501. * @param array $courseInfo
  1502. * @param array $userInfo
  1503. * @param string $base_work_dir course document dir
  1504. * @param string $folderPath folder to read
  1505. * @param int $sessionId
  1506. * @param int $groupId group.id
  1507. * @param bool $output
  1508. * @param array $parent
  1509. * @param string $whatIfFileExists
  1510. *
  1511. * @return bool
  1512. */
  1513. function add_all_documents_in_folder_to_database(
  1514. $courseInfo,
  1515. $userInfo,
  1516. $base_work_dir,
  1517. $folderPath,
  1518. $sessionId = 0,
  1519. $groupId = 0,
  1520. $output = false,
  1521. $parent = [],
  1522. $whatIfFileExists = 'overwrite'
  1523. ) {
  1524. if (empty($userInfo) || empty($courseInfo)) {
  1525. return false;
  1526. }
  1527. $userId = $userInfo['user_id'];
  1528. // Open dir
  1529. $handle = opendir($folderPath);
  1530. if (is_dir($folderPath)) {
  1531. // Run trough
  1532. while ($file = readdir($handle)) {
  1533. if ($file == '.' || $file == '..') {
  1534. continue;
  1535. }
  1536. $parentPath = '';
  1537. if (!empty($parent) && isset($parent['path'])) {
  1538. $parentPath = $parent['path'];
  1539. if ($parentPath == '/') {
  1540. $parentPath = '';
  1541. }
  1542. }
  1543. $completePath = $parentPath.'/'.$file;
  1544. $sysFolderPath = $folderPath.'/'.$file;
  1545. // Is directory?
  1546. if (is_dir($sysFolderPath)) {
  1547. $folderExists = DocumentManager::folderExists(
  1548. $completePath,
  1549. $courseInfo,
  1550. $sessionId,
  1551. $groupId
  1552. );
  1553. if ($folderExists === true) {
  1554. switch ($whatIfFileExists) {
  1555. case 'overwrite':
  1556. $documentId = DocumentManager::get_document_id($courseInfo, $completePath, $sessionId);
  1557. if ($documentId) {
  1558. $newFolderData = DocumentManager::get_document_data_by_id(
  1559. $documentId,
  1560. $courseInfo['code'],
  1561. false,
  1562. $sessionId
  1563. );
  1564. }
  1565. break;
  1566. case 'rename':
  1567. $newFolderData = create_unexisting_directory(
  1568. $courseInfo,
  1569. $userId,
  1570. $sessionId,
  1571. $groupId,
  1572. null,
  1573. $base_work_dir,
  1574. $completePath,
  1575. null,
  1576. null,
  1577. true
  1578. );
  1579. break;
  1580. case 'nothing':
  1581. if ($output) {
  1582. $documentId = DocumentManager::get_document_id($courseInfo, $completePath, $sessionId);
  1583. if ($documentId) {
  1584. $folderData = DocumentManager::get_document_data_by_id(
  1585. $documentId,
  1586. $courseInfo['code'],
  1587. false,
  1588. $sessionId
  1589. );
  1590. Display::addFlash(
  1591. Display::return_message(
  1592. $folderData['path'].' '.get_lang(' already exists.'),
  1593. 'warning'
  1594. )
  1595. );
  1596. }
  1597. }
  1598. continue 2;
  1599. break;
  1600. }
  1601. } else {
  1602. $newFolderData = create_unexisting_directory(
  1603. $courseInfo,
  1604. $userId,
  1605. $sessionId,
  1606. $groupId,
  1607. null,
  1608. $base_work_dir,
  1609. $completePath,
  1610. null,
  1611. null,
  1612. false
  1613. );
  1614. }
  1615. // Recursive
  1616. add_all_documents_in_folder_to_database(
  1617. $courseInfo,
  1618. $userInfo,
  1619. $base_work_dir,
  1620. $sysFolderPath,
  1621. $sessionId,
  1622. $groupId,
  1623. $output,
  1624. $newFolderData,
  1625. $whatIfFileExists
  1626. );
  1627. } else {
  1628. // Rename
  1629. $uploadedFile = [
  1630. 'name' => $file,
  1631. 'tmp_name' => $sysFolderPath,
  1632. 'size' => filesize($sysFolderPath),
  1633. 'type' => null,
  1634. 'from_file' => true,
  1635. 'move_file' => true,
  1636. ];
  1637. handle_uploaded_document(
  1638. $courseInfo,
  1639. $uploadedFile,
  1640. $base_work_dir,
  1641. $parentPath,
  1642. $userId,
  1643. $groupId,
  1644. null,
  1645. 0,
  1646. $whatIfFileExists,
  1647. $output,
  1648. false,
  1649. null,
  1650. $sessionId
  1651. );
  1652. }
  1653. }
  1654. }
  1655. }