testlink1.9.6 /lib/functions/tlTestCaseFilterControl.class.php

Language PHP Lines 1511
MD5 Hash b99f357b3f875cfc13c477c6ea6be25a Estimated Cost $22,188 (why?)
Repository https://bitbucket.org/pfernandez/testlink1.9.6 View Raw File
   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
<?php
/**
 * TestLink Open Source Project - http://testlink.sourceforge.net/
 * This script is distributed under the GNU General Public License 2 or later.
 *
 * @package    TestLink
 * @author     Andreas Simon
 * @copyright  2006-2010, TestLink community
 * @version    CVS: $Id: tlTestCaseFilterControl.class.php,v 1.18 2010/08/30 09:14:59 asimon83 Exp $
 * @link       http://www.teamst.org/index.php
 * @filesource http://testlink.cvs.sourceforge.net/viewvc/testlink/testlink/lib/functions/tlTestCaseFilterControl.class.php?view=markup
 *
 * This class extends tlFilterPanel for the specific use with test case tree.
 * It holds the logic to be used at GUI level to manage a common set of settings and filters for test cases.
 * 
 * This class is used from different navigator-frames (left frames with a test case tree in it)
 * with different modes for different features.
 * This is a little overview about its usage in TestLink:
 * 
 * - planTCNavigator.php/tpl use it in "plan_mode" for these features:
 *    --> assign test case execution
 *    --> update linked test case versions
 *    --> set urgent tests
 * 
 * - execNavigator.php/tpl in "execution_mode" 
 *    --> test execution
 * 
 * - planAddTCNavigator.php/tpl in "plan_add_mode"
 *    --> add/remove test cases
 * 
 * - listTestCases.php/tcTree.tpl in "edit_mode"
 *    --> edit test specification
 *    --> assign keywords
 *    --> assign requirements
 *
 * @internal Revisions:
 *
 * 20100830 - asimon - BUGID 3726: store user's selection of build and platform
 * 20100811 - asimon - BUGID 3566: show/hide CF
 * 20100810 - asimon - added TC ID filter for Test Cases
 * 20100807 - franciscom - BUGID 3660
 * 20100727 - asimon - BUGID 3630 - syntax error in get_argument_string()
 * 20100716 - asimon - BUGID 3406 - changes on init_settings() and $mode_setting_mapping
 * 20100713 - asimon - fixed Drag&Drop error caused by init_filter_custom_fields()
 * 20100702 - asimon - fixed error in init_setting_testplan()
 * 20100701 - asimon - BUGID 3414 - additional work in init_filter_custom_fields()
 * 20100628 - asimon - removal of constants
 * 20100624 - asimon - CVS merge (experimental branch to HEAD)
 * 20100503 - asimon - start of implementation of filter panel class hierarchy
 *                     to simplify/generalize filter panel handling
 *                     for test cases and requirements
 */

/*
 * --------------------------------------------------------
 * An important note on BUGID 3516 (request-URL too large):
 * --------------------------------------------------------
 * 
 * That problem has been solved by attaching some data (the set of active filters, settings and
 * testcase IDs to show if filtering has been done) to session.
 * 
 * Since a user can have the same feature open in multiple tabs, that alone is not enough to
 * solve this issue. When a user opens e.g. the test case execution page and sets filter options
 * there, then opens the same page in another tab, the data saved in session would also be
 * applied to this second tab although no filter options have been set there yet by the user.
 * 
 * This has now been solved by a so called form token. This token is, on first opening of a
 * navigator frame, generated by the method generate_form_token() and then stored in a member
 * variable with the name $form_token. This token will be stored in an identically named hidden 
 * input field within the HTML filter form, so it gets sent by POST to every called page.
 * It is also attached to the GET argument string returned by get_argument_string() that gets 
 * passed to multiple JavaScript functions, which are used to open nodes from the tree in the 
 * left frame in a new page in the right frame.
 * 
 * So the token is used to identify (from pages within the right frame) the data that got stored
 * for them in session by the navigator page in the left frame.
 * If the navigator page calls itself (when the user presses one of the submit buttons in the form),
 * it sends the stored token via POST to itself. So the same token can be used again to store data
 * in session, instead of generating a new token blindly on every page call no matter where the
 * call comes from. But if the user opens a new tab, the new navigator page knows this because
 * no token has been sent to it - so it generates a new one.
 * 
 * The data is saved in session in the form of an array like this example:
 * 
 * [execution_mode] => Array                              // "mode" used by navigator
 *   (
 *     [1986901204] => Array                              // form token to identify the correct tab
 *       (
 *         [filter_keywords_filter_type] => Or            // the active filters and settings,
 *         [filter_result_result] => f                    // prefixed with "filter_" and "setting_"
 *         [filter_result_method] => 3
 *         [filter_result_build] => 71
 *         [filter_assigned_user_include_unassigned] => 1
 *         [filter_testcase_name] => 
 *         [filter_toplevel_testsuite] => Array
 *           (
 *           )
 *
 *         [filter_keywords] => 
 *         [filter_priority] => 3
 *         [filter_execution_type] => 2
 *         [filter_assigned_user] => Array
 *           (
 *             [3] => 3
 *           )
 * 
 *         [filter_custom_fields] => 
 *         [setting_testplan] => 4990
 *         [setting_build] => 71
 *         [setting_platform] => 
 *         [setting_refresh_tree_on_action] => 1
 *         [testcases_to_show] => Array                   // The internal IDs of the test cases which
 *           (                                            // where not filtered out by user's choices.
 *             [0] => 1852                                // This was the part which earlier caused
 *             [1] => 60                                  // the error because of the too long URL.
 *             [2] => 2039
 *             [3] => 2033
 *             [4] => 2065
 *             [5] => 2159
 *             [6] => 3733
 *           )
 *
 *         [timestamp] => 1277727920                      // additional means to check age of session data
 *       )
 *   )
 * 
 * The access to this data can be done in the following way from the right frame page:
 * 
 * $form_token = isset($_REQUEST['form_token']) ? $_REQUEST['form_token'] : 0;
 * $mode = 'execution_mode';
 * $session_data = isset($_SESSION[$mode]) && isset($_SESSION[$mode][$form_token])
 *                 ? $_SESSION[$mode][$form_token] : null;
 * 
 * The variable $session_data then holds the array with all the active filters,
 * settings and filtered test case IDs in it, or is null if nothing has been stored yet
 * in the session.
 * 
 * But now we have another problem:
 * There can be one array for each mode in the session. In each of these arrays is a set of
 * further arrays with the form tokens as keys and the filter information in it.
 * If a user now opens the same page more than once in a row (by switching back and forth 
 * between features or by using the same feature in multiple tabs) there can be more and more
 * arrays with filter information in this set of arrays.
 * 
 * Because of this, an additional timestamp is written into each of these information arrays.
 * On each storage process that writes information into the session triggered by a call 
 * to a navigator page, the timestamp gets refreshed if an old token has been reused or
 * it gets created with the creation of a new data array.
 * 
 * This timestamp can be used to delete old arrays with information that is not needed anymore.
 * Since we have no means to otherwise detect the case that a user has closed the tab 
 * and doesn't need this information in the session anymore, we have to determine the age of 
 * those arrays with the timestamp and delete everything that is older than a certain given 
 * threshold. This is done by the method delete_old_session_data() which is automatically called
 * from the contstructor of this class. It checks the age of all the saved 
 * arrays inside the array for the active mode and then deletes everything that's older than
 * the given threshold. This threshold can be passed as a parameter to the method, otherwise a
 * default value of one hour is used.
 * 
 * If a user logs out of TestLink, of course all this data in the session is deleted,
 * no matter if the one hour threshold has passed or not.
 * ------------------------------------------------------------------------------------------------
 */

