Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
108be2a
Add a license page and connector activating a free license to myDot
pattonwebz Dec 19, 2025
6cb0b73
Implement periodic license checks and SSL verification
pattonwebz Dec 19, 2025
5fec71e
Init the connector in the main plugin class
pattonwebz Dec 19, 2025
2acb695
Update the LicensePage with a secondary form for registering sites fo…
pattonwebz Dec 20, 2025
e8895ac
Update the Connector to handle registering and unregistering sites fo…
pattonwebz Dec 20, 2025
e798fb1
Allow a jwt token in request to validate for getting scan stats
pattonwebz Dec 20, 2025
9f16a33
Include ext-openssl as a requrement for the plugin as well as ext-json
pattonwebz Dec 20, 2025
f6e3cf1
Update composer.lock after changing requirement packages to include i…
pattonwebz Dec 20, 2025
3efe96e
Remove handle_jwt_registration() method as it is redundant
pattonwebz Dec 20, 2025
50231be
Correct the endpoint and option key used to connect to and store lice…
pattonwebz Dec 20, 2025
0df4710
Add key cycling handling and verify `iss`, `aud` and `nbf` values on key
pattonwebz Dec 22, 2025
4a4463b
Fix expected array key that will hold the public key when revalidating
pattonwebz Jan 2, 2026
cc7d926
Rename the License tab to Connected Services and handle some free vs …
pattonwebz Jan 7, 2026
095f889
Update some links on connected services page to use the helper for li…
pattonwebz Jan 7, 2026
4b4f37d
Merge branch 'william/pro-286-update-the-free-plugin-to-be-able-to-ha…
pattonwebz Jan 7, 2026
79614f1
Fix typo in tab header copy
pattonwebz Jan 7, 2026
5aa493d
Handle the parts splitting for bearer checking more safely
pattonwebz Jan 7, 2026
b058479
Merge branch 'william/pro-286-update-the-free-plugin-to-be-able-to-ha…
pattonwebz Jan 7, 2026
c20eb30
Use link helper wrapper function for expired license key link
pattonwebz Jan 7, 2026
d8a1dd5
Use edac rather than edacp for schedule to check license
pattonwebz Jan 7, 2026
1c77fb1
Check if the license_data has a license key before accesing it
pattonwebz Jan 7, 2026
512fdec
Merge branch 'william/pro-286-update-the-free-plugin-to-be-able-to-ha…
pattonwebz Jan 7, 2026
2a2ed34
Update a since tag to reflect not yet known release version
pattonwebz Jan 7, 2026
518d038
Define a default capability for viewing connected services page
pattonwebz Jan 7, 2026
cff4290
Handle some admin notice display on the connected services page for e…
pattonwebz Jan 7, 2026
ce98297
Hook the admin notices in at a later action to avoid them being unhooked
pattonwebz Jan 8, 2026
45af7d6
When the pro plugin has license deactivated we should unregister the …
pattonwebz Jan 8, 2026
0800d3e
Don't get the option twice when doing periodic_check_license
pattonwebz Jan 8, 2026
7ef9049
Add helper to figure out if we are using the free key or the pro key …
pattonwebz Jan 8, 2026
3f57bcb
Calculate the top 5 posts with issues when generating the scan stats
pattonwebz Jan 8, 2026
543f666
Fix mismatched key for top post
pattonwebz Jan 8, 2026
fdc1452
Fix typo in docblock
pattonwebz Jan 9, 2026
b0e986a
For top 5 posts get only those in the scannable types or status array…
pattonwebz Jan 9, 2026
cb2c23d
Fix some of the status notices for adding and removing sites from con…
pattonwebz Jan 9, 2026
ab0602e
Show the connected services page to those with manage_options capability
pattonwebz Jan 9, 2026
48cf076
Fix order of vip_safe_wp_remote_get params
pattonwebz Jan 12, 2026
02989fe
Handle base64url_decode in a strict way
pattonwebz Jan 12, 2026
ffebe5c
Validate only if there is an aud, exp and iss value
pattonwebz Jan 12, 2026
027ddfa
Fix mismatched filter name in docblock
pattonwebz Jan 12, 2026
15e22e4
Clear the license checking cron when deactivated
pattonwebz Jan 12, 2026
cb1950e
Swap to init for the license check cron scheduler
pattonwebz Jan 12, 2026
0103f65
Set the prod url for the API_ENDPOINT and filter it when using
pattonwebz Jan 12, 2026
5f60721
Use the strict base64url decode method for signature
pattonwebz Jan 12, 2026
b38b1e0
Use prefix on a filter for the mydot api endpoint
pattonwebz Jan 12, 2026
ef2e00d
Align vip_safe_wp_remote_get timeout and reties with expected boundary
pattonwebz Jan 12, 2026
8fb2f7a
Never need to store the jwt token on this site, can decrypt with the …
pattonwebz Jan 13, 2026
bac44fb
Disable free periodic check for license when pro is enabled
pattonwebz Jan 13, 2026
0f52eab
Make unregister method public so it can be added to an action
pattonwebz Jan 13, 2026
5856ef4
Use site id rather than a no longer stored jwt for connected check
pattonwebz Jan 13, 2026
ceb87f5
Handle showing correct button when pro is enabled and site is connected
pattonwebz Jan 13, 2026
3cefde3
Pass the site id when unregistering
pattonwebz Jan 13, 2026
acdcec7
Bump version to a phony alpha for testing sake
pattonwebz Jan 13, 2026
5a3b4e2
Add a filter with a test url for mydot endpoint that will be used in …
pattonwebz Jan 13, 2026
6362687
Merge remote-tracking branch 'origin/develop' into william/pro-286-up…
pattonwebz Feb 2, 2026
7232377
Add a note about a testing api remap
pattonwebz Feb 2, 2026
432f5be
Merge branch 'develop' into william/pro-286-update-the-free-plugin-to…
pattonwebz Mar 23, 2026
bc9ed1d
Updating composer lock after backmerge of develop here - both branche…
pattonwebz Mar 23, 2026
78ebbd3
Add escaping to post title and permalinks passed through rest in top_…
pattonwebz Mar 23, 2026
ee1c1f5
Verify a 200 status code from periodic_check_license()
pattonwebz Mar 23, 2026
cb5b420
Enforce numeric not before value (nbf)
pattonwebz Mar 23, 2026
6b463a6
Clamp the max timeout for vip_safe_wp_remote_get to max of 5
pattonwebz Mar 23, 2026
677e531
Make sure that the limit is set at a positive int otherwise return em…
pattonwebz Mar 23, 2026
84717fa
Swap to single shared key of edacp across free and pro plugins
pattonwebz Mar 24, 2026
9861bf8
Bump DB version for new key to 1.0.7 to trigger migration
pattonwebz Mar 24, 2026
acbf0d0
Make the product id filterable for easier local and staging site testing
pattonwebz Mar 24, 2026
f9969ed
Use item_id as the product identifier instead of name
pattonwebz Mar 24, 2026
da52157
Add method to store top issues found on site in the site stats data
pattonwebz Mar 24, 2026
87e5998
Add Accessibility Reports settings page and UI enhancements
SteveJonesDev Apr 2, 2026
f1f5b04
For top issues get them based on severity first, then by count.
pattonwebz Apr 2, 2026
9793b90
Agent-Logs-Url: https://github.com/equalizedigital/accessibility-chec…
Copilot Apr 3, 2026
e026b99
Fix undefined $preview_data in AccessibilityReportsPage::render_page()
Copilot Apr 3, 2026
8cad96c
Merge pull request #1605 from equalizedigital/codex/accessibility-rep…
pattonwebz Apr 3, 2026
2bb0fa9
Cleanup the license key on activation and it's state always
pattonwebz Apr 3, 2026
7ba2c42
Bump version to 1.40.0-alpha
pattonwebz Apr 6, 2026
553fc59
Update product id for free reports to 24
pattonwebz Apr 6, 2026
5d929d0
Determine if we are using a pro key or not
pattonwebz Apr 10, 2026
d48231a
Fix SVG viewBox casing and update report summary text; adjust status …
SteveJonesDev Apr 14, 2026
29d17c5
Update next report date calculation and change button link to manage …
SteveJonesDev Apr 14, 2026
70315b7
fix(mydot): implement free/pro fallback state handling and metadata r…
pattonwebz Apr 15, 2026
451a981
test(mydot): add coverage for fallback, cleanup, and metadata inference
pattonwebz Apr 15, 2026
e6001b1
docs(mydot): add free-pro license state machine documentation
pattonwebz Apr 15, 2026
95122e6
Merge remote-tracking branch 'origin/william/pro-286-update-the-free-…
pattonwebz Apr 15, 2026
5c60210
Fix local disconnect cleanup to clear stale Pro license state
pattonwebz Apr 16, 2026
a9cb69e
Make accessibility reports UI fallback-aware for Pro-to-free transitions
pattonwebz Apr 16, 2026
6b06d43
Use effective license context in connected services and notices
pattonwebz Apr 16, 2026
52d2b2b
Add free->pro enrollment handoff and clear fallback marker on free re…
pattonwebz Apr 16, 2026
195a62f
Guard reports fallback UI from stale connected state and use stored n…
pattonwebz Apr 16, 2026
ab13d4c
Guard connected services fallback UI from stale connected state
pattonwebz Apr 16, 2026
a1aa1a9
Preserve active Pro license when disabling email reports
pattonwebz Apr 16, 2026
1567a9b
Use deactivation hook license args for connector unregistration
pattonwebz Apr 16, 2026
9ae7e66
Fix: Clear Pro license error on recovery during periodic checks
pattonwebz Apr 16, 2026
b6a0562
Remove a testing filter added when working on dev for this
pattonwebz Apr 16, 2026
9ade581
Don't respond with stats if site is unregistered even if JWT is valid
pattonwebz Apr 17, 2026
d4285bc
Try handle pro degraded license state better
pattonwebz Apr 20, 2026
46a89da
Remove a dev-only planning file from repo
pattonwebz Apr 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
353 changes: 353 additions & 0 deletions admin/AdminPage/ConnectedServicesPage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
<?php
/**
* Connected Services Page and Notices for Accessibility Checker
*
* Handles the rendering and functionality of the license/connected services settings page
* in the WordPress admin, including form submission, error notices, and
* license status management.
*
* @package Accessibility_Checker
* @since 1.34.0
*/

