Plugin Directory

Changeset 3238385


Ignore:
Timestamp:
02/11/2025 08:09:10 AM (12 months ago)
Author:
progressplanner
Message:

Update to version 1.0.4 from GitHub

Location:
progress-planner
Files:
30 added
4 deleted
74 edited
1 copied

Legend:

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

    r3217671 r3238385  
     1= 1.0.4 =
     2
     3Enhancements:
     4
     5* We've moved Ravi's recommendations to the top left of your Progress Planner dashboard. They're the most important thing on there, so we wanted to give it prime placement.
     6* We changed "Update post" to "Review post" / "Review page" and [wrote better instructions for reviewing old posts and pages](https://progressplanner.com/recommendations/review-post/). These tasks now prioritize the most important pages, like your About page, Privacy policy, Contact page and FAQ page.
     7* Added an option to redirect users to the Progress Planner dashboard after login. The WordPress dashboard isn't particularly useful in our eyes, this mind entice you to action more.
     8* Added a plugin-deactivation feedback form (we tell you, because you'll never see it, right? :) ).
     9* Removed the celebration for "Perform all updates" if it was done by WordPress's automatic update. We all love confetti, but when it comes all the time without you doing anything, it loses its value, right? Hence this fix.
     10
     11We've added the following Recommendations from Ravi:
     12
     13* [Setting site icon](https://progressplanner.com/recommendations/set-a-site-icon-aka-favicon/).
     14* [Setting the tagline](https://progressplanner.com/recommendations/set-tagline/).
     15* [Deactivating the display of PHP debug messages](https://progressplanner.com/recommendations/set-wp-debug/).
     16* [Removing the default WP "Hello world" post](https://progressplanner.com/recommendations/delete-the-default-wordpress-hello-world-post/).
     17* [Removing the default WP "Sample page" page](https://progressplanner.com/recommendations/delete-the-default-wordpress-sample-page-post/).
     18
     19Under the hood:
     20
     21* Improvements to the REST-API endpoint for getting stats.
     22* Removed admin notices on the Progress Planner page.
     23
     24= 1.0.3 =
     25
     26Fixed:
     27
     28* Detection of page-types in the settings page.
     29* Properly resetting caches for monthly badges.
     30
     31Enhancements:
     32
     33* Added a new "Challenges" widget to the dashboard.
     34
    135= 1.0.2 =
    236
     
    2256* Assets versioning.
    2357* Duplicate update-core tasks.
    24 * Update old post task being celebrated as completed when post is trashed.
    2558* Information icon for 'Create a long post' task was showing text of 'create a short post' task.
    2659* Numerous other minor bugfixes.
     
    4881= 0.9.5 =
    4982
     83Enhancements:
     84
     85* Added functionality to make it easier to demo the plugin on the WordPress playground.
     86* Improved the onboarding and added a tour of the plugin.
     87
    5088Fixed:
    5189
     
    68106Security:
    69107
    70 * Stricter sanitization & escaping of data in to-do items. Props to [justakazh](https://github.com/justakazh) for reporting through our [PatchStack Vulnerability Disclosure Program](https://patchstack.com/database/vdp/progress-planner).
     108* Stricter sanitization & escaping of data in to-do items.  Props to [justakazh](https://github.com/justakazh) for reporting through our [PatchStack Vulnerability Disclosure Program](https://patchstack.com/database/vdp/progress-planner).
    71109* Restrict access to the plugin's dashboard widgets to users with the `publish_posts` capability.
    72110
  • progress-planner/tags/1.0.4/assets/css/page-widgets/suggested-tasks.css

    r3210975 r3238385  
    100100
    101101        hr {
    102             display: initial;
     102            display: block;
    103103        }
    104104    }
  • progress-planner/tags/1.0.4/assets/css/settings-page.css

    r3226821 r3238385  
    6969            width: 1.25em;
    7070            height: 1.25em;
    71 
    72             svg path {
    73                 fill: currentcolor;
    74             }
    7571        }
    7672    }
  • progress-planner/tags/1.0.4/assets/js/web-components/prpl-badge.js

    r3217671 r3238385  
    1212            complete =
    1313                true === complete && 'true' === this.getAttribute( 'complete' );
     14
     15            badgeId = badgeId || this.getAttribute( 'badge-id' );
    1416            this.innerHTML = `
    1517                <img
    1618                    src="${
    1719                        progressPlannerBadge.remoteServerRootUrl
    18                     }/wp-json/progress-planner-saas/v1/badge-svg/?badge_id=${
    19                         badgeId || this.getAttribute( 'badge-id' )
    20                     }"
     20                    }/wp-json/progress-planner-saas/v1/badge-svg/?badge_id=${ badgeId }"
    2121                    alt="Badge"
    2222                    ${ false === complete ? 'style="filter: grayscale(1);opacity: 0.25;"' : '' }
  • progress-planner/tags/1.0.4/assets/js/web-components/prpl-suggested-task.js

    r3210975 r3238385  
    1313            taskPoints,
    1414            taskAction = '',
    15             taskUrl = ''
     15            taskUrl = '',
     16            taskDismissable = false,
     17            taskType = ''
    1618        ) {
    1719            // Get parent class properties
     
    2628
    2729            const isRemoteTask = taskId.startsWith( 'remote-task-' );
     30            const isDismissable = taskDismissable || isRemoteTask;
    2831
    2932            const actionButtons = {
     
    5255                            <span class="screen-reader-text">${ progressPlannerSuggestedTask.i18n.snooze }</span>
    5356                        </button>`,
    54                 complete: isRemoteTask
     57                complete: isDismissable
    5558                    ? `<button
    5659                            type="button"
     
    6972
    7073            this.innerHTML = `
    71             <li class="prpl-suggested-task" data-task-id="${ taskId }" data-task-action="${ taskAction }" data-task-url="${ taskUrl }">
     74            <li class="prpl-suggested-task" data-task-id="${ taskId }" data-task-action="${ taskAction }" data-task-url="${ taskUrl }" data-task-type="${ taskType }" data-task-points="${ taskPoints }">
    7275                <h3><span>${ taskHeading }</span></h3>
    7376                <div class="prpl-suggested-task-actions">
     
    278281        runTaskAction = ( taskId, actionType, snoozeDuration ) => {
    279282            taskId = taskId.toString();
     283            const type =
     284                this.querySelector( 'li' ).getAttribute( 'data-task-type' );
    280285
    281286            const data = {
     
    308313                        ) {
    309314                            window.progressPlannerSuggestedTasks.tasks.snoozed.push(
    310                                 taskId
     315                                {
     316                                    id: taskId,
     317                                }
    311318                            );
    312319                        }
     
    321328                        el.setAttribute( 'data-task-action', 'celebrate' );
    322329
     330                        const event = new CustomEvent(
     331                            'prplUpdateRaviGaugeEvent',
     332                            {
     333                                detail: {
     334                                    pointsDiff: parseInt(
     335                                        this.querySelector( 'li' ).getAttribute(
     336                                            'data-task-points'
     337                                        )
     338                                    ),
     339                                },
     340                            }
     341                        );
     342                        document.dispatchEvent( event );
     343
    323344                        // Trigger the celebration event.
    324345                        document.dispatchEvent(
     
    329350                }
    330351
    331                 const event = new Event( 'prplMaybeInjectSuggestedTaskEvent' );
     352                const event = new CustomEvent(
     353                    'prplMaybeInjectSuggestedTaskEvent',
     354                    {
     355                        detail: {
     356                            taskId,
     357                            type,
     358                        },
     359                    }
     360                );
    332361                document.dispatchEvent( event );
    333362            } );
  • progress-planner/tags/1.0.4/assets/js/widgets/suggested-tasks.js

    r3217671 r3238385  
    1 /* global customElements, progressPlannerSuggestedTasks, confetti, prplDocumentReady, progressPlannerSuggestedTask */
     1/* global customElements, progressPlannerSuggestedTasks, confetti, prplDocumentReady */
    22
    33/**
    44 * Count the number of items in the list.
    55 *
     6 * @param {string} type The type of items to count.
    67 * @return {number} The number of items in the list.
    78 */
    8 const progressPlannerCountItems = () => {
    9     const items = document.querySelectorAll( '.prpl-suggested-task' );
     9const progressPlannerCountItems = ( type ) => {
     10    // We want to display all pending celebration tasks on page load.
     11    if ( 'pending_celebration' === type ) {
     12        return 0;
     13    }
     14
     15    const items = document.querySelectorAll(
     16        `.prpl-suggested-task[data-task-type="${ type }"]`
     17    );
    1018    return items.length;
    1119};
     
    1422 * Get the next item to inject.
    1523 *
     24 * @param {string} type The type of items to get the next item from.
    1625 * @return {Object} The next item to inject.
    1726 */
    18 const progressPlannerGetNextItem = () => {
     27const progressPlannerGetNextItemFromType = ( type ) => {
     28    // If the are no items of this type, return null.
     29    if (
     30        'undefined' ===
     31        typeof progressPlannerSuggestedTasks.tasks.details[ type ]
     32    ) {
     33        return null;
     34    }
     35
    1936    // Remove completed and snoozed items.
    2037    const tasks = progressPlannerSuggestedTasks.tasks;
    21     const items = tasks.details;
     38    const items = tasks.details[ type ];
    2239    const completed = tasks.completed;
    2340    const snoozed = tasks.snoozed;
     
    7188/**
    7289 * Inject the next item.
     90 * @param {string} type The type of items to inject the next item from.
    7391 */
    74 const progressPlannerInjectNextItem = () => {
    75     const nextItem = progressPlannerGetNextItem();
     92const progressPlannerInjectNextItem = ( type ) => {
     93    const nextItem = progressPlannerGetNextItemFromType( type );
    7694    if ( ! nextItem ) {
    7795        return;
     
    95113        details.points ?? 1,
    96114        details.action ?? '',
    97         details.url ?? ''
     115        details.url ?? '',
     116        details.dismissable ?? false,
     117        details.type ?? ''
    98118    );
    99119
     
    155175
    156176    const progressPlannerRenderAttemptshoot = () => {
    157         confetti( {
    158             ...prplConfettiDefaults,
    159             particleCount: 40,
    160             scalar: 1.2,
    161             shapes: [ 'star' ],
    162         } );
    163 
    164         confetti( {
    165             ...prplConfettiDefaults,
    166             particleCount: 10,
    167             scalar: 0.75,
    168             shapes: [ 'circle' ],
    169         } );
     177        let confettiOptions = [
     178            {
     179                particleCount: 40,
     180                scalar: 1.2,
     181                shapes: [ 'star' ],
     182            },
     183            {
     184                particleCount: 10,
     185                scalar: 0.75,
     186                shapes: [ 'circle' ],
     187            },
     188        ];
     189
     190        // Tripple check if the confetti options are an array and not undefined.
     191        if (
     192            'undefined' !==
     193                typeof progressPlannerSuggestedTasks.confettiOptions &&
     194            true ===
     195                Array.isArray(
     196                    progressPlannerSuggestedTasks.confettiOptions
     197                ) &&
     198            progressPlannerSuggestedTasks.confettiOptions.length
     199        ) {
     200            confettiOptions = progressPlannerSuggestedTasks.confettiOptions;
     201        }
     202
     203        for ( const value of confettiOptions ) {
     204            confetti( {
     205                ...prplConfettiDefaults,
     206                ...value,
     207            } );
     208        }
    170209    };
    171210
     
    192231            .querySelectorAll( '.prpl-suggested-task-celebrated' )
    193232            .forEach( ( item ) => {
    194                 const taskId = item.getAttribute( 'data-task-id' );
    195 
    196                 const request = wp.ajax.post(
    197                     'progress_planner_suggested_task_action',
     233                const taskId = item.getAttribute( 'data-task-id' ),
     234                    type = item.getAttribute( 'data-task-type' );
     235                const el = document.querySelector(
     236                    `.prpl-suggested-task[data-task-id="${ taskId }"]`
     237                );
     238
     239                if ( el ) {
     240                    el.parentElement.remove();
     241                }
     242
     243                // Remove the task from the pending celebration.
     244                window.progressPlannerSuggestedTasks.tasks.pending_celebration =
     245                    window.progressPlannerSuggestedTasks.tasks.pending_celebration.filter(
     246                        ( id ) => id !== taskId
     247                    );
     248
     249                // Add the task to the completed tasks.
     250                if (
     251                    window.progressPlannerSuggestedTasks.tasks.completed.indexOf(
     252                        taskId
     253                    ) === -1
     254                ) {
     255                    window.progressPlannerSuggestedTasks.tasks.completed.push(
     256                        taskId
     257                    );
     258                }
     259
     260                // Refresh the list.
     261                const event = new CustomEvent(
     262                    'prplMaybeInjectSuggestedTaskEvent',
    198263                    {
    199                         task_id: taskId,
    200                         nonce: progressPlannerSuggestedTask.nonce,
    201                         action_type: 'celebrated',
     264                        detail: {
     265                            taskId,
     266                            type,
     267                        },
    202268                    }
    203269                );
    204                 request.done( () => {
    205                     const el = document.querySelector(
    206                         `.prpl-suggested-task[data-task-id="${ taskId }"]`
    207                     );
    208 
    209                     if ( el ) {
    210                         el.parentElement.remove();
    211                     }
    212 
    213                     // Remove the task from the pending celebration.
    214                     window.progressPlannerSuggestedTasks.tasks.pending_celebration =
    215                         window.progressPlannerSuggestedTasks.tasks.pending_celebration.filter(
    216                             ( id ) => id !== taskId
    217                         );
    218 
    219                     // Add the task to the completed tasks.
    220                     if (
    221                         window.progressPlannerSuggestedTasks.tasks.completed.indexOf(
    222                             taskId
    223                         ) === -1
    224                     ) {
    225                         window.progressPlannerSuggestedTasks.tasks.completed.push(
    226                             taskId
    227                         );
    228                     }
    229 
    230                     // Refresh the list.
    231                     const event = new Event(
    232                         'prplMaybeInjectSuggestedTaskEvent'
    233                     );
    234                     document.dispatchEvent( event );
    235                 } );
     270                document.dispatchEvent( event );
    236271            } );
    237272    }, 2000 );
     
    260295    }
    261296
    262     // Inject items, until we reach the maximum number of items.
    263     while (
    264         progressPlannerCountItems() <
    265             parseInt( progressPlannerSuggestedTasks.maxItems ) &&
    266         progressPlannerGetNextItem()
    267     ) {
    268         progressPlannerInjectNextItem();
    269     }
    270 
    271     const event = new Event( 'prplResizeAllGridItemsEvent' );
     297    // Loop through each type and inject items.
     298    for ( const type in progressPlannerSuggestedTasks.tasks.details ) {
     299        // Inject items, until we reach the maximum number of channel items.
     300        while (
     301            progressPlannerCountItems( type ) <
     302                parseInt(
     303                    progressPlannerSuggestedTasks.maxItemsPerType[ type ]
     304                ) &&
     305            progressPlannerGetNextItemFromType( type )
     306        ) {
     307            progressPlannerInjectNextItem( type );
     308        }
     309    }
     310
     311    const event = new CustomEvent( 'prplResizeAllGridItemsEvent' );
    272312    document.dispatchEvent( event );
    273313} );
     
    422462);
    423463
     464const prplGetRaviGaugeProps = () => {
     465    const gauge = document.getElementById( 'prpl-gauge-ravi' );
     466    if ( ! gauge ) {
     467        return;
     468    }
     469
     470    return {
     471        id: gauge.id,
     472        background: gauge.getAttribute( 'background' ),
     473        color: gauge.getAttribute( 'color' ),
     474        max: gauge.getAttribute( 'data-max' ),
     475        value: gauge.getAttribute( 'data-value' ),
     476        badgeId: gauge.getAttribute( 'data-badge-id' ),
     477    };
     478};
     479
     480const prplUpdateRaviGauge = ( pointsDiff = 0 ) => {
     481    if ( ! pointsDiff ) {
     482        return;
     483    }
     484
     485    const gaugeProps = prplGetRaviGaugeProps();
     486
     487    if ( ! gaugeProps ) {
     488        return;
     489    }
     490
     491    let newValue = parseInt( gaugeProps.value ) + pointsDiff;
     492    newValue = Math.round( newValue );
     493    newValue = Math.max( 0, newValue );
     494    newValue = Math.min( newValue, parseInt( gaugeProps.max ) );
     495
     496    const Gauge = customElements.get( 'prpl-gauge' );
     497    const gauge = new Gauge(
     498        {
     499            max: parseInt( gaugeProps.max ),
     500            value: parseFloat( newValue / parseInt( gaugeProps.max ) ),
     501            background: gaugeProps.background,
     502            color: gaugeProps.color,
     503            maxDeg: '180deg',
     504            start: '270deg',
     505            cutout: '57%',
     506            contentFontSize: 'var(--prpl-font-size-6xl)',
     507            contentPadding:
     508                'var(--prpl-padding) var(--prpl-padding) calc(var(--prpl-padding) * 2) var(--prpl-padding)',
     509            marginBottom: 'var(--prpl-padding)',
     510        },
     511        `<prpl-badge complete="true" badge-id="${ gaugeProps.badgeId }"></prpl-badge>`
     512    );
     513    gauge.id = gaugeProps.id;
     514    gauge.setAttribute( 'background', gaugeProps.background );
     515    gauge.setAttribute( 'color', gaugeProps.color );
     516    gauge.setAttribute( 'data-max', gaugeProps.max );
     517    gauge.setAttribute( 'data-value', newValue );
     518    gauge.setAttribute( 'data-badge-id', gaugeProps.badgeId );
     519
     520    // Replace the old gauge with the new one.
     521    const oldGauge = document.getElementById( gaugeProps.id );
     522    if ( oldGauge ) {
     523        oldGauge.replaceWith( gauge );
     524    }
     525
     526    const oldCounter = document.getElementById(
     527        'prpl-widget-content-ravi-points-number'
     528    );
     529    if ( oldCounter ) {
     530        oldCounter.textContent = newValue + 'pt';
     531    }
     532};
     533
     534// Listen for the event.
     535document.addEventListener(
     536    'prplUpdateRaviGaugeEvent',
     537    ( e ) => {
     538        prplUpdateRaviGauge( e.detail.pointsDiff );
     539    },
     540    false
     541);
     542
    424543// Listen for the event.
    425544document.addEventListener(
    426545    'prplMaybeInjectSuggestedTaskEvent',
    427     () => {
     546    ( e ) => {
     547        const type = e.detail.type;
     548
     549        if ( 'pending_celebration' === type ) {
     550            return;
     551        }
     552
    428553        while (
    429             progressPlannerCountItems() <
    430                 parseInt( progressPlannerSuggestedTasks.maxItems ) &&
    431             progressPlannerGetNextItem()
     554            progressPlannerCountItems( type ) <
     555                parseInt(
     556                    progressPlannerSuggestedTasks.maxItemsPerType[ type ]
     557                ) &&
     558            progressPlannerGetNextItemFromType( type )
    432559        ) {
    433             progressPlannerInjectNextItem();
     560            progressPlannerInjectNextItem( type );
    434561        }
    435562
  • progress-planner/tags/1.0.4/classes/activities/class-suggested-task.php

    r3210975 r3238385  
    5959
    6060        $data = \progress_planner()->get_suggested_tasks()->get_local()->get_data_from_task_id( $this->data_id );
    61         if ( isset( $data['type'] ) && ( 'create-post' === $data['type'] || 'update-post' === $data['type'] ) && isset( $data['long'] ) && true === $data['long'] ) {
     61        if ( isset( $data['type'] ) && ( 'create-post' === $data['type'] || 'review-post' === $data['type'] ) && isset( $data['long'] ) && true === $data['long'] ) {
    6262            $points = 2;
    6363        }
  • progress-planner/tags/1.0.4/classes/admin/class-page-settings.php

    r3226821 r3238385  
    160160
    161161                // Skip if the ID is not set.
    162                 if ( 1 > (int) $page_args['id'] ) {
     162                if ( ! isset( $page_args['id'] ) || 1 > (int) $page_args['id'] ) {
    163163                    continue;
    164164                }
     
    171171        }
    172172
     173        $this->save_settings();
    173174        $this->save_license();
    174175
     
    176177
    177178        \wp_send_json_success( \esc_html__( 'Options stored successfully', 'progress-planner' ) );
     179    }
     180
     181    /**
     182     * Save the settings.
     183     *
     184     * @return void
     185     */
     186    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
     189            : false;
     190
     191        \update_user_meta( \get_current_user_id(), 'prpl_redirect_on_login', (bool) $redirect_on_login );
    178192    }
    179193
     
    205219        // Call the custom API.
    206220        $response = \wp_remote_post(
    207             'https://progressplanner.com',
     221            \progress_planner()->get_remote_server_root_url(),
    208222            [
    209223                'timeout'   => 15,
  • progress-planner/tags/1.0.4/classes/admin/class-page.php

    r3226821 r3238385  
    2929        \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
    3030        \add_action( 'wp_ajax_progress_planner_save_cpt_settings', [ $this, 'save_cpt_settings' ] );
     31        \add_action( 'in_admin_header', [ $this, 'remove_admin_notices' ], PHP_INT_MAX );
    3132    }
    3233
     
    3435     * Get the widgets objects
    3536     *
    36      * @return array<\Progress_Planner\Widget>
     37     * @return array<\Progress_Planner\Widgets\Widget>
    3738     */
    3839    public function get_widgets() {
    3940        $widgets = [
     41            \progress_planner()->get_widgets__suggested_tasks(),
    4042            \progress_planner()->get_widgets__activity_scores(),
    41             \progress_planner()->get_widgets__suggested_tasks(),
    4243            \progress_planner()->get_widgets__todo(),
    4344            \progress_planner()->get_widgets__challenge(),
     
    5152         * Filter the widgets.
    5253         *
    53          * @param array<\Progress_Planner\Widget> $widgets The widgets.
     54         * @param array<\Progress_Planner\Widgets\Widget> $widgets The widgets.
    5455         *
    55          * @return array<\Progress_Planner\Widget>
     56         * @return array<\Progress_Planner\Widgets\Widget>
    5657         */
    5758        return \apply_filters( 'progress_planner_admin_widgets', $widgets );
     
    6364     * @param string $id The widget ID.
    6465     *
    65      * @return \Progress_Planner\Widget|void
     66     * @return \Progress_Planner\Widgets\Widget|void
    6667     */
    6768    public function get_widget( $id ) {
     
    174175            );
    175176        }
     177
     178        $prpl_privacy_policy_accepted = \progress_planner()->is_privacy_policy_accepted();
     179        if ( ! $prpl_privacy_policy_accepted ) {
     180            // Enqueue welcome styles.
     181            \wp_enqueue_style(
     182                'progress-planner-welcome',
     183                PROGRESS_PLANNER_URL . '/assets/css/welcome.css',
     184                [],
     185                \progress_planner()->get_file_version( PROGRESS_PLANNER_DIR . '/assets/css/welcome.css' )
     186            );
     187
     188            // Enqueue onboarding styles.
     189            \wp_enqueue_style(
     190                'progress-planner-onboard',
     191                PROGRESS_PLANNER_URL . '/assets/css/onboard.css',
     192                [],
     193                \progress_planner()->get_file_version( PROGRESS_PLANNER_DIR . '/assets/css/onboard.css' )
     194            );
     195        }
    176196    }
    177197
     
    193213        );
    194214    }
     215
     216    /**
     217     * Remove all admin notices when the user is on the Progress Planner page.
     218     *
     219     * @return void
     220     */
     221    public function remove_admin_notices() {
     222        $current_screen = \get_current_screen();
     223        if ( ! $current_screen ) {
     224            return;
     225        }
     226        if ( ! \in_array(
     227            $current_screen->id,
     228            [
     229                'toplevel_page_progress-planner',
     230                'progress-planner_page_progress-planner-settings',
     231            ],
     232            true
     233        ) ) {
     234            return;
     235        }
     236
     237        \remove_all_actions( 'admin_notices' );
     238    }
    195239}
  • progress-planner/tags/1.0.4/classes/badges/class-monthly.php

    r3226821 r3238385  
    5555
    5656        $activation_date = \progress_planner()->get_base()->get_activation_date();
    57         $start_date      = $activation_date->modify( 'first day of this month' );
     57        if ( $activation_date < new \DateTime( 'first day of November 2024' ) ) { // When badges were introduced.
     58            $start_date = $activation_date->modify( 'first day of November 2024' );
     59        } else {
     60            $start_date = $activation_date->modify( 'first day of this month' );
     61        }
    5862
    5963        // Year when plugin was released.
  • progress-planner/tags/1.0.4/classes/class-base.php

    r3226821 r3238385  
    9999        $this->cached['badges']          = new Badges();
    100100
    101         // Dont add the widget if the privacy policy is not accepted.
    102101        if ( true === $this->is_privacy_policy_accepted() ) {
    103102            $this->cached['settings_page'] = new Admin_Page_Settings();
    104         }
     103
     104            new Plugin_Deactivation();
     105        }
     106
     107        /**
     108         * Redirect on login.
     109         */
     110        \add_action( 'wp_login', [ $this, 'redirect_on_login' ], 10, 2 );
    105111    }
    106112
     
    156162     */
    157163    public function get_placeholder_svg( $width = 1200, $height = 675 ) {
    158         return 'data:image/svg+xml;base64,' . base64_encode( sprintf( '<svg width="%1$d" height="%2$d" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="%3$d" height="%4$d" style="fill:#F6F5FB;stroke:#534786;stroke-width:2"/><text x="50%%" y="50%%" font-size="20" text-anchor="middle" alignment-baseline="middle" font-family="monospace" fill="#534786">progressplanner.com</text></svg>', $width, $height, ( $width - 4 ), ( $height - 4 ) ) );
     164        return 'data:image/svg+xml;base64,' . base64_encode( sprintf( '<svg width="%1$d" height="%2$d" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="%3$d" height="%4$d" style="fill:#F6F5FB;stroke:#534786;stroke-width:2"/><text x="50%%" y="50%%" font-size="20" text-anchor="middle" alignment-baseline="middle" font-family="monospace" fill="#534786">progressplanner.com</text></svg>', $width, $height, ( $width - 4 ), ( $height - 4 ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
    159165    }
    160166
     
    370376            && 'valid' === \get_option( 'progress_planner_pro_license_status' );
    371377    }
     378
     379    /**
     380     * Redirect on login.
     381     *
     382     * @param string   $user_login The user login.
     383     * @param \WP_User $user The user object.
     384     *
     385     * @return void
     386     */
     387    public function redirect_on_login( $user_login, $user ) {
     388        // Check if the $user can `manage_options`.
     389        if ( ! $user->has_cap( 'manage_options' ) ) {
     390            return;
     391        }
     392
     393        // Check if the user has the `prpl_redirect_on_login` meta.
     394        if ( ! \get_user_meta( $user->ID, 'prpl_redirect_on_login', true ) ) {
     395            return;
     396        }
     397
     398        // Redirect to the Progress Planner dashboard.
     399        \wp_safe_redirect( \admin_url( 'admin.php?page=progress-planner' ) );
     400        exit;
     401    }
    372402}
    373403// phpcs:enable Generic.Commenting.Todo
  • progress-planner/tags/1.0.4/classes/class-playground.php

    r3156816 r3238385  
    153153            $this->create_random_post();
    154154        }
     155        for ( $i = 0; $i < 5; $i++ ) {
     156            $this->create_random_post( true, 'page' );
     157        }
    155158        // One post for today.
    156159        $this->create_random_post( false );
     
    160163     * Create a random post.
    161164     *
    162      * @param bool $random_date Whether to use a random date or not.
     165     * @param bool   $random_date Whether to use a random date or not.
     166     * @param string $post_type   The post type to create.
    163167     *
    164168     * @return int Post ID.
    165169     */
    166     private function create_random_post( $random_date = true ) {
     170    private function create_random_post( $random_date = true, $post_type = 'post' ) {
    167171        $postarr = [
    168172            'post_title'   => str_replace( '.', '', $this->create_random_string( 5 ) ),
    169173            'post_content' => $this->create_random_string( wp_rand( 200, 500 ) ),
    170174            'post_status'  => 'publish',
    171             'post_type'    => 'post',
     175            'post_type'    => $post_type,
    172176            'post_date'    => $this->get_random_date_last_12_months(),
    173177        ];
  • progress-planner/tags/1.0.4/classes/class-rest-api-stats.php

    r3217671 r3238385  
    167167        $data['todo'] = $pending_todo_items;
    168168
     169        $ravis_recommendations   = \progress_planner()->get_suggested_tasks()->get_tasks();
     170        $data['recommendations'] = [];
     171        foreach ( $ravis_recommendations as $recommendation ) {
     172            $data['recommendations'][] = [
     173                'id'    => $recommendation['task_id'],
     174                'title' => $recommendation['title'],
     175                'url'   => isset( $recommendation['url'] ) ? $recommendation['url'] : '',
     176            ];
     177        }
     178
    169179        $data['plugin_url'] = \esc_url( \get_admin_url( null, 'admin.php?page=progress-planner' ) );
    170180
     
    180190     */
    181191    public function validate_token( $token ) {
    182         $token       = str_replace( 'token/', '', $token );
     192        $token = str_replace( 'token/', '', $token );
     193        if ( \progress_planner()->is_pro_site() && $token === \get_option( 'progress_planner_pro_license_key' ) ) {
     194            return true;
     195        }
    183196        $license_key = \get_option( 'progress_planner_license_key', false );
    184197        if ( ! $license_key || 'no-license' === $license_key ) {
  • progress-planner/tags/1.0.4/classes/class-suggested-tasks.php

    r3210975 r3238385  
    1111use Progress_Planner\Suggested_Tasks\Remote_Tasks;
    1212use Progress_Planner\Activities\Suggested_Task;
     13use Progress_Planner\Suggested_Tasks\Local_Tasks\Local_Task_Factory;
    1314
    1415/**
     
    5253            \add_action( 'init', [ $this, 'init' ], 1 );
    5354        }
     55
     56        // Add the automatic updates complete action.
     57        \add_action( 'automatic_updates_complete', [ $this, 'on_automatic_updates_complete' ] );
    5458    }
    5559
     
    6771
    6872        foreach ( $completed_tasks as $task_id ) {
     73            // Change the task status to pending celebration.
    6974            $this->mark_task_as_pending_celebration( $task_id );
    7075
    7176            // Insert an activity.
    72             $activity          = new Suggested_Task();
    73             $activity->type    = 'completed';
    74             $activity->data_id = (string) $task_id;
    75             $activity->date    = new \DateTime();
    76             $activity->user_id = \get_current_user_id();
    77             $activity->save();
    78 
    79             // Allow other classes to react to the completion of a suggested task.
    80             do_action( 'progress_planner_suggested_task_completed', $task_id );
     77            $this->insert_activity( $task_id );
     78        }
     79    }
     80
     81    /**
     82     * Insert an activity.
     83     *
     84     * @param string $task_id The task ID.
     85     *
     86     * @return void
     87     */
     88    public function insert_activity( $task_id ) {
     89        // Insert an activity.
     90        $activity          = new Suggested_Task();
     91        $activity->type    = 'completed';
     92        $activity->data_id = (string) $task_id;
     93        $activity->date    = new \DateTime();
     94        $activity->user_id = \get_current_user_id();
     95        $activity->save();
     96
     97        // Allow other classes to react to the completion of a suggested task.
     98        do_action( 'progress_planner_suggested_task_completed', $task_id );
     99    }
     100
     101    /**
     102     * If done via automatic updates, the "core update" task should be marked as "completed" (and skip "pending celebration" status).
     103     *
     104     * @param array $update_results The update results.
     105     *
     106     * @return void
     107     */
     108    public function on_automatic_updates_complete( $update_results ) {
     109
     110        $pending_tasks = $this->local->get_pending_tasks(); // @phpstan-ignore-line method.nonObject
     111
     112        if ( empty( $pending_tasks ) ) {
     113            return;
     114        }
     115
     116        // TODO: Get this from task provider.
     117        $update_core_task_id = 'update-core';
     118
     119        foreach ( $pending_tasks as $task_id ) {
     120            $task_object = ( new Local_Task_Factory( $task_id ) )->get_task();
     121            $task_data   = $task_object->get_data();
     122
     123            if ( $task_data['type'] === $update_core_task_id && \gmdate( 'YW' ) === $task_data['year_week'] ) {
     124                // Remove from local (pending tasks).
     125                $this->local->remove_pending_task( $task_id ); // @phpstan-ignore-line method.nonObject
     126
     127                // Change the task status to completed.
     128                $this->mark_task_as_completed( $task_id );
     129
     130                // Insert an activity.
     131                $this->insert_activity( $task_id );
     132                break;
     133            }
    81134        }
    82135    }
     
    470523
    471524    /**
     525     * Check if a task was completed.
     526     *
     527     * @param string $task_id The task ID.
     528     *
     529     * @return bool
     530     */
     531    public function was_task_completed( $task_id ) {
     532        return true === $this->check_task_condition(
     533            [
     534                'type'    => 'completed',
     535                'task_id' => $task_id,
     536            ]
     537        );
     538    }
     539
     540    /**
    472541     * Handle the suggested task action.
    473542     *
     
    489558        switch ( $action ) {
    490559            case 'complete':
     560                // It's local task, remove it from pending tasks.
     561                if ( false === strpos( $task_id, 'remote-task' ) ) {
     562                    $this->local->remove_pending_task( $task_id ); // @phpstan-ignore-line method.nonObject
     563                }
     564
     565                // Mark the task as completed.
    491566                $this->mark_task_as( 'completed', $task_id );
     567
     568                // Insert an activity.
     569                $this->insert_activity( $task_id );
    492570                $updated = true;
    493571                break;
     
    498576                break;
    499577
    500             case 'celebrated':
    501                 // We dont need to do anything here, since the task is already marked as completed.
    502                 $updated = true;
    503                 break;
    504 
    505578            default:
    506579                \wp_send_json_error( [ 'message' => \esc_html__( 'Invalid action.', 'progress-planner' ) ] );
  • progress-planner/tags/1.0.4/classes/suggested-tasks/class-local-tasks-manager.php

    r3217671 r3238385  
    1010use Progress_Planner\Suggested_Tasks\Local_Tasks\Local_Task_Factory;
    1111use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Content_Create;
    12 use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Content_Update;
     12use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Content_Review;
    1313use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Core_Update;
     14use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Core_Blogdescription;
    1415use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Settings_Saved;
    15 
     16use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Debug_Display;
     17use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Sample_Page;
     18use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Hello_World;
     19use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Core_Siteicon;
    1620
    1721/**
     
    4549        $this->task_providers = [
    4650            new Content_Create(),
    47             new Content_Update(),
     51            new Content_Review(),
    4852            new Core_Update(),
     53            new Core_Blogdescription(),
    4954            new Settings_Saved(),
     55            new Debug_Display(),
     56            new Sample_Page(),
     57            new Hello_World(),
     58            new Core_Siteicon(),
    5059        ];
    5160
     
    8897     * Get a task provider by its type.
    8998     *
    90      * @param string $provider_type The provider type.
     99     * @param string $provider_id The provider ID.
    91100     *
    92101     * @return \Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Local_Tasks_Interface|null
    93102     */
    94     public function get_task_provider( $provider_type ) {
     103    public function get_task_provider( $provider_id ) {
    95104        foreach ( $this->task_providers as $provider_instance ) {
    96             if ( $provider_instance->get_provider_type() === $provider_type ) {
     105            if ( $provider_instance->get_provider_id() === $provider_id ) {
    97106                return $provider_instance;
    98107            }
     
    156165    public function evaluate_task( $task_id ) {
    157166        $task_object   = ( new Local_Task_Factory( $task_id ) )->get_task();
    158         $task_provider = $this->get_task_provider( $task_object->get_provider_type() );
     167        $task_provider = $this->get_task_provider( $task_object->get_provider_id() );
    159168
    160169        if ( ! $task_provider ) {
     
    174183    public function get_task_details( $task_id ) {
    175184        $task_object   = ( new Local_Task_Factory( $task_id ) )->get_task();
    176         $task_provider = $this->get_task_provider( $task_object->get_provider_type() );
     185        $task_provider = $this->get_task_provider( $task_object->get_provider_id() );
    177186
    178187        if ( ! $task_provider ) {
     
    262271                $task_data   = $task_object->get_data();
    263272
     273                // If the task was already completed, remove it.
     274                if ( true === \progress_planner()->get_suggested_tasks()->was_task_completed( $task_data['task_id'] ) ) {
     275                    return false;
     276                }
     277
    264278                if ( isset( $task_data['year_week'] ) ) {
    265279                    return \gmdate( 'YW' ) === $task_data['year_week'];
    266280                }
    267281
     282                // We have changed type name, so we need to remove all tasks of the old type.
     283                if ( isset( $task_data['type'] ) && 'update-post' === $task_data['type'] ) {
     284                    return false;
     285                }
     286
    268287                return true;
    269288            }
  • progress-planner/tags/1.0.4/classes/suggested-tasks/class-remote-tasks.php

    r3226821 r3238385  
    4141                continue;
    4242            }
     43
     44            // If the task with this id is completed, don't add a task.
     45            if ( true === \progress_planner()->get_suggested_tasks()->was_task_completed( "remote-task-{$item['task_id']}" ) ) {
     46                continue;
     47            }
     48
     49            // TODO: Maybe skip task which don't have type defined (to not allow wrongly defined 3rd party tasks to override default type).
     50            $item['type']    = 'remote-' . ( isset( $item['type'] ) ? $item['type'] : 'default' );
    4351            $item['task_id'] = "remote-task-{$item['task_id']}";
    4452            $items[]         = $item;
  • progress-planner/tags/1.0.4/classes/suggested-tasks/local-tasks/class-local-task-factory.php

    r3217671 r3238385  
    3838        // Parse simple format, e.g. 'update-core-202449'.
    3939        if ( ! str_contains( $this->task_id, '|' ) ) {
     40
    4041            $last_pos = strrpos( $this->task_id, '-' );
    41             if ( false === $last_pos ) {
    42                 return new Task_Local( [ 'task_id' => $this->task_id ] );
     42
     43            // Check if the task ID ends with a '-12345' or not.
     44            if ( $last_pos === false || ! preg_match( '/-\d+$/', $this->task_id ) ) {
     45                return new Task_Local(
     46                    [
     47                        'task_id' => $this->task_id,
     48                        'type'    => $this->task_id,
     49                    ]
     50                );
    4351            }
    4452
     
    5058            return new Task_Local(
    5159                [
     60                    'task_id'        => $this->task_id,
    5261                    'type'           => $type,
    5362                    $task_suffix_key => $task_suffix,
  • progress-planner/tags/1.0.4/classes/suggested-tasks/local-tasks/class-task-local.php

    r3210975 r3238385  
    4848    /**
    4949     * Alias for get_type().
     50     * For compatibility with the old provider system.
    5051     *
    5152     * @return string
    5253     */
    53     public function get_provider_type() {
     54    public function get_provider_id() {
    5455        return $this->get_type();
    5556    }
  • progress-planner/tags/1.0.4/classes/suggested-tasks/local-tasks/providers/class-content-abstract.php

    r3210975 r3238385  
    1919     */
    2020    protected $capability = 'edit_others_posts';
     21
     22    /**
     23     * The provider type.
     24     *
     25     * @var string
     26     */
     27    const TYPE = 'writing';
    2128
    2229    /**
  • progress-planner/tags/1.0.4/classes/suggested-tasks/local-tasks/providers/class-content-create.php

    r3210975 r3238385  
    2020     * @var string
    2121     */
    22     const TYPE = 'create-post';
     22    const ID = 'create-post';
     23
     24    /**
     25     * The provider type.
     26     *
     27     * @var string
     28     */
     29    const TYPE = 'content-new';
    2330
    2431    /**
     
    2835     */
    2936    const ITEMS_TO_INJECT = 2;
    30 
    31     /**
    32      * Get the provider ID.
    33      *
    34      * @return string
    35      */
    36     public function get_provider_type() {
    37         return self::TYPE;
    38     }
    3937
    4038    /**
     
    8886
    8987        // If the task with this length and id is completed, don't add a task.
    90         if ( true === \progress_planner()->get_suggested_tasks()->check_task_condition(
    91             [
    92                 'type'    => 'completed',
    93                 'task_id' => $task_id,
    94             ]
    95         ) ) {
     88        if ( true === \progress_planner()->get_suggested_tasks()->was_task_completed( $task_id ) ) {
    9689            return [];
    9790        }
     
    157150     * @return array
    158151     */
    159     public function get_task_details( $task_id ) {
     152    public function get_task_details( $task_id = '' ) {
     153
     154        if ( ! $task_id ) {
     155            return [];
     156        }
    160157
    161158        $data = $this->get_data_from_task_id( $task_id );
     
    168165            'parent'      => 0,
    169166            'priority'    => 'medium',
    170             'type'        => 'writing',
     167            'type'        => $this->get_provider_type(),
    171168            'points'      => isset( $data['long'] ) && $data['long'] ? 2 : 1,
    172169            'url'         => \esc_url( \admin_url( 'post-new.php?post_type=post' ) ),
  • progress-planner/tags/1.0.4/classes/suggested-tasks/local-tasks/providers/class-core-update.php

    r3217671 r3238385  
    1212 * Add tasks for Core updates.
    1313 */
    14 class Core_Update extends Local_Tasks_Abstract {
     14class Core_Update extends Local_Repetitive_Tasks_Abstract {
    1515
    1616    /**
     
    2222
    2323    /**
     24     * The provider type.
     25     *
     26     * @var string
     27     */
     28    const TYPE = 'maintenance';
     29
     30    /**
    2431     * The provider ID.
    2532     *
    2633     * @var string
    2734     */
    28     const TYPE = 'update-core';
    29 
    30     /**
    31      * Get the provider ID.
    32      *
    33      * @return string
    34      */
    35     public function get_provider_type() {
    36         return self::TYPE;
    37     }
     35    const ID = 'update-core';
    3836
    3937    /**
     
    4442     * @return bool|string
    4543     */
    46     public function evaluate_task( $task_id ) {
    4744
    48         // Early bail if the user does not have the capability to update the core, since \wp_get_update_data()['counts']['total'] will return 0.
    49         if ( ! $this->capability_required() ) {
    50             return false;
    51         }
    52 
     45    /**
     46     * Check if the task condition is met.
     47     *
     48     * @return bool
     49     */
     50    public function check_task_condition() {
    5351        // Without this \wp_get_update_data() might not return correct data for the core updates (depending on the timing).
    5452        if ( ! function_exists( 'get_core_updates' ) ) {
     
    5654        }
    5755
    58         $task_object = ( new Local_Task_Factory( $task_id ) )->get_task();
    59         $task_data   = $task_object->get_data();
    60 
    61         if ( $task_data['type'] === self::TYPE && \gmdate( 'YW' ) === $task_data['year_week'] && 0 === \wp_get_update_data()['counts']['total'] ) {
    62             return $task_id;
    63         }
    64         return false;
    65     }
    66 
    67     /**
    68      * Get an array of tasks to inject.
    69      *
    70      * @return array
    71      */
    72     public function get_tasks_to_inject() {
    73 
    74         // Early bail if the user does not have the capability to update the core or if the task is snoozed.
    75         if ( true === $this->is_task_type_snoozed() || ! $this->capability_required() ) {
    76             return [];
    77         }
    78 
    79         // Without this \wp_get_update_data() might not return correct data for the core updates (depending on the timing).
    80         if ( ! function_exists( 'get_core_updates' ) ) {
    81             require_once ABSPATH . 'wp-admin/includes/update.php'; // @phpstan-ignore requireOnce.fileNotFound
    82         }
    83 
    84         // If all updates are performed, do not add the task.
    85         if ( 0 === \wp_get_update_data()['counts']['total'] ) {
    86             return [];
    87         }
    88 
    89         return [
    90             $this->get_task_details( self::TYPE . '-' . \gmdate( 'YW' ) ),
    91         ];
     56        return 0 === \wp_get_update_data()['counts']['total'] ? true : false;
    9257    }
    9358
     
    10166    public function get_task_details( $task_id ) {
    10267
     68        if ( ! $task_id ) {
     69            $task_id = $this->get_provider_id() . '-' . \gmdate( 'YW' );
     70        }
     71
    10372        return [
    10473            'task_id'     => $task_id,
     
    10675            'parent'      => 0,
    10776            'priority'    => 'high',
    108             'type'        => 'maintenance',
     77            'type'        => $this->get_provider_type(),
    10978            'points'      => 1,
    11079            'url'         => $this->capability_required() ? \esc_url( \admin_url( 'update-core.php' ) ) : '',
     
    11281        ];
    11382    }
    114 
    115     /**
    116      * Get the data from a task-ID.
    117      *
    118      * @param string $task_id The task ID.
    119      *
    120      * @return array The data.
    121      */
    122     public function get_data_from_task_id( $task_id ) {
    123         $data = [
    124             'type' => self::TYPE,
    125             'id'   => $task_id,
    126         ];
    127 
    128         return $data;
    129     }
    130 
    131     /**
    132      * Check if a task type is snoozed.
    133      *
    134      * @return bool
    135      */
    136     public function is_task_type_snoozed() {
    137         $snoozed = \progress_planner()->get_suggested_tasks()->get_snoozed_tasks();
    138         if ( ! \is_array( $snoozed ) || empty( $snoozed ) ) {
    139             return false;
    140         }
    141 
    142         foreach ( $snoozed as $task ) {
    143             $task_object = ( new Local_Task_Factory( $task['id'] ) )->get_task();
    144             $task_data   = $task_object->get_data();
    145             if ( $task_data['type'] === self::TYPE ) {
    146                 return true;
    147             }
    148         }
    149 
    150         return false;
    151     }
    15283}
  • progress-planner/tags/1.0.4/classes/suggested-tasks/local-tasks/providers/class-local-tasks-abstract.php

    r3210975 r3238385  
    99
    1010use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Local_Tasks_Interface;
     11use Progress_Planner\Suggested_Tasks\Local_Tasks\Local_Task_Factory;
    1112
    1213/**
     
    1617
    1718    /**
     19     * The type of the task.
     20     *
     21     * @var string
     22     */
     23    const TYPE = '';
     24
     25    /**
     26     * The ID of the task.
     27     *
     28     * @var string
     29     */
     30    const ID = '';
     31
     32    /**
    1833     * The capability required to perform the task.
    1934     *
     
    2136     */
    2237    protected $capability = 'manage_options';
     38
     39    /**
     40     * Get the provider type.
     41     *
     42     * @return string
     43     */
     44    public function get_provider_type() {
     45        return static::TYPE;
     46    }
     47
     48    /**
     49     * Get the provider ID.
     50     *
     51     * @return string
     52     */
     53    public function get_provider_id() {
     54        return static::ID;
     55    }
    2356
    2457    /**
     
    3265            : true;
    3366    }
     67
     68    /**
     69     * Get the data from a task-ID.
     70     *
     71     * @param string $task_id The task ID (unused here).
     72     *
     73     * @return array The data.
     74     */
     75    public function get_data_from_task_id( $task_id ) {
     76        $data = [
     77            'type' => $this->get_provider_id(),
     78            'id'   => $task_id,
     79        ];
     80
     81        return $data;
     82    }
     83
     84    /**
     85     * Check if a task type is snoozed.
     86     *
     87     * @return bool
     88     */
     89    public function is_task_type_snoozed() {
     90        $snoozed = \progress_planner()->get_suggested_tasks()->get_snoozed_tasks();
     91        if ( ! \is_array( $snoozed ) || empty( $snoozed ) ) {
     92            return false;
     93        }
     94
     95        foreach ( $snoozed as $task ) {
     96            $task_object = ( new Local_Task_Factory( $task['id'] ) )->get_task();
     97            $provider_id = $task_object->get_provider_id();
     98
     99            if ( $provider_id === $this->get_provider_id() ) {
     100                return true;
     101            }
     102        }
     103
     104        return false;
     105    }
    34106}
  • progress-planner/tags/1.0.4/classes/suggested-tasks/local-tasks/providers/class-local-tasks-interface.php

    r3210975 r3238385  
    4848
    4949    /**
     50     * Get the provider type.
     51     *
     52     * @return string
     53     */
     54    public function get_provider_type();
     55
     56    /**
    5057     * Get the provider ID.
    5158     *
    5259     * @return string
    5360     */
    54     public function get_provider_type();
     61    public function get_provider_id();
    5562
    5663    /**
  • progress-planner/tags/1.0.4/classes/suggested-tasks/local-tasks/providers/class-settings-saved.php

    r3210975 r3238385  
    1111 * Add tasks for settings saved.
    1212 */
    13 class Settings_Saved extends Local_Tasks_Abstract {
     13class Settings_Saved extends Local_OneTime_Tasks_Abstract {
     14
     15    /**
     16     * The provider type.
     17     *
     18     * @var string
     19     */
     20    const TYPE = 'configuration';
    1421
    1522    /**
     
    1825     * @var string
    1926     */
    20     const TYPE = 'settings-saved';
     27    const ID = 'settings-saved';
    2128
    2229    /**
    23      * Get the provider ID.
     30     * Check if the task condition is met.
    2431     *
    25      * @return string
     32     * @return bool
    2633     */
    27     public function get_provider_type() {
    28         return self::TYPE;
    29     }
    30 
    31     /**
    32      * Evaluate a task.
    33      *
    34      * @param string $task_id The task ID.
    35      *
    36      * @return bool|string
    37      */
    38     public function evaluate_task( $task_id ) {
    39 
    40         // Early bail if the user does not have the capability to manage options.
    41         if ( ! $this->capability_required() ) {
    42             return false;
    43         }
    44 
    45         if ( 0 === strpos( $task_id, self::TYPE ) && false !== \get_option( 'progress_planner_pro_license_key', false ) ) {
    46             return $task_id;
    47         }
    48         return false;
    49     }
    50 
    51     /**
    52      * Get an array of tasks to inject.
    53      *
    54      * @return array
    55      */
    56     public function get_tasks_to_inject() {
    57 
    58         // Early bail if the user does not have the capability to manage options or if the task is snoozed.
    59         if ( true === $this->is_task_type_snoozed() || ! $this->capability_required() ) {
    60             return [];
    61         }
    62 
     34    public function check_task_condition() {
    6335        $prpl_pro_license_key = \get_option( 'progress_planner_pro_license_key', false );
    6436
    65         if ( false !== $prpl_pro_license_key ) {
    66             return [];
    67         }
    68 
    69         $task_id = self::TYPE . '-' . \gmdate( 'YW' );
    70 
    71         // If the task with this id is completed, don't add a task.
    72         if ( true === \progress_planner()->get_suggested_tasks()->check_task_condition(
    73             [
    74                 'type'    => 'completed',
    75                 'task_id' => $task_id,
    76             ]
    77         ) ) {
    78             return [];
    79         }
    80 
    81         return [
    82             $this->get_task_details( self::TYPE . '-' . \gmdate( 'YW' ) ),
    83         ];
     37        return false !== $prpl_pro_license_key ? true : false;
    8438    }
    8539
     
    9145     * @return array
    9246     */
    93     public function get_task_details( $task_id ) {
     47    public function get_task_details( $task_id = '' ) {
     48
     49        if ( ! $task_id ) {
     50            $task_id = $this->get_provider_id();
     51        }
    9452
    9553        return [
     
    9856            'parent'      => 0,
    9957            'priority'    => 'high',
    100             'type'        => 'maintenance',
     58            'type'        => $this->get_provider_type(),
    10159            'points'      => 1,
    102             'url'         => \current_user_can( 'manage_options' ) ? \esc_url( \admin_url( 'admin.php?page=progress-planner-settings' ) ) : '',
     60            'url'         => $this->capability_required() ? \esc_url( \admin_url( 'admin.php?page=progress-planner-settings' ) ) : '',
    10361            'description' => '<p>' . \esc_html__( 'Head over to the settings page and fill in the required information.', 'progress-planner' ) . '</p>',
    10462        ];
    10563    }
    106 
    107     /**
    108      * Get the data from a task-ID.
    109      *
    110      * @param string $task_id The task ID.
    111      *
    112      * @return array The data.
    113      */
    114     public function get_data_from_task_id( $task_id ) {
    115         $data = [
    116             'type' => self::TYPE,
    117             'id'   => $task_id,
    118         ];
    119 
    120         return $data;
    121     }
    122 
    123     /**
    124      * Check if a task type is snoozed.
    125      *
    126      * @return bool
    127      */
    128     public function is_task_type_snoozed() {
    129         $snoozed = \progress_planner()->get_suggested_tasks()->get_snoozed_tasks();
    130         if ( ! \is_array( $snoozed ) || empty( $snoozed ) ) {
    131             return false;
    132         }
    133 
    134         foreach ( $snoozed as $task ) {
    135             if ( self::TYPE === $task['id'] ) {
    136                 return true;
    137             }
    138         }
    139 
    140         return false;
    141     }
    14264}
  • progress-planner/tags/1.0.4/classes/widgets/class-activity-scores.php

    r3226821 r3238385  
    88namespace Progress_Planner\Widgets;
    99
    10 use Progress_Planner\Widget;
    1110use Progress_Planner\Goals\Goal_Recurring;
    1211use Progress_Planner\Goals\Goal;
  • progress-planner/tags/1.0.4/classes/widgets/class-badge-streak.php

    r3210975 r3238385  
    77
    88namespace Progress_Planner\Widgets;
    9 
    10 use Progress_Planner\Widget;
    119
    1210/**
  • progress-planner/tags/1.0.4/classes/widgets/class-challenge.php

    r3226821 r3238385  
    1111 * Challenge class.
    1212 */
    13 final class Challenge extends \Progress_Planner\Widget {
     13final class Challenge extends Widget {
    1414
    1515    /**
  • progress-planner/tags/1.0.4/classes/widgets/class-latest-badge.php

    r3210975 r3238385  
    77
    88namespace Progress_Planner\Widgets;
    9 
    10 use Progress_Planner\Widget;
    119
    1210/**
  • progress-planner/tags/1.0.4/classes/widgets/class-published-content.php

    r3210975 r3238385  
    77
    88namespace Progress_Planner\Widgets;
    9 
    10 use Progress_Planner\Widget;
    119
    1210/**
  • progress-planner/tags/1.0.4/classes/widgets/class-suggested-tasks.php

    r3217671 r3238385  
    88namespace Progress_Planner\Widgets;
    99
    10 use Progress_Planner\Widget;
    1110use Progress_Planner\Badges\Monthly;
    1211use Progress_Planner\Suggested_Tasks\Local_Tasks\Local_Task_Factory;
     
    2322     */
    2423    protected $id = 'suggested-tasks';
     24
     25    /**
     26     * The tasks.
     27     *
     28     * @var array|null
     29     */
     30    protected $pending_tasks = null;
    2531
    2632    /**
     
    5561
    5662        $pending_celebration = \progress_planner()->get_suggested_tasks()->get_pending_celebration();
     63        $pending_tasks       = $this->get_pending_tasks();
    5764        $deps                = [
    5865            'progress-planner-todo',
     
    6370
    6471        // Check if need to load confetti.
    65         if ( ! empty( $pending_celebration ) ) {
     72        if ( isset( $pending_tasks['content-update'] ) || ! empty( $pending_celebration ) ) {
    6673            $deps[] = 'particles-confetti';
    6774        } else {
     
    9097        $handle = 'progress-planner-' . $this->id;
    9198
    92         $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
    93 
    9499        // Enqueue the script.
    95100        \wp_enqueue_script( $handle );
     
    99104
    100105        // Get pending tasks.
    101         $tasks['details'] = \progress_planner()->get_suggested_tasks()->get_tasks();
     106        $tasks['details'] = $this->get_pending_tasks();
    102107
    103108        // Insert the pending celebration tasks as high priority tasks, so they are shown always.
     
    105110
    106111            $task_object   = ( new Local_Task_Factory( $task_id ) )->get_task();
    107             $task_provider = \progress_planner()->get_suggested_tasks()->get_local()->get_task_provider( $task_object->get_provider_type() );
    108 
    109             if ( $task_provider->capability_required() ) {
     112            $task_provider = \progress_planner()->get_suggested_tasks()->get_local()->get_task_provider( $task_object->get_provider_id() );
     113
     114            if ( $task_provider && $task_provider->capability_required() ) {
    110115                $task_details = \progress_planner()->get_suggested_tasks()->get_local()->get_task_details( $task_id );
    111116
     
    113118                    $task_details['priority'] = 'high'; // Celebrate tasks are always on top.
    114119                    $task_details['action']   = 'celebrate';
    115                     $tasks['details'][]       = $task_details;
     120                    $task_details['type']     = 'pending_celebration';
     121
     122                    if ( ! isset( $tasks['details']['pending_celebration'] ) ) {
     123                        $tasks['details']['pending_celebration'] = [];
     124                    }
     125
     126                    $tasks['details']['pending_celebration'][] = $task_details;
    116127                }
    117128
     
    119130                \progress_planner()->get_suggested_tasks()->transition_task_status( $task_id, 'pending_celebration', 'completed' );
    120131            }
     132        }
     133
     134        $max_items_per_type = [];
     135        foreach ( $tasks['details'] as $type => $items ) {
     136            $max_items_per_type[ $type ] = $type === 'content-update' ? 2 : 1;
     137        }
     138
     139        // We want all pending_celebration' tasks to be shown.
     140        if ( isset( $max_items_per_type['pending_celebration'] ) ) {
     141            $max_items_per_type['pending_celebration'] = 99;
     142        }
     143
     144        // Check if current date is between Feb 12-16.
     145        $confetti_options = [];
     146        $year             = \gmdate( 'Y' );
     147        $current_date     = $year . '-' . \gmdate( 'm-d' );
     148
     149        // TODO: GET params just for testing.
     150        $start_date = $year . '-' . ( isset( $_GET['start_date'] ) ? \sanitize_text_field( \wp_unslash( $_GET['start_date'] ) ) : '02-12' );
     151        $end_date   = $year . '-' . ( isset( $_GET['end_date'] ) ? \sanitize_text_field( \wp_unslash( $_GET['end_date'] ) ) : '02-16' );
     152
     153        if ( $current_date >= $start_date && $current_date <= $end_date ) {
     154            $confetti_options = [
     155                [
     156                    'particleCount' => 50,
     157                    'scalar'        => 2.2,
     158                    'shapes'        => [ 'heart' ],
     159                    'colors'        => [ 'FFC0CB', 'FF69B4', 'FF1493', 'C71585' ],
     160                ],
     161                [
     162                    'particleCount' => 20,
     163                    'scalar'        => 3.2,
     164                    'shapes'        => [ 'heart' ],
     165                    'colors'        => [ 'FFC0CB', 'FF69B4', 'FF1493', 'C71585' ],
     166                ],
     167            ];
    121168        }
    122169
     
    126173            'progressPlannerSuggestedTasks',
    127174            [
    128                 'ajaxUrl'  => \admin_url( 'admin-ajax.php' ),
    129                 'nonce'    => \wp_create_nonce( 'progress_planner' ),
    130                 'tasks'    => $tasks,
    131                 'maxItems' => $current_screen && 'dashboard' === $current_screen->id ? 3 : 5,
     175                'ajaxUrl'         => \admin_url( 'admin-ajax.php' ),
     176                'nonce'           => \wp_create_nonce( 'progress_planner' ),
     177                'tasks'           => $tasks,
     178                'maxItemsPerType' => apply_filters( 'progress_planner_suggested_tasks_max_items_per_type', $max_items_per_type ),
     179                'confettiOptions' => $confetti_options,
    132180            ]
    133181        );
    134182    }
     183
     184    /**
     185     * Get the tasks.
     186     *
     187     * @return array The tasks.
     188     */
     189    public function get_pending_tasks() {
     190
     191        if ( null === $this->pending_tasks ) {
     192            $tasks         = [];
     193            $pending_tasks = \progress_planner()->get_suggested_tasks()->get_tasks();
     194
     195            // Sort them by type (channel).
     196            foreach ( $pending_tasks as $task ) {
     197
     198                if ( ! isset( $tasks[ $task['type'] ] ) ) {
     199                    $tasks[ $task['type'] ] = [];
     200                }
     201
     202                $tasks[ $task['type'] ][] = $task;
     203            }
     204
     205            $this->pending_tasks = $tasks;
     206        }
     207
     208        return $this->pending_tasks;
     209    }
    135210}
  • progress-planner/tags/1.0.4/classes/widgets/class-todo.php

    r3210975 r3238385  
    77
    88namespace Progress_Planner\Widgets;
    9 
    10 use Progress_Planner\Widget;
    119
    1210/**
  • progress-planner/tags/1.0.4/classes/widgets/class-whats-new.php

    r3210975 r3238385  
    88namespace Progress_Planner\Widgets;
    99
    10 use Progress_Planner\Widget;
    1110use Progress_Planner\Cache;
    1211
  • progress-planner/tags/1.0.4/progress-planner.php

    r3226821 r3238385  
    1010 * Requires at least: 6.3
    1111 * Requires PHP:      7.4
    12  * Version:           1.0.3
     12 * Version:           1.0.4
    1313 * Author:            Team Emilia Projects
    1414 * Author URI:        https://prpl.fyi/about
  • progress-planner/tags/1.0.4/readme.txt

    r3226821 r3238385  
    55Tested up to: 6.7
    66Requires PHP: 7.4
    7 Stable tag: 1.0.3
     7Stable tag: 1.0.4
    88License: GPL3+
    99License URI: https://www.gnu.org/licenses/gpl-3.0.en.html
     
    7878
    7979== Changelog ==
     80
     81= 1.0.4 =
     82
     83Enhancements:
     84
     85* We've moved Ravi's recommendations to the top left of your Progress Planner dashboard. They're the most important thing on there, so we wanted to give it prime placement.
     86* We changed "Update post" to "Review post" / "Review page" and [wrote better instructions for reviewing old posts and pages](https://progressplanner.com/recommendations/review-post/). These tasks now prioritize the most important pages, like your About page, Privacy policy, Contact page and FAQ page.
     87* Added an option to redirect users to the Progress Planner dashboard after login. The WordPress dashboard isn't particularly useful in our eyes, this mind entice you to action more.
     88* Added a plugin-deactivation feedback form (we tell you, because you'll never see it, right? :) ).
     89* Removed the celebration for "Perform all updates" if it was done by WordPress's automatic update. We all love confetti, but when it comes all the time without you doing anything, it loses its value, right? Hence this fix.
     90
     91We've added the following Recommendations from Ravi:
     92
     93* [Setting site icon](https://progressplanner.com/recommendations/set-a-site-icon-aka-favicon/).
     94* [Setting the tagline](https://progressplanner.com/recommendations/set-tagline/).
     95* [Deactivating the display of PHP debug messages](https://progressplanner.com/recommendations/set-wp-debug/).
     96* [Removing the default WP "Hello world" post](https://progressplanner.com/recommendations/delete-the-default-wordpress-hello-world-post/).
     97* [Removing the default WP "Sample page" page](https://progressplanner.com/recommendations/delete-the-default-wordpress-sample-page-post/).
     98
     99Under the hood:
     100
     101* Improvements to the REST-API endpoint for getting stats.
     102* Removed admin notices on the Progress Planner page.
    80103
    81104= 1.0.3 =
  • progress-planner/tags/1.0.4/views/admin-page-settings.php

    r3226821 r3238385  
    3434
    3535    <form id="prpl-settings">
    36         <div class="prpl-column">
    37             <div class="prpl-widget-wrapper">
    38                 <h2 class="prpl-settings-section-title">
    39                     <span class="icon">
    40                         <?php \progress_planner()->the_asset( 'images/icon_pages.svg' ); ?>
    41                     </span>
    42                     <span>
    43                         <?php esc_html_e( 'Your pages', 'progress-planner' ); ?>
    44                     </span>
    45                 </h2>
    46                 <p>
    47                     <?php esc_html_e( 'Let us know if you have following pages.', 'progress-planner' ); ?>
    48                 </p>
    49                 <div class="prpl-pages-list">
    50                     <?php
    51                     foreach ( \progress_planner()->get_admin__page_settings()->get_settings() as $prpl_setting ) {
    52                         \progress_planner()->the_view( "setting/{$prpl_setting['type']}.php", [ 'prpl_setting' => $prpl_setting ] );
    53                     }
    54                     ?>
    55                 </div>
    56             </div>
    57         </div>
    58 
    59         <div class="prpl-column">
    60             <div class="prpl-widget-wrapper">
    61                 <h2 class="prpl-settings-section-license">
    62                     <span>
    63                         <?php \esc_html_e( 'License', 'progress-planner' ); ?>
    64                     </span>
    65                 </h2>
    66                 <div class="prpl-license-keys-wrapper">
    67                     <?php
    68                     $prpl_pro_license        = \get_option( 'progress_planner_pro_license_key', '' );
    69                     $prpl_pro_license_status = \get_option( 'progress_planner_pro_license_status', '' );
    70                     ?>
    71                     <?php if ( empty( $prpl_pro_license ) || 'valid' !== $prpl_pro_license_status ) : ?>
    72                         <p>
    73                             <?php
    74                             printf(
    75                                 // translators: %s is a link to the Pro page, with the text "Progress Planner Pro".
    76                                 \esc_html__( 'Take part in interactive challenges to solve website problems like broken links and sharpen your skills with in-context mini courses. Upgrade to %s!', 'progress-planner' ),
    77                                 '<a href="https://progressplanner.com/pro/" target="_blank">Progress Planner Pro</a>'
    78                             );
    79                             ?>
    80                         </p>
    81                     <?php endif; ?>
    82                     <label for="prpl-setting-pro-license-key">
    83                         <?php \esc_html_e( 'Progress Planner Pro license key', 'progress-planner' ); ?>
    84                     </label>
    85                     <div class="prpl-license-key-wrapper">
    86                         <input
    87                             id="prpl-setting-pro-license-key"
    88                             name="prpl-pro-license-key"
    89                             type="text"
    90                             value="<?php echo \esc_attr( $prpl_pro_license ); ?>"
    91                         />
    92                         <?php if ( ! empty( $prpl_pro_license ) ) : ?>
    93                             <span class="prpl-license-status prpl-license-status-<?php echo ( 'valid' === $prpl_pro_license_status ) ? 'valid' : 'invalid'; ?>">
    94                                 <?php if ( 'valid' === $prpl_pro_license_status ) : ?>
    95                                     <span class="prpl-license-status-valid" title="<?php esc_attr_e( 'Valid', 'progress-planner' ); ?>">
    96                                         <?php \progress_planner()->the_asset( 'images/icon_check_circle.svg' ); ?>
    97                                     </span>
    98                                 <?php else : ?>
    99                                     <span class="prpl-license-status-invalid" title="<?php esc_attr_e( 'Invalid', 'progress-planner' ); ?>">
    100                                         <?php \progress_planner()->the_asset( 'images/icon_exclamation_circle.svg' ); ?>
    101                                     </span>
    102                                 <?php endif; ?>
    103                             </span>
    104                         <?php endif; ?>
    105                     </div>
    106                 </div>
    107             </div>
    108         </div>
     36        <?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' ); ?>
    10939
    11040        <?php wp_nonce_field( 'prpl-settings' ); ?>
  • progress-planner/tags/1.0.4/views/page-widgets/parts/monthly-badges.php

    r3226821 r3238385  
    3535    <?php if ( $prpl_badges ) : ?>
    3636        <?php
    37         $prpl_badges_per_row = 3;
    38         $prpl_badges_count   = count( $prpl_badges );
    39         $prpl_scroll_to_row  = 1;
     37        $prpl_badges_per_row         = 3;
     38        $prpl_badges_count           = count( $prpl_badges );
     39        $prpl_scroll_to_row          = 1;
    4040        $prpl_current_month_badge_id = Monthly::get_badge_id_from_date( new \DateTime() );
    4141        if ( 'popover' !== $prpl_location ) {
  • progress-planner/tags/1.0.4/views/page-widgets/suggested-tasks.php

    r3226821 r3238385  
    1515$prpl_badge  = \progress_planner()->get_badges()->get_badge( Monthly::get_badge_id_from_date( new \DateTime() ) );
    1616?>
     17
     18
     19<div class="prpl-dashboard-widget-suggested-tasks">
     20    <h2 class="prpl-widget-title">
     21        <?php \esc_html_e( 'Ravi\'s Recommendations', 'progress-planner' ); ?>
     22    </h2>
     23
     24    <ul style="display:none"></ul>
     25    <ul class="prpl-suggested-tasks-list"></ul>
     26
     27    <hr>
     28</div>
     29
    1730<?php if ( $prpl_badge ) : ?>
    1831    <h2 class="prpl-widget-title">
     
    4457    </h2>
    4558
    46     <prpl-gauge background="var(--prpl-background-orange)" color="var(--prpl-color-accent-orange)">
     59    <prpl-gauge
     60        id="prpl-gauge-ravi"
     61        background="var(--prpl-background-orange)"
     62        color="var(--prpl-color-accent-orange)"
     63        data-max="<?php echo (int) Monthly::TARGET_POINTS; ?>"
     64        data-value="<?php echo (float) $prpl_widget->get_score(); ?>"
     65        data-badge-id="<?php echo esc_attr( $prpl_badge->get_id() ); ?>"
     66    >
    4767        <progress max="<?php echo (int) Monthly::TARGET_POINTS; ?>" value="<?php echo (float) $prpl_widget->get_score(); ?>">
    4868            <prpl-badge complete="true" badge-id="<?php echo esc_attr( $prpl_badge->get_id() ); ?>"></prpl-badge>
     
    5272    <div class="prpl-widget-content-points">
    5373        <span><?php \esc_html_e( 'Progress monthly badge', 'progress-planner' ); ?></span>
    54         <span class="prpl-widget-content-points-number">
     74        <span id="prpl-widget-content-ravi-points-number" class="prpl-widget-content-points-number">
    5575            <?php echo (int) $prpl_widget->get_score(); ?>pt
    5676        </span>
     
    5979    <hr>
    6080<?php endif; ?>
    61 
    62 <div class="prpl-dashboard-widget-suggested-tasks">
    63     <h2 class="prpl-widget-title">
    64         <?php \esc_html_e( 'Ravi\'s Recommendations', 'progress-planner' ); ?>
    65     </h2>
    66 
    67     <ul style="display:none"></ul>
    68     <ul class="prpl-suggested-tasks-list"></ul>
    69 
    70     <hr>
    71 </div>
    7281
    7382<div class="prpl-widget-content">
  • progress-planner/trunk/CHANGELOG.md

    r3217671 r3238385  
     1= 1.0.4 =
     2
     3Enhancements:
     4
     5* We've moved Ravi's recommendations to the top left of your Progress Planner dashboard. They're the most important thing on there, so we wanted to give it prime placement.
     6* We changed "Update post" to "Review post" / "Review page" and [wrote better instructions for reviewing old posts and pages](https://progressplanner.com/recommendations/review-post/). These tasks now prioritize the most important pages, like your About page, Privacy policy, Contact page and FAQ page.
     7* Added an option to redirect users to the Progress Planner dashboard after login. The WordPress dashboard isn't particularly useful in our eyes, this mind entice you to action more.
     8* Added a plugin-deactivation feedback form (we tell you, because you'll never see it, right? :) ).
     9* Removed the celebration for "Perform all updates" if it was done by WordPress's automatic update. We all love confetti, but when it comes all the time without you doing anything, it loses its value, right? Hence this fix.
     10
     11We've added the following Recommendations from Ravi:
     12
     13* [Setting site icon](https://progressplanner.com/recommendations/set-a-site-icon-aka-favicon/).
     14* [Setting the tagline](https://progressplanner.com/recommendations/set-tagline/).
     15* [Deactivating the display of PHP debug messages](https://progressplanner.com/recommendations/set-wp-debug/).
     16* [Removing the default WP "Hello world" post](https://progressplanner.com/recommendations/delete-the-default-wordpress-hello-world-post/).
     17* [Removing the default WP "Sample page" page](https://progressplanner.com/recommendations/delete-the-default-wordpress-sample-page-post/).
     18
     19Under the hood:
     20
     21* Improvements to the REST-API endpoint for getting stats.
     22* Removed admin notices on the Progress Planner page.
     23
     24= 1.0.3 =
     25
     26Fixed:
     27
     28* Detection of page-types in the settings page.
     29* Properly resetting caches for monthly badges.
     30
     31Enhancements:
     32
     33* Added a new "Challenges" widget to the dashboard.
     34
    135= 1.0.2 =
    236
     
    2256* Assets versioning.
    2357* Duplicate update-core tasks.
    24 * Update old post task being celebrated as completed when post is trashed.
    2558* Information icon for 'Create a long post' task was showing text of 'create a short post' task.
    2659* Numerous other minor bugfixes.
     
    4881= 0.9.5 =
    4982
     83Enhancements:
     84
     85* Added functionality to make it easier to demo the plugin on the WordPress playground.
     86* Improved the onboarding and added a tour of the plugin.
     87
    5088Fixed:
    5189
     
    68106Security:
    69107
    70 * Stricter sanitization & escaping of data in to-do items. Props to [justakazh](https://github.com/justakazh) for reporting through our [PatchStack Vulnerability Disclosure Program](https://patchstack.com/database/vdp/progress-planner).
     108* Stricter sanitization & escaping of data in to-do items.  Props to [justakazh](https://github.com/justakazh) for reporting through our [PatchStack Vulnerability Disclosure Program](https://patchstack.com/database/vdp/progress-planner).
    71109* Restrict access to the plugin's dashboard widgets to users with the `publish_posts` capability.
    72110
  • progress-planner/trunk/assets/css/page-widgets/suggested-tasks.css

    r3210975 r3238385  
    100100
    101101        hr {
    102             display: initial;
     102            display: block;
    103103        }
    104104    }
  • progress-planner/trunk/assets/css/settings-page.css

    r3226821 r3238385  
    6969            width: 1.25em;
    7070            height: 1.25em;
    71 
    72             svg path {
    73                 fill: currentcolor;
    74             }
    7571        }
    7672    }
  • progress-planner/trunk/assets/js/web-components/prpl-badge.js

    r3217671 r3238385  
    1212            complete =
    1313                true === complete && 'true' === this.getAttribute( 'complete' );
     14
     15            badgeId = badgeId || this.getAttribute( 'badge-id' );
    1416            this.innerHTML = `
    1517                <img
    1618                    src="${
    1719                        progressPlannerBadge.remoteServerRootUrl
    18                     }/wp-json/progress-planner-saas/v1/badge-svg/?badge_id=${
    19                         badgeId || this.getAttribute( 'badge-id' )
    20                     }"
     20                    }/wp-json/progress-planner-saas/v1/badge-svg/?badge_id=${ badgeId }"
    2121                    alt="Badge"
    2222                    ${ false === complete ? 'style="filter: grayscale(1);opacity: 0.25;"' : '' }
  • progress-planner/trunk/assets/js/web-components/prpl-suggested-task.js

    r3210975 r3238385  
    1313            taskPoints,
    1414            taskAction = '',
    15             taskUrl = ''
     15            taskUrl = '',
     16            taskDismissable = false,
     17            taskType = ''
    1618        ) {
    1719            // Get parent class properties
     
    2628
    2729            const isRemoteTask = taskId.startsWith( 'remote-task-' );
     30            const isDismissable = taskDismissable || isRemoteTask;
    2831
    2932            const actionButtons = {
     
    5255                            <span class="screen-reader-text">${ progressPlannerSuggestedTask.i18n.snooze }</span>
    5356                        </button>`,
    54                 complete: isRemoteTask
     57                complete: isDismissable
    5558                    ? `<button
    5659                            type="button"
     
    6972
    7073            this.innerHTML = `
    71             <li class="prpl-suggested-task" data-task-id="${ taskId }" data-task-action="${ taskAction }" data-task-url="${ taskUrl }">
     74            <li class="prpl-suggested-task" data-task-id="${ taskId }" data-task-action="${ taskAction }" data-task-url="${ taskUrl }" data-task-type="${ taskType }" data-task-points="${ taskPoints }">
    7275                <h3><span>${ taskHeading }</span></h3>
    7376                <div class="prpl-suggested-task-actions">
     
    278281        runTaskAction = ( taskId, actionType, snoozeDuration ) => {
    279282            taskId = taskId.toString();
     283            const type =
     284                this.querySelector( 'li' ).getAttribute( 'data-task-type' );
    280285
    281286            const data = {
     
    308313                        ) {
    309314                            window.progressPlannerSuggestedTasks.tasks.snoozed.push(
    310                                 taskId
     315                                {
     316                                    id: taskId,
     317                                }
    311318                            );
    312319                        }
     
    321328                        el.setAttribute( 'data-task-action', 'celebrate' );
    322329
     330                        const event = new CustomEvent(
     331                            'prplUpdateRaviGaugeEvent',
     332                            {
     333                                detail: {
     334                                    pointsDiff: parseInt(
     335                                        this.querySelector( 'li' ).getAttribute(
     336                                            'data-task-points'
     337                                        )
     338                                    ),
     339                                },
     340                            }
     341                        );
     342                        document.dispatchEvent( event );
     343
    323344                        // Trigger the celebration event.
    324345                        document.dispatchEvent(
     
    329350                }
    330351
    331                 const event = new Event( 'prplMaybeInjectSuggestedTaskEvent' );
     352                const event = new CustomEvent(
     353                    'prplMaybeInjectSuggestedTaskEvent',
     354                    {
     355                        detail: {
     356                            taskId,
     357                            type,
     358                        },
     359                    }
     360                );
    332361                document.dispatchEvent( event );
    333362            } );
  • progress-planner/trunk/assets/js/widgets/suggested-tasks.js

    r3217671 r3238385  
    1 /* global customElements, progressPlannerSuggestedTasks, confetti, prplDocumentReady, progressPlannerSuggestedTask */
     1/* global customElements, progressPlannerSuggestedTasks, confetti, prplDocumentReady */
    22
    33/**
    44 * Count the number of items in the list.
    55 *
     6 * @param {string} type The type of items to count.
    67 * @return {number} The number of items in the list.
    78 */
    8 const progressPlannerCountItems = () => {
    9     const items = document.querySelectorAll( '.prpl-suggested-task' );
     9const progressPlannerCountItems = ( type ) => {
     10    // We want to display all pending celebration tasks on page load.
     11    if ( 'pending_celebration' === type ) {
     12        return 0;
     13    }
     14
     15    const items = document.querySelectorAll(
     16        `.prpl-suggested-task[data-task-type="${ type }"]`
     17    );
    1018    return items.length;
    1119};
     
    1422 * Get the next item to inject.
    1523 *
     24 * @param {string} type The type of items to get the next item from.
    1625 * @return {Object} The next item to inject.
    1726 */
    18 const progressPlannerGetNextItem = () => {
     27const progressPlannerGetNextItemFromType = ( type ) => {
     28    // If the are no items of this type, return null.
     29    if (
     30        'undefined' ===
     31        typeof progressPlannerSuggestedTasks.tasks.details[ type ]
     32    ) {
     33        return null;
     34    }
     35
    1936    // Remove completed and snoozed items.
    2037    const tasks = progressPlannerSuggestedTasks.tasks;
    21     const items = tasks.details;
     38    const items = tasks.details[ type ];
    2239    const completed = tasks.completed;
    2340    const snoozed = tasks.snoozed;
     
    7188/**
    7289 * Inject the next item.
     90 * @param {string} type The type of items to inject the next item from.
    7391 */
    74 const progressPlannerInjectNextItem = () => {
    75     const nextItem = progressPlannerGetNextItem();
     92const progressPlannerInjectNextItem = ( type ) => {
     93    const nextItem = progressPlannerGetNextItemFromType( type );
    7694    if ( ! nextItem ) {
    7795        return;
     
    95113        details.points ?? 1,
    96114        details.action ?? '',
    97         details.url ?? ''
     115        details.url ?? '',
     116        details.dismissable ?? false,
     117        details.type ?? ''
    98118    );
    99119
     
    155175
    156176    const progressPlannerRenderAttemptshoot = () => {
    157         confetti( {
    158             ...prplConfettiDefaults,
    159             particleCount: 40,
    160             scalar: 1.2,
    161             shapes: [ 'star' ],
    162         } );
    163 
    164         confetti( {
    165             ...prplConfettiDefaults,
    166             particleCount: 10,
    167             scalar: 0.75,
    168             shapes: [ 'circle' ],
    169         } );
     177        let confettiOptions = [
     178            {
     179                particleCount: 40,
     180                scalar: 1.2,
     181                shapes: [ 'star' ],
     182            },
     183            {
     184                particleCount: 10,
     185                scalar: 0.75,
     186                shapes: [ 'circle' ],
     187            },
     188        ];
     189
     190        // Tripple check if the confetti options are an array and not undefined.
     191        if (
     192            'undefined' !==
     193                typeof progressPlannerSuggestedTasks.confettiOptions &&
     194            true ===
     195                Array.isArray(
     196                    progressPlannerSuggestedTasks.confettiOptions
     197                ) &&
     198            progressPlannerSuggestedTasks.confettiOptions.length
     199        ) {
     200            confettiOptions = progressPlannerSuggestedTasks.confettiOptions;
     201        }
     202
     203        for ( const value of confettiOptions ) {
     204            confetti( {
     205                ...prplConfettiDefaults,
     206                ...value,
     207            } );
     208        }
    170209    };
    171210
     
    192231            .querySelectorAll( '.prpl-suggested-task-celebrated' )
    193232            .forEach( ( item ) => {
    194                 const taskId = item.getAttribute( 'data-task-id' );
    195 
    196                 const request = wp.ajax.post(
    197                     'progress_planner_suggested_task_action',
     233                const taskId = item.getAttribute( 'data-task-id' ),
     234                    type = item.getAttribute( 'data-task-type' );
     235                const el = document.querySelector(
     236                    `.prpl-suggested-task[data-task-id="${ taskId }"]`
     237                );
     238
     239                if ( el ) {
     240                    el.parentElement.remove();
     241                }
     242
     243                // Remove the task from the pending celebration.
     244                window.progressPlannerSuggestedTasks.tasks.pending_celebration =
     245                    window.progressPlannerSuggestedTasks.tasks.pending_celebration.filter(
     246                        ( id ) => id !== taskId
     247                    );
     248
     249                // Add the task to the completed tasks.
     250                if (
     251                    window.progressPlannerSuggestedTasks.tasks.completed.indexOf(
     252                        taskId
     253                    ) === -1
     254                ) {
     255                    window.progressPlannerSuggestedTasks.tasks.completed.push(
     256                        taskId
     257                    );
     258                }
     259
     260                // Refresh the list.
     261                const event = new CustomEvent(
     262                    'prplMaybeInjectSuggestedTaskEvent',
    198263                    {
    199                         task_id: taskId,
    200                         nonce: progressPlannerSuggestedTask.nonce,
    201                         action_type: 'celebrated',
     264                        detail: {
     265                            taskId,
     266                            type,
     267                        },
    202268                    }
    203269                );
    204                 request.done( () => {
    205                     const el = document.querySelector(
    206                         `.prpl-suggested-task[data-task-id="${ taskId }"]`
    207                     );
    208 
    209                     if ( el ) {
    210                         el.parentElement.remove();
    211                     }
    212 
    213                     // Remove the task from the pending celebration.
    214                     window.progressPlannerSuggestedTasks.tasks.pending_celebration =
    215                         window.progressPlannerSuggestedTasks.tasks.pending_celebration.filter(
    216                             ( id ) => id !== taskId
    217                         );
    218 
    219                     // Add the task to the completed tasks.
    220                     if (
    221                         window.progressPlannerSuggestedTasks.tasks.completed.indexOf(
    222                             taskId
    223                         ) === -1
    224                     ) {
    225                         window.progressPlannerSuggestedTasks.tasks.completed.push(
    226                             taskId
    227                         );
    228                     }
    229 
    230                     // Refresh the list.
    231                     const event = new Event(
    232                         'prplMaybeInjectSuggestedTaskEvent'
    233                     );
    234                     document.dispatchEvent( event );
    235                 } );
     270                document.dispatchEvent( event );
    236271            } );
    237272    }, 2000 );
     
    260295    }
    261296
    262     // Inject items, until we reach the maximum number of items.
    263     while (
    264         progressPlannerCountItems() <
    265             parseInt( progressPlannerSuggestedTasks.maxItems ) &&
    266         progressPlannerGetNextItem()
    267     ) {
    268         progressPlannerInjectNextItem();
    269     }
    270 
    271     const event = new Event( 'prplResizeAllGridItemsEvent' );
     297    // Loop through each type and inject items.
     298    for ( const type in progressPlannerSuggestedTasks.tasks.details ) {
     299        // Inject items, until we reach the maximum number of channel items.
     300        while (
     301            progressPlannerCountItems( type ) <
     302                parseInt(
     303                    progressPlannerSuggestedTasks.maxItemsPerType[ type ]
     304                ) &&
     305            progressPlannerGetNextItemFromType( type )
     306        ) {
     307            progressPlannerInjectNextItem( type );
     308        }
     309    }
     310
     311    const event = new CustomEvent( 'prplResizeAllGridItemsEvent' );
    272312    document.dispatchEvent( event );
    273313} );
     
    422462);
    423463
     464const prplGetRaviGaugeProps = () => {
     465    const gauge = document.getElementById( 'prpl-gauge-ravi' );
     466    if ( ! gauge ) {
     467        return;
     468    }
     469
     470    return {
     471        id: gauge.id,
     472        background: gauge.getAttribute( 'background' ),
     473        color: gauge.getAttribute( 'color' ),
     474        max: gauge.getAttribute( 'data-max' ),
     475        value: gauge.getAttribute( 'data-value' ),
     476        badgeId: gauge.getAttribute( 'data-badge-id' ),
     477    };
     478};
     479
     480const prplUpdateRaviGauge = ( pointsDiff = 0 ) => {
     481    if ( ! pointsDiff ) {
     482        return;
     483    }
     484
     485    const gaugeProps = prplGetRaviGaugeProps();
     486
     487    if ( ! gaugeProps ) {
     488        return;
     489    }
     490
     491    let newValue = parseInt( gaugeProps.value ) + pointsDiff;
     492    newValue = Math.round( newValue );
     493    newValue = Math.max( 0, newValue );
     494    newValue = Math.min( newValue, parseInt( gaugeProps.max ) );
     495
     496    const Gauge = customElements.get( 'prpl-gauge' );
     497    const gauge = new Gauge(
     498        {
     499            max: parseInt( gaugeProps.max ),
     500            value: parseFloat( newValue / parseInt( gaugeProps.max ) ),
     501            background: gaugeProps.background,
     502            color: gaugeProps.color,
     503            maxDeg: '180deg',
     504            start: '270deg',
     505            cutout: '57%',
     506            contentFontSize: 'var(--prpl-font-size-6xl)',
     507            contentPadding:
     508                'var(--prpl-padding) var(--prpl-padding) calc(var(--prpl-padding) * 2) var(--prpl-padding)',
     509            marginBottom: 'var(--prpl-padding)',
     510        },
     511        `<prpl-badge complete="true" badge-id="${ gaugeProps.badgeId }"></prpl-badge>`
     512    );
     513    gauge.id = gaugeProps.id;
     514    gauge.setAttribute( 'background', gaugeProps.background );
     515    gauge.setAttribute( 'color', gaugeProps.color );
     516    gauge.setAttribute( 'data-max', gaugeProps.max );
     517    gauge.setAttribute( 'data-value', newValue );
     518    gauge.setAttribute( 'data-badge-id', gaugeProps.badgeId );
     519
     520    // Replace the old gauge with the new one.
     521    const oldGauge = document.getElementById( gaugeProps.id );
     522    if ( oldGauge ) {
     523        oldGauge.replaceWith( gauge );
     524    }
     525
     526    const oldCounter = document.getElementById(
     527        'prpl-widget-content-ravi-points-number'
     528    );
     529    if ( oldCounter ) {
     530        oldCounter.textContent = newValue + 'pt';
     531    }
     532};
     533
     534// Listen for the event.
     535document.addEventListener(
     536    'prplUpdateRaviGaugeEvent',
     537    ( e ) => {
     538        prplUpdateRaviGauge( e.detail.pointsDiff );
     539    },
     540    false
     541);
     542
    424543// Listen for the event.
    425544document.addEventListener(
    426545    'prplMaybeInjectSuggestedTaskEvent',
    427     () => {
     546    ( e ) => {
     547        const type = e.detail.type;
     548
     549        if ( 'pending_celebration' === type ) {
     550            return;
     551        }
     552
    428553        while (
    429             progressPlannerCountItems() <
    430                 parseInt( progressPlannerSuggestedTasks.maxItems ) &&
    431             progressPlannerGetNextItem()
     554            progressPlannerCountItems( type ) <
     555                parseInt(
     556                    progressPlannerSuggestedTasks.maxItemsPerType[ type ]
     557                ) &&
     558            progressPlannerGetNextItemFromType( type )
    432559        ) {
    433             progressPlannerInjectNextItem();
     560            progressPlannerInjectNextItem( type );
    434561        }
    435562
  • progress-planner/trunk/classes/activities/class-suggested-task.php

    r3210975 r3238385  
    5959
    6060        $data = \progress_planner()->get_suggested_tasks()->get_local()->get_data_from_task_id( $this->data_id );
    61         if ( isset( $data['type'] ) && ( 'create-post' === $data['type'] || 'update-post' === $data['type'] ) && isset( $data['long'] ) && true === $data['long'] ) {
     61        if ( isset( $data['type'] ) && ( 'create-post' === $data['type'] || 'review-post' === $data['type'] ) && isset( $data['long'] ) && true === $data['long'] ) {
    6262            $points = 2;
    6363        }
  • progress-planner/trunk/classes/admin/class-page-settings.php

    r3226821 r3238385  
    160160
    161161                // Skip if the ID is not set.
    162                 if ( 1 > (int) $page_args['id'] ) {
     162                if ( ! isset( $page_args['id'] ) || 1 > (int) $page_args['id'] ) {
    163163                    continue;
    164164                }
     
    171171        }
    172172
     173        $this->save_settings();
    173174        $this->save_license();
    174175
     
    176177
    177178        \wp_send_json_success( \esc_html__( 'Options stored successfully', 'progress-planner' ) );
     179    }
     180
     181    /**
     182     * Save the settings.
     183     *
     184     * @return void
     185     */
     186    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
     189            : false;
     190
     191        \update_user_meta( \get_current_user_id(), 'prpl_redirect_on_login', (bool) $redirect_on_login );
    178192    }
    179193
     
    205219        // Call the custom API.
    206220        $response = \wp_remote_post(
    207             'https://progressplanner.com',
     221            \progress_planner()->get_remote_server_root_url(),
    208222            [
    209223                'timeout'   => 15,
  • progress-planner/trunk/classes/admin/class-page.php

    r3226821 r3238385  
    2929        \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
    3030        \add_action( 'wp_ajax_progress_planner_save_cpt_settings', [ $this, 'save_cpt_settings' ] );
     31        \add_action( 'in_admin_header', [ $this, 'remove_admin_notices' ], PHP_INT_MAX );
    3132    }
    3233
     
    3435     * Get the widgets objects
    3536     *
    36      * @return array<\Progress_Planner\Widget>
     37     * @return array<\Progress_Planner\Widgets\Widget>
    3738     */
    3839    public function get_widgets() {
    3940        $widgets = [
     41            \progress_planner()->get_widgets__suggested_tasks(),
    4042            \progress_planner()->get_widgets__activity_scores(),
    41             \progress_planner()->get_widgets__suggested_tasks(),
    4243            \progress_planner()->get_widgets__todo(),
    4344            \progress_planner()->get_widgets__challenge(),
     
    5152         * Filter the widgets.
    5253         *
    53          * @param array<\Progress_Planner\Widget> $widgets The widgets.
     54         * @param array<\Progress_Planner\Widgets\Widget> $widgets The widgets.
    5455         *
    55          * @return array<\Progress_Planner\Widget>
     56         * @return array<\Progress_Planner\Widgets\Widget>
    5657         */
    5758        return \apply_filters( 'progress_planner_admin_widgets', $widgets );
     
    6364     * @param string $id The widget ID.
    6465     *
    65      * @return \Progress_Planner\Widget|void
     66     * @return \Progress_Planner\Widgets\Widget|void
    6667     */
    6768    public function get_widget( $id ) {
     
    174175            );
    175176        }
     177
     178        $prpl_privacy_policy_accepted = \progress_planner()->is_privacy_policy_accepted();
     179        if ( ! $prpl_privacy_policy_accepted ) {
     180            // Enqueue welcome styles.
     181            \wp_enqueue_style(
     182                'progress-planner-welcome',
     183                PROGRESS_PLANNER_URL . '/assets/css/welcome.css',
     184                [],
     185                \progress_planner()->get_file_version( PROGRESS_PLANNER_DIR . '/assets/css/welcome.css' )
     186            );
     187
     188            // Enqueue onboarding styles.
     189            \wp_enqueue_style(
     190                'progress-planner-onboard',
     191                PROGRESS_PLANNER_URL . '/assets/css/onboard.css',
     192                [],
     193                \progress_planner()->get_file_version( PROGRESS_PLANNER_DIR . '/assets/css/onboard.css' )
     194            );
     195        }
    176196    }
    177197
     
    193213        );
    194214    }
     215
     216    /**
     217     * Remove all admin notices when the user is on the Progress Planner page.
     218     *
     219     * @return void
     220     */
     221    public function remove_admin_notices() {
     222        $current_screen = \get_current_screen();
     223        if ( ! $current_screen ) {
     224            return;
     225        }
     226        if ( ! \in_array(
     227            $current_screen->id,
     228            [
     229                'toplevel_page_progress-planner',
     230                'progress-planner_page_progress-planner-settings',
     231            ],
     232            true
     233        ) ) {
     234            return;
     235        }
     236
     237        \remove_all_actions( 'admin_notices' );
     238    }
    195239}
  • progress-planner/trunk/classes/badges/class-monthly.php

    r3226821 r3238385  
    5555
    5656        $activation_date = \progress_planner()->get_base()->get_activation_date();
    57         $start_date      = $activation_date->modify( 'first day of this month' );
     57        if ( $activation_date < new \DateTime( 'first day of November 2024' ) ) { // When badges were introduced.
     58            $start_date = $activation_date->modify( 'first day of November 2024' );
     59        } else {
     60            $start_date = $activation_date->modify( 'first day of this month' );
     61        }
    5862
    5963        // Year when plugin was released.
  • progress-planner/trunk/classes/class-base.php

    r3226821 r3238385  
    9999        $this->cached['badges']          = new Badges();
    100100
    101         // Dont add the widget if the privacy policy is not accepted.
    102101        if ( true === $this->is_privacy_policy_accepted() ) {
    103102            $this->cached['settings_page'] = new Admin_Page_Settings();
    104         }
     103
     104            new Plugin_Deactivation();
     105        }
     106
     107        /**
     108         * Redirect on login.
     109         */
     110        \add_action( 'wp_login', [ $this, 'redirect_on_login' ], 10, 2 );
    105111    }
    106112
     
    156162     */
    157163    public function get_placeholder_svg( $width = 1200, $height = 675 ) {
    158         return 'data:image/svg+xml;base64,' . base64_encode( sprintf( '<svg width="%1$d" height="%2$d" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="%3$d" height="%4$d" style="fill:#F6F5FB;stroke:#534786;stroke-width:2"/><text x="50%%" y="50%%" font-size="20" text-anchor="middle" alignment-baseline="middle" font-family="monospace" fill="#534786">progressplanner.com</text></svg>', $width, $height, ( $width - 4 ), ( $height - 4 ) ) );
     164        return 'data:image/svg+xml;base64,' . base64_encode( sprintf( '<svg width="%1$d" height="%2$d" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="%3$d" height="%4$d" style="fill:#F6F5FB;stroke:#534786;stroke-width:2"/><text x="50%%" y="50%%" font-size="20" text-anchor="middle" alignment-baseline="middle" font-family="monospace" fill="#534786">progressplanner.com</text></svg>', $width, $height, ( $width - 4 ), ( $height - 4 ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
    159165    }
    160166
     
    370376            && 'valid' === \get_option( 'progress_planner_pro_license_status' );
    371377    }
     378
     379    /**
     380     * Redirect on login.
     381     *
     382     * @param string   $user_login The user login.
     383     * @param \WP_User $user The user object.
     384     *
     385     * @return void
     386     */
     387    public function redirect_on_login( $user_login, $user ) {
     388        // Check if the $user can `manage_options`.
     389        if ( ! $user->has_cap( 'manage_options' ) ) {
     390            return;
     391        }
     392
     393        // Check if the user has the `prpl_redirect_on_login` meta.
     394        if ( ! \get_user_meta( $user->ID, 'prpl_redirect_on_login', true ) ) {
     395            return;
     396        }
     397
     398        // Redirect to the Progress Planner dashboard.
     399        \wp_safe_redirect( \admin_url( 'admin.php?page=progress-planner' ) );
     400        exit;
     401    }
    372402}
    373403// phpcs:enable Generic.Commenting.Todo
  • progress-planner/trunk/classes/class-playground.php

    r3156816 r3238385  
    153153            $this->create_random_post();
    154154        }
     155        for ( $i = 0; $i < 5; $i++ ) {
     156            $this->create_random_post( true, 'page' );
     157        }
    155158        // One post for today.
    156159        $this->create_random_post( false );
     
    160163     * Create a random post.
    161164     *
    162      * @param bool $random_date Whether to use a random date or not.
     165     * @param bool   $random_date Whether to use a random date or not.
     166     * @param string $post_type   The post type to create.
    163167     *
    164168     * @return int Post ID.
    165169     */
    166     private function create_random_post( $random_date = true ) {
     170    private function create_random_post( $random_date = true, $post_type = 'post' ) {
    167171        $postarr = [
    168172            'post_title'   => str_replace( '.', '', $this->create_random_string( 5 ) ),
    169173            'post_content' => $this->create_random_string( wp_rand( 200, 500 ) ),
    170174            'post_status'  => 'publish',
    171             'post_type'    => 'post',
     175            'post_type'    => $post_type,
    172176            'post_date'    => $this->get_random_date_last_12_months(),
    173177        ];
  • progress-planner/trunk/classes/class-rest-api-stats.php

    r3217671 r3238385  
    167167        $data['todo'] = $pending_todo_items;
    168168
     169        $ravis_recommendations   = \progress_planner()->get_suggested_tasks()->get_tasks();
     170        $data['recommendations'] = [];
     171        foreach ( $ravis_recommendations as $recommendation ) {
     172            $data['recommendations'][] = [
     173                'id'    => $recommendation['task_id'],
     174                'title' => $recommendation['title'],
     175                'url'   => isset( $recommendation['url'] ) ? $recommendation['url'] : '',
     176            ];
     177        }
     178
    169179        $data['plugin_url'] = \esc_url( \get_admin_url( null, 'admin.php?page=progress-planner' ) );
    170180
     
    180190     */
    181191    public function validate_token( $token ) {
    182         $token       = str_replace( 'token/', '', $token );
     192        $token = str_replace( 'token/', '', $token );
     193        if ( \progress_planner()->is_pro_site() && $token === \get_option( 'progress_planner_pro_license_key' ) ) {
     194            return true;
     195        }
    183196        $license_key = \get_option( 'progress_planner_license_key', false );
    184197        if ( ! $license_key || 'no-license' === $license_key ) {
  • progress-planner/trunk/classes/class-suggested-tasks.php

    r3210975 r3238385  
    1111use Progress_Planner\Suggested_Tasks\Remote_Tasks;
    1212use Progress_Planner\Activities\Suggested_Task;
     13use Progress_Planner\Suggested_Tasks\Local_Tasks\Local_Task_Factory;
    1314
    1415/**
     
    5253            \add_action( 'init', [ $this, 'init' ], 1 );
    5354        }
     55
     56        // Add the automatic updates complete action.
     57        \add_action( 'automatic_updates_complete', [ $this, 'on_automatic_updates_complete' ] );
    5458    }
    5559
     
    6771
    6872        foreach ( $completed_tasks as $task_id ) {
     73            // Change the task status to pending celebration.
    6974            $this->mark_task_as_pending_celebration( $task_id );
    7075
    7176            // Insert an activity.
    72             $activity          = new Suggested_Task();
    73             $activity->type    = 'completed';
    74             $activity->data_id = (string) $task_id;
    75             $activity->date    = new \DateTime();
    76             $activity->user_id = \get_current_user_id();
    77             $activity->save();
    78 
    79             // Allow other classes to react to the completion of a suggested task.
    80             do_action( 'progress_planner_suggested_task_completed', $task_id );
     77            $this->insert_activity( $task_id );
     78        }
     79    }
     80
     81    /**
     82     * Insert an activity.
     83     *
     84     * @param string $task_id The task ID.
     85     *
     86     * @return void
     87     */
     88    public function insert_activity( $task_id ) {
     89        // Insert an activity.
     90        $activity          = new Suggested_Task();
     91        $activity->type    = 'completed';
     92        $activity->data_id = (string) $task_id;
     93        $activity->date    = new \DateTime();
     94        $activity->user_id = \get_current_user_id();
     95        $activity->save();
     96
     97        // Allow other classes to react to the completion of a suggested task.
     98        do_action( 'progress_planner_suggested_task_completed', $task_id );
     99    }
     100
     101    /**
     102     * If done via automatic updates, the "core update" task should be marked as "completed" (and skip "pending celebration" status).
     103     *
     104     * @param array $update_results The update results.
     105     *
     106     * @return void
     107     */
     108    public function on_automatic_updates_complete( $update_results ) {
     109
     110        $pending_tasks = $this->local->get_pending_tasks(); // @phpstan-ignore-line method.nonObject
     111
     112        if ( empty( $pending_tasks ) ) {
     113            return;
     114        }
     115
     116        // TODO: Get this from task provider.
     117        $update_core_task_id = 'update-core';
     118
     119        foreach ( $pending_tasks as $task_id ) {
     120            $task_object = ( new Local_Task_Factory( $task_id ) )->get_task();
     121            $task_data   = $task_object->get_data();
     122
     123            if ( $task_data['type'] === $update_core_task_id && \gmdate( 'YW' ) === $task_data['year_week'] ) {
     124                // Remove from local (pending tasks).
     125                $this->local->remove_pending_task( $task_id ); // @phpstan-ignore-line method.nonObject
     126
     127                // Change the task status to completed.
     128                $this->mark_task_as_completed( $task_id );
     129
     130                // Insert an activity.
     131                $this->insert_activity( $task_id );
     132                break;
     133            }
    81134        }
    82135    }
     
    470523
    471524    /**
     525     * Check if a task was completed.
     526     *
     527     * @param string $task_id The task ID.
     528     *
     529     * @return bool
     530     */
     531    public function was_task_completed( $task_id ) {
     532        return true === $this->check_task_condition(
     533            [
     534                'type'    => 'completed',
     535                'task_id' => $task_id,
     536            ]
     537        );
     538    }
     539
     540    /**
    472541     * Handle the suggested task action.
    473542     *
     
    489558        switch ( $action ) {
    490559            case 'complete':
     560                // It's local task, remove it from pending tasks.
     561                if ( false === strpos( $task_id, 'remote-task' ) ) {
     562                    $this->local->remove_pending_task( $task_id ); // @phpstan-ignore-line method.nonObject
     563                }
     564
     565                // Mark the task as completed.
    491566                $this->mark_task_as( 'completed', $task_id );
     567
     568                // Insert an activity.
     569                $this->insert_activity( $task_id );
    492570                $updated = true;
    493571                break;
     
    498576                break;
    499577
    500             case 'celebrated':
    501                 // We dont need to do anything here, since the task is already marked as completed.
    502                 $updated = true;
    503                 break;
    504 
    505578            default:
    506579                \wp_send_json_error( [ 'message' => \esc_html__( 'Invalid action.', 'progress-planner' ) ] );
  • progress-planner/trunk/classes/suggested-tasks/class-local-tasks-manager.php

    r3217671 r3238385  
    1010use Progress_Planner\Suggested_Tasks\Local_Tasks\Local_Task_Factory;
    1111use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Content_Create;
    12 use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Content_Update;
     12use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Content_Review;
    1313use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Core_Update;
     14use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Core_Blogdescription;
    1415use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Settings_Saved;
    15 
     16use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Debug_Display;
     17use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Sample_Page;
     18use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Hello_World;
     19use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Core_Siteicon;
    1620
    1721/**
     
    4549        $this->task_providers = [
    4650            new Content_Create(),
    47             new Content_Update(),
     51            new Content_Review(),
    4852            new Core_Update(),
     53            new Core_Blogdescription(),
    4954            new Settings_Saved(),
     55            new Debug_Display(),
     56            new Sample_Page(),
     57            new Hello_World(),
     58            new Core_Siteicon(),
    5059        ];
    5160
     
    8897     * Get a task provider by its type.
    8998     *
    90      * @param string $provider_type The provider type.
     99     * @param string $provider_id The provider ID.
    91100     *
    92101     * @return \Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Local_Tasks_Interface|null
    93102     */
    94     public function get_task_provider( $provider_type ) {
     103    public function get_task_provider( $provider_id ) {
    95104        foreach ( $this->task_providers as $provider_instance ) {
    96             if ( $provider_instance->get_provider_type() === $provider_type ) {
     105            if ( $provider_instance->get_provider_id() === $provider_id ) {
    97106                return $provider_instance;
    98107            }
     
    156165    public function evaluate_task( $task_id ) {
    157166        $task_object   = ( new Local_Task_Factory( $task_id ) )->get_task();
    158         $task_provider = $this->get_task_provider( $task_object->get_provider_type() );
     167        $task_provider = $this->get_task_provider( $task_object->get_provider_id() );
    159168
    160169        if ( ! $task_provider ) {
     
    174183    public function get_task_details( $task_id ) {
    175184        $task_object   = ( new Local_Task_Factory( $task_id ) )->get_task();
    176         $task_provider = $this->get_task_provider( $task_object->get_provider_type() );
     185        $task_provider = $this->get_task_provider( $task_object->get_provider_id() );
    177186
    178187        if ( ! $task_provider ) {
     
    262271                $task_data   = $task_object->get_data();
    263272
     273                // If the task was already completed, remove it.
     274                if ( true === \progress_planner()->get_suggested_tasks()->was_task_completed( $task_data['task_id'] ) ) {
     275                    return false;
     276                }
     277
    264278                if ( isset( $task_data['year_week'] ) ) {
    265279                    return \gmdate( 'YW' ) === $task_data['year_week'];
    266280                }
    267281
     282                // We have changed type name, so we need to remove all tasks of the old type.
     283                if ( isset( $task_data['type'] ) && 'update-post' === $task_data['type'] ) {
     284                    return false;
     285                }
     286
    268287                return true;
    269288            }
  • progress-planner/trunk/classes/suggested-tasks/class-remote-tasks.php

    r3226821 r3238385  
    4141                continue;
    4242            }
     43
     44            // If the task with this id is completed, don't add a task.
     45            if ( true === \progress_planner()->get_suggested_tasks()->was_task_completed( "remote-task-{$item['task_id']}" ) ) {
     46                continue;
     47            }
     48
     49            // TODO: Maybe skip task which don't have type defined (to not allow wrongly defined 3rd party tasks to override default type).
     50            $item['type']    = 'remote-' . ( isset( $item['type'] ) ? $item['type'] : 'default' );
    4351            $item['task_id'] = "remote-task-{$item['task_id']}";
    4452            $items[]         = $item;
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/class-local-task-factory.php

    r3217671 r3238385  
    3838        // Parse simple format, e.g. 'update-core-202449'.
    3939        if ( ! str_contains( $this->task_id, '|' ) ) {
     40
    4041            $last_pos = strrpos( $this->task_id, '-' );
    41             if ( false === $last_pos ) {
    42                 return new Task_Local( [ 'task_id' => $this->task_id ] );
     42
     43            // Check if the task ID ends with a '-12345' or not.
     44            if ( $last_pos === false || ! preg_match( '/-\d+$/', $this->task_id ) ) {
     45                return new Task_Local(
     46                    [
     47                        'task_id' => $this->task_id,
     48                        'type'    => $this->task_id,
     49                    ]
     50                );
    4351            }
    4452
     
    5058            return new Task_Local(
    5159                [
     60                    'task_id'        => $this->task_id,
    5261                    'type'           => $type,
    5362                    $task_suffix_key => $task_suffix,
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/class-task-local.php

    r3210975 r3238385  
    4848    /**
    4949     * Alias for get_type().
     50     * For compatibility with the old provider system.
    5051     *
    5152     * @return string
    5253     */
    53     public function get_provider_type() {
     54    public function get_provider_id() {
    5455        return $this->get_type();
    5556    }
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/class-content-abstract.php

    r3210975 r3238385  
    1919     */
    2020    protected $capability = 'edit_others_posts';
     21
     22    /**
     23     * The provider type.
     24     *
     25     * @var string
     26     */
     27    const TYPE = 'writing';
    2128
    2229    /**
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/class-content-create.php

    r3210975 r3238385  
    2020     * @var string
    2121     */
    22     const TYPE = 'create-post';
     22    const ID = 'create-post';
     23
     24    /**
     25     * The provider type.
     26     *
     27     * @var string
     28     */
     29    const TYPE = 'content-new';
    2330
    2431    /**
     
    2835     */
    2936    const ITEMS_TO_INJECT = 2;
    30 
    31     /**
    32      * Get the provider ID.
    33      *
    34      * @return string
    35      */
    36     public function get_provider_type() {
    37         return self::TYPE;
    38     }
    3937
    4038    /**
     
    8886
    8987        // If the task with this length and id is completed, don't add a task.
    90         if ( true === \progress_planner()->get_suggested_tasks()->check_task_condition(
    91             [
    92                 'type'    => 'completed',
    93                 'task_id' => $task_id,
    94             ]
    95         ) ) {
     88        if ( true === \progress_planner()->get_suggested_tasks()->was_task_completed( $task_id ) ) {
    9689            return [];
    9790        }
     
    157150     * @return array
    158151     */
    159     public function get_task_details( $task_id ) {
     152    public function get_task_details( $task_id = '' ) {
     153
     154        if ( ! $task_id ) {
     155            return [];
     156        }
    160157
    161158        $data = $this->get_data_from_task_id( $task_id );
     
    168165            'parent'      => 0,
    169166            'priority'    => 'medium',
    170             'type'        => 'writing',
     167            'type'        => $this->get_provider_type(),
    171168            'points'      => isset( $data['long'] ) && $data['long'] ? 2 : 1,
    172169            'url'         => \esc_url( \admin_url( 'post-new.php?post_type=post' ) ),
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/class-core-update.php

    r3217671 r3238385  
    1212 * Add tasks for Core updates.
    1313 */
    14 class Core_Update extends Local_Tasks_Abstract {
     14class Core_Update extends Local_Repetitive_Tasks_Abstract {
    1515
    1616    /**
     
    2222
    2323    /**
     24     * The provider type.
     25     *
     26     * @var string
     27     */
     28    const TYPE = 'maintenance';
     29
     30    /**
    2431     * The provider ID.
    2532     *
    2633     * @var string
    2734     */
    28     const TYPE = 'update-core';
    29 
    30     /**
    31      * Get the provider ID.
    32      *
    33      * @return string
    34      */
    35     public function get_provider_type() {
    36         return self::TYPE;
    37     }
     35    const ID = 'update-core';
    3836
    3937    /**
     
    4442     * @return bool|string
    4543     */
    46     public function evaluate_task( $task_id ) {
    4744
    48         // Early bail if the user does not have the capability to update the core, since \wp_get_update_data()['counts']['total'] will return 0.
    49         if ( ! $this->capability_required() ) {
    50             return false;
    51         }
    52 
     45    /**
     46     * Check if the task condition is met.
     47     *
     48     * @return bool
     49     */
     50    public function check_task_condition() {
    5351        // Without this \wp_get_update_data() might not return correct data for the core updates (depending on the timing).
    5452        if ( ! function_exists( 'get_core_updates' ) ) {
     
    5654        }
    5755
    58         $task_object = ( new Local_Task_Factory( $task_id ) )->get_task();
    59         $task_data   = $task_object->get_data();
    60 
    61         if ( $task_data['type'] === self::TYPE && \gmdate( 'YW' ) === $task_data['year_week'] && 0 === \wp_get_update_data()['counts']['total'] ) {
    62             return $task_id;
    63         }
    64         return false;
    65     }
    66 
    67     /**
    68      * Get an array of tasks to inject.
    69      *
    70      * @return array
    71      */
    72     public function get_tasks_to_inject() {
    73 
    74         // Early bail if the user does not have the capability to update the core or if the task is snoozed.
    75         if ( true === $this->is_task_type_snoozed() || ! $this->capability_required() ) {
    76             return [];
    77         }
    78 
    79         // Without this \wp_get_update_data() might not return correct data for the core updates (depending on the timing).
    80         if ( ! function_exists( 'get_core_updates' ) ) {
    81             require_once ABSPATH . 'wp-admin/includes/update.php'; // @phpstan-ignore requireOnce.fileNotFound
    82         }
    83 
    84         // If all updates are performed, do not add the task.
    85         if ( 0 === \wp_get_update_data()['counts']['total'] ) {
    86             return [];
    87         }
    88 
    89         return [
    90             $this->get_task_details( self::TYPE . '-' . \gmdate( 'YW' ) ),
    91         ];
     56        return 0 === \wp_get_update_data()['counts']['total'] ? true : false;
    9257    }
    9358
     
    10166    public function get_task_details( $task_id ) {
    10267
     68        if ( ! $task_id ) {
     69            $task_id = $this->get_provider_id() . '-' . \gmdate( 'YW' );
     70        }
     71
    10372        return [
    10473            'task_id'     => $task_id,
     
    10675            'parent'      => 0,
    10776            'priority'    => 'high',
    108             'type'        => 'maintenance',
     77            'type'        => $this->get_provider_type(),
    10978            'points'      => 1,
    11079            'url'         => $this->capability_required() ? \esc_url( \admin_url( 'update-core.php' ) ) : '',
     
    11281        ];
    11382    }
    114 
    115     /**
    116      * Get the data from a task-ID.
    117      *
    118      * @param string $task_id The task ID.
    119      *
    120      * @return array The data.
    121      */
    122     public function get_data_from_task_id( $task_id ) {
    123         $data = [
    124             'type' => self::TYPE,
    125             'id'   => $task_id,
    126         ];
    127 
    128         return $data;
    129     }
    130 
    131     /**
    132      * Check if a task type is snoozed.
    133      *
    134      * @return bool
    135      */
    136     public function is_task_type_snoozed() {
    137         $snoozed = \progress_planner()->get_suggested_tasks()->get_snoozed_tasks();
    138         if ( ! \is_array( $snoozed ) || empty( $snoozed ) ) {
    139             return false;
    140         }
    141 
    142         foreach ( $snoozed as $task ) {
    143             $task_object = ( new Local_Task_Factory( $task['id'] ) )->get_task();
    144             $task_data   = $task_object->get_data();
    145             if ( $task_data['type'] === self::TYPE ) {
    146                 return true;
    147             }
    148         }
    149 
    150         return false;
    151     }
    15283}
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/class-local-tasks-abstract.php

    r3210975 r3238385  
    99
    1010use Progress_Planner\Suggested_Tasks\Local_Tasks\Providers\Local_Tasks_Interface;
     11use Progress_Planner\Suggested_Tasks\Local_Tasks\Local_Task_Factory;
    1112
    1213/**
     
    1617
    1718    /**
     19     * The type of the task.
     20     *
     21     * @var string
     22     */
     23    const TYPE = '';
     24
     25    /**
     26     * The ID of the task.
     27     *
     28     * @var string
     29     */
     30    const ID = '';
     31
     32    /**
    1833     * The capability required to perform the task.
    1934     *
     
    2136     */
    2237    protected $capability = 'manage_options';
     38
     39    /**
     40     * Get the provider type.
     41     *
     42     * @return string
     43     */
     44    public function get_provider_type() {
     45        return static::TYPE;
     46    }
     47
     48    /**
     49     * Get the provider ID.
     50     *
     51     * @return string
     52     */
     53    public function get_provider_id() {
     54        return static::ID;
     55    }
    2356
    2457    /**
     
    3265            : true;
    3366    }
     67
     68    /**
     69     * Get the data from a task-ID.
     70     *
     71     * @param string $task_id The task ID (unused here).
     72     *
     73     * @return array The data.
     74     */
     75    public function get_data_from_task_id( $task_id ) {
     76        $data = [
     77            'type' => $this->get_provider_id(),
     78            'id'   => $task_id,
     79        ];
     80
     81        return $data;
     82    }
     83
     84    /**
     85     * Check if a task type is snoozed.
     86     *
     87     * @return bool
     88     */
     89    public function is_task_type_snoozed() {
     90        $snoozed = \progress_planner()->get_suggested_tasks()->get_snoozed_tasks();
     91        if ( ! \is_array( $snoozed ) || empty( $snoozed ) ) {
     92            return false;
     93        }
     94
     95        foreach ( $snoozed as $task ) {
     96            $task_object = ( new Local_Task_Factory( $task['id'] ) )->get_task();
     97            $provider_id = $task_object->get_provider_id();
     98
     99            if ( $provider_id === $this->get_provider_id() ) {
     100                return true;
     101            }
     102        }
     103
     104        return false;
     105    }
    34106}
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/class-local-tasks-interface.php

    r3210975 r3238385  
    4848
    4949    /**
     50     * Get the provider type.
     51     *
     52     * @return string
     53     */
     54    public function get_provider_type();
     55
     56    /**
    5057     * Get the provider ID.
    5158     *
    5259     * @return string
    5360     */
    54     public function get_provider_type();
     61    public function get_provider_id();
    5562
    5663    /**
  • progress-planner/trunk/classes/suggested-tasks/local-tasks/providers/class-settings-saved.php

    r3210975 r3238385  
    1111 * Add tasks for settings saved.
    1212 */
    13 class Settings_Saved extends Local_Tasks_Abstract {
     13class Settings_Saved extends Local_OneTime_Tasks_Abstract {
     14
     15    /**
     16     * The provider type.
     17     *
     18     * @var string
     19     */
     20    const TYPE = 'configuration';
    1421
    1522    /**
     
    1825     * @var string
    1926     */
    20     const TYPE = 'settings-saved';
     27    const ID = 'settings-saved';
    2128
    2229    /**
    23      * Get the provider ID.
     30     * Check if the task condition is met.
    2431     *
    25      * @return string
     32     * @return bool
    2633     */
    27     public function get_provider_type() {
    28         return self::TYPE;
    29     }
    30 
    31     /**
    32      * Evaluate a task.
    33      *
    34      * @param string $task_id The task ID.
    35      *
    36      * @return bool|string
    37      */
    38     public function evaluate_task( $task_id ) {
    39 
    40         // Early bail if the user does not have the capability to manage options.
    41         if ( ! $this->capability_required() ) {
    42             return false;
    43         }
    44 
    45         if ( 0 === strpos( $task_id, self::TYPE ) && false !== \get_option( 'progress_planner_pro_license_key', false ) ) {
    46             return $task_id;
    47         }
    48         return false;
    49     }
    50 
    51     /**
    52      * Get an array of tasks to inject.
    53      *
    54      * @return array
    55      */
    56     public function get_tasks_to_inject() {
    57 
    58         // Early bail if the user does not have the capability to manage options or if the task is snoozed.
    59         if ( true === $this->is_task_type_snoozed() || ! $this->capability_required() ) {
    60             return [];
    61         }
    62 
     34    public function check_task_condition() {
    6335        $prpl_pro_license_key = \get_option( 'progress_planner_pro_license_key', false );
    6436
    65         if ( false !== $prpl_pro_license_key ) {
    66             return [];
    67         }
    68 
    69         $task_id = self::TYPE . '-' . \gmdate( 'YW' );
    70 
    71         // If the task with this id is completed, don't add a task.
    72         if ( true === \progress_planner()->get_suggested_tasks()->check_task_condition(
    73             [
    74                 'type'    => 'completed',
    75                 'task_id' => $task_id,
    76             ]
    77         ) ) {
    78             return [];
    79         }
    80 
    81         return [
    82             $this->get_task_details( self::TYPE . '-' . \gmdate( 'YW' ) ),
    83         ];
     37        return false !== $prpl_pro_license_key ? true : false;
    8438    }
    8539
     
    9145     * @return array
    9246     */
    93     public function get_task_details( $task_id ) {
     47    public function get_task_details( $task_id = '' ) {
     48
     49        if ( ! $task_id ) {
     50            $task_id = $this->get_provider_id();
     51        }
    9452
    9553        return [
     
    9856            'parent'      => 0,
    9957            'priority'    => 'high',
    100             'type'        => 'maintenance',
     58            'type'        => $this->get_provider_type(),
    10159            'points'      => 1,
    102             'url'         => \current_user_can( 'manage_options' ) ? \esc_url( \admin_url( 'admin.php?page=progress-planner-settings' ) ) : '',
     60            'url'         => $this->capability_required() ? \esc_url( \admin_url( 'admin.php?page=progress-planner-settings' ) ) : '',
    10361            'description' => '<p>' . \esc_html__( 'Head over to the settings page and fill in the required information.', 'progress-planner' ) . '</p>',
    10462        ];
    10563    }
    106 
    107     /**
    108      * Get the data from a task-ID.
    109      *
    110      * @param string $task_id The task ID.
    111      *
    112      * @return array The data.
    113      */
    114     public function get_data_from_task_id( $task_id ) {
    115         $data = [
    116             'type' => self::TYPE,
    117             'id'   => $task_id,
    118         ];
    119 
    120         return $data;
    121     }
    122 
    123     /**
    124      * Check if a task type is snoozed.
    125      *
    126      * @return bool
    127      */
    128     public function is_task_type_snoozed() {
    129         $snoozed = \progress_planner()->get_suggested_tasks()->get_snoozed_tasks();
    130         if ( ! \is_array( $snoozed ) || empty( $snoozed ) ) {
    131             return false;
    132         }
    133 
    134         foreach ( $snoozed as $task ) {
    135             if ( self::TYPE === $task['id'] ) {
    136                 return true;
    137             }
    138         }
    139 
    140         return false;
    141     }
    14264}
  • progress-planner/trunk/classes/widgets/class-activity-scores.php

    r3226821 r3238385  
    88namespace Progress_Planner\Widgets;
    99
    10 use Progress_Planner\Widget;
    1110use Progress_Planner\Goals\Goal_Recurring;
    1211use Progress_Planner\Goals\Goal;
  • progress-planner/trunk/classes/widgets/class-badge-streak.php

    r3210975 r3238385  
    77
    88namespace Progress_Planner\Widgets;
    9 
    10 use Progress_Planner\Widget;
    119
    1210/**
  • progress-planner/trunk/classes/widgets/class-challenge.php

    r3226821 r3238385  
    1111 * Challenge class.
    1212 */
    13 final class Challenge extends \Progress_Planner\Widget {
     13final class Challenge extends Widget {
    1414
    1515    /**
  • progress-planner/trunk/classes/widgets/class-latest-badge.php

    r3210975 r3238385  
    77
    88namespace Progress_Planner\Widgets;
    9 
    10 use Progress_Planner\Widget;
    119
    1210/**
  • progress-planner/trunk/classes/widgets/class-published-content.php

    r3210975 r3238385  
    77
    88namespace Progress_Planner\Widgets;
    9 
    10 use Progress_Planner\Widget;
    119
    1210/**
  • progress-planner/trunk/classes/widgets/class-suggested-tasks.php

    r3217671 r3238385  
    88namespace Progress_Planner\Widgets;
    99
    10 use Progress_Planner\Widget;
    1110use Progress_Planner\Badges\Monthly;
    1211use Progress_Planner\Suggested_Tasks\Local_Tasks\Local_Task_Factory;
     
    2322     */
    2423    protected $id = 'suggested-tasks';
     24
     25    /**
     26     * The tasks.
     27     *
     28     * @var array|null
     29     */
     30    protected $pending_tasks = null;
    2531
    2632    /**
     
    5561
    5662        $pending_celebration = \progress_planner()->get_suggested_tasks()->get_pending_celebration();
     63        $pending_tasks       = $this->get_pending_tasks();
    5764        $deps                = [
    5865            'progress-planner-todo',
     
    6370
    6471        // Check if need to load confetti.
    65         if ( ! empty( $pending_celebration ) ) {
     72        if ( isset( $pending_tasks['content-update'] ) || ! empty( $pending_celebration ) ) {
    6673            $deps[] = 'particles-confetti';
    6774        } else {
     
    9097        $handle = 'progress-planner-' . $this->id;
    9198
    92         $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
    93 
    9499        // Enqueue the script.
    95100        \wp_enqueue_script( $handle );
     
    99104
    100105        // Get pending tasks.
    101         $tasks['details'] = \progress_planner()->get_suggested_tasks()->get_tasks();
     106        $tasks['details'] = $this->get_pending_tasks();
    102107
    103108        // Insert the pending celebration tasks as high priority tasks, so they are shown always.
     
    105110
    106111            $task_object   = ( new Local_Task_Factory( $task_id ) )->get_task();
    107             $task_provider = \progress_planner()->get_suggested_tasks()->get_local()->get_task_provider( $task_object->get_provider_type() );
    108 
    109             if ( $task_provider->capability_required() ) {
     112            $task_provider = \progress_planner()->get_suggested_tasks()->get_local()->get_task_provider( $task_object->get_provider_id() );
     113
     114            if ( $task_provider && $task_provider->capability_required() ) {
    110115                $task_details = \progress_planner()->get_suggested_tasks()->get_local()->get_task_details( $task_id );
    111116
     
    113118                    $task_details['priority'] = 'high'; // Celebrate tasks are always on top.
    114119                    $task_details['action']   = 'celebrate';
    115                     $tasks['details'][]       = $task_details;
     120                    $task_details['type']     = 'pending_celebration';
     121
     122                    if ( ! isset( $tasks['details']['pending_celebration'] ) ) {
     123                        $tasks['details']['pending_celebration'] = [];
     124                    }
     125
     126                    $tasks['details']['pending_celebration'][] = $task_details;
    116127                }
    117128
     
    119130                \progress_planner()->get_suggested_tasks()->transition_task_status( $task_id, 'pending_celebration', 'completed' );
    120131            }
     132        }
     133
     134        $max_items_per_type = [];
     135        foreach ( $tasks['details'] as $type => $items ) {
     136            $max_items_per_type[ $type ] = $type === 'content-update' ? 2 : 1;
     137        }
     138
     139        // We want all pending_celebration' tasks to be shown.
     140        if ( isset( $max_items_per_type['pending_celebration'] ) ) {
     141            $max_items_per_type['pending_celebration'] = 99;
     142        }
     143
     144        // Check if current date is between Feb 12-16.
     145        $confetti_options = [];
     146        $year             = \gmdate( 'Y' );
     147        $current_date     = $year . '-' . \gmdate( 'm-d' );
     148
     149        // TODO: GET params just for testing.
     150        $start_date = $year . '-' . ( isset( $_GET['start_date'] ) ? \sanitize_text_field( \wp_unslash( $_GET['start_date'] ) ) : '02-12' );
     151        $end_date   = $year . '-' . ( isset( $_GET['end_date'] ) ? \sanitize_text_field( \wp_unslash( $_GET['end_date'] ) ) : '02-16' );
     152
     153        if ( $current_date >= $start_date && $current_date <= $end_date ) {
     154            $confetti_options = [
     155                [
     156                    'particleCount' => 50,
     157                    'scalar'        => 2.2,
     158                    'shapes'        => [ 'heart' ],
     159                    'colors'        => [ 'FFC0CB', 'FF69B4', 'FF1493', 'C71585' ],
     160                ],
     161                [
     162                    'particleCount' => 20,
     163                    'scalar'        => 3.2,
     164                    'shapes'        => [ 'heart' ],
     165                    'colors'        => [ 'FFC0CB', 'FF69B4', 'FF1493', 'C71585' ],
     166                ],
     167            ];
    121168        }
    122169
     
    126173            'progressPlannerSuggestedTasks',
    127174            [
    128                 'ajaxUrl'  => \admin_url( 'admin-ajax.php' ),
    129                 'nonce'    => \wp_create_nonce( 'progress_planner' ),
    130                 'tasks'    => $tasks,
    131                 'maxItems' => $current_screen && 'dashboard' === $current_screen->id ? 3 : 5,
     175                'ajaxUrl'         => \admin_url( 'admin-ajax.php' ),
     176                'nonce'           => \wp_create_nonce( 'progress_planner' ),
     177                'tasks'           => $tasks,
     178                'maxItemsPerType' => apply_filters( 'progress_planner_suggested_tasks_max_items_per_type', $max_items_per_type ),
     179                'confettiOptions' => $confetti_options,
    132180            ]
    133181        );
    134182    }
     183
     184    /**
     185     * Get the tasks.
     186     *
     187     * @return array The tasks.
     188     */
     189    public function get_pending_tasks() {
     190
     191        if ( null === $this->pending_tasks ) {
     192            $tasks         = [];
     193            $pending_tasks = \progress_planner()->get_suggested_tasks()->get_tasks();
     194
     195            // Sort them by type (channel).
     196            foreach ( $pending_tasks as $task ) {
     197
     198                if ( ! isset( $tasks[ $task['type'] ] ) ) {
     199                    $tasks[ $task['type'] ] = [];
     200                }
     201
     202                $tasks[ $task['type'] ][] = $task;
     203            }
     204
     205            $this->pending_tasks = $tasks;
     206        }
     207
     208        return $this->pending_tasks;
     209    }
    135210}
  • progress-planner/trunk/classes/widgets/class-todo.php

    r3210975 r3238385  
    77
    88namespace Progress_Planner\Widgets;
    9 
    10 use Progress_Planner\Widget;
    119
    1210/**
  • progress-planner/trunk/classes/widgets/class-whats-new.php

    r3210975 r3238385  
    88namespace Progress_Planner\Widgets;
    99
    10 use Progress_Planner\Widget;
    1110use Progress_Planner\Cache;
    1211
  • progress-planner/trunk/progress-planner.php

    r3226821 r3238385  
    1010 * Requires at least: 6.3
    1111 * Requires PHP:      7.4
    12  * Version:           1.0.3
     12 * Version:           1.0.4
    1313 * Author:            Team Emilia Projects
    1414 * Author URI:        https://prpl.fyi/about
  • progress-planner/trunk/readme.txt

    r3226821 r3238385  
    55Tested up to: 6.7
    66Requires PHP: 7.4
    7 Stable tag: 1.0.3
     7Stable tag: 1.0.4
    88License: GPL3+
    99License URI: https://www.gnu.org/licenses/gpl-3.0.en.html
     
    7878
    7979== Changelog ==
     80
     81= 1.0.4 =
     82
     83Enhancements:
     84
     85* We've moved Ravi's recommendations to the top left of your Progress Planner dashboard. They're the most important thing on there, so we wanted to give it prime placement.
     86* We changed "Update post" to "Review post" / "Review page" and [wrote better instructions for reviewing old posts and pages](https://progressplanner.com/recommendations/review-post/). These tasks now prioritize the most important pages, like your About page, Privacy policy, Contact page and FAQ page.
     87* Added an option to redirect users to the Progress Planner dashboard after login. The WordPress dashboard isn't particularly useful in our eyes, this mind entice you to action more.
     88* Added a plugin-deactivation feedback form (we tell you, because you'll never see it, right? :) ).
     89* Removed the celebration for "Perform all updates" if it was done by WordPress's automatic update. We all love confetti, but when it comes all the time without you doing anything, it loses its value, right? Hence this fix.
     90
     91We've added the following Recommendations from Ravi:
     92
     93* [Setting site icon](https://progressplanner.com/recommendations/set-a-site-icon-aka-favicon/).
     94* [Setting the tagline](https://progressplanner.com/recommendations/set-tagline/).
     95* [Deactivating the display of PHP debug messages](https://progressplanner.com/recommendations/set-wp-debug/).
     96* [Removing the default WP "Hello world" post](https://progressplanner.com/recommendations/delete-the-default-wordpress-hello-world-post/).
     97* [Removing the default WP "Sample page" page](https://progressplanner.com/recommendations/delete-the-default-wordpress-sample-page-post/).
     98
     99Under the hood:
     100
     101* Improvements to the REST-API endpoint for getting stats.
     102* Removed admin notices on the Progress Planner page.
    80103
    81104= 1.0.3 =
  • progress-planner/trunk/views/admin-page-settings.php

    r3226821 r3238385  
    3434
    3535    <form id="prpl-settings">
    36         <div class="prpl-column">
    37             <div class="prpl-widget-wrapper">
    38                 <h2 class="prpl-settings-section-title">
    39                     <span class="icon">
    40                         <?php \progress_planner()->the_asset( 'images/icon_pages.svg' ); ?>
    41                     </span>
    42                     <span>
    43                         <?php esc_html_e( 'Your pages', 'progress-planner' ); ?>
    44                     </span>
    45                 </h2>
    46                 <p>
    47                     <?php esc_html_e( 'Let us know if you have following pages.', 'progress-planner' ); ?>
    48                 </p>
    49                 <div class="prpl-pages-list">
    50                     <?php
    51                     foreach ( \progress_planner()->get_admin__page_settings()->get_settings() as $prpl_setting ) {
    52                         \progress_planner()->the_view( "setting/{$prpl_setting['type']}.php", [ 'prpl_setting' => $prpl_setting ] );
    53                     }
    54                     ?>
    55                 </div>
    56             </div>
    57         </div>
    58 
    59         <div class="prpl-column">
    60             <div class="prpl-widget-wrapper">
    61                 <h2 class="prpl-settings-section-license">
    62                     <span>
    63                         <?php \esc_html_e( 'License', 'progress-planner' ); ?>
    64                     </span>
    65                 </h2>
    66                 <div class="prpl-license-keys-wrapper">
    67                     <?php
    68                     $prpl_pro_license        = \get_option( 'progress_planner_pro_license_key', '' );
    69                     $prpl_pro_license_status = \get_option( 'progress_planner_pro_license_status', '' );
    70                     ?>
    71                     <?php if ( empty( $prpl_pro_license ) || 'valid' !== $prpl_pro_license_status ) : ?>
    72                         <p>
    73                             <?php
    74                             printf(
    75                                 // translators: %s is a link to the Pro page, with the text "Progress Planner Pro".
    76                                 \esc_html__( 'Take part in interactive challenges to solve website problems like broken links and sharpen your skills with in-context mini courses. Upgrade to %s!', 'progress-planner' ),
    77                                 '<a href="https://progressplanner.com/pro/" target="_blank">Progress Planner Pro</a>'
    78                             );
    79                             ?>
    80                         </p>
    81                     <?php endif; ?>
    82                     <label for="prpl-setting-pro-license-key">
    83                         <?php \esc_html_e( 'Progress Planner Pro license key', 'progress-planner' ); ?>
    84                     </label>
    85                     <div class="prpl-license-key-wrapper">
    86                         <input
    87                             id="prpl-setting-pro-license-key"
    88                             name="prpl-pro-license-key"
    89                             type="text"
    90                             value="<?php echo \esc_attr( $prpl_pro_license ); ?>"
    91                         />
    92                         <?php if ( ! empty( $prpl_pro_license ) ) : ?>
    93                             <span class="prpl-license-status prpl-license-status-<?php echo ( 'valid' === $prpl_pro_license_status ) ? 'valid' : 'invalid'; ?>">
    94                                 <?php if ( 'valid' === $prpl_pro_license_status ) : ?>
    95                                     <span class="prpl-license-status-valid" title="<?php esc_attr_e( 'Valid', 'progress-planner' ); ?>">
    96                                         <?php \progress_planner()->the_asset( 'images/icon_check_circle.svg' ); ?>
    97                                     </span>
    98                                 <?php else : ?>
    99                                     <span class="prpl-license-status-invalid" title="<?php esc_attr_e( 'Invalid', 'progress-planner' ); ?>">
    100                                         <?php \progress_planner()->the_asset( 'images/icon_exclamation_circle.svg' ); ?>
    101                                     </span>
    102                                 <?php endif; ?>
    103                             </span>
    104                         <?php endif; ?>
    105                     </div>
    106                 </div>
    107             </div>
    108         </div>
     36        <?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' ); ?>
    10939
    11040        <?php wp_nonce_field( 'prpl-settings' ); ?>
  • progress-planner/trunk/views/page-widgets/parts/monthly-badges.php

    r3226821 r3238385  
    3535    <?php if ( $prpl_badges ) : ?>
    3636        <?php
    37         $prpl_badges_per_row = 3;
    38         $prpl_badges_count   = count( $prpl_badges );
    39         $prpl_scroll_to_row  = 1;
     37        $prpl_badges_per_row         = 3;
     38        $prpl_badges_count           = count( $prpl_badges );
     39        $prpl_scroll_to_row          = 1;
    4040        $prpl_current_month_badge_id = Monthly::get_badge_id_from_date( new \DateTime() );
    4141        if ( 'popover' !== $prpl_location ) {
  • progress-planner/trunk/views/page-widgets/suggested-tasks.php

    r3226821 r3238385  
    1515$prpl_badge  = \progress_planner()->get_badges()->get_badge( Monthly::get_badge_id_from_date( new \DateTime() ) );
    1616?>
     17
     18
     19<div class="prpl-dashboard-widget-suggested-tasks">
     20    <h2 class="prpl-widget-title">
     21        <?php \esc_html_e( 'Ravi\'s Recommendations', 'progress-planner' ); ?>
     22    </h2>
     23
     24    <ul style="display:none"></ul>
     25    <ul class="prpl-suggested-tasks-list"></ul>
     26
     27    <hr>
     28</div>
     29
    1730<?php if ( $prpl_badge ) : ?>
    1831    <h2 class="prpl-widget-title">
     
    4457    </h2>
    4558
    46     <prpl-gauge background="var(--prpl-background-orange)" color="var(--prpl-color-accent-orange)">
     59    <prpl-gauge
     60        id="prpl-gauge-ravi"
     61        background="var(--prpl-background-orange)"
     62        color="var(--prpl-color-accent-orange)"
     63        data-max="<?php echo (int) Monthly::TARGET_POINTS; ?>"
     64        data-value="<?php echo (float) $prpl_widget->get_score(); ?>"
     65        data-badge-id="<?php echo esc_attr( $prpl_badge->get_id() ); ?>"
     66    >
    4767        <progress max="<?php echo (int) Monthly::TARGET_POINTS; ?>" value="<?php echo (float) $prpl_widget->get_score(); ?>">
    4868            <prpl-badge complete="true" badge-id="<?php echo esc_attr( $prpl_badge->get_id() ); ?>"></prpl-badge>
     
    5272    <div class="prpl-widget-content-points">
    5373        <span><?php \esc_html_e( 'Progress monthly badge', 'progress-planner' ); ?></span>
    54         <span class="prpl-widget-content-points-number">
     74        <span id="prpl-widget-content-ravi-points-number" class="prpl-widget-content-points-number">
    5575            <?php echo (int) $prpl_widget->get_score(); ?>pt
    5676        </span>
     
    5979    <hr>
    6080<?php endif; ?>
    61 
    62 <div class="prpl-dashboard-widget-suggested-tasks">
    63     <h2 class="prpl-widget-title">
    64         <?php \esc_html_e( 'Ravi\'s Recommendations', 'progress-planner' ); ?>
    65     </h2>
    66 
    67     <ul style="display:none"></ul>
    68     <ul class="prpl-suggested-tasks-list"></ul>
    69 
    70     <hr>
    71 </div>
    7281
    7382<div class="prpl-widget-content">
Note: See TracChangeset for help on using the changeset viewer.