@@ -38,26 +38,28 @@
'-3 weeks' => esc_html__( '3 Weeks', 'progress-planner' ),
'month' => esc_html__( 'Month', 'progress-planner' ),
'year' => esc_html__( 'Year', 'progress-planner' ),
- ] as $period => $period_label ) :
- ?>
+ ] as $progress_planner_period => $progress_planner_period_label ) :
+ ?>
-
+
- get_stats()
->get_stat( 'posts' )
- ->set_post_type( $post_type )
- ->get_data( $period )['count'];
+ ->set_post_type( $progress_planner_post_type )
+ ->get_data( $progress_planner_period )['count'];
?>
- get_stats()
->get_stat( 'posts' )
- ->set_post_type( $post_type )
- ->get_data( $period )['word_count'];
+ ->set_post_type( $progress_planner_post_type )
+ ->get_data( $progress_planner_period )['word_count'];
?>
@@ -67,12 +69,13 @@
get_stats()
->get_stat( 'posts' )
- ->set_post_type( $post_type )
+ ->set_post_type( $progress_planner_post_type )
->get_data( 'all' )['publish']
)
);
From 8d0b1ace32ad643d788cf53753ba5621d0387f2d Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 6 Feb 2024 14:06:56 +0200
Subject: [PATCH 022/490] Revert "improve readability"
This reverts commit 731b978a17bb27dda5c3241dfb45fdd6300862ca.
---
includes/stats/class-stat-posts.php | 78 ++++++++++++++++++-----------
1 file changed, 48 insertions(+), 30 deletions(-)
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 619181b22..837734467 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -56,40 +56,63 @@ public function get_data( $period = 'week' ) {
switch ( $period ) {
case 'all':
- $stats = (array) \wp_count_posts( $this->post_type );
-
- self::$stats[ $this->post_type ][ $period ] = $stats;
- return $stats;
+ self::$stats[ $this->post_type ][ $period ] = (array) \wp_count_posts( $this->post_type );
+ return self::$stats[ $this->post_type ][ $period ];
case 'day':
- $stats = $this->get_posts_stats_by_date( 'today' );
-
- self::$stats[ $this->post_type ][ $period ] = $stats;
- return $stats;
+ self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
+ [
+ [
+ 'after' => 'today',
+ 'inclusive' => true,
+ ],
+ ]
+ );
+ return self::$stats[ $this->post_type ][ $period ];
case 'week':
- $stats = $this->get_posts_stats_by_date( '-1 week' );
-
- self::$stats[ $this->post_type ][ $period ] = $stats;
- return $stats;
+ self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
+ [
+ [
+ 'after' => '-1 week',
+ 'inclusive' => true,
+ ],
+ ]
+ );
+ return self::$stats[ $this->post_type ][ $period ];
case 'month':
- $stats = $this->get_posts_stats_by_date( '-1 month' );
-
- self::$stats[ $this->post_type ][ $period ] = $stats;
- return $stats;
+ self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
+ [
+ [
+ 'after' => '-1 month',
+ 'inclusive' => true,
+ ],
+ ]
+ );
+ return self::$stats[ $this->post_type ][ $period ];
case 'year':
- $stats = $this->get_posts_stats_by_date( '-1 year' );
-
- self::$stats[ $this->post_type ][ $period ] = $stats;
- return $stats;
+ self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
+ [
+ [
+ 'after' => '-1 year',
+ 'inclusive' => true,
+ ],
+ ]
+ );
+ return self::$stats[ $this->post_type ][ $period ];
default:
- $stats = $this->get_posts_stats_by_date( $period );
-
- self::$stats[ $this->post_type ][ $period ] = $stats;
- return $stats;
+ self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
+ [
+ [
+ 'after' => $period,
+ 'inclusive' => true,
+ ],
+ ]
+ );
+ return self::$stats[ $this->post_type ][ $period ];
}
}
@@ -104,12 +127,7 @@ private function get_posts_stats_by_date( $date_query ) {
'posts_per_page' => 1000, // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
'post_type' => $this->post_type,
'post_status' => 'publish',
- 'date_query' => [
- [
- 'after' => $date_query,
- 'inclusive' => true,
- ],
- ],
+ 'date_query' => $date_query,
'suppress_filters' => false,
];
From db46accb2ab17876f2d92799e96a44bba31e1d34 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 6 Feb 2024 14:58:29 +0200
Subject: [PATCH 023/490] Add basic Settings class
---
includes/class-progress-planner.php | 21 ++++++-
includes/class-settings.php | 91 +++++++++++++++++++++++++++++
2 files changed, 110 insertions(+), 2 deletions(-)
create mode 100644 includes/class-settings.php
diff --git a/includes/class-progress-planner.php b/includes/class-progress-planner.php
index 2115b14e3..e5e7a49cf 100644
--- a/includes/class-progress-planner.php
+++ b/includes/class-progress-planner.php
@@ -33,6 +33,13 @@ class Progress_Planner {
*/
private $admin;
+ /**
+ * The Settings object.
+ *
+ * @var \ProgressPlanner\Settings
+ */
+ private $settings;
+
/**
* Get the single instance of this class.
*
@@ -50,8 +57,18 @@ public static function get_instance() {
* Constructor.
*/
private function __construct() {
- $this->admin = new Admin();
- $this->stats = new Stats();
+ $this->admin = new Admin();
+ $this->settings = new Settings();
+ $this->stats = new Stats();
+ }
+
+ /**
+ * Get the settings object.
+ *
+ * @return \ProgressPlanner\Settings
+ */
+ public function get_settings() {
+ return $this->settings;
}
/**
diff --git a/includes/class-settings.php b/includes/class-settings.php
new file mode 100644
index 000000000..5f270dcd4
--- /dev/null
+++ b/includes/class-settings.php
@@ -0,0 +1,91 @@
+option_name, [] );
+
+ // Get the value for current week & month.
+ $current_value = $this->get_current_value();
+
+ // Merge the saved value with the default value.
+ return \array_replace_recursive( $current_value, $saved_value );
+ }
+
+ /**
+ * Get the value for the current week & month.
+ *
+ * @return array
+ */
+ private function get_current_value() {
+ // Get the values for current week and month.
+ $curr_y = \gmdate( 'Y' );
+ $curr_m = \gmdate( 'n' );
+ $curr_w = \gmdate( 'W' );
+ $curr_value = [
+ 'stats' => [
+ $curr_y => [
+ 'weeks' => [
+ $curr_w => [
+ 'posts' => [],
+ 'words' => [],
+ ],
+ ],
+ 'months' => [
+ $curr_m => [
+ 'posts' => [],
+ 'words' => [],
+ ],
+ ],
+ ],
+ ],
+ ];
+ foreach ( \array_keys( \get_post_types( [ 'public' => true ] ) ) as $post_type ) {
+ $week_stats = Progress_Planner::get_instance()
+ ->get_stats()
+ ->get_stat( 'posts' )
+ ->set_post_type( $post_type )
+ ->get_data( 'this week' );
+
+ $month_stats = Progress_Planner::get_instance()
+ ->get_stats()
+ ->get_stat( 'posts' )
+ ->set_post_type( $post_type )
+ ->get_data( gmdate( 'F Y' ) );
+
+ $curr_value['stats'][ $curr_y ]['weeks'][ $curr_w ]['posts'][ $post_type ] = $week_stats['count'];
+ $curr_value['stats'][ $curr_y ]['weeks'][ $curr_w ]['words'][ $post_type ] = $week_stats['word_count'];
+ $curr_value['stats'][ $curr_y ]['months'][ $curr_m ]['posts'][ $post_type ] = $month_stats['count'];
+ $curr_value['stats'][ $curr_y ]['months'][ $curr_m ]['words'][ $post_type ] = $month_stats['word_count'];
+ }
+
+ return $curr_value;
+ }
+
+ /**
+ * Update value for previous week.
+ */
+}
From 65819e685e2910593bc82e792d64496ad56c31e5 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 6 Feb 2024 14:58:59 +0200
Subject: [PATCH 024/490] Add method to update setting for previous
months/weeks
---
includes/class-settings.php | 43 ++++++++++++++++++++++++++++-
includes/stats/class-stat-posts.php | 2 +-
2 files changed, 43 insertions(+), 2 deletions(-)
diff --git a/includes/class-settings.php b/includes/class-settings.php
index 5f270dcd4..37585c633 100644
--- a/includes/class-settings.php
+++ b/includes/class-settings.php
@@ -86,6 +86,47 @@ private function get_current_value() {
}
/**
- * Update value for previous week.
+ * Update value for a previous, unsaved week.
+ *
+ * @param string $interval_type The interval type. Can be "week" or "month".
+ * @param int $interval_value The number of weeks or months back to update the value for.
+ *
+ * @return bool Returns the result of the update_option function.
*/
+ public function update_value_previous_unsaved_week( $interval_type = 'weeks', $interval_value = 0 ) {
+ // Get the saved value.
+ $saved_value = \get_option( $this->option_name, [] );
+
+ // Get the year & week numbers for the defined week/month.
+ $year = \gmdate( 'Y', strtotime( "-$interval_value $interval_type" ) );
+ $interval_type_nr = \gmdate(
+ 'weeks' === $interval_type ? 'W' : 'n',
+ strtotime( "-$interval_value $interval_type" )
+ );
+
+ foreach ( \array_keys( \get_post_types( [ 'public' => true ] ) ) as $post_type ) {
+ $interval_stats = Progress_Planner::get_instance()
+ ->get_stats()
+ ->get_stat( 'posts' )
+ ->set_post_type( $post_type )
+ ->get_posts_stats_by_date(
+ [
+ [
+ 'after' => '-' . ( $interval_value + 1 ) . $interval_type,
+ 'inclusive' => true,
+ ],
+ [
+ 'before' => '-' . $interval_value . $interval_type,
+ 'inclusive' => false,
+ ],
+ ]
+ );
+
+ // Set the value.
+ $saved_values['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['posts'][ $post_type ] = $interval_stats['count'];
+ }
+
+ // Update the option value.
+ return \update_option( $this->option_name, $saved_value );
+ }
}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 837734467..fd858adfc 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -122,7 +122,7 @@ public function get_data( $period = 'week' ) {
* @param array $date_query The date query.
* @return array
*/
- private function get_posts_stats_by_date( $date_query ) {
+ public function get_posts_stats_by_date( $date_query ) {
$args = [
'posts_per_page' => 1000, // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
'post_type' => $this->post_type,
From a3b1ab4c620b19d6a4616fe9deb15111466c33ba Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 6 Feb 2024 15:07:43 +0200
Subject: [PATCH 025/490] Update method name
---
includes/class-settings.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/class-settings.php b/includes/class-settings.php
index 37585c633..77a7abea1 100644
--- a/includes/class-settings.php
+++ b/includes/class-settings.php
@@ -93,7 +93,7 @@ private function get_current_value() {
*
* @return bool Returns the result of the update_option function.
*/
- public function update_value_previous_unsaved_week( $interval_type = 'weeks', $interval_value = 0 ) {
+ public function update_value_previous_unsaved_interval( $interval_type = 'weeks', $interval_value = 0 ) {
// Get the saved value.
$saved_value = \get_option( $this->option_name, [] );
From 1f2cedc9e323a9bc4865453d1dbe7700a407220e Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 7 Feb 2024 11:33:58 +0200
Subject: [PATCH 026/490] refactor some structure
---
includes/class-settings.php | 104 +++++++++++++++++---------
includes/stats/class-stat-posts.php | 85 +--------------------
includes/stats/class-stat-terms.php | 4 +-
includes/stats/class-stat.php | 25 ++++++-
phpcs.xml.dist | 1 +
views/admin-page.php | 110 ++++++++++++++--------------
6 files changed, 152 insertions(+), 177 deletions(-)
diff --git a/includes/class-settings.php b/includes/class-settings.php
index 77a7abea1..8289899b8 100644
--- a/includes/class-settings.php
+++ b/includes/class-settings.php
@@ -22,9 +22,15 @@ class Settings {
/**
* Get the option value.
*
+ * @param string[] ...$args Get the value for a specific key in the array.
+ * This will go over the array recursively, returning the value for the last key.
+ * Example: If the value is ['a' => ['b' => 'c']], get_value('a', 'b') will return 'c'.
+ * If the key does not exist, it will return null.
+ * If no keys are provided, it will return the entire array.
+ *
* @return array
*/
- public function get_value() {
+ public function get_value( ...$args ) {
// Get the saved value.
$saved_value = \get_option( $this->option_name, [] );
@@ -32,7 +38,17 @@ public function get_value() {
$current_value = $this->get_current_value();
// Merge the saved value with the default value.
- return \array_replace_recursive( $current_value, $saved_value );
+ $value = \array_replace_recursive( $current_value, $saved_value );
+
+ // Get the value for a specific key.
+ foreach ( $args as $arg ) {
+ if ( ! isset( $value[ $arg ] ) ) {
+ $value = null;
+ break;
+ }
+ $value = $value[ $arg ];
+ }
+ return $value;
}
/**
@@ -42,9 +58,9 @@ public function get_value() {
*/
private function get_current_value() {
// Get the values for current week and month.
- $curr_y = \gmdate( 'Y' );
- $curr_m = \gmdate( 'n' );
- $curr_w = \gmdate( 'W' );
+ $curr_y = (int) \gmdate( 'Y' );
+ $curr_m = (int) \gmdate( 'n' );
+ $curr_w = (int) \gmdate( 'W' );
$curr_value = [
'stats' => [
$curr_y => [
@@ -63,18 +79,31 @@ private function get_current_value() {
],
],
];
+
+ $stats = Progress_Planner::get_instance()->get_stats()->get_stat( 'posts' );
foreach ( \array_keys( \get_post_types( [ 'public' => true ] ) ) as $post_type ) {
- $week_stats = Progress_Planner::get_instance()
- ->get_stats()
- ->get_stat( 'posts' )
- ->set_post_type( $post_type )
- ->get_data( 'this week' );
-
- $month_stats = Progress_Planner::get_instance()
- ->get_stats()
- ->get_stat( 'posts' )
- ->set_post_type( $post_type )
- ->get_data( gmdate( 'F Y' ) );
+ // Set the post-type.
+ $stats->set_post_type( $post_type );
+
+ // Get weekly stats.
+ $week_stats = $stats->set_date_query(
+ [
+ [
+ 'after' => '-1 week',
+ 'inclusive' => true,
+ ],
+ ]
+ )->get_data();
+
+ // Get monthly stats.
+ $month_stats = $stats->set_date_query(
+ [
+ [
+ 'after' => gmdate( 'F Y' ),
+ 'inclusive' => true,
+ ],
+ ]
+ )->get_data();
$curr_value['stats'][ $curr_y ]['weeks'][ $curr_w ]['posts'][ $post_type ] = $week_stats['count'];
$curr_value['stats'][ $curr_y ]['weeks'][ $curr_w ]['words'][ $post_type ] = $week_stats['word_count'];
@@ -95,35 +124,40 @@ private function get_current_value() {
*/
public function update_value_previous_unsaved_interval( $interval_type = 'weeks', $interval_value = 0 ) {
// Get the saved value.
- $saved_value = \get_option( $this->option_name, [] );
+ $saved_value = $this->get_value();
// Get the year & week numbers for the defined week/month.
- $year = \gmdate( 'Y', strtotime( "-$interval_value $interval_type" ) );
- $interval_type_nr = \gmdate(
+ $year = (int) \gmdate( 'Y', strtotime( "-$interval_value $interval_type" ) );
+ $interval_type_nr = (int) \gmdate(
'weeks' === $interval_type ? 'W' : 'n',
strtotime( "-$interval_value $interval_type" )
);
+ $stats = Progress_Planner::get_instance()->get_stats()->get_stat( 'posts' );
foreach ( \array_keys( \get_post_types( [ 'public' => true ] ) ) as $post_type ) {
- $interval_stats = Progress_Planner::get_instance()
- ->get_stats()
- ->get_stat( 'posts' )
- ->set_post_type( $post_type )
- ->get_posts_stats_by_date(
+ if (
+ isset( $saved_value['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['posts'][ $post_type ] ) &&
+ isset( $saved_value['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['words'][ $post_type ] )
+ ) {
+ continue;
+ }
+
+ $interval_stats = $stats->set_post_type( $post_type )->set_date_query(
+ [
[
- [
- 'after' => '-' . ( $interval_value + 1 ) . $interval_type,
- 'inclusive' => true,
- ],
- [
- 'before' => '-' . $interval_value . $interval_type,
- 'inclusive' => false,
- ],
- ]
- );
+ 'after' => '-' . ( $interval_value + 1 ) . ' ' . $interval_type,
+ 'inclusive' => true,
+ ],
+ [
+ 'before' => '-' . $interval_value . ' ' . $interval_type,
+ 'inclusive' => false,
+ ],
+ ]
+ )->get_data();
// Set the value.
- $saved_values['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['posts'][ $post_type ] = $interval_stats['count'];
+ $saved_value['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['posts'][ $post_type ] = $interval_stats['count'];
+ $saved_value['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['words'][ $post_type ] = $interval_stats['word_count'];
}
// Update the option value.
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index fd858adfc..d02b1de29 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -39,95 +39,16 @@ public function set_post_type( $post_type ) {
}
/**
- * Get the stat data.
+ * Get the data.
*
- * @param string $period The period to get the data for.
- *
- * @return array
- */
- public function get_data( $period = 'week' ) {
-
- if ( ! isset( self::$stats[ $this->post_type ] ) ) {
- self::$stats[ $this->post_type ] = [];
- }
- if ( isset( self::$stats[ $this->post_type ][ $period ] ) ) {
- return self::$stats[ $this->post_type ][ $period ];
- }
-
- switch ( $period ) {
- case 'all':
- self::$stats[ $this->post_type ][ $period ] = (array) \wp_count_posts( $this->post_type );
- return self::$stats[ $this->post_type ][ $period ];
-
- case 'day':
- self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
- [
- [
- 'after' => 'today',
- 'inclusive' => true,
- ],
- ]
- );
- return self::$stats[ $this->post_type ][ $period ];
-
- case 'week':
- self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
- [
- [
- 'after' => '-1 week',
- 'inclusive' => true,
- ],
- ]
- );
- return self::$stats[ $this->post_type ][ $period ];
-
- case 'month':
- self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
- [
- [
- 'after' => '-1 month',
- 'inclusive' => true,
- ],
- ]
- );
- return self::$stats[ $this->post_type ][ $period ];
-
- case 'year':
- self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
- [
- [
- 'after' => '-1 year',
- 'inclusive' => true,
- ],
- ]
- );
- return self::$stats[ $this->post_type ][ $period ];
-
- default:
- self::$stats[ $this->post_type ][ $period ] = $this->get_posts_stats_by_date(
- [
- [
- 'after' => $period,
- 'inclusive' => true,
- ],
- ]
- );
- return self::$stats[ $this->post_type ][ $period ];
- }
- }
-
- /**
- * Get posts by dates.
- *
- * @param array $date_query The date query.
* @return array
*/
- public function get_posts_stats_by_date( $date_query ) {
+ public function get_data() {
$args = [
'posts_per_page' => 1000, // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
'post_type' => $this->post_type,
'post_status' => 'publish',
- 'date_query' => $date_query,
+ 'date_query' => $this->date_query,
'suppress_filters' => false,
];
diff --git a/includes/stats/class-stat-terms.php b/includes/stats/class-stat-terms.php
index d14396553..9eca97630 100644
--- a/includes/stats/class-stat-terms.php
+++ b/includes/stats/class-stat-terms.php
@@ -31,11 +31,9 @@ public function set_taxonomy( $taxonomy ) {
/**
* Get the stat data.
*
- * @param string $period The period to get the data for.
- *
* @return array
*/
- public function get_data( $period = 'week' ) {
+ public function get_data() {
return [
'total' => (array) \wp_count_terms( [ 'taxonomy' => $this->taxonomy ] ),
];
diff --git a/includes/stats/class-stat.php b/includes/stats/class-stat.php
index d2ff72177..4377cd3da 100644
--- a/includes/stats/class-stat.php
+++ b/includes/stats/class-stat.php
@@ -17,11 +17,30 @@
abstract class Stat {
/**
- * Get the stat data.
+ * Date Query.
+ *
+ * The date query, which will be then passed-on to the WP_Date_Query object.
+ *
+ * @var array
+ */
+ protected $date_query = [];
+
+ /**
+ * Set the date query.
*
- * @param string $period The period to get the data for.
+ * @param array $date_query The date query.
+ *
+ * @return Stat Returns this object to allow chaining methods.
+ */
+ public function set_date_query( $date_query ) {
+ $this->date_query = $date_query;
+ return $this;
+ }
+
+ /**
+ * Get the stat data.
*
* @return array
*/
- abstract public function get_data( $period = 'week' );
+ abstract public function get_data();
}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 9ca2d44fb..13839c80c 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -110,6 +110,7 @@
+
diff --git a/views/admin-page.php b/views/admin-page.php
index 38b5a34cd..25f5b7501 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -5,6 +5,17 @@
* @package ProgressPlanner
*/
+// TODO: This pre-populates the option with previous weeks and months values.
+// This should be moved to a separate function that can be called from the admin page.
+foreach ( [ 1, 2, 3, 4, 5 ] as $prpl_i ) {
+ \ProgressPlanner\Progress_Planner::get_instance()
+ ->get_settings()
+ ->update_value_previous_unsaved_interval( 'weeks', $prpl_i );
+ \ProgressPlanner\Progress_Planner::get_instance()
+ ->get_settings()
+ ->update_value_previous_unsaved_interval( 'months', $prpl_i );
+}
+
?>
@@ -12,12 +23,12 @@
-
- $progress_planner_post_type_object ) : ?>
- public ) : ?>
+
+ $prpl_post_type_object ) : ?>
+ public ) : ?>
-
label ); ?>
+
label ); ?>
@@ -30,57 +41,48 @@
- esc_html__( 'Day', 'progress-planner' ),
- 'week' => esc_html__( 'Week', 'progress-planner' ),
- '-2 weeks' => esc_html__( '2 Weeks', 'progress-planner' ),
- '-3 weeks' => esc_html__( '3 Weeks', 'progress-planner' ),
- 'month' => esc_html__( 'Month', 'progress-planner' ),
- 'year' => esc_html__( 'Year', 'progress-planner' ),
- ] as $progress_planner_period => $progress_planner_period_label ) :
- ?>
-
-
-
-
-
- get_stats()
- ->get_stat( 'posts' )
- ->set_post_type( $progress_planner_post_type )
- ->get_data( $progress_planner_period )['count'];
- ?>
-
-
- get_stats()
- ->get_stat( 'posts' )
- ->set_post_type( $progress_planner_post_type )
- ->get_data( $progress_planner_period )['word_count'];
- ?>
-
-
+
+
+
+
+
+ get_settings()->get_value(
+ 'stats',
+ (int) gmdate(
+ 'Y',
+ strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type )
+ ),
+ $prpl_interval_type,
+ (int) gmdate(
+ ( 'weeks' === $prpl_interval_type ) ? 'W' : 'n',
+ strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type )
+ ),
+ 'posts',
+ $prpl_post_type
+ )
+ );
+ ?>
+
+
+ get_settings()->get_value(
+ 'stats',
+ gmdate( 'Y', strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type ) ),
+ $prpl_interval_type,
+ gmdate( 'n', strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type ) ),
+ 'words',
+ $prpl_post_type
+ )
+ );
+ ?>
+
+
+
+
-
-
- get_stats()
- ->get_stat( 'posts' )
- ->set_post_type( $progress_planner_post_type )
- ->get_data( 'all' )['publish']
- )
- );
- ?>
-
-
From dbd1a6f3f84a9eb6cf162cb99c15cd28afb18deb Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 7 Feb 2024 12:05:28 +0200
Subject: [PATCH 027/490] Add set_value method and refactor get_value
---
includes/class-settings.php | 58 +++++++++++++++++++++----------------
1 file changed, 33 insertions(+), 25 deletions(-)
diff --git a/includes/class-settings.php b/includes/class-settings.php
index 8289899b8..47a263b25 100644
--- a/includes/class-settings.php
+++ b/includes/class-settings.php
@@ -40,15 +40,30 @@ public function get_value( ...$args ) {
// Merge the saved value with the default value.
$value = \array_replace_recursive( $current_value, $saved_value );
- // Get the value for a specific key.
- foreach ( $args as $arg ) {
- if ( ! isset( $value[ $arg ] ) ) {
- $value = null;
- break;
- }
- $value = $value[ $arg ];
- }
- return $value;
+ return empty( $args )
+ ? $value
+ : \_wp_array_get( $value, $args );
+ }
+
+ /**
+ * Update the option value.
+ *
+ * @param string[] $args The keys to update.
+ * This will go over the array recursively, updating the value for the last key.
+ * See get_value for more info.
+ * @param mixed $value The new value.
+ *
+ * @return bool Returns the result of the update_option function.
+ */
+ public function set_value( $args, $value ) {
+ // Get the saved value.
+ $saved_value = \get_option( $this->option_name, [] );
+
+ // Update item in the array.
+ \_wp_array_set( $saved_value, $args, $value );
+
+ // Update the option value.
+ return \update_option( $this->option_name, $saved_value );
}
/**
@@ -120,11 +135,9 @@ private function get_current_value() {
* @param string $interval_type The interval type. Can be "week" or "month".
* @param int $interval_value The number of weeks or months back to update the value for.
*
- * @return bool Returns the result of the update_option function.
+ * @return void
*/
public function update_value_previous_unsaved_interval( $interval_type = 'weeks', $interval_value = 0 ) {
- // Get the saved value.
- $saved_value = $this->get_value();
// Get the year & week numbers for the defined week/month.
$year = (int) \gmdate( 'Y', strtotime( "-$interval_value $interval_type" ) );
@@ -135,13 +148,6 @@ public function update_value_previous_unsaved_interval( $interval_type = 'weeks'
$stats = Progress_Planner::get_instance()->get_stats()->get_stat( 'posts' );
foreach ( \array_keys( \get_post_types( [ 'public' => true ] ) ) as $post_type ) {
- if (
- isset( $saved_value['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['posts'][ $post_type ] ) &&
- isset( $saved_value['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['words'][ $post_type ] )
- ) {
- continue;
- }
-
$interval_stats = $stats->set_post_type( $post_type )->set_date_query(
[
[
@@ -155,12 +161,14 @@ public function update_value_previous_unsaved_interval( $interval_type = 'weeks'
]
)->get_data();
- // Set the value.
- $saved_value['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['posts'][ $post_type ] = $interval_stats['count'];
- $saved_value['stats'][ $year ][ $interval_type ][ $interval_type_nr ]['words'][ $post_type ] = $interval_stats['word_count'];
+ $this->set_value(
+ [ 'stats', $year, $interval_type, $interval_type_nr, 'posts', $post_type ],
+ $interval_stats['count']
+ );
+ $this->set_value(
+ [ 'stats', $year, $interval_type, $interval_type_nr, 'words', $post_type ],
+ $interval_stats['word_count']
+ );
}
-
- // Update the option value.
- return \update_option( $this->option_name, $saved_value );
}
}
From 9566b098e14299e7ef2c5b8b9a9c86a2f5cac157 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 8 Feb 2024 12:29:28 +0200
Subject: [PATCH 028/490] Cleanup
---
includes/class-settings.php | 32 ++++++++---------------------
includes/stats/class-stat-posts.php | 7 -------
2 files changed, 9 insertions(+), 30 deletions(-)
diff --git a/includes/class-settings.php b/includes/class-settings.php
index 47a263b25..ff7f08f2d 100644
--- a/includes/class-settings.php
+++ b/includes/class-settings.php
@@ -76,26 +76,9 @@ private function get_current_value() {
$curr_y = (int) \gmdate( 'Y' );
$curr_m = (int) \gmdate( 'n' );
$curr_w = (int) \gmdate( 'W' );
- $curr_value = [
- 'stats' => [
- $curr_y => [
- 'weeks' => [
- $curr_w => [
- 'posts' => [],
- 'words' => [],
- ],
- ],
- 'months' => [
- $curr_m => [
- 'posts' => [],
- 'words' => [],
- ],
- ],
- ],
- ],
- ];
+ $curr_value = [];
+ $stats = Progress_Planner::get_instance()->get_stats()->get_stat( 'posts' );
- $stats = Progress_Planner::get_instance()->get_stats()->get_stat( 'posts' );
foreach ( \array_keys( \get_post_types( [ 'public' => true ] ) ) as $post_type ) {
// Set the post-type.
$stats->set_post_type( $post_type );
@@ -110,6 +93,10 @@ private function get_current_value() {
]
)->get_data();
+ // Set weekly stats.
+ \_wp_array_set( $curr_value, [ 'stats', $curr_y, 'weeks', $curr_w, 'posts', $post_type ], $week_stats['count'] );
+ \_wp_array_set( $curr_value, [ 'stats', $curr_y, 'weeks', $curr_w, 'words', $post_type ], $week_stats['word_count'] );
+
// Get monthly stats.
$month_stats = $stats->set_date_query(
[
@@ -120,10 +107,9 @@ private function get_current_value() {
]
)->get_data();
- $curr_value['stats'][ $curr_y ]['weeks'][ $curr_w ]['posts'][ $post_type ] = $week_stats['count'];
- $curr_value['stats'][ $curr_y ]['weeks'][ $curr_w ]['words'][ $post_type ] = $week_stats['word_count'];
- $curr_value['stats'][ $curr_y ]['months'][ $curr_m ]['posts'][ $post_type ] = $month_stats['count'];
- $curr_value['stats'][ $curr_y ]['months'][ $curr_m ]['words'][ $post_type ] = $month_stats['word_count'];
+ // Set monthly stats.
+ \_wp_array_set( $curr_value, [ 'stats', $curr_y, 'months', $curr_m, 'posts', $post_type ], $month_stats['count'] );
+ \_wp_array_set( $curr_value, [ 'stats', $curr_y, 'months', $curr_m, 'words', $post_type ], $month_stats['word_count'] );
}
return $curr_value;
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index d02b1de29..1d90f619d 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -19,13 +19,6 @@ class Stat_Posts extends Stat {
*/
protected $post_type = 'post';
- /**
- * Static var to hold the stats and avoid multiple queries.
- *
- * @var array
- */
- private static $stats = [];
-
/**
* Set the post-type for this stat.
*
From 254a94bfe76e1d8728aa7e069021ac759747951b Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 8 Feb 2024 13:44:27 +0200
Subject: [PATCH 029/490] POC - Experiment with Chart
---
includes/admin/class-chart.php | 59 +++++++++++
includes/class-admin.php | 19 ++++
includes/class-progress-planner.php | 9 ++
includes/class-settings.php | 46 +++++++--
views/admin-page.php | 146 +++++++++++++++++-----------
5 files changed, 216 insertions(+), 63 deletions(-)
create mode 100644 includes/admin/class-chart.php
diff --git a/includes/admin/class-chart.php b/includes/admin/class-chart.php
new file mode 100644
index 000000000..8041d237e
--- /dev/null
+++ b/includes/admin/class-chart.php
@@ -0,0 +1,59 @@
+register_hooks();
+ }
+
+ /**
+ * Register the hooks.
+ */
+ private function register_hooks() {
+ \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
+ }
+
+ /**
+ * Enqueue the scripts and styles.
+ */
+ public function enqueue_scripts() {
+ \wp_enqueue_script( 'chartjs', 'https://cdn.jsdelivr.net/npm/chart.js', [], '4.4.1', true );
+ }
+
+ /**
+ * Render the chart.
+ *
+ * @param string $id The ID of the chart.
+ * @param string $type The type of chart.
+ * @param array $data The data for the chart.
+ * @param array $options The options for the chart.
+ *
+ * @return void
+ */
+ public function render_chart( $id, $type, $data, $options ) {
+ $id = 'progress-planner-chart-' . $id;
+ ?>
+
+
+ admin_page = new Admin\Page();
+ $this->chart = new Admin\Chart();
+ }
+
+ /**
+ * Get the admin page object.
+ *
+ * @return \ProgressPlanner\Admin\Page
+ */
+ public function get_admin_page() {
+ return $this->admin_page;
+ }
+
+ /**
+ * Get the chart object.
+ *
+ * @return \ProgressPlanner\Admin\Chart
+ */
+ public function get_chart() {
+ return $this->chart;
}
}
diff --git a/includes/class-progress-planner.php b/includes/class-progress-planner.php
index e5e7a49cf..e6c2a3492 100644
--- a/includes/class-progress-planner.php
+++ b/includes/class-progress-planner.php
@@ -79,4 +79,13 @@ public function get_settings() {
public function get_stats() {
return $this->stats;
}
+
+ /**
+ * Get the admin object.
+ *
+ * @return \ProgressPlanner\Admin
+ */
+ public function get_admin() {
+ return $this->admin;
+ }
}
diff --git a/includes/class-settings.php b/includes/class-settings.php
index ff7f08f2d..f813fea08 100644
--- a/includes/class-settings.php
+++ b/includes/class-settings.php
@@ -22,15 +22,17 @@ class Settings {
/**
* Get the option value.
*
- * @param string[] ...$args Get the value for a specific key in the array.
- * This will go over the array recursively, returning the value for the last key.
- * Example: If the value is ['a' => ['b' => 'c']], get_value('a', 'b') will return 'c'.
- * If the key does not exist, it will return null.
- * If no keys are provided, it will return the entire array.
+ * @param string[] $args Get the value for a specific key in the array.
+ * This will go over the array recursively, returning the value for the last key.
+ * Example: If the value is ['a' => ['b' => 'c']], get_value('a', 'b') will return 'c'.
+ * If the key does not exist, it will return null.
+ * If no keys are provided, it will return the entire array.
+ * @param string $order The order. Can be "ASC" or "DESC".
+ * If null, then the order will be the same as the saved value.
*
* @return array
*/
- public function get_value( ...$args ) {
+ public function get_value( $args, $order = null ) {
// Get the saved value.
$saved_value = \get_option( $this->option_name, [] );
@@ -40,9 +42,39 @@ public function get_value( ...$args ) {
// Merge the saved value with the default value.
$value = \array_replace_recursive( $current_value, $saved_value );
- return empty( $args )
+ $value = empty( $args )
? $value
: \_wp_array_get( $value, $args );
+
+ return null === $order
+ ? $value
+ : $this->order( $value, $order );
+ }
+
+ /**
+ * Get the value, ordered by date.
+ *
+ * @param mixed $value The value.
+ * @param string $order The order. Can be "ASC" or "DESC".
+ *
+ * @return array
+ */
+ public function order( $value, $order = 'ASC' ) {
+ if ( ! is_array( $value ) ) {
+ return $value;
+ }
+
+ // Order the array.
+ if ( 'ASC' === $order ) {
+ \ksort( $value );
+ } else {
+ \krsort( $value );
+ }
+
+ foreach ( $value as $key => $val ) {
+ $value[ $key ] = $this->order( $val, $order );
+ }
+ return $value;
}
/**
diff --git a/views/admin-page.php b/views/admin-page.php
index 25f5b7501..3a5f6eb13 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -7,7 +7,7 @@
// TODO: This pre-populates the option with previous weeks and months values.
// This should be moved to a separate function that can be called from the admin page.
-foreach ( [ 1, 2, 3, 4, 5 ] as $prpl_i ) {
+foreach ( [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] as $prpl_i ) {
\ProgressPlanner\Progress_Planner::get_instance()
->get_settings()
->update_value_previous_unsaved_interval( 'weeks', $prpl_i );
@@ -17,7 +17,7 @@
}
?>
-
+
@@ -29,60 +29,94 @@
label ); ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- get_settings()->get_value(
- 'stats',
- (int) gmdate(
- 'Y',
- strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type )
- ),
- $prpl_interval_type,
- (int) gmdate(
- ( 'weeks' === $prpl_interval_type ) ? 'W' : 'n',
- strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type )
- ),
- 'posts',
- $prpl_post_type
- )
- );
- ?>
-
-
- get_settings()->get_value(
- 'stats',
- gmdate( 'Y', strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type ) ),
- $prpl_interval_type,
- gmdate( 'n', strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type ) ),
- 'words',
- $prpl_post_type
- )
- );
- ?>
-
-
+
+
+ get_settings()->get_value( [ 'stats' ], 'ASC' );
+ $data = [
+ 'labels' => [],
+ 'datasets' => [ [ 'data' => [] ] ],
+ ];
+ foreach ( $prpl_settings as $year => $prpl_setting ) {
+ if ( ! isset( $prpl_setting['weeks'] ) ) {
+ continue;
+ }
+ foreach ( $prpl_setting['weeks'] as $week => $prpl_week ) {
+ $data['labels'][] = $year . 'W' . $week;
+ foreach ( $prpl_week['posts'] as $post_type => $nr ) {
+ if ( $prpl_post_type === $post_type ) {
+ $data['datasets'][0]['data'][] = $nr;
+ }
+ }
+ }
+ }
+ \ProgressPlanner\Progress_Planner::get_instance()->get_admin()->get_chart()->render_chart(
+ $prpl_post_type,
+ 'line',
+ $data,
+ [ 'scales' => [ 'y' => [ 'beginAtZero' => true ] ] ]
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ get_settings()->get_value(
+ [
+ 'stats',
+ (int) gmdate(
+ 'Y',
+ strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type )
+ ),
+ $prpl_interval_type,
+ (int) gmdate(
+ ( 'weeks' === $prpl_interval_type ) ? 'W' : 'n',
+ strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type )
+ ),
+ 'posts',
+ $prpl_post_type,
+ ]
+ )
+ );
+ ?>
+
+
+ get_settings()->get_value(
+ [
+ 'stats',
+ gmdate( 'Y', strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type ) ),
+ $prpl_interval_type,
+ gmdate( 'n', strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type ) ),
+ 'words',
+ $prpl_post_type,
+ ]
+ )
+ );
+ ?>
+
+
+
+
-
-
-
+
+
From 27132bc22e2ec499c367a987964b2c02ec6eb70c Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 8 Feb 2024 14:02:29 +0200
Subject: [PATCH 030/490] CS & cleanup
---
includes/admin/class-chart.php | 26 +++++---------------------
views/admin-page.php | 19 +++++++++----------
2 files changed, 14 insertions(+), 31 deletions(-)
diff --git a/includes/admin/class-chart.php b/includes/admin/class-chart.php
index 8041d237e..2c7f15124 100644
--- a/includes/admin/class-chart.php
+++ b/includes/admin/class-chart.php
@@ -12,27 +12,6 @@
*/
class Chart {
- /**
- * Constructor.
- */
- public function __construct() {
- $this->register_hooks();
- }
-
- /**
- * Register the hooks.
- */
- private function register_hooks() {
- \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
- }
-
- /**
- * Enqueue the scripts and styles.
- */
- public function enqueue_scripts() {
- \wp_enqueue_script( 'chartjs', 'https://cdn.jsdelivr.net/npm/chart.js', [], '4.4.1', true );
- }
-
/**
* Render the chart.
*
@@ -45,7 +24,12 @@ public function enqueue_scripts() {
*/
public function render_chart( $id, $type, $data, $options ) {
$id = 'progress-planner-chart-' . $id;
+
+ // TODO: This should be properly enqueued.
+ // phpcs:ignore
+ echo '';
?>
+
@@ -32,20 +31,20 @@
get_settings()->get_value( [ 'stats' ], 'ASC' );
- $data = [
+ $prpl_settings = \ProgressPlanner\Progress_Planner::get_instance()->get_settings()->get_value( [ 'stats' ], 'ASC' );
+ $prpl_chart_data = [
'labels' => [],
'datasets' => [ [ 'data' => [] ] ],
];
- foreach ( $prpl_settings as $year => $prpl_setting ) {
+ foreach ( $prpl_settings as $prpl_chart_data_year => $prpl_setting ) {
if ( ! isset( $prpl_setting['weeks'] ) ) {
continue;
}
- foreach ( $prpl_setting['weeks'] as $week => $prpl_week ) {
- $data['labels'][] = $year . 'W' . $week;
- foreach ( $prpl_week['posts'] as $post_type => $nr ) {
- if ( $prpl_post_type === $post_type ) {
- $data['datasets'][0]['data'][] = $nr;
+ foreach ( $prpl_setting['weeks'] as $prpl_week_nr => $prpl_week ) {
+ $prpl_chart_data['labels'][] = $prpl_chart_data_year . 'W' . $prpl_week_nr;
+ foreach ( $prpl_week['posts'] as $prpl_pt => $prpl_chart_nr ) {
+ if ( $prpl_post_type === $prpl_pt ) {
+ $prpl_chart_data['datasets'][0]['data'][] = $prpl_chart_nr;
}
}
}
@@ -53,7 +52,7 @@
\ProgressPlanner\Progress_Planner::get_instance()->get_admin()->get_chart()->render_chart(
$prpl_post_type,
'line',
- $data,
+ $prpl_chart_data,
[ 'scales' => [ 'y' => [ 'beginAtZero' => true ] ] ]
);
?>
From 759d7400f69d6fa861e3de1fd40a940ab03a3bc8 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Fri, 9 Feb 2024 16:04:55 +0200
Subject: [PATCH 031/490] Refactor almost everything. Still buggy, WIP
---
assets/css/admin.css | 5 +
assets/js/admin.js | 109 ++++++++++
includes/admin/class-page.php | 96 +++++++++
includes/class-admin.php | 10 -
includes/{admin => }/class-chart.php | 4 +-
includes/class-progress-planner.php | 21 +-
includes/class-settings.php | 192 ------------------
includes/class-stats.php | 1 -
.../stats/class-stat-posts-prepopulate.php | 159 +++++++++++++++
includes/stats/class-stat-posts.php | 154 ++++++++++++--
includes/stats/class-stat-terms.php | 41 ----
includes/stats/class-stat.php | 93 +++++++--
progress-planner.php | 1 +
views/admin-page.php | 157 +++++---------
14 files changed, 643 insertions(+), 400 deletions(-)
create mode 100644 assets/css/admin.css
create mode 100644 assets/js/admin.js
rename includes/{admin => }/class-chart.php (91%)
delete mode 100644 includes/class-settings.php
create mode 100644 includes/stats/class-stat-posts-prepopulate.php
delete mode 100644 includes/stats/class-stat-terms.php
diff --git a/assets/css/admin.css b/assets/css/admin.css
new file mode 100644
index 000000000..2e230ca43
--- /dev/null
+++ b/assets/css/admin.css
@@ -0,0 +1,5 @@
+#progress-planner-scan-progress progress{
+ width: 100%;
+ max-width: 500px;
+ min-height: 1px;
+}
diff --git a/assets/js/admin.js b/assets/js/admin.js
new file mode 100644
index 000000000..6f5bd23c2
--- /dev/null
+++ b/assets/js/admin.js
@@ -0,0 +1,109 @@
+/**
+ * Loaded on edit-tags admin pages, this file contains the JavaScript for the ProgressPlanner plugin.
+ *
+ * @file This files contains the functionality for the ProgressPlanner plugin.
+ * @author Joost de Valk
+ */
+
+/* global progressPlanner, tb_remove */
+
+/**
+ * A helper to make AJAX requests.
+ *
+ * @param {Object} params The callback parameters.
+ * @param {string} params.url The URL to send the request to.
+ * @param {Object} params.data The data to send with the request.
+ * @param {Function} params.successAction The callback to run on success.
+ * @param {Function} params.failAction The callback to run on failure.
+ */
+const progressPlannerAjaxRequest = ( { url, data, successAction, failAction } ) => {
+ const http = new XMLHttpRequest();
+ http.open( 'POST', url, true );
+ http.onreadystatechange = () => {
+ let response;
+ try {
+ response = JSON.parse( http.response );
+ } catch ( e ) {
+ if ( http.readyState === 4 && http.status !== 200 ) {
+ // eslint-disable-next-line no-console
+ console.warn( http, e );
+ return http.response;
+ }
+ }
+ if ( http.readyState === 4 && http.status === 200 ) {
+ return successAction ? successAction( response ) : response;
+ }
+ return failAction ? failAction( response ) : response;
+ };
+
+ const dataForm = new FormData();
+
+ // eslint-disable-next-line prefer-const
+ for ( let [ key, value ] of Object.entries( data ) ) {
+ dataForm.append( key, value );
+ }
+
+ http.send( dataForm );
+};
+
+const progressPlannerTriggerScan = () => {
+ document.getElementById( 'progress-planner-scan-progress' ).style.display = 'block';
+ progressPlannerAjaxRequest( {
+ url: progressPlanner.ajaxUrl,
+ data: {
+ action: 'progress_planner_scan_posts',
+ _ajax_nonce: progressPlanner.nonce,
+ },
+ successAction: ( response ) => {
+ document.querySelector( '#progress-planner-scan-progress progress' ).value = response.data.progress;
+
+ progressPlannerTriggerScan();
+ if ( response.data.progress >= 100 ) {
+ location.reload();
+ }
+ },
+ } );
+};
+
+/**
+ * Similar to jQuery's $( document ).ready().
+ * Runs a callback when the DOM is ready.
+ *
+ * @param {Function} callback The callback to run when the DOM is ready.
+ */
+function progressPlannerDomReady( callback ) {
+ if ( document.readyState !== 'loading' ) {
+ callback();
+ return;
+ }
+ document.addEventListener( 'DOMContentLoaded', callback );
+}
+
+progressPlannerDomReady( () => {
+ const scanForm = document.getElementById( 'progress-planner-scan' );
+ const resetForm = document.getElementById( 'progress-planner-stats-reset' );
+ if ( scanForm ) {
+ scanForm.addEventListener( 'submit', ( e ) => {
+ e.preventDefault();
+ progressPlannerTriggerScan();
+ } );
+ }
+ if ( resetForm ) {
+ resetForm.addEventListener( 'submit', ( e ) => {
+ e.preventDefault();
+ resetForm.querySelector( 'input[type="submit"]' ).disabled = true;
+ progressPlannerAjaxRequest( {
+ url: progressPlanner.ajaxUrl,
+ data: {
+ action: 'progress_planner_reset_stats',
+ _ajax_nonce: progressPlanner.nonce,
+ },
+ successAction: ( response ) => {
+ resetForm.querySelector( 'input[type="submit"]' ).value = progressPlanner.l10n.resettingStats;
+ // Refresh the page.
+ location.reload();
+ },
+ } );
+ } );
+ }
+} );
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index c624aea82..ffbce7679 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -7,6 +7,8 @@
namespace ProgressPlanner\Admin;
+use PROGRESS_PLANNER_URL;
+
/**
* Admin page class.
*/
@@ -24,6 +26,9 @@ public function __construct() {
*/
private function register_hooks() {
\add_action( 'admin_menu', [ $this, 'add_page' ] );
+ \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
+ \add_action( 'wp_ajax_progress_planner_scan_posts', [ $this, 'ajax_scan' ] );
+ \add_action( 'wp_ajax_progress_planner_reset_stats', [ $this, 'ajax_reset_stats' ] );
}
/**
@@ -46,4 +51,95 @@ public function add_page() {
public function render_page() {
include PROGRESS_PLANNER_DIR . '/views/admin-page.php';
}
+
+ /**
+ * Enqueue scripts and styles.
+ *
+ * @param string $hook The current admin page.
+ */
+ public function enqueue_scripts( $hook ) {
+ if ( 'toplevel_page_progress-planner' !== $hook ) {
+ return;
+ }
+
+ \wp_enqueue_script(
+ 'progress-planner-admin',
+ PROGRESS_PLANNER_URL . 'assets/js/admin.js',
+ [],
+ filemtime( PROGRESS_PLANNER_DIR . '/assets/js/admin.js' ),
+ true
+ );
+
+ // Localize the script.
+ \wp_localize_script(
+ 'progress-planner-admin',
+ 'progressPlanner',
+ [
+ 'ajaxUrl' => \admin_url( 'admin-ajax.php' ),
+ 'nonce' => \wp_create_nonce( 'progress_planner_scan' ),
+ 'l10n' => [
+ 'resettingStats' => \esc_html__( 'Resetting stats...', 'progress-planner' ),
+ ],
+ ]
+ );
+
+ \wp_enqueue_style(
+ 'progress-planner-admin',
+ PROGRESS_PLANNER_URL . 'assets/css/admin.css',
+ [],
+ filemtime( PROGRESS_PLANNER_DIR . '/assets/css/admin.css' )
+ );
+ }
+
+ /**
+ * Ajax scan.
+ */
+ public function ajax_scan() {
+ // Check the nonce.
+ if ( ! \check_ajax_referer( 'progress_planner_scan', 'nonce', false ) ) {
+ \wp_send_json_error( [ 'message' => \esc_html__( 'Invalid nonce.', 'progress-planner' ) ] );
+ }
+
+ // Scan the posts.
+ $prepopulate = new \ProgressPlanner\Stats\Stat_Posts_Prepopulate();
+ $prepopulate->prepopulate();
+
+ // Get the total pages.
+ $total_pages = $prepopulate->get_total_pages();
+
+ // Get the last page.
+ $last_page = $prepopulate->get_last_prepopulated_page();
+
+ \wp_send_json_success(
+ [
+ 'totalPages' => $total_pages,
+ 'lastPage' => $last_page,
+ 'isComplete' => $prepopulate->is_prepopulating_complete(),
+ 'progress' => round( ( $last_page / $total_pages ) * 100 ),
+ 'messages' => [
+ 'scanComplete' => \esc_html__( 'Scan complete.', 'progress-planner' ),
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Ajax reset stats.
+ */
+ public function ajax_reset_stats() {
+ // Check the nonce.
+ if ( ! \check_ajax_referer( 'progress_planner_scan', 'nonce', false ) ) {
+ \wp_send_json_error( [ 'message' => \esc_html__( 'Invalid nonce.', 'progress-planner' ) ] );
+ }
+
+ // Reset the stats.
+ $stats = new \ProgressPlanner\Stats\Stat_Posts();
+ $stats->reset_stats();
+
+ \wp_send_json_success(
+ [
+ 'message' => \esc_html__( 'Stats reset. Refreshing the page...', 'progress-planner' ),
+ ]
+ );
+ }
}
diff --git a/includes/class-admin.php b/includes/class-admin.php
index da0531a06..cc2f89e9a 100644
--- a/includes/class-admin.php
+++ b/includes/class-admin.php
@@ -24,7 +24,6 @@ class Admin {
*/
public function __construct() {
$this->admin_page = new Admin\Page();
- $this->chart = new Admin\Chart();
}
/**
@@ -35,13 +34,4 @@ public function __construct() {
public function get_admin_page() {
return $this->admin_page;
}
-
- /**
- * Get the chart object.
- *
- * @return \ProgressPlanner\Admin\Chart
- */
- public function get_chart() {
- return $this->chart;
- }
}
diff --git a/includes/admin/class-chart.php b/includes/class-chart.php
similarity index 91%
rename from includes/admin/class-chart.php
rename to includes/class-chart.php
index 2c7f15124..63971517c 100644
--- a/includes/admin/class-chart.php
+++ b/includes/class-chart.php
@@ -5,7 +5,7 @@
* @package ProgressPlanner
*/
-namespace ProgressPlanner\Admin;
+namespace ProgressPlanner;
/**
* Render a chart.
@@ -35,7 +35,7 @@ public function render_chart( $id, $type, $data, $options ) {
var chart = new Chart( document.getElementById( '' ), {
type: '',
data: ,
- options: ,
+ // options: ,
} );
admin = new Admin();
- $this->settings = new Settings();
- $this->stats = new Stats();
- }
-
- /**
- * Get the settings object.
- *
- * @return \ProgressPlanner\Settings
- */
- public function get_settings() {
- return $this->settings;
+ $this->admin = new Admin();
+ $this->stats = new Stats();
}
/**
diff --git a/includes/class-settings.php b/includes/class-settings.php
deleted file mode 100644
index f813fea08..000000000
--- a/includes/class-settings.php
+++ /dev/null
@@ -1,192 +0,0 @@
- ['b' => 'c']], get_value('a', 'b') will return 'c'.
- * If the key does not exist, it will return null.
- * If no keys are provided, it will return the entire array.
- * @param string $order The order. Can be "ASC" or "DESC".
- * If null, then the order will be the same as the saved value.
- *
- * @return array
- */
- public function get_value( $args, $order = null ) {
- // Get the saved value.
- $saved_value = \get_option( $this->option_name, [] );
-
- // Get the value for current week & month.
- $current_value = $this->get_current_value();
-
- // Merge the saved value with the default value.
- $value = \array_replace_recursive( $current_value, $saved_value );
-
- $value = empty( $args )
- ? $value
- : \_wp_array_get( $value, $args );
-
- return null === $order
- ? $value
- : $this->order( $value, $order );
- }
-
- /**
- * Get the value, ordered by date.
- *
- * @param mixed $value The value.
- * @param string $order The order. Can be "ASC" or "DESC".
- *
- * @return array
- */
- public function order( $value, $order = 'ASC' ) {
- if ( ! is_array( $value ) ) {
- return $value;
- }
-
- // Order the array.
- if ( 'ASC' === $order ) {
- \ksort( $value );
- } else {
- \krsort( $value );
- }
-
- foreach ( $value as $key => $val ) {
- $value[ $key ] = $this->order( $val, $order );
- }
- return $value;
- }
-
- /**
- * Update the option value.
- *
- * @param string[] $args The keys to update.
- * This will go over the array recursively, updating the value for the last key.
- * See get_value for more info.
- * @param mixed $value The new value.
- *
- * @return bool Returns the result of the update_option function.
- */
- public function set_value( $args, $value ) {
- // Get the saved value.
- $saved_value = \get_option( $this->option_name, [] );
-
- // Update item in the array.
- \_wp_array_set( $saved_value, $args, $value );
-
- // Update the option value.
- return \update_option( $this->option_name, $saved_value );
- }
-
- /**
- * Get the value for the current week & month.
- *
- * @return array
- */
- private function get_current_value() {
- // Get the values for current week and month.
- $curr_y = (int) \gmdate( 'Y' );
- $curr_m = (int) \gmdate( 'n' );
- $curr_w = (int) \gmdate( 'W' );
- $curr_value = [];
- $stats = Progress_Planner::get_instance()->get_stats()->get_stat( 'posts' );
-
- foreach ( \array_keys( \get_post_types( [ 'public' => true ] ) ) as $post_type ) {
- // Set the post-type.
- $stats->set_post_type( $post_type );
-
- // Get weekly stats.
- $week_stats = $stats->set_date_query(
- [
- [
- 'after' => '-1 week',
- 'inclusive' => true,
- ],
- ]
- )->get_data();
-
- // Set weekly stats.
- \_wp_array_set( $curr_value, [ 'stats', $curr_y, 'weeks', $curr_w, 'posts', $post_type ], $week_stats['count'] );
- \_wp_array_set( $curr_value, [ 'stats', $curr_y, 'weeks', $curr_w, 'words', $post_type ], $week_stats['word_count'] );
-
- // Get monthly stats.
- $month_stats = $stats->set_date_query(
- [
- [
- 'after' => gmdate( 'F Y' ),
- 'inclusive' => true,
- ],
- ]
- )->get_data();
-
- // Set monthly stats.
- \_wp_array_set( $curr_value, [ 'stats', $curr_y, 'months', $curr_m, 'posts', $post_type ], $month_stats['count'] );
- \_wp_array_set( $curr_value, [ 'stats', $curr_y, 'months', $curr_m, 'words', $post_type ], $month_stats['word_count'] );
- }
-
- return $curr_value;
- }
-
- /**
- * Update value for a previous, unsaved week.
- *
- * @param string $interval_type The interval type. Can be "week" or "month".
- * @param int $interval_value The number of weeks or months back to update the value for.
- *
- * @return void
- */
- public function update_value_previous_unsaved_interval( $interval_type = 'weeks', $interval_value = 0 ) {
-
- // Get the year & week numbers for the defined week/month.
- $year = (int) \gmdate( 'Y', strtotime( "-$interval_value $interval_type" ) );
- $interval_type_nr = (int) \gmdate(
- 'weeks' === $interval_type ? 'W' : 'n',
- strtotime( "-$interval_value $interval_type" )
- );
-
- $stats = Progress_Planner::get_instance()->get_stats()->get_stat( 'posts' );
- foreach ( \array_keys( \get_post_types( [ 'public' => true ] ) ) as $post_type ) {
- $interval_stats = $stats->set_post_type( $post_type )->set_date_query(
- [
- [
- 'after' => '-' . ( $interval_value + 1 ) . ' ' . $interval_type,
- 'inclusive' => true,
- ],
- [
- 'before' => '-' . $interval_value . ' ' . $interval_type,
- 'inclusive' => false,
- ],
- ]
- )->get_data();
-
- $this->set_value(
- [ 'stats', $year, $interval_type, $interval_type_nr, 'posts', $post_type ],
- $interval_stats['count']
- );
- $this->set_value(
- [ 'stats', $year, $interval_type, $interval_type_nr, 'words', $post_type ],
- $interval_stats['word_count']
- );
- }
- }
-}
diff --git a/includes/class-stats.php b/includes/class-stats.php
index f89b19300..6dc7ee91f 100644
--- a/includes/class-stats.php
+++ b/includes/class-stats.php
@@ -62,6 +62,5 @@ public function get_stat( $id ) {
*/
private function register_stats() {
$this->add_stat( 'posts', new Stats\Stat_Posts() );
- $this->add_stat( 'terms', new Stats\Stat_Terms() );
}
}
diff --git a/includes/stats/class-stat-posts-prepopulate.php b/includes/stats/class-stat-posts-prepopulate.php
new file mode 100644
index 000000000..47a4b4fb2
--- /dev/null
+++ b/includes/stats/class-stat-posts-prepopulate.php
@@ -0,0 +1,159 @@
+last_page = $this->get_last_prepopulated_page();
+ }
+
+ /**
+ * Get the last page that was prepopulated from the API.
+ *
+ * @return int
+ */
+ public function get_last_prepopulated_page() {
+ $option_value = $this->get_value();
+
+ return ( isset( $option_value[ self::LAST_PAGE_KEY ] ) )
+ ? $option_value[ self::LAST_PAGE_KEY ]
+ : 0;
+ }
+
+ /**
+ * Get the total number of pages that need to be prepopulated.
+ *
+ * @return int
+ */
+ public function get_total_pages() {
+
+ $post_types = array_keys( \get_post_types( [ 'public' => true ] ) );
+ $total = 0;
+
+ foreach ( $post_types as $post_type ) {
+ $total += (int) \wp_count_posts( $post_type )->publish;
+ }
+
+ // Calculate the total number of pages.
+ return (int) ceil( $total / $this->posts_per_page );
+ }
+
+ /**
+ * Set the last page that was prepopulated from the API.
+ *
+ * @param int $page The page number.
+ *
+ * @return void
+ */
+ private function set_last_prepopulated_page( $page ) {
+ $option_value = $this->get_value();
+
+ $option_value[ self::LAST_PAGE_KEY ] = $page;
+ $this->set_value( [], $option_value );
+ }
+
+ /**
+ * Whether prepopulating is complete.
+ *
+ * @return bool
+ */
+ public function is_prepopulating_complete() {
+ $option_value = $this->get_value();
+ if (
+ isset( $option_value[ self::FINISHED_KEY ] ) &&
+ $option_value[ self::FINISHED_KEY ]
+ ) {
+ // Remove the last page key. It's no longer needed.
+ if ( isset( $option_value[ self::LAST_PAGE_KEY ] ) ) {
+ unset( $option_value[ self::LAST_PAGE_KEY ] );
+ $this->set_value( [], $option_value );
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get posts and prepopulate the stats.
+ *
+ * @return void
+ */
+ public function prepopulate() {
+ // Bail early if prepopulating is complete.
+ if ( $this->is_prepopulating_complete() ) {
+ return;
+ }
+ $posts = \get_posts(
+ [
+ 'posts_per_page' => $this->posts_per_page,
+ 'paged' => $this->last_page + 1,
+ 'post_type' => array_keys( \get_post_types( [ 'public' => true ] ) ),
+ 'post_status' => 'publish',
+ 'suppress_filters' => false,
+ // Start from oldest to newest.
+ 'order' => 'ASC',
+ 'orderby' => 'date',
+ ]
+ );
+
+ // If there are no posts for this page, then prepopulating is complete.
+ if ( empty( $posts ) ) {
+ $option_value = $this->get_value();
+
+ $option_value[ self::FINISHED_KEY ] = true;
+ $this->set_value( [], $option_value );
+ return;
+ }
+
+ // Save the posts stats.
+ foreach ( $posts as $post ) {
+ $this->save_post( $post );
+ }
+
+ // Set the last page that was prepopulated from the API.
+ $this->set_last_prepopulated_page( $this->last_page + 1 );
+ }
+}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 1d90f619d..1f4b943bc 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -7,6 +7,8 @@
namespace ProgressPlanner\Stats;
+use ProgressPlanner\Chart;
+
/**
* Stats about posts.
*/
@@ -19,6 +21,13 @@ class Stat_Posts extends Stat {
*/
protected $post_type = 'post';
+ /**
+ * The stat type. This is used as a key in the settings array.
+ *
+ * @var string
+ */
+ protected $type = 'posts';
+
/**
* Set the post-type for this stat.
*
@@ -32,31 +41,140 @@ public function set_post_type( $post_type ) {
}
/**
- * Get the data.
+ * Save a post to the stats.
+ *
+ * @param \WP_Post $post The post.
+ */
+ protected function save_post( $post ) {
+ // Get the date.
+ $date = (int) gmdate( 'Ymd', strtotime( $post->post_date ) );
+
+ // Add the post to the stats.
+ $this->set_value(
+ [ $date, $post->ID ],
+ [
+ 'post_type' => $post->post_type,
+ 'words' => \str_word_count( $post->post_content ),
+ ],
+ );
+ }
+
+ /**
+ * Get stats for date range.
+ *
+ * @param string $start_date The start date.
+ * @param string $end_date The end date.
+ * @param array $post_types The post types.
*
* @return array
*/
- public function get_data() {
- $args = [
- 'posts_per_page' => 1000, // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
- 'post_type' => $this->post_type,
- 'post_status' => 'publish',
- 'date_query' => $this->date_query,
- 'suppress_filters' => false,
- ];
+ public function get_stats( $start_date, $end_date, $post_types = [] ) {
+ $stats = $this->get_value();
- $posts = get_posts( $args );
+ // Format the start and end dates.
+ $start_date = (int) gmdate( 'Ymd', strtotime( $start_date ) );
+ $end_date = (int) gmdate( 'Ymd', strtotime( $end_date ) );
- // Get the number of words.
- $word_count = 0;
- foreach ( $posts as $post ) {
- $word_count += str_word_count( $post->post_content );
+ // Get the stats for the date range and post types.
+ foreach ( array_keys( $stats ) as $key ) {
+ // Remove the stats that are outside the date range.
+ if ( $key <= $start_date || $key > $end_date ) {
+ unset( $stats[ $key ] );
+ continue;
+ }
+
+ // If we have not defined post types, then we don't need to filter by post type.
+ if ( empty( $post_types ) ) {
+ continue;
+ }
+
+ // Remove the stats that are not in the post types.
+ foreach ( $stats[ $key ] as $post_id => $details ) {
+ if ( ! \in_array( $details['post_type'], $post_types, true ) ) {
+ unset( $stats[ $key ][ $post_id ] );
+ }
+ }
}
- return [
- 'count' => count( $posts ),
- 'post_ids' => \wp_list_pluck( $posts, 'ID' ),
- 'word_count' => $word_count,
+ // Filter out empty dates.
+ $stats = \array_filter( $stats );
+
+ return $stats;
+ }
+
+ /**
+ * Build a chart for the stats.
+ *
+ * @param array $post_types The post types.
+ * @param string $context The context for the chart. Can be 'count' or 'words'.
+ * @param string $interval The interval for the chart. Can be 'days', 'weeks', 'months', 'years'.
+ * @param int $range The number of intervals to show.
+ * @param int $offset The offset for the intervals.
+ */
+ public function build_chart( $post_types = [], $context = 'count', $interval = 'weeks', $range = 10, $offset = 0 ) {
+ $post_types = empty( $post_types )
+ ? array_keys( \get_post_types( [ 'public' => true ] ) )
+ : $post_types;
+
+ $range_array_end = \range( $offset, $range - 1 );
+ $range_array_start = \range( $offset + 1, $range );
+ \krsort( $range_array_start );
+ \krsort( $range_array_end );
+
+ $range_array = \array_combine( $range_array_start, $range_array_end );
+
+ $data = [
+ 'labels' => [],
+ 'datasets' => [],
];
+ $datasets = [];
+ $post_type_count_totals = [];
+ foreach ( $post_types as $post_type ) {
+ $post_type_count_totals[ $post_type ] = 0;
+ $datasets[ $post_type ] = [
+ 'label' => \get_post_type_object( $post_type )->label,
+ 'data' => [],
+ ];
+ }
+
+ foreach ( $range_array as $start => $end ) {
+ $stats = $this->get_stats( "-$start $interval", "-$end $interval", $post_types );
+
+ // TODO: Format the date depending on the user's locale.
+ $data['labels'][] = gmdate( 'Y-m-d', strtotime( "-$start $interval" ) );
+
+ foreach ( $post_types as $post_type ) {
+ foreach ( $stats as $posts ) {
+ foreach ( $posts as $post_details ) {
+ if ( $post_details['post_type'] === $post_type ) {
+ if ( 'words' === $context ) {
+ $post_type_count_totals[ $post_type ] += $post_details['words'];
+ continue;
+ }
+ ++$post_type_count_totals[ $post_type ];
+ }
+ }
+ }
+ $datasets[ $post_type ]['data'][] = $post_type_count_totals[ $post_type ];
+ }
+ }
+ $data['datasets'] = \array_values( $datasets );
+
+ $chart = new Chart();
+ $chart->render_chart(
+ md5( wp_json_encode( [ $post_types, $context, $interval, $range, $offset ] ) ),
+ 'line',
+ $data,
+ []
+ );
+ }
+
+ /**
+ * Reset the stats in our database.
+ *
+ * @return void
+ */
+ public function reset_stats() {
+ $this->set_value( [], [] );
}
}
diff --git a/includes/stats/class-stat-terms.php b/includes/stats/class-stat-terms.php
deleted file mode 100644
index 9eca97630..000000000
--- a/includes/stats/class-stat-terms.php
+++ /dev/null
@@ -1,41 +0,0 @@
-taxonomy = $taxonomy;
- }
-
- /**
- * Get the stat data.
- *
- * @return array
- */
- public function get_data() {
- return [
- 'total' => (array) \wp_count_terms( [ 'taxonomy' => $this->taxonomy ] ),
- ];
- }
-}
diff --git a/includes/stats/class-stat.php b/includes/stats/class-stat.php
index 4377cd3da..356388fd1 100644
--- a/includes/stats/class-stat.php
+++ b/includes/stats/class-stat.php
@@ -2,7 +2,7 @@
/**
* An object containing info about an individual stat.
*
- * This is an abstract class, meant to be extended by individual stat classes.
+ * This object is meant to be extended by individual stat classes.
*
* @package ProgressPlanner
*/
@@ -12,9 +12,35 @@
/**
* An object containing info about an individual stat.
*
- * This is an abstract class, meant to be extended by individual stat classes.
+ * This object is meant to be extended by individual stat classes.
*/
-abstract class Stat {
+class Stat {
+
+ /**
+ * The setting name.
+ */
+ const SETTING_NAME = 'progress_planner_stats';
+
+ /**
+ * The stat type. This is used as a key in the settings array.
+ *
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * The stats setting value.
+ *
+ * @var array
+ */
+ protected $stats;
+
+ /**
+ * The value.
+ *
+ * @var array
+ */
+ protected $value;
/**
* Date Query.
@@ -26,21 +52,64 @@ abstract class Stat {
protected $date_query = [];
/**
- * Set the date query.
+ * Constructor.
+ */
+ public function __construct() {
+ $this->value = $this->get_value();
+ }
+
+ /**
+ * Get the value.
*
- * @param array $date_query The date query.
+ * @param array $index The index. This is an array of keys, which will be used to get the value.
+ * This will go over the array recursively, getting the value for the last key.
+ * See _wp_array_get for more info.
+ * @return mixed
+ */
+ public function get_value( $index = [] ) {
+ if ( $this->value ) {
+ return $this->value;
+ }
+
+ if ( ! isset( $this->stats[ $this->type ] ) ) {
+ $this->stats = \get_option( self::SETTING_NAME, [ $this->type => [] ] );
+ }
+
+ if ( ! empty( $index ) ) {
+ return \_wp_array_get( $this->stats[ $this->type ], $index );
+ }
+
+ return $this->stats[ $this->type ];
+ }
+
+ /**
+ * Set the value.
*
- * @return Stat Returns this object to allow chaining methods.
+ * @param array $index The index. This is an array of keys, which will be used to set the value.
+ * This will go over the array recursively, updating the value for the last key.
+ * See _wp_array_set for more info.
+ * @param mixed $value The value.
*/
- public function set_date_query( $date_query ) {
- $this->date_query = $date_query;
- return $this;
+ public function set_value( $index, $value ) {
+ // Call $this->get_value, to populate $this->stats.
+ $this->get_value();
+
+ // Add $this->type to the beginning of the index array.
+ \array_unshift( $index, $this->type );
+
+ // Update the value in the array.
+ \_wp_array_set( $this->stats, $index, $value );
+
+ // Save the option.
+ \update_option( self::SETTING_NAME, $this->stats );
}
/**
- * Get the stat data.
+ * Set the date query.
*
- * @return array
+ * @param array $date_query The date query.
*/
- abstract public function get_data();
+ public function set_date_query( $date_query ) {
+ $this->date_query = $date_query;
+ }
}
diff --git a/progress-planner.php b/progress-planner.php
index cd32120b8..d7be3d221 100644
--- a/progress-planner.php
+++ b/progress-planner.php
@@ -6,6 +6,7 @@
*/
define( 'PROGRESS_PLANNER_DIR', __DIR__ );
+define( 'PROGRESS_PLANNER_URL', plugin_dir_url( __FILE__ ) );
require_once PROGRESS_PLANNER_DIR . '/includes/autoload.php';
diff --git a/views/admin-page.php b/views/admin-page.php
index 3cb390588..aa3f7c7e2 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -5,117 +5,64 @@
* @package ProgressPlanner
*/
-// TODO: This pre-populates the option with previous weeks and months values.
-// This should be moved to a separate function that can be called from the admin page.
-foreach ( [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] as $prpl_i ) {
- \ProgressPlanner\Progress_Planner::get_instance()
- ->get_settings()
- ->update_value_previous_unsaved_interval( 'weeks', $prpl_i );
- \ProgressPlanner\Progress_Planner::get_instance()
- ->get_settings()
- ->update_value_previous_unsaved_interval( 'months', $prpl_i );
-}
+// TODO: Move this to a method to allow prepopulating stats from the admin page.
+$prpl_prepopulate = new ProgressPlanner\Stats\Stat_Posts_Prepopulate();
+
+// Get the stats object.
+$prpl_stats_posts = new ProgressPlanner\Stats\Stat_Posts();
+
+// var_dump($prpl_stats_posts->get_value());
+// Check if we have a scan pending.
+$prpl_scan_pending = false;
+$prpl_scan_progress = 0;
+if ( ! $prpl_stats_posts->get_value( $prpl_prepopulate::FINISHED_KEY ) ) {
+ $prpl_scan_pending = true;
+}
?>
-
+
+
+
+
+
+
+
+
+
-
- $prpl_post_type_object ) : ?>
- public ) : ?>
-
-
-
label ); ?>
-
-
- get_settings()->get_value( [ 'stats' ], 'ASC' );
- $prpl_chart_data = [
- 'labels' => [],
- 'datasets' => [ [ 'data' => [] ] ],
- ];
- foreach ( $prpl_settings as $prpl_chart_data_year => $prpl_setting ) {
- if ( ! isset( $prpl_setting['weeks'] ) ) {
- continue;
- }
- foreach ( $prpl_setting['weeks'] as $prpl_week_nr => $prpl_week ) {
- $prpl_chart_data['labels'][] = $prpl_chart_data_year . 'W' . $prpl_week_nr;
- foreach ( $prpl_week['posts'] as $prpl_pt => $prpl_chart_nr ) {
- if ( $prpl_post_type === $prpl_pt ) {
- $prpl_chart_data['datasets'][0]['data'][] = $prpl_chart_nr;
- }
- }
- }
- }
- \ProgressPlanner\Progress_Planner::get_instance()->get_admin()->get_chart()->render_chart(
- $prpl_post_type,
- 'line',
- $prpl_chart_data,
- [ 'scales' => [ 'y' => [ 'beginAtZero' => true ] ] ]
- );
- ?>
+
+
+
+
+ build_chart( [], 'count', 'weeks', 10, 0 ); ?>
+
+
+
+ build_chart( [], 'words', 'weeks', 10, 0 ); ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- get_settings()->get_value(
- [
- 'stats',
- (int) gmdate(
- 'Y',
- strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type )
- ),
- $prpl_interval_type,
- (int) gmdate(
- ( 'weeks' === $prpl_interval_type ) ? 'W' : 'n',
- strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type )
- ),
- 'posts',
- $prpl_post_type,
- ]
- )
- );
- ?>
-
-
- get_settings()->get_value(
- [
- 'stats',
- gmdate( 'Y', strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type ) ),
- $prpl_interval_type,
- gmdate( 'n', strtotime( '-' . $prpl_interval_value . ' ' . $prpl_interval_type ) ),
- 'words',
- $prpl_post_type,
- ]
- )
- );
- ?>
-
-
-
-
-
-
-
+
From 6a7c324afecca27d8896a16e8d8a4f06368203cc Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 12 Feb 2024 10:02:12 +0200
Subject: [PATCH 032/490] use a const
---
includes/stats/class-stat-posts-prepopulate.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/includes/stats/class-stat-posts-prepopulate.php b/includes/stats/class-stat-posts-prepopulate.php
index 47a4b4fb2..409b0e0bf 100644
--- a/includes/stats/class-stat-posts-prepopulate.php
+++ b/includes/stats/class-stat-posts-prepopulate.php
@@ -17,7 +17,7 @@ class Stat_Posts_Prepopulate extends Stat_Posts {
*
* @var int
*/
- private $posts_per_page = 100;
+ const POSTS_PER_PAGE = 10;
/**
* Key used to store the last page that was prepopulated from the API.
@@ -78,7 +78,7 @@ public function get_total_pages() {
}
// Calculate the total number of pages.
- return (int) ceil( $total / $this->posts_per_page );
+ return (int) ceil( $total / self::POSTS_PER_PAGE );
}
/**
@@ -128,7 +128,7 @@ public function prepopulate() {
}
$posts = \get_posts(
[
- 'posts_per_page' => $this->posts_per_page,
+ 'posts_per_page' => self::POSTS_PER_PAGE,
'paged' => $this->last_page + 1,
'post_type' => array_keys( \get_post_types( [ 'public' => true ] ) ),
'post_status' => 'publish',
From 285d9aa80f94a5a59219c9bc970b4cea30bb7380 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 12 Feb 2024 10:02:42 +0200
Subject: [PATCH 033/490] Use mysql2date instead of gmdate
---
includes/stats/class-stat-posts.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 1f4b943bc..3a65e5df0 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -47,7 +47,7 @@ public function set_post_type( $post_type ) {
*/
protected function save_post( $post ) {
// Get the date.
- $date = (int) gmdate( 'Ymd', strtotime( $post->post_date ) );
+ $date = (int) mysql2date( 'Ymd', $post->post_date );
// Add the post to the stats.
$this->set_value(
From f091cfb2b7e72d1d19eefa0fa7560133a5b6bd26 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 12 Feb 2024 10:04:13 +0200
Subject: [PATCH 034/490] add debug for the option
---
views/admin-page.php | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/views/admin-page.php b/views/admin-page.php
index aa3f7c7e2..eebeae0d8 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -53,6 +53,15 @@
+
+
+
+
+ get_value() ); ?>
+
+
+
+
From 5275f9c4f80e466730ad48128831d1b72b76ebce Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 12 Feb 2024 14:29:07 +0200
Subject: [PATCH 035/490] Prepopulation now works.
---
assets/js/admin.js | 40 +++--
includes/admin/class-page.php | 17 +-
includes/class-chart.php | 8 +-
.../stats/class-stat-posts-prepopulate.php | 149 ++++++------------
includes/stats/class-stat-posts.php | 18 ++-
includes/stats/class-stat.php | 7 +-
views/admin-page.php | 64 +++++---
7 files changed, 155 insertions(+), 148 deletions(-)
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 6f5bd23c2..8b46bda75 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -1,11 +1,8 @@
/**
* Loaded on edit-tags admin pages, this file contains the JavaScript for the ProgressPlanner plugin.
- *
- * @file This files contains the functionality for the ProgressPlanner plugin.
- * @author Joost de Valk
*/
-/* global progressPlanner, tb_remove */
+/* global progressPlanner */
/**
* A helper to make AJAX requests.
@@ -48,20 +45,39 @@ const progressPlannerAjaxRequest = ( { url, data, successAction, failAction } )
const progressPlannerTriggerScan = () => {
document.getElementById( 'progress-planner-scan-progress' ).style.display = 'block';
+ const successAction = ( response ) => {
+ const progressBar = document.querySelector( '#progress-planner-scan-progress progress' );
+ // Update the progressbar.
+ if ( response.data.progress > progressBar.value ) {
+ progressBar.value = response.data.progress;
+ }
+
+ // Refresh the page when scan has finished.
+ if ( response.data.progress >= 100 ) {
+ location.reload();
+ return;
+ }
+
+ progressPlannerTriggerScan();
+ };
+ const failAction = ( response ) => {
+ if ( response && response.data && response.data.progress ) {
+ successAction( response );
+ return;
+ }
+ // Wait 1 second and re-trigger.
+ setTimeout( () => {
+ progressPlannerTriggerScan();
+ }, 1000 );
+ };
progressPlannerAjaxRequest( {
url: progressPlanner.ajaxUrl,
data: {
action: 'progress_planner_scan_posts',
_ajax_nonce: progressPlanner.nonce,
},
- successAction: ( response ) => {
- document.querySelector( '#progress-planner-scan-progress progress' ).value = response.data.progress;
-
- progressPlannerTriggerScan();
- if ( response.data.progress >= 100 ) {
- location.reload();
- }
- },
+ successAction: successAction,
+ failAction: failAction,
} );
};
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index ffbce7679..2546a1b53 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -104,19 +104,18 @@ public function ajax_scan() {
$prepopulate = new \ProgressPlanner\Stats\Stat_Posts_Prepopulate();
$prepopulate->prepopulate();
- // Get the total pages.
- $total_pages = $prepopulate->get_total_pages();
+ // Get the last scanned post ID.
+ $last_scanned_id = $prepopulate->get_last_prepopulated_post();
- // Get the last page.
- $last_page = $prepopulate->get_last_prepopulated_page();
+ // Get the last post-ID that exists on the site.
+ $last_post_id = $prepopulate->get_last_post_id();
\wp_send_json_success(
[
- 'totalPages' => $total_pages,
- 'lastPage' => $last_page,
- 'isComplete' => $prepopulate->is_prepopulating_complete(),
- 'progress' => round( ( $last_page / $total_pages ) * 100 ),
- 'messages' => [
+ 'lastScanned' => $last_scanned_id,
+ 'lastPost' => $last_post_id,
+ 'progress' => round( ( $last_scanned_id / $last_post_id ) * 100 ),
+ 'messages' => [
'scanComplete' => \esc_html__( 'Scan complete.', 'progress-planner' ),
],
]
diff --git a/includes/class-chart.php b/includes/class-chart.php
index 63971517c..01408b651 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -22,20 +22,22 @@ class Chart {
*
* @return void
*/
- public function render_chart( $id, $type, $data, $options ) {
+ public function render_chart( $id, $type, $data, $options = [] ) {
$id = 'progress-planner-chart-' . $id;
+ $options['responsive'] = true;
+
// TODO: This should be properly enqueued.
// phpcs:ignore
echo '';
?>
-
+
last_page = $this->get_last_prepopulated_page();
- }
+ private $last_scanned_post_id = 0;
/**
* Get the last page that was prepopulated from the API.
*
* @return int
*/
- public function get_last_prepopulated_page() {
- $option_value = $this->get_value();
-
- return ( isset( $option_value[ self::LAST_PAGE_KEY ] ) )
- ? $option_value[ self::LAST_PAGE_KEY ]
- : 0;
- }
-
- /**
- * Get the total number of pages that need to be prepopulated.
- *
- * @return int
- */
- public function get_total_pages() {
-
- $post_types = array_keys( \get_post_types( [ 'public' => true ] ) );
- $total = 0;
-
- foreach ( $post_types as $post_type ) {
- $total += (int) \wp_count_posts( $post_type )->publish;
+ public function get_last_prepopulated_post() {
+ if ( $this->last_scanned_post_id ) {
+ return $this->last_scanned_post_id;
}
- // Calculate the total number of pages.
- return (int) ceil( $total / self::POSTS_PER_PAGE );
+ $option_value = $this->get_value();
+ foreach ( $option_value as $posts ) {
+ foreach ( $posts as $post_id => $details ) {
+ if ( $post_id > $this->last_scanned_post_id ) {
+ $this->last_scanned_post_id = $post_id;
+ }
+ }
+ }
+ return $this->last_scanned_post_id;
}
/**
- * Set the last page that was prepopulated from the API.
- *
- * @param int $page The page number.
+ * Get posts and prepopulate the stats.
*
* @return void
*/
- private function set_last_prepopulated_page( $page ) {
- $option_value = $this->get_value();
+ public function prepopulate() {
+ // Get the last post we processed.
+ $last_id = $this->get_last_prepopulated_post();
- $option_value[ self::LAST_PAGE_KEY ] = $page;
- $this->set_value( [], $option_value );
- }
+ // Build an array of posts to save.
+ $post_ids = \range( $last_id, $last_id + self::POSTS_PER_PAGE );
- /**
- * Whether prepopulating is complete.
- *
- * @return bool
- */
- public function is_prepopulating_complete() {
- $option_value = $this->get_value();
- if (
- isset( $option_value[ self::FINISHED_KEY ] ) &&
- $option_value[ self::FINISHED_KEY ]
- ) {
- // Remove the last page key. It's no longer needed.
- if ( isset( $option_value[ self::LAST_PAGE_KEY ] ) ) {
- unset( $option_value[ self::LAST_PAGE_KEY ] );
- $this->set_value( [], $option_value );
+ foreach ( $post_ids as $post_id ) {
+ $post = get_post( $post_id );
+
+ // If the post doesn't exist or is not publish, skip it.
+ if ( ! $post || 'publish' !== $post->post_status ) {
+ if ( $post ) {
+ $this->last_scanned_post_id = $post->ID;
+ }
+ continue;
}
- return true;
+
+ $this->save_post( $post );
+ $this->last_scanned_post_id = $post->ID;
}
- return false;
}
/**
- * Get posts and prepopulate the stats.
+ * Get the last post-ID created.
*
- * @return void
+ * @return int
*/
- public function prepopulate() {
- // Bail early if prepopulating is complete.
- if ( $this->is_prepopulating_complete() ) {
- return;
+ public function get_last_post_id() {
+ if ( $this->last_post_id ) {
+ return $this->last_post_id;
}
- $posts = \get_posts(
+ $last_post = \get_posts(
[
- 'posts_per_page' => self::POSTS_PER_PAGE,
- 'paged' => $this->last_page + 1,
- 'post_type' => array_keys( \get_post_types( [ 'public' => true ] ) ),
+ 'posts_per_page' => 1,
+ 'post_type' => $this->get_post_types_names(),
'post_status' => 'publish',
'suppress_filters' => false,
- // Start from oldest to newest.
- 'order' => 'ASC',
- 'orderby' => 'date',
+ 'order' => 'DESC',
+ 'orderby' => 'ID',
]
);
-
- // If there are no posts for this page, then prepopulating is complete.
- if ( empty( $posts ) ) {
- $option_value = $this->get_value();
-
- $option_value[ self::FINISHED_KEY ] = true;
- $this->set_value( [], $option_value );
- return;
+ if ( empty( $last_post ) ) {
+ return 0;
}
-
- // Save the posts stats.
- foreach ( $posts as $post ) {
- $this->save_post( $post );
- }
-
- // Set the last page that was prepopulated from the API.
- $this->set_last_prepopulated_page( $this->last_page + 1 );
+ $this->last_post_id = $last_post[0]->ID;
+ return $this->last_post_id;
}
}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 3a65e5df0..a864c0260 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -44,8 +44,11 @@ public function set_post_type( $post_type ) {
* Save a post to the stats.
*
* @param \WP_Post $post The post.
+ *
+ * @return bool
*/
protected function save_post( $post ) {
+ // error_log( $post->post_date . ' => ' . mysql2date( 'Ymd', $post->post_date ) );
// Get the date.
$date = (int) mysql2date( 'Ymd', $post->post_date );
@@ -113,7 +116,7 @@ public function get_stats( $start_date, $end_date, $post_types = [] ) {
*/
public function build_chart( $post_types = [], $context = 'count', $interval = 'weeks', $range = 10, $offset = 0 ) {
$post_types = empty( $post_types )
- ? array_keys( \get_post_types( [ 'public' => true ] ) )
+ ? $this->get_post_types_names()
: $post_types;
$range_array_end = \range( $offset, $range - 1 );
@@ -177,4 +180,17 @@ public function build_chart( $post_types = [], $context = 'count', $interval = '
public function reset_stats() {
$this->set_value( [], [] );
}
+
+ /**
+ * Get an array of post-types names for the stats.
+ *
+ * @return array
+ */
+ public function get_post_types_names() {
+ $post_types = \get_post_types( [ 'public' => true ] );
+ unset( $post_types['attachment'] );
+
+ return array_keys( $post_types );
+ }
+
}
diff --git a/includes/stats/class-stat.php b/includes/stats/class-stat.php
index 356388fd1..1d89fbb63 100644
--- a/includes/stats/class-stat.php
+++ b/includes/stats/class-stat.php
@@ -92,16 +92,17 @@ public function get_value( $index = [] ) {
*/
public function set_value( $index, $value ) {
// Call $this->get_value, to populate $this->stats.
- $this->get_value();
+ $stats = \get_option( self::SETTING_NAME, [ $this->type => [] ] );
// Add $this->type to the beginning of the index array.
\array_unshift( $index, $this->type );
// Update the value in the array.
- \_wp_array_set( $this->stats, $index, $value );
+ \_wp_array_set( $stats, $index, $value );
// Save the option.
- \update_option( self::SETTING_NAME, $this->stats );
+ \update_option( self::SETTING_NAME, $stats );
+ $this->stats = $stats;
}
/**
diff --git a/views/admin-page.php b/views/admin-page.php
index eebeae0d8..c7ca8cb53 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -11,12 +11,19 @@
// Get the stats object.
$prpl_stats_posts = new ProgressPlanner\Stats\Stat_Posts();
-// var_dump($prpl_stats_posts->get_value());
+// Values for the graph filters.
+$prpl_filters_intervals = [
+ 'days' => __( 'Days', 'progress-planner' ),
+ 'weeks' => __( 'Weeks', 'progress-planner' ),
+ 'months' => __( 'Months', 'progress-planner' ),
+];
+$prpl_filters_interval = isset( $_POST['interval'] ) ? sanitize_key( $_POST['interval'] ) : 'weeks';
+$prpl_filters_number = isset( $_POST['number'] ) ? (int) $_POST['number'] : 10;
// Check if we have a scan pending.
$prpl_scan_pending = false;
$prpl_scan_progress = 0;
-if ( ! $prpl_stats_posts->get_value( $prpl_prepopulate::FINISHED_KEY ) ) {
+if ( empty( $prpl_stats_posts->get_value() ) ) {
$prpl_scan_pending = true;
}
?>
@@ -44,34 +51,49 @@
/**
* The scan is not pending.
*
- * Show a form to reset the stats (while we're still in development).
- *
- * Show the stats.
+ * Show the stats, and at the end a form to reset the stats
+ * (while we're still in development).
*/
?>
+
+
+
+
+
+
+
+ build_chart( [], 'count', $prpl_filters_interval, $prpl_filters_number, 0 ); ?>
+
+
+
+ build_chart( [], 'words', $prpl_filters_interval, $prpl_filters_number, 0 ); ?>
+
+
+
+
-
-
-
+
get_value() ); ?>
-
-
-
-
-
-
- build_chart( [], 'count', 'weeks', 10, 0 ); ?>
-
-
-
- build_chart( [], 'words', 'weeks', 10, 0 ); ?>
-
-
+
Date: Tue, 13 Feb 2024 09:29:43 +0200
Subject: [PATCH 036/490] minor tweaks & fixes
---
assets/js/admin.js | 43 +++++++++++++++++--
.../stats/class-stat-posts-prepopulate.php | 2 +-
includes/stats/class-stat-posts.php | 3 +-
views/admin-page.php | 8 ++--
4 files changed, 47 insertions(+), 9 deletions(-)
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 8b46bda75..952095146 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -45,6 +45,14 @@ const progressPlannerAjaxRequest = ( { url, data, successAction, failAction } )
const progressPlannerTriggerScan = () => {
document.getElementById( 'progress-planner-scan-progress' ).style.display = 'block';
+
+ /**
+ * The action to run on a successful AJAX request.
+ * This function should update the UI and re-trigger the scan if necessary.
+ *
+ * @param {Object} response The response from the server.
+ * The response should contain a `progress` property.
+ */
const successAction = ( response ) => {
const progressBar = document.querySelector( '#progress-planner-scan-progress progress' );
// Update the progressbar.
@@ -52,24 +60,42 @@ const progressPlannerTriggerScan = () => {
progressBar.value = response.data.progress;
}
+ console.info( `Progress: ${response.data.progress}%, (${response.data.lastScanned}/${response.data.lastPost})` );
+
// Refresh the page when scan has finished.
if ( response.data.progress >= 100 ) {
- location.reload();
+ // location.reload();
return;
}
- progressPlannerTriggerScan();
+ // Wait half a second and re-trigger.
+ setTimeout( () => {
+ progressPlannerTriggerScan();
+ }, 500 );
};
+
+ /**
+ * The action to run on a failed AJAX request.
+ * This function should re-trigger the scan if necessary.
+ * If the response contains a `progress` property, the successAction should be run instead.
+ *
+ * @param {Object} response The response from the server.
+ */
const failAction = ( response ) => {
if ( response && response.data && response.data.progress ) {
successAction( response );
return;
}
- // Wait 1 second and re-trigger.
+
+ // Wait 2 seconds and re-trigger.
setTimeout( () => {
progressPlannerTriggerScan();
}, 1000 );
};
+
+ /**
+ * The AJAX request to run.
+ */
progressPlannerAjaxRequest( {
url: progressPlanner.ajaxUrl,
data: {
@@ -98,16 +124,27 @@ function progressPlannerDomReady( callback ) {
progressPlannerDomReady( () => {
const scanForm = document.getElementById( 'progress-planner-scan' );
const resetForm = document.getElementById( 'progress-planner-stats-reset' );
+
+ /**
+ * Add an event listener for the scan form.
+ */
if ( scanForm ) {
scanForm.addEventListener( 'submit', ( e ) => {
e.preventDefault();
+ scanForm.querySelector( 'input[type="submit"]' ).disabled = true;
progressPlannerTriggerScan();
} );
}
+
+ /**
+ * Add an event listener for the reset form.
+ */
if ( resetForm ) {
resetForm.addEventListener( 'submit', ( e ) => {
e.preventDefault();
resetForm.querySelector( 'input[type="submit"]' ).disabled = true;
+
+ // Make an AJAX request to reset the stats.
progressPlannerAjaxRequest( {
url: progressPlanner.ajaxUrl,
data: {
diff --git a/includes/stats/class-stat-posts-prepopulate.php b/includes/stats/class-stat-posts-prepopulate.php
index 238e1bd49..fb3679e52 100644
--- a/includes/stats/class-stat-posts-prepopulate.php
+++ b/includes/stats/class-stat-posts-prepopulate.php
@@ -17,7 +17,7 @@ class Stat_Posts_Prepopulate extends Stat_Posts {
*
* @var int
*/
- const POSTS_PER_PAGE = 30;
+ const POSTS_PER_PAGE = 100;
/**
* The last post-ID.
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index a864c0260..e5c178a63 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -45,7 +45,7 @@ public function set_post_type( $post_type ) {
*
* @param \WP_Post $post The post.
*
- * @return bool
+ * @return void
*/
protected function save_post( $post ) {
// error_log( $post->post_date . ' => ' . mysql2date( 'Ymd', $post->post_date ) );
@@ -192,5 +192,4 @@ public function get_post_types_names() {
return array_keys( $post_types );
}
-
}
diff --git a/views/admin-page.php b/views/admin-page.php
index c7ca8cb53..2c13d823d 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -17,8 +17,10 @@
'weeks' => __( 'Weeks', 'progress-planner' ),
'months' => __( 'Months', 'progress-planner' ),
];
-$prpl_filters_interval = isset( $_POST['interval'] ) ? sanitize_key( $_POST['interval'] ) : 'weeks';
-$prpl_filters_number = isset( $_POST['number'] ) ? (int) $_POST['number'] : 10;
+// phpcs:ignore WordPress.Security.NonceVerification.Missing
+$prpl_filters_interval = isset( $_POST['interval'] ) ? sanitize_key( $_POST['interval'] ) : 'weeks';
+// phpcs:ignore WordPress.Security.NonceVerification.Missing
+$prpl_filters_number = isset( $_POST['number'] ) ? (int) $_POST['number'] : 10;
// Check if we have a scan pending.
$prpl_scan_pending = false;
@@ -67,7 +69,7 @@
-
+
From 3250e7de3b6a08af2de2f8397413f64f34e66b12 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 13 Feb 2024 09:32:08 +0200
Subject: [PATCH 037/490] Reload page when prepopulating is finished
---
assets/js/admin.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 952095146..20b5ee75a 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -64,7 +64,7 @@ const progressPlannerTriggerScan = () => {
// Refresh the page when scan has finished.
if ( response.data.progress >= 100 ) {
- // location.reload();
+ location.reload();
return;
}
From 40e0b6cb0d37db5933a416850518fd6e6da75f2e Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 13 Feb 2024 09:45:57 +0200
Subject: [PATCH 038/490] Use a short-lived transient to save the last-scanned
post.
---
.../stats/class-stat-posts-prepopulate.php | 22 ++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/includes/stats/class-stat-posts-prepopulate.php b/includes/stats/class-stat-posts-prepopulate.php
index fb3679e52..656861998 100644
--- a/includes/stats/class-stat-posts-prepopulate.php
+++ b/includes/stats/class-stat-posts-prepopulate.php
@@ -39,10 +39,19 @@ class Stat_Posts_Prepopulate extends Stat_Posts {
* @return int
*/
public function get_last_prepopulated_post() {
+ // If we have the last scanned post, return it.
if ( $this->last_scanned_post_id ) {
return $this->last_scanned_post_id;
}
+ // Try to get the value from the transient.
+ $cached = \get_transient( 'progress_planner_last_prepopulated_post' );
+ if ( $cached ) {
+ $this->last_scanned_post_id = $cached;
+ return $this->last_scanned_post_id;
+ }
+
+ // Get the last scanned post-ID from the stats.
$option_value = $this->get_value();
foreach ( $option_value as $posts ) {
foreach ( $posts as $post_id => $details ) {
@@ -54,6 +63,13 @@ public function get_last_prepopulated_post() {
return $this->last_scanned_post_id;
}
+ /**
+ * Set the last prepopulated post.
+ */
+ public function save_last_prepopulated_post() {
+ \set_transient( 'progress_planner_last_prepopulated_post', $this->last_scanned_post_id, \HOUR_IN_SECONDS );
+ }
+
/**
* Get posts and prepopulate the stats.
*
@@ -71,14 +87,14 @@ public function prepopulate() {
// If the post doesn't exist or is not publish, skip it.
if ( ! $post || 'publish' !== $post->post_status ) {
- if ( $post ) {
- $this->last_scanned_post_id = $post->ID;
- }
+ $this->last_scanned_post_id = $post_id;
+ $this->save_last_prepopulated_post();
continue;
}
$this->save_post( $post );
$this->last_scanned_post_id = $post->ID;
+ $this->save_last_prepopulated_post();
}
}
From 05a72d55f91245a7be9fc5b74345c7fd6db0e68f Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 13 Feb 2024 14:10:37 +0200
Subject: [PATCH 039/490] Add basic structure for goals
---
includes/class-goals.php | 81 ++++++++++++++++
includes/class-progress-planner.php | 17 ++++
includes/goals/class-goal.php | 139 ++++++++++++++++++++++++++++
3 files changed, 237 insertions(+)
create mode 100644 includes/class-goals.php
create mode 100644 includes/goals/class-goal.php
diff --git a/includes/class-goals.php b/includes/class-goals.php
new file mode 100644
index 000000000..3a958dc8b
--- /dev/null
+++ b/includes/class-goals.php
@@ -0,0 +1,81 @@
+register_goals();
+ }
+
+ /**
+ * Add a goal to the collection.
+ *
+ * @param Goal $goal The goal object.
+ */
+ public function add_goal( $goal ) {
+ $this->goals[] = $goal;
+ }
+
+ /**
+ * Get all goals.
+ *
+ * @return array
+ */
+ public function get_all_goals() {
+ return $this->goals;
+ }
+
+ /**
+ * Get an individual goal.
+ *
+ * @param string $id The ID of the goal.
+ * @return Goal
+ */
+ public function get_goal( $id ) {
+ foreach ( $this->goals as $goal ) {
+ if ( $id === $goal->get_details()['id'] ) {
+ return $goal;
+ }
+ }
+ return new Goals\Goal();
+ }
+
+ /**
+ * Register the individual goals.
+ */
+ private function register_goals() {
+ $this->add_goal(
+ new Goals\Goal(
+ [
+ 'id' => 'weekly_post',
+ 'title' => esc_html__( 'Write a weekly blog post', 'progress-planner' ),
+ 'description' => '',
+ 'type' => 'post',
+ 'frequency' => 'weekly',
+ 'priority' => 'high',
+ ]
+ )
+ );
+ }
+}
diff --git a/includes/class-progress-planner.php b/includes/class-progress-planner.php
index 36d020e3b..2462cda56 100644
--- a/includes/class-progress-planner.php
+++ b/includes/class-progress-planner.php
@@ -33,6 +33,13 @@ class Progress_Planner {
*/
private $admin;
+ /**
+ * The Goals object.
+ *
+ * @var \ProgressPlanner\Goals
+ */
+ private $goals;
+
/**
* Get the single instance of this class.
*
@@ -52,6 +59,7 @@ public static function get_instance() {
private function __construct() {
$this->admin = new Admin();
$this->stats = new Stats();
+ $this->goals = new Goals();
}
/**
@@ -71,4 +79,13 @@ public function get_stats() {
public function get_admin() {
return $this->admin;
}
+
+ /**
+ * Get the goals object.
+ *
+ * @return \ProgressPlanner\Goals
+ */
+ public function get_goals() {
+ return $this->goals;
+ }
}
diff --git a/includes/goals/class-goal.php b/includes/goals/class-goal.php
new file mode 100644
index 000000000..ae8fe6d8d
--- /dev/null
+++ b/includes/goals/class-goal.php
@@ -0,0 +1,139 @@
+ '',
+ 'title' => '',
+ 'description' => '',
+ 'type' => '',
+ 'frequency' => '',
+ 'start_date' => '',
+ 'end_date' => '',
+ 'status' => '',
+ 'priority' => '',
+ 'progress' => '',
+ ]
+ );
+ $this->id = $args['id'];
+ $this->title = $args['title'];
+ $this->description = $args['description'];
+ $this->type = $args['type'];
+ $this->frequency = $args['frequency'];
+ $this->start_date = $args['start_date'];
+ $this->end_date = $args['end_date'];
+ $this->status = $args['status'];
+ $this->priority = $args['priority'];
+ $this->progress = $args['progress'];
+ }
+
+ /**
+ * Get the goal ID.
+ *
+ * @return string
+ */
+ public function get_details() {
+ return [
+ 'id' => $this->id,
+ 'title' => $this->title,
+ 'description' => $this->description,
+ 'type' => $this->type,
+ 'frequency' => $this->frequency,
+ 'start_date' => $this->start_date,
+ 'end_date' => $this->end_date,
+ 'status' => $this->status,
+ 'priority' => $this->priority,
+ 'progress' => $this->progress,
+ ];
+ }
+}
From 33e3fab25c2a032632edab384ae22f22f875b48f Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 13 Feb 2024 15:04:53 +0200
Subject: [PATCH 040/490] separate chart class, cleanup & fixes
---
includes/charts/class-posts.php | 80 ++++++++++++++++++++++++++
includes/class-chart.php | 10 ++--
includes/class-goals.php | 2 +-
includes/goals/class-goal.php | 2 +-
includes/stats/class-stat-posts.php | 88 +----------------------------
views/admin-page.php | 20 ++++++-
6 files changed, 106 insertions(+), 96 deletions(-)
create mode 100644 includes/charts/class-posts.php
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
new file mode 100644
index 000000000..9971ae075
--- /dev/null
+++ b/includes/charts/class-posts.php
@@ -0,0 +1,80 @@
+ [],
+ 'datasets' => [],
+ ];
+ $datasets = [];
+ $post_type_count_totals = [];
+ foreach ( $post_types as $post_type ) {
+ $post_type_count_totals[ $post_type ] = 0;
+ $datasets[ $post_type ] = [
+ 'label' => \get_post_type_object( $post_type )->label,
+ 'data' => [],
+ ];
+ }
+
+ $stat_posts = new Stat_Posts();
+ foreach ( $range_array as $start => $end ) {
+ $stats = $stat_posts->get_stats( "-$start $interval", "-$end $interval", $post_types );
+
+ // TODO: Format the date depending on the user's locale.
+ $data['labels'][] = gmdate( 'Y-m-d', strtotime( "-$start $interval" ) );
+
+ foreach ( $post_types as $post_type ) {
+ foreach ( $stats as $posts ) {
+ foreach ( $posts as $post_details ) {
+ if ( $post_details['post_type'] === $post_type ) {
+ if ( 'words' === $context ) {
+ $post_type_count_totals[ $post_type ] += $post_details['words'];
+ continue;
+ }
+ ++$post_type_count_totals[ $post_type ];
+ }
+ }
+ }
+ $datasets[ $post_type ]['data'][] = $post_type_count_totals[ $post_type ];
+ }
+ }
+ $data['datasets'] = \array_values( $datasets );
+
+ $this->render_chart(
+ md5( wp_json_encode( [ $post_types, $context, $interval, $range, $offset ] ) ),
+ 'line',
+ $data,
+ []
+ );
+ }
+}
diff --git a/includes/class-chart.php b/includes/class-chart.php
index 01408b651..ef6774ca1 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -32,12 +32,12 @@ public function render_chart( $id, $type, $data, $options = [] ) {
echo '';
?>
-
+
'weekly_post',
- 'title' => esc_html__( 'Write a weekly blog post', 'progress-planner' ),
+ 'title' => \esc_html__( 'Write a weekly blog post', 'progress-planner' ),
'description' => '',
'type' => 'post',
'frequency' => 'weekly',
diff --git a/includes/goals/class-goal.php b/includes/goals/class-goal.php
index ae8fe6d8d..6f0e3b7cc 100644
--- a/includes/goals/class-goal.php
+++ b/includes/goals/class-goal.php
@@ -90,7 +90,7 @@ class Goal {
* @param array $args The goal arguments.
*/
public function __construct( $args = [] ) {
- $args = wp_parse_args(
+ $args = \wp_parse_args(
$args,
[
'id' => '',
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index e5c178a63..9233bf5cd 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -7,20 +7,13 @@
namespace ProgressPlanner\Stats;
-use ProgressPlanner\Chart;
+use ProgressPlanner\Charts\Posts as Posts_Chart;
/**
* Stats about posts.
*/
class Stat_Posts extends Stat {
- /**
- * The post-type for this stat.
- *
- * @var string
- */
- protected $post_type = 'post';
-
/**
* The stat type. This is used as a key in the settings array.
*
@@ -28,18 +21,6 @@ class Stat_Posts extends Stat {
*/
protected $type = 'posts';
- /**
- * Set the post-type for this stat.
- *
- * @param string $post_type The post-type.
- *
- * @return Stat_Posts Returns this object to allow chaining methods.
- */
- public function set_post_type( $post_type ) {
- $this->post_type = $post_type;
- return $this;
- }
-
/**
* Save a post to the stats.
*
@@ -105,73 +86,6 @@ public function get_stats( $start_date, $end_date, $post_types = [] ) {
return $stats;
}
- /**
- * Build a chart for the stats.
- *
- * @param array $post_types The post types.
- * @param string $context The context for the chart. Can be 'count' or 'words'.
- * @param string $interval The interval for the chart. Can be 'days', 'weeks', 'months', 'years'.
- * @param int $range The number of intervals to show.
- * @param int $offset The offset for the intervals.
- */
- public function build_chart( $post_types = [], $context = 'count', $interval = 'weeks', $range = 10, $offset = 0 ) {
- $post_types = empty( $post_types )
- ? $this->get_post_types_names()
- : $post_types;
-
- $range_array_end = \range( $offset, $range - 1 );
- $range_array_start = \range( $offset + 1, $range );
- \krsort( $range_array_start );
- \krsort( $range_array_end );
-
- $range_array = \array_combine( $range_array_start, $range_array_end );
-
- $data = [
- 'labels' => [],
- 'datasets' => [],
- ];
- $datasets = [];
- $post_type_count_totals = [];
- foreach ( $post_types as $post_type ) {
- $post_type_count_totals[ $post_type ] = 0;
- $datasets[ $post_type ] = [
- 'label' => \get_post_type_object( $post_type )->label,
- 'data' => [],
- ];
- }
-
- foreach ( $range_array as $start => $end ) {
- $stats = $this->get_stats( "-$start $interval", "-$end $interval", $post_types );
-
- // TODO: Format the date depending on the user's locale.
- $data['labels'][] = gmdate( 'Y-m-d', strtotime( "-$start $interval" ) );
-
- foreach ( $post_types as $post_type ) {
- foreach ( $stats as $posts ) {
- foreach ( $posts as $post_details ) {
- if ( $post_details['post_type'] === $post_type ) {
- if ( 'words' === $context ) {
- $post_type_count_totals[ $post_type ] += $post_details['words'];
- continue;
- }
- ++$post_type_count_totals[ $post_type ];
- }
- }
- }
- $datasets[ $post_type ]['data'][] = $post_type_count_totals[ $post_type ];
- }
- }
- $data['datasets'] = \array_values( $datasets );
-
- $chart = new Chart();
- $chart->render_chart(
- md5( wp_json_encode( [ $post_types, $context, $interval, $range, $offset ] ) ),
- 'line',
- $data,
- []
- );
- }
-
/**
* Reset the stats in our database.
*
diff --git a/views/admin-page.php b/views/admin-page.php
index 2c13d823d..7a9d4dfae 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -78,11 +78,27 @@
- build_chart( [], 'count', $prpl_filters_interval, $prpl_filters_number, 0 ); ?>
+ render(
+ $prpl_stats_posts->get_post_types_names(),
+ 'count',
+ $prpl_filters_interval,
+ $prpl_filters_number,
+ 0
+ );
+ ?>
- build_chart( [], 'words', $prpl_filters_interval, $prpl_filters_number, 0 ); ?>
+ render(
+ $prpl_stats_posts->get_post_types_names(),
+ 'words',
+ $prpl_filters_interval,
+ $prpl_filters_number,
+ 0
+ );
+ ?>
From 66352bbdc7a28a372b13fe4adda36253e3395916 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 14 Feb 2024 13:34:10 +0200
Subject: [PATCH 041/490] refactor to introduce Goals framework
---
includes/charts/class-posts.php | 6 +-
...ss-progress-planner.php => class-base.php} | 22 +--
includes/class-goals.php | 73 ++++------
includes/goals/class-goal-posts.php | 26 ++++
includes/goals/class-goal-recurring.php | 131 ++++++++++++++++++
includes/goals/class-goal.php | 47 +++++--
includes/stats/class-stat-posts.php | 5 -
progress-planner.php | 2 +-
8 files changed, 230 insertions(+), 82 deletions(-)
rename includes/{class-progress-planner.php => class-base.php} (75%)
create mode 100644 includes/goals/class-goal-posts.php
create mode 100644 includes/goals/class-goal-recurring.php
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index 9971ae075..ee68df359 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -48,7 +48,11 @@ public function render( $post_types = [], $context = 'count', $interval = 'weeks
$stat_posts = new Stat_Posts();
foreach ( $range_array as $start => $end ) {
- $stats = $stat_posts->get_stats( "-$start $interval", "-$end $interval", $post_types );
+ $stats = $stat_posts->get_stats(
+ (int) gmdate( 'Ymd', strtotime( "-$start $interval" ) ),
+ (int) gmdate( 'Ymd', strtotime( "-$end $interval" ) ),
+ $post_types
+ );
// TODO: Format the date depending on the user's locale.
$data['labels'][] = gmdate( 'Y-m-d', strtotime( "-$start $interval" ) );
diff --git a/includes/class-progress-planner.php b/includes/class-base.php
similarity index 75%
rename from includes/class-progress-planner.php
rename to includes/class-base.php
index 2462cda56..44cf34c56 100644
--- a/includes/class-progress-planner.php
+++ b/includes/class-base.php
@@ -10,12 +10,12 @@
/**
* Main plugin class.
*/
-class Progress_Planner {
+class Base {
/**
* An instance of this class.
*
- * @var \ProgressPlanner\Progress_Planner
+ * @var \ProgressPlanner\Base
*/
private static $instance;
@@ -33,17 +33,10 @@ class Progress_Planner {
*/
private $admin;
- /**
- * The Goals object.
- *
- * @var \ProgressPlanner\Goals
- */
- private $goals;
-
/**
* Get the single instance of this class.
*
- * @return \ProgressPlanner\Progress_Planner
+ * @return \ProgressPlanner\Base
*/
public static function get_instance() {
if ( null === self::$instance ) {
@@ -79,13 +72,4 @@ public function get_stats() {
public function get_admin() {
return $this->admin;
}
-
- /**
- * Get the goals object.
- *
- * @return \ProgressPlanner\Goals
- */
- public function get_goals() {
- return $this->goals;
- }
}
diff --git a/includes/class-goals.php b/includes/class-goals.php
index 24b767369..44e857f6d 100644
--- a/includes/class-goals.php
+++ b/includes/class-goals.php
@@ -9,73 +9,56 @@
/**
* Goals class.
- *
- * This is a collection of individual Goal objects.
*/
-class Goals {
-
- /**
- * The individual goals.
- *
- * @var array
- */
- private $goals = [];
+class Goals extends Base {
/**
* Constructor.
*/
public function __construct() {
- $this->register_goals();
+ $this->register_core_goals();
}
/**
- * Add a goal to the collection.
- *
- * @param Goal $goal The goal object.
+ * Register the goals.
*/
- public function add_goal( $goal ) {
- $this->goals[] = $goal;
+ private function register_core_goals() {
+ $this->register_weekly_post_goal();
}
/**
- * Get all goals.
- *
- * @return array
+ * Register weekly-post goal.
*/
- public function get_all_goals() {
- return $this->goals;
- }
+ private function register_weekly_post_goal() {
+ $stats = $this->get_stats();
- /**
- * Get an individual goal.
- *
- * @param string $id The ID of the goal.
- * @return Goal
- */
- public function get_goal( $id ) {
- foreach ( $this->goals as $goal ) {
- if ( $id === $goal->get_details()['id'] ) {
- return $goal;
- }
- }
- return new Goals\Goal();
- }
+ // Get the start date for all stats.
+ $start_date = array_keys( $this->get_stats()->get_stat( 'posts' )->get_value() );
+ sort( $start_date );
+ $start_date = $start_date[0];
- /**
- * Register the individual goals.
- */
- private function register_goals() {
- $this->add_goal(
- new Goals\Goal(
+ new \ProgressPlanner\Goals\Goal_Recurring(
+ new \ProgressPlanner\Goals\Goal_Posts(
[
'id' => 'weekly_post',
'title' => \esc_html__( 'Write a weekly blog post', 'progress-planner' ),
'description' => '',
- 'type' => 'post',
- 'frequency' => 'weekly',
+ 'status' => 'active',
'priority' => 'high',
+ 'evaluate' => function ( $goal_object ) use ( $stats ) {
+ return (bool) count(
+ $stats->get_stat( 'posts' )->get_stats(
+ $goal_object->get_details()['start_date'],
+ $goal_object->get_details()['end_date'],
+ [ 'post' ]
+ )
+ );
+ },
]
- )
+ ),
+ 'weekly',
+ $start_date, // Beginning of the stats.
+ gmdate( 'Ymd' ) // Today.
);
}
}
diff --git a/includes/goals/class-goal-posts.php b/includes/goals/class-goal-posts.php
new file mode 100644
index 000000000..f58b7e8e9
--- /dev/null
+++ b/includes/goals/class-goal-posts.php
@@ -0,0 +1,26 @@
+get_details()['evaluate'];
+ return $callback( $this );
+ }
+}
diff --git a/includes/goals/class-goal-recurring.php b/includes/goals/class-goal-recurring.php
new file mode 100644
index 000000000..c271fdd5d
--- /dev/null
+++ b/includes/goals/class-goal-recurring.php
@@ -0,0 +1,131 @@
+goal = $goal;
+ $this->frequency = $frequency;
+ $this->start = $start;
+ $this->end = $end;
+ }
+
+ /**
+ * Build an array of occurences for the goal.
+ *
+ * @return Goal[]
+ */
+ public function get_occurences() {
+ if ( ! empty( $this->occurences ) ) {
+ return $this->occurences;
+ }
+
+ $ranges = $this->get_date_periods();
+
+ foreach ( $ranges as $range ) {
+ $goal = clone $this->goal;
+ $goal->set_start_date( $range['start'] );
+ $goal->set_end_date( $range['end'] );
+ $this->occurences[] = $goal;
+ }
+
+ return $this->occurences;
+ }
+
+ /**
+ * Get an array of periods with start and end dates.
+ *
+ * @return array
+ */
+ public function get_date_periods() {
+ $start = \DateTime::createFromFormat( 'Ymd', $this->start );
+ $end = \DateTime::createFromFormat( 'Ymd', $this->end );
+ $end = $end->modify( '+1 day' );
+
+ switch ( $this->frequency ) {
+ case 'daily':
+ $interval = new \DateInterval( 'P1D' );
+ break;
+
+ case 'weekly':
+ $interval = new \DateInterval( 'P1W' );
+ break;
+
+ case 'monthly':
+ $interval = new \DateInterval( 'P1M' );
+ break;
+ }
+
+ $period = new \DatePeriod( $start, $interval, 100 );
+ $dates_array = [];
+ foreach ( $period as $date ) {
+ $dates_array[] = $date->format( 'Ymd' );
+ }
+
+ $date_ranges = [];
+ foreach ( $dates_array as $key => $date ) {
+ if ( isset( $dates_array[ $key + 1 ] ) ) {
+ $date_ranges[] = [
+ 'start' => $date,
+ 'end' => \DateTime::createFromFormat( 'Ymd', $dates_array[ $key + 1 ] )
+ ->modify( '-1 day' )
+ ->format( 'Ymd' ),
+ ];
+ }
+ }
+
+ return $date_ranges;
+ }
+}
diff --git a/includes/goals/class-goal.php b/includes/goals/class-goal.php
index 6f0e3b7cc..4ce08b6a4 100644
--- a/includes/goals/class-goal.php
+++ b/includes/goals/class-goal.php
@@ -12,7 +12,7 @@
/**
* An object containing info about an individual goal.
*/
-class Goal {
+abstract class Goal {
/**
* The goal ID.
@@ -42,13 +42,6 @@ class Goal {
*/
protected $type;
- /**
- * The goal frequency.
- *
- * @var string
- */
- protected $frequency;
-
/**
* The goal start date.
*
@@ -84,6 +77,13 @@ class Goal {
*/
protected $progress;
+ /**
+ * The goal evaluation function.
+ *
+ * @var string|callable
+ */
+ protected $evaluate;
+
/**
* Constructor.
*
@@ -97,24 +97,24 @@ public function __construct( $args = [] ) {
'title' => '',
'description' => '',
'type' => '',
- 'frequency' => '',
'start_date' => '',
'end_date' => '',
'status' => '',
'priority' => '',
'progress' => '',
+ 'evaluate' => '__return_false',
]
);
$this->id = $args['id'];
$this->title = $args['title'];
$this->description = $args['description'];
$this->type = $args['type'];
- $this->frequency = $args['frequency'];
$this->start_date = $args['start_date'];
$this->end_date = $args['end_date'];
$this->status = $args['status'];
$this->priority = $args['priority'];
$this->progress = $args['progress'];
+ $this->evaluate = $args['evaluate'];
}
/**
@@ -128,12 +128,37 @@ public function get_details() {
'title' => $this->title,
'description' => $this->description,
'type' => $this->type,
- 'frequency' => $this->frequency,
'start_date' => $this->start_date,
'end_date' => $this->end_date,
'status' => $this->status,
'priority' => $this->priority,
'progress' => $this->progress,
+ 'evaluate' => $this->evaluate,
];
}
+
+ /**
+ * Set the start date.
+ *
+ * @param string $start_date The start date.
+ */
+ public function set_start_date( $start_date ) {
+ $this->start_date = $start_date;
+ }
+
+ /**
+ * Set the end date.
+ *
+ * @param string $end_date The end date.
+ */
+ public function set_end_date( $end_date ) {
+ $this->end_date = $end_date;
+ }
+
+ /**
+ * Whether the goal is accomplished for a date-range.
+ *
+ * @return bool
+ */
+ abstract public function evaluate();
}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 9233bf5cd..c8ff13a32 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -29,7 +29,6 @@ class Stat_Posts extends Stat {
* @return void
*/
protected function save_post( $post ) {
- // error_log( $post->post_date . ' => ' . mysql2date( 'Ymd', $post->post_date ) );
// Get the date.
$date = (int) mysql2date( 'Ymd', $post->post_date );
@@ -55,10 +54,6 @@ protected function save_post( $post ) {
public function get_stats( $start_date, $end_date, $post_types = [] ) {
$stats = $this->get_value();
- // Format the start and end dates.
- $start_date = (int) gmdate( 'Ymd', strtotime( $start_date ) );
- $end_date = (int) gmdate( 'Ymd', strtotime( $end_date ) );
-
// Get the stats for the date range and post types.
foreach ( array_keys( $stats ) as $key ) {
// Remove the stats that are outside the date range.
diff --git a/progress-planner.php b/progress-planner.php
index d7be3d221..f4e629505 100644
--- a/progress-planner.php
+++ b/progress-planner.php
@@ -10,4 +10,4 @@
require_once PROGRESS_PLANNER_DIR . '/includes/autoload.php';
-\ProgressPlanner\Progress_Planner::get_instance();
+\ProgressPlanner\Base::get_instance();
From 99557cf9c13494029d00406808a257b2af578758 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 14 Feb 2024 13:36:52 +0200
Subject: [PATCH 042/490] bugfix
---
includes/class-goals.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/includes/class-goals.php b/includes/class-goals.php
index 44e857f6d..8a39d1b34 100644
--- a/includes/class-goals.php
+++ b/includes/class-goals.php
@@ -10,7 +10,7 @@
/**
* Goals class.
*/
-class Goals extends Base {
+class Goals {
/**
* Constructor.
@@ -30,10 +30,10 @@ private function register_core_goals() {
* Register weekly-post goal.
*/
private function register_weekly_post_goal() {
- $stats = $this->get_stats();
+ $stats = new Stats();
// Get the start date for all stats.
- $start_date = array_keys( $this->get_stats()->get_stat( 'posts' )->get_value() );
+ $start_date = array_keys( $stats->get_stat( 'posts' )->get_value() );
sort( $start_date );
$start_date = $start_date[0];
From c7ee80b0d53086ea48b45e9cadd76176ada1161b Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 14 Feb 2024 13:41:42 +0200
Subject: [PATCH 043/490] Sort posts stats
---
includes/class-goals.php | 7 +------
includes/stats/class-stat-posts.php | 14 ++++++++++++++
2 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/includes/class-goals.php b/includes/class-goals.php
index 8a39d1b34..78db75a9d 100644
--- a/includes/class-goals.php
+++ b/includes/class-goals.php
@@ -32,11 +32,6 @@ private function register_core_goals() {
private function register_weekly_post_goal() {
$stats = new Stats();
- // Get the start date for all stats.
- $start_date = array_keys( $stats->get_stat( 'posts' )->get_value() );
- sort( $start_date );
- $start_date = $start_date[0];
-
new \ProgressPlanner\Goals\Goal_Recurring(
new \ProgressPlanner\Goals\Goal_Posts(
[
@@ -57,7 +52,7 @@ private function register_weekly_post_goal() {
]
),
'weekly',
- $start_date, // Beginning of the stats.
+ array_keys( $stats->get_stat( 'posts' )->get_value() )[0], // Beginning of the stats.
gmdate( 'Ymd' ) // Today.
);
}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index c8ff13a32..51503c361 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -21,6 +21,20 @@ class Stat_Posts extends Stat {
*/
protected $type = 'posts';
+ /**
+ * Get the value.
+ *
+ * @param array $index The index. This is an array of keys, which will be used to get the value.
+ * This will go over the array recursively, getting the value for the last key.
+ * See _wp_array_get for more info.
+ * @return mixed
+ */
+ public function get_value( $index = [] ) {
+ $value = parent::get_value( $index );
+ ksort( $value );
+ return $value;
+ }
+
/**
* Save a post to the stats.
*
From 80ff6ca927a76101041276b435f4d953143b6640 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 14 Feb 2024 14:12:04 +0200
Subject: [PATCH 044/490] Add a Date class
---
includes/charts/class-posts.php | 5 +-
includes/class-date.php | 97 +++++++++++++++++++++++++
includes/class-goals.php | 4 +-
includes/goals/class-goal-recurring.php | 50 +------------
includes/stats/class-stat-posts.php | 4 +-
includes/stats/class-stat.php | 18 -----
6 files changed, 109 insertions(+), 69 deletions(-)
create mode 100644 includes/class-date.php
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index ee68df359..b0f462177 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -8,6 +8,7 @@
namespace ProgressPlanner\Charts;
use ProgressPlanner\Chart;
+use ProgressPlanner\Date;
use ProgressPlanner\Stats\Stat_Posts;
/**
@@ -49,8 +50,8 @@ public function render( $post_types = [], $context = 'count', $interval = 'weeks
$stat_posts = new Stat_Posts();
foreach ( $range_array as $start => $end ) {
$stats = $stat_posts->get_stats(
- (int) gmdate( 'Ymd', strtotime( "-$start $interval" ) ),
- (int) gmdate( 'Ymd', strtotime( "-$end $interval" ) ),
+ (int) gmdate( Date::FORMAT, strtotime( "-$start $interval" ) ),
+ (int) gmdate( Date::FORMAT, strtotime( "-$end $interval" ) ),
$post_types
);
diff --git a/includes/class-date.php b/includes/class-date.php
new file mode 100644
index 000000000..73ea11e16
--- /dev/null
+++ b/includes/class-date.php
@@ -0,0 +1,97 @@
+ 'Ymd',
+ * 'end' => 'Ymd',
+ * 'dates' => [ 'Ymd', 'Ymd', ... ],
+ * ].
+ */
+ public function get_range( $start, $end ) {
+ $start = \DateTime::createFromFormat( $this->format, $start );
+ $end = \DateTime::createFromFormat( $this->format, $end );
+
+ $dates = [];
+ $range = new \DatePeriod( $start, new \DateInterval( 'P1D' ), $end );
+ foreach ( $range as $date ) {
+ $dates[] = $date->format( $this->format );
+ }
+
+ return [
+ 'start' => $start->format( $this->format ),
+ 'end' => $end->format( $this->format ),
+ 'dates' => $dates,
+ ];
+ }
+
+ /**
+ * Get an array of periods with start and end dates.
+ *
+ * @param string $start The start date.
+ * @param string $end The end date.
+ * @param string $frequency The frequency. Can be 'daily', 'weekly', 'monthly'.
+ *
+ * @return array
+ */
+ public function get_periods( $start, $end, $frequency ) {
+ $start = \DateTime::createFromFormat( $this->format, $start );
+ $end = \DateTime::createFromFormat( $this->format, $end );
+ $end = $end->modify( '+1 day' );
+
+ switch ( $frequency ) {
+ case 'daily':
+ $interval = new \DateInterval( 'P1D' );
+ break;
+
+ case 'weekly':
+ $interval = new \DateInterval( 'P1W' );
+ break;
+
+ case 'monthly':
+ $interval = new \DateInterval( 'P1M' );
+ break;
+ }
+
+ $period = new \DatePeriod( $start, $interval, 100 );
+ $dates_array = [];
+ foreach ( $period as $date ) {
+ $dates_array[] = $date->format( $this->format );
+ }
+
+ $date_ranges = [];
+ foreach ( $dates_array as $key => $date ) {
+ if ( isset( $dates_array[ $key + 1 ] ) ) {
+ $date_ranges[] = $this->get_range(
+ $date,
+ \DateTime::createFromFormat( $this->format, $dates_array[ $key + 1 ] )
+ );
+ }
+ }
+
+ return $date_ranges;
+ }
+}
diff --git a/includes/class-goals.php b/includes/class-goals.php
index 78db75a9d..815741993 100644
--- a/includes/class-goals.php
+++ b/includes/class-goals.php
@@ -7,6 +7,8 @@
namespace ProgressPlanner;
+use ProgressPlanner\Date;
+
/**
* Goals class.
*/
@@ -53,7 +55,7 @@ private function register_weekly_post_goal() {
),
'weekly',
array_keys( $stats->get_stat( 'posts' )->get_value() )[0], // Beginning of the stats.
- gmdate( 'Ymd' ) // Today.
+ gmdate( Date::FORMAT ) // Today.
);
}
}
diff --git a/includes/goals/class-goal-recurring.php b/includes/goals/class-goal-recurring.php
index c271fdd5d..7e8e7fb78 100644
--- a/includes/goals/class-goal-recurring.php
+++ b/includes/goals/class-goal-recurring.php
@@ -7,6 +7,8 @@
namespace ProgressPlanner\Goals;
+use ProgressPlanner\Date;
+
/**
* A recurring goal.
*/
@@ -72,7 +74,8 @@ public function get_occurences() {
return $this->occurences;
}
- $ranges = $this->get_date_periods();
+ $date = new Date();
+ $ranges = $date->get_periods( $this->start, $this->end, $this->frequency );
foreach ( $ranges as $range ) {
$goal = clone $this->goal;
@@ -83,49 +86,4 @@ public function get_occurences() {
return $this->occurences;
}
-
- /**
- * Get an array of periods with start and end dates.
- *
- * @return array
- */
- public function get_date_periods() {
- $start = \DateTime::createFromFormat( 'Ymd', $this->start );
- $end = \DateTime::createFromFormat( 'Ymd', $this->end );
- $end = $end->modify( '+1 day' );
-
- switch ( $this->frequency ) {
- case 'daily':
- $interval = new \DateInterval( 'P1D' );
- break;
-
- case 'weekly':
- $interval = new \DateInterval( 'P1W' );
- break;
-
- case 'monthly':
- $interval = new \DateInterval( 'P1M' );
- break;
- }
-
- $period = new \DatePeriod( $start, $interval, 100 );
- $dates_array = [];
- foreach ( $period as $date ) {
- $dates_array[] = $date->format( 'Ymd' );
- }
-
- $date_ranges = [];
- foreach ( $dates_array as $key => $date ) {
- if ( isset( $dates_array[ $key + 1 ] ) ) {
- $date_ranges[] = [
- 'start' => $date,
- 'end' => \DateTime::createFromFormat( 'Ymd', $dates_array[ $key + 1 ] )
- ->modify( '-1 day' )
- ->format( 'Ymd' ),
- ];
- }
- }
-
- return $date_ranges;
- }
}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 51503c361..12bffe084 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -7,7 +7,7 @@
namespace ProgressPlanner\Stats;
-use ProgressPlanner\Charts\Posts as Posts_Chart;
+use ProgressPlanner\Date;
/**
* Stats about posts.
@@ -44,7 +44,7 @@ public function get_value( $index = [] ) {
*/
protected function save_post( $post ) {
// Get the date.
- $date = (int) mysql2date( 'Ymd', $post->post_date );
+ $date = (int) mysql2date( Date::FORMAT, $post->post_date );
// Add the post to the stats.
$this->set_value(
diff --git a/includes/stats/class-stat.php b/includes/stats/class-stat.php
index 1d89fbb63..12e65451f 100644
--- a/includes/stats/class-stat.php
+++ b/includes/stats/class-stat.php
@@ -42,15 +42,6 @@ class Stat {
*/
protected $value;
- /**
- * Date Query.
- *
- * The date query, which will be then passed-on to the WP_Date_Query object.
- *
- * @var array
- */
- protected $date_query = [];
-
/**
* Constructor.
*/
@@ -104,13 +95,4 @@ public function set_value( $index, $value ) {
\update_option( self::SETTING_NAME, $stats );
$this->stats = $stats;
}
-
- /**
- * Set the date query.
- *
- * @param array $date_query The date query.
- */
- public function set_date_query( $date_query ) {
- $this->date_query = $date_query;
- }
}
From 8febe19a7ba965781f48316caf7a57161c96af0e Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 14 Feb 2024 14:28:10 +0200
Subject: [PATCH 045/490] Split views
---
views/admin-page-debug.php | 17 +++++
views/admin-page-form-filters.php | 32 +++++++++
views/admin-page-form-scan.php | 17 +++++
views/admin-page-posts-count-progress.php | 18 +++++
views/admin-page-words-count-progress.php | 18 +++++
views/admin-page.php | 85 ++---------------------
6 files changed, 107 insertions(+), 80 deletions(-)
create mode 100644 views/admin-page-debug.php
create mode 100644 views/admin-page-form-filters.php
create mode 100644 views/admin-page-form-scan.php
create mode 100644 views/admin-page-posts-count-progress.php
create mode 100644 views/admin-page-words-count-progress.php
diff --git a/views/admin-page-debug.php b/views/admin-page-debug.php
new file mode 100644
index 000000000..51513cb05
--- /dev/null
+++ b/views/admin-page-debug.php
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ get_value() ); ?>
+
diff --git a/views/admin-page-form-filters.php b/views/admin-page-form-filters.php
new file mode 100644
index 000000000..1aa2c43d9
--- /dev/null
+++ b/views/admin-page-form-filters.php
@@ -0,0 +1,32 @@
+ __( 'Days', 'progress-planner' ),
+ 'weeks' => __( 'Weeks', 'progress-planner' ),
+ 'months' => __( 'Months', 'progress-planner' ),
+];
+
+?>
+
diff --git a/views/admin-page-form-scan.php b/views/admin-page-form-scan.php
new file mode 100644
index 000000000..07626d5ad
--- /dev/null
+++ b/views/admin-page-form-scan.php
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/views/admin-page-posts-count-progress.php b/views/admin-page-posts-count-progress.php
new file mode 100644
index 000000000..a5c29a228
--- /dev/null
+++ b/views/admin-page-posts-count-progress.php
@@ -0,0 +1,18 @@
+';
+esc_html_e( 'Posts count progress', 'progress-planner' );
+echo '';
+
+( new \ProgressPlanner\Charts\Posts() )->render(
+ $prpl_stats_posts->get_post_types_names(),
+ 'count',
+ $prpl_filters_interval,
+ $prpl_filters_number,
+ 0
+);
diff --git a/views/admin-page-words-count-progress.php b/views/admin-page-words-count-progress.php
new file mode 100644
index 000000000..a887f1721
--- /dev/null
+++ b/views/admin-page-words-count-progress.php
@@ -0,0 +1,18 @@
+';
+esc_html_e( 'Words count progress', 'progress-planner' );
+echo '';
+
+( new \ProgressPlanner\Charts\Posts() )->render(
+ $prpl_stats_posts->get_post_types_names(),
+ 'words',
+ $prpl_filters_interval,
+ $prpl_filters_number,
+ 0
+);
diff --git a/views/admin-page.php b/views/admin-page.php
index 7a9d4dfae..10176c093 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -5,18 +5,9 @@
* @package ProgressPlanner
*/
-// TODO: Move this to a method to allow prepopulating stats from the admin page.
-$prpl_prepopulate = new ProgressPlanner\Stats\Stat_Posts_Prepopulate();
-
// Get the stats object.
$prpl_stats_posts = new ProgressPlanner\Stats\Stat_Posts();
-// Values for the graph filters.
-$prpl_filters_intervals = [
- 'days' => __( 'Days', 'progress-planner' ),
- 'weeks' => __( 'Weeks', 'progress-planner' ),
- 'months' => __( 'Months', 'progress-planner' ),
-];
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$prpl_filters_interval = isset( $_POST['interval'] ) ? sanitize_key( $_POST['interval'] ) : 'weeks';
// phpcs:ignore WordPress.Security.NonceVerification.Missing
@@ -33,84 +24,18 @@
-
-
-
-
-
+
-
-
-
+
-
-
-
- render(
- $prpl_stats_posts->get_post_types_names(),
- 'count',
- $prpl_filters_interval,
- $prpl_filters_number,
- 0
- );
- ?>
+
-
- render(
- $prpl_stats_posts->get_post_types_names(),
- 'words',
- $prpl_filters_interval,
- $prpl_filters_number,
- 0
- );
- ?>
+
-
-
-
-
-
-
- get_value() ); ?>
-
+
From 43e294f53a48c219a21e3dabb133c4d64a86fc95 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 14 Feb 2024 14:36:35 +0200
Subject: [PATCH 046/490] Add PHPStan
---
composer.json | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/composer.json b/composer.json
index d3de0ff35..fafe6df8c 100644
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,10 @@
"wp-coding-standards/wpcs": "^3.0",
"phpcompatibility/phpcompatibility-wp": "*",
"php-parallel-lint/php-parallel-lint": "^1.3",
- "yoast/wp-test-utils": "^1.2"
+ "yoast/wp-test-utils": "^1.2",
+ "phpstan/phpstan": "^1.10",
+ "szepeviktor/phpstan-wordpress": "^1.3",
+ "phpstan/extension-installer": "^1.3"
},
"scripts": {
"check-cs": [
@@ -34,7 +37,8 @@
},
"config": {
"allow-plugins": {
- "dealerdirect/phpcodesniffer-composer-installer": true
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "phpstan/extension-installer": true
}
}
}
From 90085c2c5bf82f68d536edab3acd1b20f95b2f62 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 14 Feb 2024 14:38:06 +0200
Subject: [PATCH 047/490] Fix issues detected by PHPStan
---
includes/class-base.php | 7 +++++++
includes/class-date.php | 18 +++++++++---------
2 files changed, 16 insertions(+), 9 deletions(-)
diff --git a/includes/class-base.php b/includes/class-base.php
index 44cf34c56..e5c9786a1 100644
--- a/includes/class-base.php
+++ b/includes/class-base.php
@@ -33,6 +33,13 @@ class Base {
*/
private $admin;
+ /**
+ * The Goals object.
+ *
+ * @var \ProgressPlanner\Goals
+ */
+ private $goals;
+
/**
* Get the single instance of this class.
*
diff --git a/includes/class-date.php b/includes/class-date.php
index 73ea11e16..3aebaf372 100644
--- a/includes/class-date.php
+++ b/includes/class-date.php
@@ -32,18 +32,18 @@ class Date {
* ].
*/
public function get_range( $start, $end ) {
- $start = \DateTime::createFromFormat( $this->format, $start );
- $end = \DateTime::createFromFormat( $this->format, $end );
+ $start = \DateTime::createFromFormat( self::FORMAT, $start );
+ $end = \DateTime::createFromFormat( self::FORMAT, $end );
$dates = [];
$range = new \DatePeriod( $start, new \DateInterval( 'P1D' ), $end );
foreach ( $range as $date ) {
- $dates[] = $date->format( $this->format );
+ $dates[] = $date->format( self::FORMAT );
}
return [
- 'start' => $start->format( $this->format ),
- 'end' => $end->format( $this->format ),
+ 'start' => $start->format( self::FORMAT ),
+ 'end' => $end->format( self::FORMAT ),
'dates' => $dates,
];
}
@@ -58,8 +58,8 @@ public function get_range( $start, $end ) {
* @return array
*/
public function get_periods( $start, $end, $frequency ) {
- $start = \DateTime::createFromFormat( $this->format, $start );
- $end = \DateTime::createFromFormat( $this->format, $end );
+ $start = \DateTime::createFromFormat( self::FORMAT, $start );
+ $end = \DateTime::createFromFormat( self::FORMAT, $end );
$end = $end->modify( '+1 day' );
switch ( $frequency ) {
@@ -79,7 +79,7 @@ public function get_periods( $start, $end, $frequency ) {
$period = new \DatePeriod( $start, $interval, 100 );
$dates_array = [];
foreach ( $period as $date ) {
- $dates_array[] = $date->format( $this->format );
+ $dates_array[] = $date->format( self::FORMAT );
}
$date_ranges = [];
@@ -87,7 +87,7 @@ public function get_periods( $start, $end, $frequency ) {
if ( isset( $dates_array[ $key + 1 ] ) ) {
$date_ranges[] = $this->get_range(
$date,
- \DateTime::createFromFormat( $this->format, $dates_array[ $key + 1 ] )
+ \DateTime::createFromFormat( self::FORMAT, $dates_array[ $key + 1 ] )
);
}
}
From 7268a40507d63042a80ed60f10cf0caa9f87dd03 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 15 Feb 2024 12:24:14 +0200
Subject: [PATCH 048/490] PHPStan config + more tweaks & fixes
---
composer.json | 7 ++-
includes/admin/class-page.php | 32 ++++++++++++-
includes/charts/class-posts.php | 2 +
includes/class-base.php | 26 +----------
includes/class-date.php | 24 ++++++----
includes/class-goals.php | 17 +++++--
includes/class-stats.php | 14 ++++--
includes/goals/class-goal-recurring.php | 10 ++--
includes/goals/class-goal.php | 6 ++-
.../stats/class-stat-posts-prepopulate.php | 2 +
includes/stats/class-stat-posts.php | 10 ++--
includes/stats/class-stat.php | 46 ++++++++-----------
phpstan.neon.dist | 11 +++++
views/admin-page-debug.php | 2 +-
views/admin-page-form-filters.php | 8 ++--
views/admin-page-form-scan.php | 2 +-
views/admin-page-posts-count-progress.php | 6 +--
views/admin-page-words-count-progress.php | 6 +--
views/admin-page.php | 16 +------
19 files changed, 135 insertions(+), 112 deletions(-)
create mode 100644 phpstan.neon.dist
diff --git a/composer.json b/composer.json
index fafe6df8c..05fb464cc 100644
--- a/composer.json
+++ b/composer.json
@@ -32,8 +32,11 @@
"@php -r \"exit( intval( is_null( json_decode( file_get_contents( './.wordpress-org/blueprints/blueprint.json' ) ) ) ) );\""
],
"test": [
- "@php ./vendor/phpunit/phpunit/phpunit"
- ]
+ "@php ./vendor/phpunit/phpunit/phpunit"
+ ],
+ "phpstan": [
+ "@php ./vendor/bin/phpstan analyse --memory-limit=2048M"
+ ]
},
"config": {
"allow-plugins": {
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index 2546a1b53..6e33ea4fe 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -7,8 +7,6 @@
namespace ProgressPlanner\Admin;
-use PROGRESS_PLANNER_URL;
-
/**
* Admin page class.
*/
@@ -23,6 +21,8 @@ public function __construct() {
/**
* Register the hooks.
+ *
+ * @return void
*/
private function register_hooks() {
\add_action( 'admin_menu', [ $this, 'add_page' ] );
@@ -33,6 +33,8 @@ private function register_hooks() {
/**
* Add the admin page.
+ *
+ * @return void
*/
public function add_page() {
\add_menu_page(
@@ -47,6 +49,8 @@ public function add_page() {
/**
* Render the admin page.
+ *
+ * @return void
*/
public function render_page() {
include PROGRESS_PLANNER_DIR . '/views/admin-page.php';
@@ -56,6 +60,8 @@ public function render_page() {
* Enqueue scripts and styles.
*
* @param string $hook The current admin page.
+ *
+ * @return void
*/
public function enqueue_scripts( $hook ) {
if ( 'toplevel_page_progress-planner' !== $hook ) {
@@ -93,6 +99,8 @@ public function enqueue_scripts( $hook ) {
/**
* Ajax scan.
+ *
+ * @return void
*/
public function ajax_scan() {
// Check the nonce.
@@ -124,6 +132,8 @@ public function ajax_scan() {
/**
* Ajax reset stats.
+ *
+ * @return void
*/
public function ajax_reset_stats() {
// Check the nonce.
@@ -141,4 +151,22 @@ public function ajax_reset_stats() {
]
);
}
+
+ /**
+ * Get params for the admin page.
+ *
+ * @return array The params.
+ */
+ public static function get_params() {
+ static $stats = null;
+ if ( null === $stats ) {
+ $stats = new \ProgressPlanner\Stats\Stat_Posts();
+ }
+ return [
+ 'stats' => $stats,
+ 'filter_interval' => isset( $_POST['interval'] ) ? sanitize_key( $_POST['interval'] ) : 'weeks',
+ 'filter_number' => isset( $_POST['number'] ) ? (int) $_POST['number'] : 10,
+ 'scan_pending' => empty( $stats->get_value() ),
+ ];
+ }
}
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index b0f462177..bd3beb41a 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -24,6 +24,8 @@ class Posts extends Chart {
* @param string $interval The interval for the chart. Can be 'days', 'weeks', 'months', 'years'.
* @param int $range The number of intervals to show.
* @param int $offset The offset for the intervals.
+ *
+ * @return void
*/
public function render( $post_types = [], $context = 'count', $interval = 'weeks', $range = 10, $offset = 0 ) {
$range_array_end = \range( $offset, $range - 1 );
diff --git a/includes/class-base.php b/includes/class-base.php
index e5c9786a1..08120cec7 100644
--- a/includes/class-base.php
+++ b/includes/class-base.php
@@ -19,13 +19,6 @@ class Base {
*/
private static $instance;
- /**
- * The Stats object.
- *
- * @var \ProgressPlanner\Stats
- */
- private $stats;
-
/**
* The Admin object.
*
@@ -33,13 +26,6 @@ class Base {
*/
private $admin;
- /**
- * The Goals object.
- *
- * @var \ProgressPlanner\Goals
- */
- private $goals;
-
/**
* Get the single instance of this class.
*
@@ -58,17 +44,9 @@ public static function get_instance() {
*/
private function __construct() {
$this->admin = new Admin();
- $this->stats = new Stats();
- $this->goals = new Goals();
- }
- /**
- * Get the stats object.
- *
- * @return \ProgressPlanner\Stats
- */
- public function get_stats() {
- return $this->stats;
+ new Stats();
+ new Goals();
}
/**
diff --git a/includes/class-date.php b/includes/class-date.php
index 3aebaf372..1b8d10892 100644
--- a/includes/class-date.php
+++ b/includes/class-date.php
@@ -22,8 +22,8 @@ class Date {
/**
* Get a range of dates.
*
- * @param string $start The start date.
- * @param string $end The end date.
+ * @param string|int $start The start date.
+ * @param string|int $end The end date.
*
* @return array [
* 'start' => 'Ymd',
@@ -51,9 +51,9 @@ public function get_range( $start, $end ) {
/**
* Get an array of periods with start and end dates.
*
- * @param string $start The start date.
- * @param string $end The end date.
- * @param string $frequency The frequency. Can be 'daily', 'weekly', 'monthly'.
+ * @param int|string $start The start date.
+ * @param int|string $end The end date.
+ * @param string $frequency The frequency. Can be 'daily', 'weekly', 'monthly'.
*
* @return array
*/
@@ -67,13 +67,13 @@ public function get_periods( $start, $end, $frequency ) {
$interval = new \DateInterval( 'P1D' );
break;
- case 'weekly':
- $interval = new \DateInterval( 'P1W' );
- break;
-
case 'monthly':
$interval = new \DateInterval( 'P1M' );
break;
+
+ default: // Default to weekly.
+ $interval = new \DateInterval( 'P1W' );
+ break;
}
$period = new \DatePeriod( $start, $interval, 100 );
@@ -85,9 +85,13 @@ public function get_periods( $start, $end, $frequency ) {
$date_ranges = [];
foreach ( $dates_array as $key => $date ) {
if ( isset( $dates_array[ $key + 1 ] ) ) {
+ $datetime = \DateTime::createFromFormat( self::FORMAT, $dates_array[ $key + 1 ] );
+ if ( ! $datetime ) {
+ continue;
+ }
$date_ranges[] = $this->get_range(
$date,
- \DateTime::createFromFormat( self::FORMAT, $dates_array[ $key + 1 ] )
+ $datetime->format( self::FORMAT )
);
}
}
diff --git a/includes/class-goals.php b/includes/class-goals.php
index 815741993..d2bb8ec67 100644
--- a/includes/class-goals.php
+++ b/includes/class-goals.php
@@ -8,6 +8,9 @@
namespace ProgressPlanner;
use ProgressPlanner\Date;
+use ProgressPlanner\Stats\Stat_Posts;
+use ProgressPlanner\Goals\Goal_Recurring;
+use ProgressPlanner\Goals\Goal_Posts;
/**
* Goals class.
@@ -23,6 +26,8 @@ public function __construct() {
/**
* Register the goals.
+ *
+ * @return void
*/
private function register_core_goals() {
$this->register_weekly_post_goal();
@@ -30,12 +35,14 @@ private function register_core_goals() {
/**
* Register weekly-post goal.
+ *
+ * @return void
*/
private function register_weekly_post_goal() {
- $stats = new Stats();
+ $stats = new Stat_Posts();
- new \ProgressPlanner\Goals\Goal_Recurring(
- new \ProgressPlanner\Goals\Goal_Posts(
+ new Goal_Recurring(
+ new Goal_Posts(
[
'id' => 'weekly_post',
'title' => \esc_html__( 'Write a weekly blog post', 'progress-planner' ),
@@ -44,7 +51,7 @@ private function register_weekly_post_goal() {
'priority' => 'high',
'evaluate' => function ( $goal_object ) use ( $stats ) {
return (bool) count(
- $stats->get_stat( 'posts' )->get_stats(
+ $stats->get_stats(
$goal_object->get_details()['start_date'],
$goal_object->get_details()['end_date'],
[ 'post' ]
@@ -54,7 +61,7 @@ private function register_weekly_post_goal() {
]
),
'weekly',
- array_keys( $stats->get_stat( 'posts' )->get_value() )[0], // Beginning of the stats.
+ array_keys( $stats->get_value() )[0], // Beginning of the stats.
gmdate( Date::FORMAT ) // Today.
);
}
diff --git a/includes/class-stats.php b/includes/class-stats.php
index 6dc7ee91f..52c8b87c9 100644
--- a/includes/class-stats.php
+++ b/includes/class-stats.php
@@ -7,6 +7,9 @@
namespace ProgressPlanner;
+use ProgressPlanner\Stats\Stat;
+use ProgressPlanner\Stats\Stat_Posts;
+
/**
* Stats class.
*
@@ -33,8 +36,10 @@ public function __construct() {
*
* @param string $id The ID of the stat.
* @param Stat $stat The stat object.
+ *
+ * @return void
*/
- public function add_stat( $id, $stat ) {
+ public function add_stat( $id, Stat $stat ) {
$this->stats[ $id ] = $stat;
}
@@ -51,16 +56,19 @@ public function get_all_stats() {
* Get an individual stat.
*
* @param string $id The ID of the stat.
+ *
* @return Stat
*/
- public function get_stat( $id ) {
+ public function get_stat( $id ): Stat {
return $this->stats[ $id ];
}
/**
* Register the individual stats.
+ *
+ * @return void
*/
private function register_stats() {
- $this->add_stat( 'posts', new Stats\Stat_Posts() );
+ $this->add_stat( 'posts', new Stat_Posts() );
}
}
diff --git a/includes/goals/class-goal-recurring.php b/includes/goals/class-goal-recurring.php
index 7e8e7fb78..869dceea7 100644
--- a/includes/goals/class-goal-recurring.php
+++ b/includes/goals/class-goal-recurring.php
@@ -31,21 +31,21 @@ class Goal_Recurring {
/**
* The start date.
*
- * @var string
+ * @var int|string
*/
private $start;
/**
* The end date.
*
- * @var string
+ * @var int|string
*/
private $end;
/**
* An array of occurences.
*
- * @var array
+ * @var Goal[]
*/
private $occurences = [];
@@ -54,8 +54,8 @@ class Goal_Recurring {
*
* @param \ProgressPlanner\Goals\Goal $goal The goal object.
* @param string $frequency The goal frequency.
- * @param string $start The start date.
- * @param string $end The end date.
+ * @param int|string $start The start date.
+ * @param int|string $end The end date.
*/
public function __construct( $goal, $frequency, $start, $end ) {
$this->goal = $goal;
diff --git a/includes/goals/class-goal.php b/includes/goals/class-goal.php
index 4ce08b6a4..4a2974322 100644
--- a/includes/goals/class-goal.php
+++ b/includes/goals/class-goal.php
@@ -120,7 +120,7 @@ public function __construct( $args = [] ) {
/**
* Get the goal ID.
*
- * @return string
+ * @return array
*/
public function get_details() {
return [
@@ -141,6 +141,8 @@ public function get_details() {
* Set the start date.
*
* @param string $start_date The start date.
+ *
+ * @return void
*/
public function set_start_date( $start_date ) {
$this->start_date = $start_date;
@@ -150,6 +152,8 @@ public function set_start_date( $start_date ) {
* Set the end date.
*
* @param string $end_date The end date.
+ *
+ * @return void
*/
public function set_end_date( $end_date ) {
$this->end_date = $end_date;
diff --git a/includes/stats/class-stat-posts-prepopulate.php b/includes/stats/class-stat-posts-prepopulate.php
index 656861998..6b35c4c6c 100644
--- a/includes/stats/class-stat-posts-prepopulate.php
+++ b/includes/stats/class-stat-posts-prepopulate.php
@@ -65,6 +65,8 @@ public function get_last_prepopulated_post() {
/**
* Set the last prepopulated post.
+ *
+ * @return void
*/
public function save_last_prepopulated_post() {
\set_transient( 'progress_planner_last_prepopulated_post', $this->last_scanned_post_id, \HOUR_IN_SECONDS );
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 12bffe084..aff5a39c3 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -24,7 +24,7 @@ class Stat_Posts extends Stat {
/**
* Get the value.
*
- * @param array $index The index. This is an array of keys, which will be used to get the value.
+ * @param string[]|int[] $index The index. This is an array of keys, which will be used to get the value.
* This will go over the array recursively, getting the value for the last key.
* See _wp_array_get for more info.
* @return mixed
@@ -59,9 +59,9 @@ protected function save_post( $post ) {
/**
* Get stats for date range.
*
- * @param string $start_date The start date.
- * @param string $end_date The end date.
- * @param array $post_types The post types.
+ * @param int|string $start_date The start date.
+ * @param int|string $end_date The end date.
+ * @param string[] $post_types The post types.
*
* @return array
*/
@@ -107,7 +107,7 @@ public function reset_stats() {
/**
* Get an array of post-types names for the stats.
*
- * @return array
+ * @return string[]
*/
public function get_post_types_names() {
$post_types = \get_post_types( [ 'public' => true ] );
diff --git a/includes/stats/class-stat.php b/includes/stats/class-stat.php
index 12e65451f..252a156e1 100644
--- a/includes/stats/class-stat.php
+++ b/includes/stats/class-stat.php
@@ -35,33 +35,17 @@ class Stat {
*/
protected $stats;
- /**
- * The value.
- *
- * @var array
- */
- protected $value;
-
- /**
- * Constructor.
- */
- public function __construct() {
- $this->value = $this->get_value();
- }
-
/**
* Get the value.
*
- * @param array $index The index. This is an array of keys, which will be used to get the value.
- * This will go over the array recursively, getting the value for the last key.
- * See _wp_array_get for more info.
+ * @param string[]|int[] $index The index. This is an array of keys,
+ * which will be used to get the value.
+ * It will go over the array recursively,
+ * getting the value for the last key.
+ * See _wp_array_get for more info.
* @return mixed
*/
- public function get_value( $index = [] ) {
- if ( $this->value ) {
- return $this->value;
- }
-
+ public function get_value( array $index = [] ) {
if ( ! isset( $this->stats[ $this->type ] ) ) {
$this->stats = \get_option( self::SETTING_NAME, [ $this->type => [] ] );
}
@@ -76,12 +60,16 @@ public function get_value( $index = [] ) {
/**
* Set the value.
*
- * @param array $index The index. This is an array of keys, which will be used to set the value.
- * This will go over the array recursively, updating the value for the last key.
- * See _wp_array_set for more info.
- * @param mixed $value The value.
+ * @param string[]|int[] $index The index. This is an array of keys,
+ * which will be used to set the value.
+ * It will go over the array recursively,
+ * updating the value for the last key.
+ * See _wp_array_set for more info.
+ * @param mixed $value The value.
+ *
+ * @return bool
*/
- public function set_value( $index, $value ) {
+ public function set_value( array $index, $value ): bool {
// Call $this->get_value, to populate $this->stats.
$stats = \get_option( self::SETTING_NAME, [ $this->type => [] ] );
@@ -92,7 +80,9 @@ public function set_value( $index, $value ) {
\_wp_array_set( $stats, $index, $value );
// Save the option.
- \update_option( self::SETTING_NAME, $stats );
+ $updated = \update_option( self::SETTING_NAME, $stats );
$this->stats = $stats;
+
+ return $updated;
}
}
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 000000000..8f93d17fa
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,11 @@
+parameters:
+ level: 6
+ paths:
+ - .
+ excludePaths:
+ - vendor
+ ignoreErrors:
+ - '#Constant PROGRESS_PLANNER_URL not found.#'
+ - '#Property [a-zA-Z0-9\\_]+::\$[a-zA-Z0-9\\_]+ type has no value type specified in iterable type array.#'
+ - '#Method [a-zA-Z0-9\\_\:\(\)]+ return type has no value type specified in iterable type array.#'
+ - '#Method [a-zA-Z0-9\\_\:\(\)]+ has parameter \$[a-zA-Z0-9\\_]+ with no value type specified in iterable type array.#'
diff --git a/views/admin-page-debug.php b/views/admin-page-debug.php
index 51513cb05..5e2c54654 100644
--- a/views/admin-page-debug.php
+++ b/views/admin-page-debug.php
@@ -13,5 +13,5 @@
- get_value() ); ?>
+ get_value() ); ?>
diff --git a/views/admin-page-form-filters.php b/views/admin-page-form-filters.php
index 1aa2c43d9..cc7bd724f 100644
--- a/views/admin-page-form-filters.php
+++ b/views/admin-page-form-filters.php
@@ -8,7 +8,7 @@
*/
// Values for the graph filters.
-$prpl_filters_intervals = [
+$prpl_filter_intervals = [
'days' => __( 'Days', 'progress-planner' ),
'weeks' => __( 'Weeks', 'progress-planner' ),
'months' => __( 'Months', 'progress-planner' ),
@@ -19,14 +19,14 @@
diff --git a/views/admin-page-form-scan.php b/views/admin-page-form-scan.php
index 07626d5ad..f3c2c3434 100644
--- a/views/admin-page-form-scan.php
+++ b/views/admin-page-form-scan.php
@@ -13,5 +13,5 @@
diff --git a/views/admin-page-posts-count-progress.php b/views/admin-page-posts-count-progress.php
index a5c29a228..f96ab30ad 100644
--- a/views/admin-page-posts-count-progress.php
+++ b/views/admin-page-posts-count-progress.php
@@ -10,9 +10,9 @@
echo '';
( new \ProgressPlanner\Charts\Posts() )->render(
- $prpl_stats_posts->get_post_types_names(),
+ \ProgressPlanner\Admin\Page::get_params()['stats']->get_post_types_names(),
'count',
- $prpl_filters_interval,
- $prpl_filters_number,
+ \ProgressPlanner\Admin\Page::get_params()['filter_interval'],
+ \ProgressPlanner\Admin\Page::get_params()['filter_number'],
0
);
diff --git a/views/admin-page-words-count-progress.php b/views/admin-page-words-count-progress.php
index a887f1721..2d6a13d88 100644
--- a/views/admin-page-words-count-progress.php
+++ b/views/admin-page-words-count-progress.php
@@ -10,9 +10,9 @@
echo '';
( new \ProgressPlanner\Charts\Posts() )->render(
- $prpl_stats_posts->get_post_types_names(),
+ \ProgressPlanner\Admin\Page::get_params()['stats']->get_post_types_names(),
'words',
- $prpl_filters_interval,
- $prpl_filters_number,
+ \ProgressPlanner\Admin\Page::get_params()['filter_interval'],
+ \ProgressPlanner\Admin\Page::get_params()['filter_number'],
0
);
diff --git a/views/admin-page.php b/views/admin-page.php
index 10176c093..827cfea85 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -5,25 +5,11 @@
* @package ProgressPlanner
*/
-// Get the stats object.
-$prpl_stats_posts = new ProgressPlanner\Stats\Stat_Posts();
-
-// phpcs:ignore WordPress.Security.NonceVerification.Missing
-$prpl_filters_interval = isset( $_POST['interval'] ) ? sanitize_key( $_POST['interval'] ) : 'weeks';
-// phpcs:ignore WordPress.Security.NonceVerification.Missing
-$prpl_filters_number = isset( $_POST['number'] ) ? (int) $_POST['number'] : 10;
-
-// Check if we have a scan pending.
-$prpl_scan_pending = false;
-$prpl_scan_progress = 0;
-if ( empty( $prpl_stats_posts->get_value() ) ) {
- $prpl_scan_pending = true;
-}
?>
-
+
From 71fb42464dafdbc630cd7abb1f9537bca86400e0 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 15 Feb 2024 12:26:47 +0200
Subject: [PATCH 049/490] CS fixes
---
includes/admin/class-page.php | 2 ++
includes/goals/class-goal-recurring.php | 2 +-
includes/stats/class-stat.php | 2 +-
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index 6e33ea4fe..b7597859f 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -164,7 +164,9 @@ public static function get_params() {
}
return [
'stats' => $stats,
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
'filter_interval' => isset( $_POST['interval'] ) ? sanitize_key( $_POST['interval'] ) : 'weeks',
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
'filter_number' => isset( $_POST['number'] ) ? (int) $_POST['number'] : 10,
'scan_pending' => empty( $stats->get_value() ),
];
diff --git a/includes/goals/class-goal-recurring.php b/includes/goals/class-goal-recurring.php
index 869dceea7..a65c3ec46 100644
--- a/includes/goals/class-goal-recurring.php
+++ b/includes/goals/class-goal-recurring.php
@@ -74,7 +74,7 @@ public function get_occurences() {
return $this->occurences;
}
- $date = new Date();
+ $date = new Date();
$ranges = $date->get_periods( $this->start, $this->end, $this->frequency );
foreach ( $ranges as $range ) {
diff --git a/includes/stats/class-stat.php b/includes/stats/class-stat.php
index 252a156e1..ac4edcf90 100644
--- a/includes/stats/class-stat.php
+++ b/includes/stats/class-stat.php
@@ -80,7 +80,7 @@ public function set_value( array $index, $value ): bool {
\_wp_array_set( $stats, $index, $value );
// Save the option.
- $updated = \update_option( self::SETTING_NAME, $stats );
+ $updated = \update_option( self::SETTING_NAME, $stats );
$this->stats = $stats;
return $updated;
From 9349fb7161dbd3fd578cf41426884f8355791944 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 15 Feb 2024 12:28:52 +0200
Subject: [PATCH 050/490] Fix error when stats are empty
---
includes/class-goals.php | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/includes/class-goals.php b/includes/class-goals.php
index d2bb8ec67..54c20c417 100644
--- a/includes/class-goals.php
+++ b/includes/class-goals.php
@@ -41,6 +41,13 @@ private function register_core_goals() {
private function register_weekly_post_goal() {
$stats = new Stat_Posts();
+ $stats_value = $stats->get_value();
+
+ // Bail early if there are no stats.
+ if ( empty( $stats_value ) ) {
+ return;
+ }
+
new Goal_Recurring(
new Goal_Posts(
[
@@ -61,7 +68,7 @@ private function register_weekly_post_goal() {
]
),
'weekly',
- array_keys( $stats->get_value() )[0], // Beginning of the stats.
+ array_keys( $stats_value )[0], // Beginning of the stats.
gmdate( Date::FORMAT ) // Today.
);
}
From 637776b811192cbe06dd16c50e650954036fb14d Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 15 Feb 2024 13:03:20 +0200
Subject: [PATCH 051/490] Add streaks
---
includes/class-base.php | 1 -
.../{class-goals.php => class-streaks.php} | 50 ++++++++++++-------
views/admin-page-streak.php | 16 ++++++
views/admin-page.php | 2 +
4 files changed, 49 insertions(+), 20 deletions(-)
rename includes/{class-goals.php => class-streaks.php} (61%)
create mode 100644 views/admin-page-streak.php
diff --git a/includes/class-base.php b/includes/class-base.php
index 08120cec7..6bed57846 100644
--- a/includes/class-base.php
+++ b/includes/class-base.php
@@ -46,7 +46,6 @@ private function __construct() {
$this->admin = new Admin();
new Stats();
- new Goals();
}
/**
diff --git a/includes/class-goals.php b/includes/class-streaks.php
similarity index 61%
rename from includes/class-goals.php
rename to includes/class-streaks.php
index 54c20c417..c3c1fadff 100644
--- a/includes/class-goals.php
+++ b/includes/class-streaks.php
@@ -1,36 +1,48 @@
register_core_goals();
- }
+class Streaks {
/**
- * Register the goals.
+ * Get the streak for weekly posts.
*
- * @return void
+ * @return int The number of weeks for this streak.
*/
- private function register_core_goals() {
- $this->register_weekly_post_goal();
+ public function get_weekly_post_streak() {
+ $goal = $this->get_weekly_post_goal();
+
+ // Bail early if there is no goal.
+ if ( ! $goal ) {
+ return 0;
+ }
+
+ // Reverse the order of the occurences.
+ $occurences = array_reverse( $goal->get_occurences() );
+ $streak_nr = 0;
+
+ foreach ( $occurences as $occurence ) {
+ // If the goal was not met, break the streak.
+ if ( ! $occurence->evaluate() ) {
+ break;
+ }
+
+ ++$streak_nr;
+ }
+
+ return $streak_nr;
}
/**
@@ -38,7 +50,7 @@ private function register_core_goals() {
*
* @return void
*/
- private function register_weekly_post_goal() {
+ private function get_weekly_post_goal() {
$stats = new Stat_Posts();
$stats_value = $stats->get_value();
@@ -48,7 +60,7 @@ private function register_weekly_post_goal() {
return;
}
- new Goal_Recurring(
+ return new Goal_Recurring(
new Goal_Posts(
[
'id' => 'weekly_post',
diff --git a/views/admin-page-streak.php b/views/admin-page-streak.php
new file mode 100644
index 000000000..eadd2a3f6
--- /dev/null
+++ b/views/admin-page-streak.php
@@ -0,0 +1,16 @@
+get_weekly_post_streak();
+$prpl_streak_color = 'hsl(' . min( 100, $prpl_streak_nr * 10 ) . ', 100%, 40%)';
+?>
+
diff --git a/views/admin-page.php b/views/admin-page.php
index 827cfea85..414169826 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -14,6 +14,8 @@
+
+
From 7b2cb02aef4c391e64c8d0af42dec47eda31f949 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Fri, 16 Feb 2024 10:17:39 +0200
Subject: [PATCH 052/490] minor tweak in views
---
includes/charts/class-posts.php | 1 +
views/admin-page-posts-count-progress.php | 3 +++
views/admin-page-words-count-progress.php | 2 ++
views/admin-page.php | 8 ++------
4 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index bd3beb41a..d77cc49dc 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -46,6 +46,7 @@ public function render( $post_types = [], $context = 'count', $interval = 'weeks
$datasets[ $post_type ] = [
'label' => \get_post_type_object( $post_type )->label,
'data' => [],
+ 'fill' => true,
];
}
diff --git a/views/admin-page-posts-count-progress.php b/views/admin-page-posts-count-progress.php
index f96ab30ad..9ae5d804f 100644
--- a/views/admin-page-posts-count-progress.php
+++ b/views/admin-page-posts-count-progress.php
@@ -5,6 +5,7 @@
* @package ProgressPlanner
*/
+echo '';
echo '
';
esc_html_e( 'Posts count progress', 'progress-planner' );
echo ' ';
@@ -16,3 +17,5 @@
\ProgressPlanner\Admin\Page::get_params()['filter_number'],
0
);
+
+echo '';
diff --git a/views/admin-page-words-count-progress.php b/views/admin-page-words-count-progress.php
index 2d6a13d88..2fad846f3 100644
--- a/views/admin-page-words-count-progress.php
+++ b/views/admin-page-words-count-progress.php
@@ -5,6 +5,7 @@
* @package ProgressPlanner
*/
+echo '';
echo '
';
esc_html_e( 'Words count progress', 'progress-planner' );
echo ' ';
@@ -16,3 +17,4 @@
\ProgressPlanner\Admin\Page::get_params()['filter_number'],
0
);
+echo '';
diff --git a/views/admin-page.php b/views/admin-page.php
index 414169826..75e9987dd 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -16,12 +16,8 @@
-
-
-
-
-
-
+
+
From ffa1d85e594d4ae2934c31ef96423e9492adfd1c Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Fri, 16 Feb 2024 11:26:57 +0200
Subject: [PATCH 053/490] Allow multiple streaks goals & fix the related view
---
includes/class-streaks.php | 117 ++++++++++++++++++++++--
includes/goals/class-goal-recurring.php | 9 ++
views/admin-page-streak.php | 23 +++--
3 files changed, 135 insertions(+), 14 deletions(-)
diff --git a/includes/class-streaks.php b/includes/class-streaks.php
index c3c1fadff..e2c9a4185 100644
--- a/includes/class-streaks.php
+++ b/includes/class-streaks.php
@@ -16,17 +16,70 @@
*/
class Streaks {
+ /**
+ * An array of recurring goals.
+ *
+ * @var Goal_Recurring[]
+ */
+ private $recurring_goals = [];
+
+ /**
+ * An instance of this class.
+ *
+ * @var \ProgressPlanner\Streaks
+ */
+ private static $instance;
+
+ /**
+ * Get the single instance of this class.
+ *
+ * @return \ProgressPlanner\Streaks
+ */
+ public static function get_instance() {
+ if ( null === self::$instance ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Constructor.
+ */
+ private function __construct() {
+ $this->register_recurring_goals();
+ }
+
+ /**
+ * Register recurring goals.
+ *
+ * @return void
+ */
+ private function register_recurring_goals() {
+ $this->recurring_goals['weekly_post'] = $this->get_weekly_post_goal();
+ $this->recurring_goals['weekly_words'] = $this->get_weekly_words_goal();
+ }
+
/**
* Get the streak for weekly posts.
*
+ * @param string $goal_id The goal ID.
+ * @param int $target The target number of weeks.
+ * Affects the color of the streak.
+ *
* @return int The number of weeks for this streak.
*/
- public function get_weekly_post_streak() {
- $goal = $this->get_weekly_post_goal();
+ public function get_streak( $goal_id, $target ) {
+ $goal = $this->recurring_goals[ $goal_id ];
// Bail early if there is no goal.
if ( ! $goal ) {
- return 0;
+ return [
+ 'number' => 0,
+ 'color' => 'hsl(0, 100%, 40%)',
+ 'title' => $goal->get_goal()->get_details()['title'],
+ 'description' => $goal->get_goal()->get_details()['description'],
+ ];
}
// Reverse the order of the occurences.
@@ -42,7 +95,12 @@ public function get_weekly_post_streak() {
++$streak_nr;
}
- return $streak_nr;
+ return [
+ 'number' => $streak_nr,
+ 'color' => 'hsl(' . (int) min( 100, $streak_nr * 100 / $target ) . ', 100%, 40%)',
+ 'title' => $goal->get_goal()->get_details()['title'],
+ 'description' => $goal->get_goal()->get_details()['description'],
+ ];
}
/**
@@ -65,15 +123,15 @@ private function get_weekly_post_goal() {
[
'id' => 'weekly_post',
'title' => \esc_html__( 'Write a weekly blog post', 'progress-planner' ),
- 'description' => '',
+ 'description' => \esc_html__( 'Streak: The number of weeks this goal has been accomplished consistently.', 'progress-planner' ),
'status' => 'active',
- 'priority' => 'high',
+ 'priority' => 'low',
'evaluate' => function ( $goal_object ) use ( $stats ) {
return (bool) count(
$stats->get_stats(
$goal_object->get_details()['start_date'],
$goal_object->get_details()['end_date'],
- [ 'post' ]
+ []
)
);
},
@@ -84,4 +142,49 @@ private function get_weekly_post_goal() {
gmdate( Date::FORMAT ) // Today.
);
}
+
+ /**
+ * Register a weekly-words goal.
+ *
+ * @return void
+ */
+ private function get_weekly_words_goal() {
+ $stats = new Stat_Posts();
+
+ $stats_value = $stats->get_value();
+
+ // Bail early if there are no stats.
+ if ( empty( $stats_value ) ) {
+ return;
+ }
+
+ return new Goal_Recurring(
+ new Goal_Posts(
+ [
+ 'id' => 'weekly_words',
+ 'title' => \esc_html__( 'Write 500 words/week', 'progress-planner' ),
+ 'description' => \esc_html__( 'Streak: The number of weeks this goal has been accomplished consistently.', 'progress-planner' ),
+ 'status' => 'active',
+ 'priority' => 'low',
+ 'evaluate' => function ( $goal_object ) use ( $stats ) {
+ $words = 0;
+ $posts = $stats->get_stats(
+ $goal_object->get_details()['start_date'],
+ $goal_object->get_details()['end_date'],
+ [ 'post' ]
+ );
+ foreach ( $posts as $post_dates ) {
+ foreach ( $post_dates as $post_details ) {
+ $words += $post_details['words'];
+ }
+ }
+ return $words >= 500;
+ },
+ ]
+ ),
+ 'weekly',
+ array_keys( $stats_value )[0], // Beginning of the stats.
+ gmdate( Date::FORMAT ) // Today.
+ );
+ }
}
diff --git a/includes/goals/class-goal-recurring.php b/includes/goals/class-goal-recurring.php
index a65c3ec46..6a155093e 100644
--- a/includes/goals/class-goal-recurring.php
+++ b/includes/goals/class-goal-recurring.php
@@ -64,6 +64,15 @@ public function __construct( $goal, $frequency, $start, $end ) {
$this->end = $end;
}
+ /**
+ * Get the goal title.
+ *
+ * @return string
+ */
+ public function get_goal() {
+ return $this->goal;
+ }
+
/**
* Build an array of occurences for the goal.
*
diff --git a/views/admin-page-streak.php b/views/admin-page-streak.php
index eadd2a3f6..de4d445af 100644
--- a/views/admin-page-streak.php
+++ b/views/admin-page-streak.php
@@ -5,12 +5,21 @@
* @package ProgressPlanner
*/
-$prpl_streak_nr = ( new \ProgressPlanner\Streaks() )->get_weekly_post_streak();
-$prpl_streak_color = 'hsl(' . min( 100, $prpl_streak_nr * 10 ) . ', 100%, 40%)';
+$prpl_streaks = [
+ 'weekly_post' => 10, // Number of posts per week, targetting for 10 weeks.
+ 'weekly_words' => 10, // Number of words per week, targetting for 10 weeks.
+];
?>
-
-
-
-
-
+
+
+ $prpl_streak_goal ) : ?>
+
+ get_streak( $prpl_streak_id, $prpl_streak_goal ); ?>
+
+
+
+
+
+
+
From c2aa2afe0f2ae6821cf8bfe4d6e92b38da8384a3 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Fri, 16 Feb 2024 11:29:32 +0200
Subject: [PATCH 054/490] rearrange views a bit
---
views/admin-page.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/views/admin-page.php b/views/admin-page.php
index 75e9987dd..b1cd7264b 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -12,10 +12,10 @@
-
-
+
+
From 48b88f39d4f483667ac259d03ab26d33430ee4ff Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 19 Feb 2024 10:26:58 +0200
Subject: [PATCH 055/490] Add dashboard widget
---
includes/admin/class-dashboard-widget.php | 48 +++++++++++++++++++++++
includes/class-admin.php | 2 +
2 files changed, 50 insertions(+)
create mode 100644 includes/admin/class-dashboard-widget.php
diff --git a/includes/admin/class-dashboard-widget.php b/includes/admin/class-dashboard-widget.php
new file mode 100644
index 000000000..d2b9fe6e0
--- /dev/null
+++ b/includes/admin/class-dashboard-widget.php
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+ admin_page = new Admin\Page();
+
+ new Admin\Dashboard_Widget();
}
/**
From 47239359cc927e428dadaf0e3ff0428b1febd087 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 19 Feb 2024 10:56:52 +0200
Subject: [PATCH 056/490] Some fixes for the admin dashboard
---
includes/admin/class-dashboard-widget.php | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/includes/admin/class-dashboard-widget.php b/includes/admin/class-dashboard-widget.php
index d2b9fe6e0..e8cc7ee87 100644
--- a/includes/admin/class-dashboard-widget.php
+++ b/includes/admin/class-dashboard-widget.php
@@ -37,10 +37,21 @@ public function render_dashboard_widget() {
?>
Date: Wed, 21 Feb 2024 12:49:59 +0200
Subject: [PATCH 057/490] refactor posts scanning
---
includes/admin/class-page.php | 18 +-
includes/class-stats.php | 11 +-
.../stats/class-stat-posts-prepopulate.php | 166 ++++++++++--------
includes/stats/class-stat-posts.php | 69 ++++----
includes/stats/class-stat.php | 88 ----------
5 files changed, 147 insertions(+), 205 deletions(-)
delete mode 100644 includes/stats/class-stat.php
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index b7597859f..08fa718e2 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -110,19 +110,19 @@ public function ajax_scan() {
// Scan the posts.
$prepopulate = new \ProgressPlanner\Stats\Stat_Posts_Prepopulate();
- $prepopulate->prepopulate();
+ $prepopulate->update_stats();
- // Get the last scanned post ID.
- $last_scanned_id = $prepopulate->get_last_prepopulated_post();
+ // Get the last scanned page.
+ $last_scanned_page = $prepopulate->get_last_scanned_page();
// Get the last post-ID that exists on the site.
- $last_post_id = $prepopulate->get_last_post_id();
+ $total_pages_to_scan = $prepopulate->get_total_pages_to_scan();
\wp_send_json_success(
[
- 'lastScanned' => $last_scanned_id,
- 'lastPost' => $last_post_id,
- 'progress' => round( ( $last_scanned_id / $last_post_id ) * 100 ),
+ 'lastScanned' => $last_scanned_page,
+ 'lastPost' => $total_pages_to_scan,
+ 'progress' => round( ( $last_scanned_page / $total_pages_to_scan ) * 100 ),
'messages' => [
'scanComplete' => \esc_html__( 'Scan complete.', 'progress-planner' ),
],
@@ -142,8 +142,8 @@ public function ajax_reset_stats() {
}
// Reset the stats.
- $stats = new \ProgressPlanner\Stats\Stat_Posts();
- $stats->reset_stats();
+ \delete_option( static::SETTING_NAME );
+ \delete_option( static::LAST_SCANNED_PAGE_OPTION );
\wp_send_json_success(
[
diff --git a/includes/class-stats.php b/includes/class-stats.php
index 52c8b87c9..d08c22842 100644
--- a/includes/class-stats.php
+++ b/includes/class-stats.php
@@ -7,13 +7,12 @@
namespace ProgressPlanner;
-use ProgressPlanner\Stats\Stat;
use ProgressPlanner\Stats\Stat_Posts;
/**
* Stats class.
*
- * This is a collection of individual Stat objects.
+ * This is a collection of objects.
*/
class Stats {
@@ -35,11 +34,11 @@ public function __construct() {
* Add a stat to the collection.
*
* @param string $id The ID of the stat.
- * @param Stat $stat The stat object.
+ * @param Object $stat The stat object.
*
* @return void
*/
- public function add_stat( $id, Stat $stat ) {
+ public function add_stat( $id, $stat ) {
$this->stats[ $id ] = $stat;
}
@@ -57,9 +56,9 @@ public function get_all_stats() {
*
* @param string $id The ID of the stat.
*
- * @return Stat
+ * @return Object
*/
- public function get_stat( $id ): Stat {
+ public function get_stat( $id ) {
return $this->stats[ $id ];
}
diff --git a/includes/stats/class-stat-posts-prepopulate.php b/includes/stats/class-stat-posts-prepopulate.php
index 6b35c4c6c..52a3a521d 100644
--- a/includes/stats/class-stat-posts-prepopulate.php
+++ b/includes/stats/class-stat-posts-prepopulate.php
@@ -7,6 +7,8 @@
namespace ProgressPlanner\Stats;
+use ProgressPlanner\Date;
+
/**
* Prepopulate the posts stats.
*/
@@ -17,112 +19,134 @@ class Stat_Posts_Prepopulate extends Stat_Posts {
*
* @var int
*/
- const POSTS_PER_PAGE = 100;
+ const POSTS_PER_PAGE = 20;
/**
- * The last post-ID.
+ * The option used to store the last scanned page.
*
- * @var int
+ * @var string
*/
- private $last_post_id = 0;
+ const LAST_SCANNED_PAGE_OPTION = 'progress_planner_stats_last_scanned_page';
/**
- * The last scanned post-ID.
+ * The total posts count.
*
* @var int
*/
- private $last_scanned_post_id = 0;
+ private static $posts_count = 0;
/**
- * Get the last page that was prepopulated from the API.
+ * Update stats for posts.
+ * - Gets the next page to scan.
+ * - Gets the posts for the page.
+ * - Updates the stats for the posts.
+ * - Updates the last scanned page option.
*
- * @return int
+ * @return void
*/
- public function get_last_prepopulated_post() {
- // If we have the last scanned post, return it.
- if ( $this->last_scanned_post_id ) {
- return $this->last_scanned_post_id;
- }
+ public function update_stats() {
+ $last_page = $this->get_last_scanned_page();
+ $next_page = $last_page + 1;
+ $this->update_stats_for_posts( $next_page );
+ $this->update_last_scanned_page( $next_page );
+ }
- // Try to get the value from the transient.
- $cached = \get_transient( 'progress_planner_last_prepopulated_post' );
- if ( $cached ) {
- $this->last_scanned_post_id = $cached;
- return $this->last_scanned_post_id;
+ /**
+ * Update stats for posts.
+ *
+ * @param int $page The page to query.
+ */
+ private function update_stats_for_posts( $page ) {
+ $posts = \get_posts(
+ [
+ 'posts_per_page' => static::POSTS_PER_PAGE,
+ 'paged' => $page,
+ 'post_type' => $this->get_post_types_names(),
+ 'post_status' => 'publish',
+ ]
+ );
+
+ if ( ! $posts ) {
+ return;
}
- // Get the last scanned post-ID from the stats.
- $option_value = $this->get_value();
- foreach ( $option_value as $posts ) {
- foreach ( $posts as $post_id => $details ) {
- if ( $post_id > $this->last_scanned_post_id ) {
- $this->last_scanned_post_id = $post_id;
- }
+ // Get the value from the option.
+ $value = \get_option( static::SETTING_NAME, [] );
+
+ // Loop through the posts and update the $value stats.
+ foreach ( $posts as $post ) {
+ // Get the date from the post, and convert it to the format we use.
+ $date = (int) mysql2date( Date::FORMAT, $post->post_date );
+
+ // If the date is not set in the option, set it to an empty array.
+ if ( ! isset( $value[ $date ] ) ) {
+ $value[ $date ] = [];
}
+
+ // Add the post to the date.
+ $value[ $date ][ $post->ID ] = [
+ 'post_type' => $post->post_type,
+ 'words' => $this->get_word_count( $post->post_content ),
+ ];
}
- return $this->last_scanned_post_id;
+
+ // Save the option.
+ \update_option( static::SETTING_NAME, $value );
}
/**
- * Set the last prepopulated post.
+ * Get the total posts count.
*
- * @return void
+ * @return int
*/
- public function save_last_prepopulated_post() {
- \set_transient( 'progress_planner_last_prepopulated_post', $this->last_scanned_post_id, \HOUR_IN_SECONDS );
+ private function get_posts_count() {
+ if ( static::$posts_count ) {
+ return static::$posts_count;
+ }
+ foreach ( $this->get_post_types_names() as $post_type ) {
+ static::$posts_count += \wp_count_posts( $post_type )->publish;
+ }
+ return static::$posts_count;
}
/**
- * Get posts and prepopulate the stats.
+ * Get number of pages to scan.
*
- * @return void
+ * @return int
*/
- public function prepopulate() {
- // Get the last post we processed.
- $last_id = $this->get_last_prepopulated_post();
-
- // Build an array of posts to save.
- $post_ids = \range( $last_id, $last_id + self::POSTS_PER_PAGE );
-
- foreach ( $post_ids as $post_id ) {
- $post = get_post( $post_id );
+ public function get_total_pages_to_scan() {
+ return \ceil( $this->get_posts_count() / static::POSTS_PER_PAGE );
+ }
- // If the post doesn't exist or is not publish, skip it.
- if ( ! $post || 'publish' !== $post->post_status ) {
- $this->last_scanned_post_id = $post_id;
- $this->save_last_prepopulated_post();
- continue;
- }
+ /**
+ * Get last scanned page.
+ *
+ * @return int
+ */
+ public function get_last_scanned_page() {
+ return (int) \get_option( static::LAST_SCANNED_PAGE_OPTION, 1 );
+ }
- $this->save_post( $post );
- $this->last_scanned_post_id = $post->ID;
- $this->save_last_prepopulated_post();
+ /**
+ * Update last scanned page.
+ *
+ * @param int $page The page to set.
+ */
+ private function update_last_scanned_page( $page ) {
+ if ( $page > $this->get_total_pages_to_scan() ) {
+ \delete_option( static::LAST_SCANNED_PAGE_OPTION );
+ return;
}
+ \update_option( static::LAST_SCANNED_PAGE_OPTION, $page );
}
/**
- * Get the last post-ID created.
+ * Reset the stats in our database.
*
- * @return int
+ * @return void
*/
- public function get_last_post_id() {
- if ( $this->last_post_id ) {
- return $this->last_post_id;
- }
- $last_post = \get_posts(
- [
- 'posts_per_page' => 1,
- 'post_type' => $this->get_post_types_names(),
- 'post_status' => 'publish',
- 'suppress_filters' => false,
- 'order' => 'DESC',
- 'orderby' => 'ID',
- ]
- );
- if ( empty( $last_post ) ) {
- return 0;
- }
- $this->last_post_id = $last_post[0]->ID;
- return $this->last_post_id;
+ public function reset_stats() {
+ \delete_option( static::SETTING_NAME );
+ \delete_option( static::LAST_SCANNED_PAGE_OPTION );
}
}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index aff5a39c3..02c6d367f 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -12,25 +12,20 @@
/**
* Stats about posts.
*/
-class Stat_Posts extends Stat {
+class Stat_Posts {
/**
- * The stat type. This is used as a key in the settings array.
- *
- * @var string
+ * The setting name.
*/
- protected $type = 'posts';
+ const SETTING_NAME = 'progress_planner_stats_posts';
/**
* Get the value.
*
- * @param string[]|int[] $index The index. This is an array of keys, which will be used to get the value.
- * This will go over the array recursively, getting the value for the last key.
- * See _wp_array_get for more info.
* @return mixed
*/
- public function get_value( $index = [] ) {
- $value = parent::get_value( $index );
+ public function get_value() {
+ $value = \get_option( static::SETTING_NAME, [] );
ksort( $value );
return $value;
}
@@ -40,20 +35,20 @@ public function get_value( $index = [] ) {
*
* @param \WP_Post $post The post.
*
- * @return void
+ * @return bool
*/
protected function save_post( $post ) {
- // Get the date.
- $date = (int) mysql2date( Date::FORMAT, $post->post_date );
-
- // Add the post to the stats.
- $this->set_value(
- [ $date, $post->ID ],
- [
- 'post_type' => $post->post_type,
- 'words' => \str_word_count( $post->post_content ),
- ],
- );
+ $value = \get_option( static::SETTING_NAME, [] );
+ $date = (int) mysql2date( Date::FORMAT, $post->post_date );
+
+ if ( ! isset( $value[ $date ] ) ) {
+ $value[ $date ] = [];
+ }
+ $value[ $date ][ $post->ID ] = [
+ 'post_type' => $post->post_type,
+ 'words' => $this->get_word_count( $post->post_content ),
+ ];
+ return \update_option( static::SETTING_NAME, $value );
}
/**
@@ -95,15 +90,6 @@ public function get_stats( $start_date, $end_date, $post_types = [] ) {
return $stats;
}
- /**
- * Reset the stats in our database.
- *
- * @return void
- */
- public function reset_stats() {
- $this->set_value( [], [] );
- }
-
/**
* Get an array of post-types names for the stats.
*
@@ -115,4 +101,25 @@ public function get_post_types_names() {
return array_keys( $post_types );
}
+
+ /**
+ * Get words count from content.
+ *
+ * This method will render shortcodes, blocks,
+ * and strip HTML before counting the words.
+ *
+ * @param string $content The content.
+ *
+ * @return int
+ */
+ protected function get_word_count( $content ) {
+ // Parse blocks and shortcodes.
+ $content = \do_blocks( \do_shortcode( $content ) );
+
+ // Strip HTML.
+ $content = \wp_strip_all_tags( $content, true );
+
+ // Count words.
+ return \str_word_count( $content );
+ }
}
diff --git a/includes/stats/class-stat.php b/includes/stats/class-stat.php
deleted file mode 100644
index ac4edcf90..000000000
--- a/includes/stats/class-stat.php
+++ /dev/null
@@ -1,88 +0,0 @@
-stats[ $this->type ] ) ) {
- $this->stats = \get_option( self::SETTING_NAME, [ $this->type => [] ] );
- }
-
- if ( ! empty( $index ) ) {
- return \_wp_array_get( $this->stats[ $this->type ], $index );
- }
-
- return $this->stats[ $this->type ];
- }
-
- /**
- * Set the value.
- *
- * @param string[]|int[] $index The index. This is an array of keys,
- * which will be used to set the value.
- * It will go over the array recursively,
- * updating the value for the last key.
- * See _wp_array_set for more info.
- * @param mixed $value The value.
- *
- * @return bool
- */
- public function set_value( array $index, $value ): bool {
- // Call $this->get_value, to populate $this->stats.
- $stats = \get_option( self::SETTING_NAME, [ $this->type => [] ] );
-
- // Add $this->type to the beginning of the index array.
- \array_unshift( $index, $this->type );
-
- // Update the value in the array.
- \_wp_array_set( $stats, $index, $value );
-
- // Save the option.
- $updated = \update_option( self::SETTING_NAME, $stats );
- $this->stats = $stats;
-
- return $updated;
- }
-}
From 036084c0bb2a929a133fcbbc34290fa53cd1f608 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 21 Feb 2024 12:50:07 +0200
Subject: [PATCH 058/490] CS
---
includes/admin/class-dashboard-widget.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/admin/class-dashboard-widget.php b/includes/admin/class-dashboard-widget.php
index e8cc7ee87..ccdc9fc95 100644
--- a/includes/admin/class-dashboard-widget.php
+++ b/includes/admin/class-dashboard-widget.php
@@ -16,7 +16,7 @@ class Dashboard_Widget {
* Constructor.
*/
public function __construct() {
- \add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widget' ) );
+ \add_action( 'wp_dashboard_setup', [ $this, 'add_dashboard_widget' ] );
}
/**
From d111dc3d4a6957d1623b908fdde9464bf3d48621 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 21 Feb 2024 12:50:46 +0200
Subject: [PATCH 059/490] JS fix
---
assets/js/admin.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 20b5ee75a..8de488f9f 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -143,6 +143,7 @@ progressPlannerDomReady( () => {
resetForm.addEventListener( 'submit', ( e ) => {
e.preventDefault();
resetForm.querySelector( 'input[type="submit"]' ).disabled = true;
+ resetForm.querySelector( 'input[type="submit"]' ).value = progressPlanner.l10n.resettingStats;
// Make an AJAX request to reset the stats.
progressPlannerAjaxRequest( {
@@ -152,7 +153,6 @@ progressPlannerDomReady( () => {
_ajax_nonce: progressPlanner.nonce,
},
successAction: ( response ) => {
- resetForm.querySelector( 'input[type="submit"]' ).value = progressPlanner.l10n.resettingStats;
// Refresh the page.
location.reload();
},
From 3ab9fc116df89ecdfafa3472f92c4d76a2c7942c Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 21 Feb 2024 14:34:33 +0200
Subject: [PATCH 060/490] Simplify structure for posts stats & add hook on
post-save
---
assets/js/admin.js | 2 +-
includes/admin/class-page.php | 20 +-
.../stats/class-stat-posts-prepopulate.php | 152 ---------------
includes/stats/class-stat-posts.php | 181 ++++++++++++++++--
4 files changed, 175 insertions(+), 180 deletions(-)
delete mode 100644 includes/stats/class-stat-posts-prepopulate.php
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 8de488f9f..9fb5cf69d 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -60,7 +60,7 @@ const progressPlannerTriggerScan = () => {
progressBar.value = response.data.progress;
}
- console.info( `Progress: ${response.data.progress}%, (${response.data.lastScanned}/${response.data.lastPost})` );
+ console.info( `Progress: ${response.data.progress}%, (${response.data.lastScanned}/${response.data.lastPage})` );
// Refresh the page when scan has finished.
if ( response.data.progress >= 100 ) {
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index 08fa718e2..2688c007e 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -109,20 +109,14 @@ public function ajax_scan() {
}
// Scan the posts.
- $prepopulate = new \ProgressPlanner\Stats\Stat_Posts_Prepopulate();
- $prepopulate->update_stats();
-
- // Get the last scanned page.
- $last_scanned_page = $prepopulate->get_last_scanned_page();
-
- // Get the last post-ID that exists on the site.
- $total_pages_to_scan = $prepopulate->get_total_pages_to_scan();
+ $posts_stats = new \ProgressPlanner\Stats\Stat_Posts();
+ $updated_stats = $posts_stats->update_stats();
\wp_send_json_success(
[
- 'lastScanned' => $last_scanned_page,
- 'lastPost' => $total_pages_to_scan,
- 'progress' => round( ( $last_scanned_page / $total_pages_to_scan ) * 100 ),
+ 'lastScanned' => $updated_stats['lastScannedPage'],
+ 'lastPage' => $updated_stats['lastPage'],
+ 'progress' => $updated_stats['progress'],
'messages' => [
'scanComplete' => \esc_html__( 'Scan complete.', 'progress-planner' ),
],
@@ -142,8 +136,8 @@ public function ajax_reset_stats() {
}
// Reset the stats.
- \delete_option( static::SETTING_NAME );
- \delete_option( static::LAST_SCANNED_PAGE_OPTION );
+ $posts_stats = new \ProgressPlanner\Stats\Stat_Posts();
+ $posts_stats->reset_stats();
\wp_send_json_success(
[
diff --git a/includes/stats/class-stat-posts-prepopulate.php b/includes/stats/class-stat-posts-prepopulate.php
deleted file mode 100644
index 52a3a521d..000000000
--- a/includes/stats/class-stat-posts-prepopulate.php
+++ /dev/null
@@ -1,152 +0,0 @@
-get_last_scanned_page();
- $next_page = $last_page + 1;
- $this->update_stats_for_posts( $next_page );
- $this->update_last_scanned_page( $next_page );
- }
-
- /**
- * Update stats for posts.
- *
- * @param int $page The page to query.
- */
- private function update_stats_for_posts( $page ) {
- $posts = \get_posts(
- [
- 'posts_per_page' => static::POSTS_PER_PAGE,
- 'paged' => $page,
- 'post_type' => $this->get_post_types_names(),
- 'post_status' => 'publish',
- ]
- );
-
- if ( ! $posts ) {
- return;
- }
-
- // Get the value from the option.
- $value = \get_option( static::SETTING_NAME, [] );
-
- // Loop through the posts and update the $value stats.
- foreach ( $posts as $post ) {
- // Get the date from the post, and convert it to the format we use.
- $date = (int) mysql2date( Date::FORMAT, $post->post_date );
-
- // If the date is not set in the option, set it to an empty array.
- if ( ! isset( $value[ $date ] ) ) {
- $value[ $date ] = [];
- }
-
- // Add the post to the date.
- $value[ $date ][ $post->ID ] = [
- 'post_type' => $post->post_type,
- 'words' => $this->get_word_count( $post->post_content ),
- ];
- }
-
- // Save the option.
- \update_option( static::SETTING_NAME, $value );
- }
-
- /**
- * Get the total posts count.
- *
- * @return int
- */
- private function get_posts_count() {
- if ( static::$posts_count ) {
- return static::$posts_count;
- }
- foreach ( $this->get_post_types_names() as $post_type ) {
- static::$posts_count += \wp_count_posts( $post_type )->publish;
- }
- return static::$posts_count;
- }
-
- /**
- * Get number of pages to scan.
- *
- * @return int
- */
- public function get_total_pages_to_scan() {
- return \ceil( $this->get_posts_count() / static::POSTS_PER_PAGE );
- }
-
- /**
- * Get last scanned page.
- *
- * @return int
- */
- public function get_last_scanned_page() {
- return (int) \get_option( static::LAST_SCANNED_PAGE_OPTION, 1 );
- }
-
- /**
- * Update last scanned page.
- *
- * @param int $page The page to set.
- */
- private function update_last_scanned_page( $page ) {
- if ( $page > $this->get_total_pages_to_scan() ) {
- \delete_option( static::LAST_SCANNED_PAGE_OPTION );
- return;
- }
- \update_option( static::LAST_SCANNED_PAGE_OPTION, $page );
- }
-
- /**
- * Reset the stats in our database.
- *
- * @return void
- */
- public function reset_stats() {
- \delete_option( static::SETTING_NAME );
- \delete_option( static::LAST_SCANNED_PAGE_OPTION );
- }
-}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 02c6d367f..7a6d7812e 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -16,18 +16,81 @@ class Stat_Posts {
/**
* The setting name.
+ *
+ * @var string
*/
const SETTING_NAME = 'progress_planner_stats_posts';
+ /**
+ * The number of posts to scan at a time.
+ *
+ * @var int
+ */
+ const SCAN_POSTS_PER_PAGE = 20;
+
+ /**
+ * The option used to store the last scanned page.
+ *
+ * @var string
+ */
+ const LAST_SCANNED_PAGE_OPTION = 'progress_planner_stats_last_scanned_page';
+
+ /**
+ * The stats. Used for caching purposes.
+ *
+ * @var array
+ */
+ private static $stats;
+
+ /**
+ * Constructor.
+ */
+ public function __construct() {
+ $this->register_hooks();
+ }
+
+ /**
+ * Register the hooks.
+ *
+ * @return void
+ */
+ private function register_hooks() {
+ \add_action( 'save_post', [ $this, 'save_post' ], 10, 2 );
+ }
+
+ /**
+ * Run actions when saving a post.
+ *
+ * @param int $post_id The post ID.
+ * @param \WP_Post $post The post object.
+ */
+ public function save_post( $post_id, $post ) {
+ // Bail if the post is not included in the post-types we're tracking.
+ $post_types = $this->get_post_types_names();
+ if ( ! \in_array( $post->post_type, $post_types, true ) ) {
+ return;
+ }
+
+ // Bail if the post is not published.
+ if ( 'publish' !== $post->post_status ) {
+ return;
+ }
+
+ $this->save_post_stats( $post );
+ }
+
/**
* Get the value.
*
* @return mixed
*/
public function get_value() {
- $value = \get_option( static::SETTING_NAME, [] );
- ksort( $value );
- return $value;
+ if ( ! self::$stats ) {
+ $value = \get_option( static::SETTING_NAME, [] );
+ ksort( $value );
+ self::$stats = $value;
+ }
+ return self::$stats;
}
/**
@@ -37,10 +100,17 @@ public function get_value() {
*
* @return bool
*/
- protected function save_post( $post ) {
+ protected function save_post_stats( $post ) {
$value = \get_option( static::SETTING_NAME, [] );
$date = (int) mysql2date( Date::FORMAT, $post->post_date );
+ // Remove the post from stats if it's already stored in another date.
+ foreach ( $value as $date_key => $date_value ) {
+ if ( isset( $date_value[ $post->ID ] ) ) {
+ unset( $value[ $date_key ][ $post->ID ] );
+ }
+ }
+
if ( ! isset( $value[ $date ] ) ) {
$value[ $date ] = [];
}
@@ -64,10 +134,10 @@ public function get_stats( $start_date, $end_date, $post_types = [] ) {
$stats = $this->get_value();
// Get the stats for the date range and post types.
- foreach ( array_keys( $stats ) as $key ) {
- // Remove the stats that are outside the date range.
- if ( $key <= $start_date || $key > $end_date ) {
- unset( $stats[ $key ] );
+ foreach ( $stats as $date => $date_stats ) {
+ // Remove stats outside the date range.
+ if ( $date <= $start_date || $date > $end_date ) {
+ unset( $stats[ $date ] );
continue;
}
@@ -76,16 +146,19 @@ public function get_stats( $start_date, $end_date, $post_types = [] ) {
continue;
}
- // Remove the stats that are not in the post types.
- foreach ( $stats[ $key ] as $post_id => $details ) {
+ // Remove stats not in the post types.
+ foreach ( $stats[ $date ] as $post_id => $details ) {
if ( ! \in_array( $details['post_type'], $post_types, true ) ) {
- unset( $stats[ $key ][ $post_id ] );
+ unset( $stats[ $date ][ $post_id ] );
}
}
- }
- // Filter out empty dates.
- $stats = \array_filter( $stats );
+ // Remove empty dates.
+ if ( ! $stats[ $date ] || empty( $stats[ $date ] ) ) {
+ unset( $stats[ $date ] );
+ continue;
+ }
+ }
return $stats;
}
@@ -122,4 +195,84 @@ protected function get_word_count( $content ) {
// Count words.
return \str_word_count( $content );
}
+
+ /**
+ * Update stats for posts.
+ * - Gets the next page to scan.
+ * - Gets the posts for the page.
+ * - Updates the stats for the posts.
+ * - Updates the last scanned page option.
+ *
+ * @return array
+ */
+ public function update_stats() {
+
+ // Get the total number of posts.
+ $total_posts_count = 0;
+ foreach ( $this->get_post_types_names() as $post_type ) {
+ $total_posts_count += \wp_count_posts( $post_type )->publish;
+ }
+ // Calculate the total pages to scan.
+ $total_pages = \ceil( $total_posts_count / static::SCAN_POSTS_PER_PAGE );
+ // Get the last scanned page.
+ $last_page = (int) \get_option( static::LAST_SCANNED_PAGE_OPTION, 0 );
+ // The current page to scan.
+ $current_page = $last_page + 1;
+
+ // Get posts.
+ $posts = \get_posts(
+ [
+ 'posts_per_page' => static::SCAN_POSTS_PER_PAGE,
+ 'paged' => $current_page,
+ 'post_type' => $this->get_post_types_names(),
+ 'post_status' => 'publish',
+ ]
+ );
+
+ if ( $posts ) {
+ // Get the value from the option.
+ $value = \get_option( static::SETTING_NAME, [] );
+
+ // Loop through the posts and update the $value stats.
+ foreach ( $posts as $post ) {
+ // Get the date from the post, and convert it to the format we use.
+ $date = (int) mysql2date( Date::FORMAT, $post->post_date );
+
+ // If the date is not set in the option, set it to an empty array.
+ if ( ! isset( $value[ $date ] ) ) {
+ $value[ $date ] = [];
+ }
+
+ // Add the post to the date.
+ $value[ $date ][ $post->ID ] = [
+ 'post_type' => $post->post_type,
+ 'words' => $this->get_word_count( $post->post_content ),
+ ];
+ }
+
+ // Save the option.
+ \update_option( static::SETTING_NAME, $value );
+ }
+
+ if ( $current_page > $total_pages ) {
+ \delete_option( static::LAST_SCANNED_PAGE_OPTION );
+ }
+ \update_option( static::LAST_SCANNED_PAGE_OPTION, $current_page );
+
+ return [
+ 'lastScannedPage' => $last_page,
+ 'lastPage' => $total_pages,
+ 'progress' => round( ( $current_page / max( 1, $total_pages ) ) * 100 ),
+ ];
+ }
+
+ /**
+ * Reset the stats in our database.
+ *
+ * @return void
+ */
+ public function reset_stats() {
+ \delete_option( static::SETTING_NAME );
+ \delete_option( static::LAST_SCANNED_PAGE_OPTION );
+ }
}
From 79823a48ef22d425efd47a6243651c9524818ac0 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 21 Feb 2024 15:06:48 +0200
Subject: [PATCH 061/490] Update stats when changing and deleting posts
---
includes/stats/class-stat-posts.php | 42 +++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 7a6d7812e..92006931a 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -56,6 +56,9 @@ public function __construct() {
*/
private function register_hooks() {
\add_action( 'save_post', [ $this, 'save_post' ], 10, 2 );
+ \add_action( 'wp_insert_post', [ $this, 'save_post' ], 10, 2 );
+ \add_action( 'delete_post', [ $this, 'delete_post' ] );
+ \add_action( 'transition_post_status', [ $this, 'transition_post_status' ], 10, 3 );
}
/**
@@ -79,6 +82,45 @@ public function save_post( $post_id, $post ) {
$this->save_post_stats( $post );
}
+ /**
+ * Delete a post from stats.
+ *
+ * @param int $post_id The post ID.
+ */
+ public function delete_post( $post_id ) {
+ $value = \get_option( static::SETTING_NAME, [] );
+ $updated = false;
+ // Remove the post from stats if it's already stored in another date.
+ foreach ( $value as $date_key => $date_value ) {
+ if ( isset( $date_value[ $post_id ] ) ) {
+ unset( $value[ $date_key ][ $post_id ] );
+ $updated = true;
+ }
+ }
+
+ if ( $updated ) {
+ \update_option( static::SETTING_NAME, $value );
+ }
+ }
+
+ /**
+ * Run actions when transitioning a post status.
+ *
+ * @param string $new_status The new status.
+ * @param string $old_status The old status.
+ * @param \WP_Post $post The post object.
+ */
+ public function transition_post_status( $new_status, $old_status, $post ) {
+ // Delete the post from stats.
+ if ( 'publish' === $old_status && 'publish' !== $new_status ) {
+ $this->delete_post( $post->ID );
+ }
+ // Add the post to stats.
+ if ( 'publish' !== $old_status && 'publish' === $new_status ) {
+ $this->save_post_stats( $post );
+ }
+ }
+
/**
* Get the value.
*
From c5c98d39a41bf974179f324540c44d6f2a22b400 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 21 Feb 2024 15:06:59 +0200
Subject: [PATCH 062/490] Disable streaks for now
---
views/admin-page-streak.php | 3 +++
1 file changed, 3 insertions(+)
diff --git a/views/admin-page-streak.php b/views/admin-page-streak.php
index de4d445af..a7447b07c 100644
--- a/views/admin-page-streak.php
+++ b/views/admin-page-streak.php
@@ -5,6 +5,9 @@
* @package ProgressPlanner
*/
+// TODO: DISABLE THIS FOR NOW, IT'S NOT WORKING.
+return;
+
$prpl_streaks = [
'weekly_post' => 10, // Number of posts per week, targetting for 10 weeks.
'weekly_words' => 10, // Number of words per week, targetting for 10 weeks.
From dd3c02f9007aa4adbbb44de6f2a0260232da367b Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 21 Feb 2024 15:43:03 +0200
Subject: [PATCH 063/490] Add offset to charts
---
includes/charts/class-posts.php | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index d77cc49dc..55d313cff 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -51,6 +51,21 @@ public function render( $post_types = [], $context = 'count', $interval = 'weeks
}
$stat_posts = new Stat_Posts();
+
+ // Calculate zero stats to be used as the baseline.
+ $zero_stats = $stat_posts->get_stats(
+ 19700101,
+ (int) gmdate( Date::FORMAT, strtotime( "-$range $interval" ) ),
+ $post_types
+ );
+ foreach ( $zero_stats as $zero_posts ) {
+ foreach ( $zero_posts as $zero_post ) {
+ $post_type_count_totals[ $zero_post['post_type'] ] += 'words' === $context
+ ? $zero_post['words']
+ : 1;
+ }
+ }
+
foreach ( $range_array as $start => $end ) {
$stats = $stat_posts->get_stats(
(int) gmdate( Date::FORMAT, strtotime( "-$start $interval" ) ),
@@ -65,11 +80,9 @@ public function render( $post_types = [], $context = 'count', $interval = 'weeks
foreach ( $stats as $posts ) {
foreach ( $posts as $post_details ) {
if ( $post_details['post_type'] === $post_type ) {
- if ( 'words' === $context ) {
- $post_type_count_totals[ $post_type ] += $post_details['words'];
- continue;
- }
- ++$post_type_count_totals[ $post_type ];
+ $post_type_count_totals[ $post_type ] += 'words' === $context
+ ? $post_details['words']
+ : 1;
}
}
}
From e07d8559641f2970acc01fe76690427b9731a620 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 21 Feb 2024 15:55:02 +0200
Subject: [PATCH 064/490] Improve stats display
---
includes/charts/class-posts.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index 55d313cff..ba6eae17e 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -44,9 +44,9 @@ public function render( $post_types = [], $context = 'count', $interval = 'weeks
foreach ( $post_types as $post_type ) {
$post_type_count_totals[ $post_type ] = 0;
$datasets[ $post_type ] = [
- 'label' => \get_post_type_object( $post_type )->label,
- 'data' => [],
- 'fill' => true,
+ 'label' => \get_post_type_object( $post_type )->label,
+ 'data' => [],
+ 'tension' => 0.2,
];
}
From 66e6c54a32b4db59cdc35f890c6b4a3ac4b2fbc0 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 22 Feb 2024 10:21:43 +0200
Subject: [PATCH 065/490] Add scrolling to debug data
---
views/admin-page-debug.php | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/views/admin-page-debug.php b/views/admin-page-debug.php
index 5e2c54654..6f9b5f70f 100644
--- a/views/admin-page-debug.php
+++ b/views/admin-page-debug.php
@@ -13,5 +13,10 @@
- get_value() ); ?>
+
+ get_value() );
+ ?>
+
From 56075913f9830820cdab69fc7fe9cad7d1199da5 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 22 Feb 2024 10:30:02 +0200
Subject: [PATCH 066/490] Fix streaks implementation
---
includes/class-date.php | 5 ++++-
includes/class-streaks.php | 19 ++++++++++---------
includes/goals/class-goal-recurring.php | 8 ++++++++
views/admin-page-streak.php | 3 ---
4 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/includes/class-date.php b/includes/class-date.php
index 1b8d10892..c420d7a59 100644
--- a/includes/class-date.php
+++ b/includes/class-date.php
@@ -73,10 +73,13 @@ public function get_periods( $start, $end, $frequency ) {
default: // Default to weekly.
$interval = new \DateInterval( 'P1W' );
+ // Make sure we start and end on a Monday.
+ $start->modify( 'last Monday' );
+ $end->modify( 'next Monday' );
break;
}
- $period = new \DatePeriod( $start, $interval, 100 );
+ $period = new \DatePeriod( $start, $interval, $end );
$dates_array = [];
foreach ( $period as $date ) {
$dates_array[] = $date->format( self::FORMAT );
diff --git a/includes/class-streaks.php b/includes/class-streaks.php
index e2c9a4185..7632c2064 100644
--- a/includes/class-streaks.php
+++ b/includes/class-streaks.php
@@ -83,21 +83,22 @@ public function get_streak( $goal_id, $target ) {
}
// Reverse the order of the occurences.
- $occurences = array_reverse( $goal->get_occurences() );
- $streak_nr = 0;
+ $occurences = $goal->get_occurences();
+ // Calculate the streak number.
+ $streak_nr = 0;
foreach ( $occurences as $occurence ) {
- // If the goal was not met, break the streak.
- if ( ! $occurence->evaluate() ) {
- break;
- }
-
- ++$streak_nr;
+ /**
+ * Evaluate the occurence.
+ * If the occurence is true, then increment the streak number.
+ * Otherwise, reset the streak number.
+ */
+ $streak_nr = $occurence->evaluate() ? $streak_nr + 1 : 0;
}
return [
'number' => $streak_nr,
- 'color' => 'hsl(' . (int) min( 100, $streak_nr * 100 / $target ) . ', 100%, 40%)',
+ 'color' => 'hsl(' . (int) min( 100, $streak_nr * 200 / $target ) . ', 100%, 40%)',
'title' => $goal->get_goal()->get_details()['title'],
'description' => $goal->get_goal()->get_details()['description'],
];
diff --git a/includes/goals/class-goal-recurring.php b/includes/goals/class-goal-recurring.php
index 6a155093e..8af6271ed 100644
--- a/includes/goals/class-goal-recurring.php
+++ b/includes/goals/class-goal-recurring.php
@@ -86,6 +86,14 @@ public function get_occurences() {
$date = new Date();
$ranges = $date->get_periods( $this->start, $this->end, $this->frequency );
+ // If the last range ends before today, add a new range.
+ if ( (int) gmdate( Date::FORMAT ) > (int) end( $ranges )['end'] ) {
+ $ranges[] = $date->get_range(
+ end( $ranges )['end'],
+ gmdate( Date::FORMAT, strtotime( '+1 day' ) ),
+ );
+ }
+
foreach ( $ranges as $range ) {
$goal = clone $this->goal;
$goal->set_start_date( $range['start'] );
diff --git a/views/admin-page-streak.php b/views/admin-page-streak.php
index a7447b07c..de4d445af 100644
--- a/views/admin-page-streak.php
+++ b/views/admin-page-streak.php
@@ -5,9 +5,6 @@
* @package ProgressPlanner
*/
-// TODO: DISABLE THIS FOR NOW, IT'S NOT WORKING.
-return;
-
$prpl_streaks = [
'weekly_post' => 10, // Number of posts per week, targetting for 10 weeks.
'weekly_words' => 10, // Number of words per week, targetting for 10 weeks.
From 506350cecf9cee9667b9ebabfdea40b03bc340a6 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 22 Feb 2024 10:30:29 +0200
Subject: [PATCH 067/490] Style debug data
---
views/admin-page-debug.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/views/admin-page-debug.php b/views/admin-page-debug.php
index 6f9b5f70f..d3a3ae7c3 100644
--- a/views/admin-page-debug.php
+++ b/views/admin-page-debug.php
@@ -13,7 +13,7 @@
-
+
get_value() );
From b5462b7330cfffb8368e0dbe6383f5d8e9cfc721 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 22 Feb 2024 10:33:36 +0200
Subject: [PATCH 068/490] partial revert fot streak color calculation
---
includes/class-streaks.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/class-streaks.php b/includes/class-streaks.php
index 7632c2064..0ff7a5430 100644
--- a/includes/class-streaks.php
+++ b/includes/class-streaks.php
@@ -98,7 +98,7 @@ public function get_streak( $goal_id, $target ) {
return [
'number' => $streak_nr,
- 'color' => 'hsl(' . (int) min( 100, $streak_nr * 200 / $target ) . ', 100%, 40%)',
+ 'color' => 'hsl(' . (int) min( 100, $streak_nr * 100 / $target ) . ', 100%, 40%)',
'title' => $goal->get_goal()->get_details()['title'],
'description' => $goal->get_goal()->get_details()['description'],
];
From bcbb646239126735413693f21cf86664a4072b53 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Thu, 22 Feb 2024 10:51:19 +0200
Subject: [PATCH 069/490] Change max hue to 125
---
includes/class-streaks.php | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/includes/class-streaks.php b/includes/class-streaks.php
index 0ff7a5430..689e75074 100644
--- a/includes/class-streaks.php
+++ b/includes/class-streaks.php
@@ -96,9 +96,12 @@ public function get_streak( $goal_id, $target ) {
$streak_nr = $occurence->evaluate() ? $streak_nr + 1 : 0;
}
+ // Calculate the hue for the color.
+ $hue = (int) min( 125, $streak_nr * 125 / $target );
+
return [
'number' => $streak_nr,
- 'color' => 'hsl(' . (int) min( 100, $streak_nr * 100 / $target ) . ', 100%, 40%)',
+ 'color' => "hsl($hue, 100%, 40%)",
'title' => $goal->get_goal()->get_details()['title'],
'description' => $goal->get_goal()->get_details()['description'],
];
From cd1e3fcb6651eec45a5c632d40a3ac496ef78914 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 27 Feb 2024 11:04:09 +0200
Subject: [PATCH 070/490] Refactor for custom tables & activities framework
---
includes/activities/class-activity-post.php | 271 +++++++++++++++
includes/activities/class-activity.php | 197 +++++++++++
includes/activities/class-query.php | 350 ++++++++++++++++++++
includes/admin/class-page.php | 6 +-
includes/charts/class-posts.php | 82 ++---
includes/class-date.php | 56 ++--
includes/class-stats.php | 16 +
includes/class-streaks.php | 72 ++--
includes/goals/class-goal-recurring.php | 5 +-
includes/scan/class-posts.php | 120 +++++++
includes/stats/class-stat-posts.php | 298 +----------------
progress-planner.php | 2 +
views/admin-page-debug.php | 2 +-
views/admin-page-posts-count-progress.php | 2 +-
views/admin-page-words-count-progress.php | 2 +-
15 files changed, 1059 insertions(+), 422 deletions(-)
create mode 100644 includes/activities/class-activity-post.php
create mode 100644 includes/activities/class-activity.php
create mode 100644 includes/activities/class-query.php
create mode 100644 includes/scan/class-posts.php
diff --git a/includes/activities/class-activity-post.php b/includes/activities/class-activity-post.php
new file mode 100644
index 000000000..0b6a4402a
--- /dev/null
+++ b/includes/activities/class-activity-post.php
@@ -0,0 +1,271 @@
+insert_post( $post_id, $post );
+ }
+
+ /**
+ * Insert a post.
+ *
+ * Runs on wp_insert_post hook.
+ *
+ * @param int $post_id The post ID.
+ * @param WP_Post $post The post object.
+ * @return void
+ */
+ public function insert_post( $post_id, $post ) {
+ // Bail if the post is not included in the post-types we're tracking.
+ $post_types = static::get_post_types_names();
+ if ( ! \in_array( $post->post_type, $post_types, true ) ) {
+ return;
+ }
+
+ // Bail if the post is not published.
+ if ( 'publish' !== $post->post_status ) {
+ return;
+ }
+
+ // Add a publish activity.
+ $activity = new Activity();
+ $activity->set_category( 'post' );
+ $activity->set_type( 'publish' );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_date ) );
+ $activity->set_data_id( $post_id );
+ $activity->set_data(
+ [
+ 'post_type' => $post->post_type,
+ 'word_count' => static::get_word_count( $post->post_content ),
+ ]
+ );
+ $activity->save();
+ }
+
+ /**
+ * Run actions when transitioning a post status.
+ *
+ * @param string $new_status The new status.
+ * @param string $old_status The old status.
+ * @param \WP_Post $post The post object.
+ */
+ public function transition_post_status( $new_status, $old_status, $post ) {
+
+ // If the post is published, check if it was previously published,
+ // and if so, delete the old activity and create a new one.
+ if ( 'publish' !== $old_status && 'publish' === $new_status ) {
+ $old_publish_activities = Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish',
+ 'data_id' => $post->ID,
+ ]
+ );
+ if ( ! empty( $old_publish_activities ) ) {
+ foreach ( $old_publish_activities as $activity ) {
+ $activity->delete();
+ }
+ }
+
+ // Add a publish activity.
+ $activity = new Activity();
+ $activity->set_category( 'post' );
+ $activity->set_type( 'publish' );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_date ) );
+ $activity->set_data_id( $post->ID );
+ $activity->set_data(
+ [
+ 'post_type' => $post->post_type,
+ 'word_count' => static::get_word_count( $post->post_content ),
+ ]
+ );
+ return $activity->save();
+ }
+
+ // Add an update activity.
+ $activity = new Activity();
+ $activity->set_category( 'post' );
+ $activity->set_type( 'update' );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_modified ) );
+ $activity->set_data_id( $post->ID );
+ $activity->set_data(
+ [
+ 'post_type' => $post->post_type,
+ 'word_count' => static::get_word_count( $post->post_content ),
+ ]
+ );
+ return $activity->save();
+ }
+
+ /**
+ * Update a post.
+ *
+ * Runs on pre_post_update hook.
+ *
+ * @param int $post_id The post ID.
+ * @param WP_Post $post The post object.
+ *
+ * @return bool
+ */
+ public function pre_post_update( $post_id, $post ) {
+ // Add an update activity.
+ $activity = new Activity();
+ $activity->set_category( 'post' );
+ $activity->set_type( 'update' );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_modified ) );
+ $activity->set_data_id( $post->ID );
+ $activity->set_data(
+ [
+ 'post_type' => $post->post_type,
+ 'word_count' => static::get_word_count( $post->post_content ),
+ ]
+ );
+ return $activity->save();
+ }
+
+ /**
+ * Trash a post.
+ *
+ * Runs on wp_trash_post hook.
+ *
+ * @param int $post_id The post ID.
+ * @return void
+ */
+ public function trash_post( $post_id ) {
+ $post = \get_post( $post_id );
+
+ // Bail if the post is not included in the post-types we're tracking.
+ $post_types = static::get_post_types_names();
+ if ( ! \in_array( $post->post_type, $post_types, true ) ) {
+ return;
+ }
+
+ // Add an update activity.
+ $activity = new Activity();
+ $activity->set_category( 'post' );
+ $activity->set_type( 'update' );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_modified ) );
+ $activity->set_data_id( $post->ID );
+ $activity->set_data(
+ [
+ 'post_type' => $post->post_type,
+ 'word_count' => static::get_word_count( $post->post_content ),
+ ]
+ );
+ return $activity->save();
+ }
+
+ /**
+ * Delete a post.
+ *
+ * Runs on delete_post hook.
+ *
+ * @param int $post_id The post ID.
+ * @return void
+ */
+ public function delete_post( $post_id ) {
+ $post = \get_post( $post_id );
+
+ // Bail if the post is not included in the post-types we're tracking.
+ $post_types = static::get_post_types_names();
+ if ( ! \in_array( $post->post_type, $post_types, true ) ) {
+ return;
+ }
+
+ // Update existing activities, and remove the words count.
+ $activities = Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'data_id' => $post->ID,
+ ]
+ );
+ if ( ! empty( $activities ) ) {
+ foreach ( $activities as $activity ) {
+ $activity->set_data( [ 'post_type' => $post->post_type ] );
+ $activity->save();
+ }
+ Query::get_instance()->delete_activities( $activities );
+ }
+
+ $activity = new Activity();
+ $activity->set_category( 'post' );
+ $activity->set_type( 'delete' );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_date ) );
+ $activity->set_data_id( $post->ID );
+ $activity->save();
+ }
+
+ /**
+ * Get an array of post-types names for the stats.
+ *
+ * @return string[]
+ */
+ public static function get_post_types_names() {
+ $post_types = \get_post_types( [ 'public' => true ] );
+ unset( $post_types['attachment'] );
+
+ return array_keys( $post_types );
+ }
+
+ /**
+ * Get words count from content.
+ *
+ * This method will render shortcodes, blocks,
+ * and strip HTML before counting the words.
+ *
+ * @param string $content The content.
+ *
+ * @return int
+ */
+ public static function get_word_count( $content ) {
+ // Parse blocks and shortcodes.
+ $content = \do_blocks( \do_shortcode( $content ) );
+
+ // Strip HTML.
+ $content = \wp_strip_all_tags( $content, true );
+
+ // Count words.
+ return \str_word_count( $content );
+ }
+}
diff --git a/includes/activities/class-activity.php b/includes/activities/class-activity.php
new file mode 100644
index 000000000..5f0fa623e
--- /dev/null
+++ b/includes/activities/class-activity.php
@@ -0,0 +1,197 @@
+id = $id;
+ }
+
+ /**
+ * Get the ID of the activity.
+ *
+ * @return int
+ */
+ public function get_id() {
+ return $this->id;
+ }
+
+ /**
+ * Set the date.
+ *
+ * @param \DateTime $date The date of the activity.
+ */
+ public function set_date( \DateTime $date ) {
+ $this->date = $date;
+ }
+
+ /**
+ * Get the date of the activity.
+ *
+ * @return \DateTime
+ */
+ public function get_date() {
+ return $this->date;
+ }
+
+ /**
+ * Set the category.
+ *
+ * @param string $category The category of the activity.
+ */
+ public function set_category( string $category ) {
+ $this->category = $category;
+ }
+
+ /**
+ * Get the category of the activity.
+ *
+ * @return string
+ */
+ public function get_category() {
+ return $this->category;
+ }
+
+ /**
+ * Set the type.
+ *
+ * @param string $type The type of the activity.
+ */
+ public function set_type( string $type ) {
+ $this->type = $type;
+ }
+
+ /**
+ * Get the type of the activity.
+ *
+ * @return string
+ */
+ public function get_type() {
+ return $this->type;
+ }
+
+ /**
+ * Set the data ID.
+ *
+ * @param int $data_id The data ID.
+ */
+ public function set_data_id( int $data_id ) {
+ $this->data_id = $data_id;
+ }
+
+ /**
+ * Get the data ID.
+ *
+ * @return int
+ */
+ public function get_data_id() {
+ return $this->data_id;
+ }
+
+ /**
+ * Set the data of the activity.
+ *
+ * @param array $data The data of the activity.
+ */
+ public function set_data( array $data ) {
+ $this->data = $data;
+ }
+
+ /**
+ * Get the data of the activity.
+ *
+ * @return array
+ */
+ public function get_data() {
+ return $this->data;
+ }
+
+ /**
+ * Save the activity.
+ *
+ * @return void
+ */
+ public function save() {
+ $existing = Query::get_instance()->query_activities(
+ [
+ 'category' => $this->category,
+ 'type' => $this->type,
+ 'data_id' => $this->data_id,
+ ]
+ );
+ if ( ! empty( $existing ) ) {
+ Query::get_instance()->update_activity( $existing[0]->id, $this );
+ } else {
+ Query::get_instance()->insert_activity( $this );
+ }
+ }
+
+ /**
+ * Delete the activity.
+ *
+ * @return void
+ */
+ public function delete() {
+ Query::get_instance()->delete_activity( $this );
+ }
+}
diff --git a/includes/activities/class-query.php b/includes/activities/class-query.php
new file mode 100644
index 000000000..39fad776c
--- /dev/null
+++ b/includes/activities/class-query.php
@@ -0,0 +1,350 @@
+create_tables();
+ }
+
+ /**
+ * Create database tables.
+ *
+ * @return void
+ */
+ public function create_tables() {
+ $this->create_activities_table();
+ }
+
+ /**
+ * Create the activities table.
+ *
+ * @return void
+ */
+ private function create_activities_table() {
+ global $wpdb;
+
+ $table_name = $wpdb->prefix . static::TABLE_NAME;
+ $charset_collate = $wpdb->get_charset_collate();
+
+ /**
+ * Create a table for activities.
+ *
+ * Columns:
+ * - date: The date of the activity.
+ * - category: The category of the activity.
+ * - type: The type of the activity.
+ * - data_id: The ID of the data of the activity.
+ * - data: The data of the activity.
+ */
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL
+ $wpdb->query(
+ "CREATE TABLE IF NOT EXISTS $table_name (
+ id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ date DATE NOT NULL,
+ category VARCHAR(255) NOT NULL,
+ type VARCHAR(255) NOT NULL,
+ data_id BIGINT(20) UNSIGNED NOT NULL,
+ data LONGTEXT NOT NULL,
+ PRIMARY KEY (id)
+ ) $charset_collate;"
+ );
+ }
+
+ /**
+ * Query the database for activities.
+ *
+ * @param array $args The arguments for the query.
+ *
+ * @return \ProgressPlanner\Activities\Activity[] The activities.
+ */
+ public function query_activities( $args ) {
+ global $wpdb;
+
+ $defaults = [
+ 'start_date' => null,
+ 'end_date' => null,
+ 'category' => '%',
+ 'type' => '%',
+ 'data_id' => '%',
+ ];
+
+ $args = \wp_parse_args( $args, $defaults );
+
+ // If start and end dates are defined, then get activities by date.
+ if ( $args['start_date'] && $args['end_date'] ) {
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ $results = $wpdb->get_results(
+ $wpdb->prepare(
+ 'SELECT * FROM %i WHERE date >= %s AND date <= %s AND category LIKE %s AND type LIKE %s AND data_id LIKE %s',
+ $wpdb->prefix . static::TABLE_NAME,
+ $args['start_date']->format( 'Y-m-d' ),
+ $args['end_date']->format( 'Y-m-d' ),
+ $args['category'],
+ $args['type'],
+ $args['data_id']
+ )
+ );
+ } else {
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ $results = $wpdb->get_results(
+ $wpdb->prepare(
+ 'SELECT * FROM %i WHERE category LIKE %s AND type LIKE %s AND data_id LIKE %s',
+ $wpdb->prefix . static::TABLE_NAME,
+ $args['category'],
+ $args['type'],
+ $args['data_id']
+ )
+ );
+ }
+
+ $activities = $this->get_activities_from_results( $results );
+
+ if ( isset( $args['data'] ) && ! empty( $args['data'] ) ) {
+ foreach ( $activities as $key => $activity ) {
+ $data = $activity->get_data();
+ foreach ( $args['data'] as $data_key => $data_value ) {
+ if ( ! isset( $data[ $data_key ] ) || $data[ $data_key ] !== $data_value ) {
+ unset( $activities[ $key ] );
+ }
+ }
+ }
+ $activities = \array_values( $activities );
+ }
+ return $activities;
+ }
+
+ /**
+ * Insert multiple activities into the database.
+ *
+ * @param \ProgressPlanner\Activities\Activity[] $activities The activities to insert.
+ *
+ * @return int[]|false The IDs of the inserted activities, or false on failure.
+ */
+ public function insert_activities( $activities ) {
+ $ids = [];
+ foreach ( $activities as $activity ) {
+ $id = $this->insert_activity( $activity );
+ if ( false === $id ) {
+ continue;
+ }
+ $ids[] = $id;
+ }
+ if ( empty( $ids ) ) {
+ return false;
+ }
+ return $ids;
+ }
+
+
+ /**
+ * Insert an activity into the database.
+ *
+ * @param \ProgressPlanner\Activities\Activity $activity The activity to insert.
+ *
+ * @return int|false The ID of the inserted activity, or false on failure.
+ */
+ public function insert_activity( $activity ) {
+ global $wpdb;
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
+ $result = $wpdb->insert(
+ $wpdb->prefix . static::TABLE_NAME,
+ [
+ 'date' => $activity->get_date()->format( 'Y-m-d H:i:s' ),
+ 'category' => $activity->get_category(),
+ 'type' => $activity->get_type(),
+ 'data_id' => $activity->get_data_id(),
+ 'data' => \wp_json_encode( $activity->get_data() ),
+ ],
+ [
+ '%s',
+ '%s',
+ '%s',
+ '%s',
+ ]
+ );
+
+ if ( false === $result ) {
+ return false;
+ }
+
+ return (int) $wpdb->insert_id;
+ }
+
+ /**
+ * Get activities objects from results.
+ *
+ * @param array $results The results.
+ *
+ * @return \ProgressPlanner\Activities\Activity[] The activities.
+ */
+ private function get_activities_from_results( $results ) {
+ $activities = [];
+ foreach ( $results as $result ) {
+ $activity = new Activity();
+ $activity->set_date( new \DateTime( $result->date ) );
+ $activity->set_category( $result->category );
+ $activity->set_type( $result->type );
+ $activity->set_data_id( (int) $result->data_id );
+ $activity->set_data( \json_decode( $result->data, true ) );
+ $activity->set_id( (int) $result->id );
+ $activities[] = $activity;
+ }
+
+ return $activities;
+ }
+
+ /**
+ * Update an activity in the database.
+ *
+ * @param int $id The ID of the activity to update.
+ * @param \ProgressPlanner\Activities\Activity $activity The activity to update.
+ *
+ * @return void
+ */
+ public function update_activity( $id, $activity ) {
+ global $wpdb;
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
+ $wpdb->update(
+ $wpdb->prefix . static::TABLE_NAME,
+ [
+ 'date' => $activity->get_date()->format( 'Y-m-d H:i:s' ),
+ 'category' => $activity->get_category(),
+ 'type' => $activity->get_type(),
+ 'data_id' => $activity->get_data_id(),
+ 'data' => \wp_json_encode( $activity->get_data() ),
+ ],
+ [ 'id' => $id ],
+ [
+ '%s',
+ '%s',
+ '%s',
+ '%s',
+ '%s',
+ ],
+ [ '%d' ]
+ );
+ }
+
+ /**
+ * Delete activities from the database.
+ *
+ * @param \ProgressPlanner\Activities\Activity[] $activities The activity to delete.
+ *
+ * @return void
+ */
+ public function delete_activities( $activities ) {
+ foreach ( $activities as $activity ) {
+ $this->delete_activity( $activity );
+ }
+ }
+
+ /**
+ * Delete an activity from the database.
+ *
+ * @param \ProgressPlanner\Activities\Activity $activity The activity to delete.
+ *
+ * @return void
+ */
+ public function delete_activity( $activity ) {
+ global $wpdb;
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
+ $wpdb->delete(
+ $wpdb->prefix . static::TABLE_NAME,
+ [ 'id' => $activity->get_id() ],
+ [ '%d' ]
+ );
+ }
+
+ /**
+ * Detele all activities in a category.
+ *
+ * @param string $category The category of the activities to delete.
+ *
+ * @return void
+ */
+ public function delete_category_activities( $category ) {
+ global $wpdb;
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
+ $wpdb->delete(
+ $wpdb->prefix . static::TABLE_NAME,
+ [ 'category' => $category ],
+ [ '%s' ]
+ );
+ }
+
+ /**
+ * Get oldest activity.
+ *
+ * @return \ProgressPlanner\Activities\Activity
+ */
+ public function get_oldest_activity() {
+ global $wpdb;
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ $result = $wpdb->get_row(
+ $wpdb->prepare(
+ 'SELECT * FROM %i ORDER BY date ASC LIMIT 1',
+ $wpdb->prefix . static::TABLE_NAME
+ )
+ );
+
+ $activity = new Activity();
+ $activity->set_date( new \DateTime( $result->date ) );
+ $activity->set_category( $result->category );
+ $activity->set_type( $result->type );
+ $activity->set_data_id( (int) $result->data_id );
+ $activity->set_data( \json_decode( $result->data, true ) );
+ $activity->set_id( (int) $result->id );
+
+ return $activity;
+ }
+}
+// phpcs:enable
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index 2688c007e..1d630c3ed 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -109,8 +109,7 @@ public function ajax_scan() {
}
// Scan the posts.
- $posts_stats = new \ProgressPlanner\Stats\Stat_Posts();
- $updated_stats = $posts_stats->update_stats();
+ $updated_stats = \ProgressPlanner\Scan\Posts::update_stats();
\wp_send_json_success(
[
@@ -136,8 +135,7 @@ public function ajax_reset_stats() {
}
// Reset the stats.
- $posts_stats = new \ProgressPlanner\Stats\Stat_Posts();
- $posts_stats->reset_stats();
+ \ProgressPlanner\Scan\Posts::reset_stats();
\wp_send_json_success(
[
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index ba6eae17e..05d9d641a 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -8,8 +8,7 @@
namespace ProgressPlanner\Charts;
use ProgressPlanner\Chart;
-use ProgressPlanner\Date;
-use ProgressPlanner\Stats\Stat_Posts;
+use ProgressPlanner\Activities\Query;
/**
* Posts chart.
@@ -19,7 +18,7 @@ class Posts extends Chart {
/**
* Build a chart for the stats.
*
- * @param array $post_types The post types.
+ * @param string $post_type The post type.
* @param string $context The context for the chart. Can be 'count' or 'words'.
* @param string $interval The interval for the chart. Can be 'days', 'weeks', 'months', 'years'.
* @param int $range The number of intervals to show.
@@ -27,7 +26,7 @@ class Posts extends Chart {
*
* @return void
*/
- public function render( $post_types = [], $context = 'count', $interval = 'weeks', $range = 10, $offset = 0 ) {
+ public function render( $post_type = 'post', $context = 'count', $interval = 'weeks', $range = 10, $offset = 0 ) {
$range_array_end = \range( $offset, $range - 1 );
$range_array_start = \range( $offset + 1, $range );
\krsort( $range_array_start );
@@ -40,59 +39,60 @@ public function render( $post_types = [], $context = 'count', $interval = 'weeks
'datasets' => [],
];
$datasets = [];
- $post_type_count_totals = [];
- foreach ( $post_types as $post_type ) {
- $post_type_count_totals[ $post_type ] = 0;
- $datasets[ $post_type ] = [
- 'label' => \get_post_type_object( $post_type )->label,
- 'data' => [],
- 'tension' => 0.2,
- ];
- }
-
- $stat_posts = new Stat_Posts();
+ $post_type_count_totals = 0;
+ $dataset = [
+ 'label' => \get_post_type_object( $post_type )->label,
+ 'data' => [],
+ 'tension' => 0.2,
+ ];
// Calculate zero stats to be used as the baseline.
- $zero_stats = $stat_posts->get_stats(
- 19700101,
- (int) gmdate( Date::FORMAT, strtotime( "-$range $interval" ) ),
- $post_types
+ $zero_activities = Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish',
+ 'start_date' => \DateTime::createFromFormat( 'Y-m-d', '1970-01-01' ),
+ 'end_date' => new \DateTime( "-$range $interval" ),
+ 'data' => [
+ 'post_type' => $post_type,
+ ],
+ ]
);
- foreach ( $zero_stats as $zero_posts ) {
- foreach ( $zero_posts as $zero_post ) {
- $post_type_count_totals[ $zero_post['post_type'] ] += 'words' === $context
- ? $zero_post['words']
- : 1;
- }
+ foreach ( $zero_activities as $zero_activity ) {
+ $activity_data = $zero_activity->get_data();
+ $post_type_count_totals += 'words' === $context
+ ? $activity_data['word_count']
+ : 1;
}
foreach ( $range_array as $start => $end ) {
- $stats = $stat_posts->get_stats(
- (int) gmdate( Date::FORMAT, strtotime( "-$start $interval" ) ),
- (int) gmdate( Date::FORMAT, strtotime( "-$end $interval" ) ),
- $post_types
+ $activities = Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish',
+ 'start_date' => new \DateTime( "-$start $interval" ),
+ 'end_date' => new \DateTime( "-$end $interval" ),
+ 'data' => [
+ 'post_type' => $post_type,
+ ],
+ ]
);
// TODO: Format the date depending on the user's locale.
$data['labels'][] = gmdate( 'Y-m-d', strtotime( "-$start $interval" ) );
- foreach ( $post_types as $post_type ) {
- foreach ( $stats as $posts ) {
- foreach ( $posts as $post_details ) {
- if ( $post_details['post_type'] === $post_type ) {
- $post_type_count_totals[ $post_type ] += 'words' === $context
- ? $post_details['words']
- : 1;
- }
- }
- }
- $datasets[ $post_type ]['data'][] = $post_type_count_totals[ $post_type ];
+ foreach ( $activities as $activity ) {
+ $activity_data = $activity->get_data();
+ $post_type_count_totals += 'words' === $context
+ ? $activity_data['word_count']
+ : 1;
}
+ $datasets[ $post_type ]['data'][] = $post_type_count_totals;
}
$data['datasets'] = \array_values( $datasets );
$this->render_chart(
- md5( wp_json_encode( [ $post_types, $context, $interval, $range, $offset ] ) ),
+ md5( wp_json_encode( [ [ $post_type ], $context, $interval, $range, $offset ] ) ),
'line',
$data,
[]
diff --git a/includes/class-date.php b/includes/class-date.php
index c420d7a59..e8b3150a7 100644
--- a/includes/class-date.php
+++ b/includes/class-date.php
@@ -12,13 +12,6 @@
*/
class Date {
- /**
- * Date format.
- *
- * @var string
- */
- const FORMAT = 'Ymd';
-
/**
* Get a range of dates.
*
@@ -32,19 +25,10 @@ class Date {
* ].
*/
public function get_range( $start, $end ) {
- $start = \DateTime::createFromFormat( self::FORMAT, $start );
- $end = \DateTime::createFromFormat( self::FORMAT, $end );
-
- $dates = [];
- $range = new \DatePeriod( $start, new \DateInterval( 'P1D' ), $end );
- foreach ( $range as $date ) {
- $dates[] = $date->format( self::FORMAT );
- }
-
return [
- 'start' => $start->format( self::FORMAT ),
- 'end' => $end->format( self::FORMAT ),
- 'dates' => $dates,
+ 'start' => $start,
+ 'end' => $end,
+ 'dates' => iterator_to_array( new \DatePeriod( $start, new \DateInterval( 'P1D' ), $end ), false ),
];
}
@@ -58,9 +42,7 @@ public function get_range( $start, $end ) {
* @return array
*/
public function get_periods( $start, $end, $frequency ) {
- $start = \DateTime::createFromFormat( self::FORMAT, $start );
- $end = \DateTime::createFromFormat( self::FORMAT, $end );
- $end = $end->modify( '+1 day' );
+ $end = $end->modify( '+1 day' );
switch ( $frequency ) {
case 'daily':
@@ -79,26 +61,26 @@ public function get_periods( $start, $end, $frequency ) {
break;
}
- $period = new \DatePeriod( $start, $interval, $end );
- $dates_array = [];
- foreach ( $period as $date ) {
- $dates_array[] = $date->format( self::FORMAT );
- }
+ $period = iterator_to_array( new \DatePeriod( $start, $interval, $end ), false );
$date_ranges = [];
- foreach ( $dates_array as $key => $date ) {
- if ( isset( $dates_array[ $key + 1 ] ) ) {
- $datetime = \DateTime::createFromFormat( self::FORMAT, $dates_array[ $key + 1 ] );
- if ( ! $datetime ) {
- continue;
- }
- $date_ranges[] = $this->get_range(
- $date,
- $datetime->format( self::FORMAT )
- );
+ foreach ( $period as $key => $date ) {
+ if ( isset( $period[ $key + 1 ] ) ) {
+ $date_ranges[] = $this->get_range( $date, $period[ $key + 1 ] );
}
}
return $date_ranges;
}
+
+ /**
+ * Get DateTime object from a mysql date.
+ *
+ * @param string $date The date.
+ *
+ * @return \DateTime
+ */
+ public static function get_datetime_from_mysql_date( $date ) {
+ return \DateTime::createFromFormat( 'U', (int) mysql2date( 'U', $date ) );
+ }
}
diff --git a/includes/class-stats.php b/includes/class-stats.php
index d08c22842..2b23c8948 100644
--- a/includes/class-stats.php
+++ b/includes/class-stats.php
@@ -70,4 +70,20 @@ public function get_stat( $id ) {
private function register_stats() {
$this->add_stat( 'posts', new Stat_Posts() );
}
+
+ /**
+ * Get number of activities for date range.
+ *
+ * @param \DateTime $start_date The start date.
+ * @param \DateTime $end_date The end date.
+ * @param array $args The query arguments.
+ *
+ * @return int
+ */
+ public function get_number_of_activities( $start_date, $end_date, $args = [] ) {
+ $args['start_date'] = $start_date;
+ $args['end_date'] = $end_date;
+ $activities = Query::get_instance()->query_activities( $args );
+ return count( $activities );
+ }
}
diff --git a/includes/class-streaks.php b/includes/class-streaks.php
index 689e75074..e38953426 100644
--- a/includes/class-streaks.php
+++ b/includes/class-streaks.php
@@ -9,7 +9,7 @@
use ProgressPlanner\Goals\Goal_Posts;
use ProgressPlanner\Goals\Goal_Recurring;
-use ProgressPlanner\Stats\Stat_Posts;
+use ProgressPlanner\Activities\Query;
/**
* Streaks class.
@@ -113,15 +113,6 @@ public function get_streak( $goal_id, $target ) {
* @return void
*/
private function get_weekly_post_goal() {
- $stats = new Stat_Posts();
-
- $stats_value = $stats->get_value();
-
- // Bail early if there are no stats.
- if ( empty( $stats_value ) ) {
- return;
- }
-
return new Goal_Recurring(
new Goal_Posts(
[
@@ -130,20 +121,26 @@ private function get_weekly_post_goal() {
'description' => \esc_html__( 'Streak: The number of weeks this goal has been accomplished consistently.', 'progress-planner' ),
'status' => 'active',
'priority' => 'low',
- 'evaluate' => function ( $goal_object ) use ( $stats ) {
+ 'evaluate' => function ( $goal_object ) {
return (bool) count(
- $stats->get_stats(
- $goal_object->get_details()['start_date'],
- $goal_object->get_details()['end_date'],
- []
+ Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish',
+ 'start_date' => $goal_object->get_details()['start_date'],
+ 'end_date' => $goal_object->get_details()['end_date'],
+ 'data' => [
+ 'post_type' => 'post',
+ ],
+ ]
)
);
},
]
),
'weekly',
- array_keys( $stats_value )[0], // Beginning of the stats.
- gmdate( Date::FORMAT ) // Today.
+ Query::get_instance()->get_oldest_activity()->get_date(), // Beginning of the stats.
+ new \DateTime( 'now' ) // Today.
);
}
@@ -153,42 +150,37 @@ private function get_weekly_post_goal() {
* @return void
*/
private function get_weekly_words_goal() {
- $stats = new Stat_Posts();
-
- $stats_value = $stats->get_value();
-
- // Bail early if there are no stats.
- if ( empty( $stats_value ) ) {
- return;
- }
-
return new Goal_Recurring(
new Goal_Posts(
[
- 'id' => 'weekly_words',
- 'title' => \esc_html__( 'Write 500 words/week', 'progress-planner' ),
+ 'id' => 'weekly_post',
+ 'title' => \esc_html__( 'Write a weekly blog post', 'progress-planner' ),
'description' => \esc_html__( 'Streak: The number of weeks this goal has been accomplished consistently.', 'progress-planner' ),
'status' => 'active',
'priority' => 'low',
- 'evaluate' => function ( $goal_object ) use ( $stats ) {
- $words = 0;
- $posts = $stats->get_stats(
- $goal_object->get_details()['start_date'],
- $goal_object->get_details()['end_date'],
- [ 'post' ]
+ 'evaluate' => function ( $goal_object ) {
+ $activities = Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish',
+ 'start_date' => $goal_object->get_details()['start_date'],
+ 'end_date' => $goal_object->get_details()['end_date'],
+ 'data' => [
+ 'post_type' => 'post',
+ ],
+ ]
);
- foreach ( $posts as $post_dates ) {
- foreach ( $post_dates as $post_details ) {
- $words += $post_details['words'];
- }
+ $words = 0;
+ foreach ( $activities as $activity ) {
+ $words += $activity->get_data()['word_count'];
}
return $words >= 500;
},
]
),
'weekly',
- array_keys( $stats_value )[0], // Beginning of the stats.
- gmdate( Date::FORMAT ) // Today.
+ Query::get_instance()->get_oldest_activity()->get_date(), // Beginning of the stats.
+ new \DateTime( 'now' ) // Today.
);
}
}
diff --git a/includes/goals/class-goal-recurring.php b/includes/goals/class-goal-recurring.php
index 8af6271ed..584e8017b 100644
--- a/includes/goals/class-goal-recurring.php
+++ b/includes/goals/class-goal-recurring.php
@@ -82,15 +82,14 @@ public function get_occurences() {
if ( ! empty( $this->occurences ) ) {
return $this->occurences;
}
-
$date = new Date();
$ranges = $date->get_periods( $this->start, $this->end, $this->frequency );
// If the last range ends before today, add a new range.
- if ( (int) gmdate( Date::FORMAT ) > (int) end( $ranges )['end'] ) {
+ if ( (int) gmdate( 'Ymd' ) > (int) end( $ranges )['end']->format( 'Ymd' ) ) {
$ranges[] = $date->get_range(
end( $ranges )['end'],
- gmdate( Date::FORMAT, strtotime( '+1 day' ) ),
+ new \DateTime( 'tomorrow' )
);
}
diff --git a/includes/scan/class-posts.php b/includes/scan/class-posts.php
new file mode 100644
index 000000000..c280f05d2
--- /dev/null
+++ b/includes/scan/class-posts.php
@@ -0,0 +1,120 @@
+publish;
+ }
+ // Calculate the total pages to scan.
+ $total_pages = \ceil( $total_posts_count / static::SCAN_POSTS_PER_PAGE );
+ // Get the last scanned page.
+ $last_page = (int) \get_option( static::LAST_SCANNED_PAGE_OPTION, 0 );
+ // The current page to scan.
+ $current_page = $last_page + 1;
+
+ // Get posts.
+ $posts = \get_posts(
+ [
+ 'posts_per_page' => static::SCAN_POSTS_PER_PAGE,
+ 'paged' => $current_page,
+ 'post_type' => Activity_Post::get_post_types_names(),
+ 'post_status' => 'any',
+ ]
+ );
+
+ if ( ! $posts ) {
+ \delete_option( static::LAST_SCANNED_PAGE_OPTION );
+ return [
+ 'lastScannedPage' => $last_page,
+ 'lastPage' => $total_pages,
+ 'progress' => 100,
+ ];
+ }
+
+ // Loop through the posts and update the stats.
+ foreach ( $posts as $post ) {
+ $activity = new Activity();
+ $activity->set_category( 'post' );
+ $activity->set_data_id( $post->ID );
+ $activity->set_data(
+ [
+ 'post_type' => $post->post_type,
+ 'word_count' => Activity_Post::get_word_count( $post->post_content ),
+ ]
+ );
+
+ switch ( $post->post_status ) {
+ case 'publish':
+ $activity->set_type( 'publish' );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_date ) );
+ break;
+
+ default:
+ $activity->set_type( 'update' );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_modified ) );
+ }
+
+ $activity->save();
+ }
+
+ \update_option( static::LAST_SCANNED_PAGE_OPTION, $current_page );
+
+ return [
+ 'lastScannedPage' => $last_page,
+ 'lastPage' => $total_pages,
+ 'progress' => round( ( $current_page / max( 1, $total_pages ) ) * 100 ),
+ ];
+ }
+
+ /**
+ * Reset the stats in our database.
+ *
+ * @return void
+ */
+ public static function reset_stats() {
+ Query::get_instance()->delete_category_activities( 'post' );
+ \delete_option( static::LAST_SCANNED_PAGE_OPTION );
+ }
+}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
index 92006931a..e96a5e3b6 100644
--- a/includes/stats/class-stat-posts.php
+++ b/includes/stats/class-stat-posts.php
@@ -7,314 +7,24 @@
namespace ProgressPlanner\Stats;
-use ProgressPlanner\Date;
+use ProgressPlanner\Activities\Query;
/**
* Stats about posts.
*/
class Stat_Posts {
- /**
- * The setting name.
- *
- * @var string
- */
- const SETTING_NAME = 'progress_planner_stats_posts';
-
- /**
- * The number of posts to scan at a time.
- *
- * @var int
- */
- const SCAN_POSTS_PER_PAGE = 20;
-
- /**
- * The option used to store the last scanned page.
- *
- * @var string
- */
- const LAST_SCANNED_PAGE_OPTION = 'progress_planner_stats_last_scanned_page';
-
- /**
- * The stats. Used for caching purposes.
- *
- * @var array
- */
- private static $stats;
-
- /**
- * Constructor.
- */
- public function __construct() {
- $this->register_hooks();
- }
-
- /**
- * Register the hooks.
- *
- * @return void
- */
- private function register_hooks() {
- \add_action( 'save_post', [ $this, 'save_post' ], 10, 2 );
- \add_action( 'wp_insert_post', [ $this, 'save_post' ], 10, 2 );
- \add_action( 'delete_post', [ $this, 'delete_post' ] );
- \add_action( 'transition_post_status', [ $this, 'transition_post_status' ], 10, 3 );
- }
-
- /**
- * Run actions when saving a post.
- *
- * @param int $post_id The post ID.
- * @param \WP_Post $post The post object.
- */
- public function save_post( $post_id, $post ) {
- // Bail if the post is not included in the post-types we're tracking.
- $post_types = $this->get_post_types_names();
- if ( ! \in_array( $post->post_type, $post_types, true ) ) {
- return;
- }
-
- // Bail if the post is not published.
- if ( 'publish' !== $post->post_status ) {
- return;
- }
-
- $this->save_post_stats( $post );
- }
-
- /**
- * Delete a post from stats.
- *
- * @param int $post_id The post ID.
- */
- public function delete_post( $post_id ) {
- $value = \get_option( static::SETTING_NAME, [] );
- $updated = false;
- // Remove the post from stats if it's already stored in another date.
- foreach ( $value as $date_key => $date_value ) {
- if ( isset( $date_value[ $post_id ] ) ) {
- unset( $value[ $date_key ][ $post_id ] );
- $updated = true;
- }
- }
-
- if ( $updated ) {
- \update_option( static::SETTING_NAME, $value );
- }
- }
-
- /**
- * Run actions when transitioning a post status.
- *
- * @param string $new_status The new status.
- * @param string $old_status The old status.
- * @param \WP_Post $post The post object.
- */
- public function transition_post_status( $new_status, $old_status, $post ) {
- // Delete the post from stats.
- if ( 'publish' === $old_status && 'publish' !== $new_status ) {
- $this->delete_post( $post->ID );
- }
- // Add the post to stats.
- if ( 'publish' !== $old_status && 'publish' === $new_status ) {
- $this->save_post_stats( $post );
- }
- }
-
/**
* Get the value.
*
* @return mixed
*/
public function get_value() {
- if ( ! self::$stats ) {
- $value = \get_option( static::SETTING_NAME, [] );
- ksort( $value );
- self::$stats = $value;
- }
- return self::$stats;
- }
-
- /**
- * Save a post to the stats.
- *
- * @param \WP_Post $post The post.
- *
- * @return bool
- */
- protected function save_post_stats( $post ) {
- $value = \get_option( static::SETTING_NAME, [] );
- $date = (int) mysql2date( Date::FORMAT, $post->post_date );
-
- // Remove the post from stats if it's already stored in another date.
- foreach ( $value as $date_key => $date_value ) {
- if ( isset( $date_value[ $post->ID ] ) ) {
- unset( $value[ $date_key ][ $post->ID ] );
- }
- }
-
- if ( ! isset( $value[ $date ] ) ) {
- $value[ $date ] = [];
- }
- $value[ $date ][ $post->ID ] = [
- 'post_type' => $post->post_type,
- 'words' => $this->get_word_count( $post->post_content ),
- ];
- return \update_option( static::SETTING_NAME, $value );
- }
-
- /**
- * Get stats for date range.
- *
- * @param int|string $start_date The start date.
- * @param int|string $end_date The end date.
- * @param string[] $post_types The post types.
- *
- * @return array
- */
- public function get_stats( $start_date, $end_date, $post_types = [] ) {
- $stats = $this->get_value();
-
- // Get the stats for the date range and post types.
- foreach ( $stats as $date => $date_stats ) {
- // Remove stats outside the date range.
- if ( $date <= $start_date || $date > $end_date ) {
- unset( $stats[ $date ] );
- continue;
- }
-
- // If we have not defined post types, then we don't need to filter by post type.
- if ( empty( $post_types ) ) {
- continue;
- }
-
- // Remove stats not in the post types.
- foreach ( $stats[ $date ] as $post_id => $details ) {
- if ( ! \in_array( $details['post_type'], $post_types, true ) ) {
- unset( $stats[ $date ][ $post_id ] );
- }
- }
-
- // Remove empty dates.
- if ( ! $stats[ $date ] || empty( $stats[ $date ] ) ) {
- unset( $stats[ $date ] );
- continue;
- }
- }
-
- return $stats;
- }
-
- /**
- * Get an array of post-types names for the stats.
- *
- * @return string[]
- */
- public function get_post_types_names() {
- $post_types = \get_post_types( [ 'public' => true ] );
- unset( $post_types['attachment'] );
-
- return array_keys( $post_types );
- }
-
- /**
- * Get words count from content.
- *
- * This method will render shortcodes, blocks,
- * and strip HTML before counting the words.
- *
- * @param string $content The content.
- *
- * @return int
- */
- protected function get_word_count( $content ) {
- // Parse blocks and shortcodes.
- $content = \do_blocks( \do_shortcode( $content ) );
-
- // Strip HTML.
- $content = \wp_strip_all_tags( $content, true );
-
- // Count words.
- return \str_word_count( $content );
- }
-
- /**
- * Update stats for posts.
- * - Gets the next page to scan.
- * - Gets the posts for the page.
- * - Updates the stats for the posts.
- * - Updates the last scanned page option.
- *
- * @return array
- */
- public function update_stats() {
-
- // Get the total number of posts.
- $total_posts_count = 0;
- foreach ( $this->get_post_types_names() as $post_type ) {
- $total_posts_count += \wp_count_posts( $post_type )->publish;
- }
- // Calculate the total pages to scan.
- $total_pages = \ceil( $total_posts_count / static::SCAN_POSTS_PER_PAGE );
- // Get the last scanned page.
- $last_page = (int) \get_option( static::LAST_SCANNED_PAGE_OPTION, 0 );
- // The current page to scan.
- $current_page = $last_page + 1;
-
- // Get posts.
- $posts = \get_posts(
+ return Query::get_instance()->query_activities(
[
- 'posts_per_page' => static::SCAN_POSTS_PER_PAGE,
- 'paged' => $current_page,
- 'post_type' => $this->get_post_types_names(),
- 'post_status' => 'publish',
+ 'category' => 'post',
+ 'type' => 'publish',
]
);
-
- if ( $posts ) {
- // Get the value from the option.
- $value = \get_option( static::SETTING_NAME, [] );
-
- // Loop through the posts and update the $value stats.
- foreach ( $posts as $post ) {
- // Get the date from the post, and convert it to the format we use.
- $date = (int) mysql2date( Date::FORMAT, $post->post_date );
-
- // If the date is not set in the option, set it to an empty array.
- if ( ! isset( $value[ $date ] ) ) {
- $value[ $date ] = [];
- }
-
- // Add the post to the date.
- $value[ $date ][ $post->ID ] = [
- 'post_type' => $post->post_type,
- 'words' => $this->get_word_count( $post->post_content ),
- ];
- }
-
- // Save the option.
- \update_option( static::SETTING_NAME, $value );
- }
-
- if ( $current_page > $total_pages ) {
- \delete_option( static::LAST_SCANNED_PAGE_OPTION );
- }
- \update_option( static::LAST_SCANNED_PAGE_OPTION, $current_page );
-
- return [
- 'lastScannedPage' => $last_page,
- 'lastPage' => $total_pages,
- 'progress' => round( ( $current_page / max( 1, $total_pages ) ) * 100 ),
- ];
- }
-
- /**
- * Reset the stats in our database.
- *
- * @return void
- */
- public function reset_stats() {
- \delete_option( static::SETTING_NAME );
- \delete_option( static::LAST_SCANNED_PAGE_OPTION );
}
}
diff --git a/progress-planner.php b/progress-planner.php
index f4e629505..dc4a2e116 100644
--- a/progress-planner.php
+++ b/progress-planner.php
@@ -11,3 +11,5 @@
require_once PROGRESS_PLANNER_DIR . '/includes/autoload.php';
\ProgressPlanner\Base::get_instance();
+
+$prpl_storage = \ProgressPlanner\Activities\Query::get_instance();
diff --git a/views/admin-page-debug.php b/views/admin-page-debug.php
index d3a3ae7c3..391c9681d 100644
--- a/views/admin-page-debug.php
+++ b/views/admin-page-debug.php
@@ -16,7 +16,7 @@
get_value() );
+ print_r( \ProgressPlanner\Activities\Query::get_instance()->query_activities( [] ) );
?>
diff --git a/views/admin-page-posts-count-progress.php b/views/admin-page-posts-count-progress.php
index 9ae5d804f..e0c6448dd 100644
--- a/views/admin-page-posts-count-progress.php
+++ b/views/admin-page-posts-count-progress.php
@@ -11,7 +11,7 @@
echo '';
( new \ProgressPlanner\Charts\Posts() )->render(
- \ProgressPlanner\Admin\Page::get_params()['stats']->get_post_types_names(),
+ 'post',
'count',
\ProgressPlanner\Admin\Page::get_params()['filter_interval'],
\ProgressPlanner\Admin\Page::get_params()['filter_number'],
diff --git a/views/admin-page-words-count-progress.php b/views/admin-page-words-count-progress.php
index 2fad846f3..82ed0fa5b 100644
--- a/views/admin-page-words-count-progress.php
+++ b/views/admin-page-words-count-progress.php
@@ -11,7 +11,7 @@
echo '';
( new \ProgressPlanner\Charts\Posts() )->render(
- \ProgressPlanner\Admin\Page::get_params()['stats']->get_post_types_names(),
+ 'post',
'words',
\ProgressPlanner\Admin\Page::get_params()['filter_interval'],
\ProgressPlanner\Admin\Page::get_params()['filter_number'],
From 20ff45e1f536c0b7503950a3a7655c84aa154658 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 27 Feb 2024 11:14:03 +0200
Subject: [PATCH 071/490] Fix saving a post
---
includes/activities/class-activity-post.php | 9 +++++----
includes/class-base.php | 4 ++++
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/includes/activities/class-activity-post.php b/includes/activities/class-activity-post.php
index 0b6a4402a..ff9f3ae78 100644
--- a/includes/activities/class-activity-post.php
+++ b/includes/activities/class-activity-post.php
@@ -149,16 +149,17 @@ public function transition_post_status( $new_status, $old_status, $post ) {
* @return bool
*/
public function pre_post_update( $post_id, $post ) {
+ $post_array = (array) $post;
// Add an update activity.
$activity = new Activity();
$activity->set_category( 'post' );
$activity->set_type( 'update' );
- $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_modified ) );
- $activity->set_data_id( $post->ID );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post_array['post_modified'] ) );
+ $activity->set_data_id( $post_id );
$activity->set_data(
[
- 'post_type' => $post->post_type,
- 'word_count' => static::get_word_count( $post->post_content ),
+ 'post_type' => $post_array['post_type'],
+ 'word_count' => static::get_word_count( $post_array['post_content'] ),
]
);
return $activity->save();
diff --git a/includes/class-base.php b/includes/class-base.php
index 6bed57846..5b04e6ebf 100644
--- a/includes/class-base.php
+++ b/includes/class-base.php
@@ -7,6 +7,8 @@
namespace ProgressPlanner;
+use ProgressPlanner\Activities\Activity_Post;
+
/**
* Main plugin class.
*/
@@ -45,6 +47,8 @@ public static function get_instance() {
private function __construct() {
$this->admin = new Admin();
+ ( new Activity_Post() )->register_hooks();
+
new Stats();
}
From e072658cd3e278aa4ff2a157038771d594db9b2e Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 27 Feb 2024 11:21:52 +0200
Subject: [PATCH 072/490] Allow defining the date format for graphs
---
includes/charts/class-posts.php | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index 05d9d641a..f3ffae301 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -23,10 +23,11 @@ class Posts extends Chart {
* @param string $interval The interval for the chart. Can be 'days', 'weeks', 'months', 'years'.
* @param int $range The number of intervals to show.
* @param int $offset The offset for the intervals.
+ * @param string $date_format The date format.
*
* @return void
*/
- public function render( $post_type = 'post', $context = 'count', $interval = 'weeks', $range = 10, $offset = 0 ) {
+ public function render( $post_type = 'post', $context = 'count', $interval = 'weeks', $range = 10, $offset = 0, $date_format = 'Y-m-d' ) {
$range_array_end = \range( $offset, $range - 1 );
$range_array_start = \range( $offset + 1, $range );
\krsort( $range_array_start );
@@ -51,7 +52,7 @@ public function render( $post_type = 'post', $context = 'count', $interval = 'we
[
'category' => 'post',
'type' => 'publish',
- 'start_date' => \DateTime::createFromFormat( 'Y-m-d', '1970-01-01' ),
+ 'start_date' => \DateTime::createFromFormat( $date_format, '1970-01-01' ),
'end_date' => new \DateTime( "-$range $interval" ),
'data' => [
'post_type' => $post_type,
@@ -79,7 +80,7 @@ public function render( $post_type = 'post', $context = 'count', $interval = 'we
);
// TODO: Format the date depending on the user's locale.
- $data['labels'][] = gmdate( 'Y-m-d', strtotime( "-$start $interval" ) );
+ $data['labels'][] = gmdate( $date_format, strtotime( "-$start $interval" ) );
foreach ( $activities as $activity ) {
$activity_data = $activity->get_data();
From f2f60d92c0097f20bece64e5f58964f273660ac2 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 27 Feb 2024 11:30:26 +0200
Subject: [PATCH 073/490] Remove Stat_Posts class
---
includes/admin/class-page.php | 16 +++++++++-----
includes/class-stats.php | 34 -----------------------------
includes/stats/class-stat-posts.php | 30 -------------------------
3 files changed, 10 insertions(+), 70 deletions(-)
delete mode 100644 includes/stats/class-stat-posts.php
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index 1d630c3ed..505015035 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -7,6 +7,8 @@
namespace ProgressPlanner\Admin;
+use ProgressPlanner\Activities\Query;
+
/**
* Admin page class.
*/
@@ -150,17 +152,19 @@ public function ajax_reset_stats() {
* @return array The params.
*/
public static function get_params() {
- static $stats = null;
- if ( null === $stats ) {
- $stats = new \ProgressPlanner\Stats\Stat_Posts();
- }
return [
- 'stats' => $stats,
// phpcs:ignore WordPress.Security.NonceVerification.Missing
'filter_interval' => isset( $_POST['interval'] ) ? sanitize_key( $_POST['interval'] ) : 'weeks',
// phpcs:ignore WordPress.Security.NonceVerification.Missing
'filter_number' => isset( $_POST['number'] ) ? (int) $_POST['number'] : 10,
- 'scan_pending' => empty( $stats->get_value() ),
+ 'scan_pending' => empty(
+ Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish',
+ ]
+ )
+ ),
];
}
}
diff --git a/includes/class-stats.php b/includes/class-stats.php
index 2b23c8948..71c477313 100644
--- a/includes/class-stats.php
+++ b/includes/class-stats.php
@@ -7,8 +7,6 @@
namespace ProgressPlanner;
-use ProgressPlanner\Stats\Stat_Posts;
-
/**
* Stats class.
*
@@ -23,13 +21,6 @@ class Stats {
*/
private $stats = [];
- /**
- * Constructor.
- */
- public function __construct() {
- $this->register_stats();
- }
-
/**
* Add a stat to the collection.
*
@@ -61,29 +52,4 @@ public function get_all_stats() {
public function get_stat( $id ) {
return $this->stats[ $id ];
}
-
- /**
- * Register the individual stats.
- *
- * @return void
- */
- private function register_stats() {
- $this->add_stat( 'posts', new Stat_Posts() );
- }
-
- /**
- * Get number of activities for date range.
- *
- * @param \DateTime $start_date The start date.
- * @param \DateTime $end_date The end date.
- * @param array $args The query arguments.
- *
- * @return int
- */
- public function get_number_of_activities( $start_date, $end_date, $args = [] ) {
- $args['start_date'] = $start_date;
- $args['end_date'] = $end_date;
- $activities = Query::get_instance()->query_activities( $args );
- return count( $activities );
- }
}
diff --git a/includes/stats/class-stat-posts.php b/includes/stats/class-stat-posts.php
deleted file mode 100644
index e96a5e3b6..000000000
--- a/includes/stats/class-stat-posts.php
+++ /dev/null
@@ -1,30 +0,0 @@
-query_activities(
- [
- 'category' => 'post',
- 'type' => 'publish',
- ]
- );
- }
-}
From 6261af453117a3fc81f984cafb6ea0f4efde3aec Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 27 Feb 2024 11:32:07 +0200
Subject: [PATCH 074/490] Styling graphs: Remove points
---
includes/charts/class-posts.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index f3ffae301..5ee84541f 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -96,7 +96,7 @@ public function render( $post_type = 'post', $context = 'count', $interval = 'we
md5( wp_json_encode( [ [ $post_type ], $context, $interval, $range, $offset ] ) ),
'line',
$data,
- []
+ [ 'pointStyle' => false ]
);
}
}
From 5fa60cfe99a2b2c7be89ec637c05615e61f04ba5 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 27 Feb 2024 11:33:22 +0200
Subject: [PATCH 075/490] Remove Stats class
---
includes/class-base.php | 3 +--
includes/class-stats.php | 55 ----------------------------------------
2 files changed, 1 insertion(+), 57 deletions(-)
delete mode 100644 includes/class-stats.php
diff --git a/includes/class-base.php b/includes/class-base.php
index 5b04e6ebf..8c61af08f 100644
--- a/includes/class-base.php
+++ b/includes/class-base.php
@@ -8,6 +8,7 @@
namespace ProgressPlanner;
use ProgressPlanner\Activities\Activity_Post;
+use ProgressPlanner\Admin;
/**
* Main plugin class.
@@ -48,8 +49,6 @@ private function __construct() {
$this->admin = new Admin();
( new Activity_Post() )->register_hooks();
-
- new Stats();
}
/**
diff --git a/includes/class-stats.php b/includes/class-stats.php
deleted file mode 100644
index 71c477313..000000000
--- a/includes/class-stats.php
+++ /dev/null
@@ -1,55 +0,0 @@
-stats[ $id ] = $stat;
- }
-
- /**
- * Get all stats.
- *
- * @return array
- */
- public function get_all_stats() {
- return $this->stats;
- }
-
- /**
- * Get an individual stat.
- *
- * @param string $id The ID of the stat.
- *
- * @return Object
- */
- public function get_stat( $id ) {
- return $this->stats[ $id ];
- }
-}
From 1da48e0ec943bdbc7c4fd5d38e6fc669548d2f0d Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 27 Feb 2024 13:53:51 +0200
Subject: [PATCH 076/490] WIP - refactoring charts
---
assets/css/admin.css | 53 ++++++++++++
includes/admin/class-page.php | 40 +++++++++
includes/charts/class-posts.php | 83 ------------------
includes/class-chart.php | 100 ++++++++++++++++++++++
views/admin-page-posts-count-progress.php | 21 -----
views/admin-page-streak.php | 25 ------
views/admin-page-words-count-progress.php | 20 -----
views/admin-page.php | 10 +--
views/widget-published-posts.php | 68 +++++++++++++++
9 files changed, 263 insertions(+), 157 deletions(-)
delete mode 100644 views/admin-page-posts-count-progress.php
delete mode 100644 views/admin-page-streak.php
delete mode 100644 views/admin-page-words-count-progress.php
create mode 100644 views/widget-published-posts.php
diff --git a/assets/css/admin.css b/assets/css/admin.css
index 2e230ca43..ca6ca5df0 100644
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -1,5 +1,58 @@
+/*
+Set variables.
+*/
+.prpl-wrap {
+ --prpl-color-gray-1: #e5e7eb;
+ --prpl-color-gray-2: #d1d5db;
+ --prpl-color-gray-3: #9ca3af;
+ --prpl-color-gray-4: #6b7280;
+ --prpl-color-gray-5: #4b5563;
+ --prpl-color-gray-6: #374151;
+
+ --prpl-color-accent-orange: #faa310;
+ --prpl-color-accent-purple: #0d6b9e;
+ --prpl-color-accent-green: #14b8a6;
+}
+
+.prpl-wrap {
+ background: #fff;
+ border: 1px solid var(--prpl-color-gray-3);
+ border-radius: 5px;
+ padding: 20px;
+}
+
#progress-planner-scan-progress progress{
width: 100%;
max-width: 500px;
min-height: 1px;
}
+
+.prpl-widget-wrapper {
+ max-width: 300px; /* TODO: This should be dynamic based on the columns. */
+ border: 1px solid var(--prpl-color-gray-3);
+ border-radius: 5px;
+ padding: 20px;
+}
+
+.prpl-wrap .counter-big-wrapper {
+ background-color: var(--prpl-color-gray-1);
+ padding: 20px;
+ border-radius: 5px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.prpl-wrap .counter-big-number {
+ font-size: 4rem;
+ line-height: 5rem;
+ font-weight: 700;
+}
+
+.prpl-wrap .counter-big-text {
+ font-size: 1.5rem;
+}
+
+.prpl-wrap .prpl-widget-content p {
+ font-size: 1.25em;
+}
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index 505015035..6bdb6ca6e 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -167,4 +167,44 @@ public static function get_params() {
),
];
}
+
+ /**
+ * Get total number of published posts.
+ *
+ * @return int
+ */
+ public static function get_posts_published_all() {
+ $activities = Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish',
+ 'data' => [
+ 'post_type' => 'post',
+ ],
+ ]
+ );
+
+ return count( $activities );
+ }
+
+ /**
+ * Get number of posts published in the past week.
+ *
+ * @return int
+ */
+ public static function get_posts_published_this_week() {
+ $activities = Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish',
+ 'start_date' => new \DateTime( '-7 days' ),
+ 'end_date' => new \DateTime( 'now' ),
+ 'data' => [
+ 'post_type' => 'post',
+ ],
+ ]
+ );
+
+ return count( $activities );
+ }
}
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
index 5ee84541f..f6180bd46 100644
--- a/includes/charts/class-posts.php
+++ b/includes/charts/class-posts.php
@@ -15,88 +15,5 @@
*/
class Posts extends Chart {
- /**
- * Build a chart for the stats.
- *
- * @param string $post_type The post type.
- * @param string $context The context for the chart. Can be 'count' or 'words'.
- * @param string $interval The interval for the chart. Can be 'days', 'weeks', 'months', 'years'.
- * @param int $range The number of intervals to show.
- * @param int $offset The offset for the intervals.
- * @param string $date_format The date format.
- *
- * @return void
- */
- public function render( $post_type = 'post', $context = 'count', $interval = 'weeks', $range = 10, $offset = 0, $date_format = 'Y-m-d' ) {
- $range_array_end = \range( $offset, $range - 1 );
- $range_array_start = \range( $offset + 1, $range );
- \krsort( $range_array_start );
- \krsort( $range_array_end );
- $range_array = \array_combine( $range_array_start, $range_array_end );
-
- $data = [
- 'labels' => [],
- 'datasets' => [],
- ];
- $datasets = [];
- $post_type_count_totals = 0;
- $dataset = [
- 'label' => \get_post_type_object( $post_type )->label,
- 'data' => [],
- 'tension' => 0.2,
- ];
-
- // Calculate zero stats to be used as the baseline.
- $zero_activities = Query::get_instance()->query_activities(
- [
- 'category' => 'post',
- 'type' => 'publish',
- 'start_date' => \DateTime::createFromFormat( $date_format, '1970-01-01' ),
- 'end_date' => new \DateTime( "-$range $interval" ),
- 'data' => [
- 'post_type' => $post_type,
- ],
- ]
- );
- foreach ( $zero_activities as $zero_activity ) {
- $activity_data = $zero_activity->get_data();
- $post_type_count_totals += 'words' === $context
- ? $activity_data['word_count']
- : 1;
- }
-
- foreach ( $range_array as $start => $end ) {
- $activities = Query::get_instance()->query_activities(
- [
- 'category' => 'post',
- 'type' => 'publish',
- 'start_date' => new \DateTime( "-$start $interval" ),
- 'end_date' => new \DateTime( "-$end $interval" ),
- 'data' => [
- 'post_type' => $post_type,
- ],
- ]
- );
-
- // TODO: Format the date depending on the user's locale.
- $data['labels'][] = gmdate( $date_format, strtotime( "-$start $interval" ) );
-
- foreach ( $activities as $activity ) {
- $activity_data = $activity->get_data();
- $post_type_count_totals += 'words' === $context
- ? $activity_data['word_count']
- : 1;
- }
- $datasets[ $post_type ]['data'][] = $post_type_count_totals;
- }
- $data['datasets'] = \array_values( $datasets );
-
- $this->render_chart(
- md5( wp_json_encode( [ [ $post_type ], $context, $interval, $range, $offset ] ) ),
- 'line',
- $data,
- [ 'pointStyle' => false ]
- );
- }
}
diff --git a/includes/class-chart.php b/includes/class-chart.php
index ef6774ca1..23ceb80b2 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -7,11 +7,111 @@
namespace ProgressPlanner;
+use ProgressPlanner\Activities\Query;
+
/**
* Render a chart.
*/
class Chart {
+ /**
+ * Build a chart for the stats.
+ *
+ * @param array $query_params The query parameters.
+ * string $query_params['category'] The category for the query.
+ * string $query_params['type'] The type for the query.
+ * array $query_params['data'] The data for the query.
+ * @param array $dates_params The dates parameters for the query.
+ * string $dates_params['start'] The start date for the query.
+ * string $dates_params['end'] The end date for the query.
+ * string $dates_params['interval'] The interval for the query.
+ * string $dates_params['format'] The format for the query.
+ * int $dates_params['range'] The range for the query.
+ * @param array $chart_params The chart parameters.
+ *
+ * @return void
+ */
+ public function the_chart( $query_params = [], $dates_params = [], $chart_params = [] ) {
+ $chart_params = wp_parse_args(
+ $chart_params,
+ [
+ 'type' => 'line',
+ 'options' => [
+ 'pointStyle' => false,
+ 'plugins' => [
+ 'legend' => [
+ 'display' => false,
+ ],
+ ],
+ ],
+ ]
+ );
+
+ $range_array_end = \range( 0, $dates_params['range'] - 1 );
+ $range_array_start = \range( 1, $dates_params['range'] );
+ \krsort( $range_array_start );
+ \krsort( $range_array_end );
+
+ $range_array = \array_combine( $range_array_start, $range_array_end );
+
+ $data = [
+ 'labels' => [],
+ 'datasets' => [],
+ ];
+ $datasets = [
+ [
+ 'label' => '',
+ 'data' => [],
+ 'tension' => 0.2,
+ ],
+ ];
+
+ // Calculate zero stats to be used as the baseline.
+ $zero_activities = Query::get_instance()->query_activities(
+ array_merge(
+ $query_params,
+ [
+ 'start_date' => \DateTime::createFromFormat( 'Y-m-d', '1970-01-01' ),
+ 'end_date' => new \DateTime( "-{$dates_params['range']} {$dates_params['interval']}" ),
+ ]
+ )
+ );
+ $activities_count = count( $zero_activities );
+
+ foreach ( $range_array as $start => $end ) {
+ $activities = Query::get_instance()->query_activities(
+ array_merge(
+ $query_params,
+ [
+ 'start_date' => new \DateTime(
+ "-$start {$dates_params['interval']}"
+ ),
+ 'end_date' => new \DateTime(
+ "-$end {$dates_params['interval']}"
+ ),
+ ]
+ )
+ );
+
+ // TODO: Format the date depending on the user's locale.
+ $data['labels'][] = gmdate(
+ $dates_params['format'],
+ strtotime( "-$start {$dates_params['interval']}" )
+ );
+
+ $activities_count += count( $activities );
+ $datasets[0]['data'][] = $activities_count;
+ }
+ $data['datasets'] = $datasets;
+
+ $this->render_chart(
+ md5( wp_json_encode( [ $query_params, $dates_params ] ) ),
+ $chart_params['type'],
+ $data,
+ $chart_params['options']
+ );
+ }
+
/**
* Render the chart.
*
diff --git a/views/admin-page-posts-count-progress.php b/views/admin-page-posts-count-progress.php
deleted file mode 100644
index e0c6448dd..000000000
--- a/views/admin-page-posts-count-progress.php
+++ /dev/null
@@ -1,21 +0,0 @@
-';
-echo '';
-esc_html_e( 'Posts count progress', 'progress-planner' );
-echo ' ';
-
-( new \ProgressPlanner\Charts\Posts() )->render(
- 'post',
- 'count',
- \ProgressPlanner\Admin\Page::get_params()['filter_interval'],
- \ProgressPlanner\Admin\Page::get_params()['filter_number'],
- 0
-);
-
-echo ' ';
diff --git a/views/admin-page-streak.php b/views/admin-page-streak.php
deleted file mode 100644
index de4d445af..000000000
--- a/views/admin-page-streak.php
+++ /dev/null
@@ -1,25 +0,0 @@
- 10, // Number of posts per week, targetting for 10 weeks.
- 'weekly_words' => 10, // Number of words per week, targetting for 10 weeks.
-];
-?>
-
-
- $prpl_streak_goal ) : ?>
-
- get_streak( $prpl_streak_id, $prpl_streak_goal ); ?>
-
-
-
-
-
-
-
-
diff --git a/views/admin-page-words-count-progress.php b/views/admin-page-words-count-progress.php
deleted file mode 100644
index 82ed0fa5b..000000000
--- a/views/admin-page-words-count-progress.php
+++ /dev/null
@@ -1,20 +0,0 @@
-';
-echo '';
-esc_html_e( 'Words count progress', 'progress-planner' );
-echo ' ';
-
-( new \ProgressPlanner\Charts\Posts() )->render(
- 'post',
- 'words',
- \ProgressPlanner\Admin\Page::get_params()['filter_interval'],
- \ProgressPlanner\Admin\Page::get_params()['filter_number'],
- 0
-);
-echo ' ';
diff --git a/views/admin-page.php b/views/admin-page.php
index b1cd7264b..f8463f8c7 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -6,19 +6,13 @@
*/
?>
-
+
-
-
-
-
-
-
-
+
diff --git a/views/widget-published-posts.php b/views/widget-published-posts.php
new file mode 100644
index 000000000..2bbeaf686
--- /dev/null
+++ b/views/widget-published-posts.php
@@ -0,0 +1,68 @@
+ 'post',
+ 'type' => 'publish',
+ 'data' => [
+ 'post_type' => 'post',
+ ],
+];
+$prpl_last_week_posts = Admin\Page::get_posts_published_this_week();
+$prpl_all_posts_count = count(
+ Activities\Query::get_instance()->query_activities( $prpl_query_args )
+);
+
+?>
+
From b72897956a38f40ac8439f8c5603ac7e353c1910 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 27 Feb 2024 15:02:36 +0200
Subject: [PATCH 077/490] more refactors & cleanup
---
includes/admin/class-dashboard-widget.php | 13 ++++-
includes/admin/class-page.php | 22 --------
includes/charts/class-posts.php | 19 -------
includes/class-chart.php | 66 +++++++++++------------
includes/class-date.php | 33 ++++++++++--
views/admin-page-form-filters.php | 32 -----------
views/admin-page.php | 16 +++++-
views/widget-published-posts.php | 13 ++---
8 files changed, 93 insertions(+), 121 deletions(-)
delete mode 100644 includes/charts/class-posts.php
delete mode 100644 views/admin-page-form-filters.php
diff --git a/includes/admin/class-dashboard-widget.php b/includes/admin/class-dashboard-widget.php
index ccdc9fc95..dc14276e9 100644
--- a/includes/admin/class-dashboard-widget.php
+++ b/includes/admin/class-dashboard-widget.php
@@ -7,6 +7,8 @@
namespace ProgressPlanner\Admin;
+use ProgressPlanner\Activities\Query;
+
/**
* Class Dashboard_Widget
*/
@@ -34,9 +36,18 @@ public function add_dashboard_widget() {
* Render the dashboard widget.
*/
public function render_dashboard_widget() {
+ $scan_pending = empty(
+ Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish'
+ ]
+ )
+ );
+
?>
-
+
isset( $_POST['interval'] ) ? sanitize_key( $_POST['interval'] ) : 'weeks',
- // phpcs:ignore WordPress.Security.NonceVerification.Missing
- 'filter_number' => isset( $_POST['number'] ) ? (int) $_POST['number'] : 10,
- 'scan_pending' => empty(
- Query::get_instance()->query_activities(
- [
- 'category' => 'post',
- 'type' => 'publish',
- ]
- )
- ),
- ];
- }
-
/**
* Get total number of published posts.
*
diff --git a/includes/charts/class-posts.php b/includes/charts/class-posts.php
deleted file mode 100644
index f6180bd46..000000000
--- a/includes/charts/class-posts.php
+++ /dev/null
@@ -1,19 +0,0 @@
- [],
@@ -67,37 +67,31 @@ public function the_chart( $query_params = [], $dates_params = [], $chart_params
];
// Calculate zero stats to be used as the baseline.
- $zero_activities = Query::get_instance()->query_activities(
- array_merge(
- $query_params,
- [
- 'start_date' => \DateTime::createFromFormat( 'Y-m-d', '1970-01-01' ),
- 'end_date' => new \DateTime( "-{$dates_params['range']} {$dates_params['interval']}" ),
- ]
+ $activities_count = count(
+ Query::get_instance()->query_activities(
+ array_merge(
+ $query_params,
+ [
+ 'start_date' => Query::get_instance()->get_oldest_activity()->get_date(),
+ 'end_date' => $periods[0]['dates'][0]->modify( '-1 day' ),
+ ]
+ )
)
);
- $activities_count = count( $zero_activities );
- foreach ( $range_array as $start => $end ) {
+ foreach ( $periods as $period ) {
$activities = Query::get_instance()->query_activities(
array_merge(
$query_params,
[
- 'start_date' => new \DateTime(
- "-$start {$dates_params['interval']}"
- ),
- 'end_date' => new \DateTime(
- "-$end {$dates_params['interval']}"
- ),
+ 'start_date' => $period['dates'][0],
+ 'end_date' => end( $period['dates'] ),
]
)
);
// TODO: Format the date depending on the user's locale.
- $data['labels'][] = gmdate(
- $dates_params['format'],
- strtotime( "-$start {$dates_params['interval']}" )
- );
+ $data['labels'][] = $period['start']->format( $dates_params['format'] );
$activities_count += count( $activities );
$datasets[0]['data'][] = $activities_count;
diff --git a/includes/class-date.php b/includes/class-date.php
index e8b3150a7..ca65fb114 100644
--- a/includes/class-date.php
+++ b/includes/class-date.php
@@ -24,7 +24,7 @@ class Date {
* 'dates' => [ 'Ymd', 'Ymd', ... ],
* ].
*/
- public function get_range( $start, $end ) {
+ public static function get_range( $start, $end ) {
return [
'start' => $start,
'end' => $end,
@@ -41,7 +41,7 @@ public function get_range( $start, $end ) {
*
* @return array
*/
- public function get_periods( $start, $end, $frequency ) {
+ public static function get_periods( $start, $end, $frequency ) {
$end = $end->modify( '+1 day' );
switch ( $frequency ) {
@@ -66,9 +66,12 @@ public function get_periods( $start, $end, $frequency ) {
$date_ranges = [];
foreach ( $period as $key => $date ) {
if ( isset( $period[ $key + 1 ] ) ) {
- $date_ranges[] = $this->get_range( $date, $period[ $key + 1 ] );
+ $date_ranges[] = static::get_range( $date, $period[ $key + 1 ] );
}
}
+ if ( $end->format( 'z' ) !== end( $date_ranges )['end']->format( 'z' ) ) {
+ $date_ranges[] = static::get_range( end( $date_ranges )['end'], $end );
+ }
return $date_ranges;
}
@@ -83,4 +86,28 @@ public function get_periods( $start, $end, $frequency ) {
public static function get_datetime_from_mysql_date( $date ) {
return \DateTime::createFromFormat( 'U', (int) mysql2date( 'U', $date ) );
}
+
+ /**
+ * Get start of week from a date.
+ *
+ * @param \DateTime $date The date.
+ *
+ * @return \DateTime
+ */
+ public static function get_start_of_week( $date ) {
+ $day_of_week = (int) $date->format( 'N' );
+ $day_of_week = $day_of_week === 7 ? 0 : $day_of_week;
+ return $date->modify( "-{$day_of_week} days" );
+ }
+
+ /**
+ * Get start of month from a date.
+ *
+ * @param \DateTime $date The date.
+ *
+ * @return \DateTime
+ */
+ public static function get_start_of_month( $date ) {
+ return $date->modify( 'first day of this month' );
+ }
}
diff --git a/views/admin-page-form-filters.php b/views/admin-page-form-filters.php
deleted file mode 100644
index cc7bd724f..000000000
--- a/views/admin-page-form-filters.php
+++ /dev/null
@@ -1,32 +0,0 @@
- __( 'Days', 'progress-planner' ),
- 'weeks' => __( 'Weeks', 'progress-planner' ),
- 'months' => __( 'Months', 'progress-planner' ),
-];
-
-?>
-
diff --git a/views/admin-page.php b/views/admin-page.php
index f8463f8c7..ca3a7aedf 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -5,11 +5,23 @@
* @package ProgressPlanner
*/
+namespace ProgressPlanner;
+
+use ProgressPlanner\Activities\Query;
+
+$prpl_scan_pending = empty(
+ Query::get_instance()->query_activities(
+ [
+ 'category' => 'post',
+ 'type' => 'publish'
+ ]
+ )
+);
?>
-
+
-
+
diff --git a/views/widget-published-posts.php b/views/widget-published-posts.php
index 2bbeaf686..cec056266 100644
--- a/views/widget-published-posts.php
+++ b/views/widget-published-posts.php
@@ -14,6 +14,7 @@
'post_type' => 'post',
],
];
+
$prpl_last_week_posts = Admin\Page::get_posts_published_this_week();
$prpl_all_posts_count = count(
Activities\Query::get_instance()->query_activities( $prpl_query_args )
@@ -47,7 +48,7 @@
the_chart(
+ ( new Chart() )->the_chart(
[
'category' => 'post',
'type' => 'publish',
@@ -56,11 +57,11 @@
],
],
[
- 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01', \strtotime( 'now' ) ) )->modify( '-6 months' ),
- 'end' => new \DateTime( 'now' ),
- 'interval' => 'months',
- 'range' => 6,
- 'format' => 'M',
+ 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01', \strtotime( 'now' ) ) )->modify( '-5 months' ),
+ 'end' => new \DateTime( 'now' ),
+ 'frequency' => 'monthly',
+ 'range' => 6,
+ 'format' => 'M',
]
);
?>
From 6e8385a926b857d71405fcc76320569238ea14f6 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 27 Feb 2024 15:31:01 +0200
Subject: [PATCH 078/490] Add a progress_planner function in root, and a
get_query method in Base
---
includes/activities/class-activity-post.php | 7 +++----
includes/activities/class-activity.php | 8 ++++----
includes/admin/class-dashboard-widget.php | 4 +---
includes/admin/class-page.php | 6 ++----
includes/class-base.php | 10 ++++++++++
includes/class-chart.php | 9 ++++-----
includes/class-streaks.php | 9 ++++-----
includes/scan/class-posts.php | 3 +--
progress-planner.php | 11 +++++++++--
views/admin-page-debug.php | 2 +-
views/admin-page.php | 4 +---
views/widget-published-posts.php | 2 +-
12 files changed, 41 insertions(+), 34 deletions(-)
diff --git a/includes/activities/class-activity-post.php b/includes/activities/class-activity-post.php
index ff9f3ae78..12fed6395 100644
--- a/includes/activities/class-activity-post.php
+++ b/includes/activities/class-activity-post.php
@@ -7,7 +7,6 @@
namespace ProgressPlanner\Activities;
-use ProgressPlanner\Activities\Query;
use ProgressPlanner\Activities\Activity;
use ProgressPlanner\Date;
@@ -95,7 +94,7 @@ public function transition_post_status( $new_status, $old_status, $post ) {
// If the post is published, check if it was previously published,
// and if so, delete the old activity and create a new one.
if ( 'publish' !== $old_status && 'publish' === $new_status ) {
- $old_publish_activities = Query::get_instance()->query_activities(
+ $old_publish_activities = \progress_planner()->get_query()->query_activities(
[
'category' => 'post',
'type' => 'publish',
@@ -215,7 +214,7 @@ public function delete_post( $post_id ) {
}
// Update existing activities, and remove the words count.
- $activities = Query::get_instance()->query_activities(
+ $activities = \progress_planner()->get_query()->query_activities(
[
'category' => 'post',
'data_id' => $post->ID,
@@ -226,7 +225,7 @@ public function delete_post( $post_id ) {
$activity->set_data( [ 'post_type' => $post->post_type ] );
$activity->save();
}
- Query::get_instance()->delete_activities( $activities );
+ \progress_planner()->get_query()->delete_activities( $activities );
}
$activity = new Activity();
diff --git a/includes/activities/class-activity.php b/includes/activities/class-activity.php
index 5f0fa623e..b850df9dd 100644
--- a/includes/activities/class-activity.php
+++ b/includes/activities/class-activity.php
@@ -172,7 +172,7 @@ public function get_data() {
* @return void
*/
public function save() {
- $existing = Query::get_instance()->query_activities(
+ $existing = \progress_planner()->get_query()->query_activities(
[
'category' => $this->category,
'type' => $this->type,
@@ -180,9 +180,9 @@ public function save() {
]
);
if ( ! empty( $existing ) ) {
- Query::get_instance()->update_activity( $existing[0]->id, $this );
+ \progress_planner()->get_query()->update_activity( $existing[0]->id, $this );
} else {
- Query::get_instance()->insert_activity( $this );
+ \progress_planner()->get_query()->insert_activity( $this );
}
}
@@ -192,6 +192,6 @@ public function save() {
* @return void
*/
public function delete() {
- Query::get_instance()->delete_activity( $this );
+ \progress_planner()->get_query()->delete_activity( $this );
}
}
diff --git a/includes/admin/class-dashboard-widget.php b/includes/admin/class-dashboard-widget.php
index dc14276e9..52974123a 100644
--- a/includes/admin/class-dashboard-widget.php
+++ b/includes/admin/class-dashboard-widget.php
@@ -7,8 +7,6 @@
namespace ProgressPlanner\Admin;
-use ProgressPlanner\Activities\Query;
-
/**
* Class Dashboard_Widget
*/
@@ -37,7 +35,7 @@ public function add_dashboard_widget() {
*/
public function render_dashboard_widget() {
$scan_pending = empty(
- Query::get_instance()->query_activities(
+ \progress_planner()->get_query()->query_activities(
[
'category' => 'post',
'type' => 'publish'
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index 52366fc79..6627b7bf6 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -7,8 +7,6 @@
namespace ProgressPlanner\Admin;
-use ProgressPlanner\Activities\Query;
-
/**
* Admin page class.
*/
@@ -152,7 +150,7 @@ public function ajax_reset_stats() {
* @return int
*/
public static function get_posts_published_all() {
- $activities = Query::get_instance()->query_activities(
+ $activities = \progress_planner()->get_query()->query_activities(
[
'category' => 'post',
'type' => 'publish',
@@ -171,7 +169,7 @@ public static function get_posts_published_all() {
* @return int
*/
public static function get_posts_published_this_week() {
- $activities = Query::get_instance()->query_activities(
+ $activities = \progress_planner()->get_query()->query_activities(
[
'category' => 'post',
'type' => 'publish',
diff --git a/includes/class-base.php b/includes/class-base.php
index 8c61af08f..93fe0bdd2 100644
--- a/includes/class-base.php
+++ b/includes/class-base.php
@@ -7,6 +7,7 @@
namespace ProgressPlanner;
+use ProgressPlanner\Activities\Query;
use ProgressPlanner\Activities\Activity_Post;
use ProgressPlanner\Admin;
@@ -59,4 +60,13 @@ private function __construct() {
public function get_admin() {
return $this->admin;
}
+
+ /**
+ * Get the query object.
+ *
+ * @return \ProgressPlanner\Activities\Query
+ */
+ public function get_query() {
+ return Query::get_instance();
+ }
}
diff --git a/includes/class-chart.php b/includes/class-chart.php
index fb0f68931..2d3ae5202 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -7,7 +7,6 @@
namespace ProgressPlanner;
-use ProgressPlanner\Activities\Query;
use ProgressPlanner\Date;
/**
@@ -68,11 +67,11 @@ public function the_chart( $query_params = [], $dates_params = [], $chart_params
// Calculate zero stats to be used as the baseline.
$activities_count = count(
- Query::get_instance()->query_activities(
+ \progress_planner()->get_query()->query_activities(
array_merge(
$query_params,
[
- 'start_date' => Query::get_instance()->get_oldest_activity()->get_date(),
+ 'start_date' => \progress_planner()->get_query()->get_oldest_activity()->get_date(),
'end_date' => $periods[0]['dates'][0]->modify( '-1 day' ),
]
)
@@ -80,7 +79,7 @@ public function the_chart( $query_params = [], $dates_params = [], $chart_params
);
foreach ( $periods as $period ) {
- $activities = Query::get_instance()->query_activities(
+ $activities = \progress_planner()->get_query()->query_activities(
array_merge(
$query_params,
[
@@ -91,7 +90,7 @@ public function the_chart( $query_params = [], $dates_params = [], $chart_params
);
// TODO: Format the date depending on the user's locale.
- $data['labels'][] = $period['start']->format( $dates_params['format'] );
+ $data['labels'][] = $period['dates'][0]->format( $dates_params['format'] );
$activities_count += count( $activities );
$datasets[0]['data'][] = $activities_count;
diff --git a/includes/class-streaks.php b/includes/class-streaks.php
index e38953426..4bed156f0 100644
--- a/includes/class-streaks.php
+++ b/includes/class-streaks.php
@@ -9,7 +9,6 @@
use ProgressPlanner\Goals\Goal_Posts;
use ProgressPlanner\Goals\Goal_Recurring;
-use ProgressPlanner\Activities\Query;
/**
* Streaks class.
@@ -123,7 +122,7 @@ private function get_weekly_post_goal() {
'priority' => 'low',
'evaluate' => function ( $goal_object ) {
return (bool) count(
- Query::get_instance()->query_activities(
+ \progress_planner()->get_query()->query_activities(
[
'category' => 'post',
'type' => 'publish',
@@ -139,7 +138,7 @@ private function get_weekly_post_goal() {
]
),
'weekly',
- Query::get_instance()->get_oldest_activity()->get_date(), // Beginning of the stats.
+ \progress_planner()->get_query()->get_oldest_activity()->get_date(), // Beginning of the stats.
new \DateTime( 'now' ) // Today.
);
}
@@ -159,7 +158,7 @@ private function get_weekly_words_goal() {
'status' => 'active',
'priority' => 'low',
'evaluate' => function ( $goal_object ) {
- $activities = Query::get_instance()->query_activities(
+ $activities = \progress_planner()->get_query()->query_activities(
[
'category' => 'post',
'type' => 'publish',
@@ -179,7 +178,7 @@ private function get_weekly_words_goal() {
]
),
'weekly',
- Query::get_instance()->get_oldest_activity()->get_date(), // Beginning of the stats.
+ \progress_planner()->get_query()->get_oldest_activity()->get_date(), // Beginning of the stats.
new \DateTime( 'now' ) // Today.
);
}
diff --git a/includes/scan/class-posts.php b/includes/scan/class-posts.php
index c280f05d2..3648c13d6 100644
--- a/includes/scan/class-posts.php
+++ b/includes/scan/class-posts.php
@@ -10,7 +10,6 @@
use ProgressPlanner\Activities\Activity;
use ProgressPlanner\Date;
use ProgressPlanner\Activities\Activity_Post;
-use ProgressPlanner\Activities\Query;
/**
* Scan existing posts and populate the options.
@@ -114,7 +113,7 @@ public static function update_stats() {
* @return void
*/
public static function reset_stats() {
- Query::get_instance()->delete_category_activities( 'post' );
+ \progress_planner()->get_query()->delete_category_activities( 'post' );
\delete_option( static::LAST_SCANNED_PAGE_OPTION );
}
}
diff --git a/progress-planner.php b/progress-planner.php
index dc4a2e116..2ad632391 100644
--- a/progress-planner.php
+++ b/progress-planner.php
@@ -10,6 +10,13 @@
require_once PROGRESS_PLANNER_DIR . '/includes/autoload.php';
-\ProgressPlanner\Base::get_instance();
+/**
+ * Get the progress planner instance.
+ *
+ * @return \ProgressPlanner\Base
+ */
+function progress_planner() {
+ return \ProgressPlanner\Base::get_instance();
+}
-$prpl_storage = \ProgressPlanner\Activities\Query::get_instance();
+progress_planner();
diff --git a/views/admin-page-debug.php b/views/admin-page-debug.php
index 391c9681d..b01d8350c 100644
--- a/views/admin-page-debug.php
+++ b/views/admin-page-debug.php
@@ -16,7 +16,7 @@
query_activities( [] ) );
+ print_r( \progress_planner()->get_query()->query_activities( [] ) );
?>
diff --git a/views/admin-page.php b/views/admin-page.php
index ca3a7aedf..86880b0af 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -7,10 +7,8 @@
namespace ProgressPlanner;
-use ProgressPlanner\Activities\Query;
-
$prpl_scan_pending = empty(
- Query::get_instance()->query_activities(
+ \progress_planner()->get_query()->query_activities(
[
'category' => 'post',
'type' => 'publish'
diff --git a/views/widget-published-posts.php b/views/widget-published-posts.php
index cec056266..4234287fe 100644
--- a/views/widget-published-posts.php
+++ b/views/widget-published-posts.php
@@ -17,7 +17,7 @@
$prpl_last_week_posts = Admin\Page::get_posts_published_this_week();
$prpl_all_posts_count = count(
- Activities\Query::get_instance()->query_activities( $prpl_query_args )
+ \progress_planner()->get_query()->query_activities( $prpl_query_args )
);
?>
From b8b5de70c199e09519d6f9e33a736ae990396616 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 28 Feb 2024 11:22:22 +0200
Subject: [PATCH 079/490] Improve queries
---
includes/activities/class-query.php | 61 +++++++++++++++++------------
1 file changed, 36 insertions(+), 25 deletions(-)
diff --git a/includes/activities/class-query.php b/includes/activities/class-query.php
index 39fad776c..65cd7031d 100644
--- a/includes/activities/class-query.php
+++ b/includes/activities/class-query.php
@@ -114,33 +114,44 @@ public function query_activities( $args ) {
$args = \wp_parse_args( $args, $defaults );
- // If start and end dates are defined, then get activities by date.
- if ( $args['start_date'] && $args['end_date'] ) {
- // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
- $results = $wpdb->get_results(
- $wpdb->prepare(
- 'SELECT * FROM %i WHERE date >= %s AND date <= %s AND category LIKE %s AND type LIKE %s AND data_id LIKE %s',
- $wpdb->prefix . static::TABLE_NAME,
- $args['start_date']->format( 'Y-m-d' ),
- $args['end_date']->format( 'Y-m-d' ),
- $args['category'],
- $args['type'],
- $args['data_id']
- )
- );
- } else {
- // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
- $results = $wpdb->get_results(
- $wpdb->prepare(
- 'SELECT * FROM %i WHERE category LIKE %s AND type LIKE %s AND data_id LIKE %s',
- $wpdb->prefix . static::TABLE_NAME,
- $args['category'],
- $args['type'],
- $args['data_id']
- )
- );
+ $where_args = [];
+ $prepare_args = [];
+ if ( $args['start_date'] ) {
+ $where_args[] = 'date >= %s';
+ $prepare_args[] = $args['start_date']->format( 'Y-m-d H:i:s' );
+ }
+ if ( $args['end_date'] ) {
+ $where_args[] = 'date <= %s';
+ $prepare_args[] = $args['end_date']->format( 'Y-m-d H:i:s' );
+ }
+ if ( $args['category'] !== '%' ) {
+ $where_args[] = 'category = %s';
+ $prepare_args[] = $args['category'];
+ }
+ if ( $args['type'] !== '%' ) {
+ $where_args[] = 'type = %s';
+ $prepare_args[] = $args['type'];
+ }
+ if ( $args['data_id'] !== '%' ) {
+ $where_args[] = 'data_id = %s';
+ $prepare_args[] = $args['data_id'];
}
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ $results = $wpdb->get_results(
+ // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- This is a false positive.
+ $wpdb->prepare(
+ sprintf(
+ 'SELECT * FROM %%i WHERE %s',
+ \implode( ' AND ', $where_args )
+ ),
+ array_merge(
+ [ $wpdb->prefix . static::TABLE_NAME ],
+ $prepare_args
+ )
+ )
+ );
+
$activities = $this->get_activities_from_results( $results );
if ( isset( $args['data'] ) && ! empty( $args['data'] ) ) {
From 34cd2891590445d94fbb7a06e53508764eed44d1 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 28 Feb 2024 11:42:09 +0200
Subject: [PATCH 080/490] bugfix for query
---
includes/activities/class-query.php | 31 +++++++++++++++++------------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/includes/activities/class-query.php b/includes/activities/class-query.php
index 65cd7031d..a121d2197 100644
--- a/includes/activities/class-query.php
+++ b/includes/activities/class-query.php
@@ -137,20 +137,25 @@ public function query_activities( $args ) {
$prepare_args[] = $args['data_id'];
}
- // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
- $results = $wpdb->get_results(
- // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- This is a false positive.
- $wpdb->prepare(
- sprintf(
- 'SELECT * FROM %%i WHERE %s',
- \implode( ' AND ', $where_args )
- ),
- array_merge(
- [ $wpdb->prefix . static::TABLE_NAME ],
- $prepare_args
- )
+ $results = ( empty( $where_args ) )
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ ? $wpdb->get_results(
+ $wpdb->prepare( 'SELECT * FROM %i', $wpdb->prefix . static::TABLE_NAME )
)
- );
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ : $wpdb->get_results(
+ // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- This is a false positive.
+ $wpdb->prepare(
+ sprintf(
+ 'SELECT * FROM %%i WHERE %s',
+ \implode( ' AND ', $where_args )
+ ),
+ array_merge(
+ [ $wpdb->prefix . static::TABLE_NAME ],
+ $prepare_args
+ )
+ )
+ );
$activities = $this->get_activities_from_results( $results );
From bf768e7f2d2e41b978cb073162996f08f4760e8b Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 28 Feb 2024 13:47:14 +0200
Subject: [PATCH 081/490] scanner bugfixes
---
assets/js/admin.js | 25 +----------
includes/activities/class-activity-post.php | 4 --
includes/activities/class-query.php | 48 +++++++++++++--------
includes/admin/class-dashboard-widget.php | 2 +-
includes/scan/class-posts.php | 27 ++++--------
views/admin-page.php | 2 +-
6 files changed, 42 insertions(+), 66 deletions(-)
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 9fb5cf69d..9c8fe7a42 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -68,29 +68,7 @@ const progressPlannerTriggerScan = () => {
return;
}
- // Wait half a second and re-trigger.
- setTimeout( () => {
- progressPlannerTriggerScan();
- }, 500 );
- };
-
- /**
- * The action to run on a failed AJAX request.
- * This function should re-trigger the scan if necessary.
- * If the response contains a `progress` property, the successAction should be run instead.
- *
- * @param {Object} response The response from the server.
- */
- const failAction = ( response ) => {
- if ( response && response.data && response.data.progress ) {
- successAction( response );
- return;
- }
-
- // Wait 2 seconds and re-trigger.
- setTimeout( () => {
- progressPlannerTriggerScan();
- }, 1000 );
+ progressPlannerTriggerScan();
};
/**
@@ -103,7 +81,6 @@ const progressPlannerTriggerScan = () => {
_ajax_nonce: progressPlanner.nonce,
},
successAction: successAction,
- failAction: failAction,
} );
};
diff --git a/includes/activities/class-activity-post.php b/includes/activities/class-activity-post.php
index 12fed6395..3f9e94060 100644
--- a/includes/activities/class-activity-post.php
+++ b/includes/activities/class-activity-post.php
@@ -221,10 +221,6 @@ public function delete_post( $post_id ) {
]
);
if ( ! empty( $activities ) ) {
- foreach ( $activities as $activity ) {
- $activity->set_data( [ 'post_type' => $post->post_type ] );
- $activity->save();
- }
\progress_planner()->get_query()->delete_activities( $activities );
}
diff --git a/includes/activities/class-query.php b/includes/activities/class-query.php
index a121d2197..d9874cfd0 100644
--- a/includes/activities/class-query.php
+++ b/includes/activities/class-query.php
@@ -97,42 +97,43 @@ private function create_activities_table() {
/**
* Query the database for activities.
*
- * @param array $args The arguments for the query.
+ * @param array $args The arguments for the query.
+ * @param string $return_type The type of the return value. Can be "RAW" or "ACTIVITIES".
*
* @return \ProgressPlanner\Activities\Activity[] The activities.
*/
- public function query_activities( $args ) {
+ public function query_activities( $args, $return_type = 'ACTIVITIES' ) {
global $wpdb;
$defaults = [
'start_date' => null,
'end_date' => null,
- 'category' => '%',
- 'type' => '%',
- 'data_id' => '%',
+ 'category' => null,
+ 'type' => null,
+ 'data_id' => null,
];
$args = \wp_parse_args( $args, $defaults );
$where_args = [];
$prepare_args = [];
- if ( $args['start_date'] ) {
+ if ( $args['start_date'] !== null ) {
$where_args[] = 'date >= %s';
$prepare_args[] = $args['start_date']->format( 'Y-m-d H:i:s' );
}
- if ( $args['end_date'] ) {
+ if ( $args['end_date'] !== null ) {
$where_args[] = 'date <= %s';
$prepare_args[] = $args['end_date']->format( 'Y-m-d H:i:s' );
}
- if ( $args['category'] !== '%' ) {
+ if ( $args['category'] !== null ) {
$where_args[] = 'category = %s';
$prepare_args[] = $args['category'];
}
- if ( $args['type'] !== '%' ) {
+ if ( $args['type'] !== null ) {
$where_args[] = 'type = %s';
$prepare_args[] = $args['type'];
}
- if ( $args['data_id'] !== '%' ) {
+ if ( $args['data_id'] !== null ) {
$where_args[] = 'data_id = %s';
$prepare_args[] = $args['data_id'];
}
@@ -157,20 +158,20 @@ public function query_activities( $args ) {
)
);
- $activities = $this->get_activities_from_results( $results );
-
if ( isset( $args['data'] ) && ! empty( $args['data'] ) ) {
- foreach ( $activities as $key => $activity ) {
- $data = $activity->get_data();
+ foreach ( $results as $key => $activity ) {
+ $data = \json_decode( $activity->data, true );
foreach ( $args['data'] as $data_key => $data_value ) {
if ( ! isset( $data[ $data_key ] ) || $data[ $data_key ] !== $data_value ) {
- unset( $activities[ $key ] );
+ unset( $results[ $key ] );
}
}
}
- $activities = \array_values( $activities );
+ $results = \array_values( $results );
}
- return $activities;
+ return 'RAW' === $return_type
+ ? $results
+ : $this->get_activities_from_results( $results );
}
/**
@@ -308,12 +309,23 @@ public function delete_activities( $activities ) {
* @return void
*/
public function delete_activity( $activity ) {
+ $this->delete_activity_by_id( $activity->get_id() );
+ }
+
+ /**
+ * Delete activitiy by ID.
+ *
+ * @param int $id The ID of the activity to delete.
+ *
+ * @return void
+ */
+ public function delete_activity_by_id( $id ) {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$wpdb->delete(
$wpdb->prefix . static::TABLE_NAME,
- [ 'id' => $activity->get_id() ],
+ [ 'id' => $id ],
[ '%d' ]
);
}
diff --git a/includes/admin/class-dashboard-widget.php b/includes/admin/class-dashboard-widget.php
index 52974123a..4500a6cda 100644
--- a/includes/admin/class-dashboard-widget.php
+++ b/includes/admin/class-dashboard-widget.php
@@ -38,7 +38,7 @@ public function render_dashboard_widget() {
\progress_planner()->get_query()->query_activities(
[
'category' => 'post',
- 'type' => 'publish'
+ 'type' => 'publish',
]
)
);
diff --git a/includes/scan/class-posts.php b/includes/scan/class-posts.php
index 3648c13d6..c3db298bf 100644
--- a/includes/scan/class-posts.php
+++ b/includes/scan/class-posts.php
@@ -21,7 +21,7 @@ class Posts {
*
* @var int
*/
- const SCAN_POSTS_PER_PAGE = 50;
+ const SCAN_POSTS_PER_PAGE = 30;
/**
* The option used to store the last scanned page.
@@ -59,20 +59,21 @@ public static function update_stats() {
'posts_per_page' => static::SCAN_POSTS_PER_PAGE,
'paged' => $current_page,
'post_type' => Activity_Post::get_post_types_names(),
- 'post_status' => 'any',
+ 'post_status' => 'publish',
]
);
if ( ! $posts ) {
\delete_option( static::LAST_SCANNED_PAGE_OPTION );
return [
- 'lastScannedPage' => $last_page,
+ 'lastScannedPage' => $current_page,
'lastPage' => $total_pages,
'progress' => 100,
];
}
// Loop through the posts and update the stats.
+ $activities = [];
foreach ( $posts as $post ) {
$activity = new Activity();
$activity->set_category( 'post' );
@@ -83,25 +84,15 @@ public static function update_stats() {
'word_count' => Activity_Post::get_word_count( $post->post_content ),
]
);
-
- switch ( $post->post_status ) {
- case 'publish':
- $activity->set_type( 'publish' );
- $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_date ) );
- break;
-
- default:
- $activity->set_type( 'update' );
- $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_modified ) );
- }
-
- $activity->save();
+ $activity->set_type( 'publish' );
+ $activity->set_date( Date::get_datetime_from_mysql_date( $post->post_date ) );
+ $activities[ $post->ID ] = $activity;
}
-
+ \progress_planner()->get_query()->insert_activities( $activities );
\update_option( static::LAST_SCANNED_PAGE_OPTION, $current_page );
return [
- 'lastScannedPage' => $last_page,
+ 'lastScannedPage' => $current_page,
'lastPage' => $total_pages,
'progress' => round( ( $current_page / max( 1, $total_pages ) ) * 100 ),
];
diff --git a/views/admin-page.php b/views/admin-page.php
index 86880b0af..86303be40 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -11,7 +11,7 @@
\progress_planner()->get_query()->query_activities(
[
'category' => 'post',
- 'type' => 'publish'
+ 'type' => 'publish',
]
)
);
From beb6ac36209fb3e7f7e5219453ba59ad0f605673 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 28 Feb 2024 13:48:54 +0200
Subject: [PATCH 082/490] Activity bugfix
---
includes/activities/class-activity.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/includes/activities/class-activity.php b/includes/activities/class-activity.php
index b850df9dd..8f1435d94 100644
--- a/includes/activities/class-activity.php
+++ b/includes/activities/class-activity.php
@@ -177,7 +177,8 @@ public function save() {
'category' => $this->category,
'type' => $this->type,
'data_id' => $this->data_id,
- ]
+ ],
+ 'RAW'
);
if ( ! empty( $existing ) ) {
\progress_planner()->get_query()->update_activity( $existing[0]->id, $this );
From 11778f980d9cd6cba023bbffbfb6c698f08280e3 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 28 Feb 2024 15:52:33 +0200
Subject: [PATCH 083/490] jigsaw falling into place
---
assets/css/admin.css | 15 +++-
includes/activities/class-activity.php | 11 ++-
includes/admin/class-page.php | 6 +-
includes/class-chart.php | 116 ++++++++++++++++---------
includes/class-streaks.php | 2 +-
views/admin-page.php | 7 +-
views/widget-activity-scores.php | 54 ++++++++++++
views/widget-published-pages.php | 73 ++++++++++++++++
views/widget-published-posts.php | 30 ++++---
views/widget-published-words.php | 91 +++++++++++++++++++
10 files changed, 341 insertions(+), 64 deletions(-)
create mode 100644 views/widget-activity-scores.php
create mode 100644 views/widget-published-pages.php
create mode 100644 views/widget-published-words.php
diff --git a/assets/css/admin.css b/assets/css/admin.css
index ca6ca5df0..399fcd9c4 100644
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -27,11 +27,19 @@ Set variables.
min-height: 1px;
}
+.prpl-widgets-container {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ grid-gap: 20px;
+}
+
.prpl-widget-wrapper {
- max-width: 300px; /* TODO: This should be dynamic based on the columns. */
border: 1px solid var(--prpl-color-gray-3);
border-radius: 5px;
padding: 20px;
+ max-height: 500px;
+ display: flex;
+ flex-direction: column;
}
.prpl-wrap .counter-big-wrapper {
@@ -56,3 +64,8 @@ Set variables.
.prpl-wrap .prpl-widget-content p {
font-size: 1.25em;
}
+
+.prpl-graph-wrapper {
+ position: relative;
+ height: 100%;
+}
diff --git a/includes/activities/class-activity.php b/includes/activities/class-activity.php
index 8f1435d94..38cd58f33 100644
--- a/includes/activities/class-activity.php
+++ b/includes/activities/class-activity.php
@@ -160,10 +160,15 @@ public function set_data( array $data ) {
/**
* Get the data of the activity.
*
- * @return array
+ * @param string|null $key The key of the data to get. If null, then all data is returned.
+ *
+ * @return mixed
*/
- public function get_data() {
- return $this->data;
+ public function get_data( $key = null ) {
+ if ( null === $key ) {
+ return $this->data;
+ }
+ return isset( $this->data[ $key ] ) ? $this->data[ $key ] : null;
}
/**
diff --git a/includes/admin/class-page.php b/includes/admin/class-page.php
index 6627b7bf6..e6071c36a 100644
--- a/includes/admin/class-page.php
+++ b/includes/admin/class-page.php
@@ -166,9 +166,11 @@ public static function get_posts_published_all() {
/**
* Get number of posts published in the past week.
*
+ * @param string $post_type The post type.
+ *
* @return int
*/
- public static function get_posts_published_this_week() {
+ public static function get_posts_published_this_week( $post_type ) {
$activities = \progress_planner()->get_query()->query_activities(
[
'category' => 'post',
@@ -176,7 +178,7 @@ public static function get_posts_published_this_week() {
'start_date' => new \DateTime( '-7 days' ),
'end_date' => new \DateTime( 'now' ),
'data' => [
- 'post_type' => 'post',
+ 'post_type' => $post_type,
],
]
);
diff --git a/includes/class-chart.php b/includes/class-chart.php
index 2d3ae5202..c91dd7357 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -17,28 +17,52 @@ class Chart {
/**
* Build a chart for the stats.
*
- * @param array $query_params The query parameters.
- * string $query_params['category'] The category for the query.
- * string $query_params['type'] The type for the query.
- * array $query_params['data'] The data for the query.
- * @param array $dates_params The dates parameters for the query.
- * string $dates_params['start'] The start date for the chart.
- * string $dates_params['end'] The end date for the chart.
- * string $dates_params['frequency'] The frequency for the chart.
- * string $dates_params['format'] The format for the chart.
- * int $dates_params['range'] The range for the chart.
- * @param array $chart_params The chart parameters.
+ * @param array $args The arguments for the chart.
+ * ['query_params'] The query parameters.
+ * See Query::query_activities for the available parameters.
+ *
+ * ['dates_params'] The dates parameters for the query.
+ * ['start'] The start date for the chart.
+ * ['end'] The end date for the chart.
+ * ['frequency'] The frequency for the chart nodes.
+ * ['format'] The format for the label
+ *
+ * ['chart_params'] The chart parameters.
+ *
+ * [additive] Whether to add the stats for next node to the previous one.
*
* @return void
*/
- public function the_chart( $query_params = [], $dates_params = [], $chart_params = [] ) {
- $chart_params = wp_parse_args(
- $chart_params,
+ public function the_chart( $args = [] ) {
+ $args = wp_parse_args(
+ $args,
+ [
+ 'query_params' => [],
+ 'dates_params' => [],
+ 'chart_params' => [],
+ 'additive' => true,
+ 'colors' => [
+ 'background' => function () {
+ return '#534786';
+ },
+ 'border' => function () {
+ return '#534786';
+ },
+ ],
+ 'count_callback' => function ( $activities ) {
+ return count( $activities );
+ },
+ ]
+ );
+ $args['chart_params'] = wp_parse_args(
+ $args['chart_params'],
[
'type' => 'line',
'options' => [
- 'pointStyle' => false,
- 'plugins' => [
+ 'responsive' => true,
+ 'maintainAspectRatio' => false,
+ 'pointStyle' => false,
+ 'plugins' => [
'legend' => [
'display' => false,
],
@@ -48,9 +72,9 @@ public function the_chart( $query_params = [], $dates_params = [], $chart_params
);
$periods = Date::get_periods(
- $dates_params['start'],
- $dates_params['end'],
- 'monthly'
+ $args['dates_params']['start'],
+ $args['dates_params']['end'],
+ $args['dates_params']['frequency']
);
$data = [
@@ -59,29 +83,32 @@ public function the_chart( $query_params = [], $dates_params = [], $chart_params
];
$datasets = [
[
- 'label' => '',
- 'data' => [],
- 'tension' => 0.2,
+ 'label' => '',
+ 'data' => [],
+ 'tension' => 0.2,
+ 'backgroundColor' => [],
+ 'borderColor' => [],
],
];
// Calculate zero stats to be used as the baseline.
- $activities_count = count(
- \progress_planner()->get_query()->query_activities(
- array_merge(
- $query_params,
- [
- 'start_date' => \progress_planner()->get_query()->get_oldest_activity()->get_date(),
- 'end_date' => $periods[0]['dates'][0]->modify( '-1 day' ),
- ]
+ $activities_count = $args['additive']
+ ? $args['count_callback'](
+ \progress_planner()->get_query()->query_activities(
+ array_merge(
+ $args['query_params'],
+ [
+ 'start_date' => \progress_planner()->get_query()->get_oldest_activity()->get_date(),
+ 'end_date' => $periods[0]['dates'][0]->modify( '-1 day' ),
+ ]
+ )
)
- )
- );
+ ) : 0;
foreach ( $periods as $period ) {
$activities = \progress_planner()->get_query()->query_activities(
array_merge(
- $query_params,
+ $args['query_params'],
[
'start_date' => $period['dates'][0],
'end_date' => end( $period['dates'] ),
@@ -89,19 +116,22 @@ public function the_chart( $query_params = [], $dates_params = [], $chart_params
)
);
- // TODO: Format the date depending on the user's locale.
- $data['labels'][] = $period['dates'][0]->format( $dates_params['format'] );
+ $data['labels'][] = $period['dates'][0]->format( $args['dates_params']['format'] );
- $activities_count += count( $activities );
- $datasets[0]['data'][] = $activities_count;
+ $activities_count = $args['additive']
+ ? $activities_count + $args['count_callback']( $activities )
+ : $args['count_callback']( $activities );
+ $datasets[0]['data'][] = $activities_count;
+ $datasets[0]['backgroundColor'][] = $args['colors']['background']( $activities_count );
+ $datasets[0]['borderColor'][] = $args['colors']['border']( $activities_count );
}
$data['datasets'] = $datasets;
$this->render_chart(
- md5( wp_json_encode( [ $query_params, $dates_params ] ) ),
- $chart_params['type'],
+ md5( wp_json_encode( $args ) ),
+ $args['chart_params']['type'],
$data,
- $chart_params['options']
+ $args['chart_params']['options']
);
}
@@ -118,14 +148,14 @@ public function the_chart( $query_params = [], $dates_params = [], $chart_params
public function render_chart( $id, $type, $data, $options = [] ) {
$id = 'progress-planner-chart-' . $id;
- $options['responsive'] = true;
-
// TODO: This should be properly enqueued.
// phpcs:ignore
echo '';
?>
-
+
+
+
';
?>
diff --git a/views/widgets/badges-progress.php b/views/widgets/badges-progress.php
index 61157ddcd..0452f26f2 100644
--- a/views/widgets/badges-progress.php
+++ b/views/widgets/badges-progress.php
@@ -33,7 +33,9 @@
%
- 🏆
+
+ 🏆
+
diff --git a/views/widgets/website-activity-score.php b/views/widgets/website-activity-score.php
index 4aaf38123..16c2db43c 100644
--- a/views/widgets/website-activity-score.php
+++ b/views/widgets/website-activity-score.php
@@ -43,7 +43,7 @@
]
)
);
-$prpl_pending_updates = wp_get_update_data()['counts']['total'];
+$prpl_pending_updates = wp_get_update_data()['counts']['total'];
// Target is the number of pending updates + the ones that have already been done.
$prpl_maintenance_score = max( 1, $prpl_maintenance_count ) / max( 1, $prpl_maintenance_count + $prpl_pending_updates );
From 291aa2bf9791cb87bc49761ce06d8086c534a9d7 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 4 Mar 2024 11:09:24 +0200
Subject: [PATCH 115/490] Implement badges grouping
---
includes/class-badges.php | 210 ++++++++++++------------------
views/widgets/badges-progress.php | 51 +++++---
2 files changed, 112 insertions(+), 149 deletions(-)
diff --git a/includes/class-badges.php b/includes/class-badges.php
index c6d31b963..41fbbb080 100644
--- a/includes/class-badges.php
+++ b/includes/class-badges.php
@@ -90,7 +90,17 @@ public function get_badge_progress( $badge_id ) {
return 0;
}
- return $badge['progress_callback']();
+ $progress = [];
+
+ foreach ( $badge['steps'] as $step ) {
+ $progress[] = [
+ 'name' => $step['name'],
+ 'icon' => $step['icon'],
+ 'progress' => $badge['progress_callback']( $step['target'] ),
+ ];
+ }
+
+ return $progress;
}
/**
@@ -99,159 +109,103 @@ public function get_badge_progress( $badge_id ) {
* @return void
*/
private function register_badges() {
- // First Post.
- $this->register_badge(
- 'first_post',
- [
- 'name' => __( 'First Post', 'progress-planner' ),
- 'description' => __( 'You published your first post.', 'progress-planner' ),
- 'progress_callback' => function () {
- $activities = \progress_planner()->get_query()->query_activities(
- [
- 'category' => 'content',
- 'type' => 'publish',
- ]
- );
- return empty( $activities ) ? 0 : 100;
- },
- ]
- );
-
- // 100 posts.
- $this->register_badge(
- '100_posts',
- [
- 'name' => __( '100 Posts', 'progress-planner' ),
- 'description' => __( 'You published 100 posts.', 'progress-planner' ),
- 'progress_callback' => function () {
- $activities = \progress_planner()->get_query()->query_activities(
- [
- 'category' => 'content',
- 'type' => 'publish',
- ]
- );
- return min( count( $activities ), 100 );
- },
- ]
- );
-
- // 1000 posts
+ // Badges for number of posts.
$this->register_badge(
- '1000_posts',
+ 'content_published_count',
[
- 'name' => __( '1000 Posts', 'progress-planner' ),
- 'description' => __( 'You published 1000 posts.', 'progress-planner' ),
- 'progress_callback' => function () {
+ 'steps' => [
+ [
+ 'target' => 100,
+ 'name' => __( '100 Posts', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ [
+ 'target' => 1000,
+ 'name' => __( '1000 Posts', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ [
+ 'target' => 2000,
+ 'name' => __( '2000 Posts', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ [
+ 'target' => 5000,
+ 'name' => __( '5000 Posts', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ ],
+ 'progress_callback' => function ( $target ) {
$activities = \progress_planner()->get_query()->query_activities(
[
'category' => 'content',
'type' => 'publish',
]
);
- return min( floor( count( $activities ) / 10 ), 100 );
- },
- ]
- );
-
- // 2000 posts
- $this->register_badge(
- '2000_posts',
- [
- 'name' => __( '1000 Posts', 'progress-planner' ),
- 'description' => __( 'You published 1000 posts.', 'progress-planner' ),
- 'progress_callback' => function () {
- $activities = \progress_planner()->get_query()->query_activities(
- [
- 'category' => 'content',
- 'type' => 'publish',
- ]
- );
- return min( floor( count( $activities ) / 20 ), 100 );
- },
- ]
- );
-
- // 5000 posts
- $this->register_badge(
- '5000_posts',
- [
- 'name' => __( '5000 Posts', 'progress-planner' ),
- 'description' => __( 'You published 5000 posts.', 'progress-planner' ),
- 'progress_callback' => function () {
- $activities = \progress_planner()->get_query()->query_activities(
- [
- 'category' => 'content',
- 'type' => 'publish',
- ]
- );
- return min( floor( count( $activities ) / 50 ), 100 );
+ return min( floor( 100 * count( $activities ) / $target ), 100 );
},
]
);
// 100 maintenance tasks.
$this->register_badge(
- '100_maintenance_tasks',
+ 'maintenance_tasks',
[
- 'name' => __( '100 Maintenance Tasks', 'progress-planner' ),
- 'description' => __( 'You completed 100 maintenance tasks.', 'progress-planner' ),
- 'progress_callback' => function () {
+ 'steps' => [
+ [
+ 'target' => 10,
+ 'name' => __( '10 maintenance tasks', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ [
+ 'target' => 100,
+ 'name' => __( '100 maintenance tasks', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ [
+ 'target' => 1000,
+ 'name' => __( '1000 maintenance tasks', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ ],
+ 'progress_callback' => function ( $target ) {
$activities = \progress_planner()->get_query()->query_activities(
[
'category' => 'maintenance',
]
);
- return min( count( $activities ), 100 );
+ return min( floor( 100 * count( $activities ) / $target ), 100 );
},
]
);
// Write a post for 10 consecutive weeks.
$this->register_badge(
- '10_weeks_consecutive_posts',
- [
- 'name' => __( '10 Weeks Consecutive Posts', 'progress-planner' ),
- 'description' => __( 'You wrote a post for 10 consecutive weeks.', 'progress-planner' ),
- 'progress_callback' => function () {
- $goal = new Goal_Recurring(
- new Goal_Posts(
- [
- 'id' => 'weekly_post',
- 'title' => \esc_html__( 'Write a weekly blog post', 'progress-planner' ),
- 'description' => \esc_html__( 'Streak: The number of weeks this goal has been accomplished consistently.', 'progress-planner' ),
- 'status' => 'active',
- 'priority' => 'low',
- 'evaluate' => function ( $goal_object ) {
- return (bool) count(
- \progress_planner()->get_query()->query_activities(
- [
- 'category' => 'content',
- 'type' => 'publish',
- 'start_date' => $goal_object->get_details()['start_date'],
- 'end_date' => $goal_object->get_details()['end_date'],
- ]
- )
- );
- },
- ]
- ),
- 'weekly',
- \progress_planner()->get_query()->get_oldest_activity()->get_date(), // Beginning of the stats.
- new \DateTime() // Today.
- );
-
- return ( min( 100, $goal->get_streak()['max_streak'] * 10 ) );
- },
- ]
- );
-
- // Write a post for 100 consecutive weeks.
- $this->register_badge(
- '100_weeks_consecutive_posts',
+ 'consecutive_weeks_posts',
[
- 'name' => __( '100 Weeks Consecutive Posts', 'progress-planner' ),
- 'description' => __( 'You wrote a post for 10 consecutive weeks.', 'progress-planner' ),
- 'progress_callback' => function () {
+ 'steps' => [
+ [
+ 'target' => 10,
+ 'name' => __( '10 weeks posting streak', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ [
+ 'target' => 52,
+ 'name' => __( '52 weeks posting streak', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ [
+ 'target' => 104,
+ 'name' => __( '104 weeks posting streak', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ [
+ 'target' => 208,
+ 'name' => __( '208 weeks posting streak', 'progress-planner' ),
+ 'icon' => '🏆',
+ ],
+ ],
+ 'progress_callback' => function ( $target ) {
$goal = new Goal_Recurring(
new Goal_Posts(
[
@@ -279,7 +233,7 @@ private function register_badges() {
new \DateTime() // Today.
);
- return ( min( 100, $goal->get_streak()['max_streak'] ) );
+ return min( floor( 100 * $goal->get_streak()['max_streak'] / $target ), 100 );
},
]
);
diff --git a/views/widgets/badges-progress.php b/views/widgets/badges-progress.php
index 0452f26f2..c7cd92adc 100644
--- a/views/widgets/badges-progress.php
+++ b/views/widgets/badges-progress.php
@@ -8,34 +8,43 @@
namespace ProgressPlanner;
$prpl_badges = \progress_planner()->get_badges()->get_badges();
+
+$prpl_get_progress_color = function ( $progress ) {
+ $color = 'var(--prpl-color-accent-red)';
+ if ( $progress > 50 ) {
+ $color = 'var(--prpl-color-accent-orange)';
+ }
+ if ( $progress > 75 ) {
+ $color = 'var(--prpl-color-accent-green)';
+ }
+ return $color;
+};
+
?>
- get_badges()->get_badge_progress( $prpl_badge['id'] );
- $prpl_badge_progress_color = 'var(--prpl-color-accent-red)';
- if ( $prpl_badge_progress > 50 ) {
- $prpl_badge_progress_color = 'var(--prpl-color-accent-orange)';
- }
- if ( $prpl_badge_progress > 75 ) {
- $prpl_badge_progress_color = 'var(--prpl-color-accent-green)';
- }
- ?>
-
-
-
-
-
-
-
- %
-
- 🏆
+ get_badges()->get_badge_progress( $prpl_badge['id'] ); ?>
+ $prpl_badge_step_progress ) : ?>
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
-
+
+
From e8e2f3a1001eb84233427873b6bd8c52d502dbb9 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 4 Mar 2024 11:11:54 +0200
Subject: [PATCH 116/490] CSS fix
---
assets/css/admin.css | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/assets/css/admin.css b/assets/css/admin.css
index 287d714c0..39fa09682 100644
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -129,14 +129,10 @@ Set variables.
margin-top: -1em;
}
-.progress-wrapper {
- display:
-}
-
.progress-wrapper .progress-bar {
height: 1em;
background-color: var(--prpl-color-gray-1);
- width: calc(100% - 6em);
+ width: 100%;
display: inline-block;
}
From d387b14577ff2c53a149f9d7e032bcbb8848b65e Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 4 Mar 2024 11:37:29 +0200
Subject: [PATCH 117/490] No need to query for tomorrow, date queries were
fixed earlier today
---
views/widgets/published-content-density.php | 2 +-
views/widgets/published-words.php | 2 +-
views/widgets/website-activity-score.php | 6 +++---
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/views/widgets/published-content-density.php b/views/widgets/published-content-density.php
index b2b607d1a..4fe2f94c0 100644
--- a/views/widgets/published-content-density.php
+++ b/views/widgets/published-content-density.php
@@ -41,7 +41,7 @@ function ( $activity ) {
$prpl_query_args,
[
'start_date' => new \DateTime( '-7 days' ),
- 'end_date' => new \DateTime( 'tomorrow' ),
+ 'end_date' => new \DateTime(),
]
)
)
diff --git a/views/widgets/published-words.php b/views/widgets/published-words.php
index 900a04ca6..1b333b865 100644
--- a/views/widgets/published-words.php
+++ b/views/widgets/published-words.php
@@ -35,7 +35,7 @@ function ( $activity ) {
$prpl_query_args,
[
'start_date' => new \DateTime( '-7 days' ),
- 'end_date' => new \DateTime( 'tomorrow' ),
+ 'end_date' => new \DateTime(),
]
)
)
diff --git a/views/widgets/website-activity-score.php b/views/widgets/website-activity-score.php
index 16c2db43c..d6df7d8cd 100644
--- a/views/widgets/website-activity-score.php
+++ b/views/widgets/website-activity-score.php
@@ -15,14 +15,14 @@
\progress_planner()->get_query()->query_activities(
[
'start_date' => new \DateTime( '-7 days' ),
- 'end_date' => new \DateTime( 'tomorrow' ),
+ 'end_date' => new \DateTime(),
'category' => 'content',
]
),
\progress_planner()->get_query()->query_activities(
[
'start_date' => new \DateTime( '-7 days' ),
- 'end_date' => new \DateTime( 'tomorrow' ),
+ 'end_date' => new \DateTime(),
'category' => 'comments',
]
)
@@ -38,7 +38,7 @@
\progress_planner()->get_query()->query_activities(
[
'start_date' => new \DateTime( '-7 days' ),
- 'end_date' => new \DateTime( 'tomorrow' ),
+ 'end_date' => new \DateTime(),
'category' => 'maintenance',
]
)
From 6a533bb05c0cffca6c135d36a26e6ff3b4b86f43 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 4 Mar 2024 12:50:21 +0200
Subject: [PATCH 118/490] CSS tweaks
---
assets/css/admin.css | 88 +++++++++++++++++++++++++++++++++-----------
1 file changed, 66 insertions(+), 22 deletions(-)
diff --git a/assets/css/admin.css b/assets/css/admin.css
index 39fa09682..c3434d4ec 100644
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -2,6 +2,13 @@
Set variables.
*/
.prpl-wrap {
+ --prpl-gap: 20px;
+ --prpl-column-min-width: 22rem;
+ --prpl-max-columns: 4;
+ --prpl-border-radius: 7px;
+
+ --prpl-container-max-width: calc(var(--prpl-column-min-width) * var(--prpl-max-columns) + var(--prpl-gap) * (var(--prpl-max-columns) - 1));
+
--prpl-color-gray-1: #e5e7eb;
--prpl-color-gray-2: #d1d5db;
--prpl-color-gray-3: #9ca3af;
@@ -14,17 +21,58 @@ Set variables.
--prpl-color-accent-purple: #0d6b9e;
--prpl-color-accent-green: #14b8a6;
+ --prpl-color-headings: #38296d;
+ --prpl-color-text: var(--prpl-color-gray-6);
+ --prpl-color-link: #1e40af;
+
+ --prpl-color-notification-green: #16a34a;
+ --prpl-color-notification-red: #e73136;
+
--prpl-background-orange: #fff9f0;
+ --prpl-background-purple: #f6f5fb;
+ --prpl-background-green: #f2faf9;
+ --prpl-background-red: #fff6f7;
+ --prpl-background-blue: #effbfe;
+
+ --prpl-font-size-small: 0.875rem; /* 14px */
+ --prpl-font-size-base: 1rem; /* 16px */
+ --prpl-font-size-lg: 1.125rem; /* 18px */
+ --prpl-font-size-xl: 1.25rem; /* 20px */
+ --prpl-font-size-2xl: 1.5rem; /* 24px */
+ --prpl-font-size-3xl: 2rem; /* 32px */
+ --prpl-font-size-4xl: 3rem; /* 48px */
+ --prpl-font-size-5xl: 4rem; /* 64px */
}
.prpl-wrap {
background: #fff;
border: 1px solid var(--prpl-color-gray-3);
- border-radius: 5px;
- padding: 20px;
+ border-radius: var(--prpl-border-radius);
+ padding: var(--prpl-gap);
+ max-width: var(--prpl-container-max-width);
+ color: var(--prpl-color-text);
+ font-size: var(--prpl-font-size-base);
+ line-height: 1.4
+}
+
+.prpl-wrap p {
+ font-size: var(--prpl-font-size-base);
+}
+
+.prpl-wrap h1,
+.prpl-wrap h2,
+.prpl-wrap h3,
+.prpl-wrap h4,
+.prpl-wrap h5,
+.prpl-wrap h6 {
+ color: var(--prpl-color-headings);
+}
+
+.prpl-wrap a {
+ color: var(--prpl-color-link);
}
-#progress-planner-scan-progress progress{
+#progress-planner-scan-progress progress {
width: 100%;
max-width: 500px;
min-height: 1px;
@@ -32,39 +80,35 @@ Set variables.
.prpl-widgets-container {
display: grid;
- grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
- grid-gap: 20px;
+ grid-template-columns: repeat(auto-fit, minmax(var(--prpl-column-min-width), 1fr));
+ grid-gap: var(--prpl-gap);
}
.prpl-widget-wrapper {
- border: 1px solid var(--prpl-color-gray-3);
- border-radius: 5px;
- padding: 20px;
+ border: 1px solid var(--prpl-color-gray-2);
+ border-radius: var(--prpl-border-radius);
+ padding: var(--prpl-gap);
display: flex;
flex-direction: column;
}
.prpl-wrap .counter-big-wrapper {
- background-color: var(--prpl-color-gray-1);
- padding: 20px;
- border-radius: 5px;
+ background-color: var(--prpl-background-purple);
+ padding: var(--prpl-gap);
+ border-radius: var(--prpl-border-radius);
display: flex;
flex-direction: column;
align-items: center;
}
.prpl-wrap .counter-big-number {
- font-size: 4rem;
- line-height: 5rem;
+ font-size: var(--prpl-font-size-5xl);
+ line-height: 1;
font-weight: 700;
}
.prpl-wrap .counter-big-text {
- font-size: 1.5rem;
-}
-
-.prpl-wrap .prpl-widget-content p {
- font-size: 1.25em;
+ font-size: var(--prpl-font-size-2xl);
}
.prpl-graph-wrapper {
@@ -80,7 +124,7 @@ Set variables.
display: grid;
grid-template-columns: 1fr 1fr;
height: 100%;
- grid-gap: 20px;
+ grid-gap: var(--prpl-gap);
}
.prpl-top-counter-bottom-content {
@@ -98,9 +142,9 @@ Set variables.
}
.prpl-gauge-container {
- padding: 20px;
+ padding: var(--prpl-gap);
background-color: var(--prpl-background-orange);
- border-radius: 5px;
+ border-radius: var(--prpl-border-radius);
height: 50%;
overflow: hidden;
}
@@ -124,7 +168,7 @@ Set variables.
}
.prpl-gauge-number {
- font-size: 3em;
+ font-size: var(--prpl-font-size-4xl);
line-height: 1;
margin-top: -1em;
}
From 4ff95fc74be25b23f97451eb54e3b9684ac2bc56 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 4 Mar 2024 12:58:41 +0200
Subject: [PATCH 119/490] Make words graph green
---
views/widgets/published-words.php | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/views/widgets/published-words.php b/views/widgets/published-words.php
index 1b333b865..10265765b 100644
--- a/views/widgets/published-words.php
+++ b/views/widgets/published-words.php
@@ -41,6 +41,10 @@ function ( $activity ) {
)
);
+$prpl_color_callback = function () {
+ return '#14b8a6';
+};
+
?>
@@ -88,6 +92,10 @@ function ( $activity ) {
],
'count_callback' => $prpl_count_words_callback,
'additive' => false,
+ 'colors' => [
+ 'background' => $prpl_color_callback,
+ 'border' => $prpl_color_callback,
+ ],
],
);
?>
From d658b48a5fbe960eb5fb8629cb72e2d8e1579db4 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Mon, 4 Mar 2024 13:08:05 +0200
Subject: [PATCH 120/490] Add some inline docs in views
---
views/widgets/activity-scores.php | 7 +++++++
views/widgets/badges-progress.php | 8 ++++++++
views/widgets/published-content-density.php | 19 +++++++++++++++++++
views/widgets/published-content.php | 3 +++
views/widgets/published-pages.php | 3 +++
views/widgets/published-posts.php | 3 +++
views/widgets/published-words.php | 15 +++++++++++++++
views/widgets/website-activity-score.php | 8 +++++++-
8 files changed, 65 insertions(+), 1 deletion(-)
diff --git a/views/widgets/activity-scores.php b/views/widgets/activity-scores.php
index 8d148632e..bb6d598ec 100644
--- a/views/widgets/activity-scores.php
+++ b/views/widgets/activity-scores.php
@@ -7,6 +7,13 @@
namespace ProgressPlanner;
+/**
+ * Callback to calculate the color of the chart.
+ *
+ * @param int $number The number to calculate the color for.
+ *
+ * @return string The color.
+ */
$prpl_color_callback = function ( $number ) {
if ( $number > 90 ) {
return '#14b8a6';
diff --git a/views/widgets/badges-progress.php b/views/widgets/badges-progress.php
index c7cd92adc..9d5e05c48 100644
--- a/views/widgets/badges-progress.php
+++ b/views/widgets/badges-progress.php
@@ -7,8 +7,16 @@
namespace ProgressPlanner;
+// Get an array of badges.
$prpl_badges = \progress_planner()->get_badges()->get_badges();
+/**
+ * Callback to get the progress color.
+ *
+ * @param int $progress The progress.
+ *
+ * @return string The color.
+ */
$prpl_get_progress_color = function ( $progress ) {
$color = 'var(--prpl-color-accent-red)';
if ( $progress > 50 ) {
diff --git a/views/widgets/published-content-density.php b/views/widgets/published-content-density.php
index 4fe2f94c0..0f95b68eb 100644
--- a/views/widgets/published-content-density.php
+++ b/views/widgets/published-content-density.php
@@ -9,11 +9,19 @@
use ProgressPlanner\Activities\Content_Helpers;
+// Arguments for the query.
$prpl_query_args = [
'category' => 'content',
'type' => 'publish',
];
+/**
+ * Callback to count the words in the activities.
+ *
+ * @param \ProgressPlanner\Activity[] $activities The activities array.
+ *
+ * @return int
+ */
$prpl_count_words_callback = function ( $activities ) {
return Content_Helpers::get_posts_stats_by_ids(
array_map(
@@ -25,16 +33,27 @@ function ( $activity ) {
)['words'];
};
+/**
+ * Callback to count the density of the activities.
+ *
+ * Returns the average number of words per activity.
+ *
+ * @param \ProgressPlanner\Activity[] $activities The activities array.
+ *
+ * @return int
+ */
$prpl_count_density_callback = function ( $activities ) use ( $prpl_count_words_callback ) {
$words = $prpl_count_words_callback( $activities );
$count = count( $activities );
return round( $words / max( 1, $count ) );
};
+// Get the all-time average.
$prpl_all_activities_density = $prpl_count_density_callback(
\progress_planner()->get_query()->query_activities( $prpl_query_args )
);
+// Get the weekly average.
$prpl_weekly_activities_density = $prpl_count_density_callback(
\progress_planner()->get_query()->query_activities(
array_merge(
diff --git a/views/widgets/published-content.php b/views/widgets/published-content.php
index 0997d716b..df8592a6f 100644
--- a/views/widgets/published-content.php
+++ b/views/widgets/published-content.php
@@ -9,6 +9,7 @@
use ProgressPlanner\Activities\Content_Helpers;
+// Get the content published this week.
$prpl_last_week_content = count(
get_posts(
[
@@ -23,6 +24,8 @@
]
)
);
+
+// Get the total number of posts for this week.
$prpl_all_content_count = 0;
foreach ( Content_Helpers::get_post_types_names() as $prpl_post_type ) {
$prpl_all_content_count += wp_count_posts( $prpl_post_type )->publish;
diff --git a/views/widgets/published-pages.php b/views/widgets/published-pages.php
index a0933fb0b..48a2e7155 100644
--- a/views/widgets/published-pages.php
+++ b/views/widgets/published-pages.php
@@ -7,6 +7,7 @@
namespace ProgressPlanner;
+// Get the pages published in the last week.
$prpl_last_week_pages = count(
get_posts(
[
@@ -21,6 +22,8 @@
]
)
);
+
+// Get the total number of pages.
$prpl_all_pages_count = wp_count_posts( 'page' );
?>
diff --git a/views/widgets/published-posts.php b/views/widgets/published-posts.php
index 5aeeec1f8..240b062cf 100644
--- a/views/widgets/published-posts.php
+++ b/views/widgets/published-posts.php
@@ -7,6 +7,7 @@
namespace ProgressPlanner;
+// Get the posts published in the last week.
$prpl_last_week_posts = count(
get_posts(
[
@@ -21,6 +22,8 @@
]
)
);
+
+// Get the total number of posts.
$prpl_all_posts_count = wp_count_posts();
?>
diff --git a/views/widgets/published-words.php b/views/widgets/published-words.php
index 10265765b..016d473f4 100644
--- a/views/widgets/published-words.php
+++ b/views/widgets/published-words.php
@@ -9,11 +9,19 @@
use ProgressPlanner\Activities\Content_Helpers;
+// Arguments for the query.
$prpl_query_args = [
'category' => 'content',
'type' => 'publish',
];
+/**
+ * Callback to count the words in the activities.
+ *
+ * @param \ProgressPlanner\Activity[] $activities The activities array.
+ *
+ * @return int
+ */
$prpl_count_words_callback = function ( $activities ) {
return Content_Helpers::get_posts_stats_by_ids(
array_map(
@@ -25,10 +33,12 @@ function ( $activity ) {
)['words'];
};
+// Get the all-time words count.
$prpl_all_time_words = $prpl_count_words_callback(
\progress_planner()->get_query()->query_activities( $prpl_query_args )
);
+// Get the weekly words count.
$prpl_this_week_words = $prpl_count_words_callback(
\progress_planner()->get_query()->query_activities(
array_merge(
@@ -41,6 +51,11 @@ function ( $activity ) {
)
);
+/**
+ * Callback to get the color for the chart.
+ *
+ * @return string
+ */
$prpl_color_callback = function () {
return '#14b8a6';
};
diff --git a/views/widgets/website-activity-score.php b/views/widgets/website-activity-score.php
index d6df7d8cd..49cd19774 100644
--- a/views/widgets/website-activity-score.php
+++ b/views/widgets/website-activity-score.php
@@ -28,6 +28,7 @@
)
)
);
+
// Target 5 content activities per week.
$prpl_content_score = min( $prpl_content_count, 5 ) / 5;
@@ -43,7 +44,9 @@
]
)
);
-$prpl_pending_updates = wp_get_update_data()['counts']['total'];
+
+// Get the number of pending updates.
+$prpl_pending_updates = wp_get_update_data()['counts']['total'];
// Target is the number of pending updates + the ones that have already been done.
$prpl_maintenance_score = max( 1, $prpl_maintenance_count ) / max( 1, $prpl_maintenance_count + $prpl_pending_updates );
@@ -53,7 +56,10 @@
*/
$prpl_score = 0.7 * $prpl_content_score + 0.3 * $prpl_maintenance_score;
+// Get the score.
$prpl_score = round( 100 * $prpl_score );
+
+// Calculate the color.
$prpl_gauge_color = 'var(--prpl-color-accent-red)';
if ( $prpl_score > 50 ) {
$prpl_gauge_color = 'var(--prpl-color-accent-orange)';
From 9d5ad647c6fd95b0df588626d6f147da44aed48d Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 11:23:55 +0200
Subject: [PATCH 121/490] Add on_install method
---
includes/activities/class-content.php | 14 ++++++++++++++
includes/scan/class-maintenance.php | 17 +++++++++++++++++
2 files changed, 31 insertions(+)
diff --git a/includes/activities/class-content.php b/includes/activities/class-content.php
index 0443519a2..aeca1ff16 100644
--- a/includes/activities/class-content.php
+++ b/includes/activities/class-content.php
@@ -8,12 +8,26 @@
namespace ProgressPlanner\Activities;
use ProgressPlanner\Activity;
+use ProgressPlanner\Date;
+use ProgressPlanner\Activities\Content_Helpers;
/**
* Handler for posts activities.
*/
class Content extends Activity {
+ /**
+ * The points awarded for each activity.
+ *
+ * @var array
+ */
+ const ACTIVITIES_POINTS = [
+ 'publish' => 50,
+ 'update' => 10,
+ 'delete' => 5,
+ 'comment' => 2,
+ ];
+
/**
* Category of the activity.
*
diff --git a/includes/scan/class-maintenance.php b/includes/scan/class-maintenance.php
index 4ddcb2e9f..d23147910 100644
--- a/includes/scan/class-maintenance.php
+++ b/includes/scan/class-maintenance.php
@@ -45,6 +45,23 @@ protected function register_hooks() {
\add_action( 'switch_theme', [ $this, 'on_switch_theme' ], 10, 2 );
}
+ /**
+ * On install.
+ *
+ * @param \WP_Upgrader $upgrader The upgrader object.
+ * @param array $options The options.
+ *
+ * @return void
+ */
+ public function on_install( $upgrader, $options ) {
+ if ( 'install' !== $options['action'] ) {
+ return;
+ }
+ $activity = new Activity_Maintenance();
+ $activity->set_type( 'install_' . $this->get_install_type( $options ) );
+ $activity->save();
+ }
+
/**
* On upgrade.
*
From 8b4b2c94d7afa974b55ba83560074f97fd4065df Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 11:27:08 +0200
Subject: [PATCH 122/490] Add WIP page header
---
assets/images/logo.png | Bin 0 -> 15437 bytes
assets/js/admin.js | 7 ++++++
views/admin-page-header.php | 41 ++++++++++++++++++++++++++++++++++++
views/admin-page.php | 3 ++-
4 files changed, 50 insertions(+), 1 deletion(-)
create mode 100644 assets/images/logo.png
create mode 100644 views/admin-page-header.php
diff --git a/assets/images/logo.png b/assets/images/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..4e3278d7ca4aa77948d81ea5ecf8e1cb38cefb54
GIT binary patch
literal 15437
zcmb8VWmH^CvjB=SSQs?82Dbr%ySuwHNPyt(KDdPhcSwS}6Wj?-aEB1w-Qi8n`R@Jh
zdhgfUYxQ*R-Q`ugcUSd9sVd80pc0|Nz`$V0$x5ojz`#PF<5B<;^tZx}ml6gBwai9B
zLRC&ef+>B@B#gRB|e^>X$|Qpwri)2uKPLmLZ>KpCu@15?=O{TO6Ar35Ww1
zVx(u--H=BW5}l`jg*Xp4qz|KJ7h?vGXY@%*Oz$mPLBU0UDd42T`*ciz(`uY&SL8O!
z_hd!DH2`Ml!Y&_#mx4^8^obJn+PO$(u_}yqe8$hn~6^#u2Fbs@4;QY|S5>V7k3N8gKQuGLE
zWbyV=YmkVHS%XM%$PR)*crb!dWB4?1q6O3eKD2aLKP6lw7R0-@(SZ0=N1szt#-;8M
zdctMVe^V=MTlEE|(w#fPl=_Ab$p
z-V861^H5IiHno;Q7-XGtzp{U4qx|_!dXz)v2uonZu(7Dq&Hkfl7c<>iyWTHB!tyh{
zov8Mn4|+*tj1|cg7pCJJ2JdxrK4Vi})1B-7dg7DYRIAJSnSh>U=`5f2Y8V?a!}b{>
ze>;J5*E}(MopO^y`%-Bltnh=Inqfs}Nx;|PRS?#dwTM^$2M_DSv@I*GI1$^ZQ|cUc
zA(FzDcZsliCoiYs4{rFDPp-D1->uR{Z_siqfiQkBq<3&Cz-V3KAh+CZgLL-UNSPlX
zgFY3-;XKa7w*z165J>6}-I&mrh+zuX4oBU+WHJLHfX5G6j>keUBmpvcmg8@KA$Bbq
z!u&}XyT4*dEOUbin$wP#Oyb+h3{W5tMQ{m1)eJ(KgbnY))rA$B?LDCmw
z83g}Xln5BiTuo#LM+aIYip&O??Q{22wM|N$6l6CVGgDDXt$3
z4iI6Jf8ET}(t;NDKfCC(&n8Lh$
zYmIuGrMINPJ9#>J^94->3X`*YNNxfTtUt(wqF=%(&1XQg1TyGoIB{})7T^nzRGR!K
z;i%=P|0u-{Jbuh~Fh=!_u8AEwH(@JkE1oZmL4tj^2N=29oR>ry(JI7s0C97n8QqY~
zf&7HvgzDst1Mn7$Ep)!mV6*+a#tSxpx&si18weYS{0b)`_K9u}iv&dsft50|I@<{{
zsn{$<@Jf;*D+sfBTx?K0|{
zEC;Rkr<70czS)0cR7cyV+$T*}oTF<^H2+EX$Nmq|10cjJ@(z50FK#p;DQDlVcz7)99ounJTe
zyh;XihTpi?cfPpTuroW>%IRq6-BFv#p~7d<
zVtn6mVTJc2I#oV;SUFibnXc4Y%X}6%%RC!enyMwUqp>S6wmUkJ8|d;v&80i0%c5zY
zyaaB@{AhkabRc{ne(isdIwSMmn#h6ZCs8XGt2Mf<`g^1I_Zh8RY#%$mF-@gkY5K6Y
zXA75pFWfGpnPQwjn8}#ro{XPdD>kZnmW(Lwos^qgte7t3EG;W(mGx|Js5qe(3L%qV
zlXI*xj#z8%2o6Yk`S2|LObKrZpCT47Rv}hJxi~rN6b2646rwK2>ZR}B3!1VwUs#yq
z{n<4_)#;NkDqlT7B@A7-)
z%Qu&f_>K4__%h6728{X-dJgq=jefS4cKtPs4L_`u8gCkntyV3b2L}4Wtz0Jy2UE;i
zhdk=+Gmmuyd<7DXvRA9yEB&*c10EfalCee?hMNz9oSi&%cbJzkJO$58sj-?95Wk
zR?mv($6$G{J7nNxu^Cj?$hq9I(lQZtg1zf9^nF5WB>7l>^I=qJFz!%$|0cwyPw@kp
zd51@n)7zF@XYyO8E8VTB-%%G==cBhrSL#>MXN>1Q2NlQJdq#&s2j+)$!$tjXw+>HR
zr<*drkN>XzsQWeGCcyS-=~?at{6h0G0T&A|2|E_EAr%ng!E%5xgNBR1jTB6wJz1GY
z*NxQ86cUKh9gYx@6fTI_fNn&(ge8q)K<4rONTS*I?&fR|D;(={WTR}$5KSUi;+9&D
zT9}&u)XtP`fn#6sz?pf5nR88IO>&KSOgsLmo?vsIhuqp{WCe_rN<$y}^n=P_;>T16L>!Bt+fnSa?4O0Yju#?o2Ins%e^>f2jViW|JRB^unmZrIW=@fdRC_%h?
zI`HC|ZpsPp?qeAHKY5ijGlWK?OY_i#eHn&|#Yi_GFLL*!nn{;QH+;BuRNrzWbCvK!F3_awxY)F9Ak6dX6D%dB)}30BRWTn}
z`&4pQ^wS^)2W4+69I9%nO881W+Xf#tcQ8NVH!I+L<-n`|bbDz|_~#NS~nXkXsCvc@+645amO43L*nnuI!V{r8Q1
zWqo%oUq^JRyi8hVx{sQ@X73MLY~$z0jjaqF+rXY*Hw%x)I6D_#JPM~P>o!xKD+-t2
zG-bBpdl)ru`4nAO?yfT3>fSo6V79GaQ~VZFx&3_7LFOANM@#60ht%R>IL|EZV9^OyZ4?#8+*$op)Ii+-X7uD
zY53%FmV7qbC%cx58h8?DfA?@@;t;q>5=TbrNA1IT?{O%Q?5TBXzt$9om>~50=>}{j
z`1*Bt@IWP2g*5l8aA(@kL&&50)9jw@CC{s4`xl!pU&I?Dg@jP^fMGb;FkS|TFnmZb
z!}c(dF;+YKHo+fWU<*;6=yK9qiu&IK@$Iq-n(4c8$
zwZ6~iFaQ42E2P|1bQ0$_qp3JJaA*|$b+zNwCiwl~`cWrLOD!S^Qh>-NboUx1a?oDG+{Mz;!PVN)jXQIZ0r~*NNmkDl
z1_qzzZ-bRne|H9@KWC$<>!z!u$Zzgw&kDA1G_z#&vUmDR2S(6~A3C(RbOTd(+1oj|
z@_PwU{R6=d9shmIMn&-th?}htm9CO1g@mJvB?UJth!sR7j7mX4A?RZ9fnQxx`akH<
zJ0U7-H#aAKHa1UBPgYM(R!0{rHg-NfJ~j{s8wUpq6oSRo+rbUp2*f{V$RKp(APO
zYVKm=FQfmx{*}|x%jUnF99;jS7F0pDzcXy?tRS}kr42;Kg(t`fdL)+gT+6Xki=AO_(dkQ&8F-jJYa6!mZN
zU*fM=Npah*)|OcTXv`E+qEe-BROMY?nGJ`3#n4Cox-vrBK9?-*`7Z07J}cVc$N4k8
zo^A2;PlA%m&L~4?`k$tsJn3?tGK)dn;z2+P1VC`H3XN0
z{fCJxz)K#$lyVI5{4Kosd#nFT&spi+pwr`%u?H|=T_Lw#*=!5B_GvR|si
z6nUa3U%QJhA!GPQ&`sGbtwz4r`hLh06_8BNV1g0DR|`dFkme{BN9
z19(XT)=7m>ScnqDLJx2mhCT%oWodM1`PS3@qX6J9fRMmM5b1J9a!=n$ocDRi7|8ia
zogufh)@Z$ybm?D0xCnyOkjAlr-h2rWxh=En0p`o-=(kB)oq`7MsQ*ieR9__o{_%AX
zo3?B&Aw-63EUeOZ_lA_-xEM{}y()reoQAr>rcsk^7#S3PO8
zj45`jJk9brrLDM@1;gG_XDj8V_>WaGq`El8n?0_Pl33QJxj(VB-Fc?97MrAzHkM1A
zhuq|%UWM;7h$R5Bq4J9A{{=RM(I-5|vDwfmkBe9sbdljlzfk#0D(DaZq@CJ4fG3|F@eR@C4o41)@ho;HXN|6ZJU2wK!9ZEv6>_%}*{`I6f{wWK
zH@;Y?MIk|UMpOvzRKH38r6L6lD_P@bM08?a9;cx18u)e#m)G@O{YtR^m@w$?ylL>1
zEw$a{Co*Wb0^!yufmq_aE_+tit9DtQdi;N)<`kU(vK$i7xeiGRDoEF-ydngX8)VdI
zXiygr1V>`Q^=8RZM*EU|SN;nlK~Vq!inzg+>(?xM=A|LNH_?;a<%^PX!yj8m_=25E
znDJdrG5k!G63WgiNK_Ifcsq422it@3%V8W*2XOqwN3Hb!SCI;4E2eJllgcn5!#|>S
ziBbH9beW+tGB};p(-agT7@VnDLf^pZs30~#@V4kT7?6*jZ?R@W^%(8
ze>B6YZ$T(fM^FJIvw-|fxo{?a&dgLlAIJ6~C62vi+b<)IU^PJ~Ss)ESG6}d>LQ$VO
zRXwdLxEBkxW}6h$zD`$X&epST{DPb)r3Ej+6b!F;GB6O#8^1`WGJb}|Qe23>6c-Ao)_S5pMKIV70-
z(?zbhQcYW!!_%vi0!;1?@0upo8TA4rg;`hGB(_cRF{@`v-c%_664BP!;wO&Hx{kZ>7NXL>fU}d
zk6;1M6M_=iWW@dO-EEu@{5}fsx46uBl;Avy4I;{BJ|4BNw3hj&8?;!1cdyF15ldPT
zOY!DgHxNRRTFL2@j&C&peHrnCLTd=G?XOHKx1K`gO
zfJPEqivqog+w|XeWSn{fm2#n7?Bdteqf%W!9G26?pQ^S=49mqjtFSl$Yz+4&Z2~!Q
zQ{B6Ew!4aUdNOgCn4m4l#(P4|3S(U9c>9KEQuawPKGuTY`f?BL2#73uV#8taL?~91_
zDUE%(fdrXgImH4kSU;c>h;?i{y7`hf7LvSIa&`V?I&|wsrXkeH&@2UDRdPvt8tA?JsnD>6Tj4Qg6J*YBTxCbY31^W3SDcUiVu%M6+veB*1r
z+%M50e9g>Jtb(RQ?**S^mD3qJ=DGB--vfBwV|6)T!(B%0Yxq2s&wx8V>l}xHLw9O=
zKPfqieEa0eVSa&{b~)vvEZ0mA?#WcGxft0*d?Od|a{ks@la9>N{_Jc=wMdL1|D@nP
z(^UO8$L0`gS@Xm_1>pz9$5T*eZF8rw4JeDxm3=hcc(TkSlKu@}&D!^g6i^>=-fKUVr#5PSG>Ya1x7Y@mH35DL~qIo;xl7TC;!K
z1z4L~%{gydFgBVI=R_%YRjAtgJEAzjq%FSP5=Bl8p0D}u2DV0Uo#hA|Lo1!>tkShK
zeP5$AB`=e~06L-R?hC`&c@_r9^4BhyR?^G#Vt%ed$DcQg-<^B3gK_epl;ip3p@I19
zfJx}uY{olButj5+UxL9kNuHh)cg4drJOpETKq5Y?*>`QPIjq`m@s09(mx85h`R-4=
zTTR$0D4S#WI2#+?YY=)9(QfxU&E8y#PHWBi4I&A7VG{2nBMQ%J8cm%?LGd-Lay
z`~xJYdrn@ppwXg{87l-Q*yBJSZK+LbOkOyCUa#E+vkxPEqT#SlMzu4i1%ZnRL%zXr
zhF}H8kKQc?_|oi*BmcJlxu|?_DoVH{Oy4m#`Db5I1C8tGB~xjqSNDfB-hh(g1+^A>
zRyHTADBfckL}XY0*6rm;;L63%3;YHerZ2RRZ#2sk;2AUR`HP!L2|n;x;Z(|@fF2gS
zU&A_3+iFE4i$Fl}5fWu}y7?k{Ei`K{VLy&+7`wWTsi
z4~-Ts-W$Q?)izcI>2hGc%k9**Ffl5$ELsF^DSltAh*d9Jlc9Q=frO5VtVTy%i9$s}
z@L=kfI88xnIxc_^S?P+6;Z;EO`uE~<-PObAQje^0m4=ZUDsiU+PwWb|;~ILMLh5VC
z@;4_8YY7~IsT=Ck9^vYkX
z-Y8<&Mt@7W=Q<(u#vh4GxdG(2A=sNJ>=Sroh?|hvyskZPm8$w8F=$tCM>H25x+Rj!
z&edHOxP8G@tXkS+W3(nh{~+I%;3)--4cyBV%Vv%PI~J=MLN*;PV+Qdqgi@lNV+{`_
zh~4e_Ak&3m_}KUM3jrCK?b~=Uf8h{}!%l2tf)6NJ!tQSlG|BM`ZZtb|b*tU-wnWS#
zc%3)jeTGmRj4s^Zb|;Di8V^0Idhj~`X3o--PO^R;T8Q^8;?k_TMx9s6EZo{S+stjT
z6wK#FvTsooq#`aAssrbh<_yIvGMBO(8}~mi)Mz-ptT?7Pz3nf2SZu!px%oZ{T_?2$
zDyHkdx~|SVjHI3fO`W5)Jm}}A2Uf5OXtOs-0BVI({XWA*nZIUvO&7Cf0FPKnymjEJiDrpN)hdpI$Y5kI(4_NoC1>8<2?)3W*1
zD*BOH`N`wF7yBGdg
z1WJpa1y9wTI#0bFdcGgAR{TvfE)LkTwciO2EG6%+FX&($B~Slg$u}wFCrwC&P=rpE
zZnshdq@6qI-$2@}?}6ZD8#GWT{?$1cCHQ`7e)b_nXrR$~Y>CG7t~jN32jf}|qFQzV
zyxUGi3lEP)o&O#iX2>nP$X#pSFpBO*N1WErE2c-cIUUAu1Z?jg80d<2r=EH+X7Eu0
zObFc~jGCAUZszOxmM#o6G7UJ)ley@Zk+
zJ8>m*`_I*i{D+s=>a%GcVrFGF6!2rm&rf`=($rq+BcQtu<01rf;;7ezgR57@&PPqO
z=hcX}k;yM(v|{EGL}T3T_KG)}Dbia9WO|bt)5uZ{
z=h3WHc+|K4w0(_xeGIv?$vWlEAv)z$>xGbN_e-(8>1Tg6e~p&Wt(FpYWIRybh@tL>
zeDG8Hq7w%hr-}5oF|g;+)u(4fDu)}D`Wt#^6*HwD$R2O>!PGvId@X9$qr0!X!T)Fo
z#d`7G72fAnuSnnt<T0dd)s~Ih#Ogd2H%3}FPKh>1-fQn@KFFmT{IvEpO!u)&=Rk}}#=yB;QZi4PQsGk{tJ3c<
z@UH6Unn@nzl7Tp3DyzMA)lUmCHB;Bg-K?Dbq~bgzuEY*WcO`K
zM?h%uL3v>aVc;%tsT#6j`DlKIXLLqI!fw8Bj1JS)rwF0=NV>Z%{R}^?U5k}I7PJwr
zg8y~O-9{F0oJrTiv;IV%xohN4J?NQC@3>
z^Bn_28XaSI2kPxlTb)eseajT}`p=Vfa>8utemHMMe*@_Qz?b3hlb!Psg{m>EtaZ_8
z0r?I7=RKCi>f>JZc$Xy==J@bfJwlMV@9*fpQjP^aS*2OswF61nbuCBrQ?BouIWIND#y^RTt`yFNBB|pvyIO(8gR9thWJ$?=ouMv+}S+E2I7~f{M40bpgjajRl_1VOFWkBJeJUheM-v4gs
zG0x*pY!H7UP%qg){Xh
z^mm&P)4%lYNjt5nAjI>pK^*-)c}vxPVId#~7i5XmUt!7Ld6EfL*)xmDP1(G5sJ-hh
z)N~zQe9ud_k-EarI?csVeAU&Mz-BcUV4}K&nz1W5&)COseRNp7FUQ#J$tjSe;_%XQg^|qqQoI4>^7%*yku+rk%
zvoy9DljMvNJhmG6aK+b7K68J;<@|~6RDyoBzWke`W?9RDK#Z1v@>~qJxak7lY|erQ
z$kwy7FEpx`ky5u`sEh;a_mRp&SLpDcAiL$JTLN@a5YM&ll?wbNDNhq2Z&DV0SFG8FX$$?J9+#vBwJCk_;Vs-
zP{j89$qdx6#o1a*)-yo*v>g-$JfavW8NrXXa;;ikJyOiIoo~{6wx&S{kI8&%8!BBT
z{+h2ewt>k|rCZBm*x|w=xif%rI?(@QdRDCf7*%PwfZu$&Uth>)%FKQ$N7DRe9gxP}
z>Z|LHUI6+#IR&C0^BRe}iRS~O0+N2Ib*ikiWXdg@J1%PEc7psNu73zFeTsUtwwdLV^
zwBv9#_NH*u(-8=ns35*dEOpWs?fNk39E_4@$()jT4P|7n3N7Ju@kU6y&ugV(>EM=+^8kg(1QRT#PCu$I6IjJ$pzHmv9Zu8>(hDT`7
z)#m!h;PhwD*+G6RLnr9O|6p1F_b1zhaNy9?k%DlN
z0Pbbet%s^4u5f4aygei6-itowBz7idKMQz<4vS8?x4U%C49!OPprCI=<6DX)9SY7%
zP
z3lW?Hf}SOT1_o89TBdWMPg8GXxrOR0zB&9
za8z}0bsd#c8S~RkrH5{w?&}@5IWM6HmHZ=~9i=A{*H;ztj682Zq&$W~b8g#5ZWEik
zSqy!N^~7uZp7w9*-p0pLiKc(LUR
zAbC|h*|dm1rmmcGBU8$^>@VZ#@;$PN8a1<61Tpn0$f}dsd4z>+>96)yhYh86okwA4
z5XQ2X+qX1%-49}aeZ0-#>AK?cq~DeeBKX8^sszw(xw?99J(H(9n59UJoPua;K1go$eYT(L}}qvDcJCHzic1K`19pUn%$Q&A0U=D&h$E
zT>~vT!nEtVbr4)!jEiTFqRT2Nz3HNEkwrjyQUu7!$yGmowZaLOj-Kj+3WY=HCXe2S
z4Ie;@;G>1BKrKc-7YUhU@kPygp`1@fCX%eQrv47(1bsWT^RhplQKhDpIv414u9#=Xe@6
zSlEUeyeKDC#5&)bMR)2}AYSV{EGh|Ib$sq~We6N{eq~Ki20n}G7-<)+++rqgNuw6TiUoR>J1KlA~EP#c1=LKguWLRc8xzP
z+|iWwZA2$Sf>6^Sk+8%_zZogBUN2a9Z@b+xoz9ig;a^ZcnZ1^&v~hjz6ZCSgnW>p}
z2RWY8C-I@P$^+;b<>=oKK+C;ZE3rTBNA%7ODrh;8Pp3b!CB}it+W7Cg17s4qrBKAI
zCO%bwI4KkXtNaAHIXND;`wVE@`aD1y$k`jN(a?F!tJ0dt?(emm$PTq~=#rtZU~yAX
z5KlC^(|d=!9(dQhyDwZEvXuw6AJ~FtVwQgVqWNA5wUCMy3ty|eoLo5O8$oKVo{i`M
zuN1gKB(p9VBtJR8lx~^K?NMHc>(=gLdg#rAY~b&-0YR^4GxN*%umQ$AuCN{UW_n6M
z$=6|Ci>_2imlx^-?F9`bf%D`#)>W*}XsAvCT@Bu%SKr3x{b(UI7It7Ox~jP96)7#{
zRr|g3-NT^Vw%bc;dz}_li?VcB|y`oX)#89K3qFVVW^Y>O)L<@6k{xn?AZ=
zkl_fO!x|q
z1~J}e90(3%9sfdKq$<@aqu=!g*^A%p)|}=AR;A|%KKgUw*%71Atdrb8)V>>X+OmH$
z{%#It>q^0@oo}I8F$gi;l;#Y+I*l(m7R}Z29
zLe-}l&Axx83utXt3#?AUi3Q$&XAglZm@jc(1JNUJiJM34(<*Kac$Gn9S7ZzbuKQ18t$H7>(J#pChXIHA#NNE^|`kF
zYEdEX@DRDUw|0(Y?s${MxHAxUir9N{r2k#{HjIPPqA5Ui5zMH%BbN8seTl3P@@e)0
zme+SKSHwYJyI*BI+YEe>MjZOHr9ZBQt@cueKt?&D^|G-Ly5qVO1v;xYt1KhSRbi>ppgu8v)Av`%7XozpwFgQYh4
z>;pI)x>M>8Bg(?KC}N1}QUKf2#a0@{OfGcl1KFLUV7k1$FFYjs&3KiI=X|pElfSgv
z+uBIY*|(7&bAxgJbSx9BYmkwmyXNW!JF_IoacX~B4b~OI&jc=X{BGep(V@=awS++@
zSP_fV(u&$tuncWjjzcaJ9-ym>@IEHwLFs0Q(pI;e7aM!i{Hp71|Hq>1V&RFEod>CR
z1F7}xGJ%nRB7uL=io>%hmc@*`PU-!o!Jn%oXquW6cn_C>jA(+S1I
zJXkfSh(!U<9LGTGW)n5_)~U9O2aLK8Pwh*`K3tJ4>}i-saRcZ7e)x{awGFi`h(i^=
z4cbHZL*-w7jng%4r8+vS8%hXt=wW@xtWo}0GvA38C#q7OIZ_a&)<2)|)#nUvLJ@phZ&>myZE
z_0n|yaJTIpxZRcNi|n?HK>13(iK`autp(+Z)t13k*4t>#MqgIUFL!MF@Y8kj
zALuc%xh%^Zzbh-9mIK|2gAAlg9Vof92Ps!Q^<
zD{?*HMkhpUOSN*B&NrY3Njg8dK59l3T=uJK8WtddKUepLu1?c%^)Gl_2i={b=>{A-
zncAoIe9j4Hr7YL&K+Mr^)<-2+b~=6bH{BZ=*F6Wctjr$gwGF*2Zv$wQ@EGFiD8Dv5
zoHFnu!y!HFwYK&avkQR=)DlrLFkL?xJL)FCAikXm@oXv!Bp76GuoWh|KC@leORfI6
zx@E{x;MP;Z7!KG9iH=*|5nJ{7eR`fvi$Ito@%Re+Rc52()0ailN%j@DJ?p2n-!_ly
z2KDU$g1D&*bfv50glKd`3fnPn(BCAIm6heBpvDpL6k7}XT=7C!DM1#-aOIIlyMagi
z)68`I33Eq5<|tdX)NFni%}>nkal}OggDbvh=$&Pmni!!Ym=5d#gw_J<1jOb8gy#t<
zgA-BR3q-yqY|Z9LcM)m}kZQ}_FREpY&&Jy-Y`Tb5?X}4gUs5<6v^zUUcP0XiN(YH8
z9@?@~868;L>xIkxt&rYUsh7Hy0f^sD5RpSxO#KM3D4dbe(hv`etF!sr<5GV+)vSK1xQ;U!3g}mz7rO+mFkK-|v6>
z3b(dm-FbzIl$z;W+Vi@~BhtfSM6MO(tqjmTA|Ug*aj<;Kk$qA_L+ZO}-5U<`9SqC?
zH)P-Gky1^be6EkR))n6jXdiMbnf^Q-%$Q}I*y5wCrqFsC8iv5PhJO5Q){+MWqNgn)b&%y`}QIE!8&7YmjdXfOP4YR*q;?JW5i}fJHgvC?RFxSD8;p{Z1GAMEk-(tl6R?pm_eS~0
ztGBClaMp;|z!~E-210XxR1u`h4h+|L7u%e5H6*$xLeEsNW~ioZ@x<=!?)I8yH_$uI
zjuQNHe_;8>3}VOvZTM
zjALZ?6Fy2%o-vu%*1#<{xBp&2EKd?jw}Xu~5d!I3q*MToG-hv%
zy!4;ULsQVgzN^4rABRDL;`&Ki<6Q|S2#hU%@H_D}(udg
zh)5#A`jhkBQB^A*Bcd_UjqD-({24cEF
zt$o7QIo?2vP~Og?+wpl5s`qYo#`Fq~1Nqhb+Ws5QAcb3r&Rl;tk!dk3254$6=
z!H90l{+Y57^RwfoB72+qgGk8g9ryBi>FJDi`8$Lo`Q|iX4%c`5R+*>o9nVh-3Zwb6
z{olD4)QH)Nn>G)uHsIbI2^T=_I%*EpF#0NGz`3MnqU?1Lq1O1W=BX1Nlne7Ol=17Q
z*skUvbbbeQx*AicHtu$pz+!FGhJ#2@3sje_zq;tk0hn3Qc@*DFt0<29{CP^l%&w!A
z_$rNIy8XjuZfDpQ{Y?`AW45*6R1T1!v2oyIe1G(JJN9?Dfvq^GO{GOUME6eis9%6-
z&_p*exXX!lI?h9g`y|i2Z==?%r`&$JI=@DJ-{8hJ0Lt=2rs_f2zTaHjltj_1m>ivK
zZb~uY%I{$&75vO^&Qh0)DG$g(k+R{@t>8%;)hr)f1K+Z2)R%X((AgNZ&nO2$*68f0ak{36fbrWfnF
zHkxQ40drAiWZvktN^1_=CDdpK-kd1~8!ofECOvB!TF|uLIH_q8)%W?>Pln-GxrWji
zc*0UbC=XEoNJT6edQ>~Y3gJ2|l0h~2I2@;?GqM?*tAtABl(ZX%LI_oIhoHIplG$}cUf6?{7fnKDmc`=8~ffNd8
z9l{()neALuCcuH~jfff{iI{ek2PhMAx(^c{T6NK+&dTUT?n1f%P8_N=JY3NW^eEsh
z41R5NR;)tv+(0z=$5GTIr9=2d77r}zehVQ+f4#yRa<_x?ELUSq_~Hq
zCIj8eE={i|og6JsA?3jnMaS5ikJ%(DaD9{=Qqi}TR1i^>3k|ZLAaJA3Ow{Uw=uOCK
zG@ALy!MYvwC`$0=)!{W34Z(2`}D9Ag97Tk{EiJdNmN6aI^-@K3*)AysfSg7TBmU|A10D%TZTb6&|J#FsL-(
z7S+P88zi<)LSV?KNY^Ug@M~!ndDs9gf%EkmYcvWfpuUpg@X*T_*%6
ziPOIXVB$KEZTWVI@fZ6a(iUsCSN_`m)KRxdo||1vB~us5=r)PSE{cxOC2EZ=-Zu8e
zYrQ3%rh4Dzuu-WQg)s`&EQkOA308dvX^Vr;JGy!sj6hQVg{Y!POn=K*zKO0=03Lee
zSMllVV(@2H?7w`;2>?>!k@JhNr)+d}khjL!0HsW>ol;F~_&ykRAOXCZ3V@Hv06qy9
z8Qge|t$amk))JG9(GIRwQ@nyT`uq2R@h%BI&PXaZKamZeacMqo$vu4>JW_ZtuCTU2I*kbitKAex5~`~g`DnRE=h^g>MMFE1k`dP>Q}KzV@L
zyIeyY_&!kt_+VPdiu-Dw;4Ad6%aY}?Hdz>i6>n+H_Ha4>%AOP~iUYS5WK8`09^2Xj
z_)8$f6bl|@RCEI+*g1PoF8H$;+`2O^KReJ2so*m(;P+RO;qjcSu#7gIAHKB)trI>f
z4`$o%inX`IopEOW^$HwPK^pKWW^Ee%n?8rd+J`5n*f&tqQ6OQ*1~)4L$}v}I5q+}1
zn0ms5DL{>$jNWH2PA|xd9Gy^Jf{yE|Uia)L+kQnGOaJq)3)z95e*sK|k~{O+l3woc
zN~hHWX9+(xX2pD_q5OyL@5JC{Wk7i^C}w}k%=MhNo`LqStA<5a=pgdqvD=N8O)GD{
zuLr8DCA*w+eVPL6S#oCoL;KSn`a5ND*{htwyMKaH6(ouxCI;k!S`LQ&6Fs!5B%72|
g$ov0$?up!kC659otV#n~kcN?yQkJX~Hx2oJ0QUgG*#H0l
literal 0
HcmV?d00001
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 9c8fe7a42..5a3660741 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -137,3 +137,10 @@ progressPlannerDomReady( () => {
} );
}
} );
+
+document.getElementById( 'prpl-select-range' ).addEventListener( 'change', function() {
+ const range = this.value;
+ const url = new URL( window.location.href );
+ url.searchParams.set( 'range', range );
+ window.location.href = url.href;
+} );
diff --git a/views/admin-page-header.php b/views/admin-page-header.php
new file mode 100644
index 000000000..023c38834
--- /dev/null
+++ b/views/admin-page-header.php
@@ -0,0 +1,41 @@
+
+
+
+
+
+
diff --git a/views/admin-page.php b/views/admin-page.php
index 234f83679..19a47d13e 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -17,7 +17,8 @@
);
?>
-
+
+
From 31839d7a14edaffbf71ba0c0e24b70b1303e525d Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 11:28:38 +0200
Subject: [PATCH 123/490] Add get_points methods in activities
---
includes/activities/class-content.php | 47 +++++++++++++++++++++++
includes/activities/class-maintenance.php | 22 +++++++++++
includes/class-activity.php | 16 ++++++++
includes/class-date.php | 10 +++++
4 files changed, 95 insertions(+)
diff --git a/includes/activities/class-content.php b/includes/activities/class-content.php
index aeca1ff16..69d3f1b75 100644
--- a/includes/activities/class-content.php
+++ b/includes/activities/class-content.php
@@ -43,4 +43,51 @@ class Content extends Activity {
public function get_post() {
return \get_post( $this->data_id );
}
+
+ /**
+ * Get the points for an activity.
+ *
+ * @param \DateTime $date The date for which we want to get the points of the activity.
+ *
+ * @return int
+ */
+ public function get_points( $date ) {
+ $points = self::ACTIVITIES_POINTS[ $this->get_type() ];
+ $post = $this->get_post();
+ if ( ! $post ) {
+ return 0;
+ }
+ $words = Content_Helpers::get_word_count( $post->post_content );
+ if ( $words > 1000 ) {
+ $points -= 10;
+ } elseif ( $words > 350 ) {
+ $points += 5;
+ } elseif ( $words > 100 ) {
+ $points += 2;
+ } else {
+ $points -= 2;
+ }
+
+ // Decay the points based on the age of the activity.
+ $days = Date::get_days_between_dates( $date, $this->get_date() );
+
+ // If $days is > 0, then the activity is in the future.
+ if ( $days > 0 ) {
+ return 0;
+ }
+ $days = absint( $days );
+
+ // Maximum range for awarded points is 30 days.
+ if ( $days >= 30 ) {
+ return 0;
+ }
+
+ // If the activity is new (less than 7 days old), award full points.
+ if ( $days < 7 ) {
+ return (int) $points;
+ }
+
+ // Decay the points based on the age of the activity.
+ return (int) $points * ( 1 - $days / 30 );
+ }
}
diff --git a/includes/activities/class-maintenance.php b/includes/activities/class-maintenance.php
index 6210322e0..4889f5ecb 100644
--- a/includes/activities/class-maintenance.php
+++ b/includes/activities/class-maintenance.php
@@ -8,6 +8,7 @@
namespace ProgressPlanner\Activities;
use ProgressPlanner\Activity;
+use ProgressPlanner\Date;
/**
* Handle activities for Core updates.
@@ -41,4 +42,25 @@ public function save() {
parent::save();
}
+
+ /**
+ * Get the points for an activity.
+ *
+ * @param \DateTime $date The date for which we want to get the points of the activity.
+ *
+ * @return int
+ */
+ public function get_points( $date ) {
+ $points = 7;
+ if ( str_starts_with( $this->type, 'install_' ) ) {
+ $points = 5;
+ } elseif ( str_starts_with( $this->type, 'delete_' ) ) {
+ $points = 3;
+ }
+
+ // Decay the points based on the age of the activity.
+ $days = Date::get_days_between_dates( $date, $this->get_date() );
+
+ return ( $days > 0 && $days < 7 ) ? $points : 0;
+ }
}
diff --git a/includes/class-activity.php b/includes/class-activity.php
index f3be1a1a4..f436d0d77 100644
--- a/includes/class-activity.php
+++ b/includes/class-activity.php
@@ -7,6 +7,8 @@
namespace ProgressPlanner;
+use ProgressPlanner\Date;
+
/**
* Activity class.
*/
@@ -195,4 +197,18 @@ public function save() {
public function delete() {
\progress_planner()->get_query()->delete_activity( $this );
}
+
+ /**
+ * Get the points for an activity.
+ *
+ * @param \DateTime $date The date for which we want to get the points of the activity.
+ *
+ * @return int
+ */
+ public function get_points( $date ) {
+ $days = Date::get_days_between_dates( $date, $this->get_date() );
+ return ( $days > 0 && $days < 7 )
+ ? 10
+ : 10 * ( 1 - $days / 30 );
+ }
}
diff --git a/includes/class-date.php b/includes/class-date.php
index dc45204ce..bddc3cc2f 100644
--- a/includes/class-date.php
+++ b/includes/class-date.php
@@ -111,4 +111,14 @@ public static function get_start_of_week( $date ) {
public static function get_start_of_month( $date ) {
return $date->modify( 'first day of this month' );
}
+
+ /**
+ * Get number of days between two dates.
+ *
+ * @param \DateTime $date1 The first date.
+ * @param \DateTime $date2 The second date.
+ */
+ public static function get_days_between_dates( $date1, $date2 ) {
+ return (int) $date1->diff( $date2 )->format( '%R%a' );
+ }
}
From 4fb98db33ba00dec7dff1c57f5972a9bf3081153 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 12:04:28 +0200
Subject: [PATCH 124/490] WIP - rolling charts
---
includes/activities/class-content.php | 11 +++--
includes/activities/class-maintenance.php | 8 ++-
includes/class-chart.php | 46 +++++++++++-------
views/widgets/activity-scores.php | 20 +++++---
views/widgets/published-pages.php | 1 +
views/widgets/published-posts.php | 1 +
views/widgets/published-words.php | 2 +-
views/widgets/website-activity-score.php | 59 ++++++-----------------
8 files changed, 68 insertions(+), 80 deletions(-)
diff --git a/includes/activities/class-content.php b/includes/activities/class-content.php
index 69d3f1b75..a7b869489 100644
--- a/includes/activities/class-content.php
+++ b/includes/activities/class-content.php
@@ -22,9 +22,9 @@ class Content extends Activity {
* @var array
*/
const ACTIVITIES_POINTS = [
- 'publish' => 50,
- 'update' => 10,
- 'delete' => 5,
+ 'publish' => 20,
+ 'update' => 7,
+ 'delete' => 3,
'comment' => 2,
];
@@ -75,6 +75,7 @@ public function get_points( $date ) {
if ( $days > 0 ) {
return 0;
}
+
$days = absint( $days );
// Maximum range for awarded points is 30 days.
@@ -84,10 +85,10 @@ public function get_points( $date ) {
// If the activity is new (less than 7 days old), award full points.
if ( $days < 7 ) {
- return (int) $points;
+ return round( $points );
}
// Decay the points based on the age of the activity.
- return (int) $points * ( 1 - $days / 30 );
+ return round( $points * ( 1 - $days / 30 ) );
}
}
diff --git a/includes/activities/class-maintenance.php b/includes/activities/class-maintenance.php
index 4889f5ecb..901c29971 100644
--- a/includes/activities/class-maintenance.php
+++ b/includes/activities/class-maintenance.php
@@ -51,11 +51,9 @@ public function save() {
* @return int
*/
public function get_points( $date ) {
- $points = 7;
- if ( str_starts_with( $this->type, 'install_' ) ) {
- $points = 5;
- } elseif ( str_starts_with( $this->type, 'delete_' ) ) {
- $points = 3;
+ $points = 2;
+ if ( str_starts_with( $this->type, 'install_' ) || str_starts_with( $this->type, 'delete_' ) ) {
+ $points = 1;
}
// Decay the points based on the age of the activity.
diff --git a/includes/class-chart.php b/includes/class-chart.php
index 768bfe6df..f1ad61570 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -42,7 +42,8 @@ public function the_chart( $args = [] ) {
'filter_results' => null,
'dates_params' => [],
'chart_params' => [],
- 'additive' => true,
+ 'additive' => false,
+ 'rolling' => false,
'colors' => [
'background' => function () {
return '#534786';
@@ -94,7 +95,7 @@ public function the_chart( $args = [] ) {
];
// Calculate zero stats to be used as the baseline.
- $activities_count = 0;
+ $score = 0;
if ( $args['additive'] ) {
$activities = \progress_planner()->get_query()->query_activities(
array_merge(
@@ -108,31 +109,40 @@ public function the_chart( $args = [] ) {
if ( $args['filter_results'] ) {
$activities = $args['filter_results']( $activities );
}
- $activities_count = $args['count_callback']( $activities );
+ $score = $args['count_callback']( $activities );
}
foreach ( $periods as $period ) {
- $activities = \progress_planner()->get_query()->query_activities(
- array_merge(
- $args['query_params'],
- [
- 'start_date' => $period['start'],
- 'end_date' => $period['end'],
- ]
- )
- );
+ $activities = $args['rolling']
+ ? \progress_planner()->get_query()->query_activities(
+ array_merge(
+ $args['query_params'],
+ [
+ 'start_date' => $period['start']->modify( '-61 days' ),
+ 'end_date' => $period['end'],
+ ]
+ )
+ ) : \progress_planner()->get_query()->query_activities(
+ array_merge(
+ $args['query_params'],
+ [
+ 'start_date' => $period['start'],
+ 'end_date' => $period['end'],
+ ]
+ )
+ );
if ( $args['filter_results'] ) {
$activities = $args['filter_results']( $activities );
}
$data['labels'][] = $period['dates'][0]->format( $args['dates_params']['format'] );
- $activities_count = $args['additive']
- ? $activities_count + $args['count_callback']( $activities )
- : $args['count_callback']( $activities );
- $datasets[0]['data'][] = $activities_count;
- $datasets[0]['backgroundColor'][] = $args['colors']['background']( $activities_count );
- $datasets[0]['borderColor'][] = $args['colors']['border']( $activities_count );
+ $score = $args['additive']
+ ? $score + $args['count_callback']( $activities, $period['start'] )
+ : $args['count_callback']( $activities, $period['start'] );
+ $datasets[0]['data'][] = $score;
+ $datasets[0]['backgroundColor'][] = $args['colors']['background']( $score );
+ $datasets[0]['borderColor'][] = $args['colors']['border']( $score );
}
$data['datasets'] = $datasets;
diff --git a/views/widgets/activity-scores.php b/views/widgets/activity-scores.php
index bb6d598ec..c034d582e 100644
--- a/views/widgets/activity-scores.php
+++ b/views/widgets/activity-scores.php
@@ -32,18 +32,26 @@
the_chart(
[
- 'query_params' => [],
- 'dates_params' => [
- 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-11 months' ),
+ 'query_params' => [],
+ 'dates_params' => [
+ 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-24 months' ),
'end' => new \DateTime(),
'frequency' => 'monthly',
'format' => 'M',
],
- 'chart_params' => [
+ 'chart_params' => [
'type' => 'bar',
],
- 'additive' => false,
- 'colors' => [
+ 'count_callback' => function ( $activities, $date ) {
+ $score = 0;
+ foreach ( $activities as $activity ) {
+ $score += $activity->get_points( $date );
+ }
+ return round( min( 100, $score ) );
+ },
+ 'additive' => false,
+ 'rolling' => true,
+ 'colors' => [
'background' => $prpl_color_callback,
'border' => $prpl_color_callback,
],
diff --git a/views/widgets/published-pages.php b/views/widgets/published-pages.php
index 48a2e7155..ecd1b62d8 100644
--- a/views/widgets/published-pages.php
+++ b/views/widgets/published-pages.php
@@ -78,6 +78,7 @@
'chart_params' => [
'type' => 'line',
],
+ 'additive' => true,
]
);
?>
diff --git a/views/widgets/published-posts.php b/views/widgets/published-posts.php
index 240b062cf..aaf0f4098 100644
--- a/views/widgets/published-posts.php
+++ b/views/widgets/published-posts.php
@@ -78,6 +78,7 @@
'chart_params' => [
'type' => 'line',
],
+ 'additive' => true,
],
);
?>
diff --git a/views/widgets/published-words.php b/views/widgets/published-words.php
index 016d473f4..1aea7e967 100644
--- a/views/widgets/published-words.php
+++ b/views/widgets/published-words.php
@@ -107,7 +107,7 @@ function ( $activity ) {
],
'count_callback' => $prpl_count_words_callback,
'additive' => false,
- 'colors' => [
+ 'colors' => [
'background' => $prpl_color_callback,
'border' => $prpl_color_callback,
],
diff --git a/views/widgets/website-activity-score.php b/views/widgets/website-activity-score.php
index 49cd19774..3175d8134 100644
--- a/views/widgets/website-activity-score.php
+++ b/views/widgets/website-activity-score.php
@@ -7,57 +7,26 @@
namespace ProgressPlanner;
-/*
- * Get the content score.
- */
-$prpl_content_count = count(
- array_merge(
- \progress_planner()->get_query()->query_activities(
- [
- 'start_date' => new \DateTime( '-7 days' ),
- 'end_date' => new \DateTime(),
- 'category' => 'content',
- ]
- ),
- \progress_planner()->get_query()->query_activities(
- [
- 'start_date' => new \DateTime( '-7 days' ),
- 'end_date' => new \DateTime(),
- 'category' => 'comments',
- ]
- )
- )
+$prpl_activities = \progress_planner()->get_query()->query_activities(
+ [
+ 'start_date' => new \DateTime( '-31 days' ),
+ 'end_date' => new \DateTime(),
+ ]
);
-// Target 5 content activities per week.
-$prpl_content_score = min( $prpl_content_count, 5 ) / 5;
-
-/*
- * Get the maintenance score.
- */
-$prpl_maintenance_count = count(
- \progress_planner()->get_query()->query_activities(
- [
- 'start_date' => new \DateTime( '-7 days' ),
- 'end_date' => new \DateTime(),
- 'category' => 'maintenance',
- ]
- )
-);
+$prpl_score = 0;
+$prpl_current_date = new \DateTime();
+foreach ( $prpl_activities as $prpl_activity ) {
+ $prpl_score += $prpl_activity->get_points( $prpl_current_date ) / 2;
+}
+$prpl_score = min( 100, max( 0, $prpl_score / 2 ) );
// Get the number of pending updates.
$prpl_pending_updates = wp_get_update_data()['counts']['total'];
-// Target is the number of pending updates + the ones that have already been done.
-$prpl_maintenance_score = max( 1, $prpl_maintenance_count ) / max( 1, $prpl_maintenance_count + $prpl_pending_updates );
-
-/**
- * Calculate the score.
- */
-$prpl_score = 0.7 * $prpl_content_score + 0.3 * $prpl_maintenance_score;
-
-// Get the score.
-$prpl_score = round( 100 * $prpl_score );
+// Reduce points for pending updates.
+$prpl_pending_updates_penalty = min( min( $prpl_score / 2, 25 ), $prpl_pending_updates * 5 );
+$prpl_score -= $prpl_pending_updates_penalty;
// Calculate the color.
$prpl_gauge_color = 'var(--prpl-color-accent-red)';
From d47a0b81386588f836fd0bf52935ff9775a9892f Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 12:31:43 +0200
Subject: [PATCH 125/490] wip - tweaks for scores
---
includes/activities/class-content.php | 21 ++++++++++-----------
includes/activities/class-maintenance.php | 4 ++++
includes/class-activity.php | 8 ++++++--
includes/class-chart.php | 8 ++++----
views/widgets/activity-scores.php | 5 +++--
views/widgets/website-activity-score.php | 4 ++--
6 files changed, 29 insertions(+), 21 deletions(-)
diff --git a/includes/activities/class-content.php b/includes/activities/class-content.php
index a7b869489..ffb673ef9 100644
--- a/includes/activities/class-content.php
+++ b/includes/activities/class-content.php
@@ -22,10 +22,10 @@ class Content extends Activity {
* @var array
*/
const ACTIVITIES_POINTS = [
- 'publish' => 20,
- 'update' => 7,
- 'delete' => 3,
- 'comment' => 2,
+ 'publish' => 50,
+ 'update' => 20,
+ 'delete' => 10,
+ 'comment' => 5,
];
/**
@@ -68,7 +68,6 @@ public function get_points( $date ) {
$points -= 2;
}
- // Decay the points based on the age of the activity.
$days = Date::get_days_between_dates( $date, $this->get_date() );
// If $days is > 0, then the activity is in the future.
@@ -83,12 +82,12 @@ public function get_points( $date ) {
return 0;
}
- // If the activity is new (less than 7 days old), award full points.
- if ( $days < 7 ) {
- return round( $points );
- }
+ $points = ( $days < 7 )
+ ? round( $points ) // If the activity is new (less than 7 days old), award full points.
+ : round( $points * ( 1 - $days / 30 ) ); // Decay the points based on the age of the activity.
+
+ error_log( 'Days: ' . $days . ' Points: ' . $points );
- // Decay the points based on the age of the activity.
- return round( $points * ( 1 - $days / 30 ) );
+ return $points;
}
}
diff --git a/includes/activities/class-maintenance.php b/includes/activities/class-maintenance.php
index 901c29971..6ab891ec4 100644
--- a/includes/activities/class-maintenance.php
+++ b/includes/activities/class-maintenance.php
@@ -58,6 +58,10 @@ public function get_points( $date ) {
// Decay the points based on the age of the activity.
$days = Date::get_days_between_dates( $date, $this->get_date() );
+ if ( $days > 0 ) {
+ return 0;
+ }
+ $days = abs( $days );
return ( $days > 0 && $days < 7 ) ? $points : 0;
}
diff --git a/includes/class-activity.php b/includes/class-activity.php
index f436d0d77..ca110cdda 100644
--- a/includes/class-activity.php
+++ b/includes/class-activity.php
@@ -207,8 +207,12 @@ public function delete() {
*/
public function get_points( $date ) {
$days = Date::get_days_between_dates( $date, $this->get_date() );
- return ( $days > 0 && $days < 7 )
+ if ( $days > 0 ) {
+ return 0;
+ }
+ $days = abs( $days );
+ return ( $days < 0 && $days < 7 )
? 10
- : 10 * ( 1 - $days / 30 );
+ : round( 10 * ( 1 - $days / 30 ) );
}
}
diff --git a/includes/class-chart.php b/includes/class-chart.php
index f1ad61570..bb157cf17 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -118,7 +118,7 @@ public function the_chart( $args = [] ) {
array_merge(
$args['query_params'],
[
- 'start_date' => $period['start']->modify( '-61 days' ),
+ 'start_date' => $period['start']->modify( '-31 days' ),
'end_date' => $period['end'],
]
)
@@ -137,9 +137,9 @@ public function the_chart( $args = [] ) {
$data['labels'][] = $period['dates'][0]->format( $args['dates_params']['format'] );
- $score = $args['additive']
- ? $score + $args['count_callback']( $activities, $period['start'] )
- : $args['count_callback']( $activities, $period['start'] );
+ $period_score = $args['count_callback']( $activities, $period['start'] );
+ $score = $args['additive'] ? $score + $period_score : $period_score;
+
$datasets[0]['data'][] = $score;
$datasets[0]['backgroundColor'][] = $args['colors']['background']( $score );
$datasets[0]['borderColor'][] = $args['colors']['border']( $score );
diff --git a/views/widgets/activity-scores.php b/views/widgets/activity-scores.php
index c034d582e..57578075b 100644
--- a/views/widgets/activity-scores.php
+++ b/views/widgets/activity-scores.php
@@ -34,7 +34,7 @@
[
'query_params' => [],
'dates_params' => [
- 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-24 months' ),
+ 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-12 months' ),
'end' => new \DateTime(),
'frequency' => 'monthly',
'format' => 'M',
@@ -47,7 +47,8 @@
foreach ( $activities as $activity ) {
$score += $activity->get_points( $date );
}
- return round( min( 100, $score ) );
+ $target = 200; // 200 points is 4 posts per week.
+ return round( ( $score / $target ) * 100 );
},
'additive' => false,
'rolling' => true,
diff --git a/views/widgets/website-activity-score.php b/views/widgets/website-activity-score.php
index 3175d8134..4dcf52167 100644
--- a/views/widgets/website-activity-score.php
+++ b/views/widgets/website-activity-score.php
@@ -14,7 +14,7 @@
]
);
-$prpl_score = 0;
+$prpl_score = 0;
$prpl_current_date = new \DateTime();
foreach ( $prpl_activities as $prpl_activity ) {
$prpl_score += $prpl_activity->get_points( $prpl_current_date ) / 2;
@@ -26,7 +26,7 @@
// Reduce points for pending updates.
$prpl_pending_updates_penalty = min( min( $prpl_score / 2, 25 ), $prpl_pending_updates * 5 );
-$prpl_score -= $prpl_pending_updates_penalty;
+$prpl_score -= $prpl_pending_updates_penalty;
// Calculate the color.
$prpl_gauge_color = 'var(--prpl-color-accent-red)';
From 6dfcfcd5381f9cffc5a4f0e443e0bce2f83df54a Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 12:36:53 +0200
Subject: [PATCH 126/490] implement 6/12-month switch
---
views/admin-page-header.php | 8 ++++----
views/widgets/activity-scores.php | 5 ++++-
views/widgets/published-content-density.php | 5 ++++-
views/widgets/published-content.php | 5 ++++-
views/widgets/published-pages.php | 5 ++++-
views/widgets/published-posts.php | 5 ++++-
views/widgets/published-words.php | 5 ++++-
7 files changed, 28 insertions(+), 10 deletions(-)
diff --git a/views/admin-page-header.php b/views/admin-page-header.php
index 023c38834..44cb897ed 100644
--- a/views/admin-page-header.php
+++ b/views/admin-page-header.php
@@ -5,8 +5,8 @@
* @package ProgressPlanner
*/
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
-$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '6-months';
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
?>
@@ -25,8 +25,8 @@
esc_html__( 'Activity over the past 6 months', 'progress-planner' ),
- '12-months' => esc_html__( 'Activity over the past 12 months', 'progress-planner' ),
+ '-6 months' => esc_html__( 'Activity over the past 6 months', 'progress-planner' ),
+ '-12 months' => esc_html__( 'Activity over the past 12 months', 'progress-planner' ),
] as $prpl_range => $prpl_label ) {
printf(
'%3$s ',
diff --git a/views/widgets/activity-scores.php b/views/widgets/activity-scores.php
index 57578075b..eb81d3027 100644
--- a/views/widgets/activity-scores.php
+++ b/views/widgets/activity-scores.php
@@ -7,6 +7,9 @@
namespace ProgressPlanner;
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+
/**
* Callback to calculate the color of the chart.
*
@@ -34,7 +37,7 @@
[
'query_params' => [],
'dates_params' => [
- 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-12 months' ),
+ 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
'frequency' => 'monthly',
'format' => 'M',
diff --git a/views/widgets/published-content-density.php b/views/widgets/published-content-density.php
index 0f95b68eb..11039e951 100644
--- a/views/widgets/published-content-density.php
+++ b/views/widgets/published-content-density.php
@@ -9,6 +9,9 @@
use ProgressPlanner\Activities\Content_Helpers;
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+
// Arguments for the query.
$prpl_query_args = [
'category' => 'content',
@@ -98,7 +101,7 @@ function ( $activity ) {
'type' => 'publish',
],
'dates_params' => [
- 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-12 months' ),
+ 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
'frequency' => 'weekly',
'format' => 'M',
diff --git a/views/widgets/published-content.php b/views/widgets/published-content.php
index df8592a6f..4d6bfa1bc 100644
--- a/views/widgets/published-content.php
+++ b/views/widgets/published-content.php
@@ -9,6 +9,9 @@
use ProgressPlanner\Activities\Content_Helpers;
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+
// Get the content published this week.
$prpl_last_week_content = count(
get_posts(
@@ -67,7 +70,7 @@
'type' => 'publish',
],
'dates_params' => [
- 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-5 months' ),
+ 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
'frequency' => 'weekly',
'format' => 'M, d',
diff --git a/views/widgets/published-pages.php b/views/widgets/published-pages.php
index ecd1b62d8..06314fd47 100644
--- a/views/widgets/published-pages.php
+++ b/views/widgets/published-pages.php
@@ -7,6 +7,9 @@
namespace ProgressPlanner;
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+
// Get the pages published in the last week.
$prpl_last_week_pages = count(
get_posts(
@@ -70,7 +73,7 @@
return $activities;
},
'dates_params' => [
- 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-5 months' ),
+ 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
'frequency' => 'monthly',
'format' => 'M',
diff --git a/views/widgets/published-posts.php b/views/widgets/published-posts.php
index aaf0f4098..a5e3ece2e 100644
--- a/views/widgets/published-posts.php
+++ b/views/widgets/published-posts.php
@@ -7,6 +7,9 @@
namespace ProgressPlanner;
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+
// Get the posts published in the last week.
$prpl_last_week_posts = count(
get_posts(
@@ -70,7 +73,7 @@
return $activities;
},
'dates_params' => [
- 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-5 months' ),
+ 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
'frequency' => 'monthly',
'format' => 'M',
diff --git a/views/widgets/published-words.php b/views/widgets/published-words.php
index 1aea7e967..e721724a1 100644
--- a/views/widgets/published-words.php
+++ b/views/widgets/published-words.php
@@ -9,6 +9,9 @@
use ProgressPlanner\Activities\Content_Helpers;
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+
// Arguments for the query.
$prpl_query_args = [
'category' => 'content',
@@ -97,7 +100,7 @@ function ( $activity ) {
'type' => 'publish',
],
'dates_params' => [
- 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( '-5 months' ),
+ 'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
'frequency' => 'monthly',
'format' => 'M',
From 794bfb60d18058f1cf3d4a8c986541315687fa85 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 12:40:37 +0200
Subject: [PATCH 127/490] rename "rolling" to "normalized"
---
includes/class-chart.php | 4 ++--
views/widgets/activity-scores.php | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/includes/class-chart.php b/includes/class-chart.php
index bb157cf17..ceb3a9f23 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -43,7 +43,7 @@ public function the_chart( $args = [] ) {
'dates_params' => [],
'chart_params' => [],
'additive' => false,
- 'rolling' => false,
+ 'normalized' => false,
'colors' => [
'background' => function () {
return '#534786';
@@ -113,7 +113,7 @@ public function the_chart( $args = [] ) {
}
foreach ( $periods as $period ) {
- $activities = $args['rolling']
+ $activities = $args['normalized']
? \progress_planner()->get_query()->query_activities(
array_merge(
$args['query_params'],
diff --git a/views/widgets/activity-scores.php b/views/widgets/activity-scores.php
index eb81d3027..cfc7b68fe 100644
--- a/views/widgets/activity-scores.php
+++ b/views/widgets/activity-scores.php
@@ -54,7 +54,7 @@
return round( ( $score / $target ) * 100 );
},
'additive' => false,
- 'rolling' => true,
+ 'normalized' => true,
'colors' => [
'background' => $prpl_color_callback,
'border' => $prpl_color_callback,
From 890aec52c3f441f3480c6db887d038810b84e66b Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 13:28:47 +0200
Subject: [PATCH 128/490] cache DB queries
---
includes/class-chart.php | 3 +-
includes/class-query.php | 103 +++++++++++++++++++++------------------
2 files changed, 57 insertions(+), 49 deletions(-)
diff --git a/includes/class-chart.php b/includes/class-chart.php
index ceb3a9f23..1a5b0fdcc 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -52,7 +52,8 @@ public function the_chart( $args = [] ) {
return '#534786';
},
],
- 'count_callback' => function ( $activities ) {
+ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
+ 'count_callback' => function ( $activities, $date = null ) {
return count( $activities );
},
]
diff --git a/includes/class-query.php b/includes/class-query.php
index a0c8be8b0..3690753e8 100644
--- a/includes/class-query.php
+++ b/includes/class-query.php
@@ -115,57 +115,64 @@ public function query_activities( $args, $return_type = 'ACTIVITIES' ) {
$args = \wp_parse_args( $args, $defaults );
- $where_args = [];
- $prepare_args = [];
- if ( $args['start_date'] !== null ) {
- $where_args[] = 'date >= %s';
- $prepare_args[] = ( $args['start_date'] instanceof \Datetime )
- ? $args['start_date']->format( 'Y-m-d H:i:s' )
- : $args['start_date'];
- }
- if ( $args['end_date'] !== null ) {
- $where_args[] = 'date <= %s';
- $prepare_args[] = ( $args['end_date'] instanceof \Datetime )
- ? $args['end_date']->format( 'Y-m-d H:i:s' )
- : $args['end_date'];
- }
- if ( $args['category'] !== null ) {
- $where_args[] = 'category = %s';
- $prepare_args[] = $args['category'];
- }
- if ( $args['type'] !== null ) {
- $where_args[] = 'type = %s';
- $prepare_args[] = $args['type'];
- }
- if ( $args['data_id'] !== null ) {
- $where_args[] = 'data_id = %s';
- $prepare_args[] = $args['data_id'];
- }
+ $cache_key = 'progress-planner-activities-' . md5( wp_json_encode( $args ) );
+ $results = wp_cache_get( $cache_key );
+
+ if ( false === $results ) {
+ $where_args = [];
+ $prepare_args = [];
+ if ( $args['start_date'] !== null ) {
+ $where_args[] = 'date >= %s';
+ $prepare_args[] = ( $args['start_date'] instanceof \Datetime )
+ ? $args['start_date']->format( 'Y-m-d H:i:s' )
+ : $args['start_date'];
+ }
+ if ( $args['end_date'] !== null ) {
+ $where_args[] = 'date <= %s';
+ $prepare_args[] = ( $args['end_date'] instanceof \Datetime )
+ ? $args['end_date']->format( 'Y-m-d H:i:s' )
+ : $args['end_date'];
+ }
+ if ( $args['category'] !== null ) {
+ $where_args[] = 'category = %s';
+ $prepare_args[] = $args['category'];
+ }
+ if ( $args['type'] !== null ) {
+ $where_args[] = 'type = %s';
+ $prepare_args[] = $args['type'];
+ }
+ if ( $args['data_id'] !== null ) {
+ $where_args[] = 'data_id = %s';
+ $prepare_args[] = $args['data_id'];
+ }
- if ( $args['user_id'] !== null ) {
- $where_args[] = 'user_id = %s';
- $prepare_args[] = $args['user_id'];
- }
+ if ( $args['user_id'] !== null ) {
+ $where_args[] = 'user_id = %s';
+ $prepare_args[] = $args['user_id'];
+ }
- $results = ( empty( $where_args ) )
- // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
- ? $wpdb->get_results(
- $wpdb->prepare( 'SELECT * FROM %i', $wpdb->prefix . static::TABLE_NAME )
- )
- // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
- : $wpdb->get_results(
- // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- This is a false positive.
- $wpdb->prepare(
- sprintf(
- 'SELECT * FROM %%i WHERE %s',
- \implode( ' AND ', $where_args )
- ),
- array_merge(
- [ $wpdb->prefix . static::TABLE_NAME ],
- $prepare_args
- )
+ $results = ( empty( $where_args ) )
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
+ ? $wpdb->get_results(
+ $wpdb->prepare( 'SELECT * FROM %i SORT BY date', $wpdb->prefix . static::TABLE_NAME )
)
- );
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
+ : $wpdb->get_results(
+ // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- This is a false positive.
+ $wpdb->prepare(
+ sprintf(
+ 'SELECT * FROM %%i WHERE %s',
+ \implode( ' AND ', $where_args )
+ ),
+ array_merge(
+ [ $wpdb->prefix . static::TABLE_NAME ],
+ $prepare_args
+ )
+ )
+ );
+
+ wp_cache_set( $cache_key, $results );
+ }
return 'RAW' === $return_type
? $results
From 1728fbcd5b7679e8b27b1ba7ad47a9f14cc39979 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 13:34:28 +0200
Subject: [PATCH 129/490] typo
---
includes/class-query.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/class-query.php b/includes/class-query.php
index 3690753e8..0b503aa59 100644
--- a/includes/class-query.php
+++ b/includes/class-query.php
@@ -154,7 +154,7 @@ public function query_activities( $args, $return_type = 'ACTIVITIES' ) {
$results = ( empty( $where_args ) )
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
? $wpdb->get_results(
- $wpdb->prepare( 'SELECT * FROM %i SORT BY date', $wpdb->prefix . static::TABLE_NAME )
+ $wpdb->prepare( 'SELECT * FROM %i ORDER BY date', $wpdb->prefix . static::TABLE_NAME )
)
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
: $wpdb->get_results(
From b86ecc6b14a474429e2952a3c2f1cad3613a7c4c Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 13:56:23 +0200
Subject: [PATCH 130/490] fix scores calculation
---
includes/activities/class-content.php | 21 ++++++---------------
includes/activities/class-maintenance.php | 8 ++------
includes/class-activity.php | 10 +++-------
includes/class-chart.php | 2 +-
views/widgets/activity-scores.php | 2 +-
views/widgets/website-activity-score.php | 2 ++
6 files changed, 15 insertions(+), 30 deletions(-)
diff --git a/includes/activities/class-content.php b/includes/activities/class-content.php
index ffb673ef9..3e612483a 100644
--- a/includes/activities/class-content.php
+++ b/includes/activities/class-content.php
@@ -59,23 +59,16 @@ public function get_points( $date ) {
}
$words = Content_Helpers::get_word_count( $post->post_content );
if ( $words > 1000 ) {
- $points -= 10;
+ $points *= 0.8;
} elseif ( $words > 350 ) {
- $points += 5;
+ $points *= 1.25;
} elseif ( $words > 100 ) {
- $points += 2;
+ $points *= 1.1;
} else {
- $points -= 2;
+ $points *= 0.9;
}
- $days = Date::get_days_between_dates( $date, $this->get_date() );
-
- // If $days is > 0, then the activity is in the future.
- if ( $days > 0 ) {
- return 0;
- }
-
- $days = absint( $days );
+ $days = absint( Date::get_days_between_dates( $date, $this->get_date() ) );
// Maximum range for awarded points is 30 days.
if ( $days >= 30 ) {
@@ -84,9 +77,7 @@ public function get_points( $date ) {
$points = ( $days < 7 )
? round( $points ) // If the activity is new (less than 7 days old), award full points.
- : round( $points * ( 1 - $days / 30 ) ); // Decay the points based on the age of the activity.
-
- error_log( 'Days: ' . $days . ' Points: ' . $points );
+ : round( $points * max( 0, ( 1 - $days / 30 ) ) ); // Decay the points based on the age of the activity.
return $points;
}
diff --git a/includes/activities/class-maintenance.php b/includes/activities/class-maintenance.php
index 6ab891ec4..132262f77 100644
--- a/includes/activities/class-maintenance.php
+++ b/includes/activities/class-maintenance.php
@@ -57,12 +57,8 @@ public function get_points( $date ) {
}
// Decay the points based on the age of the activity.
- $days = Date::get_days_between_dates( $date, $this->get_date() );
- if ( $days > 0 ) {
- return 0;
- }
- $days = abs( $days );
+ $days = abs( Date::get_days_between_dates( $date, $this->get_date() ) );
- return ( $days > 0 && $days < 7 ) ? $points : 0;
+ return ( $days < 7 ) ? $points : 0;
}
}
diff --git a/includes/class-activity.php b/includes/class-activity.php
index ca110cdda..a762ccd5f 100644
--- a/includes/class-activity.php
+++ b/includes/class-activity.php
@@ -206,13 +206,9 @@ public function delete() {
* @return int
*/
public function get_points( $date ) {
- $days = Date::get_days_between_dates( $date, $this->get_date() );
- if ( $days > 0 ) {
- return 0;
- }
- $days = abs( $days );
- return ( $days < 0 && $days < 7 )
+ $days = abs( Date::get_days_between_dates( $date, $this->get_date() ) );
+ return ( $days < 7 )
? 10
- : round( 10 * ( 1 - $days / 30 ) );
+ : round( 10 * max( 0, ( 1 - $days / 30 ) ) );
}
}
diff --git a/includes/class-chart.php b/includes/class-chart.php
index 1a5b0fdcc..93cc261d4 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -35,7 +35,7 @@ class Chart {
* @return void
*/
public function the_chart( $args = [] ) {
- $args = wp_parse_args(
+ $args = wp_parse_args(
$args,
[
'query_params' => [],
diff --git a/views/widgets/activity-scores.php b/views/widgets/activity-scores.php
index cfc7b68fe..75494b750 100644
--- a/views/widgets/activity-scores.php
+++ b/views/widgets/activity-scores.php
@@ -51,7 +51,7 @@
$score += $activity->get_points( $date );
}
$target = 200; // 200 points is 4 posts per week.
- return round( ( $score / $target ) * 100 );
+ return round( min( 100, ( $score / $target ) * 100 ) );
},
'additive' => false,
'normalized' => true,
diff --git a/views/widgets/website-activity-score.php b/views/widgets/website-activity-score.php
index 4dcf52167..d8d248d48 100644
--- a/views/widgets/website-activity-score.php
+++ b/views/widgets/website-activity-score.php
@@ -9,6 +9,8 @@
$prpl_activities = \progress_planner()->get_query()->query_activities(
[
+ // Use 31 days to take into account
+ // the activities score decay from previous activities.
'start_date' => new \DateTime( '-31 days' ),
'end_date' => new \DateTime(),
]
From 18d4558505dc98d7981cf2df00af33a6dfe1aee6 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 14:18:36 +0200
Subject: [PATCH 131/490] Add frequency (weekly/monthly) filter
---
assets/js/admin.js | 6 ++++++
includes/class-chart.php | 2 +-
views/admin-page-header.php | 23 +++++++++++++++++++++
views/widgets/activity-scores.php | 4 +++-
views/widgets/published-content-density.php | 4 +++-
views/widgets/published-content.php | 4 +++-
views/widgets/published-pages.php | 4 +++-
views/widgets/published-posts.php | 4 +++-
views/widgets/published-words.php | 4 +++-
9 files changed, 48 insertions(+), 7 deletions(-)
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 5a3660741..f6b46355e 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -144,3 +144,9 @@ document.getElementById( 'prpl-select-range' ).addEventListener( 'change', funct
url.searchParams.set( 'range', range );
window.location.href = url.href;
} );
+document.getElementById( 'prpl-select-frequency' ).addEventListener( 'change', function() {
+ const frequency = this.value;
+ const url = new URL( window.location.href );
+ url.searchParams.set( 'frequency', frequency );
+ window.location.href = url.href;
+} );
diff --git a/includes/class-chart.php b/includes/class-chart.php
index 93cc261d4..b25d51925 100644
--- a/includes/class-chart.php
+++ b/includes/class-chart.php
@@ -148,7 +148,7 @@ public function the_chart( $args = [] ) {
$data['datasets'] = $datasets;
$this->render_chart(
- md5( wp_json_encode( $args ) ),
+ md5( wp_json_encode( $args ) ) . wp_rand( 0, 1000 ),
$args['chart_params']['type'],
$data,
$args['chart_params']['options']
diff --git a/views/admin-page-header.php b/views/admin-page-header.php
index 44cb897ed..2e4cd0232 100644
--- a/views/admin-page-header.php
+++ b/views/admin-page-header.php
@@ -7,6 +7,8 @@
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_frequency = isset( $_GET['frequency'] ) ? sanitize_text_field( wp_unslash( $_GET['frequency'] ) ) : 'monthly';
?>
@@ -25,8 +27,11 @@
esc_html__( 'Activity over the past 3 months', 'progress-planner' ),
'-6 months' => esc_html__( 'Activity over the past 6 months', 'progress-planner' ),
'-12 months' => esc_html__( 'Activity over the past 12 months', 'progress-planner' ),
+ '-18 months' => esc_html__( 'Activity over the past 18 months', 'progress-planner' ),
+ '-24 months' => esc_html__( 'Activity over the past 24 months', 'progress-planner' ),
] as $prpl_range => $prpl_label ) {
printf(
'%3$s ',
@@ -37,5 +42,23 @@
}
?>
+
+
+
+
+ esc_html__( 'Weekly', 'progress-planner' ),
+ 'monthly' => esc_html__( 'Monthly', 'progress-planner' ),
+ ] as $prpl_frequency => $prpl_label ) {
+ printf(
+ '%3$s ',
+ esc_attr( $prpl_frequency ),
+ selected( $prpl_active_frequency, $prpl_frequency, false ),
+ esc_html( $prpl_label )
+ );
+ }
+ ?>
+
diff --git a/views/widgets/activity-scores.php b/views/widgets/activity-scores.php
index 75494b750..c6d339e64 100644
--- a/views/widgets/activity-scores.php
+++ b/views/widgets/activity-scores.php
@@ -9,6 +9,8 @@
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_frequency = isset( $_GET['frequency'] ) ? sanitize_text_field( wp_unslash( $_GET['frequency'] ) ) : 'monthly';
/**
* Callback to calculate the color of the chart.
@@ -39,7 +41,7 @@
'dates_params' => [
'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
- 'frequency' => 'monthly',
+ 'frequency' => $prpl_active_frequency,
'format' => 'M',
],
'chart_params' => [
diff --git a/views/widgets/published-content-density.php b/views/widgets/published-content-density.php
index 11039e951..c0c90f1c0 100644
--- a/views/widgets/published-content-density.php
+++ b/views/widgets/published-content-density.php
@@ -11,6 +11,8 @@
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_frequency = isset( $_GET['frequency'] ) ? sanitize_text_field( wp_unslash( $_GET['frequency'] ) ) : 'monthly';
// Arguments for the query.
$prpl_query_args = [
@@ -103,7 +105,7 @@ function ( $activity ) {
'dates_params' => [
'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
- 'frequency' => 'weekly',
+ 'frequency' => $prpl_active_frequency,
'format' => 'M',
],
'chart_params' => [
diff --git a/views/widgets/published-content.php b/views/widgets/published-content.php
index 4d6bfa1bc..976114f7c 100644
--- a/views/widgets/published-content.php
+++ b/views/widgets/published-content.php
@@ -11,6 +11,8 @@
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_frequency = isset( $_GET['frequency'] ) ? sanitize_text_field( wp_unslash( $_GET['frequency'] ) ) : 'monthly';
// Get the content published this week.
$prpl_last_week_content = count(
@@ -72,7 +74,7 @@
'dates_params' => [
'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
- 'frequency' => 'weekly',
+ 'frequency' => $prpl_active_frequency,
'format' => 'M, d',
],
'chart_params' => [
diff --git a/views/widgets/published-pages.php b/views/widgets/published-pages.php
index 06314fd47..a6e6f15c1 100644
--- a/views/widgets/published-pages.php
+++ b/views/widgets/published-pages.php
@@ -9,6 +9,8 @@
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_frequency = isset( $_GET['frequency'] ) ? sanitize_text_field( wp_unslash( $_GET['frequency'] ) ) : 'monthly';
// Get the pages published in the last week.
$prpl_last_week_pages = count(
@@ -75,7 +77,7 @@
'dates_params' => [
'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
- 'frequency' => 'monthly',
+ 'frequency' => $prpl_active_frequency,
'format' => 'M',
],
'chart_params' => [
diff --git a/views/widgets/published-posts.php b/views/widgets/published-posts.php
index a5e3ece2e..784018472 100644
--- a/views/widgets/published-posts.php
+++ b/views/widgets/published-posts.php
@@ -9,6 +9,8 @@
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_frequency = isset( $_GET['frequency'] ) ? sanitize_text_field( wp_unslash( $_GET['frequency'] ) ) : 'monthly';
// Get the posts published in the last week.
$prpl_last_week_posts = count(
@@ -75,7 +77,7 @@
'dates_params' => [
'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
- 'frequency' => 'monthly',
+ 'frequency' => $prpl_active_frequency,
'format' => 'M',
],
'chart_params' => [
diff --git a/views/widgets/published-words.php b/views/widgets/published-words.php
index e721724a1..1b1d28dcb 100644
--- a/views/widgets/published-words.php
+++ b/views/widgets/published-words.php
@@ -11,6 +11,8 @@
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$prpl_active_range = isset( $_GET['range'] ) ? sanitize_text_field( wp_unslash( $_GET['range'] ) ) : '-6 months';
+// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+$prpl_active_frequency = isset( $_GET['frequency'] ) ? sanitize_text_field( wp_unslash( $_GET['frequency'] ) ) : 'monthly';
// Arguments for the query.
$prpl_query_args = [
@@ -102,7 +104,7 @@ function ( $activity ) {
'dates_params' => [
'start' => \DateTime::createFromFormat( 'Y-m-d', \gmdate( 'Y-m-01' ) )->modify( $prpl_active_range ),
'end' => new \DateTime(),
- 'frequency' => 'monthly',
+ 'frequency' => $prpl_active_frequency,
'format' => 'M',
],
'chart_params' => [
From a17f460b7ae11df968254c27a21894c8b5fcf47d Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Tue, 5 Mar 2024 15:13:15 +0200
Subject: [PATCH 132/490] Add dev to configure the scores
---
assets/js/admin.js | 11 ++++++++
includes/activities/class-content.php | 29 ++++++++------------
includes/activities/class-maintenance.php | 9 ++-----
includes/class-base.php | 33 +++++++++++++++++++++++
views/admin-page.php | 3 ++-
views/widgets/__filter-numbers.php | 24 +++++++++++++++++
views/widgets/activity-scores.php | 2 +-
7 files changed, 84 insertions(+), 27 deletions(-)
create mode 100644 views/widgets/__filter-numbers.php
diff --git a/assets/js/admin.js b/assets/js/admin.js
index f6b46355e..d20756b70 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -150,3 +150,14 @@ document.getElementById( 'prpl-select-frequency' ).addEventListener( 'change', f
url.searchParams.set( 'frequency', frequency );
window.location.href = url.href;
} );
+
+document.getElementById( 'prpl-dev-stats-numbers' ).addEventListener( 'submit', function( event ) {
+ event.preventDefault();
+ const inputs = this.querySelectorAll( 'input' );
+ const url = new URL( window.location.href );
+
+ inputs.forEach( input => {
+ url.searchParams.set( input.name, input.value );
+ } );
+ window.location.href = url.href;
+} );
diff --git a/includes/activities/class-content.php b/includes/activities/class-content.php
index 3e612483a..35b2e09ab 100644
--- a/includes/activities/class-content.php
+++ b/includes/activities/class-content.php
@@ -16,18 +16,6 @@
*/
class Content extends Activity {
- /**
- * The points awarded for each activity.
- *
- * @var array
- */
- const ACTIVITIES_POINTS = [
- 'publish' => 50,
- 'update' => 20,
- 'delete' => 10,
- 'comment' => 5,
- ];
-
/**
* Category of the activity.
*
@@ -52,20 +40,25 @@ public function get_post() {
* @return int
*/
public function get_points( $date ) {
- $points = self::ACTIVITIES_POINTS[ $this->get_type() ];
- $post = $this->get_post();
+
+ $dev_config = \progress_planner()->get_dev_config();
+ $points = isset( $dev_config[ $this->get_type() ] )
+ ? $dev_config[ $this->get_type() ]
+ : $dev_config['content-publish'];
+ $post = $this->get_post();
+
if ( ! $post ) {
return 0;
}
$words = Content_Helpers::get_word_count( $post->post_content );
if ( $words > 1000 ) {
- $points *= 0.8;
+ $points *= $dev_config['content-1000-plus-words-multiplier'];
} elseif ( $words > 350 ) {
- $points *= 1.25;
+ $points *= $dev_config['content-350-plus-words-multiplier'];
} elseif ( $words > 100 ) {
- $points *= 1.1;
+ $points *= $dev_config['content-100-plus-words-multiplier'];
} else {
- $points *= 0.9;
+ $points *= $dev_config['content-100-minus-words-multiplier'];
}
$days = absint( Date::get_days_between_dates( $date, $this->get_date() ) );
diff --git a/includes/activities/class-maintenance.php b/includes/activities/class-maintenance.php
index 132262f77..9757c75a6 100644
--- a/includes/activities/class-maintenance.php
+++ b/includes/activities/class-maintenance.php
@@ -51,13 +51,8 @@ public function save() {
* @return int
*/
public function get_points( $date ) {
- $points = 2;
- if ( str_starts_with( $this->type, 'install_' ) || str_starts_with( $this->type, 'delete_' ) ) {
- $points = 1;
- }
-
- // Decay the points based on the age of the activity.
- $days = abs( Date::get_days_between_dates( $date, $this->get_date() ) );
+ $points = \progress_planner()->get_dev_config( 'maintenance' );
+ $days = abs( Date::get_days_between_dates( $date, $this->get_date() ) );
return ( $days < 7 ) ? $points : 0;
}
diff --git a/includes/class-base.php b/includes/class-base.php
index 1a415f87f..c553d212c 100644
--- a/includes/class-base.php
+++ b/includes/class-base.php
@@ -65,4 +65,37 @@ public function get_query() {
public function get_badges() {
return new Badges();
}
+
+ /**
+ * THIS SHOULD BE DELETED.
+ * WE ONLY HAVE IT HERE TO EXPERIMENT WITH THE NUMBERS
+ * WE'LL HAVE TO USE FOR STATS/SCORES.
+ *
+ * TODO: DELETE THIS METHOD.
+ *
+ * @param string $param The parameter to get. Null to get all.
+ *
+ * @return mixed
+ */
+ public function get_dev_config( $param = null ) {
+ $config = [
+ 'content-publish' => 50,
+ 'content-update' => 10,
+ 'content-delete' => 5,
+ 'content-100-minus-words-multiplier' => 0.8,
+ 'content-100-plus-words-multiplier' => 1.1,
+ 'content-350-plus-words-multiplier' => 1.25,
+ 'content-1000-plus-words-multiplier' => 0.8,
+ 'maintenance' => 10,
+ 'activity-score-target' => 200,
+ ];
+
+ // phpcs:disable WordPress.Security
+ foreach ( $config as $key => $value ) {
+ $config[ $key ] = isset( $_GET[ $key ] ) ? (float) $_GET[ $key ] : $value;
+ }
+ // phpcs:enable WordPress.Security
+
+ return null === $param ? $config : $config[ $param ];
+ }
}
diff --git a/views/admin-page.php b/views/admin-page.php
index 19a47d13e..25e3d23cd 100644
--- a/views/admin-page.php
+++ b/views/admin-page.php
@@ -29,12 +29,13 @@
'website-activity-score',
'published-content',
'activity-scores',
- 'latest-badge',
+ // 'latest-badge',
'published-pages',
'published-posts',
'published-content-density',
'published-words',
'badges-progress',
+ '__filter-numbers',
] as $prpl_widget ) {
echo '