4 * Artifact.class - Main Artifact class
6 * SourceForge: Breaking Down the Barriers to Open Source Development
7 * Copyright 1999-2001 (c) VA Linux Systems
8 * http://sourceforge.net
13 require_once('common/include/Error.class');
15 class Artifact extends Error {
20 * @var int $status_res
25 * Artifact Type object
27 * @var object $ArtifactType
32 * Array of artifact data
34 * @var array $data_array
39 * Array of ArtifactFile objects
46 * Artifact() - constructor
48 * Use this constructor if you are modifying an existing artifact
50 * @param object The artifact type object
51 * @param integer (primary key from database OR complete assoc array)
52 * ONLY OPTIONAL WHEN YOU PLAN TO IMMEDIATELY CALL ->create()
55 function Artifact(&$ArtifactType, $data=false) {
58 $this->ArtifactType =& $ArtifactType;
60 //was ArtifactType legit?
61 if (!$ArtifactType || !is_object($ArtifactType)) {
62 $this->setError('Artifact: No Valid ArtifactType');
65 //did ArtifactType have an error?
66 if ($ArtifactType->isError()) {
67 $this->setError('Artifact: '.$ArtifactType->getErrorMessage());
72 // make sure this person has permission to view artifacts
74 if (!$this->ArtifactType->userCanView()) {
75 $this->setError('Artifact: Only Group Members Can View Private ArtifactTypes');
80 // set up data structures
83 if (is_array($data)) {
84 $this->data_array =& $data;
87 if (!$this->fetchData($data)) {
94 $this->setError('No ID Present');
100 * create() - construct a new Artifact in the database
102 * @param int The category ID
103 * @param int The artifact group ID
104 * @param string The artifact summary
105 * @param string Details of the artifact
106 * @param int The ID of the user to which this artifact is to be assigned
107 * @param int The artifacts priority
108 * @return id on success / false on failure
110 function create($category_id, $artifact_group_id, $summary, $details, $assigned_to=100, $priority=5, $monitor_email=false) {
113 // make sure this person has permission to add artifacts
115 if (!$this->ArtifactType->isPublic()) {
117 // Only admins can post/modify private artifacts
119 if (!$this->ArtifactType->userIsAdmin()) {
120 $this->setError('Artifact: Only Artifact Admins Can Modify Private ArtifactTypes');
128 if (user_isloggedin()) {
131 if ($this->ArtifactType->allowsAnon()) {
134 $this->setError('Artifact: This ArtifactType Does Not Allow Anonymous Submissions. Please Login.');
143 $this->setError('Artifact: Message Summary Is Required');
147 $this->setError('Artifact: Message Body Is Required');
151 if ($category_id == 100) {
154 //create an ArtifactCategory to determine who to auto-assign to
155 $ac=new ArtifactCategory($this->ArtifactType,$category_id);
156 if (!$ac || !is_object($ac) || $ac->isError()) {
159 $assigned_to=$ac->getAssignee();
163 if ($assigned_to==100 && $category_id != 100) {
164 //create an ArtifactCategory to determine who to auto-assign to
165 $ac=new ArtifactCategory($this->ArtifactType,$category_id);
166 if (!$ac || !is_object($ac) || $ac->isError()) {
169 $assigned_to=$ac->getAssignee();
178 if (!$artifact_group_id) {
179 $artifact_group_id=100;
181 if (!$resolution_id) {
188 // Check to see if this idiot user is trying to double-submit
190 $res=db_query("SELECT * FROM artifact
191 WHERE summary='$summary'
192 AND submitted_by='$user'
193 AND open_date > '". (time() - 86400) ."'");
194 if ($res && db_numrows($res) > 0) {
195 $this->setError("You Attempted To Double-submit this item. Please avoid double-clicking.");
200 $res=db_query("INSERT INTO artifact
201 (group_artifact_id,status_id,category_id,artifact_group_id,priority,
202 submitted_by,assigned_to,open_date,summary,details,resolution_id)
204 ('".$this->ArtifactType->getID()."','1','$category_id','$artifact_group_id',
205 '$priority','$user','$assigned_to','". time() ."','".
206 htmlspecialchars($summary)."','". htmlspecialchars($details)."','$resolution_id')");
208 $artifact_id=db_insertid($res,'artifact','artifact_id');
210 if (!$res || !$artifact_id) {
211 $this->setError('Artifact: '.db_error());
216 // Now set up our internal data structures
218 if (!$this->fetchData($artifact_id)) {
224 // now send an email if appropriate
226 $this->mailFollowup(1);
231 // Set up monitoring for the user if requested
233 if ($monitor_email) {
234 $this->setMonitor($monitor_email);
241 * fetchData() - re-fetch the data for this Artifact from the database
243 * @param int The artifact ID
246 function fetchData($artifact_id) {
247 $res=db_query("SELECT * FROM artifact_vw
248 WHERE artifact_id='$artifact_id' AND group_artifact_id='".$this->ArtifactType->getID()."'");
249 if (!$res || db_numrows($res) < 1) {
250 $this->setError('Artifact: Invalid ArtifactID');
253 $this->data_array =& db_fetch_array($res);
254 db_free_result($res);
259 * getArtifactType() - get the ArtifactType Object this Artifact is associated with
261 * @return ArtifactType
263 function &getArtifactType() {
264 return $this->ArtifactType;
268 * getID() - get this ArtifactID
270 * @return the group_artifact_id #
273 return $this->data_array['artifact_id'];
277 * getStatusID() - get open/closed/deleted flag
279 * @return (1) Open, (2) Closed, (3) Deleted
281 function getStatusID() {
282 return $this->data_array['status_id'];
286 * getStatusName() - get open/closed/deleted text
288 * @return text status name
290 function getStatusName() {
291 return $this->data_array['status_name'];
295 * getResolutionID() - get resolution flag
299 function getResolutionID() {
300 return $this->data_array['resolution_id'];
304 * getResolutionName() - get resolution name
306 * @return text resolution name
308 function getResolutionName() {
309 return $this->data_array['resolution_name'];
313 * getCategoryID() - get category_id flag
315 * @return int category_id
317 function getCategoryID() {
318 return $this->data_array['category_id'];
322 * getCategoryName() - get category text name
324 * @return text category name
326 function getCategoryName() {
327 return $this->data_array['category_name'];
331 * getArtifactGroupID() - get artifact_group_id flag
333 * @return int artifact_group_id
335 function getArtifactGroupID() {
336 return $this->data_array['artifact_group_id'];
340 * getArtifactGroupName() - get artifact_group_name text
342 * @return text artifact_group name
344 function getArtifactGroupName() {
345 return $this->data_array['group_name'];
349 * getPriority() - get priority flag
351 * @return int priority
353 function getPriority() {
354 return $this->data_array['priority'];
358 * getSubmittedBy() - get ID of submitter
360 * @return int user_id of submitter
362 function getSubmittedBy() {
363 return $this->data_array['submitted_by'];
367 * getSubmittedEmail() - get email of submitter
369 * @return text email of submitter
371 function getSubmittedEmail() {
372 return $this->data_array['submitted_email'];
376 * getSubmittedRealName() - get real name of submitter
378 * @return text real name of submitter
380 function getSubmittedRealName() {
381 return $this->data_array['submitted_realname'];
385 * getSubmittedUnixName() - get login name of submitter
387 * @return text unix name of submitter
389 function getSubmittedUnixName() {
390 return $this->data_array['submitted_unixname'];
394 * getAssignedTo() - get ID of assignee
396 * @return int user_id of assignee
398 function getAssignedTo() {
399 return $this->data_array['assigned_to'];
403 * getAssignedEmail() - get email of assignee
405 * @return text email of assignee
407 function getAssignedEmail() {
408 return $this->data_array['assigned_email'];
412 * getAssignedRealName() - get real name of assignee
414 * @return text real name of assignee
416 function getAssignedRealName() {
417 return $this->data_array['assigned_realname'];
421 * getAssignedUnixName() - get login name of assignee
423 * @return text unix name of assignee
425 function getAssignedUnixName() {
426 return $this->data_array['assigned_unixname'];
430 * getOpenDate() - get unix time of creation
432 * @return int unix time
434 function getOpenDate() {
435 return $this->data_array['open_date'];
439 * getCloseDate() - get unix time of closure
441 * @return int unix time
443 function getCloseDate() {
444 return $this->data_array['close_date'];
448 * getSummary() - get text summary of artifact
450 * @return text summary (subject)
452 function getSummary() {
453 return $this->data_array['summary'];
457 * getDetails() - get text body (message) of artifact
459 * @return text body (message)
461 function getDetails() {
462 return $this->data_array['details'];
466 * setMonitor() - user can monitor this artifact
468 * @param string The email address of the user who is monitoring this artifact
469 * @return false - always false - always use the getErrorMessage() for feedback
471 function setMonitor($email=false) {
472 if (user_isloggedin()) {
474 $user_id=user_getid();
475 $user =& user_get_object(user_getid());
478 //we don't want to include the "And email=" because
479 //a logged-in user's email may have changed
484 if (!$email || !validate_email($email)) {
485 $this->setError('SetMonitor::Valid Email Address Required');
490 $email_sql="AND email='$email'";
494 $email=strtolower($email);
496 $res=db_query("SELECT * FROM artifact_monitor
497 WHERE artifact_id='". $this->getID() ."'
498 AND user_id='$user_id' $email_sql");
500 if (!$res || db_numrows($res) < 1) {
502 $res=db_query("INSERT INTO artifact_monitor (artifact_id,user_id,email)
503 VALUES ('". $this->getID() ."','$user_id','$email')");
505 $this->setError(db_error());
508 $this->setError('Now Monitoring');
512 //already monitoring - remove their monitor
513 db_query("DELETE FROM artifact_monitor
514 WHERE artifact_id='". $this->getID() ."'
515 AND user_id='$user_id' $email_sql");
516 $this->setError('Monitoring Deactivated');
522 * getMonitorEmails() -
524 * @return array of email addresses monitoring this ArtifactType
526 function &getMonitorEmails() {
527 $res=db_query("SELECT artifact_monitor.user_id,users.email,artifact_monitor.email as email2
528 FROM users,artifact_monitor
529 WHERE users.user_id=artifact_monitor.user_id
530 AND artifact_monitor.artifact_id='". $this->getID() ."'");
532 $rows=db_numrows($res);
535 for ($i=0; $i<$rows; $i++) {
537 // for monitoring by non-logged-in users,
538 // we grab the email they gave us
540 // otherwise we use the confirmed one from the users table
542 $email[]=((db_result($res,$i,'user_id') == 100)?db_result($res,$i,'email2'):db_result($res,$i,'email') );
548 * getHistory() - returns a result set of audit trail for this support request
552 function getHistory() {
554 "FROM artifact_history_user_vw ".
555 "WHERE artifact_id='". $this->getID() ."' ".
556 "ORDER BY entrydate DESC";
557 return db_query($sql);
561 * getMessages() - get the list of messages attached to this artifact
563 * @return database result set
565 function getMessages() {
567 "FROM artifact_message_user_vw ".
568 "WHERE artifact_id='". $this->getID() ."' ORDER BY adddate DESC";
569 return db_query($sql);
573 * getFiles() - get array of ArtifactFile's
575 * @return array of ArtifactFile's
577 function &getFiles() {
578 if (!isset($this->files)) {
580 "FROM artifact_file_user_vw ".
581 "WHERE artifact_id='". $this->getID() ."'";
583 $rows=db_numrows($res);
585 for ($i=0; $i < $rows; $i++) {
586 $this->files[$i]=new ArtifactFile($this,db_fetch_array($res));
589 $this->files=array();
596 * addMessage() - attach a text message to this Artifact
598 * @param string The message being attached
599 * @param string Email address of message creator
600 * @param bool Whether to email out a followup
604 function addMessage($body,$by=false,$send_followup=false) {
606 $this->setError('ERROR - addMessage: Missing Parameters');
610 if (user_isloggedin()) {
611 $user_id=user_getid();
612 $user =& user_get_object($user_id);
613 if (!$user || !is_object($user)) {
614 $this->setError('ERROR - Logged In User Bug Could Not Get User Object');
617 $body="Logged In: YES \nuser_id=$user_id\n\n".$body;
619 // we'll store this email even though it will likely never be used -
620 // since we have their correct user_id, we can join the USERS table to get email
621 $by=$user->getEmail();
623 $body="Logged In: NO \n\n".$body;
626 $this->setError('ERROR - addMessage: Missing Email Address');
631 $sql="insert into artifact_message (artifact_id,submitted_by,from_email,adddate,body) ".
632 "VALUES ('". $this->getID() ."','$user_id','$by','". time() ."','". htmlspecialchars($body). "')";
633 $res = db_query($sql);
634 if ($send_followup) {
635 $this->mailFollowup(2,false);
641 * addHistory() - add an entry to audit trail
643 * @param string The name of the field in the database being modified
644 * @param string The former value of this field
648 function addHistory($field_name,$old_value) {
649 if (!user_isloggedin()) {
655 $sql="insert into artifact_history(artifact_id,field_name,old_value,mod_by,entrydate)
656 VALUES ('". $this->getID() ."','$field_name','$old_value','$user','". time() ."')";
657 return db_query($sql);
661 * update() - update the fields in this artifact
663 * @param int The artifact priority
664 * @param int The artifact status ID
665 * @param int The artifact category ID
666 * @param int The artifact group ID
667 * @param int The artifact resolution ID
668 * @param int The person to which this artifact is to be assigned
669 * @param int The artifact summary
670 * @param int The canned response
671 * @param int Attaching another comment
672 * @param int Allows you to move an artifact to another type
675 function update($priority,$status_id,$category_id,$artifact_group_id,$resolution_id,
676 $assigned_to,$summary,$canned_response,$details,$new_artifact_type_id) {
682 || !$artifact_group_id
685 || !$new_artifact_type_id) {
686 $this->setError('Artifact: Missing required parameters to artifact::update()');
690 // If the current status is Pending then auto-reset it to 'Open'
691 // Assumes the status ID for 'Pending' is '4'
692 if ($status_id != '2' && $status_id != '3' && $this->getStatusID() == '4') {
696 // original submitter can always modify his/her items now
697 if (!$this->ArtifactType->userIsAdmin() && ($this->getSubmittedBy() != user_getid())) {
698 $this->setError('Artifact: Update Permission Denied');
702 // Array to record which properties were changed
708 // Get a lock on this row in the database
710 $lock=db_query("SELECT * FROM artifact WHERE artifact_id='".$this->getID()."' FOR UPDATE");
712 $artifact_type_id = $this->ArtifactType->getID();
715 // Attempt to move this Artifact to a new ArtifactType
716 // need to instantiate new ArtifactType obj and test perms
718 if ($new_artifact_type_id != $artifact_type_id) {
719 $newArtifactType= new ArtifactType($this->ArtifactType->getGroup(), $new_artifact_type_id);
720 if (!is_object($newArtifactType) || $newArtifactType->isError()) {
721 $this->setError('Artifact: Could not move to new ArtifactType'.$newArtifactType->getErrorMessage());
724 // do they have perms for new ArtifactType?
725 if (!$newArtifactType->userIsAdmin()) {
726 $this->setError('Artifact: Could not move to new ArtifactType: Permission Denied');
731 // Now set ArtifactGroup, Category, and Assigned to 100 in the new ArtifactType
735 $artifact_group_id='100';
737 //can't send a canned response when changing ArtifactType
738 $canned_response=100;
739 $this->ArtifactType =& $newArtifactType;
746 // handle audit trail & build SQL statement
748 if ($this->getStatusID() != $status_id) {
749 $this->addHistory('status_id',$this->getStatusID());
750 $sqlu .= " status_id='$status_id', ";
751 $changes['status'] = 1;
754 if (($this->getResolutionID() != $resolution_id) && ($resolution_id != 100)) {
755 $this->addHistory('resolution_id',$this->getResolutionID());
756 $sqlu .= " resolution_id='$resolution_id', ";
757 $changes['resolution'] = 1;
760 if ($this->getCategoryID() != $category_id) {
761 $this->addHistory('category_id',$this->getCategoryID());
762 $sqlu .= " category_id='$category_id', ";
763 $changes['category'] = 1;
766 if ($this->getArtifactGroupID() != $artifact_group_id) {
767 $this->addHistory('artifact_group_id',$this->getArtifactGroupID());
768 $sqlu .= " artifact_group_id='$artifact_group_id', ";
769 $changes['artifact_group'] = 1;
772 if ($this->getPriority() != $priority) {
773 $this->addHistory('priority',$this->getPriority());
774 $sqlu .= " priority='$priority', ";
775 $changes['priority'] = 1;
779 if ($this->getAssignedTo() != $assigned_to) {
780 $this->addHistory('assigned_to',$this->getAssignedTo());
781 $sqlu .= " assigned_to='$assigned_to', ";
782 $changes['assigned_to'] = 1;
785 if ($summary && (addslashes($this->getSummary()) != htmlspecialchars($summary))) {
786 $this->addHistory('summary', addslashes($this->getSummary()));
787 $sqlu .= " summary='". htmlspecialchars($summary) ."', ";
788 $changes['summary'] = 1;
793 $this->addMessage($details);
794 $changes['details'] = 1;
799 // Enter the timestamp if we are changing to closed
801 if ($status_id != 1) {
803 $sqlu .= " close_date='$now', ";
804 $this->addHistory('close_date',$this->getCloseDate());
809 Finally, update the artifact itself
812 $sql = "UPDATE artifact
815 group_artifact_id='$new_artifact_type_id'
817 artifact_id='". $this->getID() ."'
818 AND group_artifact_id='$artifact_type_id'";
820 $result=db_query($sql);
822 if (!$result || db_affected_rows($result) < 1) {
823 $this->setError('Error - update failed!');
828 $this->fetchData($this->getID());
829 //error check the data fetching??
834 handle canned responses
836 Instantiate ArtifactCanned and get the body of the message
838 if ($canned_response != 100) {
839 //don't care if this response is for this group - could be hacked
840 $acr=new ArtifactCanned($this->ArtifactType,$canned_response);
841 if (!$acr || !is_object($acr)) {
842 $this->setError('Artifact: Could Not Create Canned Response Object');
843 } elseif ($acr->isError()) {
844 $this->setError('Artifact: '.$acr->getErrorMessage());
846 $body = addslashes($acr->getBody());
848 if (!$this->addMessage(util_unconvert_htmlspecialchars($body),user_getname().'@'.$GLOBALS['sys_users_host'])) {
855 $this->setError('Artifact: Unable to Use Canned Response');
861 if ($update || $send_message){
865 $this->mailFollowup(2, false, $changes);
869 //nothing changed, so cancel the transaction
870 $this->setError('Nothing Changed - Update Cancelled');
876 // function which returns proper marker for changed properties
877 function marker($prop_name,$changes) {
878 if ($changes[$prop_name]) {
886 * mailFollowup() - send out an email update for this artifact
888 * @param int (1) initial/creation (2) update
889 * @param array Array of additional addresses to mail to
890 * @param array Array of fields changed in this update
894 function mailFollowup($type, $more_addresses=false, $changes='') {
901 if ($this->ArtifactType->useResolution()) {
902 $resolution_text = $this->marker('resolution',$changes).
903 "Resolution: ". $this->getResolutionName() ."\n";
906 $body = $this->ArtifactType->getName() ." item #". $this->getID() .", was opened at ". date( $sys_datefmt, $this->getOpenDate() ).
907 "\nYou can respond by visiting: ".
908 "\nhttp://".$GLOBALS['sys_default_domain']."/tracker/?func=detail&atid=". $this->ArtifactType->getID() .
909 "&aid=". $this->getID() .
910 "&group_id=". $this->ArtifactType->Group->getID() .
912 $this->marker('category',$changes).
913 "Category: ". $this->getCategoryName() ."\n".
914 $this->marker('artifact_group',$changes).
915 "Group: ". $this->getArtifactGroupName() ."\n".
916 $this->marker('status',$changes).
917 "Status: ". $this->getStatusName() ."\n".
919 $this->marker('priority',$changes).
920 "Priority: ". $this->getPriority() ."\n".
921 "Submitted By: ". $this->getSubmittedRealName() .
922 " (". $this->getSubmittedUnixName(). ")"."\n".
923 $this->marker('assigned_to',$changes).
924 "Assigned to: ". $this->getAssignedRealName() .
925 " (". $this->getAssignedUnixName(). ")"."\n".
926 $this->marker('summary',$changes).
927 "Summary: ". util_unconvert_htmlspecialchars( $this->getSummary() );
930 $subject='[ '. $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() . '-' . $this->getID() .' ] '. util_unconvert_htmlspecialchars( $this->getSummary() );
934 get all the email addresses that are monitoring this request
936 $emails =& $this->getMonitorEmails();
939 if ($more_addresses) {
940 $emails[] = $more_addresses;
942 //we don't email the current user
943 if ($this->getAssignedTo() != user_getid()) {
944 $emails[] = $this->getAssignedEmail();
946 if ($this->getSubmittedBy() != user_getid()) {
947 $emails[] = $this->getSubmittedEmail();
951 //if an email is set for this ArtifactType
952 //add that address to the BCC: list
953 if ($this->ArtifactType->getEmailAddress()) {
954 $emails[] = $this->ArtifactType->getEmailAddress();
958 if ($this->ArtifactType->emailAll()) {
959 $emails[] = $this->ArtifactType->getEmailAddress();
963 $body .= "\n\nInitial Comment:".
964 "\n".util_unconvert_htmlspecialchars( $this->getDetails() ) .
965 "\n\n----------------------------------------------------------------------";
969 Now include the followups
971 $result2=$this->getMessages();
973 $rows=db_numrows($result2);
975 if ($result2 && $rows > 0) {
976 for ($i=0; $i<$rows; $i++) {
978 // for messages posted by non-logged-in users,
979 // we grab the email they gave us
981 // otherwise we use the confirmed one from the users table
983 if (db_result($result2,$i,'user_id') == 100) {
984 $emails[] = db_result($result2,$i,'from_email');
986 $emails[] = db_result($result2,$i,'email');
992 $body .= $this->marker('details',$changes);
994 $body .= "Comment By: ". db_result($result2,$i,'realname') . " (".db_result($result2,$i,'user_name').")".
995 "\nDate: ". date( $sys_datefmt,db_result($result2,$i,'adddate') ).
997 "\n".util_unconvert_htmlspecialchars( db_result($result2,$i,'body') ).
998 "\n\n----------------------------------------------------------------------";
1004 $body .= "\n\nYou can respond by visiting: ".
1005 "\nhttp://$GLOBALS[sys_default_domain]/tracker/?func=detail&atid=". $this->ArtifactType->getID() .
1006 "&aid=". $this->getID() .
1007 "&group_id=". $this->ArtifactType->Group->getID();
1009 //only send if some recipients were found
1010 if (count($emails) < 1) {
1014 //now remove all duplicates from the email list
1015 $BCC=implode(',',array_unique($emails));
1020 util_send_mail("noreply@$GLOBALS[sys_default_domain]",$subject,$body,"noreply@$GLOBALS[sys_default_domain]",$BCC);