From b70af85515ae23c67985e08d2f2f4f7e58f66e7c Mon Sep 17 00:00:00 2001 From: "anton.borgstrom" Date: Tue, 9 Jan 2024 09:21:16 +0100 Subject: [PATCH] First commit --- blocksy-db-migration.php | 37 +++ includes/api.php | 269 ++++++++++++++++ includes/db-search-replace.php | 353 +++++++++++++++++++++ includes/db-search-replacer.php | 196 ++++++++++++ includes/utils.php | 18 ++ plugin.php | 190 ++++++++++++ static/.DS_Store | Bin 0 -> 6148 bytes static/bundle/dashboard.js | 1 + static/bundle/dashboard.min.css | 8 + static/js/.DS_Store | Bin 0 -> 6148 bytes static/js/components/Dashboard.js | 73 +++++ static/js/components/DisplayData.js | 461 ++++++++++++++++++++++++++++ static/js/dashboard.js | 11 + static/js/test.ts | 0 static/js/utils/save-as.js | 202 ++++++++++++ static/sass/main.scss | 76 +++++ 16 files changed, 1895 insertions(+) create mode 100644 blocksy-db-migration.php create mode 100644 includes/api.php create mode 100644 includes/db-search-replace.php create mode 100644 includes/db-search-replacer.php create mode 100644 includes/utils.php create mode 100644 plugin.php create mode 100644 static/.DS_Store create mode 100644 static/bundle/dashboard.js create mode 100644 static/bundle/dashboard.min.css create mode 100644 static/js/.DS_Store create mode 100644 static/js/components/Dashboard.js create mode 100644 static/js/components/DisplayData.js create mode 100644 static/js/dashboard.js create mode 100644 static/js/test.ts create mode 100644 static/js/utils/save-as.js create mode 100644 static/sass/main.scss diff --git a/blocksy-db-migration.php b/blocksy-db-migration.php new file mode 100644 index 0000000..bacd785 --- /dev/null +++ b/blocksy-db-migration.php @@ -0,0 +1,37 @@ +attach_ajax_actions(); + + // Ensure Blocksy V1 never runs css regeneration during upgrade. + // This is the cause of many many problems. + add_action( + 'init', + function () { + if (! class_exists('\Blocksy\Plugin')) { + return; + } + + $instance = \Blocksy\Plugin::instance(); + + if (! $instance || ! $instance->cache_manager) { + return; + } + + remove_action( + 'upgrader_process_complete', + [$instance->cache_manager, 'handle_update'], + 10, 2 + ); + }, + 5 + ); + } + + public function migrate_table() { + $this->check_capability('manage_options'); + + if (! isset($_GET['table'])) { + wp_send_json_error([ + 'message' => 'No table provided' + ]); + } + + if (! isset($_GET['old'])) { + wp_send_json_error([ + 'message' => 'No old value provided' + ]); + } + + if (! isset($_GET['new'])) { + wp_send_json_error([ + 'message' => 'No new value provided' + ]); + } + + $r = new SearchReplace(); + + $result = $r->invoke([ + 'old' => $_GET['old'], + 'new' => $_GET['new'], + 'dry_run' => false, + 'tables' => [$_GET['table']] + ]); + + wp_send_json_success([ + 'result' => $result, + ]); + } + + public function get_table_status() { + $this->check_capability('manage_options'); + + if (! isset($_GET['table'])) { + wp_send_json_error([ + 'message' => 'No table provided' + ]); + } + + if (! isset($_GET['old'])) { + wp_send_json_error([ + 'message' => 'No old value provided' + ]); + } + + if (! isset($_GET['new'])) { + wp_send_json_error([ + 'message' => 'No new value provided' + ]); + } + + $result = $this->migrate_for( + $_GET['table'], + $_GET['old'], + $_GET['new'] + ); + + wp_send_json_success([ + 'result' => $result, + ]); + } + + public function migrate_for($table, $old, $new, $dry_run = true) { + $replacements = [ + [ + 'old' => $old, + 'new' => $new + ], + ]; + + /* + $replacements = [ + [ + 'old' => 'paletteColor', + 'new' => 'theme-palette-color-', + ], + + [ + 'old' => 'buttonInitialColor', + 'new' => 'theme-button-background-initial-color', + ], + + [ + 'old' => '--fontFamily', + 'new' => '--theme-font-family' + ] + ]; + + $greenshift_variables = [ + '--linkInitialColor' => '--theme-link-initial-color', + '--container-width' => '--theme-container-width', + '--normal-container-max-width' => '--theme-normal-container-max-width', + '--narrow-container-max-width' => '--theme-narrow-container-max-width', + '--buttonFontFamily' => '--theme-button-font-family', + '--buttonFontSize' => '--theme-button-font-size', + '--buttonFontWeight' => '--theme-button-font-weight', + '--buttonFontStyle' => '--theme-button-font-style', + '--buttonLineHeight' => '--theme-button-line-height', + '--buttonLetterSpacing' => '--theme-button-letter-spacing', + '--buttonTextTransform' => '--theme-button-text-transform', + '--buttonTextDecoration' => '--theme-button-text-decoration', + '--buttonTextInitialColor' => '--theme-button-text-initial-color', + '--button-border' => '--theme-button-border', + '--buttonInitialColor' => '--theme-button-background-initial-color', + '--buttonMinHeight' => '--theme-button-min-height', + '--buttonBorderRadius' => '--theme-button-border-radius', + '--button-padding' => '--theme-button-padding', + '--button-border-hover-color' => '--theme-button-border-hover-color', + '--buttonTextHoverColor' => '--theme-button-text-hover-color', + '--buttonHoverColor' => '--theme-button-background-hover-color' + ]; + + foreach ($greenshift_variables as $greenshift_key => $greenshift_value) { + $replacements[] = [ + 'old' => $greenshift_key, + 'new' => $greenshift_value + ]; + } + */ + + $r = new SearchReplace(); + + $result = [ + 'total' => 0, + ]; + + foreach ($replacements as $replacement) { + $replacement_result = $r->invoke([ + 'old' => $replacement['old'], + 'new' => $replacement['new'], + 'tables' => [$table], + 'dry_run' => $dry_run + ]); + + $result['total'] += $replacement_result['total']; + } + + wp_send_json_success([ + 'result' => $result + ]); + } + + public function regenerate_css() { + $this->check_capability('manage_options'); + + do_action('blocksy:cache-manager:purge-all'); + do_action('blocksy:dynamic-css:refresh-caches'); + + delete_option('blocksy_db_version'); + + do_action('blocksy:db-versioning:trigger-migration'); + + wp_send_json_success(); + } + + public function get_site_status() { + $this->check_capability('manage_options'); + + /* + $r = new SearchReplace(); + + $palette_result = $r->invoke([ + 'old' => 'paletteColor', + 'new' => 'theme-palette-color-' + ]); + + $button_result = $r->invoke([ + 'old' => 'buttonInitialColor', + 'new' => 'theme-button-background-initial-color' + ]); + */ + + if (! function_exists('get_plugin_data')) { + require_once(ABSPATH . 'wp-admin/includes/plugin.php'); + } + + global $wpdb; + + $data = [ + 'theme_version' => '1.0.0', + 'blocksy_db_version' => get_option('blocksy_db_version'), + + 'plugin_version' => '1.0.0', + + // 'migration_status' => $palette_result, + // 'button_result' => $button_result, + + // 'plugins' => get_plugins(), + + 'all_tables' => Utils::wp_get_table_names(), + + 'prefix' => $wpdb->base_prefix, + ]; + + if (defined('BLOCKSY__FILE__')) { + $plugin_data = get_plugin_data(BLOCKSY__FILE__); + $data['plugin_version'] = $plugin_data['Version']; + } + + if (wp_get_theme('blocksy')) { + $data['theme_version'] = wp_get_theme('blocksy')->get('Version'); + } + + wp_send_json_success($data); + } + + public function check_capability($cap = 'install_plugins') { + if (! current_user_can($cap)) { + wp_send_json_error([ + 'message' => __('You are not allowed to do this', 'blocksy-companion') + ]); + } + } + + public function attach_ajax_actions() { + foreach ($this->ajax_actions as $action) { + add_action( + 'wp_ajax_' . $action, + [$this, $action] + ); + } + } +} + diff --git a/includes/db-search-replace.php b/includes/db-search-replace.php new file mode 100644 index 0000000..94339ea --- /dev/null +++ b/includes/db-search-replace.php @@ -0,0 +1,353 @@ + '', + 'new' => '', + 'dry_run' => true, + 'tables' => null + ]); + + $old = $args['old']; + $new = $args['new']; + $this->dry_run = $args['dry_run']; + + $skip_columns = []; + + // never mess with hashed passwords + $skip_columns[] = 'user_pass'; + + $tables = $args['tables']; + + if (! $tables) { + $tables = Utils::wp_get_table_names(); + } + + $result = [ + 'tables' => [], + ]; + + foreach ($tables as $table) { + $table_sql = $this->esc_sql_ident($table); + + list($primary_keys, $columns, $all_columns) = self::get_columns($table); + + // since we'll be updating one row at a time, + // we need a primary key to identify the row + if (empty($primary_keys)) { + continue; + } + + foreach ($columns as $col) { + if (in_array($col, $skip_columns, true)) { + continue; + } + + $col_sql = $this->esc_sql_ident($col); + $wpdb->last_error = ''; + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident + $serial_row = $wpdb->get_row( + "SELECT * FROM $table_sql WHERE $col_sql REGEXP '^[aiO]:[1-9]' LIMIT 1" + ); + + // When the regex triggers an error, we should fall back to PHP + if (false !== strpos($wpdb->last_error, 'ERROR 1139')) { + $serial_row = true; + } + + if (null !== $serial_row) { + $type = 'PHP'; + + $count = $this->php_handle_col( + $col, + $primary_keys, + $table, + $old, + $new + ); + } else { + $type = 'SQL'; + + $count = $this->sql_handle_col( + $col, + $primary_keys, + $table, + $old, + $new + ); + } + + if (intval($count) > 0) { + if (! isset($result['tables'][$table])) { + $result['tables'][$table] = 0; + } + + $result['tables'][$table] += intval($count); + } + } + + } + + $total = 0; + + foreach ($result['tables'] as $table => $count) { + $total += $count; + } + + $result['total'] = $total; + + return $result; + } + + /** + * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names. + * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html + * + * @param string|array $idents A single identifier or an array of identifiers. + * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings. + */ + private function esc_sql_ident($idents) { + $backtick = static function ($v) { + // Escape any backticks in the identifier by doubling. + return '`' . str_replace('`', '``', $v) . '`'; + }; + + if (is_string($idents)) { + return $backtick($idents); + } + + return array_map($backtick, $idents); + } + + private function get_columns($table) { + global $wpdb; + + $table_sql = $this->esc_sql_ident($table); + $primary_keys = array(); + $text_columns = array(); + $all_columns = array(); + $suppress_errors = $wpdb->suppress_errors(); + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident + $results = $wpdb->get_results("DESCRIBE $table_sql"); + + if (! empty($results)) { + foreach ($results as $col) { + if ('PRI' === $col->Key) { + $primary_keys[] = $col->Field; + } + + if ($this->is_text_col($col->Type)) { + $text_columns[] = $col->Field; + } + + $all_columns[] = $col->Field; + } + } + + $wpdb->suppress_errors($suppress_errors); + + return array($primary_keys, $text_columns, $all_columns); + } + + private function is_text_col($type) { + foreach (array('text', 'varchar') as $token) { + if (false !== stripos($type, $token)) { + return true; + } + } + + return false; + } + + private function sql_handle_col($col, $primary_keys, $table, $old, $new) { + global $wpdb; + + $table_sql = $this->esc_sql_ident($table); + $col_sql = $this->esc_sql_ident($col); + + if ($this->dry_run) { + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident + $count = $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT($col_sql) FROM $table_sql WHERE $col_sql LIKE BINARY %s;", '%' . $this->esc_like($old) . '%' + ) + ); + } else { + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident + $count = $wpdb->query( + $wpdb->prepare( + "UPDATE $table_sql SET $col_sql = REPLACE($col_sql, %s, %s);", + $old, + $new + ) + ); + } + + return $count; + } + + private function esc_like($old) { + global $wpdb; + + // Remove notices in 4.0 and support backwards compatibility + if (method_exists($wpdb, 'esc_like')) { + // 4.0 + $old = $wpdb->esc_like($old); + } else { + // phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- BC-layer for WP 3.9 or less. + $old = like_escape(esc_sql($old)); // Note: this double escaping is actually necessary, even though `esc_like()` will be used in a `prepare()`. + } + + return $old; + } + + private function php_handle_col($col, $primary_keys, $table, $old, $new) { + global $wpdb; + + $count = 0; + $replacer = new SearchReplacer($old, $new, true); + + $table_sql = $this->esc_sql_ident($table); + $col_sql = $this->esc_sql_ident($col); + + $base_key_condition = "$col_sql" . $wpdb->prepare( + ' LIKE BINARY %s', + '%' . $this->esc_like($old) . '%' + ); + + $where_key = "WHERE $base_key_condition"; + + $escaped_primary_keys = $this->esc_sql_ident($primary_keys); + $primary_keys_sql = implode(',', $escaped_primary_keys); + $order_by_keys = array_map( + static function ($key) { + return "{$key} ASC"; + }, + $escaped_primary_keys + ); + + $order_by_sql = 'ORDER BY ' . implode(',', $order_by_keys); + $limit = 1000; + + // 2 errors: + // - WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident + // - WordPress.CodeAnalysis.AssignmentInCondition -- no reason to do copy-paste for a single valid assignment in while + // phpcs:ignore + while ($rows = $wpdb->get_results("SELECT {$primary_keys_sql} FROM {$table_sql} {$where_key} {$order_by_sql} LIMIT {$limit}")) { + foreach ($rows as $keys) { + $where_sql = ''; + + foreach ((array) $keys as $k => $v) { + if ('' !== $where_sql) { + $where_sql .= ' AND '; + } + + $where_sql .= $this->esc_sql_ident($k) . ' = ' . $this->esc_sql_value($v); + } + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident + $col_value = $wpdb->get_var( + "SELECT {$col_sql} FROM {$table_sql} WHERE {$where_sql}" + ); + + if ('' === $col_value) { + continue; + } + + $value = $replacer->run($col_value); + + if ($value === $col_value) { + continue; + } + + $count++; + + if (! $this->dry_run) { + $update_where = array(); + + foreach ((array) $keys as $k => $v) { + $update_where[$k] = $v; + } + + $wpdb->update($table, [$col => $value], $update_where); + } + } + + // Because we are ordering by primary keys from least to greatest, + // we can exclude previous chunks from consideration by adding greater-than conditions + // to insist the next chunk's keys must be greater than the last of this chunk's keys. + $last_row = end($rows); + $next_key_conditions = array(); + + + // NOTE: For a composite key (X, Y, Z), selecting the next rows requires the following conditions: + // ( X = lastX AND Y = lastY AND Z > lastZ ) OR + // ( X = lastX AND Y > lastY ) OR + // ( X > lastX ) + for ($last_key_index = count($primary_keys) - 1; $last_key_index >= 0; $last_key_index--) { + $next_key_subconditions = array(); + + for ($i = 0; $i <= $last_key_index; $i++) { + $k = $primary_keys[$i]; + $v = $last_row->{$k}; + + if ($i < $last_key_index) { + $next_key_subconditions[] = $this->esc_sql_ident($k) . ' = ' . $this->esc_sql_value($v); + } else { + $next_key_subconditions[] = $this->esc_sql_ident($k) . ' > ' . $this->esc_sql_value($v); + } + } + + $next_key_conditions[] = '( ' . implode(' AND ', $next_key_subconditions) . ' )'; + } + + + $where_key_conditions = array(); + + if ($base_key_condition) { + $where_key_conditions[] = $base_key_condition; + } + + $where_key_conditions[] = '( ' . implode(' OR ', $next_key_conditions) . ' )'; + + $where_key = 'WHERE ' . implode(' AND ', $where_key_conditions); + } + + return $count; + } + + /** + * Puts MySQL string values in single quotes, to avoid them being interpreted as column names. + * + * @param string|array $values A single value or an array of values. + * @return string|array A quoted string if given a string, or an array of quoted strings if given an array of strings. + */ + private function esc_sql_value($values) { + $quote = static function ($v) { + // Don't quote integer values to avoid MySQL's implicit type conversion. + if (preg_match('/^[+-]?[0-9]{1,20}$/', $v)) { // MySQL BIGINT UNSIGNED max 18446744073709551615 (20 digits). + return esc_sql($v); + } + + // Put any string values between single quotes. + return "'" . esc_sql($v) . "'"; + }; + + if (is_array($values)) { + return array_map($quote, $values); + } + + return $quote($values); + } +} + + diff --git a/includes/db-search-replacer.php b/includes/db-search-replacer.php new file mode 100644 index 0000000..831dce2 --- /dev/null +++ b/includes/db-search-replacer.php @@ -0,0 +1,196 @@ +from = $from; + $this->to = $to; + $this->recurse_objects = $recurse_objects; + $this->regex = $regex; + $this->regex_flags = $regex_flags; + $this->regex_delimiter = $regex_delimiter; + $this->regex_limit = $regex_limit; + $this->clear_log_data(); + + // Get the XDebug nesting level. Will be zero (no limit) if no value is set + $this->max_recursion = intval( ini_get( 'xdebug.max_nesting_level' ) ); + } + + /** + * Take a serialised array and unserialise it replacing elements as needed and + * unserialising any subordinate arrays and performing the replace on those too. + * Ignores any serialized objects unless $recurse_objects is set to true. + * + * @param array|string $data The data to operate on. + * @param bool $serialised Does the value of $data need to be unserialized? + * + * @return array The original array with all elements replaced as needed. + */ + public function run($data, $serialised = false) { + return $this->run_recursively($data, $serialised); + } + + /** + * @param int $recursion_level Current recursion depth within the original data. + * @param array $visited_data Data that has been seen in previous recursion iterations. + */ + private function run_recursively($data, $serialised, $recursion_level = 0, $visited_data = array()) { + + + // some unseriliased data cannot be re-serialised eg. SimpleXMLElements + try { + if ($this->recurse_objects) { + // If we've reached the maximum recursion level, short circuit + if ( + 0 !== $this->max_recursion + && + $recursion_level >= $this->max_recursion + ) { + return $data; + } + + if ( + is_array($data) + || + is_object($data) + ) { + // If we've seen this exact object or array before, short circuit + if (in_array($data, $visited_data, true)) { + return $data; // Avoid infinite loops when there's a cycle + } + + // Add this data to the list of + $visited_data[] = $data; + } + } + + // The error suppression operator is not enough in some cases, so we disable + // reporting of notices and warnings as well. + $error_reporting = error_reporting(); + error_reporting($error_reporting & ~E_NOTICE & ~E_WARNING); + + $had_error_before = error_get_last(); + $unserialized = is_string($data) ? @unserialize($data) : false; + $error_get_last = error_get_last(); + + error_reporting($error_reporting); + + if (! $had_error_before && $error_get_last) { + error_clear_last(); + } + + if (false !== $unserialized) { + $data = $this->run_recursively( + $unserialized, + true, + $recursion_level + 1 + ); + } elseif (is_array($data)) { + $keys = array_keys($data); + + foreach ($keys as $key) { + $data[$key] = $this->run_recursively( + $data[$key], + false, + $recursion_level + 1, + $visited_data + ); + } + } elseif ( + $this->recurse_objects + && + ( + is_object($data) + || + $data instanceof \__PHP_Incomplete_Class + ) + ) { + if ($data instanceof \__PHP_Incomplete_Class) { + $array = new ArrayObject($data); + } else { + foreach ($data as $key => $value) { + $data->$key = $this->run_recursively( + $value, + false, + $recursion_level + 1, + $visited_data + ); + } + } + } elseif (is_string($data)) { + $data = str_replace($this->from, $this->to, $data); + } + + if ($serialised) { + return serialize($data); + } + } catch (Exception $error) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Deliberally empty. + } + + return $data; + } + + /** + * Gets existing data saved for this run when logging. + * @return array Array of data strings, prior to replacements. + */ + public function get_log_data() { + return $this->log_data; + } + + /** + * Clears data stored for logging. + */ + public function clear_log_data() { + $this->log_data = array(); + } + + /** + * Get the PCRE error constant name from an error value. + * + * @param integer $error Error code. + * @return string Error constant name. + */ + private function preg_error_message( $error ) { + static $error_names = null; + + if ( null === $error_names ) { + $definitions = get_defined_constants( true ); + $pcre_constants = array_key_exists( 'pcre', $definitions ) + ? $definitions['pcre'] + : array(); + $error_names = array_flip( $pcre_constants ); + } + + return isset( $error_names[ $error ] ) + ? $error_names[ $error ] + : ''; + } +} + + diff --git a/includes/utils.php b/includes/utils.php new file mode 100644 index 0000000..4544d59 --- /dev/null +++ b/includes/utils.php @@ -0,0 +1,18 @@ +get_col($tables_sql, 0); + + return $tables; + } +} + + diff --git a/plugin.php b/plugin.php new file mode 100644 index 0000000..e285b6b --- /dev/null +++ b/plugin.php @@ -0,0 +1,190 @@ +is_dashboard_page()) { + add_action( + 'admin_enqueue_scripts', + [$this, 'enqueue_static'] + ); + } + + if ($this->is_dashboard_page()) { + add_action( + 'admin_print_scripts', + function () { + global $wp_filter; + + if (is_user_admin()) { + if (isset($wp_filter['user_admin_notices'])) { + unset($wp_filter['user_admin_notices']); + } + } elseif (isset($wp_filter['admin_notices'])) { + unset($wp_filter['admin_notices']); + } + + if (isset($wp_filter['all_admin_notices'])) { + unset($wp_filter['all_admin_notices']); + } + } + ); + } + } + + public function enqueue_static() { + if (! function_exists('get_plugin_data')) { + require_once(ABSPATH . 'wp-admin/includes/plugin.php'); + } + + $data = get_plugin_data(BLOCKSY_DB_MIGRATION__FILE__); + + $dependencies = [ + 'underscore', + 'wp-util', + 'react', + 'react-dom', + 'wp-element', + 'wp-date', + 'wp-i18n', + ]; + + wp_enqueue_script( + 'blocksy-migration-scripts', + BLOCKSY_DB_MIGRATION_URL . '/static/bundle/dashboard.js', + $dependencies, + $data['Version'], + false + ); + + wp_localize_script( + 'blocksy-migration-scripts', + 'ctMigrationLocalization', + [ + 'ajax_url' => admin_url('admin-ajax.php') + ] + ); + + wp_enqueue_style( + 'blocksy-migration-styles', + BLOCKSY_DB_MIGRATION_URL . '/static/bundle/dashboard.min.css', + [], + $data['Version'] + ); + } + + public function is_dashboard_page() { + return isset($_GET['page']) && 'blocksy-migration' === $_GET['page']; + } + + public function render_page() { + ?> +
+ +
+ + + + + + + +

