3 * FusionForge document manager
5 * Copyright 2000, Quentin Cregan/Sourceforge
6 * Copyright 2002, Tim Perdue/GForge, LLC
7 * Copyright 2009, Roland Mas
8 * Copyright 2010, Franck Villaume - Capgemini
9 * Copyright (C) 2011-2012 Alain Peyrat - Alcatel-Lucent
10 * Copyright 2012-2013, Franck Villaume - TrivialDev
11 * http://fusionforge.org
13 * This file is part of FusionForge. FusionForge is free software;
14 * you can redistribute it and/or modify it under the terms of the
15 * GNU General Public License as published by the Free Software
16 * Foundation; either version 2 of the Licence, or (at your option)
19 * FusionForge is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License along
25 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 require_once $gfcommon.'include/Error.class.php';
31 class DocumentGroup extends Error {
43 * @var array $data_array.
48 * DocumentGroup - constructor.
50 * Use this constructor if you are modifying an existing doc_group.
54 * @internal param \Group $object object.
55 * @internal param array $OR doc_group id from database.
56 * @return \DocumentGroup
59 function __construct(&$Group, $data = false) {
62 if (!$Group || !is_object($Group)) {
63 $this->setError(_('No Valid Group Object'));
66 if ($Group->isError()) {
67 $this->setError(_('Document Folder')._(': ').$Group->getErrorMessage());
70 $this->Group =& $Group;
73 if (is_array($data)) {
74 $this->data_array =& $data;
75 if ($this->data_array['group_id'] != $this->Group->getID()) {
76 $this->setError('DocumentGroup: '. _('group_id in db result does not match Group Object'));
77 $this->data_array = null;
82 $this->fetchData($data);
88 * create - create a new item in the database.
91 * @param int $parent_doc_group
92 * @internal param \Item $string name.
93 * @return boolean true on success / false on failure.
96 function create($name, $parent_doc_group = 0) {
101 $this->setError(_('Name is required'));
105 if ($parent_doc_group) {
106 // check if parent group exists
107 $res = db_query_params('SELECT * FROM doc_groups WHERE doc_group=$1 AND group_id=$2',
108 array($parent_doc_group, $this->Group->getID()));
109 if (!$res || db_numrows($res) < 1) {
110 $this->setError(_('Invalid Documents Folder parent ID'));
114 $parent_doc_group = 0;
117 if ($parent_doc_group || $name != 'Uncategorized Submissions') {
118 $perm =& $this->Group->getPermission();
119 if (!$perm || !$perm->isDocEditor()) {
120 $this->setPermissionDeniedError();
125 $res = db_query_params('SELECT * FROM doc_groups WHERE groupname=$1 AND parent_doc_group=$2 AND group_id=$3',
128 $this->Group->getID())
130 if ($res && db_numrows($res) > 0) {
131 $this->setError(_('Folder name already exists'));
135 $user_id = ((session_loggedin()) ? user_getid() : 100);
136 $result = db_query_params('INSERT INTO doc_groups (group_id, groupname, parent_doc_group, stateid, createdate, created_by) VALUES ($1, $2, $3, $4, $5, $6)',
137 array ($this->Group->getID(),
138 htmlspecialchars($name),
144 if ($result && db_affected_rows($result) > 0) {
147 $this->setError(_('Error Adding Folder:').' '.db_error());
151 $doc_group = db_insertid($result, 'doc_groups', 'doc_group');
153 // Now set up our internal data structures
154 if (!$this->fetchData($doc_group)) {
158 if ($parent_doc_group) {
159 /* update the parent */
160 $parentDg = new DocumentGroup($this->Group, $parent_doc_group);
161 $parentDg->update($parentDg->getName(), $parentDg->getParentID(), 1, 0);
163 $this->sendNotice(true);
168 * delete - delete a DocumentGroup.
169 * WARNING delete is recursive and permanent
171 * @param $doc_groupid
172 * @param $project_group_id
173 * @internal param \Document $integer Group Id, integer Project Group Id
174 * @return boolean success
177 function delete($doc_groupid, $project_group_id) {
178 $perm =& $this->Group->getPermission();
179 if (!$perm || !$perm->isDocEditor()) {
180 $this->setPermissionDeniedError();
184 /* delete documents in directory */
185 $result = db_query_params('DELETE FROM doc_data where doc_group = $1 and group_id = $2',
186 array($doc_groupid, $project_group_id));
188 /* delete directory */
189 $result = db_query_params('DELETE FROM doc_groups where doc_group = $1 and group_id = $2',
190 array($doc_groupid, $project_group_id));
198 /* update the parent */
199 $parentDg = new DocumentGroup($this->Group, $this->getParentID());
200 $parentDg->update($parentDg->getName(), $parentDg->getParentID(), 1, 1);
201 /* is there any subdir ? */
202 $subdir = db_query_params('select doc_group from doc_groups where parent_doc_group = $1 and group_id = $2',
203 array($doc_groupid, $project_group_id));
204 /* make a recursive call */
205 while ($arr = db_fetch_array($subdir)) {
206 $this->delete($arr['doc_group'], $project_group_id);
213 * injectArchive - extract the attachment and create the directory tree if needed
215 * @param array uploaded data
216 * @return boolean success or not
219 function injectArchive($uploaded_data) {
220 if (!is_uploaded_file($uploaded_data['tmp_name'])) {
221 $this->setError(_('Invalid file name.'));
224 if (function_exists('finfo_open')) {
225 $finfo = finfo_open(FILEINFO_MIME_TYPE);
226 $uploaded_data_type = finfo_file($finfo, $uploaded_data['tmp_name']);
228 $uploaded_data_type = $uploaded_data['type'];
231 switch ($uploaded_data_type) {
232 case "application/zip": {
233 $returned = $this->injectZip($uploaded_data);
236 case "application/x-rar-compressed": {
237 $returned = $this->injectRar($uploaded_data);
241 $this->setError( _('Unsupported injected file:') . ' ' .$uploaded_data_type);
249 * fetchData - re-fetch the data for this DocumentGroup from the database.
251 * @param integer ID of the doc_group.
252 * @return boolean success
255 function fetchData($id) {
256 $res = db_query_params('SELECT * FROM doc_groups WHERE doc_group = $1 and group_id = $2',
257 array($id, $this->Group->getID()));
258 if (!$res || db_numrows($res) < 1) {
259 $this->setError(_('Invalid Document Folder ID'));
262 $this->data_array = db_fetch_array($res);
263 $this->data_array['numberFiles'] = array();
264 db_free_result($res);
269 * getGroup - get the Group Object this DocumentGroup is associated with.
271 * @return Object Group.
274 function &getGroup() {
279 * getID - get this DocumentGroup's ID.
281 * @return integer The id #.
285 return $this->data_array['doc_group'];
289 * getID - get parent DocumentGroup's id.
291 * @return integer The id #.
294 function getParentID() {
295 return $this->data_array['parent_doc_group'];
299 * getName - get the name.
301 * @return string The name.
305 return $this->data_array['groupname'];
309 * getState - get the state id.
311 * @return integer The state id.
314 function getState() {
315 return $this->data_array['stateid'];
319 * getCreatedate - get the creation date.
321 * @return integer The creation date.
324 function getCreatedate() {
325 return $this->data_array['createdate'];
329 * getUpdatedate - get the update date.
331 * @return integer The update date.
334 function getUpdatedate() {
335 return $this->data_array['updatedate'];
339 * getLastModifyDate - get the bigger value between update date and creation date.
341 * @return integer The last modified date.
344 function getLastModifyDate() {
345 if($this->data_array['updatedate']) {
346 return $this->data_array['updatedate'];
348 return $this->data_array['createdate'];
354 * getMonitoredUserEmailAddress - get the email addresses of users who monitor this directory
356 * @return string The list of emails comma separated
358 function getMonitoredUserEmailAddress() {
359 $result = db_query_params('select users.email from users,docgroup_monitored_docman where users.user_id = docgroup_monitored_docman.user_id and docgroup_monitored_docman.docgroup_id = $1', array ($this->getID()));
360 if (!$result || db_numrows($result) < 1) {
366 while ($arr = db_fetch_array($result)) {
370 $values .= $comma.$arr['email'];
378 * isMonitoredBy - get the monitored status of this document directory for a specific user id.
380 * @param string $userid
381 * @internal param \User $int ID
382 * @return boolean true if monitored by this user
384 function isMonitoredBy($userid = 'ALL') {
385 if ( $userid == 'ALL' ) {
388 $condition = 'user_id = '.$userid.' AND';
390 $result = db_query_params('SELECT * FROM docgroup_monitored_docman WHERE '.$condition.' docgroup_id = $1',
391 array($this->getID()));
393 if (!$result || db_numrows($result) < 1)
400 * removeMonitoredBy - remove this document directory for a specific user id for monitoring.
403 * @return boolean true if success
405 function removeMonitoredBy($userid) {
406 $result = db_query_params('DELETE FROM docgroup_monitored_docman WHERE docgroup_id = $1 AND user_id = $2',
407 array($this->getID(), $userid));
410 $this->setError(_('Unable To Remove Monitor').' : '.db_error());
417 * addMonitoredBy - add this document for a specific user id for monitoring.
420 * @return boolean true if success
422 function addMonitoredBy($userid) {
423 $result = db_query_params('SELECT * FROM docgroup_monitored_docman WHERE user_id=$1 AND docgroup_id = $2',
424 array($userid, $this->getID()));
426 if (!$result || db_numrows($result) < 1) {
427 $result = db_query_params('INSERT INTO docgroup_monitored_docman (docgroup_id,user_id) VALUES ($1,$2)',
428 array($this->getID(), $userid));
431 $this->setError(_('Unable To Add Monitor').' : '.db_error());
439 * clearMonitor - remove all entries of monitoring for this document.
441 * @return boolean true if success.
443 function clearMonitor() {
444 $result = db_query_params('DELETE FROM docgroup_monitored_docman WHERE docgroup_id = $1',
445 array($this->getID()));
447 $this->setError(_('Unable To Clear Monitor').' : '.db_error());
454 * getCreated_by - get the creator (user) id.
456 * @return integer The User id.
459 function getCreated_by() {
460 return $this->data_array['created_by'];
464 * update - update a DocumentGroup.
466 * @param string Name of the category.
467 * @param integer the doc_group id of the parent. default = 0
468 * @param integer update only the metadata : created_by, updatedate
469 * @return boolean success or not
472 function update($name, $parent_doc_group = 0, $metadata = 0) {
473 $perm =& $this->Group->getPermission();
474 if (!$perm || !$perm->isDocEditor()) {
475 $this->setPermissionDeniedError();
479 $this->setMissingParamsError();
483 if ($parent_doc_group) {
484 // check if parent group exists
485 $res = db_query_params('SELECT * FROM doc_groups WHERE doc_group=$1 AND group_id=$2',
486 array($parent_doc_group,
487 $this->Group->getID())
489 if (!$res || db_numrows($res) < 1) {
490 $this->setError(_('Invalid Documents Folder parent ID'));
496 $res = db_query_params('SELECT * FROM doc_groups WHERE groupname=$1 AND parent_doc_group=$2 AND group_id=$3',
499 $this->Group->getID())
501 if ($res && db_numrows($res) > 0) {
502 $this->setError(_('Documents Folder name already exists'));
507 $user_id = ((session_loggedin()) ? user_getid() : 100);
508 $result = db_query_params('UPDATE doc_groups SET groupname=$1, parent_doc_group=$2, updatedate=$3, created_by=$4 WHERE doc_group=$5 AND group_id=$6',
509 array(htmlspecialchars($name),
514 $this->Group->getID())
516 if ($result && db_affected_rows($result) > 0) {
517 $parentDg = new DocumentGroup($this->Group, $parent_doc_group);
518 if ($parentDg->getParentID())
519 $parentDg->update($parentDg->getName(), $parentDg->getParentID(), 1);
521 $this->fetchData($this->getID());
522 $this->sendNotice(false);
525 $this->setOnUpdateError(sprintf(_('Error: %s'), db_error()));
531 * hasDocuments - Recursive function that checks if this group or any of it childs has documents associated to it
533 * A group has associated documents if and only if there are documents associated to this
534 * group or to any of its childs
536 * @param $nested_groups
537 * @param $document_factory
538 * @param int $stateid
539 * @internal param Array $array of nested groups information, fetched from DocumentGroupFactory class
540 * @internal param \The $object DocumentFactory object
541 * @internal param int $State of the documents
542 * @return boolean success
545 function hasDocuments(&$nested_groups, &$document_factory, $stateid = 0) {
546 $doc_group_id = $this->getID();
547 static $result = array(); // this function will probably be called several times so we better store results in order to speed things up
548 if (!array_key_exists($stateid, $result) || !is_array($result[$stateid]))
549 $result[$stateid] = array();
551 if (array_key_exists($doc_group_id, $result[$stateid]))
552 return $result[$stateid][$doc_group_id];
554 // check if it has documents
556 $document_factory->setStateID($stateid);
558 $document_factory->setDocGroupID($doc_group_id);
559 $docs = $document_factory->getDocuments();
560 if (is_array($docs) && count($docs) > 0) { // this group has documents
561 $result[$stateid][$doc_group_id] = true;
565 // this group doesn't have documents... check recursively on the childs
566 if (array_key_exists($doc_group_id, $nested_groups) && is_array($nested_groups[$doc_group_id])) {
567 $count = count($nested_groups[$doc_group_id]);
568 for ($i=0; $i < $count; $i++) {
569 if ($nested_groups[$doc_group_id][$i]->hasDocuments($nested_groups, $document_factory, $stateid)) {
570 // child has documents
571 $result[$stateid][$doc_group_id] = true;
575 // no child has documents, then this group doesn't have associated documents
576 $result[$stateid][$doc_group_id] = false;
578 } else { // this group doesn't have childs
579 $result[$stateid][$doc_group_id] = false;
584 function getNumberOfDocuments($stateId = 1) {
585 if (isset($this->data_array['numberFiles'][$stateId]))
586 return $this->data_array['numberFiles'][$stateId];
588 $res = db_query_params('select count(*) from docdata_vw where doc_group = $1 and group_id = $2 and stateid = $3',
589 array($this->getID(), $this->Group->getID(), $stateId));
593 $arr = db_fetch_array($res);
594 $this->data_array['numberFiles'][$stateId] = $arr[0];
599 * hasSubgroup - Checks if this group has a specified subgroup associated to it
601 * @param array Array of nested groups information, fetched from DocumentGroupFactory class
602 * @param int ID of the subgroup
603 * @return boolean success
606 function hasSubgroup(&$nested_groups, $doc_subgroup_id) {
607 $doc_group_id = $this->getID();
609 if (is_array(@$nested_groups[$doc_group_id])) {
610 $count = count($nested_groups[$doc_group_id]);
611 for ($i=0; $i < $count; $i++) {
613 if ($nested_groups[$doc_group_id][$i]->getID() == $doc_subgroup_id) {
616 // recursively check if this child has this subgroup
617 if ($nested_groups[$doc_group_id][$i]->hasSubgroup($nested_groups, $doc_subgroup_id)) {
627 * getSubgroup - Return the ids of any sub folders (first level only) in specific folder
629 * @param int ID of the specific folder
630 * @param int the state id of this specific folder (default is 1)
631 * @return array the ids of any sub folders
633 function getSubgroup($docGroupId, $stateId = 1) {
634 $returnArr = array();
635 $res = db_query_params('SELECT doc_group from doc_groups where parent_doc_group = $1 and stateid = $2 and group_id = $3 order by groupname',
636 array($docGroupId, $stateId, $this->Group->getID()));
641 while ($row = db_fetch_array($res)) {
642 $returnArr[] = $row['doc_group'];
649 * getPath - return the complete_path
651 * @param boolean does path is url clickable (default is false)
652 * @param boolean does path include this document group name ? (default is true)
653 * @return string the complete_path
656 function getPath($url = false, $includename = true) {
659 if ($this->getParentID()) {
660 $parentDg = new DocumentGroup($this->Group, $this->getParentID());
661 $returnPath = $parentDg->getPath($url);
665 $browselink = '/docman/?view=listfile&dirid='.$this->getID();
666 if (isset($GLOBALS['childgroup_id']) && $GLOBALS['childgroup_id']) {
667 $browselink .= '&childgroup_id='.$GLOBALS['childgroup_id'];
669 $browselink .= '&group_id='.$this->Group->getID();
670 $returnPath .= '/'.util_make_link($browselink, $this->getName(), array('title' => _('Browse this folder'), 'class' => 'tabtitle'));
672 $returnPath .= '/'.$this->getName();
675 if (!strlen($returnPath))
682 * setStateID - set the state id of this document group.
684 * @param int State ID.
685 * @return boolean success or not.
688 function setStateID($stateid) {
689 return $this->setValueinDB('stateid', $stateid);
693 * setParentDocGroupId - set the parent doc_group id of this document group.
695 * @param int Parent Doc_group Id.
696 * @return boolean success or not.
699 function setParentDocGroupId($parentDocGroupId) {
700 return $this->setValueinDB('parent_doc_group', $parentDocGroupId);
704 * sendNotice - Notifies of directory submissions
706 * @param boolean true = new directory (default value)
709 function sendNotice($new = true) {
710 $BCC = $this->Group->getDocEmailAddress();
711 if ($this->isMonitoredBy('ALL')) {
712 $BCC .= $this->getMonitoredUserEmailAddress();
714 if (strlen($BCC) > 0) {
715 $sess = session_get_user();
717 $status = _('New Folder');
719 $status = _('Updated folder by').' '.$sess->getRealName();
721 $subject = '['.$this->Group->getPublicName().'] '.$status.' - '.$this->getName();
722 $body = _('Project')._(': ').$this->Group->getPublicName()."\n";
723 $body .= _('Folder')._(': ').$this->getName()."\n";
724 $user = user_get_object($this->getCreated_by());
725 $body .= _('Submitter')._(': ').$user->getRealName()." (".$user->getUnixName().") \n";
727 $body .= _('Updated by')._(': ').$sess->getRealName();
729 $body .= "\n\n-------------------------------------------------------\n".
730 _('For more info, visit:').
731 "\n\n" . util_make_url('/docman/?group_id='.$this->Group->getID().'&view=listfile&dirid='.$this->getID());
733 $BCCarray = explode(',',$BCC);
734 foreach ($BCCarray as $dest_email) {
735 util_send_message($dest_email, $subject, $body, 'noreply@'.forge_get_config('web_host'), '', _('Docman'));
742 * injectZip - private method to inject a zip archive tree and files
744 * @param array uploaded zip
745 * @return boolean success or not
748 private function injectZip($uploadedZip) {
749 $zip = new ZipArchive();
750 if ($zip->open($uploadedZip['tmp_name'])) {
751 $extractDir = sys_get_temp_dir().'/'.uniqid();
752 if ($zip->extractTo($extractDir)) {
754 if ($this->injectContent($extractDir)) {
762 $this->setError(_('Unable to extract ZIP file.'));
767 $this->setError(_('Unable to open ZIP file.'));
772 * injectRar - private method to inject a rar archive tree and files
774 * @param array uploaded rar
775 * @return boolean success or not
778 private function injectRar($uploadedRar) {
783 * injectContent - private method to inject a directory tree and files
785 * @param string the directory to inject
786 * @return boolean success or not
789 private function injectContent($directory) {
790 if (is_dir($directory)) {
791 $dir_arr = scandir($directory);
792 for ($i = 0; $i < count($dir_arr); $i++) {
793 if ($dir_arr[$i] != '.' && $dir_arr[$i] != '..') {
794 if (is_dir($directory.'/'.$dir_arr[$i])) {
795 $ndg = new DocumentGroup($this->getGroup());
796 if ($ndg->create($dir_arr[$i], $this->getID())) {
797 if (!$ndg->injectContent($directory.'/'.$dir_arr[$i])) {
798 $this->setError($ndg->getErrorMessage());
802 } elseif (is_file($directory.'/'.$dir_arr[$i])) {
803 $d = new Document($this->getGroup());
804 if (function_exists('finfo_open')) {
805 $finfo = finfo_open(FILEINFO_MIME_TYPE);
806 $dir_arr_type = finfo_file($finfo, $directory.'/'.$dir_arr[$i]);
808 $dir_arr_type = 'application/binary';
810 if (util_is_valid_filename($dir_arr[$i])) {
811 // ugly hack in case of ppl injecting zip at / when there is not directory in the ZIP file...
812 // force upload in the first directory of the tree ...
813 if (!$this->getID()) {
814 $subGroupArrID = $this->getSubgroup(0);
815 $this->data_array['doc_group'] = $subGroupArrID[0];
817 if (strlen($dir_arr[$i]) < 5) {
818 $filename = $dir_arr[$i].' '._('(Title must be at least 5 characters.)');
820 $filename = $dir_arr[$i];
822 if (!$d->create($dir_arr[$i], $dir_arr_type, $directory.'/'.$dir_arr[$i], $this->getID(),
823 $filename, _('Injected by ZIP:').date(DATE_ATOM))) {
824 $this->setError($dir_arr[$i].': '.$d->getErrorMessage());
828 $this->setError($dir_arr[$i].': '._('Invalid file name.'));
832 $this->setError($dir_arr[$i].': '._('Unknown item.'));
839 $this->setError(_('Unable to open folder for injecting into tree'));
845 * setValueinDB - private function to update columns in db
847 * @param string the column to update
848 * @param int the value to store
849 * @return boolean success or not
852 private function setValueinDB($column, $value) {
855 case "parent_doc_group": {
856 $qpa = db_construct_qpa();
857 $qpa = db_construct_qpa($qpa, 'UPDATE doc_groups SET ');
858 $qpa = db_construct_qpa($qpa, $column);
859 $qpa = db_construct_qpa($qpa, '=$1
863 $this->Group->getID(),
865 $res = db_query_qpa($qpa);
866 if (!$res || db_affected_rows($res) < 1) {
867 $this->setOnUpdateError(db_error().print_r($res));
873 $this->setOnUpdateError(_('wrong column name'));
882 // c-file-style: "bsd"