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, 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.
52 * @param object Group object.
53 * @param array (all fields from doc_groups) OR doc_group id from database.
54 * @return boolean True on success.
57 function __construct(&$Group, $data = false) {
61 if (!$Group || !is_object($Group)) {
62 $this->setError(_('Document Directory: No Valid Project'));
65 //did Group have an error?
66 if ($Group->isError()) {
67 $this->setError(_('Document Directory:').' '.$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 if (!$this->fetchData($data)) {
92 * create - create a new item in the database.
94 * @param string Item name.
95 * @return boolean on success / false on failure.
98 function create($name, $parent_doc_group = 0) {
103 $this->setError(_('Name is required'));
107 if ($parent_doc_group) {
108 // check if parent group exists
109 $res = db_query_params('SELECT * FROM doc_groups WHERE doc_group=$1 AND group_id=$2',
110 array($parent_doc_group, $this->Group->getID()));
111 if (!$res || db_numrows($res) < 1) {
112 $this->setError(_('Invalid Documents folder parent ID'));
116 $parent_doc_group = 0;
119 if ($parent_doc_group || $name != 'Uncategorized Submissions') {
120 $perm =& $this->Group->getPermission();
121 if (!$perm || !$perm->isDocEditor()) {
122 $this->setPermissionDeniedError();
127 $res = db_query_params('SELECT * FROM doc_groups WHERE groupname=$1 AND parent_doc_group=$2 AND group_id=$3',
130 $this->Group->getID())
132 if ($res && db_numrows($res) > 0) {
133 $this->setError(_('Folder name already exists'));
137 $user_id = ((session_loggedin()) ? user_getid() : 100);
138 $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)',
139 array ($this->Group->getID(),
140 htmlspecialchars($name),
146 if ($result && db_affected_rows($result) > 0) {
149 $this->setError(_('Error Adding Folder:').' '.db_error());
153 $doc_group = db_insertid($result, 'doc_groups', 'doc_group');
155 // Now set up our internal data structures
156 if (!$this->fetchData($doc_group)) {
160 if ($parent_doc_group) {
161 /* update the parent */
162 $parentDg = new DocumentGroup($this->Group, $parent_doc_group);
163 $parentDg->update($parentDg->getName(), $parentDg->getParentID(), 1, 0);
165 $this->sendNotice(true);
170 * delete - delete a DocumentGroup.
171 * WARNING delete is recursive and permanent
172 * @param integer Document Group Id, integer Project Group Id
173 * @return boolean success
176 function delete($doc_groupid, $project_group_id) {
177 $perm =& $this->Group->getPermission();
178 if (!$perm || !$perm->isDocEditor()) {
179 $this->setPermissionDeniedError();
183 /* delete documents in directory */
184 $result = db_query_params('DELETE FROM doc_data where doc_group = $1 and group_id = $2',
185 array($doc_groupid, $project_group_id));
187 /* delete directory */
188 $result = db_query_params('DELETE FROM doc_groups where doc_group = $1 and group_id = $2',
189 array($doc_groupid, $project_group_id));
197 /* update the parent */
198 $parentDg = new DocumentGroup($this->Group, $this->getParentID());
199 $parentDg->update($parentDg->getName(), $parentDg->getParentID(), 1, 1);
200 /* is there any subdir ? */
201 $subdir = db_query_params('select doc_group from doc_groups where parent_doc_group = $1 and group_id = $2',
202 array($doc_groupid, $project_group_id));
203 /* make a recursive call */
204 while ($arr = db_fetch_array($subdir)) {
205 $this->delete($arr['doc_group'], $project_group_id);
212 * injectArchive - extract the attachment and create the directory tree if needed
214 * @param array uploaded data
215 * @return boolean success or not
218 function injectArchive($uploaded_data) {
219 if (!is_uploaded_file($uploaded_data['tmp_name'])) {
220 $this->setError(_('Invalid file name.'));
223 if (function_exists('finfo_open')) {
224 $finfo = finfo_open(FILEINFO_MIME_TYPE);
225 $uploaded_data_type = finfo_file($finfo, $uploaded_data['tmp_name']);
227 $uploaded_data_type = $uploaded_data['type'];
230 switch ($uploaded_data_type) {
231 case "application/zip": {
232 $returned = $this->injectZip($uploaded_data);
235 case "application/x-rar-compressed": {
236 $returned = $this->injectRar($uploaded_data);
240 $this->setError( _('Unsupported injected file:') . ' ' .$uploaded_data_type);
248 * fetchData - re-fetch the data for this DocumentGroup from the database.
250 * @param integer ID of the doc_group.
251 * @return boolean success
254 function fetchData($id) {
255 $res = db_query_params('SELECT * FROM doc_groups WHERE doc_group = $1 and group_id = $2',
256 array($id, $this->Group->getID()));
257 if (!$res || db_numrows($res) < 1) {
258 $this->setError(_('Invalid Document Folder ID'));
261 $this->data_array = db_fetch_array($res);
262 $this->data_array['numberFiles'] = array();
263 db_free_result($res);
268 * getGroup - get the Group Object this DocumentGroup is associated with.
270 * @return Object Group.
273 function &getGroup() {
278 * getID - get this DocumentGroup's ID.
280 * @return integer The id #.
284 return $this->data_array['doc_group'];
288 * getID - get parent DocumentGroup's id.
290 * @return integer The id #.
293 function getParentID() {
294 return $this->data_array['parent_doc_group'];
298 * getName - get the name.
300 * @return string The name.
304 return $this->data_array['groupname'];
308 * getState - get the state id.
310 * @return integer The state id.
313 function getState() {
314 return $this->data_array['stateid'];
318 * getCreatedate - get the creation date.
320 * @return integer The creation date.
323 function getCreatedate() {
324 return $this->data_array['createdate'];
328 * getUpdatedate - get the update date.
330 * @return integer The update date.
333 function getUpdatedate() {
334 return $this->data_array['updatedate'];
338 * getLastModifyDate - get the bigger value between update date and creation date.
340 * @return integer The last modified date.
343 function getLastModifyDate() {
344 if($this->data_array['updatedate']) {
345 return $this->data_array['updatedate'];
347 return $this->data_array['createdate'];
353 * getMonitoredUserEmailAddress - get the email addresses of users who monitor this directory
355 * @return string The list of emails comma separated
357 function getMonitoredUserEmailAddress() {
358 $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()));
359 if (!$result || db_numrows($result) < 1) {
365 while ($arr = db_fetch_array($result)) {
369 $values .= $comma.$arr['email'];
377 * isMonitoredBy - get the monitored status of this document directory for a specific user id.
380 * @return boolean true if monitored by this user
382 function isMonitoredBy($userid = 'ALL') {
383 if ( $userid == 'ALL' ) {
386 $condition = 'user_id = '.$userid.' AND';
388 $result = db_query_params('SELECT * FROM docgroup_monitored_docman WHERE '.$condition.' docgroup_id = $1',
389 array($this->getID()));
391 if (!$result || db_numrows($result) < 1)
398 * removeMonitoredBy - remove this document directory for a specific user id for monitoring.
401 * @return boolean true if success
403 function removeMonitoredBy($userid) {
404 $result = db_query_params('DELETE FROM docgroup_monitored_docman WHERE docgroup_id = $1 AND user_id = $2',
405 array($this->getID(), $userid));
408 $this->setError(_('Unable To Remove Monitor').' : '.db_error());
415 * addMonitoredBy - add this document for a specific user id for monitoring.
418 * @return boolean true if success
420 function addMonitoredBy($userid) {
421 $result = db_query_params('SELECT * FROM docgroup_monitored_docman WHERE user_id=$1 AND docgroup_id = $2',
422 array($userid, $this->getID()));
424 if (!$result || db_numrows($result) < 1) {
425 $result = db_query_params('INSERT INTO docgroup_monitored_docman (docgroup_id,user_id) VALUES ($1,$2)',
426 array($this->getID(), $userid));
429 $this->setError(_('Unable To Add Monitor').' : '.db_error());
437 * clearMonitor - remove all entries of monitoring for this document.
439 * @return boolean true if success.
441 function clearMonitor() {
442 $result = db_query_params('DELETE FROM docgroup_monitored_docman WHERE docgroup_id = $1',
443 array($this->getID()));
445 $this->setError(_('Unable To Clear Monitor').' : '.db_error());
452 * getCreated_by - get the creator (user) id.
454 * @return integer The User id.
457 function getCreated_by() {
458 return $this->data_array['created_by'];
462 * update - update a DocumentGroup.
464 * @param string Name of the category.
465 * @param integer the doc_group id of the parent. default = 0
466 * @param integer update only the metadata : created_by, updatedate
467 * @return boolean success or not
470 function update($name, $parent_doc_group = 0, $metadata = 0) {
471 $perm =& $this->Group->getPermission();
472 if (!$perm || !$perm->isDocEditor()) {
473 $this->setPermissionDeniedError();
477 $this->setMissingParamsError();
481 if ($parent_doc_group) {
482 // check if parent group exists
483 $res = db_query_params('SELECT * FROM doc_groups WHERE doc_group=$1 AND group_id=$2',
484 array($parent_doc_group,
485 $this->Group->getID())
487 if (!$res || db_numrows($res) < 1) {
488 $this->setError(_('Invalid Documents Folder parent ID'));
494 $res = db_query_params('SELECT * FROM doc_groups WHERE groupname=$1 AND parent_doc_group=$2 AND group_id=$3',
497 $this->Group->getID())
499 if ($res && db_numrows($res) > 0) {
500 $this->setError(_('Documents Folder name already exists'));
505 $user_id = ((session_loggedin()) ? user_getid() : 100);
506 $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',
507 array(htmlspecialchars($name),
512 $this->Group->getID())
514 if ($result && db_affected_rows($result) > 0) {
515 $parentDg = new DocumentGroup($this->Group, $parent_doc_group);
516 if ($parentDg->getParentID())
517 $parentDg->update($parentDg->getName(), $parentDg->getParentID(), 1);
519 $this->fetchData($this->getID());
520 $this->sendNotice(false);
523 $this->setOnUpdateError(sprintf(_('Error: %s'), db_error()));
529 * hasDocuments - Recursive function that checks if this group or any of it childs has documents associated to it
531 * A group has associated documents if and only if there are documents associated to this
532 * group or to any of its childs
534 * @param array Array of nested groups information, fetched from DocumentGroupFactory class
535 * @param object The DocumentFactory object
536 * @param int (optional) State of the documents
537 * @return boolean success
540 function hasDocuments(&$nested_groups, &$document_factory, $stateid = 0) {
541 $doc_group_id = $this->getID();
542 static $result = array(); // this function will probably be called several times so we better store results in order to speed things up
543 if (!array_key_exists($stateid, $result) || !is_array($result[$stateid]))
544 $result[$stateid] = array();
546 if (array_key_exists($doc_group_id, $result[$stateid]))
547 return $result[$stateid][$doc_group_id];
549 // check if it has documents
551 $document_factory->setStateID($stateid);
553 $document_factory->setDocGroupID($doc_group_id);
554 $docs = $document_factory->getDocuments();
555 if (is_array($docs) && count($docs) > 0) { // this group has documents
556 $result[$stateid][$doc_group_id] = true;
560 // this group doesn't have documents... check recursively on the childs
561 if (array_key_exists($doc_group_id, $nested_groups) && is_array($nested_groups[$doc_group_id])) {
562 $count = count($nested_groups[$doc_group_id]);
563 for ($i=0; $i < $count; $i++) {
564 if ($nested_groups[$doc_group_id][$i]->hasDocuments($nested_groups, $document_factory, $stateid)) {
565 // child has documents
566 $result[$stateid][$doc_group_id] = true;
570 // no child has documents, then this group doesn't have associated documents
571 $result[$stateid][$doc_group_id] = false;
573 } else { // this group doesn't have childs
574 $result[$stateid][$doc_group_id] = false;
579 function getNumberOfDocuments($stateId = 1) {
580 if (isset($this->data_array['numberFiles'][$stateId]))
581 return $this->data_array['numberFiles'][$stateId];
583 $res = db_query_params('select count(*) from docdata_vw where doc_group = $1 and group_id = $2 and stateid = $3',
584 array($this->getID(), $this->Group->getID(), $stateId));
588 $arr = db_fetch_array($res);
589 $this->data_array['numberFiles'][$stateId] = $arr[0];
594 * hasSubgroup - Checks if this group has a specified subgroup associated to it
596 * @param array Array of nested groups information, fetched from DocumentGroupFactory class
597 * @param int ID of the subgroup
598 * @return boolean success
601 function hasSubgroup(&$nested_groups, $doc_subgroup_id) {
602 $doc_group_id = $this->getID();
604 if (is_array(@$nested_groups[$doc_group_id])) {
605 $count = count($nested_groups[$doc_group_id]);
606 for ($i=0; $i < $count; $i++) {
608 if ($nested_groups[$doc_group_id][$i]->getID() == $doc_subgroup_id) {
611 // recursively check if this child has this subgroup
612 if ($nested_groups[$doc_group_id][$i]->hasSubgroup($nested_groups, $doc_subgroup_id)) {
621 function getSubgroup($docGroupId, $stateId = 1) {
622 $returnArr = array();
623 $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',
624 array($docGroupId, $stateId, $this->Group->getID()));
629 while ($row = db_fetch_array($res)) {
630 $returnArr[] = $row['doc_group'];
637 * getPath - return the complete_path
639 * @param boolean does path is url clickable (default is false)
640 * @param boolean does path include this document group name ? (default is true)
641 * @return string the complete_path
644 function getPath($url = false, $includename = true) {
647 if ($this->getParentID()) {
648 $parentDg = new DocumentGroup($this->Group, $this->getParentID());
649 $returnPath = $parentDg->getPath($url);
653 $browselink = '/docman/?view=listfile&dirid='.$this->getID();
654 if (isset($GLOBALS['childgroup_id']) && $GLOBALS['childgroup_id']) {
655 $browselink .= '&childgroup_id='.$GLOBALS['childgroup_id'];
657 $browselink .= '&group_id='.$this->Group->getID();
658 $returnPath .= '/'.util_make_link($browselink, $this->getName(), array('title' => _('Browse this folder'), 'class' => 'tabtitle'));
660 $returnPath .= '/'.$this->getName();
663 if (!strlen($returnPath))
670 * setStateID - set the state id of this document group.
672 * @param int State ID.
673 * @return boolean success or not.
676 function setStateID($stateid) {
677 return $this->setValueinDB('stateid', $stateid);
681 * setParentDocGroupId - set the parent doc_group id of this document group.
683 * @param int Parent Doc_group Id.
684 * @return boolean success or not.
687 function setParentDocGroupId($parentDocGroupId) {
688 return $this->setValueinDB('parent_doc_group', $parentDocGroupId);
692 * sendNotice - Notifies of directory submissions
694 * @param boolean true = new directory (default value)
696 function sendNotice($new = true) {
697 $BCC = $this->Group->getDocEmailAddress();
698 if ($this->isMonitoredBy('ALL')) {
699 $BCC .= $this->getMonitoredUserEmailAddress();
701 if (strlen($BCC) > 0) {
702 $sess = session_get_user();
704 $status = _('New directory');
706 $status = _('Updated directory').' '._('by').' ' . $sess->getRealName();
708 $subject = '['.$this->Group->getPublicName().'] '.$status.' - '.$this->getName();
709 $body = _('Project')._(': ').$this->Group->getPublicName()."\n";
710 $body .= _('Directory:').' '.$this->getName()."\n";
711 $user = user_get_object($this->getCreated_by());
712 $body .= _('Submitter:').' '.$user->getRealName()." (".$user->getUnixName().") \n";
714 $body .= _('Updated By:').' '. $sess->getRealName();
716 $body .= "\n\n-------------------------------------------------------\n".
717 _('For more info, visit:').
718 "\n\n" . util_make_url('/docman/?group_id='.$this->Group->getID().'&view=listfile&dirid='.$this->getID());
720 $BCCarray = explode(',',$BCC);
721 foreach ($BCCarray as $dest_email) {
722 util_send_message($dest_email, $subject, $body, 'noreply@'.forge_get_config('web_host'), '', _('Docman'));
729 * injectZip - private method to inject a zip archive tree and files
731 * @param array uploaded zip
732 * @return boolean success or not
735 private function injectZip($uploadedZip) {
736 $zip = new ZipArchive();
737 if ($zip->open($uploadedZip['tmp_name'])) {
738 $extractDir = sys_get_temp_dir().'/'.uniqid();
739 if ($zip->extractTo($extractDir)) {
741 if ($this->injectContent($extractDir)) {
750 $this->setError(_('Unable to extract zipfile.'));
755 $this->setError(_('Unable to open zipfile.'));
760 * injectRar - private method to inject a rar archive tree and files
762 * @param array uploaded rar
763 * @return boolean success or not
766 private function injectRar($uploadedRar) {
771 * injectContent - private method to inject a directory tree and files
773 * @param string the directory to inject
774 * @return boolean success or not
777 private function injectContent($directory) {
778 if (is_dir($directory)) {
779 $dir_arr = scandir($directory);
780 for ($i = 0; $i < count($dir_arr); $i++) {
781 if ($dir_arr[$i] != '.' && $dir_arr[$i] != '..') {
782 if (is_dir($directory.'/'.$dir_arr[$i])) {
783 $ndg = new DocumentGroup($this->getGroup());
784 if ($ndg->create($dir_arr[$i], $this->getID())) {
785 if (!$ndg->injectContent($directory.'/'.$dir_arr[$i])) {
789 } elseif (is_file($directory.'/'.$dir_arr[$i])) {
790 $d = new Document($this->getGroup());
791 if (function_exists('finfo_open')) {
792 $finfo = finfo_open(FILEINFO_MIME_TYPE);
793 $dir_arr_type = finfo_file($finfo, $directory.'/'.$dir_arr[$i]);
795 $dir_arr_type = 'application/binary';
797 if (util_is_valid_filename($dir_arr[$i])) {
798 // ugly hack in case of ppl injecting zip at / when there is not directory in the zipfile...
799 // force upload in the first directory of the tree ...
800 if (!$this->getID()) {
801 $subGroupArrID = $this->getSubgroup(0);
802 $this->data_array['doc_group'] = $subGroupArrID[0];
804 if (!$d->create($dir_arr[$i], $dir_arr_type, $directory.'/'.$dir_arr[$i], $this->getID(), $dir_arr[$i].' '._('injected by Zip:').date(DATE_ATOM), _('no description'))) {
805 $this->setError($dir_arr[$i].': '.$d->getErrorMessage());
809 $this->setError($dir_arr[$i].': '._('Invalid file name.'));
813 $this->setError($dir_arr[$i].': '._('Unknown item.'));
820 $this->setError(_('Unable to open directory for inject into tree'));
826 * setValueinDB - private function to update columns in db
828 * @param string the column to update
829 * @param int the value to store
830 * @return boolean success or not
833 private function setValueinDB($column, $value) {
836 case "parent_doc_group": {
837 $qpa = db_construct_qpa();
838 $qpa = db_construct_qpa($qpa, 'UPDATE doc_groups SET ');
839 $qpa = db_construct_qpa($qpa, $column);
840 $qpa = db_construct_qpa($qpa, '=$1
844 $this->Group->getID(),
846 $res = db_query_qpa($qpa);
847 if (!$res || db_affected_rows($res) < 1) {
848 $this->setOnUpdateError(db_error().print_r($res));
854 $this->setOnUpdateError(_('wrong column name'));
863 // c-file-style: "bsd"