Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions assets/css/page-widgets/todo.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
display: inline-block;
width: 24px;
height: 24px;
background-image: url("../../images/icon_progress_planner.svg");
background-image: var(--prpl-icon-url);
background-size: contain;
background-repeat: no-repeat;
}
Expand Down Expand Up @@ -136,7 +136,7 @@
display: inline-block;
width: 24px;
height: 24px;
background-image: url("../../images/icon_progress_planner.svg");
background-image: var(--prpl-icon-url);
background-size: contain;
background-repeat: no-repeat;
}
Expand Down
4 changes: 1 addition & 3 deletions assets/js/focus-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
const prplGetIndicatorElement = ( content, taskId, points ) => {
// Create an <img> element.
const imgEl = document.createElement( 'img' );
imgEl.src =
progressPlannerFocusElement.base_url +
'/assets/images/icon_progress_planner.svg';
imgEl.src = progressPlannerFocusElement.iconUrl;
imgEl.alt = points
? prplL10n( 'fixThisIssue' ).replace( '%d', points )
: '';
Expand Down
4 changes: 2 additions & 2 deletions assets/js/yoast-focus-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ProgressPlannerYoastFocus {
constructor() {
this.container = document.querySelector( '#yoast-seo-settings' );
this.tasks = progressPlannerYoastFocusElement.tasks;
this.baseUrl = progressPlannerYoastFocusElement.base_url;
this.iconUrl = progressPlannerYoastFocusElement.iconUrl;

if ( this.container ) {
this.init();
Expand Down Expand Up @@ -219,7 +219,7 @@ class ProgressPlannerYoastFocus {

// Create an icon image.
const iconImg = document.createElement( 'img' );
iconImg.src = this.baseUrl + '/assets/images/icon_progress_planner.svg';
iconImg.src = this.iconUrl;
iconImg.alt = 'Ravi';
iconImg.width = 16;
iconImg.height = 16;
Expand Down
2 changes: 1 addition & 1 deletion classes/admin/class-dashboard-widget-score.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Dashboard_Widget_Score extends Dashboard_Widget {
* @return string
*/
protected function get_title() {
return \esc_html__( 'Progress Planner', 'progress-planner' );
return \esc_html( \progress_planner()->get_ui__branding()->get_admin_menu_name() );
}

/**
Expand Down
3 changes: 2 additions & 1 deletion classes/admin/class-dashboard-widget-todo.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ class Dashboard_Widget_Todo extends Dashboard_Widget {
* @return string
*/
protected function get_title() {
return \esc_html__( 'To-do list Progress Planner', 'progress-planner' );
/* translators: %s: plugin name */
return \sprintf( \esc_html__( 'To-do list %s', 'progress-planner' ), \progress_planner()->get_ui__branding()->get_admin_menu_name() );
}

/**
Expand Down
2 changes: 1 addition & 1 deletion classes/admin/class-enqueue.php
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ public function localize_script( $handle, $localize_data = [] ) {
$localize_data = [
'name' => 'prplCelebrate',
'data' => [
'raviIconUrl' => \constant( 'PROGRESS_PLANNER_URL' ) . '/assets/images/icon_progress_planner.svg',
'raviIconUrl' => \progress_planner()->get_ui__branding()->get_admin_menu_icon(),
'confettiOptions' => $confetti_options,
],
];
Expand Down
2 changes: 1 addition & 1 deletion classes/admin/class-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public function maybe_enqueue_focus_el_script( $hook ) {
'tasks' => $tasks_details,
'totalPoints' => $total_points,
'completedPoints' => $completed_points,
'base_url' => \constant( 'PROGRESS_PLANNER_URL' ),
'iconUrl' => \progress_planner()->get_ui__branding()->get_admin_menu_icon(),
'l10n' => [
/* translators: %d: The number of points. */
'fixThisIssue' => \esc_html__( 'Fix this issue to get %d point(s) in Progress Planner', 'progress-planner' ),
Expand Down
13 changes: 13 additions & 0 deletions classes/admin/widgets/class-todo.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ final class ToDo extends Widget {
*/
protected $width = 2;

/**
* Enqueue styles.
*
* @return void
*/
public function enqueue_styles() {
parent::enqueue_styles();
\wp_add_inline_style(
"progress-planner/page-widgets/{$this->id}",
':root { --prpl-icon-url: url("' . \progress_planner()->get_ui__branding()->get_admin_menu_icon() . '"); }'
);
}

/**
* Print the widget content.
*
Expand Down
45 changes: 40 additions & 5 deletions classes/class-badges.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,19 @@ public function __construct() {
* @return \Progress_Planner\Badges\Badge[]
*/
public function get_badges( $context ) {
return isset( $this->$context ) ? $this->$context : [];
// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- Inline @var for PHPStan.
/** @var \Progress_Planner\Badges\Badge[] $badges */
$badges = isset( $this->$context ) ? $this->$context : [];

/**
* Filter the badges for a context.
*
* @param \Progress_Planner\Badges\Badge[] $badges The badges.
* @param string $context The badges context.
*
* @return \Progress_Planner\Badges\Badge[]
*/
return \apply_filters( 'progress_planner_badges', $badges, $context );
}

/**
Expand All @@ -101,7 +113,18 @@ public function get_badge( $badge_id ) {
}
}
}
return null;

/**
* Filter for retrieving a single badge by ID.
*
* Allows external plugins to provide custom badges.
*
* @param \Progress_Planner\Badges\Badge|null $badge The badge object or null.
* @param string $badge_id The badge ID.
*
* @return \Progress_Planner\Badges\Badge|null
*/
return \apply_filters( 'progress_planner_get_badge', null, $badge_id );
}

/**
Expand Down Expand Up @@ -183,6 +206,8 @@ public function get_latest_completed_badge() {
// Get the settings for badges (stores completion dates).
$settings = \progress_planner()->get_settings()->get( 'badges', [] );

// phpcs:ignore Generic.Commenting.DocComment.MissingShort -- Inline @var for PHPStan.
/** @var string|null $latest_date */
$latest_date = null;

// Loop through all badge contexts to find the most recently completed badge.
Expand All @@ -204,20 +229,30 @@ public function get_latest_completed_badge() {
if ( null === $latest_date ) {
$this->latest_completed_badge = $badge;
if ( isset( $settings[ $badge->get_id() ]['date'] ) ) {
$latest_date = $settings[ $badge->get_id() ]['date'];
$latest_date = (string) $settings[ $badge->get_id() ]['date'];
}
continue;
}

// Compare completion dates as Unix timestamps to find the most recent.
// Using >= ensures that if multiple badges complete simultaneously, the last one processed wins.
if ( \DateTime::createFromFormat( 'Y-m-d H:i:s', $settings[ $badge->get_id() ]['date'] )->format( 'U' ) >= \DateTime::createFromFormat( 'Y-m-d H:i:s', $latest_date )->format( 'U' ) ) {
$latest_date = $settings[ $badge->get_id() ]['date'];
if ( \DateTime::createFromFormat( 'Y-m-d H:i:s', (string) $settings[ $badge->get_id() ]['date'] )->format( 'U' ) >= \DateTime::createFromFormat( 'Y-m-d H:i:s', $latest_date )->format( 'U' ) ) {
$latest_date = (string) $settings[ $badge->get_id() ]['date'];
$this->latest_completed_badge = $badge;
}
}
}

/**
* Filter the latest completed badge.
*
* @param \Progress_Planner\Badges\Badge|null $badge The latest completed badge.
* @param string|null $latest_date The latest completion date.
*
* @return \Progress_Planner\Badges\Badge|null
*/
$this->latest_completed_badge = \apply_filters( 'progress_planner_latest_completed_badge', $this->latest_completed_badge, $latest_date );

return $this->latest_completed_badge;
}
}
2 changes: 1 addition & 1 deletion classes/class-lessons.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function get_remote_api_items() {
[
'site' => \get_site_url(),
'license_key' => \progress_planner()->get_license_key(),
'locale' => apply_filters( 'prpl_lesson_locale', \get_locale() ),
'locale' => \apply_filters( 'prpl_lesson_locale', \get_locale() ),
],
$url
);
Expand Down
26 changes: 19 additions & 7 deletions classes/class-plugin-upgrade-tasks.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,26 @@ protected function add_initial_onboarding_tasks() {
public function maybe_add_onboarding_tasks() {
$onboard_task_provider_ids = \apply_filters( 'prpl_onboarding_task_providers', [] );

// Privacy policy is not accepted, so it's a fresh install.
$fresh_install = ! \progress_planner()->is_privacy_policy_accepted();

// Check if task providers option exists, it will not on fresh installs and v1.0.4 and older.
$old_task_providers = \get_option( 'progress_planner_previous_version_task_providers', [] );
// Check if task providers option exists. If not, it's either a fresh install or upgrading from v1.0.4 or older.
$old_task_providers = \get_option( 'progress_planner_previous_version_task_providers', [] );
$task_providers_option_set = false !== \get_option( 'progress_planner_previous_version_task_providers', false );

// Fresh install detection:
// - Option doesn't exist AND privacy policy not yet accepted (standalone fresh install)
// - Option doesn't exist AND running as branded/hosted version (pp-hosts fresh install, privacy auto-accepted)
// In these cases, save current providers as baseline without showing upgrade popover.
if ( ! $task_providers_option_set ) {
$is_branded_version = \defined( 'PROGRESS_PLANNER_BRANDING_ID' );
$is_privacy_policy_pending = ! \progress_planner()->is_privacy_policy_accepted();

if ( $is_branded_version || $is_privacy_policy_pending ) {
// Fresh install - save current providers as baseline and skip upgrade popover.
\update_option( 'progress_planner_previous_version_task_providers', \array_unique( $onboard_task_provider_ids ), SORT_REGULAR );
return;
}

// We're upgrading from v1.0.4 or older, set the old task providers to what we had before the upgrade.
if ( ! $fresh_install && empty( $old_task_providers ) ) {
// Upgrading from v1.0.4 or older (standalone, privacy accepted but no task providers option).
// Set baseline to what existed before the upgrade.
$old_task_providers = [
'core-blogdescription',
'wp-debug-display',
Expand Down
23 changes: 18 additions & 5 deletions classes/class-suggested-tasks.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,25 @@ public function init(): void {
* @return void
*/
public function insert_activity( string $task_id ): void {
/**
* Filter the activity category for a completed task.
*
* Allows customizing the category used when recording task completion activities.
* For example, onboarding tasks may use 'onboarding_task' instead of 'suggested_task'
* to exclude them from monthly badge calculations.
*
* @param string $category The activity category (default: 'suggested_task').
* @param string $task_id The task ID being completed.
*/
$category = \apply_filters( 'progress_planner_task_activity_category', 'suggested_task', $task_id );

// Insert an activity.
$activity = new Suggested_Task_Activity();
$activity->type = 'completed';
$activity->data_id = (string) $task_id;
$activity->date = new \DateTime();
$activity->user_id = \get_current_user_id();
$activity = new Suggested_Task_Activity();
$activity->category = $category;
$activity->type = 'completed';
$activity->data_id = (string) $task_id;
$activity->date = new \DateTime();
$activity->user_id = \get_current_user_id();
$activity->save();

// Allow other classes to react to the completion of a suggested task.
Expand Down
2 changes: 1 addition & 1 deletion classes/suggested-tasks/providers/class-core-update.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public function add_core_update_link( $update_actions ) {
foreach ( \progress_planner()->get_suggested_tasks_db()->get_tasks_by( [ 'post_status' => 'publish' ] ) as $task ) {
if ( $this->get_task_id() === \progress_planner()->get_suggested_tasks()->get_task_id_from_slug( $task->post_name ) ) {
$update_actions['prpl_core_update'] =
'<img src="' . \esc_attr( \constant( 'PROGRESS_PLANNER_URL' ) . '/assets/images/icon_progress_planner.svg' ) . '" style="width:1rem;padding-left:0.25rem;padding-right:0.25rem;vertical-align:middle;" alt="Progress Planner" />' .
'<img src="' . \esc_attr( \progress_planner()->get_ui__branding()->get_admin_menu_icon() ) . '" style="width:1rem;padding-left:0.25rem;padding-right:0.25rem;vertical-align:middle;" alt="Progress Planner" />' .
'<a href="' . \esc_url( \admin_url( 'admin.php?page=progress-planner' ) ) . '" target="_parent">' . \esc_html__( 'Click here to celebrate your completed task!', 'progress-planner' ) . '</a>';
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ protected function is_plugin_active() {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}

$plugins = get_plugins();
$this->is_plugin_active = isset( $plugins[ $this->plugin_path ] ) && is_plugin_active( $this->plugin_path );
$plugins = \get_plugins();
$this->is_plugin_active = isset( $plugins[ $this->plugin_path ] ) && \is_plugin_active( $this->plugin_path );
}

return $this->is_plugin_active;
Expand All @@ -168,7 +168,7 @@ protected function get_autoloaded_options_count() {

if ( null === $this->autoloaded_options_count ) {
$autoload_values = \wp_autoload_values_to_autoload();
$placeholders = implode( ',', array_fill( 0, count( $autoload_values ), '%s' ) );
$placeholders = \implode( ',', \array_fill( 0, \count( $autoload_values ), '%s' ) );

// phpcs:disable WordPress.DB
$this->autoloaded_options_count = $wpdb->get_var(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public function check_public_post_types() {
\update_option( 'progress_planner_public_post_types', $public_post_types );

// Exit if post type was removed, or it is not public anymore, since the user will not to able to make different selection.
if ( count( $public_post_types ) < count( $previosly_set_public_post_types ) ) {
if ( \count( $public_post_types ) < \count( $previosly_set_public_post_types ) ) {
return;
}

Expand Down Expand Up @@ -180,8 +180,8 @@ public function handle_interactive_task_specific_submit() {
}

$post_types = \wp_unslash( $_POST['prpl-post-types-include'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- array elements are sanitized below.
$post_types = explode( ',', $post_types );
$post_types = array_map( 'sanitize_text_field', $post_types );
$post_types = \explode( ',', $post_types );
$post_types = \array_map( 'sanitize_text_field', $post_types );

\progress_planner()->get_admin__page_settings()->save_post_types( $post_types );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public function enqueue_assets( $hook ) {
[
'name' => 'progressPlannerYoastFocusElement',
'data' => [
'tasks' => $focus_tasks,
'base_url' => \constant( 'PROGRESS_PLANNER_URL' ),
'tasks' => $focus_tasks,
'iconUrl' => \progress_planner()->get_ui__branding()->get_admin_menu_icon(),
],
]
);
Expand Down
2 changes: 1 addition & 1 deletion progress-planner.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

require_once PROGRESS_PLANNER_DIR . '/autoload.php';

if ( ! function_exists( 'progress_planner' ) ) {
if ( ! \function_exists( 'progress_planner' ) ) {
/**
* Get the progress planner instance.
*
Expand Down
1 change: 0 additions & 1 deletion tests/e2e/blueprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"step": "setSiteOptions",
"options": {
"progress_planner_test_token": "0220a2de67fc29094281088395939f58",
"progress_planner_license_key": "test-license-for-e2e-testing",
"progress_planner_demo_data_generated": "1",
"prpl_debug": "1"
}
Expand Down
33 changes: 26 additions & 7 deletions tests/e2e/specs/onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,33 @@ test.describe( 'Progress Planner Onboarding', () => {
);
} );

await test.step( 'Submit the form', async () => {
await form
.locator(
'input[type="submit"].prpl-button-secondary--no-email'
)
.click();
await test.step( 'Complete onboarding', async () => {
// The remote API (progressplanner.com) is unreachable in WP Playground
// because page.route() cannot intercept requests handled by its
// service worker. Use Playwright's request API to call the local
// WP AJAX endpoint directly — it bypasses the service worker and
// shares the page's auth cookies.
const nonce = await page.evaluate(
() => ( window as any ).progressPlanner.nonce
);

const response = await page.request.post(
'/wp-admin/admin-ajax.php',
{
form: {
action: 'progress_planner_save_onboard_data',
_ajax_nonce: nonce,
key: 'test-license-for-e2e-testing',
},
}
);

expect( response.ok() ).toBe( true );

// Reload to see the dashboard.
await page.reload( { waitUntil: 'networkidle' } );

// Verify onboarding completion
// Verify onboarding completion — dashboard should now be visible.
await expect(
page.locator( '.prpl-widget-wrapper.prpl-suggested-tasks' )
).toBeVisible( { timeout: 15000 } );
Expand Down
4 changes: 2 additions & 2 deletions tests/phpunit/test-class-base.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ public function test_is_debug_mode_enabled() {
$this->assertFalse( $result );

// Set the current user to a user with the manage_options capability.
wp_set_current_user( 1 );
\wp_set_current_user( 1 );

// Option is set, and the user has the manage_options capability.
$result = $this->base_instance->is_debug_mode_enabled();
Expand All @@ -228,7 +228,7 @@ public function test_is_debug_mode_enabled() {
$this->assertIsBool( $result );

// Unset the current user.
wp_set_current_user( 0 );
\wp_set_current_user( 0 );
}
}

Expand Down
Loading
Loading