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. //TODO: unused parameter. to be drop!
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 $email_all = ((!$email_all) ? 0 : $email_all);
242 $res = db_query_params('INSERT INTO
256 ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)',
257 array($this->Group->getID(),
258 htmlspecialchars($name),
259 htmlspecialchars($description),
262 $due_period*(60*60*24),
264 htmlspecialchars($submit_instructions),
265 htmlspecialchars($browse_instructions),
267 $this->Group->getEffortUnitSet()
270 $id = db_insertid($res, 'artifact_group_list', 'group_artifact_id');
273 $this->setError(_('ArtifactType')._(': ').db_error());
277 if (!$this->fetchData($id)) {
281 $this->Group->normalizeAllRoles();
289 * fetchData - re-fetch the data for this ArtifactType from the database.
291 * @param int $artifact_type_id The artifact type ID.
292 * @return boolean success.
294 function fetchData($artifact_type_id) {
295 $this->voters = false;
296 $this->extra_field = false;
297 $this->extra_fields = false;
298 $res = db_query_params('SELECT * FROM artifact_group_list_vw
299 WHERE group_artifact_id=$1
301 array($artifact_type_id,
302 $this->Group->getID()));
303 if (!$res || db_numrows($res) < 1) {
304 $this->setError(_('ArtifactType')._(': ')._('Invalid ArtifactTypeID'));
307 $this->data_array = db_fetch_array($res);
308 db_free_result($res);
313 * getGroup - get the Group object this ArtifactType is associated with.
315 * @return Object The Group object.
317 function &getGroup() {
322 * getID - get this ArtifactTypeID.
324 * @return int The group_artifact_id #.
327 return $this->data_array['group_artifact_id'];
331 * getOpenCount - get the count of open tracker items in this tracker type.
333 * @return int The count.
335 function getOpenCount() {
336 return $this->data_array['open_count'];
340 * getTotalCount - get the total number of tracker items in this tracker type.
342 * @return int The total count.
344 function getTotalCount() {
345 return $this->data_array['count'];
349 * getSubmitInstructions - get the free-form string strings.
351 * @return string instructions.
353 function getSubmitInstructions() {
354 return $this->data_array['submit_instructions'];
358 * getBrowseInstructions - get the free-form string strings.
360 * @return string instructions.
362 function getBrowseInstructions() {
363 return $this->data_array['browse_instructions'];
367 * emailAll - determine if we're supposed to email on every event.
369 * @return boolean email_all.
371 function emailAll() {
372 return $this->data_array['email_all_updates'];
376 * emailAddress - defined email address to send events to.
378 * @return string email.
380 function getEmailAddress() {
381 return $this->data_array['email_address'];
385 * getName - the name of this ArtifactType.
387 * @return string name.
390 return $this->data_array['name'];
394 * getFormattedName - formatted name of this ArtifactType
396 * @return string formatted name
398 function getFormattedName() {
399 $name = preg_replace('/[^[:alnum:]]/', '', $this->getName());
400 $name = strtolower($name);
405 * getUnixName - returns the name used by email gateway
407 * @return string unix name
409 function getUnixName() {
410 return strtolower($this->Group->getUnixName()).'-'.$this->getFormattedName();
414 * getReturnEmailAddress - return the return email address for notification emails
416 * @return string return email address
418 function getReturnEmailAddress() {
421 if (forge_get_config('use_gateways')) {
422 $address .= strtolower($this->getUnixName());
424 $address .= 'noreply';
426 $address .= '@'.forge_get_config('web_host');
431 * getDescription - the description of this ArtifactType.
433 * @return string description.
435 function getDescription() {
436 return $this->data_array['description'];
440 * getDuePeriod - how many seconds until it's considered overdue.
442 * @return int seconds.
444 function getDuePeriod() {
445 return $this->data_array['due_period'];
449 * getStatusTimeout - how many seconds until an item is stale.
451 * @return int seconds.
453 function getStatusTimeout() {
454 return $this->data_array['status_timeout'];
458 * getCustomStatusField - return the extra_field_id of the field containing the custom status.
460 * @return int extra_field_id.
462 function getCustomStatusField() {
463 return $this->data_array['custom_status_field'];
467 * setCustomStatusField - set the extra_field_id of the field containing the custom status.
469 * @param int $extra_field_id The extra field id.
470 * @return boolean success.
472 function setCustomStatusField($extra_field_id) {
473 $res = db_query_params('UPDATE artifact_group_list SET custom_status_field=$1
474 WHERE group_artifact_id=$2',
475 array($extra_field_id,
477 $this->fetchData($this->getID());
482 * getAutoAssignField - get the extra_field_id of the field that triggers auto-assignment rules.
484 * @return int extra_field_id.
486 function getAutoAssignField() {
487 return $this->data_array['auto_assign_field'];
491 * setAutoAssignField - set the extra_field_id of the field that triggers auto-assignment rules.
493 * @param int $extra_field_id The extra field id.
494 * @return boolean success.
496 function setAutoAssignField($extra_field_id) {
497 $res = db_query_params('UPDATE artifact_group_list SET auto_assign_field=$1
498 WHERE group_artifact_id=$2',
499 array($extra_field_id,
501 $this->fetchData($this->getID());
506 * usesCustomStatuses - boolean
508 * @return boolean use_custom_statues.
510 function usesCustomStatuses() {
511 return $this->getCustomStatusField();
515 * remapStatus - pass the extra_fields array and return the status_id, either open/closed
517 * @param int $status_id The status_id
518 * @param array $extra_fields Complex array of extra_field_data
519 * @return int status_id.
521 function remapStatus($status_id, $extra_fields) {
522 if ($this->usesCustomStatuses()) {
523 //get the selected element for the extra_field_status element
524 $csfield = $this->getCustomStatusField();
525 if (array_key_exists($csfield, $extra_fields)) {
526 $element_id = $extra_fields[$csfield];
528 //convert that element_id into the status_id
529 $res = db_query_params('SELECT status_id FROM artifact_extra_field_elements WHERE element_id=$1',
532 $this->setError(_('Error Remapping Status')._(': ').db_error());
535 $status_id = db_result($res, 0, 'status_id');
536 if ($status_id < 1 || $status_id > 4) {
537 $this->setError(sprintf(_('INVALID STATUS REMAP: %d FROM SELECTED ELEMENT: %d'), $status_id, $element_id));
541 // custom status was not passed... use the first status from the database
542 $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',
544 if (db_numrows($res) == 0) { // No values available
545 $this->setError(_('Error Remapping Status'));
548 $status_id = db_result($res, 0, 'status_id');
557 * getDataType - flag that is generally unused but can mark the difference between bugs, patches, etc.
559 * @return int The type (1) bug (2) support (3) patch (4) feature (0) other.
561 function getDataType() {
562 return $this->data_array['datatype'];
566 * setMonitor - user can monitor this artifact.
568 * @param int $user_id
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');
598 function isMonitoring() {
599 if (!session_loggedin()) {
602 $MonitorElementObject = new MonitorElement('artifact_type');
603 return $MonitorElementObject->isMonitoredByUserId($this->getID(), user_getid());
607 * getMonitorIds - array of id of users monitoring this Artifact.
609 * @return array array of id of users monitoring this Artifact.
611 function getMonitorIds() {
612 $MonitorElementObject = new MonitorElement('artifact_type');
613 return $MonitorElementObject->getMonitorUsersIdsInArray($this->getID());
617 * getExtraFields - List of possible user built extra fields
618 * set up for this artifact type.
620 * @param array $types
621 * @param bool $get_is_disabled
622 * @param bool $get_is_hidden_on_submit
623 * @return array arrays of data;
625 function getExtraFields($types = array(), $get_is_disabled = false, $get_is_hidden_on_submit = true) {
628 if (!$get_is_disabled) {
629 $where = ' AND is_disabled = 0';
633 if (!$get_is_hidden_on_submit) {
634 $where = ' AND is_hidden_on_submit = 0';
638 $filter = implode(',', $types);
639 $types = explode(',', $filter);
643 if (!isset($this->extra_fields[$filter]) || !$use_cache) {
644 $extra_fields = array();
646 $res = db_query_params('SELECT *
647 FROM artifact_extra_field_list
648 WHERE group_artifact_id=$1
649 AND field_type = ANY ($2)'.
651 'ORDER BY field_type ASC',
652 array($this->getID(), db_int_array_to_any_clause($types)));
654 $res = db_query_params('SELECT *
655 FROM artifact_extra_field_list
656 WHERE group_artifact_id=$1'.
658 'ORDER BY field_type ASC',
659 array($this->getID()));
661 while ($arr = db_fetch_array($res)) {
662 $extra_fields[$arr['extra_field_id']] = $arr;
665 if (!isset($this->extra_fields[$filter])) {
666 $this->extra_fields[$filter] = $extra_fields;
669 $extra_fields = $this->extra_fields[$filter];
671 return $extra_fields;
675 * getExtraFieldsDefaultValue - Get array of extra fields default value
677 * @param array $types
678 * @param bool $get_is_disabled
679 * @param bool $get_is_hidden_on_submit
680 * @return array arrays of data;
682 function getExtraFieldsDefaultValue($types = array(), $get_is_disabled = false, $get_is_hidden_on_submit = true) {
683 $extra_fields = $this->getExtraFields($types, $get_is_disabled, $get_is_hidden_on_submit);
684 $efDefaultValue = array();
685 foreach ($extra_fields as $efID=>$efArr) {
686 $ef = new ArtifactExtraField($this, $efID);
687 $defaultValue = $ef->getDefaultValues();
688 if (!is_null($defaultValue)) {
689 $efDefaultValue [$efID] = $defaultValue;
691 if (in_array($efArr['field_type'],unserialize(ARTIFACT_EXTRAFIELDTYPEGROUP_SINGLECHOICE))) {
692 $efDefaultValue [$efID] = '';
696 return $efDefaultValue;
700 * getExtraFieldsInFormula - Get array of extra fields used in formula
702 * @param array $types
703 * @param bool $get_is_disabled
704 * @param bool $get_is_hidden_on_submit
705 * @return array arrays of data;
707 function getExtraFieldsInFormula($types = array(), $get_is_disabled = false, $get_is_hidden_on_submit = true) {
709 $extra_fields = $this->getExtraFields($types, $get_is_disabled, $get_is_hidden_on_submit);
710 $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',
711 array ($this->getID()));
712 if (db_numrows($res) > 0) {
713 $row = db_fetch_array($res);
714 if (preg_match_all("/([a-z]\w*)/m", $row[0], $matches)) {
715 foreach ($extra_fields as $extra_field) {
716 if (in_array($extra_field['alias'],$matches[0])) {
717 $return[]=$extra_field['extra_field_id'];
726 * getFieldsInFormula - Get array of extra fields used in formula
728 * @return array arrays of data;
730 function getFieldsInFormula() {
732 if ($this->usesCustomStatuses()) {
733 $fields = array('assigned_to','priority','summary','description');
735 $fields = array('assigned_to','priority','summary','description','status');
737 $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',
738 array($this->getID()));
739 if (db_numrows($res) > 0) {
740 $row = db_fetch_array($res);
741 if (preg_match_all("/([a-z]\w*)/m", $row[0], $matches)) {
742 foreach ($fields as $field) {
743 if (in_array($field,$matches[0])) {
753 * getExtraFieldsWithFormula - Get array of extra fields with formula
755 * @param array $types
756 * @param bool $get_is_disabled
757 * @param bool $get_is_hidden_on_submit
758 * @return array arrays of data;
760 function getExtraFieldsWithFormula($types = array(), $get_is_disabled = false, $get_is_hidden_on_submit = true) {
762 $extra_fields = $this->getExtraFields($types, $get_is_disabled, $get_is_hidden_on_submit);
763 $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',
764 array ($this->getID()));
765 while ($arr = db_fetch_array($res)) {
766 $return []= $arr['extra_field_id'];
772 * cloneFieldsFrom - clone all the fields and elements from another tracker
774 * @param int $clone_tracker_id id of the cloned tracker
775 * @param array $id_mappings array mapping between template objects and new project objects
776 * @return boolean true/false on success
778 function cloneFieldsFrom($clone_tracker_id, $id_mappings = array()) {
779 $at = artifactType_get_object($clone_tracker_id);
780 if (!$at || !is_object($at)) {
781 $this->setError(_('Could Not Get Tracker To Clone'));
783 } elseif ($at->isError()) {
784 $this->setError(_('Clone Tracker Error').' '.$at->getErrorMessage());
789 $ef_effort = $at->getExtraFields(array(ARTIFACT_EXTRAFIELDTYPE_EFFORT));
790 if (!empty($ef_effort)) {
791 $eus = new EffortUnitSet($at, $at->getEffortUnitSet());
792 $this_eus = new EffortUnitSet($this, $this->getEffortUnitSet());
793 switch ($eus->getLevel()) {
794 case EFFORTUNITSET_FORGE_LEVEL:
795 switch ($this_eus->getLevel()) {
796 case EFFORTUNITSET_PROJECT_LEVEL:
797 if (!$this_eus->isEquivalentTo($eus)) {
798 // make a copy at the tracker level
799 $new_eus = new EffortUnitSet($this);
800 $new_eus->copy($eus);
801 $this->setEffortUnitSet($new_eus->getID());
804 case EFFORTUNITSET_TRACKER_LEVEL:
805 if (!$this_eus->isEquivalentTo($eus)) {
806 $this->setError(_('Clone Tracker Error')._(':').' '._('Effort Unit Set already define and not compatible'));
812 case EFFORTUNITSET_PROJECT_LEVEL:
813 switch ($this_eus->getLevel()) {
814 case EFFORTUNITSET_FORGE_LEVEL:
815 $new_eus_id = getEffortUnitSetForLevel($this, EFFORTUNITSET_PROJECT_LEVEL);
817 $new_eus = new EffortUnitSet($this, $new_eus_id);
818 if (!$new_eus->isEquivalentTo($eus)) {
819 $this->setEffortUnitSet($new_eus->getID());
821 // make a copy at the tracker level
822 $new_eus = new EffortUnitSet($this);
823 $new_eus->copy($eus);
824 $this->setEffortUnitSet($new_eus->getID());
827 // make a copy at the project level
828 $new_eus = new EffortUnitSet($this->Group);
829 $new_eus->copy($eus);
830 $this->setEffortUnitSet($new_eus->getID());
833 case EFFORTUNITSET_PROJECT_LEVEL:
834 if (!$this_eus->isEquivalentTo($eus)) {
835 // make a copy at the tracker level
836 $new_eus = new EffortUnitSet($this);
837 $new_eus->copy($eus);
838 $this->setEffortUnitSet($new_eus->getID());
841 case EFFORTUNITSET_TRACKER_LEVEL:
842 if (!$this_eus->isEquivalentTo($eus)) {
843 $this->setError(_('Clone Tracker Error')._(':').' '._('Effort Unit Set already define and not compatible'));
849 case EFFORTUNITSET_TRACKER_LEVEL:
850 switch ($this_eus->getLevel()) {
851 case EFFORTUNITSET_FORGE_LEVEL:
852 case EFFORTUNITSET_PROJECT_LEVEL:
853 $new_eus_id = getEffortUnitSetForLevel($this, EFFORTUNITSET_TRACKER_LEVEL);
855 $new_eus = new EffortUnitSet($this, $new_eus_id);
856 if (!$new_eus->isEquivalentTo($eus)) {
857 $this->setEffortUnitSet($new_eus->getID());
859 $this->setError(_('Clone Tracker Error')._(':').' '._('Effort Unit Set already define and not compatible'));
863 // make a copy at the tracker level
864 $new_eus = new EffortUnitSet($this);
865 $new_eus->copy($eus);
866 $this->setEffortUnitSet($new_eus->getID());
869 case EFFORTUNITSET_TRACKER_LEVEL:
870 if (!$this_eus->isEquivalentTo($eus)) {
871 $this->setError(_('Clone Tracker Error')._(':').' '._('Effort Unit Set already define and not compatible'));
880 // do not filter and get disabled fields as well
881 $efs = $at->getExtraFields(array(), true);
883 // get current getExtraFields if any and includes disabled fields as well...
884 $current_efs = $this->getExtraFields(array(), true);
887 // Iterate list of extra fields
891 $newEFElIds = array();
892 foreach ($efs as $ef) {
893 //new field in this tracker
894 $nef = new ArtifactExtraField($this);
895 foreach ($current_efs as $current_ef) {
896 if ($current_ef['field_name'] == $ef['field_name'] || $current_ef['field_type'] == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
897 // we delete the current extra field and use the template one...
898 $current_ef_todelete = new ArtifactExtraField($this, $current_ef);
899 $current_ef_todelete->delete(true,true);
902 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'])) {
903 $this->setError(_('Error Creating New Extra Field')._(':').' '.$nef->getErrorMessage());
907 $newEFIds[$ef['extra_field_id']] = $nef->getID();
908 $newEFElIds[$ef['extra_field_id']] = array();
910 //by default extrafield status is created with default values: 'Open' & 'Closed'
911 if ($nef->getType() == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
912 $existingElements = $nef->getAvailableValues();
913 foreach($existingElements as $existingElement) {
914 $existingElement = new ArtifactExtraFieldElement($nef, $existingElement);
915 $existingElement->delete();
920 // Iterate the elements
922 if (in_array($ef['field_type'], unserialize(ARTIFACT_EXTRAFIELDTYPEGROUP_CHOICE))) {
923 $elements = $this->getExtraFieldElements($ef['extra_field_id']);
924 foreach ($elements as $el) {
926 $nel = new ArtifactExtraFieldElement($nef);
927 if (!$nel->create(util_unconvert_htmlspecialchars($el['element_name']), $el['status_id'], $el['auto_assign_to'], $el['is_default'])) {
929 $this->setError(_('Error Creating New Extra Field Element')._(':').' '.$nel->getErrorMessage());
932 $newEFElIds[$ef['extra_field_id']][$el['element_id']] = $nel->getID();
934 } elseif ($ef['field_type'] == ARTIFACT_EXTRAFIELDTYPE_USER) {
935 $elements = $this->getExtraFieldElements($ef['extra_field_id']);
936 $newRoles = $this->getGroup()->getRoles();
937 foreach ($elements as $el) {
938 $oldRole = RBACEngine::getInstance()->getRoleById($el['element_name']);
939 if ($oldRole && is_object($oldRole)) {
940 if ($oldRole->isPublic()) {
941 foreach ($newRoles as $newRole) {
942 if ($oldRole->getID() == $newRole->getID()) {
943 if (!$nel->create($el['element_name'])) {
945 $this->setError(_('Error Creating New Extra Field Element')._(':').' '.$nel->getErrorMessage());
948 $newEFElIds[$ef['extra_field_id']][$el['element_id']] = $nel->getID();
953 foreach ($newRoles as $newRole) {
954 if ($oldRole->getName() == $newRole->getName()) {
955 if (!$nel->create($newRole->getID())) {
957 $this->setError(_('Error Creating New Extra Field Element')._(':').' '.$nel->getErrorMessage());
960 $newEFElIds[$ef['extra_field_id']][$el['element_id']] = $nel->getID();
969 foreach ($newEFIds as $oldEFId => $newEFId) {
970 $oef = new ArtifactExtraField($at, $oldEFId);
971 $nef = new ArtifactExtraField($this, $newEFId);
972 // clone default value
973 $type = $oef->getType();
974 if (in_array($type, unserialize(ARTIFACT_EXTRAFIELDTYPEGROUP_VALUE)) || $type == ARTIFACT_EXTRAFIELDTYPE_USER) {
975 $default = $oef->getDefaultValues();
976 if (($type==ARTIFACT_EXTRAFIELDTYPE_INTEGER && $default != 0)) {
977 $nef->setDefaultValues($default);
978 } elseif ($type==ARTIFACT_EXTRAFIELDTYPE_USER && $default != 100) {
979 $roleEls = $this->getExtraFieldElements($newEFId);
980 $defaultUser = UserManager::instance()->getUserById($default);
981 foreach ($roleEls as $roleEl) {
982 $role = RBACEngine::getInstance()->getRoleById($roleEl['element_name']);
983 if ( $role->hasUser($defaultUser)) {
984 $nef->setDefaultValues($default);
990 $nef->setDefaultValues($default);
994 // update Dependency between extrafield
995 $oefParent = $oef->getParent();
996 if (!empty($oefParent) && $oef->getParent() != 100) {
997 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())) {
999 $this->setError(_('Error Updating New Extra Field Parent')._(':').' '.$nef->getErrorMessage());
1002 foreach ($newEFElIds[$oldEFId] as $oldEFElId => $newEFElId) {
1003 $oel = new ArtifactExtraFieldElement($oef,$oldEFElId);
1004 if ($oel->isError()) {
1006 $this->setError($oel->getErrorMessage());
1009 $nel = new ArtifactExtraFieldElement($nef,$newEFElId);
1010 if ($nel->isError()) {
1012 $this->setError($nel->getErrorMessage());
1015 $oPEls = $oel->getParentElements();
1017 foreach ($oPEls as $oPEl) {
1018 $nPEls[]=$newEFElIds[$oef->getParent()][$oPEl];
1020 $nel->saveParentElements($nPEls);
1021 if ($nel->isError()) {
1023 $this->setError(_('Error Saving New Extra Field Parent Elements').' '.$nel->getErrorMessage());
1029 if ($nef->getType() == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
1030 // update the allowed init values
1031 $oatw = new ArtifactWorkflow($at, $oldEFId);
1032 $natw = new ArtifactWorkflow($this, $newEFId);
1033 // template allowed init values
1034 $oaivs = $oatw->getNextNodes('100');
1036 foreach ($oaivs as $oaiv) {
1037 $naivs[] = $newEFElIds[$oldEFId][$oaiv];
1039 $natw->saveNextNodes('100', $naivs);
1041 //implement role based of the workflow
1042 if (sizeof($id_mappings) && isset($id_mappings['role'])) {
1043 $oefelements = $at->getExtraFieldElements($oldEFId);
1044 foreach ($oefelements as $oefelement) {
1045 // retrieve the allowed values for the old element
1046 $onexts = $oatw->getNextNodes($oefelement['element_id']);
1048 foreach ($onexts as $onext) {
1049 $naivs[] = $newEFElIds[$oldEFId][$onext];
1050 //retrieve the allowed old roles from old element to old next value
1051 $oars = $oatw->getAllowedRoles($oefelement['element_id'], $onext);
1052 //map old roles into new roles id
1054 foreach ($oars as $oar) {
1055 if (array_key_exists($oar, $id_mappings['role'])) {
1056 $nar[] = $id_mappings['role'][$oar];
1059 $natw->saveAllowedRoles($newEFElIds[$oldEFId][$oefelement['element_id']], $newEFElIds[$oldEFId][$onext], $nar);
1061 $natw->saveNextNodes($newEFElIds[$oldEFId][$oefelement['element_id']], $naivs);
1072 * getExtraFieldName - Get a box name using the box ID
1074 * @param int $extra_field_id id of an extra field.
1075 * @return string name of extra field.
1077 function getExtraFieldName($extra_field_id) {
1078 $arr = $this->getExtraFields();
1079 return $arr[$extra_field_id]['field_name'];
1083 * getExtraFieldElements - List of possible admin configured
1084 * extra field elements. This function is used to
1085 * present the boxes and choices on the main Add/Update page.
1087 * @param int $id id of the extra field
1088 * @return array of elements for this extra field.
1090 function getExtraFieldElements($id) {
1095 if (!isset($this->extra_field[$id])) {
1096 $this->extra_field[$id] = array();
1097 $ef = new ArtifactExtraField($this,$id);
1098 if (!$ef || $ef->isError()) {
1101 $efValues = $ef->getAvailableValues();
1102 $this->extra_field[$id] = $efValues;
1105 return $this->extra_field[$id];
1109 * getElementName - get the name of a particular element.
1112 * @return string The name.
1114 function getElementName($choice_id) {
1118 if (is_array($choice_id)) {
1119 $choice_id = implode(',', array_map('intval', $choice_id));
1121 $choice_id = intval($choice_id);
1123 if ($choice_id == 100) {
1126 if (!isset($this->element_name[$choice_id])) {
1127 $res = db_query_params('SELECT element_id, element_name
1128 FROM artifact_extra_field_elements
1129 WHERE element_id = ANY ($1)',
1130 array(db_int_array_to_any_clause(explode(',', $choice_id))));
1131 if (db_numrows($res) > 1) {
1132 $arr = util_result_column_to_array($res, 1);
1133 $this->element_name[$choice_id] = implode(',', $arr);
1135 $this->element_name[$choice_id] = db_result($res, 0, 'element_name');
1138 return $this->element_name[$choice_id];
1142 * getElementStatusID - get the status of a particular element.
1144 * @param int|array $choice_id
1145 * @return int The status
1147 function getElementStatusID($choice_id) {
1151 if (is_array($choice_id)) {
1152 $choice_id = implode(',',$choice_id);
1154 if ($choice_id == 100) {
1157 if (!$this->element_status[$choice_id]) {
1158 $res = db_query_params('SELECT element_id,extra_field_id,status_id
1159 FROM artifact_extra_field_elements
1160 WHERE element_id = ANY ($1)',
1161 array(db_int_array_to_any_clause(explode(',', $choice_id))));
1162 if (db_numrows($res) > 1) {
1163 $arr = util_result_column_to_array($res, 2);
1164 $this->element_status[$choice_id] = implode(',', $arr);
1166 $this->element_status[$choice_id] = db_result($res, 0, 'status_id');
1169 return $this->element_status[$choice_id];
1173 * delete - delete this tracker and all its related data.
1175 * @param bool $sure I'm Sure.
1176 * @param bool $really_sure I'm REALLY sure.
1177 * @return bool true/false;
1179 function delete($sure, $really_sure) {
1180 if (!$sure || !$really_sure) {
1181 $this->setMissingParamsError(_('Please tick all checkboxes.'));
1184 if (!forge_check_perm ('tracker_admin', $this->Group->getID())) {
1185 $this->setPermissionDeniedError();
1189 db_query_params('DELETE FROM artifact_extra_field_data
1190 WHERE EXISTS (SELECT artifact_id FROM artifact
1191 WHERE group_artifact_id=$1
1192 AND artifact.artifact_id=artifact_extra_field_data.artifact_id)',
1193 array($this->getID()));
1194 db_query_params('DELETE FROM artifact_extra_field_elements
1195 WHERE EXISTS (SELECT extra_field_id FROM artifact_extra_field_list
1196 WHERE group_artifact_id=$1
1197 AND artifact_extra_field_list.extra_field_id = artifact_extra_field_elements.extra_field_id)',
1198 array($this->getID()));
1199 db_query_params('DELETE FROM artifact_extra_field_list
1200 WHERE group_artifact_id=$1',
1201 array($this->getID()));
1202 db_query_params('DELETE FROM artifact_canned_responses
1203 WHERE group_artifact_id=$1',
1204 array($this->getID()));
1205 db_query_params('DELETE FROM artifact_counts_agg
1206 WHERE group_artifact_id=$1',
1207 array($this->getID()));
1209 ArtifactStorage::instance()->deleteFromQuery('SELECT id FROM artifact_file
1210 WHERE EXISTS (SELECT artifact_id FROM artifact
1211 WHERE group_artifact_id=$1
1212 AND artifact.artifact_id=artifact_file.artifact_id)',
1213 array($this->getID()));
1215 db_query_params('DELETE FROM artifact_file
1216 WHERE EXISTS (SELECT artifact_id FROM artifact
1217 WHERE group_artifact_id=$1
1218 AND artifact.artifact_id=artifact_file.artifact_id)',
1219 array($this->getID()));
1220 db_query_params('DELETE FROM artifact_message
1221 WHERE EXISTS (SELECT artifact_id FROM artifact
1222 WHERE group_artifact_id=$1
1223 AND artifact.artifact_id=artifact_message.artifact_id)',
1224 array($this->getID()));
1225 db_query_params('DELETE FROM artifact_history
1226 WHERE EXISTS (SELECT artifact_id FROM artifact
1227 WHERE group_artifact_id=$1
1228 AND artifact.artifact_id=artifact_history.artifact_id)',
1229 array($this->getID()));
1230 db_query_params('DELETE FROM artifact_monitor
1231 WHERE EXISTS (SELECT artifact_id FROM artifact
1232 WHERE group_artifact_id=$1
1233 AND artifact.artifact_id=artifact_monitor.artifact_id)',
1234 array($this->getID()));
1235 db_query_params('DELETE FROM artifact
1236 WHERE group_artifact_id=$1',
1237 array($this->getID()));
1238 db_query_params('DELETE FROM artifact_group_list
1239 WHERE group_artifact_id=$1',
1240 array($this->getID()));
1241 $MonitorElementObject = new MonitorElement('artifact_type');
1242 $MonitorElementObject->clearMonitor($this->getID());
1245 ArtifactStorage::instance()->commit();
1247 $this->Group->normalizeAllRoles();
1253 * getSubmitters - returns a result set of submitters.
1255 * @return resource database result set.
1257 function getSubmitters() {
1258 if (!isset($this->submitters_res)) {
1259 $this->submitters_res = db_query_params('SELECT DISTINCT submitted_by, submitted_realname
1261 WHERE group_artifact_id=$1
1262 ORDER BY submitted_realname',
1263 array($this->getID()));
1265 return $this->submitters_res;
1269 * getLastModifiers - returns a result set of last modifiers.
1271 * @return resource database result set.
1273 function getLastModifiers() {
1274 if (!isset($this->last_modifiers_res)) {
1275 $this->last_modifiers_res = db_query_params('SELECT DISTINCT last_modified_by, last_modified_realname
1277 WHERE group_artifact_id=$1
1278 ORDER BY last_modified_realname',
1279 array($this->getID()));
1281 return $this->last_modifiers_res;
1285 * getCannedResponses - returns a result set of canned responses.
1287 * @return resource database result set.
1289 function getCannedResponses() {
1290 if (!isset($this->cannedresponses_res)) {
1291 $this->cannedresponses_res = db_query_params('SELECT id,title
1292 FROM artifact_canned_responses
1293 WHERE group_artifact_id=$1',
1294 array($this->getID()));
1296 return $this->cannedresponses_res;
1300 * getStatuses - returns a result set of statuses.
1302 * These statuses are either the default open/closed or any number of
1303 * custom statuses that are stored in the extra fields. On insert/update
1304 * to an artifact the status_id is remapped from the extra_field_element_id to
1305 * the standard open/closed id.
1307 * @return resource database result set.
1309 function getStatuses() {
1310 if (!isset($this->status_res)) {
1311 $this->status_res = db_query_params('SELECT * FROM artifact_status', array());
1313 return $this->status_res;
1317 * getStatusName - returns the name of this status.
1319 * @param int $id The status ID.
1320 * @return string name.
1322 function getStatusName($id) {
1323 $result = db_query_params('select status_name from artifact_status WHERE id=$1',
1325 if ($result && db_numrows($result) > 0) {
1326 return db_result($result, 0, 'status_name');
1328 return 'Error: Not Found';
1333 * update - use this to update this ArtifactType in the database.
1335 * @param string $name The item name.
1336 * @param string $description The item description.
1337 * @param bool $email_all (1) true (0) false - whether to email on all updates.
1338 * @param string $email_address The address to send new entries and updates to.
1339 * @param int $due_period Days before this item is considered overdue.
1340 * @param int $status_timeout Days before stale items time out.
1341 * @param bool $use_resolution (1) true (0) false - whether the resolution box should be shown. //TODO: unused parameter. to be drop!
1342 * @param string $submit_instructions Free-form string that project admins can place on the submit page.
1343 * @param string $browse_instructions Free-form string that project admins can place on the browse page.
1344 * @return bool true on success, false on failure.
1346 function update($name, $description, $email_all, $email_address,
1347 $due_period, $status_timeout, $use_resolution, $submit_instructions, $browse_instructions) {
1349 if (!forge_check_perm ('tracker_admin', $this->Group->getID())) {
1350 $this->setPermissionDeniedError();
1354 if ($this->getDataType()) {
1355 $name=$this->getName();
1356 $description=$this->getDescription();
1359 if (!$name || !$description || !$due_period || !$status_timeout) {
1360 $this->setError(_('ArtifactType')._(': ')._('Name, Description, Due Period, and Status Timeout are required'));
1364 $result = db_query_params('SELECT count(*) AS count FROM artifact_group_list WHERE group_id=$1 AND name=$2 AND group_artifact_id!=$3',
1365 array($this->Group->getID(), $name, $this->getID()));
1367 $this->setError('ArtifactType::Update(): '.db_error());
1370 if (db_result($result, 0, 'count')) {
1371 $this->setError(_('Tracker name already used'));
1375 if ($email_address) {
1376 $invalid_emails = validate_emails($email_address);
1377 if (count($invalid_emails) > 0) {
1378 $this->setError(_('E-mail address(es) appeared invalid')._(': ').implode(',', $invalid_emails));
1383 $email_all = ((!$email_all) ? 0 : $email_all);
1385 $res = db_query_params('UPDATE artifact_group_list SET
1388 email_all_updates=$3,
1392 submit_instructions=$7,
1393 browse_instructions=$8
1394 WHERE group_artifact_id=$9 AND group_id=$10',
1396 htmlspecialchars($name),
1397 htmlspecialchars($description),
1400 $due_period * (60*60*24),
1401 $status_timeout * (60*60*24),
1402 htmlspecialchars($submit_instructions),
1403 htmlspecialchars($browse_instructions),
1405 $this->Group->getID()));
1407 if (!$res || db_affected_rows($res) < 1) {
1408 $this->setError('ArtifactType::Update(): '.db_error());
1411 $this->fetchData($this->getID());
1417 * getBrowseList - get the list of columns in browse page.
1419 * @return string instructions.
1421 function getBrowseList() {
1422 $list = $this->data_array['browse_list'];
1424 // remove status_id in the browse list if a custom status exists
1425 if (count($this->getExtraFields(array(ARTIFACT_EXTRAFIELDTYPE_STATUS))) > 0) {
1426 $arr = explode(',', $list);
1427 $idx = array_search('status_id', $arr);
1428 if ($idx !== False) {
1429 array_splice($arr, $idx, 1);
1431 return join(',', $arr);
1438 * setBrowseList - set the list of columns in browse page.
1440 * @param string $list string comma separated of ids of custom field and names of internal fields.
1441 * @return boolean success.
1443 function setBrowseList($list) {
1444 $res = db_query_params('UPDATE artifact_group_list
1446 WHERE group_artifact_id=$2',
1449 $this->fetchData($this->getID());
1454 * canVote - check whether the current user can vote on
1455 * items in this tracker
1457 * @return bool true if they can
1459 function canVote() {
1460 return forge_check_perm('tracker', $this->getID(), 'vote');
1464 * getVoters - get IDs of users that may vote on
1465 * items in this tracker
1467 * @return array list of user IDs
1469 function getVoters() {
1470 if ($this->voters !== false) {
1471 return $this->voters;
1474 $this->voters = array();
1475 if (($engine = RBACEngine::getInstance())
1476 && ($voters = $engine->getUsersByAllowedAction('tracker', $this->getID(), 'vote'))
1477 && (count($voters) > 0)) {
1478 foreach ($voters as $voter) {
1479 $voter_id = $voter->getID();
1480 $this->voters[$voter_id] = $voter_id;
1483 return $this->voters;
1489 * @param integer $unit_set_id the effort unit set id
1492 function setEffortUnitSet($unit_set_id) {
1494 $res = db_query_params ('UPDATE artifact_group_list SET unit_set_id=$1 WHERE group_artifact_id=$2',
1495 array($unit_set_id, $this->getID()));
1497 $this->data_array['unit_set_id'] = $unit_set_id;
1507 * getEffortUnitSet - Get the effort unit set id.
1509 * @return integer The id of the effort unit set.
1511 function getEffortUnitSet() {
1512 return $this->data_array['unit_set_id'];
1516 * getSettings - Get all parameters of this tracker
1518 * @return array all parameters into an multidimensional array
1520 function getSettings() {
1521 // Get list of extra fields for this artifact
1522 $extrafields = array();
1523 $tmpextrafields = $this->getExtraFields(array(), true);
1524 foreach ($tmpextrafields as $extrafield) {
1525 $aefobj = new ArtifactExtraField($this, $extrafield["extra_field_id"]);
1527 // array of available values
1528 $avtmp = $aefobj->getAvailableValues();
1530 for ($j=0; $j < count($avtmp); $j++) {
1531 $avs[$j]['auto_assign_to'] = $avtmp[$j]['auto_assign_to'];
1532 $avs[$j]['element_id'] = $avtmp[$j]['element_id'];
1533 $avs[$j]['element_name'] = $avtmp[$j]['element_name'];
1534 $avs[$j]['is_default'] = $avtmp[$j]['is_default'];
1535 $avs[$j]['status_id'] = $avtmp[$j]['status_id'];
1538 $extrafields[] = array(
1539 'alias' => $aefobj->getAlias(),
1540 'attribute1' => $aefobj->getAttribute1(),
1541 'attribute2' => $aefobj->getAttribute2(),
1542 'autoassign' => $aefobj->isAutoAssign(),
1543 'available_values' => $avs,
1544 'description' => $aefobj->getDescription(),
1545 'extra_field_id' => $aefobj->getID(),
1546 'field_name' => $aefobj->getName(),
1547 'field_type' => $aefobj->getType(),
1548 'is_disabled' => $aefobj->isDisabled(),
1549 'is_hidden_on_submit' => $aefobj->isHiddenOnSubmit(),
1550 'is_required' => $aefobj->isRequired(),
1551 'name' => $aefobj->getName(),
1552 'pattern' => $aefobj->getPattern(),
1553 'parent' => $aefobj->getParent(),
1554 'show100' => $aefobj->getShow100(),
1555 'show100label' => $aefobj->getShow100label()
1560 'browse_list' => $this->getBrowseList(),
1561 'browse_instructions' => $this->getBrowseInstructions(),
1562 'custom_status_field' => $this->getCustomStatusField(),
1563 'datatype' => $this->getDataType(),
1564 'description' => $this->getDescription(),
1565 'due_period' => $this->getDuePeriod(),
1566 'email_address' => $this->getEmailAddress(),
1567 'email_all_updates' => $this->emailAll(),
1568 'extra_fields' => $extrafields,
1570 'group_artifact_id' => $this->getID(),
1571 'group_id' => $this->getGroup()->getID(),
1572 'name' => $this->getName(),
1573 'status_timeout' => $this->getStatusTimeout(),
1574 'submit_instructions' => $this->getSubmitInstructions(),
1575 'unit_set_id' => $this->getEffortUnitSet(),
1576 'use_tracker_widget_display' => $this->getWidgetLayoutConfig());
1580 function getWidgetLayoutConfig() {
1581 $lm = new WidgetLayoutManager();
1582 return $lm->getLayout($this->getID(), WidgetLayoutManager::OWNER_TYPE_TRACKER);
1585 function getExtraFieldByAlias($alias){
1587 $efs = $this->getExtraFields();
1588 foreach ($efs as $ef) {
1589 if ($alias == $ef['alias']) {
1600 // c-file-style: "bsd"