-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Welcome to the mammute_result_lookup wiki!
A standalone WordPress plugin for publication-approved participant results and certificates. It provides private administrative records and a flexible participant lookup component; it never creates a public participant directory.
- WordPress 6.5+, PHP 7.4+, MySQL/MariaDB with CREATE/ALTER and
GET_LOCKsupport. - No Composer, Node, ACF, theme edits or search plugin required at runtime.
- Elementor is optional. The native widget uses
elementor/widgets/register; tested with Elementor 3.32.0. ACF is not required; the plugin uses its own canonical data tables. - Install
dist/mammute-result-lookup-1.5.0.zipthrough Plugins → Add New → Upload, then activate. Or copy the runtime plugin folder intowp-content/plugins/. - Activate separately on each multisite site. Network activation is deliberately rejected. Storage is site-prefixed; activation does not iterate other sites.
- Use HTTPS in production. Test with the actual site's caching, proxy, theme and Elementor configuration before publishing.
Open Participant results (requires manage_options, normally administrators).
Create a record with an event key, full name and mobile number. Optional fields are
team/group, score, placement, evaluation summary, certificate identifier and a media
attachment. Score and placement are text so event-specific grading works; 0 is
preserved. Empty optional fields are omitted from the card. Summaries are plain text.
Choose an existing event/team from the editable suggestions, or type a new value. Suggestions come from saved records, are capped at 30, and narrow as you type. Teams are scoped to the selected event. Changing the event does not silently change the team. Score and placement deliberately remain text: grades and arbitrary placements are valid.
Choose a certificate with the native Media Library picker. Preview/remove controls are
inline; Use a media ID exposes the existing numeric reference (0 means none).
Evaluation prose and embedding help use expandable sections. Publication approval stays
visible and required; all result fields are public-facing, never confidential notes.
Save and add another saves without leaving the page, keeps only event/team in the current form, clears all participant-specific fields/media/approval, and focuses the next name. Save participant keeps the saved record open. No batch selections or participant details are written to browser storage. Pending saves lock the controls. Validation errors preserve entered values and focus the relevant field. A network failure can leave the write outcome unknown: check saved records before retrying. Duplicate mobiles within an event and stale edits remain server-authoritative errors. Without JavaScript, normal form submission works and validation failures retain the form; adding another retains the event through the redirect, but not the team.
The saved-participant list has an exact event filter, 20-row pagination, masked mobile numbers, a certificate-reference status and an explicit Edit participant action. File selected indicates a stored reference, not continuous file availability; the edit form and public renderer validate the actual file. On narrow screens rows become cards. Delete remains a separate disclosure with explicit permanent-delete confirmation; it removes only the result, never media.
Event keys contain 1–64 lowercase ASCII letters/digits/hyphens/underscores, beginning with a letter or digit. Each event permits one record per normalized mobile number; the same person may have a separate record in another event. Event keys are public page scopes, never passwords. There is no score threshold or participant data in code.
Open Participant results → Data management (or its shortcut above the individual form). Download a template or current data, edit it, upload once, inspect the preview, then confirm. Manual editing remains available. Bulk operations support 500 stored records per site, 500 incoming rows, and 2 MiB per file/canonical dataset. This deliberate bound permits a single recoverable request; sites above it retain the manual workflow and should use a separately planned migration/export process.
Formats:
- UTF-8 comma-separated CSV, with optional BOM. Headers must match exactly; order may
change. Export adds a BOM and an apostrophe to every nonempty value, including the
version marker. This prevents formula interpretation and protects leading zeros.
Keep the prefixes when editing exported CSV; the importer removes exactly one when
format_versionis'1. Raw templates useformat_version=1. Keep identifiers as Text in your spreadsheet editor. If it rewrites prefixes, use Excel XML instead. - Excel 2003 XML SpreadsheetML (
.xml): text cells, one contiguous worksheet. The template sets Text formatting for all columns. Save back as XML Spreadsheet 2003, keeping text cells. Empty cells and bounded cell-index gaps are supported; row gaps, merged cells, hyperlinks, formulas, DTD/entities and nontext cells are rejected. This is not XLSX. No XLS/XLSX/ZIP/macro parser or spreadsheet library is bundled. - Versioned JSON (
.json) is reserved for full data backups/restores. Its checksum detects accidental corruption, not authenticity. Use trusted plugin-generated backups.
Canonical column order (all represented as strings):
format_version,event,full_name,mobile,team,score,placement,summary,certificate_id,attachment_id,public_consent
format_version is 1; event, full_name, mobile and public_consent=1 are
required. attachment_id=0 means no certificate. Optional text may be empty; numeric
zero is retained. Score/placement remain open text, including grades. Publication
approval must be explicit in the file and in confirmation; importing cannot make
unapproved records. Mobile normalization is identical to manual entry. A phone whose
leading zero was stripped is rejected rather than guessed. Changing an event/mobile
key creates a new record: use individual editing to deliberately change an identity.
Safe merge is the only ordinary import mode. Matching is exact by normalized
(event,mobile). Missing records are created, changed matches updated, unchanged
matches not rewritten, and records absent from the file left untouched. New valid
event keys need no separate definition. Optional event scope requires every accepted
row to use that event. Every occurrence of a duplicate key in the file is rejected,
including a duplicate with another invalid field. Unknown/missing/duplicate columns,
unsupported versions, malformed CSV/XML and incompatible backups stop file processing.
Row errors are reported by spreadsheet row number without repeating private values;
the screen shows the first 20 and offers all errors as protected CSV. Preview counts
include create/update/unchanged/invalid/conflict/delete and normalization warnings.
Confirmation and recovery. The server retains the canonical plan, never trusts a browser-reconstructed dataset, and compares all stored rows/versions against preview. Any intervening manual/bulk change requires a new preview. Confirmation saves a full pre-operation recovery backup first. Valid merge rows are then applied in one InnoDB transaction; invalid/conflict rows are skipped. Unexpected execution failure rolls back all batch writes, including earlier successful rows. Each changed row uses the same Store validation and versioned write service as manual editing. Matching is preloaded once; media metadata is primed together. There is no per-row browser request or queue. The transaction pins its MySQLi connection to prevent wpdb reconnect/retry from silently resuming in autocommit. Execution checks a 20-second budget between mutations; hosting must allow normal database calls and at least a 30-second request. A lost commit reply can have an uncertain outcome: inspect current records/recovery before retrying. Completed confirmation replays are harmless; an interrupted/failed attempt is not blindly restarted. Upload again after reviewing its result and recovery snapshot.
Backup and restore. One click downloads all plugin business data across events, including approval, certificate identifiers and attachment references. Backups contain format/version/scope, canonical records and an integrity checksum. They exclude counters, nonces, name hashes, internal IDs/versions/timestamps, temporary reports and unrelated WordPress data. Events are implicit in records; there is no separate business settings model. A data backup is not a media/site backup. Back up uploads and the WordPress attachment database separately. Restores require compatible, accessible attachment IDs on the receiving site and never fetch remote files. Missing attachments must be repaired before restoring an intact backup; ordinary import rows with invalid media are rejected.
Restore always previews a full synchronization across all events: absent records are deleted, existing keys updated, missing keys created. Every input row must validate; any invalid/duplicate row blocks confirmation. An empty valid backup explicitly previews deleting all records. The confirmation text names replacement/deletion and publication. A pre-restore backup is saved before mutation. Canonical values are restored; IDs and concurrency versions are operational references and are not restored. Download the recovery backup and preview it as a restore to undo a completed operation.
Private artifacts and limits. Uploads use PHP temporary files, not the Media Library, and are deleted after parsing. One preview/report and one latest recovery snapshot per administrator/site live in non-autoloaded private WordPress options for 24 hours. A new preview replaces only the previous preview; a confirmed operation replaces the previous recovery snapshot. Download recovery first if it needs longer retention. Downloads require the owning administrator and a fresh request nonce and send no-store headers. Reports retain phase, scope, counts and update time; no spreadsheet values go to application logs. Hourly existing cleanup scans 100 option keys per run with a cursor; expired artifacts also become inaccessible on read. Deactivation stops cleanup, so expired private options may remain until reactivation/cleanup. Downloaded files become the administrator's responsibility. Rate counters and artifacts are not restored.
Bulk uploads require PHP Fileinfo; XML imports require DOM/libxml; transactional writes require MySQLi and an InnoDB result table. No engine conversion or schema migration is performed. Unsupported capabilities fail the affected bulk action; manual/public workflows remain available. Allow sufficient database packet/option storage for the bounded private plan and recovery snapshot. An artifact-write failure prevents mutation.
On a disposable fixture, run wp eval-file tests/bulk-integration.php and
node tests/bulk-browser.mjs using the existing verifier's environment variables.
The integration suite deliberately creates/restores synthetic batches and interrupts a
test database connection; never run it against production. tools/verify.py includes
both bulk suites. See docs/bulk-review.md for actual checks and deployment limits.
In any post, page or Elementor Shortcode widget:
[mammute_results event="annual-results" title="نتیجه و گواهی شما" instruction="نام، بخشی از نام، شماره همراه یا کد گواهی"]
Use the same event key as the participant records. Omitted event uses default.
With Elementor active, drag Participant results from General and set Event key,
Title and Instruction. Both entry points use the same renderer and assets.
Multiple instances and dynamically rendered Elementor content are supported.
The public component is always Persian and RTL, even when the WordPress/site/admin
locale is English. Public source messages still use WordPress's existing
mammute-result-lookup text domain; Persian source messages provide the locale-independent
fallback, with matching Persian PO/MO entries. Administration retains its existing locale.
Participant names, grades, summaries, identifiers and filenames are never translated.
title and instruction parameters remain supported for Persian copy. To satisfy the
public language requirement, overrides without Arabic-script text or containing Latin
letters fall back to the Persian default; saved page/widget content is not rewritten.
Update any intended custom headings in Persian. Event parameters and lookup behavior
are unchanged. Both shortcode and Elementor use the same renderer.
Styles are scoped to .mrl[data-mrl], with explicit resets for theme-sensitive headings,
controls, links, facts and images. The component uses a neutral light surface even inside
a dark host; it does not change body styles or the host's color scheme. Font family is
inherited; local sizing avoids dependence on unusual root font sizes. Optional local
variables are --mrl-accent (decorative top border only) and --mrl-line.
The accent may reuse Elementor's primary token; critical text/surface pairs stay fixed.
The mrl-readability cascade layer protects component colors, backgrounds, text fill,
text shadows and placeholder opacity with narrowly scoped !important declarations.
This corrects white result values on pale tiles and dark reset labels on dark surfaces,
even with late, high-specificity unlayered theme !important color rules. All nested
text, including bidi-isolated values, inherits the appropriate protected foreground.
Existing hidden/reduced-motion exceptions remain. Host fonts and outside markup are
untouched. Forced-color accessibility modes retain browser control; no opt-out is set.
Browsers without cascade-layer support use the original scoped fallback stylesheet.
User styles/extensions, important inline declarations, earlier important host layers,
or ancestor hiding/filtering can still override rendering; no embedded component can
guarantee readability against every possible external transformation.
The panel caps at 820px. Controls stack on mobile, facts become two columns at 480px, and input/button share a row at 640px. Values wrap and use bidi isolation; search and summary direction are detected from their content. No new font, icon, image or framework is downloaded. Existing assets load only when rendered, once per page.
A concise hint appears under the field. Empty/required, no-match/ambiguity, connection failure and wait-to-retry states are Persian. Ambiguity retains the same generic server response as misses, with no candidate disclosure. Query text survives recoverable errors. The loading button is disabled; its live announcement and success announcement occupy no visible blank panel. Semantic labels/status, unique IDs, keyboard focus and the local «جستجوی جدید» action work independently for multiple shortcode instances. No-script copy explains the existing JavaScript/cookie requirement.
Result facts use semantic definition-list tiles, with score emphasis but no invented maximum or grading. Evaluation text remains unchanged. Certificate images reserve their actual intrinsic aspect ratio; client load errors hide the broken preview and retain existing view/download links with Persian guidance. Records with a certificate reference but unavailable media get a neutral availability note; entirely absent optional sections are omitted. These presentation changes do not protect otherwise public WordPress media.
One server-side normalizer handles both saves and lookup:
- Persian, Arabic-Indic and Latin digits map to Latin digits.
- Iranian
09…,+989…,00989…and989…normalize to09…(11 digits). - Ordinary/Unicode spaces, hyphens and parentheses are allowed mobile separators.
Bare
9…, wrong lengths, embedded letters, slashes, repeated plus signs and mobile control characters are rejected. Phone substrings are never searched. No fuzzy matching is used. - Names collapse presentation whitespace and map Arabic
ي/كto Persianی/ک. Case, accents and zero-width non-joiners are preserved. Names must contain letters; punctuation is limited to apostrophes, right apostrophes, periods and hyphens. - Search text additionally folds ASCII case and digit variants, while preserving accents, identifier punctuation and Persian zero-width non-joiners. No external search service.
One field accepts a full name, a meaningful name fragment, mobile number or exact certificate identifier. Add a team, placement or the page's event key to narrow a name or identifier. There are no candidate lists, hints, counts, pagination or autocomplete.
The centralized Search::fields() allowlist includes names (exact or infix), mobiles
(exact canonical), certificate identifiers (exact presentation-normalized) and team,
placement/event (whole-value narrowing only). Score and evaluation prose are deliberately
not searchable. Category-only queries never reveal a result, even with one participant.
Queries need at least three Unicode characters, at most 800 UTF-8 bytes and eight whitespace-separated tokens. Each partial-name token needs three characters. All facts must match one record; multi-word categories match their complete normalized value. Name tokens may appear in any order. Certificate prefixes and phone fragments do not match. Meaningful identifier punctuation is retained literally, never used as a wildcard.
Precedence is exact certificate, exact normalized phone, exact full name, combined name/identifier/category facts, then partial names. Ties at the strongest matching level fail closed. A category match never anchors disclosure. Keep the original query and append another detail when asked; no input is persisted outside the current page. Ambiguous, excessive, unavailable and unknown results share the same helpful message.
Search is limited to the shortcode/widget's event (default if omitted). Flexible
matching supports up to 500 participants in that event. Larger events retain only the
previous indexed exact-name/mobile behavior; no truncated partial result is returned.
This bounded fallback does not support certificate or combined search. Reduce the event
scope before relying on flexible lookup for larger datasets.
Sparse existing records can be located by their available name/certificate data even without a mobile; empty optional card sections are omitted. Manual entry/import still requires mobile and publication approval under the existing canonical identity contract. There is no draft/private-status field: all saved records are publication-approved. Use the existing supported deletion workflow to withdraw a result. Missing media does not prevent matching; unavailable certificate links/previews are omitted.
No persisted search indexes or derivatives were added. Manual edits, spreadsheet imports and restores are searched immediately through the same canonical data; they require no reindexing or spreadsheet column changes. Existing custom page/widget instructions remain saved as authored; update old exact-only hints in page content.
Public POST endpoints use wp-admin/admin-ajax.php: mrl_session returns a fresh
browser-bound WordPress nonce; mrl_lookup accepts event, query and nonce.
Success is {success:true,data:{html:"…"}} with escaped allowlisted card HTML.
Lookup misses are HTTP 200 {success:false,data:{message:"…"}}; request/CSRF denial
uses 403, throttling 429, cookie creation failure 503. Failure text never identifies
which record, field or certificate exists. No mobile, internal ID, event or name hash
is included in the result card. Only full name, team, score, placement, summary,
certificate identifier and configured certificate links are displayed.
Administrative save retains the admin-post.php?action=mrl_save form contract and
redirect response. The redesigned form opts into JSON with mrl_response=json and
save_intent=add|edit: success returns confirmed ID/version/nonces, normalized identity,
next-entry state, redirect URL and escaped private listing HTML; validation errors use
HTTP 422 with the existing message and optional field key. New POST action mrl_choices
is administrator-only, nonce-protected, and accepts only event/team prefixes; it never
returns names, phones, scores or certificates. These responses must never be cached.
Knowing a name or phone number is not proof of identity. This requested lookup workflow is suitable only for results approved for public disclosure. Do not use it for confidential evaluations, identity documents or sensitive personal data. Partial-name search makes approved results easier to discover; it is not identity verification. Strong confidentiality needs authentication/OTP and protected document storage.
Records live in private ${prefix}mrl_results, not posts, postmeta or attachments.
There is no result CPT, archive, permalink, REST collection, feed, sitemap, search
index or public list. Public lookups return at most one safely resolved result. Administrative
reads/mutations require manage_options; handlers additionally verify action-specific
nonces and submitted ID/version. Complete records are written in a single statement.
A unique (event,mobile) index prevents racing duplicates. Conditional versioned
updates/deletes reject stale requests. Prepared identifiers/values are used for custom
SQL because these atomic integrity requirements cannot be met with independent
post-meta writes. SQL errors are suppressed at the database boundary and surfaced as
safe administrative messages; the plugin does not log identities or result text.
Lookup requests require same-origin POST, bounded input and a WordPress nonce tied to a random HttpOnly/SameSite=Strict browser cookie (Secure over HTTPS). Cached page HTML contains no nonce, cookie or result data. Cookie lifetime is the browser session; WordPress nonce expiry still applies. Browser forms fetch a fresh nonce each submission. Tokens/nonces are not authentication or one-use tokens. Requests from another origin, missing cookies, missing/invalid nonces or malformed arrays fail closed.
Fixed 15-minute windows admit at most 12 lookup attempts per source IP and 600 per site; session bootstrap allows 60/IP and 3,000/site. Successes and failures both count. Atomic database counters enforce concurrency limits and do not depend on object cache. The global budget is checked before creating IP buckets, bounding storage. Counters are HMAC-keyed; raw IPs are not stored. Expired counters are removed in batches of 5,000 on admitted traffic and hourly WP-Cron. With no traffic, expired entries may remain until the next run. Fixed windows allow bursts around window boundaries.
The plugin uses REMOTE_ADDR, never untrusted forwarded headers. Configure trusted
proxy address restoration at the web server. Shared NAT/proxy users share a budget;
distributed requests can exhaust the global budget. Add edge/WAF abuse controls for
large or high-risk events. This bounded baseline reduces enumeration; it cannot
prevent a distributed attacker from guessing known names or mobile numbers.
Certificates remain public media. Only existing local JPEG/PNG/WebP/PDF attachments inside the real uploads directory are accepted, with MIME/extension, file existence, confinement and attachment checks. Administrators must be able to edit the attachment. Missing/deleted/invalid files are omitted without breaking results. SVG and remote-only media are unsupported. Images preview inline; PDFs open in the browser. Open and download links use the public media URL, not an authorization token. CDN/cross-origin URLs may ignore the HTML download hint; open the file and use the browser's Save As. File names, attachment metadata, WordPress media REST visibility, CDN copies and previously shared URLs are not protected by this plugin. Do not put private data in media names/descriptions. Removing a result does not revoke a media URL.
Suggested privacy-policy text is added to WordPress's Privacy Policy Guide. Define an organizational retention period; administrators can find/edit/delete records after verifying a person's request. No email identity is collected, so WordPress's email-keyed privacy exporter/eraser is not automatically bound to these mobile records. Handle verified requests administratively; never release records based only on an email.
Schema version mrl_schema=1 is independent of plugin version 1.5.0. Activation
creates/repairs tables with dbDelta under a database named lock, verifies columns
and indexes, and advances the version only after success. Administrative initialization
retries an incomplete install. A newer stored schema is never silently downgraded.
This release performs no data migrations or remote calls on activation.
No unrelated frontend request triggers migrations.
An initialization failure gives an admin notice; storage failures deny lookups and
saves. Check database connectivity, CREATE/ALTER permissions and named-lock support.
If an operator removed/corrupted tables while the schema option survived, restore a
verified backup rather than deleting data or marking a version successful manually.
Back up the two site tables, mrl_schema option and referenced media before upgrades.
A restored backup must be verified on staging before relying on it for recovery.
Deactivation clears only mrl_cleanup; uninstall also retains records, schema and
media by default. Reinstallation/reactivation reuses them. Revert plugin files to
1.0.0 only while schema 1 is retained; future schema versions fail closed. For complete
retirement, an authorized database operator can remove the two owned tables and
schema option after backup/retention approval. Remove media separately after verifying
that other content does not reference it. No automated bulk deletion is provided.
Do not cache POST AJAX responses at proxies/CDNs or service workers: responses send
Cache-Control: no-store, private. Ordinary page caching is supported because tokens
and results are fetched at runtime. Result text exists in the browser after success;
use Clear result on shared computers (it clears this component only). PHP/SQL single-record operations do not require
transactions or a persistent object cache. Admin lists show 20 rows (bounded to 10,000
pages). Flexible lookup reads at most 501 candidates from the indexed event range,
normalizes only allowlisted search fields and fetches one version-checked record after
resolution. There are no per-candidate queries or shared result caches. The larger-event
exact fallback uses existing indexes and LIMIT 2, without total-count scans.
This release is prepared for WordPress.org review. It uses normal WordPress directory updates once approved under the matching slug; no external updater is included. Until approval, install the reviewed ZIP manually. Source PHP/JS/CSS and translation sources are shipped directly; no minification or runtime build is required. Report vulnerabilities privately to the plugin author through the contact route on their WordPress.org profile, or to the WordPress.org Plugins Team if unavailable. Use synthetic reproduction data; do not post private records in public support forums.
No runtime build is required. From the source workspace:
php tests/unit.php
find includes -name '*.php' -exec php -l {} \;
php -l mammute-result-lookup.php
php -l uninstall.php
node --check assets/lookup.js
node --check assets/admin.js
msgfmt --check languages/mammute-result-lookup-fa_IR.po -o /tmp/mrl-fa_IR.mo
python3 tools/package.pyThe package allowlists runtime code/assets, translations, license and usage docs;
source tests, governance documents and development tools are excluded. ZIP timestamps
are fixed for reproducibility. docs/review.md records the original release audit; docs/ux-review.md records
the UX refinement, verification and remaining limits.
On an isolated disposable WordPress site, define MRL_TESTING=true, activate the
plugin, and run wp eval-file wp-content/plugins/mammute-result-lookup/tests/integration.php.
The integration scripts deliberately reset UX-prefixed synthetic events and test-event, second-event, browser-event and the
plugin's rate counters; creates synthetic users/media; and exercises lifecycle. Never
run it on a site containing real data. The source workspace must be mounted for tests,
since test files are intentionally absent from the release ZIP. A reproducible isolated runner is also available:
python3 tools/verify.py --wordpress 6.5 --elementor none
python3 tools/verify.py --wordpress 7.1 --elementor 3.32.0It installs the exact ZIP in disposable Docker containers, runs database/concurrency
and both regression/UX browser tests, and removes its containers, volumes and temporary files afterwards.
Use --keep only to retain its test environment for inspection. It downloads pinned
WP-CLI 2.12.0 and Playwright 1.55.1; Docker image defaults are recorded in the script.
The browser state file contains only disposable credentials and stays outside the repo.
Browser tests require
Playwright and Chromium installed outside the plugin; see tests/browser.mjs for the
state-file/environment contract. Neither tool is a production dependency.
- Activate the exact ZIP. Verify Participant results appears only for administrators.
- Create two approved records in one event with different mobiles and the same name; add zero score/placement, a multiline summary, and a test certificate to one record.
- Embed
[mammute_results event="your-event"]; also try the native Elementor widget and two instances on a page. Initial page source must contain no result data. - Test local, +98, 98, 0098, Persian-digit and Arabic-digit mobile equivalents.
- Give one record a unique name and test exact Arabic/Persian character equivalents.
- Restore duplicate names: name lookup must return generic guidance, never candidates; mobile must still locate the correct record. Duplicate mobiles must fail on save.
- Verify zero values, absent placement, plain-text summary, certificate image/PDF open and download, no-certificate records, and deleted/missing-file behavior.
- Test unknown and ambiguous names, malformed mobiles, arrays, missing nonce/cookie, cross-origin requests, and more than 12 attempts in a window. Errors must not disclose records.
- Check public search, REST search/types, feeds and sitemaps for absence of result records. Confirm the documented public accessibility of the selected media separately.
- Test 375px width, Persian site language/RTL, 200% zoom, keyboard-only use, live status, focus, reduced motion, slow/offline requests and the actual theme's colors.
- Edit a record in two tabs: stale saves/deletes must fail without overwriting. Test a subscriber and logged-out POST; no data may change. Check actual cache/proxy behavior.
- Deactivate/reactivate and confirm data/media remain. Test backup restoration before release; remove disposable test records, accounts and media afterwards.
Run php tests/search-unit.php locally. The disposable verifier runs
wp eval-file tests/search-integration.php and node tests/search-browser.mjs with its
existing environment variables. These fixtures intentionally mutate/restore test data;
never run integration tests on production. See docs/search-review.md for actual
verification, limitations and the repository-rule audit.
On staging, create overlapping names with different teams/certificate IDs. Try exact name, first/surname fragments, Arabic/Persian equivalents, phone formats, exact codes, and a common name plus a complete team value. Verify categories alone and ambiguous names reveal no records or certificate links, and adding another fact resolves safely. Repeat after manual edit, CSV update and backup restore. Test a sparse legacy row only in an isolated fixture; the normal form intentionally still requires mobile. Verify mobile/tablet/desktop, keyboard focus/status, RTL and unchanged nonce/rate-limit denials.
Run php tests/public-unit.php for public-message and CSS-scope checks. The disposable
verifier additionally runs wp eval-file tests/public-integration.php and
node tests/public-browser.mjs using its existing test environment. See
docs/public-review.md for observed results, theme simulations and limits. On staging,
check all states at320/360/390/430px, tablet/desktop/wide widths, dark/light parents,
long data, portrait/landscape media, keyboard and reduced motion. Confirm surrounding
host output is untouched. Refresh page/asset/minifier caches when installing1.5.0.
The submission keeps the existing mammute-result-lookup slug/text domain and
mammute_results shortcode. Author/contributor metadata uses the supplied WordPress.org
username. Directory approval, name availability and ownership verification are external
review decisions; this package does not claim approval or legal compliance.
Batch queries use Transaction_DB, a small WordPress wpdb adapter that borrows the
existing connection and refuses reconnection. All SQL is prepared through $wpdb;
ordinary operations use the normal WordPress database object. Pinning prevents a lost
transaction from silently reconnecting and autocommitting later rows. Tests deliberately
kill the connection during a batch and verify rollback with no surviving partial writes.
There are no direct MySQLi query/result calls in the plugin. No new connection opens.
The remaining scanner warnings concern custom-table reads without shared caching,
strictly validated request values, shared authorization helpers and bundled translation
loading. Private/current records intentionally avoid shared caches. Read-only admin
filters require administrator access; mutations additionally check action-specific
nonces before canonical validation. Guest lookup uses a nonce bound to its random
browser-session token. load_plugin_textdomain retains the bundled Persian fallback
before directory language packs exist. No scanner exclusions were added for this release.
All functionality is available without payment or license checks. The 500-record/2 MiB batch bounds, transaction deadline and public throttling protect finite request memory, locking and enumeration risk; no purchase can remove them. For larger datasets, the current implementation requires operational assessment rather than buying an unlock.