Conectarea împreună a două tipuri de postări personalizate diferite
Sper că cineva mă poate ajuta cu această problemă foarte frustrantă...
Încerc să construiesc un site imobiliar în WordPress și aș dori să conectez două tipuri de postări personalizate -
Unul ar fi pentru zonele de dezvoltare și celălalt pentru proprietățile individuale din acea zonă de dezvoltare
Înțeleg că pot crea un CPT numit 'Developments' (Dezvoltări) și altul numit 'Properties' (Proprietăți) dar cum aș putea să le conectez împreună?.. de exemplu, dacă am crea o proprietate și am încerca să o atașăm unei dezvoltări, cum ar funcționa acest lucru dacă sunt tipuri de postări personalizate separate?
Am citit o întrebare similară - Cum să conectezi diferite CPT-uri împreună? și răspunsul lui Scuba Kay a fost aproape ce aveam nevoie, dar nu știu cum ai putea interoga o anumită proprietate care aparține unei anumite Dezvoltări
Mulțumesc anticipat!

Dacă înțeleg corect, doriți să stabiliți o relație unu-la-mulți între zonele de dezvoltare și proprietăți.
Iată o clasă care, dacă este încărcată și apoi instanțiată, va:
- crea o căsuță meta pentru un tip de postare numit development_area care conține o listă de bifări a tuturor proprietăților disponibile, astfel încât relația dintre cele două să poată fi actualizată ușor.
- crea o căsuță meta pentru un tip de postare numit property care conține o listă derulantă pentru a alege zona de dezvoltare asociată unei proprietăți.
- asigură că există o relație 1 la mulți între zonele de dezvoltare și proprietăți, adică o zonă de dezvoltare poate fi asociată cu mai multe proprietăți diferite, dar orice proprietate va avea doar o singură zonă de dezvoltare asociată.
Notă:
Acest cod ilustrează cum se poate face acest lucru. Deși am verificat pentru erori evidente și am verificat dublu logica, este posibil să fie nevoie să ajustați câteva lucruri, deoarece nu l-am testat în timp real. Eu fac acest lucru tot timpul și funcționează foarte bine.
După clasă, urmează un exemplu despre cum să o faceți să funcționeze și apoi câteva exemple despre cum să utilizați relațiile pe care le stabilește clasa.
class One_To_Many_Linker {
protected $_already_saved = false; # Folosit pentru a evita salvarea de două ori
public function __construct() {
$this->do_initialize();
}
Configurarea căsuțelor meta și funcționalitatea de salvare
protected function do_initialize() {
add_action(
'save_post',
array( $this, 'save_meta_box_data' ),
10,
2
);
add_action(
"add_meta_boxes_for_development_area",
array( $this, 'setup_development_area_boxes' )
);
add_action(
"add_meta_boxes_for_property",
array( $this, 'setup_property_boxes' )
);
}
Crearea căsuței meta necesare Alte căsuțe meta pot fi configurate aici cu ușurință.
# Tipul de postare development_area
# - acest tip de postare poate avea mai multe postări de tip property asociate
public function setup_development_area_boxes( \WP_Post $post ) {
add_meta_box(
'area_related_properties_box',
__('Proprietăți Asociate', 'language'),
array( $this, 'draw_area_related_properties_box' ),
$post->post_type,
'advanced',
'default'
);
}
Desenarea Proprietăților Asociate Acest cod afișează proprietățile asociate ca o serie de casete de bifare.
public function draw_area_related_properties_box( \WP_Post $post ) {
$all_properties = $this->get_all_of_post_type( 'property' );
$linked_property_ids = $this->get_linked_property_ids( $post->ID );
if ( 0 == count($all_properties) ) {
$choice_block = '<p>Nu există proprietăți în sistem momentan.</p>';
} else {
$choices = array();
foreach ( $all_properties as $property ) {
$checked = ( in_array( $property->ID, $linked_property_ids ) ) ? ' checked="checked"' : '';
$display_name = esc_attr( $property->post_title );
$choices[] = <<<HTML
<label><input type="checkbox" name="property_ids[]" value="{$property->ID}" {$checked}/> {$display_name}</label>
HTML;
}
$choice_block = implode("\r\n", $choices);
}
# Asigură-te că utilizatorul a intenționat să facă acest lucru.
wp_nonce_field(
"updating_{$post->post_type}_meta_fields",
$post->post_type . '_meta_nonce'
);
echo $choice_block;
}
Obținerea listelor de postări Aceasta preia toate postările de un anumit tip. Dacă utilizați postări lipicioase, veți dori să dezactivați flagul sticky în argumentul args.
# Preia toate postările de tipul specificat
# Returnează un array de obiecte post
protected function get_all_of_post_type( $type_name = '') {
$items = array();
if ( !empty( $type_name ) ) {
$args = array(
'post_type' => "{$type_name}",
'posts_per_page' => -1,
'order' => 'ASC',
'orderby' => 'title'
);
$results = new \WP_Query( $args );
if ( $results->have_posts() ) {
while ( $results->have_posts() ) {
$items[] = $results->next_post();
}
}
}
return $items;
}
Obținerea ID-urilor proprietăților asociate pentru o zonă de dezvoltare
Dat fiind un ID de zonă de dezvoltare, acesta va returna un array cu toate ID-urile postărilor de tip property asociate.
protected function get_linked_property_ids( $area_id = 0 ) {
$ids = array();
if ( 0 < $area_id ) {
$args = array(
'post_type' => 'property',
'posts_per_page' => -1,
'order' => 'ASC',
'orderby' => 'title',
'meta_query' => array(
array(
'key' => '_development_area_id',
'value' => (int)$area_id,
'type' => 'NUMERIC',
'compare' => '='
)
)
);
$results = new \WP_Query( $args );
if ( $results->have_posts() ) {
while ( $results->have_posts() ) {
$item = $results->next_post();
$ids[] = $item->ID;
}
}
}
return $ids;
}
Configurarea căsuțelor meta pentru Proprietăți
Puteți adăuga cu ușurință mai multe căsuțe meta aici dacă doriți.
# Configurarea căsuței meta pentru tipul de postare
public function setup_property_boxes( \WP_Post $post ) {
add_meta_box(
'property_linked_area_box',
__('Zonă de Dezvoltare Asociată', 'language'),
array( $this, 'draw_property_linked_area_box' ),
$post->post_type,
'advanced',
'default'
);
}
Desenarea căsuței meta pentru editorul de Proprietăți
Aceasta desenează un element select (listă derulantă) care permite utilizatorului să aleagă ce zonă de dezvoltare este asociată proprietății. Folosim o listă derulantă pentru a ne asigura că poate fi specificată doar o singură proprietate, dar o listă de butoane radio ar funcționa și ea.
public function draw_property_linked_area_box( \WP_Post $post ) {
$all_areas = $this->get_all_of_post_type('development_area');
$related_area_id = $this->get_property_linked_area_id( $post->ID );
if ( 0 == $all_areas ) {
$choice_block = '<p>Nu există zone de dezvoltare disponibile încă.</p>';
} else {
$choices = array();
$selected = ( 0 == $related_area_id )? ' selected="selected"':'';
$choices[] = '<option value=""' . $selected . '> -- Niciuna -- </option>';
foreach ( $all_areas as $area ) {
$selected = ( $area->ID == (int)$related_area_id ) ? ' selected="selected"' : '';
$display_name = esc_attr( $area->post_title );
$choices[] = <<<HTML
<option value="{$area->ID}" {$selected}>{$display_name}</option>
HTML;
}
$choice_list = implode("\r\n" . $choices);
$choice_block = <<<HTML
<select name="development_area_id">
{$choice_list}
</select>
HTML;
}
wp_nonce_field(
"updating_{$post->post_type}_meta_fields",
$post->post_type . '_meta_nonce'
);
echo $choice_block;
}
Metoda de legare
Rețineți că stabilim legăturile noastre prin setarea unei chei _development_area_id
pe fiecare proprietate.
- zonele de dezvoltare pot interoga proprietățile cu această cheie pentru a le afișa
proprietățile pot prelua propria lor meta sau pot avea interogarea lor filtrată pentru a prelua datele de dezvoltare
protected function get_property_linked_area_id( $property_id = 0 ) { $area_id = 0; if ( 0 < $property_id ) { $area_id = (int) get_post_meta( $property_id, '_development_area_id', true ); } return $area_id; }
Salvarea metadatelor
Ne străduim să salvăm doar atunci când este necesar și corect. Consultați comentariile din cod.
public function save_meta_box_data( $post_id = 0, \WP_Post $post = null ) {
$do_save = true;
$allowed_post_types = array(
'development_area',
'property'
);
# Nu salva dacă am salvat deja actualizările noastre
if ( $this->_already_saved ) {
$do_save = false;
}
# Nu salva dacă nu există ID de postare sau postare
if ( empty($post_id) || empty( $post ) ) {
$do_save = false;
} else if ( ! in_array( $post->post_type, $allowed_post_types ) ) {
$do_save = false;
}
# Nu salva pentru revizii sau salvări automate
if (
defined('DOING_AUTOSAVE')
&& (
is_int( wp_is_post_revision( $post ) )
|| is_int( wp_is_post_autosave( $post ) )
)
) {
$do_save = false;
}
# Asigură-te că se lucrează la postarea corectă
if ( !array_key_exists('post_ID', $_POST) || $post_id != $_POST['post_ID'] ) {
$do_save = false;
}
# Asigură-te că avem permisiunile necesare pentru a salva [ presupune că ambele tipuri folosesc edit_post ]
if ( ! current_user_can( 'edit_post', $post_id ) ) {
$do_save = false;
}
# Asigură-te că nonce-ul și referrer-ul sunt corecte.
$nonce_field_name = $post->post_type . '_meta_nonce';
if ( ! array_key_exists( $nonce_field_name, $_POST) ) {
$do_save = false;
} else if ( ! wp_verify_nonce( $_POST["{$nonce_field_name}"], "updating_{$post->post_type}_meta_fields" ) ) {
$do_save = false;
} else if ( ! check_admin_referer( "updating_{$post->post_type}_meta_fields", $nonce_field_name ) ) {
$do_save = false;
}
if ( $do_save ) {
switch ( $post->post_type ) {
case "development_area":
$this->handle_development_area_meta_changes( $post_id, $_POST );
break;
case "property":
$this->handle_property_meta_changes( $post_id, $_POST );
break;
default:
# Nu facem nimic pentru alte tipuri de postări
break;
}
# Notează că am salvat datele noastre
$this->_already_saved = true;
}
return;
}
Actualizarea Proprietăților de Dezvoltare
Citim lista de tipuri de postări de proprietăți bifate, preluăm lista de tipuri de postări de proprietăți asociate în prezent și apoi folosim acele liste pentru a determina ce să actualizăm.
Notă: Aici actualizăm metadatele pe tipurile de postări de proprietăți, nu pe zona noastră de dezvoltare editată.
# Zonele de dezvoltare pot fi legate de mai multe proprietăți
# dar fiecare proprietate poate fi legată de o singură zonă de dezvoltare
protected function handle_development_area_meta_changes( $post_id = 0, $data = array() ) {
# Obține ID-urile proprietăților asociate în prezent pentru această zonă de dezvoltare
$linked_property_ids = $this->get_linked_property_ids( $post_id );
# Obține lista de ID-uri de proprietăți bifate când utilizatorul a salvat modificările
if ( array_key_exists('property_ids', $data) && is_array( $data['property_ids'] ) ) {
$chosen_property_ids = $data['property_ids'];
} else {
$chosen_property_ids = array();
}
# Construiește o listă de proprietăți care trebuie legate sau dezlegate de această zonă
$to_remove = array();
$to_add = array();
if ( 0 < count( $chosen_property_ids ) ) {
# Utilizatorul a ales cel puțin o proprietate de legat
if ( 0 < count( $linked_property_ids ) ) {
# Aveam deja cel puțin o proprietate legată
# Parcurge cele existente și notează oricare care nu au fost bifate de utilizator
foreach ( $linked_property_ids as $property_id ) {
if ( ! in_array( $property_id, $chosen_property_ids ) ) {
# Legată în prezent, dar nebifată. Elimină-o.
$to_remove[] = $property_id;
}
}
# Parcurge cele bifate și notează oricare care nu sunt în prezent legate
foreach ( $chosen_property_ids as $property_id ) {
if ( ! in_array( $property_id, $linked_property_ids ) ) {
# Bifată, dar nu în lista celor legate în prezent. Adaugă-o.
$to_add[] = $property_id;
}
}
} else {
# Nu există ID-uri alese anterior, pur și simplu le adaugă pe toate
$to_add = $chosen_property_ids;
}
} else if ( 0 < count( $linked_property_ids ) ) {
# Nu au fost alese proprietăți de legat. Elimină toate cele legate în prezent.
$to_remove = $linked_property_ids;
}
if ( 0 < count($to_add) ) {
foreach ( $to_add as $property_id ) {
# Aceasta va suprascrie orice valoare existentă pentru cheia de legătură
# pentru a ne asigura că menținem doar o zonă de dezvoltare legată de fiecare proprietate.
update_post_meta( $property_id, '_development_area_id', $post_id );
}
}
if ( 0 < count( $to_remove ) ) {
foreach ( $to_remove as $property_id ) {
# Aceasta va șterge toate zonele de dezvoltare asociate existente pentru proprietate
# pentru a ne asigura că avem doar o singură zonă de dezvoltare legată per proprietate
delete_post_meta( $property_id, '_development_area_id' );
}
}
}
Salvarea Modificărilor noastre la Proprietăți
Deoarece cheia noastră meta este pe fiecare proprietate, pur și simplu actualizăm metadatele noastre dacă este necesar. Deoarece citirea este aproape întotdeauna mai rapidă în mysql decât scrierea, actualizăm doar dacă este absolut necesar.
# Proprietățile sunt legate doar de o singură zonă de dezvoltare
protected function handle_property_meta_changes( $post_id = 0, $data = array() ) {
# Obține orice zonă de dezvoltare asociată în prezent
$linked_area_id = $this->get_property_linked_area_id( $post_id );
if ( empty($linked_area_id) ) {
$linked_area_id = 0;
}
if ( array_key_exists( 'development_area_id', $data ) && !empty($data['development_area_id'] ) ) {
$received_area_id = (int)$data['development_area_id'];
} else {
$received_area_id = 0;
}
if ( $received_area_id != $linked_area_id ) {
# Aceasta va suprascrie orice și toate copiile existente ale cheii noastre meta
# astfel încât să ne asigurăm că avem doar o singură zonă de dezvoltare legată per proprietate
update_post_meta( $post_id, '_development_area_id', $received_area_id );
}
}
}
Cum să Folosiți Clasa
Cu condiția să încărcați clasa în fișierul de funcții al temei sau într-un plugin, puteți folosi următoarele pentru a face lucrurile să funcționeze:
if ( is_admin() ) {
new One_To_Many_Linker();
}
Câteva Cazuri de Utilizare Mai jos, am furnizat câteva cazuri de utilizare pentru front-end.
- Afișarea tuturor proprietăților pentru zona de dezvoltare curentă
- Afișarea zonei de dezvoltare pentru o proprietate pe o arhivă sau o proprietate individuală
Afișarea tuturor proprietăților legate de zona de dezvoltare afișată în prezent
global $wp_query;
$area_id = $wp_query->get_queried_object_id();
$args = array(
'post_type' => 'property',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => '_development_area_id',
'value' => $area_id,
'compare' => '=',
'type' => 'NUMERIC'
)
)
);
$properties = new \WP_Query( $args );
if ( $properties->have_posts() ) {
while( $properties->have_posts() ) {
$property = $properties->next_post();
# fă ceva cu proprietatea
$property_link = get_permalink( $property->ID );
$property_name = esc_attr( $property->post_title );
echo '<a href="' . $property_link . '">' . $property_name . '</a>';
}
}
Afișarea Zonelor de Dezvoltare Legate
Metoda 1: Preluarea meta postului, încărcarea zonei și utilizarea datelor
- funcționează pe paginile unde is_singular('property') este adevărat
- funcționează pe paginile unde is_post_type_archive('property') este adevărat
global $post;
while ( have_posts() ) {
the_post();
$post_id = get_the_ID(); # s-ar putea folosi $post->ID
$dev_area_id = get_post_meta( $post_id, '_development_area_id', true);
if ( !empty( $dev_area_id ) ) {
$development_area = get_post( $dev_area_id );
# fă ceva...
$dev_area_link = get_permalink ( $development_area->ID );
$dev_area_title = $development_area->post_title;
$dev_area_content = $development_area->post_content;
echo '<a href="' . $dev_area_link . '">' . $dev_area_title . '</a><br />' . $dev_area_content;
}
}
Metoda 2: Utilizarea unui filtru de interogare
- funcționează pe paginile unde is_singular('property') este adevărat
- funcționează pe paginile unde is_post_type_archive('property') este adevărat
Observați că acest lucru evită necesitatea de a prelua meta tipului de postare și de a face o interogare suplimentară pentru datele zonei de dezvoltare. Pe măsură ce afișați mai multe proprietăți pe o singură pagină, acest lucru vă economisește din ce în ce mai multă putere de procesare.
Unde să plasați următorul cod:
- Într-un nou plugin pe care îl creați recomandat
- În fișierul functions.php al temei
add_filter('posts_clauses', 'do_my_maybe_modify_queries', 10, 2);
function do_my_maybe_modify_queries( $pieces, \WP_Query $wp_query ) {
if ( !is_admin() && $wp_query->is_main_query() ) {
# Nu suntem în panourile de administrare și suntem interogarea principală pentru șablon
if ( array_key_exists('post_type', $wp_query->query_vars) ) {
# A fost interogat un tip de postare.
# Preluăm-l ca un array al tipurilor specificate
$value = $wp_query->query_vars['post_type'];
if ( !is_array( $value ) ) {
if ( empty( $value ) ) {
$post_types = array();
} else {
$post_types = array( $value );
}
} else {
$post_types = $value;
}
if ( in_array('property', $post_types) ) {
# Am fost întrebați pentru o proprietate
if ( $wp_query->is_post_type_archive || $wp_query->is_singular ) {
# Afișăm arhiva tipului de postare property sau o proprietate singulară.
# Vrem să adăugăm ID-ul, titlul și conținutul zonei de dezvoltare la câmpurile returnate
global $wpdb;
# Legă postarea de dezvoltare la fiecare proprietate prin cheia sa postmeta
# Deoarece există doar 1 zonă de dezvoltare per proprietate, acest lucru funcționează bine
$pieces['join'] .= <<<SQL
LEFT JOIN {$wpdb->prefix}postmeta AS dev_pm ON {$wpdb->prefix}posts.ID = dev_pm.post_id AND dev_pm.meta_key = '_development_area_id'
LEFT JOIN {$wpdb->prefix}posts AS dev_post ON dev_post.ID = dev_pm.meta_value
SQL;
# Adaugă câmpurile dorite ale postării de dezvoltare la cele returnate de interogare
$pieces['fields'] .= ", IFNULL( dev_pm.meta_value, 0 ) as development_area_id";
$pieces['fields'] .= ", IFNULL( dev_post.post_title, '') as development_area_title";
$pieces['fields'] .= ", IFNULL( dev_post.post_content, '') as development_area_content";
}
}
}
}
return $pieces;
}
Cu cele de mai sus în loc, puteți accesa datele după cum urmează pe o pagină de proprietate singulară sau arhivă de proprietăți. Las ca exercițiu pentru cititor să facă acest lucru pentru taxonomiile legate de proprietăți.
if ( have_posts() ) {
global $post;
while ( have_posts() ) {
the_post();
if ( property_exists( $post, 'development_area_id' ) ) {
$dev_area_id = $post->development_area_id;
$dev_area_title = $post->development_area_title;
$dev_area_content = $post->development_area_content;
$dev_area_link = get_permalink( $dev_area_id );
echo '<a href="' . $dev_area_link . '">' . $dev_area_title . '</a><br />' . $dev_area_content;
}
}
}
Metoda 3: Filtrul WP_Query
Similar cu metoda filtrului de mai sus, dar utilizabilă pentru interogări personalizate folosind WP_Query. Excelent dacă doriți să scrieți un shortcode sau un widget care afișează o serie de proprietăți.
Mai întâi, creăm filtrul nostru (foarte similar cu cel afișat la metoda 2)
function add_dev_data_to_wp_query( $pieces, \WP_Query $wp_query ) {
global $wpdb;
if ( !is_admin() && !$wp_query->is_main_query() ) {
# Legă postarea de dezvoltare la fiecare proprietate prin cheia sa postmeta
# Deoarece există doar 1 zonă de dezvoltare per proprietate, acest lucru funcționează bine
$pieces['join'] .= <<<SQL
LEFT JOIN {$wpdb->prefix}postmeta AS dev_pm ON {$wpdb->prefix}posts.ID = dev_pm.post_id AND dev_pm.meta_key = '_development_area_id'
LEFT JOIN {$wpdb->prefix}posts AS dev_post ON dev_post.ID = dev_pm.meta_value
SQL;
# Adaugă câmpurile dorite ale postării de dezvoltare la cele returnate de interogare
$pieces['fields'] .= ", IFNULL( dev_pm.meta_value, 0 ) as development_area_id";
$pieces['fields'] .= ", IFNULL( dev_post.post_title, '') as development_area_title";
$pieces['fields'] .= ", IFNULL( dev_post.post_content, '') as development_area_content";
}
return $pieces;
}
Apoi, aplicăm filtrul nostru înainte de a crea interogarea și îl eliminăm imediat după pentru a ne asigura că nu alterăm alte interogări.
$args = array(
'post_type' => 'property',
'posts_per_page' => -1
);
# Aplică filtrul nostru
add_filter('posts_clauses', 'add_dev_data_to_wp_query', 10, 2);
# Rulează interogarea
$properties = new \WP_Query( $args );
# Elimină filtrul nostru
remove_filter('posts_clauses', 'add_dev_data_to_wp_query', 10);
if ( $properties->have_posts() ) {
while( $properties->have_posts() ) {
$property = $properties->next_post();
# Fă ceva cu proprietățile tale
echo '<p>Numele proprietății este ' . $property->post_title . '</p>';
# Fă ceva cu zonele de dezvoltare asociate dacă aceste date sunt disponibile
if ( property_exists( $property, 'development_area_id' ) ) {
echo '<p>Parte din zona de dezvoltare numită ' . $property->development_area_title . '</p>';
}
}
}
Deși necesită puțin mai multă muncă la început pentru a face lucruri de genul acesta, oferă câteva beneficii frumoase
- Faceți doar 1 interogare în loc de 2-3 interogări pentru a afișa informații asociate. Acest lucru se adună rapid.
- Odată ce vă simțiți confortabil, puteți utiliza cu relații la mai multe obiecte care vă vor economisi mai multe interogări suplimentare fiecare
- Folosind o variație a acestui lucru, puteți face același lucru pentru a adăuga titluri (și link-uri de vizualizare și editare) la coloanele de listare din editor. De exemplu, cu scenariul de mai sus, ați putea lista dezvoltarea pentru fiecare proprietate în lista de proprietăți din admin.
Desigur, acest lucru este deja suficient de lung.

