3 * FusionForge document manager
5 * Copyright 2000, Quentin Cregan/Sourceforge
6 * Copyright 2002-2003, Tim Perdue/GForge, LLC
7 * Copyright 2009, Roland Mas
8 * Copyright 2010-2011, Franck Villaume - Capgemini
9 * Copyright 2011-2012, Franck Villaume - TrivialDev
10 * Copyright (C) 2011-2012 Alain Peyrat - Alcatel-Lucent
11 * Copyright 2012, Franck Villaume - TrivialDev
12 * http://fusionforge.org
14 * This file is part of FusionForge. FusionForge is free software;
15 * you can redistribute it and/or modify it under the terms of the
16 * GNU General Public License as published by the Free Software
17 * Foundation; either version 2 of the Licence, or (at your option)
20 * FusionForge is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License along
26 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
27 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
30 require_once $gfcommon.'include/Error.class.php';
31 require_once $gfcommon.'docman/Parsedata.class.php';
32 require_once $gfcommon.'docman/DocumentManager.class.php';
33 require_once $gfcommon.'docman/DocumentGroup.class.php';
34 require_once $gfcommon.'docman/DocumentStorage.class.php';
37 class Document extends Error {
40 * Associative array of data from db.
42 * @var array $data_array.
56 * @param object The Group object to which this document is associated.
57 * @param int The docid.
58 * @param array The associative array of data.
59 * @return boolean success.
61 function __construct(&$Group, $docid = false, $arr = false) {
63 if (!$Group || !is_object($Group)) {
64 $this->setError('Document:: '. _('No Valid Group Object'));
67 if ($Group->isError()) {
68 $this->setError('Document:: '. $Group->getErrorMessage());
71 $this->Group =& $Group;
74 if (!$arr || !is_array($arr)) {
75 if (!$this->fetchData($docid)) {
79 $this->data_array =& $arr;
80 if ($this->data_array['group_id'] != $this->Group->getID()) {
81 $this->setError('Document:: '. _('group_id in db result does not match Group Object'));
82 $this->data_array = null;
86 if (!$this->isPublic()) {
87 $perm =& $this->Group->getPermission();
89 if (!$perm || !is_object($perm) || !$perm->isDocEditor()) {
90 $this->setPermissionDeniedError();
91 $this->data_array = null;
99 * create - use this function to create a new entry in the database.
101 * @param string The filename of this document. Can be a URL.
102 * @param string The filetype of this document. If filename is URL, this should be 'URL';
103 * @param string The absolute path file itself.
104 * @param int The doc_group id of the doc_groups table.
105 * @param string The title of this document.
106 * @param string The description of this document.
107 * @param int The state id of the document. At creation, can not be deleted status.
108 * @return boolean success.
110 function create($filename, $filetype, $data, $doc_group, $title, $description, $stateid = 0) {
111 if (strlen($title) < 5) {
112 $this->setError(_('Title Must Be At Least 5 Characters'));
115 if (strlen($description) < 10) {
116 $this->setError(_('Document Description Must Be At Least 10 Characters'));
120 $user_id = ((session_loggedin()) ? user_getid() : 100);
122 $doc_initstatus = '3';
123 $perm =& $this->Group->getPermission();
124 if ($perm && is_object($perm) && $perm->isDocEditor()) {
125 if ($stateid && $stateid != 2) {
126 $doc_initstatus = $stateid;
128 $doc_initstatus = '1';
132 $result = db_query_params('SELECT filename, doc_group from docdata_vw
136 array($filename, $doc_group, $doc_initstatus));
138 if (!$result || db_numrows($result) > 0) {
139 $dg = new DocumentGroup($this->getGroup(), $doc_group);
140 $this->setError(_('Document already published in this directory').' '.$dg->getPath());
144 $result = db_query_params('SELECT title FROM docdata_vw where title = $1 AND doc_group = $2',
145 array($title, $doc_group));
146 if (!$result || db_numrows($result) > 0) {
147 $dg = new DocumentGroup($this->getGroup(), $doc_group);
148 $this->setError(_('Document already published in this directory').' '.$dg->getPath());
152 // If $filetype is "text/plain", $body convert UTF-8 encoding.
153 if (strcasecmp($filetype, "text/plain") === 0 &&
154 function_exists('mb_convert_encoding') &&
155 function_exists('mb_detect_encoding')) {
156 $data = mb_convert_encoding($data, 'UTF-8', mb_detect_encoding($data));
159 $filesize = filesize($data);
160 if (!$filesize) { $filesize = 0; }
162 // key words for in-document search
163 if ($this->Group->useDocmanSearch() && $filesize) {
164 $kw = new Parsedata();
165 $kwords = $kw->get_parse_data($data, htmlspecialchars($title), htmlspecialchars($description), $filetype, $filename);
171 $result = db_query_params('INSERT INTO doc_data (group_id, title, description, createdate, doc_group,
172 stateid, filename, filetype, filesize, data_words, created_by)
173 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)',
174 array($this->Group->getId(),
175 htmlspecialchars($title),
176 htmlspecialchars($description),
187 $docid = db_insertid($result, 'doc_data', 'docid');
189 if (is_file($data)) {
190 if (!DocumentStorage::instance()->store($docid, $data)) {
191 DocumentStorage::instance()->rollback();
193 $this->setError(DocumentStorage::instance()->getErrorMessage());
197 $this->setError(_('Error Adding Document:').' '._('Not a file').' '.$filename);
203 if (!$result || !$docid) {
204 $this->setError(_('Error Adding Document:').' '.db_error().$result);
206 DocumentStorage::instance()->rollback();
212 if (!$this->fetchData($docid)) {
213 $this->setError(_('Error fetching Document'));
215 DocumentStorage::instance()->rollback();
221 $localDg = new DocumentGroup($this->Group, $doc_group);
222 if (!$localDg->update($localDg->getName(), $localDg->getParentID(), 1)) {
223 $this->setError(_('Error updating document group:').$localDg->getErrorMessage());
225 DocumentStorage::instance()->rollback();
230 $this->sendNotice(true);
233 DocumentStorage::instance()->commit();
239 * fetchData() - re-fetch the data for this document from the database.
241 * @param int The document id.
242 * @return boolean success
244 function fetchData($docid) {
245 $res = db_query_params('SELECT * FROM docdata_vw WHERE docid=$1 AND group_id=$2',
246 array($docid, $this->Group->getID()));
247 if (!$res || db_numrows($res) < 1) {
248 $this->setError(_('Document: Invalid docid'));
251 $this->data_array = db_fetch_array($res);
252 db_free_result($res);
257 * getGroup - get the Group object this Document is associated with.
259 * @return Object The Group object.
261 function &getGroup() {
266 * getID - get this docid.
268 * @return int The docid.
271 return $this->data_array['docid'];
275 * getName - get the name of this document.
277 * @return string The name of this document.
280 return $this->data_array['title'];
284 * getDescription - the description of this document.
286 * @return string The description.
288 function getDescription() {
289 return $this->data_array['description'];
293 * isURL - whether this document is a URL and not a local file.
295 * @return boolean is_url.
298 return ($this->data_array['filetype'] == 'URL');
302 * isText - whether this document is a text document and not a binary one.
304 * @return boolean is_text.
307 $doctype = $this->data_array['filetype'];
308 if (preg_match('|^text/|i', $doctype)) { // text plain, text html, text x-patch, etc
315 * isHtml - whether this document is a html document.
317 * @return boolean is_html.
320 $doctype = $this->data_array['filetype'];
321 if (preg_match('/html/i',$doctype)) {
328 * isPublic - whether this document is available to the general public.
330 * @return boolean is_public.
332 function isPublic() {
333 return (($this->data_array['stateid'] == 1) ? true : false);
337 * getStateID - get this stateid.
339 * @return int The stateid.
341 function getStateID() {
342 return $this->data_array['stateid'];
346 * getStateName - the statename of this document.
348 * @return string The statename.
350 function getStateName() {
351 return $this->data_array['state_name'];
355 * getDocGroupID - get this doc_group_id.
357 * @return int The doc_group_id.
359 function getDocGroupID() {
360 return $this->data_array['doc_group'];
364 * getDocGroupName - the doc_group_name of this document.
366 * @return string The docgroupname.
368 function getDocGroupName() {
369 return $this->data_array['group_name'];
373 * getCreatorID - get this creator's user_id.
375 * @return int The user_id.
377 function getCreatorID() {
378 return $this->data_array['created_by'];
382 * getCreatorUserName - the unix name of the person who created this document.
384 * @return string The unix name of the creator.
386 function getCreatorUserName() {
387 return $this->data_array['user_name'];
391 * getCreatorRealName - the real name of the person who created this document.
393 * @return string The real name of the creator.
395 function getCreatorRealName() {
396 return $this->data_array['realname'];
400 * getCreatorEmail - the email of the person who created this document.
402 * @return string The email of the creator.
404 function getCreatorEmail() {
405 return $this->data_array['email'];
409 * getFileName - the filename of this document.
411 * @return string The filename.
413 function getFileName() {
414 return $this->data_array['filename'];
418 * getFileType - the filetype of this document.
420 * @return string The filetype.
422 function getFileType() {
423 return $this->data_array['filetype'];
427 * getFileData - the filedata of this document.
429 * @param boolean update the download flag or not. default is true
430 * @return string The filedata.
432 function getFileData($download = true) {
436 return file_get_contents(DocumentStorage::instance()->get($this->getID()));
440 * getFileSize - Return the size of the document
442 * @return int The file size
444 function getFileSize() {
445 return $this->data_array['filesize'];
449 * getUpdated - get the time this document was updated.
451 * @return int The epoch date this document was updated.
453 function getUpdated() {
454 return $this->data_array['updatedate'];
458 * getDownload - get the number of views of this document.
460 * @return int the number of views
462 function getDownload() {
463 return $this->data_array['download'];
467 * getCreated - get the time this document was created.
469 * @return int The epoch date this document was created.
471 function getCreated() {
472 return $this->data_array['createdate'];
476 * getLocked - get the lock status of this document.
478 * @return int The lock status of this document.
480 function getLocked() {
481 return $this->data_array['locked'];
485 * getLockdate - get the lock time of this document.
487 * @return int The lock time of this document.
489 function getLockdate() {
490 return $this->data_array['lockdate'];
494 * getLockedBy - get the user id who set lock on this document.
496 * @return int The user id who set lock on this document.
498 function getLockedBy() {
499 return $this->data_array['locked_by'];
503 * getReservedBy - get the owner of the reversed status of this document.
505 * @return int The owner of the reversed status of this document.
507 function getReservedBy() {
508 return $this->data_array['reserved_by'];
512 * getReserved - get the reversed status of this document.
514 * @return int The reversed status of this document.
516 function getReserved() {
517 return $this->data_array['reserved'];
521 * getMonitoredUserEmailAddress - get the email addresses of users who monitor this file
523 * @return string The list of emails comma separated
525 function getMonitoredUserEmailAddress() {
526 $result = db_query_params('select users.email from users,docdata_monitored_docman where users.user_id = docdata_monitored_docman.user_id and docdata_monitored_docman.doc_id = $1', array ($this->getID()));
527 if (!$result || db_numrows($result) < 1) {
533 while ($arr = db_fetch_array($result)) {
537 $values .= $comma.$arr['email'];
545 * isMonitoredBy - get the monitored status of this document for a specific user id.
548 * @return boolean true if monitored by this user
550 function isMonitoredBy($userid = 'ALL') {
551 if ( $userid == 'ALL' ) {
554 $condition = 'user_id='.$userid.' AND';
556 $result = db_query_params('SELECT * FROM docdata_monitored_docman WHERE '.$condition.' doc_id=$1',
557 array($this->getID()));
559 if (!$result || db_numrows($result) < 1)
566 * removeMonitoredBy - remove this document for a specific user id for monitoring.
569 * @return boolean true if success
571 function removeMonitoredBy($userid) {
572 $result = db_query_params('DELETE FROM docdata_monitored_docman WHERE doc_id=$1 AND user_id=$2',
573 array($this->getID(), $userid));
576 $this->setError(_('Unable To Remove Monitor').' : '.db_error());
583 * addMonitoredBy - add this document for a specific user id for monitoring.
586 * @return boolean true if success
588 function addMonitoredBy($userid) {
589 $result = db_query_params('SELECT * FROM docdata_monitored_docman WHERE user_id=$1 AND doc_id=$2',
590 array($userid, $this->getID()));
592 if (!$result || db_numrows($result) < 1) {
593 $result = db_query_params('INSERT INTO docdata_monitored_docman (doc_id,user_id) VALUES ($1,$2)',
594 array($this->getID(), $userid));
597 $this->setError(_('Unable To Add Monitor').' : '.db_error());
605 * clearMonitor - remove all entries of monitoring for this document.
607 * @return boolean true if success.
609 function clearMonitor() {
610 $result = db_query_params('DELETE FROM docdata_monitored_docman WHERE doc_id = $1',
611 array($this->getID()));
613 $this->setError(_('Unable To Clear Monitor').' : '.db_error());
620 * setState - set the stateid of the document.
622 * @param int The state id of the doc_states table.
623 * @return boolean success or not.
625 function setState($stateid) {
626 return $this->setValueinDB('stateid', $stateid);
631 * setDocGroupID - set the doc_group of the document.
633 * @param int The group_id of this document.
634 * @return boolean success or not.
636 function setDocGroupID($newdocgroupid) {
637 return $this->setValueinDB('doc_group', $newdocgroupid);
641 * setLock - set the locking status of the document.
643 * @param int The status of the lock.
644 * @param int The userid who set the lock.
645 * @param time the epoch time.
646 * @return boolean success or not.
648 function setLock($stateLock, $userid = NULL, $thistime = 0) {
649 $res = db_query_params('UPDATE doc_data SET
658 $this->Group->getID(),
661 if (!$res || db_affected_rows($res) < 1) {
662 $this->setOnUpdateError(_('Document lock failed').' '.db_error());
665 $this->data_array['locked'] = $stateLock;
666 $this->data_array['locked_by'] = $userid;
667 $this->data_array['lockdate'] = $thistime;
672 * setReservedBy - set the reserved status of the document and the owner
674 * @param int The status of the reserved
675 * @param int The ID of the owner : by default : noone
676 * @return boolean success
678 function setReservedBy($statusReserved, $idReserver = NULL) {
679 $res = db_query_params('UPDATE doc_data SET
684 array($statusReserved,
686 $this->Group->getID(),
689 if (!$res || db_affected_rows($res) < 1) {
690 $this->setOnUpdateError(_('Document reservation failed').' '.db_error());
693 $this->sendNotice(false);
698 * getFileTypeImage - return the file image for icon
700 * @return string the file image name
703 function getFileTypeImage() {
704 switch ($this->getFileType()) {
709 case "image/vnd.microsoft.icon":
710 case "image/svg+xml": {
711 $image = 'docman/file_type_image.png';
715 case "audio/x-vorbis+ogg":
717 case "audio/x-ms-wma":
718 case "audio/vnd.rn-realaudio": {
719 $image = "docman/file_type_sound.png";
722 case "application/pdf": {
723 $image = 'docman/file_type_pdf.png';
728 $image = 'docman/file_type_html.png';
733 case "application/xml":
736 case "text/x-shellscript": {
737 $image = 'docman/file_type_plain.png';
740 case "application/msword":
741 case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
742 case "application/vnd.oasis.opendocument.text": {
743 $image = 'docman/file_type_writer.png';
746 case "application/vnd.ms-excel":
747 case "application/vnd.oasis.opendocument.spreadsheet":
748 case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
749 $image = 'docman/file_type_spreadsheet.png';
752 case "application/vnd.oasis.opendocument.presentation":
753 case "application/vnd.ms-powerpoint":
754 case "application/vnd.ms-office":
755 case "application/vnd.openxmlformats-officedocument.presentationml.presentation": {
756 $image = 'docman/file_type_presentation.png';
759 case "application/zip":
760 case "application/x-tar":
761 case "application/x-rpm":
762 case "application/x-rar-compressed":
763 case "application/x-bzip2":
764 case "application/x-gzip":
765 case "application/x-lzip":
766 case "application/x-compress":
767 case "application/x-7z-compressed":
768 case "application/x-gtar":
769 case "application/x-stuffitx":
770 case "application/x-lzx":
771 case "application/x-lzh":
772 case "application/x-gca-compressed":
773 case "application/x-apple-diskimage":
774 case "application/x-dgc-compressed":
775 case "application/x-dar":
776 case "application/x-cfs-compressed":
777 case "application/vnd.ms-cab-compressed":
778 case "application/x-alz-compressed":
779 case "application/x-astrotite-afa":
780 case "application/x-ace-compressed":
781 case "application/x-cpio":
782 case "application/x-shar":
783 case "application/x-xz": {
784 $image = 'docman/file_type_archive.png';
788 $image = 'docman/file_type_unknown.png';
795 * update - use this function to update an existing entry in the database.
797 * @param string The filename of this document. Can be a URL.
798 * @param string The filetype of this document. If filename is URL, this should be 'URL';
799 * @param string The contents of this document.
800 * @param int The doc_group id of the doc_groups table.
801 * @param string The title of this document.
802 * @param string The description of this document.
803 * @param int The state id of the doc_states table.
804 * @return boolean success.
806 function update($filename, $filetype, $data, $doc_group, $title, $description, $stateid) {
808 $perm =& $this->Group->getPermission();
809 if (!$perm || !is_object($perm) || !$perm->isDocEditor()) {
810 $this->setPermissionDeniedError();
814 $user = session_get_user();
815 if ($this->getLocked() && ($this->getLockedBy() != $user->getID())) {
816 $this->setPermissionDeniedError();
820 if (strlen($title) < 5) {
821 $this->setError(_('Title Must Be At Least 5 Characters'));
825 if (strlen($description) < 10) {
826 $this->setError(_('Document Description Must Be At Least 10 Characters'));
830 /* TODO : NEED REAL CHECK */
832 $result = db_query_params('SELECT filename, doc_group FROM docdata_vw WHERE filename = $1 AND doc_group = $2 AND stateid = $3 AND docid != $4',
833 array($filename, $doc_group, $stateid, $this->getID()));
834 if (!$result || db_numrows($result) > 0) {
835 $this->setError(_('Document already published in this directory'));
840 $res = db_query_params('UPDATE doc_data SET
852 array(htmlspecialchars($title),
853 htmlspecialchars($description),
861 $this->Group->getID(),
865 if (!$res || db_affected_rows($res) < 1) {
866 $this->setOnUpdateError(db_error());
870 if (filesize($data)) {
871 // key words for in-document search
872 if ($this->Group->useDocmanSearch()) {
873 $kw = new Parsedata();
874 $kwords = $kw->get_parse_data($data, htmlspecialchars($title), htmlspecialchars($description), $filetype, $filename);
879 $res = db_query_params('UPDATE doc_data SET filesize=$1, data_words=$2 WHERE group_id=$3 AND docid=$4',
880 array(filesize($data),
882 $this->Group->getID(),
886 if (!$res || db_affected_rows($res) < 1) {
887 $this->setOnUpdateError(db_error());
891 DocumentStorage::instance()->delete($this->getID())->commit();
892 DocumentStorage::instance()->store($this->getID(), $data);
894 $this->fetchData($this->getID());
895 $this->sendNotice(false);
900 * sendNotice - Notifies of document submissions
902 * @param boolean true = new document (default value)
904 function sendNotice($new = true) {
905 $BCC = $this->Group->getDocEmailAddress();
906 if ($this->isMonitoredBy('ALL')) {
907 $BCC .= $this->getMonitoredUserEmailAddress();
909 $dg = new DocumentGroup($this->Group, $this->getDocGroupID());
910 if ($dg->isMonitoredBy('ALL')) {
911 $BCC .= $dg->getMonitoredUserEmailAddress();
913 if (strlen($BCC) > 0) {
914 $session = session_get_user();
916 $status = _('New document');
918 $status = _('Updated document').' '._('by').' ' . $session->getRealName();
920 $subject = '['.$this->Group->getPublicName().'] '.$status.' - '.$this->getName();
921 $body = _('Project:').' '.$this->Group->getPublicName()."\n";
922 $body .= _('Directory:').' '.$this->getDocGroupName()."\n";
923 $body .= _('Document title:').' '.$this->getName()."\n";
924 $body .= _('Document description:').' '.util_unconvert_htmlspecialchars($this->getDescription())."\n";
925 $body .= _('Submitter:').' '.$this->getCreatorRealName()." (".$this->getCreatorUserName().") \n";
926 $body .= "\n\n-------------------------------------------------------\n".
927 _('For more info, visit:').
928 "\n\n" . util_make_url('/docman/?group_id='.$this->Group->getID().'&view=listfile&dirid='.$this->getDocGroupID());
930 $BCCarray = explode(',',$BCC);
931 foreach ($BCCarray as $dest_email) {
932 util_send_message($dest_email, $subject, $body, 'noreply@'.forge_get_config('web_host'), '', _('Docman'));
939 * delete - Delete this file
941 * @return boolean success
944 $perm =& $this->Group->getPermission();
945 if (!$perm || !is_object($perm) || !$perm->isDocEditor()) {
946 $this->setPermissionDeniedError();
950 $result = db_query_params('DELETE FROM doc_data WHERE docid=$1',
951 array($this->getID()));
953 $this->setError(_('Error Deleting Document:').' '.db_error());
958 DocumentStorage::instance()->delete($this->getID())->commit();
960 /** we should be able to send a notice that this doc has been deleted .... but we need to rewrite sendNotice
961 * $this->sendNotice(false);
962 * @TODO delete monitoring this file */
967 * trash - move this file to trash
969 * @return boolean success or not.
972 $this->setState('2');
973 $dm = new DocumentManager($this->Group);
974 $this->setDocGroupID($dm->getTrashID());
976 $this->setReservedBy(0);
977 $this->sendNotice(false);
978 $this->clearMonitor();
984 * downloadUp - insert download stats
987 private function downloadUp() {
988 if (session_loggedin()) {
989 $s =& session_get_user();
995 $ip = getStringFromServer('REMOTE_ADDR');
996 $res = db_query_params("INSERT INTO docman_dlstats_doc (ip_address, docid, month, day, user_id) VALUES ($1, $2, $3, $4, $5)", array($ip, $this->getID(), date('Ym'), date('d'), $us));
1000 * setValueinDB - private function to update columns in db
1002 * @param string the column to update
1003 * @param int the value to store
1004 * @return boolean success or not
1007 private function setValueinDB($column, $value) {
1011 $qpa = db_construct_qpa();
1012 $qpa = db_construct_qpa($qpa, 'UPDATE doc_data SET ');
1013 $qpa = db_construct_qpa($qpa, $column);
1014 $qpa = db_construct_qpa($qpa, '=$1
1018 $this->Group->getID(),
1020 $res = db_query_qpa($qpa);
1021 if (!$res || db_affected_rows($res) < 1) {
1022 $this->setOnUpdateError(db_error().print_r($qpa));
1028 $this->setOnUpdateError(_('wrong column name'));
1031 $this->sendNotice(false);
1035 function createVersion() {
1039 function deleteVersion() {
1046 // c-file-style: "bsd"