/**
 * This class extends tlFilterPanel for the specific use with the testcase tree.
 * It contains logic to be used at GUI level to manage
 * a common set of settings and filters for testcases.
 *
 * @author Andreas Simon
 * @package TestLink
 * @uses testplan
 * @uses exec_cf_mgr
 * @uses tlPlatform
 * @uses testcase
 */
class tlTestCaseFilterControl extends tlFilterControl {

	/**
	 * Testcase manager object.
	 * Initialized not in constructor, only on first use to save resources.
	 * @var testcase
	 */
	private $tc_mgr = null;
	
	/**
	 * Platform manager object.
	 * Initialized not in constructor, only on first use to save resources.
	 * @var tlPlatform
	 */
	private $platform_mgr = null;
	
	/**
	 * Custom field manager object.
	 * Initialized not in constructor, only on first use to save resources.
	 * @var exec_cf_mgr
	 */
	private $exec_cf_mgr = null;
	
	/**
	 * Testplan manager object.
	 * Initialized not in constructor, only on first use to save resources.
	 * @var testplan
	 */
	private $testplan_mgr = null;
	
	/**
	 * This array contains all possible filters.
	 * It is used as a helper to iterate over all the filters in some loops.
	 * It also sets options how and from where to load the parameters with
	 * input fetching functions in init_args()-method.
	 * Its keys are the names of the settings (class constants are used),
	 * its values are the arrays for the input parser.
	 * @var array
	 */
	private $all_filters = array('filter_tc_id' => array("POST", tlInputParameter::STRING_N),
	                             'filter_testcase_name' => array("POST", tlInputParameter::STRING_N),
	                             'filter_toplevel_testsuite' => array("POST", tlInputParameter::STRING_N),
	                             'filter_keywords' => array("POST", tlInputParameter::ARRAY_INT),
	                             'filter_priority' => array("POST", tlInputParameter::INT_N),
	                             'filter_execution_type' => array("POST", tlInputParameter::INT_N),
	                             'filter_assigned_user' => array("POST", tlInputParameter::ARRAY_INT),
	                             'filter_custom_fields' => array("POST", tlInputParameter::ARRAY_STRING_N),
	                             'filter_result' => null); // result: no info here, divided into more parts

	/**
	 * This array is used as an additional security measure. It maps all available
	 * filters to the mode in which they can be used. If a user tries to
	 * enable filters in config.inc.php which are not defined inside this array,
	 * this will be simply ignored instead of trying to initialize the filter
	 * no matter wether it has been implemented or not.
	 * The keys inside this array are the modes defined above as class constants.
	 * So it can be checked if a filter is available in a given mode without
	 * relying only on the config parameter.
	 * @var array
	 */
	private $mode_filter_mapping = array('edit_mode' => array('filter_tc_id',
	                                                          'filter_testcase_name',
	                                                          'filter_toplevel_testsuite',
	                                                          'filter_keywords',
	                                                          'filter_execution_type',
	                                                          'filter_custom_fields'),
	                                     'execution_mode' => array('filter_tc_id',
	                                                               'filter_testcase_name',
	                                                               'filter_toplevel_testsuite',
	                                                               'filter_keywords',
	                                                               'filter_priority',
	                                                               'filter_execution_type',
	                                                               'filter_assigned_user',
	                                                               'filter_custom_fields',
	                                                               'filter_result'),
	                                     'plan_mode' => array('filter_tc_id',
	                                                          'filter_testcase_name',
	                                                          'filter_toplevel_testsuite',
	                                                          'filter_keywords',
	                                                          'filter_priority',
	                                                          'filter_execution_type',
	                                                          'filter_custom_fields',
	                                                          'filter_result'),
	                                     'plan_add_mode' => array('filter_tc_id',
	                                                              'filter_testcase_name',
	                                                              'filter_toplevel_testsuite',
	                                                              'filter_keywords',
	                                                              'filter_priority',
	                                                              'filter_execution_type',
	                                                              'filter_custom_fields'));

	/**
	 * This array contains all possible settings. It is used as a helper
	 * to later iterate over all possibilities in loops.
	 * Its keys are the names of the settings, its values the arrays for the input parser.
	 * @var array
	 */
	private $all_settings = array('setting_testplan' => array("POST", tlInputParameter::INT_N),
	                              'setting_build' => array("POST", tlInputParameter::INT_N),
	                              'setting_platform' => array("POST", tlInputParameter::INT_N),
	                              'setting_refresh_tree_on_action' => array("POST", tlInputParameter::CB_BOOL));

	/**
	 * This array is used to map the modes to their available settings.
	 * @var array
	 */
	private $mode_setting_mapping = array('edit_mode' => array('setting_refresh_tree_on_action'),
	                                      'execution_mode' => array('setting_testplan',
	                                                                'setting_build',
	                                                                'setting_platform',
	                                                                'setting_refresh_tree_on_action'),
	                                      'plan_mode' => array('setting_testplan',
	                                                           // BUGID 3406
	                                                           'setting_build',
	                                                           'setting_refresh_tree_on_action'),
	                                      'plan_add_mode' => array('setting_testplan',
	                                                               'setting_refresh_tree_on_action'));

	/**
	 * The mode used. Depending on the feature for which this class will be instantiated.
	 * This mode defines which filter configuration will be loaded from config.inc.php
	 * and therefore which filters will be loaded and used for the templates.
	 * Value has to be one of the class constants for mode, default is edit mode.
	 * @var string
	 */
	private $mode = 'edit_mode';

	/**
	 * The token that will be used to identify the relationship between left frame
	 * (with navigator) and right frame (which displays execution of test case e.g.) in session.
	 * @var string
	 */
	public $form_token = null;
	
	/**
	 *
	 * @param database $dbHandler
	 * @param string $mode can be edit_mode/execution_mode/plan_mode/plan_add_mode, depending on usage
	 */
	public function __construct(&$dbHandler, $mode = 'edit_mode') {

		// set mode to define further actions before calling parent constructor
		$this->mode = array_key_exists($mode,$this->mode_filter_mapping) ? $mode : 'edit_mode';

		// Call to constructor of parent class tlFilterControl.
		// This already loads configuration and user input
		// and does all the remaining necessary method calls,
		// so no further method call is required here for initialization.
		parent::__construct($dbHandler);

		// delete any filter settings that may be left from previous calls in session
		$this->delete_own_session_data();
		$this->delete_old_session_data();
		
		$this->save_session_data();
	}

