api.filestorage.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. <?php
  2. /**
  3. * Allows to attach files to random items in some scope
  4. */
  5. class FileStorage {
  6. /**
  7. * Contains system alter config as key=>value
  8. *
  9. * @var array
  10. */
  11. protected $altCfg = array();
  12. /**
  13. * Contains array of available files in database as id=>filedata
  14. *
  15. * @var array
  16. */
  17. protected $allFiles = array();
  18. /**
  19. * Contains current filestorage items scope
  20. *
  21. * @var string
  22. */
  23. protected $scope = '';
  24. /**
  25. * Contains current instance item ID in the current scope
  26. *
  27. * @var string
  28. */
  29. protected $itemId = '';
  30. /**
  31. * Contains current administrator login
  32. *
  33. * @var string
  34. */
  35. protected $myLogin = '';
  36. /**
  37. * Current instance database abstraction layer placeholder
  38. *
  39. * @var object
  40. */
  41. protected $storageDb = '';
  42. /**
  43. * Contains default file preview container size in px
  44. *
  45. * @var int
  46. */
  47. protected $filePreviewSize = 128;
  48. /**
  49. * Contains allowed file extensions. May be configurable in future.
  50. *
  51. * @var array
  52. */
  53. protected $allowedExtensions = array();
  54. /**
  55. * System message helper placeholder
  56. *
  57. * @var object
  58. */
  59. protected $messages = '';
  60. /**
  61. * Some predefined paths and URLs
  62. */
  63. const TABLE_STORAGE = 'filestorage';
  64. const STORAGE_PATH = 'content/documents/filestorage/';
  65. const URL_ME = '?module=filestorage';
  66. const URL_UPLOAD_FILE = '?module=filestorage&uploadfile=true';
  67. const EX_NOSCOPE = 'NO_OBJECT_SCOPE_SET';
  68. const EX_WRONG_EXT = 'WRONG_FILE_EXTENSION';
  69. /**
  70. * Initializes filestorage engine for some scope/item id
  71. *
  72. * @param string $scope
  73. * @param string $itemid
  74. *
  75. * @return void
  76. */
  77. public function __construct($scope = '', $itemid = '') {
  78. $this->initMessages();
  79. $this->loadAlter();
  80. $this->setAllowedExtenstions();
  81. $this->setScope($scope);
  82. $this->setItemid($itemid);
  83. $this->setLogin();
  84. $this->initDatabase();
  85. }
  86. /**
  87. * Inits system message helper for further usage
  88. */
  89. protected function initMessages() {
  90. $this->messages = new UbillingMessageHelper();
  91. }
  92. /**
  93. * Loads system alter config into private prop
  94. *
  95. * @return void
  96. */
  97. protected function loadAlter() {
  98. global $ubillingConfig;
  99. $this->altCfg = $ubillingConfig->getAlter();
  100. }
  101. /**
  102. * Sets allowed file extensions for this instance
  103. *
  104. * @return void
  105. */
  106. protected function setAllowedExtenstions() {
  107. $this->allowedExtensions = array(
  108. 'jpg',
  109. 'gif',
  110. 'png',
  111. 'jpeg',
  112. 'dia',
  113. 'xls',
  114. 'xlsx',
  115. 'doc',
  116. 'odt',
  117. 'ods',
  118. 'docx',
  119. 'pdf',
  120. 'txt',
  121. 'mp3',
  122. 'gsm',
  123. 'conf',
  124. 'mp4',
  125. 'mpg',
  126. 'mpeg',
  127. 'avi',
  128. 'ogg',
  129. 'zip',
  130. 'rar',
  131. 'tar',
  132. 'gz',
  133. 'tgz',
  134. 'bz2',
  135. '7z',
  136. 'sql',
  137. 'dbf',
  138. 'csv',
  139. );
  140. $this->allowedExtensions = array_flip($this->allowedExtensions); //extension string => index
  141. }
  142. /**
  143. * Object scope setter
  144. *
  145. * @param string $scope Object actual scope
  146. *
  147. * @return void
  148. */
  149. protected function setScope($scope) {
  150. $this->scope = ubRouting::filters($scope, 'mres');
  151. }
  152. /**
  153. * Object scope item Id setter
  154. *
  155. * @param string $scope Object actual id in current scope
  156. *
  157. * @return void
  158. */
  159. public function setItemid($itemid) {
  160. $this->itemId = ubRouting::filters($itemid, 'mres');
  161. }
  162. /**
  163. * Administrator login setter
  164. *
  165. * @return void
  166. */
  167. protected function setLogin() {
  168. $this->myLogin = whoami();
  169. }
  170. /**
  171. * Inits protected database absctaction layer for current instance
  172. *
  173. * @return void
  174. */
  175. protected function initDatabase() {
  176. $this->storageDb = new NyanORM(self::TABLE_STORAGE);
  177. }
  178. /**
  179. * Loads files list from database into private prop
  180. *
  181. * @return void
  182. */
  183. protected function loadAllFiles() {
  184. if ((!empty($this->scope)) AND ( !empty($this->itemId))) {
  185. $this->allFiles = $this->storageDb->getAll('id');
  186. }
  187. }
  188. /**
  189. * Registers uploaded file in database
  190. *
  191. * @param string $filename
  192. *
  193. * @return void
  194. */
  195. protected function registerFile($filename) {
  196. if ((!empty($this->scope)) AND ( !empty($this->itemId))) {
  197. $filename = ubRouting::filters($filename, 'mres');
  198. $date = curdatetime();
  199. $this->storageDb->data('scope', $this->scope);
  200. $this->storageDb->data('item', $this->itemId);
  201. $this->storageDb->data('date', $date);
  202. $this->storageDb->data('admin', $this->myLogin);
  203. $this->storageDb->data('filename', $filename);
  204. $this->storageDb->create();
  205. log_register('FILESTORAGE CREATE SCOPE `' . $this->scope . '` ITEM [' . $this->itemId . ']');
  206. }
  207. }
  208. /**
  209. * Deletes uploaded file from database
  210. *
  211. * @param int $fileId
  212. *
  213. * @return void
  214. */
  215. protected function unregisterFile($fileId) {
  216. if ((!empty($this->scope)) AND ( !empty($this->itemId))) {
  217. $fileId = ubRouting::filters($fileId, 'int');
  218. $date = curdatetime();
  219. $this->storageDb->where('id', '=', $fileId);
  220. $this->storageDb->delete();
  221. log_register('FILESTORAGE DELETE SCOPE `' . $this->scope . '` ITEM [' . $this->itemId . ']');
  222. }
  223. }
  224. /**
  225. * Returns basic file controls
  226. *
  227. * @param int $fileId existing file ID
  228. *
  229. * @return string
  230. */
  231. protected function fileControls($fileId) {
  232. $fileId = ubRouting::filters($fileId, 'int');
  233. $result = wf_tag('br');
  234. $downloadUrl = self::URL_ME . '&scope=' . $this->scope . '&itemid=' . $this->itemId . '&download=' . $fileId;
  235. $result .= wf_Link($downloadUrl, wf_img('skins/icon_download.png') . ' ' . __('Download'), false, 'ubButton') . ' ';
  236. if (cfr('FILESTORAGEDELETE')) {
  237. $deleteUrl = self::URL_ME . '&scope=' . $this->scope . '&itemid=' . $this->itemId . '&delete=' . $fileId;
  238. $result .= wf_AjaxLink($deleteUrl, web_delete_icon() . ' ' . __('Delete'), 'ajRefCont_' . $fileId, false, 'ubButton') . ' ';
  239. }
  240. return ($result);
  241. }
  242. /**
  243. * Returns file upload controls
  244. *
  245. * @return string
  246. */
  247. public function uploadControlsPanel() {
  248. $result = '';
  249. if ((!empty($this->scope)) AND ( !empty($this->itemId))) {
  250. $callBackUrl = '';
  251. if (ubRouting::checkGet('callback')) {
  252. $callBackUrl = '&callback=' . ubRouting::get('callback');
  253. }
  254. $controlUrl = self::URL_ME . '&scope=' . $this->scope . '&itemid=' . $this->itemId . '&mode=loader' . $callBackUrl;
  255. $result .= wf_Link($controlUrl, wf_img('skins/photostorage_upload.png') . ' ' . __('Upload file from HDD'), false, 'ubButton');
  256. }
  257. return ($result);
  258. }
  259. /**
  260. * Returns custom module backlinks for some scopes
  261. *
  262. * @param string $customBackLinkURL
  263. *
  264. * @return string
  265. */
  266. protected function backUrlHelper($customBackLinkURL = '') {
  267. $result = '';
  268. if ($this->scope == 'USERPROFILE') {
  269. $result = web_UserControls($this->itemId);
  270. }
  271. if ($this->scope == 'USERCONTRACT') {
  272. if (ubRouting::checkGet('callback')) {
  273. $result = wf_BackLink('?module=swcash&renderswusers=' . ubRouting::get('callback', 'int'));
  274. } else {
  275. $result = wf_BackLink('?module=contractedit&username=' . $this->itemId);
  276. }
  277. }
  278. if ($this->scope == SwitchCash::FILESTORAGE_SCOPE) {
  279. $switchId = ubRouting::filters($this->itemId, 'int');
  280. $result = wf_BackLink(SwitchCash::URL_ME . '&' . SwitchCash::ROUTE_EDIT . '=' . $switchId);
  281. }
  282. if ($this->scope == 'WAREHOUSEINCOME') {
  283. $incomeId = ubRouting::filters($this->itemId, 'int');
  284. $result = wf_BackLink(Warehouse::URL_ME . '&' . Warehouse::URL_VIEWERS . '&showinid=' . $incomeId);
  285. }
  286. if ($this->scope == 'CFITEMS') {
  287. $cleanLogin = explode(CustomFields::FILESTORAGE_ITEMID_DELIMITER, $this->itemId);
  288. $cleanLogin = $cleanLogin[0];
  289. $result = wf_BackLink(CustomFields::URL_EDIT_BACK . $cleanLogin);
  290. }
  291. if (ubRouting::checkGet('callback') and empty($result)) {
  292. $result = wf_BackLink(base64_decode(ubRouting::get('callback')));
  293. }
  294. if (!empty($customBackLinkURL)) {
  295. $result = wf_BackLink($customBackLinkURL);
  296. }
  297. return ($result);
  298. }
  299. /**
  300. * Renders file preview icon
  301. *
  302. * @param string $filename
  303. * @param int $size
  304. *
  305. * @return string
  306. */
  307. protected function renderFilePreviewIcon($filename, $size = '') {
  308. $result = '';
  309. if (!empty($filename)) {
  310. $fileTypeIconsPath = 'skins/fileicons/';
  311. $extension = pathinfo(strtolower($filename), PATHINFO_EXTENSION);
  312. $fileTypeIcon = 'skins/fileicons/package.png';
  313. $customTypeIcon = $fileTypeIconsPath . $extension . '.png';
  314. if (file_exists($customTypeIcon)) {
  315. $fileTypeIcon = $customTypeIcon;
  316. }
  317. //custom icon size
  318. if ($size) {
  319. $result .= wf_img_sized($customTypeIcon, $filename, $size);
  320. } else {
  321. $result .= wf_img($fileTypeIcon, $filename);
  322. }
  323. }
  324. return($result);
  325. }
  326. /**
  327. * Renders attached files preview with optional navigation button
  328. *
  329. * @param bool $navbutton
  330. * @param string $navbuttonText
  331. * @param string $navbuttonClass
  332. * @param int $iconSize
  333. * @param string $urlAppend
  334. * @param bool $targetBlank
  335. *
  336. * @return string
  337. */
  338. public function renderFilesPreview($navButton = false, $navbuttonText = '', $navbuttonClass = 'ubButton', $iconSize = '32', $urlAppend = '', $targetBlank = false) {
  339. $result = '';
  340. $target = ($targetBlank ? ' target="_blank" ' : '');
  341. if (empty($this->allFiles)) {
  342. $this->loadAllFiles();
  343. }
  344. $result .= wf_tag('div', false, '', '');
  345. if ($navButton) {
  346. $mgmtUrl = self::URL_ME . '&scope=' . $this->scope . '&itemid=' . $this->itemId . '&mode=list' . $urlAppend;
  347. $result .= wf_Link($mgmtUrl, wf_img('skins/photostorage_upload.png', __('Upload')) . $navbuttonText, false, $navbuttonClass, $target);
  348. }
  349. if (!empty($this->allFiles)) {
  350. foreach ($this->allFiles as $io => $eachFile) {
  351. if (($eachFile['scope'] == $this->scope) AND ( $eachFile['item'] == $this->itemId)) {
  352. $result .= wf_tag('div', false, '', 'style="border: 0px dotted; float:left; margin:2px;"');
  353. $result .= wf_tag('center');
  354. if (cfr('FILESTORAGE')) {
  355. $fileDownloadUrl = self::URL_ME . '&scope=' . $this->scope . '&itemid=' . $this->itemId . '&download=' . $eachFile['id'];
  356. $result .= wf_Link($fileDownloadUrl, $this->renderFilePreviewIcon($eachFile['filename'], $iconSize));
  357. } else {
  358. $result .= $this->renderFilePreviewIcon($eachFile['filename'], $iconSize);
  359. }
  360. $result .= wf_tag('center', true);
  361. $result .= wf_tag('div', true);
  362. }
  363. }
  364. }
  365. $result .= wf_tag('div', true);
  366. $result .= wf_CleanDiv();
  367. return($result);
  368. }
  369. /**
  370. * Returns current scope/item files list
  371. *
  372. * @return string
  373. */
  374. public function renderFilesList($customBackLinkURL = '') {
  375. if (empty($this->allFiles)) {
  376. $this->loadAllFiles();
  377. }
  378. $result = wf_AjaxLoader();
  379. if (!empty($this->allFiles)) {
  380. foreach ($this->allFiles as $io => $eachFile) {
  381. if (($eachFile['scope'] == $this->scope) AND ( $eachFile['item'] == $this->itemId)) {
  382. $dimensions = 'width:' . ($this->filePreviewSize + 220) . 'px;';
  383. $dimensions .= 'height:' . ($this->filePreviewSize + 60) . 'px;';
  384. $result .= wf_tag('div', false, '', 'style="border: 1px dotted; float:left; ' . $dimensions . ' margin:15px;" id="ajRefCont_' . $eachFile['id'] . '"');
  385. $result .= wf_tag('center');
  386. $result .= $this->renderFilePreviewIcon($eachFile['filename']);
  387. $result .= $this->fileControls($eachFile['id']);
  388. $result .= wf_tag('center', true);
  389. $result .= wf_tag('div', true);
  390. }
  391. }
  392. }
  393. $result .= wf_CleanDiv();
  394. $result .= wf_delimiter();
  395. $result .= $this->backUrlHelper($customBackLinkURL);
  396. return ($result);
  397. }
  398. /**
  399. * Downloads file by its id
  400. *
  401. * @param int $fileId database file ID
  402. *
  403. * @return void
  404. */
  405. public function catchDownloadFile($fileId) {
  406. $fileId = ubRouting::filters($fileId, 'int');
  407. if (empty($this->allFiles)) {
  408. $this->loadAllFiles();
  409. }
  410. if (!empty($fileId)) {
  411. @$filename = $this->allFiles[$fileId]['filename'];
  412. if (file_exists(self::STORAGE_PATH . $filename)) {
  413. zb_DownloadFile(self::STORAGE_PATH . $filename, 'default');
  414. } else {
  415. show_error(__('File not exist'));
  416. }
  417. } else {
  418. show_error(__('File not exists'));
  419. }
  420. }
  421. /**
  422. * Deletes file from database and FS by its ID
  423. *
  424. * @param int $fileId database file ID
  425. *
  426. * @return void
  427. */
  428. public function catchDeleteFile($fileId) {
  429. $fileId = ubRouting::filters($fileId, 'int');
  430. if (empty($this->allFiles)) {
  431. $this->loadAllFiles();
  432. }
  433. if (!empty($fileId)) {
  434. @$filename = $this->allFiles[$fileId]['filename'];
  435. if (file_exists(self::STORAGE_PATH . $filename)) {
  436. if (cfr('FILESTORAGEDELETE')) {
  437. unlink(self::STORAGE_PATH . $filename);
  438. $this->unregisterFile($fileId);
  439. $deleteResult = $this->messages->getStyledMessage(__('Deleted'), 'warning');
  440. } else {
  441. $deleteResult = $this->messages->getStyledMessage(__('Access denied'), 'error');
  442. }
  443. } else {
  444. $deleteResult = $this->messages->getStyledMessage(__('File not exist') . ': ' . $filename, 'error');
  445. }
  446. } else {
  447. $deleteResult = $this->messages->getStyledMessage(__('File not exist') . ': [' . $fileId . ']', 'error');
  448. }
  449. die($deleteResult);
  450. }
  451. /**
  452. * Catches file upload in background
  453. *
  454. * @return void
  455. */
  456. public function catchFileUpload() {
  457. if (ubRouting::checkGet('uploadfile')) {
  458. $callBackUrl = '';
  459. if (ubRouting::checkGet('callback')) {
  460. $callBackUrl = '&callback=' . ubRouting::get('callback');
  461. }
  462. if (!empty($this->scope)) {
  463. $fileAccepted = true;
  464. foreach ($_FILES as $file) {
  465. if ($file['tmp_name'] > '') {
  466. $uploadedFileExtension = pathinfo(strtolower($file['name']), PATHINFO_EXTENSION);
  467. if (!isset($this->allowedExtensions[$uploadedFileExtension])) {
  468. $fileAccepted = false;
  469. }
  470. }
  471. }
  472. if ($fileAccepted) {
  473. $originalFileName = zb_TranslitString($file['name']); //prevent cyrillic filenames on FS
  474. $newFilename = zb_rand_string(6) . '_' . $originalFileName;
  475. $newSavePath = self::STORAGE_PATH . $newFilename;
  476. @move_uploaded_file($_FILES['filestorageFileUpload']['tmp_name'], $newSavePath);
  477. if (file_exists($newSavePath)) {
  478. $uploadResult = $this->messages->getStyledMessage(__('File upload complete'), 'success');
  479. $this->registerFile($newFilename);
  480. ubRouting::nav(self::URL_ME . '&scope=' . $this->scope . '&itemid=' . $this->itemId . '&mode=loader&uldd=1' . $callBackUrl);
  481. } else {
  482. $uploadResult = $this->messages->getStyledMessage(__('File upload failed'), 'error');
  483. }
  484. } else {
  485. $uploadResult = $this->messages->getStyledMessage(__('File upload failed') . ': ' . self::EX_WRONG_EXT, 'error');
  486. }
  487. } else {
  488. $uploadResult = $this->messages->getStyledMessage(__('Strange exeption') . ': ' . self::EX_NOSCOPE, 'error');
  489. }
  490. show_window('', $uploadResult);
  491. show_window('', wf_BackLink(self::URL_ME . '&scope=' . $this->scope . '&itemid=' . $this->itemId . '&mode=loader' . $callBackUrl));
  492. }
  493. }
  494. /**
  495. * Returns file upload form
  496. *
  497. * @return string
  498. */
  499. public function renderUploadForm() {
  500. $callBackUrl = '';
  501. if (ubRouting::checkGet('callback')) {
  502. $callBackUrl = '&callback=' . ubRouting::get('callback');
  503. }
  504. $postUrl = self::URL_UPLOAD_FILE . '&scope=' . $this->scope . '&itemid=' . $this->itemId . $callBackUrl;
  505. $inputs = wf_tag('form', false, 'glamour', 'action="' . $postUrl . '" enctype="multipart/form-data" method="POST"');
  506. $inputs .= wf_tag('input', false, '', 'type="file" name="filestorageFileUpload"');
  507. $inputs .= wf_Submit(__('Upload'));
  508. $inputs .= wf_tag('form', true);
  509. $result = $inputs;
  510. $result .= wf_delimiter(2);
  511. if (ubRouting::checkGet('uldd')) {
  512. $result .= $this->messages->getStyledMessage(__('File upload complete'), 'success');
  513. $result .= wf_delimiter();
  514. }
  515. $callBackUrl = '';
  516. if (ubRouting::checkGet('callback')) {
  517. $callBackUrl = '&callback=' . ubRouting::get('callback');
  518. }
  519. $result .= wf_BackLink(self::URL_ME . '&scope=' . $this->scope . '&itemid=' . $this->itemId . '&mode=list' . $callBackUrl);
  520. return ($result);
  521. }
  522. }