Plugin Directory

Changeset 3283338


Ignore:
Timestamp:
04/28/2025 10:55:46 AM (10 months ago)
Author:
progressplanner
Message:

Update to version 1.3.0 from GitHub

Location:
progress-planner
Files:
24 added
14 deleted
114 edited
1 copied

Legend:

Unmodified
Added
Removed
  • progress-planner/tags/1.3.0/CHANGELOG.md

    r3268602 r3283338  
     1= 1.3.0 =
     2
     3Enhancements:
     4
     5* Improved checks when adding Ravi icon to the Yoast SEO settings page.
     6* Add "golden" tasks to weekly emails.
     7* Add text to clarify when the user has completed all tasks.
     8* Improve the content widget & stats to show more accurate data. It now shows content _activity_ instead of content _published_.
     9* Implemented "valuable post-types" and added settings for them.
     10* Changed the "create a post" task to "create valuable content".
     11* Renamed & migrated content badges.
     12* Added a link to the 'Create valuable content' task description.
     13* Improve accessibility of Recommendations (and other links) linking to external resources
     14
     15Bugs we fixed:
     16
     17* Fixed error during plugin uninstall.
     18* Archive_Format data collector hooks weren't registered early enough.
     19* Ensure fresh plugin list by clearing plugin cache before checking for inactive plugins after deletion.
     20* Clear plugin cache when checking for inactive plugins.
     21* Delete no-longer relevant pending tasks.
     22* Fixed timing issue for tasks added by 3rd-party plugins.
     23
    124= 1.2.0 =
    225
  • progress-planner/tags/1.3.0/assets/css/admin.css

    r3264985 r3283338  
    421421    Settings popover.
    422422\*------------------------------------*/
    423 #prpl-settings-license-form,
    424 #prpl-settings-form {
    425 
    426     label {
    427         display: block;
    428     }
    429 
    430     p {
    431         max-width: 42em;
    432     }
    433 
    434     h3 {
    435         font-size: 1.15em;
    436     }
    437 
    438     button.button-primary {
    439         margin-top: 1em;
    440     }
    441 }
    442 
    443 .driver-popover.prpl-driverjs-theme {
    444     background-color: var(--prpl-background-orange);
    445     color: var(--prpl-color-text);
    446 
    447     .driver-popover-title {
    448         color: var(--prpl-color-headings);
    449     }
    450 
    451     button {
    452         color: var(--prpl-color-headings);
    453     }
    454 
    455     button:not(.driver-popover-close-btn):hover {
    456         background-color: var(--prpl-background-orange);
    457     }
    458 }
    459 
    460423#prpl-settings-license-form {
    461424
     
    466429        gap: var(--prpl-padding);
    467430    }
    468 }
    469 
     431
     432    p {
     433        max-width: 42em;
     434    }
     435
     436    h3 {
     437        font-size: 1.15em;
     438    }
     439
     440    button.button-primary {
     441        margin-top: 1em;
     442    }
     443}
     444
     445.driver-popover.prpl-driverjs-theme {
     446    background-color: var(--prpl-background-orange);
     447    color: var(--prpl-color-text);
     448
     449    .driver-popover-title {
     450        color: var(--prpl-color-headings);
     451    }
     452
     453    button {
     454        color: var(--prpl-color-headings);
     455    }
     456
     457    button:not(.driver-popover-close-btn):hover {
     458        background-color: var(--prpl-background-orange);
     459    }
     460}
     461
     462/*------------------------------------*\
     463    External link accessibility helper.
     464\*------------------------------------*/
     465.prpl-external-link-icon {
     466    display: inline-flex;
     467    margin-inline-start: 0.25em;
     468    vertical-align: middle;
     469
     470    svg {
     471        width: 1em;
     472        height: 1em;
     473    }
     474}
  • progress-planner/tags/1.3.0/assets/css/page-widgets/suggested-tasks.css

    r3264985 r3283338  
    103103        }
    104104
     105        .prpl-no-suggested-tasks {
     106            display: none;
     107        }
     108
    105109        hr {
    106110            display: block;
     
    111115    hr {
    112116        display: none;
     117    }
     118
     119    .prpl-no-suggested-tasks {
     120        display: block;
     121        background-color: var(--prpl-background-green);
     122        padding: calc(var(--prpl-padding) / 2);
    113123    }
    114124}
  • progress-planner/tags/1.3.0/assets/css/page-widgets/whats-new.css

    r3217671 r3283338  
    1111    li {
    1212
    13         > a {
    14             color: var(--prpl-color-gray-6);
    15             text-decoration: none;
     13        h3 {
     14            margin-top: 0;
     15            font-size: 1.15em;
     16            font-weight: 600;
    1617
    17             h3 {
    18                 margin-top: 0;
    19                 font-size: 1.15em;
    20                 font-weight: 600;
     18            > a {
     19                color: var(--prpl-color-headings);
     20                text-decoration: none;
    2121
    22                 &::after {
    23                     content: url('data:image/svg+xml,<%3Fxml version="1.0" encoding="UTF-8"%3F><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="%23currentcolor" d="M6 1h5v5L8.86 3.85 4.7 8 4 7.3l4.15-4.16zM2 3h2v1H2v6h6V8h1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1"/></svg>');
    24                     margin-left: 0.25em;
    25                     width: 0.75em;
    26                     height: 0.75em;
    27                     display: inline-block;
     22                .prpl-external-link-icon {
     23                    margin-inline-start: 0.15em;
    2824                }
    2925            }
    3026        }
     27
    3128
    3229        img {
     
    3835        display: flex;
    3936        justify-content: flex-end;
    40 
    41         a::after {
    42             content: url('data:image/svg+xml,<%3Fxml version="1.0" encoding="UTF-8"%3F><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="%23currentcolor" d="M6 1h5v5L8.86 3.85 4.7 8 4 7.3l4.15-4.16zM2 3h2v1H2v6h6V8h1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1"/></svg>');
    43             margin-left: 0.25em;
    44             width: 1em;
    45             height: 1em;
    46             display: inline-block;
    47         }
    4837    }
    4938}
  • progress-planner/tags/1.3.0/assets/css/settings-page.css

    r3264985 r3283338  
    220220}
    221221
    222 .prpl-license-keys-wrapper {
    223     display: flex;
    224     flex-direction: column;
    225     gap: 1rem;
    226 
    227     .prpl-license-key-wrapper {
    228         display: flex;
    229         align-items: center;
    230         gap: 0.5rem;
    231 
    232         .prpl-license-status {
    233             width: 1rem;
    234             height: 1rem;
    235 
    236             svg {
     222/* Post types */
     223.prpl-column-post-types {
     224
     225    .prpl-settings-section-title {
     226
     227        svg {
     228            color: #038d88;
     229
     230            path {
     231                fill: currentcolor;
     232            }
     233        }
     234
     235        background-color: #f3faf9;
     236    }
     237
     238}
     239
     240/* Login destination */
     241.prpl-column-login-destination {
     242
     243    .prpl-settings-section-title {
     244
     245        svg {
     246            color: var(--prpl-color-accent-red);
     247        }
     248
     249        background-color: var(--prpl-background-red);
     250    }
     251
     252}
     253
     254
     255/* License */
     256.prpl-column-license {
     257
     258    .prpl-settings-section-title {
     259
     260        svg {
     261            color: #0773b4;
     262        }
     263        background-color: #effbfe;
     264    }
     265
     266    .prpl-license-keys-wrapper {
     267        display: flex;
     268        flex-direction: column;
     269        gap: 1rem;
     270        max-width: 40rem;
     271
     272        & > p:first-child {
     273            margin-top: 0;
     274        }
     275
     276        .prpl-license-key-wrapper {
     277            display: flex;
     278            align-items: center;
     279            gap: 0.5rem;
     280
     281            .prpl-license-status {
    237282                width: 1rem;
    238283                height: 1rem;
    239             }
    240         }
    241     }
    242 
    243     input {
    244         width: 20rem;
    245         max-width: calc(100% - 2rem);
    246     }
    247 }
     284
     285                svg {
     286                    width: 1rem;
     287                    height: 1rem;
     288                }
     289            }
     290        }
     291
     292        input {
     293            width: 30rem;
     294            max-width: calc(100% - 2rem);
     295            border: 1px solid var(--prpl-color-gray-2);
     296            box-shadow: 1px 2px 4px 0 rgba(0, 0, 0, 0.05);
     297        }
     298    }
     299
     300}
     301
     302
     303/* Grid layout for wrapper for:
     304- Valuable post types
     305- Default login destination
     306- License keys
     307*/
     308#prpl-grid-column-wrapper {
     309    display: grid;
     310    margin-bottom: var(--prpl-gap);
     311
     312    /* There are 5 or less valuable post types */
     313    grid-template-columns: 1fr 1fr;
     314    grid-template-rows: auto auto;
     315    gap: var(--prpl-settings-page-gap);
     316
     317    .prpl-column {
     318        align-self: stretch;
     319        display: flex;
     320        flex-direction: column;
     321
     322        .prpl-widget-wrapper {
     323            flex: 1;
     324            margin-bottom: 0;
     325        }
     326    }
     327
     328    /* Valuable post types */
     329    .prpl-column:nth-child(1) {
     330        grid-column: 1;
     331        grid-row: 1;
     332    }
     333
     334    /* Default login destination */
     335    .prpl-column:nth-child(2) {
     336        grid-column: 2;
     337        grid-row: 1;
     338    }
     339
     340    /* License keys */
     341    .prpl-column:nth-child(3) {
     342        grid-column: 1 / span 2;
     343        grid-row: 2;
     344    }
     345
     346    /* We have more than 5 valuable post types */
     347    &:has([data-has-many-valuable-post-types]) {
     348        grid-template-rows: auto auto;
     349
     350        /* Valuable post types */
     351        .prpl-column:nth-child(1) {
     352            grid-column: 1;
     353            grid-row: 1 / span 2;
     354
     355            /* Span 2 rows on the left */
     356        }
     357
     358        /* Default login destination */
     359        .prpl-column:nth-child(2) {
     360            grid-column: 2;
     361            grid-row: 1;
     362        }
     363
     364        /* License keys */
     365        .prpl-column:nth-child(3) {
     366            grid-column: 2;
     367            grid-row: 2;
     368        }
     369    }
     370}
     371
     372/* Valuable post types */
     373#prpl-post-types-include-wrapper {
     374    padding-top: 0.75rem;
     375
     376    label {
     377        display: block;
     378        margin-top: 0.75rem;
     379
     380        &:first-child {
     381            margin-top: 0;
     382        }
     383    }
     384}
  • progress-planner/tags/1.3.0/assets/js/settings-page.js

    r3264985 r3283338  
    6767        };
    6868        formData.forEach( function ( value, key ) {
    69             data[ key ] = value;
     69            // Handle array notation in keys
     70            if ( key.endsWith( '[]' ) ) {
     71                const baseKey = key.slice( 0, -2 );
     72                if ( ! data[ baseKey ] ) {
     73                    data[ baseKey ] = [];
     74                }
     75                data[ baseKey ].push( value );
     76            } else {
     77                data[ key ] = value;
     78            }
    7079        } );
    7180        const request = wp.ajax.post( 'prpl_settings_form', data );
  • progress-planner/tags/1.3.0/assets/js/settings.js

    r3264985 r3283338  
    77 * Dependencies: progress-planner/ajax-request, progress-planner/onboard, wp-util, progress-planner/l10n
    88 */
    9 document
    10     .getElementById( 'prpl-settings-form' )
    11     .addEventListener( 'submit', function ( event ) {
    12         event.preventDefault();
    13         const form = new FormData( this );
    14         const data = form.getAll( 'prpl-settings-post-types-include[]' );
    15 
    16         // Save the options.
    17         const request = wp.ajax.post( 'progress_planner_save_cpt_settings', {
    18             _ajax_nonce: progressPlanner.nonce,
    19             include_post_types: data.join( ',' ),
    20         } );
    21         request.done( () => {
    22             window.location.reload();
    23         } );
    24 
    25         document.getElementById( 'submit-include-post-types' ).disabled = true;
    26         document.getElementById( 'submit-include-post-types' ).innerHTML =
    27             prplL10n( 'saving' );
    28     } );
    299
    3010// Submit the email.
  • progress-planner/tags/1.3.0/assets/js/web-components/prpl-big-counter.js

    r3198155 r3283338  
    1717                backgroundColor || 'var(--prpl-background-purple)';
    1818
     19            const el = this;
     20
    1921            this.innerHTML = `
    2022                <div style="
     
    3133                    margin-bottom: var(--prpl-padding);
    3234                ">
     35                    <div class="container-width" style="width: 100%;"></div>
    3336                    <span style="
    3437                        font-size: var(--prpl-font-size-5xl);
     
    3639                        font-weight: 600;
    3740                    ">${ number }</span>
    38                     <span style="font-size: var(--prpl-font-size-2xl);">${ content }</span>
     41                    <span style="font-size: var(--prpl-font-size-2xl);">
     42                        <span class="resize" style="font-size: 100%; display: inline-block; width: max-content;">${ content }</span>
     43                    </span>
    3944                </div>
    4045            `;
     46
     47            const resizeFont = () => {
     48                const element = el.querySelector( '.resize' );
     49                if ( ! element ) {
     50                    return;
     51                }
     52
     53                element.style.fontSize = '100%';
     54
     55                let size = 100;
     56                while (
     57                    element.clientWidth >
     58                    el.querySelector( '.container-width' ).clientWidth
     59                ) {
     60                    if ( size < 80 ) {
     61                        element.style.fontSize = size + '%';
     62                        element.style.width = '100%';
     63                        break;
     64                    }
     65                    size -= 1;
     66                    element.style.fontSize = size + '%';
     67                }
     68            };
     69
     70            resizeFont();
     71            window.addEventListener( 'resize', resizeFont );
    4172        }
    4273    }
  • progress-planner/tags/1.3.0/assets/js/web-components/prpl-suggested-task.js

    r3268602 r3283338  
    2222            action = '',
    2323            url = '',
     24            url_target = '_self',
    2425            dismissable = false,
    2526            provider_id = '',
     
    3839            let taskHeading = title;
    3940            if ( url ) {
    40                 taskHeading = `<a href="${ url }">${ title }</a>`;
     41                taskHeading = `<a href="${ url }" target="${ url_target }">${ title }</a>`;
    4142            }
    4243
  • progress-planner/tags/1.3.0/assets/js/yoast-focus-element.js

    r3268602 r3283338  
    66
    77/**
    8  * Check if the value of the element matches the value specified in the task.
    9  *
    10  * @param {Element} element The element to check.
    11  * @param {Object}  task    The task to check.
    12  * @return {boolean} True if the value matches, false otherwise.
     8 * Yoast Focus Element class.
    139 */
    14 function checkTaskValue( element, task ) {
    15     if ( ! task.valueElement ) {
    16         return true;
    17     }
    18 
    19     const attributeName = task.valueElement.attributeName || 'value';
    20     const attributeValue = task.valueElement.attributeValue;
    21     const operator = task.valueElement.operator || '=';
    22     const currentValue = element.getAttribute( attributeName ) || '';
    23 
    24     return '!=' === operator
    25         ? currentValue !== attributeValue
    26         : currentValue === attributeValue;
    27 }
    28 
    29 /**
    30  * Observe the Yoast sidebar clicks.
    31  */
    32 function observeYoastSidebarClicks() {
    33     const container = document.querySelector( '#yoast-seo-settings' );
    34 
    35     if ( ! container ) {
    36         return;
    37     }
    38 
    39     const waitForNav = new MutationObserver( ( mutationsList, observer ) => {
    40         const nav = container.querySelector(
    41             'nav.yst-sidebar-navigation__sidebar'
     10class ProgressPlannerYoastFocus {
     11    /**
     12     * Constructor.
     13     */
     14    constructor() {
     15        this.container = document.querySelector( '#yoast-seo-settings' );
     16        this.tasks = progressPlannerYoastFocusElement.tasks;
     17        this.baseUrl = progressPlannerYoastFocusElement.base_url;
     18
     19        if ( this.container ) {
     20            this.init();
     21        }
     22    }
     23
     24    /**
     25     * Initialize the Yoast Focus Element.
     26     */
     27    init() {
     28        this.waitForMainAndObserveContent();
     29        this.observeYoastSidebarClicks();
     30    }
     31
     32    /**
     33     * Check if the value of the element matches the value specified in the task.
     34     *
     35     * @param {Element} element The element to check.
     36     * @param {Object}  task    The task to check.
     37     * @return {boolean} True if the value matches, false otherwise.
     38     */
     39    checkTaskValue( element, task ) {
     40        if ( ! task.valueElement ) {
     41            return true;
     42        }
     43
     44        const attributeName = task.valueElement.attributeName || 'value';
     45        const attributeValue = task.valueElement.attributeValue;
     46        const operator = task.valueElement.operator || '=';
     47        const currentValue = element.getAttribute( attributeName ) || '';
     48
     49        return '!=' === operator
     50            ? currentValue !== attributeValue
     51            : currentValue === attributeValue;
     52    }
     53
     54    /**
     55     * Observe the Yoast sidebar clicks.
     56     */
     57    observeYoastSidebarClicks() {
     58        const waitForNav = new MutationObserver(
     59            ( mutationsList, observer ) => {
     60                const nav = this.container.querySelector(
     61                    'nav.yst-sidebar-navigation__sidebar'
     62                );
     63                if ( nav ) {
     64                    observer.disconnect();
     65
     66                    nav.addEventListener( 'click', ( e ) => {
     67                        const link = e.target.closest( 'a' );
     68                        if ( link ) {
     69                            this.waitForMainAndObserveContent();
     70                        }
     71                    } );
     72                }
     73            }
    4274        );
    43         if ( nav ) {
    44             // Sidebar nav loaded.
    45             observer.disconnect();
    46 
    47             nav.addEventListener( 'click', ( e ) => {
    48                 const link = e.target.closest( 'a' );
    49                 if ( link ) {
    50                     // Sidebar link clicked.
    51                     waitForMainAndObserveContent(); // re-run logic after clicking
    52                 }
    53             } );
    54         }
    55     } );
    56 
    57     waitForNav.observe( container, {
    58         childList: true,
    59         subtree: true,
    60     } );
    61 }
    62 
    63 /**
    64  * Wait for the main content to load and observe the content.
    65  */
    66 function waitForMainAndObserveContent() {
    67     const container = document.querySelector( '#yoast-seo-settings' );
    68     if ( ! container ) {
    69         return;
    70     }
    71 
    72     const waitForMain = new MutationObserver( ( mutationsList, observer ) => {
    73         const main = container.querySelector( 'main.yst-paper' );
    74         if ( main ) {
    75             // Main loaded.
    76             observer.disconnect();
    77 
    78             const childObserver = new MutationObserver( ( mutations ) => {
    79                 for ( const mutation of mutations ) {
    80                     if (
    81                         mutation.type === 'attributes' &&
    82                         mutation.attributeName === 'class'
    83                     ) {
    84                         const el = mutation.target;
    85                         if (
    86                             el.parentElement === main &&
    87                             el.classList.contains( 'yst-opacity-100' )
    88                         ) {
    89                             // Fully loaded content.
    90                             childObserver.disconnect();
    91 
    92                             // Loop through the tasks and add the focus element.
    93                             for ( const task of progressPlannerYoastFocusElement.tasks ) {
    94                                 // Try to find the toggleButton.
    95                                 const valueElement = el.querySelector(
    96                                     task.valueElement.elementSelector
    97                                 );
    98                                 let raviIconPositionAbsolute = true;
    99 
    100                                 if ( valueElement ) {
    101                                     // We usually add icon to the option header.
    102                                     let addIconElement = valueElement.closest(
    103                                         task.iconElement
    104                                     );
    105 
    106                                     // Exception is the upload input field.
     75
     76        waitForNav.observe( this.container, {
     77            childList: true,
     78            subtree: true,
     79        } );
     80    }
     81
     82    /**
     83     * Wait for the main content to load and observe the content.
     84     */
     85    waitForMainAndObserveContent() {
     86        const waitForMain = new MutationObserver(
     87            ( mutationsList, observer ) => {
     88                const main = this.container.querySelector( 'main.yst-paper' );
     89                if ( main ) {
     90                    observer.disconnect();
     91
     92                    const childObserver = new MutationObserver(
     93                        ( mutations ) => {
     94                            for ( const mutation of mutations ) {
     95                                if (
     96                                    mutation.type === 'attributes' &&
     97                                    mutation.attributeName === 'class'
     98                                ) {
     99                                    const el = mutation.target;
    107100                                    if (
    108                                         ! addIconElement &&
    109                                         valueElement.type === 'hidden'
    110                                     ) {
    111                                         addIconElement = valueElement
    112                                             .closest( 'fieldset' )
    113                                             .querySelector( task.iconElement );
    114 
    115                                         raviIconPositionAbsolute = false;
    116                                     }
    117 
    118                                     // Upload input field.
    119                                     if ( ! addIconElement ) {
    120                                         continue;
    121                                     }
    122 
    123                                     // Append next to the valueElemen, only if it's not already there.
    124                                     if (
    125                                         ! addIconElement.querySelector(
    126                                             '.prpl-form-row-ravi'
     101                                        el.parentElement === main &&
     102                                        el.classList.contains(
     103                                            'yst-opacity-100'
    127104                                        )
    128105                                    ) {
    129                                         // Check for value if specified in task.
    130                                         const valueMatches = checkTaskValue(
    131                                             valueElement,
    132                                             task
    133                                         );
    134 
    135                                         // Create a new span with the class prpl-form-row-ravi.
    136                                         const raviIconWrapper =
    137                                             document.createElement( 'span' );
    138                                         raviIconWrapper.classList.add(
    139                                             'prpl-form-row-ravi',
    140                                             'prpl-element-awards-points-icon-wrapper'
    141                                         );
    142 
    143                                         if ( valueMatches ) {
    144                                             raviIconWrapper.classList.add(
    145                                                 'complete'
    146                                             );
    147                                         }
    148 
    149                                         // Styling for absolute positioning.
    150                                         if ( raviIconPositionAbsolute ) {
    151                                             addIconElement.style.position =
    152                                                 'relative';
    153 
    154                                             raviIconWrapper.style.position =
    155                                                 'absolute';
    156                                             raviIconWrapper.style.right =
    157                                                 '3.5rem';
    158                                             raviIconWrapper.style.top = '-7px';
    159                                         }
    160 
    161                                         raviIconWrapper.appendChild(
    162                                             document.createElement( 'span' )
    163                                         );
    164 
    165                                         // Create an icon image.
    166                                         const iconImg =
    167                                             document.createElement( 'img' );
    168                                         iconImg.src =
    169                                             progressPlannerYoastFocusElement.base_url +
    170                                             '/assets/images/icon_progress_planner.svg';
    171                                         iconImg.alt = 'Ravi';
    172                                         iconImg.width = 16;
    173                                         iconImg.height = 16;
    174 
    175                                         // Append the icon image to the raviIconWrapper.
    176                                         raviIconWrapper
    177                                             .querySelector( 'span' )
    178                                             .appendChild( iconImg );
    179 
    180                                         // Add the points to the raviIconWrapper.
    181                                         const pointsWrapper =
    182                                             document.createElement( 'span' );
    183                                         pointsWrapper.classList.add(
    184                                             'prpl-form-row-points'
    185                                         );
    186                                         pointsWrapper.textContent = valueMatches
    187                                             ? '✓'
    188                                             : '+1';
    189                                         raviIconWrapper.appendChild(
    190                                             pointsWrapper
    191                                         );
    192 
    193                                         // Finally add the raviIconWrapper to the DOM.
    194                                         addIconElement.appendChild(
    195                                             raviIconWrapper
    196                                         );
    197 
    198                                         // Watch for changes in aria-checked to update the icon dynamically
    199                                         const valueElementObserver =
    200                                             new MutationObserver( () => {
    201                                                 // Check value again if specified
    202                                                 const currentValueMatches =
    203                                                     checkTaskValue(
    204                                                         valueElement,
    205                                                         task
    206                                                     );
    207 
    208                                                 if ( currentValueMatches ) {
    209                                                     raviIconWrapper.classList.add(
    210                                                         'complete'
    211                                                     );
    212 
    213                                                     pointsWrapper.textContent =
    214                                                         '✓';
    215                                                 } else {
    216                                                     raviIconWrapper.classList.remove(
    217                                                         'complete'
    218                                                     );
    219 
    220                                                     pointsWrapper.textContent =
    221                                                         '+1';
    222                                                 }
    223                                             } );
    224 
    225                                         valueElementObserver.observe(
    226                                             valueElement,
    227                                             {
    228                                                 attributes: true,
    229                                                 attributeFilter: [
    230                                                     task.valueElement
    231                                                         .attributeName,
    232                                                 ],
    233                                             }
    234                                         );
     106                                        this.processTasks( el );
    235107                                    }
    236108                                }
    237109                            }
    238110                        }
    239                     }
     111                    );
     112
     113                    main.querySelectorAll( ':scope > *' ).forEach(
     114                        ( child ) => {
     115                            childObserver.observe( child, {
     116                                attributes: true,
     117                                attributeFilter: [ 'class' ],
     118                            } );
     119                        }
     120                    );
    240121                }
    241             } );
    242 
    243             // Watch direct children of main.yst-paper
    244             main.querySelectorAll( ':scope > *' ).forEach( ( child ) => {
    245                 childObserver.observe( child, {
    246                     attributes: true,
    247                     attributeFilter: [ 'class' ],
    248                 } );
    249             } );
    250         }
    251     } );
    252 
    253     waitForMain.observe( container, {
    254         childList: true,
    255         subtree: true,
    256     } );
     122            }
     123        );
     124
     125        waitForMain.observe( this.container, {
     126            childList: true,
     127            subtree: true,
     128        } );
     129    }
     130
     131    /**
     132     * Process all tasks for a given element.
     133     *
     134     * @param {Element} el The element to process tasks for.
     135     */
     136    processTasks( el ) {
     137        for ( const task of this.tasks ) {
     138            const valueElement = el.querySelector(
     139                task.valueElement.elementSelector
     140            );
     141            const raviIconPositionAbsolute = true;
     142
     143            if ( valueElement ) {
     144                this.processTask(
     145                    valueElement,
     146                    task,
     147                    raviIconPositionAbsolute
     148                );
     149            }
     150        }
     151    }
     152
     153    /**
     154     * Process a single task.
     155     *
     156     * @param {Element} valueElement             The value element to process.
     157     * @param {Object}  task                     The task to process.
     158     * @param {boolean} raviIconPositionAbsolute Whether the icon should be absolutely positioned.
     159     */
     160    processTask( valueElement, task, raviIconPositionAbsolute ) {
     161        let addIconElement = valueElement.closest( task.iconElement );
     162
     163        // Exception is the upload input field.
     164        if ( ! addIconElement && valueElement.type === 'hidden' ) {
     165            addIconElement = valueElement
     166                .closest( 'fieldset' )
     167                .querySelector( task.iconElement );
     168            raviIconPositionAbsolute = false;
     169        }
     170
     171        if ( ! addIconElement ) {
     172            return;
     173        }
     174
     175        if (
     176            ! addIconElement.querySelector( '[data-prpl-element="ravi-icon"]' )
     177        ) {
     178            this.addIcon(
     179                valueElement,
     180                addIconElement,
     181                task,
     182                raviIconPositionAbsolute
     183            );
     184        }
     185    }
     186
     187    /**
     188     * Add icon to the element.
     189     *
     190     * @param {Element} valueElement             The value element.
     191     * @param {Element} addIconElement           The element to add the icon to.
     192     * @param {Object}  task                     The task.
     193     * @param {boolean} raviIconPositionAbsolute Whether the icon should be absolutely positioned.
     194     */
     195    addIcon( valueElement, addIconElement, task, raviIconPositionAbsolute ) {
     196        const valueMatches = this.checkTaskValue( valueElement, task );
     197
     198        // Create a new span with the class prpl-form-row-ravi.
     199        const raviIconWrapper = document.createElement( 'span' );
     200        raviIconWrapper.classList.add(
     201            'prpl-element-awards-points-icon-wrapper'
     202        );
     203        raviIconWrapper.setAttribute( 'data-prpl-element', 'ravi-icon' );
     204
     205        if ( valueMatches ) {
     206            raviIconWrapper.classList.add( 'complete' );
     207        }
     208
     209        // Styling for absolute positioning.
     210        if ( raviIconPositionAbsolute ) {
     211            addIconElement.style.position = 'relative';
     212
     213            raviIconWrapper.style.position = 'absolute';
     214            raviIconWrapper.style.right = '3.5rem';
     215            raviIconWrapper.style.top = '-7px';
     216        }
     217
     218        raviIconWrapper.appendChild( document.createElement( 'span' ) );
     219
     220        // Create an icon image.
     221        const iconImg = document.createElement( 'img' );
     222        iconImg.src = this.baseUrl + '/assets/images/icon_progress_planner.svg';
     223        iconImg.alt = 'Ravi';
     224        iconImg.width = 16;
     225        iconImg.height = 16;
     226
     227        // Append the icon image to the raviIconWrapper.
     228        raviIconWrapper.querySelector( 'span' ).appendChild( iconImg );
     229
     230        // Add the points to the raviIconWrapper.
     231        const pointsWrapper = document.createElement( 'span' );
     232        pointsWrapper.classList.add( 'prpl-form-row-points' );
     233        pointsWrapper.textContent = valueMatches ? '✓' : '+1';
     234        raviIconWrapper.appendChild( pointsWrapper );
     235
     236        // Watch for changes in aria-checked to update the icon dynamically
     237        const valueElementObserver = new MutationObserver( () => {
     238            const currentValueMatches = this.checkTaskValue(
     239                valueElement,
     240                task
     241            );
     242
     243            if ( currentValueMatches ) {
     244                raviIconWrapper.classList.add( 'complete' );
     245                pointsWrapper.textContent = '✓';
     246            } else {
     247                raviIconWrapper.classList.remove( 'complete' );
     248                pointsWrapper.textContent = '+1';
     249            }
     250        } );
     251
     252        valueElementObserver.observe( valueElement, {
     253            attributes: true,
     254            attributeFilter: [ task.valueElement.attributeName ],
     255        } );
     256
     257        // Finally add the raviIconWrapper to the DOM.
     258        addIconElement.appendChild( raviIconWrapper );
     259    }
    257260}
    258261
    259 // Run once on initial page load.
    260 waitForMainAndObserveContent();
    261 observeYoastSidebarClicks();
     262// Initialize the Yoast Focus Element.
     263new ProgressPlannerYoastFocus();
  • progress-planner/tags/1.3.0/classes/actions/class-content-scan.php

    r3268602 r3283338  
    172172        // Loop through the posts and update the stats.
    173173        foreach ( $posts as $post ) {
    174             // Set the activity.
    175             $activities[ $post->ID ] = \progress_planner()->get_activities__content_helpers()->get_activity_from_post( $post );
     174            // Set the activity, we're dealing only with published posts (but just in case).
     175            $activities[ $post->ID ] = \progress_planner()->get_activities__content_helpers()->get_activity_from_post( $post, 'publish' === $post->post_status ? 'publish' : 'update' );
    176176            // Set the word count.
    177177            \progress_planner()->get_activities__content_helpers()->get_word_count( $post->post_content, $post->ID );
  • progress-planner/tags/1.3.0/classes/actions/class-content.php

    r3268602 r3283338  
    281281        }
    282282
    283         $activity       = \progress_planner()->get_activities__content_helpers()->get_activity_from_post( $post );
    284         $activity->type = $type;
     283        $activity = \progress_planner()->get_activities__content_helpers()->get_activity_from_post( $post, $type );
    285284
    286285        // Update the badges.
     
    306305        }
    307306
     307        // We need to set the date explicitly since post_date & post_modified dates are not changed when the post is trashed.
     308        if ( 'trash' === $type ) {
     309            $activity->date = new \DateTime();
     310        }
     311
    308312        $activity->save();
    309313
  • progress-planner/tags/1.3.0/classes/activities/class-content-helpers.php

    r3268602 r3283338  
    100100     *
    101101     * @param \WP_Post $post The post object.
     102     * @param string   $activity_type The activity type.
    102103     *
    103104     * @return \Progress_Planner\Activities\Content
    104105     */
    105     public function get_activity_from_post( $post ) {
    106         $type = 'publish' === $post->post_status ? 'publish' : 'update';
    107         $date = 'publish' === $post->post_status ? $post->post_date : $post->post_modified;
    108 
     106    public function get_activity_from_post( $post, $activity_type = 'publish' ) {
    109107        $activity           = new Activities_Content();
    110108        $activity->category = 'content';
    111         $activity->type     = $type;
    112         $activity->date     = \progress_planner()->get_utils__date()->get_datetime_from_mysql_date( $date );
     109        $activity->type     = $activity_type;
     110        $activity->date     = \progress_planner()->get_utils__date()->get_datetime_from_mysql_date( $post->post_modified );
    113111        $activity->data_id  = (string) $post->ID;
    114112        $activity->user_id  = (int) $post->post_author;
  • progress-planner/tags/1.3.0/classes/activities/class-suggested-task.php

    r3268602 r3283338  
    6666
    6767        // Default points for a suggested task.
    68         $points               = 1;
    69         $create_post_provider = new Create();
     68        $points        = 1;
     69        $task_provider = \progress_planner()->get_suggested_tasks()->get_local()->get_task_provider( $this->data_id );
    7070
    71         $data = \progress_planner()->get_suggested_tasks()->get_local()->get_data_from_task_id( $this->data_id );
    72         if ( isset( $data['provider_id'] ) && $create_post_provider->get_provider_id() === $data['provider_id'] ) {
    73             $points = $create_post_provider->get_points_for_task( $this->data_id );
     71        if ( $task_provider ) {
     72            // Create post task provider had a different points system, this is for backwards compatibility.
     73            $points = $task_provider instanceof Create ? $task_provider->get_points( $this->data_id ) : $task_provider->get_points();
    7474        }
    7575
  • progress-planner/tags/1.3.0/classes/admin/class-dashboard-widget-score.php

    r3264985 r3283338  
    4848
    4949        \progress_planner()->get_admin__enqueue()->enqueue_style( "progress-planner/dashboard-widgets/{$this->id}" );
     50
     51        \progress_planner()->get_admin__enqueue()->enqueue_script( 'external-link-accessibility-helper' );
    5052
    5153        \progress_planner()->the_view( "dashboard-widgets/{$this->id}.php" );
  • progress-planner/tags/1.3.0/classes/admin/class-page-settings.php

    r3264985 r3283338  
    172172
    173173        $this->save_settings();
     174        $this->save_post_types();
    174175        $this->save_license();
    175176
    176         do_action( 'progress_planner_settings_form_options_stored' );
     177        \do_action( 'progress_planner_settings_form_options_stored' );
    177178
    178179        \wp_send_json_success( \esc_html__( 'Options stored successfully', 'progress-planner' ) );
     
    185186     */
    186187    public function save_settings() {
    187         $redirect_on_login = isset( $_POST['prpl-redirect-on-login'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
    188             ? \sanitize_text_field( \wp_unslash( $_POST['prpl-redirect-on-login'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
     188
     189        // Check the nonce.
     190        \check_admin_referer( 'progress_planner' );
     191
     192        $redirect_on_login = isset( $_POST['prpl-redirect-on-login'] )
     193            ? \sanitize_text_field( \wp_unslash( $_POST['prpl-redirect-on-login'] ) )
    189194            : false;
    190195
     
    193198
    194199    /**
     200     * Save the post types.
     201     *
     202     * @return void
     203     */
     204    public function save_post_types() {
     205
     206        // Check the nonce.
     207        \check_admin_referer( 'progress_planner' );
     208
     209        $include_post_types = isset( $_POST['prpl-post-types-include'] )
     210            ? array_map( 'sanitize_text_field', \wp_unslash( $_POST['prpl-post-types-include'] ) ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     211            // If no post types are selected, use the default post types (post and page can be deregistered).
     212            : array_intersect( [ 'post', 'page' ], \progress_planner()->get_settings()->get_public_post_types() );
     213
     214        \progress_planner()->get_settings()->set( 'include_post_types', $include_post_types );
     215    }
     216
     217    /**
    195218     * Save the license key.
    196219     *
     
    198221     */
    199222    public function save_license() {
    200         $license = isset( $_POST['prpl-pro-license-key'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
    201             ? \sanitize_text_field( \wp_unslash( $_POST['prpl-pro-license-key'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
     223
     224        // Check the nonce.
     225        \check_admin_referer( 'progress_planner' );
     226
     227        $license = isset( $_POST['prpl-pro-license-key'] )
     228            ? \sanitize_text_field( \wp_unslash( $_POST['prpl-pro-license-key'] ) )
    202229            : '';
    203230
  • progress-planner/tags/1.3.0/classes/admin/class-page.php

    r3268602 r3283338  
    2828        \add_action( 'admin_menu', [ $this, 'add_page' ] );
    2929        \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
    30         \add_action( 'wp_ajax_progress_planner_save_cpt_settings', [ $this, 'save_cpt_settings' ] );
    3130        \add_action( 'in_admin_header', [ $this, 'remove_admin_notices' ], PHP_INT_MAX );
    3231
     
    5251            \progress_planner()->get_admin__widgets__latest_badge(),
    5352            \progress_planner()->get_admin__widgets__badge_streak(),
    54             \progress_planner()->get_admin__widgets__published_content(),
     53            \progress_planner()->get_admin__widgets__content_activity(),
    5554            \progress_planner()->get_admin__widgets__whats_new(),
    5655        ];
     
    188187                \progress_planner()->get_admin__enqueue()->enqueue_script( 'onboard', $default_localization_data );
    189188            }
     189
     190            \progress_planner()->get_admin__enqueue()->enqueue_script( 'external-link-accessibility-helper' );
    190191        }
    191192
     
    200201                ]
    201202            );
     203
     204            \progress_planner()->get_admin__enqueue()->enqueue_script( 'external-link-accessibility-helper' );
    202205        }
    203206    }
     
    292295
    293296    /**
    294      * Save the post types settings.
    295      *
    296      * @return void
    297      */
    298     public function save_cpt_settings() {
    299         \check_ajax_referer( 'progress_planner', 'nonce', false );
    300         $include_post_types = isset( $_POST['include_post_types'] )
    301             ? \sanitize_text_field( \wp_unslash( $_POST['include_post_types'] ) )
    302             : 'post,page';
    303         $include_post_types = \explode( ',', $include_post_types );
    304         \progress_planner()->get_settings()->set( 'include_post_types', $include_post_types );
    305 
    306         \wp_send_json_success(
    307             [
    308                 'message' => \esc_html__( 'Settings saved.', 'progress-planner' ),
    309             ]
    310         );
    311     }
    312 
    313     /**
    314297     * Remove all admin notices when the user is on the Progress Planner page.
    315298     *
     
    363346                .update-plugins {
    364347                    position: absolute;
    365                     left: 22px;
    366                     top: 0px;
     348                    left: 18px;
     349                    bottom: 0px;
    367350                    min-width: 15px;
    368351                    height: 15px;
  • progress-planner/tags/1.3.0/classes/admin/widgets/class-suggested-tasks.php

    r3268602 r3283338  
    8181                            $task_details['action']   = 'celebrate';
    8282                            $task_details['status']   = 'pending_celebration';
    83 
    84                             // Award 2 points if last created post was long.
    85                             $create_provider = new Create();
    86                             if ( $create_provider->get_provider_id() === $task_provider->get_provider_id() ) {
    87                                 $task_details['points'] = $create_provider->get_points_for_task( $task_id );
    88                             }
    8983
    9084                            $tasks[] = $task_details;
  • progress-planner/tags/1.3.0/classes/class-badges.php

    r3268602 r3283338  
    5555    public function __construct() {
    5656        $this->content = [
    57             \progress_planner()->get_badges__content__wonderful_writer(),
    58             \progress_planner()->get_badges__content__bold_blogger(),
    59             \progress_planner()->get_badges__content__awesome_author(),
     57            \progress_planner()->get_badges__content__content_curator(),
     58            \progress_planner()->get_badges__content__revision_ranger(),
     59            \progress_planner()->get_badges__content__purposeful_publisher(),
    6060        ];
    6161
  • progress-planner/tags/1.3.0/classes/class-base.php

    r3268602 r3283338  
    194194            'get_chart'                                  => [ 'get_ui__chart', '1.1.1' ],
    195195            'get_popover'                                => [ 'get_ui__popover', '1.1.1' ],
     196
     197            'get_admin__widgets__published_content'      => [ 'get_admin__widgets__content_activity', '1.3.0' ],
    196198        ];
    197199
  • progress-planner/tags/1.3.0/classes/class-plugin-migrations.php

    r3268602 r3283338  
    1111
    1212use Progress_Planner\Update\Update_111;
     13use Progress_Planner\Update\Update_130;
    1314
    1415/**
     
    4243    private const UPGRADE_CLASSES = [
    4344        '1.1.1' => Update_111::class,
     45        '1.3.0' => Update_130::class,
    4446    ];
    4547
  • progress-planner/tags/1.3.0/classes/class-plugin-upgrade-tasks.php

    r3268602 r3283338  
    2525
    2626        // Check if the plugin was upgraded or new plugin was activated.
    27         \add_action( 'init', [ $this, 'handle_activation_or_upgrade' ], 10 );
     27        \add_action( 'init', [ $this, 'handle_activation_or_upgrade' ], 100 ); // We need to run this after the Local_Tasks_Manager::init() is called.
    2828    }
    2929
  • progress-planner/tags/1.3.0/classes/class-settings.php

    r3198155 r3283338  
    111111        return $this->save_settings();
    112112    }
     113
     114    /**
     115     * Get an array of post-types names for the stats.
     116     *
     117     * @return string[]
     118     */
     119    public function get_post_types_names() {
     120        static $include_post_types;
     121
     122        if ( ! doing_action( 'init' ) && ! did_action( 'init' ) ) {
     123            \trigger_error( // phpcs:ignore
     124                sprintf(
     125                    '%1$s was called too early. Wait for init hook to be called to have access to the post types.',
     126                    \esc_html( get_class() . '::' . __FUNCTION__ )
     127                ),
     128                E_USER_WARNING
     129            );
     130        }
     131
     132        // Since we're working with CPTs, dont cache until init.
     133        if ( isset( $include_post_types ) && ! empty( $include_post_types ) ) {
     134            return $include_post_types;
     135        }
     136
     137        $public_post_types = $this->get_public_post_types();
     138
     139        // Post or pages can be deregistered.
     140        $default = array_intersect( [ 'post', 'page' ], $public_post_types );
     141
     142        // Filter the saved post types.
     143        $include_post_types = array_intersect( $this->get( [ 'include_post_types' ], $default ), $public_post_types );
     144
     145        return empty( $include_post_types ) ? $default : \array_values( $include_post_types );
     146    }
     147
     148    /**
     149     * Get the public post types.
     150     *
     151     * @return string[]
     152     */
     153    public function get_public_post_types() {
     154        $public_post_types = \array_filter( \get_post_types( [ 'public' => true ] ), 'is_post_type_viewable' );
     155
     156        unset( $public_post_types['attachment'] );
     157        unset( $public_post_types['elementor_library'] ); // Elementor templates are not a post type we want to track.
     158
     159        /**
     160         * Filter the public post types.
     161         *
     162         * @param string[] $public_post_types The public post types.
     163         *
     164         * @return string[]
     165         */
     166        return \apply_filters( 'progress_planner_public_post_types', $public_post_types );
     167    }
    113168}
  • progress-planner/tags/1.3.0/classes/class-suggested-tasks.php

    r3268602 r3283338  
    4444
    4545        if ( \is_admin() ) {
    46             \add_action( 'init', [ $this, 'init' ], 1 );
     46            \add_action( 'init', [ $this, 'init' ], 100 ); // Wait for the post types to be initialized.
    4747        }
    4848
     
    580580
    581581    /**
     582     * Add a remote task to the pending tasks.
     583     *
     584     * @param string $task_id The task ID.
     585     *
     586     * @return bool
     587     */
     588    public function add_remote_task_to_pending_tasks( $task_id ) {
     589        $remote_task_data = $this->get_remote_task_by_task_id( $task_id );
     590
     591        if ( ! $remote_task_data ) {
     592            return false;
     593        }
     594
     595        return \progress_planner()->get_suggested_tasks()->get_local()->add_pending_task(
     596            [
     597                'task_id'     => $task_id,
     598                'provider_id' => $remote_task_data['category'] ?? '', // Remote tasks use the category as provider_id.
     599                'category'    => $remote_task_data['category'] ?? '',
     600            ]
     601        );
     602    }
     603
     604
     605    /**
    582606     * Handle the suggested task action.
    583607     *
     
    601625                // We need to add the task to the pending tasks first, before marking it as completed.
    602626                if ( false !== strpos( $task_id, 'remote-task' ) ) {
    603                     $remote_task_data = $this->get_remote_task_by_task_id( $task_id );
    604                     \progress_planner()->get_suggested_tasks()->get_local()->add_pending_task(
    605                         [
    606                             'task_id'     => $task_id,
    607                             'provider_id' => $remote_task_data['category'] ?? '', // Remote tasks use the category as provider_id.
    608                             'category'    => $remote_task_data['category'] ?? '',
    609                         ]
    610                     );
     627                    $this->add_remote_task_to_pending_tasks( $task_id );
    611628                }
    612629
     
    627644            case 'snooze':
    628645                $duration = isset( $_POST['duration'] ) ? \sanitize_text_field( \wp_unslash( $_POST['duration'] ) ) : '';
    629                 $updated  = $this->snooze_task( $task_id, $duration );
     646
     647                // We need to add the task to the pending tasks first, before marking it as snoozed.
     648                if ( false !== strpos( $task_id, 'remote-task' ) ) {
     649                    $this->add_remote_task_to_pending_tasks( $task_id );
     650                }
     651
     652                $updated = $this->snooze_task( $task_id, $duration );
    630653                break;
    631654
  • progress-planner/tags/1.3.0/classes/rest/class-stats.php

    r3268602 r3283338  
    163163        $data['recommendations'] = [];
    164164        foreach ( $ravis_recommendations as $recommendation ) {
    165             $data['recommendations'][] = [
     165            $r = [
    166166                'id'          => $recommendation['task_id'],
    167167                'title'       => $recommendation['title'],
     
    169169                'provider_id' => $recommendation['provider_id'],
    170170            ];
     171
     172            if ( 'user' === $recommendation['provider_id'] ) {
     173                $r['points'] = isset( $recommendation['points'] ) ? $recommendation['points'] : 0;
     174            }
     175            $data['recommendations'][] = $r;
    171176        }
    172177
  • progress-planner/tags/1.3.0/classes/suggested-tasks/class-local-tasks-manager.php

    r3268602 r3283338  
    2929use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Integrations\Yoast\Add_Yoast_Providers;
    3030use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\User as User_Tasks;
     31use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\One_Time\Set_Valuable_Post_Types;
    3132
    3233/**
     
    6566            new Search_Engine_Visibility(),
    6667            new User_Tasks(),
     68            new Set_Valuable_Post_Types(),
    6769        ];
    6870
     
    7173
    7274        // At this point both local and task providers for the plugins we integrate with are instantiated, so initialize them.
    73         \add_action( 'plugins_loaded', [ $this, 'init' ], 11 );
     75        \add_action( 'init', [ $this, 'init' ], 99 ); // Wait for the post types to be initialized.
    7476
    7577        // Add the cleanup action.
     
    239241            $task_id = $task_data['task_id'];
    240242
     243            // Check if the task is no longer relevant.
     244            $task_object   = Local_Task_Factory::create_task_from( 'id', $task_id );
     245            $task_provider = $this->get_task_provider( $task_object->get_provider_id() );
     246            if ( $task_provider && ! $task_provider->is_task_relevant() ) {
     247                // Remove the task from the pending tasks.
     248                \progress_planner()->get_suggested_tasks()->delete_task( $task_id );
     249            }
     250
    241251            $task_result = $this->evaluate_task( $task_id );
    242252            if ( false !== $task_result ) {
     
    353363            $tasks,
    354364            function ( $task ) {
    355                 // If the task was already completed, remove it.
    356                 if ( 'completed' === $task['status'] ) {
    357                     return false;
    358                 }
    359 
    360                 if ( isset( $task['date'] ) ) {
     365
     366                if ( 'pending' === $task['status'] && isset( $task['date'] ) ) {
    361367                    return (string) \gmdate( 'YW' ) === (string) $task['date'];
    362368                }
  • progress-planner/tags/1.3.0/classes/suggested-tasks/data-collector/class-data-collector-manager.php

    r3268602 r3283338  
    1414use Progress_Planner\Suggested_Tasks\Data_Collector\Post_Author;
    1515use Progress_Planner\Suggested_Tasks\Data_Collector\Last_Published_Post;
     16use Progress_Planner\Suggested_Tasks\Data_Collector\Archive_Format;
    1617
    1718/**
     
    4041            new Post_Author(),
    4142            new Last_Published_Post(),
     43            new Archive_Format(),
    4244        ];
    4345
  • progress-planner/tags/1.3.0/classes/suggested-tasks/data-collector/class-inactive-plugins.php

    r3268602 r3283338  
    5151        }
    5252
     53        // Clear the plugins cache, so get_plugins() returns the latest plugins.
     54        wp_cache_delete( 'plugins', 'plugins' );
     55
    5356        $plugins        = get_plugins();
    5457        $plugins_active = 0;
  • progress-planner/tags/1.3.0/classes/suggested-tasks/data-collector/class-last-published-post.php

    r3268602 r3283338  
    2323
    2424    /**
     25     * The include post types.
     26     *
     27     * @var string[]
     28     */
     29    protected $include_post_types = [];
     30
     31    /**
    2532     * Initialize the data collector.
    2633     *
     
    2835     */
    2936    public function init() {
     37        \add_action( 'init', [ $this, 'set_include_post_types' ], 99 ); // Wait for all CPTs to be registered.
    3038        \add_action( 'transition_post_status', [ $this, 'update_last_published_post_cache' ], 10, 3 );
     39    }
     40
     41    /**
     42     * Set the include post types.
     43     *
     44     * @return void
     45     */
     46    public function set_include_post_types() {
     47        $this->include_post_types = \progress_planner()->get_settings()->get_post_types_names();
    3148    }
    3249
     
    4158     */
    4259    public function update_last_published_post_cache( $new_status, $old_status, $post ) {
    43         if ( $new_status === 'publish' || $old_status === 'publish' ) {
     60        if ( true === \in_array( get_post_type( $post ), $this->include_post_types, true ) && ( $new_status === 'publish' || $old_status === 'publish' ) ) {
    4461            $this->update_cache();
    4562        }
     
    6784                'orderby'        => 'date',
    6885                'order'          => 'DESC',
     86                'post_type'      => $this->include_post_types,
    6987            ]
    7088        );
     
    7391            $data = [
    7492                'post_id'   => $last_created_posts[0]->ID,
    75                 'long'      => \progress_planner()->get_activities__content_helpers()->is_post_long( $last_created_posts[0]->ID ) ? true : false,
    7693                'post_date' => $last_created_posts[0]->post_date,
    7794            ];
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/class-local-tasks-interface.php

    r3264985 r3283338  
    8181     */
    8282    public function capability_required();
     83
     84    /**
     85     * Check if the task is still relevant.
     86     * For example, we have a task to disable author archives if there is only one author.
     87     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     88     *
     89     * @return bool
     90     */
     91    public function is_task_relevant();
    8392}
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/class-local-tasks.php

    r3268602 r3283338  
    8080
    8181    /**
     82     * The task URL target.
     83     *
     84     * @var string
     85     */
     86    protected $url_target = '_self';
     87
     88    /**
    8289     * The task link setting.
    8390     *
     
    159166
    160167        return '';
     168    }
     169
     170    /**
     171     * Get the task URL.
     172     *
     173     * @return string
     174     */
     175    public function get_url_target() {
     176        return $this->url_target ? $this->url_target : '_self';
    161177    }
    162178
     
    265281        return false;
    266282    }
     283
     284    /**
     285     * Check if the task is still relevant.
     286     * For example, we have a task to disable author archives if there is only one author.
     287     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     288     *
     289     * @return bool
     290     */
     291    public function is_task_relevant() {
     292        return true;
     293    }
    267294}
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/class-user.php

    r3264985 r3283338  
    9494                    'points'       => 0,
    9595                    'url'          => '',
     96                    'url_target'   => '_self',
    9697                    'description'  => '',
    9798                    'link_setting' => [],
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-add-yoast-providers.php

    r3268602 r3283338  
    4747        foreach ( $this->providers as $provider ) {
    4848
    49             // Add Ravi icon if the task is pending or completed.
    50             if ( $provider->should_add_task() ) {
     49            // Add Ravi icon if the task is pending or is completed.
     50            if ( $provider->is_task_relevant() || \progress_planner()->get_suggested_tasks()->was_task_completed( $provider->get_task_id() ) ) {
    5151                $focus_task = $provider->get_focus_tasks();
    5252
    5353                if ( $focus_task ) {
    54                     $focus_tasks[] = $focus_task;
     54                    $focus_tasks = array_merge( $focus_tasks, $focus_task );
    5555                }
    5656            }
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-archive-author.php

    r3268602 r3283338  
    7373    public function get_focus_tasks() {
    7474        return [
    75             'iconElement'  => '.yst-toggle-field__header',
    76             'valueElement' => [
    77                 'elementSelector' => 'button[data-id="input-wpseo_titles-disable-author"]',
    78                 'attributeName'   => 'aria-checked',
    79                 'attributeValue'  => 'false',
    80                 'operator'        => '=',
     75            [
     76                'iconElement'  => '.yst-toggle-field__header',
     77                'valueElement' => [
     78                    'elementSelector' => 'button[data-id="input-wpseo_titles-disable-author"]',
     79                    'attributeName'   => 'aria-checked',
     80                    'attributeValue'  => 'false',
     81                    'operator'        => '=',
     82                ],
    8183            ],
    8284        ];
     
    8991     */
    9092    public function should_add_task() {
     93
     94        if ( ! $this->is_task_relevant() ) {
     95            return false;
     96        }
     97
    9198        // If the author archive is already disabled, we don't need to add the task.
    9299        if ( YoastSEO()->helpers->options->get( 'disable-author' ) === true ) {
     
    94101        }
    95102
     103        return true;
     104    }
     105
     106    /**
     107     * Check if the task is still relevant.
     108     * For example, we have a task to disable author archives if there is only one author.
     109     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     110     *
     111     * @return bool
     112     */
     113    public function is_task_relevant() {
    96114        // If there is more than one author, we don't need to add the task.
    97115        if ( $this->data_collector->collect() > self::MINIMUM_AUTHOR_WITH_POSTS ) {
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-archive-date.php

    r3268602 r3283338  
    5656    public function get_focus_tasks() {
    5757        return [
    58             'iconElement'  => '.yst-toggle-field__header',
    59             'valueElement' => [
    60                 'elementSelector' => 'button[data-id="input-wpseo_titles-disable-date"]',
    61                 'attributeName'   => 'aria-checked',
    62                 'attributeValue'  => 'false',
    63                 'operator'        => '=',
     58            [
     59                'iconElement'  => '.yst-toggle-field__header',
     60                'valueElement' => [
     61                    'elementSelector' => 'button[data-id="input-wpseo_titles-disable-date"]',
     62                    'attributeName'   => 'aria-checked',
     63                    'attributeValue'  => 'false',
     64                    'operator'        => '=',
     65                ],
    6466            ],
    6567        ];
     
    7274     */
    7375    public function should_add_task() {
     76
     77        if ( ! $this->is_task_relevant() ) {
     78            return false;
     79        }
     80
     81        // If the date archive is already disabled, we don't need to add the task.
     82        return YoastSEO()->helpers->options->get( 'disable-date' ) !== true;
     83    }
     84
     85    /**
     86     * Check if the task is still relevant.
     87     * For example, we have a task to disable author archives if there is only one author.
     88     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     89     *
     90     * @return bool
     91     */
     92    public function is_task_relevant() {
    7493        // If the permalink structure includes %year%, %monthnum%, or %day%, we don't need to add the task.
    7594        $permalink_structure = \get_option( 'permalink_structure' );
     
    7897        }
    7998
    80         // If the date archive is already disabled, we don't need to add the task.
    81         return YoastSEO()->helpers->options->get( 'disable-date' ) !== true;
     99        return true;
    82100    }
    83101}
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-archive-format.php

    r3268602 r3283338  
    7373    public function get_focus_tasks() {
    7474        return [
    75             'iconElement'  => '.yst-toggle-field__header',
    76             'valueElement' => [
    77                 'elementSelector' => 'button[data-id="input-wpseo_titles-disable-post_format"]',
    78                 'attributeName'   => 'aria-checked',
    79                 'attributeValue'  => 'false',
    80                 'operator'        => '=',
     75            [
     76                'iconElement'  => '.yst-toggle-field__header',
     77                'valueElement' => [
     78                    'elementSelector' => 'button[data-id="input-wpseo_titles-disable-post_format"]',
     79                    'attributeName'   => 'aria-checked',
     80                    'attributeValue'  => 'false',
     81                    'operator'        => '=',
     82                ],
    8183            ],
    8284        ];
     
    8991     */
    9092    public function should_add_task() {
    91         $archive_format_count = $this->data_collector->collect();
    92 
    93         // If there are more than X posts with a post format, we don't need to add the task. X is set in the class.
    94         if ( $archive_format_count > static::MINIMUM_POSTS_WITH_FORMAT ) {
     93        if ( ! $this->is_task_relevant() ) {
    9594            return false;
    9695        }
     
    103102        return true;
    104103    }
     104
     105    /**
     106     * Check if the task is still relevant.
     107     * For example, we have a task to disable author archives if there is only one author.
     108     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     109     *
     110     * @return bool
     111     */
     112    public function is_task_relevant() {
     113        $archive_format_count = $this->data_collector->collect();
     114
     115        // If there are more than X posts with a post format, we don't need to add the task. X is set in the class.
     116        if ( $archive_format_count > static::MINIMUM_POSTS_WITH_FORMAT ) {
     117            return false;
     118        }
     119
     120        return true;
     121    }
    105122}
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-crawl-settings-emoji-scripts.php

    r3268602 r3283338  
    5656    public function get_focus_tasks() {
    5757        return [
    58             'iconElement'  => '.yst-toggle-field__header',
    59             'valueElement' => [
    60                 'elementSelector' => 'button[data-id="input-wpseo-remove_emoji_scripts"]',
    61                 'attributeName'   => 'aria-checked',
    62                 'attributeValue'  => 'true',
    63                 'operator'        => '=',
     58            [
     59                'iconElement'  => '.yst-toggle-field__header',
     60                'valueElement' => [
     61                    'elementSelector' => 'button[data-id="input-wpseo-remove_emoji_scripts"]',
     62                    'attributeName'   => 'aria-checked',
     63                    'attributeValue'  => 'true',
     64                    'operator'        => '=',
     65                ],
    6466            ],
    6567        ];
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-crawl-settings-feed-authors.php

    r3268602 r3283338  
    7373    public function get_focus_tasks() {
    7474        return [
    75             'iconElement'  => '.yst-toggle-field__header',
    76             'valueElement' => [
    77                 'elementSelector' => 'button[data-id="input-wpseo-remove_feed_authors"]',
    78                 'attributeName'   => 'aria-checked',
    79                 'attributeValue'  => 'true',
    80                 'operator'        => '=',
     75            [
     76                'iconElement'  => '.yst-toggle-field__header',
     77                'valueElement' => [
     78                    'elementSelector' => 'button[data-id="input-wpseo-remove_feed_authors"]',
     79                    'attributeName'   => 'aria-checked',
     80                    'attributeValue'  => 'true',
     81                    'operator'        => '=',
     82                ],
    8183            ],
    8284        ];
     
    8991     */
    9092    public function should_add_task() {
    91         // If there is more than one author, we don't need to add the task.
    92         if ( $this->data_collector->collect() > self::MINIMUM_AUTHOR_WITH_POSTS ) {
     93
     94        if ( ! $this->is_task_relevant() ) {
    9395            return false;
    9496        }
     
    104106        return true;
    105107    }
     108
     109    /**
     110     * Check if the task is still relevant.
     111     * For example, we have a task to disable author archives if there is only one author.
     112     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     113     *
     114     * @return bool
     115     */
     116    public function is_task_relevant() {
     117        // If there is more than one author, we don't need to add the task.
     118        if ( $this->data_collector->collect() > self::MINIMUM_AUTHOR_WITH_POSTS ) {
     119            return false;
     120        }
     121
     122        return true;
     123    }
    106124}
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-crawl-settings-feed-global-comments.php

    r3268602 r3283338  
    5656    public function get_focus_tasks() {
    5757        return [
    58             'iconElement'  => '.yst-toggle-field__header',
    59             'valueElement' => [
    60                 'elementSelector' => 'button[data-id="input-wpseo-remove_feed_global_comments"]',
    61                 'attributeName'   => 'aria-checked',
    62                 'attributeValue'  => 'true',
    63                 'operator'        => '=',
     58            [
     59                'iconElement'  => '.yst-toggle-field__header',
     60                'valueElement' => [
     61                    'elementSelector' => 'button[data-id="input-wpseo-remove_feed_global_comments"]',
     62                    'attributeName'   => 'aria-checked',
     63                    'attributeValue'  => 'true',
     64                    'operator'        => '=',
     65                ],
    6466            ],
    6567        ];
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-media-pages.php

    r3268602 r3283338  
    5656    public function get_focus_tasks() {
    5757        return [
    58             'iconElement'  => '.yst-toggle-field__header',
    59             'valueElement' => [
    60                 'elementSelector' => 'button[data-id="input-wpseo_titles-disable-attachment"]',
    61                 'attributeName'   => 'aria-checked',
    62                 'attributeValue'  => 'false',
    63                 'operator'        => '=',
     58            [
     59                'iconElement'  => '.yst-toggle-field__header',
     60                'valueElement' => [
     61                    'elementSelector' => 'button[data-id="input-wpseo_titles-disable-attachment"]',
     62                    'attributeName'   => 'aria-checked',
     63                    'attributeValue'  => 'false',
     64                    'operator'        => '=',
     65                ],
    6466            ],
    6567        ];
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-organization-logo.php

    r3268602 r3283338  
    7171    public function get_focus_tasks() {
    7272        return [
    73             'iconElement'  => 'legend.yst-label',
    74             'valueElement' => [
    75                 'elementSelector' => $this->yoast_seo->helpers->options->get( 'company_or_person', 'company' ) !== 'person'
    76                     ? 'input[name="wpseo_titles.company_logo"]'
    77                     : 'input[name="wpseo_titles.person_logo"]',
    78                 'attributeName'   => 'value',
    79                 'attributeValue'  => '',
    80                 'operator'        => '!=',
     73            [
     74                'iconElement'  => 'legend.yst-label',
     75                'valueElement' => [
     76                    'elementSelector' => 'input[name="wpseo_titles.company_logo"]',
     77                    'attributeName'   => 'value',
     78                    'attributeValue'  => '',
     79                    'operator'        => '!=',
     80                ],
     81            ],
     82            [
     83                'iconElement'  => 'legend.yst-label',
     84                'valueElement' => [
     85                    'elementSelector' => 'input[name="wpseo_titles.person_logo"]',
     86                    'attributeName'   => 'value',
     87                    'attributeValue'  => '',
     88                    'operator'        => '!=',
     89                ],
    8190            ],
    8291        ];
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/one-time/class-settings-saved.php

    r3268602 r3283338  
    2121     */
    2222    protected const PROVIDER_ID = 'settings-saved';
     23
     24    /**
     25     * The task priority.
     26     *
     27     * @var string
     28     */
     29    protected $priority = 'high';
    2330
    2431    /**
     
    5158     */
    5259    public function get_description() {
    53         return sprintf(
    54             /* translators: %s:<a href="https://prpl.fyi/fill-settings-page" target="_blank">settings page</a> link */
    55             \esc_html__( 'Head over to the settings page and fill in the required information. %s', 'progress-planner' ),
    56             '<a href="https://prpl.fyi/fill-settings-page" target="_blank">' . \esc_html__( 'settings page', 'progress-planner' ) . '</a>'
    57         );
     60        return \esc_html__( 'Head over to the settings page and fill in the required information.', 'progress-planner' );
    5861    }
    5962
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/repetitive/class-core-update.php

    r3268602 r3283338  
    145145            'dismissable' => $this->is_dismissable(),
    146146            'url'         => $this->get_url(),
     147            'url_target'  => $this->get_url_target(),
    147148            'description' => $this->get_description(),
    148149        ];
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/repetitive/class-create.php

    r3268602 r3283338  
    3838
    3939    /**
     40     * The task URL target.
     41     *
     42     * @var string
     43     */
     44    protected $url_target = '_blank';
     45
     46    /**
    4047     * The data collector.
    4148     *
     
    4956    public function __construct() {
    5057        $this->data_collector = new Last_Published_Post_Data_Collector();
    51         $this->url            = \admin_url( 'post-new.php?post_type=post' );
     58        $this->url            = 'https://prpl.fyi/valuable-content';
    5259    }
    5360
     
    5865     */
    5966    public function get_title() {
    60         return esc_html__( 'Create a post', 'progress-planner' );
     67        return esc_html__( 'Create valuable content', 'progress-planner' );
    6168    }
    6269
     
    6774     */
    6875    public function get_description() {
    69         return esc_html__( 'Create a new, relevant post. If you write an in-depth post you may earn an extra point.', 'progress-planner' );
     76        return sprintf(
     77            /* translators: %s: "Read more" link. */
     78            \esc_html__( 'Time to add more valuable content to your site! Check our blog for inspiration. %s.', 'progress-planner' ),
     79            '<a href="https://prpl.fyi/valuable-content" target="_blank">' . \esc_html__( 'Read more', 'progress-planner' ) . '</a>'
     80        );
    7081    }
    7182
     
    7788     * @return array
    7889     */
    79     public function modify_task_data( $task_data ) {
     90    public function modify_evaluated_task_data( $task_data ) {
    8091        $last_published_post_data = $this->data_collector->collect();
    8192
     
    8697        // Add the post ID and post length to the task data.
    8798        $task_data['post_id'] = $last_published_post_data['post_id'];
    88         $task_data['long']    = $last_published_post_data['long'];
    8999
    90100        return $task_data;
     
    133143            'dismissable' => $this->is_dismissable(),
    134144            'url'         => $this->get_url(),
     145            'url_target'  => $this->get_url_target(),
    135146            'description' => $this->get_description(),
    136147        ];
     
    147158     * @return int
    148159     */
    149     public function get_points_for_task( $task_id = '' ) {
     160    public function get_points( $task_id = '' ) {
    150161
    151162        if ( ! $task_id ) {
    152             // Get the post that was created last.
    153             $post_data = $this->data_collector->collect();
    154         } else {
    155             $post_data = \progress_planner()->get_suggested_tasks()->get_tasks_by( 'task_id', $task_id );
    156             $post_data = $post_data[0] ?? false;
    157         }
    158 
    159         // Post was created, but then deleted?
    160         if ( ! $post_data || empty( $post_data['post_id'] ) ) {
    161163            return $this->points;
    162164        }
    163165
    164         return true === $post_data['long'] ? 2 : 1;
     166        $post_data = \progress_planner()->get_suggested_tasks()->get_tasks_by( 'task_id', $task_id );
     167        $post_data = $post_data[0] ?? false;
     168
     169        // Backwards compatibility.
     170        if ( $post_data && isset( $post_data['long'] ) ) {
     171            return true === $post_data['long'] ? 2 : 1;
     172        }
     173
     174        return $this->points;
    165175    }
    166176}
  • progress-planner/tags/1.3.0/classes/suggested-tasks/local-tasks/providers/repetitive/class-review.php

    r3268602 r3283338  
    7373
    7474    /**
     75     * The include post types.
     76     *
     77     * @var string[]
     78     */
     79    protected $include_post_types = [];
     80
     81    /**
    7582     * Initialize the task provider.
    7683     *
     
    7885     */
    7986    public function init() {
     87        $this->include_post_types = \progress_planner()->get_settings()->get_post_types_names(); // Wait for the post types to be initialized.
     88
    8089        \add_filter( 'progress_planner_update_posts_tasks_args', [ $this, 'filter_update_posts_args' ] );
    8190    }
     
    179188                $last_updated_posts = $this->get_old_posts(
    180189                    [
    181                         'post__in' => $important_page_ids,
     190                        'post__in'  => $important_page_ids,
     191                        'post_type' => 'any',
    182192                    ]
    183193                );
     
    212222
    213223                $this->task_post_mappings[ $task_id ] = [
    214                     'task_id' => $task_id,
    215                     'post_id' => $post->ID,
     224                    'task_id'   => $task_id,
     225                    'post_id'   => $post->ID,
     226                    'post_type' => $post->post_type,
    216227                ];
    217228            }
     
    247258                    'category'    => $this->get_provider_category(),
    248259                    'post_id'     => $task_data['post_id'],
     260                    'post_type'   => $task_data['post_type'],
    249261                    'date'        => \gmdate( 'YW' ),
    250262                ];
     
    278290            'dismissable' => $this->is_dismissable(),
    279291            'url'         => $this->get_url( $task_id ),
     292            'url_target'  => $this->get_url_target(),
    280293            'description' => $this->get_description( $task_id ),
    281294        ];
     
    322335     */
    323336    public function get_old_posts( $args = [] ) {
    324         $args = wp_parse_args(
    325             $args,
    326             [
    327                 'posts_per_page' => static::ITEMS_TO_INJECT,
    328                 'post_type'      => [ 'page', 'post' ],
    329                 'post_status'    => 'publish',
    330                 'orderby'        => 'modified',
    331                 'order'          => 'ASC',
    332                 'date_query'     => [
    333                     [
    334                         'column' => 'post_modified',
    335                         'before' => '-6 months',
     337        $posts = [];
     338
     339        if ( ! empty( $this->include_post_types ) ) {
     340            $args = wp_parse_args(
     341                $args,
     342                [
     343                    'posts_per_page' => static::ITEMS_TO_INJECT,
     344                    'post_type'      => $this->include_post_types,
     345                    'post_status'    => 'publish',
     346                    'orderby'        => 'modified',
     347                    'order'          => 'ASC',
     348                    'date_query'     => [
     349                        [
     350                            'column' => 'post_modified',
     351                            'before' => '-6 months',
     352                        ],
    336353                    ],
    337                 ],
    338             ]
    339         );
    340 
    341         /**
    342          * Filters the args for the posts & pages we want user to review.
    343          *
    344          * @param array $args The get_postsargs.
    345          */
    346         $args = apply_filters( 'progress_planner_update_posts_tasks_args', $args );
    347 
    348         // Get the post that was updated last.
    349         $posts = \get_posts( $args );
     354                ]
     355            );
     356
     357            /**
     358             * Filters the args for the posts & pages we want user to review.
     359             *
     360             * @param array $args The get_postsargs.
     361             */
     362            $args = apply_filters( 'progress_planner_update_posts_tasks_args', $args );
     363
     364            // Get the post that was updated last.
     365            $posts = \get_posts( $args );
     366        }
     367
     368        // Get the pages saved in the settings that have not been updated in the last 6 months.
     369        $saved_page_type_ids = $this->get_saved_page_types();
     370
     371        if ( ! empty( $saved_page_type_ids ) ) {
     372            $pages_to_update = \get_posts(
     373                [
     374                    'post_type'           => 'any',
     375                    'post_status'         => 'publish',
     376                    'orderby'             => 'modified',
     377                    'order'               => 'ASC',
     378                    'ignore_sticky_posts' => true,
     379                    'date_query'          => [
     380                        [
     381                            'column' => 'post_modified',
     382                            'before' => '-6 months',
     383                        ],
     384                    ],
     385                    'post__in'            => $saved_page_type_ids,
     386                ]
     387            );
     388
     389            // Merge the posts & pages to update. Put the pages first.
     390            $posts = array_merge( $pages_to_update, $posts );
     391        }
    350392
    351393        return $posts ? $posts : [];
     
    398440
    399441    /**
     442     * Get the saved page-types.
     443     *
     444     * @return int[]
     445     */
     446    protected function get_saved_page_types() {
     447        $ids = [];
     448        // Add the saved page-types to the post__not_in array.
     449        $page_types = \progress_planner()->get_admin__page_settings()->get_settings();
     450        foreach ( $page_types as $page_type ) {
     451            if ( isset( $page_type['value'] ) && 0 !== (int) $page_type['value'] ) {
     452                $ids[] = (int) $page_type['value'];
     453            }
     454        }
     455        return $ids;
     456    }
     457
     458    /**
    400459     * Check if a specific task is completed.
    401460     *
  • progress-planner/tags/1.3.0/playwright.config.js

    r3264985 r3283338  
    44    testDir: './tests/e2e',
    55    timeout: 30000,
    6     fullyParallel: false,
    76    forbidOnly: !! process.env.CI,
    87    retries: process.env.CI ? 2 : 0,
    9     workers: 1,
    108    reporter: 'html',
    11     globalSetup: require.resolve( './tests/e2e/auth.setup.js' ),
     9    globalSetup: './tests/e2e/auth.setup.js',
     10    globalTeardown: './tests/e2e/auth.setup.js',
    1211    use: {
    1312        baseURL: process.env.WORDPRESS_URL || 'http://localhost:8080',
     
    1817    projects: [
    1918        {
    20             name: 'chromium',
     19            name: 'sequential',
    2120            use: { ...devices[ 'Desktop Chrome' ] },
     21            testMatch: 'sequential.spec.js',
     22            fullyParallel: false,
     23            workers: 1,
     24        },
     25        {
     26            name: 'parallel',
     27            use: { ...devices[ 'Desktop Chrome' ] },
     28            testIgnore: [
     29                'onboarding.spec.js',
     30                'task-tagline.spec.js',
     31                'todo.spec.js',
     32                'todo-reorder.spec.js',
     33                'todo-complete.spec.js',
     34                'sequential.spec.js',
     35            ],
     36            fullyParallel: true,
     37            workers: 4,
    2238        },
    2339    ],
  • progress-planner/tags/1.3.0/progress-planner.php

    r3268602 r3283338  
    1010 * Requires at least: 6.3
    1111 * Requires PHP:      7.4
    12  * Version:           1.2.0
     12 * Version:           1.3.0
    1313 * Author:            Team Emilia Projects
    1414 * Author URI:        https://prpl.fyi/about
     
    6767            'Progress_Planner\Onboard'                    => [ 'Progress_Planner\Utils\Onboard', '1.1.1' ],
    6868            'Progress_Planner\Playground'                 => [ 'Progress_Planner\Utils\Playground', '1.1.1' ],
     69
     70            'Progress_Planner\Admin\Widgets\Published_Content' => [ 'Progress_Planner\Admin\Widgets\Content_Activity', '1.3.0' ],
    6971        ];
    7072
  • progress-planner/tags/1.3.0/readme.txt

    r3268602 r3283338  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 1.2.0
     7Stable tag: 1.3.0
    88License: GPL3+
    99License URI: https://www.gnu.org/licenses/gpl-3.0.en.html
     
    110110
    111111== Changelog ==
     112
     113= 1.3.0 =
     114
     115Enhancements:
     116
     117* Improved checks when adding Ravi icon to the Yoast SEO settings page.
     118* Add "golden" tasks to weekly emails.
     119* Add text to clarify when the user has completed all tasks.
     120* Improve the content widget & stats to show more accurate data. It now shows content _activity_ instead of content _published_.
     121* Implemented "valuable post-types" and added settings for them.
     122* Changed the "create a post" task to "create valuable content".
     123* Renamed & migrated content badges.
     124* Added a link to the 'Create valuable content' task description.
     125* Improve accessibility of Recommendations (and other links) linking to external resources
     126
     127Bugs we fixed:
     128
     129* Fixed error during plugin uninstall.
     130* Archive_Format data collector hooks weren't registered early enough.
     131* Ensure fresh plugin list by clearing plugin cache before checking for inactive plugins after deletion.
     132* Clear plugin cache when checking for inactive plugins.
     133* Delete no-longer relevant pending tasks.
     134* Fixed timing issue for tasks added by 3rd-party plugins.
    112135
    113136= 1.2.0 =
  • progress-planner/tags/1.3.0/uninstall.php

    r3268602 r3283338  
    1414
    1515require_once __DIR__ . '/classes/class-settings.php';
    16 require_once __DIR__ . '/classes/class-query.php';
     16require_once __DIR__ . '/classes/activities/class-query.php';
    1717
    1818/**
  • progress-planner/tags/1.3.0/views/admin-page-header.php

    r3268602 r3283338  
    3535        </button>
    3636        <?php
    37         // Render the settings button.
    38         \progress_planner()->get_ui__popover()->the_popover( 'settings' )->render_button(
    39             '',
    40             \progress_planner()->get_asset( 'images/icon_settings.svg' ) . '<span class="screen-reader-text">' . \esc_html__( 'Settings', 'progress-planner' ) . '</span>'
    41         );
    42         // Render the settings popover.
    43         \progress_planner()->get_ui__popover()->the_popover( 'settings' )->render();
    4437
    4538        // Render the subscribe form button and popover if the license key is not set.
  • progress-planner/tags/1.3.0/views/admin-page-settings.php

    r3264985 r3283338  
    3535    <form id="prpl-settings">
    3636        <?php \progress_planner()->the_view( 'page-settings/pages.php' ); ?>
    37         <?php \progress_planner()->the_view( 'page-settings/settings.php' ); ?>
    38         <?php \progress_planner()->the_view( 'page-settings/license.php' ); ?>
     37
     38        <div id="prpl-grid-column-wrapper">
     39            <?php \progress_planner()->the_view( 'page-settings/post-types.php' ); ?>
     40            <?php \progress_planner()->the_view( 'page-settings/settings.php' ); ?>
     41            <?php \progress_planner()->the_view( 'page-settings/license.php' ); ?>
     42        </div>
    3943
    4044        <?php wp_nonce_field( 'progress_planner' ); ?>
  • progress-planner/tags/1.3.0/views/page-settings/license.php

    r3238385 r3283338  
    1616?>
    1717
    18 <div class="prpl-column">
     18<div class="prpl-column prpl-column-license">
    1919    <div class="prpl-widget-wrapper">
    2020        <h2 class="prpl-settings-section-title prpl-settings-section-license">
  • progress-planner/tags/1.3.0/views/page-settings/pages.php

    r3238385 r3283338  
    1212?>
    1313
    14 <div class="prpl-column">
     14<div class="prpl-column prpl-column-pages">
    1515    <div class="prpl-widget-wrapper">
    1616        <h2 class="prpl-settings-section-title">
  • progress-planner/tags/1.3.0/views/page-settings/settings.php

    r3264985 r3283338  
    1414?>
    1515
    16 <div class="prpl-column">
     16<div class="prpl-column prpl-column-login-destination">
    1717    <div class="prpl-widget-wrapper">
    1818        <h2 class="prpl-settings-section-title">
    1919            <span class="icon">
    20                 <?php \progress_planner()->the_asset( 'images/icon_forward.svg' ); ?>
     20                <?php \progress_planner()->the_asset( 'images/icon_user.svg' ); ?>
    2121            </span>
    2222            <span>
  • progress-planner/tags/1.3.0/views/page-widgets/suggested-tasks.php

    r3268602 r3283338  
    2727    <ul style="display:none"></ul>
    2828    <ul class="prpl-suggested-tasks-list"></ul>
     29    <p class="prpl-no-suggested-tasks">
     30        <?php \esc_html_e( 'You have completed all recommended tasks.', 'progress-planner' ); ?>
     31        <br>
     32        <?php \esc_html_e( 'Check back later for new tasks!', 'progress-planner' ); ?>
     33    </p>
    2934    <hr>
    3035</div>
  • progress-planner/tags/1.3.0/views/page-widgets/whats-new.php

    r3268602 r3283338  
    2525        ?>
    2626        <li>
    27             <a href="<?php echo \esc_url( $prpl_blog_post['link'] ); ?>" target="_blank">
    28                 <h3><?php echo \esc_html( $prpl_blog_post['title']['rendered'] ); ?></h3>
    29                 <?php if ( $prpl_blog_post_image_url ) : ?>
     27            <h3>
     28                <a href="<?php echo \esc_url( $prpl_blog_post['link'] ); ?>" target="_blank">
     29                    <?php echo \esc_html( $prpl_blog_post['title']['rendered'] ); ?>
     30                </a>
     31            </h3>
     32            <?php if ( $prpl_blog_post_image_url ) : ?>
     33                <a href="<?php echo \esc_url( $prpl_blog_post['link'] ); ?>" target="_blank">
    3034                    <div class="prpl-blog-post-image" style="background-image:url(<?php echo \esc_url( $prpl_blog_post_image_url ); ?>)"></div>
    31                 <?php endif; ?>
    32             </a>
     35                </a>
     36            <?php endif; ?>
    3337            <p><?php echo \esc_html( \wp_trim_words( \wp_strip_all_tags( $prpl_blog_post['content']['rendered'] ), 55 ) ); ?></p>
    3438            <hr />
  • progress-planner/tags/1.3.0/views/setting/page-select.php

    r3268602 r3283338  
    9595                            </div>
    9696                        <?php endif; ?>
    97 
    98 
    9997                    </div>
    10098                <?php endforeach; ?>
  • progress-planner/trunk/CHANGELOG.md

    r3268602 r3283338  
     1= 1.3.0 =
     2
     3Enhancements:
     4
     5* Improved checks when adding Ravi icon to the Yoast SEO settings page.
     6* Add "golden" tasks to weekly emails.
     7* Add text to clarify when the user has completed all tasks.
     8* Improve the content widget & stats to show more accurate data. It now shows content _activity_ instead of content _published_.
     9* Implemented "valuable post-types" and added settings for them.
     10* Changed the "create a post" task to "create valuable content".
     11* Renamed & migrated content badges.
     12* Added a link to the 'Create valuable content' task description.
     13* Improve accessibility of Recommendations (and other links) linking to external resources
     14
     15Bugs we fixed:
     16
     17* Fixed error during plugin uninstall.
     18* Archive_Format data collector hooks weren't registered early enough.
     19* Ensure fresh plugin list by clearing plugin cache before checking for inactive plugins after deletion.
     20* Clear plugin cache when checking for inactive plugins.
     21* Delete no-longer relevant pending tasks.
     22* Fixed timing issue for tasks added by 3rd-party plugins.
     23
    124= 1.2.0 =
    225
  • progress-planner/trunk/assets/css/admin.css

    r3264985 r3283338  
    421421    Settings popover.
    422422\*------------------------------------*/
    423 #prpl-settings-license-form,
    424 #prpl-settings-form {
    425 
    426     label {
    427         display: block;
    428     }
    429 
    430     p {
    431         max-width: 42em;
    432     }
    433 
    434     h3 {
    435         font-size: 1.15em;
    436     }
    437 
    438     button.button-primary {
    439         margin-top: 1em;
    440     }
    441 }
    442 
    443 .driver-popover.prpl-driverjs-theme {
    444     background-color: var(--prpl-background-orange);
    445     color: var(--prpl-color-text);
    446 
    447     .driver-popover-title {
    448         color: var(--prpl-color-headings);
    449     }
    450 
    451     button {
    452         color: var(--prpl-color-headings);
    453     }
    454 
    455     button:not(.driver-popover-close-btn):hover {
    456         background-color: var(--prpl-background-orange);
    457     }
    458 }
    459 
    460423#prpl-settings-license-form {
    461424
     
    466429        gap: var(--prpl-padding);
    467430    }
    468 }
    469 
     431
     432    p {
     433        max-width: 42em;
     434    }
     435
     436    h3 {
     437        font-size: 1.15em;
     438    }
     439
     440    button.button-primary {
     441        margin-top: 1em;
     442    }
     443}
     444
     445.driver-popover.prpl-driverjs-theme {
     446    background-color: var(--prpl-background-orange);
     447    color: var(--prpl-color-text);
     448
     449    .driver-popover-title {
     450        color: var(--prpl-color-headings);
     451    }
     452
     453    button {
     454        color: var(--prpl-color-headings);
     455    }
     456
     457    button:not(.driver-popover-close-btn):hover {
     458        background-color: var(--prpl-background-orange);
     459    }
     460}
     461
     462/*------------------------------------*\
     463    External link accessibility helper.
     464\*------------------------------------*/
     465.prpl-external-link-icon {
     466    display: inline-flex;
     467    margin-inline-start: 0.25em;
     468    vertical-align: middle;
     469
     470    svg {
     471        width: 1em;
     472        height: 1em;
     473    }
     474}
  • progress-planner/trunk/assets/css/page-widgets/suggested-tasks.css

    r3264985 r3283338  
    103103        }
    104104
     105        .prpl-no-suggested-tasks {
     106            display: none;
     107        }
     108
    105109        hr {
    106110            display: block;
     
    111115    hr {
    112116        display: none;
     117    }
     118
     119    .prpl-no-suggested-tasks {
     120        display: block;
     121        background-color: var(--prpl-background-green);
     122        padding: calc(var(--prpl-padding) / 2);
    113123    }
    114124}
  • progress-planner/trunk/assets/css/page-widgets/whats-new.css

    r3217671 r3283338  
    1111    li {
    1212
    13         > a {
    14             color: var(--prpl-color-gray-6);
    15             text-decoration: none;
     13        h3 {
     14            margin-top: 0;
     15            font-size: 1.15em;
     16            font-weight: 600;
    1617
    17             h3 {
    18                 margin-top: 0;
    19                 font-size: 1.15em;
    20                 font-weight: 600;
     18            > a {
     19                color: var(--prpl-color-headings);
     20                text-decoration: none;
    2121
    22                 &::after {
    23                     content: url('data:image/svg+xml,<%3Fxml version="1.0" encoding="UTF-8"%3F><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="%23currentcolor" d="M6 1h5v5L8.86 3.85 4.7 8 4 7.3l4.15-4.16zM2 3h2v1H2v6h6V8h1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1"/></svg>');
    24                     margin-left: 0.25em;
    25                     width: 0.75em;
    26                     height: 0.75em;
    27                     display: inline-block;
     22                .prpl-external-link-icon {
     23                    margin-inline-start: 0.15em;
    2824                }
    2925            }
    3026        }
     27
    3128
    3229        img {
     
    3835        display: flex;
    3936        justify-content: flex-end;
    40 
    41         a::after {
    42             content: url('data:image/svg+xml,<%3Fxml version="1.0" encoding="UTF-8"%3F><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="%23currentcolor" d="M6 1h5v5L8.86 3.85 4.7 8 4 7.3l4.15-4.16zM2 3h2v1H2v6h6V8h1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1"/></svg>');
    43             margin-left: 0.25em;
    44             width: 1em;
    45             height: 1em;
    46             display: inline-block;
    47         }
    4837    }
    4938}
  • progress-planner/trunk/assets/css/settings-page.css

    r3264985 r3283338  
    220220}
    221221
    222 .prpl-license-keys-wrapper {
    223     display: flex;
    224     flex-direction: column;
    225     gap: 1rem;
    226 
    227     .prpl-license-key-wrapper {
    228         display: flex;
    229         align-items: center;
    230         gap: 0.5rem;
    231 
    232         .prpl-license-status {
    233             width: 1rem;
    234             height: 1rem;
    235 
    236             svg {
     222/* Post types */
     223.prpl-column-post-types {
     224
     225    .prpl-settings-section-title {
     226
     227        svg {
     228            color: #038d88;
     229
     230            path {
     231                fill: currentcolor;
     232            }
     233        }
     234
     235        background-color: #f3faf9;
     236    }
     237
     238}
     239
     240/* Login destination */
     241.prpl-column-login-destination {
     242
     243    .prpl-settings-section-title {
     244
     245        svg {
     246            color: var(--prpl-color-accent-red);
     247        }
     248
     249        background-color: var(--prpl-background-red);
     250    }
     251
     252}
     253
     254
     255/* License */
     256.prpl-column-license {
     257
     258    .prpl-settings-section-title {
     259
     260        svg {
     261            color: #0773b4;
     262        }
     263        background-color: #effbfe;
     264    }
     265
     266    .prpl-license-keys-wrapper {
     267        display: flex;
     268        flex-direction: column;
     269        gap: 1rem;
     270        max-width: 40rem;
     271
     272        & > p:first-child {
     273            margin-top: 0;
     274        }
     275
     276        .prpl-license-key-wrapper {
     277            display: flex;
     278            align-items: center;
     279            gap: 0.5rem;
     280
     281            .prpl-license-status {
    237282                width: 1rem;
    238283                height: 1rem;
    239             }
    240         }
    241     }
    242 
    243     input {
    244         width: 20rem;
    245         max-width: calc(100% - 2rem);
    246     }
    247 }
     284
     285                svg {
     286                    width: 1rem;
     287                    height: 1rem;
     288                }
     289            }
     290        }
     291
     292        input {
     293            width: 30rem;
     294            max-width: calc(100% - 2rem);
     295            border: 1px solid var(--prpl-color-gray-2);
     296            box-shadow: 1px 2px 4px 0 rgba(0, 0, 0, 0.05);
     297        }
     298    }
     299
     300}
     301
     302
     303/* Grid layout for wrapper for:
     304- Valuable post types
     305- Default login destination
     306- License keys
     307*/
     308#prpl-grid-column-wrapper {
     309    display: grid;
     310    margin-bottom: var(--prpl-gap);
     311
     312    /* There are 5 or less valuable post types */
     313    grid-template-columns: 1fr 1fr;
     314    grid-template-rows: auto auto;
     315    gap: var(--prpl-settings-page-gap);
     316
     317    .prpl-column {
     318        align-self: stretch;
     319        display: flex;
     320        flex-direction: column;
     321
     322        .prpl-widget-wrapper {
     323            flex: 1;
     324            margin-bottom: 0;
     325        }
     326    }
     327
     328    /* Valuable post types */
     329    .prpl-column:nth-child(1) {
     330        grid-column: 1;
     331        grid-row: 1;
     332    }
     333
     334    /* Default login destination */
     335    .prpl-column:nth-child(2) {
     336        grid-column: 2;
     337        grid-row: 1;
     338    }
     339
     340    /* License keys */
     341    .prpl-column:nth-child(3) {
     342        grid-column: 1 / span 2;
     343        grid-row: 2;
     344    }
     345
     346    /* We have more than 5 valuable post types */
     347    &:has([data-has-many-valuable-post-types]) {
     348        grid-template-rows: auto auto;
     349
     350        /* Valuable post types */
     351        .prpl-column:nth-child(1) {
     352            grid-column: 1;
     353            grid-row: 1 / span 2;
     354
     355            /* Span 2 rows on the left */
     356        }
     357
     358        /* Default login destination */
     359        .prpl-column:nth-child(2) {
     360            grid-column: 2;
     361            grid-row: 1;
     362        }
     363
     364        /* License keys */
     365        .prpl-column:nth-child(3) {
     366            grid-column: 2;
     367            grid-row: 2;
     368        }
     369    }
     370}
     371
     372/* Valuable post types */
     373#prpl-post-types-include-wrapper {
     374    padding-top: 0.75rem;
     375
     376    label {
     377        display: block;
     378        margin-top: 0.75rem;
     379
     380        &:first-child {
     381            margin-top: 0;
     382        }
     383    }
     384}
  • progress-planner/trunk/assets/js/settings-page.js

    r3264985 r3283338  
    6767        };
    6868        formData.forEach( function ( value, key ) {
    69             data[ key ] = value;
     69            // Handle array notation in keys
     70            if ( key.endsWith( '[]' ) ) {
     71                const baseKey = key.slice( 0, -2 );
     72                if ( ! data[ baseKey ] ) {
     73                    data[ baseKey ] = [];
     74                }
     75                data[ baseKey ].push( value );
     76            } else {
     77                data[ key ] = value;
     78            }
    7079        } );
    7180        const request = wp.ajax.post( 'prpl_settings_form', data );
  • progress-planner/trunk/assets/js/settings.js

    r3264985 r3283338  
    77 * Dependencies: progress-planner/ajax-request, progress-planner/onboard, wp-util, progress-planner/l10n
    88 */
    9 document
    10     .getElementById( 'prpl-settings-form' )
    11     .addEventListener( 'submit', function ( event ) {
    12         event.preventDefault();
    13         const form = new FormData( this );
    14         const data = form.getAll( 'prpl-settings-post-types-include[]' );
    15 
    16         // Save the options.
    17         const request = wp.ajax.post( 'progress_planner_save_cpt_settings', {
    18             _ajax_nonce: progressPlanner.nonce,
    19             include_post_types: data.join( ',' ),
    20         } );
    21         request.done( () => {
    22             window.location.reload();
    23         } );
    24 
    25         document.getElementById( 'submit-include-post-types' ).disabled = true;
    26         document.getElementById( 'submit-include-post-types' ).innerHTML =
    27             prplL10n( 'saving' );
    28     } );
    299
    3010// Submit the email.
  • progress-planner/trunk/assets/js/web-components/prpl-big-counter.js

    r3198155 r3283338  
    1717                backgroundColor || 'var(--prpl-background-purple)';
    1818
     19            const el = this;
     20
    1921            this.innerHTML = `
    2022                <div style="
     
    3133                    margin-bottom: var(--prpl-padding);
    3234                ">
     35                    <div class="container-width" style="width: 100%;"></div>
    3336                    <span style="
    3437                        font-size: var(--prpl-font-size-5xl);
     
    3639                        font-weight: 600;
    3740                    ">${ number }</span>
    38                     <span style="font-size: var(--prpl-font-size-2xl);">${ content }</span>
     41                    <span style="font-size: var(--prpl-font-size-2xl);">
     42                        <span class="resize" style="font-size: 100%; display: inline-block; width: max-content;">${ content }</span>
     43                    </span>
    3944                </div>
    4045            `;
     46
     47            const resizeFont = () => {
     48                const element = el.querySelector( '.resize' );
     49                if ( ! element ) {
     50                    return;
     51                }
     52
     53                element.style.fontSize = '100%';
     54
     55                let size = 100;
     56                while (
     57                    element.clientWidth >
     58                    el.querySelector( '.container-width' ).clientWidth
     59                ) {
     60                    if ( size < 80 ) {
     61                        element.style.fontSize = size + '%';
     62                        element.style.width = '100%';
     63                        break;
     64                    }
     65                    size -= 1;
     66                    element.style.fontSize = size + '%';
     67                }
     68            };
     69
     70            resizeFont();
     71            window.addEventListener( 'resize', resizeFont );
    4172        }
    4273    }
  • progress-planner/trunk/assets/js/web-components/prpl-suggested-task.js

    r3268602 r3283338  
    2222            action = '',
    2323            url = '',
     24            url_target = '_self',
    2425            dismissable = false,
    2526            provider_id = '',
     
    3839            let taskHeading = title;
    3940            if ( url ) {
    40                 taskHeading = `<a href="${ url }">${ title }</a>`;
     41                taskHeading = `<a href="${ url }" target="${ url_target }">${ title }</a>`;
    4142            }
    4243
  • progress-planner/trunk/assets/js/yoast-focus-element.js

    r3268602 r3283338  
    66
    77/**
    8  * Check if the value of the element matches the value specified in the task.
    9  *
    10  * @param {Element} element The element to check.
    11  * @param {Object}  task    The task to check.
    12  * @return {boolean} True if the value matches, false otherwise.
     8 * Yoast Focus Element class.
    139 */
    14 function checkTaskValue( element, task ) {
    15     if ( ! task.valueElement ) {
    16         return true;
    17     }
    18 
    19     const attributeName = task.valueElement.attributeName || 'value';
    20     const attributeValue = task.valueElement.attributeValue;
    21     const operator = task.valueElement.operator || '=';
    22     const currentValue = element.getAttribute( attributeName ) || '';
    23 
    24     return '!=' === operator
    25         ? currentValue !== attributeValue
    26         : currentValue === attributeValue;
    27 }
    28 
    29 /**
    30  * Observe the Yoast sidebar clicks.
    31  */
    32 function observeYoastSidebarClicks() {
    33     const container = document.querySelector( '#yoast-seo-settings' );
    34 
    35     if ( ! container ) {
    36         return;
    37     }
    38 
    39     const waitForNav = new MutationObserver( ( mutationsList, observer ) => {
    40         const nav = container.querySelector(
    41             'nav.yst-sidebar-navigation__sidebar'
     10class ProgressPlannerYoastFocus {
     11    /**
     12     * Constructor.
     13     */
     14    constructor() {
     15        this.container = document.querySelector( '#yoast-seo-settings' );
     16        this.tasks = progressPlannerYoastFocusElement.tasks;
     17        this.baseUrl = progressPlannerYoastFocusElement.base_url;
     18
     19        if ( this.container ) {
     20            this.init();
     21        }
     22    }
     23
     24    /**
     25     * Initialize the Yoast Focus Element.
     26     */
     27    init() {
     28        this.waitForMainAndObserveContent();
     29        this.observeYoastSidebarClicks();
     30    }
     31
     32    /**
     33     * Check if the value of the element matches the value specified in the task.
     34     *
     35     * @param {Element} element The element to check.
     36     * @param {Object}  task    The task to check.
     37     * @return {boolean} True if the value matches, false otherwise.
     38     */
     39    checkTaskValue( element, task ) {
     40        if ( ! task.valueElement ) {
     41            return true;
     42        }
     43
     44        const attributeName = task.valueElement.attributeName || 'value';
     45        const attributeValue = task.valueElement.attributeValue;
     46        const operator = task.valueElement.operator || '=';
     47        const currentValue = element.getAttribute( attributeName ) || '';
     48
     49        return '!=' === operator
     50            ? currentValue !== attributeValue
     51            : currentValue === attributeValue;
     52    }
     53
     54    /**
     55     * Observe the Yoast sidebar clicks.
     56     */
     57    observeYoastSidebarClicks() {
     58        const waitForNav = new MutationObserver(
     59            ( mutationsList, observer ) => {
     60                const nav = this.container.querySelector(
     61                    'nav.yst-sidebar-navigation__sidebar'
     62                );
     63                if ( nav ) {
     64                    observer.disconnect();
     65
     66                    nav.addEventListener( 'click', ( e ) => {
     67                        const link = e.target.closest( 'a' );
     68                        if ( link ) {
     69                            this.waitForMainAndObserveContent();
     70                        }
     71                    } );
     72                }
     73            }
    4274        );
    43         if ( nav ) {
    44             // Sidebar nav loaded.
    45             observer.disconnect();
    46 
    47             nav.addEventListener( 'click', ( e ) => {
    48                 const link = e.target.closest( 'a' );
    49                 if ( link ) {
    50                     // Sidebar link clicked.
    51                     waitForMainAndObserveContent(); // re-run logic after clicking
    52                 }
    53             } );
    54         }
    55     } );
    56 
    57     waitForNav.observe( container, {
    58         childList: true,
    59         subtree: true,
    60     } );
    61 }
    62 
    63 /**
    64  * Wait for the main content to load and observe the content.
    65  */
    66 function waitForMainAndObserveContent() {
    67     const container = document.querySelector( '#yoast-seo-settings' );
    68     if ( ! container ) {
    69         return;
    70     }
    71 
    72     const waitForMain = new MutationObserver( ( mutationsList, observer ) => {
    73         const main = container.querySelector( 'main.yst-paper' );
    74         if ( main ) {
    75             // Main loaded.
    76             observer.disconnect();
    77 
    78             const childObserver = new MutationObserver( ( mutations ) => {
    79                 for ( const mutation of mutations ) {
    80                     if (
    81                         mutation.type === 'attributes' &&
    82                         mutation.attributeName === 'class'
    83                     ) {
    84                         const el = mutation.target;
    85                         if (
    86                             el.parentElement === main &&
    87                             el.classList.contains( 'yst-opacity-100' )
    88                         ) {
    89                             // Fully loaded content.
    90                             childObserver.disconnect();
    91 
    92                             // Loop through the tasks and add the focus element.
    93                             for ( const task of progressPlannerYoastFocusElement.tasks ) {
    94                                 // Try to find the toggleButton.
    95                                 const valueElement = el.querySelector(
    96                                     task.valueElement.elementSelector
    97                                 );
    98                                 let raviIconPositionAbsolute = true;
    99 
    100                                 if ( valueElement ) {
    101                                     // We usually add icon to the option header.
    102                                     let addIconElement = valueElement.closest(
    103                                         task.iconElement
    104                                     );
    105 
    106                                     // Exception is the upload input field.
     75
     76        waitForNav.observe( this.container, {
     77            childList: true,
     78            subtree: true,
     79        } );
     80    }
     81
     82    /**
     83     * Wait for the main content to load and observe the content.
     84     */
     85    waitForMainAndObserveContent() {
     86        const waitForMain = new MutationObserver(
     87            ( mutationsList, observer ) => {
     88                const main = this.container.querySelector( 'main.yst-paper' );
     89                if ( main ) {
     90                    observer.disconnect();
     91
     92                    const childObserver = new MutationObserver(
     93                        ( mutations ) => {
     94                            for ( const mutation of mutations ) {
     95                                if (
     96                                    mutation.type === 'attributes' &&
     97                                    mutation.attributeName === 'class'
     98                                ) {
     99                                    const el = mutation.target;
    107100                                    if (
    108                                         ! addIconElement &&
    109                                         valueElement.type === 'hidden'
    110                                     ) {
    111                                         addIconElement = valueElement
    112                                             .closest( 'fieldset' )
    113                                             .querySelector( task.iconElement );
    114 
    115                                         raviIconPositionAbsolute = false;
    116                                     }
    117 
    118                                     // Upload input field.
    119                                     if ( ! addIconElement ) {
    120                                         continue;
    121                                     }
    122 
    123                                     // Append next to the valueElemen, only if it's not already there.
    124                                     if (
    125                                         ! addIconElement.querySelector(
    126                                             '.prpl-form-row-ravi'
     101                                        el.parentElement === main &&
     102                                        el.classList.contains(
     103                                            'yst-opacity-100'
    127104                                        )
    128105                                    ) {
    129                                         // Check for value if specified in task.
    130                                         const valueMatches = checkTaskValue(
    131                                             valueElement,
    132                                             task
    133                                         );
    134 
    135                                         // Create a new span with the class prpl-form-row-ravi.
    136                                         const raviIconWrapper =
    137                                             document.createElement( 'span' );
    138                                         raviIconWrapper.classList.add(
    139                                             'prpl-form-row-ravi',
    140                                             'prpl-element-awards-points-icon-wrapper'
    141                                         );
    142 
    143                                         if ( valueMatches ) {
    144                                             raviIconWrapper.classList.add(
    145                                                 'complete'
    146                                             );
    147                                         }
    148 
    149                                         // Styling for absolute positioning.
    150                                         if ( raviIconPositionAbsolute ) {
    151                                             addIconElement.style.position =
    152                                                 'relative';
    153 
    154                                             raviIconWrapper.style.position =
    155                                                 'absolute';
    156                                             raviIconWrapper.style.right =
    157                                                 '3.5rem';
    158                                             raviIconWrapper.style.top = '-7px';
    159                                         }
    160 
    161                                         raviIconWrapper.appendChild(
    162                                             document.createElement( 'span' )
    163                                         );
    164 
    165                                         // Create an icon image.
    166                                         const iconImg =
    167                                             document.createElement( 'img' );
    168                                         iconImg.src =
    169                                             progressPlannerYoastFocusElement.base_url +
    170                                             '/assets/images/icon_progress_planner.svg';
    171                                         iconImg.alt = 'Ravi';
    172                                         iconImg.width = 16;
    173                                         iconImg.height = 16;
    174 
    175                                         // Append the icon image to the raviIconWrapper.
    176                                         raviIconWrapper
    177                                             .querySelector( 'span' )
    178                                             .appendChild( iconImg );
    179 
    180                                         // Add the points to the raviIconWrapper.
    181                                         const pointsWrapper =
    182                                             document.createElement( 'span' );
    183                                         pointsWrapper.classList.add(
    184                                             'prpl-form-row-points'
    185                                         );
    186                                         pointsWrapper.textContent = valueMatches
    187                                             ? '✓'
    188                                             : '+1';
    189                                         raviIconWrapper.appendChild(
    190                                             pointsWrapper
    191                                         );
    192 
    193                                         // Finally add the raviIconWrapper to the DOM.
    194                                         addIconElement.appendChild(
    195                                             raviIconWrapper
    196                                         );
    197 
    198                                         // Watch for changes in aria-checked to update the icon dynamically
    199                                         const valueElementObserver =
    200                                             new MutationObserver( () => {
    201                                                 // Check value again if specified
    202                                                 const currentValueMatches =
    203                                                     checkTaskValue(
    204                                                         valueElement,
    205                                                         task
    206                                                     );
    207 
    208                                                 if ( currentValueMatches ) {
    209                                                     raviIconWrapper.classList.add(
    210                                                         'complete'
    211                                                     );
    212 
    213                                                     pointsWrapper.textContent =
    214                                                         '✓';
    215                                                 } else {
    216                                                     raviIconWrapper.classList.remove(
    217                                                         'complete'
    218                                                     );
    219 
    220                                                     pointsWrapper.textContent =
    221                                                         '+1';
    222                                                 }
    223                                             } );
    224 
    225                                         valueElementObserver.observe(
    226                                             valueElement,
    227                                             {
    228                                                 attributes: true,
    229                                                 attributeFilter: [
    230                                                     task.valueElement
    231                                                         .attributeName,
    232                                                 ],
    233                                             }
    234                                         );
     106                                        this.processTasks( el );
    235107                                    }
    236108                                }
    237109                            }
    238110                        }
    239                     }
     111                    );
     112
     113                    main.querySelectorAll( ':scope > *' ).forEach(
     114                        ( child ) => {
     115                            childObserver.observe( child, {
     116                                attributes: true,
     117                                attributeFilter: [ 'class' ],
     118                            } );
     119                        }
     120                    );
    240121                }
    241             } );
    242 
    243             // Watch direct children of main.yst-paper
    244             main.querySelectorAll( ':scope > *' ).forEach( ( child ) => {
    245                 childObserver.observe( child, {
    246                     attributes: true,
    247                     attributeFilter: [ 'class' ],
    248                 } );
    249             } );
    250         }
    251     } );
    252 
    253     waitForMain.observe( container, {
    254         childList: true,
    255         subtree: true,
    256     } );
     122            }
     123        );
     124
     125        waitForMain.observe( this.container, {
     126            childList: true,
     127            subtree: true,
     128        } );
     129    }
     130
     131    /**
     132     * Process all tasks for a given element.
     133     *
     134     * @param {Element} el The element to process tasks for.
     135     */
     136    processTasks( el ) {
     137        for ( const task of this.tasks ) {
     138            const valueElement = el.querySelector(
     139                task.valueElement.elementSelector
     140            );
     141            const raviIconPositionAbsolute = true;
     142
     143            if ( valueElement ) {
     144                this.processTask(
     145                    valueElement,
     146                    task,
     147                    raviIconPositionAbsolute
     148                );
     149            }
     150        }
     151    }
     152
     153    /**
     154     * Process a single task.
     155     *
     156     * @param {Element} valueElement             The value element to process.
     157     * @param {Object}  task                     The task to process.
     158     * @param {boolean} raviIconPositionAbsolute Whether the icon should be absolutely positioned.
     159     */
     160    processTask( valueElement, task, raviIconPositionAbsolute ) {
     161        let addIconElement = valueElement.closest( task.iconElement );
     162
     163        // Exception is the upload input field.
     164        if ( ! addIconElement && valueElement.type === 'hidden' ) {
     165            addIconElement = valueElement
     166                .closest( 'fieldset' )
     167                .querySelector( task.iconElement );
     168            raviIconPositionAbsolute = false;
     169        }
     170
     171        if ( ! addIconElement ) {
     172            return;
     173        }
     174
     175        if (
     176            ! addIconElement.querySelector( '[data-prpl-element="ravi-icon"]' )
     177        ) {
     178            this.addIcon(
     179                valueElement,
     180                addIconElement,
     181                task,
     182                raviIconPositionAbsolute
     183            );
     184        }
     185    }
     186
     187    /**
     188     * Add icon to the element.
     189     *
     190     * @param {Element} valueElement             The value element.
     191     * @param {Element} addIconElement           The element to add the icon to.
     192     * @param {Object}  task                     The task.
     193     * @param {boolean} raviIconPositionAbsolute Whether the icon should be absolutely positioned.
     194     */
     195    addIcon( valueElement, addIconElement, task, raviIconPositionAbsolute ) {
     196        const valueMatches = this.checkTaskValue( valueElement, task );
     197
     198        // Create a new span with the class prpl-form-row-ravi.
     199        const raviIconWrapper = document.createElement( 'span' );
     200        raviIconWrapper.classList.add(
     201            'prpl-element-awards-points-icon-wrapper'
     202        );
     203        raviIconWrapper.setAttribute( 'data-prpl-element', 'ravi-icon' );
     204
     205        if ( valueMatches ) {
     206            raviIconWrapper.classList.add( 'complete' );
     207        }
     208
     209        // Styling for absolute positioning.
     210        if ( raviIconPositionAbsolute ) {
     211            addIconElement.style.position = 'relative';
     212
     213            raviIconWrapper.style.position = 'absolute';
     214            raviIconWrapper.style.right = '3.5rem';
     215            raviIconWrapper.style.top = '-7px';
     216        }
     217
     218        raviIconWrapper.appendChild( document.createElement( 'span' ) );
     219
     220        // Create an icon image.
     221        const iconImg = document.createElement( 'img' );
     222        iconImg.src = this.baseUrl + '/assets/images/icon_progress_planner.svg';
     223        iconImg.alt = 'Ravi';
     224        iconImg.width = 16;
     225        iconImg.height = 16;
     226
     227        // Append the icon image to the raviIconWrapper.
     228        raviIconWrapper.querySelector( 'span' ).appendChild( iconImg );
     229
     230        // Add the points to the raviIconWrapper.
     231        const pointsWrapper = document.createElement( 'span' );
     232        pointsWrapper.classList.add( 'prpl-form-row-points' );
     233        pointsWrapper.textContent = valueMatches ? '✓' : '+1';
     234        raviIconWrapper.appendChild( pointsWrapper );
     235
     236        // Watch for changes in aria-checked to update the icon dynamically
     237        const valueElementObserver = new MutationObserver( () => {
     238            const currentValueMatches = this.checkTaskValue(
     239                valueElement,
     240                task
     241            );
     242
     243            if ( currentValueMatches ) {
     244                raviIconWrapper.classList.add( 'complete' );
     245                pointsWrapper.textContent = '✓';
     246            } else {
     247                raviIconWrapper.classList.remove( 'complete' );
     248                pointsWrapper.textContent = '+1';
     249            }
     250        } );
     251
     252        valueElementObserver.observe( valueElement, {
     253            attributes: true,
     254            attributeFilter: [ task.valueElement.attributeName ],
     255        } );
     256
     257        // Finally add the raviIconWrapper to the DOM.
     258        addIconElement.appendChild( raviIconWrapper );
     259    }
    257260}
    258261
    259 // Run once on initial page load.
    260 waitForMainAndObserveContent();
    261 observeYoastSidebarClicks();
     262// Initialize the Yoast Focus Element.
     263new ProgressPlannerYoastFocus();
  • progress-planner/trunk/classes/actions/class-content-scan.php

    r3268602 r3283338  
    172172        // Loop through the posts and update the stats.
    173173        foreach ( $posts as $post ) {
    174             // Set the activity.
    175             $activities[ $post->ID ] = \progress_planner()->get_activities__content_helpers()->get_activity_from_post( $post );
     174            // Set the activity, we're dealing only with published posts (but just in case).
     175            $activities[ $post->ID ] = \progress_planner()->get_activities__content_helpers()->get_activity_from_post( $post, 'publish' === $post->post_status ? 'publish' : 'update' );
    176176            // Set the word count.
    177177            \progress_planner()->get_activities__content_helpers()->get_word_count( $post->post_content, $post->ID );
  • progress-planner/trunk/classes/actions/class-content.php

    r3268602 r3283338  
    281281        }
    282282
    283         $activity       = \progress_planner()->get_activities__content_helpers()->get_activity_from_post( $post );
    284         $activity->type = $type;
     283        $activity = \progress_planner()->get_activities__content_helpers()->get_activity_from_post( $post, $type );
    285284
    286285        // Update the badges.
     
    306305        }
    307306
     307        // We need to set the date explicitly since post_date & post_modified dates are not changed when the post is trashed.
     308        if ( 'trash' === $type ) {
     309            $activity->date = new \DateTime();
     310        }
     311
    308312        $activity->save();
    309313
  • progress-planner/trunk/classes/activities/class-content-helpers.php

    r3268602 r3283338  
    100100     *
    101101     * @param \WP_Post $post The post object.
     102     * @param string   $activity_type The activity type.
    102103     *
    103104     * @return \Progress_Planner\Activities\Content
    104105     */
    105     public function get_activity_from_post( $post ) {
    106         $type = 'publish' === $post->post_status ? 'publish' : 'update';
    107         $date = 'publish' === $post->post_status ? $post->post_date : $post->post_modified;
    108 
     106    public function get_activity_from_post( $post, $activity_type = 'publish' ) {
    109107        $activity           = new Activities_Content();
    110108        $activity->category = 'content';
    111         $activity->type     = $type;
    112         $activity->date     = \progress_planner()->get_utils__date()->get_datetime_from_mysql_date( $date );
     109        $activity->type     = $activity_type;
     110        $activity->date     = \progress_planner()->get_utils__date()->get_datetime_from_mysql_date( $post->post_modified );
    113111        $activity->data_id  = (string) $post->ID;
    114112        $activity->user_id  = (int) $post->post_author;
  • progress-planner/trunk/classes/activities/class-suggested-task.php

    r3268602 r3283338  
    6666
    6767        // Default points for a suggested task.
    68         $points               = 1;
    69         $create_post_provider = new Create();
     68        $points        = 1;
     69        $task_provider = \progress_planner()->get_suggested_tasks()->get_local()->get_task_provider( $this->data_id );
    7070
    71         $data = \progress_planner()->get_suggested_tasks()->get_local()->get_data_from_task_id( $this->data_id );
    72         if ( isset( $data['provider_id'] ) && $create_post_provider->get_provider_id() === $data['provider_id'] ) {
    73             $points = $create_post_provider->get_points_for_task( $this->data_id );
     71        if ( $task_provider ) {
     72            // Create post task provider had a different points system, this is for backwards compatibility.
     73            $points = $task_provider instanceof Create ? $task_provider->get_points( $this->data_id ) : $task_provider->get_points();
    7474        }
    7575
  • progress-planner/trunk/classes/admin/class-dashboard-widget-score.php

    r3264985 r3283338  
    4848
    4949        \progress_planner()->get_admin__enqueue()->enqueue_style( "progress-planner/dashboard-widgets/{$this->id}" );
     50
     51        \progress_planner()->get_admin__enqueue()->enqueue_script( 'external-link-accessibility-helper' );
    5052
    5153        \progress_planner()->the_view( "dashboard-widgets/{$this->id}.php" );
  • progress-planner/trunk/classes/admin/class-page-settings.php

    r3264985 r3283338  
    172172
    173173        $this->save_settings();
     174        $this->save_post_types();
    174175        $this->save_license();
    175176
    176         do_action( 'progress_planner_settings_form_options_stored' );
     177        \do_action( 'progress_planner_settings_form_options_stored' );
    177178
    178179        \wp_send_json_success( \esc_html__( 'Options stored successfully', 'progress-planner' ) );
     
    185186     */
    186187    public function save_settings() {
    187         $redirect_on_login = isset( $_POST['prpl-redirect-on-login'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
    188             ? \sanitize_text_field( \wp_unslash( $_POST['prpl-redirect-on-login'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
     188
     189        // Check the nonce.
     190        \check_admin_referer( 'progress_planner' );
     191
     192        $redirect_on_login = isset( $_POST['prpl-redirect-on-login'] )
     193            ? \sanitize_text_field( \wp_unslash( $_POST['prpl-redirect-on-login'] ) )
    189194            : false;
    190195
     
    193198
    194199    /**
     200     * Save the post types.
     201     *
     202     * @return void
     203     */
     204    public function save_post_types() {
     205
     206        // Check the nonce.
     207        \check_admin_referer( 'progress_planner' );
     208
     209        $include_post_types = isset( $_POST['prpl-post-types-include'] )
     210            ? array_map( 'sanitize_text_field', \wp_unslash( $_POST['prpl-post-types-include'] ) ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     211            // If no post types are selected, use the default post types (post and page can be deregistered).
     212            : array_intersect( [ 'post', 'page' ], \progress_planner()->get_settings()->get_public_post_types() );
     213
     214        \progress_planner()->get_settings()->set( 'include_post_types', $include_post_types );
     215    }
     216
     217    /**
    195218     * Save the license key.
    196219     *
     
    198221     */
    199222    public function save_license() {
    200         $license = isset( $_POST['prpl-pro-license-key'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
    201             ? \sanitize_text_field( \wp_unslash( $_POST['prpl-pro-license-key'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
     223
     224        // Check the nonce.
     225        \check_admin_referer( 'progress_planner' );
     226
     227        $license = isset( $_POST['prpl-pro-license-key'] )
     228            ? \sanitize_text_field( \wp_unslash( $_POST['prpl-pro-license-key'] ) )
    202229            : '';
    203230
  • progress-planner/trunk/classes/admin/class-page.php

    r3268602 r3283338  
    2828        \add_action( 'admin_menu', [ $this, 'add_page' ] );
    2929        \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
    30         \add_action( 'wp_ajax_progress_planner_save_cpt_settings', [ $this, 'save_cpt_settings' ] );
    3130        \add_action( 'in_admin_header', [ $this, 'remove_admin_notices' ], PHP_INT_MAX );
    3231
     
    5251            \progress_planner()->get_admin__widgets__latest_badge(),
    5352            \progress_planner()->get_admin__widgets__badge_streak(),
    54             \progress_planner()->get_admin__widgets__published_content(),
     53            \progress_planner()->get_admin__widgets__content_activity(),
    5554            \progress_planner()->get_admin__widgets__whats_new(),
    5655        ];
     
    188187                \progress_planner()->get_admin__enqueue()->enqueue_script( 'onboard', $default_localization_data );
    189188            }
     189
     190            \progress_planner()->get_admin__enqueue()->enqueue_script( 'external-link-accessibility-helper' );
    190191        }
    191192
     
    200201                ]
    201202            );
     203
     204            \progress_planner()->get_admin__enqueue()->enqueue_script( 'external-link-accessibility-helper' );
    202205        }
    203206    }
     
    292295
    293296    /**
    294      * Save the post types settings.
    295      *
    296      * @return void
    297      */
    298     public function save_cpt_settings() {
    299         \check_ajax_referer( 'progress_planner', 'nonce', false );
    300         $include_post_types = isset( $_POST['include_post_types'] )
    301             ? \sanitize_text_field( \wp_unslash( $_POST['include_post_types'] ) )
    302             : 'post,page';
    303         $include_post_types = \explode( ',', $include_post_types );
    304         \progress_planner()->get_settings()->set( 'include_post_types', $include_post_types );
    305 
    306         \wp_send_json_success(
    307             [
    308                 'message' => \esc_html__( 'Settings saved.', 'progress-planner' ),
    309             ]
    310         );
    311     }
    312 
    313     /**
    314297     * Remove all admin notices when the user is on the Progress Planner page.
    315298     *
     
    363346                .update-plugins {
    364347                    position: absolute;
    365                     left: 22px;
    366                     top: 0px;
     348                    left: 18px;
     349                    bottom: 0px;
    367350                    min-width: 15px;
    368351                    height: 15px;
  • progress-planner/trunk/classes/admin/widgets/class-suggested-tasks.php

    r3268602 r3283338  
    8181                            $task_details['action']   = 'celebrate';
    8282                            $task_details['status']   = 'pending_celebration';
    83 
    84                             // Award 2 points if last created post was long.
    85                             $create_provider = new Create();
    86                             if ( $create_provider->get_provider_id() === $task_provider->get_provider_id() ) {
    87                                 $task_details['points'] = $create_provider->get_points_for_task( $task_id );
    88                             }
    8983
    9084                            $tasks[] = $task_details;
  • progress-planner/trunk/classes/class-badges.php

    r3268602 r3283338  
    5555    public function __construct() {
    5656        $this->content = [
    57             \progress_planner()->get_badges__content__wonderful_writer(),
    58             \progress_planner()->get_badges__content__bold_blogger(),
    59             \progress_planner()->get_badges__content__awesome_author(),
     57            \progress_planner()->get_badges__content__content_curator(),
     58            \progress_planner()->get_badges__content__revision_ranger(),
     59            \progress_planner()->get_badges__content__purposeful_publisher(),
    6060        ];
    6161
  • progress-planner/trunk/classes/class-base.php

    r3268602 r3283338  
    194194            'get_chart'                                  => [ 'get_ui__chart', '1.1.1' ],
    195195            'get_popover'                                => [ 'get_ui__popover', '1.1.1' ],
     196
     197            'get_admin__widgets__published_content'      => [ 'get_admin__widgets__content_activity', '1.3.0' ],
    196198        ];
    197199
  • progress-planner/trunk/classes/class-plugin-migrations.php

    r3268602 r3283338  
    1111
    1212use Progress_Planner\Update\Update_111;
     13use Progress_Planner\Update\Update_130;
    1314
    1415/**
     
    4243    private const UPGRADE_CLASSES = [
    4344        '1.1.1' => Update_111::class,
     45        '1.3.0' => Update_130::class,
    4446    ];
    4547
  • progress-planner/trunk/classes/class-plugin-upgrade-tasks.php

    r3268602 r3283338  
    2525
    2626        // Check if the plugin was upgraded or new plugin was activated.
    27         \add_action( 'init', [ $this, 'handle_activation_or_upgrade' ], 10 );
     27        \add_action( 'init', [ $this, 'handle_activation_or_upgrade' ], 100 ); // We need to run this after the Local_Tasks_Manager::init() is called.
    2828    }
    2929
  • progress-planner/trunk/classes/class-settings.php

    r3198155 r3283338  
    111111        return $this->save_settings();
    112112    }
     113
     114    /**
     115     * Get an array of post-types names for the stats.
     116     *
     117     * @return string[]
     118     */
     119    public function get_post_types_names() {
     120        static $include_post_types;
     121
     122        if ( ! doing_action( 'init' ) && ! did_action( 'init' ) ) {
     123            \trigger_error( // phpcs:ignore
     124                sprintf(
     125                    '%1$s was called too early. Wait for init hook to be called to have access to the post types.',
     126                    \esc_html( get_class() . '::' . __FUNCTION__ )
     127                ),
     128                E_USER_WARNING
     129            );
     130        }
     131
     132        // Since we're working with CPTs, dont cache until init.
     133        if ( isset( $include_post_types ) && ! empty( $include_post_types ) ) {
     134            return $include_post_types;
     135        }
     136
     137        $public_post_types = $this->get_public_post_types();
     138
     139        // Post or pages can be deregistered.
     140        $default = array_intersect( [ 'post', 'page' ], $public_post_types );
     141
     142        // Filter the saved post types.
     143        $include_post_types = array_intersect( $this->get( [ 'include_post_types' ], $default ), $public_post_types );
     144
     145        return empty( $include_post_types ) ? $default : \array_values( $include_post_types );
     146    }
     147
     148    /**
     149     * Get the public post types.
     150     *
     151     * @return string[]
     152     */
     153    public function get_public_post_types() {
     154        $public_post_types = \array_filter( \get_post_types( [ 'public' => true ] ), 'is_post_type_viewable' );
     155
     156        unset( $public_post_types['attachment'] );
     157        unset( $public_post_types['elementor_library'] ); // Elementor templates are not a post type we want to track.
     158
     159        /**
     160         * Filter the public post types.
     161         *
     162         * @param string[] $public_post_types The public post types.
     163         *
     164         * @return string[]
     165         */
     166        return \apply_filters( 'progress_planner_public_post_types', $public_post_types );
     167    }
    113168}
  • progress-planner/trunk/classes/class-suggested-tasks.php

    r3268602 r3283338  
    4444
    4545        if ( \is_admin() ) {
    46             \add_action( 'init', [ $this, 'init' ], 1 );
     46            \add_action( 'init', [ $this, 'init' ], 100 ); // Wait for the post types to be initialized.
    4747        }
    4848
     
    580580
    581581    /**
     582     * Add a remote task to the pending tasks.
     583     *
     584     * @param string $task_id The task ID.
     585     *
     586     * @return bool
     587     */
     588    public function add_remote_task_to_pending_tasks( $task_id ) {
     589        $remote_task_data = $this->get_remote_task_by_task_id( $task_id );
     590
     591        if ( ! $remote_task_data ) {
     592            return false;
     593        }
     594
     595        return \progress_planner()->get_suggested_tasks()->get_local()->add_pending_task(
     596            [
     597                'task_id'     => $task_id,
     598                'provider_id' => $remote_task_data['category'] ?? '', // Remote tasks use the category as provider_id.
     599                'category'    => $remote_task_data['category'] ?? '',
     600            ]
     601        );
     602    }
     603
     604
     605    /**
    582606     * Handle the suggested task action.
    583607     *
     
    601625                // We need to add the task to the pending tasks first, before marking it as completed.
    602626                if ( false !== strpos( $task_id, 'remote-task' ) ) {
    603                     $remote_task_data = $this->get_remote_task_by_task_id( $task_id );
    604                     \progress_planner()->get_suggested_tasks()->get_local()->add_pending_task(
    605                         [
    606                             'task_id'     => $task_id,
    607                             'provider_id' => $remote_task_data['category'] ?? '', // Remote tasks use the category as provider_id.
    608                             'category'    => $remote_task_data['category'] ?? '',
    609                         ]
    610                     );
     627                    $this->add_remote_task_to_pending_tasks( $task_id );
    611628                }
    612629
     
    627644            case 'snooze':
    628645                $duration = isset( $_POST['duration'] ) ? \sanitize_text_field( \wp_unslash( $_POST['duration'] ) ) : '';
    629                 $updated  = $this->snooze_task( $task_id, $duration );
     646
     647                // We need to add the task to the pending tasks first, before marking it as snoozed.
     648                if ( false !== strpos( $task_id, 'remote-task' ) ) {
     649                    $this->add_remote_task_to_pending_tasks( $task_id );
     650                }
     651
     652                $updated = $this->snooze_task( $task_id, $duration );
    630653                break;
    631654
  • progress-planner/trunk/classes/rest/class-stats.php

    r3268602 r3283338  
    163163        $data['recommendations'] = [];
    164164        foreach ( $ravis_recommendations as $recommendation ) {
    165             $data['recommendations'][] = [
     165            $r = [
    166166                'id'          => $recommendation['task_id'],
    167167                'title'       => $recommendation['title'],
     
    169169                'provider_id' => $recommendation['provider_id'],
    170170            ];
     171
     172            if ( 'user' === $recommendation['provider_id'] ) {
     173                $r['points'] = isset( $recommendation['points'] ) ? $recommendation['points'] : 0;
     174            }
     175            $data['recommendations'][] = $r;
    171176        }
    172177
  • progress-planner/trunk/classes/suggested-tasks/class-local-tasks-manager.php

    r3268602 r3283338  
    2929use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Integrations\Yoast\Add_Yoast_Providers;
    3030use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\User as User_Tasks;
     31use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\One_Time\Set_Valuable_Post_Types;
    3132
    3233/**
     
    6566            new Search_Engine_Visibility(),
    6667            new User_Tasks(),
     68            new Set_Valuable_Post_Types(),
    6769        ];
    6870
     
    7173
    7274        // At this point both local and task providers for the plugins we integrate with are instantiated, so initialize them.
    73         \add_action( 'plugins_loaded', [ $this, 'init' ], 11 );
     75        \add_action( 'init', [ $this, 'init' ], 99 ); // Wait for the post types to be initialized.
    7476
    7577        // Add the cleanup action.
     
    239241            $task_id = $task_data['task_id'];
    240242
     243            // Check if the task is no longer relevant.
     244            $task_object   = Local_Task_Factory::create_task_from( 'id', $task_id );
     245            $task_provider = $this->get_task_provider( $task_object->get_provider_id() );
     246            if ( $task_provider && ! $task_provider->is_task_relevant() ) {
     247                // Remove the task from the pending tasks.
     248                \progress_planner()->get_suggested_tasks()->delete_task( $task_id );
     249            }
     250
    241251            $task_result = $this->evaluate_task( $task_id );
    242252            if ( false !== $task_result ) {
     
    353363            $tasks,
    354364            function ( $task ) {
    355                 // If the task was already completed, remove it.
    356                 if ( 'completed' === $task['status'] ) {
    357                     return false;
    358                 }
    359 
    360                 if ( isset( $task['date'] ) ) {
     365
     366                if ( 'pending' === $task['status'] && isset( $task['date'] ) ) {
    361367                    return (string) \gmdate( 'YW' ) === (string) $task['date'];
    362368                }
  • progress-planner/trunk/classes/suggested-tasks/data-collector/class-data-collector-manager.php

    r3268602 r3283338  
    1414use Progress_Planner\Suggested_Tasks\Data_Collector\Post_Author;
    1515use Progress_Planner\Suggested_Tasks\Data_Collector\Last_Published_Post;
     16use Progress_Planner\Suggested_Tasks\Data_Collector\Archive_Format;
    1617
    1718/**
     
    4041            new Post_Author(),
    4142            new Last_Published_Post(),
     43            new Archive_Format(),
    4244        ];
    4345
  • progress-planner/trunk/classes/suggested-tasks/data-collector/class-inactive-plugins.php

    r3268602 r3283338  
    5151        }
    5252
     53        // Clear the plugins cache, so get_plugins() returns the latest plugins.
     54        wp_cache_delete( 'plugins', 'plugins' );
     55
    5356        $plugins        = get_plugins();
    5457        $plugins_active = 0;
  • progress-planner/trunk/classes/suggested-tasks/data-collector/class-last-published-post.php

    r3268602 r3283338  
    2323
    2424    /**
     25     * The include post types.
     26     *
     27     * @var string[]
     28     */
     29    protected $include_post_types = [];
     30
     31    /**
    2532     * Initialize the data collector.
    2633     *
     
    2835     */
    2936    public function init() {
     37        \add_action( 'init', [ $this, 'set_include_post_types' ], 99 ); // Wait for all CPTs to be registered.
    3038        \add_action( 'transition_post_status', [ $this, 'update_last_published_post_cache' ], 10, 3 );
     39    }
     40
     41    /**
     42     * Set the include post types.
     43     *
     44     * @return void
     45     */
     46    public function set_include_post_types() {
     47        $this->include_post_types = \progress_planner()->get_settings()->get_post_types_names();
    3148    }
    3249
     
    4158     */
    4259    public function update_last_published_post_cache( $new_status, $old_status, $post ) {
    43         if ( $new_status === 'publish' || $old_status === 'publish' ) {
     60        if ( true === \in_array( get_post_type( $post ), $this->include_post_types, true ) && ( $new_status === 'publish' || $old_status === 'publish' ) ) {
    4461            $this->update_cache();
    4562        }
     
    6784                'orderby'        => 'date',
    6885                'order'          => 'DESC',
     86                'post_type'      => $this->include_post_types,
    6987            ]
    7088        );
     
    7391            $data = [
    7492                'post_id'   => $last_created_posts[0]->ID,
    75                 'long'      => \progress_planner()->get_activities__content_helpers()->is_post_long( $last_created_posts[0]->ID ) ? true : false,
    7693                'post_date' => $last_created_posts[0]->post_date,
    7794            ];
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/class-local-tasks-interface.php

    r3264985 r3283338  
    8181     */
    8282    public function capability_required();
     83
     84    /**
     85     * Check if the task is still relevant.
     86     * For example, we have a task to disable author archives if there is only one author.
     87     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     88     *
     89     * @return bool
     90     */
     91    public function is_task_relevant();
    8392}
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/class-local-tasks.php

    r3268602 r3283338  
    8080
    8181    /**
     82     * The task URL target.
     83     *
     84     * @var string
     85     */
     86    protected $url_target = '_self';
     87
     88    /**
    8289     * The task link setting.
    8390     *
     
    159166
    160167        return '';
     168    }
     169
     170    /**
     171     * Get the task URL.
     172     *
     173     * @return string
     174     */
     175    public function get_url_target() {
     176        return $this->url_target ? $this->url_target : '_self';
    161177    }
    162178
     
    265281        return false;
    266282    }
     283
     284    /**
     285     * Check if the task is still relevant.
     286     * For example, we have a task to disable author archives if there is only one author.
     287     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     288     *
     289     * @return bool
     290     */
     291    public function is_task_relevant() {
     292        return true;
     293    }
    267294}
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/class-user.php

    r3264985 r3283338  
    9494                    'points'       => 0,
    9595                    'url'          => '',
     96                    'url_target'   => '_self',
    9697                    'description'  => '',
    9798                    'link_setting' => [],
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-add-yoast-providers.php

    r3268602 r3283338  
    4747        foreach ( $this->providers as $provider ) {
    4848
    49             // Add Ravi icon if the task is pending or completed.
    50             if ( $provider->should_add_task() ) {
     49            // Add Ravi icon if the task is pending or is completed.
     50            if ( $provider->is_task_relevant() || \progress_planner()->get_suggested_tasks()->was_task_completed( $provider->get_task_id() ) ) {
    5151                $focus_task = $provider->get_focus_tasks();
    5252
    5353                if ( $focus_task ) {
    54                     $focus_tasks[] = $focus_task;
     54                    $focus_tasks = array_merge( $focus_tasks, $focus_task );
    5555                }
    5656            }
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-archive-author.php

    r3268602 r3283338  
    7373    public function get_focus_tasks() {
    7474        return [
    75             'iconElement'  => '.yst-toggle-field__header',
    76             'valueElement' => [
    77                 'elementSelector' => 'button[data-id="input-wpseo_titles-disable-author"]',
    78                 'attributeName'   => 'aria-checked',
    79                 'attributeValue'  => 'false',
    80                 'operator'        => '=',
     75            [
     76                'iconElement'  => '.yst-toggle-field__header',
     77                'valueElement' => [
     78                    'elementSelector' => 'button[data-id="input-wpseo_titles-disable-author"]',
     79                    'attributeName'   => 'aria-checked',
     80                    'attributeValue'  => 'false',
     81                    'operator'        => '=',
     82                ],
    8183            ],
    8284        ];
     
    8991     */
    9092    public function should_add_task() {
     93
     94        if ( ! $this->is_task_relevant() ) {
     95            return false;
     96        }
     97
    9198        // If the author archive is already disabled, we don't need to add the task.
    9299        if ( YoastSEO()->helpers->options->get( 'disable-author' ) === true ) {
     
    94101        }
    95102
     103        return true;
     104    }
     105
     106    /**
     107     * Check if the task is still relevant.
     108     * For example, we have a task to disable author archives if there is only one author.
     109     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     110     *
     111     * @return bool
     112     */
     113    public function is_task_relevant() {
    96114        // If there is more than one author, we don't need to add the task.
    97115        if ( $this->data_collector->collect() > self::MINIMUM_AUTHOR_WITH_POSTS ) {
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-archive-date.php

    r3268602 r3283338  
    5656    public function get_focus_tasks() {
    5757        return [
    58             'iconElement'  => '.yst-toggle-field__header',
    59             'valueElement' => [
    60                 'elementSelector' => 'button[data-id="input-wpseo_titles-disable-date"]',
    61                 'attributeName'   => 'aria-checked',
    62                 'attributeValue'  => 'false',
    63                 'operator'        => '=',
     58            [
     59                'iconElement'  => '.yst-toggle-field__header',
     60                'valueElement' => [
     61                    'elementSelector' => 'button[data-id="input-wpseo_titles-disable-date"]',
     62                    'attributeName'   => 'aria-checked',
     63                    'attributeValue'  => 'false',
     64                    'operator'        => '=',
     65                ],
    6466            ],
    6567        ];
     
    7274     */
    7375    public function should_add_task() {
     76
     77        if ( ! $this->is_task_relevant() ) {
     78            return false;
     79        }
     80
     81        // If the date archive is already disabled, we don't need to add the task.
     82        return YoastSEO()->helpers->options->get( 'disable-date' ) !== true;
     83    }
     84
     85    /**
     86     * Check if the task is still relevant.
     87     * For example, we have a task to disable author archives if there is only one author.
     88     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     89     *
     90     * @return bool
     91     */
     92    public function is_task_relevant() {
    7493        // If the permalink structure includes %year%, %monthnum%, or %day%, we don't need to add the task.
    7594        $permalink_structure = \get_option( 'permalink_structure' );
     
    7897        }
    7998
    80         // If the date archive is already disabled, we don't need to add the task.
    81         return YoastSEO()->helpers->options->get( 'disable-date' ) !== true;
     99        return true;
    82100    }
    83101}
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-archive-format.php

    r3268602 r3283338  
    7373    public function get_focus_tasks() {
    7474        return [
    75             'iconElement'  => '.yst-toggle-field__header',
    76             'valueElement' => [
    77                 'elementSelector' => 'button[data-id="input-wpseo_titles-disable-post_format"]',
    78                 'attributeName'   => 'aria-checked',
    79                 'attributeValue'  => 'false',
    80                 'operator'        => '=',
     75            [
     76                'iconElement'  => '.yst-toggle-field__header',
     77                'valueElement' => [
     78                    'elementSelector' => 'button[data-id="input-wpseo_titles-disable-post_format"]',
     79                    'attributeName'   => 'aria-checked',
     80                    'attributeValue'  => 'false',
     81                    'operator'        => '=',
     82                ],
    8183            ],
    8284        ];
     
    8991     */
    9092    public function should_add_task() {
    91         $archive_format_count = $this->data_collector->collect();
    92 
    93         // If there are more than X posts with a post format, we don't need to add the task. X is set in the class.
    94         if ( $archive_format_count > static::MINIMUM_POSTS_WITH_FORMAT ) {
     93        if ( ! $this->is_task_relevant() ) {
    9594            return false;
    9695        }
     
    103102        return true;
    104103    }
     104
     105    /**
     106     * Check if the task is still relevant.
     107     * For example, we have a task to disable author archives if there is only one author.
     108     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     109     *
     110     * @return bool
     111     */
     112    public function is_task_relevant() {
     113        $archive_format_count = $this->data_collector->collect();
     114
     115        // If there are more than X posts with a post format, we don't need to add the task. X is set in the class.
     116        if ( $archive_format_count > static::MINIMUM_POSTS_WITH_FORMAT ) {
     117            return false;
     118        }
     119
     120        return true;
     121    }
    105122}
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-crawl-settings-emoji-scripts.php

    r3268602 r3283338  
    5656    public function get_focus_tasks() {
    5757        return [
    58             'iconElement'  => '.yst-toggle-field__header',
    59             'valueElement' => [
    60                 'elementSelector' => 'button[data-id="input-wpseo-remove_emoji_scripts"]',
    61                 'attributeName'   => 'aria-checked',
    62                 'attributeValue'  => 'true',
    63                 'operator'        => '=',
     58            [
     59                'iconElement'  => '.yst-toggle-field__header',
     60                'valueElement' => [
     61                    'elementSelector' => 'button[data-id="input-wpseo-remove_emoji_scripts"]',
     62                    'attributeName'   => 'aria-checked',
     63                    'attributeValue'  => 'true',
     64                    'operator'        => '=',
     65                ],
    6466            ],
    6567        ];
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-crawl-settings-feed-authors.php

    r3268602 r3283338  
    7373    public function get_focus_tasks() {
    7474        return [
    75             'iconElement'  => '.yst-toggle-field__header',
    76             'valueElement' => [
    77                 'elementSelector' => 'button[data-id="input-wpseo-remove_feed_authors"]',
    78                 'attributeName'   => 'aria-checked',
    79                 'attributeValue'  => 'true',
    80                 'operator'        => '=',
     75            [
     76                'iconElement'  => '.yst-toggle-field__header',
     77                'valueElement' => [
     78                    'elementSelector' => 'button[data-id="input-wpseo-remove_feed_authors"]',
     79                    'attributeName'   => 'aria-checked',
     80                    'attributeValue'  => 'true',
     81                    'operator'        => '=',
     82                ],
    8183            ],
    8284        ];
     
    8991     */
    9092    public function should_add_task() {
    91         // If there is more than one author, we don't need to add the task.
    92         if ( $this->data_collector->collect() > self::MINIMUM_AUTHOR_WITH_POSTS ) {
     93
     94        if ( ! $this->is_task_relevant() ) {
    9395            return false;
    9496        }
     
    104106        return true;
    105107    }
     108
     109    /**
     110     * Check if the task is still relevant.
     111     * For example, we have a task to disable author archives if there is only one author.
     112     * If in the meantime more authors are added, the task is no longer relevant and the task should be removed.
     113     *
     114     * @return bool
     115     */
     116    public function is_task_relevant() {
     117        // If there is more than one author, we don't need to add the task.
     118        if ( $this->data_collector->collect() > self::MINIMUM_AUTHOR_WITH_POSTS ) {
     119            return false;
     120        }
     121
     122        return true;
     123    }
    106124}
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-crawl-settings-feed-global-comments.php

    r3268602 r3283338  
    5656    public function get_focus_tasks() {
    5757        return [
    58             'iconElement'  => '.yst-toggle-field__header',
    59             'valueElement' => [
    60                 'elementSelector' => 'button[data-id="input-wpseo-remove_feed_global_comments"]',
    61                 'attributeName'   => 'aria-checked',
    62                 'attributeValue'  => 'true',
    63                 'operator'        => '=',
     58            [
     59                'iconElement'  => '.yst-toggle-field__header',
     60                'valueElement' => [
     61                    'elementSelector' => 'button[data-id="input-wpseo-remove_feed_global_comments"]',
     62                    'attributeName'   => 'aria-checked',
     63                    'attributeValue'  => 'true',
     64                    'operator'        => '=',
     65                ],
    6466            ],
    6567        ];
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-media-pages.php

    r3268602 r3283338  
    5656    public function get_focus_tasks() {
    5757        return [
    58             'iconElement'  => '.yst-toggle-field__header',
    59             'valueElement' => [
    60                 'elementSelector' => 'button[data-id="input-wpseo_titles-disable-attachment"]',
    61                 'attributeName'   => 'aria-checked',
    62                 'attributeValue'  => 'false',
    63                 'operator'        => '=',
     58            [
     59                'iconElement'  => '.yst-toggle-field__header',
     60                'valueElement' => [
     61                    'elementSelector' => 'button[data-id="input-wpseo_titles-disable-attachment"]',
     62                    'attributeName'   => 'aria-checked',
     63                    'attributeValue'  => 'false',
     64                    'operator'        => '=',
     65                ],
    6466            ],
    6567        ];
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/integrations/yoast/class-organization-logo.php

    r3268602 r3283338  
    7171    public function get_focus_tasks() {
    7272        return [
    73             'iconElement'  => 'legend.yst-label',
    74             'valueElement' => [
    75                 'elementSelector' => $this->yoast_seo->helpers->options->get( 'company_or_person', 'company' ) !== 'person'
    76                     ? 'input[name="wpseo_titles.company_logo"]'
    77                     : 'input[name="wpseo_titles.person_logo"]',
    78                 'attributeName'   => 'value',
    79                 'attributeValue'  => '',
    80                 'operator'        => '!=',
     73            [
     74                'iconElement'  => 'legend.yst-label',
     75                'valueElement' => [
     76                    'elementSelector' => 'input[name="wpseo_titles.company_logo"]',
     77                    'attributeName'   => 'value',
     78                    'attributeValue'  => '',
     79                    'operator'        => '!=',
     80                ],
     81            ],
     82            [
     83                'iconElement'  => 'legend.yst-label',
     84                'valueElement' => [
     85                    'elementSelector' => 'input[name="wpseo_titles.person_logo"]',
     86                    'attributeName'   => 'value',
     87                    'attributeValue'  => '',
     88                    'operator'        => '!=',
     89                ],
    8190            ],
    8291        ];
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/one-time/class-settings-saved.php

    r3268602 r3283338  
    2121     */
    2222    protected const PROVIDER_ID = 'settings-saved';
     23
     24    /**
     25     * The task priority.
     26     *
     27     * @var string
     28     */
     29    protected $priority = 'high';
    2330
    2431    /**
     
    5158     */
    5259    public function get_description() {
    53         return sprintf(
    54             /* translators: %s:<a href="https://prpl.fyi/fill-settings-page" target="_blank">settings page</a> link */
    55             \esc_html__( 'Head over to the settings page and fill in the required information. %s', 'progress-planner' ),
    56             '<a href="https://prpl.fyi/fill-settings-page" target="_blank">' . \esc_html__( 'settings page', 'progress-planner' ) . '</a>'
    57         );
     60        return \esc_html__( 'Head over to the settings page and fill in the required information.', 'progress-planner' );
    5861    }
    5962
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/repetitive/class-core-update.php

    r3268602 r3283338  
    145145            'dismissable' => $this->is_dismissable(),
    146146            'url'         => $this->get_url(),
     147            'url_target'  => $this->get_url_target(),
    147148            'description' => $this->get_description(),
    148149        ];
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/repetitive/class-create.php

    r3268602 r3283338  
    3838
    3939    /**
     40     * The task URL target.
     41     *
     42     * @var string
     43     */
     44    protected $url_target = '_blank';
     45
     46    /**
    4047     * The data collector.
    4148     *
     
    4956    public function __construct() {
    5057        $this->data_collector = new Last_Published_Post_Data_Collector();
    51         $this->url            = \admin_url( 'post-new.php?post_type=post' );
     58        $this->url            = 'https://prpl.fyi/valuable-content';
    5259    }
    5360
     
    5865     */
    5966    public function get_title() {
    60         return esc_html__( 'Create a post', 'progress-planner' );
     67        return esc_html__( 'Create valuable content', 'progress-planner' );
    6168    }
    6269
     
    6774     */
    6875    public function get_description() {
    69         return esc_html__( 'Create a new, relevant post. If you write an in-depth post you may earn an extra point.', 'progress-planner' );
     76        return sprintf(
     77            /* translators: %s: "Read more" link. */
     78            \esc_html__( 'Time to add more valuable content to your site! Check our blog for inspiration. %s.', 'progress-planner' ),
     79            '<a href="https://prpl.fyi/valuable-content" target="_blank">' . \esc_html__( 'Read more', 'progress-planner' ) . '</a>'
     80        );
    7081    }
    7182
     
    7788     * @return array
    7889     */
    79     public function modify_task_data( $task_data ) {
     90    public function modify_evaluated_task_data( $task_data ) {
    8091        $last_published_post_data = $this->data_collector->collect();
    8192
     
    8697        // Add the post ID and post length to the task data.
    8798        $task_data['post_id'] = $last_published_post_data['post_id'];
    88         $task_data['long']    = $last_published_post_data['long'];
    8999
    90100        return $task_data;
     
    133143            'dismissable' => $this->is_dismissable(),
    134144            'url'         => $this->get_url(),
     145            'url_target'  => $this->get_url_target(),
    135146            'description' => $this->get_description(),
    136147        ];
     
    147158     * @return int
    148159     */
    149     public function get_points_for_task( $task_id = '' ) {
     160    public function get_points( $task_id = '' ) {
    150161
    151162        if ( ! $task_id ) {
    152             // Get the post that was created last.
    153             $post_data = $this->data_collector->collect();
    154         } else {
    155             $post_data = \progress_planner()->get_suggested_tasks()->get_tasks_by( 'task_id', $task_id );
    156             $post_data = $post_data[0] ?? false;
    157         }
    158 
    159         // Post was created, but then deleted?
    160         if ( ! $post_data || empty( $post_data['post_id'] ) ) {
    161163            return $this->points;
    162164        }
    163165
    164         return true === $post_data['long'] ? 2 : 1;
     166        $post_data = \progress_planner()->get_suggested_tasks()->get_tasks_by( 'task_id', $task_id );
     167        $post_data = $post_data[0] ?? false;
     168
     169        // Backwards compatibility.
     170        if ( $post_data && isset( $post_data['long'] ) ) {
     171            return true === $post_data['long'] ? 2 : 1;
     172        }
     173
     174        return $this->points;
    165175    }
    166176}
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/repetitive/class-review.php

    r3268602 r3283338  
    7373
    7474    /**
     75     * The include post types.
     76     *
     77     * @var string[]
     78     */
     79    protected $include_post_types = [];
     80
     81    /**
    7582     * Initialize the task provider.
    7683     *
     
    7885     */
    7986    public function init() {
     87        $this->include_post_types = \progress_planner()->get_settings()->get_post_types_names(); // Wait for the post types to be initialized.
     88
    8089        \add_filter( 'progress_planner_update_posts_tasks_args', [ $this, 'filter_update_posts_args' ] );
    8190    }
     
    179188                $last_updated_posts = $this->get_old_posts(
    180189                    [
    181                         'post__in' => $important_page_ids,
     190                        'post__in'  => $important_page_ids,
     191                        'post_type' => 'any',
    182192                    ]
    183193                );
     
    212222
    213223                $this->task_post_mappings[ $task_id ] = [
    214                     'task_id' => $task_id,
    215                     'post_id' => $post->ID,
     224                    'task_id'   => $task_id,
     225                    'post_id'   => $post->ID,
     226                    'post_type' => $post->post_type,
    216227                ];
    217228            }
     
    247258                    'category'    => $this->get_provider_category(),
    248259                    'post_id'     => $task_data['post_id'],
     260                    'post_type'   => $task_data['post_type'],
    249261                    'date'        => \gmdate( 'YW' ),
    250262                ];
     
    278290            'dismissable' => $this->is_dismissable(),
    279291            'url'         => $this->get_url( $task_id ),
     292            'url_target'  => $this->get_url_target(),
    280293            'description' => $this->get_description( $task_id ),
    281294        ];
     
    322335     */
    323336    public function get_old_posts( $args = [] ) {
    324         $args = wp_parse_args(
    325             $args,
    326             [
    327                 'posts_per_page' => static::ITEMS_TO_INJECT,
    328                 'post_type'      => [ 'page', 'post' ],
    329                 'post_status'    => 'publish',
    330                 'orderby'        => 'modified',
    331                 'order'          => 'ASC',
    332                 'date_query'     => [
    333                     [
    334                         'column' => 'post_modified',
    335                         'before' => '-6 months',
     337        $posts = [];
     338
     339        if ( ! empty( $this->include_post_types ) ) {
     340            $args = wp_parse_args(
     341                $args,
     342                [
     343                    'posts_per_page' => static::ITEMS_TO_INJECT,
     344                    'post_type'      => $this->include_post_types,
     345                    'post_status'    => 'publish',
     346                    'orderby'        => 'modified',
     347                    'order'          => 'ASC',
     348                    'date_query'     => [
     349                        [
     350                            'column' => 'post_modified',
     351                            'before' => '-6 months',
     352                        ],
    336353                    ],
    337                 ],
    338             ]
    339         );
    340 
    341         /**
    342          * Filters the args for the posts & pages we want user to review.
    343          *
    344          * @param array $args The get_postsargs.
    345          */
    346         $args = apply_filters( 'progress_planner_update_posts_tasks_args', $args );
    347 
    348         // Get the post that was updated last.
    349         $posts = \get_posts( $args );
     354                ]
     355            );
     356
     357            /**
     358             * Filters the args for the posts & pages we want user to review.
     359             *
     360             * @param array $args The get_postsargs.
     361             */
     362            $args = apply_filters( 'progress_planner_update_posts_tasks_args', $args );
     363
     364            // Get the post that was updated last.
     365            $posts = \get_posts( $args );
     366        }
     367
     368        // Get the pages saved in the settings that have not been updated in the last 6 months.
     369        $saved_page_type_ids = $this->get_saved_page_types();
     370
     371        if ( ! empty( $saved_page_type_ids ) ) {
     372            $pages_to_update = \get_posts(
     373                [
     374                    'post_type'           => 'any',
     375                    'post_status'         => 'publish',
     376                    'orderby'             => 'modified',
     377                    'order'               => 'ASC',
     378                    'ignore_sticky_posts' => true,
     379                    'date_query'          => [
     380                        [
     381                            'column' => 'post_modified',
     382                            'before' => '-6 months',
     383                        ],
     384                    ],
     385                    'post__in'            => $saved_page_type_ids,
     386                ]
     387            );
     388
     389            // Merge the posts & pages to update. Put the pages first.
     390            $posts = array_merge( $pages_to_update, $posts );
     391        }
    350392
    351393        return $posts ? $posts : [];
     
    398440
    399441    /**
     442     * Get the saved page-types.
     443     *
     444     * @return int[]
     445     */
     446    protected function get_saved_page_types() {
     447        $ids = [];
     448        // Add the saved page-types to the post__not_in array.
     449        $page_types = \progress_planner()->get_admin__page_settings()->get_settings();
     450        foreach ( $page_types as $page_type ) {
     451            if ( isset( $page_type['value'] ) && 0 !== (int) $page_type['value'] ) {
     452                $ids[] = (int) $page_type['value'];
     453            }
     454        }
     455        return $ids;
     456    }
     457
     458    /**
    400459     * Check if a specific task is completed.
    401460     *
  • progress-planner/trunk/playwright.config.js

    r3264985 r3283338  
    44    testDir: './tests/e2e',
    55    timeout: 30000,
    6     fullyParallel: false,
    76    forbidOnly: !! process.env.CI,
    87    retries: process.env.CI ? 2 : 0,
    9     workers: 1,
    108    reporter: 'html',
    11     globalSetup: require.resolve( './tests/e2e/auth.setup.js' ),
     9    globalSetup: './tests/e2e/auth.setup.js',
     10    globalTeardown: './tests/e2e/auth.setup.js',
    1211    use: {
    1312        baseURL: process.env.WORDPRESS_URL || 'http://localhost:8080',
     
    1817    projects: [
    1918        {
    20             name: 'chromium',
     19            name: 'sequential',
    2120            use: { ...devices[ 'Desktop Chrome' ] },
     21            testMatch: 'sequential.spec.js',
     22            fullyParallel: false,
     23            workers: 1,
     24        },
     25        {
     26            name: 'parallel',
     27            use: { ...devices[ 'Desktop Chrome' ] },
     28            testIgnore: [
     29                'onboarding.spec.js',
     30                'task-tagline.spec.js',
     31                'todo.spec.js',
     32                'todo-reorder.spec.js',
     33                'todo-complete.spec.js',
     34                'sequential.spec.js',
     35            ],
     36            fullyParallel: true,
     37            workers: 4,
    2238        },
    2339    ],
  • progress-planner/trunk/progress-planner.php

    r3268602 r3283338  
    1010 * Requires at least: 6.3
    1111 * Requires PHP:      7.4
    12  * Version:           1.2.0
     12 * Version:           1.3.0
    1313 * Author:            Team Emilia Projects
    1414 * Author URI:        https://prpl.fyi/about
     
    6767            'Progress_Planner\Onboard'                    => [ 'Progress_Planner\Utils\Onboard', '1.1.1' ],
    6868            'Progress_Planner\Playground'                 => [ 'Progress_Planner\Utils\Playground', '1.1.1' ],
     69
     70            'Progress_Planner\Admin\Widgets\Published_Content' => [ 'Progress_Planner\Admin\Widgets\Content_Activity', '1.3.0' ],
    6971        ];
    7072
  • progress-planner/trunk/readme.txt

    r3268602 r3283338  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 1.2.0
     7Stable tag: 1.3.0
    88License: GPL3+
    99License URI: https://www.gnu.org/licenses/gpl-3.0.en.html
     
    110110
    111111== Changelog ==
     112
     113= 1.3.0 =
     114
     115Enhancements:
     116
     117* Improved checks when adding Ravi icon to the Yoast SEO settings page.
     118* Add "golden" tasks to weekly emails.
     119* Add text to clarify when the user has completed all tasks.
     120* Improve the content widget & stats to show more accurate data. It now shows content _activity_ instead of content _published_.
     121* Implemented "valuable post-types" and added settings for them.
     122* Changed the "create a post" task to "create valuable content".
     123* Renamed & migrated content badges.
     124* Added a link to the 'Create valuable content' task description.
     125* Improve accessibility of Recommendations (and other links) linking to external resources
     126
     127Bugs we fixed:
     128
     129* Fixed error during plugin uninstall.
     130* Archive_Format data collector hooks weren't registered early enough.
     131* Ensure fresh plugin list by clearing plugin cache before checking for inactive plugins after deletion.
     132* Clear plugin cache when checking for inactive plugins.
     133* Delete no-longer relevant pending tasks.
     134* Fixed timing issue for tasks added by 3rd-party plugins.
    112135
    113136= 1.2.0 =
  • progress-planner/trunk/uninstall.php

    r3268602 r3283338  
    1414
    1515require_once __DIR__ . '/classes/class-settings.php';
    16 require_once __DIR__ . '/classes/class-query.php';
     16require_once __DIR__ . '/classes/activities/class-query.php';
    1717
    1818/**
  • progress-planner/trunk/views/admin-page-header.php

    r3268602 r3283338  
    3535        </button>
    3636        <?php
    37         // Render the settings button.
    38         \progress_planner()->get_ui__popover()->the_popover( 'settings' )->render_button(
    39             '',
    40             \progress_planner()->get_asset( 'images/icon_settings.svg' ) . '<span class="screen-reader-text">' . \esc_html__( 'Settings', 'progress-planner' ) . '</span>'
    41         );
    42         // Render the settings popover.
    43         \progress_planner()->get_ui__popover()->the_popover( 'settings' )->render();
    4437
    4538        // Render the subscribe form button and popover if the license key is not set.
  • progress-planner/trunk/views/admin-page-settings.php

    r3264985 r3283338  
    3535    <form id="prpl-settings">
    3636        <?php \progress_planner()->the_view( 'page-settings/pages.php' ); ?>
    37         <?php \progress_planner()->the_view( 'page-settings/settings.php' ); ?>
    38         <?php \progress_planner()->the_view( 'page-settings/license.php' ); ?>
     37
     38        <div id="prpl-grid-column-wrapper">
     39            <?php \progress_planner()->the_view( 'page-settings/post-types.php' ); ?>
     40            <?php \progress_planner()->the_view( 'page-settings/settings.php' ); ?>
     41            <?php \progress_planner()->the_view( 'page-settings/license.php' ); ?>
     42        </div>
    3943
    4044        <?php wp_nonce_field( 'progress_planner' ); ?>
  • progress-planner/trunk/views/page-settings/license.php

    r3238385 r3283338  
    1616?>
    1717
    18 <div class="prpl-column">
     18<div class="prpl-column prpl-column-license">
    1919    <div class="prpl-widget-wrapper">
    2020        <h2 class="prpl-settings-section-title prpl-settings-section-license">
  • progress-planner/trunk/views/page-settings/pages.php

    r3238385 r3283338  
    1212?>
    1313
    14 <div class="prpl-column">
     14<div class="prpl-column prpl-column-pages">
    1515    <div class="prpl-widget-wrapper">
    1616        <h2 class="prpl-settings-section-title">
  • progress-planner/trunk/views/page-settings/settings.php

    r3264985 r3283338  
    1414?>
    1515
    16 <div class="prpl-column">
     16<div class="prpl-column prpl-column-login-destination">
    1717    <div class="prpl-widget-wrapper">
    1818        <h2 class="prpl-settings-section-title">
    1919            <span class="icon">
    20                 <?php \progress_planner()->the_asset( 'images/icon_forward.svg' ); ?>
     20                <?php \progress_planner()->the_asset( 'images/icon_user.svg' ); ?>
    2121            </span>
    2222            <span>
  • progress-planner/trunk/views/page-widgets/suggested-tasks.php

    r3268602 r3283338  
    2727    <ul style="display:none"></ul>
    2828    <ul class="prpl-suggested-tasks-list"></ul>
     29    <p class="prpl-no-suggested-tasks">
     30        <?php \esc_html_e( 'You have completed all recommended tasks.', 'progress-planner' ); ?>
     31        <br>
     32        <?php \esc_html_e( 'Check back later for new tasks!', 'progress-planner' ); ?>
     33    </p>
    2934    <hr>
    3035</div>
  • progress-planner/trunk/views/page-widgets/whats-new.php

    r3268602 r3283338  
    2525        ?>
    2626        <li>
    27             <a href="<?php echo \esc_url( $prpl_blog_post['link'] ); ?>" target="_blank">
    28                 <h3><?php echo \esc_html( $prpl_blog_post['title']['rendered'] ); ?></h3>
    29                 <?php if ( $prpl_blog_post_image_url ) : ?>
     27            <h3>
     28                <a href="<?php echo \esc_url( $prpl_blog_post['link'] ); ?>" target="_blank">
     29                    <?php echo \esc_html( $prpl_blog_post['title']['rendered'] ); ?>
     30                </a>
     31            </h3>
     32            <?php if ( $prpl_blog_post_image_url ) : ?>
     33                <a href="<?php echo \esc_url( $prpl_blog_post['link'] ); ?>" target="_blank">
    3034                    <div class="prpl-blog-post-image" style="background-image:url(<?php echo \esc_url( $prpl_blog_post_image_url ); ?>)"></div>
    31                 <?php endif; ?>
    32             </a>
     35                </a>
     36            <?php endif; ?>
    3337            <p><?php echo \esc_html( \wp_trim_words( \wp_strip_all_tags( $prpl_blog_post['content']['rendered'] ), 55 ) ); ?></p>
    3438            <hr />
  • progress-planner/trunk/views/setting/page-select.php

    r3268602 r3283338  
    9595                            </div>
    9696                        <?php endif; ?>
    97 
    98 
    9997                    </div>
    10098                <?php endforeach; ?>
Note: See TracChangeset for help on using the changeset viewer.