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 ) {

- +

+ @@ -190,12 +193,14 @@ 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} - -
- -
- -
- - -
- `) - - // 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 - - ?> -
-
-
-
-

Hello there

-
-
-
-
-

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(){ - ?> - - -
-
-
-
-

Title

-
-
-
-
-

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 - - - 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 -
- - -
  • - -
    - - -
    - - -

    Replace with your custom content

    - -
    - - - 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; } ?> +