From a6e4e69502d936e0b04ded75e5fbd3fcdc586eab Mon Sep 17 00:00:00 2001
From: kodinkat
Date: Tue, 31 Mar 2026 14:27:52 +0100
Subject: [PATCH 1/5] Update Disciple.Tools Migration plugin to version 1.0.0
- Revamped plugin description to clarify functionality for exporting and importing settings and records via REST API or JSON.
- Updated version number to 1.0.0 and tested compatibility up to WordPress 6.7.
- Removed deprecated spinner.svg and several unused files related to charts and magic links to streamline the plugin.
- Enhanced user migration functionality, allowing for better handling of administrator roles during import.
- Improved export tab messaging and added conditional filters for record exports.
- Refactored code for better readability and maintainability, including updates to the admin interface and REST API integration.
---
.github/workflows/release.yml | 2 +-
admin/class-dt-migration-tab-export.php | 8 +-
admin/class-dt-migration-tab-settings.php | 2 +-
admin/config-required-plugins.php | 77 ---
charts/charts-base.js | 9 -
charts/charts-loader.php | 24 -
charts/one-page-chart-template.js | 105 ----
charts/one-page-chart-template.php | 97 ---
disciple-tools-migration.php | 342 ++++------
includes/class-dt-migration-system-users.php | 126 ++--
magic-link/magic-link-home.php | 95 ---
magic-link/magic-link-login-user-app.php | 383 ------------
magic-link/magic-link-map.php | 315 ----------
magic-link/magic-link-non-object.php | 192 ------
magic-link/magic-link-user-app.php | 362 -----------
.../magic-link-post-type.php | 277 ---------
.../post-type-magic-link/magic-link.css | 6 -
magic-link/post-type-magic-link/magic-link.js | 61 --
magic-link/templates/starter-template.php | 443 -------------
post-type/loader.php | 38 --
post-type/module-base.php | 583 ------------------
site-link/custom-site-to-site-links.php | 40 --
spinner.svg | 21 -
tile/custom-tile.php | 300 ---------
tile/profile-settings-tile.php | 84 ---
version-control.json | 14 +-
workflows/workflows.php | 112 ----
27 files changed, 208 insertions(+), 3910 deletions(-)
delete mode 100644 admin/config-required-plugins.php
delete mode 100644 charts/charts-base.js
delete mode 100644 charts/charts-loader.php
delete mode 100644 charts/one-page-chart-template.js
delete mode 100644 charts/one-page-chart-template.php
delete mode 100644 magic-link/magic-link-home.php
delete mode 100644 magic-link/magic-link-login-user-app.php
delete mode 100644 magic-link/magic-link-map.php
delete mode 100644 magic-link/magic-link-non-object.php
delete mode 100644 magic-link/magic-link-user-app.php
delete mode 100644 magic-link/post-type-magic-link/magic-link-post-type.php
delete mode 100644 magic-link/post-type-magic-link/magic-link.css
delete mode 100644 magic-link/post-type-magic-link/magic-link.js
delete mode 100644 magic-link/templates/starter-template.php
delete mode 100644 post-type/loader.php
delete mode 100644 post-type/module-base.php
delete mode 100644 site-link/custom-site-to-site-links.php
delete mode 100644 spinner.svg
delete mode 100644 tile/custom-tile.php
delete mode 100644 tile/profile-settings-tile.php
delete mode 100644 workflows/workflows.php
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 897e6aa..2135a5b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -23,7 +23,7 @@ jobs:
- name: Build project
run: |
mkdir disciple-tools-migration
- cp -r disciple-tools-migration.php admin charts languages magic-link post-type rest-api site-link tile workflows spinner.svg version-control.json LICENSE SECURITY.md disciple-tools-migration/
+ cp -r disciple-tools-migration.php admin includes languages rest-api version-control.json LICENSE SECURITY.md disciple-tools-migration/
zip -r disciple-tools-migration.zip disciple-tools-migration
- name: Create Release
id: create_release
diff --git a/admin/class-dt-migration-tab-export.php b/admin/class-dt-migration-tab-export.php
index 6ca3802..b21a7d3 100644
--- a/admin/class-dt-migration-tab-export.php
+++ b/admin/class-dt-migration-tab-export.php
@@ -143,15 +143,18 @@ public function main_column( array $settings ) {
-
+
+
+
diff --git a/admin/class-dt-migration-tab-settings.php b/admin/class-dt-migration-tab-settings.php
index 52ca70e..0e40fde 100644
--- a/admin/class-dt-migration-tab-settings.php
+++ b/admin/class-dt-migration-tab-settings.php
@@ -101,7 +101,7 @@ public function main_column( array $settings ) {
-
+
diff --git a/admin/config-required-plugins.php b/admin/config-required-plugins.php
deleted file mode 100644
index 5d6e36a..0000000
--- a/admin/config-required-plugins.php
+++ /dev/null
@@ -1,77 +0,0 @@
- 'REST API Console', // The plugin name.
-// 'slug' => 'rest-api-console', // The plugin slug (typically the folder name).
-// 'source' => dirname( __FILE__ ) . '/lib/plugins/rest-api-console.zip', // The plugin source.
-// 'required' => true, // If false, the plugin is only 'recommended' instead of required.
-// 'version' => '', // E.g. 1.0.0. If set, the active plugin must be this version or higher. If the plugin version is higher than the plugin version installed, the user will be notified to update the plugin.
-// 'force_activation' => false, // If true, plugin is activated upon theme activation and cannot be deactivated until theme switch.
-// 'force_deactivation' => false, // If true, plugin is deactivated upon theme switch, useful for theme-specific plugins.
-// 'external_url' => '', // If set, overrides default API URL and points to an external URL.
-// 'is_callable' => '', // If set, this callable will be be checked for availability to determine if a plugin is active.
-// ),
-//
- */
-
-add_action( 'tgmpa_register', function() {
- /*
- * Array of plugin arrays. Required keys are name and slug.
- * If the source is NOT from the .org repo, then source is also required.
- */
- $plugins = [];
- // a wordpress plugin:
- // $plugins[] = [
- // 'name' => 'iThemes Security',
- // 'slug' => 'better-wp-security',
- // 'required' => false,
- // 'version' => '7.2.0',
- // ];
-
- // a D.T plugin
- // $plugins[] = [
- // 'name' => 'Disciple.Tools Dashboard',
- // 'slug' => 'disciple-tools-dashboard',
- // 'source' => 'https://github.com/DiscipleTools/disciple-tools-dashboard/releases/latest/download/disciple-tools-dashboard.zip',
- // 'required' => false
- // ];
-
- /*
- * Array of configuration settings. Amend each line as needed.
- *
- * Only uncomment the strings in the config array if you want to customize the strings.
- */
- $config = array(
- 'id' => 'disciple_tools', // Unique ID for hashing notices for multiple instances of TGMPA.
- 'default_path' => '/includes/plugins/', // Default absolute path to bundled plugins.
- 'menu' => 'tgmpa-install-plugins', // Menu slug.
- 'parent_slug' => 'plugins.php', // Parent menu slug.
- 'capability' => 'manage_options', // Capability needed to view plugin install page, should be a capability associated with the parent menu used.
- 'has_notices' => true, // Show admin notices or not.
- 'dismissable' => true, // If false, a user cannot dismiss the nag message.
- 'dismiss_msg' => 'These are recommended plugins to complement your Disciple.Tools system.', // If 'dismissable' is false, this message will be output at top of nag.
- 'is_automatic' => true, // Automatically activate plugins after installation or not.
- 'message' => '', // Message to output right before the plugins table.
- );
-
- tgmpa( $plugins, $config );
-} );
diff --git a/charts/charts-base.js b/charts/charts-base.js
deleted file mode 100644
index 9ebd375..0000000
--- a/charts/charts-base.js
+++ /dev/null
@@ -1,9 +0,0 @@
-(function() {
- "use strict";
-
- jQuery(document).ready(function() {
- jQuery('#metrics-sidemenu').foundation('down', jQuery(`#${window.wpApiBase.slug}`));
-
- })
-})();
-
diff --git a/charts/charts-loader.php b/charts/charts-loader.php
deleted file mode 100644
index f0f9962..0000000
--- a/charts/charts-loader.php
+++ /dev/null
@@ -1,24 +0,0 @@
-${localizedObject.translations.title}
-
-
-
-
-
-
-
- ${translations["Sample API Call"]}
-
- `)
-
- // Create chart instance
- var chart = am4core.create("chartdiv", am4charts.PieChart);
-
- // Add data
- chart.data = [{
- "country": "Lithuania",
- "litres": 501.9
- }, {
- "country": "Czech Republic",
- "litres": 301.9
- }, {
- "country": "Ireland",
- "litres": 201.1
- }, {
- "country": "Germany",
- "litres": 165.8
- }, {
- "country": "Australia",
- "litres": 139.9
- }, {
- "country": "Austria",
- "litres": 128.3
- }, {
- "country": "UK",
- "litres": 99
- }, {
- "country": "Belgium",
- "litres": 60
- }, {
- "country": "The Netherlands",
- "litres": 50
- }];
-
- // Add and configure Series
- var pieSeries = chart.series.push(new am4charts.PieSeries());
- pieSeries.dataFields.value = "litres";
- pieSeries.dataFields.category = "country";
- }
-
- window.sample_api_call = function sample_api_call( button_data ) {
-
-
- let localizedObject = window.wp_js_object // change this object to the one named in ui-menu-and-enqueue.php
-
- let button = jQuery('#sample_button')
-
- $('#sample_spinner').addClass("active")
-
- let data = { "button_data": button_data };
- return jQuery.ajax({
- type: "POST",
- data: JSON.stringify(data),
- contentType: "application/json; charset=utf-8",
- dataType: "json",
- url: `${localizedObject.rest_endpoints_base}/sample`,
- beforeSend: function(xhr) {
- xhr.setRequestHeader('X-WP-Nonce', localizedObject.nonce);
- },
- })
- .done(function (data) {
- $('#sample_spinner').removeClass("active")
- button.empty().append(data)
- console.log( 'success' )
- console.log( data )
- })
- .fail(function (err) {
- $('#sample_spinner').removeClass("active")
- button.empty().append("error. Something went wrong")
- console.log("error");
- console.log(err);
- })
- }
-})();
diff --git a/charts/one-page-chart-template.php b/charts/one-page-chart-template.php
deleted file mode 100644
index 0f181ed..0000000
--- a/charts/one-page-chart-template.php
+++ /dev/null
@@ -1,97 +0,0 @@
-has_permission() ){
- return;
- }
- $url_path = dt_get_url_path();
-
- // only load scripts if exact url
- if ( "metrics/$this->base_slug/$this->slug" === $url_path ) {
-
- add_action( 'wp_enqueue_scripts', [ $this, 'scripts' ], 99 );
- }
- }
-
-
- /**
- * Load scripts for the plugin
- */
- public function scripts() {
-
- wp_register_script( 'amcharts-core', 'https://www.amcharts.com/lib/4/core.js', false, '4' );
- wp_register_script( 'amcharts-charts', 'https://www.amcharts.com/lib/4/charts.js', false, '4' );
-
- wp_enqueue_script( 'dt_'.$this->slug.'_script', trailingslashit( plugin_dir_url( __FILE__ ) ) . $this->js_file_name, [
- 'jquery',
- 'amcharts-core',
- 'amcharts-charts'
- ], filemtime( plugin_dir_path( __FILE__ ) .$this->js_file_name ), true );
-
- // Localize script with array data
- wp_localize_script(
- 'dt_'.$this->slug.'_script', $this->js_object_name, [
- 'rest_endpoints_base' => esc_url_raw( rest_url() ) . "$this->base_slug/$this->slug",
- 'base_slug' => $this->base_slug,
- 'slug' => $this->slug,
- 'root' => esc_url_raw( rest_url() ),
- 'plugin_uri' => plugin_dir_url( __DIR__ ),
- 'nonce' => wp_create_nonce( 'wp_rest' ),
- 'current_user_login' => wp_get_current_user()->user_login,
- 'current_user_id' => get_current_user_id(),
- 'stats' => [
- // add preload stats data into arrays here
- ],
- 'translations' => [
- 'title' => $this->title,
- 'Sample API Call' => __( 'Sample API Call', 'disciple-tools-migration' )
- ]
- ]
- );
- }
-
- public function add_api_routes() {
- $namespace = "$this->base_slug/$this->slug";
- register_rest_route(
- $namespace, '/sample', [
- 'methods' => 'POST',
- 'callback' => [ $this, 'sample' ],
- 'permission_callback' => function( WP_REST_Request $request ) {
- return $this->has_permission();
- },
- ]
- );
- }
-
- public function sample( WP_REST_Request $request ) {
- $params = $request->get_params();
- if ( isset( $params['button_data'] ) ) {
- // Do something
- $results = $params['button_data'];
- return $results;
- } else {
- return new WP_Error( __METHOD__, 'Missing parameters.' );
- }
- }
-}
diff --git a/disciple-tools-migration.php b/disciple-tools-migration.php
index cc7bbde..ef8965a 100755
--- a/disciple-tools-migration.php
+++ b/disciple-tools-migration.php
@@ -2,15 +2,14 @@
/**
* Plugin Name: Disciple.Tools - Migrations
* Plugin URI: https://github.com/DiscipleTools/disciple-tools-migration
- * Description: Disciple.Tools - Migrations is intended to help developers and integrator jumpstart their extension of the Disciple.Tools system.
+ * Description: Export and import Disciple.Tools settings and records via REST API or downloadable JSON.
* Text Domain: disciple-tools-migration
* Domain Path: /languages
- * Version: 0.1
+ * Version: 1.0.0
* Author URI: https://github.com/DiscipleTools
* GitHub Plugin URI: https://github.com/DiscipleTools/disciple-tools-migration
* Requires at least: 4.7.0
- * (Requires 4.7+ because of the integration of the REST API at 4.7 and the security requirements of this milestone version.)
- * Tested up to: 5.6
+ * Tested up to: 6.7
*
* @package Disciple_Tools
* @link https://github.com/DiscipleTools
@@ -18,46 +17,34 @@
* https://www.gnu.org/licenses/gpl-2.0.html
*/
-/**
- * Refactoring (renaming) this plugin as your own:
- * 1. @todo Rename the `disciple-tools-migration.php file.
- * 2. @todo Refactor all occurrences of the name Disciple_Tools_Migration, disciple_tools_migration, disciple-tools-migration, migrations, and "Migrations"
- * 3. @todo Update the README.md and LICENSE
- * 4. @todo Update the default.pot file if you intend to make your plugin multilingual. Use a tool like POEdit
- */
-
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
- * Gets the instance of the `Disciple_Tools_Migration` class.
+ * Gets the instance of the Disciple_Tools_Migration_Plugin class.
*
- * @since 0.1
- * @access public
+ * @since 1.0.0
* @return object|bool
*/
function disciple_tools_migration() {
- $disciple_tools_migration_required_dt_theme_version = '1.19';
+ global $disciple_tools_migration_required_dt_theme_version;
+
+ $disciple_tools_migration_required_dt_theme_version = '1.20';
$wp_theme = wp_get_theme();
- $version = $wp_theme->version;
+ $version = $wp_theme->version;
- /*
- * Check if the Disciple.Tools theme is loaded and is the latest required version
- */
$is_theme_dt = class_exists( 'Disciple_Tools' );
if ( $is_theme_dt && version_compare( $version, $disciple_tools_migration_required_dt_theme_version, '<' ) ) {
add_action( 'admin_notices', 'disciple_tools_migration_hook_admin_notice' );
add_action( 'wp_ajax_dismissed_notice_handler', 'dt_hook_ajax_notice_handler' );
return false;
}
- if ( !$is_theme_dt ){
+ if ( ! $is_theme_dt ) {
return false;
}
- /**
- * Load useful function from the theme
- */
- if ( !defined( 'DT_FUNCTIONS_READY' ) ){
+
+ if ( ! defined( 'DT_FUNCTIONS_READY' ) ) {
require_once get_template_directory() . '/dt-core/global-functions.php';
}
@@ -65,26 +52,36 @@ function disciple_tools_migration() {
}
add_action( 'after_setup_theme', 'disciple_tools_migration', 20 );
-//register the D.T Plugin
-add_filter( 'dt_plugins', function ( $plugins ){
- $plugin_data = get_file_data( __FILE__, [ 'Version' => 'Version', 'Plugin Name' => 'Plugin Name' ], false );
- $plugins['disciple-tools-migration'] = [
- 'plugin_url' => trailingslashit( plugin_dir_url( __FILE__ ) ),
- 'version' => $plugin_data['Version'] ?? null,
- 'name' => $plugin_data['Plugin Name'] ?? null,
- ];
- return $plugins;
-});
+add_filter(
+ 'dt_plugins',
+ function ( $plugins ) {
+ $plugin_data = get_file_data( __FILE__, [ 'Version' => 'Version', 'Plugin Name' => 'Plugin Name' ], false );
+ $plugins['disciple-tools-migration'] = [
+ 'plugin_url' => trailingslashit( plugin_dir_url( __FILE__ ) ),
+ 'version' => $plugin_data['Version'] ?? null,
+ 'name' => $plugin_data['Plugin Name'] ?? null,
+ ];
+ return $plugins;
+ }
+);
/**
* Singleton class for setting up the plugin.
*
- * @since 0.1
- * @access public
+ * @since 1.0.0
*/
class Disciple_Tools_Migration_Plugin {
+ /**
+ * Instance.
+ *
+ * @var Disciple_Tools_Migration_Plugin|null
+ */
private static $_instance = null;
+
+ /**
+ * @return Disciple_Tools_Migration_Plugin
+ */
public static function instance() {
if ( is_null( self::$_instance ) ) {
self::$_instance = new self();
@@ -94,235 +91,127 @@ public static function instance() {
private function __construct() {
$is_rest = dt_is_rest();
- /**
- * Load the migration REST API endpoints.
- *
- * Originally this was only loaded when the URL path contained "disciple-tools-migration".
- * For the dedicated dt-migration REST namespace, we also want to load it for
- * /wp-json/dt-migration/... requests. The overhead is small, so we simply load it
- * for all REST requests handled by this plugin.
- */
- if ( $is_rest ) {
- require_once( 'rest-api/rest-api.php' );
- }
- /**
- * @todo Decide if you want to create a new post type
- * To remove: delete the line below and remove the folder named /post-type
- */
- require_once( 'post-type/loader.php' ); // add starter post type extension to Disciple.Tools system
-
- /**
- * @todo Decide if you want to create a custom site-to-site link
- * To remove: delete the line below and remove the folder named /site-link
- */
- require_once( 'site-link/custom-site-to-site-links.php' ); // add site to site link class and capabilities
-
- /**
- * @todo Decide if you want to add new charts to the metrics section
- * To remove: delete the line below and remove the folder named /charts
- */
- if ( strpos( dt_get_url_path(), 'metrics' ) !== false || ( $is_rest && strpos( dt_get_url_path(), 'disciple-tools-migration-metrics' ) !== false ) ){
- require_once( 'charts/charts-loader.php' ); // add custom charts to the metrics area
- }
-
- /**
- * @todo Decide if you want to add a custom tile or settings page tile
- * To remove: delete the lines below and remove the folder named /tile
- */
- require_once( 'tile/custom-tile.php' ); // add custom tile
- if ( 'settings' === dt_get_url_path() && ! $is_rest ) {
- require_once( 'tile/profile-settings-tile.php' ); // add custom settings page tile
+ if ( $is_rest ) {
+ require_once 'rest-api/rest-api.php';
}
- /**
- * @todo Decide if you want to create a magic link
- * To remove: delete the line below and remove the folder named /magic-link
- */
- require_once( 'magic-link/post-type-magic-link/magic-link-post-type.php' );
- require_once( 'magic-link/magic-link-user-app.php' );
- require_once( 'magic-link/magic-link-login-user-app.php' );
- require_once( 'magic-link/magic-link-non-object.php' );
- require_once( 'magic-link/magic-link-map.php' );
- require_once( 'magic-link/templates/starter-template.php' );
-// require_once( 'magic-link/magic-link-home.php' );
-
- /**
- * Load the admin menu and shared settings helpers.
- *
- * The admin menu UI is only relevant in wp-admin, but the settings helper
- * methods (e.g. Disciple_Tools_Migration_Menu::get_settings()) are also
- * required by the REST API endpoints. We therefore load this file for both
- * admin and REST requests.
- */
if ( is_admin() || $is_rest ) {
- require_once( 'admin/admin-menu-and-tabs.php' ); // adds admin page and exposes settings helpers
+ require_once 'admin/admin-menu-and-tabs.php';
}
- /**
- * Load the import engine (shared logic for settings + records).
- */
if ( is_admin() || $is_rest ) {
- require_once( 'includes/class-dt-migration-system-users.php' );
- require_once( 'includes/class-dt-migration-import-engine.php' );
+ require_once 'includes/class-dt-migration-system-users.php';
+ require_once 'includes/class-dt-migration-import-engine.php';
}
- /**
- * Load the export file generator and download handler (Downloadable File mode).
- */
if ( is_admin() ) {
- require_once( 'includes/class-dt-migration-export-file.php' );
- require_once( 'admin/class-dt-migration-export-download.php' );
+ require_once 'includes/class-dt-migration-export-file.php';
+ require_once 'admin/class-dt-migration-export-download.php';
new Disciple_Tools_Migration_Export_Download();
}
- /**
- * Load the import AJAX handlers (admin only).
- */
if ( is_admin() ) {
- require_once( 'admin/class-dt-migration-import-ajax.php' );
+ require_once 'admin/class-dt-migration-import-ajax.php';
new Disciple_Tools_Migration_Import_Ajax();
}
- /**
- * @todo Decide if you want to support localization of your plugin
- * To remove: delete the line below and remove the folder named /languages
- */
$this->i18n();
- /**
- * @todo Decide if you want to customize links for your plugin in the plugin admin area
- * To remove: delete the lines below and remove the function named "plugin_description_links"
- */
- if ( is_admin() ) { // adds links to the plugin description area in the plugin admin list.
+ if ( is_admin() ) {
add_filter( 'plugin_row_meta', [ $this, 'plugin_description_links' ], 10, 4 );
}
-
- /**
- * @todo Decide if you want to create default workflows
- * To remove: delete the line below and remove the folder named /workflows
- */
- require_once( 'workflows/workflows.php' );
}
/**
- * Filters the array of row meta for each/specific plugin in the Plugins list table.
- * Appends additional links below each/specific plugin on the plugins page.
+ * Appends links below the plugin on the Plugins list table.
+ *
+ * @param string[] $links_array Row meta.
+ * @param string $plugin_file_name Plugin file.
+ * @param array $plugin_data Plugin data.
+ * @param string $status Status.
+ * @return string[]
*/
public function plugin_description_links( $links_array, $plugin_file_name, $plugin_data, $status ) {
- if ( strpos( $plugin_file_name, basename( __FILE__ ) ) ) {
- // You can still use `array_unshift()` to add links at the beginning.
-
- $links_array[] = 'Disciple.Tools Community '; // @todo replace with your links.
- // @todo add other links here
+ if ( strpos( $plugin_file_name, basename( __FILE__ ) ) !== false ) {
+ $links_array[] = '' . esc_html__( 'Disciple.Tools Community', 'disciple-tools-migration' ) . ' ';
}
-
return $links_array;
}
/**
- * Method that runs only when the plugin is activated.
- *
- * @since 0.1
- * @access public
- * @return void
+ * Runs when the plugin is activated.
*/
public static function activation() {
- // add elements here that need to fire on activation
}
/**
- * Method that runs only when the plugin is deactivated.
- *
- * @since 0.1
- * @access public
- * @return void
+ * Runs when the plugin is deactivated.
*/
public static function deactivation() {
- // add functions here that need to happen on deactivation
delete_option( 'dismissed-disciple-tools-migration' );
}
/**
- * Loads the translation files.
- *
- * @since 0.1
- * @access public
- * @return void
+ * Loads translation files.
*/
public function i18n() {
- $domain = 'disciple-tools-migration';
- load_plugin_textdomain( $domain, false, trailingslashit( dirname( plugin_basename( __FILE__ ) ) ). 'languages' );
+ load_plugin_textdomain( 'disciple-tools-migration', false, trailingslashit( dirname( plugin_basename( __FILE__ ) ) ) . 'languages' );
}
/**
- * Magic method to output a string if trying to use the object as a string.
- *
- * @since 0.1
- * @access public
* @return string
*/
public function __toString() {
return 'disciple-tools-migration';
}
- /**
- * Magic method to keep the object from being cloned.
- *
- * @since 0.1
- * @access public
- * @return void
- */
public function __clone() {
- _doing_it_wrong( __FUNCTION__, 'Whoah, partner!', '0.1' );
+ _doing_it_wrong( __FUNCTION__, 'Cloning is forbidden.', '1.0.0' );
}
- /**
- * Magic method to keep the object from being unserialized.
- *
- * @since 0.1
- * @access public
- * @return void
- */
public function __wakeup() {
- _doing_it_wrong( __FUNCTION__, 'Whoah, partner!', '0.1' );
+ _doing_it_wrong( __FUNCTION__, 'Unserializing instances is forbidden.', '1.0.0' );
}
/**
- * Magic method to prevent a fatal error when calling a method that doesn't exist.
- *
- * @param string $method
- * @param array $args
+ * @param string $method Method name.
+ * @param array $args Arguments.
* @return null
- * @since 0.1
- * @access public
*/
public function __call( $method = '', $args = array() ) {
- _doing_it_wrong( 'disciple_tools_migration::' . esc_html( $method ), 'Method does not exist.', '0.1' );
+ _doing_it_wrong( 'disciple_tools_migration::' . esc_html( $method ), 'Method does not exist.', '1.0.0' );
unset( $method, $args );
return null;
}
}
-
-// Register activation hook.
register_activation_hook( __FILE__, [ 'Disciple_Tools_Migration_Plugin', 'activation' ] );
register_deactivation_hook( __FILE__, [ 'Disciple_Tools_Migration_Plugin', 'deactivation' ] );
if ( ! function_exists( 'disciple_tools_migration_hook_admin_notice' ) ) {
+ /**
+ * Admin notice when theme is missing or outdated.
+ */
function disciple_tools_migration_hook_admin_notice() {
global $disciple_tools_migration_required_dt_theme_version;
- $wp_theme = wp_get_theme();
+
+ $wp_theme = wp_get_theme();
$current_version = $wp_theme->version;
- $message = "'Disciple.Tools - Migrations' plugin requires 'Disciple.Tools' theme to work. Please activate 'Disciple.Tools' theme or make sure it is latest version.";
- if ( $wp_theme->get_template() === 'disciple-tools-theme' ){
- $message .= ' ' . sprintf( esc_html( 'Current Disciple.Tools version: %1$s, required version: %2$s' ), esc_html( $current_version ), esc_html( $disciple_tools_migration_required_dt_theme_version ) );
+ $message = __( '\'Disciple.Tools - Migrations\' requires the Disciple.Tools theme. Please activate Disciple.Tools or update it to a supported version.', 'disciple-tools-migration' );
+ if ( $wp_theme->get_template() === 'disciple-tools-theme' ) {
+ $message .= ' ' . sprintf(
+ /* translators: 1: current theme version, 2: required version */
+ __( 'Current Disciple.Tools version: %1$s, required: %2$s', 'disciple-tools-migration' ),
+ esc_html( $current_version ),
+ esc_html( $disciple_tools_migration_required_dt_theme_version )
+ );
}
- // Check if it's been dismissed...
- if ( ! get_option( 'dismissed-disciple-tools-migration', false ) ) { ?>
+ if ( ! get_option( 'dismissed-disciple-tools-migration', false ) ) {
+ ?>
- new_id map.
*
* @param array $system_users Block from export.system_users.
- * @return array{ error?: string, map: array, created: int, mapped_existing: int, admin_mapped: int }
+ * @return array{ error?: string, map: array, created: int, mapped_existing: int, admin_mapped: int, admin_created: int }
*/
public static function apply_import( array $system_users ) : array {
$out = [
@@ -121,6 +122,7 @@ public static function apply_import( array $system_users ) : array {
'created' => 0,
'mapped_existing' => 0,
'admin_mapped' => 0,
+ 'admin_created' => 0,
];
$rows = $system_users['users'] ?? [];
@@ -151,37 +153,21 @@ static function ( $a, $b ) {
$email = isset( $row['user_email'] ) ? sanitize_email( (string) $row['user_email'] ) : '';
$login = isset( $row['user_login'] ) ? sanitize_user( (string) $row['user_login'], true ) : '';
-
- if ( self::is_migration_admin_user( $row ) ) {
- $existing = self::find_existing_user( $email, $login );
- if ( ! $existing instanceof WP_User ) {
- return array_merge( $out, [
- 'error' => sprintf(
- /* translators: 1: old user ID, 2: email or login */
- __( 'Administrator user from source (old ID %1$d, %2$s) must already exist on this site. Create the account first or fix email/login match.', 'disciple-tools-migration' ),
- $old_id,
- $email !== '' ? $email : $login
- ),
- ] );
- }
- if ( ! in_array( 'administrator', (array) $existing->roles, true ) ) {
- return array_merge( $out, [
- 'error' => sprintf(
- /* translators: 1: existing user ID */
- __( 'Matched user ID %1$d for a source administrator must be an administrator on this site.', 'disciple-tools-migration' ),
- (int) $existing->ID
- ),
- ] );
- }
- $out['map'][ (string) $old_id ] = (int) $existing->ID;
- ++$out['admin_mapped'];
- continue;
- }
+ $is_src_admin = self::is_migration_admin_user( $row );
$existing = self::find_existing_user( $email, $login );
if ( $existing instanceof WP_User ) {
$out['map'][ (string) $old_id ] = (int) $existing->ID;
++$out['mapped_existing'];
+
+ if ( $is_src_admin ) {
+ $roles_err = self::assign_roles_to_wp_user( $existing->ID, self::normalized_roles_from_row( $row ) );
+ if ( $roles_err !== null ) {
+ return array_merge( $out, [ 'error' => $roles_err ] );
+ }
+ ++$out['admin_mapped'];
+ }
+
self::maybe_update_user_profile( $existing->ID, $row );
continue;
}
@@ -190,7 +176,18 @@ static function ( $a, $b ) {
return array_merge( $out, [
'error' => sprintf(
/* translators: 1: old user ID */
- __( 'Cannot create missing non-admin user (source ID %1$d): create_users capability required.', 'disciple-tools-migration' ),
+ __( 'Cannot create missing user (source ID %1$d): create_users capability required.', 'disciple-tools-migration' ),
+ $old_id
+ ),
+ ] );
+ }
+
+ $norm_roles = self::normalized_roles_from_row( $row );
+ if ( in_array( 'administrator', $norm_roles, true ) && ! current_user_can( 'promote_users' ) ) {
+ return array_merge( $out, [
+ 'error' => sprintf(
+ /* translators: 1: old user ID */
+ __( 'Cannot create administrator account for source ID %1$d: promote_users capability required.', 'disciple-tools-migration' ),
$old_id
),
] );
@@ -209,6 +206,9 @@ static function ( $a, $b ) {
}
$out['map'][ (string) $old_id ] = (int) $new_id;
++$out['created'];
+ if ( $is_src_admin ) {
+ ++$out['admin_created'];
+ }
}
$option_value = [
@@ -290,6 +290,49 @@ private static function maybe_update_user_profile( int $user_id, array $row ) :
}
}
+ /**
+ * Role slugs from the export row, with administrator ensured when the row is a source admin.
+ *
+ * @param array $row Export user row.
+ * @return string[]
+ */
+ private static function normalized_roles_from_row( array $row ) : array {
+ $roles = isset( $row['roles'] ) && is_array( $row['roles'] ) ? $row['roles'] : [];
+ $roles = array_values( array_filter( array_map( 'sanitize_key', $roles ) ) );
+ if ( self::is_migration_admin_user( $row ) && ! in_array( 'administrator', $roles, true ) ) {
+ $roles[] = 'administrator';
+ }
+ return $roles;
+ }
+
+ /**
+ * Replaces the user's roles with the given list (same rules as new-user creation).
+ *
+ * @param int $user_id WordPress user ID.
+ * @param array $roles Sanitized role slugs (may be empty).
+ * @return string|null Error message, or null on success.
+ */
+ private static function assign_roles_to_wp_user( int $user_id, array $roles ) : ?string {
+ $roles = array_values( array_filter( array_map( 'sanitize_key', $roles ) ) );
+ if ( in_array( 'administrator', $roles, true ) && ! current_user_can( 'promote_users' ) ) {
+ return __( 'You do not have permission to assign the administrator role.', 'disciple-tools-migration' );
+ }
+
+ $user = new WP_User( $user_id );
+ $user->set_role( '' );
+ if ( ! empty( $roles ) ) {
+ $primary = array_shift( $roles );
+ $user->set_role( $primary );
+ foreach ( $roles as $extra_role ) {
+ $user->add_role( $extra_role );
+ }
+ } else {
+ $user->set_role( get_option( 'default_role', 'subscriber' ) );
+ }
+
+ return null;
+ }
+
/**
* @param array $row
* @return int|WP_Error
@@ -306,6 +349,14 @@ private static function create_user_from_row( array $row ) {
return new WP_Error( 'exists', __( 'User login or email already exists.', 'disciple-tools-migration' ) );
}
+ $norm_roles = self::normalized_roles_from_row( $row );
+ if ( in_array( 'administrator', $norm_roles, true ) && ! current_user_can( 'promote_users' ) ) {
+ return new WP_Error(
+ 'no_promote',
+ __( 'You do not have permission to assign the administrator role when creating users.', 'disciple-tools-migration' )
+ );
+ }
+
$password = wp_generate_password( 24, true, true );
$user_id = wp_insert_user(
[
@@ -322,18 +373,9 @@ private static function create_user_from_row( array $row ) {
return $user_id;
}
- $roles = isset( $row['roles'] ) && is_array( $row['roles'] ) ? $row['roles'] : [];
- $roles = array_values( array_filter( array_map( 'sanitize_key', $roles ) ) );
- $user = new WP_User( $user_id );
- $user->set_role( '' );
- if ( ! empty( $roles ) ) {
- $primary = array_shift( $roles );
- $user->set_role( $primary );
- foreach ( $roles as $extra_role ) {
- $user->add_role( $extra_role );
- }
- } else {
- $user->set_role( get_option( 'default_role', 'subscriber' ) );
+ $role_err = self::assign_roles_to_wp_user( (int) $user_id, $norm_roles );
+ if ( $role_err !== null ) {
+ return new WP_Error( 'role_assign_failed', $role_err );
}
if ( ! empty( $row['meta'] ) && is_array( $row['meta'] ) ) {
diff --git a/magic-link/magic-link-home.php b/magic-link/magic-link-home.php
deleted file mode 100644
index 4b54d21..0000000
--- a/magic-link/magic-link-home.php
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
- Insert Your Home Page Here
- meta = [
- 'app_type' => 'magic_link',
- 'post_type' => $this->post_type,
- 'contacts_only' => false,
- 'fields' => [
- [
- 'id' => 'name',
- 'label' => 'Name'
- ]
- ],
- 'icon' => 'mdi mdi-cog-outline',
- 'show_in_home_apps' => false
- ];
-
- $this->meta_key = $this->root . '_' . $this->type . '_magic_key';
- parent::__construct();
-
- /**
- * user_app and module section
- */
- add_filter( 'dt_settings_apps_list', [ $this, 'dt_settings_apps_list' ], 10, 1 );
- add_action( 'rest_api_init', [ $this, 'add_endpoints' ] );
-
- /**
- * tests if other URL
- */
- $url = dt_get_url_path();
- if ( strpos( $url, $this->root . '/' . $this->type ) === false ) {
- return;
- }
- /**
- * tests magic link parts are registered and have valid elements
- */
- if ( !$this->check_parts_match() ){
- return;
- }
-
- if ( !is_user_logged_in() ) {
- /* redirect user to login page with a redirect_to back to here */
- wp_redirect( dt_login_url( 'login', '?redirect_to=' . rawurlencode( site_url( dt_get_url_path() ) ) . '&hide-nav' ) );
- exit;
- }
-
- // load if valid url
- add_action( 'dt_blank_body', [ $this, 'body' ] );
- add_filter( 'dt_magic_url_base_allowed_css', [ $this, 'dt_magic_url_base_allowed_css' ], 10, 1 );
- add_filter( 'dt_magic_url_base_allowed_js', [ $this, 'dt_magic_url_base_allowed_js' ], 10, 1 );
- }
-
- public function dt_magic_url_base_allowed_js( $allowed_js ) {
- // @todo add or remove js files with this filter
- return $allowed_js;
- }
-
- public function dt_magic_url_base_allowed_css( $allowed_css ) {
- // @todo add or remove js files with this filter
- return $allowed_css;
- }
-
- /**
- * Builds magic link type settings payload:
- * - key: Unique magic link type key; which is usually composed of root, type and _magic_key suffix.
- * - url_base: URL path information to map with parent magic link type.
- * - label: Magic link type name.
- * - description: Magic link type description.
- * - settings_display: Boolean flag which determines if magic link type is to be listed within frontend user profile settings.
- *
- * @param $apps_list
- *
- * @return mixed
- */
- public function dt_settings_apps_list( $apps_list ) {
- $apps_list[ $this->meta_key ] = [
- 'key' => $this->meta_key,
- 'url_base' => $this->root . '/' . $this->type,
- 'label' => $this->page_title,
- 'description' => $this->page_description,
- 'settings_display' => true
- ];
-
- return $apps_list;
- }
-
- /**
- * Writes custom styles to header
- *
- * @see DT_Magic_Url_Base()->header_style() for default state
- * @todo remove if not needed
- */
- public function header_style(){
- ?>
-
- header_javascript() for default state
- * @todo remove if not needed
- */
- public function header_javascript(){
- ?>
-
- footer_javascript() for default state
- * @todo remove if not needed
- */
- public function footer_javascript(){
- ?>
-
- parts['post_id'];
- $app_owner = get_user_by( 'ID', $app_owner_id );
- $app_owner_display_name = dt_get_user_display_name( $app_owner_id );
-
- // @todo Create an app here that interacts with both the logged in user and the user who owns the app
-
- ?>
-
-
-
-
-
-
This app belongs to
-
-
-
-
-
-
-
-
-
Form
-
-
-
-
-
-
- root . '/v1';
- register_rest_route(
- $namespace, '/'.$this->type, [
- [
- 'methods' => 'GET',
- 'callback' => [ $this, 'endpoint_get' ],
- 'permission_callback' => function( WP_REST_Request $request ){
- $magic = new DT_Magic_URL( $this->root );
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- register_rest_route(
- $namespace, '/'.$this->type, [
- [
- 'methods' => 'POST',
- 'callback' => [ $this, 'update_record' ],
- 'permission_callback' => function( WP_REST_Request $request ){
- $magic = new DT_Magic_URL( $this->root );
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- }
-
- public function update_record( WP_REST_Request $request ) {
- $params = $request->get_params();
- $params = dt_recursive_sanitize_array( $params );
-
- $post_id = $params['parts']['post_id']; //has been verified in verify_rest_endpoint_permissions_on_post()
-
- $args = [];
- if ( !is_user_logged_in() ){
- $global_name = apply_filters( 'dt_magic_link_global_name', __( 'Magic Link', 'disciple-tools-migration' ) );
- $args['comment_author'] = sprintf( __( '%s Submission', 'disciple-tools-migration' ), $global_name );
- wp_set_current_user( 0 );
- $current_user = wp_get_current_user();
- $current_user->add_cap( 'magic_link' );
- $current_user->display_name = sprintf( __( '%s Submission', 'disciple-tools-migration' ), $global_name );
- }
-
- if ( isset( $params['update']['comment'] ) && !empty( $params['update']['comment'] ) ){
- $update = DT_Posts::add_post_comment( $this->post_type, $post_id, $params['update']['comment'], 'comment', $args, false );
- if ( is_wp_error( $update ) ){
- return $update;
- }
- }
-
- if ( isset( $params['update']['start_date'] ) && !empty( $params['update']['start_date'] ) ){
- $update = DT_Posts::update_post( $this->post_type, $post_id, [ 'start_date' => $params['update']['start_date'] ], false, false );
- if ( is_wp_error( $update ) ){
- return $update;
- }
- }
-
- return true;
- }
-
- public function endpoint_get( WP_REST_Request $request ) {
- $params = $request->get_params();
- if ( ! isset( $params['parts'], $params['action'] ) ) {
- return new WP_Error( __METHOD__, 'Missing parameters', [ 'status' => 400 ] );
- }
-
- $data = [];
-
- $data[] = [ 'name' => 'List item' ]; // @todo remove example
- $data[] = [ 'name' => 'List item' ]; // @todo remove example
-
- return $data;
- }
-}
-Disciple_Tools_Migration_Magic_Login_User_App::instance();
diff --git a/magic-link/magic-link-map.php b/magic-link/magic-link-map.php
deleted file mode 100644
index 3ae0545..0000000
--- a/magic-link/magic-link-map.php
+++ /dev/null
@@ -1,315 +0,0 @@
-root . '/' . $this->type ) === $url ) {
-
- $this->magic = new DT_Magic_URL( $this->root );
- $this->parts = $this->magic->parse_url_parts();
-
-
- // register url and access
- add_action( 'template_redirect', [ $this, 'theme_redirect' ] );
- add_filter( 'dt_blank_access', function (){ return true;
- }, 100, 1 );
- add_filter( 'dt_allow_non_login_access', function (){ return true;
- }, 100, 1 );
- add_filter( 'dt_override_header_meta', function (){ return true;
- }, 100, 1 );
-
- // header content
- add_filter( 'dt_blank_title', [ $this, 'page_tab_title' ] ); // adds basic title to browser tab
- add_action( 'wp_print_scripts', [ $this, 'print_scripts' ], 1500 ); // authorizes scripts
- add_action( 'wp_print_styles', [ $this, 'print_styles' ], 1500 ); // authorizes styles
-
-
- // page content
- add_action( 'dt_blank_head', [ $this, '_header' ] );
- add_action( 'dt_blank_footer', [ $this, '_footer' ] );
- add_action( 'dt_blank_body', [ $this, 'body' ] ); // body for no post key
-
- add_filter( 'dt_magic_url_base_allowed_css', [ $this, 'dt_magic_url_base_allowed_css' ], 10, 1 );
- add_filter( 'dt_magic_url_base_allowed_js', [ $this, 'dt_magic_url_base_allowed_js' ], 10, 1 );
- add_action( 'wp_enqueue_scripts', [ $this, '_wp_enqueue_scripts' ], 100 );
- }
-
- if ( dt_is_rest() ) {
- add_action( 'rest_api_init', [ $this, 'add_endpoints' ] );
- add_filter( 'dt_allow_rest_access', [ $this, 'authorize_url' ], 10, 1 );
- }
- }
-
- public function dt_magic_url_base_allowed_js( $allowed_js ) {
- $allowed_js[] = 'jquery-touch-punch';
- $allowed_js[] = 'mapbox-gl';
- $allowed_js[] = 'jquery-cookie';
- $allowed_js[] = 'mapbox-cookie';
- $allowed_js[] = 'heatmap-js';
- return $allowed_js;
- }
-
- public function dt_magic_url_base_allowed_css( $allowed_css ) {
- $allowed_css[] = 'mapbox-gl-css';
- $allowed_css[] = 'introjs-css';
- $allowed_css[] = 'heatmap-css';
- $allowed_css[] = 'site-css';
- return $allowed_css;
- }
-
- public function header_javascript(){
- ?>
-
-
-
-
-
-
- root . '/v1';
- register_rest_route(
- $namespace,
- '/'.$this->type,
- [
- [
- 'methods' => WP_REST_Server::CREATABLE,
- 'callback' => [ $this, 'endpoint' ],
- 'permission_callback' => '__return_true',
- ],
- ]
- );
- }
-
- public function endpoint( WP_REST_Request $request ) {
- $params = $request->get_params();
-
- if ( ! isset( $params['parts'], $params['action'] ) ) {
- return new WP_Error( __METHOD__, 'Missing parameters', [ 'status' => 400 ] );
- }
-
- $params = dt_recursive_sanitize_array( $params );
-
- switch ( $params['action'] ) {
- case 'geojson':
- return $this->endpoint_geojson( $params['parts'] );
- default:
- return new WP_Error( __METHOD__, 'Missing valid action parameters', [ 'status' => 400 ] );
- }
- }
-
- public function endpoint_geojson( $parts ) {
- global $wpdb;
-
- $results = $wpdb->get_results(
- "SELECT * FROM $wpdb->dt_location_grid WHERE level = 0", ARRAY_A );
-
- if ( empty( $results ) ) {
- return $this->_empty_geojson();
- }
-
- $features = [];
- foreach ( $results as $result ) {
- $features[] = array(
- 'type' => 'Feature',
- 'properties' => array(
- 'grid_id' => $result['grid_id'],
- 'name' => $result['name'],
- 'value' => rand( 1, 10 ) // random value
- ),
- 'geometry' => array(
- 'type' => 'Point',
- 'coordinates' => array(
- (float) $result['longitude'],
- (float) $result['latitude'],
- 1
- ),
- ),
- );
- }
-
- $geojson = array(
- 'type' => 'FeatureCollection',
- 'features' => $features,
- );
-
- return $geojson;
- }
-
- private function _empty_geojson() {
- return array(
- 'type' => 'FeatureCollection',
- 'features' => array()
- );
- }
-}
-Disciple_Tools_Migration_Magic_Map_App::instance();
diff --git a/magic-link/magic-link-non-object.php b/magic-link/magic-link-non-object.php
deleted file mode 100644
index 21b920f..0000000
--- a/magic-link/magic-link-non-object.php
+++ /dev/null
@@ -1,192 +0,0 @@
-root . '/' . $this->type ) === $url ) {
-
- $this->magic = new DT_Magic_URL( $this->root );
- $this->parts = $this->magic->parse_url_parts();
-
-
- // register url and access
- add_filter( 'dt_override_header_meta', '__return_true', 100, 1 );
-
- // page content
- add_action( 'dt_blank_body', [ $this, 'body' ] ); // body for no post key
-
- add_filter( 'dt_magic_url_base_allowed_css', [ $this, 'dt_magic_url_base_allowed_css' ], 10, 1 );
- add_filter( 'dt_magic_url_base_allowed_js', [ $this, 'dt_magic_url_base_allowed_js' ], 10, 1 );
- add_action( 'wp_enqueue_scripts', [ $this, '_wp_enqueue_scripts' ], 100 );
- }
-
- if ( dt_is_rest() ) {
- add_action( 'rest_api_init', [ $this, 'add_endpoints' ] );
- }
- }
-
- public function dt_magic_url_base_allowed_js( $allowed_js ) {
- return $allowed_js;
- }
-
- public function dt_magic_url_base_allowed_css( $allowed_css ) {
- return $allowed_css;
- }
-
- public function header_style(){
- ?>
-
-
-
-
-
- root . '/v1';
- register_rest_route(
- $namespace,
- '/'.$this->type,
- [
- [
- 'methods' => WP_REST_Server::CREATABLE,
- 'callback' => [ $this, 'endpoint' ],
- 'permission_callback' => '__return_true',
- ],
- ]
- );
- }
-
- public function endpoint( WP_REST_Request $request ) {
- $params = $request->get_params();
-
- if ( ! isset( $params['parts'], $params['action'] ) ) {
- return new WP_Error( __METHOD__, 'Missing parameters', [ 'status' => 400 ] );
- }
-
- $params = dt_recursive_sanitize_array( $params );
-
- switch ( $params['action'] ) {
- case 'get':
- // do something
- return true;
- case 'excited':
- // do something else
- default:
- return true;
- }
- }
-}
-Disciple_Tools_Migration_Magic_Non_Object_App::instance();
diff --git a/magic-link/magic-link-user-app.php b/magic-link/magic-link-user-app.php
deleted file mode 100644
index 3fecd70..0000000
--- a/magic-link/magic-link-user-app.php
+++ /dev/null
@@ -1,362 +0,0 @@
-meta = [
- 'app_type' => 'magic_link',
- 'post_type' => $this->post_type,
- 'contacts_only' => false,
- 'fields' => [
- [
- 'id' => 'name',
- 'label' => 'Name'
- ]
- ],
- 'icon' => 'mdi mdi-cog-outline',
- 'show_in_home_apps' => false
- ];
-
- $this->meta_key = $this->root . '_' . $this->type . '_magic_key';
- parent::__construct();
-
- /**
- * user_app and module section
- */
- add_filter( 'dt_settings_apps_list', [ $this, 'dt_settings_apps_list' ], 10, 1 );
- add_action( 'rest_api_init', [ $this, 'add_endpoints' ] );
-
- /**
- * tests if other URL
- */
- $url = dt_get_url_path();
- if ( strpos( $url, $this->root . '/' . $this->type ) === false ) {
- return;
- }
- /**
- * tests magic link parts are registered and have valid elements
- */
- if ( !$this->check_parts_match() ){
- return;
- }
-
- // load if valid url
- add_action( 'dt_blank_body', [ $this, 'body' ] );
- add_filter( 'dt_magic_url_base_allowed_css', [ $this, 'dt_magic_url_base_allowed_css' ], 10, 1 );
- add_filter( 'dt_magic_url_base_allowed_js', [ $this, 'dt_magic_url_base_allowed_js' ], 10, 1 );
- }
-
- public function dt_magic_url_base_allowed_js( $allowed_js ) {
- // @todo add or remove js files with this filter
- return $allowed_js;
- }
-
- public function dt_magic_url_base_allowed_css( $allowed_css ) {
- // @todo add or remove js files with this filter
- return $allowed_css;
- }
-
- /**
- * Builds magic link type settings payload:
- * - key: Unique magic link type key; which is usually composed of root, type and _magic_key suffix.
- * - url_base: URL path information to map with parent magic link type.
- * - label: Magic link type name.
- * - description: Magic link type description.
- * - settings_display: Boolean flag which determines if magic link type is to be listed within frontend user profile settings.
- *
- * @param $apps_list
- *
- * @return mixed
- */
- public function dt_settings_apps_list( $apps_list ) {
- $apps_list[ $this->meta_key ] = [
- 'key' => $this->meta_key,
- 'url_base' => $this->root . '/' . $this->type,
- 'label' => $this->page_title,
- 'description' => $this->page_description,
- 'settings_display' => true
- ];
-
- return $apps_list;
- }
-
- /**
- * Writes custom styles to header
- *
- * @see DT_Magic_Url_Base()->header_style() for default state
- * @todo remove if not needed
- */
- public function header_style(){
- ?>
-
- header_javascript() for default state
- * @todo remove if not needed
- */
- public function header_javascript(){
- ?>
-
- footer_javascript() for default state
- * @todo remove if not needed
- */
- public function footer_javascript(){
- ?>
-
-
-
-
-
-
-
-
List From API
-
-
-
-
-
-
-
-
-
Form
-
-
-
-
-
-
- root . '/v1';
- register_rest_route(
- $namespace, '/'.$this->type, [
- [
- 'methods' => 'GET',
- 'callback' => [ $this, 'endpoint_get' ],
- 'permission_callback' => function( WP_REST_Request $request ){
- $magic = new DT_Magic_URL( $this->root );
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- register_rest_route(
- $namespace, '/'.$this->type, [
- [
- 'methods' => 'POST',
- 'callback' => [ $this, 'update_record' ],
- 'permission_callback' => function( WP_REST_Request $request ){
- $magic = new DT_Magic_URL( $this->root );
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- }
-
- public function update_record( WP_REST_Request $request ) {
- $params = $request->get_params();
- $params = dt_recursive_sanitize_array( $params );
-
- $post_id = $params['parts']['post_id']; //has been verified in verify_rest_endpoint_permissions_on_post()
-
- $args = [];
- if ( !is_user_logged_in() ){
- $global_name = apply_filters( 'dt_magic_link_global_name', __( 'Magic Link', 'disciple-tools-migration' ) );
- $args['comment_author'] = sprintf( __( '%s Submission', 'disciple-tools-migration' ), $global_name );
- wp_set_current_user( 0 );
- $current_user = wp_get_current_user();
- $current_user->add_cap( 'magic_link' );
- $current_user->display_name = sprintf( __( '%s Submission', 'disciple-tools-migration' ), $global_name );
- }
-
- if ( isset( $params['update']['comment'] ) && !empty( $params['update']['comment'] ) ){
- $update = DT_Posts::add_post_comment( $this->post_type, $post_id, $params['update']['comment'], 'comment', $args, false );
- if ( is_wp_error( $update ) ){
- return $update;
- }
- }
-
- if ( isset( $params['update']['start_date'] ) && !empty( $params['update']['start_date'] ) ){
- $update = DT_Posts::update_post( $this->post_type, $post_id, [ 'start_date' => $params['update']['start_date'] ], false, false );
- if ( is_wp_error( $update ) ){
- return $update;
- }
- }
-
- return true;
- }
-
- public function endpoint_get( WP_REST_Request $request ) {
- $params = $request->get_params();
- if ( ! isset( $params['parts'], $params['action'] ) ) {
- return new WP_Error( __METHOD__, 'Missing parameters', [ 'status' => 400 ] );
- }
-
- $data = [];
-
- $data[] = [ 'name' => 'List item' ]; // @todo remove example
- $data[] = [ 'name' => 'List item' ]; // @todo remove example
-
- return $data;
- }
-}
-Disciple_Tools_Migration_Magic_User_App::instance();
diff --git a/magic-link/post-type-magic-link/magic-link-post-type.php b/magic-link/post-type-magic-link/magic-link-post-type.php
deleted file mode 100644
index 527c560..0000000
--- a/magic-link/post-type-magic-link/magic-link-post-type.php
+++ /dev/null
@@ -1,277 +0,0 @@
-meta = [
- 'app_type' => 'magic_link',
- 'post_type' => $this->post_type,
- 'contacts_only' => true,
- 'fields' => [
- [
- 'id' => 'name',
- 'label' => 'Name'
- ]
- ],
- 'icon' => 'mdi mdi-cog-outline',
- 'show_in_home_apps' => false
- ];
-
- $this->meta_key = $this->root . '_' . $this->type . '_magic_key';
- parent::__construct();
-
- /**
- * post type and module section
- */
-// add_action( 'dt_details_additional_section', [ $this, 'dt_details_additional_section' ], 30, 2 );
-// add_filter( 'dt_details_additional_tiles', [ $this, 'dt_details_additional_tiles' ], 10, 2 );
- add_action( 'rest_api_init', [ $this, 'add_endpoints' ] );
-
-
- /**
- * tests if other URL
- */
- $url = dt_get_url_path();
- if ( strpos( $url, $this->root . '/' . $this->type ) === false ) {
- return;
- }
- /**
- * tests magic link parts are registered and have valid elements
- */
- if ( !$this->check_parts_match() ){
- return;
- }
-
- // load if valid url
- add_action( 'dt_blank_body', [ $this, 'body' ] ); // body for no post key
- add_filter( 'dt_magic_url_base_allowed_css', [ $this, 'dt_magic_url_base_allowed_css' ], 10, 1 );
- add_filter( 'dt_magic_url_base_allowed_js', [ $this, 'dt_magic_url_base_allowed_js' ], 10, 1 );
- add_action( 'wp_enqueue_scripts', [ $this, 'wp_enqueue_scripts' ], 100 );
- }
-
- public function wp_enqueue_scripts(){
- wp_enqueue_script( 'magic_link_scripts', trailingslashit( plugin_dir_url( __FILE__ ) ) . 'magic-link.js', [
- 'jquery',
- 'lodash',
- ], filemtime( plugin_dir_path( __FILE__ ) . 'magic-link.js' ), true );
- wp_localize_script(
- 'magic_link_scripts', 'jsObject', [
- 'map_key' => DT_Mapbox_API::get_key(),
- 'rest_base' => esc_url( rest_url() ),
- 'nonce' => wp_create_nonce( 'wp_rest' ),
- 'parts' => $this->parts,
- 'translations' => [
- 'add' => __( 'Add Magic', 'disciple-tools-migration' ),
- ],
- 'rest_namespace' => $this->root . '/v1/' . $this->type,
- ]
- );
- wp_enqueue_style( 'magic_link_css', trailingslashit( plugin_dir_url( __FILE__ ) ) . 'magic-link.css', [], filemtime( plugin_dir_path( __FILE__ ) . 'magic-link.css' ) );
- }
-
- public function dt_magic_url_base_allowed_js( $allowed_js ) {
- // @todo add or remove js files with this filter
- $allowed_js[] = 'magic_link_scripts';
- return $allowed_js;
- }
-
- public function dt_magic_url_base_allowed_css( $allowed_css ) {
- // @todo add or remove js files with this filter
- $allowed_css[] = 'magic_link_css';
- return $allowed_css;
- }
-
- /**
- * Post Type Tile Examples
- */
- public function dt_details_additional_tiles( $tiles, $post_type = '' ) {
- if ( $post_type === $this->post_type ){
- $tiles['dt_migrations_magic_url'] = [
- 'label' => __( 'Magic Url', 'disciple-tools-migration' ),
- 'description' => 'The Magic URL sets up a page accessible without authentication, only the link is needed. Useful for small applications liked to this record, like quick surveys or updates.'
- ];
- }
- return $tiles;
- }
- public function dt_details_additional_section( $section, $post_type ) {
- // test if campaigns post type and campaigns_app_module enabled
- if ( $post_type === $this->post_type ) {
- if ( 'dt_migrations_magic_url' === $section ) {
- $link = DT_Magic_URL::get_link_url_for_post( $post_type, get_the_ID(), $this->root, $this->type )
- ?>
- See help for description.
- Open magic link
-
-
-
-
-
-
List From API
-
-
-
-
-
-
-
-
-
Form
-
- parts['post_id'];
-
- // get the post. Make sure to only display the needed pieces on the front end as this link does net require auth
- $post = DT_Posts::get_post( $this->post_type, $post_id, true, false );
- if ( is_wp_error( $post ) ){
- return;
- }
- $fields = DT_Posts::get_post_field_settings( $this->post_type );
- render_field_for_display( 'start_date', $fields, $post );
- ?>
-
-
- Comment
-
-
-
- Submit Update
-
-
-
-
- root . '/v1';
- register_rest_route(
- $namespace, '/' . $this->type, [
- [
- 'methods' => 'GET',
- 'callback' => [ $this, 'endpoint_get' ],
- 'permission_callback' => function( WP_REST_Request $request ){
- $magic = new DT_Magic_URL( $this->root );
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- register_rest_route(
- $namespace, '/' . $this->type, [
- [
- 'methods' => 'POST',
- 'callback' => [ $this, 'update_record' ],
- 'permission_callback' => function( WP_REST_Request $request ){
- $magic = new DT_Magic_URL( $this->root );
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- }
-
- public function update_record( WP_REST_Request $request ) {
- $params = $request->get_params();
- $params = dt_recursive_sanitize_array( $params );
-
- $post_id = $params['parts']['post_id']; //has been verified in verify_rest_endpoint_permissions_on_post()
-
- $args = [];
- if ( !is_user_logged_in() ){
- $global_name = apply_filters( 'dt_magic_link_global_name', __( 'Magic Link', 'disciple-tools-migration' ) );
- $args['comment_author'] = sprintf( __( '%s Submission', 'disciple-tools-migration' ), $global_name );
- wp_set_current_user( 0 );
- $current_user = wp_get_current_user();
- $current_user->add_cap( 'magic_link' );
- $current_user->display_name = sprintf( __( '%s Submission', 'disciple-tools-migration' ), $global_name );
- }
-
- if ( isset( $params['update']['comment'] ) && !empty( $params['update']['comment'] ) ){
- $update = DT_Posts::add_post_comment( $this->post_type, $post_id, $params['update']['comment'], 'comment', $args, false );
- if ( is_wp_error( $update ) ){
- return $update;
- }
- }
-
- if ( isset( $params['update']['start_date'] ) && !empty( $params['update']['start_date'] ) ){
- $update = DT_Posts::update_post( $this->post_type, $post_id, [ 'start_date' => $params['update']['start_date'] ], false, false );
- if ( is_wp_error( $update ) ){
- return $update;
- }
- }
-
- return true;
- }
-
- public function endpoint_get( WP_REST_Request $request ) {
- $params = $request->get_params();
- if ( ! isset( $params['parts'], $params['action'] ) ) {
- return new WP_Error( __METHOD__, 'Missing parameters', [ 'status' => 400 ] );
- }
-
- $data = [];
-
- $data[] = [ 'name' => 'List item' ]; // @todo remove example
- $data[] = [ 'name' => 'List item' ]; // @todo remove example
-
- return $data;
- }
-}
-Disciple_Tools_Migration_Magic_Link::instance();
diff --git a/magic-link/post-type-magic-link/magic-link.css b/magic-link/post-type-magic-link/magic-link.css
deleted file mode 100644
index ef408c0..0000000
--- a/magic-link/post-type-magic-link/magic-link.css
+++ /dev/null
@@ -1,6 +0,0 @@
-body {
- background-color: white !important;
-}
-#magic-link-wrapper {
- padding: 1em;
-}
diff --git a/magic-link/post-type-magic-link/magic-link.js b/magic-link/post-type-magic-link/magic-link.js
deleted file mode 100644
index 5a0e84b..0000000
--- a/magic-link/post-type-magic-link/magic-link.js
+++ /dev/null
@@ -1,61 +0,0 @@
-window.get_magic = () => {
- window.makeRequest( "GET", '/', { action: 'get', parts: jsObject.parts }, jsObject.rest_namespace )
- .done(function(data){
- window.load_magic( data )
- })
- .fail(function(e) {
- console.log(e)
- jQuery('#error').html(e)
- })
-}
-window.get_magic()
-
-window.load_magic = ( data ) => {
- let content = jQuery('#api-content')
- let spinner = jQuery('.loading-spinner')
-
- content.empty()
- let html = ``
- data.forEach(v=>{
- html += `
-
- ${window.lodash.escape(v.name)}
-
- `
- })
- content.html(html)
-
- spinner.removeClass('active')
-
-}
-
-$('.dt_date_picker').datepicker({
- constrainInput: false,
- dateFormat: 'yy-mm-dd',
- changeMonth: true,
- changeYear: true,
- yearRange: "1900:2050",
-}).each(function() {
- if (this.value && moment.unix(this.value).isValid()) {
- this.value = window.SHAREDFUNCTIONS.formatDate(this.value);
- }
-})
-
-
-$('#submit-form').on("click", function (){
- $(this).addClass("loading")
- let start_date = $('#start_date').val()
- let comment = $('#comment-input').val()
- let update = {
- start_date,
- comment
- }
-
- window.makeRequest( "POST", '/', { parts: jsObject.parts, update }, jsObject.rest_namespace ).done(function(data){
- window.location.reload()
- })
- .fail(function(e) {
- console.log(e)
- jQuery('#error').html(e)
- })
-})
diff --git a/magic-link/templates/starter-template.php b/magic-link/templates/starter-template.php
deleted file mode 100644
index 655cbd3..0000000
--- a/magic-link/templates/starter-template.php
+++ /dev/null
@@ -1,443 +0,0 @@
- 'migrations-template',
- 'text' => 'Migrations Template',
- ];
- $types['default-options'][] = [
- 'value' => 'migrations-template',
- 'text' => 'Migrations Template',
- ];
- return $types;
-});
-
-add_action('dt_magic_link_template_load', function ( $template ) {
- if ( isset( $template['type'] ) && $template['type'] === 'migrations-template' ) {
- new Disciple_Tools_Magic_Links_Template_Migrations_Template( $template );
- }
-} );
-
-/**
- * Class Disciple_Tools_Magic_Links_Templates
- */
-class Disciple_Tools_Magic_Links_Template_Migrations_Template extends DT_Magic_Url_Base {
-
- protected $template_type = 'migrations-template';
- public $page_title = 'Migrations Template';
- public $page_description = 'Edit all connections to a given post';
- public $root = 'templates'; // @todo define the root of the url {yoursite}/root/type/key/action
- public $type = 'template_id'; // Placeholder to be replaced with actual template ids
- public $type_name = '';
- public $post_type = 'contacts'; // Main post type that the ML is linked to.
- public $record_post_type = 'groups'; // Child post type determined by the connection field selected
- private $post = null;
- private $items = [];
- private $meta_key = '';
-
- public $show_bulk_send = true;
- public $show_app_tile = true;
-
- private static $_instance = null;
- public $meta = []; // Allows for instance specific data.
- public $translatable = [
- 'query',
- 'user',
- 'contact'
- ]; // Order of translatable flags to be checked. Translate on first hit..!
-
- private $template = null;
- private $link_obj = null;
- private $layout = null;
-
- public function __construct( $template = null ) {
-
- // only handle this template type
- if ( empty( $template ) || $template['type'] !== $this->template_type ) {
- return;
- }
-
- $this->template = $template;
- $this->post_type = $template['post_type'];
- $this->type = array_map( 'sanitize_key', wp_unslash( explode( '_', $template['id'] ) ) )[1];
- $this->type_name = $template['name'];
- $this->page_title = $template['name'];
- $this->page_description = '';
-
- if ( !isset( $this->template['record_type'] ) ) {
- $this->template['record_type'] = $this->record_post_type;
- }
-
- /**
- * Specify metadata structure, specific to the processing of current
- * magic link class type.
- *
- * - meta: Magic link plugin related data.
- * - app_type: Flag indicating type to be processed by magic link plugin.
- * - class_type: Flag indicating template class type.
- */
-
- $show_in_apps = false;
-
- if ( $template['post_type'] == 'contacts' ) {
- $show_in_apps = true;
- }
-
- $this->meta = [
- 'app_type' => 'magic_link',
- 'class_type' => 'template',
- 'show_in_home_apps' => $show_in_apps,
- 'icon' => 'mdi mdi-account-network',
- ];
-
- /**
- * Once adjustments have been made, proceed with parent instantiation!
- */
-
- $this->meta_key = $this->root . '_' . $this->type;
- parent::__construct();
- add_action( 'rest_api_init', [ $this, 'add_endpoints' ] );
-
- /**
- * Test magic link parts are registered and have valid elements.
- */
-
- if ( ! $this->check_parts_match() ) {
- return;
- }
-
- /**
- * Attempt to load sooner, rather than later; corresponding post record details.
- */
-
- $this->post = DT_Posts::get_post( $this->post_type, $this->parts['post_id'], true, false );
-
- // @todo remove example and replace with DT_Posts::list_posts()
- $data = [];
- $data[] = [
- 'ID' => '123',
- 'name' => 'List item 1',
- 'last_modified' => [
- 'timestamp' => 1735678800,
- ],
- ];
- $data[] = [
- 'ID' => '124',
- 'name' => 'List item 2',
- 'last_modified' => [
- 'timestamp' => 1735678800,
- ],
- ];
- $this->items = [
- 'posts' => $data
- ];
-
- /**
- * Attempt to load corresponding link object, if a valid incoming id has been detected.
- */
-
- $this->link_obj = Disciple_Tools_Bulk_Magic_Link_Sender_API::fetch_option_link_obj( $this->fetch_incoming_link_param( 'id' ) );
-
- // Revert back to dt translations
- $this->hard_switch_to_default_dt_text_domain();
-
- // Initialize layout front-end
- $this->layout = new Disciple_Tools_Magic_Links_Layout_List_Detail(
- $this->template,
- $this->post,
- $this->link_obj
- );
-
- /**
- * Load if valid url
- */
-
- add_action( 'dt_blank_body', [ $this, 'body' ] );
- add_filter( 'dt_magic_url_base_allowed_css', [ $this, 'dt_magic_url_base_allowed_css' ], 10, 1 );
- add_filter( 'dt_magic_url_base_allowed_js', [ $this, 'dt_magic_url_base_allowed_js' ], 10, 1 );
- add_action( 'wp_enqueue_scripts', [ $this, 'wp_enqueue_scripts' ], 100 );
- add_filter( 'dt_can_update_permission', [ $this, 'can_update_permission_filter' ], 10, 3 );
- }
-
- // Ensure template fields remain editable
- public function can_update_permission_filter( $has_permission, $post_id, $post_type ) {
- return true;
- }
-
- public function wp_enqueue_scripts() {
- $this->layout->wp_enqueue_scripts();
-
- Disciple_Tools_Bulk_Magic_Link_Sender_API::enqueue_magic_link_utilities_script();
- }
-
- public function dt_magic_url_base_allowed_js( $allowed_js ) {
- $allowed_js[] = Disciple_Tools_Bulk_Magic_Link_Sender_API::get_magic_link_utilities_script_handle();
-
- return $this->layout->allowed_js( $allowed_js );
- }
-
- public function dt_magic_url_base_allowed_css( $allowed_css ) {
- return $this->layout->allowed_css( $allowed_css );
- }
-
- /**
- * Writes javascript to the footer
- *
- * @see DT_Magic_Url_Base()->footer_javascript() for default state
- */
- public function footer_javascript() {
- $this->layout->footer_javascript( $this->parts, $this->items );
- }
-
- public function body() {
- $this->layout->body();
- }
-
- /**
- * Register REST Endpoints
- * @link https://github.com/DiscipleTools/disciple-tools-theme/wiki/Site-to-Site-Link for outside of wordpress authentication
- */
- public function add_endpoints() {
- $namespace = $this->root . '/v1';
- register_rest_route(
- $namespace, '/' . $this->type . '/post', [
- [
- 'methods' => 'POST',
- 'callback' => [ $this, 'get_post' ],
- 'permission_callback' => function ( WP_REST_Request $request ) {
- $magic = new DT_Magic_URL( $this->root );
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- register_rest_route(
- $namespace, '/' . $this->type . '/update', [
- [
- 'methods' => 'POST',
- 'callback' => [ $this, 'update_record' ],
- 'permission_callback' => function ( WP_REST_Request $request ) {
- $magic = new DT_Magic_URL( $this->root );
-
- $params = $request->get_params();
-
- $permissions = $this->check_permissions( $params['parts']['post_id'], $params['post_id'] );
- if ( !$permissions ) {
- return false;
- }
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- register_rest_route(
- $namespace, '/' . $this->type . '/comment', [
- [
- 'methods' => 'POST',
- 'callback' => [ $this, 'new_comment' ],
- 'permission_callback' => function ( WP_REST_Request $request ) {
- $magic = new DT_Magic_URL( $this->root );
-
- $params = $request->get_params();
-
- $permissions = $this->check_permissions( $params['parts']['post_id'], $params['post_id'] );
- if ( !$permissions ) {
- return false;
- }
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- register_rest_route(
- $namespace, '/' . $this->type . '/sort_post', [
- [
- 'methods' => 'POST',
- 'callback' => [ $this, 'sorted_list_posts' ],
- 'permission_callback' => function ( WP_REST_Request $request ) {
- $magic = new DT_Magic_URL( $this->root );
-
- $params = $request->get_params();
-
- $permissions = $this->check_permissions( $params['parts']['post_id'], $params['post_id'] );
- if ( !$permissions ) {
- return false;
- }
-
- return $magic->verify_rest_endpoint_permissions_on_post( $request );
- },
- ],
- ]
- );
- }
-
- public function check_permissions( $post_id, $connection_id ) {
-
- // if connection is actually the main post id, we're good
- if ( strval( $post_id ) === strval( $connection_id ) ) {
- return true;
- }
-
- //set query fields to search for our post_id
- $query_fields = [];
- //todo: based on list of posts that are accessible, verify current user
- // has permission to edit the given connection_id
-
- //get related records that have our query fields
- $this->items = DT_Posts::list_posts( $this->record_post_type, [
- 'limit' => 1000,
- 'fields' => [
- $query_fields
- ]
- ], false );
-
- //return true if the post_id in the request is in the list
- foreach ( $this->items['posts'] as $item ) {
- if ( strval( $connection_id ) === strval( $item['ID'] ) ) {
- return true;
- }
- }
- return false;
- }
-
- public function get_post( WP_REST_Request $request ){
- $params = $request->get_params();
- if ( !isset( $params['post_type'], $params['post_id'], $params['parts'], $params['action'], $params['comment_count'] ) ){
- return new WP_Error( __METHOD__, 'Missing parameters', [ 'status' => 400 ] );
- }
-
- // Sanitize and fetch user/post id
- $params = dt_recursive_sanitize_array( $params );
-
- // Update logged-in user state if required accordingly, based on their sys_type
- if ( !is_user_logged_in() ) {
- DT_ML_Helper::update_user_logged_in_state();
- }
-
- return [
- 'success' => true,
- 'post' => $this->items['posts'][0],
- 'comments' => [],
- ];
- }
-
- public function update_record( WP_REST_Request $request ){
- $params = $request->get_params();
- if ( !isset( $params['post_id'], $params['post_type'], $params['parts'], $params['action'], $params['fields'] ) ){
- return new WP_Error( __METHOD__, 'Missing core parameters', [ 'status' => 400 ] );
- }
-
- // Sanitize and fetch user id
- $params = dt_recursive_sanitize_array( $params );
-
- // Update logged-in user state, if required
- if ( !is_user_logged_in() ){
- DT_ML_Helper::update_user_logged_in_state();
- }
-
- $updates = [];
-
- //todo: handle all input fields
- /*
- foreach ( $params['fields']['dt'] ?? [] as $field ) {
- }
- */
-
- // Update specified post record
- if ( empty( $params['post_id'] ) ) {
- // if ID is empty ("0", 0, or generally falsy), create a new post
- $updates['type'] = 'access';
-
- $updated_post = DT_Posts::create_post( $params['post_type'], $updates, false, false );
- } else {
- // dt_write_log( json_encode( $updates ) );
- $updated_post = DT_Posts::update_post( $params['post_type'], $params['post_id'], $updates, false, false );
- }
-
- if ( empty( $updated_post ) || is_wp_error( $updated_post ) ) {
- dt_write_log( $updated_post );
- return [
- 'success' => false,
- 'message' => 'Unable to update/create contact record details!'
- ];
- }
-
- // Next, any identified custom fields, are to be added as comments
- foreach ( $params['fields']['custom'] ?? [] as $field ) {
- $field = dt_recursive_sanitize_array( $field );
- if ( ! empty( $field['value'] ) ) {
- $updated_comment = DT_Posts::add_post_comment( $updated_post['post_type'], $updated_post['ID'], $field['value'], 'comment', [], false );
- if ( empty( $updated_comment ) || is_wp_error( $updated_comment ) ) {
- return [
- 'success' => false,
- 'message' => 'Unable to add comment to record details!'
- ];
- }
- }
- }
-
- // Next, dispatch submission notification, accordingly; always send by default.
- if ( isset( $params['send_submission_notifications'] ) && $params['send_submission_notifications'] && isset( $updated_post['assigned_to'], $updated_post['assigned_to']['id'], $updated_post['assigned_to']['display'] ) ) {
- $default_comment = sprintf( __( '%s Updates Submitted', 'disciple_tools' ), $params['template_name'] );
- $submission_comment = '@[' . $updated_post['assigned_to']['display'] . '](' . $updated_post['assigned_to']['id'] . ') ' . $default_comment;
- DT_Posts::add_post_comment( $updated_post['post_type'], $updated_post['ID'], $submission_comment, 'comment', [], false );
- }
-
- // Finally, return successful response
- return [
- 'success' => true,
- 'message' => '',
- 'post' => $updated_post,
- ];
- }
-
- public function new_comment( WP_REST_Request $request ){
- $params = $request->get_params();
- if ( !isset( $params['post_type'], $params['post_id'], $params['parts'], $params['action'] ) ){
- return new WP_Error( __METHOD__, 'Missing parameters', [ 'status' => 400 ] );
- }
-
- // Sanitize and fetch user id
- $params = dt_recursive_sanitize_array( $params );
-
- // Update logged-in user state, if required
- if ( !is_user_logged_in() ){
- DT_ML_Helper::update_user_logged_in_state();
- }
-
- $post = DT_Posts::get_post( $params['post_type'], $params['post_id'], false, false );
- //$params['comment']
- DT_Posts::add_post_comment( $post['post_type'], $post['ID'], $params['comment'], 'comment', [], false );
-
- return [
- 'success' => true,
- 'message' => '',
- 'post' => $post,
- ];
- }
-
- public function sorted_list_posts( WP_REST_Request $request ){
- $params = $request->get_params();
- if ( !isset( $params['post_type'], $params['post_id'], $params['parts'], $params['action'] ) ){
- return new WP_Error( __METHOD__, 'Missing parameters', [ 'status' => 400 ] );
- }
-
- // Sanitize and fetch user id
- $params = dt_recursive_sanitize_array( $params );
-
- // Update logged-in user state, if required
- if ( !is_user_logged_in() ){
- DT_ML_Helper::update_user_logged_in_state();
- }
-
- //todo: get sorted items using DT_Posts::list_posts()
-
- return $this->items;
- }
-}
diff --git a/post-type/loader.php b/post-type/loader.php
deleted file mode 100644
index 1908513..0000000
--- a/post-type/loader.php
+++ /dev/null
@@ -1,38 +0,0 @@
- __( 'Migration', 'disciple-tools-migration' ),
- 'enabled' => true,
- 'locked' => true,
- 'prerequisites' => [ 'contacts_base' ],
- 'post_type' => 'migrations',
- 'description' => __( 'Default migration functionality', 'disciple-tools-migration' )
- ];
-
- return $modules;
-}, 20, 1 );
-
-require_once 'module-base.php';
-Disciple_Tools_Migration_Base::instance();
-
-/**
- * @todo require_once and load additional modules
- */
diff --git a/post-type/module-base.php b/post-type/module-base.php
deleted file mode 100644
index 5ccd133..0000000
--- a/post-type/module-base.php
+++ /dev/null
@@ -1,583 +0,0 @@
-single_name = __( 'Migration', 'disciple-tools-migration' );
- $this->plural_name = __( 'Migrations', 'disciple-tools-migration' );
-
- if ( class_exists( 'Disciple_Tools_Post_Type_Template' ) ) {
- new Disciple_Tools_Post_Type_Template( $this->post_type, $this->single_name, $this->plural_name );
- }
- }
-
- /**
- * Set the singular and plural translations for this post types settings
- * The add_filter is set onto a higher priority than the one in Disciple_tools_Post_Type_Template
- * so as to enable localisation changes. Otherwise the system translation passed in to the custom post type
- * will prevail.
- */
- public function dt_get_post_type_settings( $settings, $post_type ){
- if ( $post_type === $this->post_type ){
- $settings['label_singular'] = __( 'Migration', 'disciple-tools-migration' );
- $settings['label_plural'] = __( 'Migrations', 'disciple-tools-migration' );
- }
- return $settings;
- }
-
- /**
- * @todo define the permissions for the roles
- * Documentation
- * @link https://github.com/DiscipleTools/Documentation/blob/master/Theme-Core/roles-permissions.md#rolesd
- */
- public function dt_set_roles_and_permissions( $expected_roles ){
-
- if ( !isset( $expected_roles['my_migration_role'] ) ){
- $expected_roles['my_migration_role'] = [
-
- 'label' => __( 'My Migration Role', 'disciple-tools-migration' ),
- 'description' => 'Does something Cool',
- 'permissions' => [
- 'access_contacts' => true,
- // @todo more capabilities
- ]
- ];
- }
-
- // if the user can access contact they also can access this post type
- foreach ( $expected_roles as $role => $role_value ){
- if ( isset( $expected_roles[$role]['permissions']['access_contacts'] ) && $expected_roles[$role]['permissions']['access_contacts'] ){
- $expected_roles[$role]['permissions']['access_' . $this->post_type ] = true;
- $expected_roles[$role]['permissions']['create_' . $this->post_type] = true;
- $expected_roles[$role]['permissions']['update_' . $this->post_type] = true;
- }
- }
-
- if ( isset( $expected_roles['dt_admin'] ) ){
- $expected_roles['dt_admin']['permissions']['view_any_'.$this->post_type ] = true;
- $expected_roles['dt_admin']['permissions']['update_any_'.$this->post_type ] = true;
- }
- if ( isset( $expected_roles['administrator'] ) ){
- $expected_roles['administrator']['permissions']['view_any_'.$this->post_type ] = true;
- $expected_roles['administrator']['permissions']['update_any_'.$this->post_type ] = true;
- $expected_roles['administrator']['permissions']['delete_any_'.$this->post_type ] = true;
- }
-
- return $expected_roles;
- }
-
- /**
- * @todo define fields
- * Documentation
- * @link https://github.com/DiscipleTools/Documentation/blob/master/Theme-Core/fields.md
- */
- public function dt_custom_fields_settings( $fields, $post_type ){
- if ( $post_type === $this->post_type ){
-
-
-
- /**
- * @todo configure status appropriate to your post type
- * @todo modify strings and add elements to default array
- */
- $fields['status'] = [
- 'name' => __( 'Status', 'disciple-tools-migration' ),
- 'description' => __( 'Set the current status.', 'disciple-tools-migration' ),
- 'type' => 'key_select',
- 'default' => [
- 'inactive' => [
- 'label' => __( 'Inactive', 'disciple-tools-migration' ),
- 'description' => __( 'No longer active.', 'disciple-tools-migration' ),
- 'color' => '#F43636'
- ],
- 'active' => [
- 'label' => __( 'Active', 'disciple-tools-migration' ),
- 'description' => __( 'Is active.', 'disciple-tools-migration' ),
- 'color' => '#4CAF50'
- ],
- ],
- 'tile' => 'status',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/status.svg',
- 'default_color' => '#366184',
- 'show_in_table' => 10,
- ];
- $fields['assigned_to'] = [
- 'name' => __( 'Assigned To', 'disciple-tools-migration' ),
- 'description' => __( 'Select the main person who is responsible for reporting on this record.', 'disciple-tools-migration' ),
- 'type' => 'user_select',
- 'default' => '',
- 'tile' => 'status',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/assigned-to.svg',
- 'show_in_table' => 16,
- ];
-
-
-
- /**
- * Common and recommended fields
- */
- $fields['start_date'] = [
- 'name' => __( 'Start Date', 'disciple-tools-migration' ),
- 'description' => '',
- 'type' => 'date',
- 'default' => time(),
- 'tile' => 'details',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/date-start.svg',
- ];
- $fields['end_date'] = [
- 'name' => __( 'End Date', 'disciple-tools-migration' ),
- 'description' => '',
- 'type' => 'date',
- 'default' => '',
- 'tile' => 'details',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/date-end.svg',
- ];
- $fields['multi_select'] = [
- 'name' => __( 'Multi-Select', 'disciple-tools-migration' ),
- 'description' => __( 'Multi Select Field', 'disciple-tools-migration' ),
- 'type' => 'multi_select',
- 'default' => [
- 'item_1' => [
- 'label' => __( 'Item 1', 'disciple-tools-migration' ),
- 'description' => __( 'Item 1.', 'disciple-tools-migration' ),
- ],
- 'item_2' => [
- 'label' => __( 'Item 2', 'disciple-tools-migration' ),
- 'description' => __( 'Item 2.', 'disciple-tools-migration' ),
- ],
- 'item_3' => [
- 'label' => __( 'Item 3', 'disciple-tools-migration' ),
- 'description' => __( 'Item 3.', 'disciple-tools-migration' ),
- ],
- ],
- 'tile' => 'details',
- 'in_create_form' => true,
- 'icon' => get_template_directory_uri() . '/dt-assets/images/languages.svg?v=2',
- ];
-
- $fields['contact_address'] = [
- 'name' => __( 'Address', 'disciple-tools-migration' ),
- 'icon' => get_template_directory_uri() . '/dt-assets/images/house.svg',
- 'type' => 'communication_channel',
- 'tile' => 'details',
- 'mapbox' => false,
- 'customizable' => false
- ];
- if ( DT_Mapbox_API::get_key() ){
- $fields['contact_address']['custom_display'] = true;
- $fields['contact_address']['mapbox'] = true;
- unset( $fields['contact_address']['tile'] );
- }
- // end locations
-
- /**
- * @todo this adds generational support to this post type. remove if not needed.
- * generation and peer connection fields
- */
- $fields['parents'] = [
- 'name' => __( 'Parents', 'disciple-tools-migration' ),
- 'description' => '',
- 'type' => 'connection',
- 'post_type' => $this->post_type,
- 'p2p_direction' => 'from',
- 'p2p_key' => $this->post_type.'_to_'.$this->post_type,
- 'tile' => 'connections',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/group-parent.svg',
- 'create-icon' => get_template_directory_uri() . '/dt-assets/images/add-group.svg',
- ];
- $fields['peers'] = [
- 'name' => __( 'Peers', 'disciple-tools-migration' ),
- 'description' => '',
- 'type' => 'connection',
- 'post_type' => $this->post_type,
- 'p2p_direction' => 'any',
- 'p2p_key' => $this->post_type.'_to_peers',
- 'tile' => 'connections',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/group-peer.svg',
- 'create-icon' => get_template_directory_uri() . '/dt-assets/images/add-group.svg',
- ];
- $fields['children'] = [
- 'name' => __( 'Children', 'disciple-tools-migration' ),
- 'description' => '',
- 'type' => 'connection',
- 'post_type' => $this->post_type,
- 'p2p_direction' => 'to',
- 'p2p_key' => $this->post_type.'_to_'.$this->post_type,
- 'tile' => 'connections',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/group-child.svg',
- 'create-icon' => get_template_directory_uri() . '/dt-assets/images/add-group.svg',
- ];
- // end generations
-
- /**
- * @todo this adds people groups support to this post type. remove if not needed.
- * Connections to other post types
- */
- $fields['peoplegroups'] = [
- 'name' => __( 'People Groups', 'disciple-tools-migration' ),
- 'description' => __( 'The people groups connected to this record.', 'disciple-tools-migration' ),
- 'type' => 'connection',
- 'tile' => 'details',
- 'post_type' => 'peoplegroups',
- 'p2p_direction' => 'to',
- 'p2p_key' => $this->post_type.'_to_peoplegroups',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/people-group.svg',
- ];
-
- $fields['contacts'] = [
- 'name' => __( 'Contacts', 'disciple-tools-migration' ),
- 'description' => '',
- 'type' => 'connection',
- 'post_type' => 'contacts',
- 'p2p_direction' => 'to',
- 'p2p_key' => $this->post_type.'_to_contacts',
- 'tile' => 'status',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/group-type.svg',
- 'create-icon' => get_template_directory_uri() . '/dt-assets/images/add-contact.svg',
- 'show_in_table' => 35
- ];
- }
-
- /**
- * @todo this adds connection to contacts. remove if not needed.
- */
- if ( $post_type === 'contacts' ){
- $fields[$this->post_type] = [
- 'name' => $this->plural_name,
- 'description' => '',
- 'type' => 'connection',
- 'post_type' => $this->post_type,
- 'p2p_direction' => 'from',
- 'p2p_key' => $this->post_type.'_to_contacts',
- 'tile' => 'other',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/group-type.svg',
- 'create-icon' => get_template_directory_uri() . '/dt-assets/images/add-group.svg',
- 'show_in_table' => 35
- ];
- }
-
- /**
- * @todo this adds connection to groups. remove if not needed.
- */
- if ( $post_type === 'groups' ){
- $fields[$this->post_type] = [
- 'name' => $this->plural_name,
- 'description' => '',
- 'type' => 'connection',
- 'post_type' => $this->post_type,
- 'p2p_direction' => 'from',
- 'p2p_key' => $this->post_type.'_to_groups',
- 'tile' => 'other',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/group-type.svg',
- 'create-icon' => get_template_directory_uri() . '/dt-assets/images/add-group.svg',
- 'show_in_table' => 35
- ];
- }
- return $fields;
- }
-
- /**
- * @todo define tiles
- * @link https://github.com/DiscipleTools/Documentation/blob/master/Theme-Core/field-and-tiles.md
- */
- public function dt_details_additional_tiles( $tiles, $post_type = '' ){
- if ( $post_type === $this->post_type ){
- $tiles['connections'] = [ 'label' => __( 'Connections', 'disciple-tools-migration' ) ];
- $tiles['other'] = [ 'label' => __( 'Other', 'disciple-tools-migration' ) ];
- }
- return $tiles;
- }
-
- /**
- * @todo define additional section content
- * Documentation
- * @link https://github.com/DiscipleTools/Documentation/blob/master/Theme-Core/field-and-tiles.md#add-custom-content
- */
- public function dt_details_additional_section( $section, $post_type ){
-
- if ( $post_type === $this->post_type && $section === 'other' ) {
- $fields = DT_Posts::get_post_field_settings( $post_type );
- $post = DT_Posts::get_post( $this->post_type, get_the_ID() );
- ?>
-
-
-
Add information or custom fields here
-
-
- post_type ){
-// if ( $field_key === "members" ){
-// // @todo change 'members'
-// // execute your code here, if field key match
-// }
-// if ( $field_key === "coaches" ){
-// // @todo change 'coaches'
-// // execute your code here, if field key match
-// }
-// }
-// if ( $post_type === "contacts" && $field_key === $this->post_type ){
-// // execute your code here, if a change is made in contacts and a field key is matched
-// }
- }
-
- //action when a post connection is removed during create or update
- public function post_connection_removed( $post_type, $post_id, $field_key, $value ){
-// if ( $post_type === $this->post_type ){
-// // execute your code here, if connection removed
-// }
- }
-
- //filter at the start of post update
- public function dt_post_update_fields( $fields, $post_type, $post_id ){
-// if ( $post_type === $this->post_type ){
-// // execute your code here
-// }
- return $fields;
- }
-
-
- //filter when a comment is created
- public function dt_comment_created( $post_type, $post_id, $comment_id, $type ){
- }
-
- // filter at the start of post creation
- public function dt_post_create_fields( $fields, $post_type ){
- if ( $post_type === $this->post_type ){
- $post_fields = DT_Posts::get_post_field_settings( $post_type );
- if ( isset( $post_fields['status'] ) && !isset( $fields['status'] ) ){
- $fields['status'] = 'active';
- }
- }
- return $fields;
- }
-
- //action when a post has been created
- public function dt_post_created( $post_type, $post_id, $initial_fields ){
- }
-
- //list page filters function
-
- /**
- * @todo adjust queries to support list counts
- * Documentation
- * @link https://github.com/DiscipleTools/Documentation/blob/master/Theme-Core/list-query.md
- */
- private static function count_records_assigned_to_me_by_status(){
- global $wpdb;
- $post_type = self::post_type();
- $current_user = get_current_user_id();
-
- $results = $wpdb->get_results( $wpdb->prepare( "
- SELECT status.meta_value as status, count(pm.post_id) as count
- FROM $wpdb->postmeta pm
- INNER JOIN $wpdb->posts a ON( a.ID = pm.post_id AND a.post_type = %s and a.post_status = 'publish' )
- INNER JOIN $wpdb->postmeta status ON ( status.post_id = pm.post_id AND status.meta_key = 'status' )
- WHERE pm.meta_key = 'assigned_to'
- AND pm.meta_value = CONCAT( 'user-', %s )
- GROUP BY status.meta_value
- ", $post_type, $current_user ), ARRAY_A);
-
- return $results;
- }
-
- //list page filters function
- private static function count_records_by_status(){
- global $wpdb;
- $results = $wpdb->get_results($wpdb->prepare( "
- SELECT status.meta_value as status, count(status.post_id) as count
- FROM $wpdb->postmeta status
- INNER JOIN $wpdb->posts a ON( a.ID = status.post_id AND a.post_type = %s and a.post_status = 'publish' )
- WHERE status.meta_key = 'status'
- GROUP BY status.meta_value
- ", self::post_type() ), ARRAY_A );
-
- return $results;
- }
-
- //build list page filters
- public static function dt_user_list_filters( $filters, $post_type ){
- /**
- * @todo process and build filter lists
- */
- if ( $post_type === self::post_type() ){
- $records_assigned_to_me_by_status_counts = self::count_records_assigned_to_me_by_status();
- $fields = DT_Posts::get_post_field_settings( $post_type );
- /**
- * Setup my filters
- */
- $active_counts = [];
- $status_counts = [];
- $total_my = 0;
- foreach ( $records_assigned_to_me_by_status_counts as $count ){
- $total_my += $count['count'];
- dt_increment( $status_counts[$count['status']], $count['count'] );
- }
-
- // add assigned to me tab
- $filters['tabs'][] = [
- 'key' => 'assigned_to_me',
- 'label' => __( 'Assigned to me', 'disciple-tools-migration' ),
- 'count' => $total_my,
- 'order' => 20
- ];
- // add assigned to me filters
- $filters['filters'][] = [
- 'ID' => 'my_all',
- 'tab' => 'assigned_to_me',
- 'name' => __( 'All', 'disciple-tools-migration' ),
- 'query' => [
- 'assigned_to' => [ 'me' ],
- 'sort' => 'status'
- ],
- 'count' => $total_my,
- ];
- //add a filter for each status
- foreach ( $fields['status']['default'] as $status_key => $status_value ) {
- if ( isset( $status_counts[$status_key] ) ){
- $filters['filters'][] = [
- 'ID' => 'my_' . $status_key,
- 'tab' => 'assigned_to_me',
- 'name' => $status_value['label'],
- 'query' => [
- 'assigned_to' => [ 'me' ],
- 'status' => [ $status_key ],
- 'sort' => '-post_date'
- ],
- 'count' => $status_counts[$status_key]
- ];
- }
- }
-
- if ( DT_Posts::can_view_all( self::post_type() ) ){
- $records_by_status_counts = self::count_records_by_status();
- $status_counts = [];
- $total_all = 0;
- foreach ( $records_by_status_counts as $count ){
- $total_all += $count['count'];
- dt_increment( $status_counts[$count['status']], $count['count'] );
- }
-
- // add by Status Tab
- $filters['tabs'][] = [
- 'key' => 'by_status',
- 'label' => __( 'All By Status', 'disciple-tools-migration' ),
- 'count' => $total_all,
- 'order' => 30
- ];
- // add assigned to me filters
- $filters['filters'][] = [
- 'ID' => 'all_status',
- 'tab' => 'by_status',
- 'name' => __( 'All', 'disciple-tools-migration' ),
- 'query' => [
- 'sort' => '-post_date'
- ],
- 'count' => $total_all
- ];
-
- foreach ( $fields['status']['default'] as $status_key => $status_value ) {
- if ( isset( $status_counts[$status_key] ) ){
- $filters['filters'][] = [
- 'ID' => 'all_' . $status_key,
- 'tab' => 'by_status',
- 'name' => $status_value['label'],
- 'query' => [
- 'status' => [ $status_key ],
- 'sort' => '-post_date'
- ],
- 'count' => $status_counts[$status_key]
- ];
- }
- }
- }
- }
- return $filters;
- }
-
- // access permission
- public static function dt_filter_access_permissions( $permissions, $post_type ){
- if ( $post_type === self::post_type() ){
- if ( DT_Posts::can_view_all( $post_type ) ){
- $permissions = [];
- }
- }
- return $permissions;
- }
-
- // scripts
- public function scripts(){
- if ( is_singular( $this->post_type ) && get_the_ID() && DT_Posts::can_view( $this->post_type, get_the_ID() ) ){
- $test = '';
- // @todo add enqueue scripts
- }
- }
-}
-
-
diff --git a/site-link/custom-site-to-site-links.php b/site-link/custom-site-to-site-links.php
deleted file mode 100644
index 8cfeec2..0000000
--- a/site-link/custom-site-to-site-links.php
+++ /dev/null
@@ -1,40 +0,0 @@
-type === $args['connection_type'] ) {
- $args['capabilities'][] = 'create_' . $this->type;
- $args['capabilities'][] = 'update_any_' . $this->type;
- // @todo add other capabilities here
- }
- return $args;
- }
-
- public function site_link_type( $type ) {
- $type[$this->type] = __( 'Migrations' );
- return $type;
- }
-}
-Disciple_Tools_Migration_Site_Links::instance();
diff --git a/spinner.svg b/spinner.svg
deleted file mode 100644
index 5c09396..0000000
--- a/spinner.svg
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/tile/custom-tile.php b/tile/custom-tile.php
deleted file mode 100644
index 148f79b..0000000
--- a/tile/custom-tile.php
+++ /dev/null
@@ -1,300 +0,0 @@
- __( 'Migrations', 'disciple-tools-migration' ) ];
- $tiles['a_beautiful_tile'] = [ 'label' => __( 'A Beautiful Tile', 'disciple-tools-migration' ) ];
- }
- return $tiles;
- }
-
- /**
- * @param array $fields
- * @param string $post_type
- * @return array
- */
- public function dt_custom_fields( array $fields, string $post_type = '' ) {
- /**
- * @todo set the post type
- */
- if ( $post_type === 'contacts' || $post_type === 'migrations' ){
- /**
- * @todo Add the fields that you want to include in your tile.
- *
- * Examples for creating the $fields array
- * Contacts
- * @link https://github.com/DiscipleTools/disciple-tools-theme/blob/256c9d8510998e77694a824accb75522c9b6ed06/dt-contacts/base-setup.php#L108
- *
- * Groups
- * @link https://github.com/DiscipleTools/disciple-tools-theme/blob/256c9d8510998e77694a824accb75522c9b6ed06/dt-groups/base-setup.php#L83
- */
-
- /**
- * This is an example of a text field
- */
- $fields['disciple_tools_migration_text'] = [
- 'name' => __( 'Text', 'disciple-tools-migration' ),
- 'description' => _x( 'Text', 'Optional Documentation', 'disciple-tools-migration' ),
- 'type' => 'text',
- 'default' => '',
- 'tile' => 'disciple_tools_migration',
- 'icon' => get_template_directory_uri() . '/dt-assets/images/edit.svg',
- ];
- /**
- * This is an example of a multiselect field
- */
- $fields['disciple_tools_migration_multiselect'] = [
- 'name' => __( 'Multiselect', 'disciple-tools-migration' ),
- 'default' => [
- 'one' => [ 'label' => __( 'One', 'disciple-tools-migration' ) ],
- 'two' => [ 'label' => __( 'Two', 'disciple-tools-migration' ) ],
- 'three' => [ 'label' => __( 'Three', 'disciple-tools-migration' ) ],
- 'four' => [ 'label' => __( 'Four', 'disciple-tools-migration' ) ],
- ],
- 'tile' => 'disciple_tools_migration',
- 'type' => 'multi_select',
- 'hidden' => false,
- 'icon' => get_template_directory_uri() . '/dt-assets/images/edit.svg',
- ];
- /**
- * This is an example of a key select field
- */
- $fields['disciple_tools_migration_keyselect'] = [
- 'name' => 'Key Select',
- 'type' => 'key_select',
- 'tile' => 'disciple_tools_migration',
- 'default' => [
- 'first' => [
- 'label' => _x( 'First', 'Key Select Label', 'disciple-tools-migration' ),
- 'description' => _x( 'First Key Description', 'Training Status field description', 'disciple-tools-migration' ),
- 'color' => '#ff9800'
- ],
- 'second' => [
- 'label' => _x( 'Second', 'Key Select Label', 'disciple-tools-migration' ),
- 'description' => _x( 'Second Key Description', 'Training Status field description', 'disciple-tools-migration' ),
- 'color' => '#4CAF50'
- ],
- 'third' => [
- 'label' => _x( 'Third', 'Key Select Label', 'disciple-tools-migration' ),
- 'description' => _x( 'Third Key Description', 'Training Status field description', 'disciple-tools-migration' ),
- 'color' => '#366184'
- ],
- ],
- 'icon' => get_template_directory_uri() . '/dt-assets/images/edit.svg',
- 'default_color' => '#366184',
- 'select_cannot_be_empty' => true
- ];
-
- //test fields
- $fields['number_test'] = [
- 'name' => __( 'Number field', 'disciple-tools-migration' ),
- 'type' => 'number',
- 'default' => 0,
- 'tile' => 'a_beautiful_tile',
- 'min_option' => '5',
- ];
- $fields['number_test_private'] = [
- 'name' => __( 'Number field private', 'disciple-tools-migration' ),
- 'type' => 'number',
- 'default' => 0,
- 'tile' => 'a_beautiful_tile',
- 'private' => true,
- 'max_option' => '200',
- ];
- $fields['text_test'] = [
- 'name' => __( 'Text', 'disciple-tools-migration' ),
- 'type' => 'text',
- 'default' => 0,
- 'tile' => 'a_beautiful_tile',
- ];
- $fields['text_test_private'] = [
- 'name' => __( 'Text', 'disciple-tools-migration' ),
- 'type' => 'text',
- 'default' => 0,
- 'tile' => 'a_beautiful_tile',
- 'private' => true
- ];
- $fields['contact_communication_channel_test'] = [
- 'name' => __( 'Communication Channel', 'disciple-tools-migration' ),
- 'type' => 'communication_channel',
- 'default' => 0,
- 'tile' => 'a_beautiful_tile',
- ];
-
- $fields['user_select_test'] = [
- 'name' => __( 'User Select', 'disciple-tools-migration' ),
- 'type' => 'user_select',
- 'tile' => 'a_beautiful_tile'
- ];
- $fields['array_test'] = [
- 'name' => __( 'Array', 'disciple-tools-migration' ),
- 'type' => 'array',
- 'tile' => 'a_beautiful_tile'
- ];
- $fields['location_test'] = [
- 'name' => 'location field',
- 'type' => 'location',
- 'tile' => 'a_beautiful_tile'
- ];
- $fields['date_test'] = [
- 'name' => __( ' Date Field', 'disciple-tools-migration' ),
- 'description' => '',
- 'type' => 'date',
- 'default' => '',
- 'tile' => 'a_beautiful_tile'
- ];
- $fields['date_test_private'] = [
- 'name' => __( ' Date Field', 'disciple-tools-migration' ),
- 'description' => '',
- 'type' => 'date',
- 'default' => '',
- 'tile' => 'a_beautiful_tile',
- 'private' => true
- ];
- $fields['boolean_test'] = [
- 'name' => __( 'Boolean', 'disciple-tools-migration' ),
- 'type' => 'boolean',
- 'default' => false,
- ];
- $fields['boolean_test_private'] = [
- 'name' => __( 'Boolean', 'disciple-tools-migration' ),
- 'type' => 'boolean',
- 'default' => false,
- 'private' => true
- ];
- $fields['multi_select_test'] = [
- 'name' => 'Random Options Multiselect',
- 'type' => 'multi_select',
- 'default' => [
- 'one' => [ 'label' => 'option 1' ],
- 'two' => [ 'label' => 'option 2' ],
- 'three' => [ 'label' => 'option 3' ],
- ],
- 'tile' => 'a_beautiful_tile',
- ];
- $fields['multi_select_test_private'] = [
- 'name' => 'Random Private Options',
- 'type' => 'multi_select',
- 'default' => [
- 'one_private' => [ 'label' => 'option 1' ],
- 'two_private' => [ 'label' => 'option 2' ],
- 'three_private' => [ 'label' => 'option 3' ],
- ],
- 'tile' => 'a_beautiful_tile',
- 'private' => true
- ];
- $fields['key_select_test'] = [
- 'name' => 'Random Options Key Select',
- 'type' => 'key_select',
- 'default' => [
- 'one' => [ 'label' => 'option 1' ],
- 'two' => [ 'label' => 'option 2' ],
- 'three' => [ 'label' => 'option 3' ],
- ],
- 'tile' => 'a_beautiful_tile',
- ];
- $fields['key_select_test_private'] = [
- 'name' => 'Random Private Key Select Options',
- 'type' => 'key_select',
- 'default' => [
- 'one_private' => [ 'label' => 'option 1' ],
- 'two_private' => [ 'label' => 'option 2' ],
- 'three_private' => [ 'label' => 'option 3' ],
- ],
- 'tile' => 'a_beautiful_tile',
- 'private' => true
- ];
- $fields['tags_test'] = [
- 'name' => 'Random Tags',
- 'type' => 'tags',
- 'default' => [
- 'one' => [ 'label' => 'option 1' ],
- 'two' => [ 'label' => 'option 2' ],
- 'three' => [ 'label' => 'option 3' ],
- ],
- 'tile' => 'a_beautiful_tile',
- ];
- $fields['tags_test_private'] = [
- 'name' => 'Random Tags Private',
- 'type' => 'tags',
- 'default' => [
- 'one' => [ 'label' => 'option 1' ],
- 'two' => [ 'label' => 'option 2' ],
- 'three' => [ 'label' => 'option 3' ],
- ],
- 'tile' => 'a_beautiful_tile',
- 'private' => true
- ];
-
- $fields['links_test'] = [
- 'name' => 'Links',
- 'type' => 'link',
- 'default' => [
- 'default' => [ 'label' => 'Default', ],
- 'one' => [ 'label' => 'option 1' ],
- 'two' => [ 'label' => 'option 2' ],
- 'three' => [ 'label' => 'option 3' ],
- ],
- 'tile' => 'a_beautiful_tile',
- ];
- }
- return $fields;
- }
-
- public function dt_add_section( $section, $post_type ) {
- /**
- * @todo set the post type and the section key that you created in the dt_details_additional_tiles() function
- */
- if ( ( $post_type === 'contacts' || $post_type === 'migrations' ) && $section === 'disciple_tools_migration' ){
- /**
- * These are two sets of key data:
- * $this_post is the details for this specific post
- * $post_type_fields is the list of the default fields for the post type
- *
- * You can pull any query data into this section and display it.
- */
- $this_post = DT_Posts::get_post( $post_type, get_the_ID() );
- $post_type_fields = DT_Posts::get_post_field_settings( $post_type );
- ?>
-
-
-
-
-
-
You can do a number of customizations here.
- See post types and field keys and values:
click here
-
-
-
-
-
-
-
-
- build_default_workflows_contacts( $workflows );
- break;
- case 'groups':
- $this->build_default_workflows_groups( $workflows );
- break;
- case 'migrations':
- $this->build_default_workflows_migration( $workflows );
- break;
- }
-
- return $workflows;
- }
-
- private function build_default_workflows_contacts( &$workflows ) {
- }
-
- private function build_default_workflows_groups( &$workflows ) {
- }
-
- private function build_default_workflows_migration( &$workflows ) {
- $dt_fields = DT_Posts::get_post_field_settings( 'migrations' );
-
- $workflows[] = (object) [
- 'id' => 'migration_00001',
- 'name' => 'Migration Template Add Text On Creation',
- 'enabled' => false, // Can be enabled via admin view
- 'trigger' => Disciple_Tools_Workflows_Defaults::$trigger_created['id'],
- 'conditions' => [
- Disciple_Tools_Workflows_Defaults::new_condition( Disciple_Tools_Workflows_Defaults::$condition_is_set,
- [
- 'id' => 'name',
- 'label' => $dt_fields['name']['name']
- ], [
- 'id' => '',
- 'label' => ''
- ]
- )
- ],
- 'actions' => [
- Disciple_Tools_Workflows_Defaults::new_action( Disciple_Tools_Workflows_Defaults::$action_update,
- [
- 'id' => 'disciple_tools_migration_text',
- 'label' => $dt_fields['disciple_tools_migration_text']['name']
- ], [
- 'id' => 'Auto Filled By Workflow Engine',
- 'label' => 'Auto Filled By Workflow Engine'
- ]
- )
- ]
- ];
- }
-}
-
-Disciple_Tools_Migration_Workflows::instance();
From 9c546c998d69b608e76faeb3d0bf3a30f4d143c4 Mon Sep 17 00:00:00 2001
From: kodinkat
Date: Tue, 31 Mar 2026 14:37:34 +0100
Subject: [PATCH 2/5] Refactor migration export and import classes for improved
data handling
- Simplified data sanitization in the export download class by introducing a new method for processing POST arrays.
- Enhanced SQL query preparation in the export file class for better security and performance.
- Improved data display in the import and export tabs by ensuring proper HTML escaping for counts and labels.
- Added comments for clarity and disabled PHPCS warnings related to nonce verification in AJAX handlers.
---
admin/class-dt-migration-export-download.php | 42 +++++++++++++------
admin/class-dt-migration-import-ajax.php | 5 +++
admin/class-dt-migration-tab-export.php | 6 +--
admin/class-dt-migration-tab-import.php | 14 +++----
includes/class-dt-migration-export-file.php | 19 ++++++---
includes/class-dt-migration-import-engine.php | 13 ++++--
6 files changed, 68 insertions(+), 31 deletions(-)
diff --git a/admin/class-dt-migration-export-download.php b/admin/class-dt-migration-export-download.php
index 89d2a21..66da5d0 100644
--- a/admin/class-dt-migration-export-download.php
+++ b/admin/class-dt-migration-export-download.php
@@ -39,18 +39,10 @@ public function handle_download() : void {
}
$record_options = [];
- $export_by = isset( $_POST['dt_migration_export_by'] ) && is_array( $_POST['dt_migration_export_by'] )
- ? wp_unslash( $_POST['dt_migration_export_by'] )
- : [];
- $limits = isset( $_POST['dt_migration_export_limit'] ) && is_array( $_POST['dt_migration_export_limit'] )
- ? wp_unslash( $_POST['dt_migration_export_limit'] )
- : [];
- $min_ids = isset( $_POST['dt_migration_export_min_id'] ) && is_array( $_POST['dt_migration_export_min_id'] )
- ? wp_unslash( $_POST['dt_migration_export_min_id'] )
- : [];
- $max_ids = isset( $_POST['dt_migration_export_max_id'] ) && is_array( $_POST['dt_migration_export_max_id'] )
- ? wp_unslash( $_POST['dt_migration_export_max_id'] )
- : [];
+ $export_by = $this->sanitize_post_type_assoc_array( 'dt_migration_export_by', 'sanitize_key' );
+ $limits = $this->sanitize_post_type_assoc_array( 'dt_migration_export_limit', 'absint' );
+ $min_ids = $this->sanitize_post_type_assoc_array( 'dt_migration_export_min_id', 'absint' );
+ $max_ids = $this->sanitize_post_type_assoc_array( 'dt_migration_export_max_id', 'absint' );
$allowed_records = $settings['allowed_items']['records'] ?? [];
foreach ( $allowed_records as $post_type => $enabled ) {
@@ -95,4 +87,30 @@ public function handle_download() : void {
echo wp_json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
exit;
}
+
+ /**
+ * Reads a POST array keyed by post type with sanitized values.
+ *
+ * @param string $post_key Key in $_POST.
+ * @param string $value_sanitizer 'sanitize_key' or 'absint'.
+ * @return array
+ */
+ private function sanitize_post_type_assoc_array( string $post_key, string $value_sanitizer ) : array {
+ // Nonce verified in handle_download(); values sanitized per key below.
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ $source = ( isset( $_POST[ $post_key ] ) && is_array( $_POST[ $post_key ] ) ) ? wp_unslash( $_POST[ $post_key ] ) : [];
+ $out = [];
+ foreach ( $source as $raw_key => $raw_val ) {
+ $key = sanitize_key( (string) $raw_key );
+ if ( $key === '' ) {
+ continue;
+ }
+ if ( 'absint' === $value_sanitizer ) {
+ $out[ $key ] = absint( $raw_val );
+ } else {
+ $out[ $key ] = sanitize_key( (string) $raw_val );
+ }
+ }
+ return $out;
+ }
}
diff --git a/admin/class-dt-migration-import-ajax.php b/admin/class-dt-migration-import-ajax.php
index 147137e..ea54129 100644
--- a/admin/class-dt-migration-import-ajax.php
+++ b/admin/class-dt-migration-import-ajax.php
@@ -257,6 +257,9 @@ public function handle_import_batch() : void {
* @param array $settings Migration settings.
*/
private function handle_file_mode_batch( string $step, array $settings ) : void {
+ // Nonce verified in handle_import_batch() via check_ajax_referer( 'dt_migration_import', 'nonce' ).
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
+
$transient_key = 'dt_migration_file_payload_' . get_current_user_id();
$payload = get_transient( $transient_key );
@@ -345,6 +348,8 @@ private function handle_file_mode_batch( string $step, array $settings ) : void
] );
}
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
+
wp_send_json_error( [ 'message' => __( 'Invalid step.', 'disciple-tools-migration' ) ] );
}
}
diff --git a/admin/class-dt-migration-tab-export.php b/admin/class-dt-migration-tab-export.php
index b21a7d3..94a2f98 100644
--- a/admin/class-dt-migration-tab-export.php
+++ b/admin/class-dt-migration-tab-export.php
@@ -125,7 +125,7 @@ public function main_column( array $settings ) {
-
+
@@ -200,7 +200,7 @@ public function main_column( array $settings ) {
-
+
-
+
diff --git a/admin/class-dt-migration-tab-import.php b/admin/class-dt-migration-tab-import.php
index 786486a..8213f56 100644
--- a/admin/class-dt-migration-tab-import.php
+++ b/admin/class-dt-migration-tab-import.php
@@ -232,15 +232,15 @@ public function main_column( array $settings ) {
connection_result['allowed_items']['records'] ?? [];
- $recordLabels = [];
+ $records = $this->connection_result['allowed_items']['records'] ?? [];
+ $record_labels = [];
if ( ! empty( $records['contacts'] ) ) {
- $recordLabels[] = esc_html__( 'Contacts', 'disciple-tools-migration' );
+ $record_labels[] = esc_html__( 'Contacts', 'disciple-tools-migration' );
}
if ( ! empty( $records['groups'] ) ) {
- $recordLabels[] = esc_html__( 'Groups', 'disciple-tools-migration' );
+ $record_labels[] = esc_html__( 'Groups', 'disciple-tools-migration' );
}
- echo esc_html( implode( ', ', $recordLabels ) );
+ echo esc_html( implode( ', ', $record_labels ) );
?>
@@ -348,7 +348,7 @@ class="dt-migration-record-checkbox"
-
+
-
+
diff --git a/includes/class-dt-migration-export-file.php b/includes/class-dt-migration-export-file.php
index c3e568d..603b52f 100644
--- a/includes/class-dt-migration-export-file.php
+++ b/includes/class-dt-migration-export-file.php
@@ -104,16 +104,23 @@ public static function fetch_records( string $post_type, int $limit = 0, int $mi
*/
private static function get_record_ids( string $post_type, int $limit, int $min_id, int $max_id ) : array {
global $wpdb;
- $pt = $wpdb->prepare( '%s', $post_type );
- $where = $wpdb->prepare( "post_type = %s AND post_status != 'trash'", $post_type );
+ $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash'";
+ $args = [ $post_type ];
if ( $min_id > 0 ) {
- $where .= $wpdb->prepare( ' AND ID >= %d', $min_id );
+ $sql .= ' AND ID >= %d';
+ $args[] = $min_id;
}
if ( $max_id > 0 ) {
- $where .= $wpdb->prepare( ' AND ID <= %d', $max_id );
+ $sql .= ' AND ID <= %d';
+ $args[] = $max_id;
}
- $limit_sql = $limit > 0 ? $wpdb->prepare( ' LIMIT %d', $limit ) : '';
- $ids = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE $where ORDER BY ID ASC $limit_sql" );
+ $sql .= ' ORDER BY ID ASC';
+ if ( $limit > 0 ) {
+ $sql .= ' LIMIT %d';
+ $args[] = $limit;
+ }
+ $prepared = call_user_func_array( [ $wpdb, 'prepare' ], array_merge( [ $sql ], $args ) );
+ $ids = $wpdb->get_col( $prepared );
return array_map( 'intval', (array) $ids );
}
diff --git a/includes/class-dt-migration-import-engine.php b/includes/class-dt-migration-import-engine.php
index 47fc3eb..e9045eb 100644
--- a/includes/class-dt-migration-import-engine.php
+++ b/includes/class-dt-migration-import-engine.php
@@ -742,9 +742,16 @@ private static function sort_records_by_connection_deps( string $post_type, arra
$in_degree[ $id ] = count( array_unique( $deps ) );
}
- $ready = array_keys( array_filter( $in_degree, function ( $d ) { return $d === 0; } ) );
- $out = [];
- $done = [];
+ $ready = array_keys(
+ array_filter(
+ $in_degree,
+ function ( $d ) {
+ return $d === 0;
+ }
+ )
+ );
+ $out = [];
+ $done = [];
while ( ! empty( $ready ) ) {
$nid = array_shift( $ready );
if ( isset( $done[ $nid ] ) ) {
From 480d5a35522793fd2d57d56d4d10b8eb61316080 Mon Sep 17 00:00:00 2001
From: kodinkat
Date: Tue, 31 Mar 2026 14:42:13 +0100
Subject: [PATCH 3/5] Improve code quality in migration export classes
- Updated PHPCS ignore comments in the export download class to include nonce verification warnings for better security practices.
- Simplified SQL query preparation in the export file class for enhanced readability and maintainability.
---
admin/class-dt-migration-export-download.php | 2 +-
includes/class-dt-migration-export-file.php | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/admin/class-dt-migration-export-download.php b/admin/class-dt-migration-export-download.php
index 66da5d0..d2ad388 100644
--- a/admin/class-dt-migration-export-download.php
+++ b/admin/class-dt-migration-export-download.php
@@ -97,7 +97,7 @@ public function handle_download() : void {
*/
private function sanitize_post_type_assoc_array( string $post_key, string $value_sanitizer ) : array {
// Nonce verified in handle_download(); values sanitized per key below.
- // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
$source = ( isset( $_POST[ $post_key ] ) && is_array( $_POST[ $post_key ] ) ) ? wp_unslash( $_POST[ $post_key ] ) : [];
$out = [];
foreach ( $source as $raw_key => $raw_val ) {
diff --git a/includes/class-dt-migration-export-file.php b/includes/class-dt-migration-export-file.php
index 603b52f..959ad51 100644
--- a/includes/class-dt-migration-export-file.php
+++ b/includes/class-dt-migration-export-file.php
@@ -119,8 +119,7 @@ private static function get_record_ids( string $post_type, int $limit, int $min_
$sql .= ' LIMIT %d';
$args[] = $limit;
}
- $prepared = call_user_func_array( [ $wpdb, 'prepare' ], array_merge( [ $sql ], $args ) );
- $ids = $wpdb->get_col( $prepared );
+ $ids = $wpdb->get_col( call_user_func_array( [ $wpdb, 'prepare' ], array_merge( [ $sql ], $args ) ) );
return array_map( 'intval', (array) $ids );
}
From 53fedfe3195995be3751dcaabd2e0f87592db85a Mon Sep 17 00:00:00 2001
From: kodinkat
Date: Tue, 31 Mar 2026 14:45:41 +0100
Subject: [PATCH 4/5] Refactor SQL query handling in migration export file
class
- Enhanced the get_record_ids method to improve SQL query preparation by consolidating conditions for fetching post IDs based on post type, status, and ID range.
- Improved readability and maintainability of the code by reducing redundancy in SQL query logic.
---
includes/class-dt-migration-export-file.php | 85 +++++++++++++++++----
1 file changed, 71 insertions(+), 14 deletions(-)
diff --git a/includes/class-dt-migration-export-file.php b/includes/class-dt-migration-export-file.php
index 959ad51..3fc630e 100644
--- a/includes/class-dt-migration-export-file.php
+++ b/includes/class-dt-migration-export-file.php
@@ -104,22 +104,79 @@ public static function fetch_records( string $post_type, int $limit = 0, int $mi
*/
private static function get_record_ids( string $post_type, int $limit, int $min_id, int $max_id ) : array {
global $wpdb;
- $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash'";
- $args = [ $post_type ];
- if ( $min_id > 0 ) {
- $sql .= ' AND ID >= %d';
- $args[] = $min_id;
- }
- if ( $max_id > 0 ) {
- $sql .= ' AND ID <= %d';
- $args[] = $max_id;
- }
- $sql .= ' ORDER BY ID ASC';
+
if ( $limit > 0 ) {
- $sql .= ' LIMIT %d';
- $args[] = $limit;
+ if ( $min_id > 0 && $max_id > 0 ) {
+ $ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash' AND ID >= %d AND ID <= %d ORDER BY ID ASC LIMIT %d",
+ $post_type,
+ $min_id,
+ $max_id,
+ $limit
+ )
+ );
+ } elseif ( $min_id > 0 ) {
+ $ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash' AND ID >= %d ORDER BY ID ASC LIMIT %d",
+ $post_type,
+ $min_id,
+ $limit
+ )
+ );
+ } elseif ( $max_id > 0 ) {
+ $ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash' AND ID <= %d ORDER BY ID ASC LIMIT %d",
+ $post_type,
+ $max_id,
+ $limit
+ )
+ );
+ } else {
+ $ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash' ORDER BY ID ASC LIMIT %d",
+ $post_type,
+ $limit
+ )
+ );
+ }
+ } elseif ( $min_id > 0 && $max_id > 0 ) {
+ $ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash' AND ID >= %d AND ID <= %d ORDER BY ID ASC",
+ $post_type,
+ $min_id,
+ $max_id
+ )
+ );
+ } elseif ( $min_id > 0 ) {
+ $ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash' AND ID >= %d ORDER BY ID ASC",
+ $post_type,
+ $min_id
+ )
+ );
+ } elseif ( $max_id > 0 ) {
+ $ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash' AND ID <= %d ORDER BY ID ASC",
+ $post_type,
+ $max_id
+ )
+ );
+ } else {
+ $ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status != 'trash' ORDER BY ID ASC",
+ $post_type
+ )
+ );
}
- $ids = $wpdb->get_col( call_user_func_array( [ $wpdb, 'prepare' ], array_merge( [ $sql ], $args ) ) );
+
return array_map( 'intval', (array) $ids );
}
From 1df473249b037896953c5dccb1b90921c475a907 Mon Sep 17 00:00:00 2001
From: kodinkat
Date: Wed, 8 Apr 2026 13:46:40 +0100
Subject: [PATCH 5/5] Add preflight checks for migration imports
- Introduced a new preflight check feature to validate import selections before proceeding, enhancing user experience by providing advisory warnings.
- Added AJAX handling for preflight requests, allowing users to run checks for potential issues with selected settings and records.
- Updated the import UI to include buttons for running preflight checks and displaying results in a modal.
- Enhanced JavaScript functionality to manage preflight request flow and display relevant information and warnings to users.
- Created a new class for handling preflight analysis, ensuring non-destructive checks are performed on the import data.
---
admin/class-dt-migration-import-ajax.php | 214 ++++++++++++++-
admin/class-dt-migration-tab-import.php | 48 +++-
admin/js/import.js | 151 +++++++++--
disciple-tools-migration.php | 1 +
includes/class-dt-migration-preflight.php | 317 ++++++++++++++++++++++
5 files changed, 710 insertions(+), 21 deletions(-)
create mode 100644 includes/class-dt-migration-preflight.php
diff --git a/admin/class-dt-migration-import-ajax.php b/admin/class-dt-migration-import-ajax.php
index ea54129..302d3f4 100644
--- a/admin/class-dt-migration-import-ajax.php
+++ b/admin/class-dt-migration-import-ajax.php
@@ -15,6 +15,7 @@ class Disciple_Tools_Migration_Import_Ajax {
*/
public function __construct() {
add_action( 'wp_ajax_dt_migration_import_batch', [ $this, 'handle_import_batch' ] );
+ add_action( 'wp_ajax_dt_migration_preflight', [ $this, 'handle_preflight' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
@@ -36,7 +37,7 @@ public function enqueue_scripts( string $hook ) : void {
'dt-migration-import',
$plugin_url . 'admin/js/import.js',
[ 'jquery' ],
- '0.3.2',
+ '0.3.5',
true
);
wp_localize_script(
@@ -53,6 +54,14 @@ public function enqueue_scripts( string $hook ) : void {
'continueImport' => __( 'Continue import', 'disciple-tools-migration' ),
'confirmImport' => __( 'Confirm Import', 'disciple-tools-migration' ),
'importCompleteWithLog' => __( 'Import complete. Review logged issues below.', 'disciple-tools-migration' ),
+ 'preflightTitle' => __( 'Preflight results', 'disciple-tools-migration' ),
+ 'preflightIntro' => __( 'These checks are advisory. You can proceed; the import may still log per-record issues.', 'disciple-tools-migration' ),
+ 'preflightNoIssues' => __( 'No preflight warnings for the current selection and sample data.', 'disciple-tools-migration' ),
+ 'preflightProceed' => __( 'Proceed with import', 'disciple-tools-migration' ),
+ 'preflightClose' => __( 'Close', 'disciple-tools-migration' ),
+ 'preflightRunning' => __( 'Running preflight…', 'disciple-tools-migration' ),
+ 'preflightFailed' => __( 'Preflight request failed.', 'disciple-tools-migration' ),
+ 'runPreflight' => __( 'Run preflight', 'disciple-tools-migration' ),
],
]
);
@@ -69,6 +78,13 @@ private function get_modal_css() : string {
.dt-migration-modal { position: fixed; inset: 0; z-index: 100000; display: flex; align-items: center; justify-content: center; }
.dt-migration-modal-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
.dt-migration-modal-content { position: relative; background: #fff; padding: 24px; max-width: 500px; width: 90%; box-shadow: 0 4px 20px rgba(0,0,0,0.2); border-radius: 4px; }
+ .dt-migration-modal-content--wide { max-width: 640px; }
+ .dt-migration-preflight-field-label { margin: 12px 0 6px; color: #1d2327; font-size: 13px; }
+ .dt-migration-preflight-field-label:first-of-type { margin-top: 0; }
+ .dt-migration-preflight-textarea { display: block; width: 100%; max-width: 100%; box-sizing: border-box; margin: 0 0 4px; padding: 8px 10px; font-size: 13px; line-height: 1.5; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; color: #1d2327; background: #fff; border: 1px solid #8c8f94; border-radius: 4px; resize: vertical; }
+ .dt-migration-preflight-textarea--notes { min-height: 72px; height: 100px; max-height: 200px; overflow-y: auto; white-space: pre-wrap; word-break: break-word; }
+ .dt-migration-preflight-textarea--warnings { min-height: 160px; height: 260px; max-height: 420px; overflow: auto; white-space: pre; overflow-wrap: normal; word-break: normal; }
+ .dt-migration-preflight-status { font-style: italic; color: #50575e; }
.dt-migration-modal-body { margin-top: 16px; }
.dt-migration-modal-warning { color: #b32d2e; font-weight: 600; }
.dt-migration-modal-summary { margin: 12px 0; padding: 12px; background: #f0f0f1; border-radius: 4px; font-size: 13px; }
@@ -352,4 +368,200 @@ private function handle_file_mode_batch( string $step, array $settings ) : void
wp_send_json_error( [ 'message' => __( 'Invalid step.', 'disciple-tools-migration' ) ] );
}
+
+ /**
+ * AJAX: non-destructive preflight warnings for the current import selection.
+ */
+ public function handle_preflight() : void {
+ check_ajax_referer( 'dt_migration_import', 'nonce' );
+
+ if ( ! current_user_can( 'manage_dt' ) ) {
+ wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'disciple-tools-migration' ) ] );
+ }
+
+ $settings = Disciple_Tools_Migration_Menu::get_settings();
+ if ( empty( $settings['enabled'] ) ) {
+ wp_send_json_error( [ 'message' => __( 'Migration is not enabled.', 'disciple-tools-migration' ) ] );
+ }
+
+ $channel = isset( $_POST['import_channel'] ) ? sanitize_key( wp_unslash( $_POST['import_channel'] ) ) : '';
+ if ( $channel !== 'file' && $channel !== 'api' ) {
+ $channel = 'api';
+ }
+
+ $selected_settings = isset( $_POST['settings_selected'] ) && is_array( $_POST['settings_selected'] )
+ ? array_map( 'sanitize_key', wp_unslash( $_POST['settings_selected'] ) )
+ : [];
+ $settings_map = array_fill_keys( $selected_settings, true );
+ $records_selected_in = isset( $_POST['records_selected'] ) && is_array( $_POST['records_selected'] )
+ ? array_map( 'sanitize_key', wp_unslash( $_POST['records_selected'] ) )
+ : [];
+
+ if ( empty( $selected_settings ) && empty( $records_selected_in ) ) {
+ wp_send_json_error( [ 'message' => __( 'Select at least one setting or record type for preflight.', 'disciple-tools-migration' ) ] );
+ }
+
+ if ( $channel === 'file' ) {
+ $result = $this->preflight_file_payload( $settings_map, $records_selected_in );
+ } else {
+ $result = $this->preflight_api_payload( $settings, $settings_map, $records_selected_in );
+ }
+
+ if ( isset( $result['error'] ) ) {
+ wp_send_json_error( [ 'message' => $result['error'] ] );
+ }
+
+ wp_send_json_success( $result );
+ }
+
+ /**
+ * Preflight using uploaded JSON transient.
+ *
+ * @param array $settings_map Selected settings.
+ * @param string[] $records_selected_in Post types.
+ * @return array|array{ error: string }
+ */
+ private function preflight_file_payload( array $settings_map, array $records_selected_in ) : array {
+ $transient_key = 'dt_migration_file_payload_' . get_current_user_id();
+ $payload = get_transient( $transient_key );
+
+ $export_block = ( is_array( $payload ) && isset( $payload['export'] ) && is_array( $payload['export'] ) ) ? $payload['export'] : [];
+ $has_dt = ! empty( $export_block['dt_settings'] );
+ $has_users = array_key_exists( 'system_users', $export_block ) && is_array( $export_block['system_users'] );
+ if ( ! is_array( $payload ) || ( ! $has_dt && ! $has_users ) ) {
+ return [ 'error' => __( 'No migration file loaded or payload expired. Please upload the file again.', 'disciple-tools-migration' ) ];
+ }
+
+ $records_all = isset( $payload['records'] ) && is_array( $payload['records'] ) ? $payload['records'] : [];
+ $records = [];
+ foreach ( $records_selected_in as $pt ) {
+ if ( isset( $records_all[ $pt ] ) && is_array( $records_all[ $pt ] ) ) {
+ $records[ $pt ] = $records_all[ $pt ];
+ }
+ }
+
+ $analysis = Disciple_Tools_Migration_Preflight::analyze(
+ [
+ 'export' => $export_block,
+ 'records' => $records,
+ 'records_sampled' => false,
+ 'settings_selected' => $settings_map,
+ 'records_selected' => $records_selected_in,
+ ]
+ );
+
+ return [
+ 'warnings' => $analysis['warnings'],
+ 'info' => $analysis['info'],
+ ];
+ }
+
+ /**
+ * Preflight using Server A export + sampled record batches.
+ *
+ * @param array $settings Plugin settings.
+ * @param array $settings_map Selected settings.
+ * @param string[] $records_selected_in Post types.
+ * @return array|array{ error: string }
+ */
+ private function preflight_api_payload( array $settings, array $settings_map, array $records_selected_in ) : array {
+ $remote_url = $settings['api']['remote_base_url'] ?? '';
+ $jwt = $settings['api']['jwt_token'] ?? '';
+ $token_at = (int) ( $settings['api']['jwt_token_set_at'] ?? 0 );
+
+ if ( empty( $remote_url ) || empty( $jwt ) ) {
+ return [ 'error' => __( 'Not connected to Server A. Run Test Connection first.', 'disciple-tools-migration' ) ];
+ }
+
+ if ( $token_at < ( time() - HOUR_IN_SECONDS ) ) {
+ return [ 'error' => __( 'JWT token expired. Please re-run Test Connection.', 'disciple-tools-migration' ) ];
+ }
+
+ $base = rtrim( $remote_url, '/' );
+
+ $export_res = wp_remote_post(
+ $base . '/wp-json/dt-migration/v1/export',
+ [
+ 'timeout' => 60,
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $jwt,
+ 'Content-Type' => 'application/json',
+ ],
+ 'body' => wp_json_encode( [ 'settings_only' => true ] ),
+ ]
+ );
+
+ if ( is_wp_error( $export_res ) ) {
+ return [ 'error' => $export_res->get_error_message() ];
+ }
+
+ $code = wp_remote_retrieve_response_code( $export_res );
+ $body = json_decode( (string) wp_remote_retrieve_body( $export_res ), true );
+ if ( $code < 200 || $code >= 300 || ! is_array( $body ) ) {
+ return [ 'error' => __( 'Failed to fetch export from Server A.', 'disciple-tools-migration' ) ];
+ }
+
+ $export_block = isset( $body['export'] ) && is_array( $body['export'] ) ? $body['export'] : [];
+
+ $records = [];
+ $records_sample = false;
+ $fetch_notes = [];
+ foreach ( $records_selected_in as $pt ) {
+ $records_res = wp_remote_get(
+ add_query_arg(
+ [ 'offset' => 0, 'limit' => 100 ],
+ $base . '/wp-json/dt-migration/v1/records/' . rawurlencode( $pt )
+ ),
+ [
+ 'timeout' => 60,
+ 'headers' => [ 'Authorization' => 'Bearer ' . $jwt ],
+ ]
+ );
+ if ( is_wp_error( $records_res ) ) {
+ $fetch_notes[] = sprintf(
+ /* translators: %s: post type slug */
+ __( 'Could not fetch sample records for "%s" from Server A.', 'disciple-tools-migration' ),
+ $pt
+ );
+ continue;
+ }
+ $rc = wp_remote_retrieve_response_code( $records_res );
+ $rb = json_decode( (string) wp_remote_retrieve_body( $records_res ), true );
+ if ( $rc < 200 || $rc >= 300 || ! is_array( $rb ) ) {
+ $fetch_notes[] = sprintf(
+ /* translators: %s: post type slug */
+ __( 'Server A returned an error when fetching sample records for "%s".', 'disciple-tools-migration' ),
+ $pt
+ );
+ continue;
+ }
+ $rec = isset( $rb['records'] ) && is_array( $rb['records'] ) ? $rb['records'] : [];
+ if ( ! empty( $rec ) ) {
+ $records[ $pt ] = $rec;
+ }
+ if ( ! empty( $rb['has_more'] ) ) {
+ $records_sample = true;
+ }
+ }
+
+ $analysis = Disciple_Tools_Migration_Preflight::analyze(
+ [
+ 'export' => $export_block,
+ 'records' => $records,
+ 'records_sampled' => $records_sample,
+ 'settings_selected' => $settings_map,
+ 'records_selected' => $records_selected_in,
+ ]
+ );
+
+ $info_out = $analysis['info'];
+ if ( ! empty( $fetch_notes ) ) {
+ $info_out = array_merge( $info_out, $fetch_notes );
+ }
+
+ return [
+ 'warnings' => $analysis['warnings'],
+ 'info' => $info_out,
+ ];
+ }
}
diff --git a/admin/class-dt-migration-tab-import.php b/admin/class-dt-migration-tab-import.php
index 8213f56..392cb52 100644
--- a/admin/class-dt-migration-tab-import.php
+++ b/admin/class-dt-migration-tab-import.php
@@ -104,7 +104,7 @@ public function main_column( array $settings ) {
-
+
@@ -357,7 +357,10 @@ class="dt-migration-record-checkbox"
-
+
+
+
+
@@ -476,7 +479,10 @@ class="dt-migration-record-checkbox"
-
+
+
+
+
@@ -537,6 +543,42 @@ private function render_import_modal_and_progress() : void {
return;
}
?>
+
diff --git a/admin/js/import.js b/admin/js/import.js
index bec51db..9a9994a 100644
--- a/admin/js/import.js
+++ b/admin/js/import.js
@@ -14,6 +14,8 @@
let $confirmGate, $modalWarning;
let $progressBar, $progressText, $stepList, $currentPhase, $cancelImport, $importSpinner;
let $errorDetails, $errorScroll;
+ let $pfModal, $pfInfoWrap, $pfInfoText, $pfWarningsWrap, $pfWarningsText, $pfStatus, $pfProceed, $pfClose, $pfOverlay;
+ let pendingPreflightSection = null;
let cancelled = false;
let phases = [];
@@ -35,6 +37,19 @@
return div.innerHTML;
}
+ /**
+ * One logical row per array item. Preserves leading spaces (e.g. indented field lines).
+ * Replaces embedded newlines only so each server line stays a single textarea row.
+ */
+ function preflightLinesToTextareaValue( lines ) {
+ if ( ! Array.isArray( lines ) || ! lines.length ) {
+ return '';
+ }
+ return lines.map( function( line ) {
+ return String( line == null ? '' : line ).replace( /\r?\n/g, ' ' );
+ } ).join( '\n' );
+ }
+
function getSelectedSettings( $scope ) {
const root = $scope && $scope.length ? $scope : $( document );
const out = [];
@@ -55,6 +70,94 @@
return out;
}
+ function beginImportFlow( $section ) {
+ const fromBtn = $section.find( '.dt-migration-start-import' ).first().data( 'importChannel' );
+ activeImportChannel = fromBtn === 'file' ? 'file' : 'api';
+
+ const settings = getSelectedSettings( $section );
+ const records = getSelectedRecords( $section );
+ if ( ! settings.length && ! Object.keys( records ).length ) {
+ window.alert( 'Please select at least one setting type or record type to import.' );
+ return;
+ }
+ phases = buildPhases( $section );
+ if ( ! phases.length ) {
+ return;
+ }
+ totalSteps = phases.length;
+ currentPhaseIndex = 0;
+ completedSteps = 0;
+ sentRecordsImportInit = false;
+ startNextPhase();
+ }
+
+ function runPreflightRequest( $section ) {
+ const fromBtn = $section.find( '.dt-migration-run-preflight' ).first().data( 'importChannel' );
+ const channel = fromBtn === 'file' ? 'file' : 'api';
+ const settings = getSelectedSettings( $section );
+ const records = getSelectedRecords( $section );
+ const recordPts = Object.keys( records );
+
+ if ( ! settings.length && ! recordPts.length ) {
+ window.alert( 'Please select at least one setting type or record type.' );
+ return;
+ }
+
+ if ( ! $pfModal.length ) {
+ window.alert( t( 'preflightFailed', 'Preflight is not available on this screen.' ) );
+ return;
+ }
+
+ $pfStatus.text( t( 'preflightRunning', 'Running preflight…' ) ).prop( 'hidden', false );
+ $pfInfoText.val( '' );
+ $pfWarningsText.val( '' );
+ $pfInfoWrap.prop( 'hidden', true );
+ $pfWarningsWrap.prop( 'hidden', true );
+ $pfModal.show();
+
+ const payload = {
+ action: 'dt_migration_preflight',
+ nonce: dtMigrationImport.nonce,
+ import_channel: channel,
+ settings_selected: settings,
+ records_selected: recordPts
+ };
+
+ $.post( dtMigrationImport.ajaxUrl, payload ).done( function( r ) {
+ $pfStatus.prop( 'hidden', true );
+ if ( ! r.success || ! r.data ) {
+ const msg = r.data && r.data.message ? r.data.message : t( 'preflightFailed', 'Preflight request failed.' );
+ window.alert( msg );
+ $pfModal.hide();
+ return;
+ }
+ const data = r.data;
+ const warnings = data.warnings || [];
+ const info = data.info || [];
+
+ if ( info.length ) {
+ $pfInfoText.val( preflightLinesToTextareaValue( info ) );
+ $pfInfoWrap.prop( 'hidden', false );
+ } else {
+ $pfInfoText.val( '' );
+ $pfInfoWrap.prop( 'hidden', true );
+ }
+
+ if ( warnings.length ) {
+ $pfWarningsText.val( preflightLinesToTextareaValue( warnings ) );
+ } else {
+ $pfWarningsText.val( t( 'preflightNoIssues', 'No preflight warnings for the current selection and sample data.' ) );
+ }
+ $pfWarningsWrap.prop( 'hidden', false );
+
+ pendingPreflightSection = $section;
+ } ).fail( function() {
+ $pfStatus.prop( 'hidden', true );
+ window.alert( t( 'preflightFailed', 'Preflight request failed.' ) );
+ $pfModal.hide();
+ } );
+ }
+
function buildPhases( $section ) {
const settings = getSelectedSettings( $section );
const records = getSelectedRecords( $section );
@@ -366,6 +469,15 @@
$importSpinner = $( '.dt-migration-import-spinner' );
$errorDetails = $( '#dt-migration-error-details' );
$errorScroll = $( '.dt-migration-error-scroll' );
+ $pfModal = $( '#dt-migration-preflight-modal' );
+ $pfInfoWrap = $( '.dt-migration-preflight-info-wrap' );
+ $pfInfoText = $( '#dt-migration-preflight-info-text' );
+ $pfWarningsWrap = $( '.dt-migration-preflight-warnings-wrap' );
+ $pfWarningsText = $( '#dt-migration-preflight-warnings-text' );
+ $pfStatus = $( '.dt-migration-preflight-status' );
+ $pfProceed = $( '.dt-migration-preflight-proceed' );
+ $pfClose = $( '.dt-migration-preflight-close' );
+ $pfOverlay = $pfModal.find( '.dt-migration-preflight-overlay' );
if ( ! $modal.length || ! $( '.dt-migration-start-import' ).length ) {
return;
@@ -400,26 +512,31 @@
$( this ).prop( 'disabled', true ).text( 'Cancelling...' );
} );
- $( '.dt-migration-start-import' ).on( 'click', function() {
+ $( '.dt-migration-run-preflight' ).on( 'click', function() {
const $section = $( this ).closest( '.dt-migration-import-section' );
- const fromBtn = $( this ).data( 'importChannel' );
- activeImportChannel = fromBtn === 'file' ? 'file' : 'api';
+ runPreflightRequest( $section );
+ } );
- const settings = getSelectedSettings( $section );
- const records = getSelectedRecords( $section );
- if ( ! settings.length && ! Object.keys( records ).length ) {
- alert( 'Please select at least one setting type or record type to import.' );
- return;
- }
- phases = buildPhases( $section );
- if ( ! phases.length ) {
- return;
+ $pfClose.on( 'click', function() {
+ pendingPreflightSection = null;
+ $pfModal.hide();
+ } );
+ $pfOverlay.on( 'click', function() {
+ pendingPreflightSection = null;
+ $pfModal.hide();
+ } );
+ $pfProceed.on( 'click', function() {
+ const $section = pendingPreflightSection;
+ pendingPreflightSection = null;
+ $pfModal.hide();
+ if ( $section && $section.length ) {
+ beginImportFlow( $section );
}
- totalSteps = phases.length;
- currentPhaseIndex = 0;
- completedSteps = 0;
- sentRecordsImportInit = false;
- startNextPhase();
+ } );
+
+ $( '.dt-migration-start-import' ).on( 'click', function() {
+ const $section = $( this ).closest( '.dt-migration-import-section' );
+ beginImportFlow( $section );
} );
$( document ).on( 'change', '.dt-migration-select-all-settings', function() {
diff --git a/disciple-tools-migration.php b/disciple-tools-migration.php
index ef8965a..3ff3bfa 100755
--- a/disciple-tools-migration.php
+++ b/disciple-tools-migration.php
@@ -103,6 +103,7 @@ private function __construct() {
if ( is_admin() || $is_rest ) {
require_once 'includes/class-dt-migration-system-users.php';
require_once 'includes/class-dt-migration-import-engine.php';
+ require_once 'includes/class-dt-migration-preflight.php';
}
if ( is_admin() ) {
diff --git a/includes/class-dt-migration-preflight.php b/includes/class-dt-migration-preflight.php
new file mode 100644
index 0000000..4ce9046
--- /dev/null
+++ b/includes/class-dt-migration-preflight.php
@@ -0,0 +1,317 @@
+ list of record arrays.
+ * @type bool $records_sampled True when $records do not cover the full export.
+ * @type array $settings_selected Keys selected for import (e.g. [ 'system_users' => true ]).
+ * @type string[] $records_selected Post types selected for record import.
+ * }
+ * @return array{ warnings: string[], info: string[] }
+ */
+ public static function analyze( array $args ) : array {
+ $export = isset( $args['export'] ) && is_array( $args['export'] ) ? $args['export'] : [];
+ $records = isset( $args['records'] ) && is_array( $args['records'] ) ? $args['records'] : [];
+ $records_sampled = ! empty( $args['records_sampled'] );
+ $settings_selected = isset( $args['settings_selected'] ) && is_array( $args['settings_selected'] ) ? $args['settings_selected'] : [];
+ $records_selected = isset( $args['records_selected'] ) && is_array( $args['records_selected'] ) ? $args['records_selected'] : [];
+
+ $warnings = [];
+ $info = [];
+
+ if ( is_multisite() && ! empty( $settings_selected['system_users'] ) ) {
+ $info[] = __(
+ 'Multisite: user roles are stored per subsite. System user import matches existing accounts by email; a network Super Admin may not appear under Users on this site as an Administrator. If role updates fail, use Users → Add User to add them to this site as an Administrator first, or run the import as someone with promote_users capability.',
+ 'disciple-tools-migration'
+ );
+ }
+
+ if ( $records_sampled ) {
+ $info[] = __( 'Record checks are based on a sample from the source (first batches). Other records may differ.', 'disciple-tools-migration' );
+ }
+
+ if ( ! empty( $settings_selected['system_users'] ) ) {
+ $warnings = array_merge( $warnings, self::warnings_system_users( $export ) );
+ }
+
+ $will_import_field_defs = ! empty( $settings_selected['fields'] );
+ $add_post_type_sep = false;
+ foreach ( $records_selected as $post_type ) {
+ $post_type = sanitize_key( (string) $post_type );
+ if ( $post_type === '' ) {
+ continue;
+ }
+ $batch = isset( $records[ $post_type ] ) && is_array( $records[ $post_type ] ) ? $records[ $post_type ] : [];
+
+ $chunk = array_merge(
+ self::warnings_unknown_fields( $export, $post_type, $batch, $will_import_field_defs ),
+ self::warnings_post_id_collisions( $post_type, $batch )
+ );
+
+ if ( ! empty( $chunk ) ) {
+ if ( $add_post_type_sep ) {
+ $warnings[] = '----------';
+ }
+ $warnings = array_merge( $warnings, $chunk );
+ $add_post_type_sep = true;
+ }
+ }
+
+ return [
+ 'warnings' => self::dedupe_preflight_lines( $warnings ),
+ 'info' => array_values( array_unique( array_filter( $info ) ) ),
+ ];
+ }
+
+ /**
+ * @param string[] $lines
+ * @return string[]
+ */
+ private static function dedupe_preflight_lines( array $lines ) : array {
+ $out = [];
+ $prev = null;
+ foreach ( $lines as $line ) {
+ if ( ! is_string( $line ) ) {
+ continue;
+ }
+ if ( $line === '----------' && $prev === '----------' ) {
+ continue;
+ }
+ $out[] = $line;
+ $prev = $line;
+ }
+ return $out;
+ }
+
+ /**
+ * @param array $export Export block.
+ * @return string[]
+ */
+ private static function warnings_system_users( array $export ) : array {
+ $out = [];
+ $sys = $export['system_users'] ?? null;
+ $rows = ( is_array( $sys ) && isset( $sys['users'] ) && is_array( $sys['users'] ) ) ? $sys['users'] : [];
+ if ( empty( $rows ) ) {
+ return $out;
+ }
+
+ foreach ( $rows as $row ) {
+ if ( ! is_array( $row ) ) {
+ continue;
+ }
+ $old_id = isset( $row['id'] ) ? (int) $row['id'] : 0;
+ if ( $old_id <= 0 ) {
+ continue;
+ }
+ if ( ! Disciple_Tools_Migration_System_Users::is_migration_admin_user( $row ) ) {
+ continue;
+ }
+
+ $email = isset( $row['user_email'] ) ? sanitize_email( (string) $row['user_email'] ) : '';
+ $login = isset( $row['user_login'] ) ? sanitize_user( (string) $row['user_login'], true ) : '';
+
+ $existing = Disciple_Tools_Migration_System_Users::find_existing_user( $email, $login );
+ if ( ! $existing instanceof WP_User ) {
+ continue;
+ }
+
+ if ( self::user_is_effectively_site_admin( (int) $existing->ID ) ) {
+ continue;
+ }
+
+ $out[] = sprintf(
+ /* translators: 1: source user ID, 2: target user ID, 3: user login */
+ __( 'Source administrator (exported user ID %1$d) matches existing user ID %2$d (%3$s), who is not an Administrator on this site. User import will try to apply exported roles; ensure your account can promote users, or assign Administrator on this site before importing.', 'disciple-tools-migration' ),
+ $old_id,
+ (int) $existing->ID,
+ $existing->user_login
+ );
+ }
+
+ return $out;
+ }
+
+ /**
+ * @param int $user_id WordPress user ID.
+ */
+ private static function user_is_effectively_site_admin( int $user_id ) : bool {
+ if ( $user_id <= 0 ) {
+ return false;
+ }
+ if ( is_multisite() && is_super_admin( $user_id ) ) {
+ return true;
+ }
+ $user = new WP_User( $user_id );
+ return in_array( 'administrator', (array) $user->roles, true );
+ }
+
+ /**
+ * @param array $export Export block.
+ * @param string $post_type Post type.
+ * @param array $records Records to scan.
+ * @param bool $will_import_fields Whether field definitions from export will be imported.
+ * @return string[]
+ */
+ private static function warnings_unknown_fields( array $export, string $post_type, array $records, bool $will_import_fields ) : array {
+ if ( empty( $records ) || ! class_exists( 'DT_Posts' ) ) {
+ return [];
+ }
+
+ try {
+ $post_settings = DT_Posts::get_post_settings( $post_type, false );
+ $target_fields = isset( $post_settings['fields'] ) && is_array( $post_settings['fields'] ) ? $post_settings['fields'] : [];
+ } catch ( Throwable $e ) {
+ return [];
+ }
+
+ $target_keys = array_keys( $target_fields );
+
+ if ( $will_import_fields ) {
+ $dt = $export['dt_settings'] ?? [];
+ $exported = $dt['dt_fields_settings']['values'][ $post_type ] ?? [];
+ $exported = is_array( $exported ) ? $exported : [];
+ $merge_keys = array_keys( $exported );
+ $target_keys = array_values( array_unique( array_merge( $target_keys, $merge_keys ) ) );
+ }
+
+ $target_lookup = array_fill_keys( $target_keys, true );
+
+ $unknown_with_count = [];
+ foreach ( $records as $record ) {
+ if ( ! is_array( $record ) ) {
+ continue;
+ }
+ foreach ( array_keys( $record ) as $key ) {
+ if ( ! is_string( $key ) ) {
+ continue;
+ }
+ if ( in_array( $key, self::$record_key_exclude, true ) ) {
+ continue;
+ }
+ if ( isset( $target_lookup[ $key ] ) ) {
+ continue;
+ }
+ if ( ! isset( $unknown_with_count[ $key ] ) ) {
+ $unknown_with_count[ $key ] = 0;
+ }
+ ++$unknown_with_count[ $key ];
+ }
+ }
+
+ if ( empty( $unknown_with_count ) ) {
+ return [];
+ }
+
+ ksort( $unknown_with_count );
+
+ $out = [];
+ $out[] = sprintf(
+ /* translators: %s: post type slug */
+ __( '%s: unknown field keys in sampled records (not defined on this site for your selected import options). One field per line:', 'disciple-tools-migration' ),
+ $post_type
+ );
+ foreach ( $unknown_with_count as $field_key => $count ) {
+ $out[] = sprintf(
+ /* translators: 1: field key, 2: number of sampled records. Leading spaces align under the header. */
+ __( ' - %1$s (in %2$d sampled record(s))', 'disciple-tools-migration' ),
+ $field_key,
+ $count
+ );
+ }
+ $out[] = sprintf(
+ /* translators: %s: post type slug */
+ __( '%s: values for those keys may fail validation or be skipped until field definitions match the source.', 'disciple-tools-migration' ),
+ $post_type
+ );
+
+ return $out;
+ }
+
+ /**
+ * @param string $post_type Post type.
+ * @param array $records Records to scan.
+ * @return string[]
+ */
+ private static function warnings_post_id_collisions( string $post_type, array $records ) : array {
+ if ( empty( $records ) ) {
+ return [];
+ }
+
+ $collisions = [];
+ foreach ( $records as $record ) {
+ if ( ! is_array( $record ) || empty( $record['ID'] ) ) {
+ continue;
+ }
+ $pid = (int) $record['ID'];
+ if ( $pid <= 0 ) {
+ continue;
+ }
+ $existing = get_post( $pid );
+ if ( ! $existing instanceof WP_Post ) {
+ continue;
+ }
+ if ( get_post_type( $pid ) === $post_type ) {
+ continue;
+ }
+ $collisions[] = $pid;
+ }
+
+ $collisions = array_values( array_unique( $collisions ) );
+ if ( empty( $collisions ) ) {
+ return [];
+ }
+
+ $slice = array_slice( $collisions, 0, 15 );
+ $list = implode( ', ', $slice );
+ if ( count( $collisions ) > 15 ) {
+ $list .= sprintf(
+ /* translators: %d: number of additional IDs */
+ __( ' …and %d more', 'disciple-tools-migration' ),
+ count( $collisions ) - 15
+ );
+ }
+
+ return [
+ sprintf(
+ /* translators: 1: post type, 2: list of post IDs */
+ __( '%1$s: these post IDs already exist on this site with a different post type (import may not preserve ID): %2$s', 'disciple-tools-migration' ),
+ $post_type,
+ $list
+ ),
+ ];
+ }
+}