Files
2026-04-28 15:13:50 +02:00

398 lines
12 KiB
PHP

<?php
namespace AIOSEO\Plugin\Common\Api;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class SeoChecklist {
/**
* Get all checks (simple endpoint for initial load).
*
* @since 4.9.4
*
* @return \WP_REST_Response The response.
*/
public static function getChecks() {
// User should have access to the general settings or setup wizard.
if ( ! aioseo()->access->hasCapability( 'aioseo_general_settings' ) && ! aioseo()->access->hasCapability( 'aioseo_setup_wizard' ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'You do not have access to the SEO Checklist.'
], 403 );
}
return new \WP_REST_Response( [
'success' => true,
'checks' => array_values( aioseo()->seoChecklist->getChecks() )
], 200 );
}
/**
* Get the list of completed check names (lightweight endpoint for polling).
*
* @since 4.9.4
*
* @return \WP_REST_Response The response.
*/
public static function getCompletedChecks() {
// User should have access to the general settings or setup wizard.
if ( ! aioseo()->access->hasCapability( 'aioseo_general_settings' ) && ! aioseo()->access->hasCapability( 'aioseo_setup_wizard' ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'You do not have access to the SEO Checklist.'
], 403 );
}
$checks = aioseo()->seoChecklist->getChecks();
$totalCompleted = 0;
$totalIncomplete = 0;
foreach ( $checks as $check ) {
if ( ! empty( $check['completed'] ) ) {
$totalCompleted++;
} else {
$totalIncomplete++;
}
}
return new \WP_REST_Response( [
'success' => true,
'totalCompleted' => $totalCompleted,
'totalIncomplete' => $totalIncomplete
], 200 );
}
/**
* Fetch checks with filtering, sorting, and pagination support.
*
* @since 4.9.4
*
* @param \WP_REST_Request $request The request.
* @return \WP_REST_Response The response.
*/
public static function fetchChecks( $request ) {
// User should have access to the general settings or setup wizard.
if ( ! aioseo()->access->hasCapability( 'aioseo_general_settings' ) && ! aioseo()->access->hasCapability( 'aioseo_setup_wizard' ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'You do not have access to the SEO Checklist.'
], 403 );
}
$body = $request->get_json_params();
$filter = ! empty( $body['filter'] ) ? sanitize_text_field( $body['filter'] ) : 'all';
$orderBy = ! empty( $body['orderBy'] ) ? sanitize_text_field( $body['orderBy'] ) : 'priority';
$orderDir = ! empty( $body['orderDir'] ) ? strtoupper( sanitize_text_field( $body['orderDir'] ) ) : 'ASC';
$limit = ! empty( $body['limit'] ) ? intval( $body['limit'] ) : 10;
$offset = ! empty( $body['offset'] ) ? intval( $body['offset'] ) : 0;
$additionalFilters = ! empty( $body['additionalFilters'] ) ? $body['additionalFilters'] : [];
$checks = array_values( aioseo()->seoChecklist->getChecks() );
// Filter by status.
if ( 'completed' === $filter ) {
$checks = array_filter( $checks, function ( $check ) {
return ! empty( $check['completed'] );
} );
} elseif ( 'incomplete' === $filter ) {
$checks = array_filter( $checks, function ( $check ) {
return empty( $check['completed'] );
} );
}
// Filter by priority (from additional filters).
if ( ! empty( $additionalFilters['priority'] ) && 'all' !== $additionalFilters['priority'] ) {
$priorityFilter = sanitize_text_field( $additionalFilters['priority'] );
$checks = array_filter( $checks, function ( $check ) use ( $priorityFilter ) {
return $check['priority'] === $priorityFilter;
} );
}
// Re-index array after filtering.
$checks = array_values( $checks );
$total = count( $checks );
// Sort checks.
usort( $checks, function ( $a, $b ) use ( $orderBy, $orderDir ) {
$priorityOrder = [
'high' => 1,
'medium' => 2,
'low' => 3,
'optional' => 4
];
if ( 'priority' === $orderBy ) {
// Primary sort: priority
$aVal = $priorityOrder[ $a['priority'] ?? '' ] ?? 99;
$bVal = $priorityOrder[ $b['priority'] ?? '' ] ?? 99;
$result = $aVal <=> $bVal;
// Apply direction to primary sort
$result = 'DESC' === $orderDir ? -$result : $result;
if ( 0 !== $result ) {
return $result;
}
// Secondary sort: undismissable first (dismissable = false comes first)
$aDismissable = isset( $a['dismissable'] ) ? $a['dismissable'] : true;
$bDismissable = isset( $b['dismissable'] ) ? $b['dismissable'] : true;
$dismissableResult = (int) $aDismissable <=> (int) $bDismissable;
if ( 0 !== $dismissableResult ) {
return $dismissableResult;
}
// Tertiary sort: Setup Wizard always first among dismissable tasks
$aIsSetupWizard = isset( $a['name'] ) && 'finishSetupWizard' === $a['name'];
$bIsSetupWizard = isset( $b['name'] ) && 'finishSetupWizard' === $b['name'];
$setupWizardResult = (int) $bIsSetupWizard <=> (int) $aIsSetupWizard;
if ( 0 !== $setupWizardResult ) {
return $setupWizardResult;
}
// Quaternary sort: time estimate (low to high)
$aTime = is_array( $a['time'] ) ? ( $a['time']['value'] ?? 0 ) : 0;
$bTime = is_array( $b['time'] ) ? ( $b['time']['value'] ?? 0 ) : 0;
return $aTime <=> $bTime;
} elseif ( 'time' === $orderBy ) {
// Sort by the time value (in seconds).
$aVal = is_array( $a['time'] ) ? ( $a['time']['value'] ?? 0 ) : 0;
$bVal = is_array( $b['time'] ) ? ( $b['time']['value'] ?? 0 ) : 0;
} else {
$aVal = $a[ $orderBy ] ?? '';
$bVal = $b[ $orderBy ] ?? '';
}
$result = $aVal <=> $bVal;
return 'DESC' === $orderDir ? -$result : $result;
} );
// Apply pagination.
$checks = array_slice( $checks, $offset, $limit );
// Build filters with counts.
$allChecks = array_values( aioseo()->seoChecklist->getChecks() );
$completedCount = count( array_filter( $allChecks, function ( $c ) {
return ! empty( $c['completed'] );
} ) );
$totalCount = count( $allChecks );
$filters = [
[
'slug' => 'incomplete',
'name' => __( 'Incomplete', 'all-in-one-seo-pack' ),
'count' => $totalCount - $completedCount,
'active' => 'incomplete' === $filter
],
[
'slug' => 'completed',
'name' => __( 'Completed', 'all-in-one-seo-pack' ),
'count' => $completedCount,
'active' => 'completed' === $filter
]
];
return new \WP_REST_Response( [
'success' => true,
'rows' => $checks,
'totals' => [
'total' => $total,
'pages' => 0 === $total ? 1 : ceil( $total / $limit ),
'page' => 1
],
'filters' => $filters,
'completedCount' => $completedCount,
'totalCount' => $totalCount
], 200 );
}
/**
* Complete a check.
*
* @since 4.9.4
*
* @param \WP_REST_Request $request The request object.
* @return \WP_REST_Response The response object.
*/
public static function completeCheck( $request ) {
// User should have access to the general settings or setup wizard.
if ( ! aioseo()->access->hasCapability( 'aioseo_general_settings' ) && ! aioseo()->access->hasCapability( 'aioseo_setup_wizard' ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'You do not have access to the SEO Checklist.'
], 403 );
}
$body = $request->get_json_params();
$check = isset( $body['check'] ) ? sanitize_text_field( $body['check'] ) : '';
if ( ! $check ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'Missing check.'
], 400 );
}
aioseo()->seoChecklist->completeCheck( $check );
return new \WP_REST_Response( [
'success' => true,
'checks' => aioseo()->seoChecklist->getChecks()
], 200 );
}
/**
* Uncomplete a check (remove completed status).
*
* @since 4.9.4
*
* @param \WP_REST_Request $request The request object.
* @return \WP_REST_Response The response object.
*/
public static function uncompleteCheck( $request ) {
// User should have access to the general settings.
if ( ! aioseo()->access->hasCapability( 'aioseo_general_settings' ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'You do not have access to the SEO Checklist.'
], 403 );
}
$body = $request->get_json_params();
$check = isset( $body['check'] ) ? sanitize_text_field( $body['check'] ) : '';
if ( ! $check ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'Missing check.'
], 400 );
}
aioseo()->seoChecklist->uncompleteCheck( $check );
return new \WP_REST_Response( [
'success' => true,
'checks' => aioseo()->seoChecklist->getChecks()
], 200 );
}
/**
* Bulk complete checks.
*
* @since 4.9.4
*
* @param \WP_REST_Request $request The request object.
* @return \WP_REST_Response The response object.
*/
public static function bulkCompleteChecks( $request ) {
// User should have access to the general settings.
if ( ! aioseo()->access->hasCapability( 'aioseo_general_settings' ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'You do not have access to the SEO Checklist.'
], 403 );
}
$body = $request->get_json_params();
$checks = isset( $body['checks'] ) ? $body['checks'] : [];
if ( empty( $checks ) || ! is_array( $checks ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'Missing checks.'
], 400 );
}
foreach ( $checks as $check ) {
aioseo()->seoChecklist->completeCheck( sanitize_text_field( $check ) );
}
return new \WP_REST_Response( [
'success' => true,
'checks' => aioseo()->seoChecklist->getChecks()
], 200 );
}
/**
* Bulk uncomplete checks.
*
* @since 4.9.4
*
* @param \WP_REST_Request $request The request object.
* @return \WP_REST_Response The response object.
*/
public static function bulkUncompleteChecks( $request ) {
// User should have access to the general settings.
if ( ! aioseo()->access->hasCapability( 'aioseo_general_settings' ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'You do not have access to the SEO Checklist.'
], 403 );
}
$body = $request->get_json_params();
$checks = isset( $body['checks'] ) ? $body['checks'] : [];
if ( empty( $checks ) || ! is_array( $checks ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'Missing checks.'
], 400 );
}
foreach ( $checks as $check ) {
aioseo()->seoChecklist->uncompleteCheck( sanitize_text_field( $check ) );
}
return new \WP_REST_Response( [
'success' => true,
'checks' => aioseo()->seoChecklist->getChecks()
], 200 );
}
/**
* Execute an action for a check.
*
* @since 4.9.4
*
* @param \WP_REST_Request $request The request object.
* @return \WP_REST_Response The response object.
*/
public static function doAction( $request ) {
// User should have access to the general settings at a minimum.
if ( ! aioseo()->access->hasCapability( 'aioseo_general_settings' ) ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'You do not have access to the SEO Checklist.'
], 403 );
}
$body = $request->get_json_params();
$check = isset( $body['check'] ) ? sanitize_text_field( $body['check'] ) : '';
$callback = isset( $body['callback'] ) ? sanitize_text_field( $body['callback'] ) : '';
if ( ! $check || ! $callback ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'Missing check or callback.'
], 400 );
}
$success = aioseo()->seoChecklist->doAction( $check, $callback );
if ( ! $success ) {
return new \WP_REST_Response( [
'success' => false,
'message' => 'Failed to execute action.'
], 400 );
}
// Mark the check as completed.
aioseo()->seoChecklist->completeCheck( $check );
return new \WP_REST_Response( [
'success' => $success,
'checks' => aioseo()->seoChecklist->getChecks()
], 200 );
}
}