From 3daf28d63250fb7640d9bf5b5c65f1736c1d6032 Mon Sep 17 00:00:00 2001 From: Michelle Blanchette Date: Sun, 14 Jan 2024 21:11:19 -0500 Subject: [PATCH 1/4] shortcode attribute sort_tasks_by to sort tasks by the given field --- src/includes/class-asana-interface.php | 76 ++++++++++++++++++++++++++ src/public/class-shortcodes.php | 1 + 2 files changed, 77 insertions(+) diff --git a/src/includes/class-asana-interface.php b/src/includes/class-asana-interface.php index 34ba82e2..6a4ddfb0 100755 --- a/src/includes/class-asana-interface.php +++ b/src/includes/class-asana-interface.php @@ -670,6 +670,7 @@ public static function get_project_data( 'show_tasks_due' => true, 'show_tasks_attachments' => true, 'show_tasks_tags' => true, + 'sort_tasks_by' => '', ) ); @@ -889,6 +890,10 @@ public static function get_project_data( self::load_subtasks( $tasks, $subtask_fields ); } + if ( $args['sort_tasks_by'] ) { + static::sort_tasks_by( $tasks, $args['sort_tasks_by'] ); + } + // Clean data and map tasks to project sections. foreach ( $tasks as &$task ) { @@ -972,6 +977,77 @@ public static function get_project_data( return $project; } + /** + * Sorts tasks by the given field. + * + * @since [unreleased] + * + * @param \stdClass[] $tasks The tasks to be sorted. + * @param string $sort_field The task attribute to sort tasks by. + */ + public static function sort_tasks_by( array &$tasks, string $sort_field ) { + usort( + $tasks, + function ( $task1, $task2 ) use ( $sort_field ) { + + // Ensure the specified field exists in both tasks. + if ( + isset( $task1->{$sort_field} ) && + isset( $task2->{$sort_field} ) + ) { + + $value1 = &$task1->{$sort_field}; + $value2 = &$task2->{$sort_field}; + + if ( is_bool( $value1 ) && is_bool( $value2 ) ) { + if ( + true === $value1 && + true === $value2 && + isset( $task1->name ) && + isset( $task2->name ) + ) { + // If both true, sort alphabetically by task name. + return strcmp( $task1->name, $task2->name ); + } + // Sort true values first. + return $value2 - $value1; + } elseif ( is_object( $value1 ) && is_object( $value2 ) ) { + if ( + isset( $value1->name ) && + isset( $value2->name ) + ) { + // Sort by the objects' name fields, such as "assignee". + return strcmp( $value1->name, $value2->name ); + } + // Don't know how to sort by object. + return 0; + } elseif ( is_numeric( $value1 ) && is_numeric( $value2 ) ) { + return $value1 - $value2; // Numeric comparison. + } elseif ( is_string( $value1 ) && is_string( $value2 ) ) { + return strcmp( $value1, $value2 ); // String comparison. + } else { + return 0; // Not sure how to sort. + } + } elseif ( + isset( $task1->{$sort_field} ) && + ! isset( $task2->{$sort_field} ) + ) { + // If the first task has the field, then put it first. + return -1; + } elseif ( + ! isset( $task1->{$sort_field} ) && + isset( $task2->{$sort_field} ) + ) { + // If the second task has the field, then put it first. + return 1; + } + + // No opinion when both tasks are missing the specified field. + return 0; + } + ); + } + /** * Sanitizes, localizes, and tidies a task object. * diff --git a/src/public/class-shortcodes.php b/src/public/class-shortcodes.php index e46f4221..f961454d 100644 --- a/src/public/class-shortcodes.php +++ b/src/public/class-shortcodes.php @@ -66,6 +66,7 @@ class Shortcodes { 'show_tasks_due' => 'true', 'show_tasks_attachments' => 'true', 'show_tasks_tags' => 'true', + 'sort_tasks_by' => '', ), 'script_handles' => array( 'ptc-completionist-shortcode-asana-project', From aae749af05f8d20f643822e810da7824c7528a16 Mon Sep 17 00:00:00 2001 From: Michelle Blanchette Date: Sun, 14 Jan 2024 21:21:24 -0500 Subject: [PATCH 2/4] also sort subtasks --- src/includes/class-asana-interface.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/includes/class-asana-interface.php b/src/includes/class-asana-interface.php index 6a4ddfb0..b6deba3b 100755 --- a/src/includes/class-asana-interface.php +++ b/src/includes/class-asana-interface.php @@ -936,6 +936,12 @@ public static function get_project_data( if ( ! $args['show_tasks_completed'] ) { $task->subtasks = array_values( $task->subtasks ); } + + // Asana doesn't currently sort subtasks when the + // view's sort is changed, but we will. + if ( $args['sort_tasks_by'] ) { + static::sort_tasks_by( $task->subtasks, $args['sort_tasks_by'] ); + } } // Clone in case the task appears in another membership. From 5ea8857573e5b59db37f54d04a6eeea54d2ac84e Mon Sep 17 00:00:00 2001 From: Michelle Blanchette Date: Sun, 14 Jan 2024 22:04:05 -0500 Subject: [PATCH 3/4] allow sorting tasks by a field that isn't ultimately displayed --- changelog.md | 1 + src/includes/class-asana-interface.php | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index efb6a616..9f71b287 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ #### Added - New shortcode attribute `layout` to specify a custom layout for the `[ptc_asana_project]` shortcode. Note this is only useful for extenders and third-party customizations. Completionist always displays projects in `list` layout by default. +- New shortcode attribute `sort_tasks_by` to sort tasks by the specified field. Common values are `due_on`, `assignee`, `completed`, and `name`. Unlike Asana, subtasks are also sorted. - `.pdf` video attachments on tasks are now supported. - External attachments (eg. Vimeo and YouTube embeds) are now displayed as their oEmbed HTML representations when available. diff --git a/src/includes/class-asana-interface.php b/src/includes/class-asana-interface.php index b6deba3b..3ac6715e 100755 --- a/src/includes/class-asana-interface.php +++ b/src/includes/class-asana-interface.php @@ -836,7 +836,7 @@ public static function get_project_data( $task_fields .= ',html_notes'; } if ( $args['show_tasks_assignee'] ) { - $task_fields .= ',assignee,this.assignee.name,this.assignee.photo.image_36x36'; + $task_fields .= ',assignee,assignee.name,assignee.photo.image_36x36'; } if ( $args['show_tasks_due'] ) { $task_fields .= ',due_on'; @@ -868,6 +868,20 @@ public static function get_project_data( $args ); + $do_remove_tasks_sort_field = false; + if ( + $args['sort_tasks_by'] && + false === in_array( + $args['sort_tasks_by'], + explode( ',', $task_fields ) + ) + ) { + // Ensure sorting field is returned. + // Always add "name" subfield in case its an object. + $task_fields .= ",{$args['sort_tasks_by']},{$args['sort_tasks_by']}.name"; + $do_remove_tasks_sort_field = true; + } + $tasks = $asana->tasks->getTasksForProject( $project_gid, $task_request_params, @@ -953,6 +967,14 @@ public static function get_project_data( } } } + + if ( + $args['sort_tasks_by'] && + true === $do_remove_tasks_sort_field + ) { + // Remove extra field only used for sorting, not for display. + Util::deep_unset_prop( $project, $args['sort_tasks_by'] ); + } } // Commit all buffered request tokens. From 4b30dc3e97e70be70456da38f14598c3758d70f1 Mon Sep 17 00:00:00 2001 From: Michelle Blanchette Date: Sun, 14 Jan 2024 22:06:18 -0500 Subject: [PATCH 4/4] tweak --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 9f71b287..f53dfe5a 100644 --- a/changelog.md +++ b/changelog.md @@ -3,7 +3,7 @@ #### Added - New shortcode attribute `layout` to specify a custom layout for the `[ptc_asana_project]` shortcode. Note this is only useful for extenders and third-party customizations. Completionist always displays projects in `list` layout by default. -- New shortcode attribute `sort_tasks_by` to sort tasks by the specified field. Common values are `due_on`, `assignee`, `completed`, and `name`. Unlike Asana, subtasks are also sorted. +- New shortcode attribute `sort_tasks_by` to sort tasks by the specified field. Common values are `due_on`, `assignee`, `completed_at`, and `name`. Unlike Asana, subtasks are also sorted. - `.pdf` video attachments on tasks are now supported. - External attachments (eg. Vimeo and YouTube embeds) are now displayed as their oEmbed HTML representations when available.