3 * Artifact.class - Main Artifact class
5 * Copyright 1999-2001 (c) VA Linux Systems
9 * This file is part of GForge.
11 * GForge is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * GForge is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with GForge; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 US
25 require_once('common/include/Error.class');
26 require_once('common/tracker/ArtifactMessage.class');
27 require_once('common/tracker/ArtifactExtraField.class');
29 // This string is used when sending the notification mail for identifying the
31 define('ARTIFACT_MAIL_MARKER', '#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+');
34 * Factory method which creates an Artifact from an artifact ID
36 * @param int The artifact ID
37 * @param array The result array, if it's passed in
38 * @return object Artifact object
40 function &artifact_get_object($artifact_id,$data=false) {
42 if (!isset($ARTIFACT_OBJ["_".$artifact_id."_"])) {
44 //the db result handle was passed in
46 $res=db_query("SELECT * FROM artifact_vw WHERE artifact_id='$artifact_id'");
47 if (db_numrows($res) <1 ) {
48 $ARTIFACT_OBJ["_".$artifact_id."_"]=false;
51 $data =& db_fetch_array($res);
53 $ArtifactType =& artifactType_get_object($data["group_artifact_id"]);
54 $ARTIFACT_OBJ["_".$artifact_id."_"]= new Artifact($ArtifactType,$data);
56 return $ARTIFACT_OBJ["_".$artifact_id."_"];
59 class Artifact extends Error {
64 * @var int $status_res.
69 * Artifact Type object.
71 * @var object $ArtifactType.
76 * Array of artifact data.
78 * @var array $data_array.
83 * Array of artifact data for extra fields defined by Admin.
85 * @var array $extra_field_data.
87 var $extra_field_data;
90 * Array of ArtifactFile objects.
97 * Database result set of related tasks
99 * @var result $relatedtasks
104 * Artifact - constructor.
106 * @param object The ArtifactType object.
107 * @param integer (primary key from database OR complete assoc array)
108 * ONLY OPTIONAL WHEN YOU PLAN TO IMMEDIATELY CALL ->create()
109 * @return boolean success.
111 function Artifact(&$ArtifactType, $data=false) {
115 $this->ArtifactType =& $ArtifactType;
117 //was ArtifactType legit?
118 if (!$ArtifactType || !is_object($ArtifactType)) {
119 $this->setError('Artifact: No Valid ArtifactType');
123 //did ArtifactType have an error?
124 if ($ArtifactType->isError()) {
125 $this->setError('Artifact: '.$ArtifactType->getErrorMessage());
130 // make sure this person has permission to view artifacts
132 if (!$this->ArtifactType->userCanView()) {
133 $this->setError(_('Artifact: Only group members can view private artifact types'));
138 // set up data structures
141 if (is_array($data)) {
142 $this->data_array =& $data;
144 // Should verify ArtifactType ID
148 if (!$this->fetchData($data)) {
159 * create - construct a new Artifact in the database.
161 * @param string The artifact summary.
162 * @param string Details of the artifact.
163 * @param int The ID of the user to which this artifact is to be assigned.
164 * @param int The artifacts priority.
165 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
166 * @return id on success / false on failure.
168 function create( $summary, $details, $assigned_to=100, $priority=3, $extra_fields=array()) {
172 // make sure this person has permission to add artifacts
174 if (!$this->ArtifactType->isPublic()) {
176 // Only admins can post/modify private artifacts
178 if (!$this->ArtifactType->userIsAdmin()) {
179 $this->setError(_('Artifact: Only Artifact Admins Can Modify Private ArtifactTypes'));
187 if (session_loggedin()) {
190 if ($this->ArtifactType->allowsAnon()) {
193 $this->setError(_('Artifact: This ArtifactType Does Not Allow Anonymous Submissions. Please Login.'));
202 $this->setError(_('Artifact: Message Summary Is Required'));
206 $this->setError(_('Artifact: Message Body Is Required'));
215 // if (!$status_id) {
216 $status_id=1; // on creation, status is set to "open"
219 // They may be using an extra field "status" box so we have to remap
220 // the status_id based on the extra field - this keeps the counters
223 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
225 $this->setError(_('Artifact: Error remapping status'));
232 // Check to see if this user is trying to double-submit
234 $res=db_query("SELECT * FROM artifact
235 WHERE summary='".htmlspecialchars($summary)."'
236 AND submitted_by='$user'
237 AND group_artifact_id='".$this->ArtifactType->getID()."'
238 AND open_date > '". (time() - 86400) ."'");
239 if ($res && db_numrows($res) > 0) {
240 $this->setError(_('You Attempted To Double-submit this item. Please avoid double-clicking.'));
245 $sql="INSERT INTO artifact
246 (group_artifact_id,status_id,priority,
247 submitted_by,assigned_to,open_date,summary,details)
249 ('".$this->ArtifactType->getID()."','$status_id','$priority',
250 '$user','$assigned_to','". time() ."','". htmlspecialchars($summary)."','". htmlspecialchars($details)."')";
253 $this->setError('Artifact: '.db_error());
258 $artifact_id=db_insertid($res,'artifact','artifact_id');
260 if (!$res || !$artifact_id) {
261 $this->setError('Artifact: '.db_error());
266 // Now set up our internal data structures
268 if (!$this->fetchData($artifact_id)) {
272 // the changes to the extra fields will be logged in this array.
273 // (we won't use it however)
274 $extra_field_changes = array();
275 if (!$this->updateExtraFields($extra_fields,$extra_field_changes)) {
281 // now send an email if appropriate
283 $this->mailFollowup(1);
291 * fetchData - re-fetch the data for this Artifact from the database.
293 * @param int The artifact ID.
294 * @return boolean success.
296 function fetchData($artifact_id) {
297 $res=db_query("SELECT * FROM artifact_vw
298 WHERE artifact_id='$artifact_id' AND group_artifact_id='".$this->ArtifactType->getID()."'");
299 if (!$res || db_numrows($res) < 1) {
300 $this->setError('Artifact: Invalid ArtifactID');
303 $this->data_array =& db_fetch_array($res);
304 db_free_result($res);
309 * getArtifactType - get the ArtifactType Object this Artifact is associated with.
311 * @return object ArtifactType.
313 function &getArtifactType() {
314 return $this->ArtifactType;
318 * getID - get this ArtifactID.
320 * @return int The artifact_id #.
323 return $this->data_array['artifact_id'];
327 * getStatusID - get open/closed/deleted flag.
329 * @return int Status: (1) Open, (2) Closed, (3) Deleted.
331 function getStatusID() {
332 return $this->data_array['status_id'];
336 * getStatusName - get open/closed/deleted text.
338 * @return string The status name.
340 function getStatusName() {
341 return $this->data_array['status_name'];
345 * getPriority - get priority flag.
347 * @return int priority.
349 function getPriority() {
350 return $this->data_array['priority'];
354 * getSubmittedBy - get ID of submitter.
356 * @return int user_id of submitter.
358 function getSubmittedBy() {
359 return $this->data_array['submitted_by'];
363 * getSubmittedEmail - get email of submitter.
365 * @return string The email of submitter.
367 function getSubmittedEmail() {
368 return $this->data_array['submitted_email'];
372 * getSubmittedRealName - get real name of submitter.
374 * @return string The real name of submitter.
376 function getSubmittedRealName() {
377 return $this->data_array['submitted_realname'];
381 * getSubmittedUnixName - get login name of submitter.
383 * @return string The unix name of submitter.
385 function getSubmittedUnixName() {
386 return $this->data_array['submitted_unixname'];
390 * getAssignedTo - get ID of assignee.
392 * @return int user_id of assignee.
394 function getAssignedTo() {
395 return $this->data_array['assigned_to'];
399 * getAssignedEmail - get email of assignee.
401 * @return string The email of assignee.
403 function getAssignedEmail() {
404 return $this->data_array['assigned_email'];
408 * getAssignedRealName - get real name of assignee.
410 * @return string The real name of assignee.
412 function getAssignedRealName() {
413 return $this->data_array['assigned_realname'];
417 * getAssignedUnixName - get login name of assignee.
419 * @return string The unix name of assignee.
421 function getAssignedUnixName() {
422 return $this->data_array['assigned_unixname'];
426 * getOpenDate - get unix time of creation.
428 * @return int unix time.
430 function getOpenDate() {
431 return $this->data_array['open_date'];
435 * getCloseDate - get unix time of closure.
437 * @return int unix time.
439 function getCloseDate() {
440 return $this->data_array['close_date'];
444 * getLastModifiedDate - the last_modified_date of this task.
446 * @return int the last_modified_date.
448 function getLastModifiedDate() {
449 return $this->data_array['last_modified_date'];
453 * getSummary - get text summary of artifact.
455 * @return string The summary (subject).
457 function getSummary() {
458 return $this->data_array['summary'];
462 * getDetails - get text body (message) of artifact.
464 * @return string The body (message).
466 function getDetails() {
467 return $this->data_array['details'];
471 * delete - delete this tracker and all its related data.
473 * @param bool I'm Sure.
474 * @return bool true/false;
476 function delete($sure) {
478 $this->setMissingParamsError();
481 if (!$this->ArtifactType->userIsAdmin()) {
482 $this->setPermissionDeniedError();
486 $res = db_query("DELETE FROM artifact_extra_field_data WHERE artifact_id='".$this->getID()."'");
488 $this->setError('Error deleting extra field data: '.db_error());
492 $res = db_query("DELETE FROM artifact_file WHERE artifact_id='".$this->getID()."'");
494 $this->setError('Error deleting file from db: '.db_error());
498 $res = db_query("DELETE FROM artifact_message WHERE artifact_id='".$this->getID()."'");
500 $this->setError('Error deleting message: '.db_error());
504 $res = db_query("DELETE FROM artifact_history WHERE artifact_id='".$this->getID()."'");
506 $this->setError('Error deleting history: '.db_error());
510 $res = db_query("DELETE FROM artifact_monitor WHERE artifact_id='".$this->getID()."'");
512 $this->setError('Error deleting monitor: '.db_error());
516 $res = db_query("DELETE FROM artifact WHERE artifact_id='".$this->getID()."'");
518 $this->setError('Error deleting artifact: '.db_error());
523 if ($this->getStatusID() == 1) {
524 $res = db_query("UPDATE artifact_counts_agg SET count=count-1,open_count=open_count-1
525 WHERE group_artifact_id='".$this->getID()."'");
527 $this->setError('Error updating artifact_counts_agg (1): '.db_error());
531 } elseif ($this->getStatusID() == 2) {
532 $res = db_query("UPDATE artifact_counts_agg SET count=count-1
533 WHERE group_artifact_id='".$this->getID()."'");
535 $this->setError('Error updating artifact_counts_agg (2): '.db_error());
546 * setMonitor - user can monitor this artifact.
548 * @return false - always false - always use the getErrorMessage() for feedback
550 function setMonitor() {
552 if (session_loggedin()) {
554 $user_id=user_getid();
555 $user =& user_get_object(user_getid());
558 //we don't want to include the "And email=" because
559 //a logged-in user's email may have changed
564 $this->setError(_('SetMonitor::Valid Email Address Required'));
569 $res=db_query("SELECT * FROM artifact_monitor
570 WHERE artifact_id='". $this->getID() ."'
571 AND user_id='$user_id'");
573 if (!$res || db_numrows($res) < 1) {
575 $res=db_query("INSERT INTO artifact_monitor (artifact_id,user_id)
576 VALUES ('". $this->getID() ."','$user_id')");
578 $this->setError(db_error());
581 $this->setError(_('Now Monitoring Artifact'));
585 //already monitoring - remove their monitor
586 db_query("DELETE FROM artifact_monitor
587 WHERE artifact_id='". $this->getID() ."'
588 AND user_id='$user_id'");
589 $this->setError(_('Artifact Monitoring Deactivated'));
594 function isMonitoring() {
595 if (!session_loggedin()) {
598 $sql="SELECT count(*) AS count FROM artifact_monitor WHERE user_id='".user_getid()."' AND artifact_id='".$this->getID()."';";
599 $result = db_query($sql);
600 $row_count = db_fetch_array($result);
601 return $result && $row_count['count'] > 0;
605 * getMonitorIds - array of email addresses monitoring this Artifact.
607 * @return array of email addresses monitoring this Artifact.
609 function &getMonitorIds() {
610 $res=db_query("SELECT user_id
611 FROM artifact_monitor
612 WHERE artifact_id='". $this->getID() ."'");
613 return array_unique(array_merge($this->ArtifactType->getMonitorIds(),util_result_column_to_array($res)));
617 * getHistory - returns a result set of audit trail for this support request.
619 * @return database result set.
621 function getHistory() {
623 "FROM artifact_history_user_vw ".
624 "WHERE artifact_id='". $this->getID() ."' ".
625 "ORDER BY entrydate DESC";
626 return db_query($sql);
630 * getMessages - get the list of messages attached to this artifact.
632 * @return database result set.
634 function getMessages() {
636 "FROM artifact_message_user_vw ".
637 "WHERE artifact_id='". $this->getID() ."' ORDER BY adddate DESC";
638 return db_query($sql);
642 * getMessageObjects - get an array of message objects.
644 * @return array Of ArtifactMessage objects.
646 function &getMessageObjects() {
647 $res=$this->getMessages();
649 while ($arr = db_fetch_array($res)) {
650 //$return[]=new ArtifactMessage($arr['artifact_id'],$arr);
651 $return[] = new ArtifactMessage($this, $arr);
657 * getFiles - get array of ArtifactFile's.
659 * @return array of ArtifactFile's.
661 function &getFiles() {
662 if (!isset($this->files)) {
664 "FROM artifact_file_user_vw ".
665 "WHERE artifact_id='". $this->getID() ."'";
667 $rows=db_numrows($res);
669 for ($i=0; $i < $rows; $i++) {
670 $this->files[$i]=new ArtifactFile($this,db_fetch_array($res));
673 $this->files=array();
680 * getRelatedTasks - get array of related tasks
682 * @return Database result set
684 function getRelatedTasks() {
685 if (!$this->relatedtasks) {
687 db_query("SELECT pt.group_project_id,pt.project_task_id,pt.summary,pt.start_date,pt.end_date,pgl.group_id
688 FROM project_task pt, project_group_list pgl
689 WHERE pt.group_project_id = pgl.group_project_id AND
690 EXISTS (SELECT project_task_id FROM project_task_artifact
691 WHERE project_task_id=pt.project_task_id
692 AND artifact_id = ". $this->getID() . ")");
694 return $this->relatedtasks;
698 * addMessage - attach a text message to this Artifact.
700 * @param string The message being attached.
701 * @param string Email address of message creator.
702 * @param bool Whether to email out a followup.
704 * @return boolean success.
706 function addMessage($body,$by=false,$send_followup=false) {
709 $this->setMissingParamsError();
712 if (session_loggedin()) {
713 $user_id=user_getid();
714 $user =& user_get_object($user_id);
715 if (!$user || !is_object($user)) {
716 $this->setError('ERROR - Logged In User Bug Could Not Get User Object');
719 // we'll store this email even though it will likely never be used -
720 // since we have their correct user_id, we can join the USERS table to get email
721 $by=$user->getEmail();
722 } elseif (!$this->ArtifactType->allowsAnon()) {
723 $this->setError(_('Artifact: This ArtifactType Does Not Allow Anonymous Submissions. Please Login.'));
727 if (!$by || !validate_email($by)) {
728 $this->setMissingParamsError();
733 $sql="insert into artifact_message (artifact_id,submitted_by,from_email,adddate,body) ".
734 "VALUES ('". $this->getID() ."','$user_id','$by','". time() ."','". htmlspecialchars($body). "')";
735 $res = db_query($sql);
736 if ($send_followup) {
737 $this->mailFollowup(2,false);
743 * addHistory - add an entry to audit trail.
745 * @param string The name of the field in the database being modified.
746 * @param string The former value of this field.
748 * @return boolean success.
750 function addHistory($field_name,$old_value) {
751 if (!session_loggedin()) {
756 $sql="insert into artifact_history(artifact_id,field_name,old_value,mod_by,entrydate)
757 VALUES ('". $this->getID() ."','$field_name','".addslashes($old_value)."','$user','". time() ."')";
758 return db_query($sql);
762 * update - update the fields in this artifact.
764 * @param int The artifact priority.
765 * @param int The artifact status ID.
766 * @param int The person to which this artifact is to be assigned.
767 * @param string The artifact summary.
768 * @param int The canned response.
769 * @param string Attaching another comment.
770 * @param int Allows you to move an artifact to another type.
771 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
772 * @return boolean success.
774 function update($priority,$status_id,
775 $assigned_to,$summary,$canned_response,$details,$new_artifact_type_id,$extra_fields=array()) {
779 Field-level permission checking
781 if ($this->ArtifactType->userIsAdmin()) {
782 //admin can do everything
784 //everyone else cannot modify these fields
785 $priority=$this->getPriority();
786 $summary=addslashes($this->getSummary());
787 $canned_response=100;
788 $new_artifact_type_id=$this->ArtifactType->getID();
789 $assigned_to=$this->getAssignedTo();
791 if ($this->ArtifactType->userIsTechnician()) {
792 //technician can update only certain fields
793 //which were not overridden above
795 //submitter can no longer call this function
796 $this->setPermissionDeniedError();
802 // They may be using an extra field "status" box so we have to remap
803 // the status_id based on the extra field - this keeps the counters
806 if (count($extra_fields) > 0) {
807 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
813 || !$new_artifact_type_id) {
814 $this->setMissingParamsError();
819 // Array to record which properties were changed
825 // Get a lock on this row in the database
827 $lock=db_query("SELECT * FROM artifact WHERE artifact_id='".$this->getID()."' FOR UPDATE");
828 $artifact_type_id = $this->ArtifactType->getID();
830 // Attempt to move this Artifact to a new ArtifactType
831 // need to instantiate new ArtifactType obj and test perms
833 if ($new_artifact_type_id != $artifact_type_id) {
834 $newArtifactType= new ArtifactType($this->ArtifactType->getGroup(), $new_artifact_type_id);
835 if (!is_object($newArtifactType) || $newArtifactType->isError()) {
836 $this->setError('Artifact: Could not move to new ArtifactType'. $newArtifactType->getErrorMessage());
840 // do they have perms for new ArtifactType?
841 if (!$newArtifactType->userIsAdmin()) {
842 $this->setPermissionDeniedError();
848 // Now set Assigned to 100 in the new ArtifactType
852 //can't send a canned response when changing ArtifactType
853 $canned_response=100;
854 $this->ArtifactType =& $newArtifactType;
858 // This is a major problem - the extra fields
859 // are completely different IDs, and may not even
860 // exist in the new tracker. All extra_fields will be deleted and
861 // then set to 100 in the new tracker.
863 $res=db_query("DELETE FROM artifact_extra_field_data WHERE artifact_id='".$this->getID()."'");
864 $extra_fields=array();
872 // handle audit trail & build SQL statement
874 if ($this->getStatusID() != $status_id) {
875 $this->addHistory('status_id',$this->getStatusID());
876 $sqlu .= " status_id='$status_id', ";
877 $changes['status'] = 1;
880 // Reset the close_date if bug is re-opened
881 // (otherwise stat reports will be wrong).
882 if ($status_id == 1) {
883 $sqlu .= " close_date='0', ";
884 $this->addHistory('close_date',0);
887 if ($this->getPriority() != $priority) {
888 $this->addHistory('priority',$this->getPriority());
889 $sqlu .= " priority='$priority', ";
890 $changes['priority'] = 1;
894 if ($this->getAssignedTo() != $assigned_to) {
895 $this->addHistory('assigned_to',$this->getAssignedTo());
896 $sqlu .= " assigned_to='$assigned_to', ";
897 $changes['assigned_to'] = 1;
900 if ($summary && (addslashes($this->getSummary()) != htmlspecialchars($summary))) {
901 $this->addHistory('summary', addslashes($this->getSummary()));
902 $sqlu .= " summary='". htmlspecialchars($summary) ."', ";
903 $changes['summary'] = 1;
908 $this->addMessage($details,'',0);
909 $changes['details'] = 1;
914 // Enter the timestamp if we are changing to closed
916 if ($status_id != 1) {
918 $sqlu .= " close_date='$now', ";
919 $this->addHistory('close_date',$now);
924 Finally, update the artifact itself
927 $sql = "UPDATE artifact
930 group_artifact_id='$new_artifact_type_id'
932 artifact_id='". $this->getID() ."'
933 AND group_artifact_id='$artifact_type_id'";
934 $result=db_query($sql);
936 if (!$result || db_affected_rows($result) < 1) {
937 $this->setError('Error - update failed!'.db_error());
941 if (!$this->fetchData($this->getID())) {
948 //extra field handling
950 if (!$this->updateExtraFields($extra_fields,$changes)) {
951 //TODO - see if anything actually did change
957 handle canned responses
959 Instantiate ArtifactCanned and get the body of the message
961 if ($canned_response != 100) {
962 //don't care if this response is for this group - could be hacked
963 $acr=new ArtifactCanned($this->ArtifactType,$canned_response);
964 if (!$acr || !is_object($acr)) {
965 $this->setError('Artifact: Could Not Create Canned Response Object');
966 } elseif ($acr->isError()) {
967 $this->setError('Artifact: '.$acr->getErrorMessage());
969 $body = addslashes($acr->getBody());
971 if (!$this->addMessage(util_unconvert_htmlspecialchars($body),'',0)) {
978 $this->setError('Artifact: Unable to Use Canned Response');
984 if ($update || $send_message){
988 $this->mailFollowup(2, false, $changes);
992 //nothing changed, so cancel the transaction
993 $this->setError(_('Nothing Changed - Update Cancelled'));
1001 * updateExtraFields - updates the extra data elements for this artifact
1002 * e.g. the extra fields created and defined by the admin.
1004 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
1005 * @param array Array where changes to the extra fields should be logged
1006 * @return true on success / false on failure
1008 function updateExtraFields($extra_fields,&$changes){
1010 This is extremely complex code - we have take the passed array
1011 and see if we need to insert it into the db, and may have to
1012 add history rows for the audit trail
1014 start by getting all the available extra fields from ArtifactType
1015 For each field from ArtifacType, check the passed array -
1016 This prevents someone from passing bogus extra field entries - they will be ignored
1017 if the passed entry is blank, may have to force a default value
1018 if the passed array is different from the existing data in db,
1019 delete old entry and insert new entries, along with possible audit trail
1021 skip it and continue to next item
1024 if (empty($extra_fields)) {
1027 //get a list of extra fields for this artifact_type
1028 $ef = $this->ArtifactType->getExtraFields();
1029 $efk=array_keys($ef);
1031 //now we'll update this artifact for each extra field
1032 for ($i=0; $i<count($efk); $i++) {
1034 $type=$ef[$efid]['field_type'];
1037 // Force each field to have some value if it is a numeric field
1038 // text fields will just be purged and skipped
1040 if (!array_key_exists($efid, $extra_fields) || !$extra_fields[$efid]) {
1041 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1042 $this->setError('Status Custom Field Must Be Set');
1044 } elseif (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO)) {
1045 $extra_fields[$efid]='100';
1046 } elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX)) {
1047 $extra_fields[$efid]=array('100');
1049 $resdel=db_query("DELETE FROM artifact_extra_field_data
1051 artifact_id='".$this->getID()."'
1052 AND extra_field_id='".$efid."'");
1057 // get the old rows of data
1059 $resd=db_query("SELECT * FROM artifact_extra_field_data
1061 artifact_id='".$this->getID()."'
1062 AND extra_field_id='".$efid."'");
1063 $rows=db_numrows($resd);
1064 if ($resd && $rows) {
1066 //POTENTIAL PROBLEM - no entry was there before, but adding one now - may need history
1069 // Compare for history purposes
1072 // these types have arrays associated to them, so they need
1073 // special handling to check for differences
1074 if ($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) {
1075 // check the differences between the old values and the new values
1076 $old_values = util_result_column_to_array($resd,"field_data");
1078 $added_values = array_diff($extra_fields[$efid], $old_values);
1079 $deleted_values = array_diff($old_values, $extra_fields[$efid]);
1081 if (!empty($added_values) || !empty($deleted_values)) { // there are differences...
1082 $field_name = $ef[$efid]['field_name'];
1083 $changes["extra_fields"][$efid] = 1;
1085 // Do a history entry only for deleted values
1086 if (!empty($deleted_values)) {
1087 $this->addHistory($field_name, $this->ArtifactType->getElementName($deleted_values));
1091 $resdel=db_query("DELETE FROM artifact_extra_field_data
1093 artifact_id='".$this->getID()."'
1094 AND extra_field_id='".$efid."'");
1098 } elseif (addslashes(db_result($resd,0,'field_data')) == htmlspecialchars($extra_fields[$efid])) {
1099 //element did not change
1102 //element DID change - do a history entry
1103 $field_name = $ef[$efid]['field_name'];
1104 $changes["extra_fields"][$efid] = 1;
1105 $resdel=db_query("DELETE FROM artifact_extra_field_data
1107 artifact_id='".$this->getID()."'
1108 AND extra_field_id='".$efid."'");
1109 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO) || ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS)) {
1110 //don't add history for text fields
1111 $this->addHistory($field_name,$this->ArtifactType->getElementName(db_result($resd,0,'field_data')));
1116 //no history for this extra field exists
1120 // See if anything was even passed for this extra_field_id
1122 if (!$extra_fields[$efid]) {
1123 //nothing in field to update - text fields may be blank
1125 //determine the type of field and whether it should have multiple rows supporting it
1126 $type=$ef[$efid]['field_type'];
1127 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1129 $count=count($extra_fields[$efid]);
1130 for ($fin=0; $fin<$count; $fin++) {
1131 $sql="INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data)
1132 values ('".$this->getID()."','".$efid."',
1133 '".$extra_fields[$efid][$fin]."')";
1134 $res=db_query($sql);
1136 $this->setError('Artifact::updateExtraFields:: '.$sql.db_error());
1143 $res=db_query("INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data)
1144 values ('".$this->getID()."','".$efid."',
1145 '".htmlspecialchars($extra_fields[$efid])."')");
1147 $this->setError('Artifact::updateExtraFields:: '.db_error());
1153 unset($this->extra_field_data);
1158 * getExtraFieldData - get an array of data for the extra fields associated with this artifact
1160 * @return array array of data
1162 function &getExtraFieldData() {
1163 if (!isset($this->extra_field_data)) {
1164 $this->extra_field_data = array();
1165 $res=db_query("SELECT * FROM artifact_extra_field_data
1166 WHERE artifact_id='".$this->getID()."' ORDER BY extra_field_id");
1167 $ef = $this->ArtifactType->getExtraFields();
1168 while ($arr = db_fetch_array($res)) {
1169 $type=$ef[$arr['extra_field_id']]['field_type'];
1170 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1171 //accumulate a sub-array of values in cases where you may have multiple rows
1172 if (!is_array($this->extra_field_data[$arr['extra_field_id']])) {
1173 $this->extra_field_data[$arr['extra_field_id']] = array();
1175 $this->extra_field_data[$arr['extra_field_id']][]=$arr['field_data'];
1177 $this->extra_field_data[$arr['extra_field_id']]=$arr['field_data'];
1181 return $this->extra_field_data;
1185 * marker - adds the > symbol to fields that have been modified for the email message
1189 function marker($prop_name,$changes,$extra_field_id=0) {
1190 if ($prop_name == 'extra_fields' && $changes[$prop_name][$extra_field_id]) {
1192 } else if ($prop_name != 'extra_fields' && $changes[$prop_name]) {
1200 * mailFollowup - send out an email update for this artifact.
1202 * @param int (1) initial/creation (2) update.
1203 * @param array Array of additional addresses to mail to.
1204 * @param array Array of fields changed in this update .
1206 * @return boolean success.
1208 function mailFollowup($type, $more_addresses=false, $changes='') {
1209 global $sys_datefmt;
1215 $body = $this->ArtifactType->getName() ." item #". $this->getID() .", was opened at ". date( $sys_datefmt, $this->getOpenDate() ).
1216 "\nYou can respond by visiting: ".
1217 "\nhttp://".$GLOBALS['sys_default_domain']."/tracker/?func=detail&atid=". $this->ArtifactType->getID() .
1218 "&aid=". $this->getID() .
1219 "&group_id=". $this->ArtifactType->Group->getID() .
1220 "\nOr by replying to this e-mail entering your response between the following markers: ".
1221 "\n".ARTIFACT_MAIL_MARKER.
1222 "\n(enter your response here)".
1223 "\n".ARTIFACT_MAIL_MARKER.
1225 $this->marker('status',$changes).
1226 "Status: ". $this->getStatusName() ."\n".
1227 $this->marker('priority',$changes).
1228 "Priority: ". $this->getPriority() ."\n".
1229 "Submitted By: ". $this->getSubmittedRealName() .
1230 " (". $this->getSubmittedUnixName(). ")"."\n".
1231 $this->marker('assigned_to',$changes).
1232 "Assigned to: ". $this->getAssignedRealName() .
1233 " (". $this->getAssignedUnixName(). ")"."\n".
1234 $this->marker('summary',$changes).
1235 "Summary: ". util_unconvert_htmlspecialchars( $this->getSummary() )." \n";
1237 // Now display the extra fields
1238 $efd = $this->getExtraFieldDataText();
1239 foreach ($efd as $efid => $ef) {
1240 $body .= $this->marker('extra_fields', $changes, $efid);
1241 $body .= $ef["name"].": ".$ef["value"]."\n";
1244 $subject='['. $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() . '][' . $this->getID() .'] '. util_unconvert_htmlspecialchars( $this->getSummary() );
1247 // get all the email addresses that are monitoring this request or the ArtifactType
1248 $monitor_ids =& $this->getMonitorIds();
1250 // initial creation, we just get the users monitoring the ArtifactType
1251 $monitor_ids =& $this->ArtifactType->getMonitorIds();
1254 if ($more_addresses) {
1255 $emails[] = $more_addresses;
1257 //we don't email the current user
1258 if ($this->getAssignedTo() != user_getid()) {
1259 $monitor_ids[] = $this->getAssignedTo();
1261 if ($this->getSubmittedBy() != user_getid()) {
1262 $monitor_ids[] = $this->getSubmittedBy();
1264 //initial submission
1266 //if an email is set for this ArtifactType
1267 //add that address to the BCC: list
1268 if ($this->ArtifactType->getEmailAddress()) {
1269 $emails[] = $this->ArtifactType->getEmailAddress();
1273 if ($this->ArtifactType->emailAll()) {
1274 $emails[] = $this->ArtifactType->getEmailAddress();
1278 $body .= "\n\nInitial Comment:".
1279 "\n".util_unconvert_htmlspecialchars( $this->getDetails() ) .
1280 "\n\n----------------------------------------------------------------------";
1284 Now include the followups
1286 $result2=$this->getMessages();
1288 $rows=db_numrows($result2);
1290 if ($result2 && $rows > 0) {
1291 for ($i=0; $i<$rows; $i++) {
1293 // for messages posted by non-logged-in users,
1294 // we grab the email they gave us
1296 // otherwise we use the confirmed one from the users table
1298 if (db_result($result2,$i,'user_id') == 100) {
1299 $emails[] = db_result($result2,$i,'from_email');
1301 $monitor_ids[] = db_result($result2,$i,'user_id');
1307 $body .= $this->marker('details',$changes);
1309 $body .= "Comment By: ". db_result($result2,$i,'realname') . " (".db_result($result2,$i,'user_name').")".
1310 "\nDate: ". date( $sys_datefmt,db_result($result2,$i,'adddate') ).
1312 "\n".util_unconvert_htmlspecialchars( db_result($result2,$i,'body') ).
1313 "\n\n----------------------------------------------------------------------";
1319 $body .= "\n\nYou can respond by visiting: ".
1320 "\nhttp://".$GLOBALS['sys_default_domain']."/tracker/?func=detail&atid=". $this->ArtifactType->getID() .
1321 "&aid=". $this->getID() .
1322 "&group_id=". $this->ArtifactType->Group->getID();
1324 //only send if some recipients were found
1325 if (count($emails) < 1 && count($monitor_ids) < 1) {
1329 if (count($monitor_ids) < 1) {
1330 $monitor_ids=array();
1332 $monitor_ids=array_unique($monitor_ids);
1335 $from = $this->ArtifactType->getReturnEmailAddress();
1336 $extra_headers = 'Reply-to: '.$from;
1338 // load the e-mail addresses of the users
1339 $users =& user_get_objects($monitor_ids);
1340 if (count($users) > 0) {
1341 foreach ($users as $user) {
1342 if ($user->getStatus() == "A") { //we are only sending emails to active users
1343 $emails[] = $user->getEmail();
1350 //now remove all duplicates from the email list
1351 if (count($emails) > 0) {
1352 $BCC=implode(',',array_unique($emails));
1353 util_send_message('',$subject,$body,$from,$BCC,'',$extra_headers);
1357 //util_handle_message($monitor_ids,$subject,$body,$BCC);
1363 * getExtraFieldDataText - Return the extra fields' data in a human-readable form.
1365 * @return array Array containing field ID => field name and value associated to it for
1368 function getExtraFieldDataText() {
1371 // First we get the list of extra fields and the data
1372 // associated to the fields
1373 $efs = $this->ArtifactType->getExtraFields();
1374 $efd = $this->getExtraFieldData();
1378 foreach ($efs as $efid => $ef) {
1379 $name = $ef["field_name"];
1381 // Get the value according to the type
1382 switch ($ef["field_type"]) {
1384 // for these types, the associated value comes straight
1385 case ARTIFACT_EXTRAFIELDTYPE_TEXT:
1386 case ARTIFACT_EXTRAFIELDTYPE_TEXTAREA:
1387 $value = $efd[$efid];
1390 // the other types have and ID or an array of IDs associated to them
1392 $value = $this->ArtifactType->getElementName($efd[$efid]);
1395 $return[$efid] = array("name" => $name, "value" => $value);