Drupal EntityFieldQuery group results by taxonomy - drupal

I have an entity bundle called Map Points that has a taxonomy term reference field of countries. I'm using EntityFieldQuery in a custom module to list out all the map point entities but I need to group them by the country reference field.
Here is the code I have right now which filters the query by tid 1 and I know I can repeat the query for each country to group the results but I'm looking for a more refined way to group the results by the country term id's without having to write a new query for each country.
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'givepower')
->entityCondition('bundle', 'map_points')
->propertyCondition('type', 'map_points', '=')
->fieldCondition('field_map_point_country', 'tid', '1');
$result = $query->execute();
// set empty map point id array
$map_point_ids = array();
// loop through all givepower entities
foreach($result['givepower'] as $record) {
$map_point_ids[] = $record->id;
}
// load entities
$map_point = entity_load('givepower', $map_point_ids);
// set entity view
$entities = entity_view('givepower', $map_point);
return $entities;

I believe I figured this out. Here's the finished code:
//load recent_work_countries taxonomy
$vocabulary = taxonomy_vocabulary_machine_name_load('recent_work_countries');
// load all terms in the recent_work_countries taxonomy
$terms = entity_load('taxonomy_term', FALSE, array('vid' => $vocabulary->vid));
// set empty countries array
$countries = array();
// loop through each recent_work_countries term
foreach($terms as $tid => $value) {
// set country term name and term id(tid) vars
$country_name = $terms[$tid]->name;
$country_tid = $terms[$tid]->tid;
// add each country to the counties array
$countries[$country_name] = array();
// create new query to grab all map point entities with the current recent_work_countries tid
$query = new EntityFieldQuery();
$query
->entityCondition('entity_type', 'givepower')
->entityCondition('bundle', 'map_points')
->propertyCondition('type', 'map_points', '=')
->fieldCondition('field_map_point_country', 'tid', $country_tid);
$result = $query->execute();
// set empty tab id array
$map_point_ids = array();
// loop through all givepower entities that have the map_point_ids
foreach($result['givepower'] as $record) {
$map_point_ids[] = $record->id;
}
// load entities
$map_point = entity_load('givepower', $map_point_ids);
// set entity view
$entities = entity_view('givepower', $map_point);
// loop through each entities results
foreach($entities['givepower'] as $eid => $e_val) {
// add entity result data to countries array
$countries[$country_name][] = array(
'title' => $e_val['field_map_point_title']['#items'][0]['safe_value'],
'description' => $e_val['field_map_point_description']['#items'][0]['safe_value'],
'latitude' => $e_val['field_map_point_latitude']['#items'][0]['safe_value'],
'longitude' => $e_val['field_map_point_longitute']['#items'][0]['safe_value'],
);
}
}
This will create an array that looks like this:
Array (
[Kenya] => Array (
[0] => Array (
[title] => Test Map Point 1
[description] => lorum ipsum
[latitude] => 1.765404
[longitude] => 40.079880 )
[1] => Array (
[title] => Test Map Point 1
[description] => Lorum ipsum
[latitude] => 0.633657
[longitude] => 37.350050 ) )
[Haiti] => Array (
[0] => Array (
[title] => GA University
[description] => lorum ipsum
[latitude] => 18.574420
[longitude] => -72.310981 ) )
[Nepal] => Array ( )
[Ghana] => Array ( )
)

Related

Dynamically pull values from nested form using Gravity Forms & Gravity Wiz Nested Forms

