/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php

https://github.com/woothemes/woocommerce · PHP · 420 lines · 214 code · 49 blank · 157 comment · 12 complexity · 1cdbfe6fda42810e2a0ee01f1483db71 MD5 · raw file

  1. <?php
  2. /**
  3. * Handles storage and retrieval of a task list
  4. */
  5. namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
  6. use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
  7. use Automattic\WooCommerce\Admin\WCAdminHelper;
  8. /**
  9. * Task List class.
  10. */
  11. class TaskList {
  12. /**
  13. * Task traits.
  14. */
  15. use TaskTraits;
  16. /**
  17. * Option name hidden task lists.
  18. */
  19. const HIDDEN_OPTION = 'woocommerce_task_list_hidden_lists';
  20. /**
  21. * Option name of completed task lists.
  22. */
  23. const COMPLETED_OPTION = 'woocommerce_task_list_completed_lists';
  24. /**
  25. * Option name of hidden reminder bar.
  26. */
  27. const REMINDER_BAR_HIDDEN_OPTION = 'woocommerce_task_list_reminder_bar_hidden';
  28. /**
  29. * ID.
  30. *
  31. * @var string
  32. */
  33. public $id = '';
  34. /**
  35. * ID.
  36. *
  37. * @var string
  38. */
  39. public $hidden_id = '';
  40. /**
  41. * ID.
  42. *
  43. * @var boolean
  44. */
  45. public $display_progress_header = false;
  46. /**
  47. * Title.
  48. *
  49. * @var string
  50. */
  51. public $title = '';
  52. /**
  53. * Tasks.
  54. *
  55. * @var array
  56. */
  57. public $tasks = array();
  58. /**
  59. * Sort keys.
  60. *
  61. * @var array
  62. */
  63. public $sort_by = array();
  64. /**
  65. * Event prefix.
  66. *
  67. * @var string|null
  68. */
  69. public $event_prefix = null;
  70. /**
  71. * Task list visibility.
  72. *
  73. * @var boolean
  74. */
  75. public $visible = true;
  76. /**
  77. * Array of custom options.
  78. *
  79. * @var array
  80. */
  81. public $options = array();
  82. /**
  83. * Array of TaskListSection.
  84. *
  85. * @var array
  86. */
  87. private $sections = array();
  88. /**
  89. * Key value map of task class and id used for sections.
  90. *
  91. * @var array
  92. */
  93. public $task_class_id_map = array();
  94. /**
  95. * Constructor
  96. *
  97. * @param array $data Task list data.
  98. */
  99. public function __construct( $data = array() ) {
  100. $defaults = array(
  101. 'id' => null,
  102. 'hidden_id' => null,
  103. 'title' => '',
  104. 'tasks' => array(),
  105. 'sort_by' => array(),
  106. 'event_prefix' => null,
  107. 'options' => array(),
  108. 'visible' => true,
  109. 'display_progress_header' => false,
  110. 'sections' => array(),
  111. );
  112. $data = wp_parse_args( $data, $defaults );
  113. $this->id = $data['id'];
  114. $this->hidden_id = $data['hidden_id'];
  115. $this->title = $data['title'];
  116. $this->sort_by = $data['sort_by'];
  117. $this->event_prefix = $data['event_prefix'];
  118. $this->options = $data['options'];
  119. $this->visible = $data['visible'];
  120. $this->display_progress_header = $data['display_progress_header'];
  121. foreach ( $data['tasks'] as $task_name ) {
  122. $class = 'Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\\' . $task_name;
  123. $task = new $class( $this );
  124. $this->add_task( $task );
  125. }
  126. $this->possibly_remove_reminder_bar();
  127. $this->sections = array_map(
  128. function( $section ) {
  129. return new TaskListSection( $section, $this );
  130. },
  131. $data['sections']
  132. );
  133. }
  134. /**
  135. * Check if the task list is hidden.
  136. *
  137. * @return bool
  138. */
  139. public function is_hidden() {
  140. $hidden = get_option( self::HIDDEN_OPTION, array() );
  141. return in_array( $this->hidden_id ? $this->hidden_id : $this->id, $hidden, true );
  142. }
  143. /**
  144. * Check if the task list is visible.
  145. *
  146. * @return bool
  147. */
  148. public function is_visible() {
  149. if ( ! $this->visible || ! count( $this->get_viewable_tasks() ) > 0 ) {
  150. return false;
  151. }
  152. return ! $this->is_hidden();
  153. }
  154. /**
  155. * Hide the task list.
  156. *
  157. * @return bool
  158. */
  159. public function hide() {
  160. if ( $this->is_hidden() ) {
  161. return;
  162. }
  163. $viewable_tasks = $this->get_viewable_tasks();
  164. $completed_count = array_reduce(
  165. $viewable_tasks,
  166. function( $total, $task ) {
  167. return $task->is_complete() ? $total + 1 : $total;
  168. },
  169. 0
  170. );
  171. $this->record_tracks_event(
  172. 'completed',
  173. array(
  174. 'action' => 'remove_card',
  175. 'completed_task_count' => $completed_count,
  176. 'incomplete_task_count' => count( $viewable_tasks ) - $completed_count,
  177. )
  178. );
  179. $hidden = get_option( self::HIDDEN_OPTION, array() );
  180. $hidden[] = $this->hidden_id ? $this->hidden_id : $this->id;
  181. return update_option( self::HIDDEN_OPTION, array_unique( $hidden ) );
  182. }
  183. /**
  184. * Undo hiding of the task list.
  185. *
  186. * @return bool
  187. */
  188. public function unhide() {
  189. $hidden = get_option( self::HIDDEN_OPTION, array() );
  190. $hidden = array_diff( $hidden, array( $this->hidden_id ? $this->hidden_id : $this->id ) );
  191. return update_option( self::HIDDEN_OPTION, $hidden );
  192. }
  193. /**
  194. * Check if all viewable tasks are complete.
  195. *
  196. * @return bool
  197. */
  198. public function is_complete() {
  199. $viewable_tasks = $this->get_viewable_tasks();
  200. return array_reduce(
  201. $viewable_tasks,
  202. function( $is_complete, $task ) {
  203. return ! $task->is_complete() ? false : $is_complete;
  204. },
  205. true
  206. );
  207. }
  208. /**
  209. * Check if a task list has previously been marked as complete.
  210. *
  211. * @return bool
  212. */
  213. public function has_previously_completed() {
  214. $complete = get_option( self::COMPLETED_OPTION, array() );
  215. return in_array( $this->get_list_id(), $complete, true );
  216. }
  217. /**
  218. * Add task to the task list.
  219. *
  220. * @param Task $task Task class.
  221. */
  222. public function add_task( $task ) {
  223. if ( ! is_subclass_of( $task, 'Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task' ) ) {
  224. return new \WP_Error(
  225. 'woocommerce_task_list_invalid_task',
  226. __( 'Task is not a subclass of `Task`', 'woocommerce' )
  227. );
  228. }
  229. if ( array_search( $task, $this->tasks, true ) ) {
  230. return;
  231. }
  232. $task_class_name = substr( get_class( $task ), strrpos( get_class( $task ), '\\' ) + 1 );
  233. $this->task_class_id_map[ $task_class_name ] = $task->get_id();
  234. $this->tasks[] = $task;
  235. }
  236. /**
  237. * Get only visible tasks in list.
  238. *
  239. * @param string $task_id id of task.
  240. * @return Task
  241. */
  242. public function get_task( $task_id ) {
  243. return current(
  244. array_filter(
  245. $this->tasks,
  246. function( $task ) use ( $task_id ) {
  247. return $task->get_id() === $task_id;
  248. }
  249. )
  250. );
  251. }
  252. /**
  253. * Get only visible tasks in list.
  254. *
  255. * @return array
  256. */
  257. public function get_viewable_tasks() {
  258. return array_values(
  259. array_filter(
  260. $this->tasks,
  261. function( $task ) {
  262. return $task->can_view();
  263. }
  264. )
  265. );
  266. }
  267. /**
  268. * Get task list sections.
  269. *
  270. * @return array
  271. */
  272. public function get_sections() {
  273. return $this->sections;
  274. }
  275. /**
  276. * Track list completion of viewable tasks.
  277. */
  278. public function possibly_track_completion() {
  279. if ( ! $this->is_complete() ) {
  280. return;
  281. }
  282. if ( $this->has_previously_completed() ) {
  283. return;
  284. }
  285. $completed_lists = get_option( self::COMPLETED_OPTION, array() );
  286. $completed_lists[] = $this->get_list_id();
  287. update_option( self::COMPLETED_OPTION, $completed_lists );
  288. $this->record_tracks_event( 'tasks_completed' );
  289. }
  290. /**
  291. * Sorts the attached tasks array.
  292. *
  293. * @param array $sort_by list of columns with sort order.
  294. * @return TaskList returns $this, for chaining.
  295. */
  296. public function sort_tasks( $sort_by = array() ) {
  297. $sort_by = count( $sort_by ) > 0 ? $sort_by : $this->sort_by;
  298. if ( 0 !== count( $sort_by ) ) {
  299. usort(
  300. $this->tasks,
  301. function( $a, $b ) use ( $sort_by ) {
  302. return Task::sort( $a, $b, $sort_by );
  303. }
  304. );
  305. }
  306. return $this;
  307. }
  308. /**
  309. * Prefix event for track event naming.
  310. *
  311. * @param string $event_name Event name.
  312. * @return string
  313. */
  314. public function prefix_event( $event_name ) {
  315. if ( null !== $this->event_prefix ) {
  316. return $this->event_prefix . $event_name;
  317. }
  318. return $this->get_list_id() . '_tasklist_' . $event_name;
  319. }
  320. /**
  321. * Returns option to keep completed task list.
  322. *
  323. * @return string
  324. */
  325. public function get_keep_completed_task_list() {
  326. return get_option( 'woocommerce_task_list_keep_completed', 'no' );
  327. }
  328. /**
  329. * Remove reminder bar four weeks after store creation.
  330. */
  331. public static function possibly_remove_reminder_bar() {
  332. $bar_hidden = get_option( self::REMINDER_BAR_HIDDEN_OPTION, 'no' );
  333. $active_for_four_weeks = WCAdminHelper::is_wc_admin_active_for( WEEK_IN_SECONDS * 4 );
  334. if ( 'yes' === $bar_hidden || ! $active_for_four_weeks ) {
  335. return;
  336. }
  337. update_option( self::REMINDER_BAR_HIDDEN_OPTION, 'yes' );
  338. }
  339. /**
  340. * Get the list for use in JSON.
  341. *
  342. * @return array
  343. */
  344. public function get_json() {
  345. $this->possibly_track_completion();
  346. $tasks_json = array();
  347. foreach ( $this->tasks as $task ) {
  348. $json = $task->get_json();
  349. if ( $json['canView'] ) {
  350. $tasks_json[] = $json;
  351. }
  352. }
  353. return array(
  354. 'id' => $this->get_list_id(),
  355. 'title' => $this->title,
  356. 'isHidden' => $this->is_hidden(),
  357. 'isVisible' => $this->is_visible(),
  358. 'isComplete' => $this->is_complete(),
  359. 'tasks' => $tasks_json,
  360. 'eventPrefix' => $this->prefix_event( '' ),
  361. 'displayProgressHeader' => $this->display_progress_header,
  362. 'keepCompletedTaskList' => $this->get_keep_completed_task_list(),
  363. 'sections' => array_map(
  364. function( $section ) {
  365. return $section->get_json();
  366. },
  367. $this->sections
  368. ),
  369. );
  370. }
  371. }