	public function __destruct() {
		parent::__destruct(); //destroys testproject manager
		
		// destroy member objects
		unset($this->tc_mgr);
		unset($this->testplan_mgr);
		unset($this->platform_mgr);
		unset($this->exec_cf_mgr);
	}

	/**
	 * Reads the configuration from the configuration file specific for test cases,
	 * additionally to those parts of the config which were already loaded by parent class.
	 *
	 */
	protected function read_config() {

		// some configuration reading already done in parent class
		parent::read_config();

		// load configuration for active mode only
		$this->configuration = config_get('tree_filter_cfg')->testcases->{$this->mode};

		// load also exec config - it is not only needed in exec mode
		$this->configuration->exec_cfg = config_get('exec_cfg');

		// some additional testcase configuration
		$this->configuration->tc_cfg = config_get('testcase_cfg');
		
		// is choice of advanced filter mode enabled?
    	if (isset($this->configuration->advanced_filter_mode_choice)
    	&& $this->configuration->advanced_filter_mode_choice == ENABLED) {
    		$this->filter_mode_choice_enabled = true;
    	} else {
    		$this->filter_mode_choice_enabled = false;
    	}
		
		return tl::OK;
	} // end of method

	/**
	 * Does what init_args() usually does in all scripts: Reads the user input
	 * from request ($_GET and $_POST). Later configuration,
	 * settings and filters get modified according to that user input.
	 */
	protected function init_args() {
		
		// some common user input is already read in parent class
		parent::init_args();

		// add settings and filters to parameter info array for request parsers
		$params = array();
		foreach ($this->all_settings as $name => $info) {
			if (is_array($info)) {
				$params[$name] = $info;
			}
		}
		foreach ($this->all_filters as $name => $info) {
			if (is_array($info)) {
				$params[$name] = $info;
			}
		}
		I_PARAMS($params, $this->args);

		$type = 'filter_keywords_filter_type';
		$this->args->{$type} = (isset($_REQUEST[$type])) ? trim($_REQUEST[$type]) : 'Or';

		$extra_keys = array('filter_result_result',
		                    'filter_result_method',
		                    'filter_result_build');

		foreach ($extra_keys as $ek) {
			$this->args->{$ek} = (isset($_REQUEST[$ek])) ? $_REQUEST[$ek] : null;
		}

		$this->args->{'filter_assigned_user_include_unassigned'} = 
			isset($_REQUEST['filter_assigned_user_include_unassigned']) ? 1 : 0;

		// got session token sent by form or do we have to generate a new one?
		$sent_token = null;
		$this->args->form_token = null;
		if (isset($_REQUEST['form_token'])) {
			// token got sent
			$sent_token = $_REQUEST['form_token'];
		}
		if (!is_null($sent_token) && isset($_SESSION[$this->mode][$sent_token])) {
			// sent token is valid
			$this->form_token = $sent_token;
			$this->args->form_token = $sent_token;
		} else {
			$this->generate_form_token();
		}
		
		// "feature" is needed for plan and edit modes
		$this->args->feature = isset($_REQUEST['feature']) ? trim($_REQUEST['feature']) : null;
		
		switch ($this->mode) {
			
			case 'plan_mode':
				switch($this->args->feature) {
					case 'planUpdateTC':
					case 'test_urgency':
					case 'tc_exec_assignment':
						// feature OK
					break;
				
					default:
						// feature not OK
						tLog("Wrong or missing GET argument 'feature'.", 'ERROR');
						exit();
					break;
				}
			break;
			
			case 'edit_mode':
				switch($this->args->feature) {
					case 'edit_tc':
					case 'keywordsAssign':
					case 'assignReqs':
						// feature OK
					break;
				
					default:
						// feature not OK
						tLog("Wrong or missing GET argument 'feature'.", 'ERROR');
						exit();
					break;
				}
			break;
		}
	    
	} // end of method

	/**
	 * Initializes all settings.
	 * Iterates through all available settings and adds an array to $this->settings
	 * for the active ones, sets the rest to false so this can be
	 * checked from templates and elsewhere.
	 * Then calls the initializing method for each still active setting.
	 */
	protected function init_settings() {
		$at_least_one_active = false;

		foreach ($this->all_settings as $name => $info) {
			$init_method = "init_$name";
			if (in_array($name, $this->mode_setting_mapping[$this->mode])
			&& method_exists($this, $init_method)) {
				// is valid, configured, exists and therefore can be used, so initialize this setting
				$this->$init_method();
				$at_least_one_active = true;
			} else {
				// is not needed, simply deactivate it by setting it to false in main array
				$this->settings[$name] = false;
			}
		}
		
		// special situation: the build setting is in plan mode only needed for one feature
		// BUGID 3406
		if ($this->mode == 'plan_mode' && $this->args->feature != 'tc_exec_assignment') {
			$this->settings['setting_build'] = false;
		}
		
		// if at least one active setting is left to display, switch settings panel on
		if ($at_least_one_active) {
			$this->display_settings = true;
		}
	}

	/**
	 * Initialize all filters. (called by parent::__construct())
	 * I'm double checking here with loaded configuration _and_ additional array
	 * $mode_filter_mapping, set according to defined mode, because this can avoid errors in case
	 * when users try to enable a filter in config that doesn't exist for a mode.
	 * Effect: Only existing and implemented filters can be activated in config file.
	 */
	protected function init_filters() {
		// In resulting data structure, all values have to be defined (at least initialized),
		// no matter wether they are wanted for filtering or not.
		$additional_filters_to_init = array('filter_keywords_filter_type',
		                                    'filter_result_result',
		                                    'filter_result_method',
		                                    'filter_result_build',
		                                    'filter_assigned_user_include_unassigned');
		
		// now nullify them
		foreach ($additional_filters_to_init as $filtername) {
			$this->active_filters[$filtername] = null;
		}
		

		// iterate through all filters and activate the needed ones
		$this->display_filters = false;
		foreach ($this->all_filters as $name => $info) {
			$init_method = "init_$name";
			if (in_array($name, $this->mode_filter_mapping[$this->mode]) && 
				method_exists($this, $init_method) && $this->configuration->{$name} == ENABLED) {

				$this->$init_method();
				
				// there is at least one filter item to display => switch panel on
				$this->display_filters = true;
				
			} else {
				// is not needed, deactivate filter by setting it to false in main array
				// and of course also in active filters array
				$this->filters[$name] = false;
				$this->active_filters[$name] = null;
			}
		}

		// add the important settings to active filter array
		foreach ($this->all_settings as $name => $info) {
			if ($this->settings[$name]) {
				$this->active_filters[$name] = $this->settings[$name]['selected'];
			} else {
				$this->active_filters[$name] = null;
			}
		}
	} // end of method