Puteți utiliza minunatul și gratuitul plugin Advanced Custom Fields, care vă permite să creați un câmp personalizat de tip Relationship, care relaționează un articol cu altul.
În orice caz, mai întâi ar trebui să vă asigurați că chiar aveți nevoie de două tipuri de postări personalizate și că nu puteți utiliza taxonomii sau doar câmpuri personalizate. De exemplu, o Proprietate individuală poate fi un tip de postare, iar Zona de Dezvoltare căreia îi aparține poate fi o Taxonomie sau un Câmp Personalizat care conține numele acelei Zone de Dezvoltare... Practic, depinde de cât de multe informații trebuie să păstrați despre Zonele de Dezvoltare și Proprietăți...

Aș ține lucrurile simple și aș face ca proprietățile găsite în cadrul unui proiect imobiliar să fie posturi copil ale postului părinte al proiectului. De asemenea, ai putea folosi taxonomia standard de categorii și a marca fiecare proprietate "sub" ca parte a proiectului mai mare. Ai putea introduce aceste proiecte direct ca categorii în taxonomie. În acest fel, păstrezi toate proprietățile într-o listă lungă și ușor de gestionat. Poți include sau exclude orice ai nevoie când apelezi această listă folosind parametrul de adâncime (depth) sau taxonomia.

Mulțumesc pentru ajutor, cum aș putea face ca proprietățile găsite în cadrul unui dezvoltare să devină postări copil ale postării părinte de dezvoltare?

Mai întâi creează pagina/postarea părinte (dezvoltarea). Apoi adaugă o pagină nouă și în partea dreaptă a editorului ar trebui să vezi o casetă de atribute ale paginii, unde unul dintre câmpuri se numește Părinte cu un meniu derulant. Folosește acel meniu pentru a selecta dezvoltarea. Asta ar trebui să fie suficient!
