diff --git a/dt-contacts/contacts.php b/dt-contacts/contacts.php index dbf409a93..7f62387ad 100644 --- a/dt-contacts/contacts.php +++ b/dt-contacts/contacts.php @@ -9,11 +9,11 @@ 'description' => 'Default contact functionality' ]; $modules['access_module'] = [ - 'name' => 'Access Module', + 'name' => 'Follow-up', 'enabled' => true, 'prerequisites' => [ 'contacts_base' ], 'post_type' => 'contacts', - 'description' => 'Field and workflows for follow-up ministries', + 'description' => 'Manage incoming contacts from various sources and assign them to users for follow-up.', 'submodule' => true ]; return $modules; diff --git a/dt-contacts/module-baptisms.php b/dt-contacts/module-baptisms.php index ced4b4ca7..97d67e126 100644 --- a/dt-contacts/module-baptisms.php +++ b/dt-contacts/module-baptisms.php @@ -3,11 +3,11 @@ add_filter( 'dt_post_type_modules', function( $modules ){ $modules['contacts_baptisms_module'] = [ - 'name' => 'Baptisms Module', + 'name' => 'Baptisms', 'enabled' => true, 'prerequisites' => [ 'contacts_base' ], 'post_type' => 'contacts', - 'description' => 'Adds baptism fields and workflows', + 'description' => 'Track contact baptism relationships, dates and generations', 'submodule' => true, ]; return $modules; @@ -332,4 +332,4 @@ function(e, newContact, fieldKey, optionKey, action) { 'Coaching Module', + 'name' => 'Coaching', 'enabled' => true, 'prerequisites' => [ 'contacts_base' ], 'post_type' => 'contacts', - 'description' => 'Adds coaching fields, list filters and workflows', + 'description' => 'Track contact coaching relationships', 'submodule' => true, ]; return $modules; diff --git a/dt-contacts/module-faith.php b/dt-contacts/module-faith.php index 35518d4f2..c8fa5977d 100644 --- a/dt-contacts/module-faith.php +++ b/dt-contacts/module-faith.php @@ -3,11 +3,11 @@ add_filter( 'dt_post_type_modules', function( $modules ){ $modules['contacts_faith_module'] = [ - 'name' => 'Faith Module', + 'name' => 'Faith', 'enabled' => true, 'prerequisites' => [ 'contacts_base' ], 'post_type' => 'contacts', - 'description' => 'Adds faith field status and faith milestones', + 'description' => 'Track progress of contacts in their faith journey', 'submodule' => true, ]; return $modules; diff --git a/dt-core/admin/admin-settings-endpoints.php b/dt-core/admin/admin-settings-endpoints.php index 4f2fbb246..e88fda82a 100644 --- a/dt-core/admin/admin-settings-endpoints.php +++ b/dt-core/admin/admin-settings-endpoints.php @@ -223,6 +223,52 @@ public function add_api_routes() { 'permission_callback' => [ $this, 'default_permission_check' ], ] ); + register_rest_route( + $this->namespace, '/modules-update', [ + 'methods' => 'POST', + 'callback' => [ $this, 'update_modules' ], + 'permission_callback' => [ $this, 'default_permission_check' ], + ] + ); + register_rest_route( + $this->namespace, '/update-dt-options', [ + 'methods' => 'POST', + 'callback' => [ $this, 'update_dt_options' ], + 'permission_callback' => [ $this, 'default_permission_check' ], + ] + ); + } + + public function update_dt_options( WP_REST_REQUEST $request ){ + $params = $request->get_params(); + $updated = false; + foreach ( $params as $option_key => $option_value ){ + //only allow updating D.T options + if ( strpos( $option_key, 'dt_' ) !== 0 ){ + continue; + } + update_option( $option_key, $option_value ); + $updated = true; + } + return $updated; + } + + public function update_modules( WP_REST_REQUEST $request ) { + $modules_option_name = 'dt_post_type_modules'; + $modules_to_update = $request->get_param( 'modules' ); + $modules_to_update = dt_recursive_sanitize_array( $modules_to_update ); + + $modules = get_option( $modules_option_name ); + + foreach ( $modules_to_update as $key => $enabled ){ + if ( !isset( $modules[$key] ) ){ + $modules[$key] = []; + } + $modules[$key]['enabled'] = !empty( $enabled ); + } + + update_option( $modules_option_name, $modules ); + return true; } public function update_languages( WP_REST_REQUEST $request ) { diff --git a/dt-core/admin/components/setup-wizard-celebration.js b/dt-core/admin/components/setup-wizard-celebration.js new file mode 100644 index 000000000..e33214a59 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-celebration.js @@ -0,0 +1,60 @@ +import { html } from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardCelebration extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + }; + } + + constructor() { + super(); + this.translations = window.setupWizardShare.translations; + } + + back() { + this.dispatchEvent(new CustomEvent('back')); + } + async next() { + await window.dt_admin_shared.update_dt_options({ + dt_setup_wizard_completed: true, + }); + window.location.href = window.setupWizardShare.admin_url; + } + + render() { + return html` +
+

All finished

+
+

+ After closing this setup wizard, you'll find yourself in the + WordPress admin dashboard. From there, you can explore these + settings and continue customizing your Disciple.Tools site. +

+

+ Disciple.Tools has a huge ability to be customized to serve your + needs. If you have any questions or need any further assistance in + getting Disciple.Tools. to work for you, please reach out on our + community forum or our discord channel. +

+ + Community Forum + + + Discord Invitation + +
+ +
+ `; + } +} +customElements.define('setup-wizard-celebration', SetupWizardCelebration); diff --git a/dt-core/admin/components/setup-wizard-controls.js b/dt-core/admin/components/setup-wizard-controls.js new file mode 100644 index 000000000..45a440619 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-controls.js @@ -0,0 +1,54 @@ +import { html } from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardControls extends OpenLitElement { + static get properties() { + return { + hideBack: { type: Boolean }, + hideSkip: { type: Boolean }, + backLabel: { type: String }, + nextLabel: { type: String }, + skipLabel: { type: String }, + saving: { type: Boolean }, + }; + } + back() { + this.dispatchEvent(new CustomEvent('back')); + } + next() { + if (this.saving) { + return; + } + this.dispatchEvent(new CustomEvent('next')); + } + skip() { + this.dispatchEvent(new CustomEvent('skip')); + } + render() { + return html` +
+ ${this.hideSkip + ? '' + : html` + + `} + ${this.hideBack + ? '' + : html` + + `} + +
+ `; + } +} +customElements.define('setup-wizard-controls', SetupWizardControls); diff --git a/dt-core/admin/components/setup-wizard-details.js b/dt-core/admin/components/setup-wizard-details.js new file mode 100644 index 000000000..ebea0e740 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-details.js @@ -0,0 +1,43 @@ +import { + html, + css, +} from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardDetails extends OpenLitElement { + static styles = [ + css` + :host { + display: block; + } + `, + ]; + + static get properties() { + return { + step: { type: Object }, + firstStep: { type: Boolean }, + }; + } + + back() { + this.dispatchEvent(new CustomEvent('back')); + } + next() { + this.dispatchEvent(new CustomEvent('next')); + } + + render() { + return html` +
+
Sort out details here
+ +
+ `; + } +} +customElements.define('setup-wizard-details', SetupWizardDetails); diff --git a/dt-core/admin/components/setup-wizard-intro.js b/dt-core/admin/components/setup-wizard-intro.js new file mode 100644 index 000000000..e12d0d524 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-intro.js @@ -0,0 +1,64 @@ +import { html } from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardIntro extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + }; + } + + back() { + this.dispatchEvent(new CustomEvent('back')); + } + next() { + this.dispatchEvent(new CustomEvent('next')); + } + + render() { + return html` +
+

Setting up Disciple.Tools for you

+
+

+ We're glad you are here, and we want to help set you up so you can take + advantage of the power tool that is Disciple.Tools. +

+

+ Disciple.Tools can be used in many ways from managing connections and + relationships, all the way through to tracking and managing a + movement of Disciple Making. +

+

+ In order to help you, we want to take you through a series of + choices to give you the best start at getting Disciple.Tools setup + ready to suit your needs. +

+

+

    +
  1. + We'll choose which parts of the system we want to enable +
  2. +
  3. + We'll select which plugins we want to install +
  4. +
  5. + We'll look at some extra setup options +
  6. +
+

+

+ Ready? Let's get started. +

+
+ +
+ `; + } +} +customElements.define('setup-wizard-intro', SetupWizardIntro); diff --git a/dt-core/admin/components/setup-wizard-keys.js b/dt-core/admin/components/setup-wizard-keys.js new file mode 100644 index 000000000..c25b19297 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-keys.js @@ -0,0 +1,275 @@ +import { html } from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardKeys extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + firstStep: { type: Boolean }, + toastMessage: { type: String, attribute: false }, + _changed: { type: String, attribute: false }, + _options: { type: Object, attribute: false }, + _saving: { type: Boolean, attribute: false }, + _finished: { type: Boolean, attribute: false }, + }; + } + + constructor() { + super(); + this.toastMessage = ''; + this._saving = false; + this._finished = false; + this._changed = false; + this._options = { + dt_mapbox_api_key: '', + dt_google_map_key: '', + }; + this.show_mapbox_instructions = false; + this.show_google_instructions = false; + } + + back() { + this.dispatchEvent(new CustomEvent('back')); + } + async next() { + if (this._finished && !this._changed) { + this.dispatchEvent(new CustomEvent('next')); + return; + } + this._saving = true; + await window.dt_admin_shared.update_dt_options(this._options); + this._saving = false; + this._finished = true; + this._changed = false; + this.setToastMessage('Keys saved'); + } + skip() { + this.dispatchEvent(new CustomEvent('next')); + } + nextLabel() { + if (this._finished || !this._changed) { + return 'Next'; + } + return 'Confirm'; + } + setToastMessage(message) { + this.toastMessage = message; + } + dismissToast() { + this.toastMessage = ''; + } + + updateOption(option, value) { + this._options[option] = value; + this._changed = true; + this._finished = false; + this.dismissToast(); + } + + firstUpdated() { + this._options.dt_mapbox_api_key = this.step.config.dt_mapbox_api_key; + this._options.dt_google_map_key = this.step.config.dt_google_map_key; + } + + render() { + return html` +
+

Mapping and Geocoding

+
+

+ Disciple.Tools provides basic mapping functionality for locations at + the country, state, or county level. For more precise geolocation, + such as street addresses or cities, additional tools like Mapbox and + Google API keys are recommended but not mandatory. +

+

+ Mapbox offers detailed maps with precise location pins, while Google + enables accurate worldwide geocoding, especially in certain + countries where Mapbox data is limited. +

+

+ Both tools provide free usage tiers sufficient for most users, + though exceeding limits may incur charges. Setup involves creating + accounts, generating API keys, and adding them here (or in + Disciple.Tools settings later). +

+

+ For additional details and information, refer to the + Geolocation Documentation. +

+ + + + + + + + + + + + + + + + + + + + +
NameDescriptionKey
Mapbox key + Upgrade maps and get precise locations with a Mapbox key. +
+ +
+
    +
  1. + Go to + Mapbox.com. +
  2. +
  3. + Register for a new account (Mapbox.com)
    + (email required. A credit card might be required, + though you will likely not go over the free monthly + quota.) +
  4. +
  5. + Once registered, go to your account home page. (Account Page)
    +
  6. +
  7. + Inside the section labeled "Access Tokens", either + create a new token or use the default token provided. + Copy this token. +
  8. +
  9. + Paste the token into the "Mapbox API Token" field in the + box above. +
  10. +
+
+
+ { + this.updateOption('dt_mapbox_api_key', e.target.value); + }} + /> +
Google key + Upgrade maps and get even more precise locations with a Google + key. +
+ +
+
    +
  1. + Go to + https://console.cloud.google.com. +
  2. +
  3. + Register with your Google Account or Gmail Account +
  4. +
  5. Once registered, create a new project.
  6. +
  7. + Then go to APIs & Services > Credentials and "Create + Credentials" API Key. Copy this key. +
  8. +
  9. + Paste the key into the "Google API Key" field in the box + above here in the Disciple.Tools Mapping Admin. +
  10. +
  11. + Again, in Google Cloud Console, in APIs & Services go to + Library. Find and enable: (1) Maps Javascript API, (2) + Places API, and (3) GeoCoding API. +
  12. +
  13. + Lastly, in in Credentials for the API key it is + recommended in the settings of the API key to be set + "None" for Application Restrictions and "Don't restrict + key" in API Restrictions. +
  14. +
+
+
+ { + this.updateOption('dt_google_map_key', e.target.value); + }} + /> +
+
+ + ${this.toastMessage} +
+
+ +
+ `; + } +} +customElements.define('setup-wizard-keys', SetupWizardKeys); diff --git a/dt-core/admin/components/setup-wizard-modules.js b/dt-core/admin/components/setup-wizard-modules.js new file mode 100644 index 000000000..c6a783bc3 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-modules.js @@ -0,0 +1,198 @@ +import { + html, + repeat, +} from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardModules extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + stage: { type: String, attribute: false }, + toastMessage: { type: String, attribute: false }, + availableModules: { type: Array, attribute: false }, + selectedModules: { type: Object, attribute: false }, + loading: { Boolean, attribute: false }, + finished: { Boolean, attribute: false }, + }; + } + + constructor() { + super(); + this.toastMessage = ''; + this.stage = 'work'; + this.data = window.setupWizardShare.data; + this.translations = window.setupWizardShare.translations; + this.loading = false; + this.availableModules = Object.entries(this.data.modules) + .map(([key, module]) => { + return { + key, + ...module, + }; + }) + .reduce((modules, module) => { + if (module.locked) { + return modules; + } + modules.push(module); + return modules; + }, []); + this.selectedModules = this.availableModules.reduce((modules, module) => { + modules[module.key] = false; + return modules; + }, {}); + + Object.entries(this.data.use_cases).forEach(([key, useCase]) => { + if (useCase.selected) { + useCase.recommended_modules.forEach((moduleKey) => { + this.selectedModules[moduleKey] = true; + }); + } + }); + } + + back() { + this.dispatchEvent(new CustomEvent('back')); + } + async next() { + if (this.finished) { + this.dispatchEvent(new CustomEvent('next')); + } else { + await this.submitModuleChanges(); + this.finished = true; + this.setToastMessage('The modules you have chosen have been turned on.'); + } + } + skip() { + this.dispatchEvent(new CustomEvent('next')); + } + nextLabel() { + if (this.finished) { + return this.translations.next; + } + return this.translations.confirm; + } + setToastMessage(message) { + this.toastMessage = message; + } + dismissToast() { + this.toastMessage = ''; + } + toggleModule(key) { + const checkbox = this.renderRoot.querySelector(`#${key}`); + if (this.selectedModules[key]) { + checkbox.checked = false; + this.selectedModules[key] = false; + } else { + checkbox.checked = true; + this.selectedModules[key] = true; + } + this.finished = false; + this.dismissToast(); + } + async submitModuleChanges() { + this.loading = true; + this.requestUpdate(); + await window.dt_admin_shared.modules_update(this.selectedModules); + + this.dispatchEvent( + new CustomEvent('enableSteps', { + detail: { people_groups: this.selectedModules.people_groups_module }, + }), + ); + + window.setupWizardShare.enabledModules = this.selectedModules; + this.loading = false; + } + + render() { + return html` +
+

Module selection

+
+ ${this.stage === 'work' + ? html` +

+ The recommended modules for your chosen use case(s) are + selected below. +

+

+ Feel free to change this selection according to what you need + Disciple.Tools to do. +

+
+ + + + + + + + + + ${Object.keys(this.availableModules).length > 0 + ? html` + ${repeat( + this.availableModules, + (module) => module.key, + (module) => { + return html` + + this.toggleModule(module.key)} + > + + + + + `; + }, + )} + ` + : ''} + +
ModuleDescription
+ + ${module.name}${module.description}
+
+
+ +

${this.toastMessage}

+

+ You can enable and disable these modules to your liking in + the "Settings (D.T)" section of the Wordpress admin. +

+
+ ` + : ''} +
+ +
+ `; + } +} +customElements.define('setup-wizard-modules', SetupWizardModules); diff --git a/dt-core/admin/components/setup-wizard-open-element.js b/dt-core/admin/components/setup-wizard-open-element.js new file mode 100644 index 000000000..cd1c45d7b --- /dev/null +++ b/dt-core/admin/components/setup-wizard-open-element.js @@ -0,0 +1,7 @@ +import { LitElement } from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; + +export class OpenLitElement extends LitElement { + createRenderRoot() { + return this; + } +} diff --git a/dt-core/admin/components/setup-wizard-people-groups.js b/dt-core/admin/components/setup-wizard-people-groups.js new file mode 100644 index 000000000..87eaba1f7 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-people-groups.js @@ -0,0 +1,353 @@ +import { + html, + repeat, +} from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardPeopleGroups extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + peopleGroups: { type: Array, attribute: false }, + batchSize: { type: Number, attribute: false }, + countryInstalling: { type: String, attribute: false }, + toastMessage: { type: String, attribute: false }, + firstStep: { type: Boolean }, + saving: { type: Boolean, attribute: false }, + finished: { type: Boolean, attribute: false }, + gettingBatches: { type: Boolean, attribute: false }, + importingAll: { type: Boolean, attribute: false }, + totalPeopleGroupsInstalled: { type: Boolean, attribute: false }, + totalPeopleGroups: { type: Boolean, attribute: false }, + importingFinished: { type: Boolean, attribute: false }, + }; + } + + constructor() { + super(); + this.saving = false; + this.finished = false; + this.toastMessage = ''; + this.peopleGroups = []; + this.peopleGroupsInstalled = []; + + this.stoppingImport = false; + this.importStopped = false; + } + + back() { + this.dispatchEvent(new CustomEvent('back')); + } + async next() { + if (this.isFinished() || this.getSelectedPeopleGroups().length === 0) { + this.dispatchEvent(new CustomEvent('next')); + return; + } + + this.saving = true; + await this.installPeopleGroups(); + this.saving = false; + this.finished = true; + this.setToastMessage('People groups installed'); + } + skip() { + this.dispatchEvent(new CustomEvent('next')); + } + nextLabel() { + if (this.isFinished() || this.getSelectedPeopleGroups().length === 0) { + return 'Next'; + } + return 'Confirm'; + } + isFinished() { + return this.finished || this.importingFinished; + } + + async selectCountry(event) { + const country = event.target.value; + + const peopleGroups = + await window.dt_admin_shared.people_groups_get(country); + + this.peopleGroups = peopleGroups; + } + selectPeopleGroup(people) { + people.selected = !people.selected; + this.finished = false; + this.requestUpdate(); + } + selectAll() { + const selectAllOrNone = + this.getSelectedPeopleGroups().length === this.peopleGroups.length + ? false + : true; + const peopleGroupsInstalled = this.peopleGroups.filter( + ({ installed, ROP3 }) => + !installed && !this.peopleGroupsInstalled.includes(ROP3), + ); + + peopleGroupsInstalled.forEach((group) => { + group.selected = selectAllOrNone; + }); + if (peopleGroupsInstalled.length > 0) { + this.finished = false; + this.requestUpdate(); + } + } + getSelectedPeopleGroups() { + return this.peopleGroups.filter((peopleGroup) => peopleGroup.selected); + } + async installPeopleGroups() { + const peopleGroupsToInstall = this.getSelectedPeopleGroups(); + const installationPromises = peopleGroupsToInstall.map((peopleGroup, i) => { + return this.wait(500 * i).then(() => { + return this.installPeopleGroup(peopleGroup); + }); + }); + + await Promise.all(installationPromises); + } + async installPeopleGroup(peopleGroup) { + peopleGroup.selected = false; + peopleGroup.installing = true; + this.requestUpdate(); + + let data = { + rop3: peopleGroup.ROP3, + country: peopleGroup.Ctry, + location_grid: peopleGroup.location_grid, + }; + + await window.dt_admin_shared.people_groups_install(data); + + peopleGroup.installing = false; + peopleGroup.installed = true; + this.peopleGroupsInstalled.push(peopleGroup.ROP3); + this.requestUpdate(); + } + wait(time) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, time); + }); + } + + async getAllBatches() { + this.gettingBatches = true; + const result = await window.dt_admin_shared.people_groups_get_batches(); + this.gettingBatches = false; + + if ( + confirm( + `Are you sure you want to import a total of ${result.total_records} people groups?`, + ) + ) { + return this.importAll(result.batches, result.total_records); + } + } + + async importAll(batches, total) { + this.stoppingImport = false; + this.importStopped = false; + this.importingAll = true; + this.totalPeopleGroupsInstalled = 0; + this.totalPeopleGroups = total; + for (const country in batches) { + if (this.stoppingImport) { + this.stoppingImport = false; + this.importStopped = true; + this.finishImport('Importing stopped'); + return; + } + const batch = batches[country]; + + this.countryInstalling = country; + this.batchSize = batch.length; + + await window.dt_admin_shared.people_groups_install_batch(batch); + + this.totalPeopleGroupsInstalled = + this.totalPeopleGroupsInstalled + batch.length; + /* if (this.batchNumber === 2) { + break; + } */ + } + this.finishImport('Finished importing all people groups'); + } + finishImport(message) { + this.importingAll = false; + this.importingFinished = true; + this.setToastMessage(message); + } + stopImport() { + this.stoppingImport = true; + this.requestUpdate(); + } + setToastMessage(message) { + this.toastMessage = message; + } + dismissToast() { + this.toastMessage = ''; + } + + render() { + return html` +
+

Import People Groups

+
+
+

+ If you're not sure which people groups to add, you can add them + all.
(There are around 17,000.) +

+ ${!this.importingAll && !this.importingFinished + ? html` + + ` + : ''} + ${this.importingAll && + !this.importingFinished && + !this.importStopped + ? html` +
+ +

+ Installing ${this.batchSize} people groups of + ${this.countryInstalling} +

+

+ Installed: + ${this.totalPeopleGroupsInstalled}/${this + .totalPeopleGroups} +

+
+ ` + : ''} + ${this.importingAll + ? html` + + ` + : ''} + ${this.stoppingImport ? html`

Stopping Import

` : ''} + ${this.importStopped ? html`

Import Stopped

` : ''} +
+
+ ${!this.importingAll && !this.importingFinished + ? html`
+

or

+
    +
  1. Choose a country in the dropdown
  2. +
  3. + Add only the people groups that you need for linking to + contacts in D.T. +
  4. +
+ +
` + : ''} +
+ ${this.peopleGroups.length > 0 + ? html` + + + + + + + + + + ${repeat( + this.peopleGroups, + (people) => people[28], + (people) => { + let action = 'Added'; + if (people.installing) { + action = html``; + } else if ( + !people.installed && + !this.peopleGroupsInstalled.includes(people.ROP3) + ) { + action = html``; + } + + return html` + this.selectPeopleGroup(people)} + > + + + + + `; + }, + )} + +
NameROP3 + Add
+ +
${people.PeopNameAcrossCountries}${people.ROP3}${action}
+ ` + : ''} +
+
+
+ + ${this.toastMessage} +
+
+ +
+ `; + } +} +customElements.define('setup-wizard-people-groups', SetupWizardPeopleGroups); diff --git a/dt-core/admin/components/setup-wizard-plugins.js b/dt-core/admin/components/setup-wizard-plugins.js new file mode 100644 index 000000000..1c8370a03 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-plugins.js @@ -0,0 +1,215 @@ +import { html } from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardPlugins extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + firstStep: { type: Boolean }, + toastMessage: { type: String, attribute: false }, + loading: { type: Boolean, attribute: false }, + finished: { type: Boolean, attribute: false }, + }; + } + constructor() { + super(); + this.toastMessage = ''; + this.loading = false; + this.finished = false; + this.plugins = window.setupWizardShare.data.plugins; + let recommended_plugins = []; + Object.keys(window.setupWizardShare.data.use_cases || {}).forEach( + (use_case) => { + if (window.setupWizardShare.data.use_cases[use_case].selected) { + recommended_plugins = recommended_plugins.concat( + window.setupWizardShare.data.use_cases[use_case] + .recommended_plugins, + ); + } + }, + ); + //pre select recommended plugins + this.plugins.forEach((plugin) => { + if (recommended_plugins.includes(plugin.slug)) { + //only install plugins if the user has permissions to. + plugin.selected = + plugin.installed || window.setupWizardShare.can_install_plugins; + } + }); + } + + back() { + if (!this.loading) { + this.dispatchEvent(new CustomEvent('back')); + } + } + skip() { + if (!this.loading) { + this.dispatchEvent(new CustomEvent('next')); + } + } + async next() { + const plugins_to_install = this.getPluginsToInstall(); + if (this.canNavigate()) { + this.dispatchEvent(new CustomEvent('next')); + return; + } + this.loading = true; + + for (let plugin of plugins_to_install) { + if (!plugin.installed) { + plugin.installing = true; + this.requestUpdate(); + await window.dt_admin_shared.plugin_install(plugin.download_url); + await window.dt_admin_shared.plugin_activate(plugin.slug); + plugin.installing = false; + plugin.installed = true; + // plugin.active = true; + } + if (plugin.selected && !plugin.active) { + plugin.installing = true; + this.requestUpdate(); + await window.dt_admin_shared.plugin_activate(plugin.slug); + plugin.installing = false; + plugin.active = true; + } + } + this.loading = false; + this.finished = true; + plugins_to_install.length && + this.setToastMessage('Finished installing and activating plugins'); + this.requestUpdate(); + } + + select_all() { + let already_all_selected = + this.plugins.filter((plugin) => plugin.selected).length === + this.plugins.length; + this.plugins.forEach((plugin) => { + plugin.selected = !already_all_selected; + }); + this.requestUpdate(); + } + nextLabel() { + if (this.canNavigate()) { + return 'Next'; + } + return 'Confirm'; + } + canNavigate() { + return this.finished || this.getPluginsToInstall().length === 0; + } + setToastMessage(message) { + this.toastMessage = message; + } + dismissToast() { + this.toastMessage = ''; + } + getPluginsToInstall() { + return this.plugins.filter((plugin) => plugin.selected && !plugin.active); + } + togglePlugin(plugin, disabled) { + plugin.selected = !plugin.selected && !disabled; + this.finished = false; + this.dismissToast(); + this.requestUpdate(); + } + + render() { + return html` +
+

Recommended Plugins

+
+

+ Plugins are optional and add additional functionality + to Disciple.Tools based on your needs. +

+

+ Plugins can be activated or deactivated at any time. You can find the full list of + Disciple.Tools plugin in the "Extensions (D.T)" tab later. +

+

+ + + + + + + + + + ${this.plugins.map((plugin) => { + const disabled = + !window.setupWizardShare.can_install_plugins && + !plugin.installed; + let action = 'Active'; + if (plugin.installing) { + action = html``; + } else if (!plugin.active) { + action = html`${disabled ? '*' : ''}`; + } + + return html` + this.togglePlugin(plugin, disabled)}> + + + + + `; + })} + +
Plugin Name + Install/Activate
+ this.select_all()} + > + select all + +
Description
${plugin.name}${action} e.stopImmediatePropagation()} + > + ${plugin.description} + + More Info + +
+ ${ + !window.setupWizardShare.can_install_plugins + ? html`

+ *Only your server administrator can install + plugins. +

` + : '' + } +
+ + ${this.toastMessage} +
+
+ +
+ `; + } +} +customElements.define('setup-wizard-plugins', SetupWizardPlugins); diff --git a/dt-core/admin/components/setup-wizard-use-cases.js b/dt-core/admin/components/setup-wizard-use-cases.js new file mode 100644 index 000000000..120208c43 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-use-cases.js @@ -0,0 +1,195 @@ +import { + html, + repeat, +} from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardUseCases extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + firstStep: { type: Boolean }, + toastMessage: { type: String, attribute: false }, + stage: { type: String, attribute: false }, + useCases: { type: Array, attribute: false }, + options: { type: Object, attribute: false }, + availableModules: { type: Array, attribute: false }, + selectedModules: { type: Array, attribute: false }, + }; + } + + constructor() { + super(); + this.stage = 'work'; + this.toastMessage = ''; + this.data = window.setupWizardShare.data; + this.translations = window.setupWizardShare.translations; + this.availableModules = []; + this.selectedModules = []; + this.options = Object.entries(this.data.use_cases).reduce( + (options, [key, useCase]) => { + const selected = + (useCase.selected && useCase.selected === true) || false; + return { + ...options, + [key]: selected, + }; + }, + {}, + ); + } + + firstUpdated() { + /* Reduce the keys down to ones that exist in the details list of use cases */ + const useCaseKeys = this.step.config.reduce((keys, key) => { + if (this.data.use_cases[key]) { + return [...keys, key]; + } + return keys; + }, []); + this.useCases = useCaseKeys.map( + (useCaseKey) => this.data.use_cases[useCaseKey], + ); + } + + back() { + switch (this.stage) { + case 'follow-up': + this.stage = 'work'; + break; + case 'work': + this.dispatchEvent(new CustomEvent('back')); + break; + } + } + next() { + switch (this.stage) { + case 'work': + this.saveOptions(); + this.stage = 'follow-up'; + break; + case 'follow-up': + this.dispatchEvent(new CustomEvent('next')); + break; + } + } + skip() { + this.dispatchEvent(new CustomEvent('next')); + } + nextLabel() { + switch (this.stage) { + case 'work': + return this.translations.confirm; + default: + return this.translations.next; + } + } + toggleOption(option) { + if (this.options[option]) { + this.options[option] = false; + } else { + this.options[option] = true; + } + this.dismissToast(); + this.stage = 'work'; + this.requestUpdate(); + } + selectedOptions() { + return Object.keys(this.options).filter((option) => this.options[option]); + } + saveOptions() { + for (const option in this.options) { + window.setupWizardShare.data.use_cases[option].selected = + this.options[option]; + } + if (this.selectedOptions().length) { + this.setToastMessage('Use cases selected'); + } else { + this.setToastMessage('No use cases selected'); + } + } + setToastMessage(message) { + this.toastMessage = message; + } + dismissToast() { + this.toastMessage = ''; + } + + render() { + return html` +
+

