Merge branch 'fix_hierarchy_facets' into develop

This commit is contained in:
vnmedeiros 2019-05-02 15:30:19 -03:00
commit 1534017483
2 changed files with 637 additions and 47 deletions

View File

@ -897,7 +897,7 @@ class Metadata extends Repository {
* @type mixed $collection_id The collection ID you want to consider or null for all collections. If a collectoin is set
* then only values applied to items in this collection will be returned
*
* @type int $number The number of values to return (for pagination). Default 0 (unlimited)
* @type int $number The number of values to return (for pagination). Default empty (unlimited)
*
* @type int $offset The offset (for pagination). Default 0
*
@ -1014,39 +1014,84 @@ class Metadata extends Repository {
if ( $metadatum_type === 'Tainacan\Metadata_Types\Taxonomy' ) {
if ($items_query) {
$base_query = $wpdb->prepare("FROM $wpdb->term_relationships tr
INNER JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
INNER JOIN $wpdb->terms t ON tt.term_id = t.term_id
WHERE
tt.parent = %d AND
tr.object_id IN ($items_query) AND
tt.taxonomy = %s
$search_q
ORDER BY t.name ASC
",
$args['parent_id'],
$taxonomy_slug
);
$check_hierarchy_q = $wpdb->prepare("SELECT term_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s AND parent > 0 LIMIT 1", $taxonomy_slug);
$has_hierarchy = ! is_null($wpdb->get_var($check_hierarchy_q));
if ( ! $has_hierarchy ) {
$base_query = $wpdb->prepare("FROM $wpdb->term_relationships tr
INNER JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
INNER JOIN $wpdb->terms t ON tt.term_id = t.term_id
WHERE
tt.parent = %d AND
tr.object_id IN ($items_query) AND
tt.taxonomy = %s
$search_q
ORDER BY t.name ASC
",
$args['parent_id'],
$taxonomy_slug
);
$query = "SELECT DISTINCT t.name, t.term_id, tt.term_taxonomy_id, tt.parent $base_query $pagination";
$total_query = "SELECT COUNT(DISTINCT tt.term_taxonomy_id) $base_query";
$total = $wpdb->get_var($total_query);
$results = $wpdb->get_results($query);
} else {
$base_query = $wpdb->prepare("
SELECT DISTINCT t.term_id, t.name, tt.parent, coalesce(tr.term_taxonomy_id, 0) as have_items
FROM
$wpdb->terms t INNER JOIN $wpdb->term_taxonomy tt ON t.term_id = tt.term_id
LEFT JOIN $wpdb->term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id AND tr.object_id IN ($items_query)
WHERE tt.taxonomy = %s ORDER BY t.name ASC", $taxonomy_slug
);
$all_hierarchy = $wpdb->get_results($base_query);
if (empty($search)) {
$results = $this->_process_terms_tree($all_hierarchy, $args['parent_id'], 'parent');
} else {
$results = $this->_process_terms_tree($all_hierarchy, $search, 'name');
}
$total = count($results);
if ( $args['offset'] >= 0 && $args['number'] >= 1 ) {
$results = array_slice($results, (int) $args['offset'], (int) $args['number']);
}
}
} else {
$parent_q = $wpdb->prepare("AND tt.parent = %d", $args['parent_id']);
if ($search_q) {
$parent_q = '';
}
$base_query = $wpdb->prepare("FROM $wpdb->term_taxonomy tt
INNER JOIN $wpdb->terms t ON tt.term_id = t.term_id
WHERE
tt.parent = %d AND
tt.taxonomy = %s
WHERE 1=1
$parent_q
AND tt.taxonomy = %s
$search_q
ORDER BY t.name ASC
",
$args['parent_id'],
$taxonomy_slug
);
$query = "SELECT DISTINCT t.name, t.term_id, tt.term_taxonomy_id, tt.parent $base_query $pagination";
$total_query = "SELECT COUNT(DISTINCT tt.term_taxonomy_id) $base_query";
$total = $wpdb->get_var($total_query);
$results = $wpdb->get_results($query);
}
$query = "SELECT DISTINCT t.name, t.term_id, tt.term_taxonomy_id, tt.parent $base_query $pagination";
$total_query = "SELECT COUNT(DISTINCT tt.term_taxonomy_id) $base_query";
$results = $wpdb->get_results($query);
// add selected to the result
if ( !empty($args['include']) ) {
@ -1071,7 +1116,7 @@ class Metadata extends Repository {
}
}
$total = $wpdb->get_var($total_query);
$number = is_integer($args['number']) && $args['number'] >=1 ? $args['number'] : $total;
if( $number < 1){
$pages = 1;
@ -1197,6 +1242,80 @@ class Metadata extends Repository {
];
}
/**
* This method processes the result of the query for all terms in a taxonomy done in get_all_metadatum_values()
* It efficiently runs through all the terms and checks what terms with a given $parent have items in itself or any of
* its descendants, keeping the order they originally came.
*
* It returns an array with the term objects with the given $parent that have items considering items in its descendants. The objects are
* in the same format they came, as expected by the rest of the method.
*
* This method is public only for tests purposes, it should not be used anywhere else
*/
public function _process_terms_tree($tree, $search_value, $search_type='parent') {
$h_map = [];
$results = [];
foreach ( $tree as $h ) {
if ( $h->have_items > 0 || ( isset($h_map[$h->term_id]) && $h_map[$h->term_id]->have_items > 0 ) ) {
$h->have_items = 1;
$h_map[$h->term_id] = $h;
if(($search_type == 'parent' && $h->parent == $search_value) ||
($search_type == 'name' && $h->have_items > 0 && strpos(strtolower($h->name), strtolower($search_value)) !== false)) {
$results[$h->term_id] = $h;
}
$_parent = $h->parent;
if ( $h->parent > 0 && !isset($h_map[$_parent]) ) {
$h_map[$_parent] = (object)['have_items' => 1];
}
while( isset($h_map[$_parent]) && $h_map[$_parent]->have_items != 1 ) {
$h_map[$_parent]->have_items = 1;
if ( isset($h_map[$_parent]->parent) ) {
if(($search_type == 'parent' && $h->parent == $search_value) ||
($search_type == 'name' && $h->have_items > 0 && strpos(strtolower($h->name), strtolower($search_value)) !== false)) {
$results[$h_map[$_parent]->term_id] = $h_map[$_parent];
}
$_parent = $h_map[$_parent]->parent;
} else {
$_parent = 0;
}
}
} else {
$h_map[$h->term_id] = $h;
if ( $h->parent > 0 && !isset($h_map[$h->parent]) ) {
$h_map[$h->parent] = (object)['have_items' => $h->have_items];
}
if(($search_type == 'parent' && $h->parent == $search_value) ||
($search_type == 'name' && $h->have_items > 0 && strpos(strtolower($h->name), strtolower($search_value)) !== false)) {
$results[$h->term_id] = $h;
}
}
}
// Results have all terms with wanted parent. Now we unset those who dont have items
// and set it back to incremental keys]
// we could have sent to $results only those with items, but doing that we would not preserve their order
$results = array_reduce($results, function ($return, $el) {
if ($el->have_items > 0) {
$return[] = $el;
}
return $return;
}, []);
return $results;
}
/**
* Stores the value of the taxonomy_id option to use on update_taxonomy_metadatum method.

View File

@ -40,14 +40,27 @@ class Facets extends TAINACAN_UnitApiTestCase {
$this->collection2 = $collection2;
$taxonomy = $this->tainacan_entity_factory->create_entity(
'taxonomy',
array(
'name' => 'genero',
'description' => 'tipos de musica',
'allow_insert' => 'yes',
'taxonomy',
array(
'name' => 'genero',
'description' => 'tipos de musica',
'allow_insert' => 'yes',
'status' => 'publish'
),
true
),
true
);
$this->taxonomy = $taxonomy;
$taxonomy2 = $this->tainacan_entity_factory->create_entity(
'taxonomy',
array(
'name' => 'genero2',
'description' => 'tipos de musica2',
'allow_insert' => 'yes',
'status' => 'publish'
),
true
);
$this->taxonomy = $taxonomy;
@ -113,6 +126,106 @@ class Facets extends TAINACAN_UnitApiTestCase {
true
);
$term2_root = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'Root'
),
true
);
$term2_root2 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'Root2'
),
true
);
$term2_root_c1 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'Children',
'parent' => $term2_root->get_id()
),
true
);
$term2_root_c2 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'Children2',
'parent' => $term2_root->get_id()
),
true
);
$term2_root_gc1 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'GChildren',
'parent' => $term2_root_c2->get_id()
),
true
);
$term2_root_gc2 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'GChildren2',
'parent' => $term2_root_c2->get_id()
),
true
);
$term2_root2_c1 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'Children',
'parent' => $term2_root2->get_id()
),
true
);
$term2_root2_c2 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'Children2',
'parent' => $term2_root2->get_id()
),
true
);
$this->term2_root2_c2 = $term2_root2_c2;
$term2_root2_gc1 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'GChildren',
'parent' => $term2_root2_c2->get_id()
),
true
);
$term2_root2_gc2 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'GChildren2',
'parent' => $term2_root2_c2->get_id()
),
true
);
$this->term2_root2_gc2 = $term2_root2_gc2;
$term2_root2_ggc1 = $this->tainacan_entity_factory->create_entity(
'term',
array(
'taxonomy' => $taxonomy2->get_db_identifier(),
'name' => 'GGChildren1',
'parent' => $term2_root2_gc2->get_id()
),
true
);
$meta_1_tax = $this->tainacan_entity_factory->create_entity(
'metadatum',
array(
@ -150,6 +263,24 @@ class Facets extends TAINACAN_UnitApiTestCase {
$this->meta_2_tax = $meta_2_tax;
$meta_3_tax = $this->tainacan_entity_factory->create_entity(
'metadatum',
array(
'name' => 'test taxonomy',
'status' => 'publish',
'collection' => $collection2,
'metadata_type' => 'Tainacan\Metadata_Types\Taxonomy',
'metadata_type_options' => [
'allow_new_terms' => true,
'taxonomy_id' => $taxonomy2->get_id()
],
'multiple' => 'yes'
),
true
);
$this->meta_3_tax = $meta_3_tax;
$metadatum_text = $this->tainacan_entity_factory->create_entity(
'metadatum',
array(
@ -239,8 +370,16 @@ class Facets extends TAINACAN_UnitApiTestCase {
$this->tainacan_item_metadata_factory->create_item_metadata($item, $meta_2_tax, [$term_2_c->get_id()]);
}
// hierarchical taxonomy
if ($i <= 10) {
$this->tainacan_item_metadata_factory->create_item_metadata($item, $meta_3_tax, [$term2_root_c1->get_id()]);
} elseif($i <= 20) {
$this->tainacan_item_metadata_factory->create_item_metadata($item, $meta_3_tax, [$term2_root2_c1->get_id()]);
} elseif($i <= 30) {
$this->tainacan_item_metadata_factory->create_item_metadata($item, $meta_3_tax, [$term2_root2_gc2->get_id()]);
} elseif($i <= 40) {
$this->tainacan_item_metadata_factory->create_item_metadata($item, $meta_3_tax, [$term2_root2_ggc1->get_id()]);
}
}
$this->repository = \Tainacan\Repositories\Metadata::get_instance();
@ -575,19 +714,21 @@ class Facets extends TAINACAN_UnitApiTestCase {
$values = $this->repository->fetch_all_metadatum_values( $this->meta_2_tax->get_id(), [
'count_items' => true,
'search' => 'collection',
'items_filter' => false
'search' => 'child',
'items_filter' => false,
] );
$values = $this->get_values($values);
$this->assertEquals( 2, sizeof($values) );
$this->assertEquals( 3, sizeof($values) );
$valuesParsed = array_map(function($el) {
$this->assertEquals( 20, $el['total_items'] );
//$this->assertEquals( 10, $el['total_items'] );
$this->assertContains($el['total_items'], [10,20]);
return $el['label'];
}, $values);
$this->assertContains( 'Term for collection 1', $valuesParsed);
$this->assertContains( 'Term for collection 2', $valuesParsed);
$this->assertContains( 'Term for collection 2 child', $valuesParsed);
$this->assertContains( 'Term for collection 1 child', $valuesParsed);
$this->assertContains( 'Term for all child', $valuesParsed);
// test search relationship without filter
$values = $this->repository->fetch_all_metadatum_values( $this->meta_relationship->get_id(), [
@ -644,23 +785,353 @@ class Facets extends TAINACAN_UnitApiTestCase {
$this->assertContains( 'Value 55', $valuesParsed);
$this->assertContains( 'Value 77', $valuesParsed);
// test count items normal
// test count items normal
// test default taxonomy
// test default taxonomy without filter
// test search taxonomy
// test default taxonomy
$values = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id());
$values = $this->get_values($values);
$this->assertEquals(2, sizeof($values));
// test default taxonomy without filter
$values = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'items_filter' => false
]);
$values = $this->get_values($values);
$this->assertEquals(2, sizeof($values));
//test search taxonomy
$values = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'items_filter' => false,
'search' => 't2',
]);
$values = $this->get_values($values);
$this->assertEquals(1, sizeof($values));
$values = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'items_filter' => false,
'search' => 'Children',
]);
$values = $this->get_values($values);
$this->assertEquals(9, sizeof($values));
$values = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'items_filter' => false,
'search' => 'GGC',
]);
$values = $this->get_values($values);
$this->assertEquals(1, sizeof($values));
// test search taxonomy with filter
// test search taxonomy without filter
$values = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'count_items' => true,
'search' => 'GGC',
'items_filter' => [
'meta_query' => [
[
'key' => $this->metadatum_text->get_id(),
'value' => ['even']
]
]
]
] );
$values = $this->get_values($values);
$this->assertEquals( 1, sizeof($values) );
$this->assertEquals( 5, $values[0]['total_items']);
// test offset taxonomy
$values = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'items_filter' => false,
'search' => 'Children',
'number' => 9,
'offset' => 0
]);
$values = $this->get_values($values);
$this->assertEquals(9, sizeof($values));
$values_p1 = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'items_filter' => false,
'search' => 'Children',
'number' => 3,
'offset' => 0
]);
$values_p1 = $this->get_values($values_p1);
$this->assertEquals(3, sizeof($values_p1));
$values_p2 = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'items_filter' => false,
'search' => 'Children',
'number' => 3,
'offset' => 3
]);
$values_p2 = $this->get_values($values_p2);
$this->assertEquals(3, sizeof($values_p2));
$values_p3 = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'items_filter' => false,
'search' => 'Children',
'number' => 3,
'offset' => 6
]);
$values_p3 = $this->get_values($values_p3);
$this->assertEquals(3, sizeof($values_p3));
$this->assertEquals($values[0]['label'], $values_p1[0]['label']);
$this->assertEquals($values[3]['label'], $values_p2[0]['label']);
$this->assertEquals($values[6]['label'], $values_p3[0]['label']);
// test include taxonomy
$values = $this->repository->fetch_all_metadatum_values( $this->meta_3_tax->get_id(), [
'count_items' => true,
'include' => [$this->term2_root2_gc2->get_id(), $this->term2_root2_c2->get_id()], //['18','16'],
'search' => 'GGC',
'items_filter' => [
'meta_query' => [
[
'key' => $this->metadatum_text->get_id(),
'value' => ['even']
]
]
]
] );
$values = $this->get_values($values);
$this->assertEquals(3, sizeof($values));
// test search taxonomy without filter
// test count items taxonomy
//
}
/**
* @group term_tree
*/
public function test_process_term_tree() {
$data = [
(object) [
'term_id' => 1,
'name' => 'Root',
'parent' => 0,
'have_items' => 0
],
(object) [
'term_id' => 2,
'name' => 'Child 1',
'parent' => 1,
'have_items' => 0
],
(object) [
'term_id' => 3,
'name' => 'G Child 1',
'parent' => 2,
'have_items' => 0
],
(object) [
'term_id' => 4,
'name' => 'G G Child',
'parent' => 3,
'have_items' => 0
],
(object) [
'term_id' => 5,
'name' => 'G G Child 2',
'parent' => 3,
'have_items' => 0
],
(object) [
'term_id' => 6,
'name' => 'G G G Child',
'parent' => 4,
'have_items' => 0
],
(object) [
'term_id' => 7,
'name' => 'G G G Child 2',
'parent' => 5,
'have_items' => 0
],
(object) [
'term_id' => 8,
'name' => '2 Root',
'parent' => 0,
'have_items' => 0
],
(object) [
'term_id' => 9,
'name' => '2 Child',
'parent' => 8,
'have_items' => 0
],
(object) [
'term_id' => 10,
'name' => '2 Child 2',
'parent' => 8,
'have_items' => 0
],
(object) [
'term_id' => 11,
'name' => '2 G Child',
'parent' => 10,
'have_items' => 0
],
(object) [
'term_id' => 12,
'name' => '2 G Child 2',
'parent' => 10,
'have_items' => 0
]
];
$data_b = $data;
$MetaRepo = \Tainacan\Repositories\Metadata::get_instance();
// items on 5 and 12
$data[4]->have_items = 1;
$data[11]->have_items = 1;
$i = 0;
while ($i<100) {
$i++;
shuffle($data);
$results = $MetaRepo->_process_terms_tree($data, 0);
$this->assertEquals(2, count($results));
$ids = array_map(function($el) {return $el->term_id; }, $results);
$this->assertContains(1, $ids);
$this->assertContains(8, $ids);
$results = $MetaRepo->_process_terms_tree($data, 3);
$this->assertEquals(1, count($results));
$ids = array_map(function($el) {return $el->term_id; }, $results);
$this->assertContains(5, $ids);
$results = $MetaRepo->_process_terms_tree($data, 5);
$this->assertEquals(0, count($results));
$results = $MetaRepo->_process_terms_tree($data, 10);
$this->assertEquals(1, count($results));
$this->assertEquals(12, $results[0]->term_id);
}
// items on 6, 7 and 8
$data = $data_b;
$data[4]->have_items = 0;
$data[11]->have_items = 0;
$data[7]->have_items = 1;
$data[6]->have_items = 1;
$data[5]->have_items = 1;
$i = 0;
while ($i<100) {
$i++;
shuffle($data);
$results = $MetaRepo->_process_terms_tree($data, 0);
$this->assertEquals(2, count($results));
$ids = array_map(function($el) {return $el->term_id; }, $results);
$this->assertContains(1, $ids);
$this->assertContains(8, $ids);
$results = $MetaRepo->_process_terms_tree($data, 3);
$this->assertEquals(2, count($results));
$ids = array_map(function($el) {return $el->term_id; }, $results);
$this->assertContains(5, $ids);
$this->assertContains(4, $ids);
$results = $MetaRepo->_process_terms_tree($data, 5);
$this->assertEquals(1, count($results));
$ids = array_map(function($el) {return $el->term_id; }, $results);
$this->assertContains(7, $ids);
$results = $MetaRepo->_process_terms_tree($data, 10);
$this->assertEquals(0, count($results));
$results = $MetaRepo->_process_terms_tree($data, 6);
$this->assertEquals(0, count($results));
}
}
/**
* @group term_tree
*/
public function test_process_term_tree_naria() {
$MetaRepo = \Tainacan\Repositories\Metadata::get_instance();
$nchildrens = 3;
$h = 6;
$data = $this->generate_narias_tree_test($nchildrens, $h);
$this->set_items_in_tree($data, $nchildrens, $h, [1,3], 1);
//var_dump($data);
$start = microtime(true);
$results = $MetaRepo->_process_terms_tree($data, 0);
$time = microtime(true) - $start;
$this->assertEquals(2, count($results));
$ids = array_map(function($el) {return $el->term_id; }, $results);
$this->assertContains(1, $ids);
$this->assertContains(3, $ids);
}
private function set_items_in_tree($data, $nchildrens, $h, $parents=[], $items_repeat=1) {
if (empty($parents) || $nchildrens < 2 || $h < 1)
return $data;
foreach ($parents as $parent) {
for($i=0; $i < $items_repeat; $i++) {
$rando_h = rand (1, $h);
$id = $parent;
for ($count=0; $count < $rando_h; $count++ ) {
$rando_c = rand (1, $nchildrens) - 1;
$idx = ($id * $nchildrens) + $rando_c;
if($idx > count($data)-1) {
$idx = ($parent * $nchildrens) + $rando_c;
}
$id = $data[$idx]->term_id;
}
$data[$idx]->have_items = 1;
}
}
}
/*
* generate n-árias trees
*
* $nchildrens || $h=2 | $h=4 | $h=8 | $h=9
* 2 || 3 | 15 | 255 | 511
* 3 || 4 | 40 | 3280 | 9841
* 4 || 5 | 85 | 21845| 87381
* 5 || 6 | 156 | 97656| 488281
*/
private function generate_narias_tree_test($nchildrens, $h) {
if ($nchildrens < 2 || $h < 2)
return [];
$n = (pow($nchildrens, $h) - 1) / ($nchildrens - 1);
$data = [];
for ($i = 0; $i < $n-1; $i++) {
$id = $i+1;
$parent = floor($i/$nchildrens);
$data[] = (object) [
'term_id' => $id,
'name' => "i-$id",
'parent' => $parent,
'have_items' => 0
];
}
return $data;
}
}