diff --git a/.github/workflows/build-dev-artifacts.yml b/.github/workflows/build-dev-artifacts.yml
index b13dae1b0..61b2c634a 100755
--- a/.github/workflows/build-dev-artifacts.yml
+++ b/.github/workflows/build-dev-artifacts.yml
@@ -28,7 +28,13 @@ jobs:
run: |
composer install --no-dev --prefer-dist --no-progress
- name: Create zip
- run: npm run dist
+ run: |
+ npm ci
+ CURRENT_VERSION=$(node -p -e "require('./package.json').version")
+ COMMIT_HASH=$(git rev-parse --short HEAD)
+ DEV_VERSION="${CURRENT_VERSION}-dev.${COMMIT_HASH}"
+ npm run grunt version::${DEV_VERSION}
+ npm run dist
- name: Retrieve branch name
id: retrieve-branch-name
run: echo "::set-output name=branch_name::$(REF=${GITHUB_HEAD_REF:-$GITHUB_REF} && echo ${REF#refs/heads/} | sed 's/\//-/g')"
diff --git a/.github/workflows/translations.yml b/.github/workflows/translations.yml
new file mode 100644
index 000000000..b8c2c5f8e
--- /dev/null
+++ b/.github/workflows/translations.yml
@@ -0,0 +1,45 @@
+name: Translations Diff
+
+on:
+ pull_request_review:
+ pull_request:
+ types: [opened, edited, synchronize, ready_for_review]
+ branches:
+ - development
+ - master
+
+jobs:
+ translation:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Base Branch
+ uses: actions/checkout@master
+ with:
+ ref: ${{ github.base_ref }}
+ path: visualizer-base
+ - name: Setup node 22
+ uses: actions/setup-node@v6
+ with:
+ node-version: 22.x
+ - name: Checkout PR Branch (Head)
+ uses: actions/checkout@master
+ with:
+ path: visualizer-head
+ - name: Build POT for PR Branch
+ run: |
+ chmod +x ./visualizer-head/bin/make-pot.sh
+ ./visualizer-head/bin/make-pot.sh ./visualizer-head ./visualizer-head/languages/visualizer.pot
+ ls ./visualizer-head/languages/
+ - name: Build POT for Base Branch
+ run: |
+ ./visualizer-head/bin/make-pot.sh ./visualizer-base ./visualizer-base/languages/visualizer.pot
+ ls ./visualizer-base/languages/
+ - name: Compare POT files
+ uses: Codeinwp/action-i18n-string-reviewer@main
+ with:
+ fail-on-changes: "false"
+ openrouter-key: ${{ secrets.OPEN_ROUTER_API_KEY }}
+ openrouter-model: "google/gemini-2.5-flash"
+ base-pot-file: "visualizer-base/languages/visualizer.pot"
+ target-pot-file: "visualizer-head/languages/visualizer.pot"
+ github-token: ${{ secrets.BOT_TOKEN }}
diff --git a/.wp-env.json b/.wp-env.json
index d4d36aa0b..86dd6cef1 100644
--- a/.wp-env.json
+++ b/.wp-env.json
@@ -1,5 +1,5 @@
{
- "core": "WordPress/WordPress#6.5.0",
+ "core": null,
"phpVersion": "7.4",
"plugins": ["."],
"themes": [],
diff --git a/AGENTS.md b/AGENTS.md
index 5e9877b11..9dbe23c17 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -34,14 +34,31 @@ npm run dev # Watch mode for development
```
### E2E Tests & Environment
+
+Requires Docker to be running. Uses `docker-compose.ci.yml` (MariaDB + WordPress on port 8889).
+
```bash
-npm install # Install root-level JS dependencies
-npm run test:env:start # Start wp-env WordPress environment
-npm run test:env:stop # Stop wp-env
-npm run test:e2e:playwright # Run Playwright E2E tests
-npm run test:e2e:playwright:debug # Playwright UI debug mode
+# 1. Install dependencies
+npm ci
+npx playwright install --with-deps chromium
+composer install --no-dev
+
+# 2. Start the WordPress environment (boots Docker, installs WP, activates plugin)
+DOCKER_FILE=docker-compose.ci.yml bash bin/wp-init.sh
+
+# 3. Run the full Playwright suite
+npm run test:e2e:playwright
+
+# 4. Run a single spec file
+npx wp-scripts test-playwright --config tests/e2e/playwright.config.js tests/e2e/specs/gutenberg-editor.spec.js
+
+# 5. Tear down
+DOCKER_FILE=docker-compose.ci.yml bash bin/wp-down.sh
```
+WordPress is installed at `http://localhost:8889` with credentials `admin` / `password`.
+The `TI_E2E_TESTING` constant is set to `true` in `wp-config.php` by the setup script, which enables test-only code paths in the plugin.
+
---
## Architecture Overview
diff --git a/bin/make-pot.sh b/bin/make-pot.sh
new file mode 100644
index 000000000..af19c5d73
--- /dev/null
+++ b/bin/make-pot.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+# Script to generate POT file via Docker
+# Usage: ./bin/make-pot.sh [plugin-path] [destination-path]
+
+# Set defaults
+PLUGIN_PATH="${1:-.}"
+DESTINATION="${2:-.}"
+
+# Resolve absolute paths
+PLUGIN_PATH="$(cd "$PLUGIN_PATH" 2>/dev/null && pwd)" || {
+ echo "Error: Plugin path '$1' does not exist"
+ exit 1
+}
+
+DESTINATION="$(cd "$(dirname "$DESTINATION")" 2>/dev/null && pwd)/$(basename "$DESTINATION")" || {
+ echo "Error: Unable to resolve destination path"
+ exit 1
+}
+
+# Extract destination filename and directory
+DEST_DIR="$(dirname "$DESTINATION")"
+DEST_FILE="$(basename "$DESTINATION")"
+
+# Ensure destination directory exists
+mkdir -p "$DEST_DIR"
+
+echo "Generating POT file..."
+echo "Plugin Path: $PLUGIN_PATH"
+echo "Destination: $DESTINATION"
+echo ""
+
+# Run Docker container with wp-cli to generate POT
+docker run --user root --rm \
+ --volume "$PLUGIN_PATH:/var/www/html/plugin" \
+ wordpress:cli \
+ bash -c 'php -d memory_limit=512M "$(which wp)" --version --allow-root && wp i18n make-pot plugin ./plugin/languages/'"$DEST_FILE"' --include=admin,includes,libs,assets,views --allow-root --domain=anti-spam'
+
+# Check if the file was created inside the container
+if [ $? -eq 0 ]; then
+ echo ""
+ echo "✓ POT file successfully generated at: $DESTINATION"
+else
+ echo ""
+ echo "✗ Error generating POT file"
+ exit 1
+fi
diff --git a/classes/Visualizer/Gutenberg/Block.php b/classes/Visualizer/Gutenberg/Block.php
index 045819b54..38b479ee9 100644
--- a/classes/Visualizer/Gutenberg/Block.php
+++ b/classes/Visualizer/Gutenberg/Block.php
@@ -186,8 +186,7 @@ public function gutenberg_block_callback( $atts ) {
return '';
}
- // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
- if ( $atts['lazy'] == -1 || $atts['lazy'] == false ) {
+ if ( $atts['lazy'] === '-1' || $atts['lazy'] === false ) {
$atts['lazy'] = 'no';
}
@@ -472,11 +471,12 @@ public function get_visualizer_data( $post ) {
$permissions = get_post_meta( $post_id, Visualizer_Pro::CF_PERMISSIONS, true );
if ( empty( $permissions ) ) {
- $permissions = array( 'permissions' => array(
+ $permissions = array(
+ 'permissions' => array(
'read' => 'all',
'edit' => 'roles',
'edit-specific' => array( 'administrator' ),
- ),
+ ),
);
}
@@ -824,7 +824,7 @@ public function get_permission_data( $data ) {
foreach ( $users as $user ) {
$options[ $i ]['value'] = $user->ID;
$options[ $i ]['label'] = $user->display_name;
- $i++;
+ ++$i;
}
}
break;
@@ -838,7 +838,7 @@ public function get_permission_data( $data ) {
foreach ( get_editable_roles() as $name => $info ) {
$options[ $i ]['value'] = $name;
$options[ $i ]['label'] = $name;
- $i++;
+ ++$i;
}
}
break;
@@ -874,7 +874,7 @@ public function add_rest_query_vars( $args, \WP_REST_Request $request ) {
* @param mixed $value The value to sanitize.
* @return mixed Sanitized value.
*/
- private function sanitize_value( $value ) {
+ public function sanitize_value( $value ) {
if ( is_string( $value ) ) {
return sanitize_text_field( $value );
}
diff --git a/classes/Visualizer/Module.php b/classes/Visualizer/Module.php
index 8ef678c16..9762a56e2 100644
--- a/classes/Visualizer/Module.php
+++ b/classes/Visualizer/Module.php
@@ -68,8 +68,7 @@ public function __construct( Visualizer_Plugin $plugin ) {
$this->_addFilter( Visualizer_Plugin::FILTER_HANDLE_REVISIONS, 'handleExistingRevisions', 10, 2 );
$this->_addFilter( Visualizer_Plugin::FILTER_GET_CHART_DATA_AS, 'getDataAs', 10, 3 );
$this->_addAction( 'pre_get_posts', 'PreGetPosts' );
- register_shutdown_function( array($this, 'onShutdown') );
-
+ register_shutdown_function( array( $this, 'onShutdown' ) );
}
/**
@@ -114,16 +113,16 @@ protected function _addAction( $tag, $method, $methodClass = null, $priority = 1
* @param string $tag The name of the AJAX action to which the $method is hooked.
* @param string $method Optional. The name of the method to be called. If the name of the method is not provided, tag name will be used as method name.
* @param bool $methodClass The root of the method.
- * @param boolean $private Optional. Determines if we should register hook for logged in users.
- * @param boolean $public Optional. Determines if we should register hook for not logged in users.
+ * @param boolean $logged_in Optional. Determines if we should register hook for logged in users.
+ * @param boolean $logged_out Optional. Determines if we should register hook for not logged in users.
* @return Visualizer_Module
*/
- protected function _addAjaxAction( $tag, $method = '', $methodClass = null, $private = true, $public = false ) {
- if ( $private ) {
+ protected function _addAjaxAction( $tag, $method = '', $methodClass = null, $logged_in = true, $logged_out = false ) {
+ if ( $logged_in ) {
$this->_addAction( 'wp_ajax_' . $tag, $method, $methodClass );
}
- if ( $public ) {
+ if ( $logged_out ) {
$this->_addAction( 'wp_ajax_nopriv_' . $tag, $method, $methodClass );
}
@@ -169,7 +168,7 @@ protected function _addShortcode( $tag, $method ) {
*
* @since 3.2.0
*/
- public function getDataAs( $final, $chart_id, $type ) {
+ public function getDataAs( $data, $chart_id, $type ) {
return $this->_getDataAs( $chart_id, $type );
}
@@ -270,12 +269,15 @@ private function _getCSV( $rows, $filename, $enclose ) {
$bom = chr( 0xEF ) . chr( 0xBB ) . chr( 0xBF );
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
$fp = function_exists( 'tmpfile' ) ? @tmpfile() : null;
- if ( null === $fp ) {
+ if ( ! $fp ) {
if ( ! function_exists( 'wp_tempnam' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
$fp = fopen( wp_tempnam(), 'w+' );
}
+ if ( ! $fp ) {
+ return array( 'csv' => '', 'name' => $filename, 'string' => '' );
+ }
if ( ! apply_filters( 'vizualizer_export_include_series_type', true ) ) {
unset( $rows[1] );
$rows = array_values( $rows );
@@ -287,8 +289,8 @@ private function _getCSV( $rows, $filename, $enclose ) {
}
rewind( $fp );
$csv = '';
- // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
- while ( ( $array = fgetcsv( $fp ) ) !== false ) {
+ $array = fgetcsv( $fp );
+ while ( $array !== false ) {
if ( strlen( $csv ) > 0 ) {
$csv .= PHP_EOL;
}
@@ -304,7 +306,8 @@ private function _getCSV( $rows, $filename, $enclose ) {
}
$array = $temp_array;
}
- $csv .= implode( ',', $array );
+ $csv .= implode( ',', $array );
+ $array = fgetcsv( $fp );
}
fclose( $fp );
@@ -330,7 +333,7 @@ private function _getExcel( $rows, $filename ) {
unset( $rows[1] );
$rows = array_values( $rows );
$rows = array_map(
- function( $r ) {
+ function ( $r ) {
return array_map( 'strval', $r );
},
$rows
@@ -402,7 +405,7 @@ private function _getHTML( $rows ) {
foreach ( $rows as $row ) {
// skip the data type row.
if ( 1 === $index ) {
- $index++;
+ ++$index;
continue;
}
@@ -415,7 +418,7 @@ private function _getHTML( $rows ) {
}
}
$table .= '';
- $index++;
+ ++$index;
}
$table .= '';
@@ -430,9 +433,9 @@ private function _getHTML( $rows ) {
/**
* Disable revisions temporarily for visualizer post type.
*/
- protected final function disableRevisionsTemporarily() {
+ final protected function disableRevisionsTemporarily() {
add_filter(
- 'wp_revisions_to_keep', function( $num, $post ) {
+ 'wp_revisions_to_keep', function ( $num, $post ) {
if ( $post->post_type === Visualizer_Plugin::CPT_VISUALIZER ) {
return 0;
}
@@ -446,7 +449,7 @@ protected final function disableRevisionsTemporarily() {
*
* @return bool If any revisions were found.
*/
- public final function undoRevisions( $chart_id, $restore = false ) {
+ final public function undoRevisions( $chart_id, $restore = false ) {
do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'undoRevisions for %d with%s restore', $chart_id, ( $restore ? '' : 'out' ) ), 'debug', __FILE__, __LINE__ );
if ( get_post_type( $chart_id ) !== Visualizer_Plugin::CPT_VISUALIZER ) {
return false;
@@ -478,7 +481,7 @@ public final function undoRevisions( $chart_id, $restore = false ) {
/**
* If existing revisions exist for the chart, restore the earliest version and then create a new revision to initiate editing.
*/
- public final function handleExistingRevisions( $chart_id, $chart ) {
+ final public function handleExistingRevisions( $chart_id, $chart ) {
do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'handleExistingRevisions for %d', $chart_id ), 'debug', __FILE__, __LINE__ );
if ( get_post_type( $chart_id ) !== Visualizer_Plugin::CPT_VISUALIZER ) {
@@ -529,50 +532,53 @@ protected function get_user_customization_js() {
return $default;
}
- require_once( ABSPATH . 'wp-admin/includes/file.php' );
- WP_Filesystem();
- global $wp_filesystem;
- if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base' ) ) {
- $creds = request_filesystem_credentials( site_url() );
- wp_filesystem( $creds );
- }
-
- $multisite_arg = '/';
- if ( is_multisite() && ! is_main_site() ) {
- $multisite_arg = '/sites/' . get_current_blog_id() . '/';
- }
+ try {
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ WP_Filesystem();
+ global $wp_filesystem;
+ if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base' ) ) {
+ return $default;
+ }
- $dir = $wp_filesystem->wp_content_dir() . 'uploads' . $multisite_arg . 'visualizer';
- $file = $wp_filesystem->wp_content_dir() . 'uploads' . $multisite_arg . 'visualizer/customization.js';
+ $multisite_arg = '/';
+ if ( is_multisite() && ! is_main_site() ) {
+ $multisite_arg = '/sites/' . get_current_blog_id() . '/';
+ }
- if ( $wp_filesystem->is_readable( $file ) ) {
- return $specific;
- }
+ $dir = $wp_filesystem->wp_content_dir() . 'uploads' . $multisite_arg . 'visualizer';
+ $file = $wp_filesystem->wp_content_dir() . 'uploads' . $multisite_arg . 'visualizer/customization.js';
- if ( $wp_filesystem->exists( $file ) && ! $wp_filesystem->is_readable( $file ) ) {
- do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to read file %s', $file ), 'error', __FILE__, __LINE__ );
- return $default;
- }
+ if ( $wp_filesystem->is_readable( $file ) ) {
+ return $specific;
+ }
- if ( ! $wp_filesystem->exists( $dir ) ) {
- // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
- if ( ( $done = $wp_filesystem->mkdir( $dir ) ) === false ) {
- do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to create directory %s', $dir ), 'error', __FILE__, __LINE__ );
+ if ( $wp_filesystem->exists( $file ) && ! $wp_filesystem->is_readable( $file ) ) {
+ do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to read file %s', $file ), 'error', __FILE__, __LINE__ );
return $default;
}
- }
- // if file does not exist, copy.
- if ( ! $wp_filesystem->exists( $file ) ) {
- $src = str_replace( ABSPATH, $wp_filesystem->abspath(), VISUALIZER_ABSPATH . '/js/customization.js' );
- // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
- if ( ( $done = $wp_filesystem->copy( $src, $file ) ) === false ) {
- do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to copy file %s to %s', $src, $file ), 'error', __FILE__, __LINE__ );
- return $default;
+ if ( ! $wp_filesystem->exists( $dir ) ) {
+ $done = $wp_filesystem->mkdir( $dir );
+ if ( $done === false ) {
+ do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to create directory %s', $dir ), 'error', __FILE__, __LINE__ );
+ return $default;
+ }
+ }
+
+ // if file does not exist, copy.
+ if ( ! $wp_filesystem->exists( $file ) ) {
+ $src = str_replace( ABSPATH, $wp_filesystem->abspath(), VISUALIZER_ABSPATH . '/js/customization.js' );
+ $done = $wp_filesystem->copy( $src, $file );
+ if ( $done === false ) {
+ do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Unable to copy file %s to %s', $src, $file ), 'error', __FILE__, __LINE__ );
+ return $default;
+ }
}
- }
- return $specific;
+ return $specific;
+ } catch ( \Throwable $e ) {
+ return $default;
+ }
}
/**
@@ -582,10 +588,7 @@ protected function load_chart_type( $chart_id ) {
$name = $this->load_chart_class_name( $chart_id );
$class = null;
if ( class_exists( $name ) || true === apply_filters( 'visualizer_load_chart', false, $name ) ) {
- if ( 'Visualizer_Render_Sidebar_Type_DataTable_DataTable' === $name ) {
- $name = 'Visualizer_Render_Sidebar_Type_DataTable_Tabular';
- }
- $class = new $name;
+ $class = new $name();
}
if ( is_null( $class ) && Visualizer_Module::is_pro() ) {
@@ -746,7 +749,7 @@ public static function can_show_feature( $feature ) {
/**
* Gets the features for the provided license type.
*/
- public static final function get_features_for_license( $plan ) {
+ final public static function get_features_for_license( $plan ) {
$is_new_personal = apply_filters( 'visualizer_is_new_personal', false );
switch ( $plan ) {
case 1:
diff --git a/classes/Visualizer/Module/AMP.php b/classes/Visualizer/Module/AMP.php
index b9b3a079a..9b5977140 100644
--- a/classes/Visualizer/Module/AMP.php
+++ b/classes/Visualizer/Module/AMP.php
@@ -100,5 +100,4 @@ public function get_chart( $chart, $data, $series, $settings ) {
}
return $output['csv'];
}
-
}
diff --git a/classes/Visualizer/Module/Admin.php b/classes/Visualizer/Module/Admin.php
index 2c02da8d0..02a3f16db 100644
--- a/classes/Visualizer/Module/Admin.php
+++ b/classes/Visualizer/Module/Admin.php
@@ -96,7 +96,6 @@ public function __construct( Visualizer_Plugin $plugin ) {
if ( defined( 'TI_E2E_TESTING' ) ) {
$this->load_cypress_hooks();
}
-
}
/**
* Display review notice.
@@ -104,7 +103,7 @@ public function __construct( Visualizer_Plugin $plugin ) {
public function render_review_notice( $footer_text ) {
$current_screen = get_current_screen();
- $visualizer_page_ids = ['toplevel_page_visualizer', 'visualizer_page_viz-support', 'visualizer_page_ti-about-visualizer' ];
+ $visualizer_page_ids = array( 'toplevel_page_visualizer', 'visualizer_page_viz-support', 'visualizer_page_ti-about-visualizer' );
if ( ! empty( $current_screen ) && isset( $current_screen->id ) ) {
foreach ( $visualizer_page_ids as $page_to_check ) {
@@ -134,7 +133,7 @@ public function render_review_notice( $footer_text ) {
private function load_cypress_hooks() {
// all charts should load on the same page without pagination.
add_filter(
- 'visualizer_query_args', function( $args ) {
+ 'visualizer_query_args', function ( $args ) {
$args['posts_per_page'] = 20;
return $args;
}, 10, 1
@@ -253,8 +252,7 @@ public function restoreRevision( $post_id, $revision_id ) {
* @access public
*/
public function init() {
- // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
- if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) && 'true' == get_user_option( 'rich_editing' ) ) {
+ if ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) && 'true' === get_user_option( 'rich_editing' ) ) {
$this->_addFilter( 'mce_external_languages', 'add_tinymce_lang', 10, 1 );
$this->_addFilter( 'mce_external_plugins', 'tinymce_plugin', 10, 1 );
$this->_addFilter( 'mce_buttons', 'register_mce_button', 10, 1 );
@@ -268,7 +266,7 @@ public function init() {
* @since ?
* @access friendly
*/
- function get_strings_for_block( $settings ) {
+ public function get_strings_for_block( $settings ) {
$class = new Visualizer_Module_Language();
$strings = $class->get_strings();
$array = array( 'visualizer_tinymce_plugin' => json_encode( $strings ) );
@@ -797,7 +795,7 @@ public function handleGetProSubMenu() {
/**
* Adds the screen options for pagination.
*/
- function addScreenOptions() {
+ public function addScreenOptions() {
$screen = get_current_screen();
// bail if it's some other page.
@@ -816,7 +814,7 @@ function addScreenOptions() {
/**
* Returns the screen option for pagination.
*/
- function setScreenOptions( $status, $option, $value ) {
+ public function setScreenOptions( $status, $option, $value ) {
if ( 'visualizer_library_per_page' === $option ) {
return $value;
}
diff --git a/classes/Visualizer/Module/Chart.php b/classes/Visualizer/Module/Chart.php
index e31133b1a..4c5038b6b 100644
--- a/classes/Visualizer/Module/Chart.php
+++ b/classes/Visualizer/Module/Chart.php
@@ -70,7 +70,6 @@ public function __construct( Visualizer_Plugin $plugin ) {
$this->_addAjaxAction( Visualizer_Plugin::ACTION_SAVE_FILTER_QUERY, 'saveFilter' );
$this->_addFilter( 'visualizer_get_sidebar', 'getSidebar', 10, 2 );
-
}
/**
@@ -539,7 +538,8 @@ public function renderChartPages() {
if ( ! empty( $_POST ) ) {
$_POST = map_deep( $_POST, 'wp_strip_all_tags' );
}
- if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
+ $chart = $chart_id ? get_post( $chart_id ) : null;
+ if ( ! $chart_id || ! $chart || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) {
if ( empty( $_GET['lang'] ) || empty( $_GET['parent_chart_id'] ) ) {
$this->deleteOldCharts();
$default_type = isset( $_GET['type'] ) && ! empty( $_GET['type'] ) ? $_GET['type'] : 'line';
@@ -738,7 +738,7 @@ private function loadCodeEditorAssets( $chart_id ) {
wp_register_script( 'visualizer-codemirror-closebrackets', '//codemirror.net/addon/edit/closebrackets.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
wp_register_script( 'visualizer-codemirror-sql', '//codemirror.net/mode/sql/sql.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
wp_register_script( 'visualizer-codemirror-sql-hint', '//codemirror.net/addon/hint/sql-hint.js', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
- wp_register_script( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.js', array( 'visualizer-codemirror-sql', 'visualizer-codemirror-sql-hint', 'visualizer-codemirror-placeholder', 'visualizer-codemirror-matchbrackets', 'visualizer-codemirror-closebrackets' ), Visualizer_Plugin::VERSION );
+ wp_register_script( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.js', array( 'visualizer-codemirror-sql', 'visualizer-codemirror-sql-hint', 'visualizer-codemirror-placeholder', 'visualizer-codemirror-matchbrackets', 'visualizer-codemirror-closebrackets' ), Visualizer_Plugin::VERSION );
wp_register_style( 'visualizer-codemirror-core', '//codemirror.net/lib/codemirror.css', array(), Visualizer_Plugin::VERSION );
wp_register_style( 'visualizer-codemirror-hint', '//codemirror.net/addon/hint/show-hint.css', array( 'visualizer-codemirror-core' ), Visualizer_Plugin::VERSION );
@@ -862,10 +862,8 @@ private function _handleDataAndSettingsPage() {
array(
'ajax' => array(
'url' => admin_url( 'admin-ajax.php' ),
- 'nonces' => array(
- ),
- 'actions' => array(
- ),
+ 'nonces' => array(),
+ 'actions' => array(),
),
)
);
@@ -878,12 +876,13 @@ private function _handleDataAndSettingsPage() {
'visualizer',
array(
'l10n' => array(
- 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
- 'loading' => esc_html__( 'Loading...', 'visualizer' ),
- 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
- 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
- 'save_settings' => __( 'You have modified the chart\'s settings. To modify the source/data again, you must save this chart and reopen it for editing. If you continue without saving the chart, you may lose your changes.', 'visualizer' ),
- 'copied' => __( 'The data has been copied to your clipboard. Hit Ctrl-V/Cmd-V in your spreadsheet editor to paste the data.', 'visualizer' ),
+ 'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
+ 'loading' => esc_html__( 'Loading...', 'visualizer' ),
+ 'json_error' => esc_html__( 'An error occured in fetching data.', 'visualizer' ),
+ 'select_columns' => esc_html__( 'Please select a few columns to include in the chart.', 'visualizer' ),
+ 'save_settings' => __( 'You have modified the chart\'s settings. To modify the source/data again, you must save this chart and reopen it for editing. If you continue without saving the chart, you may lose your changes.', 'visualizer' ),
+ 'copied' => __( 'The data has been copied to your clipboard. Hit Ctrl-V/Cmd-V in your spreadsheet editor to paste the data.', 'visualizer' ),
+ 'invalid_format' => esc_html__( 'This format pattern is not supported in the series settings field. Use the Manual Configuration option instead.', 'visualizer' ),
),
'charts' => array(
'canvas' => $data,
@@ -1028,7 +1027,7 @@ private function handleCSVasString( $data, $editor_type ) {
}
$row = explode( ',', $row );
$row = array_map(
- function( $r ) {
+ function ( $r ) {
return '' === $r ? ' ' : $r;
},
$row
@@ -1079,7 +1078,7 @@ private function handleTabularData() {
if ( empty( $type ) ) {
$exclude[] = $index;
}
- $index++;
+ ++$index;
}
// when N headers are being renamed, the number of headers increases by N
@@ -1156,9 +1155,10 @@ public function uploadData() {
// check chart, if chart exists
// do not use filter_input as it does not work for phpunit test cases, use filter_var instead
$chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
+ $chart = $chart_id ? get_post( $chart_id ) : null;
if (
! $chart_id ||
- ! ( $chart = get_post( $chart_id ) ) ||
+ ! $chart ||
$chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ||
! current_user_can( 'edit_post', $chart_id )
) {
@@ -1214,8 +1214,7 @@ public function uploadData() {
if ( isset( $_POST['vz-import-time'] ) ) {
apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $remote_data, $_POST['vz-import-time'] );
}
- // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
- } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] == 0 ) {
+ } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] === 0 ) {
$source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
} elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
$source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
@@ -1407,7 +1406,8 @@ private function _handleDataPage() {
array(
'l10n' => array(
'invalid_source' => esc_html__( 'You have entered an invalid URL. Please provide a valid URL.', 'visualizer' ),
- 'loading' => esc_html__( 'Loading...', 'visualizer' ),
+ 'loading' => esc_html__( 'Loading...', 'visualizer' ),
+ 'invalid_format' => esc_html__( 'This format pattern is not supported in the series settings field. To display percentages, use the Manual Configuration option instead.', 'visualizer' ),
),
'charts' => array(
'canvas' => $data,
diff --git a/classes/Visualizer/Module/Frontend.php b/classes/Visualizer/Module/Frontend.php
index c8cb044bc..1905165e6 100644
--- a/classes/Visualizer/Module/Frontend.php
+++ b/classes/Visualizer/Module/Frontend.php
@@ -87,7 +87,7 @@ public function __construct( Visualizer_Plugin $plugin ) {
/**
* Adds the async attribute to certain scripts.
*/
- function script_loader_tag( $tag, $handle, $src ) {
+ public function script_loader_tag( $tag, $handle, $src ) {
if ( is_admin() ) {
return $tag;
}
@@ -98,7 +98,7 @@ function script_loader_tag( $tag, $handle, $src ) {
$tag = str_replace( ' src', ' async src', $tag );
break;
}
- };
+ }
// Async scripts.
$scripts = array( 'dom-to-image' );
@@ -124,7 +124,7 @@ function script_loader_tag( $tag, $handle, $src ) {
/**
* Returns the language/locale.
*/
- function getLanguage( $dummy, $only_language ) {
+ public function getLanguage( $dummy, $only_language ) {
return $this->get_language();
}
@@ -132,7 +132,7 @@ function getLanguage( $dummy, $only_language ) {
/**
* Registers the endpoints
*/
- function endpoint_register() {
+ public function endpoint_register() {
register_rest_route(
'visualizer/v' . VISUALIZER_REST_VERSION,
'/action/(?P ' . $msg . '