Use Cases

+
+ ${this.useCases + ? html` +

+ Choose one or more of these use cases to tailor what parts of + Disciple.Tools to turn on. +

+

+ You can fine tune those choices further to your own needs in + the following steps. +

+
+ + + + + + + + + + ${repeat( + this.useCases, + (option) => option.key, + (option) => html` + + + + + + `, + )} + +
Use CaseDescription
+ + ${option.name}${option.description ?? ''}
+
+ ` + : ''} +
+ + +

${this.toastMessage}

+

+ ${this.selectedOptions().length + ? 'Based on the use case(s) you have now chosen, we can recommend some modules and plugins that we think will be helpful.' + : 'In the next steps, simply choose what options seems best'} +

+
+
+ +
+ `; + } +} +customElements.define('setup-wizard-use-cases', SetupWizardUseCases); diff --git a/dt-core/admin/components/setup-wizard.js b/dt-core/admin/components/setup-wizard.js new file mode 100644 index 000000000..5755bfe2c --- /dev/null +++ b/dt-core/admin/components/setup-wizard.js @@ -0,0 +1,747 @@ +import { + html, + css, + repeat, + LitElement, + staticHtml, + unsafeStatic, +} from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; + +export class SetupWizard extends LitElement { + static styles = [ + css` + :host { + display: block; + font-size: 18px; + line-height: 1.4; + font-family: Arial, Helvetica, sans-serif; + --primary-color: #3f729b; + --primary-hover-color: #366184; + --secondary-color: #4caf50; + --default-color: #efefef; + --default-hover-color: #cdcdcd; + --default-dark: #ababab; + --s1: 1rem; + } + /* Resets */ + /* Inherit fonts for inputs and buttons */ + input, + button, + textarea, + select { + font-family: inherit; + font-size: inherit; + } + /* Set shorter line heights on headings and interactive elements */ + h1, + h2, + h3, + h4, + button, + input, + label { + line-height: 1.1; + } + /* Box sizing rules */ + *, + *::before, + *::after { + box-sizing: border-box; + } + + /* To force macs to show scrollbars */ + ::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; + } + + ::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, 0.5); + } + /* Global */ + h1, + h2, + h3 { + font-weight: 500; + color: var(--primary-color); + } + p { + max-width: 60ch; + } + ul[role='list'], + ol[role='list'] { + list-style: none; + } + button { + border: none; + padding: 0.5rem 1.5rem; + border-radius: 8px; + cursor: pointer; + background-color: var(--default-color); + transition: all 120ms linear; + } + button:hover, + button:active, + button:focus { + background-color: var(--default-hover-color); + } + select, + input { + padding: 0.2em 0.5em; + border-radius: 8px; + border: 2px solid var(--default-hover-color); + background-color: white; + } + /* Composition */ + .wrap { + padding: 1rem; + min-height: 100vh; + max-width: 1200px; + margin: auto; + } + .cluster { + display: flex; + flex-wrap: wrap; + gap: var(--space, 1rem); + justify-content: flex-start; + align-items: center; + } + .cluster[position='end'] { + justify-content: flex-end; + } + .repel { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + flex-direction: row-reverse; + } + .stack { + display: flex; + flex-direction: column; + justify-content: flex-start; + } + .stack > * { + margin-block: 0; + } + .stack > * + * { + margin-block-start: var(--spacing, 1rem); + } + .grid { + display: grid; + grid-gap: 1rem; + + &[size='small'] { + --column-size: 100px; + } + } + @supports (width: min(250px, 100%)) { + .grid { + grid-template-columns: repeat( + auto-fit, + minmax(min(var(--column-size, 250px), 100%), 1fr) + ); + } + } + .step-layout { + display: flex; + position: relative; + flex-direction: column; + height: min(80vh, 800px); + } + .step-layout > * { + margin-block: 1rem; + } + .step-layout > .content { + margin-block-end: auto; + } + .step-layout > :first-child:not(.content) { + margin-block-start: 0; + } + .step-layout > :last-child:not(.content) { + margin-block-end: 0; + } + .with-sidebar { + display: flex; + flex-wrap: wrap; + gap: var(--s1); + } + + .with-sidebar > :first-child { + flex-grow: 1; + } + + .with-sidebar > :last-child { + flex-basis: 0; + flex-grow: 999; + min-inline-size: 50%; + } + .center { + margin-left: auto; + margin-right: auto; + } + /* Utilities */ + .ms-auto { + margin-left: auto; + } + .me-auto { + margin-right: auto; + } + .align-start { + align-items: flex-start; + } + .fit-content { + width: fit-content; + } + .white { + color: white; + } + /* Blocks */ + .wizard { + border-radius: 12px; + border: 1px solid transparent; + overflow: hidden; + background-color: white; + padding: 1rem; + } + .sidebar { + background-color: white; + padding: 1rem; + border-radius: 10px; + } + .content { + overflow-y: auto; + } + .steps { + padding-left: 24px; + padding-right: 24px; + } + .step { + position: relative; + } + .step::before { + content: var(--svg-url, ''); + position: absolute; + top: 0; + bottom: 0; + height: 100%; + left: 0; + transform: translateX(-150%) scale(1.4); + } + .step[current]::before { + transform: translate(-210%) scale(1.4); + } + .btn-primary { + background-color: var(--primary-color); + color: var(--default-color); + } + .btn-primary.saving { + background-color: var(--default-dark); + cursor: progress; + } + .btn-primary:hover, + .btn-primary:focus, + .btn-primary:active { + background-color: var(--primary-hover-color); + + &.saving { + background-color: var(--default-dark); + } + } + .btn-outline { + border: 1px solid transparent; + background-color: transparent; + color: var(--primary-color); + box-shadow: none; + } + .btn-outline:hover, + .btn-outline:focus { + border-color: var(--primary-color); + background-color: transparent; + } + .btn-card { + background-color: var(--primary-color); + color: var(--default-color); + padding: 1rem 2rem; + box-shadow: 1px 1px 3px 0px var(--default-dark); + cursor: pointer; + position: relative; + } + .btn-card.selected { + background-color: var(--secondary-color); + } + .btn-card.selected:focus, + .btn-card.selected:hover, + .btn-card.selected:active { + background-color: var(--secondary-color); + } + .btn-card:focus, + .btn-card:hover, + .btn-card:active { + background-color: var(--primary-hover-color); + } + .btn-card-gray { + background-color: var(--default-color); + color: black; + } + .btn-card-gray:hover { + background-color: var(--secondary-color); + opacity: 0.8; + } + .btn-card.disabled { + background-color: var(--secondary-color); + opacity: 0.5; + color: var(--default-color); + } + .btn-card.disabled:hover { + background-color: var(--default-hover-color); + } + .card { + background-color: var(--default-color); + border-radius: 12px; + padding: 1rem 2rem; + width: fit-content; + + &.success { + background-color: var(--secondary-color); + color: var(--default-color); + } + } + .toast { + position: absolute; + bottom: 0; + right: 0; + margin: 1rem; + margin-bottom: 4rem; + transition: + opacity 300ms ease 200ms, + transform 500ms cubic-bezier(0.5, 0.05, 0.2, 1.5) 200ms; + + &[data-state='empty'] { + opacity: 0; + transform: translateY(0.25em); + transition: none; + padding: 0; + + & .close-btn { + height: 0; + } + } + + & .close-btn { + position: absolute; + color: inherit; + top: 0; + right: -0.8rem; + + &:hover { + border-color: transparent; + color: black; + } + } + } + .input-group { + display: flex; + flex-direction: column; + gap: 0.4rem; + } + .toggle { + position: relative; + display: inline-flex; + cursor: pointer; + input { + display: none; + } + div { + display: inline-block; + padding: 1rem 0.5rem; + background-color: var(--default-color); + border-radius: 8px; + width: 100%; + text-align: center; + transition: all 120ms linear; + } + input:checked + div { + background-color: var(--secondary-color); + color: white; + h1, + h2, + h3 { + color: white; + } + } + } + .breadcrumbs { + --gap: 6rem; + --divider-width: calc(var(--gap) / 2); + display: flex; + } + .breadcrumbs > * + * { + margin-left: var(--gap); + } + .breadcrumbs > * + *:before { + content: ''; + width: var(--divider-width); + position: absolute; + height: 3px; + border-radius: 10px; + background-color: var(--primary); + left: calc((var(--gap) + var(--divider-width)) / -2 - 2px); + top: calc(50% - 1px); + } + .crumb { + position: relative; + width: 16px; + height: 16px; + border-radius: 100%; + border: 2px solid var(--default-hover-color); + } + .crumb.complete { + background-color: var(--primary); + border-color: var(--primary); + } + .crumb.active { + outline: 5px solid var(--primary); + outline-offset: -10px; + } + .tag { + border: 1px solid black; + display: inline; + padding: 0.2em 0.5em; + background-color: var(--primary-color); + } + .spinner { + background: url('images/spinner.gif') no-repeat; + background-size: 20px 20px; + opacity: 0.7; + width: 20px; + height: 20px; + display: inline-block; + } + .spinner.light { + background: url('images/wpspin_light-2x.gif') no-repeat; + background-size: 20px 20px; + } + button .spinner { + vertical-align: bottom; + } + table { + padding-bottom: 1rem; + } + table td { + padding: 0.5rem; + vertical-align: top; + } + table thead tr { + background-color: var(--default-color); + } + table tr:nth-child(even) { + background-color: var(--default-color); + } + `, + ]; + + static get properties() { + return { + steps: { type: Array }, + currentStepNumber: { type: Number, attribute: false }, + decision: { type: String, attribute: false }, + }; + } + + constructor() { + super(); + + this.translations = window.setupWizardShare.translations; + this.adminUrl = window.setupWizardShare.admin_url; + this.imageUrl = window.setupWizardShare.image_url; + this.steps = []; + this.currentStepNumber = 0; + + const url = new URL(location.href); + + this.isKitchenSink = url.searchParams.has('kitchen-sink'); + //get step number from step url param + if (url.searchParams.has('step')) { + this.currentStepNumber = parseInt(url.searchParams.get('step')); + } + } + + firstUpdated() { + if ( + this.steps.length === 0 && + window.setupWizardShare && + window.setupWizardShare.steps && + window.setupWizardShare.steps.length !== 0 + ) { + this.steps = window.setupWizardShare.steps; + } + } + updated() { + const allSteps = this.renderRoot.querySelectorAll('.step') || []; + const completedSteps = + this.renderRoot.querySelectorAll('.step[completed]') || []; + const currentStep = this.renderRoot.querySelector('.step[current]'); + allSteps.forEach((step) => { + step.style.setProperty('--svg-url', ''); + }); + completedSteps.forEach((step) => { + step.style.setProperty( + '--svg-url', + `url('${this.imageUrl + 'verified.svg'}')`, + ); + }); + if (currentStep) { + currentStep.style.setProperty( + '--svg-url', + `url('${this.imageUrl + 'chevron_right.svg'}')`, + ); + } + } + + render() { + return html` +
+
+ +

${this.translations.title}

+
+
+ +
+ ${this.isKitchenSink + ? this.kitchenSink() + : html` ${this.renderStep()} `} +
+
+
+ `; + } + + back() { + this.gotoStep(this.currentStepNumber - 1); + } + next() { + this.gotoStep(this.currentStepNumber + 1); + } + enableSteps(event) { + const steps = event.detail; + + for (const key in steps) { + if (Object.prototype.hasOwnProperty.call(steps, key)) { + const enabled = steps[key]; + const stepIndex = this.steps.findIndex((step) => step.key === key); + this.steps[stepIndex].disabled = !enabled; + } + } + this.requestUpdate(); + } + gotoStep(i) { + if (i < 0) { + this.currentStepNumber = 0; + return; + } + if (i > this.steps.length - 1) { + this.currentStepNumber = this.steps.length - 1; + return; + } + if (this.steps[i].disabled) { + if (this.currentStepNumber < i) { + this.gotoStep(i + 1); + } else { + this.gotoStep(i - 1); + } + } else { + this.currentStepNumber = i; + } + } + async exit() { + await window.dt_admin_shared.update_dt_options({ + dt_setup_wizard_completed: true, + }); + location.href = this.adminUrl; + } + + renderStep() { + if (this.steps.length === 0) { + return; + } + const step = this.steps[this.currentStepNumber]; + const { component } = step; + + return staticHtml` + <${unsafeStatic(component)} + .step=${step} + ?firstStep=${this.currentStepNumber === 0} + @back=${this.back} + @next=${this.next} + @enableSteps=${this.enableSteps} + > + `; + } + + renderMultiSelect(component) { + return html` +
+ ${component.description ? html`

${component.description}

` : ''} +
+ ${component.options && component.options.length > 0 + ? component.options.map( + (option) => html` + + `, + ) + : ''} +
+
+ `; + } + renderModuleDecision(component) { + return html` +
+ ${component.description ? html`

${component.description}

` : ''} +
+ ${component.options && component.options.length > 0 + ? component.options.map( + (option) => html` + + `, + ) + : ''} +
+
+ `; + } + renderFields(component) { + return html` +
+ ${component.description ? html`

${component.description}

` : ''} +
+ ${component.options && component.options.length > 0 + ? component.options.map( + (option) => html` +
+ + +
+ `, + ) + : ''} +
+
+ `; + } + + kitchenSink() { + return html` +
+

A cluster of buttons

+
+ + +
+

A grid of button cards

+
+ + + +
+

Fields

+
+ + +
+
+ + +
+
+ + +
+

Breadcrumbs

+ + +

Selectable items

+
+ + + + + +
+

Stepper

+
+ ${this.renderStep()} +
+ + +
+
+
+ `; + } +} +customElements.define('setup-wizard', SetupWizard); diff --git a/dt-core/admin/config-dashboard.php b/dt-core/admin/config-dashboard.php index 2ab8066ec..8f989a8c7 100644 --- a/dt-core/admin/config-dashboard.php +++ b/dt-core/admin/config-dashboard.php @@ -231,7 +231,8 @@ function dt_show_news_widget() { add_meta_box( 'dt_news_feed', esc_html__( 'Disciple.Tools News Feed', 'disciple_tools' ), 'dt_show_news_widget', 'dashboard', 'side', 'high' ); - wp_add_dashboard_widget( 'dt_setup_wizard', 'Disciple.Tools Setup Wizard', function (){ + + wp_add_dashboard_widget( 'dt_setup_wizard', 'Disciple.Tools Setup Tasks', function (){ $setup_options = get_option( 'dt_setup_wizard_options', [] ); $default = [ @@ -258,11 +259,6 @@ function dt_show_news_widget() { } } - // Order array by complete status - uasort( $dt_setup_wizard_items, function ( $a, $b ) { - return $a['complete'] <=> $b['complete']; - } ); - ?>

Completed of tasks

@@ -396,16 +392,9 @@ function dt_show_news_widget() { 'hide_mark_done' => false ]; $items['explore_custom_fields'] = [ - 'label' => 'Explore Custom Fields', + 'label' => 'Explore Custom Tiles and Fields', 'description' => 'Explore the custom fields section and unlock its full potential.', - 'link' => admin_url( 'admin.php?page=dt_options&tab=custom-fields' ), - 'complete' => false, - 'hide_mark_done' => false - ]; - $items['explore_custom_tiles'] = [ - 'label' => 'Explore Custom Tiles', - 'description' => 'Explore the custom tiles section and personalize your Disicple.Tools instance.', - 'link' => admin_url( 'admin.php?page=dt_options&tab=custom-tiles' ), + 'link' => admin_url( 'admin.php?page=dt_customizations' ), 'complete' => false, 'hide_mark_done' => false ]; diff --git a/dt-core/admin/js/dt-extensions.js b/dt-core/admin/js/dt-extensions.js index 7d1d7b9a3..84806da8b 100644 --- a/dt-core/admin/js/dt-extensions.js +++ b/dt-core/admin/js/dt-extensions.js @@ -92,7 +92,7 @@ jQuery(function ($) {
-

${plugin_description}

+

${shorten_description(plugin['description'])}

${plugin_by_text.replace( '%s', @@ -151,8 +151,8 @@ jQuery(function ($) { } function shorten_description(description = '') { - if (description.length > 75) { - description = description.slice(0, 175) + '...'; + if (description.length > 88) { + description = description.slice(0, 88) + '...'; } return description; } @@ -185,70 +185,6 @@ jQuery(function ($) { card_back.append('
'); }); - function makeRequest(type, url, data, base = 'dt/v1/') { - // Add trailing slash if missing - if (!base.endsWith('/') && !url.startsWith('/')) { - base += '/'; - } - const options = { - type: type, - contentType: 'application/json; charset=utf-8', - dataType: 'json', - url: url.startsWith('http') - ? url - : `${window.wpApiSettings.root}${base}${url}`, - beforeSend: (xhr) => { - xhr.setRequestHeader('X-WP-Nonce', window.wpApiSettings.nonce); - }, - }; - if (data) { - options.data = type === 'GET' ? data : JSON.stringify(data); - } - return jQuery.ajax(options); - } - - window.API = { - plugin_install: (download_url) => - makeRequest( - 'POST', - `plugin-install`, - { - download_url: download_url, - }, - `dt-admin-settings/`, - ), - - plugin_delete: (plugin_slug) => - makeRequest( - 'POST', - `plugin-delete`, - { - plugin_slug: plugin_slug, - }, - `dt-admin-settings/`, - ), - - plugin_activate: (plugin_slug) => - makeRequest( - 'POST', - `plugin-activate`, - { - plugin_slug: plugin_slug, - }, - `dt-admin-settings/`, - ), - - plugin_deactivate: (plugin_slug) => - makeRequest( - 'POST', - `plugin-deactivate`, - { - plugin_slug: plugin_slug, - }, - `dt-admin-settings/`, - ), - }; - function get_plugin_download_url(plugin_slug) { const all_plugins = window.plugins.all_plugins; var download_url = false; @@ -262,7 +198,8 @@ jQuery(function ($) { function plugin_install(plugin_slug) { var download_url = get_plugin_download_url(plugin_slug); - window.API.plugin_install(download_url) + window.dt_admin_shared + .plugin_install(download_url) .promise() .then(function (response) { if (!response) { @@ -284,7 +221,8 @@ jQuery(function ($) { } function plugin_delete(plugin_slug) { - window.API.plugin_delete(plugin_slug) + window.dt_admin_shared + .plugin_delete(plugin_slug) .promise() .then(function (response) { if (!response) { @@ -303,7 +241,8 @@ jQuery(function ($) { } function plugin_activate(plugin_slug) { - window.API.plugin_activate(plugin_slug) + window.dt_admin_shared + .plugin_activate(plugin_slug) .promise() .then(function (response) { if (!response) { @@ -322,7 +261,8 @@ jQuery(function ($) { } function plugin_deactivate(plugin_slug) { let can_install_plugins = window.plugins.can_install_plugins; - window.API.plugin_deactivate(plugin_slug) + window.dt_admin_shared + .plugin_deactivate(plugin_slug) .promise() .then(function (response) { if (!response) { diff --git a/dt-core/admin/js/dt-shared.js b/dt-core/admin/js/dt-shared.js index bc4cd1219..84055fc24 100644 --- a/dt-core/admin/js/dt-shared.js +++ b/dt-core/admin/js/dt-shared.js @@ -4,6 +4,28 @@ shared scripts applicable to all sections. */ 'use strict'; +function makeRequest(type, url, data, base = 'dt/v1/') { + // Add trailing slash if missing + if (!base.endsWith('/') && !url.startsWith('/')) { + base += '/'; + } + const options = { + type: type, + contentType: 'application/json; charset=utf-8', + dataType: 'json', + url: url.startsWith('http') + ? url + : `${window.wpApiSettings.root}${base}${url}`, + beforeSend: (xhr) => { + xhr.setRequestHeader('X-WP-Nonce', window.wpApiSettings.nonce); + }, + }; + if (data) { + options.data = type === 'GET' ? data : JSON.stringify(data); + } + return jQuery.ajax(options); +} + window.dt_admin_shared = { escape(str) { if (typeof str !== 'string') return str; @@ -14,6 +36,85 @@ window.dt_admin_shared = { .replace(/"/g, '"') .replace(/'/g, '''); }, + + /** + * Update options. Provide an object with the options to update. + * @param options + */ + update_dt_options: (options) => + makeRequest('POST', 'update-dt-options', options, 'dt-admin-settings/'), + + plugin_install: (download_url) => + makeRequest( + 'POST', + `plugin-install`, + { + download_url: download_url, + }, + `dt-admin-settings/`, + ), + + plugin_delete: (plugin_slug) => + makeRequest( + 'POST', + `plugin-delete`, + { + plugin_slug: plugin_slug, + }, + `dt-admin-settings/`, + ), + + plugin_activate: (plugin_slug) => + makeRequest( + 'POST', + `plugin-activate`, + { + plugin_slug: plugin_slug, + }, + `dt-admin-settings/`, + ), + + plugin_deactivate: (plugin_slug) => + makeRequest( + 'POST', + `plugin-deactivate`, + { + plugin_slug: plugin_slug, + }, + `dt-admin-settings/`, + ), + modules_update: (modules) => + makeRequest( + 'POST', + 'modules-update', + { + modules, + }, + `dt-admin-settings/`, + ), + people_groups_get: (country) => + makeRequest( + 'POST', + 'search_csv', + { s: country, as_object: true }, + 'dt/v1/people-groups', + ), + people_groups_install: (data) => + makeRequest('POST', 'add_single_people_group', data, 'dt/v1/people-groups'), + people_groups_get_batches: () => + makeRequest( + 'GET', + 'get_bulk_people_groups_import_batches', + {}, + 'dt/v1/people-groups', + ), + people_groups_install_batch: (groups) => + makeRequest( + 'POST', + 'add_bulk_people_groups', + { groups }, + 'dt/v1/people-groups', + ), }; jQuery(function ($) { diff --git a/dt-core/admin/menu/menu-setup-wizard.php b/dt-core/admin/menu/menu-setup-wizard.php new file mode 100644 index 000000000..46c995877 --- /dev/null +++ b/dt-core/admin/menu/menu-setup-wizard.php @@ -0,0 +1,317 @@ + [ + 'title' => esc_html__( 'Disciple.Tools Setup Wizard', 'disciple_tools' ), + 'next' => esc_html__( 'Next', 'disciple_tools' ), + 'submit' => esc_html__( 'Submit', 'disciple_tools' ), + 'confirm' => esc_html__( 'Confirm', 'disciple_tools' ), + 'back' => esc_html__( 'Back', 'disciple_tools' ), + 'skip' => esc_html__( 'Skip', 'disciple_tools' ), + 'finish' => esc_html__( 'Finish', 'disciple_tools' ), + 'exit' => esc_html__( 'Exit', 'disciple_tools' ), + ], + 'steps' => $this->setup_wizard_steps(), + 'data' => $this->setup_wizard_data(), + 'admin_url' => admin_url(), + 'image_url' => trailingslashit( get_template_directory_uri() ) . 'dt-assets/images/', + 'can_install_plugins' => current_user_can( 'install_plugins' ), + ] ); + } + + public function dt_setup_wizard_items( $items ){ + + $is_completed = !empty( get_option( 'dt_setup_wizard_completed' ) ); + $is_administrator = current_user_can( 'manage_options' ); + + $setup_wizard_step = [ + 'label' => 'Setup Wizard', + 'description' => 'D.T. can be used in many ways from managing connections and relationships, all the way through to tracking and managing a movement of Disciple Making. In order to help you, we want to take you through a series of choices to give you the best start at getting Disciple.Tools setup ready to suit your needs.', + 'link' => esc_url( admin_url( 'admin.php?page=dt_setup_wizard' ) ), + 'complete' => $is_completed || !$is_administrator, + 'hide_mark_done' => true + ]; + + return [ + 'getting_started' => $setup_wizard_step, + ...$items, + ]; + } + + public function has_access_permission() { + return !current_user_can( 'manage_dt' ); + } + + public function filter_script_loader_tag( $tag, $handle ) { + if ( str_starts_with( $handle, 'setup-wizard' ) ) { + $tag = preg_replace( '/(.*)(><\/script>)/', '$1 type="module"$2', $tag ); + } + return $tag; + } + + public function add_dt_options_menu() { + if ( $this->has_access_permission() ) { + return; + } + + $image_url = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDUwLjY0IDQzMS41NCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgZmlsbDogIzhiYzM0YTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiB1cmwoI2xpbmVhci1ncmFkaWVudCk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudCIgeDE9IjIyNS4zMyIgeTE9IjI0My44IiB4Mj0iNDUwLjY0IiB5Mj0iMjQzLjgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMWQxZDFiIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iLjQ3IiBzdG9wLWNvbG9yPSIjOGJjMzRhIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzhiYzM0YSIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICA8L2RlZnM+CiAgPGcgaWQ9IkxheWVyXzEtMiIgZGF0YS1uYW1lPSJMYXllciAxIj4KICAgIDxnPgogICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTIiIHBvaW50cz0iNDUwLjY0IDQzMS41NCAzNzUuNTQgNDMxLjU0IDIyNS4zMyAxMTcuMjcgMjU0LjU5IDU2LjA1IDQ1MC42NCA0MzEuNTQiLz4KICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy0xIiBwb2ludHM9IjI1NC41OSA1Ni4wNSAyMjUuMzMgMTE3LjI3IDIyNS4zMiAxMTcuMjcgNzUuMTEgNDMxLjU0IDAgNDMxLjU0IDI5LjM1IDM3NS4zMyAyMDUuMyAzOC4zNSAyMjUuMzIgMCAyNDQuMzQgMzYuNDMgMjU0LjU5IDU2LjA1Ii8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4='; + add_menu_page( + __( 'Setup Wizard (D.T)', 'disciple_tools' ), + __( 'Setup Wizard (D.T)', 'disciple_tools' ), + 'manage_dt', + 'dt_setup_wizard', + [ $this, 'content' ], + $image_url, + 52, + ); + /* Hide the setup wizard in the menu */ + remove_menu_page( 'dt_setup_wizard' ); + } + + public function content() { + if ( $this->has_access_permission() ) { + wp_die( 'You do not have sufficient permissions to access this page.' ); + } + + ?> + + + + + + 'intro', + 'name' => 'Intro', + 'component' => 'setup-wizard-intro', + ], + [ + 'key' => 'choose_your_use_cases', + 'name' => 'Use cases', + 'component' => 'setup-wizard-use-cases', + 'description' => 'How are you planning to use DT?', + 'config' => [ + 'crm', + 'media', + 'dmm', + ] + ], + [ + 'key' => 'choose_your_modules', + 'name' => 'Modules', + 'component' => 'setup-wizard-modules', + 'description' => 'What modules do you want to use?', + ], + [ + 'key' => 'plugins', + 'name' => 'Plugins', + 'description' => 'Choose which plugins to install.', + 'component' => 'setup-wizard-plugins', + ], + [ + 'key' => 'site_keys', + 'name' => 'Site keys', + 'description' => 'Fill in some site details', + 'component' => 'setup-wizard-keys', + 'config' => [ + 'dt_google_map_key' => Disciple_Tools_Google_Geocode_API::get_key(), + 'dt_mapbox_api_key' => DT_Mapbox_API::get_key(), + ], + ], + [ + 'key' => 'people_groups', + 'name' => 'People Groups', + 'description' => 'Install desired people groups data', + 'component' => 'setup-wizard-people-groups', + 'disabled' => true, + 'config' => [ + 'countries' => Disciple_Tools_People_Groups::get_country_dropdown(), + ], + ], + [ + 'key' => 'celebration', + 'name' => 'Finished', + 'component' => 'setup-wizard-celebration', + ] + ]; + + $steps = apply_filters( 'dt_setup_wizard_steps', $steps ); + + return $steps; + } + + public static function get_plugins_list(){ + $dt_plugins = Disciple_Tools_Tab_Featured_Extensions::get_dt_plugins(); + $enabled_plugins = [ + 'disciple-tools-dashboard', + 'disciple-tools-webform', + 'disciple-tools-facebook', + 'disciple-tools-import', + 'disciple-tools-bulk-magic-link-sender', + 'disciple-tools-team-module', + 'disciple-tools-storage', + 'disciple-tools-prayer-campaigns', + ]; + if ( is_multisite() ){ + $enabled_plugins[] = 'disciple-tools-multisite'; + } + //dt-home + //auto assignment + //share app + + + $plugin_data = []; + foreach ( $dt_plugins as $plugin ) { + if ( in_array( $plugin->slug, $enabled_plugins, true ) ) { + $plugin_data[] = $plugin; + } + } + return $plugin_data; + } + + + public function setup_wizard_data() : array { + $modules = dt_get_option( 'dt_post_type_modules' ); + $plugin_data = self::get_plugins_list(); + $data = [ + 'use_cases' => [ + 'crm' => [ + 'key' => 'crm', + 'name' => 'Simple Setup - Relationship Manager', + 'description' => 'Launch people ( seekers or believers ) on a journey that leads + them closer to Christ. Set up your own fields, integrations and workflows to + track them as them go.', + 'recommended_modules' => [], + 'recommended_plugins' => [ + 'disciple-tools-webform', + 'disciple-tools-import', + 'disciple-tools-bulk-magic-link-sender', + ], + ], + 'media' => [ + 'key' => 'media', + 'name' => 'Media or Follow-up Ministry', + 'description' => 'Do you find seekers through media or through events + or trainings? Let your team(s) steward these leads, letting none fall + through the cracks and inviting them into deeper relationships + with God and others.', + 'recommended_modules' => [ + 'access_module', + 'contacts_faith_module', + 'contacts_baptisms_module', + ], + 'recommended_plugins' => [ + 'disciple-tools-webform', + 'disciple-tools-facebook', + 'disciple-tools-dashboard', + 'disciple-tools-import', + 'disciple-tools-bulk-magic-link-sender', + ], + ], + 'dmm' => [ + 'key' => 'dmm', + 'name' => 'Church Growth and Disciple Making', + 'description' => 'Invest in the growth of individuals and churches as they multiply. + Monitor church health and track individuals through coaching and faith milestones.', + 'recommended_modules' => [ + 'contacts_baptisms_module', + 'contacts_faith_module', + 'groups_base', + 'people_groups_module' + ], + 'recommended_plugins' => [ + 'disciple-tools-webform', + 'disciple-tools-bulk-magic-link-sender', + 'disciple-tools-training', + 'disciple-tools-import', + ], + ], + ], + 'modules' => $modules, + 'plugins' => $plugin_data, + ]; + + $data = apply_filters( 'dt_setup_wizard_data', $data ); + + return $data; + } +} +DT_Setup_Wizard::instance(); diff --git a/dt-core/admin/menu/tabs/tab-featured-extensions.php b/dt-core/admin/menu/tabs/tab-featured-extensions.php index f9261d3f6..3b5e7c5f3 100644 --- a/dt-core/admin/menu/tabs/tab-featured-extensions.php +++ b/dt-core/admin/menu/tabs/tab-featured-extensions.php @@ -232,7 +232,7 @@ public function box_message( $tab ) { blog_url = 'https://disciple.tools/plugins/' . $plugin->slug; $plugin->folder_name = get_home_path() . 'wp-content/plugins/' . $plugin->slug; $plugin->author_github_username = explode( '/', $plugin->homepage )[3]; - $plugin->description = count_chars( $plugin->description ) > 128 ? trim( substr( $plugin->description, 0, 128 ) ) . '...' : $plugin->description; // Shorten descriptions to 88 chars $plugin->icon = ! isset( $plugin->icon ) ? 'https://s.w.org/plugins/geopattern-icon/' . $plugin->slug . '.svg' : $plugin->icon; $plugin->name = str_replace( 'Disciple Tools - ', '', $plugin->name ); $plugin->name = str_replace( 'Disciple.Tools - ', '', $plugin->name ); @@ -320,13 +319,13 @@ public function get_dt_plugins() { $plugin->active = false; $plugin->activation_path = ''; - if ( $this->partial_array_search( $all_plugins, $plugin->slug ) !== -1 ) { + if ( self::partial_array_search( $all_plugins, $plugin->slug ) !== -1 ) { $plugin->installed = true; } - if ( $this->partial_array_search( $active_plugins, $plugin->slug ) !== -1 ) { + if ( self::partial_array_search( $active_plugins, $plugin->slug ) !== -1 ) { $plugin->active = true; - $plugin->activation_path = $this->partial_array_search( $active_plugins, $plugin->slug ); + $plugin->activation_path = self::partial_array_search( $active_plugins, $plugin->slug ); } } return $plugins; diff --git a/dt-core/configuration/class-migration-engine.php b/dt-core/configuration/class-migration-engine.php index 4d903e2c9..01797ba31 100644 --- a/dt-core/configuration/class-migration-engine.php +++ b/dt-core/configuration/class-migration-engine.php @@ -12,7 +12,7 @@ class Disciple_Tools_Migration_Engine { - public static $migration_number = 59; + public static $migration_number = 60; protected static $migrations = null; diff --git a/dt-core/configuration/config-site-defaults.php b/dt-core/configuration/config-site-defaults.php index feec9d936..8b8a9e656 100644 --- a/dt-core/configuration/config-site-defaults.php +++ b/dt-core/configuration/config-site-defaults.php @@ -135,8 +135,6 @@ function dt_get_option( string $name ) { } return get_option( 'dt_site_options' ); - break; - case 'dt_site_custom_lists': $default_custom_lists = dt_get_site_custom_lists(); @@ -154,7 +152,6 @@ function dt_get_option( string $name ) { } //return apply_filters( "dt_site_custom_lists", get_option( 'dt_site_custom_lists' ) ); return get_option( 'dt_site_custom_lists' ); - break; case 'dt_field_customizations': return get_option( 'dt_field_customizations', [ @@ -192,8 +189,6 @@ function dt_get_option( string $name ) { else { return get_option( 'dt_base_user' ); } - break; - case 'location_levels': $default_levels = dt_get_location_levels(); @@ -216,7 +211,6 @@ function dt_get_option( string $name ) { $levels = get_option( 'dt_location_levels' ); } return $levels['location_levels']; - break; case 'auto_location': $setting = get_option( 'dt_auto_location' ); if ( false === $setting ) { @@ -224,8 +218,6 @@ function dt_get_option( string $name ) { $setting = get_option( 'dt_auto_location' ); } return $setting; - break; - case 'dt_storage_connection_id': return get_option( 'dt_storage_connection_id', '' ); @@ -235,8 +227,6 @@ function dt_get_option( string $name ) { update_option( 'dt_email_base_subject', 'Disciple.Tools' ); } return $subject_base; - break; - case 'dt_email_base_address_reply_to': return get_option( 'dt_email_base_address_reply_to', '' ); case 'dt_email_base_address': @@ -282,7 +272,6 @@ function dt_get_option( string $name ) { default: return false; - break; } } @@ -313,15 +302,11 @@ function dt_update_option( $name, $value, $autoload = false ) { $levels = wp_parse_args( $levels, $default_levels ); return update_option( 'dt_location_levels', $levels, $autoload ); - - break; case 'auto_location': return update_option( 'dt_auto_location', $value, $autoload ); - break; default: return false; - break; } } diff --git a/dt-core/migrations/0060-setup-wizard-only-new.php b/dt-core/migrations/0060-setup-wizard-only-new.php new file mode 100644 index 000000000..ef3b7a721 --- /dev/null +++ b/dt-core/migrations/0060-setup-wizard-only-new.php @@ -0,0 +1,28 @@ += 60 ){ + return; + } + + update_option( 'dt_setup_wizard_completed', true ); + } + + public function down() { + } + + public function test() { + } + + public function get_expected_tables(): array { + return []; + } +} diff --git a/dt-groups/groups.php b/dt-groups/groups.php index 9ba708d5d..ee202502b 100644 --- a/dt-groups/groups.php +++ b/dt-groups/groups.php @@ -7,7 +7,7 @@ 'locked' => false, 'prerequisites' => [ 'contacts_base' ], 'post_type' => 'groups', - 'description' => 'Default group functionality' + 'description' => 'Track church health and generational growth' ]; return $modules; }, 20, 1 ); diff --git a/dt-people-groups/people-groups-base.php b/dt-people-groups/people-groups-base.php index 91a955652..0edb2660a 100644 --- a/dt-people-groups/people-groups-base.php +++ b/dt-people-groups/people-groups-base.php @@ -5,11 +5,11 @@ add_filter( 'dt_post_type_modules', function( $modules ){ $modules['people_groups_module'] = [ - 'name' => 'People Groups Module', + 'name' => 'People Groups', 'enabled' => true, 'prerequisites' => [], 'post_type' => 'peoplegroups', - 'description' => 'Enables people groups' + 'description' => 'Create your own people groups or use Joshua Project data. Link your contacts and groups to their people group.', ]; return $modules; }, 20, 1 ); diff --git a/dt-people-groups/people-groups-endpoints.php b/dt-people-groups/people-groups-endpoints.php index 799060894..aae6bd59b 100644 --- a/dt-people-groups/people-groups-endpoints.php +++ b/dt-people-groups/people-groups-endpoints.php @@ -134,8 +134,12 @@ public function get_people_groups_compact( WP_REST_Request $request ) { public function search_csv( WP_REST_Request $request ) { $params = $request->get_params(); + $as_object = false; + if ( isset( $params['as_object'] ) ) { + $as_object = true; + } if ( isset( $params['s'] ) ) { - $people_groups = Disciple_Tools_People_Groups::search_csv( $params['s'] ); + $people_groups = Disciple_Tools_People_Groups::search_csv( $params['s'], $as_object ); return $people_groups; } else { return new WP_Error( __METHOD__, 'Missing required parameter `s`' ); diff --git a/dt-people-groups/people-groups.php b/dt-people-groups/people-groups.php index 0a116b200..9ba45fe42 100644 --- a/dt-people-groups/people-groups.php +++ b/dt-people-groups/people-groups.php @@ -49,16 +49,21 @@ public static function get_imb_source() { return $imb_csv; } - public static function search_csv( $search ) { // gets a list by country + public static function search_csv( $search, $as_object = false ) { // gets a list by country if ( ! current_user_can( 'manage_dt' ) ) { return new WP_Error( __METHOD__, 'Insufficient permissions', [] ); } $data = self::get_jp_source(); + $columns = [ ...$data[0], 'duplicate' ]; $result = []; foreach ( $data as $row ) { if ( $row[1] === $search ) { $row[] = ( self::duplicate_db_checker_by_rop3( $row[1], $row[3] ) > 0 ); - $result[] = $row; + if ( $as_object === true ) { + $result[] = array_combine( $columns, $row ); + } else { + $result[] = $row; + } } } return $result; diff --git a/functions.php b/functions.php index c6c3a2de3..5ce06506e 100755 --- a/functions.php +++ b/functions.php @@ -87,6 +87,19 @@ function dt_theme_load() { } catch ( Throwable $e ) { new WP_Error( 'migration_error', 'Migration engine failed to migrate.', [ 'message' => $e->getMessage() ] ); } + + $is_rest = dt_is_rest(); + + /** + * Redirect to setup wizard if not seen + */ + $setup_wizard_completed = get_option( 'dt_setup_wizard_completed' ); + $setup_wizard_completed = apply_filters( 'dt_setup_wizard_completed', $setup_wizard_completed ); + $current_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; + $is_administrator = current_user_can( 'manage_options' ); + if ( !$is_rest && !is_network_admin() && !wp_doing_cron() && !$setup_wizard_completed && $is_administrator && $current_page !== 'dt_setup_wizard' ) { + wp_redirect( admin_url( 'admin.php?page=dt_setup_wizard' ) ); + } } ); /** @@ -229,8 +242,10 @@ public function __construct() { require_once( 'dt-core/multisite.php' ); require_once( 'dt-core/global-functions.php' ); require_once( 'dt-core/utilities/loader.php' ); + $is_rest = dt_is_rest(); $url_path = dt_get_url_path(); + require_once( 'dt-core/libraries/posts-to-posts/posts-to-posts.php' ); // P2P library/plugin. Required before DT instance require_once( 'dt-core/libraries/wp-queue/wp-queue.php' ); //w if ( !class_exists( 'Jwt_Auth' ) ) { @@ -496,6 +511,7 @@ public function __construct() { /* Note: The load order matters for the menus and submenus. Submenu must load after menu. */ require_once( 'dt-core/admin/menu/tabs/abstract-tabs-base.php' ); // registers all the menu pages and tabs require_once( 'dt-core/admin/menu/menu-settings.php' ); + require_once( 'dt-core/admin/menu/menu-setup-wizard.php' ); require_once( 'dt-core/admin/menu/menu-extensions.php' ); require_once( 'dt-core/admin/menu/tabs/tab-featured-extensions.php' );