[ Index ] |
PHP Cross Reference of Eventum |
[Summary view] [Print] [Text view]
1 <?php 2 /* vim: set expandtab tabstop=4 shiftwidth=4 encoding=utf-8: */ 3 // +----------------------------------------------------------------------+ 4 // | Eventum - Issue Tracking System | 5 // +----------------------------------------------------------------------+ 6 // | Copyright (c) 2003, 2004, 2005, 2006, 2007 MySQL AB | 7 // | | 8 // | This program is free software; you can redistribute it and/or modify | 9 // | it under the terms of the GNU General Public License as published by | 10 // | the Free Software Foundation; either version 2 of the License, or | 11 // | (at your option) any later version. | 12 // | | 13 // | This program is distributed in the hope that it will be useful, | 14 // | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 // | GNU General Public License for more details. | 17 // | | 18 // | You should have received a copy of the GNU General Public License | 19 // | along with this program; if not, write to: | 20 // | | 21 // | Free Software Foundation, Inc. | 22 // | 59 Temple Place - Suite 330 | 23 // | Boston, MA 02111-1307, USA. | 24 // +----------------------------------------------------------------------+ 25 // | Authors: João Prado Maia <jpm@mysql.com> | 26 // +----------------------------------------------------------------------+ 27 // 28 // @(#) $Id: class.custom_field.php 3389 2007-10-21 01:33:24Z balsdorf $ 29 // 30 31 require_once (APP_INC_PATH . "class.error_handler.php"); 32 require_once (APP_INC_PATH . "class.misc.php"); 33 require_once (APP_INC_PATH . "class.issue.php"); 34 require_once (APP_INC_PATH . "class.user.php"); 35 require_once (APP_INC_PATH . "class.auth.php"); 36 require_once (APP_INC_PATH . "class.history.php"); 37 38 /** 39 * Class to handle the business logic related to the administration 40 * of custom fields in the system. 41 * 42 * @version 1.0 43 * @author João Prado Maia <jpm@mysql.com> 44 */ 45 46 class Custom_Field 47 { 48 /** 49 * Method used to remove a group of custom field options. 50 * 51 * @access public 52 * @param array $fld_id The list of custom field IDs 53 * @param array $fld_id The list of custom field option IDs 54 * @return boolean 55 */ 56 function removeOptions($fld_id, $cfo_id) 57 { 58 $fld_id = Misc::escapeInteger($fld_id); 59 $cfo_id = Misc::escapeInteger($cfo_id); 60 if (!is_array($fld_id)) { 61 $fld_id = array($fld_id); 62 } 63 if (!is_array($cfo_id)) { 64 $cfo_id = array($cfo_id); 65 } 66 $stmt = "DELETE FROM 67 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field_option 68 WHERE 69 cfo_id IN (" . implode(",", $cfo_id) . ")"; 70 $res = $GLOBALS["db_api"]->dbh->query($stmt); 71 if (PEAR::isError($res)) { 72 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 73 return false; 74 } else { 75 // also remove any custom field option that is currently assigned to an issue 76 // XXX: review this 77 $stmt = "DELETE FROM 78 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 79 WHERE 80 icf_fld_id IN (" . implode(", ", $fld_id) . ") AND 81 icf_value IN (" . implode(", ", $cfo_id) . ")"; 82 $GLOBALS["db_api"]->dbh->query($stmt); 83 return true; 84 } 85 } 86 87 88 /** 89 * Method used to add possible options into a given custom field. 90 * 91 * @access public 92 * @param integer $fld_id The custom field ID 93 * @param array $options The list of options that need to be added 94 * @return integer 1 if the insert worked, -1 otherwise 95 */ 96 function addOptions($fld_id, $options) 97 { 98 $fld_id = Misc::escapeInteger($fld_id); 99 if (!is_array($options)) { 100 $options = array($options); 101 } 102 foreach ($options as $option) { 103 $stmt = "INSERT INTO 104 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field_option 105 ( 106 cfo_fld_id, 107 cfo_value 108 ) VALUES ( 109 $fld_id, 110 '" . Misc::escapeString($option) . "' 111 )"; 112 $res = $GLOBALS["db_api"]->dbh->query($stmt); 113 if (PEAR::isError($res)) { 114 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 115 return -1; 116 } 117 } 118 return 1; 119 } 120 121 122 /** 123 * Method used to update an existing custom field option value. 124 * 125 * @access public 126 * @param integer $cfo_id The custom field option ID 127 * @param string $cfo_value The custom field option value 128 * @return boolean 129 */ 130 function updateOption($cfo_id, $cfo_value) 131 { 132 $cfo_id = Misc::escapeInteger($cfo_id); 133 $stmt = "UPDATE 134 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field_option 135 SET 136 cfo_value='" . Misc::escapeString($cfo_value) . "' 137 WHERE 138 cfo_id=" . $cfo_id; 139 $res = $GLOBALS["db_api"]->dbh->query($stmt); 140 if (PEAR::isError($res)) { 141 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 142 return false; 143 } else { 144 return true; 145 } 146 } 147 148 149 /** 150 * Method used to update the values stored in the database. 151 * 152 * @access public 153 * @return integer 1 if the update worked properly, any other value otherwise 154 */ 155 function updateValues() 156 { 157 $prj_id = Auth::getCurrentProject(); 158 $issue_id = Misc::escapeInteger($_POST["issue_id"]); 159 160 $old_values = Custom_Field::getValuesByIssue($prj_id, $issue_id); 161 162 163 if ((isset($_POST['custom_fields'])) && (count($_POST['custom_fields']) > 0)) { 164 // get the types for all of the custom fields being submitted 165 $stmt = "SELECT 166 fld_id, 167 fld_type 168 FROM 169 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 170 WHERE 171 fld_id IN (" . implode(", ", Misc::escapeInteger(@array_keys($_POST['custom_fields']))) . ")"; 172 $field_types = $GLOBALS["db_api"]->dbh->getAssoc($stmt); 173 174 // get the titles for all of the custom fields being submitted 175 $stmt = "SELECT 176 fld_id, 177 fld_title 178 FROM 179 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 180 WHERE 181 fld_id IN (" . implode(", ", Misc::escapeInteger(@array_keys($_POST['custom_fields']))) . ")"; 182 $field_titles = $GLOBALS["db_api"]->dbh->getAssoc($stmt); 183 184 $updated_fields = array(); 185 foreach ($_POST["custom_fields"] as $fld_id => $value) { 186 187 $fld_id = Misc::escapeInteger($fld_id); 188 189 // security check 190 $sql = "SELECT 191 fld_min_role 192 FROM 193 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 194 WHERE 195 fld_id = $fld_id"; 196 $min_role = $GLOBALS["db_api"]->dbh->getOne($sql); 197 if ($min_role > Auth::getCurrentRole()) { 198 continue; 199 } 200 201 $option_types = array( 202 'multiple', 203 'combo' 204 ); 205 if (!in_array($field_types[$fld_id], $option_types)) { 206 // check if this is a date field 207 if ($field_types[$fld_id] == 'date') { 208 $value = $value['Year'] . "-" . $value['Month'] . "-" . $value['Day']; 209 if ($value == '--') { 210 $value = ''; 211 } 212 } elseif ($field_types[$fld_id] == 'integer') { 213 $value = Misc::escapeInteger($value); 214 } 215 $fld_db_name = Custom_Field::getDBValueFieldNameByType($field_types[$fld_id]); 216 217 // first check if there is actually a record for this field for the issue 218 $stmt = "SELECT 219 icf_id, 220 $fld_db_name as value 221 FROM 222 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 223 WHERE 224 icf_iss_id=" . $issue_id . " AND 225 icf_fld_id=$fld_id"; 226 $res = $GLOBALS["db_api"]->dbh->getRow($stmt, DB_FETCHMODE_ASSOC); 227 if (PEAR::isError($res)) { 228 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 229 return -1; 230 } 231 $icf_id = $res['icf_id']; 232 $old_value = $res['value']; 233 234 if ($old_value == $value) { 235 continue; 236 } 237 238 if (empty($value)) { 239 $value = 'NULL'; 240 } else { 241 $value = "'" . Misc::escapeString($value) . "'"; 242 } 243 244 if (empty($icf_id)) { 245 // record doesn't exist, insert new record 246 $stmt = "INSERT INTO 247 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 248 ( 249 icf_iss_id, 250 icf_fld_id, 251 $fld_db_name 252 ) VALUES ( 253 " . $issue_id . ", 254 $fld_id, 255 $value 256 )"; 257 $res = $GLOBALS["db_api"]->dbh->query($stmt); 258 if (PEAR::isError($res)) { 259 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 260 return -1; 261 } 262 } else { 263 // record exists, update it 264 $stmt = "UPDATE 265 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 266 SET 267 $fld_db_name=$value 268 WHERE 269 icf_id=$icf_id"; 270 $res = $GLOBALS["db_api"]->dbh->query($stmt); 271 if (PEAR::isError($res)) { 272 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 273 return -1; 274 } 275 } 276 if ($field_types[$fld_id] == 'textarea') { 277 $updated_fields[$field_titles[$fld_id]] = ''; 278 } else { 279 $updated_fields[$field_titles[$fld_id]] = History::formatChanges($old_value, $value); 280 } 281 } else { 282 $old_value = Custom_Field::getDisplayValue($_POST['issue_id'], $fld_id, true); 283 284 if (!is_array($old_value)) { 285 $old_value = array($old_value); 286 } 287 if (!is_array($value)) { 288 $value = array($value); 289 } 290 if ((count(array_diff($old_value, $value)) > 0) || (count(array_diff($value, $old_value)) > 0)) { 291 292 $old_display_value = Custom_Field::getDisplayValue($_POST['issue_id'], $fld_id); 293 // need to remove all associated options from issue_custom_field and then 294 // add the selected options coming from the form 295 Custom_Field::removeIssueAssociation($fld_id, $_POST["issue_id"]); 296 if (@count($value) > 0) { 297 Custom_Field::associateIssue($_POST["issue_id"], $fld_id, $value); 298 } 299 $new_display_value = Custom_Field::getDisplayValue($_POST['issue_id'], $fld_id); 300 $updated_fields[$field_titles[$fld_id]] = History::formatChanges($old_display_value, $new_display_value); 301 } 302 } 303 } 304 305 Workflow::handleCustomFieldsUpdated($prj_id, $issue_id, $old_values, Custom_Field::getValuesByIssue($prj_id, $issue_id)); 306 Issue::markAsUpdated($_POST["issue_id"]); 307 // need to save a history entry for this 308 309 if (count($updated_fields) > 0) { 310 // log the changes 311 $changes = ''; 312 $i = 0; 313 foreach ($updated_fields as $key => $value) { 314 if ($i > 0) { 315 $changes .= "; "; 316 } 317 if (!empty($value)) { 318 $changes .= "$key: $value"; 319 } else { 320 $changes .= "$key"; 321 } 322 $i++; 323 } 324 History::add($_POST["issue_id"], Auth::getUserID(), History::getTypeID('custom_field_updated'), ev_gettext('Custom field updated (%1$s) by %2$s', $changes, User::getFullName(Auth::getUserID()))); 325 } 326 } 327 return 1; 328 } 329 330 331 /** 332 * Method used to associate a custom field value to a given 333 * issue ID. 334 * 335 * @access public 336 * @param integer $iss_id The issue ID 337 * @param integer $fld_id The custom field ID 338 * @param string $value The custom field value 339 * @return boolean Whether the association worked or not 340 */ 341 function associateIssue($iss_id, $fld_id, $value) 342 { 343 // check if this is a date field 344 $fld_details = Custom_Field::getDetails($fld_id); 345 if (($fld_details['fld_type'] == 'date') && (!empty($value))) { 346 $value= $value['Year'] . "-" . $value['Month'] . "-" . $value['Day']; 347 } 348 if (!is_array($value)) { 349 $value = array($value); 350 } 351 foreach ($value as $item) { 352 if ($fld_details['fld_type'] == 'integer') { 353 $item = Misc::escapeInteger($item); 354 } else { 355 $item = "'" . Misc::escapeString($item) . "'"; 356 } 357 $stmt = "INSERT INTO 358 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 359 ( 360 icf_iss_id, 361 icf_fld_id, 362 " . Custom_Field::getDBValueFieldNameByType($fld_details['fld_type']) . " 363 ) VALUES ( 364 " . Misc::escapeInteger($iss_id) . ", 365 " . Misc::escapeInteger($fld_id) . ", 366 $item 367 )"; 368 $res = $GLOBALS["db_api"]->dbh->query($stmt); 369 if (PEAR::isError($res)) { 370 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 371 return false; 372 } 373 } 374 return true; 375 } 376 377 378 /** 379 * Method used to get the list of custom fields associated with 380 * a given project. 381 * 382 * @access public 383 * @param integer $prj_id The project ID 384 * @param string $form_type The type of the form 385 * @param string $fld_type The type of field (optional) 386 * @return array The list of custom fields 387 */ 388 function getListByProject($prj_id, $form_type, $fld_type = false) 389 { 390 $stmt = "SELECT 391 fld_id, 392 fld_title, 393 fld_description, 394 fld_type, 395 fld_report_form_required, 396 fld_anonymous_form_required, 397 fld_min_role 398 FROM 399 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field, 400 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project_custom_field 401 WHERE 402 pcf_fld_id=fld_id AND 403 pcf_prj_id=" . Misc::escapeInteger($prj_id); 404 if ($form_type != 'anonymous_form') { 405 $stmt .= " AND 406 fld_min_role <= " . Auth::getCurrentRole(); 407 } 408 if ($form_type != '') { 409 $stmt .= " AND\nfld_" . Misc::escapeString($form_type) . "=1"; 410 } 411 if ($fld_type != '') { 412 $stmt .= " AND\nfld_type='" . Misc::escapeString($fld_type) . "'"; 413 } 414 $stmt .= " 415 ORDER BY 416 fld_rank ASC"; 417 $res = $GLOBALS["db_api"]->dbh->getAll($stmt, DB_FETCHMODE_ASSOC); 418 if (PEAR::isError($res)) { 419 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 420 return array(); 421 } else { 422 if (count($res) == 0) { 423 return array(); 424 } else { 425 for ($i = 0; $i < count($res); $i++) { 426 // check if this has a dynamic field custom backend 427 $backend = Custom_Field::getBackend($res[$i]['fld_id']); 428 if ((is_object($backend)) && (is_subclass_of($backend, "Dynamic_Custom_Field_Backend"))) { 429 $res[$i]['dynamic_options'] = $backend->getStructuredData(); 430 $res[$i]['controlling_field_id'] = $backend->getControllingCustomFieldID(); 431 $res[$i]['controlling_field_name'] = $backend->getControllingCustomFieldName(); 432 $res[$i]['hide_when_no_options'] = $backend->hideWhenNoOptions(); 433 } 434 // check if the backend implements "isRequired" 435 if ((is_object($backend)) && (method_exists($backend, 'isRequired'))) { 436 $res[$i]['fld_report_form_required'] = $backend->isRequired($res[$i]['fld_id'], 'report'); 437 $res[$i]['fld_anonymous_form_required'] = $backend->isRequired($res[$i]['fld_id'], 'anonymous'); 438 $res[$i]['fld_close_form_required'] = $backend->isRequired($res[$i]['fld_id'], 'close'); 439 } 440 if ((is_object($backend)) && (method_exists($backend, 'getValidationJS'))) { 441 $res[$i]['validation_js'] = $backend->getValidationJS($res[$i]['fld_id'], $form_type); 442 } else { 443 $res[$i]['validation_js'] = ''; 444 } 445 446 447 $res[$i]["field_options"] = Custom_Field::getOptions($res[$i]["fld_id"]); 448 } 449 return $res; 450 } 451 } 452 } 453 454 455 /** 456 * Method used to get the custom field option value. 457 * 458 * @access public 459 * @param integer $fld_id The custom field ID 460 * @param integer $value The custom field option ID 461 * @return string The custom field option value 462 */ 463 function getOptionValue($fld_id, $value) 464 { 465 static $returns; 466 467 if (empty($value)) { 468 return ""; 469 } 470 471 if (isset($returns[$fld_id . $value])) { 472 return $returns[$fld_id . $value]; 473 } 474 475 $backend = Custom_Field::getBackend($fld_id); 476 if ((is_object($backend)) && (method_exists($backend, 'getList'))) { 477 $values = $backend->getList($fld_id, false); 478 $returns[$fld_id . $value] = @$values[$value]; 479 return @$values[$value]; 480 } else { 481 $stmt = "SELECT 482 cfo_value 483 FROM 484 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field_option 485 WHERE 486 cfo_fld_id=" . Misc::escapeInteger($fld_id) . " AND 487 cfo_id=" . Misc::escapeInteger($value); 488 $res = $GLOBALS["db_api"]->dbh->getOne($stmt); 489 if (PEAR::isError($res)) { 490 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 491 return ""; 492 } else { 493 if ($res == NULL) { 494 $returns[$fld_id . $value] = ''; 495 return ""; 496 } else { 497 $returns[$fld_id . $value] = $res; 498 return $res; 499 } 500 } 501 } 502 } 503 504 505 /** 506 * Method used to get the custom field key based on the value. 507 * 508 * @access public 509 * @param integer $fld_id The custom field ID 510 * @param integer $value The custom field option ID 511 * @return string The custom field option value 512 */ 513 function getOptionKey($fld_id, $value) 514 { 515 static $returns; 516 517 if (empty($value)) { 518 return ""; 519 } 520 521 if (isset($returns[$fld_id . $value])) { 522 return $returns[$fld_id . $value]; 523 } 524 525 $backend = Custom_Field::getBackend($fld_id); 526 if ((is_object($backend)) && (method_exists($backend, 'getList'))) { 527 $values = $backend->getList($fld_id, false); 528 $key = array_search($value, $values); 529 $returns[$fld_id . $value] = $key; 530 return $key; 531 } else { 532 $stmt = "SELECT 533 cfo_id 534 FROM 535 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field_option 536 WHERE 537 cfo_fld_id=" . Misc::escapeInteger($fld_id) . " AND 538 cfo_value='" . Misc::escapeString($value) . "'"; 539 $res = $GLOBALS["db_api"]->dbh->getOne($stmt); 540 if (PEAR::isError($res)) { 541 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 542 return ""; 543 } else { 544 if ($res == NULL) { 545 $returns[$fld_id . $value] = ''; 546 return ""; 547 } else { 548 $returns[$fld_id . $value] = $res; 549 return $res; 550 } 551 } 552 } 553 } 554 555 556 /** 557 * Method used to get the list of custom fields and custom field 558 * values associated with a given issue ID. If usr_id is false method 559 * defaults to current user. 560 * 561 * @access public 562 * @param integer $prj_id The project ID 563 * @param integer $iss_id The issue ID 564 * @param integer $usr_id The ID of the user who is going to be viewing this list. 565 * @return array The list of custom fields 566 */ 567 function getListByIssue($prj_id, $iss_id, $usr_id = false, $form_type = false) 568 { 569 if ($usr_id == false) { 570 $usr_id = Auth::getUserID(); 571 } 572 573 $usr_role = User::getRoleByUser($usr_id, $prj_id); 574 if (empty($usr_role)) { 575 $usr_role = 0; 576 } 577 578 $stmt = "SELECT 579 fld_id, 580 fld_title, 581 fld_type, 582 fld_report_form_required, 583 fld_anonymous_form_required, 584 fld_close_form_required, 585 " . Custom_Field::getDBValueFieldSQL() . " as value, 586 fld_min_role 587 FROM 588 ( 589 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field, 590 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project_custom_field 591 ) 592 LEFT JOIN 593 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 594 ON 595 pcf_fld_id=icf_fld_id AND 596 icf_iss_id=" . Misc::escapeInteger($iss_id) . " 597 WHERE 598 pcf_fld_id=fld_id AND 599 pcf_prj_id=" . Misc::escapeInteger($prj_id) . " AND 600 fld_min_role <= " . $usr_role; 601 if ($form_type != '') { 602 $stmt .= " AND\nfld_" . Misc::escapeString($form_type) . "=1"; 603 } 604 $stmt .= " 605 ORDER BY 606 fld_rank ASC"; 607 $res = $GLOBALS["db_api"]->dbh->getAll($stmt, DB_FETCHMODE_ASSOC); 608 if (PEAR::isError($res)) { 609 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 610 return array(); 611 } else { 612 if (count($res) == 0) { 613 return array(); 614 } else { 615 $fields = array(); 616 for ($i = 0; $i < count($res); $i++) { 617 if ($res[$i]["fld_type"] == "combo") { 618 $res[$i]["selected_cfo_id"] = $res[$i]["value"]; 619 $res[$i]["original_value"] = $res[$i]["value"]; 620 $res[$i]["value"] = Custom_Field::getOptionValue($res[$i]["fld_id"], $res[$i]["value"]); 621 $res[$i]["field_options"] = Custom_Field::getOptions($res[$i]["fld_id"]); 622 623 // add the select option to the list of values if it isn't on the list (useful for fields with active and non-active items) 624 if (!in_array($res[$i]['value'], $res[$i]['field_options'])) { 625 $res[$i]['field_options'][$res[$i]['original_value']] = Custom_Field::getOptionValue($res[$i]['fld_id'], $res[$i]['original_value']); 626 } 627 628 $fields[] = $res[$i]; 629 630 } elseif ($res[$i]['fld_type'] == 'multiple') { 631 // check whether this field is already in the array 632 $found = 0; 633 for ($y = 0; $y < count($fields); $y++) { 634 if ($fields[$y]['fld_id'] == $res[$i]['fld_id']) { 635 $found = 1; 636 $found_index = $y; 637 } 638 } 639 $original_value = $res[$i]['value']; 640 if (!$found) { 641 $res[$i]["selected_cfo_id"] = array($res[$i]["value"]); 642 $res[$i]["value"] = Custom_Field::getOptionValue($res[$i]["fld_id"], $res[$i]["value"]); 643 $res[$i]["field_options"] = Custom_Field::getOptions($res[$i]["fld_id"]); 644 $fields[] = $res[$i]; 645 $found_index = count($fields) - 1; 646 } else { 647 $fields[$found_index]['value'] .= ', ' . Custom_Field::getOptionValue($res[$i]["fld_id"], $res[$i]["value"]); 648 $fields[$found_index]['selected_cfo_id'][] = $res[$i]["value"]; 649 } 650 651 // add the select option to the list of values if it isn't on the list (useful for fields with active and non-active items) 652 if (!in_array($original_value, $fields[$found_index]['field_options'])) { 653 $fields[$found_index]['field_options'][$original_value] = Custom_Field::getOptionValue($res[$i]['fld_id'], $original_value); 654 } 655 } else { 656 $fields[] = $res[$i]; 657 } 658 } 659 foreach ($fields as $key => $field) { 660 $backend = Custom_Field::getBackend($field['fld_id']); 661 if ((is_object($backend)) && (is_subclass_of($backend, "Dynamic_Custom_Field_Backend"))) { 662 $fields[$key]['dynamic_options'] = $backend->getStructuredData(); 663 $fields[$key]['controlling_field_id'] = $backend->getControllingCustomFieldID(); 664 $fields[$key]['controlling_field_name'] = $backend->getControllingCustomFieldName(); 665 $fields[$key]['hide_when_no_options'] = $backend->hideWhenNoOptions(); 666 } 667 668 // check if the backend implements "isRequired" 669 if ((is_object($backend)) && (method_exists($backend, 'isRequired'))) { 670 $fields[$key]['fld_report_form_required'] = $backend->isRequired($fields[$key]['fld_id'], 'report', $iss_id); 671 $fields[$key]['fld_anonymous_form_required'] = $backend->isRequired($fields[$key]['fld_id'], 'anonymous', $iss_id); 672 $fields[$key]['fld_close_form_required'] = $backend->isRequired($fields[$key]['fld_id'], 'close', $iss_id); 673 } 674 if ((is_object($backend)) && (method_exists($backend, 'getValidationJS'))) { 675 $fields[$key]['validation_js'] = $backend->getValidationJS($fields[$key]['fld_id'], $form_type, $iss_id); 676 } else { 677 $fields[$key]['validation_js'] = ''; 678 } 679 } 680 return $fields; 681 } 682 } 683 } 684 685 686 /** 687 * Returns an array of fields and values for a specific issue 688 * 689 * @access public 690 * @param integer $prj_id The ID of the project 691 * @param integer $iss_id The ID of the issue to return values for 692 * @return array An array containging fld_id => value 693 */ 694 function getValuesByIssue($prj_id, $iss_id) 695 { 696 $values = array(); 697 $list = Custom_Field::getListByIssue($prj_id, $iss_id); 698 foreach ($list as $field) { 699 if ($field['fld_type'] == 'combo') { 700 $values[$field['fld_id']] = array( 701 $field['selected_cfo_id'] => $field['value'] 702 ); 703 } elseif ($field['fld_type'] == 'multiple') { 704 $selected = $field['selected_cfo_id']; 705 foreach ($selected as $cfo_id) { 706 $values[$field['fld_id']][$cfo_id] = @$field['field_options'][$cfo_id]; 707 } 708 } else { 709 $values[$field['fld_id']] = $field['value']; 710 } 711 } 712 return $values; 713 } 714 715 716 /** 717 * Method used to remove a given list of custom fields. 718 * 719 * @access public 720 * @return boolean 721 */ 722 function remove() 723 { 724 $items = @implode(", ", Misc::escapeInteger($_POST["items"])); 725 $stmt = "DELETE FROM 726 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 727 WHERE 728 fld_id IN ($items)"; 729 $res = $GLOBALS["db_api"]->dbh->query($stmt); 730 if (PEAR::isError($res)) { 731 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 732 return false; 733 } else { 734 $stmt = "DELETE FROM 735 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project_custom_field 736 WHERE 737 pcf_fld_id IN ($items)"; 738 $res = $GLOBALS["db_api"]->dbh->query($stmt); 739 if (PEAR::isError($res)) { 740 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 741 return false; 742 } else { 743 $stmt = "DELETE FROM 744 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 745 WHERE 746 icf_fld_id IN ($items)"; 747 $res = $GLOBALS["db_api"]->dbh->query($stmt); 748 if (PEAR::isError($res)) { 749 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 750 return false; 751 } else { 752 $stmt = "DELETE FROM 753 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field_option 754 WHERE 755 cfo_fld_id IN ($items)"; 756 $res = $GLOBALS["db_api"]->dbh->query($stmt); 757 if (PEAR::isError($res)) { 758 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 759 return false; 760 } else { 761 return true; 762 } 763 } 764 } 765 } 766 } 767 768 769 /** 770 * Method used to add a new custom field to the system. 771 * 772 * @access public 773 * @return integer 1 if the insert worked, -1 otherwise 774 */ 775 function insert() 776 { 777 if (empty($_POST["report_form"])) { 778 $_POST["report_form"] = 0; 779 } 780 if (empty($_POST["report_form_required"])) { 781 $_POST["report_form_required"] = 0; 782 } 783 if (empty($_POST["anon_form"])) { 784 $_POST["anon_form"] = 0; 785 } 786 if (empty($_POST["anon_form_required"])) { 787 $_POST["anon_form_required"] = 0; 788 } 789 if (empty($_POST["close_form"])) { 790 $_POST["close_form"] = 0; 791 } 792 if (empty($_POST["close_form_required"])) { 793 $_POST["close_form_required"] = 0; 794 } 795 if (empty($_POST["list_display"])) { 796 $_POST["list_display"] = 0; 797 } 798 if (empty($_POST["min_role"])) { 799 $_POST["min_role"] = 1; 800 } 801 if (!isset($_POST["rank"])) { 802 $_POST["rank"] = (Custom_Field::getMaxRank() + 1); 803 } 804 $stmt = "INSERT INTO 805 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 806 ( 807 fld_title, 808 fld_description, 809 fld_type, 810 fld_report_form, 811 fld_report_form_required, 812 fld_anonymous_form, 813 fld_anonymous_form_required, 814 fld_close_form, 815 fld_close_form_required, 816 fld_list_display, 817 fld_min_role, 818 fld_rank, 819 fld_backend 820 ) VALUES ( 821 '" . Misc::escapeString($_POST["title"]) . "', 822 '" . Misc::escapeString($_POST["description"]) . "', 823 '" . Misc::escapeString($_POST["field_type"]) . "', 824 " . Misc::escapeInteger($_POST["report_form"]) . ", 825 " . Misc::escapeInteger($_POST["report_form_required"]) . ", 826 " . Misc::escapeInteger($_POST["anon_form"]) . ", 827 " . Misc::escapeInteger($_POST["anon_form_required"]) . ", 828 " . Misc::escapeInteger($_POST["close_form"]) . ", 829 " . Misc::escapeInteger($_POST["close_form_required"]) . ", 830 " . Misc::escapeInteger($_POST["list_display"]) . ", 831 " . Misc::escapeInteger($_POST["min_role"]) . ", 832 " . Misc::escapeInteger($_POST['rank']) . ", 833 '" . Misc::escapeString(@$_POST['custom_field_backend']) . "' 834 )"; 835 $res = $GLOBALS["db_api"]->dbh->query($stmt); 836 if (PEAR::isError($res)) { 837 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 838 return -1; 839 } else { 840 $new_id = $GLOBALS["db_api"]->get_last_insert_id(); 841 if (($_POST["field_type"] == 'combo') || ($_POST["field_type"] == 'multiple')) { 842 foreach ($_POST["field_options"] as $option_value) { 843 $params = Custom_Field::parseParameters($option_value); 844 Custom_Field::addOptions($new_id, $params["value"]); 845 } 846 } 847 // add the project associations! 848 for ($i = 0; $i < count($_POST["projects"]); $i++) { 849 Custom_Field::associateProject($_POST["projects"][$i], $new_id); 850 } 851 return 1; 852 } 853 } 854 855 856 /** 857 * Method used to associate a custom field to a project. 858 * 859 * @access public 860 * @param integer $prj_id The project ID 861 * @param integer $fld_id The custom field ID 862 * @return boolean 863 */ 864 function associateProject($prj_id, $fld_id) 865 { 866 $stmt = "INSERT INTO 867 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project_custom_field 868 ( 869 pcf_prj_id, 870 pcf_fld_id 871 ) VALUES ( 872 " . Misc::escapeInteger($prj_id) . ", 873 " . Misc::escapeInteger($fld_id) . " 874 )"; 875 $res = $GLOBALS["db_api"]->dbh->query($stmt); 876 if (PEAR::isError($res)) { 877 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 878 return false; 879 } else { 880 return true; 881 } 882 } 883 884 885 /** 886 * Method used to get the list of custom fields available in the 887 * system. 888 * 889 * @access public 890 * @return array The list of custom fields 891 */ 892 function getList() 893 { 894 $stmt = "SELECT 895 * 896 FROM 897 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 898 ORDER BY 899 fld_rank ASC"; 900 $res = $GLOBALS["db_api"]->dbh->getAll($stmt, DB_FETCHMODE_ASSOC); 901 if (PEAR::isError($res)) { 902 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 903 return ""; 904 } else { 905 for ($i = 0; $i < count($res); $i++) { 906 $res[$i]["projects"] = @implode(", ", array_values(Custom_Field::getAssociatedProjects($res[$i]["fld_id"]))); 907 if (($res[$i]["fld_type"] == "combo") || ($res[$i]["fld_type"] == "multiple")) { 908 if (!empty($res[$i]['fld_backend'])) { 909 $res[$i]["field_options"] = @implode(", ", array_values(Custom_Field::getOptions($res[$i]["fld_id"]))); 910 } 911 } 912 if (!empty($res[$i]['fld_backend'])) { 913 $res[$i]['field_options'] = 'Backend: ' . Custom_Field::getBackendName($res[$i]['fld_backend']); 914 } 915 $res[$i]['min_role_name'] = @User::getRole($res[$i]['fld_min_role']); 916 } 917 return $res; 918 } 919 } 920 921 922 /** 923 * Method used to get the list of associated projects with a given 924 * custom field ID. 925 * 926 * @access public 927 * @param integer $fld_id The project ID 928 * @return array The list of associated projects 929 */ 930 function getAssociatedProjects($fld_id) 931 { 932 $stmt = "SELECT 933 prj_id, 934 prj_title 935 FROM 936 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project, 937 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project_custom_field 938 WHERE 939 pcf_prj_id=prj_id AND 940 pcf_fld_id=" . Misc::escapeInteger($fld_id) . " 941 ORDER BY 942 prj_title ASC"; 943 $res = $GLOBALS["db_api"]->dbh->getAssoc($stmt); 944 if (PEAR::isError($res)) { 945 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 946 return ""; 947 } else { 948 return $res; 949 } 950 } 951 952 953 /** 954 * Method used to get the details of a specific custom field. 955 * 956 * @access public 957 * @param integer $fld_id The custom field ID 958 * @param boolean $force_refresh If the details must be loaded again from the database 959 * @return array The custom field details 960 */ 961 function getDetails($fld_id, $force_refresh = false) 962 { 963 static $returns; 964 965 if ((isset($returns[$fld_id])) && ($force_refresh == false)) { 966 return $returns[$fld_id]; 967 } 968 969 $stmt = "SELECT 970 * 971 FROM 972 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 973 WHERE 974 fld_id=" . Misc::escapeInteger($fld_id); 975 $res = $GLOBALS["db_api"]->dbh->getRow($stmt, DB_FETCHMODE_ASSOC); 976 if (PEAR::isError($res)) { 977 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 978 return ""; 979 } else { 980 $res["projects"] = @array_keys(Custom_Field::getAssociatedProjects($fld_id)); 981 $t = array(); 982 $options = Custom_Field::getOptions($fld_id); 983 foreach ($options as $cfo_id => $cfo_value) { 984 $res["field_options"]["existing:" . $cfo_id . ":" . $cfo_value] = $cfo_value; 985 } 986 $returns[$fld_id] = $res; 987 return $res; 988 } 989 } 990 991 992 /** 993 * Method used to get the list of custom field options associated 994 * with a given custom field ID. 995 * 996 * @access public 997 * @param integer $fld_id The custom field ID 998 * @param array $ids An array of ids to return values for. 999 * @return array The list of custom field options 1000 */ 1001 function getOptions($fld_id, $ids = false) 1002 { 1003 static $returns; 1004 1005 $return_key = $fld_id . serialize($ids); 1006 1007 if (isset($returns[$return_key])) { 1008 return $returns[$return_key]; 1009 } 1010 $backend = Custom_Field::getBackend($fld_id); 1011 if ((is_object($backend)) && (method_exists($backend, 'getList'))) { 1012 $list = $backend->getList($fld_id); 1013 if ($ids != false) { 1014 foreach ($list as $id => $value) { 1015 if (!in_array($id, $ids)) { 1016 unset($list[$id]); 1017 } 1018 } 1019 } 1020 $returns[$return_key] = $list; 1021 return $list; 1022 } else { 1023 $stmt = "SELECT 1024 cfo_id, 1025 cfo_value 1026 FROM 1027 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field_option 1028 WHERE 1029 cfo_fld_id=" . Misc::escapeInteger($fld_id); 1030 if ($ids != false) { 1031 $stmt .= " AND 1032 cfo_id IN(" . join(', ', Misc::escapeInteger($ids)) . ")"; 1033 } 1034 $stmt .= " 1035 ORDER BY 1036 cfo_id ASC"; 1037 $res = $GLOBALS["db_api"]->dbh->getAssoc($stmt); 1038 if (PEAR::isError($res)) { 1039 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1040 return ""; 1041 } else { 1042 asort($res); 1043 $returns[$return_key] = $res; 1044 return $res; 1045 } 1046 } 1047 } 1048 1049 1050 /** 1051 * Method used to parse the special format used in the combo boxes 1052 * in the administration section of the system, in order to be 1053 * used as a way to flag the system for whether the custom field 1054 * option is a new one or one that should be updated. 1055 * 1056 * @access private 1057 * @param string $value The custom field option format string 1058 * @return array Parameters used by the update/insert methods 1059 */ 1060 function parseParameters($value) 1061 { 1062 if (substr($value, 0, 4) == 'new:') { 1063 return array( 1064 "type" => "new", 1065 "value" => substr($value, 4) 1066 ); 1067 } else { 1068 $value = substr($value, strlen("existing:")); 1069 return array( 1070 "type" => "existing", 1071 "id" => substr($value, 0, strpos($value, ":")), 1072 "value" => substr($value, strpos($value, ":")+1) 1073 ); 1074 } 1075 } 1076 1077 1078 /** 1079 * Method used to update the details for a specific custom field. 1080 * 1081 * @access public 1082 * @return integer 1 if the update worked, -1 otherwise 1083 */ 1084 function update() 1085 { 1086 if (empty($_POST["report_form"])) { 1087 $_POST["report_form"] = 0; 1088 } 1089 if (empty($_POST["report_form_required"])) { 1090 $_POST["report_form_required"] = 0; 1091 } 1092 if (empty($_POST["anon_form"])) { 1093 $_POST["anon_form"] = 0; 1094 } 1095 if (empty($_POST["anon_form_required"])) { 1096 $_POST["anon_form_required"] = 0; 1097 } 1098 if (empty($_POST["list_display"])) { 1099 $_POST["list_display"] = 0; 1100 } 1101 if (empty($_POST["close_form"])) { 1102 $_POST["close_form"] = 0; 1103 } 1104 if (empty($_POST["close_form_required"])) { 1105 $_POST["close_form_required"] = 0; 1106 } 1107 if (empty($_POST["min_role"])) { 1108 $_POST["min_role"] = 1; 1109 } 1110 if (!isset($_POST["rank"])) { 1111 $_POST["rank"] = (Custom_Field::getMaxRank() + 1); 1112 } 1113 $old_details = Custom_Field::getDetails($_POST["id"]); 1114 $stmt = "UPDATE 1115 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 1116 SET 1117 fld_title='" . Misc::escapeString($_POST["title"]) . "', 1118 fld_description='" . Misc::escapeString($_POST["description"]) . "', 1119 fld_type='" . Misc::escapeString($_POST["field_type"]) . "', 1120 fld_report_form=" . Misc::escapeInteger($_POST["report_form"]) . ", 1121 fld_report_form_required=" . Misc::escapeInteger($_POST["report_form_required"]) . ", 1122 fld_anonymous_form=" . Misc::escapeInteger($_POST["anon_form"]) . ", 1123 fld_anonymous_form_required=" . Misc::escapeInteger($_POST["anon_form_required"]) . ", 1124 fld_close_form=" . Misc::escapeInteger($_POST["close_form"]) . ", 1125 fld_close_form_required=" . Misc::escapeInteger($_POST["close_form_required"]) . ", 1126 fld_list_display=" . Misc::escapeInteger($_POST["list_display"]) . ", 1127 fld_min_role=" . Misc::escapeInteger($_POST['min_role']) . ", 1128 fld_rank = " . Misc::escapeInteger($_POST['rank']) . ", 1129 fld_backend = '" . Misc::escapeString(@$_POST['custom_field_backend']) . "' 1130 WHERE 1131 fld_id=" . Misc::escapeInteger($_POST["id"]); 1132 $res = $GLOBALS["db_api"]->dbh->query($stmt); 1133 if (PEAR::isError($res)) { 1134 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1135 return -1; 1136 } else { 1137 // if the current custom field is a combo box, get all of the current options 1138 if (in_array($_POST["field_type"], array('combo', 'multiple'))) { 1139 $stmt = "SELECT 1140 cfo_id 1141 FROM 1142 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field_option 1143 WHERE 1144 cfo_fld_id=" . Misc::escapeInteger($_POST["id"]); 1145 $current_options = $GLOBALS["db_api"]->dbh->getCol($stmt); 1146 } 1147 if ($old_details["fld_type"] != $_POST["field_type"]) { 1148 // gotta remove all custom field options if the field is being changed from a combo box to a text field 1149 if ((!in_array($old_details['fld_type'], array('text', 'textarea'))) && 1150 (!in_array($_POST["field_type"], array('combo', 'multiple')))) { 1151 Custom_Field::removeOptionsByFields($_POST["id"]); 1152 } 1153 if (in_array($_POST['field_type'], array('text', 'textarea', 'date', 'integer'))) { 1154 // update values for all other option types 1155 Custom_Field::updateValuesForNewType($_POST['id']); 1156 } 1157 } 1158 // update the custom field options, if any 1159 if (($_POST["field_type"] == "combo") || ($_POST["field_type"] == "multiple")) { 1160 $updated_options = array(); 1161 if (empty($_POST['custom_field_backend'])) { 1162 foreach ($_POST["field_options"] as $option_value) { 1163 $params = Custom_Field::parseParameters($option_value); 1164 if ($params["type"] == 'new') { 1165 Custom_Field::addOptions($_POST["id"], $params["value"]); 1166 } else { 1167 $updated_options[] = $params["id"]; 1168 // check if the user is trying to update the value of this option 1169 if ($params["value"] != Custom_Field::getOptionValue($_POST["id"], $params["id"])) { 1170 Custom_Field::updateOption($params["id"], $params["value"]); 1171 } 1172 } 1173 } 1174 } 1175 } 1176 // get the diff between the current options and the ones posted by the form 1177 // and then remove the options not found in the form submissions 1178 if (in_array($_POST["field_type"], array('combo', 'multiple'))) { 1179 $diff_ids = @array_diff($current_options, $updated_options); 1180 if (@count($diff_ids) > 0) { 1181 Custom_Field::removeOptions($_POST['id'], array_values($diff_ids)); 1182 } 1183 } 1184 // now we need to check for any changes in the project association of this custom field 1185 // and update the mapping table accordingly 1186 $old_proj_ids = @array_keys(Custom_Field::getAssociatedProjects($_POST["id"])); 1187 // COMPAT: this next line requires PHP > 4.0.4 1188 $diff_ids = array_diff($old_proj_ids, $_POST["projects"]); 1189 if (count($diff_ids) > 0) { 1190 foreach ($diff_ids as $removed_prj_id) { 1191 Custom_Field::removeIssueAssociation($_POST["id"], false, $removed_prj_id ); 1192 } 1193 } 1194 // update the project associations now 1195 $stmt = "DELETE FROM 1196 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project_custom_field 1197 WHERE 1198 pcf_fld_id=" . Misc::escapeInteger($_POST["id"]); 1199 $res = $GLOBALS["db_api"]->dbh->query($stmt); 1200 if (PEAR::isError($res)) { 1201 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1202 return -1; 1203 } else { 1204 for ($i = 0; $i < count($_POST["projects"]); $i++) { 1205 Custom_Field::associateProject($_POST["projects"][$i], $_POST["id"]); 1206 } 1207 } 1208 return 1; 1209 } 1210 } 1211 1212 1213 /** 1214 * Method used to get the list of custom fields associated with a 1215 * given project. 1216 * 1217 * @access public 1218 * @param integer $prj_id The project ID 1219 * @return array The list of custom fields 1220 */ 1221 function getFieldsByProject($prj_id) 1222 { 1223 $stmt = "SELECT 1224 pcf_fld_id 1225 FROM 1226 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project_custom_field 1227 WHERE 1228 pcf_prj_id=" . Misc::escapeInteger($prj_id); 1229 $res = $GLOBALS["db_api"]->dbh->getCol($stmt); 1230 if (PEAR::isError($res)) { 1231 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1232 return array(); 1233 } else { 1234 return $res; 1235 } 1236 } 1237 1238 1239 /** 1240 * Method used to remove the issue associations related to a given 1241 * custom field ID. 1242 * 1243 * @access public 1244 * @param integer $fld_id The custom field ID 1245 * @param integer $issue_id The issue ID (not required) 1246 * @param integer $prj_id The project ID (not required) 1247 * @return boolean 1248 */ 1249 function removeIssueAssociation($fld_id, $issue_id = FALSE, $prj_id = false) 1250 { 1251 if (is_array($fld_id)) { 1252 $fld_id = implode(", ", Misc::escapeInteger($fld_id)); 1253 } 1254 $issues = array(); 1255 if ($issue_id != false) { 1256 $issues = array($issue_id); 1257 } elseif ($prj_id != false) { 1258 $sql = "SELECT 1259 iss_id 1260 FROM 1261 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue 1262 WHERE 1263 iss_prj_id = " . Misc::escapeInteger($prj_id); 1264 $res = $GLOBALS['db_api']->dbh->getCol($sql); 1265 if (PEAR::isError($res)) { 1266 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1267 return false; 1268 } else { 1269 $issues = $res; 1270 } 1271 } 1272 $stmt = "DELETE FROM 1273 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 1274 WHERE 1275 icf_fld_id IN (" . $fld_id . ")"; 1276 if (count($issues) > 0) { 1277 $stmt .= " AND icf_iss_id IN(" . join(', ', Misc::escapeInteger($issues)) . ")"; 1278 } 1279 $res = $GLOBALS["db_api"]->dbh->query($stmt); 1280 if (PEAR::isError($res)) { 1281 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1282 return false; 1283 } else { 1284 return true; 1285 } 1286 } 1287 1288 1289 /** 1290 * Method used to remove the custom field options associated with 1291 * a given list of custom field IDs. 1292 * 1293 * @access public 1294 * @param array $ids The list of custom field IDs 1295 * @return boolean 1296 */ 1297 function removeOptionsByFields($ids) 1298 { 1299 if (!is_array($ids)) { 1300 $ids = array($ids); 1301 } 1302 $items = implode(", ", Misc::escapeInteger($ids)); 1303 $stmt = "SELECT 1304 cfo_id 1305 FROM 1306 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field_option 1307 WHERE 1308 cfo_fld_id IN ($items)"; 1309 $res = $GLOBALS["db_api"]->dbh->getCol($stmt); 1310 if (PEAR::isError($res)) { 1311 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1312 return false; 1313 } else { 1314 Custom_Field::removeOptions($ids, $res); 1315 return true; 1316 } 1317 } 1318 1319 1320 /** 1321 * Method used to remove all custom field entries associated with 1322 * a given set of issues. 1323 * 1324 * @access public 1325 * @param array $ids The array of issue IDs 1326 * @return boolean 1327 */ 1328 function removeByIssues($ids) 1329 { 1330 $items = implode(", ", Misc::escapeInteger($ids)); 1331 $stmt = "DELETE FROM 1332 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 1333 WHERE 1334 icf_iss_id IN ($items)"; 1335 $res = $GLOBALS["db_api"]->dbh->query($stmt); 1336 if (PEAR::isError($res)) { 1337 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1338 return false; 1339 } else { 1340 return true; 1341 } 1342 } 1343 1344 1345 /** 1346 * Method used to remove all custom fields associated with 1347 * a given set of projects. 1348 * 1349 * @access public 1350 * @param array $ids The array of project IDs 1351 * @return boolean 1352 */ 1353 function removeByProjects($ids) 1354 { 1355 $items = implode(", ", Misc::escapeInteger($ids)); 1356 $stmt = "DELETE FROM 1357 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project_custom_field 1358 WHERE 1359 pcf_prj_id IN ($items)"; 1360 $res = $GLOBALS["db_api"]->dbh->query($stmt); 1361 if (PEAR::isError($res)) { 1362 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1363 return false; 1364 } else { 1365 return true; 1366 } 1367 } 1368 1369 1370 /** 1371 * Method to return the names of the fields which should be displayed on the list issues page. 1372 * 1373 * @access public 1374 * @param integer $prj_id The ID of the project. 1375 * @return array An array of custom field names. 1376 */ 1377 function getFieldsToBeListed($prj_id) 1378 { 1379 $sql = "SELECT 1380 fld_id, 1381 fld_title 1382 FROM 1383 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field, 1384 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "project_custom_field 1385 WHERE 1386 fld_id = pcf_fld_id AND 1387 pcf_prj_id = " . Misc::escapeInteger($prj_id) . " AND 1388 fld_list_display = 1 AND 1389 fld_min_role <= " . Auth::getCurrentRole() . " 1390 ORDER BY 1391 fld_rank ASC"; 1392 $res = $GLOBALS["db_api"]->dbh->getAssoc($sql); 1393 if (PEAR::isError($res)) { 1394 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1395 return array(); 1396 } else { 1397 return $res; 1398 } 1399 } 1400 1401 1402 /** 1403 * Returns the fld_id of the field with the specified title 1404 * 1405 * @access public 1406 * @param string $title The title of the field 1407 * @return integer The fld_id 1408 */ 1409 function getIDByTitle($title) 1410 { 1411 $sql = "SELECT 1412 fld_id 1413 FROM 1414 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 1415 WHERE 1416 fld_title = '" . Misc::escapeString($title) . "'"; 1417 $res = $GLOBALS["db_api"]->dbh->getOne($sql); 1418 if (PEAR::isError($res)) { 1419 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1420 return 0; 1421 } else { 1422 if (empty($res)) { 1423 return 0; 1424 } else { 1425 return $res; 1426 } 1427 } 1428 } 1429 1430 1431 /** 1432 * Returns the value for the specified field 1433 * 1434 * @access public 1435 * @param integer $iss_id The ID of the issue 1436 * @param integer $fld_id The ID of the field 1437 * @param boolean $raw If the raw value should be displayed 1438 * @param mixed an array or string containing the value 1439 */ 1440 function getDisplayValue($iss_id, $fld_id, $raw = false) 1441 { 1442 $sql = "SELECT 1443 fld_id, 1444 fld_type, 1445 " . Custom_Field::getDBValueFieldSQL() . " as value 1446 FROM 1447 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field, 1448 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 1449 WHERE 1450 fld_id=icf_fld_id AND 1451 icf_iss_id=" . Misc::escapeInteger($iss_id) . " AND 1452 fld_id = " . Misc::escapeInteger($fld_id); 1453 $res = $GLOBALS["db_api"]->dbh->getAll($sql, DB_FETCHMODE_ASSOC); 1454 if (PEAR::isError($res)) { 1455 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1456 return ''; 1457 } else { 1458 $values = array(); 1459 for ($i = 0; $i < count($res); $i++) { 1460 if (($res[$i]["fld_type"] == "combo") || ($res[$i]['fld_type'] == 'multiple')) { 1461 if ($raw) { 1462 $values[] = $res[$i]['value']; 1463 } else { 1464 $values[] = Custom_Field::getOptionValue($res[$i]["fld_id"], $res[$i]["value"]); 1465 } 1466 } else { 1467 $values[] = $res[$i]['value']; 1468 } 1469 } 1470 if ($raw) { 1471 return $values; 1472 } else { 1473 return join(', ', $values); 1474 } 1475 } 1476 } 1477 1478 1479 /** 1480 * Returns the current maximum rank of any custom fields. 1481 * 1482 * @access public 1483 * @return integer The highest rank 1484 */ 1485 function getMaxRank() 1486 { 1487 $sql = "SELECT 1488 max(fld_rank) 1489 FROM 1490 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field"; 1491 return $GLOBALS["db_api"]->dbh->getOne($sql); 1492 } 1493 1494 1495 /** 1496 * Changes the rank of a custom field 1497 * 1498 * @access public 1499 */ 1500 function changeRank() 1501 { 1502 $fld_id = $_REQUEST['id']; 1503 $direction = $_REQUEST['direction']; 1504 // get array of all fields and current ranks 1505 $fields = Custom_Field::getList(); 1506 for ($i = 0;$i < count($fields); $i++) { 1507 if ($fields[$i]['fld_id'] == $fld_id) { 1508 // this is the field we want to mess with 1509 if ((($i == 0) && ($direction == -1)) || 1510 ((($i+1) == count($fields)) && ($direction == +1))) { 1511 // trying to move first entry lower or last entry higher will not work 1512 break; 1513 } 1514 1515 $target_index = ($i + $direction); 1516 $target_row = $fields[$target_index]; 1517 if (empty($target_row)) { 1518 break; 1519 } 1520 // update this entry 1521 Custom_Field::setRank($fld_id, $target_row['fld_rank']); 1522 1523 // update field we stole this rank from 1524 Custom_Field::setRank($target_row['fld_id'], $fields[$i]['fld_rank']); 1525 } 1526 } 1527 1528 // re-order everything starting from 1 1529 $fields = Custom_Field::getList(); 1530 $rank = 1; 1531 foreach ($fields as $field) { 1532 Custom_Field::setRank($field['fld_id'], $rank++); 1533 } 1534 return 1; 1535 } 1536 1537 1538 /** 1539 * Sets the rank of a custom field 1540 * 1541 * @access public 1542 * @param integer $fld_id The ID of the field 1543 * @param integer $rank The new rank for this field 1544 * @return integer 1 if successful, -1 otherwise 1545 */ 1546 function setRank($fld_id, $rank) 1547 { 1548 $sql = "UPDATE 1549 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 1550 SET 1551 fld_rank = $rank 1552 WHERE 1553 fld_id = $fld_id"; 1554 $res = $GLOBALS["db_api"]->dbh->query($sql); 1555 if (PEAR::isError($res)) { 1556 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1557 return -1; 1558 } 1559 return 1; 1560 } 1561 1562 1563 /** 1564 * Returns the list of available custom field backends by listing the class 1565 * files in the backend directory. 1566 * 1567 * @access public 1568 * @return array Associative array of filename => name 1569 */ 1570 function getBackendList() 1571 { 1572 $files = Misc::getFileList(APP_INC_PATH . "custom_field"); 1573 $list = array(); 1574 for ($i = 0; $i < count($files); $i++) { 1575 // make sure we only list the backends 1576 if (preg_match('/^class\.(.*)\.php$/', $files[$i])) { 1577 // display a prettyfied backend name in the admin section 1578 $list[$files[$i]] = Custom_Field::getBackendName($files[$i]); 1579 } 1580 } 1581 return $list; 1582 } 1583 1584 1585 /** 1586 * Returns the 'pretty' name of the backend 1587 * 1588 * @access public 1589 * @param string $backend The full backend file name 1590 * @return string The pretty name of the backend. 1591 */ 1592 function getBackendName($backend) 1593 { 1594 preg_match('/^class\.(.*)\.php$/', $backend, $matches); 1595 return ucwords(str_replace('_', ' ', $matches[1])); 1596 } 1597 1598 1599 /** 1600 * Returns an instance of custom field backend class if it exists for the 1601 * specified field. 1602 * 1603 * @access public 1604 * @param integer $fld_id The ID of the field 1605 * @return mixed false if there is no backend or an instance of the backend class 1606 */ 1607 function &getBackend($fld_id) 1608 { 1609 static $returns; 1610 1611 // poor mans caching 1612 if (isset($returns[$fld_id])) { 1613 return $returns[$fld_id]; 1614 } 1615 1616 $sql = "SELECT 1617 fld_backend 1618 FROM 1619 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "custom_field 1620 WHERE 1621 fld_id = " . Misc::escapeInteger($fld_id); 1622 $res = $GLOBALS["db_api"]->dbh->getOne($sql); 1623 if (PEAR::isError($res)) { 1624 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1625 return false; 1626 } elseif (!empty($res)) { 1627 require_once(APP_INC_PATH . "custom_field/$res"); 1628 1629 $file_name_chunks = explode(".", $res); 1630 $class_name = $file_name_chunks[1] . "_Custom_Field_Backend"; 1631 1632 $returns[$fld_id] = new $class_name; 1633 } else { 1634 $returns[$fld_id] = false; 1635 } 1636 return $returns[$fld_id]; 1637 } 1638 1639 1640 /** 1641 * Searches a specified custom field for a string and returns any issues that match 1642 * 1643 * @access public 1644 * @param integer $fld_id The ID of the custom field 1645 * @param string $search The string to search for 1646 * @return array An array of issue IDs 1647 */ 1648 function getIssuesByString($fld_id, $search) 1649 { 1650 $sql = "SELECT 1651 icf_iss_id 1652 FROM 1653 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 1654 WHERE 1655 icf_fld_id = " . Misc::escapeInteger($fld_id) . " AND 1656 ( 1657 icf_value LIKE '%" . Misc::escapeString($search) . "%' OR 1658 icf_value_integer LIKE '%" . Misc::escapeInteger($search) . "%' OR 1659 icf_value_date LIKE '%" . Misc::escapeString($search) . "%' 1660 )"; 1661 $res = $GLOBALS["db_api"]->dbh->getCol($sql); 1662 if (PEAR::isError($res)) { 1663 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1664 return array(); 1665 } 1666 return $res; 1667 } 1668 1669 1670 /** 1671 * Formats the return value 1672 * 1673 * @access public 1674 * @param mixed $value The value to format 1675 * @param integer $fld_id The ID of the field 1676 * @param integer $issue_id The ID of the issue 1677 * @return mixed the formatted value. 1678 */ 1679 function formatValue($value, $fld_id, $issue_id) 1680 { 1681 $backend = Custom_Field::getBackend($fld_id); 1682 if ((is_object($backend)) && (method_exists($backend, 'formatValue'))) { 1683 return $backend->formatValue($value, $fld_id, $issue_id); 1684 } else { 1685 return Link_Filter::processText(Auth::getCurrentProject(), htmlspecialchars($value)); 1686 } 1687 } 1688 1689 1690 /** 1691 * This method inserts a blank value for all custom fields that do not already have a record. 1692 * It currently is not called by the main code, but is included to be called from workflow classes. 1693 * 1694 * @access public 1695 * @param integer $issue_id The Issue ID 1696 */ 1697 function populateAllFields($issue_id) 1698 { 1699 $prj_id = Issue::getProjectID($issue_id); 1700 $fields = Custom_Field::getListByIssue($prj_id, $issue_id, APP_SYSTEM_USER_ID); 1701 foreach ($fields as $field) { 1702 if (empty($field['value'])) { 1703 Custom_Field::removeIssueAssociation($field['fld_id'], $issue_id); 1704 Custom_Field::associateIssue($issue_id, $field['fld_id'], ''); 1705 } 1706 } 1707 } 1708 1709 1710 /** 1711 * Returns the name of the db field this custom field uses based on the type. 1712 * 1713 * @param string $type 1714 * @return string 1715 */ 1716 function getDBValueFieldNameByType($type) 1717 { 1718 switch ($type) { 1719 case 'date': 1720 return 'icf_value_date'; 1721 case 'integer': 1722 return 'icf_value_integer'; 1723 default: 1724 return 'icf_value'; 1725 } 1726 } 1727 1728 1729 function getDBValueFieldSQL() 1730 { 1731 return "(IF(fld_type = 'date', icf_value_date, IF(fld_type = 'integer', icf_value_integer, icf_value)))"; 1732 } 1733 1734 1735 /** 1736 * Analyzes the contents of the issue_custom_field and updates 1737 * contents based on the fld_type. 1738 * 1739 * @param integer $fld_id 1740 */ 1741 function updateValuesForNewType($fld_id) 1742 { 1743 $details = Custom_Field::getDetails($fld_id, true); 1744 $db_field_name = Custom_Field::getDBValueFieldNameByType($details['fld_type']); 1745 1746 1747 $sql = "UPDATE 1748 " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue_custom_field 1749 SET 1750 "; 1751 if ($details['fld_type'] == 'integer') { 1752 $sql .= "$db_field_name = IFNULL(icf_value, IFNULL(icf_value_date, NULL)), 1753 icf_value = NULL, 1754 icf_value_date = NULL"; 1755 } elseif ($details['fld_type'] == 'date') { 1756 $sql .= "$db_field_name = IFNULL(icf_value, IFNULL(icf_value_date, NULL)), 1757 icf_value = NULL, 1758 icf_value_integer = NULL"; 1759 } else { 1760 $sql .= "$db_field_name = IFNULL(icf_value_integer, IFNULL(icf_value_date, NULL)), 1761 icf_value_integer = NULL, 1762 icf_value_date = NULL"; 1763 } 1764 $sql .= " 1765 WHERE 1766 $db_field_name IS NULL AND 1767 icf_fld_id = " . Misc::escapeInteger($fld_id); 1768 $res = $GLOBALS["db_api"]->dbh->query($sql); 1769 if (PEAR::isError($res)) { 1770 Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); 1771 return false; 1772 } 1773 return true; 1774 } 1775 } 1776 1777 // benchmarking the included file (aka setup time) 1778 if (APP_BENCHMARK) { 1779 $GLOBALS['bench']->setMarker('Included Custom_Field Class'); 1780 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Dec 19 21:21:33 2007 | Cross-referenced by PHPXref 0.7 |