So I've been going through a restructure build of an entire site, and part of that involved switching from Formidable Forms to Gravity Forms. We did this because we wanted to use the Nested Form feature, so that we could automate multiple attendees without having to create a new form for each.
Here's the problem - on our old site that did have a separate form per attendee via Formidable, we had a code using the Canvas API to send name + email info to Canvas and automatically register users for the online courses this company offers. In trying to convert sections of this code to work with my nested forms, I'm running into a snag:
The main issue is that the value is being spit out as all of the information from the nested form entry, not by name/ email/ etc.
The info is being spit out twice, perhaps because of the way the forms are structured? There are a couple calculations happening in the forms/ nested forms so I'm chalking it up to that.
[1] => WC_Meta_Data Object
(
[current_data:protected] => Array
(
[id] => 212
[key] => Attendee Registration
[value] =>
Name
Test Name
Email Address
courses#email.com
Cell Phone
(333) 333-3333
Would you like to receive text message reminders for this registration?
No
Post-class notification is required for the following states, please identify if you will be using this class to fulfill any state license requirements:
N/A
You'll receive a hard copy and digital certificate upon course completion. Additional options are available here:
All live classes include a hard copy manual and regulations. To join our effort to save paper, please also add any of the following options to take your books home:
)
[data:protected] => Array
(
[id] => 212
[key] => Attendee Registration
[value] =>
Name
Test Name
Email Address
courses#email.com
Cell Phone
(333) 333-3333
Would you like to receive text message reminders for this registration?
No
Post-class notification is required for the following states, please identify if you will be using this class to fulfill any state license requirements:
N/A
You'll receive a hard copy and digital certificate upon course completion. Additional options are available here:
All live classes include a hard copy manual and regulations. To join our effort to save paper, please also add any of the following options to take your books home:
)
)
Also: I was playing around with grabbing the ID of the main entry via [_gravity_form_linked_entry_id], and grabbing the nested info from that via [_gravity_form_lead].
The best I was able to get from that was this... so yeah kind of lost on how to progress here if anyone has any pointers! Thanks so much!
[data:protected] => Array
(
[id] => 211
[key] => _gravity_forms_history
[value] => Array
(
[_gravity_form_cart_item_key] => 72201a9586fb30895b8fb5cac2a796b9
[_gravity_form_linked_entry_id] => 125
[_gravity_form_lead] => Array
(
[form_id] => 1
[source_url] => https://chcv2.flywheelstaging.com/product/air-monitoring-specialist-live/
[ip] => 75.151.95.41
[42.1] => Course Price
[42.2] => $580.00
[42.3] => 1
[21] => 122
[40.1] => Add-On Fees
[40.2] => $0.00
[40.3] => 1
)
[_gravity_form_data] => Array
(
[id] => 1
[bulk_id] => 0
[display_title] =>
[display_description] =>
[disable_woocommerce_price] => no
[price_before] =>
[price_after] =>
[disable_calculations] => no
[disable_label_subtotal] => yes
[disable_label_options] => yes
[disable_label_total] => no
[disable_anchor] => no
[label_subtotal] => Course Fee
[label_options] => Additional Attendees + Selected Options
[label_total] => Attendee Registration + Add-Ons:
[use_ajax] => no
[enable_cart_edit] => no
[enable_cart_edit_remove] => no
[keep_cart_entries] => no
[send_notifications] => no
[enable_cart_quantity_management] => stock
[cart_quantity_field] =>
[update_payment_details] => yes
[display_totals_location] => after
[structured_data_override] => no
[structured_data_low_price] =>
[structured_data_high_price] =>
[structured_data_override_type] => overwrite
)
)
)
Update: Here's how I've incorporated the code from Rochelle's comment below, where I'm getting an error
add_action( 'woocommerce_thankyou', 'canvas_enroll', 20, 2 );
function canvas_enroll($orders) {
$query = new WC_Order_Query( array(
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
) );
$orders = $query->get_orders();
foreach($orders as $order){
foreach ($order->get_items() as $item_id => $item_data) {
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"]) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"])){
$linked_entry=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_linked_entry_id"];
$entry_id = $linked_entry;
$entry = GFAPI::get_entry( $entry_id );//id of Parent Gravity Form
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2']) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'])){
$linked_nested_value=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'];
$nested_value_array = preg_split ("/\,/", $linked_nested_value); //array of child entries
$child_entry_amt = substr_count($linked_nested_value, ",") + 1;
if ($child_entry_amt > 0){
for ($n = 0; $n < $child_entry_amt; $n++) {
$entry_id_nest[$n]=$nested_value_array[$n];
$entry_nest[$n] = GFAPI::get_entry( $entry_id_nest[$n] ); //nested form entry
$name[$n] = $entry_nest[$n]['12.3'].''.$entry_nest[$n]['12.6'];//replace 1.3 and 1.6 with nested field id of name
$email[$n] = $entry_nest[$n]['11']; //2 is the GF nested field id of email
}
}
}
}
}
}
}
Finally got this figured out! Something that was super helpful was to echo the item meta data ($value, in my case) for that to display all the ids and such, that's how I was able to figure out that I needed 21 in that ID for the child entries.
I'm not really sure why I had to switch to wc_get_order instead of wc_order_query, but it solved the errors I was getting.
function canvas_enroll($order_id) {
$order = wc_get_order( $order_id);
$order_id = array(
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
);
if(!empty($order) && isset($order)){
// Loop through order line items
foreach( $order->get_items() as $key => $value ){
// get order item data (in an unprotected array)
if(isset($value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"]) && !empty($value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"])){
$linked_entry=$value->get_meta( '_gravity_forms_history')["_gravity_form_linked_entry_id"];
$entry_id = $linked_entry;
$entry = GFAPI::get_entry( $entry_id );//id of Parent Gravity Form
if(isset($value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['21']) && !empty($value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['21'])) { //21 was the id for my child form
$linked_nested_value = $value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['21'];
$nested_value_array = preg_split ("/\,/", $linked_nested_value); //array of child entries
$child_entry_amt = substr_count($linked_nested_value, ",") + 1;
if ($child_entry_amt > 0){
for ($n = 0; $n < $child_entry_amt; $n++) {
$entry_id_nest[$n]=$nested_value_array[$n];
$entry_nest[$n] = GFAPI::get_entry( $entry_id_nest[$n] ); //nested form entry
$firstname[$n] = $entry_nest[$n]['12.3'];//replace 12.3 with nested field id of first name
$lastname[$n] = $entry_nest[$n]['12.6'];//replace 12.6 with nested field id of last name
$email[$n] = $entry_nest[$n]['11']; //replace 11 with nested field id of email
}
}
}
}
}
}
}
I'm just going to paste the code I used for another project that I needed to pull the same type of data in case it puts you on the right track. You'd have to replace the numbers with the ids from your forms:
$query = new WC_Order_Query( array(
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
) );
$orders = $query->get_orders();
foreach($orders as $order){
foreach ($order->get_items() as $item_id => $item_data) {
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"]) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"])){
$linked_entry=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_linked_entry_id"];
$entry_id = $linked_entry;
$entry = GFAPI::get_entry( $entry_id );//id of Parent Gravity Form
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2']) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'])){
$linked_nested_value=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'];
$nested_value_array = preg_split ("/\,/", $linked_nested_value); //array of child entries
$child_entry_amt = substr_count($linked_nested_value, ",") + 1;
if ($child_entry_amt > 0){
for ($n = 0; $n < $child_entry_amt; $n++) {
$entry_id_nest[$n]=$nested_value_array[$n];
$entry_nest[$n] = GFAPI::get_entry( $entry_id_nest[$n] ); //nested form entry
$name[$n] = $entry_nest[$n]['1.3'].''.$entry_nest[$n]['1.6'];//replace 1.3 and 1.6 with nested field id of name
$email[$n] = $entry_nest[$n]['2']; //2 is the GF nested field id of email
}
}
}
}
}
}
Ok. Try this first to see if it changes anything:
$query = new WC_Order_Query( array(
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
) );
$orders = $query->get_orders();
foreach($orders as $order){
if(!empty($order) && isset($order)){
foreach ($order->get_items() as $item_id => $item_data) {
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"]) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"])){
$linked_entry=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_linked_entry_id"];
$entry_id = $linked_entry;
$entry = GFAPI::get_entry( $entry_id );//id of Parent Gravity Form
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2']) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'])){
$linked_nested_value=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'];
$nested_value_array = preg_split ("/\,/", $linked_nested_value); //array of child entries
$child_entry_amt = substr_count($linked_nested_value, ",") + 1;
if ($child_entry_amt > 0){
for ($n = 0; $n < $child_entry_amt; $n++) {
$entry_id_nest[$n]=$nested_value_array[$n];
$entry_nest[$n] = GFAPI::get_entry( $entry_id_nest[$n] ); //nested form entry
$name[$n] = $entry_nest[$n]['1.3'].''.$entry_nest[$n]['1.6'];//replace 1.3 and 1.6 with nested field id of name
$email[$n] = $entry_nest[$n]['2']; //2 is the GF nested field id of email
}
}
}
}
}
}
}

