WordPress plugin for personality assessments — MBTI, DISC, EQ, and Bar-On — with results tracking, shareable result links, and a customizable frontend UI.
- WordPress 6.0+
- PHP 8.1+
- Composer (for installing dependencies)
-
Copy or clone this folder into
wp-content/plugins/PersonaPress. -
Install PHP dependencies:
composer install --no-dev
For local development:
composer install
-
Activate PersonaPress in the WordPress admin under Plugins.
Embed an assessment on any page or post:
[personapress type="mbti"]
The legacy shortcode [personality_test] is also supported.
| Attribute | Default | Description |
|---|---|---|
type |
mbti |
Assessment slug: mbti, disc, eq, bar-on |
show_progress |
global | Show the progress bar (true / false) |
show_header |
global | Show the assessment header (true / false) |
title |
— | Custom header title |
description |
— | Custom header description |
option_layout |
global | auto, scale, list, pills, or cards |
questions_per_page |
global | 0 = all questions on one page |
redirect |
— | URL to redirect to after completion |
Example:
[personapress type="disc" show_progress="true" questions_per_page="5"]
After activation, open PersonaPress in the WordPress admin:
- Dashboard — overview, shortcode copy buttons, quick links
- Results — completed sessions and CSV export
- Questions — edit assessment questions
- Result Profiles — customize result copy per profile
- Settings — global colours, typography, behaviour, and per-assessment style overrides
When sharing is enabled in settings, users can copy a public link after completing a test. Shared URLs use the pp_result query parameter:
https://yoursite.test/?pp_result=TOKEN
personapress.php Plugin bootstrap
bootstrap.php Autoload and init hook
src/ PHP application code
templates/ PHP view templates
assets/ Frontend and admin CSS/JS
languages/ Translations (text domain: personapress)
composer test # PHPUnit (tests/Unit)
composer analyse # PHPStan (level 8)Test coverage focuses on core domain logic: questions, scoring, settings resolution, repositories, helpers, and shortcode error handling. Run vendor/bin/phpunit from the plugin root (uses phpunit.xml.dist).
Persian (fa_IR) strings are maintained in languages/fa_IR-translations.php. Rebuild the .po and .mo files with:
php languages/build-fa-IR.phpPersonaPress is built around a small assessment registry, template layer, and WordPress hooks. You can add custom tests, override data, swap templates, and hook into the full request lifecycle without forking core files.
Extend PersonaPress\Assessment\AbstractAssessment and add your class via pp_default_assessments:
<?php
use PersonaPress\Assessment\AbstractAssessment;
use PersonaPress\Result\Result;
final class MyCustomTest extends AbstractAssessment {
public function slug(): string {
return 'my-test';
}
public function name(): string {
return __( 'My Custom Test', 'my-plugin' );
}
public function description(): string {
return __( 'A short description shown in the assessment header.', 'my-plugin' );
}
protected function define_questions(): array {
return [
[
'id' => 'q1',
'dimension' => 'MAIN',
'text' => __( 'I enjoy solving puzzles.', 'my-plugin' ),
'type' => 'likert',
'options' => [
1 => __( 'Strongly Disagree', 'my-plugin' ),
2 => __( 'Disagree', 'my-plugin' ),
3 => __( 'Neutral', 'my-plugin' ),
4 => __( 'Agree', 'my-plugin' ),
5 => __( 'Strongly Agree', 'my-plugin' ),
],
],
];
}
public function default_result_profiles(): array {
return [
'high' => [
'title' => __( 'High scorer', 'my-plugin' ),
'description' => __( 'You scored above average.', 'my-plugin' ),
],
'low' => [
'title' => __( 'Low scorer', 'my-plugin' ),
'description' => __( 'You scored below average.', 'my-plugin' ),
],
];
}
protected function compute_result( array $answers ): Result {
$avg = (float) ( $answers['q1'] ?? 3 );
$key = $avg >= 3 ? 'high' : 'low';
$profile = $this->default_result_profiles()[ $key ];
return new Result(
key: $key,
title: $profile['title'],
description: $profile['description'],
scores: [ 'MAIN' => $avg ],
meta: [ 'score_labels' => [ 'MAIN' => __( 'Overall', 'my-plugin' ) ] ],
);
}
}
add_filter( 'pp_default_assessments', function ( array $assessments ): array {
$assessments[] = new MyCustomTest();
return $assessments;
} );Embed it with [personapress type="my-test"].
AbstractAssessment handles answer sanitization, scoring hooks, and question caching. Implement:
| Method | Purpose |
|---|---|
slug() |
Unique key used in shortcodes and the database |
name() / description() |
Header copy on the frontend |
define_questions() |
Built-in question definitions |
default_result_profiles() |
Built-in result titles and descriptions keyed by result key |
compute_result( $answers ) |
Scoring logic; return a Result object |
Each question in define_questions() accepts:
| Field | Type | Notes |
|---|---|---|
id |
string | Unique within the assessment |
text |
string | Question label |
type |
string | likert, radio, checkbox, or text |
options |
array | Choice labels keyed by value (required for likert/radio/checkbox) |
dimension |
string | Grouping key used during scoring |
required |
bool | Default true |
reverse_scored |
bool | Flip likert values on a 1–5 scale |
Admin-edited question overrides are stored in the pp_questions_{slug} option and merged on top of built-in definitions.
add_filter( 'pp_question_overrides_mbti', function ( array $overrides ): array {
$overrides['ei_01']['text'] = 'Custom wording for question 1.';
return $overrides;
} );Use pp_question_overrides for all assessments, or pp_question_overrides_{slug} for one. Filters run when questions are loaded; admin saves go through pp_save_question_overrides.
Result profile text can be customized in PersonaPress → Result Profiles, or via filters:
add_filter( 'pp_result_copy_overrides_mbti', function ( array $overrides ): array {
$overrides['INTJ']['title'] = 'My custom INTJ title';
return $overrides;
} );Built-in profiles come from each assessment’s default_result_profiles() method.
Hook into the scoring pipeline per assessment or globally:
| Filter | When |
|---|---|
pp_sanitize_answers |
Raw POST answers are normalized |
pp_{slug}_before_score |
Answers before dimension scoring (per assessment) |
pp_before_score |
Answers before scoring (all assessments) |
pp_{slug}_result |
Result object after compute_result() |
pp_result |
Final Result before it is stored or returned |
pp_result_array |
Serialised payload sent to the frontend / REST API |
add_filter( 'pp_mbti_result', function ( $result, $answers, $assessment ) {
// Adjust titles, scores, or meta before display.
return $result;
}, 10, 3 );Copy any file from templates/ into your theme:
wp-content/themes/your-theme/personapress/assessment/wrapper.php
wp-content/themes/your-theme/personapress/result/placeholder.php
Theme templates are checked before plugin templates. Use pp_locate_template_paths to add more search locations, or pp_template_data / pp_template_data_{name} to inject variables.
Available templates:
| Template | Used for |
|---|---|
assessment/wrapper |
Main shortcode shell |
assessment/question |
Single question row |
result/placeholder |
Result card (live and SSR) |
result/shared |
Public shared result page |
admin/* |
Dashboard, settings, results, questions |
Namespace: personapress/v1
| Method | Route | Description |
|---|---|---|
POST |
/sessions |
Create a session (assessment slug required) |
POST |
/sessions/{id}/submit |
Submit answers (answers object required) |
GET |
/results/{token} |
Fetch a shared result by guest token |
Access to create/submit endpoints requires a logged-in user or the Allow guests setting. Use pp_rest_can_access to customize that check. Use pp_rest_result_response to alter the JSON returned after submission.
Register additional routes on pp_register_rest_routes.
pp_frontend_config exposes the ppConfig object used by assets/js/assessment.js (REST URL, colours, feature flags). pp_frontend_i18n controls user-facing button labels.
add_filter( 'pp_frontend_i18n', function ( array $i18n ): array {
$i18n['submit'] = 'Show results';
return $i18n;
} );Enqueue extra styles or scripts on pp_enqueue_frontend_assets.
| Filter | Purpose |
|---|---|
pp_settings_defaults |
Default option values |
pp_settings |
Merged settings read from the database |
pp_sanitize_settings |
Values saved from the admin settings form |
pp_assessment_palettes |
Default colour palettes per slug |
pp_assessment_styles |
Resolved colours and behaviour for rendering |
pp_assessment_name / pp_assessment_description |
Frontend header copy |
pp_assessment_wrapper_attrs |
HTML attributes on the assessment root element |
pp_assessment_inline_style |
CSS custom properties for theming |
pp_font_stacks / pp_font_family_key |
Typography |
pp_result_share_url |
Public share link URL |
| Filter | Purpose |
|---|---|
pp_shortcode_tag |
Change the shortcode name (default personapress) |
pp_shortcode_defaults |
Default attribute values |
pp_shortcode_atts |
Parsed attributes before render |
pp_shortcode_output |
Final HTML output |
Actions: pp_before_shortcode_render, pp_after_shortcode_render.
Plugin boot
| Hook | Arguments |
|---|---|
pp_before_boot |
$plugin |
pp_before_register_assessments |
$registry, $defaults[] |
pp_registry_loaded |
$registry |
pp_booted |
$plugin |
pp_activate / pp_deactivate |
— |
Sessions and REST
| Hook | Arguments |
|---|---|
pp_session_created |
$session_id, $slug, $user_id |
pp_before_save_answers |
$answers, $session_id |
pp_before_score_session |
$answers, $session_id, $slug |
pp_assessment_completed |
$result, $session_id, $slug |
pp_session_complete_data |
$data, $session_id, $result |
Frontend render (in templates/assessment/wrapper.php and result/placeholder.php)
| Hook | Arguments |
|---|---|
pp_before_assessment / pp_after_assessment |
$assessment, $atts |
pp_before_assessment_header / pp_after_assessment_header |
$assessment, $atts |
pp_before_question / pp_after_question |
$question, $index, $assessment |
pp_before_question_content / pp_after_question_content |
$question, $index |
pp_before_assessment_form / pp_after_assessment_form |
$assessment, $atts |
pp_before_assessment_nav / pp_after_assessment_nav |
$assessment, $atts |
pp_before_result / pp_after_result |
$assessment |
pp_before_result_score_row / pp_after_result_score_row |
$dim, $score, $result |
pp_after_result_scores |
$result |
pp_before_result_actions / pp_after_result_actions |
$result (optional) |
pp_before_shared_result / pp_after_shared_result |
$session, $result_data |
Admin
| Hook | Arguments |
|---|---|
pp_admin_init |
$admin_loader |
pp_admin_menu |
$admin_loader |
pp_enqueue_admin_assets |
$hook_suffix |
pp_before_admin_{screen} / pp_after_admin_{screen} |
Varies by screen (dashboard, results, questions, result_copy, settings) |
pp_before_save_questions / pp_after_save_questions |
$slug, … |
pp_before_reset_questions / pp_after_reset_questions |
$slug |
pp_before_save_result_copy / pp_after_save_result_copy |
$slug, … |
pp_before_reset_result_copy / pp_after_reset_result_copy |
$slug |
pp_dashboard_before_stats / pp_dashboard_after_content |
$registry, $total |
pp_settings_before_form / pp_settings_after_form |
$settings |
pp_results_before_table / pp_results_after_table |
$rows, $filter_slug, $total |
Data retrieval
| Filter | Purpose |
|---|---|
pp_session_row |
Single session row by ID |
pp_session_row_by_token |
Session row by guest token |
pp_results_rows |
Paginated admin results list |
pp_results_count |
Results count for admin pagination |
pp_results_export_rows |
CSV export rows |
pp_session_create_data |
Data used when inserting a new session |
A compact index of every hook lives in src/Hooks/HookReference.php. When adding new hooks in core, update that file so extension authors have a single source of truth.
GPL-2.0-or-later. See LICENSE.