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 The artifact ID
61 * @param array The result array, if it's passed in
62 * @return object 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(_('No Valid 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 The artifact summary.
171 * @param string Details of the artifact.
172 * @param int The ID of the user to which this artifact is to be assigned.
173 * @param int The artifacts priority.
174 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
175 * @param array Array of data to change submitter and time of submit like: array('user' => 127, 'time' => 1234556789)
176 * @return id on success / false on failure.
178 function create( $summary, $details, $assigned_to=100, $priority=3, $extra_fields=array(), $importData = array()) {
180 // make sure this person has permission to add artifacts
186 if(array_key_exists('user', $importData)){
187 $user = $importData['user'];
189 if (!forge_check_perm ('tracker',$this->ArtifactType->getID(),'submit')) {
190 $this->setError(_('You are not currently allowed to submit items to this tracker.'));
194 if (session_loggedin()) {
205 $this->setError(_('Message Summary Is Required'));
209 $this->setError(_('Message Body Is Required'));
218 // if (!$status_id) {
219 $status_id=1; // on creation, status is set to "open"
222 // They may be using an extra field "status" box so we have to remap
223 // the status_id based on the extra field - this keeps the counters
226 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
228 $this->setError(_('Error remapping status'));
233 if (array_key_exists('time',$importData)){
234 $time = $importData['time'];
238 $res = db_query_params ('INSERT INTO artifact
239 (group_artifact_id,status_id,priority,
240 submitted_by,assigned_to,open_date,summary,details)
241 VALUES ($1,$2,$3,$4,$5,$6,$7,$8)',
242 array ($this->ArtifactType->getID(),
248 htmlspecialchars($summary),
249 htmlspecialchars($details))) ;
251 $this->setError(db_error());
256 $artifact_id=db_insertid($res,'artifact','artifact_id');
258 if (!$res || !$artifact_id) {
259 $this->setError(db_error());
264 // Now set up our internal data structures
266 if (!$this->fetchData($artifact_id)) {
270 // the changes to the extra fields will be logged in this array.
271 // (we won't use it however)
272 $extra_field_changes = array();
273 if (!$this->updateExtraFields($extra_fields,$extra_field_changes)) {
279 // now send an email if appropriate
281 $this->mailFollowupEx(0, 1);
289 * fetchData - re-fetch the data for this Artifact from the database.
291 * @param int The artifact ID.
292 * @return boolean success.
294 function fetchData($artifact_id) {
295 $res = db_query_params ('SELECT * FROM artifact_vw WHERE artifact_id=$1 AND group_artifact_id=$2',
297 $this->ArtifactType->getID())) ;
298 if (!$res || db_numrows($res) < 1) {
299 $this->setError(_('Invalid Artifact ID'));
302 $this->data_array = db_fetch_array($res);
303 db_free_result($res);
308 * getArtifactType - get the ArtifactType Object this Artifact is associated with.
310 * @return object ArtifactType.
312 function &getArtifactType() {
313 return $this->ArtifactType;
317 * getID - get this ArtifactID.
319 * @return int The artifact_id #.
322 return $this->data_array['artifact_id'];
326 * getStringID - get a string display for this ArtifactID.
328 * @return string The artifact_id #.
330 function getStringID() {
331 return '[#'.$this->data_array['artifact_id'].']';
335 * getStatusID - get open/closed/deleted flag.
337 * @return int Status: (1) Open, (2) Closed, (3) Deleted.
339 function getStatusID() {
340 return $this->data_array['status_id'];
344 * getStatusName - get open/closed/deleted text.
346 * @return string The status name.
348 function getStatusName() {
349 return $this->data_array['status_name'];
353 * getCustomStatusName - get custom status value text.
355 * @return string The custom status name.
357 function getCustomStatusName() {
358 $custom_status_id = $this->ArtifactType->getCustomStatusField();
359 if ($custom_status_id) {
360 $result = db_query_params ('SELECT element_name FROM artifact_extra_field_elements aefe, artifact_extra_field_data aefd
361 WHERE artifact_id=$1 AND aefd.extra_field_id=$2 AND CAST(aefd.field_data AS INTEGER)=aefe.element_id',
362 array ($this->getID(), $custom_status_id)) ;
364 return db_result($result, 0, 'element_name');
367 return $this->data_array['status_name'];
371 * getPriority - get priority flag.
373 * @return int priority.
375 function getPriority() {
376 return $this->data_array['priority'];
380 * getSubmittedBy - get ID of submitter.
382 * @return int user_id of submitter.
384 function getSubmittedBy() {
385 return $this->data_array['submitted_by'];
389 * getSubmittedEmail - get email of submitter.
391 * @return string The email of submitter.
393 function getSubmittedEmail() {
394 return $this->data_array['submitted_email'];
398 * getSubmittedRealName - get real name of submitter.
400 * @return string The real name of submitter.
402 function getSubmittedRealName() {
403 return $this->data_array['submitted_realname'];
407 * getSubmittedUnixName - get login name of submitter.
409 * @return string The unix name of submitter.
411 function getSubmittedUnixName() {
412 return $this->data_array['submitted_unixname'];
416 * getAssignedTo - get ID of assignee.
418 * @return int user_id of assignee.
420 function getAssignedTo() {
421 return $this->data_array['assigned_to'];
425 * getAssignedEmail - get email of assignee.
427 * @return string The email of assignee.
429 function getAssignedEmail() {
430 return $this->data_array['assigned_email'];
434 * getAssignedRealName - get real name of assignee.
436 * @return string The real name of assignee.
438 function getAssignedRealName() {
439 return $this->data_array['assigned_realname'];
443 * getAssignedUnixName - get login name of assignee.
445 * @return string The unix name of assignee.
447 function getAssignedUnixName() {
448 return $this->data_array['assigned_unixname'];
452 * getOpenDate - get unix time of creation.
454 * @return int unix time.
456 function getOpenDate() {
457 return $this->data_array['open_date'];
461 * getCloseDate - get unix time of closure.
463 * @return int unix time.
465 function getCloseDate() {
466 return $this->data_array['close_date'];
470 * getLastModifiedDate - the last_modified_date of this task.
472 * @return int the last_modified_date.
474 function getLastModifiedDate() {
475 return $this->data_array['last_modified_date'];
479 * getSummary - get text summary of artifact.
481 * @return string The summary (subject).
483 function getSummary() {
484 return $this->data_array['summary'];
488 * getDetails - get text body (message) of artifact.
490 * @return string The body (message).
492 function getDetails() {
493 return $this->data_array['details'];
497 * delete - delete this tracker and all its related data.
499 * @param bool I'm Sure.
500 * @return bool true/false;
502 function delete($sure) {
504 $this->setMissingParamsError(_('Please tick all checkboxes.'));
507 if (!forge_check_perm ('tracker_admin', $this->ArtifactType->Group->getID())) {
508 $this->setPermissionDeniedError();
512 $res = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1',
513 array ($this->getID())) ;
515 $this->setError(_('Error deleting extra field data: ').db_error());
520 ArtifactStorage::instance()->deleteFromQuery('SELECT id FROM artifact_file WHERE artifact_id=$1',
521 array ($this->getID())) ;
523 $res = db_query_params ('DELETE FROM artifact_file WHERE artifact_id=$1',
524 array ($this->getID())) ;
526 $this->setError(_('Error deleting file from db: ').db_error());
528 ArtifactStorage::instance()->rollback();
531 $res = db_query_params ('DELETE FROM artifact_message WHERE artifact_id=$1',
532 array ($this->getID())) ;
534 $this->setError(_('Error deleting message: ').db_error());
536 ArtifactStorage::instance()->rollback();
539 $res = db_query_params ('DELETE FROM artifact_history WHERE artifact_id=$1',
540 array ($this->getID())) ;
542 $this->setError(_('Error deleting history: ').db_error());
544 ArtifactStorage::instance()->rollback();
547 $res = db_query_params ('DELETE FROM artifact_monitor WHERE artifact_id=$1',
548 array ($this->getID())) ;
550 $this->setError(_('Error deleting monitor: ').db_error());
552 ArtifactStorage::instance()->rollback();
555 $res = db_query_params ('DELETE FROM artifact WHERE artifact_id=$1',
556 array ($this->getID())) ;
558 $this->setError(_('Error deleting artifact: ').db_error());
560 ArtifactStorage::instance()->rollback();
564 if ($this->getStatusID() == 1) {
565 $res = db_query_params ('UPDATE artifact_counts_agg SET count=count-1,open_count=open_count-1
566 WHERE group_artifact_id=$1',
567 array ($this->getID())) ;
569 $this->setError(_('Error updating artifact counts: ').db_error());
571 ArtifactStorage::instance()->rollback();
574 } elseif ($this->getStatusID() == 2) {
575 $res = db_query_params ('UPDATE artifact_counts_agg SET count=count-1
576 WHERE group_artifact_id=$1',
577 array ($this->getID())) ;
579 $this->setError(_('Error updating artifact counts: ').db_error());
581 ArtifactStorage::instance()->rollback();
587 ArtifactStorage::instance()->commit();
592 * setMonitor - user can monitor this artifact.
594 * @return false - always false - always use the getErrorMessage() for feedback
596 function setMonitor() {
597 if (session_loggedin()) {
599 $user_id=user_getid();
600 $user =& user_get_object(user_getid());
603 //we don't want to include the "And email=" because
604 //a logged-in user's email may have changed
609 $this->setError(_('Valid Email Address Required'));
614 $res = db_query_params ('SELECT * FROM artifact_monitor WHERE artifact_id=$1 AND user_id=$2',
615 array ($this->getID(),
618 if (!$res || db_numrows($res) < 1) {
620 $res = db_query_params ('INSERT INTO artifact_monitor (artifact_id,user_id) VALUES ($1,$2)',
621 array ($this->getID(),
624 $this->setError(db_error());
627 $this->setError(_('Now Monitoring Artifact'));
631 //already monitoring - remove their monitor
632 db_query_params ('DELETE FROM artifact_monitor
635 array ($this->getID(),
637 $this->setError(_('Artifact Monitoring Deactivated'));
642 function isMonitoring() {
643 if (!session_loggedin()) {
646 $result = db_query_params ('SELECT count(*) AS count FROM artifact_monitor WHERE user_id=$1 AND artifact_id=$2',
649 $row_count = db_fetch_array($result);
650 return $result && $row_count['count'] > 0;
654 * getMonitorIds - array of email addresses monitoring this Artifact.
656 * @return array of email addresses monitoring this Artifact.
658 function getMonitorIds() {
659 $res = db_query_params ('SELECT user_id FROM artifact_monitor WHERE artifact_id=$1',
660 array ($this->getID())) ;
661 return array_unique(array_merge($this->ArtifactType->getMonitorIds(),util_result_column_to_array($res)));
665 * getHistory - returns a result set of audit trail for this support request.
667 * @return resource result set.
669 function getHistory() {
670 return db_query_params ('SELECT * FROM artifact_history_user_vw WHERE artifact_id=$1 ORDER BY entrydate DESC, id ASC',
671 array ($this->getID())) ;
675 * getMessages - get the list of messages attached to this artifact.
677 * @param string $order
678 * @return resource result set.
680 function getMessages($ascending='up') {
682 * This is necessary because someone committed a change
683 * to this method in FusionForge trunk that accepts 'up'
684 * as default (luckily, it’s the same!) and 'down' as
685 * alternative probability, whereas FusionForge 5.2 has
686 * false as default and true for ascending order, so we
687 * need to check this out and use === to be sure ☹
689 if ($ascending === 'up') {
691 } elseif ($ascending === true) {
693 } elseif ($ascending === false) {
698 return db_query_params('SELECT * FROM artifact_message_user_vw WHERE artifact_id=$1 ORDER BY adddate ' . $order . ', id ASC',
699 array($this->getID()));
703 * getMessage - get a message attached to this artifact.
705 * @param int $msg_id id of the message.
707 * @return database result set.
709 function getMessage($msg_id) {
713 return db_query_params ('SELECT * FROM artifact_message_user_vw WHERE id=$1',
718 * getMessageObjects - get an array of message objects.
720 * @return array Of ArtifactMessage objects.
722 function &getMessageObjects() {
723 $res=$this->getMessages();
725 while ($arr = db_fetch_array($res)) {
726 //$return[]=new ArtifactMessage($arr['artifact_id'],$arr);
727 $return[] = new ArtifactMessage($this, $arr);
733 * getFiles - get array of ArtifactFile's.
735 * @return array of ArtifactFile's.
737 function &getFiles() {
738 if (!isset($this->files)) {
739 $res = db_query_params ('SELECT id,artifact_id,description,filename,filesize,' .
740 'filetype,adddate,submitted_by,user_name,realname
741 FROM artifact_file_user_vw WHERE artifact_id=$1',
742 array ($this->getID())) ;
743 $rows=db_numrows($res);
745 for ($i=0; $i < $rows; $i++) {
746 $this->files[$i]=new ArtifactFile($this,db_fetch_array($res));
749 $this->files=array();
756 * getRelatedTasks - get array of related tasks
758 * @return Database result set
760 function getRelatedTasks() {
761 if (!$this->relatedtasks) {
762 $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
763 FROM project_task pt, project_group_list pgl, project_status ps
764 WHERE pt.group_project_id = pgl.group_project_id
765 AND ps.status_id = pt.status_id
766 AND EXISTS (SELECT project_task_id FROM project_task_artifact
767 WHERE project_task_id=pt.project_task_id
768 AND artifact_id = $1)',
769 array ($this->getID())) ;
771 return $this->relatedtasks;
775 * addMessage - attach a text message to this Artifact.
777 * @param string The message being attached.
778 * @param string Email address of message creator.
779 * @param bool Whether to email out a followup.
781 * @return boolean 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 The name of the field in the database being modified.
830 * @param string The former value of this field.
831 * @param array Array of data to change submitter and time of submit like: array('user' => 127, 'time' => 1234556789)
833 * @return boolean success.
835 function addHistory($field_name,$old_value, $importData = array()) {
836 if (array_key_exists('user', $importData)){
837 $user = $importData['user'];
839 if (!session_loggedin()) {
845 if (array_key_exists('time',$importData)){
846 $time = $importData['time'];
850 return db_query_params ('INSERT INTO artifact_history(artifact_id,field_name,old_value,mod_by,entrydate) VALUES ($1,$2,$3,$4,$5)',
851 array ($this->getID(),
859 * setStatus - set the status of this artifact.
861 * @param int The artifact status ID.
862 * @param int Closing date if status = 1
864 * @return boolean success.
866 function setStatus($status_id, $closingTime=False) {
868 $qpa = db_construct_qpa (false, 'UPDATE artifact SET status_id=$1', array ($status_id)) ;
869 if ($closingTime && $status_id != 1) {
871 $qpa = db_construct_qpa ($qpa, ', close_date=$1 ', array ($time)) ;
873 $qpa = db_construct_qpa ($qpa,
874 'WHERE artifact_id=$1 AND group_artifact_id=$2',
875 array ($this->getID(), $artifact_type_id)) ;
876 $result=db_query_qpa($qpa);
878 if (!$result || db_affected_rows($result) < 1) {
879 $this->setError('Error - update failed!'.db_error());
883 if (!$this->fetchData($this->getID())) {
896 * update - update the fields in this artifact.
898 * @param int The artifact priority.
899 * @param int The artifact status ID.
900 * @param int The person to which this artifact is to be assigned.
901 * @param string The artifact summary.
902 * @param int The canned response.
903 * @param string Attaching another comment.
904 * @param int Allows you to move an artifact to another type.
905 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
906 * @param string The description.
907 * @return boolean success.
909 function update($priority,$status_id,
910 $assigned_to,$summary,$canned_response,$details,$new_artifact_type_id,
911 $extra_fields=array(), $description='') {
914 Field-level permission checking
916 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'manager')) {
917 // Non-managers cannot modify these fields
918 $priority=$this->getPriority();
919 $summary=htmlspecialchars_decode($this->getSummary());
920 $description=htmlspecialchars_decode($this->getDetails());
921 $canned_response=100;
922 $new_artifact_type_id=$this->ArtifactType->getID();
923 $assigned_to=$this->getAssignedTo();
925 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'tech')) {
926 $this->setPermissionDeniedError();
931 // They may be using an extra field "status" box so we have to remap
932 // the status_id based on the extra field - this keeps the counters
935 if (count($extra_fields) > 0) {
936 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
938 if (!$this->getID()) {
939 $this->setMissingParamsError('ID');
943 $this->setMissingParamsError(_('Assigned to'));
947 $this->setMissingParamsError(_('State'));
950 if (!$canned_response) {
951 $this->setMissingParamsError(_('Canned Response'));
954 if (!$new_artifact_type_id) {
955 $this->setMissingParamsError(_('Data Type'));
959 // Check that assigned_to is a tech for the tracker
960 if ($assigned_to != 100) {
961 if (!forge_check_perm_for_user ($assigned_to, 'tracker', $this->ArtifactType->getID(), 'tech')) {
962 $this->setError(_("Invalid assigned_to (assigned person is not a technician)"));
967 // Array to record which properties were changed
974 // Get a lock on this row in the database
976 $lock = db_query_params ('SELECT * FROM artifact WHERE artifact_id=$1 FOR UPDATE',
977 array ($this->getID())) ;
978 $artifact_type_id = $this->ArtifactType->getID();
980 // Attempt to move this Artifact to a new ArtifactType
981 // need to instantiate new ArtifactType obj and test perms
983 if ($new_artifact_type_id != $artifact_type_id) {
984 $newArtifactType= new ArtifactType($this->ArtifactType->getGroup(), $new_artifact_type_id);
985 if (!is_object($newArtifactType) || $newArtifactType->isError()) {
986 $this->setError(_('Could not move to new Artifact Type'). $newArtifactType->getErrorMessage());
990 // do they have perms for new ArtifactType?
991 if (!forge_check_perm ('tracker', $newArtifactType->getID(), 'manager')) {
992 $this->setPermissionDeniedError();
997 // Add a message to explain that the tracker was moved.
998 $message = 'Moved from '.$this->ArtifactType->getName().' to '.$newArtifactType->getName();
999 $this->addHistory('type', $this->ArtifactType->getName());
1000 $this->addMessage($message,'',0);
1002 // Fake change to send a mail when moved.
1003 $changes['Type'] = 1;
1005 // Try to remap extra_fields values when possible.
1006 // If there is an extra_field with the same alias
1007 // and if the value exist in the new one, then recode
1008 // the value to keep it.
1009 $new_extra_fields = array();
1010 $ef = $this->ArtifactType->getExtraFields();
1011 $ef_new = $newArtifactType->getExtraFields();
1012 foreach($extra_fields as $extra_id => $value) {
1013 $alias = preg_replace('/^@/', '', $ef[$extra_id]['alias']);
1014 $type = $ef[$extra_id]['field_type'];
1016 // Search if there is an extra field with the same alias.
1018 foreach($ef_new as $id => $arr) {
1019 if (preg_replace('/^@/', '', $arr['alias']) == $alias) {
1024 // If we found one, copy for simple fields or
1025 // search if there is the same value.
1027 if ($type == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
1028 $type == ARTIFACT_EXTRAFIELDTYPE_INTEGER ||
1029 $type == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA ||
1030 $type == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
1031 $new_extra_fields[$new_id] = $value;
1033 $values = $newArtifactType->getExtraFieldElements($new_id);
1034 if (is_array($value)) {
1035 foreach($value as $v) {
1036 $v = $this->ArtifactType->getElementName($v);
1037 foreach($values as $ev) {
1038 if ($ev['element_name'] == $v) {
1039 $new_extra_fields[$new_id][] = $ev['element_id'];
1044 $value = $this->ArtifactType->getElementName($value);
1045 foreach($values as $ev) {
1046 if ($ev['element_name'] == $value) {
1047 $new_extra_fields[$new_id] = $ev['element_id'];
1055 // Special case if moving to a tracker with custom status (previous has not).
1056 $custom_status_id = $newArtifactType->getCustomStatusField();
1057 if ($custom_status_id && !$new_extra_fields[$custom_status_id]) {
1058 $atw = new ArtifactWorkflow($newArtifactType, $custom_status_id);
1059 $nodes = $atw->getNextNodes(100);
1061 $new_extra_fields[$custom_status_id] = $nodes[0];
1065 $extra_fields = $new_extra_fields;
1067 $res = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1',
1068 array ($this->getID()));
1070 $this->setError(_('Removal of old artifact_extra_field_data failed: ').db_error());
1075 // Check that assigned_to is a tech in the new tracker
1076 if ($assigned_to != 100) {
1077 if (!forge_check_perm ('tracker', $newArtifactType->getID(), 'tech')) {
1082 //can't send a canned response when changing ArtifactType
1083 $canned_response=100;
1084 $this->ArtifactType =& $newArtifactType;
1088 $qpa = db_construct_qpa();
1089 $qpa = db_construct_qpa($qpa, 'UPDATE artifact SET');
1092 // handle audit trail
1095 if ($this->getStatusID() != $status_id) {
1096 $this->addHistory('status_id',$this->getStatusID());
1097 $qpa = db_construct_qpa($qpa, ' status_id=$1,', array($status_id));
1098 $changes['status'] = 1;
1101 if ($status_id != 1) {
1102 $qpa = db_construct_qpa($qpa, ' close_date=$1,', array($now));
1104 $qpa = db_construct_qpa($qpa, ' close_date=$1,', array(0));
1106 $this->addHistory('close_date', $this->getCloseDate());
1108 if ($this->getPriority() != $priority) {
1109 $this->addHistory('priority',$this->getPriority());
1110 $qpa = db_construct_qpa($qpa, ' priority=$1,', array($priority));
1111 $changes['priority'] = 1;
1115 if ($this->getAssignedTo() != $assigned_to) {
1116 $this->addHistory('assigned_to',$this->getAssignedTo());
1117 $qpa = db_construct_qpa($qpa, ' assigned_to=$1,', array($assigned_to));
1118 $changes['assigned_to'] = 1;
1121 if ($summary && ($this->getSummary() != htmlspecialchars($summary))) {
1122 $this->addHistory('summary', $this->getSummary());
1123 $qpa = db_construct_qpa($qpa, ' summary=$1,', array(htmlspecialchars($summary)));
1124 $changes['summary'] = 1;
1127 if ($description && ($this->getDetails() != htmlspecialchars($description))) {
1128 $this->addHistory('details', $this->getDetails());
1129 $qpa = db_construct_qpa($qpa, ' details=$1,', array(htmlspecialchars($description)));
1130 $changes['details'] = 1;
1134 $this->addMessage($details,'',0);
1135 $changes['details'] = 1;
1140 Finally, update the artifact itself
1143 $qpa = db_construct_qpa($qpa, ' group_artifact_id=$1
1144 WHERE artifact_id=$2 AND group_artifact_id=$3',
1145 array($new_artifact_type_id,
1146 $this->getID(), $artifact_type_id));
1147 $result = db_query_qpa($qpa);
1149 if (!$result || db_affected_rows($result) < 1) {
1150 $this->setError(_('Error - update failed!').db_error());
1154 if (!$this->fetchData($this->getID())) {
1161 //extra field handling
1163 if (!$this->updateExtraFields($extra_fields,$changes)) {
1164 //TODO - see if anything actually did change
1170 handle canned responses
1172 Instantiate ArtifactCanned and get the body of the message
1174 if ($canned_response != 100) {
1175 //don't care if this response is for this group - could be hacked
1176 $acr=new ArtifactCanned($this->ArtifactType,$canned_response);
1177 if (!$acr || !is_object($acr)) {
1178 $this->setError(_('Could Not Create Canned Response Object'));
1179 } elseif ($acr->isError()) {
1180 $this->setError($acr->getErrorMessage());
1182 $body = $acr->getBody();
1184 if (!$this->addMessage(util_unconvert_htmlspecialchars($body),'',0)) {
1191 $this->setError(_('Unable to Use Canned Response'));
1197 if ($update || $send_message){
1198 if (!empty($changes)) {
1199 // Send the email with changes
1200 $this->mailFollowupEx($now, 2, false, $changes);
1205 //nothing changed, so cancel the transaction
1206 $this->setError(_('Nothing Changed - Update Cancelled'));
1214 * updateLastModifiedDate - update the last_modified_date attribute of this artifact.
1216 * @return true on success / false on failure
1218 function updateLastModifiedDate() {
1219 $res = db_query_params ('UPDATE artifact SET last_modified_date=EXTRACT(EPOCH FROM now())::integer WHERE artifact_id=$1',
1220 array ($this->getID()));
1225 * assignToMe - assigns this artifact to current user
1227 * @return true on success / false on failure
1229 function assignToMe() {
1230 if (!session_loggedin() || !($this->ArtifactType->userIsAdmin() || $this->ArtifactType->userIsTechnician())) {
1231 $this->setPermissionDeniedError();
1235 $user_id = user_getid();
1236 $res = db_query_params ('UPDATE artifact SET assigned_to=$1 WHERE artifact_id=$2',
1237 array ($user_id, $this->getID())) ;
1239 $this->setError(_('Error updating assigned_to in artifact: ').db_error());
1242 $this->fetchData($this->getID());
1248 * updateExtraFields - updates the extra data elements for this artifact
1249 * e.g. the extra fields created and defined by the admin.
1251 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
1252 * @param array Array where changes to the extra fields should be logged
1253 * @return true on success / false on failure
1255 function updateExtraFields($extra_fields,&$changes){
1257 This is extremely complex code - we have take the passed array
1258 and see if we need to insert it into the db, and may have to
1259 add history rows for the audit trail
1261 start by getting all the available extra fields from ArtifactType
1262 For each field from ArtifacType, check the passed array -
1263 This prevents someone from passing bogus extra field entries - they will be ignored
1264 if the passed entry is blank, may have to force a default value
1265 if the passed array is different from the existing data in db,
1266 delete old entry and insert new entries, along with possible audit trail
1268 skip it and continue to next item
1273 //get a list of extra fields for this artifact_type
1274 $ef = $this->ArtifactType->getExtraFields();
1275 $efk=array_keys($ef);
1277 if (empty($extra_fields) && empty($ef)) {
1281 // If there is a status field, then check against the workflow.
1282 // Unless if we change type.
1283 if (! isset($changes['Type']) || !$changes['Type']) {
1284 for ($i=0; $i<count($efk); $i++) {
1286 $type=$ef[$efid]['field_type'];
1287 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1288 // Get previous value.
1289 $res = db_query_params ('SELECT field_data FROM artifact_extra_field_data
1290 WHERE artifact_id=$1 AND extra_field_id=$2',
1291 array($this->getID(),
1293 $old = (db_numrows($res)>0) ? db_result($res,0,'field_data') : 100;
1294 if ($old != $extra_fields[$efid]) {
1295 $atw = new ArtifactWorkflow($this->ArtifactType, $efid);
1296 if (!$atw->checkEvent($old, $extra_fields[$efid])) {
1297 $this->setError('Workflow error: You are not authorized to change the Status ('.$old.' => '.$extra_fields[$efid].')');
1305 //now we'll update this artifact for each extra field
1306 for ($i=0; $i<count($efk); $i++) {
1308 $type=$ef[$efid]['field_type'];
1310 // check required fields
1311 if ($ef[$efid]['is_required']) {
1312 if (!array_key_exists($efid, $extra_fields)) {
1313 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1314 $this->setError(_('Status Custom Field Must Be Set'));
1317 $this->setMissingParamsError($ef[$efid]['field_name']);
1322 if ($extra_fields[$efid] === '') {
1323 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1324 $this->setError(_('Status Custom Field Must Be Set'));
1327 $this->setMissingParamsError($ef[$efid]['field_name']);
1332 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT || $type == ARTIFACT_EXTRAFIELDTYPE_RADIO) &&
1333 $extra_fields[$efid] == '100') {
1334 $this->setMissingParamsError($ef[$efid]['field_name']);
1337 elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) &&
1338 (count($extra_fields[$efid]) == 1 && $extra_fields[$efid][0] == '100')) {
1339 $this->setMissingParamsError($ef[$efid]['field_name']);
1346 // Force each field to have some value if it is a numeric field
1347 // text fields will just be purged and skipped
1349 if (!array_key_exists($efid, $extra_fields) || $extra_fields[$efid] === '') {
1350 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO)) {
1351 $extra_fields[$efid]='100';
1352 } elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX)) {
1353 $extra_fields[$efid]=array('100');
1355 $resdel = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1356 array ($this->getID(),
1363 // get the old rows of data
1365 $resd = db_query_params ('SELECT * FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1366 array ($this->getID(),
1368 $rows=db_numrows($resd);
1369 if ($resd && $rows) {
1371 //POTENTIAL PROBLEM - no entry was there before, but adding one now - may need history
1374 // Compare for history purposes
1377 // these types have arrays associated to them, so they need
1378 // special handling to check for differences
1379 if ($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) {
1380 // check the differences between the old values and the new values
1381 $old_values = util_result_column_to_array($resd,"field_data");
1383 $added_values = array_diff($extra_fields[$efid], $old_values);
1384 $deleted_values = array_diff($old_values, $extra_fields[$efid]);
1386 if (!empty($added_values) || !empty($deleted_values)) { // there are differences...
1387 $field_name = $ef[$efid]['field_name'];
1388 if (!preg_match('/^@/', $ef[$efid]['alias'])) {
1389 $changes["extra_fields"][$efid] = 1;
1392 $this->addHistory($field_name, $this->ArtifactType->getElementName(array_reverse($old_values)));
1395 $resdel = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1396 array ($this->getID(),
1401 } elseif (db_result($resd,0,'field_data') == htmlspecialchars($extra_fields[$efid])) {
1402 //element did not change
1405 //element DID change - do a history entry
1406 $field_name = $ef[$efid]['field_name'];
1407 if (!preg_match('/^@/', $ef[$efid]['alias'])) {
1408 $changes["extra_fields"][$efid] = 1;
1410 $resdel = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1411 array ($this->getID(),
1414 // Adding history with previous value.
1415 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO) || ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS)) {
1416 $this->addHistory($field_name,$this->ArtifactType->getElementName(db_result($resd,0,'field_data')));
1418 $this->addHistory($field_name, db_result($resd,0,'field_data'));
1424 //no history for this extra field exists
1429 // Some rewrite & consistency checks on the relation type field.
1431 // 1) Convert syntax [#NNN] to NNN
1432 // 2) Allow multiple spaces as separator.
1433 // 3) Ensure that only integers are given.
1434 // 4) Ensure that id corresponds to valid tracker id.
1436 if ($type == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
1437 $value = preg_replace('/\[\#(\d+)\]/', "\\1", trim($extra_fields[$efid]));
1438 $value = preg_replace('/\\s+/', ' ', $value);
1440 foreach (explode(' ',$value) as $id) {
1441 if (preg_match('/^(\d+)$/', $id)) {
1442 // Control that the id is present in the db
1444 $res = db_query_params ('SELECT artifact_id FROM artifact WHERE artifact_id=$1',
1446 if (db_numrows($res) == 1) {
1449 $this->setError('Illegal id '.$id.', it\'s not a valid tracker id for field: '.$ef[$efid]['field_name'].'.'); // @todo: lang
1453 $this->setError('Illegal value '.$id.', only trackers id are allowed for field: '.$ef[$efid]['field_name'].'.'); // @todo: lang
1457 $extra_fields[$efid] = trim($new);
1460 // Ensure that only integer are allowed for type ARTIFACT_EXTRAFIELDTYPE_INTEGER
1461 if ($type == ARTIFACT_EXTRAFIELDTYPE_INTEGER) {
1462 $extra_fields[$efid] = trim($extra_fields[$efid]);
1463 if (!preg_match('/^[-+]?(\d+)$/', $extra_fields[$efid])) {
1464 $this->setError('Illegal value '.$extra_fields[$efid].' for field '.$ef[$efid]['field_name'].': Only integer is allowed.');
1467 if ($extra_fields[$efid] < -2147483648 || $extra_fields[$efid] > 2147483647) {
1468 $this->setError('Illegal value '.$extra_fields[$efid].' for field '.$ef[$efid]['field_name'].': Integer out of range (-2147483648 to +2147483647).');
1471 $extra_fields[$efid] = intval($extra_fields[$efid]);
1475 // See if anything was even passed for this extra_field_id
1477 if ($extra_fields[$efid] === '') {
1478 //nothing in field to update - text fields may be blank
1480 //determine the type of field and whether it should have multiple rows supporting it
1481 $type=$ef[$efid]['field_type'];
1482 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1484 $count=count($extra_fields[$efid]);
1485 for ($fin=0; $fin<$count; $fin++) {
1486 $res = db_query_params ('INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) VALUES ($1,$2,$3)',
1487 array ($this->getID(),
1489 $extra_fields[$efid][$fin])) ;
1491 $this->setError(db_error());
1498 $res = db_query_params ('INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) VALUES ($1,$2,$3)',
1499 array ($this->getID(),
1501 htmlspecialchars($extra_fields[$efid]))) ;
1503 $this->setError(db_error());
1510 unset($this->extra_field_data);
1513 $this->updateLastModifiedDate();
1519 * getExtraFieldData - get an array of data for the extra fields associated with this artifact
1521 * @return array array of data
1523 function &getExtraFieldData() {
1524 if (!isset($this->extra_field_data)) {
1525 $this->extra_field_data = array();
1526 $res = db_query_params ('SELECT * FROM artifact_extra_field_data WHERE artifact_id=$1 ORDER BY extra_field_id',
1527 array ($this->getID())) ;
1528 $ef = $this->ArtifactType->getExtraFields();
1529 while ($arr = db_fetch_array($res)) {
1530 $type=$ef[$arr['extra_field_id']]['field_type'];
1531 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1532 //accumulate a sub-array of values in cases where you may have multiple rows
1533 if (!array_key_exists($arr['extra_field_id'], $this->extra_field_data) || !is_array($this->extra_field_data[$arr['extra_field_id']])) {
1534 $this->extra_field_data[$arr['extra_field_id']] = array();
1536 $this->extra_field_data[$arr['extra_field_id']][]=$arr['field_data'];
1538 $this->extra_field_data[$arr['extra_field_id']]=$arr['field_data'];
1542 return $this->extra_field_data;
1546 * marker - adds the > symbol to fields that have been modified for the email message
1550 function marker($prop_name,$changes,$extra_field_id=0) {
1551 if ($prop_name == 'extra_fields' && isset($changes[$prop_name][$extra_field_id])) {
1553 } elseif ($prop_name != 'extra_fields' && isset($changes[$prop_name])) {
1561 * mailFollowupEx - send out an email update for this artifact.
1563 * @param time_t Time of the change
1564 * @param int (1) initial/creation (2) update.
1565 * @param array Array of additional addresses to mail to.
1566 * @param array Array of fields changed in this update .
1568 * @return boolean success.
1570 function mailFollowupEx($tm,$type,$more_addresses=false,$changes='') {
1572 $monitor_ids = array();
1578 $sess = session_get_user() ;
1579 if ($type == 1) { // Initial opening
1581 $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 () ;
1583 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() ." item #". $this->getID() .", was opened at ". date( _('Y-m-d H:i'), $this->getOpenDate() ) ;
1587 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() .
1588 " item #" . $this->getID() .
1589 " was changed at " .
1590 date(_('Y-m-d H:i'), $tm) . " by " .
1591 $sess->getRealName();
1593 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() .
1594 " item #" . $this->getID() .
1595 " was changed at " .
1596 date(_('Y-m-d H:i'), $tm);
1601 $body .= "\nYou can respond by visiting: ".
1602 "\n".util_make_url ('/tracker/?func=detail&atid='. $this->ArtifactType->getID() .
1603 "&aid=". $this->getID() .
1604 "&group_id=". $this->ArtifactType->Group->getID()) .
1605 "\nOr by replying to this e-mail entering your response between the following markers: ".
1606 "\n".ARTIFACT_MAIL_MARKER.
1607 "\n(enter your response here, only in plain text format)".
1608 "\n".ARTIFACT_MAIL_MARKER.
1610 $this->marker('status',$changes).
1611 "Status: ". $this->getStatusName() ."\n".
1612 $this->marker('priority',$changes).
1613 "Priority: ". $this->getPriority() ."\n".
1614 "Submitted By: ". $this->getSubmittedRealName() .
1615 " (". $this->getSubmittedUnixName(). ")"."\n".
1616 $this->marker('assigned_to',$changes).
1617 "Assigned to: ". $this->getAssignedRealName() .
1618 " (". $this->getAssignedUnixName(). ")"."\n".
1619 $this->marker('summary',$changes).
1620 "Summary: ". util_unconvert_htmlspecialchars( $this->getSummary() )." \n";
1622 // Now display the extra fields
1623 $efd = $this->getExtraFieldDataText();
1624 foreach ($efd as $efid => $ef) {
1625 $body .= $this->marker('extra_fields', $changes, $efid);
1626 $body .= $ef["name"].": ".$ef["value"]."\n";
1629 $subject='['. $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() . '][' . $this->getID() .'] '. util_unconvert_htmlspecialchars( $this->getSummary() );
1632 // get all the email addresses that are monitoring this request or the ArtifactType
1633 $monitor_ids = $this->getMonitorIds();
1635 // initial creation, we just get the users monitoring the ArtifactType
1636 $monitor_ids = $this->ArtifactType->getMonitorIds();
1640 if ($more_addresses) {
1641 $emails[] = $more_addresses;
1643 //we don't email the current user
1644 if ($this->getAssignedTo() != user_getid()) {
1645 $monitor_ids[] = $this->getAssignedTo();
1647 if ($this->getSubmittedBy() != user_getid()) {
1648 $monitor_ids[] = $this->getSubmittedBy();
1650 //initial submission
1652 //if an email is set for this ArtifactType
1653 //add that address to the BCC: list
1654 if ($this->ArtifactType->getEmailAddress()) {
1655 $emails[] = $this->ArtifactType->getEmailAddress();
1659 if ($this->ArtifactType->emailAll()) {
1660 $emails[] = $this->ArtifactType->getEmailAddress();
1664 $body .= "\n\nInitial Comment:".
1665 "\n".util_unconvert_htmlspecialchars( $this->getDetails() ) .
1666 "\n\n----------------------------------------------------------------------";
1670 Now include the followups
1672 $result2=$this->getMessages();
1674 $rows=db_numrows($result2);
1676 if ($result2 && $rows > 0) {
1677 for ($i=0; $i<$rows; $i++) {
1679 // for messages posted by non-logged-in users,
1680 // we grab the email they gave us
1682 // otherwise we use the confirmed one from the users table
1684 if (db_result($result2,$i,'user_id') == 100) {
1685 $emails[] = db_result($result2,$i,'from_email');
1687 $monitor_ids[] = db_result($result2,$i,'user_id');
1693 $body .= $this->marker('details',$changes);
1695 $body .= "Comment By: ". db_result($result2,$i,'realname') . " (".db_result($result2,$i,'user_name').")".
1696 "\nDate: ". date( _('Y-m-d H:i'),db_result($result2,$i,'adddate') ).
1698 "\n".util_unconvert_htmlspecialchars( db_result($result2,$i,'body') ).
1699 "\n\n----------------------------------------------------------------------";
1705 $body .= "\n\nYou can respond by visiting: ".
1706 "\n".util_make_url ('/tracker/?func=detail&atid='. $this->ArtifactType->getID() .
1707 "&aid=". $this->getID() .
1708 "&group_id=". $this->ArtifactType->Group->getID());
1710 //only send if some recipients were found
1711 if (count($emails) < 1 && count($monitor_ids) < 1) {
1715 if (count($monitor_ids) < 1) {
1716 $monitor_ids=array();
1718 $monitor_ids=array_unique($monitor_ids);
1721 $from = $this->ArtifactType->getReturnEmailAddress();
1722 $extra_headers = 'Reply-to: '.$from;
1724 // load the e-mail addresses of the users
1725 $users =& user_get_objects($monitor_ids);
1726 if (count($users) > 0) {
1727 foreach ($users as $user) {
1728 if ($user->getStatus() == "A") { //we are only sending emails to active users
1729 $emails[] = $user->getEmail();
1734 //now remove all duplicates from the email list
1735 if (count($emails) > 0) {
1736 $BCC=implode(',',array_unique($emails));
1737 util_send_message('',$subject,$body,$from,$BCC,'',$extra_headers);
1740 $this->sendSubjectMsg = $subject;
1741 $this->sendBodyMsg = $body;
1743 //util_handle_message($monitor_ids,$subject,$body,$BCC);
1749 * getExtraFieldDataText - Return the extra fields' data in a human-readable form.
1751 * @return array Array containing field ID => field name and value associated to it for
1754 function getExtraFieldDataText() {
1755 // First we get the list of extra fields and the data
1756 // associated to the fields
1757 $efs = $this->ArtifactType->getExtraFields();
1758 $efd = $this->getExtraFieldData();
1762 foreach ($efs as $efid => $ef) {
1763 $name = $ef["field_name"];
1764 $type = $ef["field_type"];
1766 // Get the value according to the type
1769 // for these types, the associated value comes straight
1770 case ARTIFACT_EXTRAFIELDTYPE_TEXT:
1771 case ARTIFACT_EXTRAFIELDTYPE_TEXTAREA:
1772 case ARTIFACT_EXTRAFIELDTYPE_RELATION:
1773 case ARTIFACT_EXTRAFIELDTYPE_INTEGER:
1774 if (isset($efd[$efid])) {
1775 $value = $efd[$efid];
1781 // the other types have and ID or an array of IDs associated to them
1783 if (isset($efd[$efid])) {
1784 $value = $this->ArtifactType->getElementName($efd[$efid]);
1790 $return[$efid] = array("name" => $name, "value" => $value, 'type' => $type);
1797 class ArtifactComparator {
1798 var $criterion = 'artifact_id' ;
1799 var $order = 'ASC' ;
1801 function Compare ($a, $b) {
1802 if ($this->order == 'DESC') {
1803 $c = $a ; $a = $b ; $b = $c ;
1805 switch ($this->criterion) {
1807 $namecmp = strcoll ($a->getSummary(),
1809 if ($namecmp != 0) {
1814 $namecmp = strcoll (user_get_object($a->getAssignedTo())->getRealName(),
1815 user_get_object($b->getAssignedTo())->getRealName()) ;
1816 if ($namecmp != 0) {
1820 case 'submitted_by':
1821 $namecmp = strcoll (user_get_object($a->getSubmittedBy())->getRealName(),
1822 user_get_object($b->getSubmittedBy())->getRealName()) ;
1823 if ($namecmp != 0) {
1828 $a_date = $a->getOpenDate() ;
1829 $b_date = $b->getOpenDate() ;
1830 return ($a_date < $b_date) ? -1 : 1;
1833 $a_date = $a->getCloseDate() ;
1834 $b_date = $b->getCloseDate() ;
1835 return ($a_date < $b_date) ? -1 : 1;
1837 case 'last_modified_date':
1838 $a_date = $a->getLastModifiedDate() ;
1839 $b_date = $b->getLastModifiedDate() ;
1840 return ($a_date < $b_date) ? -1 : 1;
1843 $a_prority = $a->getPriority() ;
1844 $b_prority = $b->getPriority() ;
1845 return ($a_prority < $b_prority) ? -1 : 1;
1848 $aa=$a->getExtraFieldDataText();
1849 $ba=$b->getExtraFieldDataText();
1850 if(!isset($this->criterion) || empty($this->criterion)) {
1854 $criterion = $this->criterion;
1856 $af=$aa[$criterion]['value'];
1857 $bf=$ba[$criterion]['value'];
1858 $namecmp = strcoll ($af,$bf) ;
1859 if ($namecmp != 0) {
1865 // When in doubt, sort on artifact ID
1866 $aid = $a->getID() ;
1867 $bid = $b->getID() ;
1871 return ($aid < $bid) ? -1 : 1;
1875 function sortArtifactList (&$list, $criterion='name', $order='ASC') {
1876 $cmp = new ArtifactComparator () ;
1877 $cmp->criterion = $criterion ;
1878 $cmp->order = $order ;
1880 return usort ($list, array ($cmp, 'Compare')) ;
1885 // c-file-style: "bsd"