5 * Copyright 1999-2001, VA Linux Systems, Inc.
6 * Copyright 2002-2004, GForge, LLC
7 * Copyright 2009, Roland Mas
8 * Copyright (C) 2009-2013 Alain Peyrat, Alcatel-Lucent
9 * Copyright 2012, Thorsten “mirabilos” Glaser <t.glaser@tarent.de>
10 * Copyright 2014-2017,2019, Franck Villaume - TrivialDev
11 * Copyright 2016-2017, Stéphane-Eymeric Bredthauer - TrivialDev
13 * This file is part of FusionForge. FusionForge is free software;
14 * you can redistribute it and/or modify it under the terms of the
15 * GNU General Public License as published by the Free Software
16 * Foundation; either version 2 of the Licence, or (at your option)
19 * FusionForge is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License along
25 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
30 * Standard Alcatel-Lucent disclaimer for contributing to open source
32 * "The Artifact ("Contribution") has not been tested and/or
33 * validated for release as or in products, combinations with products or
34 * other commercial use. Any use of the Contribution is entirely made at
35 * the user's own responsibility and the user can not rely on any features,
36 * functionalities or performances Alcatel-Lucent has attributed to the
39 * THE CONTRIBUTION BY ALCATEL-LUCENT IS PROVIDED AS IS, WITHOUT WARRANTY
40 * OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
41 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, COMPLIANCE,
42 * NON-INTERFERENCE AND/OR INTERWORKING WITH THE SOFTWARE TO WHICH THE
43 * CONTRIBUTION HAS BEEN MADE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
44 * ALCATEL-LUCENT BE LIABLE FOR ANY DAMAGES OR OTHER LIABLITY, WHETHER IN
45 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
46 * CONTRIBUTION OR THE USE OR OTHER DEALINGS IN THE CONTRIBUTION, WHETHER
47 * TOGETHER WITH THE SOFTWARE TO WHICH THE CONTRIBUTION RELATES OR ON A STAND
50 require_once $gfcommon.'include/FFObject.class.php';
51 require_once $gfcommon.'tracker/ArtifactFile.class.php';
52 require_once $gfcommon.'tracker/ArtifactMessage.class.php';
53 require_once $gfcommon.'tracker/ArtifactExtraField.class.php';
54 require_once $gfcommon.'tracker/ArtifactWorkflow.class.php';
55 require_once $gfcommon.'tracker/ArtifactStorage.class.php';
56 require_once $gfcommon.'include/MonitorElement.class.php';
57 require_once $gfcommon.'tracker/Effort.class.php';
59 // This string is used when sending the notification mail for identifying the
61 define('ARTIFACT_MAIL_MARKER', '#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+');
63 $ARTIFACT_OBJ = array();
66 * Factory method which creates an Artifact from an artifact ID
68 * @param int $artifact_id The artifact ID
69 * @param array|bool $data The result array, if it's passed in
70 * @return Artifact Artifact object
72 function &artifact_get_object($artifact_id, $data = false) {
74 if (!isset($ARTIFACT_OBJ["_".$artifact_id."_"])) {
76 //the db result handle was passed in
78 $res = db_query_params('SELECT * FROM artifact_vw WHERE artifact_id=$1', array($artifact_id));
79 if (db_numrows($res) < 1) {
80 $ARTIFACT_OBJ["_".$artifact_id."_"] = false;
81 return $ARTIFACT_OBJ["_".$artifact_id."_"];
83 $data = db_fetch_array($res);
85 $ArtifactType =& artifactType_get_object($data["group_artifact_id"]);
86 $ARTIFACT_OBJ["_".$artifact_id."_"] = new Artifact($ArtifactType, $data);
88 return $ARTIFACT_OBJ["_".$artifact_id."_"];
91 class Artifact extends FFObject {
96 * @var int $status_res.
101 * Artifact Type object.
103 * @var object $ArtifactType.
108 * Array of artifact data.
110 * @var array $data_array.
115 * Array of artifact data for extra fields defined by Admin.
117 * @var array $extra_field_data.
119 var $extra_field_data;
122 * Array of ArtifactFile objects.
129 * Database result set of related tasks
131 * @var result $related_tasks
136 * Database result set of children
138 * @var array $children
140 var $children = array();
143 * Database result set of parent
145 * @var int|bool $parent
150 * cached return value of getVotes
151 * @var int|bool $votes
156 * cached result set of related artifacts
158 * @var array $artifact_relations
160 var $artifact_relations;
163 * @param ArtifactType $ArtifactType The ArtifactType object.
164 * @param int|bool $data (primary key from database OR complete assoc array)
165 * ONLY OPTIONAL WHEN YOU PLAN TO IMMEDIATELY CALL ->create()
167 function __construct(&$ArtifactType, $data = false) {
168 if (is_array($data)) {
169 parent::__construct($data['artifact_id'], 'Artifact');
170 } elseif (is_int($data)) {
171 parent::__construct($data, 'Artifact');
173 parent::__construct();
176 $this->ArtifactType =& $ArtifactType;
178 // Was ArtifactType legit?
179 if (!$ArtifactType || !is_object($ArtifactType)) {
180 $this->setError(_('Invalid Artifact Type'));
184 // Did ArtifactType have an error?
185 if ($ArtifactType->isError()) {
186 $this->setError($ArtifactType->getErrorMessage());
190 // Make sure this person has permission to view artifacts
191 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'read')) {
192 $this->setError(_('Only project members can view private artifact types'));
197 if (is_array($data)) {
198 $this->data_array =& $data;
200 $this->fetchData($data);
206 * create - construct a new Artifact in the database.
208 * @param string $summary The artifact summary.
209 * @param string $details Details of the artifact.
210 * @param int $assigned_to The ID of the user to which this artifact is to be assigned.
211 * @param int $priority The artifacts priority.
212 * @param array $extra_fields Array of extra fields like: array(15=>'foobar',22=>'1');
213 * @param array $importData Array of data to change submitter and time of submit like:
214 * array('user' => 127, 'time' => 1234556789, 'nopermcheck' => true, 'nonotice' => true)
215 * @return bool id on success / false on failure.
217 function create($summary, $details, $assigned_to = 100, $priority = 3, $extra_fields = array(), $importData = array()) {
219 // make sure this person has permission to add artifacts
222 if (isset($importData['nonotice']) && $importData['nonotice']) {
231 if(array_key_exists('user', $importData)){
232 $user = $importData['user'];
233 if (!forge_check_perm_for_user($user, 'tracker', $this->ArtifactType->getID(), 'submit')) {
234 $this->setError(_('The provided user is not currently allowed to submit items to this tracker.'));
238 if (!forge_check_perm ('tracker',$this->ArtifactType->getID(),'submit')) {
239 $this->setError(_('You are not currently allowed to submit items to this tracker.'));
243 $user = ((session_loggedin()) ? user_getid() : 100);
250 $this->setError(_('Message Summary Is Required'));
254 $this->setError(_('Message Body Is Required'));
258 $status_id=1; // on creation, status is set to "open"
261 // They may be using an extra field "status" box so we have to remap
262 // the status_id based on the extra field - this keeps the counters
265 $status_id = $this->ArtifactType->remapStatus($status_id,$extra_fields);
267 $this->setError($this->ArtifactType->getErrorMessage());
272 if (array_key_exists('time', $importData)){
273 $time = $importData['time'];
277 $res = db_query_params('INSERT INTO artifact
278 (group_artifact_id, status_id, priority,
279 submitted_by, assigned_to, open_date, last_modified_date, last_modified_by, summary, details)
280 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)',
281 array($this->ArtifactType->getID(),
289 htmlspecialchars($summary),
290 htmlspecialchars($details)));
292 $this->setError(db_error());
297 $artifact_id = db_insertid($res, 'artifact', 'artifact_id');
299 if (!$res || !$artifact_id) {
300 $this->setError(db_error());
305 // Now set up our internal data structures
307 if (!$this->fetchData($artifact_id)) {
311 // the changes to the extra fields will be logged in this array.
312 // (we won't use it however)
313 $extra_field_changes = array();
314 if (!$this->updateExtraFields($extra_fields, $extra_field_changes, $importData)) {
320 // now send an email if appropriate
323 $this->mailFollowupEx(0, 1);
332 * fetchData - re-fetch the data for this Artifact from the database.
334 * @param int $artifact_id The artifact ID.
335 * @return bool success.
337 function fetchData($artifact_id) {
338 $this->votes = false;
339 $res = db_query_params ('SELECT * FROM artifact_vw WHERE artifact_id=$1 AND group_artifact_id=$2',
341 $this->ArtifactType->getID()));
342 if (!$res || db_numrows($res) < 1) {
343 $this->setError(_('Invalid Artifact ID'));
346 $this->data_array = db_fetch_array($res);
347 db_free_result($res);
352 * getArtifactType - get the ArtifactType Object this Artifact is associated with.
354 * @return object ArtifactType.
356 function &getArtifactType() {
357 return $this->ArtifactType;
361 * getID - get this ArtifactID.
363 * @return int The artifact_id #.
366 return $this->data_array['artifact_id'];
370 * getStringID - get a string display for this ArtifactID.
372 * @return string The artifact_id #.
374 function getStringID() {
375 return '[#'.$this->data_array['artifact_id'].']';
379 * getStatusID - get open/closed/deleted flag.
381 * @return int Status: (1) Open, (2) Closed, (3) Deleted.
383 function getStatusID() {
384 return $this->data_array['status_id'];
388 * getStatusName - get open/closed/deleted text.
390 * @return string The status name.
392 function getStatusName() {
393 return $this->data_array['status_name'];
397 * getCustomStatusName - get custom status value text.
399 * @return string The custom status name.
401 function getCustomStatusName() {
402 $custom_status_id = $this->ArtifactType->getCustomStatusField();
403 if ($custom_status_id) {
404 $result = db_query_params ('SELECT element_name FROM artifact_extra_field_elements aefe, artifact_extra_field_data aefd
405 WHERE artifact_id=$1 AND aefd.extra_field_id=$2 AND CAST(aefd.field_data AS INTEGER)=aefe.element_id',
406 array($this->getID(), $custom_status_id));
408 return db_result($result, 0, 'element_name');
411 return $this->data_array['status_name'];
415 * getPriority - get priority flag.
417 * @return int priority.
419 function getPriority() {
420 return $this->data_array['priority'];
424 * getSubmittedBy - get ID of submitter.
426 * @return int user_id of submitter.
428 function getSubmittedBy() {
429 return $this->data_array['submitted_by'];
433 * getSubmittedEmail - get email of submitter.
435 * @return string The email of submitter.
437 function getSubmittedEmail() {
438 return $this->data_array['submitted_email'];
442 * getSubmittedRealName - get real name of submitter.
444 * @return string The real name of submitter.
446 function getSubmittedRealName() {
447 return $this->data_array['submitted_realname'];
451 * getSubmittedUnixName - get login name of submitter.
453 * @return string The unix name of submitter.
455 function getSubmittedUnixName() {
456 return $this->data_array['submitted_unixname'];
460 * getAssignedTo - get ID of assignee.
462 * @return int user_id of assignee.
464 function getAssignedTo() {
465 return $this->data_array['assigned_to'];
469 * getAssignedEmail - get email of assignee.
471 * @return string The email of assignee.
473 function getAssignedEmail() {
474 return $this->data_array['assigned_email'];
478 * getAssignedRealName - get real name of assignee.
480 * @return string The real name of assignee.
482 function getAssignedRealName() {
483 return $this->data_array['assigned_realname'];
487 * getAssignedUnixName - get login name of assignee.
489 * @return string The unix name of assignee.
491 function getAssignedUnixName() {
492 return $this->data_array['assigned_unixname'];
496 * getOpenDate - get unix time of creation.
498 * @return int unix time.
500 function getOpenDate() {
501 return $this->data_array['open_date'];
505 * getCloseDate - get unix time of closure.
507 * @return int unix time.
509 function getCloseDate() {
510 return $this->data_array['close_date'];
514 * getLastModifiedDate - the last_modified_date of this task.
516 * @return int the last_modified_date.
518 function getLastModifiedDate() {
519 return $this->data_array['last_modified_date'];
523 * getLastModifiedBy - get ID of last modifier.
525 * @return int user_id of modifier.
527 function getLastModifiedBy() {
528 return $this->data_array['last_modified_by'];
532 * getLastModifiedEmail - get email of modifier.
534 * @return string The email of modifier.
536 function getLastModifiedEmail() {
537 return $this->data_array['last_modified_email'];
541 * getLastModifiedRealName - get real name of modifier.
543 * @return string The real name of modifier.
545 function getLastModifiedRealName() {
546 return $this->data_array['last_modified_realname'];
550 * getLastModifiedUnixName - get login name of modifier.
552 * @return string The unix name of modifier.
554 function getLastModifiedUnixName() {
555 return $this->data_array['last_modified_unixname'];
559 * getSummary - get text summary of artifact.
561 * @return string The summary (subject).
563 function getSummary() {
564 return $this->data_array['summary'];
568 * getDetails - get text body (message) of artifact.
570 * @return string The body (message).
572 function getDetails() {
573 return $this->data_array['details'];
577 * delete - "delete" this artifact and all its related data, artifact is taged deleted
579 * @param bool $sure I'm Sure.
580 * @return bool true/false;
582 function delete($sure) {
584 $this->setMissingParamsError(_('Please tick all checkboxes.'));
587 if (!forge_check_perm('tracker_admin', $this->ArtifactType->Group->getID())) {
588 $this->setPermissionDeniedError();
593 $MonitorElementObject = new MonitorElement('artifact');
594 if (!$MonitorElementObject->clearMonitor($this->getID())) {
595 $this->setError(_('Error deleting monitor')._(': ').db_error());
600 $res = db_query_params('UPDATE artifact SET is_deleted=1 WHERE artifact_id=$1',
601 array($this->getID()));
603 $this->setError(_('Error deleting artifact')._(': ').db_error());
608 if (!$this->removeAllAssociations()) {
609 // error message already set by FFObject class.
614 // update counts: /!\ counts is also update by a trigger artifactgroup_update_trig
615 // and 2 rules artifact_delete_agg (for real delete), artifact_insert_agg
616 if ($this->getStatusID() == 1) {
617 $res = db_query_params ('UPDATE artifact_counts_agg SET count=count-1,open_count=open_count-1
618 WHERE group_artifact_id=$1',
619 array($this->getArtifactType()->getID()));
621 $this->setError(_('Error updating artifact counts')._(': ').db_error());
625 } elseif ($this->getStatusID() == 2) {
626 $res = db_query_params ('UPDATE artifact_counts_agg SET count=count-1
627 WHERE group_artifact_id=$1',
628 array($this->getArtifactType()->getID()));
630 $this->setError(_('Error updating artifact counts')._(': ').db_error());
640 * setMonitor - user can monitor this artifact.
642 * @return bool Always false - always use the getErrorMessage() for feedback
644 function setMonitor() {
646 if (session_loggedin()) {
647 $user_id = user_getid();
649 $this->setError(_('You can only monitor if you are logged in.'));
653 $MonitorElementObject = new MonitorElement('artifact');
654 if (!$this->isMonitoring()) {
655 if (!$MonitorElementObject->enableMonitoringByUserId($this->getID(), $user_id)) {
656 $this->setError($MonitorElementObject->getErrorMessage());
659 $feedback = _('Monitoring Started');
662 if (!$MonitorElementObject->disableMonitoringByUserId($this->getID(), $user_id)) {
663 $this->setError($MonitorElementObject->getErrorMessage());
666 $feedback = _('Monitoring Stopped');
671 function isMonitoring() {
672 if (!session_loggedin()) {
675 $MonitorElementObject = new MonitorElement('artifact');
676 return $MonitorElementObject->isMonitoredByUserId($this->getID(), user_getid());
680 * getMonitorIds - get user ids monitoring this Artifact.
682 * @return array of user ids monitoring this Artifact.
684 function getMonitorIds() {
685 $MonitorElementObject = new MonitorElement('artifact');
686 return $MonitorElementObject->getMonitorUsersIdsInArray($this->getID());
690 * getHistory - returns a result set of audit trail for this support request.
692 * @return resource result set.
694 function getHistory() {
695 return db_query_params('SELECT * FROM artifact_history_user_vw WHERE artifact_id=$1 ORDER BY entrydate DESC, id ASC',
696 array($this->getID()));
699 function hasMessages() {
700 $res = db_query_params('SELECT count(id) FROM artifact_message WHERE artifact_id=$1', array($this->getID()));
701 $row = db_fetch_array($res);
706 * getMessages - get the list of messages attached to this artifact.
708 * @param string $ascending
709 * @return resource result set.
711 function getMessages($ascending = 'up') {
713 * This is necessary because someone committed a change
714 * to this method in FusionForge trunk that accepts 'up'
715 * as default (luckily, it’s the same!) and 'down' as
716 * alternative probability, whereas FusionForge 5.2 has
717 * false as default and true for ascending order, so we
718 * need to check this out and use === to be sure ☹
720 if ($ascending === 'up') {
722 } elseif ($ascending === true) {
724 } elseif ($ascending === false) {
729 return db_query_params('SELECT * FROM artifact_message_user_vw WHERE artifact_id=$1 ORDER BY adddate ' . $order . ', id ASC',
730 array($this->getID()));
734 * getMessage - get a message attached to this artifact.
736 * @param int $msg_id id of the message.
738 * @return resource|bool database result set.
740 function getMessage($msg_id) {
744 return db_query_params('SELECT * FROM artifact_message_user_vw WHERE id=$1',
749 * getMessageObjects - get an array of message objects.
751 * @return array Of ArtifactMessage objects.
753 function &getMessageObjects() {
754 $res=$this->getMessages();
756 while ($arr = db_fetch_array($res)) {
757 $return[] = new ArtifactMessage($this, $arr);
763 * getFiles - get array of ArtifactFile's.
765 * @return array of ArtifactFile's.
767 function &getFiles() {
768 if (!isset($this->files)) {
769 $res = db_query_params('SELECT id,artifact_id,description,filename,filesize,
770 filetype,adddate,submitted_by,user_name,realname
771 FROM artifact_file_user_vw WHERE artifact_id=$1',
772 array($this->getID()));
773 $rows=db_numrows($res);
775 for ($i=0; $i < $rows; $i++) {
776 $this->files[$i] = new ArtifactFile($this, db_fetch_array($res));
779 $this->files = array();
786 * getRelatedTasks - get array of related tasks
788 * @return resource Database result set
790 function getRelatedTasks() {
791 if (!$this->related_tasks) {
792 $this->related_tasks = 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
793 FROM project_task pt, project_group_list pgl, project_status ps
794 WHERE pt.group_project_id = pgl.group_project_id
795 AND ps.status_id = pt.status_id
796 AND EXISTS (SELECT project_task_id FROM project_task_artifact
797 WHERE project_task_id=pt.project_task_id
798 AND artifact_id = $1)',
799 array($this->getID()));
801 return $this->related_tasks;
805 * addMessage - attach a text message to this Artifact.
807 * @param string $body The $string message being attached.
808 * @param bool $by Email $string address of message creator.
809 * @param bool $send_followup Whether $bool to email out a followup.
810 * @param array $importData Array of data to change submitter and time of submit like:
811 * array('user' => 127, 'time' => 1234556789, 'nopermcheck' => true, 'nonotice' => true)
813 * @return bool|resource
815 function addMessage($body, $by = false, $send_followup = false, $importData = array()) {
817 $this->setMissingParamsError();
820 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'submit')) {
821 $this->setError(_('You are not currently allowed to submit items to this tracker.'));
825 if (isset($importData['nonotice']) && $importData['nonotice']) {
831 $artfm = new ArtifactMessage($this);
832 $id = $artfm->create($body, $by, $importData);
835 $this->updateLastModified($importData);
837 if ($send_followup && $sendNotice) {
838 $this->mailFollowupEx($time, 2, false);
845 * addHistory - add an entry to audit trail.
847 * @param string $field_name The name of the field in the database being modified.
848 * @param string $old_value The former value of this field.
849 * @param array $importData Array of data to change submitter and time of submit like:
850 * array('user' => 127, 'time' => 1234556789, 'nopermcheck' => true, 'nonotice' => true)
852 * @return bool|resource
854 function addHistory($field_name, $old_value, $importData = array()) {
855 if (array_key_exists('user', $importData)){
856 $user = $importData['user'];
858 $user = ((session_loggedin()) ? user_getid() : 100);
860 if (array_key_exists('time',$importData)){
861 $time = $importData['time'];
865 return db_query_params ('INSERT INTO artifact_history(artifact_id,field_name,old_value,mod_by,entrydate) VALUES ($1,$2,$3,$4,$5)',
866 array($this->getID(),
874 * setStatus - set the status of this artifact.
876 * @param int $status_id The artifact status ID.
877 * @param int|bool $closingTime Closing date if status = 1
879 * @return bool success.
881 function setStatus($status_id, $closingTime = false) {
883 $qpa = db_construct_qpa(array(), 'UPDATE artifact SET status_id=$1', array($status_id));
884 if ($closingTime && $status_id != 1) {
886 $qpa = db_construct_qpa($qpa, ', close_date=$1 ', array($time));
888 $qpa = db_construct_qpa($qpa,
889 'WHERE artifact_id=$1 AND group_artifact_id=$2',
890 array($this->getID(), $this->ArtifactType->getID()));
891 $result=db_query_qpa($qpa);
893 if (!$result || db_affected_rows($result) < 1) {
894 $this->setError('Error - update failed!'.db_error());
898 if (!$this->fetchData($this->getID())) {
910 * update - update the fields in this artifact.
912 * @param int $priority The artifact priority.
913 * @param int $status_id The artifact status ID.
914 * @param int $assigned_to The person to which this artifact is to be assigned.
915 * @param string $summary The artifact summary.
916 * @param int $canned_response The canned response.
917 * @param string $details Attaching another comment.
918 * @param int $new_artifact_type_id Allows you to move an artifact to another type.
919 * @param array $extra_fields Array of extra fields like: array(15=>'foobar',22=>'1');
920 * @param string $description The description.
921 * @param array $importData Array of data to change submitter and time of submit like:
922 * array('user' => 127, 'time' => 1234556789, 'nopermcheck' => true, 'nonotice' => true)
923 * @return bool success.
925 function update($priority,$status_id, $assigned_to, $summary, $canned_response, $details, $new_artifact_type_id, $extra_fields = array(), $description='', $importData = array()) {
927 if (array_key_exists('user', $importData)){
928 $user = $importData['user'];
930 $user = $this->getSubmittedBy();
932 if (isset($importData['nopermcheck']) && $importData['nopermcheck']) {
938 if (isset($importData['nonotice']) && $importData['nonotice']) {
944 Field-level permission checking
947 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'manager')) {
948 // Non-managers cannot modify these fields
949 $priority=$this->getPriority();
950 $summary=htmlspecialchars_decode($this->getSummary());
951 $description=htmlspecialchars_decode($this->getDetails());
952 $canned_response=100;
953 $new_artifact_type_id=$this->ArtifactType->getID();
954 $autoAssignField = $this->getArtifactType()->getAutoAssignField();
955 if ($autoAssignField!=100) {
956 $ef = new ArtifactExtraField($this->getArtifactType(),$autoAssignField);
957 if (!$ef || !is_object($ef)) {
958 exit_error(_('Unable to create ArtifactExtraField Object'),'tracker');
959 } elseif ($ef->isError()) {
960 exit_error($ef->getErrorMessage(),'tracker');
962 $efe = new ArtifactExtraFieldElement($ef,$extra_fields[$autoAssignField]);
963 if (!$efe || !is_object($efe)) {
964 exit_error(_('Unable to create ArtifactExtraFieldElement Object'),'tracker');
965 } elseif ($efe->isError()) {
966 exit_error($efe->getErrorMessage(),'tracker');
968 $assigned_to = $efe->getAutoAssignto();
972 $assigned_to = $this->getAssignedTo();
974 if (!forge_check_perm ('tracker', $this->ArtifactType->getID(), 'tech')) {
975 $this->setPermissionDeniedError();
981 // They may be using an extra field "status" box so we have to remap
982 // the status_id based on the extra field - this keeps the counters
985 if (!empty($extra_fields)) {
986 $status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
988 if (!$this->getID()) {
989 $this->setMissingParamsError('ID');
993 $this->setMissingParamsError(_('Assigned to'));
997 $this->setMissingParamsError(_('State'));
1000 if (!$canned_response) {
1001 $this->setMissingParamsError(_('Canned Response'));
1004 if (!$new_artifact_type_id) {
1005 $this->setMissingParamsError(_('Data Type'));
1009 // Check that assigned_to is a tech for the tracker
1010 if ($assigned_to != 100 && $permCheck) {
1011 if (!forge_check_perm_for_user($assigned_to, 'tracker', $this->ArtifactType->getID(), 'tech')) {
1012 $this->setError(_("Invalid assigned person: must be a technician"));
1017 // Check that the new submitting user can submit items
1018 if ($user != 100 && $permCheck) {
1019 if (!forge_check_perm_for_user($user, 'tracker', $this->ArtifactType->getID(), 'submit')) {
1020 $this->setError(_('The provided user is not currently allowed to submit items to this tracker.'));
1025 // Array to record which properties were changed
1032 // Get a lock on this row in the database
1034 db_query_params('SELECT * FROM artifact WHERE artifact_id=$1 FOR UPDATE', array($this->getID()));
1035 $artifact_type_id = $this->ArtifactType->getID();
1037 // Attempt to move this Artifact to a new ArtifactType
1038 // need to instantiate new ArtifactType obj and test perms
1040 if ($new_artifact_type_id != $artifact_type_id) {
1041 $newArtifactType= new ArtifactType($this->ArtifactType->getGroup(), $new_artifact_type_id);
1042 if (!is_object($newArtifactType) || $newArtifactType->isError()) {
1043 $this->setError(_('Could not move to new Artifact Type'). $newArtifactType->getErrorMessage());
1047 // do they have perms for new ArtifactType?
1048 if (!forge_check_perm ('tracker', $newArtifactType->getID(), 'manager') && $permCheck) {
1049 $this->setPermissionDeniedError();
1054 // Add a message to explain that the tracker was moved.
1055 $message = sprintf(_('Moved from %1$s to %2$s'), $this->ArtifactType->getName(), $newArtifactType->getName());
1056 $this->addHistory('type', $this->ArtifactType->getName(), $importData);
1057 $this->addMessage($message,'',0, $importData);
1059 // Fake change to send a mail when moved.
1060 $changes['Type'] = 1;
1062 // Try to remap extra_fields values when possible.
1063 // If there is an extra_field with the same alias
1064 // and if the value exist in the new one, then recode
1065 // the value to keep it.
1066 $new_extra_fields = array();
1067 $ef = $this->ArtifactType->getExtraFields();
1068 $ef_new = $newArtifactType->getExtraFields();
1069 foreach($extra_fields as $extra_id => $value) {
1070 $alias = preg_replace('/^@/', '', $ef[$extra_id]['alias']);
1071 $type = $ef[$extra_id]['field_type'];
1073 // Search if there is an extra field with the same alias.
1075 foreach($ef_new as $id => $arr) {
1076 if (preg_replace('/^@/', '', $arr['alias']) == $alias) {
1081 // If we found one, copy for simple fields or
1082 // search if there is the same value.
1084 if ($type == ARTIFACT_EXTRAFIELDTYPE_TEXT ||
1085 $type == ARTIFACT_EXTRAFIELDTYPE_INTEGER ||
1086 $type == ARTIFACT_EXTRAFIELDTYPE_TEXTAREA ||
1087 $type == ARTIFACT_EXTRAFIELDTYPE_RELATION) {
1088 $new_extra_fields[$new_id] = $value;
1090 $values = $newArtifactType->getExtraFieldElements($new_id);
1091 if (is_array($value)) {
1092 foreach($value as $v) {
1093 $v = $this->ArtifactType->getElementName($v);
1094 foreach($values as $ev) {
1095 if ($ev['element_name'] == $v) {
1096 $new_extra_fields[$new_id][] = $ev['element_id'];
1101 $value = $this->ArtifactType->getElementName($value);
1102 foreach($values as $ev) {
1103 if ($ev['element_name'] == $value) {
1104 $new_extra_fields[$new_id] = $ev['element_id'];
1112 // Special case if moving to a tracker with custom status (previous has not).
1113 $custom_status_id = $newArtifactType->getCustomStatusField();
1114 if ($custom_status_id && !$new_extra_fields[$custom_status_id]) {
1115 $atw = new ArtifactWorkflow($newArtifactType, $custom_status_id);
1116 $nodes = $atw->getNextNodes(100);
1118 $new_extra_fields[$custom_status_id] = $nodes[0];
1122 $extra_fields = $new_extra_fields;
1124 $res = db_query_params('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1',
1125 array($this->getID()));
1127 $this->setError(_('Removal of old artifact_extra_field_data failed')._(': ').db_error());
1132 // Check that assigned_to is a tech in the new tracker
1133 if ($assigned_to != 100 && $permCheck) {
1134 if (!forge_check_perm ('tracker', $newArtifactType->getID(), 'tech')) {
1139 // Check that the new submitting user can submit items
1140 if ($user != 100 && $permCheck) {
1141 if (!forge_check_perm_for_user($user, 'tracker', $newArtifactType->getID(), 'submit')) {
1142 $user = $this->getSubmittedBy();
1146 //can't send a canned response when changing ArtifactType
1147 $canned_response=100;
1148 $this->ArtifactType =& $newArtifactType;
1152 $qpa = db_construct_qpa();
1153 $qpa = db_construct_qpa($qpa, 'UPDATE artifact SET');
1156 // handle audit trail
1158 if (array_key_exists('time',$importData)){
1159 $time = $importData['time'];
1164 // we want to log the current user, not the changed submitting user
1165 unset($importData['user']);
1167 if ($this->getStatusID() != $status_id) {
1168 $this->addHistory('status_id',$this->getStatusID(), $importData);
1169 $qpa = db_construct_qpa($qpa, ' status_id=$1,', array($status_id));
1170 $changes['status'] = 1;
1173 if ($status_id != 1) {
1174 $qpa = db_construct_qpa($qpa, ' close_date=$1,', array($time));
1176 $qpa = db_construct_qpa($qpa, ' close_date=$1,', array(0));
1178 $this->addHistory('close_date', $this->getCloseDate(), $importData);
1180 if ($this->getPriority() != $priority) {
1181 $this->addHistory('priority',$this->getPriority(), $importData);
1182 $qpa = db_construct_qpa($qpa, ' priority=$1,', array($priority));
1183 $changes['priority'] = 1;
1186 if ($this->getSubmittedBy() != $user) {
1187 $this->addHistory('submitted_by', $this->getSubmittedBy(), $importData);
1188 $qpa = db_construct_qpa($qpa, ' submitted_by=$1,', array($user));
1189 $changes['submitted_by'] = 1;
1192 if ($this->getAssignedTo() != $assigned_to) {
1193 $this->addHistory('assigned_to',$this->getAssignedTo(), $importData);
1194 $qpa = db_construct_qpa($qpa, ' assigned_to=$1,', array($assigned_to));
1195 $changes['assigned_to'] = 1;
1198 if ($summary && ($this->getSummary() != htmlspecialchars($summary))) {
1199 $this->addHistory('summary', $this->getSummary(), $importData);
1200 $qpa = db_construct_qpa($qpa, ' summary=$1,', array(htmlspecialchars($summary)));
1201 $changes['summary'] = 1;
1204 if ($description && ($this->getDetails() != htmlspecialchars($description))) {
1205 $this->addHistory('details', $this->getDetails(), $importData);
1206 $qpa = db_construct_qpa($qpa, ' details=$1,', array(htmlspecialchars($description)));
1207 $changes['details'] = 1;
1211 $this->addMessage($details, '' ,0 , $importData);
1212 $changes['details'] = 1;
1217 Finally, update the artifact itself
1220 $qpa = db_construct_qpa($qpa, ' group_artifact_id=$1
1221 WHERE artifact_id=$2 AND group_artifact_id=$3',
1222 array($new_artifact_type_id,
1223 $this->getID(), $artifact_type_id));
1224 $result = db_query_qpa($qpa);
1226 if (!$result || db_affected_rows($result) < 1) {
1227 $this->setError(_('Update failed')._(': ').db_error());
1231 if (!$this->fetchData($this->getID())) {
1238 //extra field handling
1240 if (!$this->updateExtraFields($extra_fields, $changes, $importData)) {
1241 //TODO - see if anything actually did change
1247 handle canned responses
1249 Instantiate ArtifactCanned and get the body of the message
1251 if ($canned_response != 100) {
1252 //don't care if this response is for this group - could be hacked
1253 $acr = new ArtifactCanned($this->ArtifactType, $canned_response);
1254 if (!$acr || !is_object($acr)) {
1255 $this->setError(_('Could Not Create Canned Response Object'));
1256 } elseif ($acr->isError()) {
1257 $this->setError($acr->getErrorMessage());
1259 $body = $acr->getBody();
1261 if (!$this->addMessage(util_unconvert_htmlspecialchars($body), '', 0, $importData)) {
1268 $this->setError(_('Unable to Use Canned Response'));
1274 if ($update || $send_message){
1275 if (!empty($changes) && $sendNotice) {
1276 // Send the email with changes
1277 $this->mailFollowupEx($time, 2, false, $changes);
1282 //nothing changed, so cancel the transaction
1283 $this->setError(_('Nothing Changed - Update Cancelled'));
1291 * updateLastModified - update last_modified_date & last_modified_by attribute of this artifact.
1292 * @param array $importData Array of data to change submitter and time of submit like:
1293 * array('user' => 127, 'time' => 1234556789)
1295 * @return bool true on success / false on failure
1297 function updateLastModified($importData = array()) {
1298 if (array_key_exists('time', $importData)){
1299 $time = $importData['time'];
1303 if (array_key_exists('user',$importData)){
1304 $user = $importData['user'];
1306 $user = ((session_loggedin()) ? user_getid() : 100);
1309 $res = db_query_params ('UPDATE artifact SET last_modified_date=$1, last_modified_by=$2 WHERE artifact_id=$3',
1310 array($time, $user, $this->getID()));
1315 * assignToMe - assigns this artifact to current user
1317 * @return bool true on success / false on failure
1319 function assignToMe() {
1320 if (!session_loggedin() || !(forge_check_perm('tracker', $this->ArtifactType->getID(), 'manager') || forge_check_perm('tracker', $this->ArtifactType->getID(), 'tech'))) {
1321 $this->setPermissionDeniedError();
1325 $user_id = user_getid();
1326 $res = db_query_params ('UPDATE artifact SET assigned_to=$1 WHERE artifact_id=$2',
1327 array($user_id, $this->getID()));
1329 $this->setError(_('Error updating assigned_to in artifact')._(': ').db_error());
1332 $this->fetchData($this->getID());
1338 * updateExtraFields - updates the extra data elements for this artifact
1339 * e.g. the extra fields created and defined by the admin.
1341 * @param array $extra_fields Array of extra fields like: array(15=>'foobar',22=>'1');
1342 * @param array $changes Array where changes to the extra fields should be logged
1343 * @param array $importData Array of data to change submitter and time of submit like:
1344 * array('user' => 127, 'time' => 1234556789)
1346 * @return bool true on success / false on failure
1348 function updateExtraFields($extra_fields, &$changes, $importData = array()){
1350 This is extremely complex code - we have take the passed array
1351 and see if we need to insert it into the db, and may have to
1352 add history rows for the audit trail
1354 start by getting all the available extra fields from ArtifactType
1355 For each field from ArtifacType, check the passed array -
1356 This prevents someone from passing bogus extra field entries - they will be ignored
1357 if the passed entry is blank, may have to force a default value
1358 if the passed array is different from the existing data in db,
1359 delete old entry and insert new entries, along with possible audit trail
1361 skip it and continue to next item
1364 if (isset($importData['nopermcheck']) && $importData['nopermcheck']) {
1371 //get a list of extra fields for this artifact_type
1372 $ef = $this->ArtifactType->getExtraFields();
1373 $efk = array_keys($ef);
1375 if (empty($extra_fields) && empty($ef)) {
1379 $status_changed = false;
1381 // If there is a status field, then check against the workflow.
1382 // Unless if we change type.
1383 if ((!isset($changes['Type']) || !$changes['Type']) && $permCheck) {
1384 for ($i=0; $i<count($efk); $i++) {
1386 $type=$ef[$efid]['field_type'];
1387 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1388 // Get previous value.
1389 $res = db_query_params ('SELECT field_data FROM artifact_extra_field_data
1390 WHERE artifact_id=$1 AND extra_field_id=$2',
1391 array($this->getID(),
1393 $from_status = (db_numrows($res)>0) ? db_result($res,0,'field_data') : 100;
1394 $to_status = $extra_fields[$efid];
1395 if ($from_status != $to_status) {
1396 $status_changed = true;
1397 $atw = new ArtifactWorkflow($this->ArtifactType, $efid);
1398 if (!$atw->checkEvent($from_status, $to_status)) {
1399 $this->setError(_('Workflow error: You are not authorized to change the Status').'('.$from_status.' => '.$to_status.')');
1407 if ($status_changed) {
1408 $CSFid = $this->ArtifactType->getCustomStatusField();
1409 $wf = new ArtifactWorkflow($this->ArtifactType, $CSFid);
1410 $rf = $wf->getRequiredFields($from_status, $to_status);
1415 //now we'll update this artifact for each extra field
1416 for ($i=0; $i<count($efk); $i++) {
1418 $type=$ef[$efid]['field_type'];
1420 // check required fields
1421 if (($ef[$efid]['is_required'] || in_array($efid, $rf)) && $permCheck) {
1422 if (!array_key_exists($efid, $extra_fields)) {
1423 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1424 $this->setError(_('Status Custom Field Must Be Set'));
1426 $this->setMissingParamsError($ef[$efid]['field_name']);
1430 if ($extra_fields[$efid] === '') {
1431 if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1432 $this->setError(_('Status Custom Field Must Be Set'));
1434 $this->setMissingParamsError($ef[$efid]['field_name']);
1438 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT || $type == ARTIFACT_EXTRAFIELDTYPE_RADIO) &&
1439 $extra_fields[$efid] == '100') {
1440 $this->setMissingParamsError($ef[$efid]['field_name']);
1442 } elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) &&
1443 (count($extra_fields[$efid]) == 1 && $extra_fields[$efid][0] == '100')) {
1444 $this->setMissingParamsError($ef[$efid]['field_name']);
1451 // check parent field
1452 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT ||
1453 $type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT ||
1454 $type == ARTIFACT_EXTRAFIELDTYPE_RADIO ||
1455 $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX)
1458 if (!is_null($ef[$efid]['parent']) && !empty($ef[$efid]['parent']) && $ef[$efid]['parent']!='100') {
1459 $aefParentId = $ef[$efid]['parent'];
1460 $selectedElmnts = (isset($extra_fields[$aefParentId]) ? $extra_fields[$aefParentId] : '');
1461 $aef = new ArtifactExtraField($this->ArtifactType,$efid);
1462 $allowed = $aef->getAllowedValues($selectedElmnts);
1465 if (is_array($allowed)) {
1466 if (($type == ARTIFACT_EXTRAFIELDTYPE_RADIO) || ($type==ARTIFACT_EXTRAFIELDTYPE_SELECT)) {
1467 if ($extra_fields[$efid]!='100' && !in_array($extra_fields[$efid],$allowed)) {
1468 //$aef = new ArtifactExtraField($this->ArtifactType,$efid);
1469 $aefe = new ArtifactExtraFieldElement($aef,$extra_fields[$efid]);
1470 $this->setError(sprintf(_('"%1$s" value of the field "%2$s", is not allowed by "%3$s" field values'), $aefe->getName(), $ef[$efid]['field_name'], $ef[$aefParentId]['field_name']));
1474 if (is_array($extra_fields[$efid])) {
1475 //$aef = new ArtifactExtraField($this->ArtifactType,$efid);
1476 foreach ($extra_fields[$efid] as $val) {
1477 if ($val!='100' && !in_array($val,$allowed)) {
1478 $aefe = new ArtifactExtraFieldElement($aef,$val);
1479 $this->setError(sprintf(_('"%1$s" value of the field "%2$s", is not allowed by "%3$s" field values'), $aefe->getName(), $ef[$efid]['field_name'], $ef[$aefParentId]['field_name']));
1489 // check pattern for text fields
1490 if ($type == ARTIFACT_EXTRAFIELDTYPE_TEXT &&
1491 !empty($ef[$efid]['pattern']) &&
1492 !empty($extra_fields[$efid]) &&
1493 !preg_match('/'.$ef[$efid]['pattern'].'/', $extra_fields[$efid]) &&
1495 $this->setError(sprintf(_("Field %s doesn't match the pattern."), $ef[$efid]['field_name']));
1500 // Force each field to have some value if it is a numeric field
1501 // text fields will just be purged and skipped
1503 if (!array_key_exists($efid, $extra_fields) || $extra_fields[$efid] === '') {
1504 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO)) {
1505 $extra_fields[$efid]='100';
1506 } elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX)) {
1507 $extra_fields[$efid] = array('100');
1509 db_query_params('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1510 array($this->getID(),
1517 // get the old rows of data
1519 $resd = db_query_params('SELECT * FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1520 array($this->getID(),
1522 $rows=db_numrows($resd);
1523 if ($resd && $rows) {
1525 //POTENTIAL PROBLEM - no entry was there before, but adding one now - may need history
1528 // Compare for history purposes
1531 // these types have arrays associated to them, so they need
1532 // special handling to check for differences
1533 if ($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) {
1534 // check the differences between the old values and the new values
1535 $old_values = util_result_column_to_array($resd,"field_data");
1537 $added_values = array_diff($extra_fields[$efid], $old_values);
1538 $deleted_values = array_diff($old_values, $extra_fields[$efid]);
1540 if (!empty($added_values) || !empty($deleted_values)) { // there are differences...
1541 $field_name = $ef[$efid]['field_name'];
1542 if (!preg_match('/^@/', $ef[$efid]['alias'])) {
1543 $changes["extra_fields"][$efid] = 1;
1546 $this->addHistory($field_name, $this->ArtifactType->getElementName(array_reverse($old_values)),$importData);
1549 db_query_params('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1550 array($this->getID(),
1555 } elseif (db_result($resd,0,'field_data') == htmlspecialchars($extra_fields[$efid])) {
1556 //element did not change
1559 //element DID change - do a history entry
1560 $field_name = $ef[$efid]['field_name'];
1561 if (!preg_match('/^@/', $ef[$efid]['alias'])) {
1562 $changes["extra_fields"][$efid] = 1;
1564 db_query_params('DELETE FROM artifact_extra_field_data WHERE artifact_id=$1 AND extra_field_id=$2',
1565 array($this->getID(),
1568 // Adding history with previous value.
1569 if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO) || ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS)) {
1570 $this->addHistory($field_name,$this->ArtifactType->getElementName(db_result($resd,0,'field_data')),$importData);
1572 $this->addHistory($field_name, db_result($resd,0,'field_data'),$importData);
1578 //no history for this extra field exists
1583 // Some rewrite & consistency checks on the relation type field.
1585 // 1) Convert syntax [#NNN] to NNN
1586 // 2) Allow multiple spaces as separator.
1587 // 3) Ensure that only integers are given.
1588 // 4) Ensure that id corresponds to valid tracker id.
1590 if ($type == ARTIFACT_EXTRAFIELDTYPE_RELATION || $type == ARTIFACT_EXTRAFIELDTYPE_PARENT) {
1591 $value = preg_replace('/\[\#(\d+)\]/', "\\1", trim($extra_fields[$efid]));
1592 $value = preg_replace('/\\s+/', ' ', $value);
1594 foreach (explode(' ',$value) as $id) {
1595 if (preg_match('/^(\d+)$/', $id)) {
1596 if ($id == $this->getID()) {
1597 $this->setError(_('Illegal id').' '.$id._(': ')._('self reference for field')._(': ').$ef[$efid]['field_name']);
1600 // Control that the id is present in the db
1601 $res = db_query_params('SELECT artifact_id FROM artifact WHERE artifact_id=$1',
1603 if (db_numrows($res) == 1) {
1606 $this->setError(_('Illegal id').' '.$id._(': ')._('it is not a valid artifact id for field')._(': ').$ef[$efid]['field_name'].'.');
1609 $progeny = $this->getProgeny();
1610 if (in_array($id, $progeny)) {
1611 $this->setError(_('Illegal id').' '.$id._(': ')._('circular dependency for field')._(': ').$ef[$efid]['field_name']);
1615 $this->setError(_('Illegal value').' '.$id._(': ')._('only artifact ids are allowed for field')._(': ').$ef[$efid]['field_name']);
1619 $extra_fields[$efid] = trim($new);
1622 // Ensure that only integer are allowed for type ARTIFACT_EXTRAFIELDTYPE_INTEGER
1623 if ($type == ARTIFACT_EXTRAFIELDTYPE_INTEGER) {
1624 $extra_fields[$efid] = trim($extra_fields[$efid]);
1625 if (!preg_match('/^[-+]?(\d+)$/', $extra_fields[$efid])) {
1626 $this->setError(_('Illegal value').' '.$extra_fields[$efid].' '._('for field ').' '.$ef[$efid]['field_name']._(': ')._('Only integer is allowed.'));
1629 if ($extra_fields[$efid] < -2147483648 || $extra_fields[$efid] > 2147483647) {
1630 $this->setError(_('Illegal value').' '.$extra_fields[$efid].' '._('for field ').' '.$ef[$efid]['field_name']._(': ')._('Integer out of range (-2147483648 to +2147483647).'));
1633 $extra_fields[$efid] = intval($extra_fields[$efid]);
1637 // See if anything was even passed for this extra_field_id
1639 if ($extra_fields[$efid] === '') {
1640 //nothing in field to update - text fields may be blank
1642 $type = $ef[$efid]['field_type'];
1643 //special treatment for DATETIME
1644 if ($type == ARTIFACT_EXTRAFIELDTYPE_DATETIME && $extra_fields[$efid] != '' ) {
1645 $dateTime = DateTime::createFromFormat(_('Y-m-d H:i'), $extra_fields[$efid]);
1646 $extra_fields[$efid] = $dateTime->format('U');
1648 //determine the type of field and whether it should have multiple rows supporting it
1649 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1651 $count=count($extra_fields[$efid]);
1652 for ($fin=0; $fin<$count; $fin++) {
1653 $res = db_query_params ('INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) VALUES ($1,$2,$3)',
1654 array($this->getID(),
1656 $extra_fields[$efid][$fin]));
1658 $this->setError(db_error());
1665 $res = db_query_params ('INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) VALUES ($1,$2,$3)',
1666 array($this->getID(),
1668 htmlspecialchars($extra_fields[$efid])));
1670 $this->setError(db_error());
1679 if ($this->hasParent()) {
1680 $parentId = $this->getParent();
1681 $parentArtifact = artifact_get_object($parentId);
1682 $parentArtifact->updateOnChildChange($changes);
1686 if ($this->hasChildren()) {
1687 $childrenArray = $this->getChildren();
1689 foreach ($childrenArray as $childArray) {
1690 $childId = $childArray['artifact_id'];
1691 $childArtifact = artifact_get_object($childId);
1692 $childArtifact->updateOnParentChange($changes);
1696 unset($this->extra_field_data);
1698 $this->updateLastModified($importData);
1704 function updateOnParentChange($parentChanges) {
1705 if ($this->hasParent()) {
1709 $extra_fields = $this->getExtraFieldData();
1710 $priority = $this->getPriority();
1711 $status_id = $this->getStatusID();
1712 $status_id = $this->getArtifactType()->remapStatus($status_id, $extra_fields);
1713 $assigned_to = $this->getAssignedTo();
1714 $summary = $this->getSummary();
1715 $canned_response = 100;
1717 $artifact_type_id = $this->getArtifactType()->getID();
1718 $description = $this->getDetails();
1719 $at = $this->getArtifactType();
1720 $ef = $at->getExtraFields();
1722 $parent = $this->getParent();
1723 $parentArtifact = artifact_get_object($parent);
1724 $parentExtra_fields = $parentArtifact->getExtraFieldData();
1725 $parentAt = $parentArtifact->getArtifactType();
1727 foreach ($extra_fields as $key=>$value) {
1728 $distribution_rule = $ef[$key]['distribution_rule'];
1729 switch ($distribution_rule) {
1730 case ARTIFACT_EXTRAFIELD_DISTRIBUTION_RULE_NO_DISTRIBUTION:
1732 case ARTIFACT_EXTRAFIELD_DISTRIBUTION_RULE_STATUS_CLOSE_RECURSIVELY:
1733 $parentStatusID = $parentArtifact->getStatusID();
1734 $parentStatusID = $parentAt->remapStatus($parentStatusID, $parentExtra_fields);
1735 if (isset($parentChanges['status']) && $parentStatusID != 1 && $status_id == 1) {
1737 if ($parentAt->getID() == $at->getID()) {
1738 $status_id = $parentStatusID;
1739 if ($at->usesCustomStatuses()) {
1740 $extra_fields[$at->getCustomStatusField()] = $parentExtra_fields[$parentAt->getCustomStatusField()];
1741 $status_id = $at->remapStatus($status_id, $extra_fields);
1744 if (!$at->usesCustomStatuses()) {
1745 $status_id = 2; //closed
1747 $parentStatusList = $parentAt->getExtraFieldElements($parentAt->getCustomStatusField());
1748 $parentStatusName = $parentStatusList[$parentAt->getCustomStatusField()];
1749 $statusList = $at->getExtraFieldElements($at->getCustomStatusField());
1751 foreach ($statusList as $id->$statusName) {
1752 if ($parentStatusName == $statusName) {
1754 $extra_fields[$at->getCustomStatusField()] = $id;
1759 //TODO: status not found
1767 return $this->update($priority,$status_id,
1768 $assigned_to,$summary,$canned_response,$details,$artifact_type_id,
1769 $extra_fields, $description);
1776 function updateOnChildChange($childChanges) {
1777 if ($this->hasChildren()) {
1781 $extra_fields = $this->getExtraFieldData();
1782 $priority = $this->getPriority();
1783 $status_id = $this->getStatusID();
1784 $status_id = $this->getArtifactType()->remapStatus($status_id, $extra_fields);
1785 $assigned_to = $this->getAssignedTo();
1786 $summary = $this->getSummary();
1787 $canned_response = 100;
1789 $artifact_type_id = $this->getArtifactType()->getID();
1790 $description = $this->getDetails();
1791 $at = $this->getArtifactType();
1792 $ef = $at->getExtraFields();
1794 $children = $this->getChildren();
1796 foreach ($extra_fields as $key=>$value) {
1797 $aggregation_rule = $ef[$key]['aggregation_rule'];
1798 $type = $ef[$key]['field_type'];
1799 $alias = $ef[$key]['alias'];
1800 switch ($aggregation_rule) {
1801 case ARTIFACT_EXTRAFIELD_AGGREGATION_RULE_NO_AGGREGATION:
1803 case ARTIFACT_EXTRAFIELD_AGGREGATION_RULE_SUM:
1805 case ARTIFACT_EXTRAFIELDTYPE_INTEGER:
1808 case ARTIFACT_EXTRAFIELDTYPE_EFFORT:
1809 $effortUnitSet = new EffortUnitSet($at, $at->getEffortUnitSet());
1810 $effortUnitFactory = new EffortUnitFactory($effortUnitSet);
1812 $baseUnit = $effortUnitFactory->getBaseUnit();
1814 $effortSum= new Effort(0, $baseUnit);
1817 foreach ($children as $child) {
1818 $childArtifact = artifact_get_object($child['artifact_id']);
1819 $childExtra_fields = $childArtifact->getExtraFieldData();
1820 if ($at->getID() == $child['group_artifact_id']) {
1824 $childAt = $childArtifact->getArtifactType();
1825 $childEf= $childAt->getExtraFieldByAlias($alias);
1827 $childEf_id = $child_ef['extra_field_id'];
1829 $childEf_id = false;
1834 case ARTIFACT_EXTRAFIELDTYPE_INTEGER:
1835 $sum = $sum+$childExtra_fields[$childEf_id];
1837 case ARTIFACT_EXTRAFIELDTYPE_EFFORT:
1838 $effort = encodedEffortToEffort($childExtra_fields[$childEf_id]);
1839 $effortSum= $effortSum->add($effort);
1840 $sum = $effortSum->toEncoded();
1844 $extra_fields[$key]=$sum;
1851 return $this->update($priority,$status_id,
1852 $assigned_to,$summary,$canned_response,$details,$artifact_type_id,
1853 $extra_fields, $description);
1861 * getExtraFieldData - get an array of data for the extra fields associated with this artifact
1863 * @return array array of data
1865 function &getExtraFieldData() {
1866 if (!isset($this->extra_field_data)) {
1867 $this->extra_field_data = array();
1868 $res = db_query_params('SELECT * FROM artifact_extra_field_data WHERE artifact_id=$1 ORDER BY extra_field_id',
1869 array($this->getID()));
1870 $ef = $this->ArtifactType->getExtraFields();
1871 while ($arr = db_fetch_array($res)) {
1872 $type=$ef[$arr['extra_field_id']]['field_type'];
1873 if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
1874 //accumulate a sub-array of values in cases where you may have multiple rows
1875 if (!array_key_exists($arr['extra_field_id'], $this->extra_field_data) || !is_array($this->extra_field_data[$arr['extra_field_id']])) {
1876 $this->extra_field_data[$arr['extra_field_id']] = array();
1878 $this->extra_field_data[$arr['extra_field_id']][] = $arr['field_data'];
1880 $this->extra_field_data[$arr['extra_field_id']] = $arr['field_data'];
1884 return $this->extra_field_data;
1888 * marker - adds the > symbol to fields that have been modified for the email message
1890 * @param string $prop_name
1891 * @param array $changes
1892 * @param int $extra_field_id
1895 function marker($prop_name, $changes, $extra_field_id=0) {
1896 if ($prop_name == 'extra_fields' && isset($changes[$prop_name][$extra_field_id])) {
1898 } elseif ($prop_name != 'extra_fields' && isset($changes[$prop_name])) {
1906 * mailFollowupEx - send out an email update for this artifact.
1908 * @param time_t $tm Time of the change
1909 * @param int $type (1) initial/creation (2) update
1910 * @param array $more_addresses Array of additional addresses to mail to
1911 * @param array $changes Array of fields changed in this update
1913 * @return bool success.
1915 function mailFollowupEx($tm, $type, $more_addresses = array(), $changes = array()) {
1921 $sess = session_get_user();
1922 $name = util_unconvert_htmlspecialchars($this->ArtifactType->getName());
1923 $body = $this->ArtifactType->Group->getUnixName() . '-' . $name .' '. $this->getStringID();
1926 $body .= sprintf(_(" was opened at %s"), date('Y-m-d H:i', $this->getOpenDate()));
1927 } elseif ($type == 3) {
1928 $body .= sprintf(_(" was deleted at %s"), date('Y-m-d H:i', time()));
1930 $body .= sprintf(_(" was changed at %s"), date('Y-m-d H:i', $tm));
1933 $body .= _(' by ') . $sess->getRealName();
1936 if ($type == 1 || $type == 2) {
1937 $body .= "\n"._("You can respond by visiting: ").
1938 "\n".util_make_url($this->getPermalink());
1939 if (false) { // currently not working
1941 sprintf(_("\nOr by replying to this e-mail entering your response between the following markers: ".
1943 "\n(enter your response here, only in plain text format)".
1944 "\n%1$s"), ARTIFACT_MAIL_MARKER, ARTIFACT_MAIL_MARKER);
1949 $body .= "\n".$this->marker('status',$changes).
1950 _("Status")._(": "). $this->getStatusName() ."\n".
1951 $this->marker('priority',$changes).
1952 _("Priority")._(": "). $this->getPriority() ."\n".
1953 _("Submitted by")._(": "). $this->getSubmittedRealName() .
1954 " (". $this->getSubmittedUnixName(). ")"."\n".
1955 $this->marker('assigned_to',$changes).
1956 _("Assigned to")._(": "). $this->getAssignedRealName() .
1957 " (". $this->getAssignedUnixName(). ")"."\n".
1958 $this->marker('summary',$changes).
1959 _("Summary")._(": "). util_unconvert_htmlspecialchars($this->getSummary())." \n";
1961 // Now display the extra fields
1962 $efd = $this->getExtraFieldDataText();
1963 foreach ($efd as $efid => $ef) {
1964 $body .= $this->marker('extra_fields', $changes, $efid);
1965 $body .= $ef["name"].": ".htmlspecialchars_decode($ef["value"])."\n";
1968 $subject='['. $this->ArtifactType->Group->getUnixName() . '-' . $name . ']' . $this->getStringID() .' '. util_unconvert_htmlspecialchars($this->getSummary());
1971 // get all the email addresses that are monitoring this request or the ArtifactType
1972 $monitor_ids = array_merge($this->getMonitorIds(), $this->ArtifactType->getMonitorIds());
1974 // initial creation, we just get the users monitoring the ArtifactType
1975 $monitor_ids = $this->ArtifactType->getMonitorIds();
1979 if ($more_addresses) {
1980 $emails[] = $more_addresses;
1982 //we don't email the current user
1983 if ($this->getAssignedTo() != user_getid()) {
1984 $monitor_ids[] = $this->getAssignedTo();
1986 if ($this->getSubmittedBy() != user_getid()) {
1987 $monitor_ids[] = $this->getSubmittedBy();
1989 //initial submission
1991 //if an email is set for this ArtifactType
1992 //add that address to the BCC: list
1993 if ($this->ArtifactType->getEmailAddress()) {
1994 $emails[] = $this->ArtifactType->getEmailAddress();
1998 if ($this->ArtifactType->emailAll()) {
1999 $emails[] = $this->ArtifactType->getEmailAddress();
2003 $body .= "\n\n"._("Initial Comment:").
2004 "\n".util_unconvert_htmlspecialchars($this->getDetails()) .
2005 "\n\n----------------------------------------------------------------------";
2009 Now include the followups
2011 $result2=$this->getMessages();
2013 $rows=db_numrows($result2);
2015 if ($result2 && $rows > 0) {
2016 for ($i=0; $i<$rows; $i++) {
2018 // for messages posted by non-logged-in users,
2019 // we grab the email they gave us
2021 // otherwise we use the confirmed one from the users table
2023 if (db_result($result2,$i,'user_id') == 100) {
2024 $emails[] = db_result($result2,$i,'from_email');
2026 $monitor_ids[] = db_result($result2,$i,'user_id');
2031 $body .= $this->marker('details',$changes);
2033 $body .= _("Comment By: "). db_result($result2,$i,'realname') . " (".db_result($result2,$i,'user_name').")".
2034 "\n"._("Date: "). date(_('Y-m-d H:i'),db_result($result2,$i,'adddate')).
2035 "\n\n"._("Message:").
2036 "\n".util_unconvert_htmlspecialchars(db_result($result2,$i,'body')).
2037 "\n\n----------------------------------------------------------------------";
2043 $body .= "\n\n"._("You can respond by visiting: ").
2044 "\n".util_make_url($this->getPermalink());
2046 //only send if some recipients were found
2047 if (empty($emails) && empty($monitor_ids)) {
2051 if (empty($monitor_ids)) {
2052 $monitor_ids = array();
2054 $monitor_ids = array_unique($monitor_ids);
2057 $from = $this->ArtifactType->getReturnEmailAddress();
2058 $extra_headers = 'Reply-to: '.$from;
2060 // load the e-mail addresses of the users
2061 $users = user_get_objects($monitor_ids);
2062 if (count($users) > 0) {
2063 foreach ($users as $user) {
2064 if ($user->getStatus() == "A") { //we are only sending emails to active users
2065 $emails[] = $user->getEmail();
2070 //now remove all duplicates from the email list
2071 if (!empty($emails)) {
2072 $bcc = implode(',',array_unique($emails));
2073 util_send_message('', $subject, $body, $from, $bcc, '', $extra_headers);
2076 $this->sendSubjectMsg = $subject;
2077 $this->sendBodyMsg = $body;
2083 * getExtraFieldDataText - Return the extra fields' data in a human-readable form.
2085 * @return array Array containing field ID => field name and value associated to it for
2088 function getExtraFieldDataText() {
2089 // First we get the list of extra fields and the data
2090 // associated to the fields
2091 $efs = $this->ArtifactType->getExtraFields();
2092 $efd = $this->getExtraFieldData();
2096 foreach ($efs as $efid => $ef) {
2097 $name = $ef["field_name"];
2098 $type = $ef["field_type"];
2100 // Get the value according to the type
2103 // for these types, the associated value comes straight
2104 case ARTIFACT_EXTRAFIELDTYPE_TEXT:
2105 case ARTIFACT_EXTRAFIELDTYPE_TEXTAREA:
2106 case ARTIFACT_EXTRAFIELDTYPE_RELATION:
2107 case ARTIFACT_EXTRAFIELDTYPE_PARENT:
2108 case ARTIFACT_EXTRAFIELDTYPE_INTEGER:
2109 case ARTIFACT_EXTRAFIELDTYPE_DATE:
2110 case ARTIFACT_EXTRAFIELDTYPE_DATETIME:
2111 case ARTIFACT_EXTRAFIELDTYPE_EFFORT:
2112 if (isset($efd[$efid])) {
2113 $value = $efd[$efid];
2119 // the other types have and ID or an array of IDs associated to them
2121 if (isset($efd[$efid])) {
2122 $value = $this->ArtifactType->getElementName($efd[$efid]);
2128 $return[$efid] = array("name" => $name, "value" => $value, 'type' => $type);
2135 * castVote - Vote on this tracker item or retract the vote
2136 * @param bool $value true to cast, false to retract
2137 * @return bool success (false sets error message)
2139 function castVote($value = true) {
2140 if (!($uid = user_getid()) || $uid == 100) {
2141 $this->setMissingParamsError(_('User ID not passed'));
2144 if (!$this->ArtifactType->canVote()) {
2145 $this->setPermissionDeniedError();
2148 $has_vote = $this->hasVote($uid);
2149 if ($has_vote == $value) {
2150 /* nothing changed */
2154 $res = db_query_params('INSERT INTO artifact_votes (artifact_id, user_id) VALUES ($1, $2)',
2155 array($this->getID(), $uid));
2157 $res = db_query_params('DELETE FROM artifact_votes WHERE artifact_id=$1 AND user_id=$2',
2158 array($this->getID(), $uid));
2161 $this->setError(db_error());
2168 * hasVote - Check if a user has voted on this tracker item
2170 * @param int|bool $uid user ID (default: current user)
2171 * @return bool true if a vote exists
2173 function hasVote($uid=false) {
2175 $uid = user_getid();
2177 if (!$uid || $uid == 100) {
2180 $res = db_query_params('SELECT * FROM artifact_votes WHERE artifact_id=$1 AND user_id=$2',
2181 array($this->getID(), $uid));
2182 return (db_numrows($res) == 1);
2186 * getVotes - get number of valid cast and potential votes
2188 * @return array|bool (votes, voters, percent)
2190 function getVotes() {
2191 if ($this->votes !== false) {
2192 return $this->votes;
2195 $voters = $this->ArtifactType->getVoters();
2196 unset($voters[0]); /* just in case */
2197 unset($voters[100]); /* need users */
2198 if (($numvoters = count($voters)) < 1) {
2199 $this->votes = array(0, 0, 0);
2200 return $this->votes;
2203 $res = db_query_params('SELECT COUNT(*) AS count FROM artifact_votes WHERE artifact_id=$1 AND user_id=ANY($2)',
2204 array($this->getID(), db_int_array_to_any_clause($voters)));
2205 $db_count = db_fetch_array($res);
2206 $numvotes = $db_count['count'];
2208 /* check for invalid values */
2209 if ($numvotes < 0 || $numvoters < $numvotes) {
2210 $this->votes = array(-1, -1, 0);
2212 $this->votes = array($numvotes, $numvoters,
2213 (int)($numvotes * 100 / $numvoters + 0.5));
2215 return $this->votes;
2218 function hasRelations() {
2219 if (db_numrows($this->getRelations())>0) {
2225 function getRelations() {
2226 if ($this->artifact_relations) {
2227 return $this->artifact_relations;
2229 $aid = $this->getID();
2230 $this->artifact_relations = db_query_params ('SELECT *
2231 FROM artifact_extra_field_list, artifact_extra_field_data, artifact_group_list, artifact, groups
2232 WHERE field_type = $1
2233 AND artifact_extra_field_list.extra_field_id=artifact_extra_field_data.extra_field_id
2234 AND artifact_group_list.group_artifact_id = artifact_extra_field_list.group_artifact_id
2235 AND artifact.artifact_id = artifact_extra_field_data.artifact_id
2236 AND groups.group_id = artifact_group_list.group_id
2237 AND (field_data = $2 OR field_data LIKE $3 OR field_data LIKE $4 OR field_data LIKE $5)
2238 AND artifact.is_deleted = 0
2239 ORDER BY artifact_group_list.group_id ASC, name ASC, artifact.artifact_id ASC',
2240 array(ARTIFACT_EXTRAFIELDTYPE_RELATION,
2245 return $this->artifact_relations;
2249 function hasChildren() {
2250 if (!$this->children) {
2251 $this->children = $this->getChildren();
2253 $nb = count($this->children);
2260 function getChildren() {
2261 if (!$this->children) {
2262 $res = db_query_params('SELECT *
2263 FROM artifact_extra_field_list, artifact_extra_field_data, artifact_group_list, artifact, groups
2264 WHERE field_type = $1
2265 AND artifact_extra_field_list.extra_field_id=artifact_extra_field_data.extra_field_id
2266 AND artifact_group_list.group_artifact_id = artifact_extra_field_list.group_artifact_id
2267 AND artifact.artifact_id = artifact_extra_field_data.artifact_id
2268 AND groups.group_id = artifact_group_list.group_id
2270 AND artifact.is_deleted = 0
2271 ORDER BY artifact_group_list.group_id ASC, name ASC, artifact.artifact_id ASC',
2272 array(ARTIFACT_EXTRAFIELDTYPE_PARENT,
2274 while ($row = db_fetch_array($res)) {
2275 $this->children[] = $row;
2277 db_free_result($res);
2279 return $this->children;
2282 function hasParent() {
2283 return ($this->getParent()?true:false);
2286 function getParent() {
2287 if (!isset($this->parent)) {
2288 $res = db_query_params ('SELECT field_data
2289 FROM artifact_extra_field_data
2290 INNER JOIN artifact_extra_field_list USING (extra_field_id)
2291 WHERE field_type = $1
2292 AND artifact_id = $2',
2293 array(ARTIFACT_EXTRAFIELDTYPE_PARENT,
2295 if (db_numrows($res) == 0) {
2296 $this->parent= false;
2298 $data = db_fetch_array($res);
2299 db_free_result($res);
2300 $this->parent= $data['field_data'];
2303 return $this->parent;
2306 function getProgeny() {
2308 $childrenArr = $this->getChildren();
2309 if (is_array($childrenArr)) {
2310 $at = $this->ArtifactType;
2311 foreach ($childrenArr as $child) {
2312 $return[] = $child['artifact_id'];
2313 $childObj = new Artifact($at,$child['artifact_id']);
2314 $childProgenyArr = $childObj->getProgeny();
2315 if (is_array($childProgenyArr)) {
2316 $return = array_merge($return, $childProgenyArr);
2323 function setParent($parent_id){
2326 if ($this->getParent()) {
2327 if ($this->getParent() == $parent_id) {
2330 $this->setError(_('Error')._(':').' '.sprintf(_('Artifact $s has already a parent'), $this->getID()));
2334 $extra_fields = $this->getExtraFieldData();
2335 $priority = $this->getPriority();
2336 $status_id = $this->getStatusID();
2337 $status_id = $this->getArtifactType()->remapStatus($status_id, $extra_fields);
2338 $assigned_to = $this->getAssignedTo();
2339 $summary = $this->getSummary();
2340 $canned_response = 100;
2342 $artifact_type_id = $this->getArtifactType()->getID();
2343 $description = $this->getDetails();
2344 $ef_parent_arr= $this->getArtifactType()->getExtraFields(array(ARTIFACT_EXTRAFIELDTYPE_PARENT));
2345 $ef_parent = array_shift($ef_parent_arr);
2346 $ef_parent_id = $ef_parent['extra_field_id'];
2347 $extra_fields[$ef_parent_id] = $parent_id;
2348 $ret['ef_parent_id'] = $ef_parent_id;
2349 $return = $this->update($priority,$status_id,
2350 $assigned_to,$summary,$canned_response,$details,$artifact_type_id,
2351 $extra_fields, $description);
2352 unset($this->parent);
2353 $this->fetchData($this->getID());
2357 function resetParent(){
2360 if (!$this->getParent()) {
2363 $extra_fields = $this->getExtraFieldData();
2364 $priority = $this->getPriority();
2365 $status_id = $this->getStatusID();
2366 $status_id = $this->getArtifactType()->remapStatus($status_id, $extra_fields);
2367 $assigned_to = $this->getAssignedTo();
2368 $summary = $this->getSummary();
2369 $canned_response = 100;
2371 $artifact_type_id = $this->getArtifactType()->getID();
2372 $description = $this->getDetails();
2373 $ef_parent_arr= $this->getArtifactType()->getExtraFields(array(ARTIFACT_EXTRAFIELDTYPE_PARENT));
2374 $ef_parent = array_shift($ef_parent_arr);
2375 $ef_parent_id = $ef_parent['extra_field_id'];
2376 $extra_fields[$ef_parent_id] = '';
2377 $return = $this->update($priority,$status_id,
2378 $assigned_to,$summary,$canned_response,$details,$artifact_type_id,
2379 $extra_fields, $description);
2380 unset($this->parent);
2381 $this->fetchData($this->getID());
2385 function getPermalink() {
2386 return '/tracker/a_follow.php/'.$this->getID();
2390 class ArtifactComparator {
2391 var $criterion = 'artifact_id';
2394 function Compare ($a, $b) {
2395 if ($this->order == 'DESC') {
2396 $c = $a; $a = $b; $b = $c;
2398 switch ($this->criterion) {
2400 $namecmp = strcoll ($a->getSummary(),
2402 if ($namecmp != 0) {
2407 $namecmp = strcoll($a->getAssignedRealName(),$b->getAssignedRealName());
2408 if ($namecmp != 0) {
2412 case 'submitted_by':
2413 $namecmp = strcoll($a->getSubmittedRealName(),$b->getSubmittedRealName());
2414 if ($namecmp != 0) {
2418 case 'last_modified_by':
2419 $namecmp = strcoll ($a->getLastModifiedRealName(),$b->getLastModifiedRealName());
2420 if ($namecmp != 0) {
2425 $a_date = $a->getOpenDate();
2426 $b_date = $b->getOpenDate();
2427 return ($a_date < $b_date) ? -1 : 1;
2430 $a_date = $a->getCloseDate();
2431 $b_date = $b->getCloseDate();
2432 return ($a_date < $b_date) ? -1 : 1;
2434 case 'last_modified_date':
2435 $a_date = $a->getLastModifiedDate();
2436 $b_date = $b->getLastModifiedDate();
2437 return ($a_date < $b_date) ? -1 : 1;
2440 $a_priority = $a->getPriority();
2441 $b_priority = $b->getPriority();
2442 return ($a_priority < $b_priority) ? -1 : 1;
2446 $a_votes = $a->votes[0];
2448 $b_votes = $b->votes[0];
2449 return ($a_votes < $b_votes) ? -1 : 1;
2453 $a_votes = $a->votes[1];
2455 $b_votes = $b->votes[1];
2456 return ($a_votes < $b_votes) ? -1 : 1;
2460 $a_votes = $a->votes[2];
2462 $b_votes = $b->votes[2];
2463 return ($a_votes < $b_votes) ? -1 : 1;
2466 $aa=$a->getExtraFieldDataText();
2467 $ba=$b->getExtraFieldDataText();
2468 if(!isset($this->criterion) || empty($this->criterion)) {
2471 $criterion = $this->criterion;
2473 $af=$aa[$criterion]['value'];
2474 $bf=$ba[$criterion]['value'];
2475 switch ($aa[$criterion]['type']) {
2476 case ARTIFACT_EXTRAFIELDTYPE_EFFORT:
2477 $namecmp = intval($af)-intval($bf);
2479 case ARTIFACT_EXTRAFIELDTYPE_INTEGER:
2480 case ARTIFACT_EXTRAFIELDTYPE_DATE:
2481 case ARTIFACT_EXTRAFIELDTYPE_DATETIME:
2485 $namecmp = strcoll ($af,$bf);
2487 if ($namecmp != 0) {
2493 // When in doubt, sort on artifact ID
2499 return ($aid < $bid) ? -1 : 1;
2503 function sortArtifactList (&$list, $criterion='name', $order='ASC') {
2504 $cmp = new ArtifactComparator ();
2505 $cmp->criterion = $criterion;
2506 $cmp->order = $order;
2508 return usort ($list, array($cmp, 'Compare'));
2513 // c-file-style: "bsd"