	/**
	 * This method returns an object or array, containing all selections chosen
	 * by the user for filtering.
	 * 
	 * @return mixed $value Return value is either an array or stdClass object,
	 * depending on active mode. It contains all filter values selected by the user.
	 */
	protected function get_active_filters() {
		static $value = null; // serves as a kind of cache
		                      // if method is called more than once
				
		// convert array to stcClass if needed
		if (!$value) {
			switch ($this->mode) {
				case 'execution_mode':
				case 'plan_mode':
					// these features are generating an exec tree,
					// they need the filters as a stdClass object
					$value = (object) $this->active_filters;
					break;
				
				default:
					// otherwise simply return the array as-is
					$value = $this->active_filters;
					break;
			}
		}
		
		return $value;
	} // end of method

	public function set_testcases_to_show($testcases_to_show = null) {
		// update active_filters
		if (!is_null($testcases_to_show)) {
			$this->active_filters['testcases_to_show'] = $testcases_to_show;
		}
		
		// Since a new filter in active_filters has been set from outside class after
		// saving of session data has already happened in constructor, 
		// we explicitly update data in session after this change here.
		$this->save_session_data();
	}
	
	/**
	 * Active filters will be saved to $_SESSION. 
	 * If there already is data for the active mode and token, it will be overwritten.
	 * This data will be read from pages in the right frame.
	 * This solves the problems with too long URLs.
	 * See issue 3516 in Mantis for a little bit more information/explanation.
	 * The therefore caused new problem that would arise now if
	 * a user uses the same feature simultaneously in multiple browser tabs
	 * is solved be the additional measure of using a form token.
	 * 
	 * @author Andreas Simon
	 * @return $tl::OK
	 */
	public function save_session_data() {		
		if (!isset($_SESSION[$this->mode]) || is_null($_SESSION[$this->mode]) || !is_array($_SESSION[$this->mode])) {
			$_SESSION[$this->mode] = array();
		}
		
		$_SESSION[$this->mode][$this->form_token] = $this->active_filters;
		$_SESSION[$this->mode][$this->form_token]['timestamp'] = time();
		
		return tl::OK;
	}
	
	/**
	 * Old filter data for active mode will be deleted from $_SESSION.
	 * It happens automatically after a session has expired and a user therefore
	 * has to log in again, but here we can configure an additional time limit
	 * only for this special filter part in session data.
	 * 
	 * @author Andreas Simon
	 * @param int $token_validity_duration data older than given timespan will be deleted
	 */
	public function delete_old_session_data($token_validity_duration = 0) {
		// TODO this duration could maybe also be configured in config/const.inc.php
		
		// how long shall the data remain in session before it will be deleted?
		if (!is_numeric($token_validity_duration) || $token_validity_duration <= 0) {
			$token_validity_duration = 60 * 60 * 1; // one hour as default
		}
		
		// delete all tokens from session that are older than given age
		if (isset($_SESSION[$this->mode]) && is_array($_SESSION[$this->mode])) {
			foreach ($_SESSION[$this->mode] as $token => $data) {
				if ($data['timestamp'] < (time() - $token_validity_duration)) {
					// too old, delete!
					unset($_SESSION[$this->mode][$token]);
				}
			}
		}
	}
	
	public function delete_own_session_data() {
		if (isset($_SESSION[$this->mode]) && isset($_SESSION[$this->mode][$this->form_token])) {
			unset($_SESSION[$this->mode][$this->form_token]);
		}
	}
	
	/**
	 * Generates a form token, which will be used to identify the relationship
	 * between left navigator-frame with its settings and right frame.
	 */
	protected function generate_form_token() {
		// Notice: I am just generating an integer here for the token.
		// Since this is not any security relevant stuff like a password hash or similar,
		// but only a means to separate multiple tabs a single user opens, this should suffice.
		// If we should some day decide that an integer is not enough,
		// we just have to change this one method and everything will still work.
		
		$min = 1234567890; // not magic, just some large number so the tokens don't get too short 
		$max = mt_getrandmax();
		$token = 0;
		
		// generate new tokens until we find one that doesn't exist yet
		do {
			$token = mt_rand($min, $max);
		} while (isset($_SESSION[$this->mode][$token]));
		
		$this->form_token = $token;
	}
	
	/**
	 * Active filters will be formatted as a GET-argument string.
	 * 
	 * @return string $string the formatted string with active filters
	 */
	public function get_argument_string() {
		static $string = null; // cache for repeated calls of this method
		
		if (!$string) {
			$string = '';

			// important: the token with which the page in right frame can access data in session
			$string .= '&form_token=' . $this->form_token;
			
			if ($this->settings['setting_build']) {
				$string .= '&setting_build=' . 
				           $this->settings['setting_build']['selected'];
			}
			
			if ($this->settings['setting_platform']) {
				$string .= '&setting_platform=' . 
				           $this->settings['setting_platform']['selected'];
			}
			
			$keyword_list = null;
			if (is_array($this->active_filters['filter_keywords'])) {
				$keyword_list = implode(',', $this->active_filters['filter_keywords']);
			} else if ($this->active_filters['filter_keywords']) {
				$keyword_list = $this->active_filters['filter_keywords'];
			}			
			if ($keyword_list) {
				$string .= '&filter_keywords=' . $keyword_list . 
				           '&filter_keywords_filter_type=' . 
				           $this->active_filters['filter_keywords_filter_type'];
			}
			
			if ($this->active_filters['filter_priority'] > 0) {
				$string .= '&filter_priority=' . $this->active_filters['filter_priority'];
			}
						
			if ($this->active_filters['filter_assigned_user']) {
				// 3630
				$unassigned = $this->active_filters['filter_assigned_user_include_unassigned'] ? '1' : '0';
				$string .= '&filter_assigned_user='. 
				           serialize($this->active_filters['filter_assigned_user']) .
				           '&filter_assigned_user_include_unassigned=' . $unassigned;
			}
			
			if ($this->active_filters['filter_result_result']) {
				$string .= '&filter_result_result=' .
				           serialize($this->active_filters['filter_result_result']) .
				           '&filter_result_method=' .
				           $this->active_filters['filter_result_method'] .
				           '&filter_result_build=' .
				           $this->active_filters['filter_result_build'];
			}
		}
		
		return $string;
	}
	
