diff --git a/.gitignore b/.gitignore
index 20ab4f93a8de..231ab1469771 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@
!/ext/civi_pledge
!/ext/civi_report
!/ext/scheduled_communications
+!/ext/user_dashboard
backdrop/
bower_components
CRM/Case/xml/configuration
diff --git a/distmaker/core-ext.txt b/distmaker/core-ext.txt
index 9e2a64087f9e..1021333ca7c7 100644
--- a/distmaker/core-ext.txt
+++ b/distmaker/core-ext.txt
@@ -35,3 +35,4 @@ civi_member
civi_pledge
civi_report
scheduled_communications
+user_dashboard
diff --git a/ext/user_dashboard/ang/afsearchUserDashboard.aff.php b/ext/user_dashboard/ang/afsearchUserDashboard.aff.php
new file mode 100644
index 000000000000..acb8944bc934
--- /dev/null
+++ b/ext/user_dashboard/ang/afsearchUserDashboard.aff.php
@@ -0,0 +1,32 @@
+ 'search',
+ 'title' => E::ts('User Dashboard'),
+ 'server_route' => 'civicrm/user',
+ 'permission' => ['access Contact Dashboard'],
+ 'layout' => '',
+ // temporary, remove after merging https://github.com/civicrm/civicrm-core/pull/27783
+ 'requires' => ['af', 'afCore', 'crmSearchDisplayTable'],
+];
+
+// Add displays for every SavedSearch tagged "UserDashboard"
+$searchDisplays = civicrm_api4('SearchDisplay', 'get', [
+ 'checkPermissions' => FALSE,
+ 'select' => ['name', 'label', 'type:name', 'saved_search_id.name'],
+ 'where' => [
+ ['saved_search_id.is_current', '=', TRUE],
+ ['saved_search_id.tags:name', 'IN', ['UserDashboard']],
+ ],
+ 'orderBy' => ['name' => 'ASC'],
+]);
+foreach ($searchDisplays as $display) {
+ $afform['layout'] .= <<
+ <{$display['type:name']} search-name="{$display['saved_search_id.name']}" display-name="$display[name]">{$display['type:name']}>
+
+ HTML;
+}
+
+return $afform;
diff --git a/ext/user_dashboard/info.xml b/ext/user_dashboard/info.xml
new file mode 100644
index 000000000000..4e87e481b109
--- /dev/null
+++ b/ext/user_dashboard/info.xml
@@ -0,0 +1,36 @@
+
+
+ user_dashboard
+ User Dashboard
+ Adds a page for users to see their contributions, memberships, etc.
+ AGPL-3.0
+
+ CiviCRM
+ info@civicrm.org
+
+
+ http://FIXME
+ http://FIXME
+ http://FIXME
+ http://www.gnu.org/licenses/agpl-3.0.html
+
+ 2023-10-07
+ 1.0
+ alpha
+
+ 5.68.alpha1
+
+ This extension is still experimental
+
+
+
+
+
+ CRM/UserDashboard
+ 23.02.1
+ crmUserDashboard
+
+
+ mgd-php@1.0.0
+
+
diff --git a/ext/user_dashboard/managed/0Tag_UserDashboard.mgd.php b/ext/user_dashboard/managed/0Tag_UserDashboard.mgd.php
new file mode 100644
index 000000000000..14d533cafccb
--- /dev/null
+++ b/ext/user_dashboard/managed/0Tag_UserDashboard.mgd.php
@@ -0,0 +1,27 @@
+ 'Tag_UserDashboard',
+ 'entity' => 'Tag',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'label' => E::ts('User Dashboard'),
+ 'name' => 'UserDashboard',
+ 'description' => E::ts('Search will appear on the User Dashboard page'),
+ 'is_reserved' => TRUE,
+ 'used_for' => [
+ 'civicrm_saved_search',
+ ],
+ 'color' => '#5d677b',
+ ],
+ 'match' => [
+ 'name',
+ ],
+ ],
+ ],
+];
diff --git a/ext/user_dashboard/managed/SavedSearch_UserDashboard_Groups.mgd.php b/ext/user_dashboard/managed/SavedSearch_UserDashboard_Groups.mgd.php
new file mode 100644
index 000000000000..fb100db9d8e4
--- /dev/null
+++ b/ext/user_dashboard/managed/SavedSearch_UserDashboard_Groups.mgd.php
@@ -0,0 +1,157 @@
+ 'SavedSearch_UserDashboard_Groups',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Groups',
+ 'label' => E::ts('User Dashboard - Groups'),
+ 'api_entity' => 'Group',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => [
+ 'frontend_title',
+ 'Group_GroupContact_Contact_01.status:label',
+ 'MAX(Group_SubscriptionHistory_group_id_01.date) AS MAX_Group_SubscriptionHistory_group_id_01_date',
+ ],
+ 'orderBy' => [],
+ 'where' => [
+ [
+ 'Group_GroupContact_Contact_01.id',
+ '=',
+ 'user_contact_id',
+ ],
+ [
+ 'visibility:name',
+ '=',
+ 'Public Pages',
+ ],
+ [
+ 'is_active',
+ '=',
+ TRUE,
+ ],
+ ],
+ 'groupBy' => [
+ 'id',
+ 'Group_GroupContact_Contact_01.id',
+ ],
+ 'join' => [
+ [
+ 'Contact AS Group_GroupContact_Contact_01',
+ 'LEFT',
+ 'GroupContact',
+ [
+ 'id',
+ '=',
+ 'Group_GroupContact_Contact_01.group_id',
+ ],
+ ],
+ [
+ 'SubscriptionHistory AS Group_SubscriptionHistory_group_id_01',
+ 'LEFT',
+ [
+ 'id',
+ '=',
+ 'Group_SubscriptionHistory_group_id_01.group_id',
+ ],
+ [
+ 'Group_SubscriptionHistory_group_id_01.contact_id',
+ '=',
+ '"user_contact_id"',
+ ],
+ ],
+ ],
+ 'having' => [],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ ],
+ 'chain' => [
+ 'tag' => [
+ 'EntityTag', 'save', [
+ 'records' => [['entity_table' => 'civicrm_saved_search', 'entity_id' => '$id', 'tag_id:name' => 'UserDashboard']],
+ 'match' => ['entity_id', 'tag_id'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Groups_SearchDisplay_UserDashboard_Groups',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Groups',
+ 'label' => E::ts('Your Group(s)'),
+ 'saved_search_id.name' => 'UserDashboard_Groups',
+ 'type' => 'table',
+ 'settings' => [
+ 'description' => NULL,
+ 'sort' => [
+ [
+ 'Group_GroupContact_Contact_01.status',
+ 'ASC',
+ ],
+ ],
+ 'limit' => 50,
+ 'pager' => [
+ 'expose_limit' => TRUE,
+ 'hide_single' => TRUE,
+ ],
+ 'placeholder' => 1,
+ 'columns' => [
+ [
+ 'type' => 'field',
+ 'key' => 'frontend_title',
+ 'dataType' => 'String',
+ 'label' => E::ts('Group'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'Group_GroupContact_Contact_01.status:label',
+ 'dataType' => 'String',
+ 'label' => E::ts('Status'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'MAX_Group_SubscriptionHistory_group_id_01_date',
+ 'dataType' => 'Timestamp',
+ 'label' => E::ts('Since'),
+ 'sortable' => TRUE,
+ ],
+ ],
+ 'actions' => FALSE,
+ 'classes' => [
+ 'table',
+ 'table-striped',
+ ],
+ 'cssRules' => [
+ [
+ 'disabled',
+ 'Group_GroupContact_Contact_01.status',
+ '=',
+ 'Removed',
+ ],
+ ],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ 'saved_search_id',
+ ],
+ ],
+ ],
+];
diff --git a/ext/user_dashboard/managed/SavedSearch_UserDashboard_Pledges.mgd.php b/ext/user_dashboard/managed/SavedSearch_UserDashboard_Pledges.mgd.php
new file mode 100644
index 000000000000..94dfe2e79b75
--- /dev/null
+++ b/ext/user_dashboard/managed/SavedSearch_UserDashboard_Pledges.mgd.php
@@ -0,0 +1,154 @@
+ 'SavedSearch_UserDashboard_Pledges',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Pledges',
+ 'label' => E::ts('User Dashboard - Pledges'),
+ 'api_entity' => 'Pledge',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => [
+ 'amount',
+ 'SUM(Pledge_PledgePayment_pledge_id_01.actual_amount) AS SUM_Pledge_PledgePayment_pledge_id_01_actual_amount',
+ 'financial_type_id:label',
+ 'create_date',
+ 'MIN(Pledge_PledgePayment_pledge_id_02.scheduled_date) AS MIN_Pledge_PledgePayment_pledge_id_02_scheduled_date',
+ 'MAX(Pledge_PledgePayment_pledge_id_02.scheduled_amount) AS MAX_Pledge_PledgePayment_pledge_id_02_scheduled_amount',
+ 'status_id:label',
+ ],
+ 'orderBy' => [],
+ 'where' => [
+ ['contact_id', '=', 'user_contact_id'],
+ ],
+ 'groupBy' => [
+ 'id',
+ ],
+ 'join' => [
+ [
+ 'PledgePayment AS Pledge_PledgePayment_pledge_id_01',
+ 'LEFT',
+ ['id', '=', 'Pledge_PledgePayment_pledge_id_01.pledge_id'],
+ ['Pledge_PledgePayment_pledge_id_01.status_id:name', '=', '"Completed"'],
+ ],
+ [
+ 'PledgePayment AS Pledge_PledgePayment_pledge_id_02',
+ 'LEFT',
+ ['id', '=', 'Pledge_PledgePayment_pledge_id_02.pledge_id'],
+ ['Pledge_PledgePayment_pledge_id_02.status_id:name', '!=', '"Completed"'],
+ ],
+ ],
+ 'having' => [],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ ],
+ 'chain' => [
+ 'tag' => [
+ 'EntityTag', 'save', [
+ 'records' => [['entity_table' => 'civicrm_saved_search', 'entity_id' => '$id', 'tag_id:name' => 'UserDashboard']],
+ 'match' => ['entity_id', 'tag_id'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Pledges_SearchDisplay_UserDashboard_Pledges',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Pledges',
+ 'label' => E::ts('Your Pledges'),
+ 'saved_search_id.name' => 'UserDashboard_Pledges',
+ 'type' => 'table',
+ 'settings' => [
+ 'description' => NULL,
+ 'sort' => [],
+ 'limit' => 50,
+ 'pager' => [
+ 'hide_single' => TRUE,
+ 'expose_limit' => TRUE,
+ ],
+ 'placeholder' => 1,
+ 'columns' => [
+ [
+ 'type' => 'field',
+ 'key' => 'amount',
+ 'dataType' => 'Money',
+ 'label' => E::ts('Pledged'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'SUM_Pledge_PledgePayment_pledge_id_01_actual_amount',
+ 'dataType' => 'Money',
+ 'label' => E::ts('Total Paid'),
+ 'sortable' => TRUE,
+ 'empty_value' => '0',
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'financial_type_id:label',
+ 'dataType' => 'Integer',
+ 'label' => E::ts('Pledged For'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'create_date',
+ 'dataType' => 'Timestamp',
+ 'label' => E::ts('Pledge Made'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'MIN_Pledge_PledgePayment_pledge_id_02_scheduled_date',
+ 'dataType' => 'Timestamp',
+ 'label' => E::ts('Next Pay Date'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'MAX_Pledge_PledgePayment_pledge_id_02_scheduled_amount',
+ 'dataType' => 'Money',
+ 'label' => E::ts('Next Amount'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'status_id:label',
+ 'dataType' => 'Integer',
+ 'label' => E::ts('Status'),
+ 'sortable' => TRUE,
+ ],
+ ],
+ 'actions' => FALSE,
+ 'classes' => [
+ 'table',
+ 'table-striped',
+ ],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ 'saved_search_id',
+ ],
+ ],
+ ],
+];
diff --git a/ext/user_dashboard/managed/UserDashboard_Contributions.mgd.php b/ext/user_dashboard/managed/UserDashboard_Contributions.mgd.php
new file mode 100644
index 000000000000..d71cbcf064d9
--- /dev/null
+++ b/ext/user_dashboard/managed/UserDashboard_Contributions.mgd.php
@@ -0,0 +1,122 @@
+ 'SavedSearch_UserDashboard_Contributions',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Contributions',
+ 'label' => E::ts('User Dashboard - Contributions'),
+ 'api_entity' => 'Contribution',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => [
+ 'total_amount',
+ 'financial_type_id:label',
+ 'contribution_status_id:label',
+ 'receive_date',
+ 'receipt_date',
+ ],
+ 'orderBy' => [],
+ 'where' => [
+ ['contact_id', '=', 'user_contact_id'],
+ ],
+ 'groupBy' => [],
+ 'join' => [],
+ 'having' => [],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ ],
+ 'chain' => [
+ 'tag' => [
+ 'EntityTag', 'save', [
+ 'records' => [['entity_table' => 'civicrm_saved_search', 'entity_id' => '$id', 'tag_id:name' => 'UserDashboard']],
+ 'match' => ['entity_id', 'tag_id'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Contributions_SearchDisplay_UserDashboard_Contributions',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Contributions',
+ 'label' => E::ts('Your Contribution(s)'),
+ 'saved_search_id.name' => 'UserDashboard_Contributions',
+ 'type' => 'table',
+ 'settings' => [
+ 'description' => NULL,
+ 'sort' => [],
+ 'limit' => 50,
+ 'pager' => [
+ 'hide_single' => TRUE,
+ 'expose_limit' => TRUE,
+ ],
+ 'placeholder' => 1,
+ 'columns' => [
+ [
+ 'type' => 'field',
+ 'key' => 'total_amount',
+ 'dataType' => 'String',
+ 'label' => E::ts('Total Amount'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'financial_type_id:label',
+ 'dataType' => 'String',
+ 'label' => E::ts('Type'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'receive_date',
+ 'dataType' => 'Date',
+ 'label' => E::ts('Date'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'receipt_date',
+ 'dataType' => 'Date',
+ 'label' => E::ts('Receipt Sent'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'contribution_status_id:label',
+ 'dataType' => 'String',
+ 'label' => E::ts('Status'),
+ 'sortable' => TRUE,
+ ],
+ ],
+ 'actions' => FALSE,
+ 'classes' => [
+ 'table',
+ 'table-striped',
+ ],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ 'saved_search_id',
+ ],
+ ],
+ ],
+];
diff --git a/ext/user_dashboard/managed/UserDashboard_Events.mgd.php b/ext/user_dashboard/managed/UserDashboard_Events.mgd.php
new file mode 100644
index 000000000000..730889c76130
--- /dev/null
+++ b/ext/user_dashboard/managed/UserDashboard_Events.mgd.php
@@ -0,0 +1,114 @@
+ 'SavedSearch_UserDashboard_Events',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Events',
+ 'label' => E::ts('User Dashboard - Events'),
+ 'api_entity' => 'Participant',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => [
+ 'event_id.title',
+ 'role_id:label',
+ 'status_id:label',
+ 'event_id.start_date',
+ ],
+ 'orderBy' => [],
+ 'where' => [
+ ['contact_id', '=', 'user_contact_id'],
+ ],
+ 'groupBy' => [],
+ 'join' => [],
+ 'having' => [],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ ],
+ 'chain' => [
+ 'tag' => [
+ 'EntityTag', 'save', [
+ 'records' => [['entity_table' => 'civicrm_saved_search', 'entity_id' => '$id', 'tag_id:name' => 'UserDashboard']],
+ 'match' => ['entity_id', 'tag_id'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Events_SearchDisplay_UserDashboard_Events',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Events',
+ 'label' => E::ts('Your Event(s)'),
+ 'saved_search_id.name' => 'UserDashboard_Events',
+ 'type' => 'table',
+ 'settings' => [
+ 'description' => NULL,
+ 'sort' => [],
+ 'limit' => 50,
+ 'pager' => [
+ 'hide_single' => TRUE,
+ 'expose_limit' => TRUE,
+ ],
+ 'placeholder' => 1,
+ 'columns' => [
+ [
+ 'type' => 'field',
+ 'key' => 'event_id.title',
+ 'dataType' => 'String',
+ 'label' => E::ts('Event'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'event_id.start_date',
+ 'dataType' => 'Date',
+ 'label' => E::ts('Event Date'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'role_id:label',
+ 'dataType' => 'String',
+ 'label' => E::ts('Role'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'status_id:label',
+ 'dataType' => 'String',
+ 'label' => E::ts('Status'),
+ 'sortable' => TRUE,
+ ],
+ ],
+ 'actions' => FALSE,
+ 'classes' => [
+ 'table',
+ 'table-striped',
+ ],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ 'saved_search_id',
+ ],
+ ],
+ ],
+];
diff --git a/ext/user_dashboard/managed/UserDashboard_Memberships.mgd.php b/ext/user_dashboard/managed/UserDashboard_Memberships.mgd.php
new file mode 100644
index 000000000000..a3b544457a09
--- /dev/null
+++ b/ext/user_dashboard/managed/UserDashboard_Memberships.mgd.php
@@ -0,0 +1,122 @@
+ 'SavedSearch_UserDashboard_Memberships',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Memberships',
+ 'label' => E::ts('User Dashboard - Memberships'),
+ 'api_entity' => 'Membership',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => [
+ 'membership_type_id:label',
+ 'status_id:label',
+ 'start_date',
+ 'end_date',
+ 'join_date',
+ ],
+ 'orderBy' => [],
+ 'where' => [
+ ['contact_id', '=', 'user_contact_id'],
+ ],
+ 'groupBy' => [],
+ 'join' => [],
+ 'having' => [],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ ],
+ 'chain' => [
+ 'tag' => [
+ 'EntityTag', 'save', [
+ 'records' => [['entity_table' => 'civicrm_saved_search', 'entity_id' => '$id', 'tag_id:name' => 'UserDashboard']],
+ 'match' => ['entity_id', 'tag_id'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Memberships_SearchDisplay_UserDashboard_Memberships',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Memberships',
+ 'label' => E::ts('Your Membership(s)'),
+ 'saved_search_id.name' => 'UserDashboard_Memberships',
+ 'type' => 'table',
+ 'settings' => [
+ 'description' => NULL,
+ 'sort' => [],
+ 'limit' => 50,
+ 'pager' => [
+ 'hide_single' => TRUE,
+ 'expose_limit' => TRUE,
+ ],
+ 'placeholder' => 1,
+ 'columns' => [
+ [
+ 'type' => 'field',
+ 'key' => 'membership_type_id:label',
+ 'dataType' => 'String',
+ 'label' => E::ts('Type'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'join_date',
+ 'dataType' => 'Date',
+ 'label' => E::ts('Member Since'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'start_date',
+ 'dataType' => 'Date',
+ 'label' => E::ts('Start Date'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'end_date',
+ 'dataType' => 'Date',
+ 'label' => E::ts('End Date'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'status_id:label',
+ 'dataType' => 'String',
+ 'label' => E::ts('Status'),
+ 'sortable' => TRUE,
+ ],
+ ],
+ 'actions' => FALSE,
+ 'classes' => [
+ 'table',
+ 'table-striped',
+ ],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ 'saved_search_id',
+ ],
+ ],
+ ],
+];
diff --git a/ext/user_dashboard/managed/UserDashboard_PCPs.mgd.php b/ext/user_dashboard/managed/UserDashboard_PCPs.mgd.php
new file mode 100644
index 000000000000..f36a56b26b83
--- /dev/null
+++ b/ext/user_dashboard/managed/UserDashboard_PCPs.mgd.php
@@ -0,0 +1,106 @@
+ 'SavedSearch_UserDashboard_PCPs',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_PCPs',
+ 'label' => E::ts('User Dashboard - PCPs'),
+ 'api_entity' => 'PCP',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => [
+ 'title',
+ 'status_id:label',
+ 'page_id.frontend_title',
+ ],
+ 'orderBy' => [],
+ 'where' => [
+ ['contact_id', '=', 'user_contact_id'],
+ ],
+ 'groupBy' => [],
+ 'join' => [],
+ 'having' => [],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ ],
+ 'chain' => [
+ 'tag' => [
+ 'EntityTag', 'save', [
+ 'records' => [['entity_table' => 'civicrm_saved_search', 'entity_id' => '$id', 'tag_id:name' => 'UserDashboard']],
+ 'match' => ['entity_id', 'tag_id'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_PCPs_SearchDisplay_UserDashboard_PCPs',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_PCPs',
+ 'label' => E::ts('Personal Campaign Pages'),
+ 'saved_search_id.name' => 'UserDashboard_PCPs',
+ 'type' => 'table',
+ 'settings' => [
+ 'description' => NULL,
+ 'sort' => [],
+ 'limit' => 50,
+ 'pager' => [
+ 'hide_single' => TRUE,
+ 'expose_limit' => TRUE,
+ ],
+ 'placeholder' => 1,
+ 'columns' => [
+ [
+ 'type' => 'field',
+ 'key' => 'title',
+ 'dataType' => 'String',
+ 'label' => E::ts('Title'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'status_id:label',
+ 'dataType' => 'String',
+ 'label' => E::ts('Type'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'page_id.frontend_title',
+ 'dataType' => 'String',
+ 'label' => E::ts('Campaign'),
+ 'sortable' => TRUE,
+ ],
+ ],
+ 'actions' => FALSE,
+ 'classes' => [
+ 'table',
+ 'table-striped',
+ ],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ 'saved_search_id',
+ ],
+ ],
+ ],
+];
diff --git a/ext/user_dashboard/managed/UserDashboard_Relationships.mgd.php b/ext/user_dashboard/managed/UserDashboard_Relationships.mgd.php
new file mode 100644
index 000000000000..987c1dfca8ad
--- /dev/null
+++ b/ext/user_dashboard/managed/UserDashboard_Relationships.mgd.php
@@ -0,0 +1,151 @@
+ 'SavedSearch_UserDashboard_Relationships',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Relationships',
+ 'label' => E::ts('User Dashboard - Relationships'),
+ 'api_entity' => 'RelationshipCache',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => [
+ 'near_relation:label',
+ 'RelationshipCache_Contact_far_contact_id_01.display_name',
+ 'start_date',
+ 'RelationshipCache_Contact_far_contact_id_01.address_primary.city',
+ 'RelationshipCache_Contact_far_contact_id_01.address_primary.state_province_id:label',
+ 'RelationshipCache_Contact_far_contact_id_01.email_primary.email',
+ 'RelationshipCache_Contact_far_contact_id_01.phone_primary.phone',
+ ],
+ 'orderBy' => [],
+ 'where' => [
+ ['near_contact_id', '=', 'user_contact_id'],
+ ['is_current', '=', TRUE],
+ ],
+ 'groupBy' => [],
+ 'join' => [
+ [
+ 'Contact AS RelationshipCache_Contact_far_contact_id_01',
+ 'LEFT',
+ ['far_contact_id', '=', 'RelationshipCache_Contact_far_contact_id_01.id'],
+ ],
+ ],
+ 'having' => [],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ ],
+ 'chain' => [
+ 'tag' => [
+ 'EntityTag', 'save', [
+ 'records' => [['entity_table' => 'civicrm_saved_search', 'entity_id' => '$id', 'tag_id:name' => 'UserDashboard']],
+ 'match' => ['entity_id', 'tag_id'],
+ ],
+ ],
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Relationships_SearchDisplay_UserDashboard_Relationships',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'unused',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Relationships',
+ 'label' => E::ts('Your Contacts / Organizations'),
+ 'saved_search_id.name' => 'UserDashboard_Relationships',
+ 'type' => 'table',
+ 'settings' => [
+ 'description' => NULL,
+ 'sort' => [],
+ 'limit' => 50,
+ 'pager' => [
+ 'hide_single' => TRUE,
+ 'expose_limit' => TRUE,
+ ],
+ 'placeholder' => 1,
+ 'columns' => [
+ [
+ 'type' => 'field',
+ 'key' => 'near_relation:label',
+ 'dataType' => 'String',
+ 'label' => E::ts('Relationship'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'RelationshipCache_Contact_far_contact_id_01.display_name',
+ 'dataType' => 'String',
+ 'label' => E::ts('With'),
+ 'sortable' => TRUE,
+ 'icons' => [
+ [
+ 'field' => 'RelationshipCache_Contact_far_contact_id_01.contact_sub_type:icon',
+ 'side' => 'left',
+ ],
+ [
+ 'field' => 'RelationshipCache_Contact_far_contact_id_01.contact_type:icon',
+ 'side' => 'left',
+ ],
+ ],
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'start_date',
+ 'dataType' => 'Date',
+ 'label' => E::ts('Since'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'RelationshipCache_Contact_far_contact_id_01.address_primary.city',
+ 'dataType' => 'String',
+ 'label' => E::ts('City'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'RelationshipCache_Contact_far_contact_id_01.address_primary.state_province_id:label',
+ 'dataType' => 'Integer',
+ 'label' => E::ts('State/Prov'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'RelationshipCache_Contact_far_contact_id_01.email_primary.email',
+ 'dataType' => 'String',
+ 'label' => E::ts('Email'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'RelationshipCache_Contact_far_contact_id_01.phone_primary.phone',
+ 'dataType' => 'String',
+ 'label' => E::ts('Phone'),
+ 'sortable' => TRUE,
+ ],
+ ],
+ 'actions' => FALSE,
+ 'classes' => [
+ 'table',
+ 'table-striped',
+ ],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ 'saved_search_id',
+ ],
+ ],
+ ],
+];
diff --git a/ext/user_dashboard/user_dashboard.civix.php b/ext/user_dashboard/user_dashboard.civix.php
new file mode 100644
index 000000000000..3f00bdefdefe
--- /dev/null
+++ b/ext/user_dashboard/user_dashboard.civix.php
@@ -0,0 +1,200 @@
+getUrl(self::LONG_NAME), '/');
+ }
+ return CRM_Core_Resources::singleton()->getUrl(self::LONG_NAME, $file);
+ }
+
+ /**
+ * Get the path of a resource file (in this extension).
+ *
+ * @param string|NULL $file
+ * Ex: NULL.
+ * Ex: 'css/foo.css'.
+ * @return string
+ * Ex: '/var/www/example.org/sites/default/ext/org.example.foo'.
+ * Ex: '/var/www/example.org/sites/default/ext/org.example.foo/css/foo.css'.
+ */
+ public static function path($file = NULL) {
+ // return CRM_Core_Resources::singleton()->getPath(self::LONG_NAME, $file);
+ return __DIR__ . ($file === NULL ? '' : (DIRECTORY_SEPARATOR . $file));
+ }
+
+ /**
+ * Get the name of a class within this extension.
+ *
+ * @param string $suffix
+ * Ex: 'Page_HelloWorld' or 'Page\\HelloWorld'.
+ * @return string
+ * Ex: 'CRM_Foo_Page_HelloWorld'.
+ */
+ public static function findClass($suffix) {
+ return self::CLASS_PREFIX . '_' . str_replace('\\', '_', $suffix);
+ }
+
+}
+
+use CRM_UserDashboard_ExtensionUtil as E;
+
+/**
+ * (Delegated) Implements hook_civicrm_config().
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_config
+ */
+function _user_dashboard_civix_civicrm_config($config = NULL) {
+ static $configured = FALSE;
+ if ($configured) {
+ return;
+ }
+ $configured = TRUE;
+
+ $extRoot = __DIR__ . DIRECTORY_SEPARATOR;
+ $include_path = $extRoot . PATH_SEPARATOR . get_include_path();
+ set_include_path($include_path);
+ // Based on , this does not currently require mixin/polyfill.php.
+}
+
+/**
+ * Implements hook_civicrm_install().
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install
+ */
+function _user_dashboard_civix_civicrm_install() {
+ _user_dashboard_civix_civicrm_config();
+ // Based on , this does not currently require mixin/polyfill.php.
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_enable().
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable
+ */
+function _user_dashboard_civix_civicrm_enable(): void {
+ _user_dashboard_civix_civicrm_config();
+ // Based on , this does not currently require mixin/polyfill.php.
+}
+
+/**
+ * Inserts a navigation menu item at a given place in the hierarchy.
+ *
+ * @param array $menu - menu hierarchy
+ * @param string $path - path to parent of this item, e.g. 'my_extension/submenu'
+ * 'Mailing', or 'Administer/System Settings'
+ * @param array $item - the item to insert (parent/child attributes will be
+ * filled for you)
+ *
+ * @return bool
+ */
+function _user_dashboard_civix_insert_navigation_menu(&$menu, $path, $item) {
+ // If we are done going down the path, insert menu
+ if (empty($path)) {
+ $menu[] = [
+ 'attributes' => array_merge([
+ 'label' => $item['name'] ?? NULL,
+ 'active' => 1,
+ ], $item),
+ ];
+ return TRUE;
+ }
+ else {
+ // Find an recurse into the next level down
+ $found = FALSE;
+ $path = explode('/', $path);
+ $first = array_shift($path);
+ foreach ($menu as $key => &$entry) {
+ if ($entry['attributes']['name'] == $first) {
+ if (!isset($entry['child'])) {
+ $entry['child'] = [];
+ }
+ $found = _user_dashboard_civix_insert_navigation_menu($entry['child'], implode('/', $path), $item);
+ }
+ }
+ return $found;
+ }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_navigationMenu().
+ */
+function _user_dashboard_civix_navigationMenu(&$nodes) {
+ if (!is_callable(['CRM_Core_BAO_Navigation', 'fixNavigationMenu'])) {
+ _user_dashboard_civix_fixNavigationMenu($nodes);
+ }
+}
+
+/**
+ * Given a navigation menu, generate navIDs for any items which are
+ * missing them.
+ */
+function _user_dashboard_civix_fixNavigationMenu(&$nodes) {
+ $maxNavID = 1;
+ array_walk_recursive($nodes, function($item, $key) use (&$maxNavID) {
+ if ($key === 'navID') {
+ $maxNavID = max($maxNavID, $item);
+ }
+ });
+ _user_dashboard_civix_fixNavigationMenuItems($nodes, $maxNavID, NULL);
+}
+
+function _user_dashboard_civix_fixNavigationMenuItems(&$nodes, &$maxNavID, $parentID) {
+ $origKeys = array_keys($nodes);
+ foreach ($origKeys as $origKey) {
+ if (!isset($nodes[$origKey]['attributes']['parentID']) && $parentID !== NULL) {
+ $nodes[$origKey]['attributes']['parentID'] = $parentID;
+ }
+ // If no navID, then assign navID and fix key.
+ if (!isset($nodes[$origKey]['attributes']['navID'])) {
+ $newKey = ++$maxNavID;
+ $nodes[$origKey]['attributes']['navID'] = $newKey;
+ $nodes[$newKey] = $nodes[$origKey];
+ unset($nodes[$origKey]);
+ $origKey = $newKey;
+ }
+ if (isset($nodes[$origKey]['child']) && is_array($nodes[$origKey]['child'])) {
+ _user_dashboard_civix_fixNavigationMenuItems($nodes[$origKey]['child'], $maxNavID, $nodes[$origKey]['attributes']['navID']);
+ }
+ }
+}
diff --git a/ext/user_dashboard/user_dashboard.php b/ext/user_dashboard/user_dashboard.php
new file mode 100644
index 000000000000..7fe4ad88ade7
--- /dev/null
+++ b/ext/user_dashboard/user_dashboard.php
@@ -0,0 +1,33 @@
+