plugin = $plugin; $this->settings = $settings; $this->state_report = $state_report; $this->cpt_settings_storages = $cpt_settings_storages; } public static function has_route_hooks( Route_Detector $route_detector ): bool { return true; } public function set_route_hooks( Route_Detector $route_detector ): void { if ( $this->settings->is_automatic_reports_disabled() ) { $this->set_disabled_hooks(); return; } $this->set_enabled_hooks( $route_detector ); } public static function hook(): string { return Hard_Layout_Cpt::cpt_name() . '_refresh'; } // WP Cron is unreliable. Execute also within the dashboard (in case the time has come). public function reschedule_outdated(): void { $check_time = wp_next_scheduled( self::hook() ); if ( false !== $check_time && $check_time > time() ) { return; } if ( false !== $check_time ) { // firstly, unschedule the outdated event. wp_unschedule_event( $check_time, self::hook() ); } // then send and schedule the next. $this->send_and_schedule_next(); } public function init(): void { $check_time = wp_next_scheduled( self::hook() ); if ( false === $check_time ) { $this->schedule_next(); return; } // WP Cron is unreliable. Execute also within the dashboard (in case the time has come). self::add_action( 'admin_init', array( $this, 'reschedule_outdated' ) ); } public function show_automatic_reports_notice(): void { $dismiss_key = 'av-reports-dismiss'; $nonce_name = 'av-reports-notice'; if ( '' !== Query_Arguments::get_string_for_admin_action( $dismiss_key, $nonce_name ) && Avf_User::can_manage() ) { $this->settings->set_is_automatic_reports_confirmed( true ); $this->settings->save(); return; } echo '
'; echo '

'; esc_html_e( 'The Advanced Views plugin sends automatic error and usage reports to developers, enabling faster issue resolution and plugin improvement.', 'acf-views' ); echo '
'; esc_html_e( 'Automatic reports do not include any private or sensitive information and can be disabled in the plugin settings.', 'acf-views' ); if ( Avf_User::can_manage() ) { $hide_url = add_query_arg( array( $dismiss_key => 1, '_wpnonce' => wp_create_nonce( $nonce_name ), ) ); echo '

'; printf( '%s', esc_url( $hide_url ), esc_html( __( 'Got it, hide', 'acf-views' ) ) ); } echo '

'; echo '
'; } public function send_and_schedule_next(): void { if ( ! $this->settings->is_automatic_reports_disabled() ) { // always unchedule, as this method can be called from outside (e.g. the License form submission). $this->unschedule(); $this->make_usage_request(); $this->schedule_next(); } } public function unschedule(): void { $check_time = wp_next_scheduled( self::hook() ); if ( false === $check_time ) { return; } wp_unschedule_event( $check_time, self::hook() ); } protected function make_usage_request(): void { File_System_Loader::instance() ->add_loaded_callback( function () { $fields = array_merge( $this->state_report->get_primary_fields(), $this->get_usage_data(), ); $this->send_json_request( self::USAGE_ENDPOINT_URL, $fields ); } ); } protected function schedule_next(): void { // next_check_time in seconds. Randomly to avoid DDOS. $next_check_time = time() + wp_rand( self::DELAY_MIN_HR * 3600, self::DELAY_MAX_HRS * 3600 ); wp_schedule_single_event( $next_check_time, self::hook() ); } /** * @return array */ protected function get_usage_data(): array { $error_logs = $this->get_logger()->get_error_logs(); if ( strlen( $error_logs ) > 5000 ) { $error_logs = substr( $error_logs, 0, 5000 ); } // IT DOESN'T SEND ANY PRIVATE DATA, only a DOMAIN. // And the domain is only used to avoid multiple counting from one website. return array( '_viewsCount' => $this->count_cpt_entities( Hard_Layout_Cpt::cpt_name(), array( Plugin_Environment::STARTER_LAYOUT_ID ) ), '_cardsCount' => $this->count_cpt_entities( Hard_Post_Selection_Cpt::cpt_name(), array( Plugin_Environment::STARTER_SELECTION_ID ) ), '_vendorPlugins' => $this->detect_vendor_plugins_usage(), '_isFsStorageActive' => $this->is_fs_storage_active(), '_gitRepositoriesCount' => count( $this->settings->get_git_repositories() ), '_language' => get_bloginfo( 'language' ), '_phpErrors' => $error_logs, '_templateEngines' => $this->calc_template_engines_usage(), ); } protected function is_fs_storage_active(): bool { $storage = reset( $this->cpt_settings_storages ); // check only the first one, as both Layout & Selection use the same setting. if ( $storage instanceof Cpt_Settings_Storage ) { return $storage->get_file_system()->is_active(); } return false; } /** * @return array plugin => is active */ protected function detect_vendor_plugins_usage(): array { $is_acf_pro = class_exists( 'acf_pro' ); $is_elementor_pro = class_exists( 'ElementorPro\Plugin' ); // 'is_plugin_active()' is available only later. return array( 'acfPro' => $is_acf_pro, 'acf' => ! $is_acf_pro && class_exists( 'acf' ) && ! defined( 'ACF_VIEWS_INNER_ACF' ), 'woo' => class_exists( 'WooCommerce' ), 'metaBox' => class_exists( 'RW_Meta_Box' ), 'pods' => class_exists( 'Pods' ), 'elementor' => ! $is_elementor_pro && class_exists( 'Elementor\Plugin' ), 'elementorPro' => $is_elementor_pro, ); } /** * @return array engine => count */ protected function calc_template_engines_usage(): array { $stat = array(); foreach ( $this->cpt_settings_storages as $cpt_settings_storage ) { foreach ( $cpt_settings_storage->get_all() as $cpt_settings ) { $engine = $cpt_settings->get_template_engine(); if ( strlen( $engine ) > 0 ) { $stat[ $engine ] = int( $stat, $engine ); ++$stat[ $engine ]; } } } return $stat; } /** * @param string[] $exclude_post_names */ protected function count_cpt_entities( string $post_type, array $exclude_post_names = array() ): int { foreach ( $this->cpt_settings_storages as $cpt_settings_storage ) { $db_management = $cpt_settings_storage->get_db_management(); $current_post_type = $db_management->get_post_type(); if ( $post_type === $current_post_type ) { $unique_ids = $db_management->get_unique_ids(); $ids_to_count = array_diff( $unique_ids, $exclude_post_names ); return count( $ids_to_count ); } } return 0; } protected function set_disabled_hooks(): void { // still sign-up the CRON job, so if it was scheduled before, then will be called without issues. self::add_action( self::hook(), function (): void { // nothing to do. } ); } protected function set_enabled_hooks( Route_Detector $route_detector ): void { self::add_action( 'init', array( $this, 'init' ) ); // CRON job. self::add_action( self::hook(), array( $this, 'send_and_schedule_next' ) ); $is_cpt_list_screen = $route_detector->is_cpt_admin_route( Hard_Layout_Cpt::cpt_name(), Route_Detector::CPT_LIST ) || $route_detector->is_cpt_admin_route( Hard_Post_Selection_Cpt::cpt_name(), Route_Detector::CPT_LIST ); if ( $is_cpt_list_screen && ! $this->settings->is_automatic_reports_confirmed() ) { self::add_action( 'admin_notices', array( $this, 'show_automatic_reports_notice' ) ); } } }