5 * Copyright 1999-2001, VA Linux Systems, Inc.
6 * Copyright 2002-2004, GForge, LLC
7 * Copyright 2009, Roland Mas
8 * Copyright 2009, Alcatel-Lucent
10 * This file is part of FusionForge. FusionForge is free software;
11 * you can redistribute it and/or modify it under the terms of the
12 * GNU General Public License as published by the Free Software
13 * Foundation; either version 2 of the Licence, or (at your option)
16 * FusionForge 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 along
22 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 * Standard Alcatel-Lucent disclaimer for contributing to open source
29 * "The Artifact ("Contribution") has not been tested and/or
30 * validated for release as or in products, combinations with products or
31 * other commercial use. Any use of the Contribution is entirely made at
32 * the user's own responsibility and the user can not rely on any features,
33 * functionalities or performances Alcatel-Lucent has attributed to the
36 * THE CONTRIBUTION BY ALCATEL-LUCENT IS PROVIDED AS IS, WITHOUT WARRANTY
37 * OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
38 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, COMPLIANCE,
39 * NON-INTERFERENCE AND/OR INTERWORKING WITH THE SOFTWARE TO WHICH THE
40 * CONTRIBUTION HAS BEEN MADE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
41 * ALCATEL-LUCENT BE LIABLE FOR ANY DAMAGES OR OTHER LIABLITY, WHETHER IN
42 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
43 * CONTRIBUTION OR THE USE OR OTHER DEALINGS IN THE CONTRIBUTION, WHETHER
44 * TOGETHER WITH THE SOFTWARE TO WHICH THE CONTRIBUTION RELATES OR ON A STAND
47 require_once $gfcommon.'include/Error.class.php';
48 require_once $gfcommon.'tracker/ArtifactMessage.class.php';
49 require_once $gfcommon.'tracker/ArtifactExtraField.class.php';
50 require_once $gfcommon.'tracker/ArtifactWorkflow.class.php';
51 require_once $gfcommon.'tracker/ArtifactStorage.class.php';
53 // This string is used when sending the notification mail for identifying the
55 define('ARTIFACT_MAIL_MARKER', '#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+');
58 * Factory method which creates an Artifact from an artifact ID
60 * @param int $artifact_id The artifact ID
61 * @param array|bool $data The result array, if it's passed in
62 * @return Artifact Artifact object
64 function &artifact_get_object($artifact_id,$data=false) {
66 if (!isset($ARTIFACT_OBJ["_".$artifact_id."_"])) {
68 //the db result handle was passed in
70 $res = db_query_params ('SELECT * FROM artifact_vw WHERE artifact_id=$1',
71 array ($artifact_id)) ;
72 if (db_numrows($res) <1 ) {
73 $ARTIFACT_OBJ["_".$artifact_id."_"]=false;
76 $data = db_fetch_array($res);
78 $ArtifactType =& artifactType_get_object($data["group_artifact_id"]);
79 $ARTIFACT_OBJ["_".$artifact_id."_"]= new Artifact($ArtifactType,$data);
81 return $ARTIFACT_OBJ["_".$artifact_id."_"];
84 class Artifact extends Error {
89 * @var int $status_res.
94 * Artifact Type object.
96 * @var object $ArtifactType.
101 * Array of artifact data.
103 * @var array $data_array.
108 * Array of artifact data for extra fields defined by Admin.
110 * @var array $extra_field_data.
112 var $extra_field_data;
115 * Array of ArtifactFile objects.
122 * Database result set of related tasks
124 * @var result $relatedtasks
129 * Artifact - constructor.
131 * @param ArtifactType $ArtifactType The ArtifactType object.
132 * @param int|bool $data (primary key from database OR complete assoc array)
133 * ONLY OPTIONAL WHEN YOU PLAN TO IMMEDIATELY CALL ->create()
135 function __construct(&$ArtifactType, $data=false) {
138 $this->ArtifactType =& $ArtifactType;
140 // Was ArtifactType legit?
141 if (!$ArtifactType || !is_object($ArtifactType)) {
142 $this->setError(_('Invalid Artifact Type'));
146 // Did ArtifactType have an error?
147 if ($ArtifactType->isError()) {
148 $this->setError($ArtifactType->getErrorMessage());
152 // Make sure this person has permission to view artifacts
153 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'read')) {
154 $this->setError(_('Only project members can view private artifact types'));
159 if (is_array($data)) {
160 $this->data_array =& $data;
162 $this->fetchData($data);
168 * create - construct a new Artifact in the database.
170 * @param string $summary The artifact summary.
171 * @param string $details Details of the artifact.
172 * @param int $assigned_to The ID of the user to which this artifact is to be assigned.
173 * @param int $priority The artifacts priority.
174 * @param array $extra_fields Array of extra fields like: array(15=>'foobar',22=>'1');
175 * @param array $importData Array of data to change submitter and time of submit like:
176 * array('user' => 127, 'time' => 1234556789)
177 * @return bool id on success / false on failure.
179 function create( $summary, $details, $assigned_to=100, $priority=3, $extra_fields=array(), $importData = array()) {
181 // make sure this person has permission to add artifacts
187 if(array_key_exists('user', $importData)){
188 $user = $importData['user'];
190 if (!forge_check_perm ('tracker',$this->ArtifactType->getID(),'submit')) {
191 $this->setError(_('You are not currently allowed to submit items to this tracker.'));
195 if (session_loggedin()) {
206 $this->setError(_('Message Summary Is Required'));
210 $this->setError(_('Message Body Is Required'));
219 // if (!$status_id) {
220 $status_id=1; // on creation, status is set to "open"
223 // They may be using an extra field "status" box so we have to remap
224 // the status_id based on the extra field - this keeps the counters
227 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
229 $this->setError(_('Error remapping status'));
234 if (array_key_exists('time',$importData)){
235 $time = $importData['time'];
239 $res = db_query_params ('INSERT INTO artifact
240 (group_artifact_id,status_id,priority,
241 submitted_by,assigned_to,open_date,summary,details)
242 VALUES ($1,$2,$3,$4,$5,$6,$7,$8)',
243 array ($this->ArtifactType->getID(),
249 htmlspecialchars($summary),
250 htmlspecialchars($details))) ;
252 $this->setError(db_error());
257 $artifact_id=db_insertid($res,'artifact','artifact_id');
259 if (!$res || !$artifact_id) {
260 $this->setError(db_error());
265 // Now set up our internal data structures
267 if (!$this->fetchData($artifact_id)) {
271 // the changes to the extra fields will be logged in this array.
272 // (we won't use it however)
273 $extra_field_changes = array();
274 if (!$this->updateExtraFields($extra_fields,$extra_field_changes)) {
280 // now send an email if appropriate
282 $this->mailFollowupEx(0, 1);
290 * fetchData - re-fetch the data for this Artifact from the database.
292 * @param int $artifact_id The artifact ID.
293 * @return boolean success.
295 function fetchData($artifact_id) {
296 $res = db_query_params ('SELECT * FROM artifact_vw WHERE artifact_id=$1 AND group_artifact_id=$2',
298 $this->ArtifactType->getID())) ;
299 if (!$res || db_numrows($res) < 1) {
300 $this->setError(_('Invalid Artifact ID'));
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 * getStringID - get a string display for this ArtifactID.
329 * @return string The artifact_id #.
331 function getStringID() {
332 return '[#'.$this->data_array['artifact_id'].']';
336 * getStatusID - get open/closed/deleted flag.
338 * @return int Status: (1) Open, (2) Closed, (3) Deleted.
340 function getStatusID() {
341 return $this->data_array['status_id'];
345 * getStatusName - get open/closed/deleted text.
347 * @return string The status name.
349 function getStatusName() {
350 return $this->data_array['status_name'];
354 * getCustomStatusName - get custom status value text.
356 * @return string The custom status name.
358 function getCustomStatusName() {
359 $custom_status_id = $this->ArtifactType->getCustomStatusField();
360 if ($custom_status_id) {
361 $result = db_query_params ('SELECT element_name FROM artifact_extra_field_elements aefe, artifact_extra_field_data aefd
362 WHERE artifact_id=$1 AND aefd.extra_field_id=$2 AND CAST(aefd.field_data AS INTEGER)=aefe.element_id',
363 array ($this->getID(), $custom_status_id)) ;
365 return db_result($result, 0, 'element_name');
368 return $this->data_array['status_name'];
372 * getPriority - get priority flag.
374 * @return int priority.
376 function getPriority() {
377 return $this->data_array['priority'];
381 * getSubmittedBy - get ID of submitter.
383 * @return int user_id of submitter.
385 function getSubmittedBy() {
386 return $this->data_array['submitted_by'];
390 * getSubmittedEmail - get email of submitter.
392 * @return string The email of submitter.
394 function getSubmittedEmail() {
395 return $this->data_array['submitted_email'];
399 * getSubmittedRealName - get real name of submitter.
401 * @return string The real name of submitter.
403 function getSubmittedRealName() {
404 return $this->data_array['submitted_realname'];
408 * getSubmittedUnixName - get login name of submitter.
410 * @return string The unix name of submitter.
412 function getSubmittedUnixName() {
413 return $this->data_array['submitted_unixname'];
417 * getAssignedTo - get ID of assignee.
419 * @return int user_id of assignee.
421 function getAssignedTo() {
422 return $this->data_array['assigned_to'];
426 * getAssignedEmail - get email of assignee.
428 * @return string The email of assignee.
430 function getAssignedEmail() {
431 return $this->data_array['assigned_email'];
435 * getAssignedRealName - get real name of assignee.
437 * @return string The real name of assignee.
439 function getAssignedRealName() {
440 return $this->data_array['assigned_realname'];
444 * getAssignedUnixName - get login name of assignee.
446 * @return string The unix name of assignee.
448 function getAssignedUnixName() {
449 return $this->data_array['assigned_unixname'];
453 * getOpenDate - get unix time of creation.
455 * @return int unix time.
457 function getOpenDate() {
458 return $this->data_array['open_date'];
462 * getCloseDate - get unix time of closure.
464 * @return int unix time.
466 function getCloseDate() {
467 return $this->data_array['close_date'];
471 * getLastModifiedDate - the last_modified_date of this task.
473 * @return int the last_modified_date.
475 function getLastModifiedDate() {
476 return $this->data_array['last_modified_date'];
480 * getSummary - get text summary of artifact.
482 * @return string The summary (subject).
484 function getSummary() {
485 return $this->data_array['summary'];
489 * getDetails - get text body (message) of artifact.
491 * @return string The body (message).
493 function getDetails() {
494 return $this->data_array['details'];
498 * delete - delete this tracker and all its related data.
500 * @param bool $sure I'm Sure.
501 * @return bool true/false;
503 function delete($sure) {
505 $this->setMissingParamsError(_('Please tick all checkboxes.'));
508 if (!forge_check_perm ('tracker_admin', $this->ArtifactType->Group->getID())) {
509 $this->setPermissionDeniedError();
513 $res = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1',
514 array ($this->getID())) ;
516 $this->setError(_('Error deleting extra field data: ').db_error());
521 ArtifactStorage::instance()->deleteFromQuery('SELECT id FROM artifact_file WHERE artifact_id=$1',
522 array ($this->getID())) ;
524 $res = db_query_params ('DELETE FROM artifact_file WHERE artifact_id=$1',
525 array ($this->getID())) ;
527 $this->setError(_('Error deleting file from db: ').db_error());
529 ArtifactStorage::instance()->rollback();
532 $res = db_query_params ('DELETE FROM artifact_message WHERE artifact_id=$1',
533 array ($this->getID())) ;
535 $this->setError(_('Error deleting message: ').db_error());
537 ArtifactStorage::instance()->rollback();
540 $res = db_query_params ('DELETE FROM artifact_history WHERE artifact_id=$1',
541 array ($this->getID())) ;
543 $this->setError(_('Error deleting history: ').db_error());
545 ArtifactStorage::instance()->rollback();
548 $res = db_query_params ('DELETE FROM artifact_monitor WHERE artifact_id=$1',
549 array ($this->getID())) ;
551 $this->setError(_('Error deleting monitor: ').db_error());
553 ArtifactStorage::instance()->rollback();
556 $res = db_query_params ('DELETE FROM artifact WHERE artifact_id=$1',
557 array ($this->getID())) ;
559 $this->setError(_('Error deleting artifact: ').db_error());
561 ArtifactStorage::instance()->rollback();
565 if ($this->getStatusID() == 1) {
566 $res = db_query_params ('UPDATE artifact_counts_agg SET count=count-1,open_count=open_count-1
567 WHERE group_artifact_id=$1',
568 array ($this->getID())) ;
570 $this->setError(_('Error updating artifact counts: ').db_error());
572 ArtifactStorage::instance()->rollback();
575 } elseif ($this->getStatusID() == 2) {
576 $res = db_query_params ('UPDATE artifact_counts_agg SET count=count-1
577 WHERE group_artifact_id=$1',
578 array ($this->getID())) ;
580 $this->setError(_('Error updating artifact counts: ').db_error());
582 ArtifactStorage::instance()->rollback();
588 ArtifactStorage::instance()->commit();
593 * setMonitor - user can monitor this artifact.
595 * @return bool Always false - always use the getErrorMessage() for feedback
597 function setMonitor() {
598 if (session_loggedin()) {
600 $user_id=user_getid();
601 $user =& user_get_object(user_getid());
604 //we don't want to include the "And email=" because
605 //a logged-in user's email may have changed
610 $this->setError(_('Valid Email Address Required'));
615 $res = db_query_params ('SELECT * FROM artifact_monitor WHERE artifact_id=$1 AND user_id=$2',
616 array ($this->getID(),
619 if (!$res || db_numrows($res) < 1) {
621 $res = db_query_params ('INSERT INTO artifact_monitor (artifact_id,user_id) VALUES ($1,$2)',
622 array ($this->getID(),
625 $this->setError(db_error());
628 $this->setError(_('Now Monitoring Artifact'));
632 //already monitoring - remove their monitor
633 db_query_params ('DELETE FROM artifact_monitor
636 array ($this->getID(),
638 $this->setError(_('Artifact Monitoring Deactivated'));
643 function isMonitoring() {
644 if (!session_loggedin()) {
647 $result = db_query_params ('SELECT count(*) AS count FROM artifact_monitor WHERE user_id=$1 AND artifact_id=$2',
650 $row_count = db_fetch_array($result);
651 return $result && $row_count['count'] > 0;
655 * getMonitorIds - array of email addresses monitoring this Artifact.
657 * @return array of email addresses monitoring this Artifact.
659 function getMonitorIds() {
660 $res = db_query_params ('SELECT user_id FROM artifact_monitor WHERE artifact_id=$1',
661 array ($this->getID())) ;
662 return array_unique(array_merge($this->ArtifactType->getMonitorIds(),util_result_column_to_array($res)));
666 * getHistory - returns a result set of audit trail for this support request.
668 * @return resource result set.
670 function getHistory() {
671 return db_query_params ('SELECT * FROM artifact_history_user_vw WHERE artifact_id=$1 ORDER BY entrydate DESC, id ASC',
672 array ($this->getID())) ;
676 * getMessages - get the list of messages attached to this artifact.
678 * @param string $order
679 * @return resource result set.
681 function getMessages($ascending='up') {
683 * This is necessary because someone committed a change
684 * to this method in FusionForge trunk that accepts 'up'
685 * as default (luckily, it’s the same!) and 'down' as
686 * alternative probability, whereas FusionForge 5.2 has
687 * false as default and true for ascending order, so we
688 * need to check this out and use === to be sure ☹
690 if ($ascending === 'up') {
692 } elseif ($ascending === true) {
694 } elseif ($ascending === false) {
699 return db_query_params('SELECT * FROM artifact_message_user_vw WHERE artifact_id=$1 ORDER BY adddate ' . $order . ', id ASC',
700 array($this->getID()));
704 * getMessage - get a message attached to this artifact.
706 * @param int $msg_id id of the message.
708 * @return database result set.
710 function getMessage($msg_id) {
714 return db_query_params ('SELECT * FROM artifact_message_user_vw WHERE id=$1',
719 * getMessageObjects - get an array of message objects.
721 * @return array Of ArtifactMessage objects.
723 function &getMessageObjects() {
724 $res=$this->getMessages();
726 while ($arr = db_fetch_array($res)) {
727 //$return[]=new ArtifactMessage($arr['artifact_id'],$arr);
728 $return[] = new ArtifactMessage($this, $arr);
734 * getFiles - get array of ArtifactFile's.
736 * @return array of ArtifactFile's.
738 function &getFiles() {
739 if (!isset($this->files)) {
740 $res = db_query_params ('SELECT id,artifact_id,description,filename,filesize,' .
741 'filetype,adddate,submitted_by,user_name,realname
742 FROM artifact_file_user_vw WHERE artifact_id=$1',
743 array ($this->getID())) ;
744 $rows=db_numrows($res);
746 for ($i=0; $i < $rows; $i++) {
747 $this->files[$i]=new ArtifactFile($this,db_fetch_array($res));
750 $this->files=array();
757 * getRelatedTasks - get array of related tasks
759 * @return resource Database result set
761 function getRelatedTasks() {
762 if (!$this->relatedtasks) {
763 $this->relatedtasks = db_query_params ('SELECT pt.group_project_id,pt.project_task_id,pt.summary,pt.start_date,pt.end_date,pgl.group_id,pt.status_id,pt.percent_complete,ps.status_name
764 FROM project_task pt, project_group_list pgl, project_status ps
765 WHERE pt.group_project_id = pgl.group_project_id
766 AND ps.status_id = pt.status_id
767 AND EXISTS (SELECT project_task_id FROM project_task_artifact
768 WHERE project_task_id=pt.project_task_id
769 AND artifact_id = $1)',
770 array ($this->getID())) ;
772 return $this->relatedtasks;
776 * addMessage - attach a text message to this Artifact.
778 * @param string $body The $string message being attached.
779 * @param bool $by Email $string address of message creator.
780 * @param bool $send_followup Whether $bool to email out a followup.
781 * @return bool success.
783 function addMessage($body,$by=false,$send_followup=false) {
785 $this->setMissingParamsError();
788 if (!forge_check_perm ('tracker',$this->ArtifactType->getID(),'submit')) {
789 $this->setError(_('You are not currently allowed to submit items to this tracker.'));
792 if (session_loggedin()) {
793 $user_id=user_getid();
794 $user =& user_get_object($user_id);
795 if (!$user || !is_object($user)) {
796 $this->setError('Error: Logged In User But Could Not Get User Object');
799 // we'll store this email even though it will likely never be used -
800 // since we have their correct user_id, we can join the USERS table to get email
801 $by=$user->getEmail();
804 if (!$by || !validate_email($by)) {
805 $this->setMissingParamsError();
811 $res = db_query_params ('INSERT INTO artifact_message (artifact_id,submitted_by,from_email,adddate,body) VALUES ($1,$2,$3,$4,$5)',
812 array ($this->getID(),
816 htmlspecialchars($body))) ;
818 $this->updateLastModifiedDate();
820 if ($send_followup) {
821 $this->mailFollowupEx($now, 2, false);
827 * addHistory - add an entry to audit trail.
829 * @param string $field_name The name of the field in the database being modified.
830 * @param string $old_value The former value of this field.
831 * @param array $importData Array of data to change submitter and time of submit like:
832 * array('user' => 127, 'time' => 1234556789)
834 * @return boolean success.
836 function addHistory($field_name,$old_value, $importData = array()) {
837 if (array_key_exists('user', $importData)){
838 $user = $importData['user'];
840 if (!session_loggedin()) {
846 if (array_key_exists('time',$importData)){
847 $time = $importData['time'];
851 return db_query_params ('INSERT INTO artifact_history(artifact_id,field_name,old_value,mod_by,entrydate) VALUES ($1,$2,$3,$4,$5)',
852 array ($this->getID(),
860 * setStatus - set the status of this artifact.
862 * @param int The artifact status ID.
863 * @param int Closing date if status = 1
865 * @return boolean success.
867 function setStatus($status_id, $closingTime=False) {
869 $qpa = db_construct_qpa (false, 'UPDATE artifact SET status_id=$1', array ($status_id)) ;
870 if ($closingTime && $status_id != 1) {
872 $qpa = db_construct_qpa ($qpa, ', close_date=$1 ', array ($time)) ;
874 $qpa = db_construct_qpa ($qpa,
875 'WHERE artifact_id=$1 AND group_artifact_id=$2',
876 array ($this->getID(), $artifact_type_id)) ;
877 $result=db_query_qpa($qpa);
879 if (!$result || db_affected_rows($result) < 1) {
880 $this->setError('Error - update failed!'.db_error());
884 if (!$this->fetchData($this->getID())) {
897 * update - update the fields in this artifact.
899 * @param int $priority The artifact priority.
900 * @param int $status_id The artifact status ID.
901 * @param int $assigned_to The person to which this artifact is to be assigned.
902 * @param string $summary The artifact summary.
903 * @param int $canned_response The canned response.
904 * @param string $details Attaching another comment.
905 * @param int $new_artifact_type_id Allows you to move an artifact to another type.
906 * @param array $extra_fields Array of extra fields like: array(15=>'foobar',22=>'1');
907 * @param string $description The description.
908 * @return boolean success.
910 function update($priority,$status_id,
911 $assigned_to,$summary,$canned_response,$details,$new_artifact_type_id,
912 $extra_fields=array(), $description='') {
915 Field-level permission checking
917 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'manager')) {
918 // Non-managers cannot modify these fields
919 $priority=$this->getPriority();
920 $summary=htmlspecialchars_decode($this->getSummary());
921 $description=htmlspecialchars_decode($this->getDetails());
922 $canned_response=100;
923 $new_artifact_type_id=$this->ArtifactType->getID();
924 $assigned_to=$this->getAssignedTo();
926 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'tech')) {
927 $this->setPermissionDeniedError();
932 // They may be using an extra field "status" box so we have to remap
933 // the status_id based on the extra field - this keeps the counters
936 if (count($extra_fields) > 0) {
937 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
939 if (!$this->getID()) {
940 $this->setMissingParamsError('ID');
944 $this->setMissingParamsError(_('Assigned to'));
948 $this->setMissingParamsError(_('State'));
951 if (!$canned_response) {
952 $this->setMissingParamsError(_('Canned Response'));
955 if (!$new_artifact_type_id) {
956 $this->setMissingParamsError(_('Data Type'));
960 // Check that assigned_to is a tech for the tracker
961 if ($assigned_to != 100) {
962 if (!forge_check_perm_for_user ($assigned_to, 'tracker', $this->ArtifactType->getID(), 'tech')) {
963 $this->setError(_("Invalid assigned_to (assigned person is not a technician)"));
968 // Array to record which properties were changed
975 // Get a lock on this row in the database
977 $lock = db_query_params ('SELECT * FROM artifact WHERE artifact_id=$1 FOR UPDATE',
978 array ($this->getID())) ;
979 $artifact_type_id = $this->ArtifactType->getID();
981 // Attempt to move this Artifact to a new ArtifactType
982 // need to instantiate new ArtifactType obj and test perms
984 if ($new_artifact_type_id != $artifact_type_id) {
985 $newArtifactType= new ArtifactType($this->ArtifactType->getGroup(), $new_artifact_type_id);
986 if (!is_object($newArtifactType) || $newArtifactType->isError()) {
987 $this->setError(_('Could not move to new Artifact Type'). $newArtifactType->getErrorMessage());
991 // do they have perms for new ArtifactType?
992 if (!forge_check_perm ('tracker', $newArtifactType->getID(), 'manager')) {
993 $this->setPermissionDeniedError();
998 // Add a message to explain that the tracker was moved.
999 $message = sprintf(_('Moved from %1$s to %2$s'),
1000 $this->ArtifactType->getName(),
1001 $newArtifactType->getName());
1002 $this->addHistory('type', $this->ArtifactType->getName());
1003 $this->addMessage($message,'',0);
1005 // Fake change to send a mail when moved.
1006 $changes['Type'] = 1;
1008 // Try to remap extra_fields values when possible.
1009 // If there is an extra_field with the same alias
1010 // and if the value exist in the new one, then recode
1011 // the value to keep it.
1012 $new_extra_fields = array();
1013 $ef = $this->ArtifactType->getExtraFields();
1014 $ef_new = $newArtifactType->getExtraFields();
1015 foreach($extra_fields as $extra_id => $value) {
1016 $alias = preg_replace('/^@/', '', $ef[$extra_id]['alias']);
1017 $type = $ef[$extra_id]['field_type'];
1019 // Search if there is an extra field with the same alias.
1021 foreach($ef_new as $id => $arr) {
1022 if (preg_replace('/^@/', '', $arr['alias']) == $alias) {
1027 // If we found one, copy for simple fields or
1028 // search if there is the same value.
1030 if ($type == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
1031 $type == ARTIFACT_EXTRAFIELDTYPE_INTEGER ||
1032 $type == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA ||
1033 $type == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
1034 $new_extra_fields[$new_id] = $value;
1036 $values = $newArtifactType->getExtraFieldElements($new_id);
1037 if (is_array($value)) {
1038 foreach($value as $v) {
1039 $v = $this->ArtifactType->getElementName($v);
1040 foreach($values as $ev) {
1041 if ($ev['element_name'] == $v) {
1042 $new_extra_fields[$new_id][] = $ev['element_id'];
1047 $value = $this->ArtifactType->getElementName($value);
1048 foreach($values as $ev) {
1049 if ($ev['element_name'] == $value) {
1050 $new_extra_fields[$new_id] = $ev['element_id'];
1058 // Special case if moving to a tracker with custom status (previous has not).
1059 $custom_status_id = $newArtifactType->getCustomStatusField();
1060 if ($custom_status_id && !$new_extra_fields[$custom_status_id]) {
1061 $atw = new ArtifactWorkflow($newArtifactType, $custom_status_id);
1062 $nodes = $atw->getNextNodes(100);
1064 $new_extra_fields[$custom_status_id] = $nodes[0];
1068 $extra_fields = $new_extra_fields;
1070 $res = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1',
1071 array ($this->getID()));
1073 $this->setError(_('Removal of old artifact_extra_field_data failed: ').db_error());
1078 // Check that assigned_to is a tech in the new tracker
1079 if ($assigned_to != 100) {
1080 if (!forge_check_perm ('tracker', $newArtifactType->getID(), 'tech')) {
1085 //can't send a canned response when changing ArtifactType
1086 $canned_response=100;
1087 $this->ArtifactType =& $newArtifactType;
1091 $qpa = db_construct_qpa();
1092 $qpa = db_construct_qpa($qpa, 'UPDATE artifact SET');
1095 // handle audit trail
1098 if ($this->getStatusID() != $status_id) {
1099 $this->addHistory('status_id',$this->getStatusID());
1100 $qpa = db_construct_qpa($qpa, ' status_id=$1,', array($status_id));
1101 $changes['status'] = 1;
1104 if ($status_id != 1) {
1105 $qpa = db_construct_qpa($qpa, ' close_date=$1,', array($now));
1107 $qpa = db_construct_qpa($qpa, ' close_date=$1,', array(0));
1109 $this->addHistory('close_date', $this->getCloseDate());
1111 if ($this->getPriority() != $priority) {
1112 $this->addHistory('priority',$this->getPriority());
1113 $qpa = db_construct_qpa($qpa, ' priority=$1,', array($priority));
1114 $changes['priority'] = 1;
1118 if ($this->getAssignedTo() != $assigned_to) {
1119 $this->addHistory('assigned_to',$this->getAssignedTo());
1120 $qpa = db_construct_qpa($qpa, ' assigned_to=$1,', array($assigned_to));
1121 $changes['assigned_to'] = 1;
1124 if ($summary && ($this->getSummary() != htmlspecialchars($summary))) {
1125 $this->addHistory('summary', $this->getSummary());
1126 $qpa = db_construct_qpa($qpa, ' summary=$1,', array(htmlspecialchars($summary)));
1127 $changes['summary'] = 1;
1130 if ($description && ($this->getDetails() != htmlspecialchars($description))) {
1131 $this->addHistory('details', $this->getDetails());
1132 $qpa = db_construct_qpa($qpa, ' details=$1,', array(htmlspecialchars($description)));
1133 $changes['details'] = 1;
1137 $this->addMessage($details,'',0);
1138 $changes['details'] = 1;
1143 Finally, update the artifact itself
1146 $qpa = db_construct_qpa($qpa, ' group_artifact_id=$1
1147 WHERE artifact_id=$2 AND group_artifact_id=$3',
1148 array($new_artifact_type_id,
1149 $this->getID(), $artifact_type_id));
1150 $result = db_query_qpa($qpa);
1152 if (!$result || db_affected_rows($result) < 1) {
1153 $this->setError(_('Update failed').db_error());
1157 if (!$this->fetchData($this->getID())) {
1164 //extra field handling
1166 if (!$this->updateExtraFields($extra_fields,$changes)) {
1167 //TODO - see if anything actually did change
1173 handle canned responses
1175 Instantiate ArtifactCanned and get the body of the message
1177 if ($canned_response != 100) {
1178 //don't care if this response is for this group - could be hacked
1179 $acr=new ArtifactCanned($this->ArtifactType,$canned_response);
1180 if (!$acr || !is_object($acr)) {
1181 $this->setError(_('Could Not Create Canned Response Object'));
1182 } elseif ($acr->isError()) {
1183 $this->setError($acr->getErrorMessage());
1185 $body = $acr->getBody();
1187 if (!$this->addMessage(util_unconvert_htmlspecialchars($body),'',0)) {
1194 $this->setError(_('Unable to Use Canned Response'));
1200 if ($update || $send_message){
1201 if (!empty($changes)) {
1202 // Send the email with changes
1203 $this->mailFollowupEx($now, 2, false, $changes);
1208 //nothing changed, so cancel the transaction
1209 $this->setError(_('Nothing Changed - Update Cancelled'));
1217 * updateLastModifiedDate - update the last_modified_date attribute of this artifact.
1219 * @return bool true on success / false on failure
1221 function updateLastModifiedDate() {
1222 $res = db_query_params ('UPDATE artifact SET last_modified_date=EXTRACT(EPOCH FROM now())::integer WHERE artifact_id=$1',
1223 array ($this->getID()));
1228 * assignToMe - assigns this artifact to current user
1230 * @return bool true on success / false on failure
1232 function assignToMe() {
1233 if (!session_loggedin() || !($this->ArtifactType->userIsAdmin() || $this->ArtifactType->userIsTechnician())) {
1234 $this->setPermissionDeniedError();
1238 $user_id = user_getid();
1239 $res = db_query_params ('UPDATE artifact SET assigned_to=$1 WHERE artifact_id=$2',
1240 array ($user_id, $this->getID())) ;
1242 $this->setError(_('Error updating assigned_to in artifact: ').db_error());
1245 $this->fetchData($this->getID());
1251 * updateExtraFields - updates the extra data elements for this artifact
1252 * e.g. the extra fields created and defined by the admin.
1254 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
1255 * @param array Array where changes to the extra fields should be logged
1256 * @return bool true on success / false on failure
1258 function updateExtraFields($extra_fields,&$changes){
1260 This is extremely complex code - we have take the passed array
1261 and see if we need to insert it into the db, and may have to
1262 add history rows for the audit trail
1264 start by getting all the available extra fields from ArtifactType
1265 For each field from ArtifacType, check the passed array -
1266 This prevents someone from passing bogus extra field entries - they will be ignored
1267 if the passed entry is blank, may have to force a default value
1268 if the passed array is different from the existing data in db,
1269 delete old entry and insert new entries, along with possible audit trail
1271 skip it and continue to next item
1276 //get a list of extra fields for this artifact_type
1277 $ef = $this->ArtifactType->getExtraFields();
1278 $efk=array_keys($ef);
1280 if (empty($extra_fields) && empty($ef)) {
1284 // If there is a status field, then check against the workflow.
1285 // Unless if we change type.
1286 if (! isset($changes['Type']) || !$changes['Type']) {
1287 for ($i=0; $i<count($efk); $i++) {
1289 $type=$ef[$efid]['field_type'];
1290 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1291 // Get previous value.
1292 $res = db_query_params ('SELECT field_data FROM artifact_extra_field_data
1293 WHERE artifact_id=$1 AND extra_field_id=$2',
1294 array($this->getID(),
1296 $old = (db_numrows($res)>0) ? db_result($res,0,'field_data') : 100;
1297 if ($old != $extra_fields[$efid]) {
1298 $atw = new ArtifactWorkflow($this->ArtifactType, $efid);
1299 if (!$atw->checkEvent($old, $extra_fields[$efid])) {
1300 $this->setError('Workflow error: You are not authorized to change the Status ('.$old.' => '.$extra_fields[$efid].')');
1308 //now we'll update this artifact for each extra field
1309 for ($i=0; $i<count($efk); $i++) {
1311 $type=$ef[$efid]['field_type'];
1313 // check required fields
1314 if ($ef[$efid]['is_required']) {
1315 if (!array_key_exists($efid, $extra_fields)) {
1316 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1317 $this->setError(_('Status Custom Field Must Be Set'));
1320 $this->setMissingParamsError($ef[$efid]['field_name']);
1325 if ($extra_fields[$efid] === '') {
1326 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1327 $this->setError(_('Status Custom Field Must Be Set'));
1330 $this->setMissingParamsError($ef[$efid]['field_name']);
1335 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT || $type == ARTIFACT_EXTRAFIELDTYPE_RADIO) &&
1336 $extra_fields[$efid] == '100') {
1337 $this->setMissingParamsError($ef[$efid]['field_name']);
1340 elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) &&
1341 (count($extra_fields[$efid]) == 1 && $extra_fields[$efid][0] == '100')) {
1342 $this->setMissingParamsError($ef[$efid]['field_name']);
1349 // Force each field to have some value if it is a numeric field
1350 // text fields will just be purged and skipped
1352 if (!array_key_exists($efid, $extra_fields) || $extra_fields[$efid] === '') {
1353 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO)) {
1354 $extra_fields[$efid]='100';
1355 } elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX)) {
1356 $extra_fields[$efid]=array('100');
1358 $resdel = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1359 array ($this->getID(),
1366 // get the old rows of data
1368 $resd = db_query_params ('SELECT * FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1369 array ($this->getID(),
1371 $rows=db_numrows($resd);
1372 if ($resd && $rows) {
1374 //POTENTIAL PROBLEM - no entry was there before, but adding one now - may need history
1377 // Compare for history purposes
1380 // these types have arrays associated to them, so they need
1381 // special handling to check for differences
1382 if ($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) {
1383 // check the differences between the old values and the new values
1384 $old_values = util_result_column_to_array($resd,"field_data");
1386 $added_values = array_diff($extra_fields[$efid], $old_values);
1387 $deleted_values = array_diff($old_values, $extra_fields[$efid]);
1389 if (!empty($added_values) || !empty($deleted_values)) { // there are differences...
1390 $field_name = $ef[$efid]['field_name'];
1391 if (!preg_match('/^@/', $ef[$efid]['alias'])) {
1392 $changes["extra_fields"][$efid] = 1;
1395 $this->addHistory($field_name, $this->ArtifactType->getElementName(array_reverse($old_values)));
1398 $resdel = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1399 array ($this->getID(),
1404 } elseif (db_result($resd,0,'field_data') == htmlspecialchars($extra_fields[$efid])) {
1405 //element did not change
1408 //element DID change - do a history entry
1409 $field_name = $ef[$efid]['field_name'];
1410 if (!preg_match('/^@/', $ef[$efid]['alias'])) {
1411 $changes["extra_fields"][$efid] = 1;
1413 $resdel = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1414 array ($this->getID(),
1417 // Adding history with previous value.
1418 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO) || ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS)) {
1419 $this->addHistory($field_name,$this->ArtifactType->getElementName(db_result($resd,0,'field_data')));
1421 $this->addHistory($field_name, db_result($resd,0,'field_data'));
1427 //no history for this extra field exists
1432 // Some rewrite & consistency checks on the relation type field.
1434 // 1) Convert syntax [#NNN] to NNN
1435 // 2) Allow multiple spaces as separator.
1436 // 3) Ensure that only integers are given.
1437 // 4) Ensure that id corresponds to valid tracker id.
1439 if ($type == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
1440 $value = preg_replace('/\[\#(\d+)\]/', "\\1", trim($extra_fields[$efid]));
1441 $value = preg_replace('/\\s+/', ' ', $value);
1443 foreach (explode(' ',$value) as $id) {
1444 if (preg_match('/^(\d+)$/', $id)) {
1445 // Control that the id is present in the db
1447 $res = db_query_params ('SELECT artifact_id FROM artifact WHERE artifact_id=$1',
1449 if (db_numrows($res) == 1) {
1452 $this->setError('Illegal id '.$id.', it\'s not a valid tracker id for field: '.$ef[$efid]['field_name'].'.'); // @todo: lang
1456 $this->setError('Illegal value '.$id.', only trackers id are allowed for field: '.$ef[$efid]['field_name'].'.'); // @todo: lang
1460 $extra_fields[$efid] = trim($new);
1463 // Ensure that only integer are allowed for type ARTIFACT_EXTRAFIELDTYPE_INTEGER
1464 if ($type == ARTIFACT_EXTRAFIELDTYPE_INTEGER) {
1465 $extra_fields[$efid] = trim($extra_fields[$efid]);
1466 if (!preg_match('/^[-+]?(\d+)$/', $extra_fields[$efid])) {
1467 $this->setError('Illegal value '.$extra_fields[$efid].' for field '.$ef[$efid]['field_name'].': Only integer is allowed.');
1470 if ($extra_fields[$efid] < -2147483648 || $extra_fields[$efid] > 2147483647) {
1471 $this->setError('Illegal value '.$extra_fields[$efid].' for field '.$ef[$efid]['field_name'].': Integer out of range (-2147483648 to +2147483647).');
1474 $extra_fields[$efid] = intval($extra_fields[$efid]);
1478 // See if anything was even passed for this extra_field_id
1480 if ($extra_fields[$efid] === '') {
1481 //nothing in field to update - text fields may be blank
1483 //determine the type of field and whether it should have multiple rows supporting it
1484 $type=$ef[$efid]['field_type'];
1485 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1487 $count=count($extra_fields[$efid]);
1488 for ($fin=0; $fin<$count; $fin++) {
1489 $res = db_query_params ('INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) VALUES ($1,$2,$3)',
1490 array ($this->getID(),
1492 $extra_fields[$efid][$fin])) ;
1494 $this->setError(db_error());
1501 $res = db_query_params ('INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) VALUES ($1,$2,$3)',
1502 array ($this->getID(),
1504 htmlspecialchars($extra_fields[$efid]))) ;
1506 $this->setError(db_error());
1513 unset($this->extra_field_data);
1516 $this->updateLastModifiedDate();
1522 * getExtraFieldData - get an array of data for the extra fields associated with this artifact
1524 * @return array array of data
1526 function &getExtraFieldData() {
1527 if (!isset($this->extra_field_data)) {
1528 $this->extra_field_data = array();
1529 $res = db_query_params ('SELECT * FROM artifact_extra_field_data WHERE artifact_id=$1 ORDER BY extra_field_id',
1530 array ($this->getID())) ;
1531 $ef = $this->ArtifactType->getExtraFields();
1532 while ($arr = db_fetch_array($res)) {
1533 $type=$ef[$arr['extra_field_id']]['field_type'];
1534 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1535 //accumulate a sub-array of values in cases where you may have multiple rows
1536 if (!array_key_exists($arr['extra_field_id'], $this->extra_field_data) || !is_array($this->extra_field_data[$arr['extra_field_id']])) {
1537 $this->extra_field_data[$arr['extra_field_id']] = array();
1539 $this->extra_field_data[$arr['extra_field_id']][]=$arr['field_data'];
1541 $this->extra_field_data[$arr['extra_field_id']]=$arr['field_data'];
1545 return $this->extra_field_data;
1549 * marker - adds the > symbol to fields that have been modified for the email message
1553 function marker($prop_name,$changes,$extra_field_id=0) {
1554 if ($prop_name == 'extra_fields' && isset($changes[$prop_name][$extra_field_id])) {
1556 } elseif ($prop_name != 'extra_fields' && isset($changes[$prop_name])) {
1564 * mailFollowupEx - send out an email update for this artifact.
1566 * @param time_t Time of the change
1567 * @param int (1) initial/creation (2) update.
1568 * @param array Array of additional addresses to mail to.
1569 * @param array Array of fields changed in this update .
1571 * @return boolean success.
1573 function mailFollowupEx($tm,$type,$more_addresses=false,$changes='') {
1575 $monitor_ids = array();
1581 $sess = session_get_user() ;
1582 if ($type == 1) { // Initial opening
1584 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() ." item #". $this->getID() .", was opened at ". date( _('Y-m-d H:i'), $this->getOpenDate() ) . " by " . $sess->getRealName () ;
1586 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() ." item #". $this->getID() .", was opened at ". date( _('Y-m-d H:i'), $this->getOpenDate() ) ;
1590 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() .
1591 " item #" . $this->getID() .
1592 " was changed at " .
1593 date(_('Y-m-d H:i'), $tm) . " by " .
1594 $sess->getRealName();
1596 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() .
1597 " item #" . $this->getID() .
1598 " was changed at " .
1599 date(_('Y-m-d H:i'), $tm);
1604 $body .= "\nYou can respond by visiting: ".
1605 "\n".util_make_url ('/tracker/?func=detail&atid='. $this->ArtifactType->getID() .
1606 "&aid=". $this->getID() .
1607 "&group_id=". $this->ArtifactType->Group->getID()) .
1608 "\nOr by replying to this e-mail entering your response between the following markers: ".
1609 "\n".ARTIFACT_MAIL_MARKER.
1610 "\n(enter your response here, only in plain text format)".
1611 "\n".ARTIFACT_MAIL_MARKER.
1613 $this->marker('status',$changes).
1614 "Status: ". $this->getStatusName() ."\n".
1615 $this->marker('priority',$changes).
1616 "Priority: ". $this->getPriority() ."\n".
1617 "Submitted By: ". $this->getSubmittedRealName() .
1618 " (". $this->getSubmittedUnixName(). ")"."\n".
1619 $this->marker('assigned_to',$changes).
1620 "Assigned to: ". $this->getAssignedRealName() .
1621 " (". $this->getAssignedUnixName(). ")"."\n".
1622 $this->marker('summary',$changes).
1623 "Summary: ". util_unconvert_htmlspecialchars( $this->getSummary() )." \n";
1625 // Now display the extra fields
1626 $efd = $this->getExtraFieldDataText();
1627 foreach ($efd as $efid => $ef) {
1628 $body .= $this->marker('extra_fields', $changes, $efid);
1629 $body .= $ef["name"].": ".$ef["value"]."\n";
1632 $subject='['. $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() . '][' . $this->getID() .'] '. util_unconvert_htmlspecialchars( $this->getSummary() );
1635 // get all the email addresses that are monitoring this request or the ArtifactType
1636 $monitor_ids = $this->getMonitorIds();
1638 // initial creation, we just get the users monitoring the ArtifactType
1639 $monitor_ids = $this->ArtifactType->getMonitorIds();
1643 if ($more_addresses) {
1644 $emails[] = $more_addresses;
1646 //we don't email the current user
1647 if ($this->getAssignedTo() != user_getid()) {
1648 $monitor_ids[] = $this->getAssignedTo();
1650 if ($this->getSubmittedBy() != user_getid()) {
1651 $monitor_ids[] = $this->getSubmittedBy();
1653 //initial submission
1655 //if an email is set for this ArtifactType
1656 //add that address to the BCC: list
1657 if ($this->ArtifactType->getEmailAddress()) {
1658 $emails[] = $this->ArtifactType->getEmailAddress();
1662 if ($this->ArtifactType->emailAll()) {
1663 $emails[] = $this->ArtifactType->getEmailAddress();
1667 $body .= "\n\nInitial Comment:".
1668 "\n".util_unconvert_htmlspecialchars( $this->getDetails() ) .
1669 "\n\n----------------------------------------------------------------------";
1673 Now include the followups
1675 $result2=$this->getMessages();
1677 $rows=db_numrows($result2);
1679 if ($result2 && $rows > 0) {
1680 for ($i=0; $i<$rows; $i++) {
1682 // for messages posted by non-logged-in users,
1683 // we grab the email they gave us
1685 // otherwise we use the confirmed one from the users table
1687 if (db_result($result2,$i,'user_id') == 100) {
1688 $emails[] = db_result($result2,$i,'from_email');
1690 $monitor_ids[] = db_result($result2,$i,'user_id');
1696 $body .= $this->marker('details',$changes);
1698 $body .= "Comment By: ". db_result($result2,$i,'realname') . " (".db_result($result2,$i,'user_name').")".
1699 "\nDate: ". date( _('Y-m-d H:i'),db_result($result2,$i,'adddate') ).
1701 "\n".util_unconvert_htmlspecialchars( db_result($result2,$i,'body') ).
1702 "\n\n----------------------------------------------------------------------";
1708 $body .= "\n\nYou can respond by visiting: ".
1709 "\n".util_make_url ('/tracker/?func=detail&atid='. $this->ArtifactType->getID() .
1710 "&aid=". $this->getID() .
1711 "&group_id=". $this->ArtifactType->Group->getID());
1713 //only send if some recipients were found
1714 if (count($emails) < 1 && count($monitor_ids) < 1) {
1718 if (count($monitor_ids) < 1) {
1719 $monitor_ids=array();
1721 $monitor_ids=array_unique($monitor_ids);
1724 $from = $this->ArtifactType->getReturnEmailAddress();
1725 $extra_headers = 'Reply-to: '.$from;
1727 // load the e-mail addresses of the users
1728 $users =& user_get_objects($monitor_ids);
1729 if (count($users) > 0) {
1730 foreach ($users as $user) {
1731 if ($user->getStatus() == "A") { //we are only sending emails to active users
1732 $emails[] = $user->getEmail();
1737 //now remove all duplicates from the email list
1738 if (count($emails) > 0) {
1739 $BCC=implode(',',array_unique($emails));
1740 util_send_message('',$subject,$body,$from,$BCC,'',$extra_headers);
1743 $this->sendSubjectMsg = $subject;
1744 $this->sendBodyMsg = $body;
1746 //util_handle_message($monitor_ids,$subject,$body,$BCC);
1752 * getExtraFieldDataText - Return the extra fields' data in a human-readable form.
1754 * @return array Array containing field ID => field name and value associated to it for
1757 function getExtraFieldDataText() {
1758 // First we get the list of extra fields and the data
1759 // associated to the fields
1760 $efs = $this->ArtifactType->getExtraFields();
1761 $efd = $this->getExtraFieldData();
1765 foreach ($efs as $efid => $ef) {
1766 $name = $ef["field_name"];
1767 $type = $ef["field_type"];
1769 // Get the value according to the type
1772 // for these types, the associated value comes straight
1773 case ARTIFACT_EXTRAFIELDTYPE_TEXT:
1774 case ARTIFACT_EXTRAFIELDTYPE_TEXTAREA:
1775 case ARTIFACT_EXTRAFIELDTYPE_RELATION:
1776 case ARTIFACT_EXTRAFIELDTYPE_INTEGER:
1777 if (isset($efd[$efid])) {
1778 $value = $efd[$efid];
1784 // the other types have and ID or an array of IDs associated to them
1786 if (isset($efd[$efid])) {
1787 $value = $this->ArtifactType->getElementName($efd[$efid]);
1793 $return[$efid] = array("name" => $name, "value" => $value, 'type' => $type);
1800 class ArtifactComparator {
1801 var $criterion = 'artifact_id' ;
1802 var $order = 'ASC' ;
1804 function Compare ($a, $b) {
1805 if ($this->order == 'DESC') {
1806 $c = $a ; $a = $b ; $b = $c ;
1808 switch ($this->criterion) {
1810 $namecmp = strcoll ($a->getSummary(),
1812 if ($namecmp != 0) {
1817 $namecmp = strcoll (user_get_object($a->getAssignedTo())->getRealName(),
1818 user_get_object($b->getAssignedTo())->getRealName()) ;
1819 if ($namecmp != 0) {
1823 case 'submitted_by':
1824 $namecmp = strcoll (user_get_object($a->getSubmittedBy())->getRealName(),
1825 user_get_object($b->getSubmittedBy())->getRealName()) ;
1826 if ($namecmp != 0) {
1831 $a_date = $a->getOpenDate() ;
1832 $b_date = $b->getOpenDate() ;
1833 return ($a_date < $b_date) ? -1 : 1;
1836 $a_date = $a->getCloseDate() ;
1837 $b_date = $b->getCloseDate() ;
1838 return ($a_date < $b_date) ? -1 : 1;
1840 case 'last_modified_date':
1841 $a_date = $a->getLastModifiedDate() ;
1842 $b_date = $b->getLastModifiedDate() ;
1843 return ($a_date < $b_date) ? -1 : 1;
1846 $a_priority = $a->getPriority() ;
1847 $b_priority = $b->getPriority() ;
1848 return ($a_priority < $b_priority) ? -1 : 1;
1851 $aa=$a->getExtraFieldDataText();
1852 $ba=$b->getExtraFieldDataText();
1853 if(!isset($this->criterion) || empty($this->criterion)) {
1857 $criterion = $this->criterion;
1859 $af=$aa[$criterion]['value'];
1860 $bf=$ba[$criterion]['value'];
1861 $namecmp = strcoll ($af,$bf) ;
1862 if ($namecmp != 0) {
1868 // When in doubt, sort on artifact ID
1869 $aid = $a->getID() ;
1870 $bid = $b->getID() ;
1874 return ($aid < $bid) ? -1 : 1;
1878 function sortArtifactList (&$list, $criterion='name', $order='ASC') {
1879 $cmp = new ArtifactComparator () ;
1880 $cmp->criterion = $criterion ;
1881 $cmp->order = $order ;
1883 return usort ($list, array ($cmp, 'Compare')) ;
1888 // c-file-style: "bsd"