FileStorage.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <?php
  2. // @requires $_SERVER['SERVER_NAME']
  3. Lib::loadClass('RouteBase');
  4. Lib::loadClass('Schema_TableFactory');
  5. Lib::loadClass('Response');
  6. Lib::loadClass('UI');
  7. Lib::loadClass('OBJ');
  8. /*
  9. # FileStorage:
  10. - [x] create CRM_FILES - sql at the end of file
  11. - [x] only meta fields, perms? - no perms in relation based on record which is connected
  12. - [x] add file to storage - API POST/PUT action
  13. - [ ] base storage folder - from config - default /Library/Server/Web/Data/p5-pliki, chmod - add to instalator, upgrade
  14. - [x] subfolders like postfix, but [0-9],[A-Z] - substr(strrev(base_convert($id, 10, 10 + 26)), 0, 1) - 36 folderów
  15. - [x] file connected to user - A_RECORD_CREATE_AUTHOR
  16. - [ ] connect file to record - {tbl_name}__REF__FILES
  17. # upload API: `index.php?_route=FileStorage&_task=upload&name={file_name}`
  18. */
  19. class Route_FileStorage extends RouteBase {
  20. public function handleAuth() {
  21. if (!User::logged()) {
  22. User::authByRequest();
  23. }
  24. }
  25. public function defaultAction() {
  26. UI::gora();
  27. UI::menu();
  28. try {
  29. echo '...';
  30. } catch (Exception $e) {
  31. UI::alert('danger', "Error #" . $e->getCode() . "|" . $e->getLine() . ": " . $e->getMessage());
  32. }
  33. UI::dol();
  34. }
  35. public function isProduction() {// TODO: mv to Config
  36. // return V::get('P5_ENV', '/Library/Server/Web/Data/p5-file-storage', $_SERVER);
  37. // $env = getenv('P5_ENV');
  38. // if (empty($env)) $env = 'production';
  39. return ('production' == V::get('P5_ENV', 'production', $_SERVER));
  40. }
  41. public function getRootStoragePath() {
  42. return ($this->isProduction()) ? '/Library/Server/Web/Data/p5-file-storage' : '/tmp/test-upload-file-storage';
  43. }
  44. public function getTableName() {
  45. return ($this->isProduction()) ? 'CRM_FILES' : 'CRM_FILES__#DEV';
  46. }
  47. public function addFile($content, $name = '') {
  48. $rootFileStoragePath = $this->getRootStoragePath();
  49. $sqlLogin = User::getLogin();
  50. $sqlLabel = DB::getPDO()->quote($sqlLabel, PDO::PARAM_STR);
  51. $sqlTblName = $this->getTableName();
  52. $sql = "
  53. insert into {$sqlTblName} (`A_RECORD_CREATE_AUTHOR`,`A_RECORD_CREATE_DATE`,`FILE_LABEL`)
  54. values ('{$sqlLogin}', NOW(), {$sqlLabel})
  55. ";
  56. DBG::_('DBG', '>2', "sql", $sql, __CLASS__, __FUNCTION__, __LINE__);
  57. DB::getPDO()->exec($sql);
  58. $dbLastId = DB::getPDO()->lastInsertId();
  59. DBG::_('DBG', '>1', "dbLastId", $dbLastId, __CLASS__, __FUNCTION__, __LINE__);
  60. $filePath = $this->generateFilePathHashFromID($dbLastId);
  61. DBG::_('DBG', '>1', "filePath", $filePath, __CLASS__, __FUNCTION__, __LINE__);
  62. $absFilePath = "{$rootFileStoragePath}/{$filePath}";
  63. $dirPath = dirname($absFilePath);
  64. @mkdir($dirPath, $mode = 0777, $recursive = true);
  65. if (!file_exists($dirPath)) throw new Exception("Cannot create path");
  66. @chmod($dirPath, $mode = 0777);
  67. $fp = fopen($absFilePath, 'w');
  68. fwrite($fp, $fileContent);
  69. fclose($fp);
  70. if (!file_exists($dirPath)) throw new Exception("Cannot save file");
  71. }
  72. public function uploadAction() {
  73. try {
  74. $fileContent = Request::getRequestBody();
  75. $sqlLabel = V::get('name', '', $_REQUEST);
  76. $this->addFile($fileContent, $sqlLabel);
  77. echo 'file uploaded';
  78. } catch (Exception $e) {
  79. echo "Error #" . $e->getCode() . "|" . $e->getLine() . ": " . $e->getMessage();
  80. }
  81. }
  82. public function uploadFromBinaryTestAction() {
  83. try {
  84. $fileContent = Request::getRequestBody();
  85. $filePath = $this->getRootStoragePath() . '/test-upload-data.txt';
  86. $fp = fopen($filePath, 'w');
  87. fwrite($fp, $fileContent);
  88. fclose($fp);
  89. echo 'file uploaded?';
  90. } catch (Exception $e) {
  91. echo "Error #" . $e->getCode() . "|" . $e->getLine() . ": " . $e->getMessage();
  92. }
  93. }
  94. public function downloadTestAction() {
  95. try {
  96. $filePath = $this->getRootStoragePath() . '/test-upload-data.txt';
  97. if (!file_exists($filePath)) throw new Exception("file not exists!");
  98. header('Content-Description: File Transfer');
  99. header('Content-Type: application/octet-stream');
  100. header('Content-Disposition: attachment; filename="'.basename($filePath).'"');
  101. header('Expires: 0');
  102. header('Cache-Control: must-revalidate');
  103. header('Pragma: public');
  104. header('Content-Length: ' . filesize($filePath));
  105. readfile($filePath);
  106. exit;
  107. } catch (Exception $e) {
  108. echo "Error #" . $e->getCode() . "|" . $e->getLine() . ": " . $e->getMessage();
  109. }
  110. }
  111. public function uploadFromFormTestAction() {
  112. try {
  113. // $fileContent = Request::getRequestBody();
  114. DBG::_(true, true, '_POST', $_POST, __CLASS__, __FUNCTION__, __LINE__);
  115. // $filePath = $this->getRootStoragePath() . '/test-upload-data.txt';
  116. // $fp = fopen($filePath, 'w');
  117. // fwrite($fp, $fileContent);
  118. // fclose($fp);
  119. // echo 'file uploaded?';
  120. } catch (Exception $e) {
  121. echo "Error #" . $e->getCode() . "|" . $e->getLine() . ": " . $e->getMessage();
  122. }
  123. }
  124. public function uploadFormTestAction() {
  125. UI::gora();
  126. UI::menu();
  127. try {
  128. // multiple: <input type="file" id="file_input" multiple="multiple" />
  129. // only images: <input type="file" id="file_input" multiple="multiple" accept="image/*" />
  130. ?>
  131. <div class="container">
  132. <form>
  133. <input type="file" id="file_input" style="display:block; width:100%; height:200px; background-color:silver; text-align:center">
  134. </form>
  135. <button class="btn btn-primary" id="upload_file_as_binary_btn">upload as binary</button>
  136. <button class="btn btn-default" id="upload_file_as_form_btn">upload as form</button>
  137. <a class="btn btn-default" href="index.php?_route=FileStorage&_task=downloadTest" target="_blank">download</a>
  138. <blockquote>
  139. <p>root storage path: <code><?php echo $this->getRootStoragePath(); ?></code></p>
  140. <p>table name: <code><?php echo $this->getTableName(); ?></code></p>
  141. </blockquote>
  142. </div>
  143. <script>
  144. document.getElementById('file_input').addEventListener('change', function() {
  145. for (var i = 0; i < this.files.length; i++){
  146. var file = this.files[i];
  147. // This code is only for demo ...
  148. console.group("File "+i);
  149. console.log("name : " + file.name);
  150. console.log("size : " + file.size);
  151. console.log("type : " + file.type);
  152. console.log("date : " + file.lastModified);
  153. console.groupEnd();
  154. }
  155. }, false);
  156. function uploadFileAsForm(file) {
  157. var serverUrl = '<?php echo Request::getPathUri() . "index.php?_route=FileStorage&_task=uploadFromFormTest"; ?>';
  158. var xhr = new XMLHttpRequest();
  159. var fd = new FormData();
  160. xhr.open("POST", serverUrl, true);
  161. xhr.onreadystatechange = function() {
  162. if (xhr.readyState == 4 && xhr.status == 200) {
  163. // Every thing ok, file uploaded
  164. console.log(xhr.responseText); // handle response.
  165. }
  166. };
  167. fd.append("upload_file", file);
  168. xhr.send(fd);
  169. }
  170. function uploadFileAsBinary(file) {
  171. var serverUrl = '<?php echo Request::getPathUri() . "index.php?_route=FileStorage&_task=uploadFromBinaryTest"; ?>';
  172. var serverUrl = '<?php echo Request::getPathUri() . "index.php?_route=FileStorage&_task=upload"; ?>';
  173. var _dbg = true;
  174. // .set('Accept', 'application/json')
  175. superagent.post(serverUrl + '&name=' + file.name)
  176. .set('Content-Type', file.type)
  177. .send(file)
  178. .end(function(err, res) {
  179. if(_dbg)console.log('DBG: res:', res, 'res.body:', res.body);
  180. })
  181. return
  182. jQuery.ajax({
  183. type: "POST",
  184. beforeSend: function (request) {
  185. request.setRequestHeader("Content-Type", file.type);
  186. },
  187. url: serverUrl,
  188. data: file,
  189. processData: false,
  190. contentType: false,
  191. success: function (data) {
  192. console.log("File available at: ", data);
  193. },
  194. error: function (data) {
  195. var obj = jQuery.parseJSON(data);
  196. alert(obj.error);
  197. }
  198. })
  199. }
  200. jQuery('#upload_file_as_binary_btn').on('click', function() {
  201. var fileInput = document.getElementById('file_input')
  202. if (!fileInput) return false;// TODO: error msg
  203. if (!fileInput.files) return false;// TODO: error msg
  204. if (!fileInput.files.length) return false;// TODO: error msg
  205. var fileInfo = fileInput.files[0];
  206. console.log('fileInfo', fileInfo);
  207. uploadFileAsBinary(fileInfo);
  208. });
  209. jQuery('#upload_file_as_form_btn').on('click', function() {
  210. var fileInput = document.getElementById('file_input')
  211. if (!fileInput) return false;// TODO: error msg
  212. if (!fileInput.files) return false;// TODO: error msg
  213. if (!fileInput.files.length) return false;// TODO: error msg
  214. var fileInfo = fileInput.files[0];
  215. console.log('fileInfo', fileInfo);
  216. uploadFileAsForm(fileInfo);
  217. });
  218. </script>
  219. <?php
  220. } catch (Exception $e) {
  221. UI::alert('danger', "Error #" . $e->getCode() . "|" . $e->getLine() . ": " . $e->getMessage());
  222. }
  223. UI::dol();
  224. }
  225. public function generateFilePathHashFromID($intId) {
  226. // $base36Id = base_convert($intId, 10, 10 + 26);// 0-9 + A-Z
  227. $base36Id = base_convert($intId, 10, 10 + 6);// 0-9 + A-F
  228. $base36Str = str_pad($base36Id, 10, "0", STR_PAD_LEFT);
  229. $base36Str = strrev($base36Str);
  230. $pathParts = array();
  231. $pathParts[] = substr($base36Str, 0, 2);
  232. $pathParts[] = substr($base36Str, 2, 2);
  233. $pathParts[] = substr($base36Str, 4);
  234. $hashPath = implode("/", $pathParts);
  235. echo "ID($intId) converted to ({$base36Id})\t to ({$base36Str})\t to path ({$hashPath})\n";
  236. return $hashPath;
  237. }
  238. public function generatePathTestAction() {
  239. try {
  240. $start = 0;
  241. $start = 446071;
  242. $limit = $start + 100;
  243. echo '<pre>';
  244. for ($i = $start; $i < $limit; $i++) {
  245. $this->generateFilePathHashFromID($i);
  246. }
  247. echo '</pre>';
  248. } catch (Exception $e) {
  249. echo "Error #" . $e->getCode() . "|" . $e->getLine() . ": " . $e->getMessage();
  250. }
  251. }
  252. public function reinstallAction() {
  253. UI::gora();
  254. UI::menu();
  255. $this->reinstall();
  256. UI::dol();
  257. }
  258. public function reinstall() {
  259. try {
  260. DB::getPDO()->exec("
  261. CREATE TABLE IF NOT EXISTS `CRM_FILES` (
  262. `ID` int(11) NOT NULL AUTO_INCREMENT,
  263. `FILE_HASH` varchar(255) NOT NULL, -- generated from ID by hash function - only for cache
  264. `FILE_LABEL` varchar(255) NOT NULL, -- original file name or system name
  265. `FILE_TYPE` varchar(32) NOT NULL DEFAULT '', -- $TRG_FILE -> config/.cnf--folders...
  266. `FILE_MTIME` datetime NOT NULL,
  267. `FILE_VERSION` int(11) NOT NULL DEFAULT 0, -- used for update
  268. `A_STATUS` enum('WAITING','NORMAL','MONITOR','OFF_HARD','OFF_SOFT','DELETED') NOT NULL DEFAULT 'WAITING',
  269. `A_RECORD_CREATE_DATE` datetime DEFAULT NULL,
  270. `A_RECORD_CREATE_AUTHOR` varchar(40) NOT NULL DEFAULT '',
  271. `A_RECORD_UPDATE_DATE` datetime DEFAULT NULL,
  272. `A_RECORD_UPDATE_AUTHOR` varchar(40) NOT NULL DEFAULT '',
  273. `A_ADM_COMPANY` varchar(100) NOT NULL DEFAULT '',
  274. `A_CLASSIFIED` varchar(100) NOT NULL DEFAULT '',
  275. PRIMARY KEY (`ID`),
  276. KEY `FILE_TYPE` (`FILE_TYPE`)
  277. ) ENGINE=MyISAM DEFAULT CHARSET=latin2;
  278. ");
  279. DB::getPDO()->exec("
  280. CREATE TABLE IF NOT EXISTS `CRM_FILES__#DEV` (
  281. `ID` int(11) NOT NULL AUTO_INCREMENT,
  282. `FILE_HASH` varchar(255) NOT NULL, -- generated from ID by hash function - only for cache
  283. `FILE_LABEL` varchar(255) NOT NULL, -- original file name or system name
  284. `FILE_TYPE` varchar(32) NOT NULL DEFAULT '', -- $TRG_FILE -> config/.cnf--folders...
  285. `FILE_MTIME` datetime NOT NULL,
  286. `FILE_VERSION` int(11) NOT NULL DEFAULT 0, -- used for update
  287. `A_STATUS` enum('WAITING','NORMAL','MONITOR','OFF_HARD','OFF_SOFT','DELETED') NOT NULL DEFAULT 'WAITING',
  288. `A_RECORD_CREATE_DATE` datetime DEFAULT NULL,
  289. `A_RECORD_CREATE_AUTHOR` varchar(40) NOT NULL DEFAULT '',
  290. `A_RECORD_UPDATE_DATE` datetime DEFAULT NULL,
  291. `A_RECORD_UPDATE_AUTHOR` varchar(40) NOT NULL DEFAULT '',
  292. `A_ADM_COMPANY` varchar(100) NOT NULL DEFAULT '',
  293. `A_CLASSIFIED` varchar(100) NOT NULL DEFAULT '',
  294. PRIMARY KEY (`ID`),
  295. KEY `FILE_TYPE` (`FILE_TYPE`)
  296. ) ENGINE=MyISAM DEFAULT CHARSET=latin2;
  297. ");
  298. {// TODO: only in cli mode - require root perms
  299. $devRootPath = '/tmp/test-upload-file-storage';
  300. if (!file_exists($devRootPath)) {
  301. @mkdir($devRootPath, $mode = 0777, $recursive = true);
  302. if (!file_exists($devRootPath)) throw new Exception("Cannot create FileStorage root path for dev");
  303. @chmod($devRootPath, $mode = 0777);
  304. @chown($devRootPath, $user = '_www');
  305. }
  306. $productionRootPath = '/Library/Server/Web/Data/p5-file-storage';
  307. if (!file_exists($productionRootPath)) {
  308. @mkdir($productionRootPath, $mode = 0775, $recursive = true);
  309. if (!file_exists($productionRootPath)) throw new Exception("Cannot create FileStorage root path");
  310. @chmod($productionRootPath, $mode = 0775);
  311. @chown($productionRootPath, $user = '_www');
  312. }
  313. }
  314. } catch (Exception $e) {
  315. UI::alert('danger', $e->getMessage());
  316. }
  317. }
  318. }