Repository navigation
Conversation
Show a notice that support for the plugin is ending, on the Progress Planner dashboard and on the plugins overview page. The notice thanks users and links to more information, and lets them download a printable single-page A4 certificate showing their site, how long they have used Progress Planner, and all completed badges. The notice is hidden when the pp-hosts companion plugin is installed or active, or when a branding ID is defined, since support continues for hosted users. A progress_planner_show_sunset_notice filter is available as an escape hatch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There will be no blog post to link to, so the notice now only contains the thank-you message (plus the certificate link/button). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The plugins-page row used core's `update-message` class, which injects the red circular-arrows dashicon (\f463) via `.update-message p:before`. That made the sunset notice visually identical to an "update available" row — the opposite of what it means. Drop `update-message` and render a warning dashicon instead. Core keys the row spacing off `.notice`, not `update-message`, so the layout is unchanged. The style is inline because `assets/css/admin.css` is only enqueued on `toplevel_page_progress-planner`, so a rule there would never reach the plugins page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011jLxPbbCpRuFZFHMBrSoPq
Exploratory. Nothing reads these files yet; they exist to find out whether recommendations can be expressed as data rather than PHP, and what the format would have to carry. All 49 existing providers written out as markdown, which produced 42 files: every Yoast recommendation has an All in One SEO twin expressing the same goal, and once a rule states an outcome instead of a setting, the pair collapses into one. That collapse is the first evidence the approach works -- one rule, no plugin named, and a model works out how to satisfy it on whatever the site actually has. Two findings shaped the format. verified_by is the load-bearing field. Thirty rules the site can answer for itself by reading an option, querying content or fetching a URL. Twelve it cannot: d/m/Y and m/d/Y are both valid date formats, and which is right depends on who reads the site. For those the goal is that the owner has looked once, which is exactly what the existing PHP checks by consulting the activity log. That is not a missing state check; it is the correct check for the goal. Structured fields are read by the model, not by PHP. The set produced 13 applies_when predicates and 10 target.find keys, most used once, which would be runaway DSL growth if something had to interpret them. Nothing does. The structure is precision for the model -- unambiguous and close to the query it will build -- so a new key costs nothing and needs no release. It also means incoming_internal_links is expressible at all, which it would not be in PHP without reading an SEO plugin's link index. The validator checks what review misses: that the prose and the frontmatter agree. It found six files where verified_by said owner_confirmation while the How to verify section described a perfectly good check, because the two axes -- can the site tell, and should a person decide -- had been conflated. It deliberately does not check the applies_when or target.find vocabulary, since constraining those would reintroduce the coupling the format exists to avoid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tjxa3zi5Z7eTKWoaxB3HGT
Turns the rules in /recommendations into real tasks so the format can be exercised end to end without a server: drop a file in, see the task appear, have a model act on it. Off unless a site opts in via the PROGRESS_PLANNER_MARKDOWN_RECOMMENDATIONS constant or the matching filter -- this is a harness, not a feature. The loader deliberately does not interpret the rule. applies_when and target.find are read by the model, and building an interpreter here would recreate the coupling the format exists to remove. The single exception is any_plugin_active, checked before a task is created: without it a bare site collects every SEO rule it can never satisfy and the model spends a call discovering that. Detection reuses the constants and classes the SEO data collector already knows, so the two cannot disagree about what "Yoast is active" means. Verified on a live site: 42 rules parse, 36 become tasks, a second run creates none, and a rule requiring only an inactive plugin is correctly skipped. The nine SEO rules list both Yoast and AIOSEO as alternatives, so one being active satisfies them all. Two things the testing corrected. The provider prefix was md: and became md-, because the provider ID ends up as a taxonomy term slug and sanitize_title() silently drops a colon -- md:foo stored as mdfoo and broke every prefix check downstream. And the summary pattern terminated on a newline, so it matched nothing on a wrapped paragraph, which is every paragraph. Rules now name Yoast by the slug the plugin itself uses, wordpress-seo, rather than yoast-seo, so no alias table is needed anywhere. Known gap: list-recommendations drops any task whose provider class is missing, which is every task created here. Fixing that belongs with the abilities work on the other branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tjxa3zi5Z7eTKWoaxB3HGT
Each rule now becomes a Markdown_Rule provider, registered through the same progress_planner_suggested_tasks_providers filter third parties already use. The previous commit wrote tasks straight into the database with nothing behind them, which left every consumer needing a special case for "a task whose provider does not exist" -- the abilities layer drops exactly those, so all 36 were invisible to a model. Registering a provider removes the special case rather than patching around it: the dashboard renders these, points and badges count them, capability filtering applies, and the abilities layer lists them with no change at all. Verified: 36 visible, none dropped. Everything the interface asks for comes from the rule's frontmatter. The two methods that cannot are evaluate_task() and is_task_completed(), which always return false. A goal like "attachment URLs must not be indexable" is satisfied by a redirect on one site and a header on another, which is why the rule is prose for a model rather than a condition for PHP, so completion arrives from outside and is never inferred here. Verified on a live site: 42 providers built, Tasks_Manager holds 83 without complaint, 36 tasks inject through the normal pipeline, and each resolves back to its provider. The six that do not inject are the per_item templates, which need a model to find their targets first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tjxa3zi5Z7eTKWoaxB3HGT
The `prpl_recommendations` CPT is registered with `show_in_rest => true` but
no `capability_type`/`capabilities`, so it inherited WordPress's default
`post` capabilities. Its REST controller added no permission overrides. This
opened two holes (1.10.0 release audit S1/S2):
S1 (HIGH) — any Contributor/Author (`edit_posts`) could, via core REST:
- create a task carrying a known recommendation slug in `trash` status;
`Suggested_Tasks_DB::add()` treats any existing post with that slug as
"the task already exists" and returns early, so the genuine
recommendation (e.g. "Perform all updates") is silently suppressed and
never shown to the admin;
- create `pending`/`publish` tasks with attacker-chosen titles that are
then rendered in every admin's dashboard widget.
An Editor could also trash/delete admin-only tasks over REST.
S2 (MEDIUM) — any published task was readable anonymously by ID
(`GET /wp/v2/prpl_recommendations/<id>`), leaking pending-update state,
draft/unpublished titles, and the active SEO plugin. The existing collection
filter (`rest_api_tax_query`) only guards the list route, not single items;
and core's `check_read_permission()` allows any `publish` post to be read
before it consults `read_post`, so capability mapping alone cannot close it.
Fix: override the four `*_permissions_check` methods plus
`get_item_permissions_check` in `Recommendations_Controller` to require
`edit_others_posts` — the exact gate the plugin already uses for its admin UI
(`Base::init()`, `Dashboard_Widget::$capability`). Editors and admins keep
full read/write access; every role below is rejected. Also map the provider
taxonomy's term-write capabilities to the same gate.
Chose controller overrides over remapping the CPT's `capability_type`: it is
surgical, fixes the anonymous single-item read that caps cannot, and avoids
the `map_meta_cap` blast radius on a surface shipped since v1.6.0.
Adds 20 regression tests: contributor/author/subscriber/anonymous are denied
create/read/update/delete (verified red against the unfixed code), while
Editor and admin flows — including creating/deleting their own task and
assigning the provider term — still pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`Branding::get_admin_submenu_position()` declared a `mixed` return type.
`mixed` only became a type in PHP 8.0; on PHP 7.4 it is resolved as a class
name, so returning `-1000` or `null` throws:
TypeError: Return value of ...::get_admin_submenu_position()
must be an instance of mixed, int returned
The method is called from `Admin\Page::add_page()` on the `admin_menu` hook,
so this fataled on every wp-admin page view for sites on PHP 7.4 — which the
plugin still advertises support for (`Requires PHP: 7.4`).
Introduced in 1c6684b ("Fix PHP 7.4 linting issue"), which replaced the
union type `int|null` (also 8.0+) with `mixed` — swapping one PHP 8-only
syntax for another. Shipped in v1.10.0; not present in v1.9.1.
Removes the native return type and keeps the `@return int|null` docblock,
which is the only form valid on 7.4.
Why CI did not catch it: the lint job does run PHP 7.4, but `php -l` only
checks syntax, and `(): mixed` is syntactically valid on 7.4 — the TypeError
only fires when the function is called. The PHPUnit matrix, which does
execute code, starts at PHP 8.2, and PHPStan ran against the latest PHP.
All three gates reported green.
Pinning `phpVersion: 70400` in phpstan.neon.dist closes that gap: PHPStan
now resolves `mixed` as an unknown class and fails, catching this and the
rest of the PHP 8-only syntax class statically, at no CI cost. Verified it
flags the original code and passes on the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Branding::get_branding_id()` read `$_GET['pp_branding_id']` for any request, with no authentication. The param is a preview seam for branded-host rendering (added in #633, "auto-register branded websites"), but because the branding ID also gates the auto-onboard remote call in `Base::init()`, an anonymous visitor appending `?pp_branding_id=N` could force a non-zero branding ID and thereby trigger the server-side onboarding request and a stored license key (1.10.0 release audit S3). Gate the param on `is_user_logged_in()`. This keeps the preview flow working for any authenticated user on both the front end and wp-admin (an admin stays logged in across both), while an anonymous request now falls through to the host-based default and can no longer steer branding. Note: the report's broader S3 remedies (re-gating the Base::init() block, adding an onboarding back-off transient) are intentionally not applied — in practice sites are redirected to the capability-gated, nonce-checked onboarding screen on activation, and the SaaS is fast and CDN-cached, so the blocking-call/DoS concern does not apply. The unauthenticated param was the only real defect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`rest_api_tax_query()` passed `$request['provider']` and `$request['exclude_provider']` straight to `explode()`. When the param arrives as an array (`?provider[]=a&provider[]=b`) that raised a PHP 8 TypeError and a 500 response (1.10.0 release audit S7.1). Route both through a new `parse_provider_param()` helper that branches on the input type — comma-splitting a string, casting an array — and sanitises each slug with `sanitize_key()`. Note: with the recommendations endpoint now gated to `edit_others_posts` (see the REST-permissions commit), this path is no longer reachable anonymously, so the fix hardens the request handling for authorised callers rather than closing an unauthenticated 500. Adds 3 regression tests (array `provider[]`, array `exclude_provider[]`, and the string form), verified red against the pre-fix code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The email-sending provider's `init()` minted a fresh task-completion token on every request (it runs on the `init` hook, front end included). Because the token lives in a single per-task/per-user transient, this overwrote the token that `ajax_test_email_sending()` stored when the test email was actually sent — so the "mark as completed" link in the user's inbox was invalidated by the next request (a Heartbeat poll or page navigation), and clicking it silently did nothing (1.10.0 release audit B2). The token generated in `init()` was never used: the AJAX handler builds and sends its own email body with a freshly generated token at send time, and `$this->email_content` (the property `init()` populated) has no readers. The token block was authored redundantly alongside the handler's own generation and never consumed. Remove the token generation and `$this->email_content` build from `init()`, and drop the now-unused property. `$this->email_subject` stays — it is used by `wp_mail()` and localized to the popover view. This also removes a needless `set_transient` + `sprintf` from every front-end request. Sending a new test email still (correctly) mints a new token and supersedes the previous link. Adds a regression test asserting `init()` no longer overwrites a token stored by the send flow (verified red against the pre-fix code). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The set-page interactive task (About / Contact / FAQ) could be completed with
"I have this page" chosen but no page actually selected. The handler only
checked that have_page was non-empty, so a submission with have_page = 'yes'
and id = 0 was saved and marked successful — recording that a page exists
while pointing at no page (1.10.0 release audit B8).
Reject have_page = 'yes' when no valid page id (>= 1) is supplied. The other
two options ("I don't have this page yet" and "My site doesn't need this
page") legitimately carry no page id and are unaffected.
Adds regression tests: yes-without-page is rejected (verified red against the
pre-fix code), while yes-with-a-real-page and the no-page options still
complete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The set-page popover passes 'context' => 'popover' to the page-select view, but the view compared it against 'popovers' (plural). The check never matched, so the "Create this page" link always fell through to target="_blank" and opened a new tab even when the user was already inside the popover flow (1.10.0 release audit B10). Match the strings: the view now checks 'popover', so the link opens _self from within the popover as intended. No test — this is a rendered-template attribute with no unit-test seam; verified via the full suite and lint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The monthly badge computed its activity window with
gmdate( 't', strtotime( $month ) ), where $month is the badge NAME (e.g.
"Felix February"), not a date. strtotime() returned false and gmdate( 't',
false ) always yielded 31, so the end date became "<year>-<month>-31" — which
DateTime overflows for shorter months. February's window ran to March 3 and
30-day months spilled one day into the next, so activity earned early in the
following month was counted toward the previous month's badge. This also
disagreed with the monthly-badges widget, which windows correctly.
Derive the window from the month number instead:
$start_date = DateTime::createFromFormat( '!Y-n-j', "{$year}-{$month_num}-1" );
$end_date = ( clone $start_date )->modify( 'last day of this month' );
Verified for 28/30/31-day months and a leap-year February. The now-unused
$month (badge name) lookup is removed.
Adds regression tests: March 2 activity does not count toward the February
badge (verified red against the pre-fix code), while February activity —
including February 28 — still does.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The earlier S1 fix added permission checks to the REST controller, but the CPT still used the default `post` capabilities, so write paths that check caps but bypass the controller — XML-RPC `wp.newPost`, the block editor — still let a Contributor/Author create (and slug-squat) recommendations (1.10.0 audit S1, review gap 1). Map the CPT's primitive capabilities to `edit_others_posts` with `map_meta_cap => true`. Only the primitive caps are listed; core derives the meta caps (edit_post/read_post/delete_post) per-post — listing those too triggers a `_doing_it_wrong` notice in WP 6.1+. Verified this does NOT break internal writes: `Suggested_Tasks_DB` injects and updates with raw `wp_insert_post()`/`wp_update_post()`, which do not run capability checks, so task injection, the AJAX action, CLI commands and migrations are unaffected. Contributors/Authors are now blocked from creating via any cap-checked path; Editors and admins still can. Adds regression tests: the create capability is `edit_others_posts` and gated per role, and internal injection still works as a Subscriber. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The earlier S3 fix honoured `?pp_branding_id` for any logged-in user, so a Subscriber could still steer the branding ID; and `Base::init()`'s auto-onboard still made the blocking remote calls for any logged-in visitor on a branded host (review gap 3). - `Branding::get_branding_id()`: honour `?pp_branding_id` only for `current_user_can( 'manage_options' )` (was `is_user_logged_in()`). - `Base::init()`: require `manage_options` (and skip cron) before the auto-onboard remote call. Deliberately NOT gated on `is_admin()`: on a branded host (pp-hosts) the administrator is redirected to the front-end homepage after Extendify set-up, and this block is the only path that fetches the license key — gating it to wp-admin would leave that flow un-onboarded. The branding check runs before the capability check, so `current_user_can()` is only reached on a branded, unlicensed site (never at bootstrap on a default install). Updates the S3 test: a Subscriber's `pp_branding_id` is now ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…roller The surface `edit_others_posts` gate let an editor read, update or delete ANY task over REST, including admin-only ones such as `update-core` (which needs `update_core`). An editor could trash it and have it count as completed (review gap 2). The AJAX handler already applies the task's provider capability; the REST controller did not. Add `current_user_can_manage_requested_task()`, mirroring the AJAX gate, to the `get_item`, `update_item` and `delete_item` permission checks: the current user must also satisfy the task's provider `capability_required()`. Tasks whose provider cannot be resolved fall back to the surface gate (matching the AJAX handler). `create` is not per-task gated — there is no existing post to resolve a provider from, and the CPT capabilities + surface gate cover creation. Also give the `user` provider `CAPABILITY = 'edit_others_posts'`. It previously inherited the base `manage_options`, so editors could not complete their own to-dos even though the widget shows them (a pre-existing bug) — and it would have made the new per-task check reject editors from their own `user` tasks. Verified: an editor can read/update/delete their own `user` task and complete one via AJAX, but is blocked (403) from `update-core`; an admin can manage `update-core`. The editor-blocked-from-update-core case is red against the pre-fix controller. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ment On multisite, `update_core` is reserved for super admins, so a plain site administrator is correctly denied the `update-core` task and `test_admin_can_manage_admin_only_task` failed on the `(+ ms)` CI jobs. Grant super admin in that test when running on multisite. Also remove the duplicated auto-onboard comment block in `Base::init()`, keeping the shorter version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The per-task provider check covered read/update/delete but not create, so an Editor could still suppress an admin-only recommendation: creating a task that carries the admin task's slug (e.g. an `update-core` slug) in `trash` status made `Suggested_Tasks_DB::add()` treat the real task as already existing, so it was never injected for the admin. Same class as S1, limited to Editors+ (the CPT capabilities already block Contributors/Authors), but the PR claims S1 is fixed. Reported in the follow-up review. Two layers: - `create_item_permissions_check`: users without `manage_options` may only create their own personal (`user`) to-dos — no client-supplied slug, and only the `user` provider term (or none). Admins are unrestricted (the plugin creates provider tasks as an admin, and internal injection uses raw wp_insert_post which bypasses this). - `Suggested_Tasks_DB::add()`: only treat an existing post as "the task" when its provider matches the one being injected, so a post that merely squats the slug under a different provider no longer suppresses the real recommendation. Filtered in PHP on the resolved provider (a name + tax_query on a trashed post does not reliably AND-combine in WP_Query). Verified live: an Editor's slug-squat / non-user-provider create is now 403, their own `user` to-do still 201, admins unrestricted; a wrong-provider squat no longer suppresses injection while a matching-provider task still dedupes. Tests are red against the pre-fix code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…it-fixes 1.10.1 release audit fixes: R1 (PHP 7.4 fatal), S1/S2/S3/S7.1, B2/B4/B8/B10
Add sunset notice with downloadable certificate
Patch release: end-of-support sunset notice, the PHP 7.4 admin fatal fix, capability/permission hardening, and several task/badge bug fixes. Version bumped in progress-planner.php and readme.txt (Stable tag); the changelog entry is added to both readme.txt and CHANGELOG.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restores develop to the tree it had at 7c18816, immediately before a03361d was pushed to it on 2026-09-23. a03361d is a merge of origin/develop INTO filip/recommendation-format, made to bring that branch up to date. It was then pushed to develop rather than to the feature branch, which carried three days of prototype work onto the release branch by accident. Nothing shipped: the two classes this removes, Markdown_Recommendations and Markdown_Rule, have no references anywhere in the plugin. Nothing instantiates them and nothing hooks them to the provider filter, so they autoloaded and sat there. The 42 markdown files are data no code reads. The risk was packaging and review, not behaviour. Reverted with -m 2, so the mainline is old develop and only the material that arrived from the feature branch is removed. PR #777's sunset notice is untouched, and `git diff 7c18816` against this commit is empty. WHEN THE EXPERIMENT COMES BACK Merging filip/recommendation-format after this will bring back nothing: git treats those commits as merged and then reverted. Revert this commit first, or rebase the work onto a fresh branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…periment Revert the markdown recommendation experiment from develop
Release 1.10.1
Contributor
|
Test merged PR on Playground |
Contributor
✅ Code Coverage Report
🎉 Great job maintaining/improving code coverage! 📊 File-level Coverage Changes (13 files)🆕 New Files
📈 Coverage Improved
ℹ️ About this report
|
Contributor
🔍 WordPress Plugin Check Report
📊 Report
|
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
103 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
📁 classes/suggested-tasks/providers/class-content-review.php (4 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
232 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
377 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
381 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
388 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in | Using exclusionary parameters, like post__not_in, in calls to get_posts() should be done with caution, see https://docs.wpvip.com/databases/optimize-queries/using-post__not_in/ for more information. |
📁 classes/activities/class-query.php (2 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
71 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $table_name used in $wpdb->query()\n$table_name assigned unsafely at line 58. |
163 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $where_args used in $wpdb->get_results()\n$where_args assigned unsafely at line 153. |
📁 classes/suggested-tasks/data-collector/class-yoast-orphaned-content.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
111 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $query used in $wpdb->get_row()\n$query assigned unsafely at line 98. |
📁 classes/suggested-tasks/data-collector/class-terms-without-description.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
108 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $query used in $wpdb->get_results()\n$query assigned unsafely at line 106. |
📁 classes/suggested-tasks/data-collector/class-terms-without-posts.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
120 |
PluginCheck.Security.DirectDB.UnescapedDBParameter | Unescaped parameter $query used in $wpdb->get_results()\n$query assigned unsafely at line 118. |
🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.