/application/plugins/files/library/backend/FileRepository_Backend_FileSystem.class.php

https://github.com/dbernar1/Project-Pier · PHP · 463 lines · 214 code · 67 blank · 182 comment · 52 complexity · 8aab6281b48a183b931cf4e002005970 MD5 · raw file

  1. <?php
  2. /**
  3. * File repository backend that stores file in destination folder on file system
  4. *
  5. * @package FileRepository.backend
  6. * @http://www.projectpier.org/
  7. */
  8. class FileRepository_Backend_FileSystem implements FileRepository_Backend {
  9. /**
  10. * Path to repository directory in the file system
  11. *
  12. * @var string
  13. */
  14. private $repository_dir;
  15. /**
  16. * Array of attributes indexed by file ID
  17. *
  18. * @var array
  19. */
  20. private $attributes = null;
  21. /**
  22. * Construct the FileRepository_Backend_FileSystem
  23. *
  24. * @param string $repository_dir Path to the file system repository
  25. * @return FileRepository_Backend_FileSystem
  26. */
  27. function __construct($repository_dir) {
  28. $this->setRepositoryDir($repository_dir);
  29. $this->loadFileAttributes();
  30. } // __construct
  31. // ---------------------------------------------------
  32. // Backend implementation
  33. // ---------------------------------------------------
  34. /**
  35. * Return array of all files in repository
  36. *
  37. * @param void
  38. * @return null
  39. */
  40. function listFiles() {
  41. return array_keys($this->attributes);
  42. } // listFiles
  43. /**
  44. * Return number of files in repository
  45. *
  46. * @param void
  47. * @return integer
  48. */
  49. function countFiles() {
  50. return count($this->attributes);
  51. } // countFiles
  52. /**
  53. * Return the file path (trigger: streaming)
  54. *
  55. * @param string $file_id
  56. * @return string
  57. */
  58. function getFilePath($file_id) {
  59. if (!$this->isInRepository($file_id)) {
  60. throw new FileNotInRepositoryError($file_id);
  61. } // if
  62. $file_path = $this->_getFilePath($file_id);
  63. if (!is_file($file_path) || !is_readable($file_path)) {
  64. throw new FileNotInRepositoryError($file_id);
  65. } // if
  66. return $file_path;
  67. } // getFilePath
  68. /**
  69. * Read the content of the file and return it
  70. *
  71. * @param string $file_id
  72. * @return string
  73. */
  74. function getFileContent($file_id) {
  75. if (!$this->isInRepository($file_id)) {
  76. throw new FileNotInRepositoryError($file_id);
  77. } // if
  78. $file_path = $this->_getFilePath($file_id);
  79. if (!is_file($file_path) || !is_readable($file_path)) {
  80. throw new FileNotInRepositoryError($file_id);
  81. } // if
  82. return file_get_contents($file_path);
  83. } // getFileContent
  84. /**
  85. * Return all file attributes for specific file. If file has no attributes empty array is
  86. * returned
  87. *
  88. * @param string $file_id
  89. * @return array
  90. * @throws FileNotInRepositoryError
  91. */
  92. function getFileAttributes($file_id) {
  93. if (!$this->isInRepository($file_id)) {
  94. throw new FileNotInRepositoryError($file_id);
  95. } // if
  96. return is_array($this->attributes[$file_id]) ? $this->attributes[$file_id] : array();
  97. } // getFileAttributes
  98. /**
  99. * Return value of specific file attribute
  100. *
  101. * @param string $file_id
  102. * @param string $attribute_name
  103. * @param mixed $default Default value is returned when attribute is not found
  104. * @return mixed
  105. * @throws FileNotInRepositoryError if file is not in repository
  106. */
  107. function getFileAttribute($file_id, $attribute_name, $default = null) {
  108. if (!$this->isInRepository($file_id)) {
  109. throw new FileNotInRepositoryError($file_id);
  110. } // if
  111. if (isset($this->attributes[$file_id]) && is_array($this->attributes[$file_id]) && isset($this->attributes[$file_id][$attribute_name])) {
  112. return $this->attributes[$file_id][$attribute_name];
  113. } // if
  114. return $default;
  115. } // getFileAttribute
  116. /**
  117. * Set attribute value for specific file
  118. *
  119. * @param string $file_id
  120. * @param string $attribute_name
  121. * @param mixed $attribute_value Objects and resources are not supported. Scalars and arrays are
  122. * @return null
  123. * @throws FileNotInRepositoryError If $file_id does not exists in repository
  124. * @throws InvalidParamError If we have an object or a resource as attribute value
  125. */
  126. function setFileAttribute($file_id, $attribute_name, $attribute_value) {
  127. if (!$this->isInRepository($file_id)) {
  128. throw new FileNotInRepositoryError($file_id);
  129. } // if
  130. if (!isset($this->attributes[$file_id]) || !is_array($this->attributes[$file_id])) {
  131. $this->attributes[$file_id] = array();
  132. } // if
  133. if (is_object($attribute_value) || is_resource($attribute_value)) {
  134. throw new InvalidParamError('$attribute_value', $attribute_value, 'Objects and resources are not supported as attribute values');
  135. } // if
  136. if (!isset($this->attributes[$file_id][$attribute_name]) || ($this->attributes[$file_id][$attribute_name] <> $attribute_value)) {
  137. $this->attributes[$file_id][$attribute_name] = $attribute_value;
  138. $this->saveFileAttributes();
  139. } // if
  140. } // setFileAttribute
  141. /**
  142. * Add file to the repository
  143. *
  144. * @param string $source Path of the source file
  145. * @param array $attributes Array of file attributes
  146. * @return string File ID
  147. * @throws FileDnxError if source is not readable
  148. * @throws FailedToCreateFolderError if we fail to create subdirectory
  149. * @throws FileRepositoryAddError if we fail to move file to the repository
  150. */
  151. function addFile($source, $attributes = null) {
  152. if (!is_readable($source)) {
  153. throw new FileDnxError($source);
  154. } // if
  155. $file_id = $this->getUniqueId();
  156. $file_path = $this->_getFilePath($file_id);
  157. $destination_dir = dirname($file_path);
  158. if (!is_dir($destination_dir)) {
  159. if (!force_mkdir($destination_dir, 0777)) {
  160. throw new FailedToCreateFolderError($destination_dir);
  161. } // if
  162. } // if
  163. if (!copy($source, $file_path)) {
  164. throw new FileRepositoryAddError($source, $file_id);
  165. } // if
  166. $this->attributes[$file_id] = true; // register file
  167. if (is_array($attributes)) {
  168. foreach ($attributes as $attribute_name => $attribute_value) {
  169. $this->setFileAttribute($file_id, $attribute_name, $attribute_value);
  170. } // foreach
  171. } // if
  172. return $file_id;
  173. } // addFile
  174. /**
  175. * Update content of specific file
  176. *
  177. * @param string $file_id
  178. * @param string $source
  179. * @return boolean
  180. * @throws FileDnxError if source file is not readable
  181. * @throws FileNotInRepositoryError if $file_id is not in the repository
  182. * @throws FileRepositoryAddError if we fail to update file
  183. */
  184. function updateFileContent($file_id, $source) {
  185. if (!is_readable($source)) {
  186. throw new FileDnxError($source);
  187. } // if
  188. if (!$this->isInRepository($file_id)) {
  189. throw new FileNotInRepositoryError($file_id);
  190. } // if
  191. $file_path = $this->_getFilePath($file_id);
  192. if (!copy($source, $file_path)) {
  193. throw new FileRepositoryAddError($source, $file_id);
  194. } // if
  195. return true;
  196. } // updateFileContent
  197. /**
  198. * Delete file from the repository
  199. *
  200. * @param string $file_id
  201. * @return boolean
  202. * @throws FileNotInRepositoryError if $file_id is not in the repository
  203. * @throws FileRepositoryDeleteError if we fail to delete file
  204. */
  205. function deleteFile($file_id) {
  206. if (!$this->isInRepository($file_id)) {
  207. throw new FileNotInRepositoryError($file_id);
  208. } // if
  209. $file_path = $this->_getFilePath($file_id);
  210. if (!unlink($file_path)) {
  211. throw new FileRepositoryDeleteError($file_id);
  212. } // if
  213. if (isset($this->attributes[$file_id])) {
  214. unset($this->attributes[$file_id]);
  215. $this->saveFileAttributes();
  216. } // if
  217. $this->cleanUpDir($file_id);
  218. return true;
  219. } // deleteFile
  220. /**
  221. * Drop all files from repository
  222. *
  223. * @param void
  224. * @return null
  225. */
  226. function cleanUp() {
  227. $dir = dir($this->getRepositoryDir());
  228. if ($dir) {
  229. while (false !== ($entry = $dir->read())) {
  230. if (str_starts_with($entry, '.')) continue; // '.', '..' and hidden files ('.svn' for instance)
  231. $path = with_slash($this->getRepositoryDir()) . $entry;
  232. if (is_dir($path)) {
  233. delete_dir($path);
  234. } elseif (is_file($path)) {
  235. unlink($path);
  236. } // if
  237. } // while
  238. } // if
  239. } // cleanUp
  240. /**
  241. * Check if specific file is in repository
  242. *
  243. * @param string $file_id
  244. * @return boolean
  245. */
  246. function isInRepository($file_id) {
  247. return isset($this->attributes[$file_id]) && is_file($this->_getFilePath($file_id));
  248. } // isInRepository
  249. // ---------------------------------------------------
  250. // Utils
  251. // ---------------------------------------------------
  252. /**
  253. * Return file path by file_id. This function does not check if file really
  254. * exists in repository, it just creates and returns the path
  255. *
  256. * @param string $file_id
  257. * @return string
  258. */
  259. private function _getFilePath($file_id) {
  260. return with_slash($this->getRepositoryDir()) . $this->idToPath($file_id);
  261. } // _getFilePath
  262. /**
  263. * This function will clean up the file dir after the file was deleted
  264. *
  265. * @param string $file_id
  266. * @return null
  267. */
  268. private function cleanUpDir($file_id) {
  269. $path = $this->idToPath($file_id);
  270. if (!$path) return;
  271. $path_parts = explode('/', $path);
  272. $repository_path = with_slash($this->getRepositoryDir());
  273. $for_cleaning = array(
  274. $repository_path . $path_parts[0] . '/' . $path_parts[1] . '/' . $path_parts[2],
  275. $repository_path . $path_parts[0] . '/' . $path_parts[1],
  276. $repository_path . $path_parts[0],
  277. ); // array
  278. foreach ($for_cleaning as $dir) {
  279. if (is_dir_empty($dir)) {
  280. delete_dir($dir);
  281. } else {
  282. return; // break, not empty
  283. } // if
  284. } // foreach
  285. } // cleanUpDir
  286. /**
  287. * Convert file ID to repository file path
  288. *
  289. * @param string $file_id
  290. * @return string
  291. */
  292. private function idToPath($file_id) {
  293. if (strlen($file_id) == 40) {
  294. $parts = array();
  295. for ($i = 0; $i < 3; $i++) {
  296. $parts[] = substr($file_id, $i * 5, 5);
  297. } // for
  298. $parts[] = substr($file_id, 15, 25);
  299. return implode('/', $parts);
  300. } else {
  301. return null;
  302. } // if
  303. } // idToPath
  304. /**
  305. * Return unique file ID
  306. *
  307. * @param void
  308. * @return string
  309. */
  310. private function getUniqueId() {
  311. do {
  312. $id = sha1(uniqid(rand(), true));
  313. $file_path = $this->_getFilePath($id);
  314. } while (is_file($file_path));
  315. return $id;
  316. } // getUniqueId
  317. // ---------------------------------------------------
  318. // Attribute handling
  319. // ---------------------------------------------------
  320. /**
  321. * Load file attributes
  322. *
  323. * @param void
  324. * @return null
  325. */
  326. protected function loadFileAttributes() {
  327. $file = $this->getAttributesFilePath();
  328. if (is_file($file)) {
  329. if (!is_readable($file)) {
  330. throw new FileDnxError($file);
  331. } // if
  332. $attributes = include $file; // read from file
  333. if (is_array($attributes)) {
  334. $this->attributes = $attributes;
  335. } else {
  336. $this->attributes = array();
  337. $this->saveFileAttributes();
  338. } // if
  339. } else {
  340. $this->attributes = array();
  341. $this->saveFileAttributes();
  342. } // if
  343. } // loadFileAttributes
  344. /**
  345. * Safe file attribute value to a file
  346. *
  347. * @param void
  348. * @return boolean
  349. */
  350. protected function saveFileAttributes() {
  351. $file = $this->getAttributesFilePath();
  352. if (is_file($file) && !file_is_writable($file)) {
  353. throw new FileNotWritableError($file);
  354. } // if
  355. return file_put_contents($file, "<?php\n\nreturn " . var_export($this->attributes, true) . ";\n\n?>");
  356. } // saveFileAttributes
  357. /**
  358. * Return path of file where we save file attributes
  359. *
  360. * @param void
  361. * @return string
  362. */
  363. protected function getAttributesFilePath() {
  364. return with_slash($this->getRepositoryDir()) . 'attributes.php';
  365. } // getAttributesFilePath
  366. // ---------------------------------------------------
  367. // Getters and setters
  368. // ---------------------------------------------------
  369. /**
  370. * Get repository_dir
  371. *
  372. * @param null
  373. * @return string
  374. */
  375. function getRepositoryDir() {
  376. return $this->repository_dir;
  377. } // getRepositoryDir
  378. /**
  379. * Set repository_dir value
  380. *
  381. * @param string $value
  382. * @return null
  383. * @throws DirDnxError
  384. * @throws DirNotWritableError
  385. */
  386. function setRepositoryDir($value) {
  387. if (!is_null($value) && !is_dir($value)) {
  388. throw new DirDnxError($value);
  389. } // if
  390. if (!folder_is_writable($value)) {
  391. throw new DirNotWritableError($value);
  392. } // if
  393. $this->repository_dir = $value;
  394. } // setRepositoryDir
  395. } // FileRepository_Backend_FileSystem
  396. ?>