PageRenderTime 84ms CodeModel.GetById 21ms RepoModel.GetById 2ms app.codeStats 0ms

/application/models/ProjectDataObject.class.php

https://github.com/fb83/Project-Pier
PHP | 852 lines | 340 code | 94 blank | 418 comment | 63 complexity | 5295f8774b1c9048ce108c23ede3f571 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. * Abstract class that implements methods that share all project objects (tags manipulation,
  4. * retrieving data about object creator etc.)
  5. *
  6. * Project object is application object with few extra functions
  7. *
  8. * @version 1.0
  9. * @http://www.projectpier.org/
  10. */
  11. abstract class ProjectDataObject extends ApplicationDataObject {
  12. /**
  13. * Cached parent project reference
  14. *
  15. * @var Project
  16. */
  17. protected $project = null;
  18. // ---------------------------------------------------
  19. // Tags
  20. // ---------------------------------------------------
  21. /**
  22. * If true this object will not throw object not taggable exception and will make tag methods available
  23. *
  24. * @var boolean
  25. */
  26. protected $is_taggable = false;
  27. // ---------------------------------------------------
  28. // Subscribers
  29. // ---------------------------------------------------
  30. /**
  31. * Mark this object as subscribable
  32. *
  33. * @var boolean
  34. */
  35. protected $is_subscribable = false;
  36. // ---------------------------------------------------
  37. // Search
  38. // ---------------------------------------------------
  39. /**
  40. * If this object is searchable search related methods will be unlocked for it. Else this methods will
  41. * throw exceptions pointing that this object is not searchable
  42. *
  43. * @var boolean
  44. */
  45. protected $is_searchable = false;
  46. /**
  47. * Array of searchable columns
  48. *
  49. * @var array
  50. */
  51. protected $searchable_columns = array();
  52. // ---------------------------------------------------
  53. // Comments
  54. // ---------------------------------------------------
  55. /**
  56. * Set this property to true if you want to let users post comments on this objects
  57. *
  58. * @var boolean
  59. */
  60. protected $is_commentable = false;
  61. /**
  62. * Cached array of all comments
  63. *
  64. * @var array
  65. */
  66. protected $all_comments;
  67. /**
  68. * Cached array of comments
  69. *
  70. * @var array
  71. */
  72. protected $comments;
  73. /**
  74. * Number of all comments
  75. *
  76. * @var integer
  77. */
  78. protected $all_comments_count;
  79. /**
  80. * Number of comments. If user is not member of owner company private comments
  81. * will be excluded from the count
  82. *
  83. * @var integer
  84. */
  85. protected $comments_count;
  86. // ---------------------------------------------------
  87. // Files
  88. // ---------------------------------------------------
  89. /**
  90. * Mark this object as file container (in this case files can be attached to
  91. * this object)
  92. *
  93. * @var boolean
  94. */
  95. protected $is_file_container = false;
  96. /**
  97. * Array of all attached files
  98. *
  99. * @var array
  100. */
  101. protected $all_attached_files;
  102. /**
  103. * Cached array of attached files (filtered by users access permissions)
  104. *
  105. * @var array
  106. */
  107. protected $attached_files;
  108. /**
  109. * Return owner project. If project_id field does not exists NULL is returned
  110. *
  111. * @param void
  112. * @return Project
  113. */
  114. function getProject() {
  115. if ($this->isNew() && function_exists('active_project')) {
  116. return active_project();
  117. } // if
  118. if (is_null($this->project)) {
  119. if ($this->columnExists('project_id')) {
  120. $this->project = Projects::findById($this->getProjectId());
  121. }
  122. } // if
  123. return $this->project;
  124. } // getProject
  125. // ---------------------------------------------------
  126. // Permissions
  127. // ---------------------------------------------------
  128. /**
  129. * Can $user view this object
  130. *
  131. * @param User $user
  132. * @return boolean
  133. */
  134. abstract function canView(User $user);
  135. /**
  136. * Check if this user can add a new object to this project. This method is called statically
  137. *
  138. * @param User $user
  139. * @param Project $project
  140. * @return boolean
  141. */
  142. abstract function canAdd(User $user, Project $project);
  143. /**
  144. * Returns true if this user can edit this object
  145. *
  146. * @param User $user
  147. * @return boolean
  148. */
  149. abstract function canEdit(User $user);
  150. /**
  151. * Returns true if this user can delete this object
  152. *
  153. * @param User $user
  154. * @return boolean
  155. */
  156. abstract function canDelete(User $user);
  157. /**
  158. * Check if specific user can comment on this object
  159. *
  160. * @param User $user
  161. * @return boolean
  162. * @throws InvalidInstanceError if $user is not instance of User or AnonymousUser
  163. */
  164. function canComment($user) {
  165. if (!($user instanceof User) && !($user instanceof AnonymousUser)) {
  166. throw new InvalidInstanceError('user', $user, 'User or AnonymousUser');
  167. } // if
  168. // Access permissions
  169. if ($user instanceof User) {
  170. if ($user->isAdministrator()) return true; // admins have all the permissions
  171. $project = $this->getProject();
  172. if (!($project instanceof Project)) {
  173. return false;
  174. }
  175. if (!$user->isProjectUser($project)) {
  176. return false; // not a project member
  177. }
  178. } // if
  179. if (!$this->isCommentable()) {
  180. return false;
  181. }
  182. if ($this->columnExists('comments_enabled') && !$this->getCommentsEnabled()) {
  183. return false;
  184. }
  185. if ($user instanceof AnonymousUser) {
  186. if ($this->columnExists('anonymous_comments_enabled') && !$this->getAnonymousCommentsEnabled()) {
  187. return false;
  188. }
  189. } // if
  190. return true;
  191. } // canComment
  192. /**
  193. * Returns true if user can attach file to this object
  194. *
  195. * @param User $user
  196. * @param Project $project
  197. * @return boolean
  198. */
  199. function canAttachFile(User $user, Project $project) {
  200. if (!$this->isFileContainer()) {
  201. return false;
  202. }
  203. if ($this->isNew()) {
  204. return $user->getProjectPermission($project, PermissionManager::CAN_UPLOAD_FILES);
  205. } else {
  206. return $this->canEdit($user);
  207. } // if
  208. } // canAttachFile
  209. /**
  210. * Check if $user can detach $file from this object
  211. *
  212. * @param User $user
  213. * @param ProjectFile $file
  214. * @return boolean
  215. */
  216. function canDetachFile(User $user, ProjectFile $file) {
  217. return $this->canEdit($user);
  218. } // canDetachFile
  219. // ---------------------------------------------------
  220. // Private
  221. // ---------------------------------------------------
  222. /**
  223. * Returns true if this object is private, false otherwise
  224. *
  225. * @param void
  226. * @return boolean
  227. */
  228. function isPrivate() {
  229. if ($this->columnExists('is_private')) {
  230. return $this->getIsPrivate();
  231. } else {
  232. return false;
  233. } // if
  234. } // isPrivate
  235. // ---------------------------------------------------
  236. // Tags
  237. // ---------------------------------------------------
  238. /**
  239. * Returns true if this project is taggable
  240. *
  241. * @param void
  242. * @return boolean
  243. */
  244. function isTaggable() {
  245. return $this->is_taggable;
  246. } // isTaggable
  247. /**
  248. * Return tags for this object
  249. *
  250. * @param void
  251. * @return array
  252. */
  253. function getTags() {
  254. if (!$this->isTaggable()) {
  255. throw new Error('Object not taggable');
  256. }
  257. return Tags::getTagsByObject($this, get_class($this->manager()));
  258. } // getTags
  259. /**
  260. * Return tag names for this object
  261. *
  262. * @access public
  263. * @param void
  264. * @return array
  265. */
  266. function getTagNames() {
  267. if (!$this->isTaggable()) {
  268. throw new Error('Object not taggable');
  269. }
  270. return Tags::getTagNamesByObject($this, get_class($this->manager()));
  271. } // getTagNames
  272. /**
  273. * Explode input string and set array of tags
  274. *
  275. * @param string $input
  276. * @return boolean
  277. */
  278. function setTagsFromCSV($input = '') {
  279. $tag_names = array();
  280. if (trim($input)) {
  281. $tag_names = explode(',', $input);
  282. foreach ($tag_names as $k => $v) {
  283. if (trim($v) <> '') {
  284. $tag_names[$k] = trim($v);
  285. }
  286. } // foreach
  287. } // if
  288. return $this->setTags($tag_names);
  289. } // setTagsFromCSV
  290. /**
  291. * Set object tags. This function accepts tags as params
  292. *
  293. * @access public
  294. * @param void
  295. * @return boolean
  296. */
  297. function setTags() {
  298. if(!plugin_active('tags')) { return null; }
  299. if (!$this->isTaggable()) {
  300. throw new Error('Object not taggable');
  301. }
  302. $args = array_flat(func_get_args());
  303. return Tags::setObjectTags($args, $this, get_class($this->manager()), $this->getProject());
  304. } // setTags
  305. /**
  306. * Clear object tags
  307. *
  308. * @access public
  309. * @param void
  310. * @return boolean
  311. */
  312. function clearTags() {
  313. if(!plugin_active('tags')) { return null; }
  314. if (!$this->isTaggable()) {
  315. throw new Error('Object not taggable');
  316. }
  317. return Tags::clearObjectTags($this, get_class($this->manager()));
  318. } // clearTags
  319. // ---------------------------------------------------
  320. // Searchable
  321. // ---------------------------------------------------
  322. /**
  323. * Returns true if this object is searchable (maked as searchable and has searchable columns)
  324. *
  325. * @param void
  326. * @return boolean
  327. */
  328. function isSearchable() {
  329. return $this->is_searchable && is_array($this->searchable_columns) && count($this->searchable_columns);
  330. } // isSearchable
  331. /**
  332. * Returns array of searchable columns or NULL if this object is not searchable or there
  333. * is no searchable columns
  334. *
  335. * @param void
  336. * @return array
  337. */
  338. function getSearchableColumns() {
  339. if (!$this->isSearchable()) {
  340. return null;
  341. }
  342. return $this->searchable_columns;
  343. } // getSearchableColumns
  344. /**
  345. * This function will return content of specific searchable column. It can be overriden in child
  346. * classes to implement extra behaviour (like reading file contents for project files)
  347. *
  348. * @param string $column_name Column name
  349. * @return string
  350. */
  351. function getSearchableColumnContent($column_name) {
  352. if (!$this->columnExists($column_name)) {
  353. throw new Error("Object column '$column_name' does not exist");
  354. }
  355. return (string) $this->getColumnValue($column_name);
  356. } // getSearchableColumnContent
  357. /**
  358. * Clear search index that is associated with this object
  359. *
  360. * @param void
  361. * @return boolean
  362. */
  363. function clearSearchIndex() {
  364. return SearchableObjects::dropContentByObject($this);
  365. } // clearSearchIndex
  366. // ---------------------------------------------------
  367. // Commentable
  368. // ---------------------------------------------------
  369. /**
  370. * Returns true if users can post comments on this object
  371. *
  372. * @param void
  373. * @return boolean
  374. */
  375. function isCommentable() {
  376. return (boolean) $this->is_commentable;
  377. } // isCommentable
  378. /**
  379. * Attach comment to this object
  380. *
  381. * @param Comment $comment
  382. * @return Comment
  383. */
  384. function attachComment(Comment $comment) {
  385. $manager_class = get_class($this->manager());
  386. $object_id = $this->getObjectId();
  387. if (($object_id == $comment->getRelObjectId()) && ($manager_class == $comment->getRelObjectManager())) {
  388. return true;
  389. } // if
  390. $comment->setRelObjectId($object_id);
  391. $comment->setRelObjectManager($manager_class);
  392. $comment->save();
  393. return $comment;
  394. } // attachComment
  395. /**
  396. * Return all comments
  397. *
  398. * @param void
  399. * @return boolean
  400. */
  401. function getAllComments() {
  402. if (is_null($this->all_comments)) {
  403. $this->all_comments = Comments::getCommentsByObject($this);
  404. } // if
  405. return $this->all_comments;
  406. } // getAllComments
  407. /**
  408. * Return object comments, filter private comments if user is not member of owner company
  409. *
  410. * @param void
  411. * @return array
  412. */
  413. function getComments() {
  414. if (logged_user()->isMemberOfOwnerCompany()) {
  415. return $this->getAllComments();
  416. } // if
  417. if (is_null($this->comments)) {
  418. $this->comments = Comments::getCommentsByObject($this, true);
  419. } // if
  420. return $this->comments;
  421. } // getComments
  422. /**
  423. * This function will return number of all comments
  424. *
  425. * @param void
  426. * @return integer
  427. */
  428. function countAllComments() {
  429. if (is_null($this->all_comments_count)) {
  430. $this->all_comments_count = Comments::countCommentsByObject($this);
  431. } // if
  432. return $this->all_comments_count;
  433. } // countAllComments
  434. /**
  435. * Return total number of comments
  436. *
  437. * @param void
  438. * @return integer
  439. */
  440. function countComments() {
  441. if (logged_user()->isMemberOfOwnerCompany()) {
  442. return $this->countAllComments();
  443. } // if
  444. if (is_null($this->comments_count)) {
  445. $this->comments_count = Comments::countCommentsByObject($this, true);
  446. } // if
  447. return $this->comments_count;
  448. } // countComments
  449. /**
  450. * Return # of specific object
  451. *
  452. * @param Comment $comment
  453. * @return integer
  454. */
  455. function getCommentNum(Comment $comment) {
  456. $comments = $this->getComments();
  457. if (is_array($comments)) {
  458. $counter = 0;
  459. foreach ($comments as $object_comment) {
  460. $counter++;
  461. if ($comment->getId() == $object_comment->getId()) {
  462. return $counter;
  463. } // if
  464. } // foreach
  465. } // if
  466. return 0;
  467. } // getCommentNum
  468. /**
  469. * Returns true if this function has associated comments
  470. *
  471. * @param void
  472. * @return boolean
  473. */
  474. function hasComments() {
  475. return (boolean) $this->countComments();
  476. } // hasComments
  477. /**
  478. * Clear object comments
  479. *
  480. * @param void
  481. * @return boolean
  482. */
  483. function clearComments() {
  484. return Comments::dropCommentsByObject($this);
  485. } // clearComments
  486. /**
  487. * This event is triggered when we create a new comments
  488. *
  489. * @param Comment $comment
  490. * @return boolean
  491. */
  492. function onAddComment(Comment $comment) {
  493. return true;
  494. } // onAddComment
  495. /**
  496. * This event is triggered when comment that belongs to this object is updated
  497. *
  498. * @param Comment $comment
  499. * @return boolean
  500. */
  501. function onEditComment(Comment $comment) {
  502. return true;
  503. } // onEditComment
  504. /**
  505. * This event is triggered when comment that belongs to this object is deleted
  506. *
  507. * @param Comment $comment
  508. * @return boolean
  509. */
  510. function onDeleteComment(Comment $comment) {
  511. return true;
  512. } // onDeleteComment
  513. /**
  514. * Per object comments lock. If there is no `comments_enabled` column this
  515. * function will return false
  516. *
  517. * @param void
  518. * @return boolean
  519. */
  520. function commentsEnabled() {
  521. return $this->columnExists('comments_enabled') ? (boolean) $this->getCommentsEnabled() : false;
  522. } // commentsEnabled
  523. /**
  524. * This function will return true if anonymous users can post comments on
  525. * this object. If column `anonymous_comments_enabled` does not exists this
  526. * function will return true
  527. *
  528. * @param void
  529. * @return boolean
  530. */
  531. function anonymousCommentsEnabled() {
  532. return $this->columnExists('anonymous_comments_enabled') ? (boolean) $this->getAnonymousCommentsEnabled() : false;
  533. } // anonymousCommentsEnabled
  534. // ---------------------------------------------------
  535. // Files
  536. // ---------------------------------------------------
  537. /**
  538. * This function will return true if this object can have files attached to it
  539. *
  540. * @param void
  541. * @return boolean
  542. */
  543. function isFileContainer() {
  544. return (boolean) $this->is_file_container;
  545. } // isFileContainer
  546. /**
  547. * Attach project file to this object
  548. *
  549. * @param ProjectFile $file
  550. * @return AttachedFiles
  551. */
  552. function attachFile(ProjectFile $file) {
  553. $manager_class = get_class($this->manager());
  554. $object_id = $this->getObjectId();
  555. $attached_file = AttachedFiles::findById(array(
  556. 'rel_object_manager' => $manager_class,
  557. 'rel_object_id' => $object_id,
  558. 'file_id' => $file->getId(),
  559. )); // findById
  560. if ($attached_file instanceof AttachedFile) {
  561. return $attached_file; // Already attached
  562. } // if
  563. $attached_file = new AttachedFile();
  564. $attached_file->setRelObjectManager($manager_class);
  565. $attached_file->setRelObjectId($object_id);
  566. $attached_file->setFileId($file->getId());
  567. $attached_file->save();
  568. if (!$file->getIsVisible()) {
  569. $file->setIsVisible(true);
  570. $file->setExpirationTime(EMPTY_DATETIME);
  571. $file->save();
  572. } // if
  573. return $attached_file;
  574. } // attachFile
  575. /**
  576. * Return all attached files
  577. *
  578. * @param void
  579. * @return array
  580. */
  581. function getAllAttachedFiles() {
  582. if (is_null($this->all_attached_files)) {
  583. $this->all_attached_files = AttachedFiles::getFilesByObject($this);
  584. } // if
  585. return $this->all_attached_files;
  586. } // getAllAttachedFiles
  587. /**
  588. * Return attached files but filter the private ones if user is not a member
  589. * of the owner company
  590. *
  591. * @param void
  592. * @return array
  593. */
  594. function getAttachedFiles() {
  595. if (logged_user()->isMemberOfOwnerCompany()) {
  596. return $this->getAllAttachedFiles();
  597. } // if
  598. if (is_null($this->attached_files)) {
  599. $this->attached_files = AttachedFiles::getFilesByObject($this, true);
  600. } // if
  601. return $this->attached_files;
  602. } // getAttachedFiles
  603. /**
  604. * Drop all relations with files for this object
  605. *
  606. * @param void
  607. * @return null
  608. */
  609. function clearAttachedFiles() {
  610. return AttachedFiles::clearRelationsByObject($this);
  611. } // clearAttachedFiles
  612. /**
  613. * Return attach files url
  614. *
  615. * @param void
  616. * @return string
  617. */
  618. function getAttachFilesUrl() {
  619. return get_url('files', 'attach_to_object', array(
  620. 'manager' => get_class($this->manager()),
  621. 'object_id' => $this->getObjectId(),
  622. 'active_project' => $this->getProject()->getId()
  623. )); // get_url
  624. } // getAttachFilesUrl
  625. /**
  626. * Return detach file URL
  627. *
  628. * @param ProjectFile $file
  629. * @return string
  630. */
  631. function getDetachFileUrl(ProjectFile $file) {
  632. return get_url('files', 'detach_from_object', array(
  633. 'manager' => get_class($this->manager()),
  634. 'object_id' => $this->getObjectId(),
  635. 'file_id' => $file->getId(),
  636. 'active_project' => $this->getProject()->getId()
  637. )); // get_url
  638. } // getDetachFileUrl
  639. /**
  640. * This event is triggered when we attach new files
  641. *
  642. * @param array $files
  643. * @return boolean
  644. */
  645. function onAttachFiles($files) {
  646. return true;
  647. } // onAttachFiles
  648. /**
  649. * This event is triggered when we detach files
  650. *
  651. * @param array $files
  652. * @return boolean
  653. */
  654. function onDetachFiles($files) {
  655. return true;
  656. } // onDetachFiles
  657. // ---------------------------------------------------
  658. // Subscribable
  659. // ---------------------------------------------------
  660. /**
  661. * Returns true if users can subscribe to this object
  662. *
  663. * @param void
  664. * @return boolean
  665. */
  666. function isSubscribable() {
  667. return (boolean) $this->is_subscribable;
  668. } // isSubscribable
  669. // ---------------------------------------------------
  670. // System
  671. // ---------------------------------------------------
  672. /**
  673. * Save object. If object is searchable this function will add conetent of searchable fields
  674. * to search index
  675. *
  676. * @param void
  677. * @return boolean
  678. */
  679. function save() {
  680. $result = parent::save();
  681. // If searchable refresh content in search table
  682. if ($this->isSearchable()) {
  683. SearchableObjects::dropContentByObject($this);
  684. $project = $this->getProject();
  685. foreach ($this->getSearchableColumns() as $column_name) {
  686. $content = $this->getSearchableColumnContent($column_name);
  687. if (trim($content) <> '') {
  688. $searchable_object = new SearchableObject();
  689. $searchable_object->setRelObjectManager(get_class($this->manager()));
  690. $searchable_object->setRelObjectId($this->getObjectId());
  691. $searchable_object->setColumnName($column_name);
  692. $searchable_object->setContent($content);
  693. if ($project instanceof Project) {
  694. $searchable_object->setProjectId($project->getId());
  695. }
  696. $searchable_object->setIsPrivate($this->isPrivate());
  697. $searchable_object->save();
  698. } // if
  699. } // if
  700. } // if
  701. return $result;
  702. } // save
  703. /**
  704. * Copy object
  705. *
  706. * @param void
  707. * @return boolean
  708. */
  709. function copy(&$source) {
  710. if ($source->isTaggable()) {
  711. //$this->copyTags($source);
  712. } // if
  713. if ($source->isSearchable()) {
  714. //$this->clearSearchIndex();
  715. } // if
  716. if ($this->isCommentable()) {
  717. //$this->copyComments($source);
  718. } // if
  719. if ($this->isFileContainer($source)) {
  720. //$this->copyAttachedFiles($source);
  721. } // if
  722. return parent::copy($source);
  723. } // copy
  724. /**
  725. * Delete object and drop content from search table
  726. *
  727. * @param void
  728. * @return boolean
  729. */
  730. function delete() {
  731. if ($this->isTaggable()) {
  732. $this->clearTags();
  733. } // if
  734. if ($this->isSearchable()) {
  735. $this->clearSearchIndex();
  736. } // if
  737. if ($this->isCommentable()) {
  738. $this->clearComments();
  739. } // if
  740. if ($this->isFileContainer()) {
  741. $this->clearAttachedFiles();
  742. } // if
  743. return parent::delete();
  744. } // delete
  745. /**
  746. * Return object path (location of the object)
  747. *
  748. * @param void
  749. * @return string
  750. */
  751. function getObjectPath() {
  752. $path = parent::getObjectPath();
  753. $p = $this->getProject();
  754. if (!is_null($p)) $path[] = $p->getObjectName();
  755. return $path;
  756. } // getObjectPath
  757. } // ProjectDataObject
  758. ?>