	/**
	 * Build the tree menu for generation of JavaScript test case tree.
	 * Depending on mode and user's selections in user interface, 
	 * either a completely filtered tree will be build and returned,
	 * or only the minimal necessary data to "lazy load" 
	 * the objects in the tree by later Ajax calls.
	 * No return value - all variables will be stored in gui object
	 * which is passed by reference.
	 * 
	 * @author Andreas Simon
	 * @param object $gui Reference to GUI object (data will be written to it)
	 */
	public function build_tree_menu(&$gui) {
		
		$tree_menu = null;
		$filters = $this->get_active_filters();
		$additional_info = null;
		$options = null;
		$loader = '';
		$children = "[]";
		$cookie_prefix = '';
		
		// by default, disable drag and drop, then later enable if needed
		$drag_and_drop = new stdClass();
		$drag_and_drop->enabled = false;
		$drag_and_drop->BackEndUrl = '';
		$drag_and_drop->useBeforeMoveNode = FALSE;
				
		if (!$this->testproject_mgr) {
			$this->testproject_mgr = new testproject($this->db);
		}
		
		$tc_prefix = $this->testproject_mgr->getTestCasePrefix($this->args->testproject_id);
					
		switch ($this->mode) {
			
			case 'plan_mode':
				// No lazy loading here.
					
				$additional_info = new stdClass();
				$additional_info->useCounters = CREATE_TC_STATUS_COUNTERS_OFF;
				$additional_info->useColours = COLOR_BY_TC_STATUS_OFF;
				$additional_info->testcases_colouring_by_selected_build = DISABLED;
				
				$filters->show_testsuite_contents = 1;
				$filters->hide_testcases = 0;
	
				if ($this->args->feature == 'test_urgency') {
					$filters->hide_testcases = 1;
				}
				
				list($tree_menu, $testcases_to_show) = generateExecTree($this->db,
		                                                       $gui->menuUrl,
		                                                       $this->args->testproject_id,
		                                                       $this->args->testproject_name,
		                                                       $this->args->testplan_id,
		                                                       $this->args->testplan_name,
		                                                       $filters,
		                                                       $additional_info);
				
				$this->set_testcases_to_show($testcases_to_show);
				
				$root_node = $tree_menu->rootnode;
				$children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
				$cookie_prefix = $this->args->feature;
			break;
			
			case 'edit_mode':
				
				if ($gui->tree_drag_and_drop_enabled[$this->args->feature]) {
					$drag_and_drop->enabled = true;
					$drag_and_drop->BackEndUrl = $this->args->basehref . 
					                             'lib/ajax/dragdroptprojectnodes.php';
					$drag_and_drop->useBeforeMoveNode = false;
				}
									
				if ($this->do_filtering) {
					$options = array('forPrinting' => NOT_FOR_PRINTING,
					                 'hideTestCases' => SHOW_TESTCASES,
						             'tc_action_enabled' => DO_ON_TESTCASE_CLICK,
						             'ignore_inactive_testcases' => DO_NOT_FILTER_INACTIVE_TESTCASES,
					                 'exclude_branches' => null);
				    
					$tree_menu = generateTestSpecTree($this->db, $this->args->testproject_id,
					                                  $this->args->testproject_name,
					                                  $gui->menuUrl, $filters, $options);
					
					$root_node = $tree_menu->rootnode;
					$children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
					$cookie_prefix = $this->args->feature;
				} else {
					$loader = $this->args->basehref . 'lib/ajax/gettprojectnodes.php?' .
					          "root_node={$this->args->testproject_id}&" .
					          "tcprefix=" . urlencode($tc_prefix .
					          $this->configuration->tc_cfg->glue_character);
					
					$tcase_qty = $this->testproject_mgr->count_testcases($this->args->testproject_id);
					
					$root_node = new stdClass();
					$root_node->href = "javascript:EP({$this->args->testproject_id})";
					$root_node->id = $this->args->testproject_id;
					$root_node->name = $this->args->testproject_name . " ($tcase_qty)";
					$root_node->testlink_node_type='testproject';
													
					$cookie_prefix = 'tproject_' . $root_node->id . "_";
				}
			break;
			
			case 'plan_add_mode':
				
				$cookie_prefix = "planaddtc_{$this->args->testproject_id}_{$this->args->user_id}_";
				
				if ($this->do_filtering) {
					$options = array('forPrinting' => NOT_FOR_PRINTING,
					                 'hideTestCases' => HIDE_TESTCASES,
					                 'tc_action_enabled' => ACTION_TESTCASE_DISABLE,
					                 'ignore_inactive_testcases' => IGNORE_INACTIVE_TESTCASES,
					                 'viewType' => 'testSpecTreeForTestPlan');
			
					$tree_menu = generateTestSpecTree($this->db,
					                                  $this->args->testproject_id,
					                                  $this->args->testproject_name,
					                                  $gui->menuUrl,
					                                  $filters,
					                                  $options);
					
					$root_node = $tree_menu->rootnode;
				    $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
				} else {
					$loader = $this->args->basehref . 'lib/ajax/gettprojectnodes.php?' .
					                    "root_node={$this->args->testproject_id}&show_tcases=0";
				
					$root_node = new stdClass();
					$root_node->href = "javascript:EP({$this->args->testproject_id})";
					$root_node->id = $this->args->testproject_id;
					$root_node->name = $this->args->testproject_name;
					$root_node->testlink_node_type = 'testproject';
				}
			break;
			
			case 'execution_mode':
			default:
				// No lazy loading here.
				// Filtering is always done in execution mode, no matter if user enters data or not,
				// since the user should usually never see the whole tree here.
				$additional_info = new stdClass();
				$filters->hide_testcases = false;
				$filters->show_testsuite_contents = $this->configuration->exec_cfg->show_testsuite_contents;
				$additional_info->useCounters = $this->configuration->exec_cfg->enable_tree_testcase_counters;
				
				$additional_info->useColours = new stdClass();
				$additional_info->useColours->testcases = 
					$this->configuration->exec_cfg->enable_tree_testcases_colouring;
				$additional_info->useColours->counters = 
					$this->configuration->exec_cfg->enable_tree_counters_colouring;
				$additional_info->testcases_colouring_by_selected_build =
					$this->configuration->exec_cfg->testcases_colouring_by_selected_build; 
					
				list($tree_menu, $testcases_to_show) = generateExecTree($this->db,
				                                                        $gui->menuUrl,
				                                                        $this->args->testproject_id,
				                                                        $this->args->testproject_name,
				                                                        $this->args->testplan_id,
				                                                        $this->args->testplan_name,
				                                                        $filters,
				                                                        $additional_info);
					
				$this->set_testcases_to_show($testcases_to_show);
				
				$root_node = $tree_menu->rootnode;
				$children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
				$cookie_prefix = 'exec_tplan_id_' . $this->args->testplan_id;
			break;
		}
		
		$gui->tree = $tree_menu;
		
		$gui->ajaxTree = new stdClass();
		$gui->ajaxTree->loader = $loader;
		$gui->ajaxTree->root_node = $root_node;
		$gui->ajaxTree->children = $children;
		$gui->ajaxTree->cookiePrefix = $cookie_prefix;
		$gui->ajaxTree->dragDrop = $drag_and_drop;
	} // end of method
	
	private function init_setting_refresh_tree_on_action() {

		$key = 'setting_refresh_tree_on_action';
		$hidden_key = 'hidden_setting_refresh_tree_on_action';
		$selection = 0;

		$this->settings[$key] = array();
		$this->settings[$key][$hidden_key] = false;

		// look where we can find the setting - POST, SESSION, config?
		if (isset($this->args->{$key})) {
			$selection = 1;
		} else if (isset($this->args->{$hidden_key})) {
			$selection = 0;
		} else if (isset($_SESSION[$key])) {
			$selection = $_SESSION[$key];
		} else {
			$spec_cfg = config_get('spec_cfg');
			$selection = ($spec_cfg->automatic_tree_refresh > 0) ? 1 : 0;
		}
		
		$this->settings[$key]['selected'] = $selection;
		$this->settings[$key][$hidden_key] = $selection;
		$_SESSION[$key] = $selection;		
	} // end of method

