From 8ee1e41a3d6a1c11e6e93c086210864012364013 Mon Sep 17 00:00:00 2001 From: brettp Date: Thu, 4 Feb 2010 04:36:34 +0000 Subject: Refs #1200: Changed logic in elgg_get_entity_type_subtype_where_sql() to return FALSE if there are no valid subtypes passed. Ignores all invalid subtypes. Added (partial) tests for elgg_get_entities() types and subtypes. git-svn-id: http://code.elgg.org/elgg/trunk@3901 36083f99-b078-4883-b0ff-0f9b5a30f544 --- engine/lib/elgglib.php | 10 + engine/lib/entities.php | 98 ++-- engine/tests/api/entity_getter_functions.php | 707 +++++++++++++++++++++++++++ 3 files changed, 786 insertions(+), 29 deletions(-) create mode 100644 engine/tests/api/entity_getter_functions.php (limited to 'engine') diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index ce383ed8c..357141baa 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -2686,6 +2686,15 @@ function elgg_boot() { elgg_view_register_simplecache('js/initialise_elgg'); } +/** + * Runs unit tests for the API. + */ +function elgg_api_test($hook, $type, $value, $params) { + global $CONFIG; + $value[] = $CONFIG->path . 'engine/tests/api/entity_getter_functions.php'; + return $value; +} + /** * Some useful constant definitions */ @@ -2697,3 +2706,4 @@ define('ACCESS_FRIENDS',-2); register_elgg_event_handler('init','system','elgg_init'); register_elgg_event_handler('boot','system','elgg_boot',1000); +register_plugin_hook('unit_test', 'system', 'elgg_api_test'); \ No newline at end of file diff --git a/engine/lib/entities.php b/engine/lib/entities.php index 7bc1d4cda..930772943 100644 --- a/engine/lib/entities.php +++ b/engine/lib/entities.php @@ -1954,67 +1954,106 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair $subtypes = array($subtypes); } - // subtypes are based upon types, so we need to look at each - // type individually to get the right subtype id. + // decrementer for valid types. Return FALSE if no valid types + $valid_types_count = count($types); + $valid_subtypes_count = 0; + // remove invalid types to get an accurate count of + // valid types for the invalid subtype detection to use + // below. + // also grab the count of ALL subtypes on valid types to decrement later on + // and check against. + // + // yes this is duplicating a foreach on $types. foreach ($types as $type) { if (!in_array($type, $valid_types)) { - return FALSE; + $valid_types_count--; + unset ($types[array_search($type, $types)]); + } else { + // do the checking (and decrementing) in the subtype section. + $valid_subtypes_count += count($subtypes); } + } + // return false if nothing is valid. + if (!$valid_types_count) { + return FALSE; + } + + // subtypes are based upon types, so we need to look at each + // type individually to get the right subtype id. + foreach ($types as $type) { $subtype_ids = array(); if ($subtypes) { - // if there is only one subtype and it is is not 0 and it invalid return false. - // if the type is 0 or null, let it through. - // if the type is set but the subtype is FALSE, return false. - if (count($subtypes) === 1) { - if ($subtypes[0] && !get_subtype_id($type, $subtypes[0])) { - return FALSE; - } - } - // subtypes can be NULL or '' or 0, which means "no subtype" foreach ($subtypes as $subtype) { // if a subtype is sent that doesn't exist if (0 === $subtype || $subtype_id = get_subtype_id($type, $subtype)) { $subtype_ids[] = (0 === $subtype) ? 0 : $subtype_id; } else { - // @todo should return false. - //return FALSE; - + $valid_subtypes_count--; elgg_log("Type-subtype $type:$subtype' does not exist!", 'WARNING'); + // return false if we're all invalid subtypes in the only valid type continue; } } + + if ($valid_subtypes_count <= 0) { + return FALSE; + } } - } - //if ($subtype_ids_str = implode(',', $subtype_ids)) { - if (is_array($subtype_ids) && count($subtype_ids)) { - $subtype_ids_str = implode(',', $subtype_ids); - $wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))"; - } else { - $wheres[] = "({$table}.type = '$type')"; - } + if (is_array($subtype_ids) && count($subtype_ids)) { + $subtype_ids_str = implode(',', $subtype_ids); + $wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))"; + } else { + $wheres[] = "({$table}.type = '$type')"; + } + } } else { // using type/subtype pairs + $valid_pairs_count = count($pairs); + $valid_pairs_subtypes_count = 0; + + // same deal as above--we need to know how many valid types + // and subtypes we have before hitting the subtype section. + // also normalize the subtypes into arrays here. foreach ($pairs as $paired_type => $paired_subtypes) { - if ($paired_subtypes && !is_array($paired_subtypes)) { - $paired_subtypes = array($paired_subtypes); + if (!in_array($paired_type, $valid_types)) { + $valid_pairs_count--; + unset ($pairs[array_search($paired_type, $pairs)]); + } else { + if ($paired_subtypes && !is_array($paired_subtypes)) { + $pairs[$paired_type] = array($paired_subtypes); + } + $valid_pairs_subtypes_count += count($paired_subtypes); } + } - if (is_array($paired_subtypes) && count($paired_subtypes)) { + if ($valid_pairs_count <= 0) { + return FALSE; + } + foreach ($pairs as $paired_type => $paired_subtypes) { + // this will always be an array because of line 2027, right? + // no...some overly clever person can say pair => array('object' => null) + if (is_array($paired_subtypes)) { $paired_subtype_ids = array(); foreach ($paired_subtypes as $paired_subtype) { if ($paired_subtype && ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) { $paired_subtype_ids[] = $paired_subtype_id; } else { - // @todo should return false. - //return FALSE; - elgg_log("Paired type-subtype $paired_type:$paired_subtype' does not exist!", 'WARNING'); + $valid_pairs_subtypes_count--; + elgg_log("Type-subtype $paired_type:$paired_subtype' does not exist!", 'WARNING'); + // return false if we're all invalid subtypes in the only valid type continue; } } + // return false if there are no valid subtypes. + if ($valid_pairs_subtypes_count <= 0) { + return FALSE; + } + + if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) { $wheres[] = "({$table}.type = '$paired_type' AND {$table}.subtype IN ($paired_subtype_ids_str))"; } @@ -2034,6 +2073,7 @@ function elgg_get_entity_type_subtype_where_sql($table, $types, $subtypes, $pair } + /** * Returns SQL for owner and containers. * diff --git a/engine/tests/api/entity_getter_functions.php b/engine/tests/api/entity_getter_functions.php new file mode 100644 index 000000000..2324374f9 --- /dev/null +++ b/engine/tests/api/entity_getter_functions.php @@ -0,0 +1,707 @@ +entities = array(); + $this->subtypes = array( + 'object' => array(), + 'user' => array(), + 'group' => array(), + //'site' => array() + ); + + // sites are a bit wonky. Don't use them just now. + $this->types = array('object', 'user', 'group'); + + // create some fun objects to play with. + // one with no subtype +// $e = new ElggObject(); +// $e->subtype = $subtype; +// $e->save(); + + // and 5 with random subtypes + for ($i=0; $i<5; $i++) { + $subtype = "test_object_subtype_" . rand(); + $e = new ElggObject(); + $e->subtype = $subtype; + $e->save(); + $this->entities[] = $e; + $this->subtypes['object'][] = $subtype; + } + + // and users + for ($i=0; $i<5; $i++) { + $subtype = "test_user_subtype_" . rand(); + $e = new ElggUser(); + $e->username = "test_user_" . rand(); + $e->subtype = $subtype; + $e->save(); + $this->entities[] = $e; + $this->subtypes['user'][] = $subtype; + } + + // and groups + for ($i=0; $i<5; $i++) { + $subtype = "test_group_subtype_" . rand(); + $e = new ElggGroup(); + $e->subtype = $subtype; + $e->save(); + $this->entities[] = $e; + $this->subtypes['group'][] = $subtype; + } + } + + /** + * Called after each test method. + */ + public function tearDown() { + //$this->swallowErrors(); + + foreach ($this->entities as $e) { + $e->delete(); + unset($this->entities); + } + } + + /** + * Called after each test object. + */ + public function __destruct() { + parent::__destruct(); + } + + + /************************************************* + * Helpers for getting random types and subtypes * + *************************************************/ + + /** + * Get a random valid subtype + * + * @param int $num + * @return array + */ + public function getRandomValidTypes($num = 1) { + $r = array(); + + for ($i=1; $i<=$num; $i++) { + do { + $t = $this->types[array_rand($this->types)]; + } while (in_array($t, $r) && count($r) < count($this->types)); + + $r[] = $t; + } + + shuffle($r); + return $r; + } + + + /** + * Get a random valid subtype (that we just created) + * + * @param array $type Type of objects to return valid subtypes for. + * @param int $num of subtypes. + * + * @return array + */ + public function getRandomValidSubtypes(array $types, $num = 1) { + $r = array(); + + for ($i=1; $i<=$num; $i++) { + do { + // make sure at least one subtype of each type is returned. + if ($i-1 < count($types)) { + $type = $types[$i-1]; + } else { + $type = $types[array_rand($types)]; + } + + $k = array_rand($this->subtypes[$type]); + $t = $this->subtypes[$type][$k]; + } while (in_array($t, $r)); + + $r[] = $t; + } + + shuffle($r); + return $r; + } + + /** + * Return an array of invalid strings for type or subtypes. + * + * @param int $num + * @return arr + */ + public function getRandomInvalids($num = 1) { + $r = array(); + + for ($i=1; $i<=$num; $i++) { + $r[] = 'random_invalid_' . rand(); + } + + return $r; + } + + /** + * + * @param unknown_type $num + * @return unknown_type + */ + public function getRandomMixedTypes($num = 2) { + $have_valid = $have_invalid = false; + $r = array(); + + // need at least one of each type. + $valid_n = rand(1, $num-1); + $r = array_merge($r, $this->getRandomValidTypes($valid_n)); + $r = array_merge($r, $this->getRandomInvalids($num - $valid_n)); + + shuffle($r); + return $r; + } + + /** + * Get random mix of valid and invalid subtypes for types given. + * + * @param array $types + * @param unknown_type $num + * @return unknown_type + */ + public function getRandomMixedSubtypes(array $types, $num = 2) { + $types_c = count($types); + $r = array(); + + // this can be more efficient but I'm very sleepy... + + // want at least one of valid and invalid of each type sent. + for ($i=0; $i < $types_c && $num > 0; $i++) { + // make sure we have a valid and invalid for each type + if (true) { + $type = $types[$i]; + $r = array_merge($r, $this->getRandomValidSubtypes(array($type), 1)); + $r = array_merge($r, $this->getRandomInvalids(1)); + + $num -= 2; + } + } + + if ($num > 0) { + $valid_n = rand(1, $num); + $r = array_merge($r, $this->getRandomValidSubtypes($types, $valid_n)); + $r = array_merge($r, $this->getRandomInvalids($num - $valid_n)); + } + + //shuffle($r); + return $r; + } + + + /*********************************** + * TYPE TESTS + *********************************** + * check for getting a valid type in all ways we can. + * note that these aren't wonderful tests as there will be + * existing entities so we can't test against the ones we just created. + * So these just test that some are returned and match the type(s) requested. + * It could definitely be the case that the first 10 entities retrieved are all + * objects. Maybe best to limit to 4 and group by type. + */ + public function testElggApiGettersValidTypeUsingType() { + $type_arr = $this->getRandomValidTypes(); + $type = $type_arr[0]; + $options = array( + 'type' => $type, + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + // should only ever return one object because of group by + $this->assertIdentical(count($es), 1); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $type_arr)); + } + } + + public function testElggApiGettersValidTypeUsingTypesAsString() { + $type_arr = $this->getRandomValidTypes(); + $type = $type_arr[0]; + $options = array( + 'types' => $type, + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + // should only ever return one object because of group by + $this->assertIdentical(count($es), 1); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $type_arr)); + } + } + + public function testElggApiGettersValidTypeUsingTypesAsArray() { + $type_arr = $this->getRandomValidTypes(); + $type = $type_arr[0]; + $options = array( + 'types' => $type_arr, + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + // should only ever return one object because of group by + $this->assertIdentical(count($es), 1); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $type_arr)); + } + } + + public function testElggApiGettersValidTypeUsingTypesAsArrayPlural() { + $num = 2; + $types = $this->getRandomValidTypes($num); + $options = array( + 'types' => $types, + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + // one of object and one of group + $this->assertIdentical(count($es), $num); + + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $types)); + } + } + + /* + * Test invalid types. + */ + public function testElggApiGettersInvalidTypeUsingType() { + $type_arr = $this->getRandomInvalids(); + $type = $type_arr[0]; + + $options = array( + 'type' => $type, + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertFalse($es); + } + + public function testElggApiGettersInvalidTypeUsingTypesAsString() { + $type_arr = $this->getRandomInvalids(); + $type = $type_arr[0]; + + $options = array( + 'types' => $type, + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertIdentical($es, FALSE); + } + + public function testElggApiGettersInvalidTypeUsingTypesAsArray() { + $type_arr = $this->getRandomInvalids(); + + $options = array( + 'types' => $type_arr, + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertIdentical($es, FALSE); + } + + public function testElggApiGettersInvalidTypeUsingTypesAsArrayPlural() { + $type_arr = $this->getRandomInvalids(2); + + $options = array( + 'types' => $type_arr, + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertIdentical($es, FALSE); + } + + + /* + * Test mixed valid and invalid types. + */ + + + public function testElggApiGettersValidAndInvalidTypes() { + //@todo replace this with $this->getRandomMixedTypes(). + $t = $this->getRandomValidTypes(); + $valid = $t[0]; + + $t = $this->getRandomInvalids(); + $invalid = $t[0]; + $options = array( + 'types' => array($invalid, $valid), + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + // should only ever return one object because of group by + $this->assertIdentical(count($es), 1); + $this->assertIdentical($es[0]->getType(), $valid); + } + + public function testElggApiGettersValidAndInvalidTypesPlural() { + $valid_num = 2; + $invalid_num = 3; + $valid = $this->getRandomValidTypes($valid_num); + $invalid = $this->getRandomInvalids($invalid_num); + + $types = array(); + foreach ($valid as $t) { + $types[] = $t; + } + + foreach ($invalid as $t) { + $types[] = $t; + } + + shuffle($types); + $options = array( + 'types' => $types, + 'group_by' => 'e.type' + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + // should only ever return one object because of group by + $this->assertIdentical(count($es), $valid_num); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $valid)); + } + } + + + + /************************************** + * SUBTYPE TESTS + ************************************** + * + * Here we can use the subtypes we created to test more finely. + * Subtypes are bound to types, so we must pass a type. + * This is where the fun logic starts. + */ + + public function testElggApiGettersValidSubtypeUsingSubtypeSingularType() { + $types = $this->getRandomValidTypes(); + $subtypes = $this->getRandomValidSubtypes($types); + $subtype = $subtypes[0]; + + $options = array( + 'types' => $types, + 'subtype' => $subtype + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + $this->assertIdentical(count($es), 1); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $types)); + $this->assertTrue(in_array($e->getSubtype(), $subtypes)); + } + } + + public function testElggApiGettersValidSubtypeUsingSubtypesAsStringSingularType() { + $types = $this->getRandomValidTypes(); + $subtypes = $this->getRandomValidSubtypes($types); + $subtype = $subtypes[0]; + + $options = array( + 'types' => $types, + 'subtypes' => $subtype + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + $this->assertIdentical(count($es), 1); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $types)); + $this->assertTrue(in_array($e->getSubtype(), $subtypes)); + } + } + + public function testElggApiGettersValidSubtypeUsingSubtypesAsArraySingularType() { + $types = $this->getRandomValidTypes(); + $subtypes = $this->getRandomValidSubtypes($types); + + $options = array( + 'types' => $types, + 'subtypes' => $subtypes + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + $this->assertIdentical(count($es), 1); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $types)); + $this->assertTrue(in_array($e->getSubtype(), $subtypes)); + } + } + + public function testElggApiGettersValidSubtypeUsingPluralSubtypesSingularType() { + $subtype_num = 2; + $types = $this->getRandomValidTypes(); + $subtypes = $this->getRandomValidSubtypes($types, $subtype_num); + + $options = array( + 'types' => $types, + 'subtypes' => $subtypes + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + $this->assertIdentical(count($es), $subtype_num); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $types)); + $this->assertTrue(in_array($e->getSubtype(), $subtypes)); + } + } + + + /* + Because we're looking for type OR subtype (sorta) + it's possible that we've pulled in entities that aren't + of the subtype we've requested. + THIS COMBINATION MAKES LITTLE SENSE. + There is no mechanism in elgg to retrieve a subtype without a type, so + this combo gets trimmed down to only including subtypes that are valid to + each particular type. + FOR THE LOVE OF ALL GOOD PLEASE JUST USE TYPE_SUBTYPE_PAIRS! + */ + public function testElggApiGettersValidSubtypeUsingPluralSubtypesPluralTypes() { + $type_num = 2; + $subtype_num = 2; + $types = $this->getRandomValidTypes($type_num); + $subtypes = $this->getRandomValidSubtypes($types, $subtype_num); + + $options = array( + 'types' => $types, + 'subtypes' => $subtypes + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + // this will unset all invalid subtypes for each type that that only + // one entity exists of each. + $this->assertIdentical(count($es), $subtype_num); + foreach ($es as $e) { + // entities must at least be in the type. + $this->assertTrue(in_array($e->getType(), $types)); + + // test that this is a valid subtype for the entity type. + $this->assertTrue(in_array($e->getSubtype(), $this->subtypes[$e->getType()])); + } + } + + public function testElggApiGettersInvalidSubtypeUsingPluralSubtypesPluralTypes() { + $type_num = 2; + $subtype_num = 2; + $types = $this->getRandomValidTypes($type_num); + $subtypes = $this->getRandomInvalids($subtype_num); + + $options = array( + 'types' => $types, + 'subtypes' => $subtypes + ); + + $es = elgg_get_entities($options); + $this->assertFalse($es); + } + + /* + * This combination will remove all invalid subtypes for this type. + */ + public function testElggApiGettersValidSubtypeUsingPluralMixedSubtypesSingleType() { + $type_num = 1; + $subtype_num = 2; + $types = $this->getRandomValidTypes($type_num); + + + //@todo replace this with $this->getRandomMixedSubtypes() + // we want this to return an invalid subtype for the returned type. + $subtype_types = $types; + $i = 1; + while ($i <= $subtype_num) { + $type = $this->types[$i-1]; + + if (!in_array($type, $subtype_types)) { + $subtype_types[] = $type; + } + $i++; + } + + $subtypes = $this->getRandomValidSubtypes($subtype_types, $type_num); + + $options = array( + 'types' => $types, + 'subtypes' => $subtypes + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + // this will unset all invalid subtypes for each type that that only + // one entity exists of each. + $this->assertIdentical(count($es), $type_num); + foreach ($es as $e) { + // entities must at least be in the type. + $this->assertTrue(in_array($e->getType(), $types)); + + // test that this is a valid subtype for the entity type. + $this->assertTrue(in_array($e->getSubtype(), $this->subtypes[$e->getType()])); + } + } + + + /*************************** + * TYPE_SUBTYPE_PAIRS + ***************************/ + + + public function testElggApiGettersTSPValidTypeValidSubtype() { + $type_num = 1; + $subtype_num = 1; + $types = $this->getRandomValidTypes($type_num); + $subtypes = $this->getRandomValidSubtypes($types, $subtype_num); + + $pair = array($types[0] => $subtypes[0]); + + $options = array( + 'type_subtype_pairs' => $pair + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + $this->assertIdentical(count($es), $type_num); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $types)); + $this->assertTrue(in_array($e->getSubtype(), $subtypes)); + } + } + + public function testElggApiGettersTSPValidTypeValidPluralSubtype() { + $type_num = 1; + $subtype_num = 3; + $types = $this->getRandomValidTypes($type_num); + $subtypes = $this->getRandomValidSubtypes($types, $subtype_num); + + $pair = array($types[0] => $subtypes); + + $options = array( + 'type_subtype_pairs' => $pair + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + $this->assertIdentical(count($es), $subtype_num); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $types)); + $this->assertTrue(in_array($e->getSubtype(), $subtypes)); + } + } + + public function testElggApiGettersTSPValidTypeInvalidSubtype() { + $type_num = 1; + $subtype_num = 1; + $types = $this->getRandomValidTypes($type_num); + $subtypes = $this->getRandomInvalids($subtype_num); + + $pair = array($types[0] => $subtypes[0]); + + $options = array( + 'type_subtype_pairs' => $pair + ); + + $es = elgg_get_entities($options); + $this->assertFalse($es); + } + + public function testElggApiGettersTSPValidTypeInvalidPluralSubtypes() { + $type_num = 1; + $subtype_num = 2; + $types = $this->getRandomValidTypes($type_num); + $subtypes = $this->getRandomInvalids($subtype_num); + + $pair = array($types[0] => $subtypes); + + $options = array( + 'type_subtype_pairs' => $pair + ); + + $es = elgg_get_entities($options); + $this->assertFalse($es); + } + + public function testElggApiGettersTSPValidTypeMixedPluralSubtype() { + $type_num = 1; + $valid_subtype_num = 2; + $types = $this->getRandomValidTypes($type_num); + $valid = $this->getRandomValidSubtypes($types, $valid_subtype_num); + $invalid = $this->getRandomInvalids(); + + $subtypes = array_merge($valid, $invalid); + shuffle($subtypes); + + $pair = array($types[0] => $subtypes); + + $options = array( + 'type_subtype_pairs' => $pair + ); + + $es = elgg_get_entities($options); + $this->assertIsA($es, 'array'); + + $this->assertIdentical(count($es), $valid_subtype_num); + foreach ($es as $e) { + $this->assertTrue(in_array($e->getType(), $types)); + $this->assertTrue(in_array($e->getSubtype(), $valid)); + } + } + + +} -- cgit v1.2.3