PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.9
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.9
4.0.9 4.0.8 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 All 150 releases
visualizer / classes / Visualizer / Module / Setup.php

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

643 lines 20.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // +----------------------------------------------------------------------+
4 // | Copyright 2013 Madpixels (email : [email protected]) |
5 // +----------------------------------------------------------------------+
6 // | This program is free software; you can redistribute it and/or modify |
7 // | it under the terms of the GNU General Public License, version 2, as |
8 // | published by the Free Software Foundation. |
9 // | |
10 // | This program is distributed in the hope that it will be useful, |
11 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 // | GNU General Public License for more details. |
14 // | |
15 // | You should have received a copy of the GNU General Public License |
16 // | along with this program; if not, write to the Free Software |
17 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
18 // | MA 02110-1301 USA |
19 // +----------------------------------------------------------------------+
20 // | Author: Eugene Manuilov <[email protected]> |
21 // +----------------------------------------------------------------------+
22 /**
23 * General module what setups all required environment.
24 *
25 * @category Visualizer
26 * @package Module
27 *
28 * @since 1.0.0
29 */
30 class Visualizer_Module_Setup extends Visualizer_Module {
31
32 const NAME = __CLASS__;
33
34 /**
35 * Hook that refreshes database charts.
36 */
37 const REFRESH_DB_HOOK = 'visualizer_schedule_refresh_db';
38
39 /**
40 * Action Scheduler group that owns the refresh.
41 */
42 const REFRESH_DB_GROUP = 'visualizer';
43
44 /**
45 * Marks the refresh trigger as checked recently.
46 */
47 const REFRESH_DB_CHECK_TRANSIENT = 'visualizer-refresh-db-checked';
48
49 /**
50 * Seconds a check stays valid; matches Action Scheduler's timeout for a killed run.
51 */
52 const REFRESH_DB_CHECK_WINDOW = 300;
53
54 /**
55 * Constructor.
56 *
57 * @since 1.0.0
58 *
59 * @access public
60 * @param Visualizer_Plugin $plugin The instance of the plugin.
61 */
62 public function __construct( Visualizer_Plugin $plugin ) {
63 parent::__construct( $plugin );
64
65 register_activation_hook( VISUALIZER_BASEFILE, array( $this, 'activate' ) );
66 register_deactivation_hook( VISUALIZER_BASEFILE, array( $this, 'deactivate' ) );
67 $this->_addAction( self::REFRESH_DB_HOOK, 'refreshDbChart' );
68 $this->_addAction( 'init', 'maybe_reschedule_refresh_db' );
69 $this->_addAction( 'action_scheduler_ensure_recurring_actions', 'ensure_refresh_db_action' );
70 $this->_addFilter( 'visualizer_schedule_refresh_chart', 'refresh_db_for_chart', 10, 3 );
71
72 $this->_addAction( 'admin_init', 'adminInit' );
73 $this->_addAction( 'init', 'setupCustomPostTypes' );
74 $this->_addFilter( 'cron_schedules', 'custom_cron_schedules' );
75 $this->_addAction( 'plugins_loaded', 'loadTextDomain' );
76 $this->_addFilter( 'visualizer_logger_data', 'getLoggerData' );
77 $this->_addFilter( 'visualizer_get_chart_counts', 'getUsage', 10, 2 );
78
79 $this->_addAction( 'init', 'checkIsExistingUser' );
80
81 // only for testing
82 // $this->_addAction( 'admin_init', 'getUsage' );
83 }
84 /**
85 * Fetches the SDK logger data.
86 *
87 * @param array $data The default data that needs to be sent.
88 *
89 * @access public
90 */
91 public function getLoggerData( $data ) {
92 return $this->getUsage( $data );
93 }
94
95 /**
96 * Fetches the usage of charts.
97 *
98 * @param array $data The default data that needs to be sent.
99 * @param array $meta_keys An array of name vs. meta keys - to return how many charts have these keys.
100 *
101 * @access public
102 */
103 public function getUsage( $data, $meta_keys = array() ) {
104 $charts = array( 'types' => array(), 'sources' => array(), 'library' => array(), 'permissions' => 0, 'manual_config' => 0, 'scheduled' => 0 );
105 $query_args = array(
106 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
107 'posts_per_page' => 300,
108 'post_status' => 'publish',
109 'fields' => 'ids',
110 'no_rows_found' => false,
111 'update_post_meta_cache' => false,
112 'update_post_term_cache' => false,
113 );
114
115 // default permissions.
116 $default_perms = array(
117 'read' => 'all',
118 'read-specific' => null,
119 'edit' => 'roles',
120 'edit-specific' => array( 'administrator' ),
121 );
122
123 // collect all schedules chart ids.
124 $scheduled = array_merge(
125 array_keys( get_option( Visualizer_Plugin::CF_CHART_SCHEDULE, array() ) ),
126 array_keys( get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() ) )
127 );
128 $query = new WP_Query( $query_args );
129 while ( $query->have_posts() ) {
130 $chart_id = $query->next_post();
131 $type = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, true );
132 $charts['types'][ $type ] = isset( $charts['types'][ $type ] ) ? $charts['types'][ $type ] + 1 : 1;
133 $source = get_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, true );
134 $charts['sources'][ $source ] = isset( $charts['sources'][ $source ] ) ? $charts['sources'][ $source ] + 1 : 1;
135 $lib = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, true );
136 $charts['library'][ $lib ] = isset( $charts['library'][ $lib ] ) ? $charts['library'][ $lib ] + 1 : 1;
137 $settings = get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true );
138 if ( is_array( $settings ) && ! empty( $settings['manual'] ) ) {
139 $charts['manual_config'] = $charts['manual_config'] + 1;
140 }
141
142 // phpcs:ignore WordPress.PHP.StrictInArray.FoundNonStrictFalse
143 if ( in_array( $chart_id, $scheduled, false ) ) {
144 $charts['scheduled'] = $charts['scheduled'] + 1;
145 }
146
147 if ( Visualizer_Module::is_pro() ) {
148 $permissions = get_post_meta( $chart_id, Visualizer_Pro::CF_PERMISSIONS, true );
149 if ( ! is_array( $permissions ) || empty( $permissions['permissions'] ) || ! is_array( $permissions['permissions'] ) ) {
150 continue;
151 }
152 $permissions = $permissions['permissions'];
153 $customized = false;
154 foreach ( $default_perms as $key => $val ) {
155 if ( ! is_array( $val ) && ! is_null( $val ) && isset( $permissions[ $key ] ) && $permissions[ $key ] !== $val ) {
156 $customized = true;
157 } elseif ( is_array( $val ) && ! is_null( $val ) && isset( $permissions[ $key ] ) && is_array( $permissions[ $key ] ) && count( $permissions[ $key ] ) !== count( $val ) ) {
158 $customized = true;
159 }
160 }
161 if ( $customized ) {
162 $charts['permissions'] = $charts['permissions'] + 1;
163 }
164 }
165
166 if ( ! empty( $meta_keys ) ) {
167 foreach ( $meta_keys as $name => $key ) {
168 $data = get_post_meta( $chart_id, $key, true );
169 if ( ! empty( $data ) ) {
170 $charts[ $name ] = isset( $charts[ $name ] ) ? $charts[ $name ] + 1 : 1;
171 } else {
172 $charts[ $name ] = 0;
173 }
174 }
175 }
176 }
177
178 return $charts;
179 }
180
181 /**
182 * Registers custom post type for charts.
183 *
184 * @since 1.0.0
185 * @uses register_post_type() To register custom post type for charts.
186 *
187 * @access public
188 */
189 public function setupCustomPostTypes() {
190 register_post_type(
191 Visualizer_Plugin::CPT_VISUALIZER,
192 array(
193 'label' => 'Visualizer Charts',
194 'public' => false,
195 'supports' => array( 'revisions' ),
196 'show_in_rest' => true,
197 'rest_base' => 'visualizer',
198 'rest_controller_class' => 'WP_REST_Posts_Controller',
199 )
200 );
201 }
202
203 /**
204 * Loads plugin text domain translations.
205 *
206 * @since 1.0.0
207 * @uses load_plugin_textdomain() To load translations for the plugin.
208 *
209 * @access public
210 */
211 public function loadTextDomain() {
212 load_plugin_textdomain( Visualizer_Plugin::NAME, false, dirname( plugin_basename( VISUALIZER_BASEFILE ) ) . '/languages/' );
213 }
214
215 /**
216 * Activate the plugin
217 */
218 public function activate( $network_wide ) {
219 if ( is_multisite() && $network_wide ) {
220 foreach ( get_sites( array( 'fields' => 'ids' ) ) as $blog_id ) {
221 switch_to_blog( $blog_id );
222 $this->activate_on_site();
223 restore_current_blog();
224 }
225 } else {
226 $this->activate_on_site();
227 }
228 }
229
230 /**
231 * Activates the plugin on a particular blog instance (supports multisite and single site).
232 */
233 private function activate_on_site() {
234 $this->schedule_refresh_db_action();
235 add_option( 'visualizer-activated', true );
236 $is_fresh_install = get_option( 'visualizer_fresh_install', false );
237 if ( ! defined( 'TI_E2E_TESTING' ) && false === $is_fresh_install ) {
238 update_option( 'visualizer_fresh_install', '1' );
239 }
240 }
241
242 /**
243 * Deactivate the plugin
244 */
245 public function deactivate( $network_wide ) {
246 if ( is_multisite() && $network_wide ) {
247 foreach ( get_sites( array( 'fields' => 'ids' ) ) as $blog_id ) {
248 switch_to_blog( $blog_id );
249 $this->deactivate_on_site();
250 restore_current_blog();
251 }
252 } else {
253 $this->deactivate_on_site();
254 }
255 }
256
257 /**
258 * Deactivates the plugin on a particular blog instance (supports multisite and single site).
259 */
260 private function deactivate_on_site() {
261 $this->unschedule_refresh_db_action();
262 delete_option( 'visualizer-activated', true );
263 }
264
265 /**
266 * Check if plugin has been activated and then redirect to the correct page.
267 */
268 public function adminInit() {
269 if ( defined( 'TI_UNIT_TESTING' ) ) {
270 return;
271 }
272
273 define( 'VISUALIZER_SURVEY', Visualizer_Module::is_pro() ? 'https://forms.gle/7Zo7FuZbvQ8DTvRi6' : 'https://forms.gle/muMtbcyvHn1aTvmJ7' );
274 // fire any upgrades necessary.
275 Visualizer_Module_Upgrade::upgrade();
276
277 $activated_flag = get_option( 'visualizer-activated' );
278 $fresh_install = get_option( 'visualizer_fresh_install', false );
279 $is_pro = Visualizer_Module::is_pro();
280 if ( $activated_flag ) {
281 if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) {
282 // Defer redirect until a normal admin request.
283 return;
284 }
285 if ( wp_doing_cron() ) {
286 // Defer redirect during cron requests.
287 return;
288 }
289 delete_option( 'visualizer-activated' );
290 if ( ! headers_sent() ) {
291 if ( ! $is_pro && ! empty( $fresh_install ) ) {
292 $redirect_url = array(
293 'page' => 'visualizer-setup-wizard',
294 'tab' => '#step-1',
295 );
296 } else {
297 $page_name = Visualizer_Module::numberOfCharts() > 0 ? Visualizer_Plugin::NAME : 'viz-support';
298 $redirect_url = array(
299 'page' => $page_name,
300 );
301 }
302 wp_safe_redirect(
303 add_query_arg(
304 $redirect_url,
305 admin_url( 'admin.php' )
306 )
307 );
308 exit();
309 }
310 }
311 }
312
313
314 /**
315 * Refresh the specific chart from the db.
316 *
317 * @param WP_Post|null $chart The chart object.
318 * @param int $chart_id The chart id.
319 * @param bool $force If this is true, then the chart data will be force refreshed. If false, data will be refreshed only if the chart requests live data.
320 *
321 * @access public
322 */
323 public function refresh_db_for_chart( $chart, $chart_id, $force = false ) {
324 if ( ! $chart_id ) {
325 return $chart;
326 }
327
328 if ( ! $chart ) {
329 $chart = get_post( $chart_id );
330 }
331
332 if ( ! $chart ) {
333 return $chart;
334 }
335
336 // check if the source is correct.
337 $source = get_post_meta( $chart_id, Visualizer_Plugin::CF_SOURCE, true );
338 $load_series = false;
339 switch ( $source ) {
340 case 'Visualizer_Source_Query':
341 // check if its a live-data chart or a cached-data chart.
342 if ( ! $force ) {
343 $hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, true );
344 if ( ! empty( $hours ) ) {
345 // cached, bail!
346 return $chart;
347 }
348 }
349
350 $params = get_post_meta( $chart_id, Visualizer_Plugin::CF_DB_QUERY, true );
351 $source = new Visualizer_Source_Query( $params, $chart_id );
352 $source->fetch( false );
353 $load_series = true;
354 break;
355 case 'Visualizer_Source_Json':
356 // check if its a live-data chart or a cached-data chart.
357 if ( ! $force ) {
358 $hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_SCHEDULE, true );
359 if ( ! empty( $hours ) ) {
360 // cached, bail!
361 return $chart;
362 }
363 }
364
365 $url = get_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_URL, true );
366 $root = get_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_ROOT, true );
367 $paging = get_post_meta( $chart_id, Visualizer_Plugin::CF_JSON_PAGING, true );
368 $series = get_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, true );
369 $source = new Visualizer_Source_Json( array( 'url' => $url, 'root' => $root, 'paging' => $paging ) );
370 $source->refresh( $series );
371 break;
372 case 'Visualizer_Source_Csv_Remote':
373 // check if its a live-data chart or a cached-data chart.
374 if ( ! $force ) {
375 $hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_SCHEDULE, true );
376 if ( ! empty( $hours ) ) {
377 // cached, bail!
378 return $chart;
379 }
380 }
381 do_action( 'visualizer_schedule_import' );
382 return get_post( $chart_id );
383 default:
384 return $chart;
385 }
386
387 $error = $source->get_error();
388 if ( empty( $error ) ) {
389 $this->disableRevisionsTemporarily();
390 if ( $load_series ) {
391 update_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, $source->getSeries() );
392 }
393
394 $allow_html = false;
395 $settings = get_post_meta( $chart_id, Visualizer_Plugin::CF_SETTINGS, true );
396 if ( isset( $settings['allowHtml'] ) && intval( $settings['allowHtml'] ) === 1 ) {
397 $allow_html = true;
398 }
399
400 $allow_html = apply_filters( 'visualizer_allow_html_content', $allow_html, $chart_id, $chart );
401
402 if ( $allow_html ) {
403 kses_remove_filters();
404 }
405
406 wp_update_post(
407 array(
408 'ID' => $chart_id,
409 'post_content' => $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) ),
410 )
411 );
412
413 if ( $allow_html ) {
414 kses_init_filters();
415 }
416
417 $chart = get_post( $chart_id );
418 delete_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR );
419 } else {
420 update_post_meta( $chart_id, Visualizer_Plugin::CF_ERROR, sprintf( 'Error while updating chart: %s', $error ) );
421 }
422
423 return $chart;
424 }
425
426 /**
427 * Refresh the Database chart type.
428 *
429 * @access public
430 */
431 public function refreshDbChart() {
432 // NOTE: This use a different key from normal schedule. Updated only by Database chart. Check `visualizer_schedule_import` action.
433 $chart_schedules = get_option( Visualizer_Plugin::CF_DB_SCHEDULE, array() );
434 if ( ! $chart_schedules ) {
435 return;
436 }
437
438 if ( ! defined( 'VISUALIZER_DO_NOT_DIE' ) ) {
439 // define this so that the ajax call does not die
440 // this means that if the new version of pro and the old version of free are installed, only the first chart will be updated
441 define( 'VISUALIZER_DO_NOT_DIE', true );
442 }
443
444 $new_schedules = array();
445 $current_time = time();
446 foreach ( $chart_schedules as $chart_id => $scheduled_time ) {
447
448 // Skip deleted charts.
449 if ( false === get_post_status( $chart_id ) ) {
450 continue;
451 }
452
453 $new_schedules[ $chart_id ] = $scheduled_time;
454
455 // Should we do an update?
456 if ( $scheduled_time > $current_time ) {
457 continue;
458 }
459
460 $this->refresh_db_for_chart( null, $chart_id, true );
461
462 // Clear existing chart cache.
463 $cache_key = Visualizer_Plugin::CF_CHART_CACHE . '_' . $chart_id;
464 if ( get_transient( $cache_key ) ) {
465 delete_transient( $cache_key );
466 }
467
468 $scheduled_hours = get_post_meta( $chart_id, Visualizer_Plugin::CF_DB_SCHEDULE, true );
469 $new_schedules[ $chart_id ] = $current_time + $scheduled_hours * HOUR_IN_SECONDS;
470 }
471 update_option( Visualizer_Plugin::CF_DB_SCHEDULE, $new_schedules );
472 }
473
474 /**
475 * Save flag for existing users.
476 */
477 public function checkIsExistingUser() {
478 $chart_exists = get_option( 'visualizer-new-user', '' );
479 if ( '' === $chart_exists ) {
480 $charts = get_posts(
481 array(
482 'post_type' => Visualizer_Plugin::CPT_VISUALIZER,
483 'fields' => 'ids',
484 )
485 );
486 update_option( 'visualizer-new-user', ! empty( $charts ) ? 'no' : 'yes' );
487 }
488 }
489
490 /**
491 * Add custom cron schedules.
492 *
493 * @param array $schedules The current schedules options.
494 * @return array The modified schedules options.
495 */
496 public function custom_cron_schedules( $schedules ) {
497 $schedules['visualizer_ten_minutes'] = array(
498 'interval' => 600,
499 'display' => __( 'Every 10 minutes', 'visualizer' ),
500 );
501
502 return $schedules;
503 }
504
505 /**
506 * Schedule the recurring DB refresh action.
507 */
508 private function schedule_refresh_db_action(): void {
509 $hook = self::REFRESH_DB_HOOK;
510 $group = self::REFRESH_DB_GROUP;
511 $schedules = wp_get_schedules();
512 $interval_key = apply_filters( 'visualizer_chart_schedule_interval', 'visualizer_ten_minutes' );
513
514 // wp_schedule_event() refuses an unregistered schedule.
515 if ( ! isset( $schedules[ $interval_key ]['interval'] ) ) {
516 $interval_key = 'visualizer_ten_minutes';
517 }
518
519 $interval = isset( $schedules[ $interval_key ]['interval'] ) ? (int) $schedules[ $interval_key ]['interval'] : 600;
520 // gmt_offset can be fractional, and WP-Cron keys its array by this value.
521 $timestamp = (int) ( strtotime( 'midnight' ) - get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
522
523 // West of UTC that midnight is still ahead; start from the previous one.
524 if ( $timestamp > time() ) {
525 $timestamp -= DAY_IN_SECONDS;
526 }
527
528 if (
529 visualizer_can_use_action_scheduler()
530 && function_exists( 'as_next_scheduled_action' )
531 && function_exists( 'as_schedule_recurring_action' )
532 ) {
533 $next = as_next_scheduled_action( $hook, array(), $group );
534 if ( false === $next ) {
535 // Unique: a concurrent request can arrive while nothing is pending.
536 as_schedule_recurring_action( $timestamp, $interval, $hook, array(), $group, true );
537
538 // Returns 0 on failure, so ask the store.
539 $next = as_next_scheduled_action( $hook, array(), $group );
540 }
541
542 // Drop the WP-Cron fallback only once the action exists.
543 if ( false !== $next ) {
544 wp_clear_scheduled_hook( $hook );
545 return;
546 }
547 }
548
549 // Re-arming a live event would pin it to a past timestamp and keep it due.
550 $event = wp_get_scheduled_event( $hook );
551 if ( $event && $event->schedule === $interval_key ) {
552 return;
553 }
554
555 // Schedule first so a refused replacement keeps the old event, then remove the old one
556 // by its timestamp: wp_clear_scheduled_hook() would take the new one too.
557 if ( false === wp_schedule_event( $timestamp, $interval_key, $hook ) ) {
558 return;
559 }
560
561 // A matching timestamp was already overwritten in place.
562 if ( $event && $event->timestamp !== $timestamp ) {
563 wp_unschedule_event( $event->timestamp, $hook );
564 }
565 }
566
567 /**
568 * Check once per window, on init, that something still fires the refresh.
569 */
570 public function maybe_reschedule_refresh_db(): void {
571 if ( get_transient( self::REFRESH_DB_CHECK_TRANSIENT ) ) {
572 return;
573 }
574
575 $this->ensure_refresh_db_action();
576
577 // Cache only a check that left a trigger; a failed one retries next request.
578 if ( $this->has_refresh_db_trigger() ) {
579 set_transient( self::REFRESH_DB_CHECK_TRANSIENT, 1, self::REFRESH_DB_CHECK_WINDOW );
580 }
581 }
582
583 /**
584 * Keep the DB refresh scheduled.
585 *
586 * A killed run never reaches schedule_next_instance(), so Action Scheduler's chain ends there.
587 */
588 public function ensure_refresh_db_action(): void {
589 if ( ! $this->refresh_db_is_settled() ) {
590 $this->schedule_refresh_db_action();
591 }
592 }
593
594 /**
595 * Whether the refresh is on Action Scheduler with no WP-Cron event beside it.
596 *
597 * @return bool
598 */
599 private function refresh_db_is_settled(): bool {
600 $hook = self::REFRESH_DB_HOOK;
601
602 if (
603 visualizer_can_use_action_scheduler()
604 && function_exists( 'as_next_scheduled_action' )
605 && function_exists( 'as_schedule_recurring_action' )
606 ) {
607 return false !== as_next_scheduled_action( $hook, array(), self::REFRESH_DB_GROUP )
608 && ! wp_next_scheduled( $hook );
609 }
610
611 return (bool) wp_next_scheduled( $hook );
612 }
613
614 /**
615 * Whether anything will fire the refresh hook again.
616 *
617 * @return bool
618 */
619 private function has_refresh_db_trigger(): bool {
620 $hook = self::REFRESH_DB_HOOK;
621
622 if ( visualizer_can_use_action_scheduler() && function_exists( 'as_next_scheduled_action' ) ) {
623 if ( false !== as_next_scheduled_action( $hook, array(), self::REFRESH_DB_GROUP ) ) {
624 return true;
625 }
626 }
627
628 return (bool) wp_next_scheduled( $hook );
629 }
630
631 /**
632 * Unschedule the recurring DB refresh action.
633 */
634 private function unschedule_refresh_db_action(): void {
635 $hook = self::REFRESH_DB_HOOK;
636 $group = self::REFRESH_DB_GROUP;
637 if ( function_exists( 'as_unschedule_all_actions' ) ) {
638 as_unschedule_all_actions( $hook, array(), $group );
639 }
640 wp_clear_scheduled_hook( $hook );
641 }
642 }
643