	private function init_setting_build() {

		$key = 'setting_build';
		if (is_null($this->testplan_mgr)) {
			$this->testplan_mgr = new testplan($this->db);
		}

		$tplan_id = $this->settings['setting_testplan']['selected'];

		// when in plan mode (assigning execution), we want all builds,
		// otherwise only those which are active and open
		$active = ($this->mode == 'plan_mode') ? null : testplan::GET_ACTIVE_BUILD;
		$open = ($this->mode == 'plan_mode') ? null : testplan::GET_OPEN_BUILD;
		
		$this->settings[$key]['items'] = $this->testplan_mgr->get_builds_for_html_options($tplan_id, $active, $open);
		$tplan_builds = array_keys($this->settings[$key]['items']);

		// BUGID 3406 - depending on mode, we need different labels for this selector on GUI
		$label = ($this->mode == 'plan_mode') ? 'assign_build' : 'exec_build';
		$this->settings[$key]['label'] = lang_get($label);
		
		// if no build has been chosen by user, select newest build by default
		$newest_build_id = $this->testplan_mgr->get_max_build_id($tplan_id, $active, $open);

		// BUGID 3726
		$session_key = $tplan_id . '_stored_setting_build';
		$session_selection = isset($_SESSION[$session_key]) ? $_SESSION[$session_key] : null;

		$this->args->{$key} = $this->args->{$key} > 0 ? $this->args->{$key} : $session_selection;

		if (!$this->args->{$key}) {
			$this->args->{$key} = $newest_build_id;
		}
		
		// only take build ID into account if it really is a build from this testplan
		$this->settings[$key]['selected'] = (in_array($this->args->{$key}, $tplan_builds)) ? 
		                                    $this->args->{$key} : $newest_build_id;

		// still no build selected? take first one from selection.
		if (!$this->settings[$key]['selected'] && sizeof($this->settings[$key]['items'])) {
			$this->settings[$key]['selected'] = end($tplan_builds);
		}

		$_SESSION[$session_key] = $this->settings[$key]['selected'];
	} // end of method

	private function init_setting_testplan() {

		if (is_null($this->testplan_mgr)) {
			$this->testplan_mgr = new testplan($this->db);
		}
		
		$key = 'setting_testplan';
		$testplans = $this->user->getAccessibleTestPlans($this->db, $this->args->testproject_id);

		if (isset($_SESSION['testplanID']) && $_SESSION['testplanID'] != $this->args->{$key}) {
			// testplan was changed, we need to reset all filters
			// --> they were chosen for another testplan, not this one!
			$this->args->reset_filters = true;

			// check if user is allowed to set chosen testplan before changing
			foreach ($testplans as $plan) {
				if ($plan['id'] == $this->args->{$key}) {
					setSessionTestPlan($plan);
				}
			}
		}

		// now load info from session
		$info = $this->testplan_mgr->get_by_id($_SESSION['testplanID']);
		$this->args->testplan_name = $info['name'];
		$this->args->testplan_id = $info['id'];
		$this->args->{$key} = $info['id'];
		$this->settings[$key]['selected'] = $info['id'];

		// Now get all selectable testplans for the user to display.
		// For execution, don't take testplans into selection which have no (active/open) builds!
		// For plan add mode, add every plan no matter if he has builds or not.
		foreach ($testplans as $plan) {
			$add_plan = $this->mode == 'plan_add_mode';
			if (!$add_plan) {
				$builds = $this->testplan_mgr->get_builds($plan['id'],
				                                          testplan::GET_ACTIVE_BUILD,
				                                          testplan::GET_OPEN_BUILD);
				$add_plan =  (is_array($builds) && count($builds));
			}
			
			if ($add_plan) {
				$this->settings[$key]['items'][$plan['id']] = $plan['name'];
			}
		}
	} // end of method

	private function init_setting_platform() {
		if (!$this->platform_mgr) {
			$this->platform_mgr = new tlPlatform($this->db);
		}
		$key = 'setting_platform';

		$this->settings[$key] = array('items' => null, 'selected' => $this->args->{$key});
		$testplan_id = $this->settings['setting_testplan']['selected'];

		$this->settings[$key]['items'] = $this->platform_mgr->getLinkedToTestplanAsMap($testplan_id);

		// BUGID 3726
		$session_key = $testplan_id . '_stored_setting_platform';
		$session_selection = isset($_SESSION[$session_key]) ? $_SESSION[$session_key] : null;

		if (!$this->settings[$key]['selected']) {
			$this->settings[$key]['selected'] = $session_selection;
		}

		if (!isset($this->settings[$key]['items']) || !is_array($this->settings[$key]['items'])) {
			$this->settings[$key] = false;
		} else if (isset($this->settings[$key]['items']) && is_array($this->settings[$key]['items']) && 
				   is_null($this->settings[$key]['selected'])) {
					// platforms exist, but none has been selected yet, so select first one
			$this->settings[$key]['selected'] =	key($this->settings[$key]['items']);
		}

		$_SESSION[$session_key] = $this->settings[$key]['selected'];
	} // end of method

	private function init_filter_tc_id() {
		$key = 'filter_tc_id';
		$selection = $this->args->{$key};
		$internal_id = null;
		
		if (!$this->testproject_mgr) {
			$this->testproject_mgr = new testproject($this->db);
		}
		if (!$this->tc_mgr) {
			$this->tc_mgr = new testcase($this->db);
		}
		
		$tc_prefix = $this->testproject_mgr->getTestCasePrefix($this->args->testproject_id);
		$tc_prefix .= $this->configuration->tc_cfg->glue_character;
		
		if (!$selection || $selection == $tc_prefix || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
			// we got the external ID here when filtering, but need the internal one
			$internal_id = $this->tc_mgr->getInternalID($selection);
		}
		
		$this->filters[$key] = array('selected' => $selection ? $selection : $tc_prefix);
		$this->active_filters[$key] = $internal_id;
	} // end of method
	
	private function init_filter_testcase_name() {
		$key = 'filter_testcase_name';
		$selection = $this->args->{$key};
		
		if (!$selection || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
		}
		
		$this->filters[$key] = array('selected' => $selection);
		$this->active_filters[$key] = $selection;
	} // end of method


	private function init_filter_toplevel_testsuite() {
		if (!$this->testproject_mgr) {
			$this->testproject_mgr = new testproject($this->db);
		}
		$key = 'filter_toplevel_testsuite';
			
		$first_level_suites = $this->testproject_mgr->get_first_level_test_suites($this->args->testproject_id,
		                                                                          'smarty_html_options');
		
		$selection = $this->args->{$key};
		if (!$selection || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
		}
		
		// this filter should only be visible if there are any top level testsuites
		$this->filters[$key] = null; // BUGID 3660
		if ($first_level_suites) {			
			$this->filters[$key] = array('items' => array(0 => ''),
			                             'selected' => $selection,
			                             'exclude_branches' => array());
		
			foreach ($first_level_suites as $suite_id => $suite_name) {
				$this->filters[$key]['items'][$suite_id] = $suite_name;
								
				if ($selection && $suite_id != $selection) {
					$this->filters[$key]['exclude_branches'][$suite_id] = 'exclude_me';
				}
			}
			
			// Important: This is the only case in which active_filters contains the items
			// which have to be deleted from tree, instead of the other way around.
			$this->active_filters[$key] = $this->filters[$key]['exclude_branches'];
		} else {
			$this->active_filters[$key] = null;
		}		
	} // end of method

