!/ext/civi_pledge
!/ext/civi_report
!/ext/scheduled_communications
+!/ext/user_dashboard
backdrop/
bower_components
CRM/Case/xml/configuration
civi_pledge
civi_report
scheduled_communications
+user_dashboard
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+$afform = [
+ 'type' => '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'] .= <<<HTML
+ <div af-fieldset="" class="af-container-style-pane" af-title="$display[label]">
+ <{$display['type:name']} search-name="{$display['saved_search_id.name']}" display-name="$display[name]"></{$display['type:name']}>
+ </div>
+ HTML;
+}
+
+return $afform;
--- /dev/null
+<?xml version="1.0"?>
+<extension key="user_dashboard" type="module">
+ <file>user_dashboard</file>
+ <name>User Dashboard</name>
+ <description>Adds a page for users to see their contributions, memberships, etc.</description>
+ <license>AGPL-3.0</license>
+ <maintainer>
+ <author>CiviCRM</author>
+ <email>info@civicrm.org</email>
+ </maintainer>
+ <urls>
+ <url desc="Main Extension Page">http://FIXME</url>
+ <url desc="Documentation">http://FIXME</url>
+ <url desc="Support">http://FIXME</url>
+ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url>
+ </urls>
+ <releaseDate>2023-10-07</releaseDate>
+ <version>1.0</version>
+ <develStage>alpha</develStage>
+ <compatibility>
+ <ver>5.68.alpha1</ver>
+ </compatibility>
+ <comments>This extension is still experimental</comments>
+ <classloader>
+ <psr0 prefix="CRM_" path="."/>
+ <psr4 prefix="Civi\" path="Civi"/>
+ </classloader>
+ <civix>
+ <namespace>CRM/UserDashboard</namespace>
+ <format>23.02.1</format>
+ <angularModule>crmUserDashboard</angularModule>
+ </civix>
+ <mixins>
+ <mixin>mgd-php@1.0.0</mixin>
+ </mixins>
+</extension>
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+return [
+ [
+ 'name' => 'Dashboard_Tag',
+ 'entity' => 'Tag',
+ 'cleanup' => 'always',
+ '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',
+ ],
+ ],
+ ],
+];
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+return [
+ [
+ 'name' => 'SavedSearch_UserDashboard_Activities',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'always',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Activities',
+ 'label' => E::ts('User Dashboard - Activities'),
+ 'api_entity' => 'Activity',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => [
+ 'subject',
+ 'activity_type_id:label',
+ 'GROUP_CONCAT(DISTINCT Activity_ActivityContact_Contact_01.sort_name) AS GROUP_CONCAT_Activity_ActivityContact_Contact_01_sort_name',
+ 'GROUP_CONCAT(DISTINCT Activity_ActivityContact_Contact_02.sort_name) AS GROUP_CONCAT_Activity_ActivityContact_Contact_02_sort_name',
+ 'activity_date_time',
+ 'status_id:label',
+ ],
+ 'orderBy' => [],
+ 'where' => [
+ [
+ 'status_id:name',
+ '!=',
+ 'Completed',
+ ],
+ ],
+ 'groupBy' => [
+ 'id',
+ ],
+ 'join' => [
+ [
+ 'Contact AS Activity_ActivityContact_Contact_01',
+ 'LEFT',
+ 'ActivityContact',
+ [
+ 'id',
+ '=',
+ 'Activity_ActivityContact_Contact_01.activity_id',
+ ],
+ [
+ 'Activity_ActivityContact_Contact_01.record_type_id:name',
+ '=',
+ '"Activity Source"',
+ ],
+ ],
+ [
+ 'Contact AS Activity_ActivityContact_Contact_02',
+ 'LEFT',
+ 'ActivityContact',
+ [
+ 'id',
+ '=',
+ 'Activity_ActivityContact_Contact_02.activity_id',
+ ],
+ [
+ 'Activity_ActivityContact_Contact_02.record_type_id:name',
+ '=',
+ '"Activity Targets"',
+ ],
+ ],
+ [
+ 'Contact AS Activity_ActivityContact_Contact_03',
+ 'INNER',
+ 'ActivityContact',
+ [
+ 'id',
+ '=',
+ 'Activity_ActivityContact_Contact_03.activity_id',
+ ],
+ [
+ 'Activity_ActivityContact_Contact_03.record_type_id:name',
+ '=',
+ '"Activity Assignees"',
+ ],
+ [
+ 'Activity_ActivityContact_Contact_03.id',
+ '=',
+ '"user_contact_id"',
+ ],
+ ],
+ ],
+ 'having' => [],
+ ],
+ ],
+ 'match' => [
+ 'name',
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Activities_SearchDisplay_UserDashboard_Activities',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'always',
+ 'update' => 'unmodified',
+ 'params' => [
+ 'version' => 4,
+ 'values' => [
+ 'name' => 'UserDashboard_Activities',
+ 'label' => E::ts('Your Assigned Activities'),
+ 'saved_search_id.name' => 'UserDashboard_Activities',
+ 'type' => 'table',
+ 'settings' => [
+ 'description' => NULL,
+ 'sort' => [],
+ 'limit' => 20,
+ 'pager' => [
+ 'expose_limit' => TRUE,
+ 'hide_single' => TRUE,
+ ],
+ 'placeholder' => 1,
+ 'columns' => [
+ [
+ 'type' => 'field',
+ 'key' => 'activity_type_id:label',
+ 'dataType' => 'Integer',
+ 'label' => E::ts('Type'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'subject',
+ 'dataType' => 'String',
+ 'label' => E::ts('Subject'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'GROUP_CONCAT_Activity_ActivityContact_Contact_01_sort_name',
+ 'dataType' => 'String',
+ 'label' => E::ts('Added by'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'GROUP_CONCAT_Activity_ActivityContact_Contact_02_sort_name',
+ 'dataType' => 'String',
+ 'label' => E::ts('With'),
+ 'sortable' => TRUE,
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'activity_date_time',
+ 'dataType' => 'Timestamp',
+ 'label' => E::ts('Date'),
+ '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',
+ ],
+ ],
+ ],
+];
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+return [
+ [
+ 'name' => 'SavedSearch_UserDashboard_Groups',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'always',
+ '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',
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Groups_SearchDisplay_UserDashboard_Groups',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'always',
+ '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' => 20,
+ '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',
+ ],
+ ],
+ ],
+];
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+if (!CRM_Core_Component::isEnabled('CiviPledge')) {
+ return [];
+}
+
+return [
+ [
+ 'name' => 'SavedSearch_UserDashboard_Pledges',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'always',
+ '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',
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Pledges_SearchDisplay_UserDashboard_Pledges',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'always',
+ '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' => 20,
+ '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',
+ ],
+ ],
+ ],
+];
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+if (!CRM_Core_Component::isEnabled('CiviContribute')) {
+ return [];
+}
+
+return [
+ [
+ 'name' => 'SavedSearch_UserDashboard_Contributions',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'always',
+ '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',
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Contributions_SearchDisplay_UserDashboard_Contributions',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'always',
+ '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' => 20,
+ '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',
+ ],
+ ],
+ ],
+];
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+if (!CRM_Core_Component::isEnabled('CiviEvent')) {
+ return [];
+}
+
+return [
+ [
+ 'name' => 'SavedSearch_UserDashboard_Events',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'always',
+ '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',
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Events_SearchDisplay_UserDashboard_Events',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'always',
+ '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' => 20,
+ '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',
+ ],
+ ],
+ ],
+];
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+if (!CRM_Core_Component::isEnabled('CiviMember')) {
+ return [];
+}
+
+return [
+ [
+ 'name' => 'SavedSearch_UserDashboard_Memberships',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'always',
+ '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',
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Memberships_SearchDisplay_UserDashboard_Memberships',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'always',
+ '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' => 20,
+ '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',
+ ],
+ ],
+ ],
+];
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+if (!CRM_Core_Component::isEnabled('CiviContribute')) {
+ return [];
+}
+
+return [
+ [
+ 'name' => 'SavedSearch_UserDashboard_PCPs',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'always',
+ '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',
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_PCPs_SearchDisplay_UserDashboard_PCPs',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'always',
+ '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' => 20,
+ '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',
+ ],
+ ],
+ ],
+];
--- /dev/null
+<?php
+use CRM_UserDashboard_ExtensionUtil as E;
+
+return [
+ [
+ 'name' => 'SavedSearch_UserDashboard_Relationships',
+ 'entity' => 'SavedSearch',
+ 'cleanup' => 'always',
+ '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',
+ ],
+ ],
+ ],
+ [
+ 'name' => 'SavedSearch_UserDashboard_Relationships_SearchDisplay_UserDashboard_Relationships',
+ 'entity' => 'SearchDisplay',
+ 'cleanup' => 'always',
+ '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' => 20,
+ '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',
+ ],
+ ],
+ ],
+];
--- /dev/null
+<?php
+
+// AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file
+
+/**
+ * The ExtensionUtil class provides small stubs for accessing resources of this
+ * extension.
+ */
+class CRM_UserDashboard_ExtensionUtil {
+ const SHORT_NAME = 'user_dashboard';
+ const LONG_NAME = 'user_dashboard';
+ const CLASS_PREFIX = 'CRM_UserDashboard';
+
+ /**
+ * Translate a string using the extension's domain.
+ *
+ * If the extension doesn't have a specific translation
+ * for the string, fallback to the default translations.
+ *
+ * @param string $text
+ * Canonical message text (generally en_US).
+ * @param array $params
+ * @return string
+ * Translated text.
+ * @see ts
+ */
+ public static function ts($text, $params = []): string {
+ if (!array_key_exists('domain', $params)) {
+ $params['domain'] = [self::LONG_NAME, NULL];
+ }
+ return ts($text, $params);
+ }
+
+ /**
+ * Get the URL of a resource file (in this extension).
+ *
+ * @param string|NULL $file
+ * Ex: NULL.
+ * Ex: 'css/foo.css'.
+ * @return string
+ * Ex: 'http://example.org/sites/default/ext/org.example.foo'.
+ * Ex: 'http://example.org/sites/default/ext/org.example.foo/css/foo.css'.
+ */
+ public static function url($file = NULL): string {
+ if ($file === NULL) {
+ return rtrim(CRM_Core_Resources::singleton()->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 <compatibility>, 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 <compatibility>, 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 <compatibility>, 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']);
+ }
+ }
+}
--- /dev/null
+<?php
+
+require_once 'user_dashboard.civix.php';
+use CRM_UserDashboard_ExtensionUtil as E;
+
+/**
+ * Implements hook_civicrm_config().
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_config/
+ */
+function user_dashboard_civicrm_config(&$config): void {
+ _user_dashboard_civix_civicrm_config($config);
+}
+
+/**
+ * Implements hook_civicrm_install().
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install
+ */
+function user_dashboard_civicrm_install(): void {
+ _user_dashboard_civix_civicrm_install();
+}
+
+/**
+ * Implements hook_civicrm_enable().
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable
+ */
+function user_dashboard_civicrm_enable(): void {
+ _user_dashboard_civix_civicrm_enable();
+}
+
+/**
+ * Tag SavedSearches with the "UserDashboard" tag.
+ *
+ * The reason for using this hook is that it's write-once (just during insert),
+ * and after that the user can freely tag and untag searches.
+ *
+ * If the tag was part of the .mgd.php file for each search then it would "stick" and
+ * the user would not be able to remove tags without their changes reverting on every managed reconcile.
+ * Also, adding it to the .mgd.php file requires hacking the exported api call with a 'chain', etc.
+ *
+ * @implements CRM_Utils_Hook::post()
+ */
+function user_dashboard_civicrm_post($action, $entity, $id, $savedSearch) {
+ if ($entity !== 'SavedSearch' || $action !== 'create' || !str_starts_with($savedSearch->name, 'UserDashboard_')) {
+ return;
+ }
+
+ // Transition note: the legacy dashboard used a setting ('user_dashboard_options')
+ // to control which panes are enabled.
+ // This new extension uses a tag.
+ // For the next year or so, we'll conditionally tag the searches in this extension based on that setting.
+ // When the transition is complete, the setting can be deleted and this class can be simplified to unconditionally
+ // tag all SavedSearches in this extension.
+ $legacySetting = Civi\Api4\Setting::get(FALSE)
+ ->addSelect('user_dashboard_options:name')
+ ->execute()
+ ->first();
+
+ // If the legacy setting corresponding to this pane is enabled, tag it
+ $settingNames = [
+ 'UserDashboard_Activities' => 'Assigned Activities',
+ 'UserDashboard_Groups' => 'Groups',
+ 'UserDashboard_Pledges' => 'CiviPledge',
+ 'UserDashboard_Contributions' => 'CiviContribute',
+ 'UserDashboard_Events' => 'CiviEvent',
+ 'UserDashboard_Memberships' => 'CiviMember',
+ 'UserDashboard_PCPs' => 'PCP',
+ 'UserDashboard_Relationships' => 'Permissioned Orgs',
+ ];
+
+ $settingName = $settingNames[$savedSearch->name] ?? NULL;
+
+ if (!$settingName || in_array($settingName, $legacySetting['value'], TRUE)) {
+ Civi\Api4\EntityTag::save(FALSE)
+ ->addRecord(['entity_table' => 'civicrm_saved_search', 'entity_id' => $id, 'tag_id:name' => 'UserDashboard'])
+ ->execute();
+ }
+}