5 * Copyright 1999-2001, VA Linux Systems, Inc.
6 * Copyright 2002-2004, GForge, LLC
8 * This file is part of FusionForge.
10 * FusionForge is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published
12 * by the Free Software Foundation; either version 2 of the License,
13 * or (at your option) any later version.
15 * FusionForge is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with FusionForge; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
26 require_once $gfcommon.'include/Error.class.php';
27 require_once $gfcommon.'tracker/ArtifactMessage.class.php';
28 require_once $gfcommon.'tracker/ArtifactExtraField.class.php';
30 // This string is used when sending the notification mail for identifying the
32 define('ARTIFACT_MAIL_MARKER', '#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+');
35 * Factory method which creates an Artifact from an artifact ID
37 * @param int The artifact ID
38 * @param array The result array, if it's passed in
39 * @return object Artifact object
41 function &artifact_get_object($artifact_id,$data=false) {
43 if (!isset($ARTIFACT_OBJ["_".$artifact_id."_"])) {
45 //the db result handle was passed in
47 $res=db_query("SELECT * FROM artifact_vw WHERE artifact_id='$artifact_id'");
48 if (db_numrows($res) <1 ) {
49 $ARTIFACT_OBJ["_".$artifact_id."_"]=false;
52 $data =& db_fetch_array($res);
54 $ArtifactType =& artifactType_get_object($data["group_artifact_id"]);
55 $ARTIFACT_OBJ["_".$artifact_id."_"]= new Artifact($ArtifactType,$data);
57 return $ARTIFACT_OBJ["_".$artifact_id."_"];
60 class Artifact extends Error {
65 * @var int $status_res.
70 * Artifact Type object.
72 * @var object $ArtifactType.
77 * Array of artifact data.
79 * @var array $data_array.
84 * Array of artifact data for extra fields defined by Admin.
86 * @var array $extra_field_data.
88 var $extra_field_data;
91 * Array of ArtifactFile objects.
98 * Database result set of related tasks
100 * @var result $relatedtasks
105 * Artifact - constructor.
107 * @param object The ArtifactType object.
108 * @param integer (primary key from database OR complete assoc array)
109 * ONLY OPTIONAL WHEN YOU PLAN TO IMMEDIATELY CALL ->create()
110 * @return boolean success.
112 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()) {
170 // make sure this person has permission to add artifacts
172 if (!$this->ArtifactType->isPublic()) {
174 // Only admins can post/modify private artifacts
176 if (!$this->ArtifactType->userIsAdmin()) {
177 $this->setError(_('Artifact: Only Artifact Admins Can Modify Private ArtifactTypes'));
185 if (session_loggedin()) {
188 if ($this->ArtifactType->allowsAnon()) {
191 $this->setError(_('Artifact: This ArtifactType Does Not Allow Anonymous Submissions. Please Login.'));
200 $this->setError(_('Artifact: Message Summary Is Required'));
204 $this->setError(_('Artifact: Message Body Is Required'));
213 // if (!$status_id) {
214 $status_id=1; // on creation, status is set to "open"
217 // They may be using an extra field "status" box so we have to remap
218 // the status_id based on the extra field - this keeps the counters
221 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
223 $this->setError(_('Artifact: Error remapping status'));
229 $sql="INSERT INTO artifact
230 (group_artifact_id,status_id,priority,
231 submitted_by,assigned_to,open_date,summary,details)
233 ('".$this->ArtifactType->getID()."','$status_id','$priority',
234 '$user','$assigned_to','". time() ."','". htmlspecialchars($summary)."','". htmlspecialchars($details)."')";
237 $this->setError('Artifact: '.db_error());
242 $artifact_id=db_insertid($res,'artifact','artifact_id');
244 if (!$res || !$artifact_id) {
245 $this->setError('Artifact: '.db_error());
250 // Now set up our internal data structures
252 if (!$this->fetchData($artifact_id)) {
256 // the changes to the extra fields will be logged in this array.
257 // (we won't use it however)
258 $extra_field_changes = array();
259 if (!$this->updateExtraFields($extra_fields,$extra_field_changes)) {
265 // now send an email if appropriate
267 $this->mailFollowup(1);
275 * fetchData - re-fetch the data for this Artifact from the database.
277 * @param int The artifact ID.
278 * @return boolean success.
280 function fetchData($artifact_id) {
281 $res=db_query("SELECT * FROM artifact_vw
282 WHERE artifact_id='$artifact_id' AND group_artifact_id='".$this->ArtifactType->getID()."'");
283 if (!$res || db_numrows($res) < 1) {
284 $this->setError('Artifact: Invalid ArtifactID');
287 $this->data_array =& db_fetch_array($res);
288 db_free_result($res);
293 * getArtifactType - get the ArtifactType Object this Artifact is associated with.
295 * @return object ArtifactType.
297 function &getArtifactType() {
298 return $this->ArtifactType;
302 * getID - get this ArtifactID.
304 * @return int The artifact_id #.
307 return $this->data_array['artifact_id'];
311 * getStatusID - get open/closed/deleted flag.
313 * @return int Status: (1) Open, (2) Closed, (3) Deleted.
315 function getStatusID() {
316 return $this->data_array['status_id'];
320 * getStatusName - get open/closed/deleted text.
322 * @return string The status name.
324 function getStatusName() {
325 return $this->data_array['status_name'];
329 * getPriority - get priority flag.
331 * @return int priority.
333 function getPriority() {
334 return $this->data_array['priority'];
338 * getSubmittedBy - get ID of submitter.
340 * @return int user_id of submitter.
342 function getSubmittedBy() {
343 return $this->data_array['submitted_by'];
347 * getSubmittedEmail - get email of submitter.
349 * @return string The email of submitter.
351 function getSubmittedEmail() {
352 return $this->data_array['submitted_email'];
356 * getSubmittedRealName - get real name of submitter.
358 * @return string The real name of submitter.
360 function getSubmittedRealName() {
361 return $this->data_array['submitted_realname'];
365 * getSubmittedUnixName - get login name of submitter.
367 * @return string The unix name of submitter.
369 function getSubmittedUnixName() {
370 return $this->data_array['submitted_unixname'];
374 * getAssignedTo - get ID of assignee.
376 * @return int user_id of assignee.
378 function getAssignedTo() {
379 return $this->data_array['assigned_to'];
383 * getAssignedEmail - get email of assignee.
385 * @return string The email of assignee.
387 function getAssignedEmail() {
388 return $this->data_array['assigned_email'];
392 * getAssignedRealName - get real name of assignee.
394 * @return string The real name of assignee.
396 function getAssignedRealName() {
397 return $this->data_array['assigned_realname'];
401 * getAssignedUnixName - get login name of assignee.
403 * @return string The unix name of assignee.
405 function getAssignedUnixName() {
406 return $this->data_array['assigned_unixname'];
410 * getOpenDate - get unix time of creation.
412 * @return int unix time.
414 function getOpenDate() {
415 return $this->data_array['open_date'];
419 * getCloseDate - get unix time of closure.
421 * @return int unix time.
423 function getCloseDate() {
424 return $this->data_array['close_date'];
428 * getLastModifiedDate - the last_modified_date of this task.
430 * @return int the last_modified_date.
432 function getLastModifiedDate() {
433 return $this->data_array['last_modified_date'];
437 * getSummary - get text summary of artifact.
439 * @return string The summary (subject).
441 function getSummary() {
442 return $this->data_array['summary'];
446 * getDetails - get text body (message) of artifact.
448 * @return string The body (message).
450 function getDetails() {
451 return $this->data_array['details'];
455 * delete - delete this tracker and all its related data.
457 * @param bool I'm Sure.
458 * @return bool true/false;
460 function delete($sure) {
462 $this->setMissingParamsError();
465 if (!$this->ArtifactType->userIsAdmin()) {
466 $this->setPermissionDeniedError();
470 $res = db_query("DELETE FROM artifact_extra_field_data WHERE artifact_id='".$this->getID()."'");
472 $this->setError('Error deleting extra field data: '.db_error());
476 $res = db_query("DELETE FROM artifact_file WHERE artifact_id='".$this->getID()."'");
478 $this->setError('Error deleting file from db: '.db_error());
482 $res = db_query("DELETE FROM artifact_message WHERE artifact_id='".$this->getID()."'");
484 $this->setError('Error deleting message: '.db_error());
488 $res = db_query("DELETE FROM artifact_history WHERE artifact_id='".$this->getID()."'");
490 $this->setError('Error deleting history: '.db_error());
494 $res = db_query("DELETE FROM artifact_monitor WHERE artifact_id='".$this->getID()."'");
496 $this->setError('Error deleting monitor: '.db_error());
500 $res = db_query("DELETE FROM artifact WHERE artifact_id='".$this->getID()."'");
502 $this->setError('Error deleting artifact: '.db_error());
507 if ($this->getStatusID() == 1) {
508 $res = db_query("UPDATE artifact_counts_agg SET count=count-1,open_count=open_count-1
509 WHERE group_artifact_id='".$this->getID()."'");
511 $this->setError('Error updating artifact_counts_agg (1): '.db_error());
515 } elseif ($this->getStatusID() == 2) {
516 $res = db_query("UPDATE artifact_counts_agg SET count=count-1
517 WHERE group_artifact_id='".$this->getID()."'");
519 $this->setError('Error updating artifact_counts_agg (2): '.db_error());
530 * setMonitor - user can monitor this artifact.
532 * @return false - always false - always use the getErrorMessage() for feedback
534 function setMonitor() {
535 if (session_loggedin()) {
537 $user_id=user_getid();
538 $user =& user_get_object(user_getid());
541 //we don't want to include the "And email=" because
542 //a logged-in user's email may have changed
547 $this->setError(_('SetMonitor::Valid Email Address Required'));
552 $res=db_query("SELECT * FROM artifact_monitor
553 WHERE artifact_id='". $this->getID() ."'
554 AND user_id='$user_id'");
556 if (!$res || db_numrows($res) < 1) {
558 $res=db_query("INSERT INTO artifact_monitor (artifact_id,user_id)
559 VALUES ('". $this->getID() ."','$user_id')");
561 $this->setError(db_error());
564 $this->setError(_('Now Monitoring Artifact'));
568 //already monitoring - remove their monitor
569 db_query("DELETE FROM artifact_monitor
570 WHERE artifact_id='". $this->getID() ."'
571 AND user_id='$user_id'");
572 $this->setError(_('Artifact Monitoring Deactivated'));
577 function isMonitoring() {
578 if (!session_loggedin()) {
581 $sql="SELECT count(*) AS count FROM artifact_monitor WHERE user_id='".user_getid()."' AND artifact_id='".$this->getID()."';";
582 $result = db_query($sql);
583 $row_count = db_fetch_array($result);
584 return $result && $row_count['count'] > 0;
588 * getMonitorIds - array of email addresses monitoring this Artifact.
590 * @return array of email addresses monitoring this Artifact.
592 function getMonitorIds() {
593 $res=db_query("SELECT user_id
594 FROM artifact_monitor
595 WHERE artifact_id='". $this->getID() ."'");
596 return array_unique(array_merge($this->ArtifactType->getMonitorIds(),util_result_column_to_array($res)));
600 * getHistory - returns a result set of audit trail for this support request.
602 * @return database result set.
604 function getHistory() {
606 "FROM artifact_history_user_vw ".
607 "WHERE artifact_id='". $this->getID() ."' ".
608 "ORDER BY entrydate DESC";
609 return db_query($sql);
613 * getMessages - get the list of messages attached to this artifact.
615 * @return database result set.
617 function getMessages() {
619 "FROM artifact_message_user_vw ".
620 "WHERE artifact_id='". $this->getID() ."' ORDER BY adddate DESC";
621 return db_query($sql);
625 * getMessageObjects - get an array of message objects.
627 * @return array Of ArtifactMessage objects.
629 function &getMessageObjects() {
630 $res=$this->getMessages();
632 while ($arr = db_fetch_array($res)) {
633 //$return[]=new ArtifactMessage($arr['artifact_id'],$arr);
634 $return[] = new ArtifactMessage($this, $arr);
640 * getFiles - get array of ArtifactFile's.
642 * @return array of ArtifactFile's.
644 function &getFiles() {
645 if (!isset($this->files)) {
647 "FROM artifact_file_user_vw ".
648 "WHERE artifact_id='". $this->getID() ."'";
650 $rows=db_numrows($res);
652 for ($i=0; $i < $rows; $i++) {
653 $this->files[$i]=new ArtifactFile($this,db_fetch_array($res));
656 $this->files=array();
663 * getRelatedTasks - get array of related tasks
665 * @return Database result set
667 function getRelatedTasks() {
668 if (!$this->relatedtasks) {
670 db_query("SELECT pt.group_project_id,pt.project_task_id,pt.summary,pt.start_date,pt.end_date,pgl.group_id
671 FROM project_task pt, project_group_list pgl
672 WHERE pt.group_project_id = pgl.group_project_id AND
673 EXISTS (SELECT project_task_id FROM project_task_artifact
674 WHERE project_task_id=pt.project_task_id
675 AND artifact_id = ". $this->getID() . ")");
677 return $this->relatedtasks;
681 * addMessage - attach a text message to this Artifact.
683 * @param string The message being attached.
684 * @param string Email address of message creator.
685 * @param bool Whether to email out a followup.
687 * @return boolean success.
689 function addMessage($body,$by=false,$send_followup=false) {
691 $this->setMissingParamsError();
694 if (session_loggedin()) {
695 $user_id=user_getid();
696 $user =& user_get_object($user_id);
697 if (!$user || !is_object($user)) {
698 $this->setError('ERROR - Logged In User Bug Could Not Get User Object');
701 // we'll store this email even though it will likely never be used -
702 // since we have their correct user_id, we can join the USERS table to get email
703 $by=$user->getEmail();
704 } elseif (!$this->ArtifactType->allowsAnon()) {
705 $this->setError(_('Artifact: This ArtifactType Does Not Allow Anonymous Submissions. Please Login.'));
709 if (!$by || !validate_email($by)) {
710 $this->setMissingParamsError();
715 $sql="insert into artifact_message (artifact_id,submitted_by,from_email,adddate,body) ".
716 "VALUES ('". $this->getID() ."','$user_id','$by','". time() ."','". htmlspecialchars($body). "')";
717 $res = db_query($sql);
718 if ($send_followup) {
719 $this->mailFollowup(2,false);
725 * addHistory - add an entry to audit trail.
727 * @param string The name of the field in the database being modified.
728 * @param string The former value of this field.
730 * @return boolean success.
732 function addHistory($field_name,$old_value) {
733 if (!session_loggedin()) {
738 $sql="insert into artifact_history(artifact_id,field_name,old_value,mod_by,entrydate)
739 VALUES ('". $this->getID() ."','$field_name','".addslashes($old_value)."','$user','". time() ."')";
740 return db_query($sql);
744 * update - update the fields in this artifact.
746 * @param int The artifact priority.
747 * @param int The artifact status ID.
748 * @param int The person to which this artifact is to be assigned.
749 * @param string The artifact summary.
750 * @param int The canned response.
751 * @param string Attaching another comment.
752 * @param int Allows you to move an artifact to another type.
753 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
754 * @return boolean success.
756 function update($priority,$status_id,
757 $assigned_to,$summary,$canned_response,$details,$new_artifact_type_id,$extra_fields=array()) {
760 Field-level permission checking
762 if ($this->ArtifactType->userIsAdmin()) {
763 //admin can do everything
765 //everyone else cannot modify these fields
766 $priority=$this->getPriority();
767 $summary=addslashes($this->getSummary());
768 $canned_response=100;
769 $new_artifact_type_id=$this->ArtifactType->getID();
770 $assigned_to=$this->getAssignedTo();
772 if ($this->ArtifactType->userIsTechnician()) {
773 //technician can update only certain fields
774 //which were not overridden above
776 //submitter can no longer call this function
777 $this->setPermissionDeniedError();
783 // They may be using an extra field "status" box so we have to remap
784 // the status_id based on the extra field - this keeps the counters
787 if (count($extra_fields) > 0) {
788 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
794 || !$new_artifact_type_id) {
795 $this->setMissingParamsError();
800 // Array to record which properties were changed
807 // Get a lock on this row in the database
809 $lock=db_query("SELECT * FROM artifact WHERE artifact_id='".$this->getID()."' FOR UPDATE");
810 $artifact_type_id = $this->ArtifactType->getID();
812 // Attempt to move this Artifact to a new ArtifactType
813 // need to instantiate new ArtifactType obj and test perms
815 if ($new_artifact_type_id != $artifact_type_id) {
816 $newArtifactType= new ArtifactType($this->ArtifactType->getGroup(), $new_artifact_type_id);
817 if (!is_object($newArtifactType) || $newArtifactType->isError()) {
818 $this->setError('Artifact: Could not move to new ArtifactType'. $newArtifactType->getErrorMessage());
822 // do they have perms for new ArtifactType?
823 if (!$newArtifactType->userIsAdmin()) {
824 $this->setPermissionDeniedError();
830 // Now set Assigned to 100 in the new ArtifactType
834 //can't send a canned response when changing ArtifactType
835 $canned_response=100;
836 $this->ArtifactType =& $newArtifactType;
840 // This is a major problem - the extra fields
841 // are completely different IDs, and may not even
842 // exist in the new tracker. All extra_fields will be deleted and
843 // then set to 100 in the new tracker.
845 $res=db_query("DELETE FROM artifact_extra_field_data WHERE artifact_id='".$this->getID()."'");
846 $extra_fields=array();
854 // handle audit trail & build SQL statement
856 if ($this->getStatusID() != $status_id) {
857 $this->addHistory('status_id',$this->getStatusID());
858 $sqlu .= " status_id='$status_id', ";
859 $changes['status'] = 1;
862 // Reset the close_date if bug is re-opened
863 // (otherwise stat reports will be wrong).
864 if ($status_id == 1) {
865 $sqlu .= " close_date='0', ";
866 $this->addHistory('close_date',0);
869 if ($this->getPriority() != $priority) {
870 $this->addHistory('priority',$this->getPriority());
871 $sqlu .= " priority='$priority', ";
872 $changes['priority'] = 1;
876 if ($this->getAssignedTo() != $assigned_to) {
877 $this->addHistory('assigned_to',$this->getAssignedTo());
878 $sqlu .= " assigned_to='$assigned_to', ";
879 $changes['assigned_to'] = 1;
882 if ($summary && (addslashes($this->getSummary()) != htmlspecialchars($summary))) {
883 $this->addHistory('summary', addslashes($this->getSummary()));
884 $sqlu .= " summary='". htmlspecialchars($summary) ."', ";
885 $changes['summary'] = 1;
890 $this->addMessage($details,'',0);
891 $changes['details'] = 1;
896 // Enter the timestamp if we are changing to closed
898 if ($status_id != 1) {
900 $sqlu .= " close_date='$now', ";
901 $this->addHistory('close_date',$now);
906 Finally, update the artifact itself
909 $sql = "UPDATE artifact
912 group_artifact_id='$new_artifact_type_id'
914 artifact_id='". $this->getID() ."'
915 AND group_artifact_id='$artifact_type_id'";
916 $result=db_query($sql);
918 if (!$result || db_affected_rows($result) < 1) {
919 $this->setError('Error - update failed!'.db_error());
923 if (!$this->fetchData($this->getID())) {
930 //extra field handling
932 if (!$this->updateExtraFields($extra_fields,$changes)) {
933 //TODO - see if anything actually did change
939 handle canned responses
941 Instantiate ArtifactCanned and get the body of the message
943 if ($canned_response != 100) {
944 //don't care if this response is for this group - could be hacked
945 $acr=new ArtifactCanned($this->ArtifactType,$canned_response);
946 if (!$acr || !is_object($acr)) {
947 $this->setError('Artifact: Could Not Create Canned Response Object');
948 } elseif ($acr->isError()) {
949 $this->setError('Artifact: '.$acr->getErrorMessage());
951 $body = addslashes($acr->getBody());
953 if (!$this->addMessage(util_unconvert_htmlspecialchars($body),'',0)) {
960 $this->setError('Artifact: Unable to Use Canned Response');
966 if ($update || $send_message){
970 $this->mailFollowup(2, false, $changes);
974 //nothing changed, so cancel the transaction
975 $this->setError(_('Nothing Changed - Update Cancelled'));
983 * updateExtraFields - updates the extra data elements for this artifact
984 * e.g. the extra fields created and defined by the admin.
986 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
987 * @param array Array where changes to the extra fields should be logged
988 * @return true on success / false on failure
990 function updateExtraFields($extra_fields,&$changes){
992 This is extremely complex code - we have take the passed array
993 and see if we need to insert it into the db, and may have to
994 add history rows for the audit trail
996 start by getting all the available extra fields from ArtifactType
997 For each field from ArtifacType, check the passed array -
998 This prevents someone from passing bogus extra field entries - they will be ignored
999 if the passed entry is blank, may have to force a default value
1000 if the passed array is different from the existing data in db,
1001 delete old entry and insert new entries, along with possible audit trail
1003 skip it and continue to next item
1006 if (empty($extra_fields)) {
1009 //get a list of extra fields for this artifact_type
1010 $ef = $this->ArtifactType->getExtraFields();
1011 $efk=array_keys($ef);
1013 //now we'll update this artifact for each extra field
1014 for ($i=0; $i<count($efk); $i++) {
1016 $type=$ef[$efid]['field_type'];
1019 // Force each field to have some value if it is a numeric field
1020 // text fields will just be purged and skipped
1022 if (!array_key_exists($efid, $extra_fields) || !$extra_fields[$efid]) {
1023 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1024 $this->setError('Status Custom Field Must Be Set');
1026 } elseif (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO)) {
1027 $extra_fields[$efid]='100';
1028 } elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX)) {
1029 $extra_fields[$efid]=array('100');
1031 $resdel=db_query("DELETE FROM artifact_extra_field_data
1033 artifact_id='".$this->getID()."'
1034 AND extra_field_id='".$efid."'");
1039 // get the old rows of data
1041 $resd=db_query("SELECT * FROM artifact_extra_field_data
1043 artifact_id='".$this->getID()."'
1044 AND extra_field_id='".$efid."'");
1045 $rows=db_numrows($resd);
1046 if ($resd && $rows) {
1048 //POTENTIAL PROBLEM - no entry was there before, but adding one now - may need history
1051 // Compare for history purposes
1054 // these types have arrays associated to them, so they need
1055 // special handling to check for differences
1056 if ($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) {
1057 // check the differences between the old values and the new values
1058 $old_values = util_result_column_to_array($resd,"field_data");
1060 $added_values = array_diff($extra_fields[$efid], $old_values);
1061 $deleted_values = array_diff($old_values, $extra_fields[$efid]);
1063 if (!empty($added_values) || !empty($deleted_values)) { // there are differences...
1064 $field_name = $ef[$efid]['field_name'];
1065 $changes["extra_fields"][$efid] = 1;
1067 // Do a history entry only for deleted values
1068 if (!empty($deleted_values)) {
1069 $this->addHistory($field_name, $this->ArtifactType->getElementName($deleted_values));
1073 $resdel=db_query("DELETE FROM artifact_extra_field_data
1075 artifact_id='".$this->getID()."'
1076 AND extra_field_id='".$efid."'");
1080 } elseif (addslashes(db_result($resd,0,'field_data')) == htmlspecialchars($extra_fields[$efid])) {
1081 //element did not change
1084 //element DID change - do a history entry
1085 $field_name = $ef[$efid]['field_name'];
1086 $changes["extra_fields"][$efid] = 1;
1087 $resdel=db_query("DELETE FROM artifact_extra_field_data
1089 artifact_id='".$this->getID()."'
1090 AND extra_field_id='".$efid."'");
1091 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO) || ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS)) {
1092 //don't add history for text fields
1093 $this->addHistory($field_name,$this->ArtifactType->getElementName(db_result($resd,0,'field_data')));
1098 //no history for this extra field exists
1102 // See if anything was even passed for this extra_field_id
1104 if (!$extra_fields[$efid]) {
1105 //nothing in field to update - text fields may be blank
1107 //determine the type of field and whether it should have multiple rows supporting it
1108 $type=$ef[$efid]['field_type'];
1109 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1111 $count=count($extra_fields[$efid]);
1112 for ($fin=0; $fin<$count; $fin++) {
1113 $sql="INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data)
1114 values ('".$this->getID()."','".$efid."',
1115 '".$extra_fields[$efid][$fin]."')";
1116 $res=db_query($sql);
1118 $this->setError('Artifact::updateExtraFields:: '.$sql.db_error());
1125 $res=db_query("INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data)
1126 values ('".$this->getID()."','".$efid."',
1127 '".htmlspecialchars($extra_fields[$efid])."')");
1129 $this->setError('Artifact::updateExtraFields:: '.db_error());
1135 unset($this->extra_field_data);
1140 * getExtraFieldData - get an array of data for the extra fields associated with this artifact
1142 * @return array array of data
1144 function &getExtraFieldData() {
1145 if (!isset($this->extra_field_data)) {
1146 $this->extra_field_data = array();
1147 $res=db_query("SELECT * FROM artifact_extra_field_data
1148 WHERE artifact_id='".$this->getID()."' ORDER BY extra_field_id");
1149 $ef = $this->ArtifactType->getExtraFields();
1150 while ($arr = db_fetch_array($res)) {
1151 $type=$ef[$arr['extra_field_id']]['field_type'];
1152 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1153 //accumulate a sub-array of values in cases where you may have multiple rows
1154 if (!is_array($this->extra_field_data[$arr['extra_field_id']])) {
1155 $this->extra_field_data[$arr['extra_field_id']] = array();
1157 $this->extra_field_data[$arr['extra_field_id']][]=$arr['field_data'];
1159 $this->extra_field_data[$arr['extra_field_id']]=$arr['field_data'];
1163 return $this->extra_field_data;
1167 * marker - adds the > symbol to fields that have been modified for the email message
1171 function marker($prop_name,$changes,$extra_field_id=0) {
1172 if ($prop_name == 'extra_fields' && isset($changes[$prop_name][$extra_field_id])) {
1174 } else if ($prop_name != 'extra_fields' && isset($changes[$prop_name])) {
1182 * mailFollowup - send out an email update for this artifact.
1184 * @param int (1) initial/creation (2) update.
1185 * @param array Array of additional addresses to mail to.
1186 * @param array Array of fields changed in this update .
1188 * @return boolean success.
1190 function mailFollowup($type, $more_addresses=false, $changes='') {
1195 $sess = session_get_user() ;
1196 if ($type == 1) { // Initial opening
1198 $body = $this->ArtifactType->getName() ." item #". $this->getID() .", was opened at ". date( _('Y-m-d H:i'), $this->getOpenDate() ) . " by " . $sess->getRealName () ;
1200 $body = $this->ArtifactType->getName() ." item #". $this->getID() .", was opened at ". date( _('Y-m-d H:i'), $this->getOpenDate() ) ;
1204 $body = $this->ArtifactType->getName() ." item #". $this->getID() .", was changed at ". date( _('Y-m-d H:i'), $this->getOpenDate() ) . " by " . $sess->getRealName ();
1206 $body = $this->ArtifactType->getName() ." item #". $this->getID() .", was changed at ". date( _('Y-m-d H:i'), $this->getOpenDate() ) ;
1211 $body .= "\nYou can respond by visiting: ".
1212 "\n".util_make_url ('/tracker/?func=detail&atid='. $this->ArtifactType->getID() .
1213 "&aid=". $this->getID() .
1214 "&group_id=". $this->ArtifactType->Group->getID()) .
1215 "\nOr by replying to this e-mail entering your response between the following markers: ".
1216 "\n".ARTIFACT_MAIL_MARKER.
1217 "\n(enter your response here)".
1218 "\n".ARTIFACT_MAIL_MARKER.
1220 $this->marker('status',$changes).
1221 "Status: ". $this->getStatusName() ."\n".
1222 $this->marker('priority',$changes).
1223 "Priority: ". $this->getPriority() ."\n".
1224 "Submitted By: ". $this->getSubmittedRealName() .
1225 " (". $this->getSubmittedUnixName(). ")"."\n".
1226 $this->marker('assigned_to',$changes).
1227 "Assigned to: ". $this->getAssignedRealName() .
1228 " (". $this->getAssignedUnixName(). ")"."\n".
1229 $this->marker('summary',$changes).
1230 "Summary: ". util_unconvert_htmlspecialchars( $this->getSummary() )." \n";
1232 // Now display the extra fields
1233 $efd = $this->getExtraFieldDataText();
1234 foreach ($efd as $efid => $ef) {
1235 $body .= $this->marker('extra_fields', $changes, $efid);
1236 $body .= $ef["name"].": ".$ef["value"]."\n";
1239 $subject='['. $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() . '][' . $this->getID() .'] '. util_unconvert_htmlspecialchars( $this->getSummary() );
1242 // get all the email addresses that are monitoring this request or the ArtifactType
1243 $monitor_ids =& $this->getMonitorIds();
1245 // initial creation, we just get the users monitoring the ArtifactType
1246 $monitor_ids =& $this->ArtifactType->getMonitorIds();
1250 if ($more_addresses) {
1251 $emails[] = $more_addresses;
1253 //we don't email the current user
1254 if ($this->getAssignedTo() != user_getid()) {
1255 $monitor_ids[] = $this->getAssignedTo();
1257 if ($this->getSubmittedBy() != user_getid()) {
1258 $monitor_ids[] = $this->getSubmittedBy();
1260 //initial submission
1262 //if an email is set for this ArtifactType
1263 //add that address to the BCC: list
1264 if ($this->ArtifactType->getEmailAddress()) {
1265 $emails[] = $this->ArtifactType->getEmailAddress();
1269 if ($this->ArtifactType->emailAll()) {
1270 $emails[] = $this->ArtifactType->getEmailAddress();
1274 $body .= "\n\nInitial Comment:".
1275 "\n".util_unconvert_htmlspecialchars( $this->getDetails() ) .
1276 "\n\n----------------------------------------------------------------------";
1280 Now include the followups
1282 $result2=$this->getMessages();
1284 $rows=db_numrows($result2);
1286 if ($result2 && $rows > 0) {
1287 for ($i=0; $i<$rows; $i++) {
1289 // for messages posted by non-logged-in users,
1290 // we grab the email they gave us
1292 // otherwise we use the confirmed one from the users table
1294 if (db_result($result2,$i,'user_id') == 100) {
1295 $emails[] = db_result($result2,$i,'from_email');
1297 $monitor_ids[] = db_result($result2,$i,'user_id');
1303 $body .= $this->marker('details',$changes);
1305 $body .= "Comment By: ". db_result($result2,$i,'realname') . " (".db_result($result2,$i,'user_name').")".
1306 "\nDate: ". date( _('Y-m-d H:i'),db_result($result2,$i,'adddate') ).
1308 "\n".util_unconvert_htmlspecialchars( db_result($result2,$i,'body') ).
1309 "\n\n----------------------------------------------------------------------";
1315 $body .= "\n\nYou can respond by visiting: ".
1316 "\n".util_make_url ('/tracker/?func=detail&atid='. $this->ArtifactType->getID() .
1317 "&aid=". $this->getID() .
1318 "&group_id=". $this->ArtifactType->Group->getID());
1320 //only send if some recipients were found
1321 if (count($emails) < 1 && count($monitor_ids) < 1) {
1325 if (count($monitor_ids) < 1) {
1326 $monitor_ids=array();
1328 $monitor_ids=array_unique($monitor_ids);
1331 $from = $this->ArtifactType->getReturnEmailAddress();
1332 $extra_headers = 'Reply-to: '.$from;
1334 // load the e-mail addresses of the users
1335 $users =& user_get_objects($monitor_ids);
1336 if (count($users) > 0) {
1337 foreach ($users as $user) {
1338 if ($user->getStatus() == "A") { //we are only sending emails to active users
1339 $emails[] = $user->getEmail();
1346 //now remove all duplicates from the email list
1347 if (count($emails) > 0) {
1348 $BCC=implode(',',array_unique($emails));
1349 util_send_message('',$subject,$body,$from,$BCC,'',$extra_headers);
1353 //util_handle_message($monitor_ids,$subject,$body,$BCC);
1359 * getExtraFieldDataText - Return the extra fields' data in a human-readable form.
1361 * @return array Array containing field ID => field name and value associated to it for
1364 function getExtraFieldDataText() {
1365 // First we get the list of extra fields and the data
1366 // associated to the fields
1367 $efs = $this->ArtifactType->getExtraFields();
1368 $efd = $this->getExtraFieldData();
1372 foreach ($efs as $efid => $ef) {
1373 $name = $ef["field_name"];
1375 // Get the value according to the type
1376 switch ($ef["field_type"]) {
1378 // for these types, the associated value comes straight
1379 case ARTIFACT_EXTRAFIELDTYPE_TEXT:
1380 case ARTIFACT_EXTRAFIELDTYPE_TEXTAREA:
1381 $value = isset($efd[$efid]) ? $efd[$efid]: '';
1384 // the other types have and ID or an array of IDs associated to them
1386 $value = $this->ArtifactType->getElementName($efd[$efid]);
1389 $return[$efid] = array("name" => $name, "value" => $value);
1399 // c-file-style: "bsd"