	private function init_filter_keywords() {
		$key = 'filter_keywords';
		$type = 'filter_keywords_filter_type';
		$this->filters[$key] = false;
		$keywords = null;

		switch ($this->mode) {
			case 'edit_mode':
				// we need the keywords for the whole testproject
				if (!$this->testproject_mgr) {
					$this->testproject_mgr = new testproject($this->db);
				}
				$keywords = $this->testproject_mgr->get_keywords_map($this->args->testproject_id);
				break;

			default:
				// otherwise (not in edit mode), we want only keywords assigned to testplan
				if (!$this->testplan_mgr) {
					$this->testplan_mgr = new testplan($this->db);
				}
				$tplan_id = $this->settings['setting_testplan']['selected'];
				$keywords = $this->testplan_mgr->get_keywords_map($tplan_id, ' ORDER BY keyword ');
				break;
		}

		$selection = $this->args->{$key};
		$type_selection = $this->args->{$type};
		
		// are there any keywords?
		if (!is_null($keywords) && count($keywords)) {
			$this->filters[$key] = array();

			if (!$selection || !$type_selection || $this->args->reset_filters) {
				// default values for filter reset
				$selection = null;
				$type_selection = 'Or';
			} else {
				$this->do_filtering = true;
			}
			
			// data for the keywords themselves
			$this->filters[$key]['items'] = array($this->option_strings['any']) + $keywords;
			$this->filters[$key]['selected'] = $selection;
			$this->filters[$key]['size'] = min(count($this->filters[$key]['items']),
			                                   self::ADVANCED_FILTER_ITEM_QUANTITY);

			// additional data for the filter type (logical and/or)
			$this->filters[$key][$type] = array();
			$this->filters[$key][$type]['items'] = array('Or' => lang_get('logical_or'),
			                                                     'And' => lang_get('logical_and'));
			$this->filters[$key][$type]['selected'] = $type_selection;
		}
		
		// set the active value to filter
		// delete keyword filter if "any" (0) is part of the selection - regardless of filter mode
		if (is_array($this->filters[$key]['selected'])
		&& in_array(0, $this->filters[$key]['selected'])) {
			$this->active_filters[$key] = null;
		} else {
			$this->active_filters[$key] = $this->filters[$key]['selected'];
		}
		$this->active_filters[$type] = $selection ? $type_selection : null;
	} // end of method

	private function init_filter_priority() {
		// This is a special case of filter: the menu items don't get initialized here,
		// they are available as a global smarty variable. So the only thing to be managed
		// here is the selection by user.
		$key = 'filter_priority';

		// default value and filter reset
		$selection = $this->args->{$key};
		if (!$selection || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
		}

		$this->filters[$key] = array('selected' => $selection);
		$this->active_filters[$key] = $selection;
	} // end of method

	private function init_filter_execution_type() {
		if (!$this->tc_mgr) {
			$this->tc_mgr = new testcase($this->db);
		}
		$key = 'filter_execution_type';

		$selection = $this->args->{$key};
		// handle filter reset
		if (!$selection || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
		}
		
		$this->filters[$key] = array('items' => array(), 'selected' => $selection);

		// load available execution types
		$this->filters[$key]['items'] = $this->tc_mgr->get_execution_types();
		// add "any" string to these types at index 0 as default selection
		$this->filters[$key]['items'] = array(0 => $this->option_strings['any'])
		                                      + $this->filters[$key]['items'];
		
		$this->active_filters[$key] = $selection;
	} // end of method

	private function init_filter_assigned_user() {
		$key = 'filter_assigned_user';
		$unassigned_key = 'filter_assigned_user_include_unassigned';
		$tplan_id = $this->settings['setting_testplan']['selected'];

		// set selection to default (any), only change if value is sent by user and reset is not requested
		$selection = $this->args->{$key};
		if (!$selection || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
		}
		
		$all_testers = getTestersForHtmlOptions($this->db, $tplan_id, $this->args->testproject_id, null,
			                                    array(TL_USER_ANYBODY => $this->option_strings['any'],
			                                          TL_USER_NOBODY => $this->option_strings['none'],
			                                          TL_USER_SOMEBODY => $this->option_strings['somebody']),
			                                    'any');
		$visible_testers = $all_testers;
		
		// in execution mode the rights of the user have to be regarded
		if ($this->mode == 'execution_mode') {
			$role = $this->user->getEffectiveRole($this->db, $this->args->testproject_id, $tplan_id);
			
			$simple_tester_roles = array_flip($this->configuration->exec_cfg->simple_tester_roles);
			
			// check the user's rights to see what he may do
			$right_to_execute = $role->hasRight('testplan_execute');
			$right_to_manage = $role->hasRight('testplan_planning');
			
			$simple = false;
			if (isset($simple_tester_roles[$role->dbID]) || ($right_to_execute && !$right_to_manage)) {
				// user is only simple tester and may not see/execute everything
				$simple = true;
			}
			
			$view_mode = $simple ? $this->configuration->exec_cfg->view_mode->tester : 'all';
			
			if ($view_mode != 'all') {
				$visible_testers = (array)$this->user->getDisplayName();
				$selection = (array)$this->user->dbID;
			}
		}
		
		$this->filters[$key] = array('items' => $visible_testers,
		                             'selected' => $selection,
		                             $unassigned_key => $this->args->{$unassigned_key});
		
		// which value shall be passed to tree generation class?
		
		if ((is_array($selection) && in_array(TL_USER_ANYBODY, $selection))
		|| ($selection == TL_USER_ANYBODY)) {
			// delete user assignment filter if "any user" is part of the selection
			$this->active_filters[$key] = null;
			$this->active_filters[$unassigned_key] = 0;
		}
		
		if (is_array($selection)) {
			// get keys of the array as values
			$this->active_filters[$key] = array_flip($selection);
			foreach ($this->active_filters[$key] as $user_key => $user_value) {
				$this->active_filters[$key][$user_key] = $user_key;
			}
			$this->active_filters[$unassigned_key] = $this->filters[$key][$unassigned_key];
		}
	} // end of method

