PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/application/plugins/files/models/ProjectFile.class.php

https://github.com/fb83/Project-Pier
PHP | 688 lines | 294 code | 72 blank | 322 comment | 49 complexity | 8b784d95d97771ff3a9bce7ee073e58c MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, AGPL-3.0, LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. * ProjectFile class
  4. *
  5. * @http://www.projectpier.org/
  6. */
  7. class ProjectFile extends BaseProjectFile {
  8. /**
  9. * This project object is taggable
  10. *
  11. * @var boolean
  12. */
  13. protected $is_taggable = true;
  14. /**
  15. * Message comments are searchable
  16. *
  17. * @var boolean
  18. */
  19. protected $is_searchable = true;
  20. /**
  21. * Array of searchable columns
  22. *
  23. * @var array
  24. */
  25. protected $searchable_columns = array('filename', 'filecontent', 'description');
  26. /**
  27. * Project file is commentable object
  28. *
  29. * @var boolean
  30. */
  31. protected $is_commentable = true;
  32. /**
  33. * Cached parent folder object
  34. *
  35. * @var ProjectFolder
  36. */
  37. private $folder;
  38. /**
  39. * Cached file type object
  40. *
  41. * @var FileType
  42. */
  43. private $file_type;
  44. /**
  45. * Last revision instance
  46. *
  47. * @var ProjectFileRevision
  48. */
  49. private $last_revision;
  50. /**
  51. * Contruct the object
  52. *
  53. * @param void
  54. * @return null
  55. */
  56. function __construct() {
  57. $this->addProtectedAttribute('system_filename', 'filename', 'type_string', 'filesize');
  58. parent::__construct();
  59. } // __construct
  60. /**
  61. * Return parent folder instance
  62. *
  63. * @param void
  64. * @return ProjectFolder
  65. */
  66. function getFolder() {
  67. if (is_null($this->folder)) {
  68. $this->folder = ProjectFolders::findById($this->getFolderId());
  69. if (($this->folder instanceof ProjectFolder) && ($this->folder->getProjectId() <> $this->getProjectId())) {
  70. $this->folder = null;
  71. } // if
  72. } // if
  73. return $this->folder;
  74. } // getFolder
  75. /**
  76. * Return parent project instance
  77. *
  78. * @param void
  79. * @return Project
  80. */
  81. function xgetProject() {
  82. if (is_null($this->project)) {
  83. $this->project = Projects::findById($this->getProjectId());
  84. } // if
  85. return $this->project;
  86. } // getProject
  87. /**
  88. * Return all file revisions
  89. *
  90. * @param void
  91. * @return array
  92. */
  93. function getRevisions($exclude_last = false) {
  94. if ($exclude_last) {
  95. $last_revision = $this->getLastRevision();
  96. if ($last_revision instanceof ProjectFileRevision) {
  97. $conditions = DB::prepareString('`id` <> ? AND `file_id` = ?', array($last_revision->getId(), $this->getId()));
  98. }
  99. } // if
  100. if (!isset($conditions)) {
  101. $conditions = DB::prepareString('`file_id` = ?', array($this->getId()));
  102. }
  103. return ProjectFileRevisions::find(array(
  104. 'conditions' => $conditions,
  105. 'order' => '`created_on` DESC'
  106. )); // find
  107. } // getRevisions
  108. /**
  109. * Return the number of file revisions
  110. *
  111. * @param void
  112. * @return integer
  113. */
  114. function countRevisions() {
  115. return ProjectFileRevisions::count(array(
  116. '`file_id` = ?', $this->getId()
  117. )); // count
  118. } // countRevisions
  119. /**
  120. * Return revision number of last revision. If there is no revisions return 0
  121. *
  122. * @param void
  123. * @return integer
  124. */
  125. function getRevisionNumber() {
  126. $last_revision = $this->getLastRevision();
  127. return $last_revision instanceof ProjectFileRevision ? $last_revision->getRevisionNumber() : 0;
  128. } // getRevisionNumber
  129. /**
  130. * Return last revision of this file
  131. *
  132. * @param void
  133. * @return ProjectFileRevision
  134. */
  135. function getLastRevision() {
  136. if (is_null($this->last_revision)) {
  137. $this->last_revision = ProjectFileRevisions::findOne(array(
  138. 'conditions' => array('`file_id` = ?', $this->getId()),
  139. 'order' => '`created_on` DESC',
  140. 'limit' => 1,
  141. )); // findOne
  142. } // if
  143. return $this->last_revision;
  144. } // getLastRevision
  145. /**
  146. * Return file type object
  147. *
  148. * @param void
  149. * @return FileType
  150. */
  151. function getFileType() {
  152. $revision = $this->getLastRevision();
  153. return $revision instanceof ProjectFileRevision ? $revision->getFileType() : null;
  154. } // getFileType
  155. /**
  156. * Return URL of file type icon
  157. *
  158. * @access public
  159. * @param void
  160. * @return string
  161. */
  162. function getTypeIconUrl() {
  163. $last_revision = $this->getLastRevision();
  164. return $last_revision instanceof ProjectFileRevision ? $last_revision->getTypeIconUrl() : '';
  165. } // getTypeIconUrl
  166. // ---------------------------------------------------
  167. // Revision interface
  168. // ---------------------------------------------------
  169. /**
  170. * Return file type ID
  171. *
  172. * @param void
  173. * @return integer
  174. */
  175. function getFileTypeId() {
  176. $revision = $this->getLastRevision();
  177. return $revision instanceof ProjectFileRevision ? $revision->getFileTypeId() : null;
  178. } // getFileTypeId
  179. /**
  180. * Return type string. We need to know mime type when forwarding file
  181. * to the client
  182. *
  183. * @param void
  184. * @return string
  185. */
  186. function getTypeString() {
  187. $revision = $this->getLastRevision();
  188. return $revision instanceof ProjectFileRevision ? $revision->getTypeString() : '';
  189. } // getTypeString
  190. /**
  191. * Return file size in bytes
  192. *
  193. * @param void
  194. * @return integer
  195. */
  196. function getFileSize() {
  197. $revision = $this->getLastRevision();
  198. return $revision instanceof ProjectFileRevision ? $revision->getFileSize() : null;
  199. } // getFileSize
  200. /**
  201. * Return file content
  202. *
  203. * @param void
  204. * @return string
  205. */
  206. function getFileContent() {
  207. $revision = $this->getLastRevision();
  208. return $revision instanceof ProjectFileRevision ? $revision->getFileContent() : null;
  209. } // getFileContent
  210. // ---------------------------------------------------
  211. // Util functions
  212. // ---------------------------------------------------
  213. /**
  214. * This function will process uploaded file
  215. *
  216. * @param array $uploaded_file
  217. * @param boolean $create_revision Create new revision or update last one
  218. * @param string $revision_comment Revision comment, if any
  219. * @return ProjectFileRevision
  220. */
  221. function handleUploadedFile($uploaded_file, $create_revision = true, $revision_comment = '') {
  222. $revision = null;
  223. if (!$create_revision) {
  224. $revision = $this->getLastRevision();
  225. } // if
  226. if (!($revision instanceof ProjectFileRevision)) {
  227. $revision = new ProjectFileRevision();
  228. $revision->setFileId($this->getId());
  229. $revision->setRevisionNumber($this->getNextRevisionNumber());
  230. if ((trim($revision_comment) == '') && ($this->countRevisions() < 1)) {
  231. $revision_comment = lang('initial versions');
  232. } // if
  233. } // if
  234. $revision->deleteThumb(false); // remove thumb
  235. // We have a file to handle!
  236. if (!is_array($uploaded_file) || !isset($uploaded_file['name']) || !isset($uploaded_file['size']) || !isset($uploaded_file['type']) || !isset($uploaded_file['tmp_name']) || !is_readable($uploaded_file['tmp_name'])) {
  237. throw new InvalidUploadError($uploaded_file);
  238. } // if
  239. if (isset($uploaded_file['error']) && ($uploaded_file['error'] > UPLOAD_ERR_OK)) {
  240. throw new InvalidUploadError($uploaded_file);
  241. } // if
  242. // http://www.projectpier.org/node/2069
  243. if (empty($uploaded_file['type'])) {
  244. $uploaded_file['type'] = 'application/octet-stream'; // TODO get_mime_type_for_filename($uploaded_file['name']);
  245. }
  246. $repository_id = FileRepository::addFile($uploaded_file['tmp_name'], array('name' => $uploaded_file['name'], 'type' => $uploaded_file['type'], 'size' => $uploaded_file['size']));
  247. $revision->setRepositoryId($repository_id);
  248. $revision->deleteThumb(false);
  249. $revision->setFilesize($uploaded_file['size']);
  250. $revision->setFilename($uploaded_file['name']);
  251. $revision->setTypeString($uploaded_file['type']);
  252. $extension = get_file_extension(basename($uploaded_file['name']));
  253. if (trim($extension)) {
  254. $file_type = FileTypes::getByExtension($extension);
  255. if ($file_type instanceof Filetype) {
  256. $revision->setFileTypeId($file_type->getId());
  257. } // if
  258. } // if
  259. $revision->setComment($revision_comment);
  260. $revision->save();
  261. $this->last_revision = $revision; // update last revision
  262. return $revision;
  263. } // handleUploadedFile
  264. /**
  265. * Return next revision number
  266. *
  267. * @param void
  268. * @return integer
  269. */
  270. protected function getNextRevisionNumber() {
  271. $last_revision = $this->getLastRevision();
  272. return $last_revision instanceof ProjectFileRevision ? $last_revision->getRevisionNumber() + 1 : 1;
  273. } // getNextRevisionNumber
  274. // ---------------------------------------------------
  275. // URLs
  276. // ---------------------------------------------------
  277. /**
  278. * Return view message URL
  279. *
  280. * @access public
  281. * @param void
  282. * @return string
  283. */
  284. function getViewUrl() {
  285. return get_url('files', 'file_details', array('id' => $this->getId(), 'active_project' => $this->getProjectId()));
  286. } // getViewUrl
  287. /**
  288. * Return file details URL
  289. *
  290. * @param void
  291. * @return string
  292. */
  293. function getDetailsUrl() {
  294. return get_url('files', 'file_details', array(
  295. 'id' => $this->getId(),
  296. 'active_project' => $this->getProjectId())
  297. ); // get_url
  298. } // getDetailsUrl
  299. /**
  300. * Return revisions URL
  301. *
  302. * @param void
  303. * @return string
  304. */
  305. function getRevisionsUrl() {
  306. return $this->getDetailsUrl() . '#revisions';
  307. } // getRevisionsUrl
  308. /**
  309. * Return comments URL
  310. *
  311. * @param void
  312. * @return string
  313. */
  314. function getCommentsUrl() {
  315. return $this->getDetailsUrl() . '#objectComments';
  316. } // getCommentsUrl
  317. /**
  318. * Return file download URL
  319. *
  320. * @param void
  321. * @return string
  322. */
  323. function getDownloadUrl() {
  324. return get_url(
  325. 'files',
  326. 'download_file',
  327. array(
  328. 'id' => $this->getId(),
  329. 'active_project' => $this->getProjectId()
  330. )
  331. ); // get_url
  332. } // getDownloadUrl
  333. /**
  334. * Return add revision URL
  335. *
  336. * @param void
  337. * @return string
  338. */
  339. function getAddRevisionUrl() {
  340. return get_url(
  341. 'files',
  342. 'add_revision',
  343. array(
  344. 'id' => $this->getId(),
  345. 'active_project' => $this->getProjectId()
  346. )
  347. ); // get_url
  348. } // getAddRevisionUrl
  349. /**
  350. * Return edit file URL
  351. *
  352. * @param void
  353. * @return string
  354. */
  355. function getEditUrl() {
  356. return get_url('files', 'edit_file', array(
  357. 'id' => $this->getId(),
  358. 'active_project' => $this->getProjectId())
  359. ); // get_url
  360. } // getEditUrl
  361. /**
  362. * Return move file URL
  363. *
  364. * @param void
  365. * @return string
  366. */
  367. function getMoveUrl() {
  368. return get_url('files', 'move', array(
  369. 'id' => $this->getId(),
  370. 'active_project' => $this->getProjectId())
  371. ); // get_url
  372. } // getMoveUrl
  373. /**
  374. * Return delete file URL
  375. *
  376. * @param void
  377. * @return string
  378. */
  379. function getDeleteUrl() {
  380. return get_url('files', 'delete_file', array(
  381. 'id' => $this->getId(),
  382. 'active_project' => $this->getProjectId())
  383. ); // get_url
  384. } // getDeleteUrl
  385. // ---------------------------------------------------
  386. // Permissions
  387. // ---------------------------------------------------
  388. /**
  389. * Check CAN_MANAGE_FILES permission
  390. *
  391. * @access public
  392. * @param User $user
  393. * @return boolean
  394. */
  395. function canManage(User $user) {
  396. trace(__FILE__,'canManage');
  397. if (!$user->isProjectUser($this->getProject())) {
  398. return false;
  399. } // if
  400. return $user->getProjectPermission($this->getProject(), PermissionManager::CAN_MANAGE_FILES);
  401. } // canManage
  402. /**
  403. * Returns value of CAN_UPLOAD_FILES permission
  404. *
  405. * @param User $user
  406. * @param Project $project
  407. * @return boolean
  408. */
  409. function canUpload(User $user, Project $project) {
  410. trace(__FILE__,'canUpload');
  411. if (!$user->isProjectUser($project)) {
  412. return false;
  413. } // if
  414. return $user->getProjectPermission($project, PermissionManager::CAN_UPLOAD_FILES);
  415. } // canUpload
  416. /**
  417. * Empty implementation of abstract method. Message determins if user have view access
  418. *
  419. * @param void
  420. * @return boolean
  421. */
  422. function canView(User $user) {
  423. if ($this->isPrivate() && !$user->isMemberOfOwnerCompany()) {
  424. return false;
  425. } // if
  426. return true;
  427. } // canView
  428. /**
  429. * Returns true if user can download this file
  430. *
  431. * @param User $user
  432. * @return boolean
  433. */
  434. function canDownload(User $user) {
  435. return $this->canView($user);
  436. } // canDownload
  437. /**
  438. * Empty implementation of abstract methods. Messages determine does user have
  439. * permissions to add comment
  440. *
  441. * @param void
  442. * @return null
  443. */
  444. function canAdd(User $user, Project $project) {
  445. return $user->isAdministrator() || ProjectFile::canUpload($user, $project);
  446. } // canAdd
  447. /**
  448. * Check if specific user can edit this file
  449. *
  450. * @access public
  451. * @param User $user
  452. * @return boolean
  453. */
  454. function canEdit(User $user) {
  455. if ($user->isAdministrator()) {
  456. return true; // give access to admin
  457. } // if
  458. if (!$user->isProjectUser($this->getProject())) {
  459. return false;
  460. } // if
  461. if (!$this->canManage($user)) {
  462. return false; // user don't have access to this project or can't manage files
  463. } // if
  464. if ($this->isPrivate() && !$user->isMemberOfOwnerCompany()) {
  465. return false; // reserved only for members of owner company
  466. } // if
  467. return true;
  468. } // canEdit
  469. /**
  470. * Returns true if $user can update file options
  471. *
  472. * @param User $user
  473. * @return boolean
  474. */
  475. function canUpdateOptions(User $user) {
  476. return $this->canEdit($user) && $user->isMemberOfOwnerCompany();
  477. } // canUpdateOptions
  478. /**
  479. * Check if specific user can delete this comment
  480. *
  481. * @access public
  482. * @param User $user
  483. * @return boolean
  484. */
  485. function canDelete(User $user) {
  486. if ($user->isAdministrator()) {
  487. return true;
  488. } // if
  489. if (!$user->isProjectUser($this->getProject())) {
  490. return false;
  491. } // if
  492. if (!$this->canManage(logged_user())) {
  493. return false; // user don't have access to this project or can't manage files
  494. } // if
  495. if ($this->isPrivate() && !$user->isMemberOfOwnerCompany()) {
  496. return false; // reserved only for members of owner company
  497. } // if
  498. return true;
  499. } // canDelete
  500. // ---------------------------------------------------
  501. // System
  502. // ---------------------------------------------------
  503. /**
  504. * Validate before save
  505. *
  506. * @param array $error
  507. * @return null
  508. */
  509. function validate(&$errors) {
  510. if (!$this->validatePresenceOf('filename')) {
  511. $errors[] = lang('filename required');
  512. } // if
  513. } // validate
  514. /**
  515. * Delete this file and all of its revisions
  516. *
  517. * @param void
  518. * @return boolean
  519. */
  520. function delete() {
  521. $this->clearRevisions();
  522. $this->clearObjectRelations();
  523. return parent::delete();
  524. } // delete
  525. /**
  526. * Remove all revisions associate with this file
  527. *
  528. * @param void
  529. * @return null
  530. */
  531. function clearRevisions() {
  532. $revisions = $this->getRevisions();
  533. if (is_array($revisions)) {
  534. foreach ($revisions as $revision) {
  535. $revision->delete();
  536. } // foreach
  537. } // if
  538. } // clearRevisions
  539. /**
  540. * Remove all object relations from the database
  541. *
  542. * @param void
  543. * @return boolean
  544. */
  545. function clearObjectRelations() {
  546. return AttachedFiles::clearRelationsByFile($this);
  547. } // clearObjectRelations
  548. /**
  549. * This function will return content of specific searchable column.
  550. *
  551. * It uses inherited behaviour for all columns except for `filecontent`. In case of this column function will return
  552. * file content if file type is marked as searchable (text documents, office documents etc).
  553. *
  554. * @param string $column_name
  555. * @return string
  556. */
  557. function getSearchableColumnContent($column_name) {
  558. if ($column_name == 'filecontent') {
  559. $file_type = $this->getFileType();
  560. // Unknown type or type not searchable
  561. if (!($file_type instanceof FileType) || !$file_type->getIsSearchable()) {
  562. return null;
  563. } // if
  564. $content = $this->getFileContent();
  565. if (strlen($content) < MAX_SEARCHABLE_FILE_SIZE) {
  566. return $content;
  567. } // if
  568. } else {
  569. return parent::getSearchableColumnContent($column_name);
  570. } // if
  571. } // getSearchableColumnContent
  572. // ---------------------------------------------------
  573. // ApplicationDataObject implementation
  574. // ---------------------------------------------------
  575. /**
  576. * Return object name
  577. *
  578. * @access public
  579. * @param void
  580. * @return string
  581. */
  582. function getObjectName() {
  583. return end(explode('/',$this->getFilename()));
  584. } // getObjectName
  585. /**
  586. * Return object type name
  587. *
  588. * @param void
  589. * @return string
  590. */
  591. function getObjectTypeName() {
  592. return lang('file');
  593. } // getObjectTypeName
  594. /**
  595. * Return object URl
  596. *
  597. * @access public
  598. * @param void
  599. * @return string
  600. */
  601. function getObjectUrl() {
  602. return $this->getDetailsurl();
  603. } // getObjectUrl
  604. /**
  605. * Return object path (location of the object)
  606. *
  607. * @param void
  608. * @return string
  609. */
  610. function getObjectPath() {
  611. $f = $this->getFolder();
  612. if (is_null($f)) return parent::getObjectPath();
  613. return $f->getObjectPath();
  614. } // getObjectPath
  615. } // ProjectFile
  616. ?>