Changeset 3726427
- Timestamp:
- 10/03/2026 03:26:42 PM (2 weeks ago)
- Location:
- omnidirectory/trunk
- Files:
-
- 2 edited
-
omnidirectory.php (modified) (28 diffs)
-
readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
omnidirectory/trunk/omnidirectory.php
r3717915 r3726427 1 1 <?php 2 2 /** 3 * Plugin Name: OmniDirectory 3 * Plugin Name: OmniDirectory – Directory & Listings 4 4 * Plugin URI: https://wordpress.org/plugins/omnidirectory/ 5 * Description: A universal, multi-purpose directory plugin — adaptable for Doctors, Lawyers, Companies, Clergy, or any professional listing. Registers an "omni_member" post type with admin-configurable field labels, a live AJAX search shortcode [omnidirectory_search], language-auto-detecting search, and Polylang/WPML compatibility.6 * Version: 4.3.3 15 * Description: A flexible WordPress directory plugin for staff, member, and business listings, with multilingual, Arabic-aware search and RTL support. 6 * Version: 4.3.32 7 7 * Author: Kerio 8 8 * Text Domain: omnidirectory … … 256 256 const IMAGE_MAX_DOWNLOAD_BYTES = 5242880; // 5 MB. 257 257 258 const VERSION = '4.3.3 1'; // single source of truth for enqueue versioning258 const VERSION = '4.3.32'; // single source of truth for enqueue versioning 259 259 260 260 /** … … 1488 1488 */ 1489 1489 private function get_import_column_aliases() { 1490 return array( 1491 'name' => array( 'title', 'full name', 'member name' ), 1492 'category' => array( 'categories', 'directory category' ), 1493 'field1' => array( 'field 1', 'specialty', 'specialty / title', 'job title', 'title / specialty' ), 1494 'field2' => array( 'field 2', 'location', 'location / office', 'address' ), 1495 'field3' => array( 'field 3' ), 1496 'field4' => array( 'field 4' ), 1497 'field5' => array( 'field 5' ), 1498 'phone' => array( 'phone', 'phone number', 'telephone' ), 1499 'email' => array( 'email address' ), 1500 'bio' => array( 'biography', 'description', 'content' ), 1501 'gallery_shortcode' => array( 'gallery' ), 1502 'featured_image' => array( 'image', 'photo', 'photo url', 'image url', 'avatar' ), 1503 'language' => array( 'lang', 'locale' ), 1490 /** 1491 * Let a companion plugin add its own CSV header aliases (internal 1492 * key => alternate header spellings) to this same column map — 1493 * e.g. OmniDirectory Pro's own Dynamic Fields, via headers like 1494 * "dynamic_{field_id}". Empty additions by default. 1495 * 1496 * @param array<string,string[]> $aliases 1497 */ 1498 return apply_filters( 1499 'omnidirectory_csv_import_aliases', 1500 array( 1501 'name' => array( 'title', 'full name', 'member name' ), 1502 'category' => array( 'categories', 'directory category' ), 1503 'field1' => array( 'field 1', 'specialty', 'specialty / title', 'job title', 'title / specialty' ), 1504 'field2' => array( 'field 2', 'location', 'location / office', 'address' ), 1505 'field3' => array( 'field 3' ), 1506 'field4' => array( 'field 4' ), 1507 'field5' => array( 'field 5' ), 1508 'phone' => array( 'phone', 'phone number', 'telephone' ), 1509 'email' => array( 'email address' ), 1510 'bio' => array( 'biography', 'description', 'content' ), 1511 'gallery_shortcode' => array( 'gallery' ), 1512 'featured_image' => array( 'image', 'photo', 'photo url', 'image url', 'avatar' ), 1513 'language' => array( 'lang', 'locale' ), 1514 ) 1504 1515 ); 1505 1516 } … … 2897 2908 // special here, but this stays correct regardless of 2898 2909 // the exact id format). 2910 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- part of the staged import swap process (publishing phase); the result set is bounded by the current import batch size ($batch_size, 50 by default) and further bounded overall by the 1,000-row CSV validation limit enforced before any Job is created. Plugin Check retains this as a generic meta-query performance warning regardless of this bounding. 2899 2911 'meta_query' => array( 2900 2912 array( … … 2957 2969 // pattern correctly excludes only members whose key 2958 2970 // genuinely starts with this job's own prefix. 2971 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- part of the staged import swap process (retiring phase); the result set is bounded by the current import batch size ($batch_size, 50 by default) and further bounded overall by the 1,000-row CSV validation limit enforced before any Job is created. Plugin Check retains this as a generic meta-query performance warning regardless of this bounding. 2959 2972 'meta_query' => array( 2960 2973 'relation' => 'OR', … … 3451 3464 'fields' => 'ids', 3452 3465 'no_found_rows' => true, 3453 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key , WordPress.DB.SlowDBQuery.slow_db_query_meta_value-- a single, exact-match lookup on a plugin-internal meta key this method itself controls the values of (never arbitrary/unbounded), called at most once per eligible member per batch (see process_import_image_batch_cron()'s own batch_size bound).3466 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- a single, exact-match lookup on a plugin-internal meta key this method itself controls the values of (never arbitrary/unbounded), called at most once per eligible member per batch (see process_import_image_batch_cron()'s own batch_size bound). 3454 3467 'meta_key' => self::IMAGE_IMPORTED_FOR_KEY_META, 3468 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- a single, exact-match lookup on a plugin-internal meta key this method itself controls the values of (never arbitrary/unbounded), called at most once per eligible member per batch (see process_import_image_batch_cron()'s own batch_size bound). 3455 3469 'meta_value' => $member_import_key, 3456 3470 ) … … 3539 3553 ); 3540 3554 3541 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- an admin-page-load-only display query, bounded to at most this one job's own members (this plugin's own 1000-row Validation ceiling bounds that further), not an arbitrary/unbounded lookup; run only when rendering this one admin screen.3542 3555 $remaining = count( 3543 3556 get_posts( … … 3548 3561 'fields' => 'ids', 3549 3562 'no_found_rows' => true, 3563 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- an admin-page-load-only display query, bounded to at most this one job's own members (this plugin's own 1000-row Validation ceiling bounds that further), not an arbitrary/unbounded lookup; run only when rendering this one admin screen. 3550 3564 'meta_query' => array_merge( 3551 3565 array( 'relation' => 'AND' ), … … 3571 3585 ); 3572 3586 3573 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- see the identical justification immediately above.3574 3587 $failed = count( 3575 3588 get_posts( … … 3580 3593 'fields' => 'ids', 3581 3594 'no_found_rows' => true, 3595 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- see the identical justification immediately above. 3582 3596 'meta_query' => array_merge( 3583 3597 array( 'relation' => 'AND' ), … … 3854 3868 } 3855 3869 3870 /** 3871 * Filter the fully-built $meta_input array for this CSV row, 3872 * immediately before it's used to create the member post — 3873 * lets a companion plugin (e.g. OmniDirectory Pro's own 3874 * dynamic custom fields) add its own additional postmeta 3875 * values from this same row, atomically, as part of this 3876 * same wp_insert_post() call below, without this plugin 3877 * knowing anything about what those fields are. 3878 * 3879 * $row and $column_map are this row's own raw CSV cell 3880 * values and this file's own column-index-to-field-key 3881 * mapping (already built earlier in this same method) — 3882 * offered here only as read-only context a callback can use 3883 * to recognize its own columns by name; this plugin's own 3884 * CSV engine does not read, validate, or map anything on a 3885 * callback's behalf, and neither $row, $column_map, nor 3886 * $args is itself modified by this filter — only the 3887 * returned $meta_input is used. Staging, Swap, Recovery, 3888 * WP-Cron batching, and the 1,000-row import limit are 3889 * entirely unaffected: this is the exact same per-row 3890 * $meta_input this method already builds and inserts today, 3891 * merely given one more chance to gain additional keys first. 3892 * Empty by default (returns $meta_input unchanged) — a 3893 * Free-only install's import behavior is identical to before 3894 * this filter existed. 3895 * 3896 * @param array $meta_input This row's own meta_input array, built above. 3897 * @param array $row This row's own raw cell values, as returned by fgetcsv(). 3898 * @param array $column_map Column index => internal field key, built earlier in this same method. 3899 * @param array $args This call's own $args (post_status, import_key, etc.) — see this method's own docblock. 3900 */ 3901 $meta_input = apply_filters( 'omnidirectory_csv_row_meta_input', $meta_input, $row, $column_map, $args ); 3902 3856 3903 $post_id = wp_insert_post( 3857 3904 array( … … 3925 3972 'fields' => 'ids', 3926 3973 'no_found_rows' => true, 3927 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key , WordPress.DB.SlowDBQuery.slow_db_query_meta_value-- a single, exact-match lookup on a plugin-internal meta key this method itself controls the values of (never arbitrary/unbounded), used once per row per batch (at most 50 times per cron run) specifically to keep staging idempotent — see IMPORT_KEY_META's own docblock.3974 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- a single, exact-match lookup on a plugin-internal meta key this method itself controls the values of (never arbitrary/unbounded), used once per row per batch (at most 50 times per cron run) specifically to keep staging idempotent — see IMPORT_KEY_META's own docblock. 3928 3975 'meta_key' => self::IMPORT_KEY_META, 3976 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- a single, exact-match lookup on a plugin-internal meta key this method itself controls the values of (never arbitrary/unbounded), used once per row per batch (at most 50 times per cron run) specifically to keep staging idempotent — see IMPORT_KEY_META's own docblock. 3929 3977 'meta_value' => $import_key, 3930 3978 ) … … 6160 6208 $data['queried_post_id'] = $queried_post_id; 6161 6209 6210 /** 6211 * Filter to inject extra markup INSIDE each of the 8 6212 * render_profile_*() layouts' own profile info area — e.g. 6213 * OmniDirectory Pro's own Dynamic Fields — as opposed to 6214 * 'omnidirectory_single_profile_extra_fields' below, which fires 6215 * only after render_profile_by_layout() as a whole and can't 6216 * place anything inside any layout's own markup. Resolved once, 6217 * here, rather than inside each of the 8 layouts separately, so 6218 * every layout simply prints this same already-built string in 6219 * its own profile info area — see each render_profile_*() 6220 * layout's own inline comment for exactly where. Empty by 6221 * default; a Free-only install's output is unaffected. 6222 * 6223 * @param string $dynamic_fields_html Extra HTML to print inside the profile info area. Empty by default. 6224 * @param int $post_id The member whose own data this content should reflect (same post actually being displayed — see $display_post_id's own docblock above for the "?member_lang" fallback this already accounts for). 6225 * @param array $data This method's own profile data array built just above (name, field1-5, phone, email, bio, etc.) — read-only context for a callback that needs it. 6226 */ 6227 $data['dynamic_fields_html'] = apply_filters( 'omnidirectory_profile_data_fields', '', $display_post_id, $data ); 6228 6162 6229 // Enqueue the same front-end styles AND script used by the search 6163 6230 // cards so the single-post layout looks and behaves consistently … … 6203 6270 add_filter( 'safe_style_css', array( $this, 'allow_display_css_property' ) ); 6204 6271 $html .= wp_kses( $actions_html, $this->get_widget_html_allowed_tags() ); 6272 remove_filter( 'safe_style_css', array( $this, 'allow_display_css_property' ) ); 6273 } 6274 6275 /** 6276 * Filter to inject extra markup for additional, non-action 6277 * content — e.g. a companion plugin's own dynamic custom fields 6278 * — at the same shared call site as 6279 * 'omnidirectory_single_profile_actions' above (once, after 6280 * whichever of the eight render_profile_*() layouts was used), 6281 * but kept as its own, separately-named filter rather than 6282 * overloading that one: that filter's own name and existing 6283 * documented purpose is specifically "actions" (Print/Share-style 6284 * buttons), not arbitrary profile content, and this plugin has no 6285 * dynamic-fields concept of its own to render here — only 6286 * OmniDirectory Pro (or another companion plugin) does. Empty by 6287 * default; a Free-only install's output here is unaffected, and 6288 * this plugin's own 8 render_profile_*() layouts are not touched 6289 * at all to support this — see this method's own docblock above 6290 * for why this one shared call site, not each layout 6291 * individually, is where both of these filters live. 6292 * 6293 * @param string $extra_fields_html Extra HTML to render after the profile. Empty by default. 6294 * @param int $post_id The post whose own data this content should reflect — the ORIGINAL queried post (see $actions_html's own docblock above for why this isn't necessarily $data['post_id']). 6295 * @param array $data The same profile data array this method's own render_profile_by_layout() call just used (name, field1-5, phone, email, bio, etc.) — offered here only as read-only context for a callback that needs it; this plugin does not itself add anything to it. 6296 */ 6297 $extra_fields_html = apply_filters( 'omnidirectory_single_profile_extra_fields', '', $queried_post_id, $data ); 6298 6299 if ( '' !== $extra_fields_html ) { 6300 // Same trust model and same reasoning as $actions_html just 6301 // above — see that block's own comment. 6302 add_filter( 'safe_style_css', array( $this, 'allow_display_css_property' ) ); 6303 $html .= wp_kses( $extra_fields_html, $this->get_widget_html_allowed_tags() ); 6205 6304 remove_filter( 'safe_style_css', array( $this, 'allow_display_css_property' ) ); 6206 6305 } … … 7261 7360 <?php endif; ?> 7262 7361 7263 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['email'] ) : ?>7362 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['email'] || ! empty( $data['dynamic_fields_html'] ) ) : ?> 7264 7363 <div class="omni-profile-details"> 7265 7364 <?php if ( $data['field2'] ) : ?> … … 7308 7407 </div> 7309 7408 <?php endif; ?> 7409 <?php if ( ! empty( $data['dynamic_fields_html'] ) ) : ?> 7410 <?php echo wp_kses( $data['dynamic_fields_html'], $this->get_widget_html_allowed_tags() ); ?> 7411 <?php endif; ?> 7310 7412 </div> 7311 7413 <?php endif; ?> … … 7400 7502 </div> 7401 7503 <?php endif; ?> 7504 <?php if ( ! empty( $data['dynamic_fields_html'] ) ) : ?> 7505 <?php echo wp_kses( $data['dynamic_fields_html'], $this->get_widget_html_allowed_tags() ); ?> 7506 <?php endif; ?> 7402 7507 </aside> 7403 7508 … … 7452 7557 </div> 7453 7558 7454 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['phone'] || $data['email'] ) : ?>7559 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['phone'] || $data['email'] || ! empty( $data['dynamic_fields_html'] ) ) : ?> 7455 7560 <div class="omni-profile-hero-cards"> 7456 7561 <?php if ( $data['field2'] ) : ?> … … 7508 7613 </div> 7509 7614 <?php endif; ?> 7615 <?php if ( ! empty( $data['dynamic_fields_html'] ) ) : ?> 7616 <?php echo wp_kses( $data['dynamic_fields_html'], $this->get_widget_html_allowed_tags() ); ?> 7617 <?php endif; ?> 7510 7618 </div> 7511 7619 <?php endif; ?> … … 7557 7665 $id_gallery = 'omni-tab-gallery-' . $post_suffix; 7558 7666 $id_documents = 'omni-tab-documents-' . $post_suffix; 7559 $has_contact_info = ( $data['field1'] || $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['phone'] || $data['email'] );7667 $has_contact_info = ( $data['field1'] || $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['phone'] || $data['email'] || ! empty( $data['dynamic_fields_html'] ) ); 7560 7668 ob_start(); 7561 7669 ?> … … 7668 7776 </div> 7669 7777 <?php endif; ?> 7778 <?php if ( ! empty( $data['dynamic_fields_html'] ) ) : ?> 7779 <?php echo wp_kses( $data['dynamic_fields_html'], $this->get_widget_html_allowed_tags() ); ?> 7780 <?php endif; ?> 7670 7781 </div> 7671 7782 <?php endif; ?> 7672 <?php if ( ! $data['phone'] && ! $data['email'] ) : ?>7783 <?php if ( ! $data['phone'] && ! $data['email'] && empty( $data['dynamic_fields_html'] ) ) : ?> 7673 7784 <p class="omni-profile-tab-empty"><?php esc_html_e( 'No contact information provided.', 'omnidirectory' ); ?></p> 7674 7785 <?php endif; ?> … … 7725 7836 ); 7726 7837 ?> 7727 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['phone'] || $data['email'] ) : ?>7838 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['phone'] || $data['email'] || ! empty( $data['dynamic_fields_html'] ) ) : ?> 7728 7839 <div class="omni-profile-minimalist-rows"> 7729 7840 <?php foreach ( $minimalist_rows as $row ) : ?> … … 7747 7858 </div> 7748 7859 <?php endif; ?> 7860 <?php if ( ! empty( $data['dynamic_fields_html'] ) ) : ?> 7861 <?php echo wp_kses( $data['dynamic_fields_html'], $this->get_widget_html_allowed_tags() ); ?> 7862 <?php endif; ?> 7749 7863 </div> 7750 7864 <?php endif; ?> … … 7794 7908 </div> 7795 7909 <div class="omni-profile-splitcover-right"> 7796 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['phone'] || $data['email'] ) : ?>7910 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['phone'] || $data['email'] || ! empty( $data['dynamic_fields_html'] ) ) : ?> 7797 7911 <div class="omni-profile-details"> 7798 7912 <?php if ( $data['field2'] ) : ?> … … 7850 7964 </div> 7851 7965 <?php endif; ?> 7966 <?php if ( ! empty( $data['dynamic_fields_html'] ) ) : ?> 7967 <?php echo wp_kses( $data['dynamic_fields_html'], $this->get_widget_html_allowed_tags() ); ?> 7968 <?php endif; ?> 7852 7969 </div> 7853 7970 <?php endif; ?> … … 7935 8052 </div> 7936 8053 <?php endif; ?> 8054 <?php if ( ! empty( $data['dynamic_fields_html'] ) ) : ?> 8055 <?php echo wp_kses( $data['dynamic_fields_html'], $this->get_widget_html_allowed_tags() ); ?> 8056 <?php endif; ?> 7937 8057 </div> 7938 8058 … … 7981 8101 <?php echo wp_kses( $this->render_language_switcher( $data['languages'] ), $this->get_widget_html_allowed_tags() );?> 7982 8102 7983 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['email'] ) : ?>8103 <?php if ( $data['field2'] || $data['field3'] || $data['field4'] || $data['field5'] || $data['email'] || ! empty( $data['dynamic_fields_html'] ) ) : ?> 7984 8104 <div class="omni-profile-details"> 7985 8105 <?php if ( $data['field2'] ) : ?> … … 8028 8148 </div> 8029 8149 <?php endif; ?> 8150 <?php if ( ! empty( $data['dynamic_fields_html'] ) ) : ?> 8151 <?php echo wp_kses( $data['dynamic_fields_html'], $this->get_widget_html_allowed_tags() ); ?> 8152 <?php endif; ?> 8030 8153 </div> 8031 8154 <?php endif; ?> -
omnidirectory/trunk/readme.txt
r3717915 r3726427 1 === OmniDirectory ===1 === OmniDirectory – Directory & Listings === 2 2 Contributors: soulja, kerio 3 Tags: directory, staff directory, team directory, search, multilingual3 Tags: directory, business directory, member directory, staff directory, multilingual 4 4 Requires at least: 6.0 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 4.3.3 17 Stable tag: 4.3.32 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 A multi-purpose directory plugin with bilingual Arabic/English search, multiple layouts, document uploads, and click-to-reveal privacy protection.11 A flexible WordPress directory plugin for staff, member, and business listings — with multilingual and Arabic/RTL search. 12 12 13 13 == Description == 14 14 15 OmniDirectory registers a "Directory Members" custom post type and lets you build a searchable staff/member directory that adapts to almost any profession — doctors, lawyers, consultants, clergy, companies — just by relabeling three custom fields from Directory Settings. 16 17 **Core features:** 18 19 * Custom post type ("Directory Members") with a photo, three admin-configurable fields (e.g. Specialty/Title, Location/Office, Phone), Email, Bio, and an optional "Directory Category" taxonomy. 20 * Live AJAX search shortcode `[omnidirectory_search]`, restricted to matching Title and Directory Category (no false-positive matches from unrelated meta/content). 21 * Built-in Arabic + English search: an Arabic search term automatically matches Arabic listings and an English term matches English listings, with Arabic letter-variant normalization (e.g. "احمد" and "أحمد" match each other) baked in, and full RTL support throughout. 22 * Eight search-results layouts: Modern Grid Cards, Horizontal List Row, Avatar Badge Compact, Structured Table View, Masonry Pinterest Grid, Horizontal Directory List, Compact Table Matrix, and Interactive Hover Flip Cards — switchable from Directory Settings. 23 * Eight single-profile layouts: Modern Centered Card, Two-Column Sidebar, Hero Banner Top, Tabbed Profile View, Minimalist Clean, Split Screen Cover, Interactive Dashboard, and Floating Card Overlay. 24 * Attachments / Documents: attach PDF, Word, Excel, or plain-text files to a member's profile, with an optional custom label per file, shown as a labeled download card on their profile. 25 * Click-to-Reveal privacy option: optionally mask phone numbers and email addresses behind a "Click to show" button, to deter basic spam-harvesting bots. 26 * Category filter dropdown, a primary accent color picker, an "hide empty fields" display toggle, and a CSV Import/Export module for bulk-adding or backing up your directory. 27 * Polylang and WPML compatible: the post type, taxonomy, and translatable fields are registered for both, with a `wpml-config.xml` file included for WPML. 15 = Overview: What Is OmniDirectory? = 16 17 OmniDirectory is one flexible directory engine for building searchable WordPress directories — staff, members, professionals, businesses, organizations, and more. Every entry shares the same underlying structure: a name, a set of relabelable directory fields, a category, and a profile page, presented through your choice of search and profile layouts. 18 19 Rather than offering separate tools for each type of directory, OmniDirectory adapts to your use case by relabeling its existing fields and choosing appropriate layouts — no code required. 20 21 = Directory Use Cases = 22 23 The same directory engine can be adapted for: 24 25 * Staff Directory 26 * Employee Directory 27 * Team Directory 28 * Member Directory 29 * Professional Directory 30 * Business Directory (can be adapted for) 31 * Company Directory (can be adapted for) 32 * Organization Directory (can be adapted for) 33 * Institution Directory (can be adapted for) 34 * Medical Directory 35 * School / University Directory (can be adapted for) 36 * Church Directory (can be adapted for) 37 * Nonprofit / Community Directory (can be adapted for) 38 * Multilingual Directory 39 * Arabic / RTL Directory 40 41 These are use cases of one directory engine, reached by relabeling fields and choosing a layout — not separate built-in modes with specialized fields for each industry. 42 43 = Search and Discovery = 44 45 Add the `[omnidirectory_search]` shortcode to any page for a live, AJAX-powered search box — results appear as visitors type, without a page reload. 46 47 By default, search matches an entry's name/title and its Directory Category. Other fields (Phone, Email, Bio, etc.) are not searched by default; a developer can extend what's searched using this plugin's own filter hooks. Live search results are cached briefly and invalidated immediately whenever a directory entry changes, and basic AJAX rate limiting helps reduce the impact of automated request flooding. Results are capped to a configurable limit rather than paginated. 48 49 = Arabic, Multilingual and RTL Support = 50 51 Search includes real Arabic-aware normalization: common letter variations (different forms of Alef, ta marbuta versus ha, and similar cases) are matched during search, so visitors find listings even when spelling varies slightly. Every layout renders with the correct `dir="rtl"` or `dir="ltr"` attribute based on the site's active language. 52 53 OmniDirectory registers its directory entries and category taxonomy as translatable for both **Polylang** and **WPML** (a `wpml-config.xml` file is included), so multilingual sites can maintain a translated version of each entry and category. This covers the directory structure and manual entry translation; it does not mean every workflow is automatically language-aware — CSV-imported entries, for example, are not automatically linked into a translation group (see the CSV section below). 54 55 = Adaptable Fields = 56 57 OmniDirectory has a fixed set of built-in directory fields, and their labels can be relabeled from Settings — without code — to fit different use cases. For example: 58 59 **Medical directory:** Specialty / Location / Phone 60 **Company directory:** Department / Office / Phone 61 **Professional directory:** Profession / Location / Phone 62 **Member directory:** Role / Location / Phone 63 64 Relabeling changes what visitors see; it does not add new fields. OmniDirectory does not currently include a tool for creating unlimited or arbitrary custom fields. 65 66 = Search-Result Layouts = 67 68 Eight layouts for search results, switchable from Settings: 69 70 * Modern Grid Cards (`grid`) — default 71 * Horizontal List Row (`horizontal`) 72 * Avatar Badge Compact (`badge`) 73 * Structured Table View (`table`) 74 * Masonry Pinterest Grid (`masonry`) 75 * Horizontal Directory List (`directory_list`) 76 * Compact Table Matrix (`table_matrix`) 77 * Interactive Hover Flip Cards (`flip_cards`) 78 79 = Single-Profile Layouts = 80 81 Eight layouts for individual profile pages, switchable from Settings: 82 83 * Modern Centered Card (`modern_card`) — default 84 * Two-Column Sidebar (`two_column_sidebar`) 85 * Hero Banner Top (`hero_banner_top`) 86 * Tabbed Profile View (`tabbed_profile`) 87 * Minimalist Clean (`minimalist_clean`) 88 * Split Screen Cover (`split_screen_cover`) 89 * Interactive Dashboard (`interactive_dashboard`) 90 * Floating Card Overlay (`floating_card_overlay`) 91 92 = Profile Information and Documents = 93 94 Each directory entry can include a name, a profile photo, its relabelable directory fields, Phone, Email, a Bio, a Website field, a Directory Category, and a gallery shortcode field (stores and renders a shortcode you provide — this is not a built-in gallery builder). 95 96 Profiles can also have Documents attached: PDF, DOC, DOCX, XLS, XLSX, and TXT files, each with its own custom label, and checked against its real file type on the server before being accepted. 97 98 = Click-to-Reveal Contact Details = 99 100 Optionally keep phone numbers and email addresses out of the initial page source until the visitor clicks Reveal. This is a lightweight deterrent against basic automated scanning of static HTML — it is not anti-scraping or bot-protection software, and does not stop a scraper that fully executes JavaScript. 101 102 = CSV Import and Reliability = 103 104 Bulk-add or back up your directory with CSV import/export. Column headers are matched automatically against common naming variants. The entire file — row count, header, and every row — is validated before anything changes; an invalid or oversized file (over 1,000 rows) is rejected up front, with nothing changed. 105 106 Import then runs as a resumable background process: new entries are staged privately first, then safely swapped in to replace the previous directory only once every new entry is fully ready — with a "Resume Import" option if it ever needs a manual nudge. Featured images referenced in the CSV are downloaded afterward, independently, in the background, using WordPress's own safe HTTP request handling (which rejects unsafe destinations such as private/internal network addresses and their redirects), with a bounded number of retries for any image that fails. 107 108 **Important:** CSV import replaces your directory's dataset — it does not selectively update individual existing entries. Re-importing a file results in a fresh set of entries matching that file, not in-place edits to the entries already there. 109 110 = Structured Data = 111 112 Profile pages include Schema.org (JSON-LD) structured data using the `Person` type by default, to help search engines understand the page. Developers can extend or change this behavior using this plugin's WordPress filters; there is no dashboard setting for configuring Schema.org types. 113 114 = Performance and Reliability = 115 116 * Live search results are cached, with immediate invalidation when a directory entry changes. 117 * Live search includes basic AJAX rate limiting. 118 * CSV import runs in the background, in small resumable batches, rather than one long page request. 119 * The whole file is validated before any existing directory entry is changed. 120 * New entries are staged privately, then safely swapped in once staging is complete. 121 * Featured images from CSV import are downloaded in the background, separately from the import itself, with bounded retries for failures. 122 123 = Who Is OmniDirectory For? = 124 125 Site owners and developers building a searchable directory of people or listings — staff and team pages, member directories, professional or business listings, and multilingual or Arabic-language directories — who want one adaptable plugin rather than a tool built narrowly for a single industry. 28 126 29 127 = Getting Started = 30 128 31 129 1. Activate the plugin. 32 2. Go to **Directory Members → Directory Settings** to relabel the custom fields for your use case (e.g. "Specialty / Title" for a medical practice, "Practice Area" for a law firm) and choose your preferred search/profile layouts.33 3. Add members under **Directory Members → Add New**, or bulk-import them via **Directory Members → Import / Export**.130 2. Go to **Directory Members → Directory Settings** to relabel the directory fields for your project and choose your search and profile layouts. 131 3. Add entries under **Directory Members → Add New**, or bulk-import them via **Directory Members → Import / Export**. 34 132 4. Place the `[omnidirectory_search]` shortcode on any page to show the live search box. 35 133 … … 52 150 53 151 1. Visit **Directory Members → Directory Settings** to configure field labels, layouts, and colors. 54 2. Add your directory members under **Directory Members → Add New**.152 2. Add your directory entries under **Directory Members → Add New**. 55 153 3. Add the `[omnidirectory_search]` shortcode to any page or post where you want the search box to appear. 56 154 57 155 == Frequently Asked Questions == 58 156 59 = Can I use this for something other than a clergy or medical directory? = 60 61 Yes. The three custom fields (default labels: Specialty/Title, Location/Office, Phone Number) can be relabeled from Directory Settings to fit any profession — law firms, consultancies, companies, or any other member/staff directory. 62 63 = Does the search work in Arabic? = 64 65 Yes. The search automatically detects whether the typed term is Arabic or English (based on the characters typed, regardless of the site's active language) and matches accordingly, including common Arabic letter variations (e.g. different Alef forms, ta marbuta vs. ha). 66 67 = Is it compatible with Polylang or WPML? = 68 69 Yes. The custom post type and Directory Category taxonomy are registered as translatable for both plugins, and a `wpml-config.xml` file is included with the plugin to declare which custom fields WPML should treat as translatable. 70 71 = What file types can I attach to a member's profile? = 72 73 PDF, Word (.doc/.docx), Excel (.xls/.xlsx), and plain text (.txt). Every uploaded file's actual MIME type is validated against this list on the server, not just restricted in the browser's file picker. 74 75 = How do I bulk-add members instead of entering them one at a time? = 76 77 Go to **Directory Members → Import / Export** and upload a .csv file. Column headers are matched automatically against common naming variants (e.g. "Title" or "Full Name" both map to Name), so you don't need to match this plugin's exact export format exactly. 78 79 = Does click-to-reveal fully hide phone/email from all bots? = 80 81 It's a lightweight deterrent (similar to common "email cloaking" plugins), not a guarantee: the real value is kept out of the page's initial HTML and only inserted after a visitor clicks. It's designed to stop basic scrapers that scan static HTML for phone/email patterns, not a bot that fully executes JavaScript and specifically targets this markup. 157 = What can I build with OmniDirectory? = 158 159 A searchable WordPress directory of people or listings — staff, team members, professionals, businesses, organizations, or a multilingual/Arabic directory — by relabeling the built-in fields and choosing a layout. 160 161 = Is OmniDirectory only for staff directories? = 162 163 No. Staff directories are one common use case, but the same fields and layouts can be relabeled for member, professional, business, or other directory types. 164 165 = Can I create a business directory? = 166 167 Yes, by adapting the existing directory fields and category taxonomy to your business listings. 168 169 = Can I create a member directory? = 170 171 Yes. You can adapt the existing fields and categories for member listings. 172 173 = Can I create a professional directory? = 174 175 Yes — the default field labels are already suited to professional/medical-style listings. 176 177 = Can I create an employee directory? = 178 179 Yes. 180 181 = Can I create a medical directory? = 182 183 Yes, the default field labels (Specialty/Title, Location/Office, Phone) are designed with this in mind. 184 185 = Can I create a school or university directory? = 186 187 You can adapt OmniDirectory for this by relabeling its existing fields; there are no fields built specifically for academic departments or courses. 188 189 = Can I create an Arabic/RTL directory? = 190 191 Yes. Search includes Arabic letter-variant normalization, and every layout renders with the correct RTL/LTR direction based on the site's language. 192 193 = Does OmniDirectory support WPML? = 194 195 Yes. The directory post type and category taxonomy are registered as translatable, and a `wpml-config.xml` file is included. 196 197 = Does OmniDirectory support Polylang? = 198 199 Yes. The directory post type and category taxonomy are registered with Polylang automatically. 200 201 = Can I customize directory field labels? = 202 203 Yes, from Directory Settings. 204 205 = Can I add unlimited custom fields? = 206 207 You can relabel OmniDirectory's existing directory fields to fit different use cases without writing any code. It does not currently include a tool for adding new, unlimited custom fields beyond that fixed set. 208 209 = Can I customize the directory layouts? = 210 211 Yes — eight search-results layouts and eight single-profile layouts are available from Directory Settings. 212 213 = Can I import directory entries from CSV? = 214 215 Yes, with automatic column mapping, a 1,000-row limit per file, and background processing. 216 217 = Does CSV import update existing entries? = 218 219 No. CSV import replaces your directory's dataset with what's in the file; it does not selectively edit individual existing entries in place. 220 221 = Can I attach documents to profiles? = 222 223 Yes — PDF, DOC, DOCX, XLS, XLSX, and TXT files, each with an optional custom label. 224 225 = Can I hide phone/email values until clicked? = 226 227 Yes, using the Click-to-Reveal option. This keeps the value out of the initial page source until clicked; it is not anti-scraping or bot-protection software. 228 229 = Does OmniDirectory include a Gutenberg block? = 230 231 No. OmniDirectory currently uses a shortcode (`[omnidirectory_search]`), which also works inside the Block Editor's Shortcode block. 232 233 = What is OmniDirectory not designed to do? = 234 235 OmniDirectory focuses on searchable directory and listings functionality. It does not provide membership management (user registration, logins, or gated content), marketplace or e-commerce features (payments, vendor accounts, product catalogs), booking systems, reviews/ratings, or affiliate functionality. 82 236 83 237 == Screenshots == … … 88 242 89 243 == Changelog == 244 245 = 4.3.32 = 246 * Updated the plugin name and description to reflect OmniDirectory's broader positioning as an adaptable WordPress directory and listings plugin, rather than a staff-directory-only tool. No functional changes. 247 * Code quality: corrected the placement of several existing code-documentation comments (`phpcs:ignore`) so they correctly annotate the specific, already-reviewed, intentionally bounded queries used by the CSV import swap process. No query logic, behavior, or performance characteristics were changed. 248 * Added `omnidirectory_single_profile_extra_fields` extension point for companion plugins. No effect by itself. 249 * Added `omnidirectory_csv_import_aliases` extension point for companion plugins. No effect by itself. 250 * Added `omnidirectory_csv_row_meta_input` extension point for companion plugins. No effect by itself. 251 * Added `omnidirectory_profile_data_fields` extension point for companion plugins, allowing their own content to be printed inside the profile details area of all 8 single-profile layouts. No effect by itself. 90 252 91 253 = 4.3.31 =
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)