5 * Copyright 1999-2001, VA Linux Systems, Inc.
6 * Copyright 2002-2004, GForge, LLC
7 * Copyright 2009, Roland Mas
8 * Copyright (C) 2011 Alain Peyrat - Alcatel-Lucent
9 * Copyright 2012, Thorsten “mirabilos” Glaser <t.glaser@tarent.de>
10 * Copyright 2014,2016-2017, 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.
29 require_once $gfcommon.'include/FFError.class.php';
30 require_once $gfcommon.'tracker/ArtifactExtraFieldElement.class.php';
31 require_once $gfcommon.'tracker/ArtifactStorage.class.php';
32 require_once $gfcommon.'tracker/EffortUnitSet.class.php';
33 require_once $gfcommon.'include/MonitorElement.class.php';
34 require_once $gfcommon.'widget/WidgetLayoutManager.class.php';
37 * Gets an ArtifactType object from the artifact type id
39 * @param int $artType_id The ArtifactType id
40 * @param resource|bool $res The DB handle if passed in (optional)
41 * @return ArtifactType The ArtifactType object
43 function &artifactType_get_object($artType_id, $res = false) {
44 global $ARTIFACTTYPE_OBJ;
45 if (!isset($ARTIFACTTYPE_OBJ["_".$artType_id."_"])) {
47 //the db result handle was passed in
49 $res = db_query_params('SELECT * FROM artifact_group_list_vw WHERE group_artifact_id=$1',
52 if (!$res || db_numrows($res) < 1) {
53 $ARTIFACTTYPE_OBJ["_".$artType_id."_"] = false;
55 $data = db_fetch_array($res);
56 $Group = group_get_object($data["group_id"]);
57 $ARTIFACTTYPE_OBJ["_".$artType_id."_"] = new ArtifactType($Group, $data["group_artifact_id"], $data);
60 return $ARTIFACTTYPE_OBJ["_".$artType_id."_"];
63 function artifacttype_get_groupid($artifact_type_id) {
64 global $ARTIFACTTYPE_OBJ;
65 if (isset($ARTIFACTTYPE_OBJ["_".$artifact_type_id."_"])) {
66 return $ARTIFACTTYPE_OBJ["_".$artifact_type_id."_"]->Group->getID();
69 $res = db_query_params('SELECT group_id FROM artifact_group_list WHERE group_artifact_id=$1',
70 array($artifact_type_id));
71 if (!$res || db_numrows($res) < 1) {
74 $arr = db_fetch_array($res);
75 return $arr['group_id'];
78 class ArtifactType extends FFError {
88 * extra_fields 3d array - the IDs and Names of the extra fields
90 * @var array extra_fields;
92 var $extra_fields = array();
95 * extra_field[extra_field_id] array - the IDs and Names of elements on the extra fields
97 * @var array extra_field
102 * Technicians db resource ID.
104 * @var int $technicians_res.
106 var $technicians_res;
109 * Submitters db resource ID.
111 * @var int $submitters_res.
116 * Last Modifiers db resource ID.
118 * @var int $last_modifiers_res.
120 var $last_modifiers_res;
123 * Status db resource ID.
125 * @var int $status_res.
130 * Canned responses resource ID.
132 * @var int $canned_responses_res.
134 var $canned_responses_res;
137 * Array of artifact data.
139 * @var array $data_array.
144 * Array of element names so they only have to be fetched once from db.
146 * @var array $element_name.
151 * Array of element status so they only have to be fetched once from db.
153 * @var array $element_status.
158 * cached return value of getVoters
159 * @var int|bool $voters
164 * @param Group $Group The Group object.
165 * @param int|bool $artifact_type_id The id # assigned to this artifact type in the db.
166 * @param array|bool $arr The associative array of data.
168 function __construct($Group, $artifact_type_id = false, $arr = false) {
169 parent::__construct();
170 if (!$Group || !is_object($Group)) {
171 $this->setError(_('Invalid Project'));
174 if ($Group->isError()) {
175 $this->setError('ArtifactType: '.$Group->getErrorMessage());
178 $this->Group = $Group;
179 if ($artifact_type_id) {
180 if (!$arr || !is_array($arr)) {
181 if (!$this->fetchData($artifact_type_id)) {
185 $this->data_array =& $arr;
186 if ($this->data_array['group_id'] != $this->Group->getID()) {
187 $this->setError('Group_id in db result does not match Group Object');
188 $this->data_array = null;
193 // Make sure they can even access this object
195 if (!forge_check_perm ('tracker', $this->getID(), 'read')) {
196 $this->setPermissionDeniedError();
197 $this->data_array = null;
204 * create - use this to create a new ArtifactType in the database.
206 * @param string $name The type name.
207 * @param string $description The type description.
208 * @param bool $email_all (1) true (0) false - whether to email on all updates.
209 * @param string $email_address The address to send new entries and updates to.
210 * @param int $due_period Days before this item is considered overdue.
211 * @param bool $use_resolution (1) true (0) false - whether the resolution box should be shown.
212 * @param string $submit_instructions Free-form string that project admins can place on the submit page.
213 * @param string $browse_instructions Free-form string that project admins can place on the browse page.
214 * @param int $datatype (1) bug tracker, (2) Support Tracker, (3) Patch Tracker, (4) features (0) other.
215 * @return int id on success, false on failure.
217 function create($name, $description, $email_all, $email_address,
218 $due_period, $use_resolution, $submit_instructions, $browse_instructions, $datatype = 0) {
220 if (!forge_check_perm('tracker_admin', $this->Group->getID())) {
221 $this->setPermissionDeniedError();
225 if (!$name || !$description || !$due_period) {
226 $this->setError(_('ArtifactType: Name, Description, Due Period, and Status Timeout are required'));
230 if ($email_address) {
231 $invalid_emails = validate_emails($email_address);
232 if (count($invalid_emails) > 0) {
233 $this->setError(_('E-mail address(es) appeared invalid')._(': ').implode(',', $invalid_emails));
238 $use_resolution = ((!$use_resolution) ? 0 : $use_resolution);
239 $email_all = ((!$email_all) ? 0 : $email_all);
243 $res = db_query_params('INSERT INTO
257 ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)',
258 array($this->Group->getID(),
259 htmlspecialchars($name),
260 htmlspecialchars($description),
263 $due_period*(60*60*24),
265 htmlspecialchars($submit_instructions),
266 htmlspecialchars($browse_instructions),
268 $this->Group->getEffortUnitSet()
271 $id = db_insertid($res, 'artifact_group_list', 'group_artifact_id');
274 $this->setError('ArtifactType: '.db_error());
278 if (!$this->fetchData($id)) {
282 $this->Group->normalizeAllRoles();
290 * fetchData - re-fetch the data for this ArtifactType from the database.
292 * @param int $artifact_type_id The artifact type ID.
293 * @return boolean success.
295 function fetchData($artifact_type_id) {
296 $this->voters = false;
297 $this->extra_field = false;
298 $this->extra_fields = false;
299 $res = db_query_params('SELECT * FROM artifact_group_list_vw
300 WHERE group_artifact_id=$1
302 array($artifact_type_id,
303 $this->Group->getID()));
304 if (!$res || db_numrows($res) < 1) {
305 $this->setError('ArtifactType: Invalid ArtifactTypeID');
308 $this->data_array = db_fetch_array($res);
309 db_free_result($res);
314 * getGroup - get the Group object this ArtifactType is associated with.
316 * @return Object The Group object.
318 function &getGroup() {
323 * getID - get this ArtifactTypeID.
325 * @return int The group_artifact_id #.
328 return $this->data_array['group_artifact_id'];
332 * getOpenCount - get the count of open tracker items in this tracker type.
334 * @return int The count.
336 function getOpenCount() {
337 return $this->data_array['open_count'];
341 * getTotalCount - get the total number of tracker items in this tracker type.
343 * @return int The total count.
345 function getTotalCount() {
346 return $this->data_array['count'];
350 * getSubmitInstructions - get the free-form string strings.
352 * @return string instructions.
354 function getSubmitInstructions() {
355 return $this->data_array['submit_instructions'];
359 * getBrowseInstructions - get the free-form string strings.
361 * @return string instructions.
363 function getBrowseInstructions() {
364 return $this->data_array['browse_instructions'];
368 * emailAll - determine if we're supposed to email on every event.
370 * @return boolean email_all.
372 function emailAll() {
373 return $this->data_array['email_all_updates'];
377 * emailAddress - defined email address to send events to.
379 * @return string email.
381 function getEmailAddress() {
382 return $this->data_array['email_address'];
386 * getName - the name of this ArtifactType.
388 * @return string name.
391 return $this->data_array['name'];
395 * getFormattedName - formatted name of this ArtifactType
397 * @return string formatted name
399 function getFormattedName() {
400 $name = preg_replace('/[^[:alnum:]]/', '', $this->getName());
401 $name = strtolower($name);
406 * getUnixName - returns the name used by email gateway
408 * @return string unix name
410 function getUnixName() {
411 return strtolower($this->Group->getUnixName()).'-'.$this->getFormattedName();
415 * getReturnEmailAddress - return the return email address for notification emails
417 * @return string return email address
419 function getReturnEmailAddress() {
422 if (forge_get_config('use_gateways')) {
423 $address .= strtolower($this->getUnixName());
425 $address .= 'noreply';
427 $address .= '@'.forge_get_config('web_host');
432 * getDescription - the description of this ArtifactType.
434 * @return string description.
436 function getDescription() {
437 return $this->data_array['description'];
441 * getDuePeriod - how many seconds until it's considered overdue.
443 * @return int seconds.
445 function getDuePeriod() {
446 return $this->data_array['due_period'];
450 * getStatusTimeout - how many seconds until an item is stale.
452 * @return int seconds.
454 function getStatusTimeout() {
455 return $this->data_array['status_timeout'];
459 * getCustomStatusField - return the extra_field_id of the field containing the custom status.
461 * @return int extra_field_id.
463 function getCustomStatusField() {
464 return $this->data_array['custom_status_field'];
468 * setCustomStatusField - set the extra_field_id of the field containing the custom status.
470 * @param int $extra_field_id The extra field id.
471 * @return boolean success.
473 function setCustomStatusField($extra_field_id) {
474 $res = db_query_params('UPDATE artifact_group_list SET custom_status_field=$1
475 WHERE group_artifact_id=$2',
476 array($extra_field_id,
478 $this->fetchData($this->getID());
483 * getAutoAssignField - get the extra_field_id of the field that triggers auto-assignment rules.
485 * @return int extra_field_id.
487 function getAutoAssignField() {
488 return $this->data_array['auto_assign_field'];
492 * setAutoAssignField - set the extra_field_id of the field that triggers auto-assignment rules.
494 * @param int $extra_field_id The extra field id.
495 * @return boolean success.
497 function setAutoAssignField($extra_field_id) {
498 $res = db_query_params('UPDATE artifact_group_list SET auto_assign_field=$1
499 WHERE group_artifact_id=$2',
500 array($extra_field_id,
502 $this->fetchData($this->getID());
507 * usesCustomStatuses - boolean
509 * @return boolean use_custom_statues.
511 function usesCustomStatuses() {
512 return $this->getCustomStatusField();
516 * remapStatus - pass the extra_fields array and return the status_id, either open/closed
518 * @param int $status_id The status_id
519 * @param array $extra_fields Complex array of extra_field_data
520 * @return int status_id.
522 function remapStatus($status_id, $extra_fields) {
523 if ($this->usesCustomStatuses()) {
524 //get the selected element for the extra_field_status element
525 $csfield = $this->getCustomStatusField();
526 if (array_key_exists($csfield, $extra_fields)) {
527 $element_id = $extra_fields[$csfield];
529 //convert that element_id into the status_id
530 $res = db_query_params('SELECT status_id FROM artifact_extra_field_elements WHERE element_id=$1',
533 $this->setError('Error Remapping Status: '.db_error());
536 $status_id = db_result($res, 0, 'status_id');
537 if ($status_id < 1 || $status_id > 4) {
538 $this->setError('INVALID STATUS REMAP: '.$status_id.' FROM SELECTED ELEMENT: '.$element_id);
542 // custom status was not passed... use the first status from the database
543 $res = db_query_params('SELECT status_id FROM artifact_extra_field_elements WHERE extra_field_id=$1 ORDER BY element_id ASC LIMIT 1 OFFSET 0',
545 if (db_numrows($res) == 0) { // No values available
546 $this->setError('Error Remapping Status');
549 $status_id = db_result($res, 0, 'status_id');
558 * getDataType - flag that is generally unused but can mark the difference between bugs, patches, etc.
560 * @return int The type (1) bug (2) support (3) patch (4) feature (0) other.
562 function getDataType() {
563 return $this->data_array['datatype'];
567 * setMonitor - user can monitor this artifact.
569 * @return bool false - always false - always use the getErrorMessage() for feedback
571 function setMonitor($user_id = -1) {
573 if ($user_id == -1) {
574 if (!session_loggedin()) {
575 $this->setError(_('You can only monitor if you are logged in.'));
578 $user_id = user_getid();
580 $MonitorElementObject = new MonitorElement('artifact_type');
581 if (!$this->isMonitoring()) {
582 if (!$MonitorElementObject->enableMonitoringByUserId($this->getID(), $user_id)) {
583 $this->setError($MonitorElementObject->getErrorMessage());
586 $feedback = _('Monitoring Started');
589 if (!$MonitorElementObject->disableMonitoringByUserId($this->getID(), $user_id)) {
590 $this->setError($MonitorElementObject->getErrorMessage());
593 $feedback = _('Monitoring Stopped');
599 function isMonitoring() {
600 if (!session_loggedin()) {
603 $MonitorElementObject = new MonitorElement('artifact_type');
604 return $MonitorElementObject->isMonitoredByUserId($this->getID(), user_getid());
608 * getMonitorIds - array of id of users monitoring this Artifact.
610 * @return array array of id of users monitoring this Artifact.
612 function getMonitorIds() {
613 $MonitorElementObject = new MonitorElement('artifact_type');
614 return $MonitorElementObject->getMonitorUsersIdsInArray($this->getID());
618 * getExtraFields - List of possible user built extra fields
619 * set up for this artifact type.
621 * @param array $types
622 * @param bool $get_is_disabled
623 * @param bool $get_is_hidden_on_submit
624 * @return array arrays of data;
626 function getExtraFields($types = array(), $get_is_disabled = false, $get_is_hidden_on_submit = true) {
629 if (!$get_is_disabled) {
630 $where = ' AND is_disabled = 0';
634 if (!$get_is_hidden_on_submit) {
635 $where = ' AND is_hidden_on_submit = 0';
639 $filter = implode(',', $types);
640 $types = explode(',', $filter);
644 if (!isset($this->extra_fields[$filter]) || !$use_cache) {
645 $extra_fields = array();
647 $res = db_query_params('SELECT *
648 FROM artifact_extra_field_list
649 WHERE group_artifact_id=$1
650 AND field_type = ANY ($2)'.
652 'ORDER BY field_type ASC',
653 array($this->getID(),
654 db_int_array_to_any_clause($types)));
656 $res = db_query_params('SELECT *
657 FROM artifact_extra_field_list
658 WHERE group_artifact_id=$1'.
660 'ORDER BY field_type ASC',
661 array($this->getID()));
663 while ($arr = db_fetch_array($res)) {
664 $extra_fields[$arr['extra_field_id']] = $arr;
667 if (!isset($this->extra_fields[$filter])) {
668 $this->extra_fields[$filter] = $extra_fields;
671 $extra_fields = $this->extra_fields[$filter];
673 return $extra_fields;
677 * getExtraFieldsDefaultValue - Get array of extra fields default value
679 * @param array $types
680 * @param bool $get_is_disabled
681 * @param bool $get_is_hidden_on_submit
682 * @return array arrays of data;
684 function getExtraFieldsDefaultValue($types = array(), $get_is_disabled = false, $get_is_hidden_on_submit = true) {
685 $extra_fields = $this->getExtraFields($types, $get_is_disabled, $get_is_hidden_on_submit);
686 $efDefaultValue = array();
687 foreach ($extra_fields as $efID=>$efArr) {
688 $ef = new ArtifactExtraField($this, $efID);
689 $defaultValue = $ef->getDefaultValues();
690 if (!is_null($defaultValue)) {
691 $efDefaultValue [$efID] = $defaultValue;
693 if (in_array($efArr['field_type'],unserialize(ARTIFACT_EXTRAFIELDTYPEGROUP_SINGLECHOICE))) {
694 $efDefaultValue [$efID] = '';
698 return $efDefaultValue;
702 * getExtraFieldsInFormula - Get array of extra fields used in formula
704 * @param array $types
705 * @param bool $get_is_disabled
706 * @param bool $get_is_hidden_on_submit
707 * @return array arrays of data;
709 function getExtraFieldsInFormula($types = array(), $get_is_disabled = false, $get_is_hidden_on_submit = true) {
711 $extra_fields = $this->getExtraFields($types, $get_is_disabled, $get_is_hidden_on_submit);
712 $res = db_query_params('SELECT string_agg(formula,chr(10)) FROM artifact_extra_field_formula NATURAL INNER JOIN artifact_extra_field_list WHERE is_disabled=0 AND group_artifact_id=$1',
713 array ($this->getID()));
714 if (db_numrows($res) > 0) {
715 $row = db_fetch_array($res);
716 if (preg_match_all("/([a-z]\w*)/m", $row[0], $matches)) {
717 foreach ($extra_fields as $extra_field) {
718 if (in_array($extra_field['alias'],$matches[0])) {
719 $return[]=$extra_field['extra_field_id'];
728 * getFieldsInFormula - Get array of extra fields used in formula
730 * @return array arrays of data;
732 function getFieldsInFormula() {
734 if ($this->usesCustomStatuses()) {
735 $fields = array('assigned_to','priority','summary','description');
737 $fields = array('assigned_to','priority','summary','description','status');
739 $res = db_query_params('SELECT string_agg(formula,chr(10)) FROM artifact_extra_field_formula NATURAL INNER JOIN artifact_extra_field_list WHERE is_disabled=0 AND group_artifact_id=$1',
740 array($this->getID()));
741 if (db_numrows($res) > 0) {
742 $row = db_fetch_array($res);
743 if (preg_match_all("/([a-z]\w*)/m", $row[0], $matches)) {
744 foreach ($fields as $field) {
745 if (in_array($field,$matches[0])) {
755 * getExtraFieldsWithFormula - Get array of extra fields with formula
757 * @param array $types
758 * @param bool $get_is_disabled
759 * @param bool $get_is_hidden_on_submit
760 * @return array arrays of data;
762 function getExtraFieldsWithFormula($types = array(), $get_is_disabled = false, $get_is_hidden_on_submit = true) {
764 $extra_fields = $this->getExtraFields($types, $get_is_disabled, $get_is_hidden_on_submit);
765 $res = db_query_params('SELECT extra_field_id FROM artifact_extra_field_formula NATURAL INNER JOIN artifact_extra_field_list WHERE is_disabled=0 AND group_artifact_id=$1',
766 array ($this->getID()));
767 while ($arr = db_fetch_array($res)) {
768 $return []= $arr['extra_field_id'];
774 * cloneFieldsFrom - clone all the fields and elements from another tracker
776 * @param int $clone_tracker_id id of the cloned tracker
777 * @param array $id_mappings array mapping between template objects and new project objects
778 * @return boolean true/false on success
780 function cloneFieldsFrom($clone_tracker_id, $id_mappings = array()) {
781 $at = artifactType_get_object($clone_tracker_id);
782 if (!$at || !is_object($at)) {
783 $this->setError(_('Could Not Get Tracker To Clone'));
785 } elseif ($at->isError()) {
786 $this->setError(_('Clone Tracker Error').' '.$at->getErrorMessage());
791 $ef_effort = $at->getExtraFields(array(ARTIFACT_EXTRAFIELDTYPE_EFFORT));
792 if (!empty($ef_effort)) {
793 $eus = new EffortUnitSet($at, $at->getEffortUnitSet());
794 $this_eus = new EffortUnitSet($this, $this->getEffortUnitSet());
795 switch ($eus->getLevel()) {
796 case EFFORTUNITSET_FORGE_LEVEL:
797 switch ($this_eus->getLevel()) {
798 case EFFORTUNITSET_PROJECT_LEVEL:
799 if (!$this_eus->isEquivalentTo($eus)) {
800 // make a copy at the tracker level
801 $new_eus = new EffortUnitSet($this);
802 $new_eus->copy($eus);
803 $this->setEffortUnitSet($new_eus->getID());
806 case EFFORTUNITSET_TRACKER_LEVEL:
807 if (!$this_eus->isEquivalentTo($eus)) {
808 $this->setError(_('Clone Tracker Error')._(':').' '._('Effort Unit Set already define and not compatible'));
814 case EFFORTUNITSET_PROJECT_LEVEL:
815 switch ($this_eus->getLevel()) {
816 case EFFORTUNITSET_FORGE_LEVEL:
817 $new_eus_id = getEffortUnitSetForLevel($this, EFFORTUNITSET_PROJECT_LEVEL);
819 $new_eus = new EffortUnitSet($this, $new_eus_id);
820 if (!$new_eus->isEquivalentTo($eus)) {
821 $this->setEffortUnitSet($new_eus->getID());
823 // make a copy at the tracker level
824 $new_eus = new EffortUnitSet($this);
825 $new_eus->copy($eus);
826 $this->setEffortUnitSet($new_eus->getID());
829 // make a copy at the project level
830 $new_eus = new EffortUnitSet($this->Group);
831 $new_eus->copy($eus);
832 $this->setEffortUnitSet($new_eus->getID());
835 case EFFORTUNITSET_PROJECT_LEVEL:
836 if (!$this_eus->isEquivalentTo($eus)) {
837 // make a copy at the tracker level
838 $new_eus = new EffortUnitSet($this);
839 $new_eus->copy($eus);
840 $this->setEffortUnitSet($new_eus->getID());
843 case EFFORTUNITSET_TRACKER_LEVEL:
844 if (!$this_eus->isEquivalentTo($eus)) {
845 $this->setError(_('Clone Tracker Error')._(':').' '._('Effort Unit Set already define and not compatible'));
851 case EFFORTUNITSET_TRACKER_LEVEL:
852 switch ($this_eus->getLevel()) {
853 case EFFORTUNITSET_FORGE_LEVEL:
854 case EFFORTUNITSET_PROJECT_LEVEL:
855 $new_eus_id = getEffortUnitSetForLevel($this, EFFORTUNITSET_TRACKER_LEVEL);
857 $new_eus = new EffortUnitSet($this, $new_eus_id);
858 if (!$new_eus->isEquivalentTo($eus)) {
859 $this->setEffortUnitSet($new_eus->getID());
861 $this->setError(_('Clone Tracker Error')._(':').' '._('Effort Unit Set already define and not compatible'));
865 // make a copy at the tracker level
866 $new_eus = new EffortUnitSet($this);
867 $new_eus->copy($eus);
868 $this->setEffortUnitSet($new_eus->getID());
871 case EFFORTUNITSET_TRACKER_LEVEL:
872 if (!$this_eus->isEquivalentTo($eus)) {
873 $this->setError(_('Clone Tracker Error')._(':').' '._('Effort Unit Set already define and not compatible'));
882 // do not filter and get disabled fields as well
883 $efs = $at->getExtraFields(array(), true);
885 // get current getExtraFields if any and includes disabled fields as well...
886 $current_efs = $this->getExtraFields(array(), true);
889 // Iterate list of extra fields
893 $newEFElIds = array();
894 foreach ($efs as $ef) {
895 //new field in this tracker
896 $nef = new ArtifactExtraField($this);
897 foreach ($current_efs as $current_ef) {
898 if ($current_ef['field_name'] == $ef['field_name'] || $current_ef['field_type'] == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
899 // we delete the current extra field and use the template one...
900 $current_ef_todelete = new ArtifactExtraField($this, $current_ef);
901 $current_ef_todelete->delete(true,true);
904 if (!$nef->create(util_unconvert_htmlspecialchars($ef['field_name']), $ef['field_type'], $ef['attribute1'], $ef['attribute2'], $ef['is_required'], $ef['alias'], $ef['show100'], $ef['show100label'], $ef['description'], $ef['pattern'], 100, 0, $ef['is_hidden_on_submit'], $ef['is_disabled'])) {
905 $this->setError(_('Error Creating New Extra Field')._(':').' '.$nef->getErrorMessage());
909 $newEFIds[$ef['extra_field_id']] = $nef->getID();
910 $newEFElIds[$ef['extra_field_id']] = array();
912 //by default extrafield status is created with default values: 'Open' & 'Closed'
913 if ($nef->getType() == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
914 $existingElements = $nef->getAvailableValues();
915 foreach($existingElements as $existingElement) {
916 $existingElement = new ArtifactExtraFieldElement($nef, $existingElement);
917 $existingElement->delete();
922 // Iterate the elements
924 if (in_array($ef['field_type'], unserialize(ARTIFACT_EXTRAFIELDTYPEGROUP_CHOICE))) {
925 $elements = $this->getExtraFieldElements($ef['extra_field_id']);
926 foreach ($elements as $el) {
928 $nel = new ArtifactExtraFieldElement($nef);
929 if (!$nel->create(util_unconvert_htmlspecialchars($el['element_name']), $el['status_id'], $el['auto_assign_to'], $el['is_default'])) {
931 $this->setError(_('Error Creating New Extra Field Element')._(':').' '.$nel->getErrorMessage());
934 $newEFElIds[$ef['extra_field_id']][$el['element_id']] = $nel->getID();
936 } elseif ($ef['field_type'] == ARTIFACT_EXTRAFIELDTYPE_USER) {
937 $elements = $this->getExtraFieldElements($ef['extra_field_id']);
938 $newRoles = $this->getGroup()->getRoles();
939 foreach ($elements as $el) {
940 $oldRole = RBACEngine::getInstance()->getRoleById($el['element_name']);
941 if ($oldRole && is_object($oldRole)) {
942 if ($oldRole->isPublic()) {
943 foreach ($newRoles as $newRole) {
944 if ($oldRole->getID() == $newRole->getID()) {
945 if (!$nel->create($el['element_name'])) {
947 $this->setError(_('Error Creating New Extra Field Element')._(':').' '.$nel->getErrorMessage());
950 $newEFElIds[$ef['extra_field_id']][$el['element_id']] = $nel->getID();
955 foreach ($newRoles as $newRole) {
956 if ($oldRole->getName() == $newRole->getName()) {
957 if (!$nel->create($newRole->getID())) {
959 $this->setError(_('Error Creating New Extra Field Element')._(':').' '.$nel->getErrorMessage());
962 $newEFElIds[$ef['extra_field_id']][$el['element_id']] = $nel->getID();
971 foreach ($newEFIds as $oldEFId => $newEFId) {
972 $oef = new ArtifactExtraField($at, $oldEFId);
973 $nef = new ArtifactExtraField($this, $newEFId);
974 // clone default value
975 $type = $oef->getType();
976 if (in_array($type, unserialize(ARTIFACT_EXTRAFIELDTYPEGROUP_VALUE)) || $type == ARTIFACT_EXTRAFIELDTYPE_USER) {
977 $default = $oef->getDefaultValues();
978 if (($type==ARTIFACT_EXTRAFIELDTYPE_INTEGER && $default != 0)) {
979 $nef->setDefaultValues($default);
980 } elseif ($type==ARTIFACT_EXTRAFIELDTYPE_USER && $default != 100) {
981 $roleEls = $this->getExtraFieldElements($newEFId);
982 $defaultUser = UserManager::instance()->getUserById($default);
983 foreach ($roleEls as $roleEl) {
984 $role = RBACEngine::getInstance()->getRoleById($roleEl['element_name']);
985 if ( $role->hasUser($defaultUser)) {
986 $nef->setDefaultValues($default);
992 $nef->setDefaultValues($default);
996 // update Dependency between extrafield
997 $oefParent = $oef->getParent();
998 if (!empty($oefParent) && $oef->getParent() != 100) {
999 if (!$nef->update($nef->getName(), $nef->getAttribute1(), $nef->getAttribute2(), $nef->isRequired(), $nef->getAlias(), $nef->getShow100(), $nef->getShow100label(), $nef->getDescription(), $nef->getPattern(), $newEFIds[$oef->getParent()], $nef->isAutoAssign(), $nef->isHiddenOnSubmit(), $nef->isDisabled())) {
1001 $this->setError(_('Error Updating New Extra Field Parent')._(':').' '.$nef->getErrorMessage());
1004 foreach ($newEFElIds[$oldEFId] as $oldEFElId => $newEFElId) {
1005 $oel = new ArtifactExtraFieldElement($oef,$oldEFElId);
1006 if ($oel->isError()) {
1008 $this->setError($oel->getErrorMessage());
1011 $nel = new ArtifactExtraFieldElement($nef,$newEFElId);
1012 if ($nel->isError()) {
1014 $this->setError($nel->getErrorMessage());
1017 $oPEls = $oel->getParentElements();
1019 foreach ($oPEls as $oPEl) {
1020 $nPEls[]=$newEFElIds[$oef->getParent()][$oPEl];
1022 $nel->saveParentElements($nPEls);
1023 if ($nel->isError()) {
1025 $this->setError(_('Error Saving New Extra Field Parent Elements').' '.$nel->getErrorMessage());
1031 if ($nef->getType() == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1032 // update the allowed init values
1033 $oatw = new ArtifactWorkflow($at, $oldEFId);
1034 $natw = new ArtifactWorkflow($this, $newEFId);
1035 // template allowed init values
1036 $oaivs = $oatw->getNextNodes('100');
1038 foreach ($oaivs as $oaiv) {
1039 $naivs[] = $newEFElIds[$oldEFId][$oaiv];
1041 $natw->saveNextNodes('100', $naivs);
1043 //implement role based of the workflow
1044 if (sizeof($id_mappings) && isset($id_mappings['role'])) {
1045 $oefelements = $at->getExtraFieldElements($oldEFId);
1046 foreach ($oefelements as $oefelement) {
1047 // retrieve the allowed values for the old element
1048 $onexts = $oatw->getNextNodes($oefelement['element_id']);
1050 foreach ($onexts as $onext) {
1051 $naivs[] = $newEFElIds[$oldEFId][$onext];
1052 //retrieve the allowed old roles from old element to old next value
1053 $oars = $oatw->getAllowedRoles($oefelement['element_id'], $onext);
1054 //map old roles into new roles id
1056 foreach ($oars as $oar) {
1057 if (array_key_exists($oar, $id_mappings['role'])) {
1058 $nar[] = $id_mappings['role'][$oar];
1061 $natw->saveAllowedRoles($newEFElIds[$oldEFId][$oefelement['element_id']], $newEFElIds[$oldEFId][$onext], $nar);
1063 $natw->saveNextNodes($newEFElIds[$oldEFId][$oefelement['element_id']], $naivs);
1074 * getExtraFieldName - Get a box name using the box ID
1076 * @param int $extra_field_id id of an extra field.
1077 * @return string name of extra field.
1079 function getExtraFieldName($extra_field_id) {
1080 $arr = $this->getExtraFields();
1081 return $arr[$extra_field_id]['field_name'];
1085 * getExtraFieldElements - List of possible admin configured
1086 * extra field elements. This function is used to
1087 * present the boxes and choices on the main Add/Update page.
1089 * @param int $id id of the extra field
1090 * @return array of elements for this extra field.
1092 function getExtraFieldElements($id) {
1097 if (!isset($this->extra_field[$id])) {
1098 $this->extra_field[$id] = array();
1099 $ef = new ArtifactExtraField($this,$id);
1100 if (!$ef || $ef->isError()) {
1103 $efValues = $ef->getAvailableValues();
1104 $this->extra_field[$id] = $efValues;
1107 return $this->extra_field[$id];
1111 * getElementName - get the name of a particular element.
1114 * @return string The name.
1116 function getElementName($choice_id) {
1120 if (is_array($choice_id)) {
1121 $choice_id = implode(',', array_map('intval', $choice_id));
1123 $choice_id = intval($choice_id);
1125 if ($choice_id == 100) {
1128 if (!isset($this->element_name[$choice_id])) {
1129 $res = db_query_params('SELECT element_id, element_name
1130 FROM artifact_extra_field_elements
1131 WHERE element_id = ANY ($1)',
1132 array(db_int_array_to_any_clause(explode(',', $choice_id))));
1133 if (db_numrows($res) > 1) {
1134 $arr = util_result_column_to_array($res, 1);
1135 $this->element_name[$choice_id] = implode(',', $arr);
1137 $this->element_name[$choice_id] = db_result($res, 0, 'element_name');
1140 return $this->element_name[$choice_id];
1144 * getElementStatusID - get the status of a particular element.
1146 * @param int|array $choice_id
1147 * @return int The status
1149 function getElementStatusID($choice_id) {
1153 if (is_array($choice_id)) {
1154 $choice_id = implode(',',$choice_id);
1156 if ($choice_id == 100) {
1159 if (!$this->element_status[$choice_id]) {
1160 $res = db_query_params('SELECT element_id,extra_field_id,status_id
1161 FROM artifact_extra_field_elements
1162 WHERE element_id = ANY ($1)',
1163 array(db_int_array_to_any_clause(explode(',', $choice_id))));
1164 if (db_numrows($res) > 1) {
1165 $arr = util_result_column_to_array($res, 2);
1166 $this->element_status[$choice_id] = implode(',', $arr);
1168 $this->element_status[$choice_id] = db_result($res, 0, 'status_id');
1171 return $this->element_status[$choice_id];
1175 * delete - delete this tracker and all its related data.
1177 * @param bool $sure I'm Sure.
1178 * @param bool $really_sure I'm REALLY sure.
1179 * @return bool true/false;
1181 function delete($sure, $really_sure) {
1182 if (!$sure || !$really_sure) {
1183 $this->setMissingParamsError(_('Please tick all checkboxes.'));
1186 if (!forge_check_perm ('tracker_admin', $this->Group->getID())) {
1187 $this->setPermissionDeniedError();
1191 db_query_params('DELETE FROM artifact_extra_field_data
1192 WHERE EXISTS (SELECT artifact_id FROM artifact
1193 WHERE group_artifact_id=$1
1194 AND artifact.artifact_id=artifact_extra_field_data.artifact_id)',
1195 array($this->getID()));
1196 db_query_params('DELETE FROM artifact_extra_field_elements
1197 WHERE EXISTS (SELECT extra_field_id FROM artifact_extra_field_list
1198 WHERE group_artifact_id=$1
1199 AND artifact_extra_field_list.extra_field_id = artifact_extra_field_elements.extra_field_id)',
1200 array($this->getID()));
1201 db_query_params('DELETE FROM artifact_extra_field_list
1202 WHERE group_artifact_id=$1',
1203 array($this->getID()));
1204 db_query_params('DELETE FROM artifact_canned_responses
1205 WHERE group_artifact_id=$1',
1206 array($this->getID()));
1207 db_query_params('DELETE FROM artifact_counts_agg
1208 WHERE group_artifact_id=$1',
1209 array($this->getID()));
1211 ArtifactStorage::instance()->deleteFromQuery('SELECT id FROM artifact_file
1212 WHERE EXISTS (SELECT artifact_id FROM artifact
1213 WHERE group_artifact_id=$1
1214 AND artifact.artifact_id=artifact_file.artifact_id)',
1215 array($this->getID()));
1217 db_query_params('DELETE FROM artifact_file
1218 WHERE EXISTS (SELECT artifact_id FROM artifact
1219 WHERE group_artifact_id=$1
1220 AND artifact.artifact_id=artifact_file.artifact_id)',
1221 array($this->getID()));
1222 db_query_params('DELETE FROM artifact_message
1223 WHERE EXISTS (SELECT artifact_id FROM artifact
1224 WHERE group_artifact_id=$1
1225 AND artifact.artifact_id=artifact_message.artifact_id)',
1226 array($this->getID()));
1227 db_query_params('DELETE FROM artifact_history
1228 WHERE EXISTS (SELECT artifact_id FROM artifact
1229 WHERE group_artifact_id=$1
1230 AND artifact.artifact_id=artifact_history.artifact_id)',
1231 array($this->getID()));
1232 db_query_params('DELETE FROM artifact_monitor
1233 WHERE EXISTS (SELECT artifact_id FROM artifact
1234 WHERE group_artifact_id=$1
1235 AND artifact.artifact_id=artifact_monitor.artifact_id)',
1236 array($this->getID()));
1237 db_query_params('DELETE FROM artifact
1238 WHERE group_artifact_id=$1',
1239 array($this->getID()));
1240 db_query_params('DELETE FROM artifact_group_list
1241 WHERE group_artifact_id=$1',
1242 array($this->getID()));
1243 $MonitorElementObject = new MonitorElement('artifact_type');
1244 $MonitorElementObject->clearMonitor($this->getID());
1247 ArtifactStorage::instance()->commit();
1249 $this->Group->normalizeAllRoles();
1255 * getSubmitters - returns a result set of submitters.
1257 * @return resource database result set.
1259 function getSubmitters() {
1260 if (!isset($this->submitters_res)) {
1261 $this->submitters_res = db_query_params('SELECT DISTINCT submitted_by, submitted_realname
1263 WHERE group_artifact_id=$1
1264 ORDER BY submitted_realname',
1265 array($this->getID()));
1267 return $this->submitters_res;
1271 * getLastModifiers - returns a result set of last modifiers.
1273 * @return resource database result set.
1275 function getLastModifiers() {
1276 if (!isset($this->last_modifiers_res)) {
1277 $this->last_modifiers_res = db_query_params('SELECT DISTINCT last_modified_by, last_modified_realname
1279 WHERE group_artifact_id=$1
1280 ORDER BY last_modified_realname',
1281 array($this->getID()));
1283 return $this->last_modifiers_res;
1287 * getCannedResponses - returns a result set of canned responses.
1289 * @return resource database result set.
1291 function getCannedResponses() {
1292 if (!isset($this->cannedresponses_res)) {
1293 $this->cannedresponses_res = db_query_params('SELECT id,title
1294 FROM artifact_canned_responses
1295 WHERE group_artifact_id=$1',
1296 array($this->getID()));
1298 return $this->cannedresponses_res;
1302 * getStatuses - returns a result set of statuses.
1304 * These statuses are either the default open/closed or any number of
1305 * custom statuses that are stored in the extra fields. On insert/update
1306 * to an artifact the status_id is remapped from the extra_field_element_id to
1307 * the standard open/closed id.
1309 * @return resource database result set.
1311 function getStatuses() {
1312 if (!isset($this->status_res)) {
1313 $this->status_res = db_query_params('SELECT * FROM artifact_status', array());
1315 return $this->status_res;
1319 * getStatusName - returns the name of this status.
1321 * @param int $id The status ID.
1322 * @return string name.
1324 function getStatusName($id) {
1325 $result = db_query_params('select status_name from artifact_status WHERE id=$1',
1327 if ($result && db_numrows($result) > 0) {
1328 return db_result($result, 0, 'status_name');
1330 return 'Error: Not Found';
1335 * update - use this to update this ArtifactType in the database.
1337 * @param string $name The item name.
1338 * @param string $description The item description.
1339 * @param bool $email_all (1) true (0) false - whether to email on all updates.
1340 * @param string $email_address The address to send new entries and updates to.
1341 * @param int $due_period Days before this item is considered overdue.
1342 * @param int $status_timeout Days before stale items time out.
1343 * @param bool $use_resolution (1) true (0) false - whether the resolution box should be shown.
1344 * @param string $submit_instructions Free-form string that project admins can place on the submit page.
1345 * @param string $browse_instructions Free-form string that project admins can place on the browse page.
1346 * @return bool true on success, false on failure.
1348 function update($name, $description, $email_all, $email_address,
1349 $due_period, $status_timeout, $use_resolution, $submit_instructions, $browse_instructions) {
1351 if (!forge_check_perm ('tracker_admin', $this->Group->getID())) {
1352 $this->setPermissionDeniedError();
1356 if ($this->getDataType()) {
1357 $name=$this->getName();
1358 $description=$this->getDescription();
1361 if (!$name || !$description || !$due_period || !$status_timeout) {
1362 $this->setError(_('ArtifactType: Name, Description, Due Period, and Status Timeout are required'));
1366 $result = db_query_params('SELECT count(*) AS count FROM artifact_group_list WHERE group_id=$1 AND name=$2 AND group_artifact_id!=$3',
1367 array($this->Group->getID(), $name, $this->getID()));
1369 $this->setError('ArtifactType::Update(): '.db_error());
1372 if (db_result($result, 0, 'count')) {
1373 $this->setError(_('Tracker name already used'));
1377 if ($email_address) {
1378 $invalid_emails = validate_emails($email_address);
1379 if (count($invalid_emails) > 0) {
1380 $this->setError(_('E-mail address(es) appeared invalid')._(': ').implode(',', $invalid_emails));
1385 $email_all = ((!$email_all) ? 0 : $email_all);
1386 $use_resolution = ((!$use_resolution) ? 0 : $use_resolution);
1388 $res = db_query_params('UPDATE artifact_group_list SET
1391 email_all_updates=$3,
1395 submit_instructions=$7,
1396 browse_instructions=$8
1397 WHERE group_artifact_id=$9 AND group_id=$10',
1399 htmlspecialchars($name),
1400 htmlspecialchars($description),
1403 $due_period * (60*60*24),
1404 $status_timeout * (60*60*24),
1405 htmlspecialchars($submit_instructions),
1406 htmlspecialchars($browse_instructions),
1408 $this->Group->getID()));
1410 if (!$res || db_affected_rows($res) < 1) {
1411 $this->setError('ArtifactType::Update(): '.db_error());
1414 $this->fetchData($this->getID());
1420 * getBrowseList - get the list of columns in browse page.
1422 * @return string instructions.
1424 function getBrowseList() {
1425 $list = $this->data_array['browse_list'];
1427 // remove status_id in the browse list if a custom status exists
1428 if (count($this->getExtraFields(array(ARTIFACT_EXTRAFIELDTYPE_STATUS))) > 0) {
1429 $arr = explode(',', $list);
1430 $idx = array_search('status_id', $arr);
1431 if ($idx !== False) {
1432 array_splice($arr, $idx, 1);
1434 return join(',', $arr);
1441 * setBrowseList - set the list of columns in browse page.
1443 * @param string $list string comma separated of ids of custom field and names of internal fields.
1444 * @return boolean success.
1446 function setBrowseList($list) {
1447 $res = db_query_params('UPDATE artifact_group_list
1449 WHERE group_artifact_id=$2',
1452 $this->fetchData($this->getID());
1457 * canVote - check whether the current user can vote on
1458 * items in this tracker
1460 * @return bool true if they can
1462 function canVote() {
1463 return forge_check_perm('tracker', $this->getID(), 'vote');
1467 * getVoters - get IDs of users that may vote on
1468 * items in this tracker
1470 * @return array list of user IDs
1472 function getVoters() {
1473 if ($this->voters !== false) {
1474 return $this->voters;
1477 $this->voters = array();
1478 if (($engine = RBACEngine::getInstance())
1479 && ($voters = $engine->getUsersByAllowedAction('tracker', $this->getID(), 'vote'))
1480 && (count($voters) > 0)) {
1481 foreach ($voters as $voter) {
1482 $voter_id = $voter->getID();
1483 $this->voters[$voter_id] = $voter_id;
1486 return $this->voters;
1492 * @param integer $unit_set_id the effort unit set id
1495 function setEffortUnitSet($unit_set_id) {
1497 $res = db_query_params ('UPDATE artifact_group_list SET unit_set_id=$1 WHERE group_artifact_id=$2',
1498 array($unit_set_id, $this->getID()));
1500 $this->data_array['unit_set_id']=$unit_set_id;
1510 * getEffortUnitSet - Get the effort unit set id.
1512 * @return integer The id of the effort unit set.
1514 function getEffortUnitSet() {
1515 return $this->data_array['unit_set_id'];
1519 * getSettings - Get all parameters of this tracker
1521 * @return array all parameters into an multidimensional array
1523 function getSettings() {
1524 // Get list of extra fields for this artifact
1525 $extrafields = array();
1526 $tmpextrafields = $this->getExtraFields(array(), true);
1527 foreach ($tmpextrafields as $extrafield) {
1528 $aefobj = new ArtifactExtraField($this, $extrafield["extra_field_id"]);
1530 // array of available values
1531 $avtmp = $aefobj->getAvailableValues();
1533 for ($j=0; $j < count($avtmp); $j++) {
1534 $avs[$j]["element_id"] = $avtmp[$j]["element_id"];
1535 $avs[$j]["element_name"] = $avtmp[$j]["element_name"];
1536 $avs[$j]["status_id"] = $avtmp[$j]["status_id"];
1539 $extrafields[] = array(
1540 "extra_field_id" => $aefobj->getID(),
1541 "field_name" => $aefobj->getName(),
1542 "field_type" => $aefobj->getType(),
1543 "attribute1" => $aefobj->getAttribute1(),
1544 "attribute2" => $aefobj->getAttribute2(),
1545 "is_required" => $aefobj->isRequired(),
1546 "alias" => $aefobj->getAlias(),
1547 "available_values" => $avs,
1548 "default_selected_id" => 0 //TODO (not implemented yet)
1553 'group_artifact_id' => $this->data_array['group_artifact_id'],
1554 'group_id' => $this->data_array['group_id'],
1555 'name' => $this->data_array['name'],
1556 'description' => $this->data_array['description'],
1557 'due_period' => $this->data_array['due_period'],
1558 'datatype' => $this->data_array['datatype'],
1559 'status_timeout' => $this->data_array['status_timeout'],
1560 'extra_fields' => $extrafields,
1561 'custom_status_field' => $this->data_array['custom_status_field'],
1562 'use_tracker_widget_display' => $this->getWidgetLayoutConfig());
1566 function getWidgetLayoutConfig() {
1567 $lm = new WidgetLayoutManager();
1568 return $lm->getLayout($this->getID(), WidgetLayoutManager::OWNER_TYPE_TRACKER);
1574 // c-file-style: "bsd"