PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.6
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.6
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 3.10.4 All 148 releases
visualizer / classes / Visualizer / Render / Library.php

Library.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 4.0.6, at classes/Visualizer/Render/Library.php

575 lines 28.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // +----------------------------------------------------------------------+
3 // | Copyright 2013 Madpixels (email : visualizer@madpixels.net) |
4 // +----------------------------------------------------------------------+
5 // | This program is free software; you can redistribute it and/or modify |
6 // | it under the terms of the GNU General Public License, version 2, as |
7 // | published by the Free Software Foundation. |
8 // | |
9 // | This program is distributed in the hope that it will be useful, |
10 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 // | GNU General Public License for more details. |
13 // | |
14 // | You should have received a copy of the GNU General Public License |
15 // | along with this program; if not, write to the Free Software |
16 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
17 // | MA 02110-1301 USA |
18 // +----------------------------------------------------------------------+
19 // | Author: Eugene Manuilov <eugene@manuilov.org> |
20 // +----------------------------------------------------------------------+
21 /**
22 * Renders visualizer library page.
23 *
24 * @category Visualizer
25 * @package Render
26 *
27 * @since 1.0.0
28 */
29 class Visualizer_Render_Library extends Visualizer_Render {
30
31 /**
32 * Cached result of _isListView() to avoid repeat DB reads per request.
33 *
34 * @var bool|null
35 */
36 private $_list_view_cached = null;
37
38 /**
39 * Renders library page.
40 *
41 * @since 1.0.0
42 *
43 * @access protected
44 */
45 protected function _toHTML() {
46 echo '<div class="wrap">';
47 echo '<div id="visualizer-icon" class="icon32"><br></div>';
48 echo '<h2>';
49 esc_html_e( 'Visualizer Library', 'visualizer' );
50 echo ' <div class="viz-add-new-group">';
51 echo '<a href="javascript:;" class="add-new-h2 add-new-chart">', esc_html__( 'Add New Chart', 'visualizer' ), '</a>';
52 echo '<button type="button" class="viz-add-new-toggle" aria-haspopup="true" aria-expanded="false"><span class="dashicons dashicons-arrow-down-alt2"></span></button>';
53 echo '<div class="viz-add-new-menu" role="menu" aria-hidden="true">';
54 echo '<button type="button" class="viz-add-new-item" data-viz-builder="ai">', esc_html__( 'AI Chart Builder', 'visualizer' ), '</button>';
55 echo '<button type="button" class="viz-add-new-item" data-viz-builder="classic">', esc_html__( 'Classic Builder', 'visualizer' ), '</button>';
56 echo '</div>';
57 if ( Visualizer_Module::is_pro() ) {
58 echo ' <a href="' . admin_url( 'options-general.php#visualizer_pro_license' ) . '" class="page-title-action">', esc_html__( 'License Settings', 'visualizer' ), '</a>';
59 }
60 echo '</div>';
61 echo '</h2>';
62 $this->_renderMessages();
63 $this->_renderLibrary();
64 echo '<div id="viz-chart-builder-root"></div>';
65 echo '</div>';
66 }
67
68 /**
69 * Renders notification messages if need be.
70 *
71 * @since 1.4.2
72 *
73 * @access private
74 */
75 private function _renderMessages() {
76 if ( ! filter_var( ini_get( 'allow_url_fopen' ), FILTER_VALIDATE_BOOLEAN ) ) {
77 echo '<div class="updated error">';
78 echo '<p>';
79 printf(
80 // translators: %s - the name of the option.
81 esc_html__( '%s option is disabled in your php.ini config. Please enable it by changing its value to 1. This option increases the speed of remote CSV uploading.', 'visualizer' ),
82 '<b>allow_url_fopen</b>'
83 );
84 echo '</p>';
85 echo '</div>';
86 }
87 }
88
89 /**
90 * Displays the search form.
91 */
92 private function getDisplayForm() {
93 echo '<div class="visualizer-library-form">
94 <form action="' . admin_url( 'admin.php' ) . '">
95 <input type="hidden" name="page" value="' . Visualizer_Plugin::NAME . '"/>
96 <input type="hidden" name="view" value="' . esc_attr( $this->_isListView() ? 'list' : 'grid' ) . '"/>
97 <span class="viz-view-toggle-group">' . $this->_getViewToggleHTML() . '</span>
98 <select class="viz-filter" name="type">
99 ';
100
101 echo '<option value="" selected>' . __( 'All types', 'visualizer' ) . '</option>';
102
103 $type = isset( $_GET['type'] ) ? $_GET['type'] : '';
104 $enabled = array();
105 $disabled = array();
106 foreach ( $this->types as $id => $array ) {
107 if ( ! is_array( $array ) ) {
108 // support for old pro
109 $array = array( 'enabled' => true, 'name' => $array );
110 }
111 if ( ! $array['enabled'] ) {
112 $disabled[ $id ] = $array['name'];
113 continue;
114 }
115 $enabled[ $id ] = $array['name'];
116 }
117
118 asort( $enabled );
119 asort( $disabled );
120
121 foreach ( $enabled as $id => $name ) {
122 echo '<option value="' . esc_attr( $id ) . '" ' . selected( $type, $id ) . '>' . $name . '</option>';
123 }
124
125 if ( $disabled ) {
126 echo '<optgroup label="' . __( 'Not available', 'visualizer' ) . '">';
127 foreach ( $disabled as $id => $name ) {
128 echo '<option value="' . esc_attr( $id ) . '" ' . selected( $type, $id ) . ' disabled>' . $name . '</option>';
129 }
130 echo '</optgroup>';
131 }
132
133 echo '
134 </select>
135 <select class="viz-filter" name="library">
136 ';
137
138 $libraries = array( '', 'ChartJS', 'DataTable', 'GoogleCharts' );
139 $library = isset( $_GET['library'] ) ? $_GET['library'] : '';
140 foreach ( $libraries as $lib ) {
141 echo '<option value="' . esc_attr( $lib ) . '" ' . selected( $library, $lib ) . '>' . ( $lib === '' ? __( 'All libraries', 'visualizer' ) : $lib ) . '</option>';
142 }
143
144 echo '
145 </select>
146 <select class="viz-filter" name="date">
147 ';
148
149 $dates = Visualizer_Plugin::getSupportedDateFilter();
150 $date = isset( $_GET['date'] ) ? $_GET['date'] : '';
151 foreach ( Visualizer_Plugin::getSupportedDateFilter() as $dt => $label ) {
152 echo '<option value="' . esc_attr( $dt ) . '" ' . selected( $date, $dt ) . '>' . $label . '</option>';
153 }
154
155 echo '
156 </select>
157 <select class="viz-filter" name="source">
158 ';
159
160 $disabled = array();
161 $sources = array( 'json' => __( 'JSON', 'visualizer' ), 'csv' => __( 'Local CSV', 'visualizer' ), 'csv_remote' => __( 'Remote CSV', 'visualizer' ), 'query' => __( 'Database', 'visualizer' ), 'query_wp' => __( 'WordPress', 'visualizer' ) );
162 if ( ! Visualizer_Module::is_pro() ) {
163 $disabled['query'] = $sources['query'];
164 unset( $sources['query'] );
165 }
166 if ( ! apply_filters( 'visualizer_is_business', false ) ) {
167 $disabled['query_wp'] = $sources['query_wp'];
168 unset( $sources['query_wp'] );
169 }
170 $sources = array_filter( $sources );
171 uasort(
172 $sources, function ( $a, $b ) {
173 if ( $a === $b ) {
174 return 0;
175 }
176 return ( $a < $b ) ? -1 : 1;
177 }
178 );
179
180 $source = isset( $_GET['source'] ) ? $_GET['source'] : '';
181 echo '<option value="">' . __( 'All sources', 'visualizer' ) . '</option>';
182 foreach ( $sources as $field => $label ) {
183 echo '<option value="' . esc_attr( $field ) . '" ' . selected( $source, $field ) . '>' . $label . '</option>';
184 }
185
186 if ( $disabled ) {
187 echo '<optgroup label="' . __( 'Not available', 'visualizer' ) . '">';
188 foreach ( $disabled as $id => $name ) {
189 echo '<option value="' . esc_attr( $id ) . '" ' . selected( $type, $id ) . ' disabled>' . $name . '</option>';
190 }
191 echo '</optgroup>';
192 }
193
194 $name = isset( $_GET['s'] ) ? $_GET['s'] : '';
195 echo '
196 </select>
197 <input class="viz-filter" type="text" name="s" placeholder="' . __( 'Enter title', 'visualizer' ) . '" value="' . esc_attr( $name ) . '">
198 ';
199
200 echo '
201 <span class="viz-filter">|</span>
202 <select class="viz-filter" name="orderby">
203 ';
204
205 $order_by_fields = apply_filters( 'visualizer_filter_order_by', array( 'date' => __( 'Date', 'visualizer' ), 's' => __( 'Title', 'visualizer' ) ) );
206 $order_by = isset( $_GET['orderby'] ) ? $_GET['orderby'] : '';
207 echo '<option value="">' . __( 'Order By', 'visualizer' ) . '</option>';
208 foreach ( $order_by_fields as $field => $label ) {
209 echo '<option value="' . esc_attr( $field ) . '" ' . selected( $order_by, $field ) . '>' . $label . '</option>';
210 }
211
212 echo '
213 </select>
214 <select class="viz-filter" name="order">
215 ';
216
217 $order_type = array( 'desc' => __( 'Descending', 'visualizer' ), 'asc' => __( 'Ascending', 'visualizer' ) );
218 $order = isset( $_GET['order'] ) ? $_GET['order'] : 'desc';
219 foreach ( $order_type as $field => $label ) {
220 echo '<option value="' . esc_attr( $field ) . '" ' . selected( $order, $field ) . '>' . $label . '</option>';
221 }
222
223 echo '
224 </select>
225 <input type="submit" class="viz-filter button button-secondary" value="' . __( 'Apply Filters', 'visualizer' ) . '">
226 <input type="button" id="viz-lib-reset" class="viz-filter button button-secondary" value="' . __( 'Clear Filters', 'visualizer' ) . '">
227 </form>
228 </div>';
229 }
230 /**
231 * Renders pro charts blocker.
232 *
233 * @access private
234 */
235 private function _renderProPopupBlocker() {
236 if ( Visualizer_Module::is_pro() ) {
237 return;
238 }
239 $license = get_option( 'visualizer_pro_license_data', 'free' );
240 $license_key = '';
241 $download_id = '';
242 if ( ! empty( $license ) && is_object( $license ) ) {
243 $license_key = isset( $license->key ) ? $license->key : '';
244 $download_id = isset( $license->download_id ) ? $license->download_id : '';
245 }
246 $admin_license_url = admin_url( 'options-general.php#visualizer_pro_license' );
247 $renew_license_url = tsdk_utmify( Visualizer_Plugin::STORE_URL . '?edd_license_key=' . $license_key . '&download_id=' . $download_id, 'visualizer_license_block' );
248 echo '
249 <div class="vizualizer-renew-notice-overlay" id="overlay-visualizer"></div>
250 <div class="vizualizer-renew-notice-popup">
251 <h1 class="vizualizer-renew-notice-heading">Alert!</h1>
252 <p class="vizualizer-renew-notice-message">' . esc_html__( 'In order to edit premium charts, benefit from updates and support for Visualizer Premium plugin, please renew your license code or activate it.', 'visualizer' ) . '</p>
253 <div class="vizualizer-renew-notice-buttons-container">
254 <a href="' . esc_url( $renew_license_url) . '" target="_blank">
255 <button class="vizualizer-renew-notice-button vizualizer-renew-notice-renew-button">
256 <span class="dashicons dashicons-cart"></span>' . esc_html__( 'Renew License', 'visualizer' ) . '
257 </button>
258 </a>
259 <a href="' . esc_url( $admin_license_url ) . '">
260 <button class="vizualizer-renew-notice-button vizualizer-renew-notice-activate-button">
261 <span class="dashicons dashicons-unlock"></span> ' . esc_html__( 'Activate License', 'visualizer' ) . '
262 </button>
263 </a>
264 <button class="vizualizer-renew-notice-button vizualizer-renew-notice-close-icon" aria-label="Close" onclick="closePopup()">
265 <i class="dashicons dashicons-no"></i>
266 </button>
267 </div>
268 </div>
269 <script>
270 function closePopup() {
271 var overlay = document.getElementById("overlay-visualizer");
272 var popup = document.querySelector(".vizualizer-renew-notice-popup");
273 overlay.style.display = "none";
274 popup.style.display = "none";
275 }
276 </script>';
277 }
278 /**
279 * Renders library content.
280 *
281 * @since 1.0.0
282 *
283 * @access private
284 */
285 private function _renderLibrary() {
286
287 // Added by Ash/Upwork
288 $filterBy = ! empty( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
289
290 // Added by Ash/Upwork
291 echo $this->custom_css;
292
293 $this->_renderProPopupBlocker();
294
295 echo '<div id="visualizer-types" class="visualizer-clearfix">';
296 echo '<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><symbol id="list-icon" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8C0 12.42 3.58 16 8 16C12.42 16 16 12.42 16 8C16 3.58 12.42 0 8 0ZM7.385 12.66H6.045L2.805 8.12L4.146 6.87L6.715 9.27L11.856 3.339L13.196 4.279L7.385 12.66Z"/></symbol></svg>';
297 $this->getDisplayForm();
298 echo '</div>';
299 echo '<div id="visualizer-content-wrapper">';
300 echo '<div id="tsdk_banner" class="visualizer-banner"></div>';
301 if ( ! empty( $this->charts ) ) {
302 if ( $this->_isListView() ) {
303 echo '<div id="visualizer-library" class="visualizer-clearfix view-list">';
304 $this->_renderSidebar();
305 echo '<table class="wp-list-table widefat striped viz-charts-table">';
306 echo '<thead><tr>';
307 echo '<th class="col-id">' . esc_html__( 'ID', 'visualizer' ) . '</th>';
308 echo '<th class="col-title">' . esc_html__( 'Title', 'visualizer' ) . '</th>';
309 echo '<th class="col-type">' . esc_html__( 'Type', 'visualizer' ) . '</th>';
310 echo '<th class="col-shortcode">' . esc_html__( 'Shortcode', 'visualizer' ) . '</th>';
311 echo '<th class="col-actions">' . esc_html__( 'Actions', 'visualizer' ) . '</th>';
312 echo '</tr></thead><tbody>';
313 foreach ( $this->charts as $placeholder_id => $chart ) {
314 $enable_controls = false;
315 $settings = isset( $chart['settings'] ) ? $chart['settings'] : array();
316 if ( ! empty( $settings['controls']['controlType'] ) ) {
317 $column_index = $settings['controls']['filterColumnIndex'];
318 $column_label = $settings['controls']['filterColumnLabel'];
319 if ( 'false' !== $column_index || 'false' !== $column_label ) {
320 $enable_controls = true;
321 }
322 }
323 $this->_renderChartBox( $placeholder_id, $chart['id'], $enable_controls );
324 }
325 echo '</tbody></table>';
326 echo '</div>';
327 } else {
328 echo '<div id="visualizer-library" class="visualizer-clearfix view-grid">';
329 $this->_renderSidebar();
330 foreach ( $this->charts as $placeholder_id => $chart ) {
331 $enable_controls = false;
332 $settings = isset( $chart['settings'] ) ? $chart['settings'] : array();
333 if ( ! empty( $settings['controls']['controlType'] ) ) {
334 $column_index = $settings['controls']['filterColumnIndex'];
335 $column_label = $settings['controls']['filterColumnLabel'];
336 if ( 'false' !== $column_index || 'false' !== $column_label ) {
337 $enable_controls = true;
338 }
339 }
340 $this->_renderChartBox( $placeholder_id, $chart['id'], $enable_controls );
341 }
342 echo '</div>';
343 }
344 } else {
345 echo '<div id="visualizer-library" class="visualizer-clearfix view-grid">';
346 $this->_renderSidebar();
347 echo '<div class="items"><div class="visualizer-chart">';
348 echo '<div class="visualizer-chart-canvas visualizer-nochart-canvas">';
349 echo '<div class="visualizer-notfound">';
350 echo esc_html__( 'No charts found. Click \'Add New Chart\' to create one.', 'visualizer' );
351 echo '<p><h2>';
352 echo '<div class="viz-add-new-group">';
353 echo '<a href="javascript:;" class="add-new-h2 add-new-chart">', esc_html__( 'Add New Chart', 'visualizer' ), '</a>';
354 echo '<button type="button" class="viz-add-new-toggle" aria-haspopup="true" aria-expanded="false"><span class="dashicons dashicons-arrow-down-alt2"></span></button>';
355 echo '<div class="viz-add-new-menu" role="menu" aria-hidden="true">';
356 echo '<button type="button" class="viz-add-new-item" data-viz-builder="ai">', esc_html__( 'AI Chart Builder', 'visualizer' ), '</button>';
357 echo '<button type="button" class="viz-add-new-item" data-viz-builder="classic">', esc_html__( 'Classic Builder', 'visualizer' ), '</button>';
358 echo '</div>';
359 echo '</div>';
360 echo '</h2></p></div>';
361 echo '</div>';
362 echo '<div class="visualizer-chart-footer visualizer-clearfix">';
363 echo '<div class="visualizer-action-group visualizer-nochart">';
364 echo '<span class="visualizer-chart-action visualizer-nochart-delete"><span class="dashicons dashicons-trash"></span></span>';
365 echo '<span class="visualizer-chart-action visualizer-nochart-shortcode"><span class="dashicons dashicons-shortcode"></span></span>';
366 echo '<span class="visualizer-chart-action visualizer-nochart-image"><span class="dashicons dashicons-format-image"></span></span>';
367 echo '<span class="visualizer-chart-action visualizer-nochart-export"><span class="dashicons dashicons-download"></span></span>';
368 echo '<span class="visualizer-chart-action visualizer-nochart-clone"><span class="dashicons dashicons-admin-page"></span></span>';
369 echo '<span class="visualizer-chart-action visualizer-nochart-edit"><span class="dashicons dashicons-edit"></span></span>';
370 echo '</div>';
371 echo '</div>';
372 echo '</div></div>';
373 echo '</div>';
374 }
375 echo '</div>';
376 if ( is_array( $this->pagination ) ) {
377 echo '<ul class=" subsubsub">';
378 foreach ( $this->pagination as $page ) {
379 echo '<li class="all">', $page, '</li>';
380 }
381 echo '</ul>';
382 }
383 }
384
385 /**
386 * Renders chart's box block.
387 *
388 * @since 1.0.0
389 *
390 * @access private
391 *
392 * @param string $placeholder_id The placeholder's id for the chart.
393 * @param int $chart_id The id of the chart.
394 */
395 private function _renderChartBox( $placeholder_id, $chart_id, $with_filter = false ) {
396 $settings = get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS );
397 $chart_library = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, true );
398 $title = '#' . $chart_id;
399 if ( ! empty( $settings[0]['title'] ) ) {
400 $title = $settings[0]['title'];
401 }
402 // for ChartJS, title is an array.
403 if ( is_array( $title ) && isset( $title['text'] ) ) {
404 $title = $title['text'];
405 }
406 if ( ! empty( $settings[0]['backend-title'] ) ) {
407 $title = $settings[0]['backend-title'];
408 }
409 if ( empty( $title ) ) {
410 $title = '#' . $chart_id;
411 }
412
413 $ajax_url = admin_url( 'admin-ajax.php' );
414 $delete_url = esc_url(
415 add_query_arg(
416 array(
417 'action' => Visualizer_Plugin::ACTION_DELETE_CHART,
418 'nonce' => wp_create_nonce(),
419 'chart' => $chart_id,
420 ),
421 $ajax_url
422 )
423 );
424 $clone_url = esc_url(
425 add_query_arg(
426 array(
427 'action' => Visualizer_Plugin::ACTION_CLONE_CHART,
428 'nonce' => wp_create_nonce( Visualizer_Plugin::ACTION_CLONE_CHART ),
429 'chart' => $chart_id,
430 'type' => $this->type,
431 ),
432 $ajax_url
433 )
434 );
435 $export_link = esc_url(
436 add_query_arg(
437 array(
438 'action' => Visualizer_Plugin::ACTION_EXPORT_DATA,
439 'chart' => $chart_id,
440 'security' => wp_create_nonce( Visualizer_Plugin::ACTION_EXPORT_DATA . Visualizer_Plugin::VERSION ),
441 ),
442 admin_url( 'admin-ajax.php' )
443 )
444 );
445 $chart_type = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, true );
446 $chart_type_label = $chart_type;
447 if ( empty( $chart_type_label ) && 'd3' === strtolower( (string) $chart_library ) ) {
448 $chart_type_label = __( 'AI', 'visualizer' );
449 }
450
451 $types = array( 'area', 'geo', 'column', 'bubble', 'scatter', 'gauge', 'candlestick', 'timeline', 'combo', 'polarArea', 'radar' );
452
453 $pro_class = '';
454
455 if ( ! empty( $chart_type ) && in_array( $chart_type, $types, true ) ) {
456 $pro_class = 'viz-is-pro-chart';
457 }
458
459 $chart_status = array( 'date' => get_the_modified_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $chart_id ), 'error' => get_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, true ), 'icon' => 'dashicons-yes-alt', 'title' => 'A-OK!' );
460 if ( ! empty( $chart_status['error'] ) ) {
461 $chart_status['icon'] = 'error dashicons-dismiss';
462 $chart_status['title'] = __( 'Click to view the error', 'visualizer' );
463 }
464 $shortcode = sprintf( '[visualizer id="%s" class=""]', $chart_id );
465
466 if ( $this->_isListView() ) {
467 // ── List view: table row ──
468 echo '<tr class="viz-list-row">';
469 echo '<td class="col-id">#' . esc_html( (string) $chart_id ) . '</td>';
470 echo '<td class="col-title">' . esc_html( $title ) . '</td>';
471 echo '<td class="col-type">' . ( ! empty( $chart_type_label ) ? '<span class="viz-chart-type-badge">' . esc_html( $chart_type_label ) . '</span>' : '&mdash;' ) . '</td>';
472 echo '<td class="col-shortcode"><code class="viz-shortcode-display">' . esc_html( $shortcode ) . '</code></td>';
473 echo '<td class="col-actions"><div class="visualizer-action-group">';
474 echo '<a class="visualizer-chart-action visualizer-chart-delete" href="' . $delete_url . '" onclick="return showNotice.warn();"><span class="dashicons dashicons-trash"></span><span class="tooltip-text">' . esc_html__( 'Delete', 'visualizer' ) . '</span></a>';
475 echo '<a class="visualizer-chart-action visualizer-chart-shortcode ' . esc_attr( $pro_class ) . '" href="javascript:;" data-clipboard-text="' . esc_attr( $shortcode ) . '"><span class="dashicons dashicons-shortcode ' . esc_attr( $pro_class ) . '"></span><span class="tooltip-text">' . esc_html__( 'Copy Shortcode', 'visualizer' ) . '</span></a>';
476 echo '<a class="visualizer-chart-action visualizer-chart-export ' . esc_attr( $pro_class ) . '" href="javascript:;" data-chart="' . $export_link . '"><span class="dashicons dashicons-download ' . esc_attr( $pro_class ) . '"></span><span class="tooltip-text">' . esc_html__( 'Export CSV', 'visualizer' ) . '</span></a>';
477 echo '<a class="visualizer-chart-action visualizer-chart-clone ' . esc_attr( $pro_class ) . '" href="' . $clone_url . '"><span class="dashicons dashicons-admin-page ' . esc_attr( $pro_class ) . '"></span><span class="tooltip-text">' . esc_html__( 'Duplicate', 'visualizer' ) . '</span></a>';
478 echo '<a class="visualizer-chart-action visualizer-chart-edit button button-secondary ' . esc_attr( $pro_class ) . '" href="javascript:;" data-chart="' . esc_attr( (string) $chart_id ) . '" data-library="' . esc_attr( $chart_library ) . '"><span class="dashicons dashicons-edit ' . esc_attr( $pro_class ) . '"></span><span class="visualizer-action-label">' . esc_html__( 'Edit', 'visualizer' ) . '</span><span class="tooltip-text">' . esc_html__( 'Edit', 'visualizer' ) . '</span></a>';
479 echo '</div></td>';
480 echo '</tr>';
481 return;
482 }
483
484 // ── Grid view: card ──
485 $type_badge = ! empty( $chart_type ) ? '<span class="viz-chart-type-badge">' . esc_html( $chart_type ) . '</span>' : '';
486 echo '<div class="items"><div class="visualizer-chart"><div class="visualizer-chart-title"><span>' . esc_html( $title ) . '</span>' . $type_badge . '</div>';
487 if ( Visualizer_Module::is_pro() && $with_filter ) {
488 echo '<div id="chart_wrapper_' . $placeholder_id . '">';
489 echo '<div id="control_wrapper_' . $placeholder_id . '" class="vz-library-chart-filter"></div>';
490 }
491 echo '<div id="', $placeholder_id, '" class="visualizer-chart-canvas">';
492 echo '<img src="', VISUALIZER_ABSURL, 'images/ajax-loader.gif" class="loader">';
493 echo '</div>';
494 if ( Visualizer_Module::is_pro() && $with_filter ) {
495 echo '</div>';
496 }
497 echo '<div class="visualizer-chart-footer visualizer-clearfix">';
498 echo '<div class="visualizer-action-group">';
499 echo '<a class="visualizer-chart-action visualizer-chart-edit button button-secondary ' . esc_attr( $pro_class ) . '" href="javascript:;" data-chart="', esc_attr( (string) $chart_id ), '" data-library="' . esc_attr( $chart_library ) . '"><span class="dashicons dashicons-edit ' . esc_attr( $pro_class ) . '"></span><span class="visualizer-action-label">' . esc_html__( 'Edit', 'visualizer' ) . '</span><span class="tooltip-text">' . esc_html__( 'Edit', 'visualizer' ) . '</span></a>';
500 echo '<a class="visualizer-chart-action visualizer-chart-shortcode ' . esc_attr( $pro_class ) . '" href="javascript:;" data-clipboard-text="', esc_attr( $shortcode ), '"><span class="dashicons dashicons-shortcode ' . esc_attr( $pro_class ) . '"></span><span class="tooltip-text">' . esc_html__( 'Copy Shortcode', 'visualizer' ) . '</span></a>';
501 if ( $this->can_chart_have_action( 'image', $chart_id ) ) {
502 echo '<a class="visualizer-chart-action visualizer-chart-image ' . esc_attr( $pro_class ) . '" href="javascript:;" data-chart="visualizer-', esc_attr( (string) $chart_id ), '" data-chart-title="', esc_attr( $title ), '"><span class="dashicons dashicons-format-image ' . esc_attr( $pro_class ) . '"></span><span class="tooltip-text">' . esc_html__( 'Download PNG', 'visualizer' ) . '</span></a>';
503 }
504 echo '<a class="visualizer-chart-action visualizer-chart-export ' . esc_attr( $pro_class ) . '" href="javascript:;" data-chart="', $export_link, '"><span class="dashicons dashicons-download ' . esc_attr( $pro_class ) . '"></span><span class="tooltip-text">' . esc_html__( 'Export CSV', 'visualizer' ) . '</span></a>';
505 echo '<a class="visualizer-chart-action visualizer-chart-clone ' . esc_attr( $pro_class ) . '" href="', $clone_url, '"><span class="dashicons dashicons-admin-page ' . esc_attr( $pro_class ) . '"></span><span class="tooltip-text">' . esc_html__( 'Duplicate', 'visualizer' ) . '</span></a>';
506 echo '<a class="visualizer-chart-action visualizer-chart-delete" href="', $delete_url, '" onclick="return showNotice.warn();"><span class="dashicons dashicons-trash"></span><span class="tooltip-text">' . esc_html__( 'Delete', 'visualizer' ) . '</span></a>';
507 echo '</div>';
508 do_action( 'visualizer_chart_languages', $chart_id );
509 echo '<hr><div class="visualizer-chart-status"><span title="' . __( 'Chart ID', 'visualizer' ) . '">(' . $chart_id . '):</span> <span class="visualizer-date" title="' . __( 'Last Updated', 'visualizer' ) . '">' . $chart_status['date'] . '</span><span class="visualizer-error"><i class="dashicons ' . $chart_status['icon'] . '" data-viz-error="' . esc_attr( str_replace( '"', "'", $chart_status['error'] ) ) . '" title="' . esc_attr( $chart_status['title'] ) . '"></i></span></div>';
510 echo '</div>';
511 echo '</div></div>';
512 }
513
514 /**
515 * Returns true when the library should render in list (no-preview) mode.
516 *
517 * Priority: ?view= URL param (saves to user meta) → saved user meta → grid default.
518 *
519 * No nonce needed: this is a bookmarkable UI preference URL. A nonce would expire
520 * and break saved/shared links for zero real security gain — the value is allowlisted
521 * to 'list'|'grid' before any write happens.
522 */
523 private function _isListView(): bool {
524 if ( null !== $this->_list_view_cached ) {
525 return $this->_list_view_cached;
526 }
527 if ( isset( $_GET['view'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
528 $view = sanitize_text_field( wp_unslash( $_GET['view'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
529 if ( in_array( $view, array( 'list', 'grid' ), true ) ) {
530 update_user_meta( get_current_user_id(), 'visualizer_library_view', $view );
531 }
532 $this->_list_view_cached = ( 'list' === $view );
533 } else {
534 $saved = get_user_meta( get_current_user_id(), 'visualizer_library_view', true );
535 $this->_list_view_cached = ( 'list' === $saved );
536 }
537 return $this->_list_view_cached;
538 }
539
540 /**
541 * Returns the HTML for the grid/list view toggle links.
542 */
543 private function _getViewToggleHTML(): string {
544 $is_list = $this->_isListView();
545 $grid_url = esc_url( add_query_arg( 'view', 'grid' ) );
546 $list_url = esc_url( add_query_arg( 'view', 'list' ) );
547 return '<a href="' . $grid_url . '" class="viz-view-toggle' . ( ! $is_list ? ' active' : '' ) . '" title="' . esc_attr__( 'Grid View', 'visualizer' ) . '"><span class="dashicons dashicons-screenoptions"></span></a>'
548 . '<a href="' . $list_url . '" class="viz-view-toggle' . ( $is_list ? ' active' : '' ) . '" title="' . esc_attr__( 'List View', 'visualizer' ) . '"><span class="dashicons dashicons-list-view"></span></a>';
549 }
550
551 /**
552 * Render 2-col sidebar
553 */
554 private function _renderSidebar() {
555 if ( ! Visualizer_Module::is_pro() ) {
556 $upgrade_url = tsdk_utmify( Visualizer_Plugin::PRO_TEASER_URL, 'sidebarMenuUpgrade', 'index' );
557 $chart_types = Visualizer_Module_Admin::proFeaturesEnabled() ? __( '6 more chart types', 'visualizer' ) : __( '11 more chart types', 'visualizer' );
558 echo '<div class="items--upsell">';
559 echo '<div class="viz-upsell-banner">';
560 echo '<span class="dashicons dashicons-star-filled viz-upsell-banner__icon"></span>';
561 echo '<div class="viz-upsell-banner__text">';
562 echo '<strong>' . esc_html__( 'Unlock the full power of Visualizer PRO!', 'visualizer' ) . '</strong>';
563 /* translators: %s: number of additional chart types (e.g. "11 more chart types") */
564 echo '<span>' . sprintf( esc_html__( '%s, periodic data sync, database queries, frontend editor, and more.', 'visualizer' ), esc_html( $chart_types ) ) . '</span>';
565 echo '</div>';
566 echo '<div class="viz-upsell-banner__actions">';
567 echo '<a href="' . esc_url( $upgrade_url . '#pro-features' ) . '" target="_blank" class="button button-secondary">' . esc_html__( 'View Features', 'visualizer' ) . '</a>';
568 echo '<a href="' . esc_url( $upgrade_url . '#pricing' ) . '" target="_blank" class="button button-primary">' . esc_html__( 'Upgrade Now', 'visualizer' ) . '</a>';
569 echo '</div>';
570 echo '</div>';
571 echo '</div>';
572 }
573 }
574 }
575