namespace EqualizeDigital\AccessibilityChecker\Admin\AdminPage;

use EqualizeDigital\AccessibilityChecker\MyDot\Connector;

/**
* Class ConnectedServicesPage
*
* Manages connected services (license) page display and functionality within the admin settings area.
*
* @since 1.xx.x
Comment thread
pattonwebz marked this conversation as resolved.
Comment thread
pattonwebz marked this conversation as resolved.
*/
class ConnectedServicesPage implements PageInterface {

/**
* The capability required to access the settings page.
*
* @since 1.xx.x
*
* @var string
*/
private string $settings_capability;

/**
* Constructor
*
* @since 1.xx.x
*
* @param string $settings_capability The capability required to view/edit license settings.
*/
public function __construct( $settings_capability ) {
$this->settings_capability = $settings_capability;
}

/**
* Register hooks to add the connected services page to the settings tabs
* and handle license-related notices.
*
* @since 1.xx.x
*
* @return void
*/
public function add_page() {
// Add Connected Services tab to settings page.
add_filter(
'edac_filter_settings_tab_items',
[ $this, 'add_connected_services_tab' ]
);

// Render Connected Services tab content when active.
add_action(
'edac_settings_tab_content',
[ $this, 'maybe_render_tab_content' ]
);

// Register admin notice display with higher priority than default.
add_action(
'in_admin_header',
[ $this, 'register_admin_notices' ],
1001
);
}

/**
* Add Connected Services tab to settings tabs array.
*
* @since 1.xx.x
*
* @param array $tabs Array of registered settings tabs.
*
* @return array Modified tabs array with Connected Services tab added.
*/
public function add_connected_services_tab( $tabs ) {
$tabs[] = [
'slug' => 'connected-services',
'label' => esc_html__( 'Connected Services', 'accessibility-checker' ),
'order' => 99, // Last tab.
'capability' => $this->settings_capability,
];
return $tabs;
}

/**
* Conditionally render the tab content if the current tab is 'connected-services'.
*
* @since 1.xx.x
*
* @param string $settings_tab The current active settings tab.
*
* @return void
*/
public function maybe_render_tab_content( $settings_tab ) {
if ( 'connected-services' === $settings_tab ) {
$this->render_page();
}
}

/**
* Register the admin notices function to display license-related messages.
*
* @since 1.xx.x
*
* @return void
*/
public function register_admin_notices() {
add_action( 'admin_notices', [ $this, 'admin_notices' ] );
}

/**
* Render the connected services settings page content.
*
* @since 1.xx.x
*
* @return void
*/
public function render_page() {
$license = get_option( 'edac_license_key' );
$status = get_option( 'edac_license_status' );
$jwt_token = get_option( 'edac_jwt_token' );
$is_connected = ( 'valid' === $status && ! empty( $jwt_token ) );
$dashboard_link = '<a href="' . esc_url( \edac_link_wrapper( 'https://my.equalizedigital.com/', 'connected-services', 'account', false ) ) . '" target="_blank" rel="noopener noreferrer">my.equalizedigital.com</a>';
$create_account_link = '<a href="' . esc_url( \edac_link_wrapper( 'https://my.equalizedigital.com/sign-up/', 'connected-services', 'signup', false ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'create a free account', 'accessibility-checker' ) . '</a>';
$terms_link = '<a href="' . esc_url( \edac_link_wrapper( 'https://equalizedigital.com/terms-of-service/', 'connected-services', 'terms', false ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Terms of Service', 'accessibility-checker' ) . '</a>';
$privacy_link = '<a href="' . esc_url( \edac_link_wrapper( 'https://equalizedigital.com/privacy-policy/', 'connected-services', 'privacy', false ) ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Privacy Policy', 'accessibility-checker' ) . '</a>';
?>
<h2><?php esc_html_e( 'Connect this site', 'accessibility-checker' ); ?></h2>
<?php if ( ! defined( 'EDACP_VERSION' ) ) : ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php settings_fields( 'edac_license' ); ?>
<table class="form-table">
Comment on lines +147 to +149

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove or verify settings_fields() usage.

The form posts to admin-post.php with a custom action handler, but settings_fields('edac_license') is called on line 140. This function is intended for forms that use the WordPress Settings API and post to options.php. Since you're using a custom admin-post.php handler and already have a nonce on line 170, the settings_fields() call appears redundant and potentially incorrect.

🔧 Proposed fix
 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
-    <?php settings_fields( 'edac_license' ); ?>
     <table class="form-table">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php settings_fields( 'edac_license' ); ?>
<table class="form-table">
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<table class="form-table">
🤖 Prompt for AI Agents
In @admin/AdminPage/ConnectedServicesPage.php around lines 139 - 141, Remove the
incorrect settings_fields('edac_license') call from the form that posts to
admin-post.php (since settings_fields is only for Settings API forms that post
to options.php); instead rely on the existing nonce you already create
(wp_nonce_field call referenced near the form) or, if you intend to use WP
Settings API, change the form action to options.php and keep
settings_fields('edac_license'); in short, either delete
settings_fields('edac_license') or convert the form to use the Settings API
(options.php) so the call is appropriate.

<tbody>
<tr valign="top">
<th scope="row" valign="top">
<?php esc_html_e( 'Free License Key', 'accessibility-checker' ); ?>
</th>
<td>
<input
id="edac_license_key"
name="edac_license_key"
type="text"
class="regular-text"
value="<?php echo esc_attr( $license ); ?>"
/>
<label class="description" for="edac_license_key">
<?php if ( $is_connected ) : ?>
<span style="color:green;"> <?php esc_html_e( 'active', 'accessibility-checker' ); ?></span>
Comment thread
pattonwebz marked this conversation as resolved.
<?php elseif ( false !== $status && 'expired' === $status ) : ?>
<span style="color:red;"> <?php esc_html_e( 'expired', 'accessibility-checker' ); ?></span>
<?php else : ?>
<?php esc_html_e( 'Enter your license key', 'accessibility-checker' ); ?>
<?php endif; ?>
</label>
</td>
</tr>
<tr valign="top">
<th scope="row" valign="top"></th>
<td>
<input type="hidden" name="action" value="edac_license" />
<?php wp_nonce_field( 'edac_license_nonce', 'edac_license_nonce' ); ?>
<?php if ( $is_connected ) : ?>
<input
type="submit"
class="button-primary"
name="edac_license_deactivate"
value="<?php esc_attr_e( 'Disconnect Site', 'accessibility-checker' ); ?>"
>
<?php else : ?>
<input
type="submit"
class="button-primary"
name="edac_license_activate"
value="<?php esc_attr_e( 'Connect Site', 'accessibility-checker' ); ?>"
/>
<?php endif; ?>
</td>
</tr>
</tbody>
</table>
</form>
<?php if ( ! $is_connected ) : ?>
<p>
<?php
printf(
/* translators: %s: link to my.equalizedigital.com dashboard */
esc_html__( 'If you downloaded the free plugin from Equalize Digital, you already have a free license key in your %s dashboard.', 'accessibility-checker' ),
wp_kses_post( $dashboard_link )
);
?>
</p>
<p>
<?php
printf(
/* translators: %s: link to create a free account */
esc_html__( 'If not, %s to generate a license key and connect this site.', 'accessibility-checker' ),
wp_kses_post( $create_account_link )
);
?>
</p>
<?php endif; ?>

<?php else : ?>
<p><?php esc_html_e( 'Get monthly accessibility email reports and access to additional services by connecting this site.', 'accessibility-checker' ); ?></p>
<table class="form-table">
<tbody>
<tr valign="top">
<th scope="row" valign="top">
<?php esc_html_e( 'Connect site', 'accessibility-checker' ); ?>
</th>
<td>
<?php if ( $is_connected ) : ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="margin:0;">
<input type="hidden" name="action" value="edac_jwt_unregister" />
<?php wp_nonce_field( 'edac_jwt_unregister', 'edac_jwt_unregister_nonce' ); ?>
<input
type="submit"
class="button-primary"
name="edac_jwt_unregister"
value="<?php esc_attr_e( 'Disconnect Site', 'accessibility-checker' ); ?>"
>
</form>
<?php else : ?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="margin:0;">
<input type="hidden" name="action" value="edac_jwt_register" />
<?php wp_nonce_field( 'edac_jwt_register', 'edac_jwt_register_nonce' ); ?>
<input
type="submit"
class="button-primary"
name="edac_jwt_register"
value="<?php esc_attr_e( 'Connect Site', 'accessibility-checker' ); ?>"
/>
</form>
<?php endif; ?>
</td>
</tr>
</tbody>
</table>
<?php endif; ?>
<p>
<?php
printf(
/* translators: %1$s: link to Terms of Service, %2$s: link to Privacy Policy */
esc_html__( 'By connecting this site, you agree to the Equalize Digital %1$s and %2$s.', 'accessibility-checker' ),
wp_kses_post( $terms_link ),
wp_kses_post( $privacy_link )
);
?>
</p>
<?php
}

/**
* Display admin notices when the license added is invalid or expired.
*
* @since 1.xx.x
*
* @return void
*/
public function admin_notices() {
$error = get_option( 'edac_license_error' );
$license_url = get_bloginfo( 'url' ) . '/wp-admin/admin.php?page=accessibility_checker_settings&tab=connected-services';
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
$message = null;

if ( $error ) {
$message = $this->get_error_message( $error, $license_url );
}

if ( isset( $message ) ) {
printf(
'<div class="notice notice-error"><p>%s</p></div>',
wp_kses_post( $message )
);
}
}

/**
* Get appropriate error message based on the license error code.
*
* @since 1.xx.x
*
* @param string $error_code The license error code.
* @param string $license_url URL to the license settings page.
*
* @return string The formatted error message.
*/
private function get_error_message( $error_code, $license_url ) {
switch ( $error_code ) {
case 'expired':
return sprintf(
/* translators: %1$s: item name, %2$s: item name */
__( 'Your %1$s license has expired. <a href="https://my.equalizedigital.com/?utm_source=wpadmin" target="_blank">Please renew your license</a> to continue accessing additional features and services for %2$s.', 'accessibility-checker' ),
Connector::PRODUCT_NAME,
Connector::PRODUCT_NAME
);

case 'disabled':
case 'revoked':
return sprintf(
/* translators: %s: item name */
__( 'Your %s license key has been disabled. Please contact our support team if you believe this is an error or would like to continue accessing additional features.', 'accessibility-checker' ),
Connector::PRODUCT_NAME
);

case 'missing':
return sprintf(
/* translators: %1$s: item name, %2$s: license url, %3$s: item name */
__( 'The license key for %1$s appears to be invalid. Please <a href="%2$s">check your license key</a> to access additional accessibility features and services for %3$s.', 'accessibility-checker' ),
Connector::PRODUCT_NAME,
$license_url,
Connector::PRODUCT_NAME
);

case 'invalid':
case 'site_inactive':
return sprintf(
/* translators: %s: item name */
__( 'This license key for %s is not active for this site. Please activate it to access additional features on this website.', 'accessibility-checker' ),
Connector::PRODUCT_NAME
);

case 'item_name_mismatch':
return sprintf(
/* translators: %s: the plugins item name */
__( 'This license key does not appear to be valid for %s. Please verify you\'ve entered the correct license key.', 'accessibility-checker' ),
Connector::PRODUCT_NAME
);

case 'no_activations_left':
return sprintf(
/* translators: %s: the plugins item name */
__( 'Your %s license key has reached its site activation limit. You can upgrade your license or deactivate it on another site to use the additional features here.', 'accessibility-checker' ),
Connector::PRODUCT_NAME
);

default:
return sprintf(
/* translators: %s: the plugins item name */
__( 'There was an issue validating your %s license key. Please try again or contact our support team if the problem persists.', 'accessibility-checker' ),
Connector::PRODUCT_NAME
);
}
}
}
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@
"require": {
"davechild/textstatistics": "dev-master",
"php": ">=7.4",
"composer/installers": "^1.12.0"
},
"composer/installers": "^1.12.0",
"ext-openssl": "*",
"ext-json": "*"
},
Comment thread
pattonwebz marked this conversation as resolved.
"autoload": {
"classmap": [
"admin/",
Expand Down
Loading
Loading