How To Create Dynamic Array These Value Use Foreach?

$data_array = array(
array(
'ID',
'Name',
'Designation ',
'Message'
)
);
$sql = mysql_query("SELECT * FROM tbl_name");
while($data = mysql_fetch_object($sql))
{
array_push($data_array, array(
$data->id,
$data->name,
$data->designation,
$data->message
));
}
How can i create dynamic array for any sql table fields or data like in data array take fields value dynamically ?

Content filter does not search drupal7

I have added a drop down of my courses title at the filter second of the drupal - admin - content area.
http://www.XYZ.com/demo/admin/content
But when I select any of the title and hit Filter nothing gets appear. The data which showed previously it shows again, nothing happens really.
My code for the add filter drop down:
function products_form_node_admin_content_alter(&$form, &$form_state){
$results = db_query("SELECT r.nid, r.title FROM {node} AS n
LEFT JOIN {node_revision} AS r ON r.nid = n.nid
WHERE type = 'product'")->fetchAll();
$optionsF = Array ( '[any]' => 'any' );
foreach($results as $key => $result) {
$options[$result->title] = $result->title;
}
$options = $optionsF + $options;
$course_titles['title'] = Array
(
'#type' => 'select',
'#options' => $options,
'#title' => 'title',
'#default_value' => 'any'
);
$form['filter']['filters']['status']['filters'] = $form['filter']['filters']['status']['filters'] + $course_titles;
$uid_column = array('uniqueid' => array(
'data' => 'UniqueID',
'field' => 'n.nid'
));
$form['admin']['nodes']['#header'] = $form['admin']['nodes']['#header']+$uid_column;
foreach ($form['admin']['nodes']['#options'] as $key => $row) {
$node = node_load(array('nid' => check_plain($key)));
$form['admin']['nodes']['#options'][$key]['uniqueid'] = $node->field_unique_code_course['und'][0]['value'];
}
}
Does any body have any idea what is lacking in my code or method?
Cheers!!!
I would recommend using Admin Views, it's built for this purpose.
https://drupal.org/project/admin_views

Why are Symfony2 validation propertyPath valus in square brackets?

I am validating some values:
$collectionConstraint = new Collection(array(
'email' => array(
new NotBlank(),
new Email(),
),
'password' => array(
new NotBlank(),
new MinLength(array('limit' => 6)),
new MaxLength(array('limit' => 25)),
),
));
$data = array('email' => $this->getRequest()->get('email'), 'password' => $this->getRequest()->get('password'));
$errors = $this->get('validator')->validateValue($data, $collectionConstraint);
But for some reason the fields (propertyPath) are stored with square brackets - I'd like to understand why Sf does that. I have to manually remove all the brackets which seems absurd so I think I am missing some functionality somewhere.
Dump of $errors:
Symfony\Component\Validator\ConstraintViolationList Object
(
[violations:protected] => Array
(
[0] => Symfony\Component\Validator\ConstraintViolation Object
(
[messageTemplate:protected] => This value should not be blank
[messageParameters:protected] => Array
(
)
[root:protected] => Array
(
[email] =>
[password] =>
)
[propertyPath:protected] => [email]
[invalidValue:protected] =>
)
[1] => Symfony\Component\Validator\ConstraintViolation Object
(
[messageTemplate:protected] => This value should not be blank
[messageParameters:protected] => Array
(
)
[root:protected] => Array
(
[email] =>
[password] =>
)
[propertyPath:protected] => [password]
[invalidValue:protected] =>
)
)
)
Even the toString function is useless.
"[email]: This value should not be blank","[password]: This value should not be blank"
Property paths can map either to properties or to indices. Consider a class OptionBag which implements \ArrayAccess and a method getSize().
The property path size refers to $optionBag->getSize()
The property path [size] refers to $optionBag['size']
In your case, you validate an array. Since array elements are also accessed by index, the resulting property path in the violation contains squared brackets.
Update:
You don't have to manually remove the squared brackets. You can use Symfony's PropertyAccess component to map errors to an array with the same structure as your data, for example:
$collectionConstraint = new Collection(array(
'email' => array(
new NotBlank(),
new Email(),
),
'password' => array(
new NotBlank(),
new MinLength(array('limit' => 6)),
new MaxLength(array('limit' => 25)),
),
));
$data = array(
'email' => $this->getRequest()->get('email'),
'password' => $this->getRequest()->get('password')
);
$violations = $this->get('validator')->validateValue($data, $collectionConstraint);
$errors = array();
$accessor = $this->get('property_accessor');
foreach ($violations as $violation) {
$accessor->setValue($errors, $violation->getPropertyPath(), $violation->getMessage());
}
=> array(
'email' => 'This value should not be blank.',
'password' => 'This value should have 6 characters or more.',
)
This also works with multi-dimensional data arrays. There the property paths will be something like [author][name]. The PropertyAccessor will insert the error messages in the same location in the $errors array, i.e. $errors['author']['name'] = 'Message'.
PropertyAccessor's setValue is no real help because it cannot handle multiple violations for a single field. For instance, a field might be shorter than a constraint length and also contain illegal characters. For this, we would have two error messages.
I had to create my own code:
$messages = [];
foreach ($violations as $violation) {
$field = substr($violation->getPropertyPath(), 1, -1);
$messages[] = [$field => $violation->getMessage()];
}
$output = [
'name' => array_unique(array_column($messages, 'name')),
'email' => array_unique(array_column($messages, 'email')),
];
return $output;
We manually strip the [] characters from the property path and create an
array of arrays of fields and corresponding messages. Later we transform the
array to group the messages by fields.
$session = $request->getSession();
$session->getFlashBag()->setAll($messages);
In the controller, we add the messages to the flash bag.

How to order categories in WordPress?

I use wp_list_categories() to get the list of all the categories and generate the navigation bar. Is there a way to order these categories in a particular order other than alphabetical ordering.
eg: Connect, News & Views, Q&A, Hello Startup, Startup 101...
Most themes don't use the description of the category for anything. Easy workaround I did was to use numbers in description. The top post here currently has some jQuery hack from here, it's unneeded.
You can add custom order fields I suppose as well.
Just
$categories = get_categories( array(
'orderby' => 'description',
'order' => 'ASC'
) );
Technical approach
The problem in wordpress core is that the table wp_terms has no term_order column. That means, standard wordpress does not support the custom term order. If you look at this WP database structure you can find the table wp_term_relationships. This table is responsible for the relationships between posts and the taxonomy (your categories) AND this table has a term_order column.
Now, with a simple SQL statement ALTER TABLE wp_terms ADD term_order INT(11) NOT NULL DEFAULT 0 (not forget the $wpdb->wp_terms variable) you can add a column to the table, which is responsible for your custom category order. Then you can put your custom category order in this two columns of wp_term_relationships and wp_terms. When all is finished, you can hook into the filter of get_terms_args and change the orderby to term_order.
Here a list of all relevant links for the technical approach:
https://codex.wordpress.org/Database_Description for the wp database structure
https://codex.wordpress.org/Class_Reference/wpdb for the $wpdb->wp_terms
https://developer.wordpress.org/reference/hooks/get_terms_args/ for the WP filter
A plugin can do the job for you
Check my plugin to solve this: WordPress Real Categories Management. WP RCM creates an extra field term_order on the wp terms table. It also brings a lot of other useful features as you can see in the screenshot below. It allows you to organize your wordpress categories in a nice way. It is easy to use, just drag&drop your categories and move it to a specific order. The plugin works in all Custom post types.
From the product description i can quote. If you want to try the plugin, there is also a demo on the plugin page.
There are a lot of free plugins
This can be solved with a lot of free plugins available within the wordpress.org plugin repository. Simply search for "category order" in your Wordpress Dashboard > Plugins > Install.
This is inbuilt in wordpress_wp_list_categories
wp_list_categories('orderby=name');
I think that would help you out
I did it generating several term lists. I call it later by my own order. I'm a PHP beginner.
First, I store, in a different variable, the ID for each category term:
$terms = get_terms('my_taxonomy', 'hide_empty=0');
foreach ( $terms as $term ) {
${$term->slug} = get_term_by('slug', $term->slug, 'product_cat');
${$term->slug.'_array'} = (array)${$term->slug};
${$term->slug.'_array_id'} =${$term->slug.'_array'}['term_id'];
};
Then, I create several args for each wp_list_categories() excluding, with this variable the terms I want to:
$args = array(
'taxonomy' => 'my_taxonomy',
'orderby' => 'name',
'show_count' => true,
'pad_counts' => false,
'hierarchical' => true,
'title_li' => '',
'hide_empty' => 0,
'show_option_all' => 'Show all',
'exclude' => array( $term1_array_id, $term2_array_id )
);
$args_1 = array(
'taxonomy' => 'my_taxonomy',
'orderby' => 'name',
'show_count' => true,
'pad_counts' => false,
'hierarchical' => true,
'title_li' => '',
'hide_empty' => 0,
'exclude' => array( $term3_array_id, $term4_array_id, $term1_array_id )
);
$args_2 = array(
'taxonomy' => 'my_taxonomy',
'orderby' => 'name',
'show_count' => true,
'pad_counts' => false,
'hierarchical' => true,
'title_li' => '',
'hide_empty' => 0,
'exclude' => array( $term1_array_id, $term4_array_id, $term5_array_id )
);
Finally, I can call separately each term list:
<ul>
<?php wp_list_categories( $args ); ?>
<?php wp_list_categories( $args_1 ); ?>
<?php wp_list_categories( $args_2 ); ?>
</ul>
Use Category Order and Taxonomy Terms Order free plugin
I didn't find anything so I constructed my own method. I abstracted it away in an abstract class for my plugin, hence the extra code, but you can pull the methods.
The main method to look at is format_hierarchy()
// The parent class
abstract class Taxonomy {
protected bool $_terms_loaded;
protected array $terms;
protected array $formatted_terms;
public function __get( $property ) {
if ( $property === 'formatted_terms' ) {
if ( !isset( $this->formatted_terms ) ) $this->format_hierarchy();
return $this->formatted_terms;
}
if ( substr( $property, 0, 1 ) === '_' ) die( 'Cannot get private properties' );
if ( property_exists( $this, $property ) )
return $this->$property;
}
/**
* Formats the taxonomy's terms into a hierarchy of term_blocks and saves the value as a property to the class
*
* #return array an array of `[term_taxonomy_id:number] => term_block` like:
* array(
* array( // parent $term_block
* 'term' => WP_Term,
* 'children' => array( $term_blocks… )
* ),
* …$term_blocks…
* )
*/
public function format_hierarchy():array {
if ( !$this->_load_terms() ) return [];
// Holds a reference to every category, parents and children
$term_blocks = [];
// Holds a reference to every top most level category
$parents = [];
foreach ( $this->terms as $term ) {
// Add itself to the list of all categories
$term_block = [
'children' => [],
'term' => $term
];
// Add itself to the array of all categories
if ( !isset( $term_blocks[ $term->term_taxonomy_id ] ) )
$term_blocks[ $term->term_taxonomy_id ] =& $term_block;
// If it's a child category…
if ( $term->parent !== 0 ) {
// If the parent hasn't been created yet, create it
if ( !isset( $term_blocks[ $term->parent ] ) )
$term_blocks[ $term->parent ] = [
'children' => [],
'term' => null
];
$term_blocks[ $term->parent ][ 'children' ][] =& $term_block;
} else
// Otherwise it's a parent
$parents[ $term->term_taxonomy_id ] =& $term_blocks[ $term->term_taxonomy_id ];
// set the term block's WP_Term property
$term_blocks[ $term->term_taxonomy_id ][ 'term' ] =& $term;
// This is needed so that the loop doesn't readd the same reference over and over again
unset( $term ); unset( $term_block );
}
return $this->formatted_terms = $parents;
}
/**
* Given a WP_Term property value, and a property key, recursively searches through all of the terms for it
*
* #property $term_val mixed The property value to find
* #property $prop string The property key for the value
* #property $with_parent ?boolean Whether to return the top level parent as well
* #property $term_blocks ?array Array of term blocks
* #return array If $with_parent is true, returns an [ $found_term_block, $top_level_parent ]
* Otherwise returns only the found term block
*/
public function find_term_by(
$term_val,
string $prop,
bool $with_parent = false,
$term_blocks = null
):?array {
if ( is_null( $term_blocks ) ) $term_blocks = $this->formatted_terms;
foreach ( $term_blocks as $term_block ) {
if ( $term_block[ 'term' ]->{$prop} === $term_val ) return $term_block;
if ( count( $term_block[ 'children' ] ) &&
( $found = $this->find_term_by( $term_val, $prop, false, $term_block[ 'children' ] ) )
) return $with_parent ? [ $found, $term_block ] : $found;
}
return null;
}
/**
* Loads the taxonomy terms once from the DB
*/
protected function _load_terms():bool {
if ( isset( $this->_terms_loaded ) ) return $this->_terms_loaded;
$this->terms = get_terms(
array(static::$taxonomy),
array(
'hide_empty' => false,
)
);
if ( !$this->terms ) {
ClassErrorHandler::handle_exception(
new \WP_Error( 500, 'Failed to load category terms: \'' . static::$taxonomy . '\'' )
);
}
return $this->_terms_loaded = !!$this->terms;
}
}
// The Implementation
class TaxonomyProductService extends Taxonomy {
public static string $taxonomy;
public static string $slug;
/**
* To be called upon taxonomy registration long before any instance is required
*/
public static function define_taxonomy( string $slug, string $taxonomy ) {
static::$slug = $slug;
static::$taxonomy = $taxonomy;
}
}
Right after registering the custom taxonomy I call
TaxonomyProductService::define_taxonomy( 'url-slug', 'product-service' );
And finally how it's used
$tax = new TaxonomyProductService();
$terms = $tax->formatted_terms;
// search for a term whose slug === `my-term`, and return the parent category
list( $current_term, $parent_term ) = $tax->find_term_by( 'my-term', 'slug', true );
For the benefit of future visitors, here’s the easy solution to this problem:
There are now a number of plugins that allow you to order categories or other custom taxonomies in WordPress. You can see some of them in the WordPress plugin directory’s “category order” tag page. I can personally confirm that Custom Taxonomy Order NE plugin does the job.

Resources