Blocksy Migrator

+ +

+ This tool will help those setups where the initial migrator process (from the theme) didn't work well after updating to version 2. +

+
+
+ +
+

Step 1: Scan database

+ +
+ +
+ +
+
+ early_init(); + + add_action('init', [$this, 'init'], 0); + } +} + +Plugin::instance(); + diff --git a/static/.DS_Store b/static/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..26f612e05ab9126518117401141f744249f885b9 GIT binary patch literal 6148 zcmeHKOHRWu5FLjIwSq;KEX{%g^ag1aPS6WL2m+y`sZ@2x!Y4pniamGX0=$_qQPU)1 zhblB9jh{0gkMd$=Y$9^2eYGIUh-e07Oy(Fyg#D}oNuuQv=wywW9_y#Y`k`&MgJ?6H zA_M&G&Z(q5ZK$Hb_j|oQ_BU-&lyzIej!hA6zTQ5T!+R&T2FlaI1^0b{@z=osMtgNHKa5rd-qIxxr;0GProg1MY^fSwJ&JYrCU1>!UnsHrYqF`TBu z?qggYF(_&}IjM|wVr3U^C@!9~oR@HNxuT86fHBZ#;7lJg?*F&b_y2y9JsAVWz`tU^ zrNz2f;gw=<9lRX(S`WR0vT$5b>{2k1R*YC~#aB=f*ga2xdBmUy3&airLW2#)z@IYk E4IzYG!vFvP literal 0 HcmV?d00001 diff --git a/static/bundle/dashboard.js b/static/bundle/dashboard.js new file mode 100644 index 0000000..46f6ad6 --- /dev/null +++ b/static/bundle/dashboard.js @@ -0,0 +1 @@ +!function(){var t={560:function(t,e,n){function r(t){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r(t)}var o="object"===("undefined"==typeof window?"undefined":r(window))&&window.window===window?window:"object"===("undefined"==typeof self?"undefined":r(self))&&self.self===self?self:"object"===(void 0===n.g?"undefined":r(n.g))&&n.g.global===n.g?n.g:this;function a(t,e,n){var r=new XMLHttpRequest;r.open("GET",t),r.responseType="blob",r.onload=function(){u(r.response,e,n)},r.onerror=function(){console.error("could not download file")},r.send()}function i(t){var e=new XMLHttpRequest;e.open("HEAD",t,!1);try{e.send()}catch(t){}return e.status>=200&&e.status<=299}function c(t){try{t.dispatchEvent(new MouseEvent("click"))}catch(n){var e=document.createEvent("MouseEvents");e.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),t.dispatchEvent(e)}}var u="object"!==("undefined"==typeof window?"undefined":r(window))||window!==o?function(){}:"download"in HTMLAnchorElement.prototype?function(t,e,n){var r=o.URL||o.webkitURL,u=document.createElement("a");e=e||t.name||"download",u.download=e,u.rel="noopener","string"==typeof t?(u.href=t,u.origin!==location.origin?i(u.href)?a(t,e,n):c(u,u.target="_blank"):c(u)):(u.href=r.createObjectURL(t),setTimeout((function(){r.revokeObjectURL(u.href)}),4e4),setTimeout((function(){c(u)}),0))}:"msSaveOrOpenBlob"in navigator?function(t,e,n){if(e=e||t.name||"download","string"==typeof t)if(i(t))a(t,e,n);else{var o=document.createElement("a");o.href=t,o.target="_blank",setTimeout((function(){c(o)}))}else navigator.msSaveOrOpenBlob(function(t,e){return void 0===e?e={autoBom:!1}:"object"!==r(e)&&(console.warn("Deprecated: Expected third argument to be a object"),e={autoBom:!e}),e.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type)?new Blob([String.fromCharCode(65279),t],{type:t.type}):t}(t,n),e)}:function(t,e,n,i){if((i=i||open("","_blank"))&&(i.document.title=i.document.body.innerText="downloading..."),"string"==typeof t)return a(t,e,n);var c="application/octet-stream"===t.type,u=/constructor/i.test(o.HTMLElement)||o.safari,l=/CriOS\/[\d]+/.test(navigator.userAgent);if((l||c&&u)&&"object"===("undefined"==typeof FileReader?"undefined":r(FileReader))){var s=new FileReader;s.onloadend=function(){var t=s.result;t=l?t:t.replace(/^data:[^;]*;/,"data:attachment/file;"),i?i.location.href=t:location=t,i=null},s.readAsDataURL(t)}else{var f=o.URL||o.webkitURL,h=f.createObjectURL(t);i?i.location=h:location.href=h,i=null,setTimeout((function(){f.revokeObjectURL(h)}),4e4)}};t.exports=u}},e={};function n(r){var o=e[r];if(void 0!==o)return o.exports;var a=e[r]={exports:{}};return t[r].call(a.exports,a,a.exports,n),a.exports}n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,{a:e}),e},n.d=function(t,e){for(var r in e)n.o(e,r)&&!n.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},function(){"use strict";var t=window.wp.element,e=n(560),r=n.n(e);function o(t){return o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},o(t)}function a(){/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */a=function(){return e};var t,e={},n=Object.prototype,r=n.hasOwnProperty,i=Object.defineProperty||function(t,e,n){t[e]=n.value},c="function"==typeof Symbol?Symbol:{},u=c.iterator||"@@iterator",l=c.asyncIterator||"@@asyncIterator",s=c.toStringTag||"@@toStringTag";function f(t,e,n){return Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{f({},"")}catch(t){f=function(t,e,n){return t[e]=n}}function h(t,e,n,r){var o=e&&e.prototype instanceof w?e:w,a=Object.create(o.prototype),c=new F(r||[]);return i(a,"_invoke",{value:k(t,n,c)}),a}function d(t,e,n){try{return{type:"normal",arg:t.call(e,n)}}catch(t){return{type:"throw",arg:t}}}e.wrap=h;var p="suspendedStart",y="suspendedYield",m="executing",v="completed",b={};function w(){}function g(){}function x(){}var E={};f(E,u,(function(){return this}));var O=Object.getPrototypeOf,S=O&&O(O(A([])));S&&S!==n&&r.call(S,u)&&(E=S);var j=x.prototype=w.prototype=Object.create(E);function L(t){["next","throw","return"].forEach((function(e){f(t,e,(function(t){return this._invoke(e,t)}))}))}function _(t,e){function n(a,i,c,u){var l=d(t[a],t,i);if("throw"!==l.type){var s=l.arg,f=s.value;return f&&"object"==o(f)&&r.call(f,"__await")?e.resolve(f.__await).then((function(t){n("next",t,c,u)}),(function(t){n("throw",t,c,u)})):e.resolve(f).then((function(t){s.value=t,c(s)}),(function(t){return n("throw",t,c,u)}))}u(l.arg)}var a;i(this,"_invoke",{value:function(t,r){function o(){return new e((function(e,o){n(t,r,e,o)}))}return a=a?a.then(o,o):o()}})}function k(e,n,r){var o=p;return function(a,i){if(o===m)throw new Error("Generator is already running");if(o===v){if("throw"===a)throw i;return{value:t,done:!0}}for(r.method=a,r.arg=i;;){var c=r.delegate;if(c){var u=T(c,r);if(u){if(u===b)continue;return u}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(o===p)throw o=v,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);o=m;var l=d(e,n,r);if("normal"===l.type){if(o=r.done?v:y,l.arg===b)continue;return{value:l.arg,done:r.done}}"throw"===l.type&&(o=v,r.method="throw",r.arg=l.arg)}}}function T(e,n){var r=n.method,o=e.iterator[r];if(o===t)return n.delegate=null,"throw"===r&&e.iterator.return&&(n.method="return",n.arg=t,T(e,n),"throw"===n.method)||"return"!==r&&(n.method="throw",n.arg=new TypeError("The iterator does not provide a '"+r+"' method")),b;var a=d(o,e.iterator,n.arg);if("throw"===a.type)return n.method="throw",n.arg=a.arg,n.delegate=null,b;var i=a.arg;return i?i.done?(n[e.resultName]=i.value,n.next=e.nextLoc,"return"!==n.method&&(n.method="next",n.arg=t),n.delegate=null,b):i:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,b)}function P(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function N(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function F(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(P,this),this.reset(!0)}function A(e){if(e||""===e){var n=e[u];if(n)return n.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var a=-1,i=function n(){for(;++a=0;--a){var i=this.tryEntries[a],c=i.completion;if("root"===i.tryLoc)return o("end");if(i.tryLoc<=this.prev){var u=r.call(i,"catchLoc"),l=r.call(i,"finallyLoc");if(u&&l){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--e){var n=this.tryEntries[e];if(n.finallyLoc===t)return this.complete(n.completion,n.afterLoc),N(n),b}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var n=this.tryEntries[e];if(n.tryLoc===t){var r=n.completion;if("throw"===r.type){var o=r.arg;N(n)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(e,n,r){return this.delegate={iterator:A(e),resultName:n,nextLoc:r},"next"===this.method&&(this.arg=t),b}},e}function i(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r)}return n}function c(t){for(var e=1;e=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,i=!0,c=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return i=t.done,t},e:function(t){c=!0,a=t},f:function(){try{i||null==n.return||n.return()}finally{if(c)throw a}}}}function s(t,e,n,r,o,a,i){try{var c=t[a](i),u=c.value}catch(t){return void n(t)}c.done?e(u):Promise.resolve(u).then(r,o)}function f(t){return function(){var e=this,n=arguments;return new Promise((function(r,o){var a=t.apply(e,n);function i(t){s(a,r,o,i,c,"next",t)}function c(t){s(a,r,o,i,c,"throw",t)}i(void 0)}))}}function h(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=n){var r,o,a,i,c=[],u=!0,l=!1;try{if(a=(n=n.call(t)).next,0===e){if(Object(n)!==n)return;u=!1}else for(;!(u=(r=a.call(n)).done)&&(c.push(r.value),c.length!==e);u=!0);}catch(t){l=!0,o=t}finally{try{if(!u&&null!=n.return&&(i=n.return(),Object(i)!==i))return}finally{if(l)throw o}}return c}}(t,e)||d(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function d(t,e){if(t){if("string"==typeof t)return p(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?p(t,e):void 0}}function p(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n0&&"success"!==p&&(0,t.createElement)("div",null,(0,t.createElement)("h4",null,"Step 2: We detected some tables that were not migrated properly:"),(0,t.createElement)("ul",null,x.sort().filter((function(t){return w.tables[t].total>0})).map((function(e){return(0,t.createElement)("li",{key:e},e," -"," ",w.tables[e].total," ","occurences",i&&i[e]&&i[e].status&&(0,t.createElement)("span",null," - Status: ".concat(i[e].status.toUpperCase())))}))),(0,t.createElement)("button",{className:"button button-primary",disabled:p,onClick:function(){var t=f(a().mark((function t(e){var n,r,o,i;return a().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:e.preventDefault(),m(!0),n=x.sort().filter((function(t){return w.tables[t].total>0})),s(n.reduce((function(t,e){return c(c({},t),{},u({},e,{status:"idle"}))}),{})),r=l(n),t.prev=5,i=a().mark((function t(){var e,n,r,i;return a().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:e=o.value,s((function(t){return c(c({},t),{},u({},e,c(c({},t[e]),{},{status:"running"})))})),n=0,r=y;case 3:if(!(n=0;--a){var i=this.tryEntries[a],c=i.completion;if("root"===i.tryLoc)return o("end");if(i.tryLoc<=this.prev){var u=r.call(i,"catchLoc"),l=r.call(i,"finallyLoc");if(u&&l){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--e){var n=this.tryEntries[e];if(n.finallyLoc===t)return this.complete(n.completion,n.afterLoc),N(n),m}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var n=this.tryEntries[e];if(n.tryLoc===t){var r=n.completion;if("throw"===r.type){var o=r.arg;N(n)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(e,n,r){return this.delegate={iterator:A(e),resultName:n,nextLoc:r},"next"===this.method&&(this.arg=t),m}},e}function w(t,e,n,r,o,a,i){try{var c=t[a](i),u=c.value}catch(t){return void n(t)}c.done?e(u):Promise.resolve(u).then(r,o)}function g(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=n){var r,o,a,i,c=[],u=!0,l=!1;try{if(a=(n=n.call(t)).next,0===e){if(Object(n)!==n)return;u=!1}else for(;!(u=(r=a.call(n)).done)&&(c.push(r.value),c.length!==e);u=!0);}catch(t){l=!0,o=t}finally{try{if(!u&&null!=n.return&&(i=n.return(),Object(i)!==i))return}finally{if(l)throw o}}return c}}(t,e)||function(t,e){if(!t)return;if("string"==typeof t)return x(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return x(t,e)}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function x(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);nR>ps@8Q{V;}d*x8I*%_v5kKU&pqu-TG3V&S{Td z)*IEd>!0rWvA;WVrcQp7j-I3w(XSt*zKb*93^)U)!T@@<$m~kdtux>ZI0FX;)aE1!^kWh{2i;f3Ub{m=!gh*oF_b%4`z~ zm+H8GDB;9W(XBJ!4D=Z|)62Q^|A+DUzn|n+&VVy;QVj64T2)ItlJ(ZX!%43V&?l&f n#AU@p3KpdmBUW1R4YUaSK^DMhm=)oH_#c5ngBxeyPZ{_DCiG2! literal 0 HcmV?d00001 diff --git a/static/js/components/Dashboard.js b/static/js/components/Dashboard.js new file mode 100644 index 0000000..d753076 --- /dev/null +++ b/static/js/components/Dashboard.js @@ -0,0 +1,73 @@ +import { createElement, useState, useEffect } from '@wordpress/element'; + +import DisplayData from './DisplayData'; + +const Dashboard = () => { + const [data, setData] = useState(null); + + const fetchData = async () => { + const response = await fetch( + `${wp.ajax.settings.url}?action=get_site_status`, + + { + method: 'POST', + } + ); + + const data = await response.json(); + + setData(data.data); + }; + + useEffect(() => { + fetchData(); + }, []); + + return ( +
+
+ + + + + + + +

Blocksy Migrator

+ +

+ This tool will help those setups where the initial migrator process (from the theme) didn't work well after updating to version 2. +

+
+
+ + {!data && ( + <> +
+

Step 1: Scan database

+ +
+ +
+ +
+ + )} + {data && } +
+ ); +}; + +export default Dashboard; diff --git a/static/js/components/DisplayData.js b/static/js/components/DisplayData.js new file mode 100644 index 0000000..5f6723b --- /dev/null +++ b/static/js/components/DisplayData.js @@ -0,0 +1,461 @@ +import { createElement, useState, useEffect } from '@wordpress/element'; + +import saveAs from '../utils/save-as'; + +let replacements = [ + { + old: 'paletteColor', + new: 'theme-palette-color-', + }, + + { + old: 'var(--color)', + new: 'var(--theme-text-color)', + }, + + { + old: 'buttonInitialColor', + new: 'theme-button-background-initial-color', + }, + + { + old: '--fontFamily', + new: '--theme-font-family', + }, + + { + old: '--linkInitialColor', + new: '--theme-link-initial-color', + }, + + { + old: '--container-width', + new: '--theme-container-width', + }, + + { + old: '--normal-container-max-width', + new: '--theme-normal-container-max-width', + }, + + { + old: '--narrow-container-max-width', + new: '--theme-narrow-container-max-width', + }, + + { + old: '--buttonFontFamily', + new: '--theme-button-font-family', + }, + + { + old: '--buttonFontSize', + new: '--theme-button-font-size', + }, + + { + old: '--buttonFontWeight', + new: '--theme-button-font-weight', + }, + + { + old: '--buttonFontStyle', + new: '--theme-button-font-style', + }, + + { + old: '--buttonLineHeight', + new: '--theme-button-line-height', + }, + + { + old: '--buttonLetterSpacing', + new: '--theme-button-letter-spacing', + }, + + { + old: '--buttonTextTransform', + new: '--theme-button-text-transform', + }, + + { + old: '--buttonTextDecoration', + new: '--theme-button-text-decoration', + }, + + { + old: '--buttonTextInitialColor', + new: '--theme-button-text-initial-color', + }, + + { + old: '--button-border', + new: '--theme-button-border', + }, + + { + old: '--buttonInitialColor', + new: '--theme-button-background-initial-color', + }, + + { + old: '--buttonMinHeight', + new: '--theme-button-min-height', + }, + + { + old: '--buttonBorderRadius', + new: '--theme-button-border-radius', + }, + + { + old: '--button-padding', + new: '--theme-button-padding', + }, + + { + old: '--button-border-hover-color', + new: '--theme-button-border-hover-color', + }, + + { + old: '--buttonTextHoverColor', + new: '--theme-button-text-hover-color', + }, + + { + old: '--buttonHoverColor', + new: '--theme-button-background-hover-color', + }, +]; + +const DisplayData = ({ data }) => { + console.log('here', { data }); + + const [migrationStatus, setMigrationStatus] = useState(null); + const [isMigrating, setIsMigrating] = useState( + // false | true | 'success' + false + ); + + const [isRegenerating, setIsRegenerating] = useState( + // false | true | 'success' + false + ); + + const [tablesData, setTablesData] = useState(null); + + let allTables = data.all_tables.filter( + (table) => + table.indexOf('woocommerce_') === -1 && + table.indexOf('wc_') === -1 && + table.indexOf('nextend2_') === -1 && + table.indexOf('rank_math_') === -1 && + table.indexOf('trp_') === -1 && + table.indexOf('_wf') === -1 && + table.indexOf('yoast') === -1 && + table.indexOf('wpforms') === -1 && + table.indexOf('_e_') === -1 && + table.indexOf('_em_') === -1 && + table.indexOf('wpmailsmtp') === -1 && + table.indexOf('mystickyelement_') === -1 && + table.indexOf('fluentform_') === -1 && + table.indexOf('gglcptch_') === -1 && + table.indexOf('tutor_') === -1 && + table.indexOf('wdp_') === -1 && + table.indexOf('tec_') === -1 && + table.indexOf('bwfan_automation') === -1 && + table.indexOf('bwf_') === -1 && + table.indexOf('glsr_') === -1 && + table.indexOf('actionscheduler_') === -1 + ); + + // allTables = [`${data.prefix}options`]; + + const fetchTablesData = async () => { + setTablesData({ + totalTables: allTables.length, + loadedTables: 0, + + tables: null, + total: 0, + }); + + let finalData = {}; + + for (const tableName of allTables) { + let tableTotal = 0; + + for (const replacement of replacements) { + const response = await fetch( + `${wp.ajax.settings.url}?action=get_table_status&table=${tableName}&old=${replacement.old}&new=${replacement.new}`, + + { + method: 'POST', + } + ); + + const data = await response.json(); + + tableTotal += data.data.result.total; + } + + setTablesData((tablesData) => ({ + ...tablesData, + loadedTables: tablesData.loadedTables + 1, + })); + + finalData[tableName] = { + total: tableTotal, + }; + } + + setTablesData({ + tables: finalData, + total: Object.values(finalData).reduce( + (acc, curr) => acc + curr.total, + 0 + ), + }); + }; + + useEffect(() => { + // fetchTablesData(); + }, []); + + console.log('here', { allTables, tablesData }); + + return ( + <> +
+ {!tablesData && ( + <> +

Step 1: Scan database

+ + + + )} + + {null && JSON.stringify(data, null, 2)} + + {tablesData && !tablesData.tables && ( +
+

+ Step 1: Scanning database... ( + {tablesData.loadedTables} of{' '} + {tablesData.totalTables} tables) +

+ +

+ Please wait, this process may take a while (this + also depends on your server resources) so don't + close this tab/window untill the migration process + will end. +

+
+ )} + + {((tablesData && tablesData.tables && tablesData.total === 0) || + isMigrating === 'success') && ( +
+

+ Congratulations! Your site is properly migrated to + Blocksy 2 🤩 +

+

+ In case you will have any other questions, please + submit a support ticket{' '} + + here + {' '} + and we will assist you. +

+
+ )} + + {tablesData && + tablesData.tables && + tablesData.total > 0 && + isMigrating !== 'success' && ( +
+

+ Step 2: We detected some tables that were not + migrated properly: +

+ +
    + {allTables + .sort() + .filter( + (table) => + tablesData.tables[table].total > 0 + ) + .map((tableName) => { + return ( +
  • + {tableName} -{' '} + { + tablesData.tables[tableName] + .total + }{' '} + occurences + {migrationStatus && + migrationStatus[ + tableName + ] && + migrationStatus[tableName] + .status && ( + + {` - Status: ${migrationStatus[ + tableName + ].status.toUpperCase()}`} + + )} +
  • + ); + })} +
+ + +
+ )} +
+ +
+ + + {null && ( + + )} +
+ + ); +}; + +export default DisplayData; diff --git a/static/js/dashboard.js b/static/js/dashboard.js new file mode 100644 index 0000000..63e986b --- /dev/null +++ b/static/js/dashboard.js @@ -0,0 +1,11 @@ +import { createElement, render } from '@wordpress/element'; +import Dashboard from './components/Dashboard'; + +document.addEventListener('DOMContentLoaded', () => { + if (document.querySelector('.blocksy-migration-wrapper')) { + render( + , + document.querySelector('.blocksy-migration-wrapper') + ); + } +}); diff --git a/static/js/test.ts b/static/js/test.ts new file mode 100644 index 0000000..e69de29 diff --git a/static/js/utils/save-as.js b/static/js/utils/save-as.js new file mode 100644 index 0000000..985afb0 --- /dev/null +++ b/static/js/utils/save-as.js @@ -0,0 +1,202 @@ +/* + * FileSaver.js + * A saveAs() FileSaver implementation. + * + * By Eli Grey, http://eligrey.com + * + * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) + * source : http://purl.eligrey.com/github/FileSaver.js + */ + +// The one and only way of getting global scope in all environments +// https://stackoverflow.com/q/3277182/1008999 +var _global = + typeof window === 'object' && window.window === window + ? window + : typeof self === 'object' && self.self === self + ? self + : typeof global === 'object' && global.global === global + ? global + : this; + +function bom(blob, opts) { + if (typeof opts === 'undefined') opts = { autoBom: false }; + else if (typeof opts !== 'object') { + console.warn('Deprecated: Expected third argument to be a object'); + opts = { autoBom: !opts }; + } + + // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + if ( + opts.autoBom && + /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test( + blob.type + ) + ) { + return new Blob([String.fromCharCode(0xfeff), blob], { + type: blob.type, + }); + } + return blob; +} + +function download(url, name, opts) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = 'blob'; + xhr.onload = function () { + saveAs(xhr.response, name, opts); + }; + xhr.onerror = function () { + console.error('could not download file'); + }; + xhr.send(); +} + +function corsEnabled(url) { + var xhr = new XMLHttpRequest(); + // use sync to avoid popup blocker + xhr.open('HEAD', url, false); + try { + xhr.send(); + } catch (e) {} + return xhr.status >= 200 && xhr.status <= 299; +} + +// `a.click()` doesn't work for all browsers (#465) +function click(node) { + try { + node.dispatchEvent(new MouseEvent('click')); + } catch (e) { + var evt = document.createEvent('MouseEvents'); + evt.initMouseEvent( + 'click', + true, + true, + window, + 0, + 0, + 0, + 80, + 20, + false, + false, + false, + false, + 0, + null + ); + node.dispatchEvent(evt); + } +} + +var saveAs = + // probably in some web worker + typeof window !== 'object' || window !== _global + ? function saveAs() { + /* noop */ + } + : // Use download attribute first if possible (#193 Lumia mobile) + 'download' in HTMLAnchorElement.prototype + ? function saveAs(blob, name, opts) { + var URL = _global.URL || _global.webkitURL; + var a = document.createElement('a'); + name = name || blob.name || 'download'; + + a.download = name; + a.rel = 'noopener'; // tabnabbing + + // TODO: detect chrome extensions & packaged apps + // a.target = '_blank' + + if (typeof blob === 'string') { + // Support regular links + a.href = blob; + if (a.origin !== location.origin) { + corsEnabled(a.href) + ? download(blob, name, opts) + : click(a, (a.target = '_blank')); + } else { + click(a); + } + } else { + // Support blobs + a.href = URL.createObjectURL(blob); + setTimeout(function () { + URL.revokeObjectURL(a.href); + }, 4e4); // 40s + setTimeout(function () { + click(a); + }, 0); + } + } + : // Use msSaveOrOpenBlob as a second approach + 'msSaveOrOpenBlob' in navigator + ? function saveAs(blob, name, opts) { + name = name || blob.name || 'download'; + + if (typeof blob === 'string') { + if (corsEnabled(blob)) { + download(blob, name, opts); + } else { + var a = document.createElement('a'); + a.href = blob; + a.target = '_blank'; + setTimeout(function () { + click(a); + }); + } + } else { + navigator.msSaveOrOpenBlob(bom(blob, opts), name); + } + } + : // Fallback to using FileReader and a popup + function saveAs(blob, name, opts, popup) { + // Open a popup immediately do go around popup blocker + // Mostly only available on user interaction and the fileReader is async so... + popup = popup || open('', '_blank'); + if (popup) { + popup.document.title = popup.document.body.innerText = + 'downloading...'; + } + + if (typeof blob === 'string') return download(blob, name, opts); + + var force = blob.type === 'application/octet-stream'; + var isSafari = + /constructor/i.test(_global.HTMLElement) || _global.safari; + var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); + + if ( + (isChromeIOS || (force && isSafari)) && + typeof FileReader === 'object' + ) { + // Safari doesn't allow downloading of blob URLs + var reader = new FileReader(); + reader.onloadend = function () { + var url = reader.result; + url = isChromeIOS + ? url + : url.replace( + /^data:[^;]*;/, + 'data:attachment/file;' + ); + if (popup) popup.location.href = url; + else location = url; + popup = null; // reverse-tabnabbing #460 + }; + reader.readAsDataURL(blob); + } else { + var URL = _global.URL || _global.webkitURL; + var url = URL.createObjectURL(blob); + if (popup) popup.location = url; + else location.href = url; + popup = null; // reverse-tabnabbing #460 + setTimeout(function () { + URL.revokeObjectURL(url); + }, 4e4); // 40s + } + }; + +module.exports = saveAs; diff --git a/static/sass/main.scss b/static/sass/main.scss new file mode 100644 index 0000000..d4a3226 --- /dev/null +++ b/static/sass/main.scss @@ -0,0 +1,76 @@ +.blocksy-migration-wrapper { + background: #fff; + padding: 40px; + margin-top: 20px; + margin-inline-end: 20px; + font-size: 14px; + line-height: 1.3; + max-width: 1200px; + + h3 { + font-size: 18px; + line-height: 1.4; + margin: 0; + } + + h4 { + font-size: 16px; + line-height: 1.4; + margin: 0 0 15px 0; + } + + p { + font-size: inherit; + line-height: inherit; + + &:last-child { + margin-bottom: 0; + } + } + + .button { + min-height: 35px; + padding: 0 15px; + font-size: 14px; + } + + .ct-migrator-info { + display: grid; + grid-template-columns: min-content 1fr; + align-items: center; + gap: 24px; + margin-bottom: 40px; + + .ct-migrator-info-icon { + display: flex; + align-items: center; + justify-content: center; + width: 50px; + height: 50px; + color: #fff; + background: #1E1E1E; + border-radius: 2px; + } + + p { + margin: 5px 0 0 0; + } + } + + .ct-table-container { + margin-inline: -40px; + padding: 40px; + border-block: 1px solid #efefef; + + ul { + margin: 0 0 30px 0; + } + } + + .ct-buttons-group { + display: flex; + flex-wrap: wrap; + gap: 15px; + margin-top: 40px; + } +} \ No newline at end of file