| Current File : /home/abedqjib/krishanfoundation.com/wp-content/plugins/surerank/inc/api/learn.php |
<?php
/**
* Learn class
*
* REST endpoints for the Learn (guided checklist) section.
*
* Step IDs here are the source of truth for validation. Keep this map in
* sync with src/apps/admin-learn/learn-config.js whenever steps change.
*
* @package SureRank\Inc\API
*/
namespace SureRank\Inc\API;
use SureRank\Inc\Functions\Send_Json;
use SureRank\Inc\Functions\Settings;
use SureRank\Inc\Traits\Get_Instance;
use WP_REST_Request;
use WP_REST_Server;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class Learn
*
* Handles Learn section progress REST endpoints.
*
* @since 1.7.4
*/
class Learn extends Api_Base {
use Get_Instance;
/**
* Option key storing the site-wide progress.
*/
public const OPTION_KEY = 'surerank_learn_progress';
/**
* Schema version for the persisted structure.
*/
public const SCHEMA_VERSION = 1;
/**
* Route - Learn progress.
*/
protected const LEARN_PROGRESS = '/learn-progress';
/**
* Register API routes.
*
* @since 1.7.4
* @return void
*/
public function register_routes() {
register_rest_route(
$this->get_api_namespace(),
self::LEARN_PROGRESS,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_progress' ],
'permission_callback' => [ $this, 'validate_permission' ],
'role_capability' => 'global_setting',
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'update_progress' ],
'permission_callback' => [ $this, 'validate_permission' ],
'role_capability' => 'global_setting',
'args' => [
'chapter_id' => [
'type' => 'string',
'required' => true,
'description' => __( 'Chapter identifier.', 'surerank' ),
'sanitize_callback' => 'sanitize_key',
'validate_callback' => static function ( $value ) {
return is_string( $value ) && array_key_exists( $value, self::get_allowed_steps() );
},
],
'step_id' => [
'type' => 'string',
'required' => true,
'description' => __( 'Step identifier within the chapter.', 'surerank' ),
'sanitize_callback' => 'sanitize_key',
'validate_callback' => static function ( $value, $request ) {
if ( ! is_string( $value ) ) {
return false;
}
$chapter_id = $request->get_param( 'chapter_id' );
$allowed = self::get_allowed_steps();
return isset( $allowed[ $chapter_id ] ) && in_array( $value, $allowed[ $chapter_id ], true );
},
],
'completed' => [
'type' => 'boolean',
'required' => true,
'description' => __( 'Whether the step is completed.', 'surerank' ),
'sanitize_callback' => 'rest_sanitize_boolean',
],
],
],
]
);
}
/**
* Allowed chapter -> step IDs map.
*
* Mirror of LEARN_CHAPTERS in src/apps/admin-learn/learn-config.js. Both
* lists must match. PHP is the validator; JS is the renderer.
*
* @since 1.7.4
* @return array<string, array<int, string>>
*/
public static function get_allowed_steps() {
return [
'getting_started' => [
'connect_gsc',
'title_templates',
'homepage_seo',
'migrate',
],
'find_you' => [
'xml_sitemap',
'robots_instructions',
'canonicals',
'site_seo_issues',
],
'optimize' => [
'page_meta',
'page_seo_check',
'schema',
'image_alt',
],
'social' => [
'og_fallback',
'facebook_og',
'x_cards',
'override_per_page',
],
];
}
/**
* Read the persisted progress structure with normalisation.
*
* @since 1.7.4
* @return array<string, mixed>
*/
public static function get_user_progress() {
$stored = get_option( self::OPTION_KEY, [] );
if ( ! is_array( $stored ) ) {
$stored = [];
}
return [
'version' => isset( $stored['version'] ) ? (int) $stored['version'] : self::SCHEMA_VERSION,
'chapters' => isset( $stored['chapters'] ) && is_array( $stored['chapters'] ) ? $stored['chapters'] : [],
];
}
/**
* Compute auto-detected step completions from existing settings.
*
* Steps where the underlying signal already says "done" are surfaced
* here so the UI can lock them as complete without persisting fake
* meta entries.
*
* @since 1.7.4
* @return array<string, array<string, bool>>
*/
public static function compute_auto_detected() {
$auto_detected = [];
// GSC connection.
if ( class_exists( '\SureRank\Inc\GoogleSearchConsole\Controller' ) ) {
$gsc_class = '\SureRank\Inc\GoogleSearchConsole\Controller';
if ( method_exists( $gsc_class, 'get_instance' ) ) {
$gsc_status = $gsc_class::get_instance()->get_auth_status();
if ( $gsc_status ) {
$auto_detected['getting_started']['connect_gsc'] = true;
}
}
}
// Migration.
if ( class_exists( '\SureRank\Inc\API\Migrations' ) && Migrations::has_migration_ever_completed() ) {
$auto_detected['getting_started']['migrate'] = true;
}
// Sitemap enabled.
if ( Settings::get( 'enable_xml_sitemap' ) ) {
$auto_detected['find_you']['xml_sitemap'] = true;
}
// Site SEO analysis ever run.
$site_seo_checks = get_option( 'surerank_site_seo_checks', [] );
if ( ! empty( $site_seo_checks ) ) {
$auto_detected['find_you']['site_seo_issues'] = true;
}
// Schema enabled.
if ( Settings::get( 'enable_schemas' ) ) {
$auto_detected['optimize']['schema'] = true;
}
// OG fallback image set.
$fallback_image = Settings::get( 'fallback_image' );
if ( ! empty( $fallback_image ) ) {
$auto_detected['social']['og_fallback'] = true;
}
// X username configured.
$twitter_username = Settings::get( 'twitter_profile_username' );
if ( ! empty( $twitter_username ) ) {
$auto_detected['social']['x_cards'] = true;
}
return $auto_detected;
}
/**
* GET /learn-progress.
*
* @since 1.7.4
* @param WP_REST_Request<array<string, mixed>> $request Request object.
* @return void
*/
public function get_progress( $request ) {
unset( $request );
Send_Json::success(
[
'progress' => self::get_user_progress(),
'auto_detected' => self::compute_auto_detected(),
]
);
}
/**
* POST /learn-progress.
*
* @since 1.7.4
* @param WP_REST_Request<array<string, mixed>> $request Request object.
* @return void
*/
public function update_progress( $request ) {
$chapter_id = (string) $request->get_param( 'chapter_id' );
$step_id = (string) $request->get_param( 'step_id' );
$completed = (bool) $request->get_param( 'completed' );
$data = self::get_user_progress();
if ( ! isset( $data['chapters'] ) || ! is_array( $data['chapters'] ) ) {
$data['chapters'] = [];
}
if ( ! isset( $data['chapters'][ $chapter_id ] ) || ! is_array( $data['chapters'][ $chapter_id ] ) ) {
$data['chapters'][ $chapter_id ] = [];
}
if ( $completed ) {
$data['chapters'][ $chapter_id ][ $step_id ] = [
'completed_at' => time(),
'completed_by' => get_current_user_id(),
];
} else {
unset( $data['chapters'][ $chapter_id ][ $step_id ] );
if ( empty( $data['chapters'][ $chapter_id ] ) ) {
unset( $data['chapters'][ $chapter_id ] );
}
}
$data['version'] = self::SCHEMA_VERSION;
update_option( self::OPTION_KEY, $data, false );
// Signal the analytics layer to resend the Learn snapshot on its next flush.
set_transient( 'surerank_learn_progress_changed', 1, DAY_IN_SECONDS );
Send_Json::success(
[
'progress' => self::get_user_progress(),
'auto_detected' => self::compute_auto_detected(),
]
);
}
}