PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 2.2.0
Visualizer – Tables & Charts Manager with Built-in AI Generator v2.2.0
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 / Module / Chart.php

Chart.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 2.2.0, at classes/Visualizer/Module/Chart.php

567 lines 19.1 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 * The module for all stuff related to getting, editing, creating and deleting charts.
23 *
24 * @category Visualizer
25 * @package Module
26 *
27 * @since 1.0.0
28 */
29 class Visualizer_Module_Chart extends Visualizer_Module {
30
31 const NAME = __CLASS__;
32
33 /**
34 * The chart object.
35 *
36 * @since 1.0.0
37 *
38 * @access private
39 * @var WP_Post
40 */
41 private $_chart;
42
43 /**
44 * Constructor.
45 *
46 * @since 1.0.0
47 *
48 * @access public
49 *
50 * @param Visualizer_Plugin $plugin The instance of the plugin.
51 */
52 public function __construct( Visualizer_Plugin $plugin ) {
53 parent::__construct( $plugin );
54 $this->_addAjaxAction( Visualizer_Plugin::ACTION_GET_CHARTS, 'getCharts' );
55 $this->_addAjaxAction( Visualizer_Plugin::ACTION_DELETE_CHART, 'deleteChart' );
56 $this->_addAjaxAction( Visualizer_Plugin::ACTION_CREATE_CHART, 'renderChartPages' );
57 $this->_addAjaxAction( Visualizer_Plugin::ACTION_EDIT_CHART, 'renderChartPages' );
58 $this->_addAjaxAction( Visualizer_Plugin::ACTION_UPLOAD_DATA, 'uploadData' );
59 $this->_addAjaxAction( Visualizer_Plugin::ACTION_CLONE_CHART, 'cloneChart' );
60 // Added by Ash/Upwork
61 $this->_addAjaxAction( Visualizer_Plugin::ACTION_EXPORT_DATA, 'exportData' );
62 // Added by Ash/Upwork
63 }
64
65 /**
66 * Fetches charts from database.
67 *
68 * @since 1.0.0
69 *
70 * @access public
71 */
72 public function getCharts() {
73 $query_args = array(
74 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
75 'posts_per_page' => 9,
76 'paged' => filter_input(
77 INPUT_GET, 'page', FILTER_VALIDATE_INT, array(
78 'options' => array(
79 'min_range' => 1,
80 'default' => 1,
81 ),
82 )
83 ),
84 );
85 $filter = filter_input( INPUT_GET, 's', FILTER_SANITIZE_STRING );
86 if ( $filter && in_array( $filter, Visualizer_Plugin::getChartTypes() ) ) {
87 $query_args['meta_query'] = array(
88 array(
89 'key' => Visualizer_Plugin::CF_CHART_TYPE,
90 'value' => $filter,
91 'compare' => '=',
92 ),
93 );
94 }
95 $query = new WP_Query( $query_args );
96 $charts = array();
97 while ( $query->have_posts() ) {
98 $chart = $query->next_post();
99 $chart_data = $this->_getChartArray( $chart );
100 $chart_data['id'] = $chart->ID;
101 $charts[] = $chart_data;
102 }
103 self::_sendResponse(
104 array(
105 'success' => true,
106 'data' => $charts,
107 'total' => $query->max_num_pages,
108 )
109 );
110 }
111
112 /**
113 * Returns chart data required for rendering.
114 *
115 * @since 1.0.0
116 *
117 * @access private
118 *
119 * @param WP_Post $chart The chart object.
120 *
121 * @return array The array of chart data.
122 */
123 private function _getChartArray( WP_Post $chart = null ) {
124 if ( is_null( $chart ) ) {
125 $chart = $this->_chart;
126 }
127 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
128 $series = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_SERIES, get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true ), $chart->ID, $type );
129 $data = apply_filters( Visualizer_Plugin::FILTER_GET_CHART_DATA, unserialize( $chart->post_content ), $chart->ID, $type );
130
131 return array(
132 'type' => $type,
133 'series' => $series,
134 'settings' => get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true ),
135 'data' => $data,
136 );
137 }
138
139 /**
140 * Sends json response.
141 *
142 * @since 1.0.0
143 *
144 * @access private
145 *
146 * @param array $results The response array.
147 */
148 public static function _sendResponse( $results ) {
149 header( 'Content-type: application/json' );
150 nocache_headers();
151 echo json_encode( $results );
152 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
153 }
154
155 /**
156 * Deletes a chart from database.
157 *
158 * @since 1.0.0
159 * @uses wp_delete_post() To delete a chart.
160 *
161 * @access public
162 */
163 public function deleteChart() {
164 $is_post = $_SERVER['REQUEST_METHOD'] == 'POST';
165 $input_method = $is_post ? INPUT_POST : INPUT_GET;
166 $chart_id = $success = false;
167 $nonce = wp_verify_nonce( filter_input( $input_method, 'nonce' ) );
168 $capable = current_user_can( 'delete_posts' );
169 if ( $nonce && $capable ) {
170 $chart_id = filter_input(
171 $input_method, 'chart', FILTER_VALIDATE_INT, array(
172 'options' => array(
173 'min_range' => 1,
174 ),
175 )
176 );
177 if ( $chart_id ) {
178 $chart = get_post( $chart_id );
179 $success = $chart && $chart->post_type == Visualizer_Plugin::CPT_VISUALIZER;
180 }
181 }
182 if ( $success ) {
183 wp_delete_post( $chart_id, true );
184 }
185 if ( $is_post ) {
186 self::_sendResponse(
187 array(
188 'success' => $success,
189 )
190 );
191 }
192 wp_redirect( wp_get_referer() );
193 exit;
194 }
195
196 /**
197 * Renders appropriate page for chart builder. Creates new auto draft chart
198 * if no chart has been specified.
199 *
200 * @since 1.0.0
201 *
202 * @access public
203 */
204 public function renderChartPages() {
205 defined( 'IFRAME_REQUEST' ) || define( 'IFRAME_REQUEST', 1 );
206 // check chart, if chart not exists, will create new one and redirects to the same page with proper chart id
207 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
208 if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type != Visualizer_Plugin::CPT_VISUALIZER ) {
209 $default_type = 'line';
210 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $default_type . '.csv' );
211 $source->fetch();
212 $chart_id = wp_insert_post(
213 array(
214 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
215 'post_title' => 'Visualization',
216 'post_author' => get_current_user_id(),
217 'post_status' => 'auto-draft',
218 'post_content' => $source->getData(),
219 )
220 );
221 if ( $chart_id && ! is_wp_error( $chart_id ) ) {
222 add_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, $default_type );
223 add_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, 1 );
224 add_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
225 add_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
226 add_post_meta(
227 $chart_id, Visualizer_Plugin::CF_SETTINGS, array(
228 'focusTarget' => 'datum',
229 )
230 );
231 }
232 wp_redirect( add_query_arg( 'chart', (int) $chart_id ) );
233 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
234 }
235 // enqueue and register scripts and styles
236 wp_register_style( 'visualizer-frame', VISUALIZER_ABSURL . 'css/frame.css', array(), Visualizer_Plugin::VERSION );
237 wp_register_script( 'visualizer-frame', VISUALIZER_ABSURL . 'js/frame.js', array( 'jquery' ), Visualizer_Plugin::VERSION, true );
238 wp_register_script( 'google-jsapi-new', '//www.gstatic.com/charts/loader.js', array(), null, true );
239 wp_register_script( 'google-jsapi-old', '//www.google.com/jsapi', array( 'google-jsapi-new' ), null, true );
240 wp_register_script(
241 'visualizer-render', VISUALIZER_ABSURL . 'js/render.js', array(
242 'google-jsapi-old',
243 'google-jsapi-new',
244 'visualizer-frame',
245 ), Visualizer_Plugin::VERSION, true
246 );
247 wp_register_script(
248 'visualizer-preview', VISUALIZER_ABSURL . 'js/preview.js', array(
249 'wp-color-picker',
250 'visualizer-render',
251 ), Visualizer_Plugin::VERSION, true
252 );
253 // added by Ash/Upwork
254 if ( VISUALIZER_PRO ) {
255 global $Visualizer_Pro;
256 $Visualizer_Pro->_addScriptsAndStyles();
257 }
258 // dispatch pages
259 $this->_chart = get_post( $chart_id );
260 switch ( isset( $_GET['tab'] ) ? $_GET['tab'] : '' ) {
261 case 'settings':
262 // changed by Ash/Upwork
263 $this->_handleDataAndSettingsPage();
264 break;
265 case 'type':
266 default:
267 $this->_handleTypesPage();
268 break;
269 }
270 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
271 }
272
273 /**
274 * Handle data and settings page
275 */
276 private function _handleDataAndSettingsPage() {
277 if ( isset( $_POST['map_api_key'] ) ) {
278 update_option( 'visualizer-map-api-key', $_POST['map_api_key'] );
279 }
280 if ( $_SERVER['REQUEST_METHOD'] == 'POST' && isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'] ) ) {
281 if ( $this->_chart->post_status == 'auto-draft' ) {
282 $this->_chart->post_status = 'publish';
283 wp_update_post( $this->_chart->to_array() );
284 }
285 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SETTINGS, $_POST );
286 $render = new Visualizer_Render_Page_Send();
287 $render->text = sprintf( '[visualizer id="%d"]', $this->_chart->ID );
288 wp_iframe( array( $render, 'render' ) );
289
290 return;
291 }
292 $data = $this->_getChartArray();
293 $sidebar = '';
294 $sidebar_class = 'Visualizer_Render_Sidebar_Type_' . ucfirst( $data['type'] );
295 if ( class_exists( $sidebar_class, true ) ) {
296 $sidebar = new $sidebar_class( $data['settings'] );
297 $sidebar->__series = $data['series'];
298 $sidebar->__data = $data['data'];
299 } else {
300 $sidebar = apply_filters( 'visualizer_pro_chart_type_sidebar', '', $data );
301 if ( $sidebar != '' ) {
302 $sidebar->__series = $data['series'];
303 $sidebar->__data = $data['data'];
304 }
305 }
306 unset( $data['settings']['width'], $data['settings']['height'] );
307 wp_enqueue_style( 'visualizer-frame' );
308 wp_enqueue_style( 'wp-color-picker' );
309 wp_enqueue_style( 'visualizer-frame' );
310 wp_enqueue_script( 'visualizer-preview' );
311 wp_enqueue_script( 'visualizer-render' );
312 wp_localize_script(
313 'visualizer-render', 'visualizer', array(
314 'l10n' => array(
315 'invalid_source' => esc_html__( 'You have entered invalid URL. Please, insert proper URL.', 'visualizer' ),
316 ),
317 'charts' => array(
318 'canvas' => $data,
319 ),
320 'map_api_key' => get_option( 'visualizer-map-api-key' ),
321 )
322 );
323 $render = new Visualizer_Render_Page_Data();
324 $render->chart = $this->_chart;
325 $render->type = $data['type'];
326 $render->sidebar = $sidebar;
327 if ( filter_input( INPUT_GET, 'library', FILTER_VALIDATE_BOOLEAN ) ) {
328 $render->button = filter_input( INPUT_GET, 'action' ) == Visualizer_Plugin::ACTION_EDIT_CHART
329 ? esc_html__( 'Save Chart', 'visualizer' )
330 : esc_html__( 'Create Chart', 'visualizer' );
331 } else {
332 $render->button = esc_attr__( 'Insert Chart', 'visualizer' );
333 }
334 if ( VISUALIZER_PRO ) {
335 global $Visualizer_Pro;
336 $Visualizer_Pro->_enqueueScriptsAndStyles( $data );
337 }
338 $this->_addAction( 'admin_head', 'renderFlattrScript' );
339 wp_iframe( array( $render, 'render' ) );
340 }
341
342 /**
343 * Handles chart type selection page.
344 *
345 * @since 1.0.0
346 *
347 * @access private
348 */
349 private function _handleTypesPage() {
350 // process post request
351 if ( $_SERVER['REQUEST_METHOD'] == 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ) ) ) {
352 $type = filter_input( INPUT_POST, 'type' );
353 if ( in_array( $type, Visualizer_Plugin::getChartTypes() ) ) {
354 // save new chart type
355 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, $type );
356 // if the chart has default data, update it with appropriate default data for new type
357 if ( filter_var( get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, true ), FILTER_VALIDATE_BOOLEAN ) ) {
358 $source = new Visualizer_Source_Csv( VISUALIZER_ABSPATH . DIRECTORY_SEPARATOR . 'samples' . DIRECTORY_SEPARATOR . $type . '.csv' );
359 $source->fetch();
360 $this->_chart->post_content = $source->getData();
361 wp_update_post( $this->_chart->to_array() );
362 update_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
363 }
364 // redirect to next tab
365 // changed by Ash/Upwork
366 wp_redirect( add_query_arg( 'tab', 'settings' ) );
367
368 return;
369 }
370 }
371 $render = new Visualizer_Render_Page_Types();
372 $render->type = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
373 $render->types = Visualizer_Module_Admin::_getChartTypesLocalized();
374 $render->chart = $this->_chart;
375 wp_enqueue_style( 'visualizer-frame' );
376 wp_enqueue_script( 'visualizer-frame' );
377 wp_iframe( array( $render, 'render' ) );
378 }
379
380 /**
381 * Renders flattr script in the iframe <head>
382 *
383 * @since 1.4.2
384 * @action admin_head
385 *
386 * @access public
387 */
388 public function renderFlattrScript() {
389 echo '';
390 }
391 // changed by Ash/Upwork
392 /**
393 * Parses uploaded CSV file and saves new data for the chart.
394 *
395 * @since 1.0.0
396 *
397 * @access public
398 */
399 public function uploadData() {
400 // validate nonce
401 // do not use filter_input as it does not work for phpunit test cases, use filter_var instead
402 if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'] ) ) {
403 status_header( 403 );
404 exit;
405 }
406 // check chart, if chart exists
407 $chart_id = isset( $_GET['chart'] ) ? filter_var( $_GET['chart'], FILTER_VALIDATE_INT ) : '';
408 if ( ! $chart_id || ! ( $chart = get_post( $chart_id ) ) || $chart->post_type != Visualizer_Plugin::CPT_VISUALIZER ) {
409 status_header( 400 );
410 exit;
411 }
412 if ( ! isset( $_POST['vz-import-time'] ) ) {
413 apply_filters( 'visualizer_pro_remove_schedule', $chart_id );
414 }
415 $source = null;
416 $render = new Visualizer_Render_Page_Update();
417 if ( isset( $_POST['remote_data'] ) && filter_var( $_POST['remote_data'], FILTER_VALIDATE_URL ) ) {
418 $source = new Visualizer_Source_Csv_Remote( $_POST['remote_data'] );
419 if ( isset( $_POST['vz-import-time'] ) ) {
420 apply_filters( 'visualizer_pro_chart_schedule', $chart_id, $_POST['remote_data'], $_POST['vz-import-time'] );
421 }
422 } elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] == 0 ) {
423 $source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
424 // Added by Ash/Upwork
425 } elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
426 $source = apply_filters( 'visualizer_pro_handle_chart_data', $_POST['chart_data'], '' );
427 // Added by Ash/Upwork
428 } else {
429 $render->message = esc_html__( 'CSV file with chart data was not uploaded. Please, try again.', 'visualizer' );
430 }
431 if ( $source ) {
432 if ( $source->fetch() ) {
433 $chart->post_content = $source->getData();
434 wp_update_post( $chart->to_array() );
435 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
436 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SOURCE, $source->getSourceName() );
437 update_post_meta( $chart->ID, Visualizer_Plugin::CF_DEFAULT_DATA, 0 );
438 $render->data = json_encode( $source->getRawData() );
439 $render->series = json_encode( $source->getSeries() );
440 } else {
441 $render->message = esc_html__( 'CSV file is broken or invalid. Please, try again.', 'visualizer' );
442 }
443 }
444 $render->render();
445 if ( ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE ) ) {
446 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
447 }
448 }
449
450 /**
451 * Clones the chart.
452 *
453 * @since 1.0.0
454 *
455 * @access public
456 */
457 public function cloneChart() {
458 $chart_id = $success = false;
459 $nonce = wp_verify_nonce( filter_input( INPUT_GET, 'nonce' ), Visualizer_Plugin::ACTION_CLONE_CHART );
460 $capable = current_user_can( 'edit_posts' );
461 if ( $nonce && $capable ) {
462 $chart_id = filter_input(
463 INPUT_GET, 'chart', FILTER_VALIDATE_INT, array(
464 'options' => array(
465 'min_range' => 1,
466 ),
467 )
468 );
469 if ( $chart_id ) {
470 $chart = get_post( $chart_id );
471 $success = $chart && $chart->post_type == Visualizer_Plugin::CPT_VISUALIZER;
472 }
473 }
474 $redirect = wp_get_referer();
475 if ( $success ) {
476 $new_chart_id = wp_insert_post(
477 array(
478 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
479 'post_title' => 'Visualization',
480 'post_author' => get_current_user_id(),
481 'post_status' => $chart->post_status,
482 'post_content' => $chart->post_content,
483 )
484 );
485 if ( $new_chart_id && ! is_wp_error( $new_chart_id ) ) {
486 add_post_meta( $new_chart_id, Visualizer_Plugin::CF_CHART_TYPE, get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, true ) );
487 add_post_meta( $new_chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, get_post_meta( $chart_id, Visualizer_Plugin::CF_DEFAULT_DATA, true ) );
488 add_post_meta( $new_chart_id, Visualizer_Plugin::CF_SOURCE, get_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, true ) );
489 add_post_meta( $new_chart_id, Visualizer_Plugin::CF_SERIES, get_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, true ) );
490 add_post_meta( $new_chart_id, Visualizer_Plugin::CF_SETTINGS, get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true ) );
491 $redirect = add_query_arg(
492 array(
493 'page' => 'visualizer',
494 'type' => filter_input( INPUT_GET, 'type' ),
495 ), admin_url( 'upload.php' )
496 );
497 }
498 }
499 wp_redirect( $redirect );
500 exit;
501 }
502
503 /**
504 * Exports the chart data
505 *
506 * @since 1.0.0
507 *
508 * @access public
509 */
510 public function exportData() {
511 check_ajax_referer( Visualizer_Plugin::ACTION_EXPORT_DATA . Visualizer_Plugin::VERSION, 'security' );
512 $capable = current_user_can( 'edit_posts' );
513 if ( $capable ) {
514 $chart_id = isset( $_GET['chart'] ) ? filter_var(
515 $_GET['chart'], FILTER_VALIDATE_INT, array(
516 'options' => array(
517 'min_range' => 1,
518 ),
519 )
520 ) : '';
521 if ( $chart_id ) {
522 $data = $this->_getDataAs( $chart_id, 'csv' );
523 if ( $data ) {
524 echo wp_send_json_success( $data );
525 }
526 }
527 }
528
529 defined( 'WP_TESTS_DOMAIN' ) ? wp_die() : exit();
530 }
531
532 /**
533 * Handles chart data page.
534 *
535 * @since 1.0.0
536 *
537 * @access private
538 */
539 private function _handleDataPage() {
540 $data = $this->_getChartArray();
541 $render = new Visualizer_Render_Page_Data();
542 $render->chart = $this->_chart;
543 $render->type = $data['type'];
544 unset( $data['settings']['width'], $data['settings']['height'] );
545 wp_enqueue_style( 'visualizer-frame' );
546 wp_enqueue_script( 'visualizer-render' );
547 wp_localize_script(
548 'visualizer-render', 'visualizer', array(
549 'l10n' => array(
550 'invalid_source' => esc_html__( 'You have entered invalid URL. Please, insert proper URL.', 'visualizer' ),
551 ),
552 'charts' => array(
553 'canvas' => $data,
554 ),
555 )
556 );
557 // Added by Ash/Upwork
558 if ( VISUALIZER_PRO ) {
559 global $Visualizer_Pro;
560 $Visualizer_Pro->_enqueueScriptsAndStyles( $data );
561 }
562 // Added by Ash/Upwork
563 $this->_addAction( 'admin_head', 'renderFlattrScript' );
564 wp_iframe( array( $render, 'render' ) );
565 }
566 }
567