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';
52 // This string is used when sending the notification mail for identifying the
54 define('ARTIFACT_MAIL_MARKER', '#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+');
57 * Factory method which creates an Artifact from an artifact ID
59 * @param int The artifact ID
60 * @param array The result array, if it's passed in
61 * @return object Artifact object
63 function &artifact_get_object($artifact_id,$data=false) {
65 if (!isset($ARTIFACT_OBJ["_".$artifact_id."_"])) {
67 //the db result handle was passed in
69 $res = db_query_params ('SELECT * FROM artifact_vw WHERE artifact_id=$1',
70 array ($artifact_id)) ;
71 if (db_numrows($res) <1 ) {
72 $ARTIFACT_OBJ["_".$artifact_id."_"]=false;
75 $data = db_fetch_array($res);
77 $ArtifactType =& artifactType_get_object($data["group_artifact_id"]);
78 $ARTIFACT_OBJ["_".$artifact_id."_"]= new Artifact($ArtifactType,$data);
80 return $ARTIFACT_OBJ["_".$artifact_id."_"];
83 class Artifact extends Error {
88 * @var int $status_res.
93 * Artifact Type object.
95 * @var object $ArtifactType.
100 * Array of artifact data.
102 * @var array $data_array.
107 * Array of artifact data for extra fields defined by Admin.
109 * @var array $extra_field_data.
111 var $extra_field_data;
114 * Array of ArtifactFile objects.
121 * Database result set of related tasks
123 * @var result $relatedtasks
128 * Artifact - constructor.
130 * @param object The ArtifactType object.
131 * @param integer (primary key from database OR complete assoc array)
132 * ONLY OPTIONAL WHEN YOU PLAN TO IMMEDIATELY CALL ->create()
133 * @return boolean success.
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());
153 // make sure this person has permission to view artifacts
155 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'read')) {
156 $this->setError(_('Only project members can view private artifact types'));
161 // set up data structures
164 if (is_array($data)) {
165 $this->data_array =& $data;
167 // Should verify ArtifactType ID
171 if (!$this->fetchData($data)) {
181 * create - construct a new Artifact in the database.
183 * @param string The artifact summary.
184 * @param string Details of the artifact.
185 * @param int The ID of the user to which this artifact is to be assigned.
186 * @param int The artifacts priority.
187 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
188 * @param array Array of data to change submitter and time of submit like: array('user' => 127, 'time' => 1234556789)
189 * @return id on success / false on failure.
191 function create( $summary, $details, $assigned_to=100, $priority=3, $extra_fields=array(), $importData = array()) {
193 // make sure this person has permission to add artifacts
196 if (!$this->ArtifactType->isPublic()) {
198 // Only admins can post/modify private artifacts
202 // ape: Disabled, private means only restricted to members. So, no special rules #2503.
203 // if (!forge_check_perm ('tracker_admin', $this->ArtifactType->Group->getID()) {
204 // $this->setError(_('Only Artifact Admins Can Modify Private Artifact Types'));
212 if(array_key_exists('user', $importData)){
213 $user = $importData['user'];
215 if (session_loggedin()) {
218 if ($this->ArtifactType->allowsAnon()) {
221 $this->setError(_('This Artifact Type Does Not Allow Anonymous Submissions. Please Login.'));
231 $this->setError(_('Message Summary Is Required'));
235 $this->setError(_('Message Body Is Required'));
244 // if (!$status_id) {
245 $status_id=1; // on creation, status is set to "open"
248 // They may be using an extra field "status" box so we have to remap
249 // the status_id based on the extra field - this keeps the counters
252 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
254 $this->setError(_('Error remapping status'));
259 if (array_key_exists('time',$importData)){
260 $time = $importData['time'];
264 $res = db_query_params ('INSERT INTO artifact
265 (group_artifact_id,status_id,priority,
266 submitted_by,assigned_to,open_date,summary,details)
267 VALUES ($1,$2,$3,$4,$5,$6,$7,$8)',
268 array ($this->ArtifactType->getID(),
274 htmlspecialchars($summary),
275 htmlspecialchars($details))) ;
277 $this->setError(db_error());
282 $artifact_id=db_insertid($res,'artifact','artifact_id');
284 if (!$res || !$artifact_id) {
285 $this->setError(db_error());
290 // Now set up our internal data structures
292 if (!$this->fetchData($artifact_id)) {
296 // the changes to the extra fields will be logged in this array.
297 // (we won't use it however)
298 $extra_field_changes = array();
299 if (!$this->updateExtraFields($extra_fields,$extra_field_changes)) {
305 // now send an email if appropriate
307 $this->mailFollowupEx(0, 1);
315 * fetchData - re-fetch the data for this Artifact from the database.
317 * @param int The artifact ID.
318 * @return boolean success.
320 function fetchData($artifact_id) {
321 $res = db_query_params ('SELECT * FROM artifact_vw WHERE artifact_id=$1 AND group_artifact_id=$2',
323 $this->ArtifactType->getID())) ;
324 if (!$res || db_numrows($res) < 1) {
325 $this->setError(_('Invalid Artifact ID'));
328 $this->data_array = db_fetch_array($res);
329 db_free_result($res);
334 * getArtifactType - get the ArtifactType Object this Artifact is associated with.
336 * @return object ArtifactType.
338 function &getArtifactType() {
339 return $this->ArtifactType;
343 * getID - get this ArtifactID.
345 * @return int The artifact_id #.
348 return $this->data_array['artifact_id'];
352 * getStringID - get a string display for this ArtifactID.
354 * @return string The artifact_id #.
356 function getStringID() {
357 return '[#'.$this->data_array['artifact_id'].']';
361 * getStatusID - get open/closed/deleted flag.
363 * @return int Status: (1) Open, (2) Closed, (3) Deleted.
365 function getStatusID() {
366 return $this->data_array['status_id'];
370 * getStatusName - get open/closed/deleted text.
372 * @return string The status name.
374 function getStatusName() {
375 return $this->data_array['status_name'];
379 * getPriority - get priority flag.
381 * @return int priority.
383 function getPriority() {
384 return $this->data_array['priority'];
388 * getSubmittedBy - get ID of submitter.
390 * @return int user_id of submitter.
392 function getSubmittedBy() {
393 return $this->data_array['submitted_by'];
397 * getSubmittedEmail - get email of submitter.
399 * @return string The email of submitter.
401 function getSubmittedEmail() {
402 return $this->data_array['submitted_email'];
406 * getSubmittedRealName - get real name of submitter.
408 * @return string The real name of submitter.
410 function getSubmittedRealName() {
411 return $this->data_array['submitted_realname'];
415 * getSubmittedUnixName - get login name of submitter.
417 * @return string The unix name of submitter.
419 function getSubmittedUnixName() {
420 return $this->data_array['submitted_unixname'];
424 * getAssignedTo - get ID of assignee.
426 * @return int user_id of assignee.
428 function getAssignedTo() {
429 return $this->data_array['assigned_to'];
433 * getAssignedEmail - get email of assignee.
435 * @return string The email of assignee.
437 function getAssignedEmail() {
438 return $this->data_array['assigned_email'];
442 * getAssignedRealName - get real name of assignee.
444 * @return string The real name of assignee.
446 function getAssignedRealName() {
447 return $this->data_array['assigned_realname'];
451 * getAssignedUnixName - get login name of assignee.
453 * @return string The unix name of assignee.
455 function getAssignedUnixName() {
456 return $this->data_array['assigned_unixname'];
460 * getOpenDate - get unix time of creation.
462 * @return int unix time.
464 function getOpenDate() {
465 return $this->data_array['open_date'];
469 * getCloseDate - get unix time of closure.
471 * @return int unix time.
473 function getCloseDate() {
474 return $this->data_array['close_date'];
478 * getLastModifiedDate - the last_modified_date of this task.
480 * @return int the last_modified_date.
482 function getLastModifiedDate() {
483 return $this->data_array['last_modified_date'];
487 * getSummary - get text summary of artifact.
489 * @return string The summary (subject).
491 function getSummary() {
492 return $this->data_array['summary'];
496 * getDetails - get text body (message) of artifact.
498 * @return string The body (message).
500 function getDetails() {
501 return $this->data_array['details'];
505 * delete - delete this tracker and all its related data.
507 * @param bool I'm Sure.
508 * @return bool true/false;
510 function delete($sure) {
512 $this->setMissingParamsError(_('Please tick all checkboxes.'));
515 if (!forge_check_perm ('tracker_admin', $this->ArtifactType->Group->getID())) {
516 $this->setPermissionDeniedError();
520 $res = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1',
521 array ($this->getID())) ;
523 $this->setError(_('Error deleting extra field data: ').db_error());
527 $res = db_query_params ('DELETE FROM artifact_file WHERE artifact_id=$1',
528 array ($this->getID())) ;
530 $this->setError(_('Error deleting file from db: ').db_error());
534 $res = db_query_params ('DELETE FROM artifact_message WHERE artifact_id=$1',
535 array ($this->getID())) ;
537 $this->setError(_('Error deleting message: ').db_error());
541 $res = db_query_params ('DELETE FROM artifact_history WHERE artifact_id=$1',
542 array ($this->getID())) ;
544 $this->setError(_('Error deleting history: ').db_error());
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());
555 $res = db_query_params ('DELETE FROM artifact WHERE artifact_id=$1',
556 array ($this->getID())) ;
558 $this->setError(_('Error deleting artifact: ').db_error());
563 if ($this->getStatusID() == 1) {
564 $res = db_query_params ('UPDATE artifact_counts_agg SET count=count-1,open_count=open_count-1
565 WHERE group_artifact_id=$1',
566 array ($this->getID())) ;
568 $this->setError(_('Error updating artifact counts: ').db_error());
572 } elseif ($this->getStatusID() == 2) {
573 $res = db_query_params ('UPDATE artifact_counts_agg SET count=count-1
574 WHERE group_artifact_id=$1',
575 array ($this->getID())) ;
577 $this->setError(_('Error updating artifact counts: ').db_error());
588 * setMonitor - user can monitor this artifact.
590 * @return false - always false - always use the getErrorMessage() for feedback
592 function setMonitor() {
593 if (session_loggedin()) {
595 $user_id=user_getid();
596 $user =& user_get_object(user_getid());
599 //we don't want to include the "And email=" because
600 //a logged-in user's email may have changed
605 $this->setError(_('Valid Email Address Required'));
610 $res = db_query_params ('SELECT * FROM artifact_monitor WHERE artifact_id=$1 AND user_id=$2',
611 array ($this->getID(),
614 if (!$res || db_numrows($res) < 1) {
616 $res = db_query_params ('INSERT INTO artifact_monitor (artifact_id,user_id) VALUES ($1,$2)',
617 array ($this->getID(),
620 $this->setError(db_error());
623 $this->setError(_('Now Monitoring Artifact'));
627 //already monitoring - remove their monitor
628 db_query_params ('DELETE FROM artifact_monitor
631 array ($this->getID(),
633 $this->setError(_('Artifact Monitoring Deactivated'));
638 function isMonitoring() {
639 if (!session_loggedin()) {
642 $result = db_query_params ('SELECT count(*) AS count FROM artifact_monitor WHERE user_id=$1 AND artifact_id=$2',
645 $row_count = db_fetch_array($result);
646 return $result && $row_count['count'] > 0;
650 * getMonitorIds - array of email addresses monitoring this Artifact.
652 * @return array of email addresses monitoring this Artifact.
654 function getMonitorIds() {
655 $res = db_query_params ('SELECT user_id FROM artifact_monitor WHERE artifact_id=$1',
656 array ($this->getID())) ;
657 return array_unique(array_merge($this->ArtifactType->getMonitorIds(),util_result_column_to_array($res)));
661 * getHistory - returns a result set of audit trail for this support request.
663 * @return database result set.
665 function getHistory() {
666 return db_query_params ('SELECT * FROM artifact_history_user_vw WHERE artifact_id=$1 ORDER BY entrydate DESC',
667 array ($this->getID())) ;
671 * getMessages - get the list of messages attached to this artifact.
673 * @return database result set.
675 function getMessages($asc=false) {
676 return db_query_params ('SELECT * FROM artifact_message_user_vw WHERE artifact_id=$1 ORDER BY adddate ' . ($asc ? 'ASC' : 'DESC'),
677 array ($this->getID())) ;
681 * getMessageObjects - get an array of message objects.
683 * @return array Of ArtifactMessage objects.
685 function &getMessageObjects() {
686 $res=$this->getMessages();
688 while ($arr = db_fetch_array($res)) {
689 //$return[]=new ArtifactMessage($arr['artifact_id'],$arr);
690 $return[] = new ArtifactMessage($this, $arr);
696 * getFiles - get array of ArtifactFile's.
698 * @return array of ArtifactFile's.
700 function &getFiles() {
701 if (!isset($this->files)) {
702 $res = db_query_params ('SELECT id,artifact_id,description,filename,filesize,' .
703 'filetype,adddate,submitted_by,user_name,realname
704 FROM artifact_file_user_vw WHERE artifact_id=$1',
705 array ($this->getID())) ;
706 $rows=db_numrows($res);
708 for ($i=0; $i < $rows; $i++) {
709 $this->files[$i]=new ArtifactFile($this,db_fetch_array($res));
712 $this->files=array();
719 * getRelatedTasks - get array of related tasks
721 * @return Database result set
723 function getRelatedTasks() {
724 if (!$this->relatedtasks) {
725 $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
726 FROM project_task pt, project_group_list pgl, project_status ps
727 WHERE pt.group_project_id = pgl.group_project_id
728 AND ps.status_id = pt.status_id
729 AND EXISTS (SELECT project_task_id FROM project_task_artifact
730 WHERE project_task_id=pt.project_task_id
731 AND artifact_id = $1)',
732 array ($this->getID())) ;
734 return $this->relatedtasks;
738 * addMessage - attach a text message to this Artifact.
740 * @param string The message being attached.
741 * @param string Email address of message creator.
742 * @param bool Whether to email out a followup.
744 * @return boolean success.
746 function addMessage($body,$by=false,$send_followup=false) {
748 $this->setMissingParamsError();
751 if (session_loggedin()) {
752 $user_id=user_getid();
753 $user =& user_get_object($user_id);
754 if (!$user || !is_object($user)) {
755 $this->setError('ERROR - Logged In User But Could Not Get User Object');
758 // we'll store this email even though it will likely never be used -
759 // since we have their correct user_id, we can join the USERS table to get email
760 $by=$user->getEmail();
761 } elseif (!$this->ArtifactType->allowsAnon()) {
762 $this->setError(_('This Artifact Type Does Not Allow Anonymous Submissions. Please Login.'));
766 if (!$by || !validate_email($by)) {
767 $this->setMissingParamsError();
773 $res = db_query_params ('INSERT INTO artifact_message (artifact_id,submitted_by,from_email,adddate,body) VALUES ($1,$2,$3,$4,$5)',
774 array ($this->getID(),
778 htmlspecialchars($body))) ;
780 $this->updateLastModifiedDate();
782 if ($send_followup) {
783 $this->mailFollowupEx($now, 2, false);
789 * addHistory - add an entry to audit trail.
791 * @param string The name of the field in the database being modified.
792 * @param string The former value of this field.
793 * @param array Array of data to change submitter and time of submit like: array('user' => 127, 'time' => 1234556789)
795 * @return boolean success.
797 function addHistory($field_name,$old_value, $importData = array()) {
798 if (array_key_exists('user', $importData)){
799 $user = $importData['user'];
801 if (!session_loggedin()) {
807 if (array_key_exists('time',$importData)){
808 $time = $importData['time'];
812 return db_query_params ('INSERT INTO artifact_history(artifact_id,field_name,old_value,mod_by,entrydate) VALUES ($1,$2,$3,$4,$5)',
813 array ($this->getID(),
821 * setStatus - set the status of this artifact.
823 * @param int The artifact status ID.
824 * @param int Closing date if status = 1
826 * @return boolean success.
828 function setStatus($status_id, $closingTime=False) {
830 $qpa = db_construct_qpa (false, 'UPDATE artifact SET status_id=$1', array ($status_id)) ;
831 if ($closingTime && $status_id != 1) {
833 $qpa = db_construct_qpa ($qpa, ', close_date=$1 ', array ($time)) ;
835 $qpa = db_construct_qpa ($qpa,
836 'WHERE artifact_id=$1 AND group_artifact_id=$2',
837 array ($this->getID(), $artifact_type_id)) ;
838 $result=db_query_qpa($qpa);
840 if (!$result || db_affected_rows($result) < 1) {
841 $this->setError('Error - update failed!'.db_error());
845 if (!$this->fetchData($this->getID())) {
858 * update - update the fields in this artifact.
860 * @param int The artifact priority.
861 * @param int The artifact status ID.
862 * @param int The person to which this artifact is to be assigned.
863 * @param string The artifact summary.
864 * @param int The canned response.
865 * @param string Attaching another comment.
866 * @param int Allows you to move an artifact to another type.
867 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
868 * @param string The description.
869 * @return boolean success.
871 function update($priority,$status_id,
872 $assigned_to,$summary,$canned_response,$details,$new_artifact_type_id,
873 $extra_fields=array(), $description='') {
876 Field-level permission checking
878 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'manager')) {
879 // Non-managers cannot modify these fields
880 $priority=$this->getPriority();
881 $summary=htmlspecialchars_decode($this->getSummary());
882 $description=htmlspecialchars_decode($this->getDetails());
883 $canned_response=100;
884 $new_artifact_type_id=$this->ArtifactType->getID();
885 $assigned_to=$this->getAssignedTo();
887 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'tech')) {
888 $this->setPermissionDeniedError();
893 // They may be using an extra field "status" box so we have to remap
894 // the status_id based on the extra field - this keeps the counters
897 if (count($extra_fields) > 0) {
898 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
900 if (!$this->getID()) {
901 $this->setMissingParamsError('ID');
905 $this->setMissingParamsError(_('Assigned to'));
909 $this->setMissingParamsError(_('State'));
912 if (!$canned_response) {
913 $this->setMissingParamsError(_('Canned Response'));
916 if (!$new_artifact_type_id) {
917 $this->setMissingParamsError(_('Data Type'));
921 // Check that assigned_to is a tech for the tracker
922 if ($assigned_to != 100) {
923 if (!forge_check_perm_for_user ($assigned_to, 'tracker', $this->ArtifactType->getID(), 'tech')) {
924 $this->setError(_("Invalid assigned_to (assigned person is not a technician)"));
929 // Array to record which properties were changed
936 // Get a lock on this row in the database
938 $lock = db_query_params ('SELECT * FROM artifact WHERE artifact_id=$1 FOR UPDATE',
939 array ($this->getID())) ;
940 $artifact_type_id = $this->ArtifactType->getID();
942 // Attempt to move this Artifact to a new ArtifactType
943 // need to instantiate new ArtifactType obj and test perms
945 if ($new_artifact_type_id != $artifact_type_id) {
946 $newArtifactType= new ArtifactType($this->ArtifactType->getGroup(), $new_artifact_type_id);
947 if (!is_object($newArtifactType) || $newArtifactType->isError()) {
948 $this->setError(_('Could not move to new Artifact Type'). $newArtifactType->getErrorMessage());
952 // do they have perms for new ArtifactType?
953 if (!forge_check_perm ('tracker', $newArtifactType->getID(), 'manager')) {
954 $this->setPermissionDeniedError();
959 // Add a message to explain that the tracker was moved.
960 $message = 'Moved from '.$this->ArtifactType->getName().' to '.$newArtifactType->getName();
961 $this->addHistory('type', $this->ArtifactType->getName());
962 $this->addMessage($message,'',0);
964 // Fake change to send a mail when moved.
965 $changes['Type'] = 1;
967 // Try to remap extra_fields values when possible.
968 // If there is an extra_field with the same alias
969 // and if the value exist in the new one, then recode
970 // the value to keep it.
971 $new_extra_fields = array();
972 $ef = $this->ArtifactType->getExtraFields();
973 $ef_new = $newArtifactType->getExtraFields();
974 foreach($extra_fields as $extra_id => $value) {
975 $alias = preg_replace('/^@/', '', $ef[$extra_id]['alias']);
976 $type = $ef[$extra_id]['field_type'];
978 // Search if there is an extra field with the same alias.
980 foreach($ef_new as $id => $arr) {
981 if (preg_replace('/^@/', '', $arr['alias']) == $alias) {
986 // If we found one, copy for simple fields or
987 // search if there is the same value.
989 if ($type == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
990 $type == ARTIFACT_EXTRAFIELDTYPE_INTEGER ||
991 $type == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA ||
992 $type == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
993 $new_extra_fields[$new_id] = $value;
995 $values = $newArtifactType->getExtraFieldElements($new_id);
996 if (is_array($value)) {
997 foreach($value as $v) {
998 $v = $this->ArtifactType->getElementName($v);
999 foreach($values as $ev) {
1000 if ($ev['element_name'] == $v) {
1001 $new_extra_fields[$new_id][] = $ev['element_id'];
1006 $value = $this->ArtifactType->getElementName($value);
1007 foreach($values as $ev) {
1008 if ($ev['element_name'] == $value) {
1009 $new_extra_fields[$new_id] = $ev['element_id'];
1017 // Special case if moving to a tracker with custom status (previous has not).
1018 $custom_status_id = $newArtifactType->getCustomStatusField();
1019 if ($custom_status_id && !$new_extra_fields[$custom_status_id]) {
1020 $atw = new ArtifactWorkflow($newArtifactType, $custom_status_id);
1021 $nodes = $atw->getNextNodes(100);
1023 $new_extra_fields[$custom_status_id] = $nodes[0];
1027 $extra_fields = $new_extra_fields;
1029 $res = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1',
1030 array ($this->getID()));
1032 $this->setError(_('Removal of old artifact_extra_field_data failed: ').db_error());
1037 // Check that assigned_to is a tech in the new tracker
1038 if ($assigned_to != 100) {
1039 if (!forge_check_perm ('tracker', $newArtifactType->getID(), 'tech')) {
1044 //can't send a canned response when changing ArtifactType
1045 $canned_response=100;
1046 $this->ArtifactType =& $newArtifactType;
1050 $qpa = db_construct_qpa();
1051 $qpa = db_construct_qpa($qpa, 'UPDATE artifact SET');
1054 // handle audit trail
1057 if ($this->getStatusID() != $status_id) {
1058 $this->addHistory('status_id',$this->getStatusID());
1059 $qpa = db_construct_qpa($qpa, ' status_id=$1,', array($status_id));
1060 $changes['status'] = 1;
1063 if ($status_id != 1) {
1064 $qpa = db_construct_qpa($qpa, ' close_date=$1,', array($now));
1066 $qpa = db_construct_qpa($qpa, ' close_date=$1,', array(0));
1068 $this->addHistory('close_date', $this->getCloseDate());
1070 if ($this->getPriority() != $priority) {
1071 $this->addHistory('priority',$this->getPriority());
1072 $qpa = db_construct_qpa($qpa, ' priority=$1,', array($priority));
1073 $changes['priority'] = 1;
1077 if ($this->getAssignedTo() != $assigned_to) {
1078 $this->addHistory('assigned_to',$this->getAssignedTo());
1079 $qpa = db_construct_qpa($qpa, ' assigned_to=$1,', array($assigned_to));
1080 $changes['assigned_to'] = 1;
1083 if ($summary && ($this->getSummary() != htmlspecialchars($summary))) {
1084 $this->addHistory('summary', $this->getSummary());
1085 $qpa = db_construct_qpa($qpa, ' summary=$1,', array(htmlspecialchars($summary)));
1086 $changes['summary'] = 1;
1089 if ($description && ($this->getDetails() != htmlspecialchars($description))) {
1090 $this->addHistory('details', $this->getDetails());
1091 $qpa = db_construct_qpa($qpa, ' details=$1,', array(htmlspecialchars($description)));
1092 $changes['details'] = 1;
1096 $this->addMessage($details,'',0);
1097 $changes['details'] = 1;
1102 Finally, update the artifact itself
1105 $qpa = db_construct_qpa($qpa, ' group_artifact_id=$1
1106 WHERE artifact_id=$2 AND group_artifact_id=$3',
1107 array($new_artifact_type_id,
1108 $this->getID(), $artifact_type_id));
1109 $result = db_query_qpa($qpa);
1111 if (!$result || db_affected_rows($result) < 1) {
1112 $this->setError(_('Error - update failed!').db_error());
1116 if (!$this->fetchData($this->getID())) {
1123 //extra field handling
1125 if (!$this->updateExtraFields($extra_fields,$changes)) {
1126 //TODO - see if anything actually did change
1132 handle canned responses
1134 Instantiate ArtifactCanned and get the body of the message
1136 if ($canned_response != 100) {
1137 //don't care if this response is for this group - could be hacked
1138 $acr=new ArtifactCanned($this->ArtifactType,$canned_response);
1139 if (!$acr || !is_object($acr)) {
1140 $this->setError(_('Could Not Create Canned Response Object'));
1141 } elseif ($acr->isError()) {
1142 $this->setError($acr->getErrorMessage());
1144 $body = $acr->getBody();
1146 if (!$this->addMessage(util_unconvert_htmlspecialchars($body),'',0)) {
1153 $this->setError(_('Unable to Use Canned Response'));
1159 if ($update || $send_message){
1160 if (!empty($changes)) {
1161 // Send the email with changes
1162 $this->mailFollowupEx($now, 2, false, $changes);
1167 //nothing changed, so cancel the transaction
1168 $this->setError(_('Nothing Changed - Update Cancelled'));
1176 * updateLastModifiedDate - update the last_modified_date attribute of this artifact.
1178 * @return true on success / false on failure
1180 function updateLastModifiedDate() {
1181 $res = db_query_params ('UPDATE artifact SET last_modified_date=EXTRACT(EPOCH FROM now())::integer WHERE artifact_id=$1',
1182 array ($this->getID()));
1187 * assignToMe - assigns this artifact to current user
1189 * @return true on success / false on failure
1191 function assignToMe() {
1192 if (!session_loggedin() || !($this->ArtifactType->userIsAdmin() || $this->ArtifactType->userIsTechnician())) {
1193 $this->setPermissionDeniedError();
1197 $user_id = user_getid();
1198 $res = db_query_params ('UPDATE artifact SET assigned_to=$1 WHERE artifact_id=$2',
1199 array ($user_id, $this->getID())) ;
1201 $this->setError(_('Error updating assigned_to in artifact: ').db_error());
1204 $this->fetchData($this->getID());
1210 * updateExtraFields - updates the extra data elements for this artifact
1211 * e.g. the extra fields created and defined by the admin.
1213 * @param array Array of extra fields like: array(15=>'foobar',22=>'1');
1214 * @param array Array where changes to the extra fields should be logged
1215 * @return true on success / false on failure
1217 function updateExtraFields($extra_fields,&$changes){
1219 This is extremely complex code - we have take the passed array
1220 and see if we need to insert it into the db, and may have to
1221 add history rows for the audit trail
1223 start by getting all the available extra fields from ArtifactType
1224 For each field from ArtifacType, check the passed array -
1225 This prevents someone from passing bogus extra field entries - they will be ignored
1226 if the passed entry is blank, may have to force a default value
1227 if the passed array is different from the existing data in db,
1228 delete old entry and insert new entries, along with possible audit trail
1230 skip it and continue to next item
1233 if (empty($extra_fields)) {
1238 //get a list of extra fields for this artifact_type
1239 $ef = $this->ArtifactType->getExtraFields();
1240 $efk=array_keys($ef);
1242 // If there is a status field, then check against the workflow.
1243 // Unless if we change type.
1244 if (! isset($changes['Type']) || !$changes['Type']) {
1245 for ($i=0; $i<count($efk); $i++) {
1247 $type=$ef[$efid]['field_type'];
1248 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1249 // Get previous value.
1250 $res = db_query_params ('SELECT field_data FROM artifact_extra_field_data
1251 WHERE artifact_id=$1 AND extra_field_id=$2',
1252 array($this->getID(),
1254 $old = (db_numrows($res)>0) ? db_result($res,0,'field_data') : 100;
1255 if ($old != $extra_fields[$efid]) {
1256 $atw = new ArtifactWorkflow($this->ArtifactType, $efid);
1257 if (!$atw->checkEvent($old, $extra_fields[$efid])) {
1258 $this->setError('Workflow error: You are not authorized to change the Status ('.$old.' => '.$extra_fields[$efid].')');
1266 //now we'll update this artifact for each extra field
1267 for ($i=0; $i<count($efk); $i++) {
1269 $type=$ef[$efid]['field_type'];
1271 // check required fields
1272 if ($ef[$efid]['is_required']) {
1273 if (!array_key_exists($efid, $extra_fields)) {
1274 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1275 $this->setError(_('Status Custom Field Must Be Set'));
1278 $this->setMissingParamsError($ef[$efid]['field_name']);
1283 if ($extra_fields[$efid] === '') {
1284 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1285 $this->setError(_('Status Custom Field Must Be Set'));
1288 $this->setMissingParamsError($ef[$efid]['field_name']);
1293 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT || $type == ARTIFACT_EXTRAFIELDTYPE_RADIO) &&
1294 $extra_fields[$efid] == '100') {
1295 $this->setMissingParamsError($ef[$efid]['field_name']);
1298 elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) &&
1299 (count($extra_fields[$efid]) == 1 && $extra_fields[$efid][0] == '100')) {
1300 $this->setMissingParamsError($ef[$efid]['field_name']);
1307 // Force each field to have some value if it is a numeric field
1308 // text fields will just be purged and skipped
1310 if (!array_key_exists($efid, $extra_fields) || $extra_fields[$efid] === '') {
1311 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO)) {
1312 $extra_fields[$efid]='100';
1313 } elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX)) {
1314 $extra_fields[$efid]=array('100');
1316 $resdel = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1317 array ($this->getID(),
1324 // get the old rows of data
1326 $resd = db_query_params ('SELECT * FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1327 array ($this->getID(),
1329 $rows=db_numrows($resd);
1330 if ($resd && $rows) {
1332 //POTENTIAL PROBLEM - no entry was there before, but adding one now - may need history
1335 // Compare for history purposes
1338 // these types have arrays associated to them, so they need
1339 // special handling to check for differences
1340 if ($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) {
1341 // check the differences between the old values and the new values
1342 $old_values = util_result_column_to_array($resd,"field_data");
1344 $added_values = array_diff($extra_fields[$efid], $old_values);
1345 $deleted_values = array_diff($old_values, $extra_fields[$efid]);
1347 if (!empty($added_values) || !empty($deleted_values)) { // there are differences...
1348 $field_name = $ef[$efid]['field_name'];
1349 if (!preg_match('/^@/', $ef[$efid]['alias'])) {
1350 $changes["extra_fields"][$efid] = 1;
1353 $this->addHistory($field_name, $this->ArtifactType->getElementName(array_reverse($old_values)));
1356 $resdel = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1357 array ($this->getID(),
1362 } elseif (db_result($resd,0,'field_data') == htmlspecialchars($extra_fields[$efid])) {
1363 //element did not change
1366 //element DID change - do a history entry
1367 $field_name = $ef[$efid]['field_name'];
1368 if (!preg_match('/^@/', $ef[$efid]['alias'])) {
1369 $changes["extra_fields"][$efid] = 1;
1371 $resdel = db_query_params ('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1372 array ($this->getID(),
1375 // Adding history with previous value.
1376 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO) || ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS)) {
1377 $this->addHistory($field_name,$this->ArtifactType->getElementName(db_result($resd,0,'field_data')));
1379 $this->addHistory($field_name, db_result($resd,0,'field_data'));
1385 //no history for this extra field exists
1390 // Some rewrite & consistency checks on the relation type field.
1392 // 1) Convert syntax [#NNN] to NNN
1393 // 2) Allow multiple spaces as separator.
1394 // 3) Ensure that only integers are given.
1395 // 4) Ensure that id corresponds to valid tracker id.
1397 if ($type == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
1398 $value = preg_replace('/\[\#(\d+)\]/', "\\1", trim($extra_fields[$efid]));
1399 $value = preg_replace('/\\s+/', ' ', $value);
1401 foreach (explode(' ',$value) as $id) {
1402 if (preg_match('/^(\d+)$/', $id)) {
1403 // Control that the id is present in the db
1405 $res = db_query_params ('SELECT artifact_id FROM artifact WHERE artifact_id=$1',
1407 if (db_numrows($res) == 1) {
1410 $this->setError('Illegal id '.$id.', it\'s not a valid tracker id for field: '.$ef[$efid]['field_name'].'.'); // @todo: lang
1414 $this->setError('Illegal value '.$id.', only trackers id are allowed for field: '.$ef[$efid]['field_name'].'.'); // @todo: lang
1418 $extra_fields[$efid] = trim($new);
1421 // Ensure that only integer are allowed for type ARTIFACT_EXTRAFIELDTYPE_INTEGER
1422 if ($type == ARTIFACT_EXTRAFIELDTYPE_INTEGER) {
1423 $extra_fields[$efid] = trim($extra_fields[$efid]);
1424 if (!preg_match('/^[-+]?(\d+)$/', $extra_fields[$efid])) {
1425 $this->setError('Illegal value '.$extra_fields[$efid].' for field '.$ef[$efid]['field_name'].': Only integer is allowed.');
1428 if ($extra_fields[$efid] < -2147483648 || $extra_fields[$efid] > 2147483647) {
1429 $this->setError('Illegal value '.$extra_fields[$efid].' for field '.$ef[$efid]['field_name'].': Integer out of range (-2147483648 to +2147483647).');
1432 $extra_fields[$efid] = intval($extra_fields[$efid]);
1436 // See if anything was even passed for this extra_field_id
1438 if ($extra_fields[$efid] === '') {
1439 //nothing in field to update - text fields may be blank
1441 //determine the type of field and whether it should have multiple rows supporting it
1442 $type=$ef[$efid]['field_type'];
1443 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1445 $count=count($extra_fields[$efid]);
1446 for ($fin=0; $fin<$count; $fin++) {
1447 $res = db_query_params ('INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) VALUES ($1,$2,$3)',
1448 array ($this->getID(),
1450 $extra_fields[$efid][$fin])) ;
1452 $this->setError(db_error());
1459 $res = db_query_params ('INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) VALUES ($1,$2,$3)',
1460 array ($this->getID(),
1462 htmlspecialchars($extra_fields[$efid]))) ;
1464 $this->setError(db_error());
1471 unset($this->extra_field_data);
1474 $this->updateLastModifiedDate();
1480 * getExtraFieldData - get an array of data for the extra fields associated with this artifact
1482 * @return array array of data
1484 function &getExtraFieldData() {
1485 if (!isset($this->extra_field_data)) {
1486 $this->extra_field_data = array();
1487 $res = db_query_params ('SELECT * FROM artifact_extra_field_data WHERE artifact_id=$1 ORDER BY extra_field_id',
1488 array ($this->getID())) ;
1489 $ef = $this->ArtifactType->getExtraFields();
1490 while ($arr = db_fetch_array($res)) {
1491 $type=$ef[$arr['extra_field_id']]['field_type'];
1492 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1493 //accumulate a sub-array of values in cases where you may have multiple rows
1494 if (!array_key_exists($arr['extra_field_id'], $this->extra_field_data) || !is_array($this->extra_field_data[$arr['extra_field_id']])) {
1495 $this->extra_field_data[$arr['extra_field_id']] = array();
1497 $this->extra_field_data[$arr['extra_field_id']][]=$arr['field_data'];
1499 $this->extra_field_data[$arr['extra_field_id']]=$arr['field_data'];
1503 return $this->extra_field_data;
1507 * marker - adds the > symbol to fields that have been modified for the email message
1511 function marker($prop_name,$changes,$extra_field_id=0) {
1512 if ($prop_name == 'extra_fields' && isset($changes[$prop_name][$extra_field_id])) {
1514 } else if ($prop_name != 'extra_fields' && isset($changes[$prop_name])) {
1522 * mailFollowupEx - send out an email update for this artifact.
1524 * @param time_t Time of the change
1525 * @param int (1) initial/creation (2) update.
1526 * @param array Array of additional addresses to mail to.
1527 * @param array Array of fields changed in this update .
1529 * @return boolean success.
1531 function mailFollowupEx($tm,$type,$more_addresses=false,$changes='') {
1533 $monitor_ids = array();
1539 $sess = session_get_user() ;
1540 if ($type == 1) { // Initial opening
1542 $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 () ;
1544 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() ." item #". $this->getID() .", was opened at ". date( _('Y-m-d H:i'), $this->getOpenDate() ) ;
1548 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() .
1549 " item #" . $this->getID() .
1550 " was changed at " .
1551 date(_('Y-m-d H:i'), $tm) . " by " .
1552 $sess->getRealName();
1554 $body = $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() .
1555 " item #" . $this->getID() .
1556 " was changed at " .
1557 date(_('Y-m-d H:i'), $tm);
1562 $body .= "\nYou can respond by visiting: ".
1563 "\n".util_make_url ('/tracker/?func=detail&atid='. $this->ArtifactType->getID() .
1564 "&aid=". $this->getID() .
1565 "&group_id=". $this->ArtifactType->Group->getID()) .
1566 "\nOr by replying to this e-mail entering your response between the following markers: ".
1567 "\n".ARTIFACT_MAIL_MARKER.
1568 "\n(enter your response here, only in plain text format)".
1569 "\n".ARTIFACT_MAIL_MARKER.
1571 $this->marker('status',$changes).
1572 "Status: ". $this->getStatusName() ."\n".
1573 $this->marker('priority',$changes).
1574 "Priority: ". $this->getPriority() ."\n".
1575 "Submitted By: ". $this->getSubmittedRealName() .
1576 " (". $this->getSubmittedUnixName(). ")"."\n".
1577 $this->marker('assigned_to',$changes).
1578 "Assigned to: ". $this->getAssignedRealName() .
1579 " (". $this->getAssignedUnixName(). ")"."\n".
1580 $this->marker('summary',$changes).
1581 "Summary: ". util_unconvert_htmlspecialchars( $this->getSummary() )." \n";
1583 // Now display the extra fields
1584 $efd = $this->getExtraFieldDataText();
1585 foreach ($efd as $efid => $ef) {
1586 $body .= $this->marker('extra_fields', $changes, $efid);
1587 $body .= $ef["name"].": ".$ef["value"]."\n";
1590 $subject='['. $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() . '][' . $this->getID() .'] '. util_unconvert_htmlspecialchars( $this->getSummary() );
1593 // get all the email addresses that are monitoring this request or the ArtifactType
1594 $monitor_ids = $this->getMonitorIds();
1596 // initial creation, we just get the users monitoring the ArtifactType
1597 $monitor_ids = $this->ArtifactType->getMonitorIds();
1601 if ($more_addresses) {
1602 $emails[] = $more_addresses;
1604 //we don't email the current user
1605 if ($this->getAssignedTo() != user_getid()) {
1606 $monitor_ids[] = $this->getAssignedTo();
1608 if ($this->getSubmittedBy() != user_getid()) {
1609 $monitor_ids[] = $this->getSubmittedBy();
1611 //initial submission
1613 //if an email is set for this ArtifactType
1614 //add that address to the BCC: list
1615 if ($this->ArtifactType->getEmailAddress()) {
1616 $emails[] = $this->ArtifactType->getEmailAddress();
1620 if ($this->ArtifactType->emailAll()) {
1621 $emails[] = $this->ArtifactType->getEmailAddress();
1625 $body .= "\n\nInitial Comment:".
1626 "\n".util_unconvert_htmlspecialchars( $this->getDetails() ) .
1627 "\n\n----------------------------------------------------------------------";
1631 Now include the followups
1633 $result2=$this->getMessages();
1635 $rows=db_numrows($result2);
1637 if ($result2 && $rows > 0) {
1638 for ($i=0; $i<$rows; $i++) {
1640 // for messages posted by non-logged-in users,
1641 // we grab the email they gave us
1643 // otherwise we use the confirmed one from the users table
1645 if (db_result($result2,$i,'user_id') == 100) {
1646 $emails[] = db_result($result2,$i,'from_email');
1648 $monitor_ids[] = db_result($result2,$i,'user_id');
1654 $body .= $this->marker('details',$changes);
1656 $body .= "Comment By: ". db_result($result2,$i,'realname') . " (".db_result($result2,$i,'user_name').")".
1657 "\nDate: ". date( _('Y-m-d H:i'),db_result($result2,$i,'adddate') ).
1659 "\n".util_unconvert_htmlspecialchars( db_result($result2,$i,'body') ).
1660 "\n\n----------------------------------------------------------------------";
1666 $body .= "\n\nYou can respond by visiting: ".
1667 "\n".util_make_url ('/tracker/?func=detail&atid='. $this->ArtifactType->getID() .
1668 "&aid=". $this->getID() .
1669 "&group_id=". $this->ArtifactType->Group->getID());
1671 //only send if some recipients were found
1672 if (count($emails) < 1 && count($monitor_ids) < 1) {
1676 if (count($monitor_ids) < 1) {
1677 $monitor_ids=array();
1679 $monitor_ids=array_unique($monitor_ids);
1682 $from = $this->ArtifactType->getReturnEmailAddress();
1683 $extra_headers = 'Reply-to: '.$from;
1685 // load the e-mail addresses of the users
1686 $users =& user_get_objects($monitor_ids);
1687 if (count($users) > 0) {
1688 foreach ($users as $user) {
1689 if ($user->getStatus() == "A") { //we are only sending emails to active users
1690 $emails[] = $user->getEmail();
1695 //now remove all duplicates from the email list
1696 if (count($emails) > 0) {
1697 $BCC=implode(',',array_unique($emails));
1698 util_send_message('',$subject,$body,$from,$BCC,'',$extra_headers);
1701 $this->sendSubjectMsg = $subject;
1702 $this->sendBodyMsg = $body;
1704 //util_handle_message($monitor_ids,$subject,$body,$BCC);
1710 * getExtraFieldDataText - Return the extra fields' data in a human-readable form.
1712 * @return array Array containing field ID => field name and value associated to it for
1715 function getExtraFieldDataText() {
1716 // First we get the list of extra fields and the data
1717 // associated to the fields
1718 $efs = $this->ArtifactType->getExtraFields();
1719 $efd = $this->getExtraFieldData();
1723 foreach ($efs as $efid => $ef) {
1724 $name = $ef["field_name"];
1725 $type = $ef["field_type"];
1727 // Get the value according to the type
1730 // for these types, the associated value comes straight
1731 case ARTIFACT_EXTRAFIELDTYPE_TEXT:
1732 case ARTIFACT_EXTRAFIELDTYPE_TEXTAREA:
1733 case ARTIFACT_EXTRAFIELDTYPE_RELATION:
1734 case ARTIFACT_EXTRAFIELDTYPE_INTEGER:
1735 if (isset($efd[$efid])) {
1736 $value = $efd[$efid];
1742 // the other types have and ID or an array of IDs associated to them
1744 if (isset($efd[$efid])) {
1745 $value = $this->ArtifactType->getElementName($efd[$efid]);
1751 $return[$efid] = array("name" => $name, "value" => $value, 'type' => $type);
1758 class ArtifactComparator {
1759 var $criterion = 'artifact_id' ;
1760 var $order = 'ASC' ;
1762 function Compare ($a, $b) {
1763 if ($this->order == 'DESC') {
1764 $c = $a ; $a = $b ; $b = $c ;
1766 switch ($this->criterion) {
1768 $namecmp = strcoll ($a->getSummary(),
1770 if ($namecmp != 0) {
1775 $namecmp = strcoll (user_get_object($a->getAssignedTo())->getRealName(),
1776 user_get_object($b->getAssignedTo())->getRealName()) ;
1777 if ($namecmp != 0) {
1781 case 'submitted_by':
1782 $namecmp = strcoll (user_get_object($a->getSubmittedBy())->getRealName(),
1783 user_get_object($b->getSubmittedBy())->getRealName()) ;
1784 if ($namecmp != 0) {
1789 $a_date = $a->getOpenDate() ;
1790 $b_date = $b->getOpenDate() ;
1791 return ($a_date < $b_date) ? -1 : 1;
1794 $a_date = $a->getCloseDate() ;
1795 $b_date = $b->getCloseDate() ;
1796 return ($a_date < $b_date) ? -1 : 1;
1798 case 'last_modified_date':
1799 $a_date = $a->getLastModifiedDate() ;
1800 $b_date = $b->getLastModifiedDate() ;
1801 return ($a_date < $b_date) ? -1 : 1;
1804 $a_prority = $a->getPriority() ;
1805 $b_prority = $b->getPriority() ;
1806 return ($a_prority < $b_prority) ? -1 : 1;
1809 $aa=$a->getExtraFieldDataText();
1810 $ba=$b->getExtraFieldDataText();
1811 $af=$aa[$this->criterion]['value'];
1812 $bf=$ba[$this->criterion]['value'];
1813 $namecmp = strcoll ($af,$bf) ;
1814 if ($namecmp != 0) {
1820 // When in doubt, sort on artifact ID
1821 $aid = $a->getID() ;
1822 $bid = $b->getID() ;
1826 return ($aid < $bid) ? -1 : 1;
1830 function sortArtifactList (&$list, $criterion='name', $order='ASC') {
1831 $cmp = new ArtifactComparator () ;
1832 $cmp->criterion = $criterion ;
1833 $cmp->order = $order ;
1835 return usort ($list, array ($cmp, 'Compare')) ;
1840 // c-file-style: "bsd"