	private function init_filter_custom_fields() {
		
		$key = 'filter_custom_fields';
		if (!$this->exec_cf_mgr) {
			$this->exec_cf_mgr = new exec_cfield_mgr($this->db, $this->args->testproject_id);
		}

		$field_names = $this->exec_cf_mgr->field_names();
		$menu = $this->exec_cf_mgr->html_table_of_custom_field_inputs(self::CF_INPUT_SIZE);
		$selection = $this->exec_cf_mgr->get_set_values();
		
		// BUGID 3566: show/hide CF
		$ses_string = $this->mode . "_cf_filter_collapsed";
		$collapsed = isset($_SESSION[$ses_string]) ? $_SESSION[$ses_string] : 0;
		$collapsed = isset($_REQUEST['btn_toggle_cf']) ? !$collapsed : $collapsed;
		$_SESSION[$ses_string] = $collapsed;	
		$btn_label = $collapsed ? lang_get('btn_show_cf') : lang_get('btn_hide_cf');
				
		// asimon - 20100713
		// Fixed drag&drop error caused by this function. It always set $this->do_filtering
		// to true here because of the missing $selection in the following if condition.
		// This caused lazy loading and DD to be disabled later.		
		// see: http://www.teamst.org/forum/viewtopic.php?f=11&t=3354&sid=70498c57842247114cc7233d0f4e5c51
		if (!$selection || $this->args->reset_filters) {
			// handle filter reset button
			$selection = null;
		} else {
			$this->do_filtering = true;
		}
		
		if (isset($selection) && is_array($selection) && count($selection)) {
			
			// BUGID 3414:
			// Insert values chosen by user into html select menu by regex.
			// The $menu string contains lines of which each looks like this, e.g.:
			// <tr><td class="labelHolder">cflabel</td><td><input type="text" name="custom_field_0_1" 
			// id="custom_field_0_1" size="32"  maxlength="255" value=""></input></td></tr>
			
			// For each sent value, search the value="" part there and
			// then insert the real value into the empty "". Depending on type, of course.
			
			// TODO this stuff could maybe fit better into cfield_mgr class?
			
			// no magic number: 1 because of course only one replacement per value shall be done
			$limit = 1;
	
			foreach ($selection as $cf_id => $value) {
				
				$cf_html_name = $field_names[$cf_id]['cf_name'];
				
				switch($field_names[$cf_id]['verbose_type']){
	
					case 'list':
						// for single selection list, only one value has to be marked with "checked"
						$pattern = '/(.*name="' . $cf_html_name . '".*value="' . $value . '")(.*)/';
						// the numbers: 1 is the part before value, 2 after
						$replacement = '${1} selected ${2}';
						$menu = preg_replace($pattern, $replacement, $menu, $limit);
					break;

					case 'checkbox':
					case 'multiselection list':
						// this is similar to single selection list, but a bit more complicated
						// and needs an additional loop because the selection can have multiple values
						$value_arr = explode('|', $value);
						foreach ($value_arr as $single_value) {
							$pattern = '/(.*id="' . $cf_html_name . '".*value="' . 
							           $single_value . '")(.*)/';
							// the numbers: 1 is the part before value, 2 after
							$replacement = '${1} selected ${2}';
							$menu = preg_replace($pattern, $replacement, $menu, $limit);
						}
					break;
					
					
					case 'numeric':
					case 'float':
					case 'email':
					case 'string':
						// for these types, replacement is simple, only replace value=""
						$pattern = '/(.*name="' . $cf_html_name . '".*value=")(".*)/';
						// 1 and 2 stand for the first and second pair of braces in above line
						$replacement = '${1}' . $value . '${2}';
						$menu = preg_replace($pattern, $replacement, $menu, $limit);
					break;
					
					default:
					break;
				} // end of switch

			} // end of foreach
		}

		// BUGID 3566: show/hide CF
		$this->filters[$key] = array('items' => $menu, 
			                         'btn_label' => $btn_label, 
			                         'collapsed' => $collapsed);
		$this->active_filters[$key] = $selection;
	} // end of method

	private function init_filter_result() {
		$key = 'filter_result';
		$result_key = 'filter_result_result';
		$method_key = 'filter_result_method';
		$build_key = 'filter_result_build';
		
		if (is_null($this->testplan_mgr)) {
			$this->testplan_mgr = new testplan($this->db);
		}
		$tplan_id = $this->settings['setting_testplan']['selected'];

		$this->configuration->results = config_get('results');

		// determine, which config to load and use for filter methods - depends on mode!
		$cfg = ($this->mode == 'execution_mode') ? 
		       'execution_filter_methods' : 'execution_assignment_filter_methods';
		$this->configuration->filter_methods = config_get($cfg);

		// determin which filter method shall be selected by the JS function in template,
		// when only one build is selectable by the user
		$js_key_to_select = 0;
		if ($this->mode == 'execution_mode') {
			$js_key_to_select = $this->configuration->filter_methods['status_code']['current_build'];
		} else if ($this->mode == 'plan_mode') {
			$js_key_to_select = $this->configuration->filter_methods['status_code']['specific_build'];
		}
		
		// values selected by user
		$result_selection = $this->args->{$result_key};
		$method_selection = $this->args->{$method_key};
		$build_selection = $this->args->{$build_key};
		
		// default values
		$default_filter_method = $this->configuration->filter_methods['default_type'];
		$any_result_key = $this->configuration->results['status_code']['all'];
		$newest_build_id = $this->testplan_mgr->get_max_build_id($tplan_id, testplan::GET_ACTIVE_BUILD);
		
		if (is_null($result_selection) || is_null($method_selection) || $this->args->reset_filters) {
			// no selection yet or filter reset requested
			$result_selection = $any_result_key;
			$method_selection = $default_filter_method;
			$build_selection = $newest_build_id;
		} else {
			$this->do_filtering = true;
		}
		
		// init array structure
		$this->filters[$key] = array($result_key => array('items' => null,
		                                                  'selected' => $result_selection),
		                             $method_key => array('items' => array(),
		                                                  'selected' => $method_selection,
		                                                  'js_selection' => $js_key_to_select),
		                             $build_key => array('items' => null,
		                                                 'selected' => $build_selection));

		// init menu for result selection by function from exec.inc.php
		$this->filters[$key][$result_key]['items'] = createResultsMenu();
		$this->filters[$key][$result_key]['items'][$any_result_key] = $this->option_strings['any'];

		// init menu for filter method selection
		foreach ($this->configuration->filter_methods['status_code'] as $statusname => $statusshortcut) {
			$code = $this->configuration->filter_methods['status_code'][$statusname];
			$this->filters[$key][$method_key]['items'][$code] =
				lang_get($this->configuration->filter_methods['status_label'][$statusname]);
		}
		
		// init menu for build selection
		$this->filters[$key][$build_key]['items'] =
			$this->testplan_mgr->get_builds_for_html_options($tplan_id, testplan::GET_ACTIVE_BUILD);
		
		// if "any" is selected, nullify the active filters
		if ((is_array($result_selection) && in_array($any_result_key, $result_selection))
		|| $result_selection == $any_result_key) {
			$this->active_filters[$result_key] = null;
			$this->active_filters[$method_key] = null;
			$this->active_filters[$build_key] = null;
		} else {
			$this->active_filters[$result_key] = $result_selection;
			$this->active_filters[$method_key] = $method_selection;
			$this->active_filters[$build_key] = $build_selection;
		}
	} // end of method
} // end of class tlTestCaseFilterControl
?>
Back to Top