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 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 DocumentGroup(&$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)) {
93 * create - create a new item in the database.
95 * @param string Item name.
96 * @return boolean on success / false on failure.
99 function create($name, $parent_doc_group = 0) {
104 $this->setError(_('Name is required'));
108 if ($parent_doc_group) {
109 // check if parent group exists
110 $res = db_query_params('SELECT * FROM doc_groups WHERE doc_group=$1 AND group_id=$2',
111 array($parent_doc_group, $this->Group->getID()));
112 if (!$res || db_numrows($res) < 1) {
113 $this->setError(_('Invalid Documents folder parent ID'));
117 $parent_doc_group = 0;
120 if ($parent_doc_group || $name != 'Uncategorized Submissions') {
121 $perm =& $this->Group->getPermission();
122 if (!$perm || !$perm->isDocEditor()) {
123 $this->setPermissionDeniedError();
128 $res = db_query_params('SELECT * FROM doc_groups WHERE groupname=$1 AND parent_doc_group=$2 AND group_id=$3',
131 $this->Group->getID())
133 if ($res && db_numrows($res) > 0) {
134 $this->setError(_('Folder name already exists'));
138 $user_id = ((session_loggedin()) ? user_getid() : 100);
139 $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)',
140 array ($this->Group->getID(),
141 htmlspecialchars($name),
147 if ($result && db_affected_rows($result) > 0) {
150 $this->setError(_('Error Adding Folder:').' '.db_error());
154 $doc_group = db_insertid($result, 'doc_groups', 'doc_group');
156 // Now set up our internal data structures
157 if (!$this->fetchData($doc_group)) {
161 if ($parent_doc_group) {
162 /* update the parent */
163 $parentDg = new DocumentGroup($this->Group, $parent_doc_group);
164 $parentDg->update($parentDg->getName(), $parentDg->getParentID(), 1, 0);
171 * delete - delete a DocumentGroup.
172 * WARNING delete is recursive and permanent
173 * @param integer Document 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'];
356 * getCreated_by - get the creator (user) id.
358 * @return integer The User id.
361 function getCreated_by() {
362 return $this->data_array['created_by'];
366 * update - update a DocumentGroup.
368 * @param string Name of the category.
369 * @param integer the doc_group id of the parent. default = 0
370 * @param integer update only the metadata : created_by, updatedate
371 * @return boolean success or not
374 function update($name, $parent_doc_group = 0, $metadata = 0) {
375 $perm =& $this->Group->getPermission();
376 if (!$perm || !$perm->isDocEditor()) {
377 $this->setPermissionDeniedError();
381 $this->setMissingParamsError();
385 if ($parent_doc_group) {
386 // check if parent group exists
387 $res = db_query_params('SELECT * FROM doc_groups WHERE doc_group=$1 AND group_id=$2',
388 array($parent_doc_group,
389 $this->Group->getID())
391 if (!$res || db_numrows($res) < 1) {
392 $this->setError(_('Invalid Documents Folder parent ID'));
398 $res = db_query_params('SELECT * FROM doc_groups WHERE groupname=$1 AND parent_doc_group=$2 AND group_id=$3',
401 $this->Group->getID())
403 if ($res && db_numrows($res) > 0) {
404 $this->setError(_('Documents Folder name already exists'));
409 $user_id = ((session_loggedin()) ? user_getid() : 100);
410 $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',
411 array(htmlspecialchars($name),
416 $this->Group->getID())
418 if ($result && db_affected_rows($result) > 0) {
419 $parentDg = new DocumentGroup($this->Group, $parent_doc_group);
420 if ($parentDg->getParentID())
421 $parentDg->update($parentDg->getName(), $parentDg->getParentID(), 1);
423 $this->fetchData($this->getID());
426 $this->setOnUpdateError(sprintf(_('Error: %s'), db_error()));
432 * hasDocuments - Recursive function that checks if this group or any of it childs has documents associated to it
434 * A group has associated documents if and only if there are documents associated to this
435 * group or to any of its childs
437 * @param array Array of nested groups information, fetched from DocumentGroupFactory class
438 * @param object The DocumentFactory object
439 * @param int (optional) State of the documents
440 * @return boolean success
443 function hasDocuments(&$nested_groups, &$document_factory, $stateid = 0) {
444 $doc_group_id = $this->getID();
445 static $result = array(); // this function will probably be called several times so we better store results in order to speed things up
446 if (!array_key_exists($stateid, $result) || !is_array($result[$stateid]))
447 $result[$stateid] = array();
449 if (array_key_exists($doc_group_id, $result[$stateid]))
450 return $result[$stateid][$doc_group_id];
452 // check if it has documents
454 $document_factory->setStateID($stateid);
456 $document_factory->setDocGroupID($doc_group_id);
457 $docs = $document_factory->getDocuments();
458 if (is_array($docs) && count($docs) > 0) { // this group has documents
459 $result[$stateid][$doc_group_id] = true;
463 // this group doesn't have documents... check recursively on the childs
464 if (array_key_exists($doc_group_id, $nested_groups) && is_array($nested_groups[$doc_group_id])) {
465 $count = count($nested_groups[$doc_group_id]);
466 for ($i=0; $i < $count; $i++) {
467 if ($nested_groups[$doc_group_id][$i]->hasDocuments($nested_groups, $document_factory, $stateid)) {
468 // child has documents
469 $result[$stateid][$doc_group_id] = true;
473 // no child has documents, then this group doesn't have associated documents
474 $result[$stateid][$doc_group_id] = false;
476 } else { // this group doesn't have childs
477 $result[$stateid][$doc_group_id] = false;
482 function getNumberOfDocuments($stateId = 1) {
483 if (isset($this->data_array['numberFiles'][$stateId]))
484 return $this->data_array['numberFiles'][$stateId];
486 $res = db_query_params('select count(*) from docdata_vw where doc_group = $1 and group_id = $2 and stateid = $3',
487 array($this->getID(), $this->Group->getID(), $stateId));
491 $arr = db_fetch_array($res);
492 $this->data_array['numberFiles'][$stateId] = $arr[0];
497 * hasSubgroup - Checks if this group has a specified subgroup associated to it
499 * @param array Array of nested groups information, fetched from DocumentGroupFactory class
500 * @param int ID of the subgroup
501 * @return boolean success
504 function hasSubgroup(&$nested_groups, $doc_subgroup_id) {
505 $doc_group_id = $this->getID();
507 if (is_array(@$nested_groups[$doc_group_id])) {
508 $count = count($nested_groups[$doc_group_id]);
509 for ($i=0; $i < $count; $i++) {
511 if ($nested_groups[$doc_group_id][$i]->getID() == $doc_subgroup_id) {
514 // recursively check if this child has this subgroup
515 if ($nested_groups[$doc_group_id][$i]->hasSubgroup($nested_groups, $doc_subgroup_id)) {
524 function getSubgroup($docGroupId, $stateId = 1) {
525 $returnArr = array();
526 $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',
527 array($docGroupId, $stateId, $this->Group->getID()));
532 while ($row = db_fetch_array($res)) {
533 $returnArr[] = $row['doc_group'];
540 * getPath - return the complete_path
542 * @param boolean does path is url clickable (default is false)
543 * @param boolean does path include this document group name ? (default is true)
544 * @return string the complete_path
547 function getPath($url = false, $includename = true) {
550 if ($this->getParentID()) {
551 $parentDg = new DocumentGroup($this->Group, $this->getParentID());
552 $returnPath = $parentDg->getPath($url);
556 $browselink = '/docman/?view=listfile&dirid='.$this->getID();
557 if (isset($GLOBALS['childgroup_id']) && $GLOBALS['childgroup_id']) {
558 $browselink .= '&childgroup_id='.$GLOBALS['childgroup_id'];
560 $browselink .= '&group_id='.$this->Group->getID();
561 $returnPath .= '/'.util_make_link($browselink, $this->getName(), array('title' => _('Browse this folder'), 'class' => 'tabtitle'));
563 $returnPath .= '/'.$this->getName();
566 if (!strlen($returnPath))
573 * setStateID - set the state id of this document group.
575 * @param int State ID.
576 * @return boolean success or not.
579 function setStateID($stateid) {
580 return $this->__setValueinDB('stateid', $stateid);
584 * setParentDocGroupId - set the parent doc_group id of this document group.
586 * @param int Parent Doc_group Id.
587 * @return boolean success or not.
590 function setParentDocGroupId($parentDocGroupId) {
591 return $this->__setValueinDB('parent_doc_group', $parentDocGroupId);
595 * __injectZip - private method to inject a zip archive tree and files
597 * @param array uploaded zip
598 * @return boolean success or not
601 private function __injectZip($uploadedZip) {
602 $zip = new ZipArchive();
603 if ($zip->open($uploadedZip['tmp_name'])) {
604 $extractDir = forge_get_config('data_path').'/'.uniqid();
605 if ($zip->extractTo($extractDir)) {
607 if ($this->__injectContent($extractDir)) {
611 $this->setError(_('Unable inject zipfile.'));
615 $this->setError(_('Unable to extract zipfile.'));
620 $this->setError(_('Unable to open zipfile.'));
625 * __injectRar - private method to inject a rar archive tree and files
627 * @param array uploaded rar
628 * @return boolean success or not
631 private function __injectRar($uploadedRar) {
636 * __injectContent - private method to inject a directory tree and files
638 * @param string the directory to inject
639 * @return boolean success or not
642 private function __injectContent($directory) {
643 if (is_dir($directory)) {
644 $dir_arr = scandir($directory);
645 for ($i = 0; $i < count($dir_arr); $i++) {
646 if ($dir_arr[$i] != '.' && $dir_arr[$i] != '..') {
647 if (is_dir($directory.'/'.$dir_arr[$i])) {
648 if ($this->create($dir_arr[$i], $this->getID())) {
649 if (!$this->__injectContent($directory.'/'.$dir_arr[$i])) {
650 $this->setError(_('Unable to open directory for inject into tree'));
653 rmdir($directory.'/'.$dir_arr[$i]);
657 $d = new Document($this->getGroup());
658 if (function_exists('finfo_open')) {
659 $finfo = finfo_open(FILEINFO_MIME_TYPE);
660 $dir_arr_type = finfo_file($finfo, $directory.'/'.$dir_arr[$i]);
662 $dir_arr_type = 'application/binary';
664 $data = fread(fopen($directory.'/'.$dir_arr[$i], 'r'), filesize($directory.'/'.$dir_arr[$i]));
665 if (!$d->create($dir_arr[$i], $dir_arr_type, $data, $this->getID(), $dir_arr[$i].' '._('injected by Zip:').date(DATE_ATOM), 'no description')) {
666 $this->setError(_('Unable to add document from zip injection.'));
669 unlink($directory.'/'.$dir_arr[$i]);
676 $this->setError(_('Unable to open directory for inject into tree'));
681 * __setValueinDB - private function to update columns in db
683 * @param string the column to update
684 * @param int the value to store
685 * @return boolean success or not
688 private function __setValueinDB($column, $value) {
691 case "parent_doc_group": {
692 $qpa = db_construct_qpa();
693 $qpa = db_construct_qpa($qpa, 'UPDATE doc_groups SET ');
694 $qpa = db_construct_qpa($qpa, $column);
695 $qpa = db_construct_qpa($qpa, '=$1
699 $this->Group->getID(),
701 $res = db_query_qpa($qpa);
702 if (!$res || db_affected_rows($res) < 1) {
703 $this->setOnUpdateError(db_error().print_r($res));
709 $this->setOnUpdateError(_('wrong column name'));
718 // c-file-style: "bsd"