PageRenderTime 21ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://github.com/woothemes/woocommerce
PHP | 556 lines | 421 code | 24 blank | 111 comment | 5 complexity | 295029e6b7f6b4478cdf3013d8533094 MD5 | raw file
  1. <?php
  2. /**
  3. * Handles task related methods.
  4. */
  5. namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
  6. use Automattic\WooCommerce\Internal\Admin\WCAdminUser;
  7. /**
  8. * Task class.
  9. */
  10. abstract class Task {
  11. /**
  12. * Task traits.
  13. */
  14. use TaskTraits;
  15. /**
  16. * Name of the dismiss option.
  17. *
  18. * @var string
  19. */
  20. const DISMISSED_OPTION = 'woocommerce_task_list_dismissed_tasks';
  21. /**
  22. * Name of the snooze option.
  23. *
  24. * @var string
  25. */
  26. const SNOOZED_OPTION = 'woocommerce_task_list_remind_me_later_tasks';
  27. /**
  28. * Name of the actioned option.
  29. *
  30. * @var string
  31. */
  32. const ACTIONED_OPTION = 'woocommerce_task_list_tracked_completed_actions';
  33. /**
  34. * Option name of completed tasks.
  35. *
  36. * @var string
  37. */
  38. const COMPLETED_OPTION = 'woocommerce_task_list_tracked_completed_tasks';
  39. /**
  40. * Name of the active task transient.
  41. *
  42. * @var string
  43. */
  44. const ACTIVE_TASK_TRANSIENT = 'wc_onboarding_active_task';
  45. /**
  46. * Parent task list.
  47. *
  48. * @var TaskList
  49. */
  50. protected $task_list;
  51. /**
  52. * Duration to milisecond mapping.
  53. *
  54. * @var string
  55. */
  56. protected $duration_to_ms = array(
  57. 'day' => DAY_IN_SECONDS * 1000,
  58. 'hour' => HOUR_IN_SECONDS * 1000,
  59. 'week' => WEEK_IN_SECONDS * 1000,
  60. );
  61. /**
  62. * Constructor
  63. *
  64. * @param TaskList|null $task_list Parent task list.
  65. */
  66. public function __construct( $task_list = null ) {
  67. $this->task_list = $task_list;
  68. }
  69. /**
  70. * ID.
  71. *
  72. * @return string
  73. */
  74. abstract public function get_id();
  75. /**
  76. * Title.
  77. *
  78. * @return string
  79. */
  80. abstract public function get_title();
  81. /**
  82. * Content.
  83. *
  84. * @return string
  85. */
  86. abstract public function get_content();
  87. /**
  88. * Time.
  89. *
  90. * @return string
  91. */
  92. abstract public function get_time();
  93. /**
  94. * Parent ID.
  95. *
  96. * @return string
  97. */
  98. public function get_parent_id() {
  99. if ( ! $this->task_list ) {
  100. return '';
  101. }
  102. return $this->task_list->get_list_id();
  103. }
  104. /**
  105. * Get task list options.
  106. *
  107. * @return array
  108. */
  109. public function get_parent_options() {
  110. if ( ! $this->task_list ) {
  111. return array();
  112. }
  113. return $this->task_list->options;
  114. }
  115. /**
  116. * Get custom option.
  117. *
  118. * @param string $option_name name of custom option.
  119. * @return mixed|null
  120. */
  121. public function get_parent_option( $option_name ) {
  122. if ( $this->task_list && isset( $this->task_list->options[ $option_name ] ) ) {
  123. return $this->task_list->options[ $option_name ];
  124. }
  125. return null;
  126. }
  127. /**
  128. * Prefix event for track event naming.
  129. *
  130. * @param string $event_name Event name.
  131. * @return string
  132. */
  133. public function prefix_event( $event_name ) {
  134. if ( ! $this->task_list ) {
  135. return '';
  136. }
  137. return $this->task_list->prefix_event( $event_name );
  138. }
  139. /**
  140. * Additional info.
  141. *
  142. * @return string
  143. */
  144. public function get_additional_info() {
  145. return '';
  146. }
  147. /**
  148. * Additional data.
  149. *
  150. * @return mixed
  151. */
  152. public function get_additional_data() {
  153. return null;
  154. }
  155. /**
  156. * Level.
  157. *
  158. * @return string
  159. */
  160. public function get_level() {
  161. return 3;
  162. }
  163. /**
  164. * Action label.
  165. *
  166. * @return string
  167. */
  168. public function get_action_label() {
  169. return __( "Let's go", 'woocommerce' );
  170. }
  171. /**
  172. * Action URL.
  173. *
  174. * @return string
  175. */
  176. public function get_action_url() {
  177. return null;
  178. }
  179. /**
  180. * Check if a task is dismissable.
  181. *
  182. * @return bool
  183. */
  184. public function is_dismissable() {
  185. return false;
  186. }
  187. /**
  188. * Bool for task dismissal.
  189. *
  190. * @return bool
  191. */
  192. public function is_dismissed() {
  193. if ( ! $this->is_dismissable() ) {
  194. return false;
  195. }
  196. $dismissed = get_option( self::DISMISSED_OPTION, array() );
  197. return in_array( $this->get_id(), $dismissed, true );
  198. }
  199. /**
  200. * Dismiss the task.
  201. *
  202. * @return bool
  203. */
  204. public function dismiss() {
  205. if ( ! $this->is_dismissable() ) {
  206. return false;
  207. }
  208. $dismissed = get_option( self::DISMISSED_OPTION, array() );
  209. $dismissed[] = $this->get_id();
  210. $update = update_option( self::DISMISSED_OPTION, array_unique( $dismissed ) );
  211. if ( $update ) {
  212. $this->record_tracks_event( 'dismiss_task', array( 'task_name' => $this->get_id() ) );
  213. }
  214. return $update;
  215. }
  216. /**
  217. * Undo task dismissal.
  218. *
  219. * @return bool
  220. */
  221. public function undo_dismiss() {
  222. $dismissed = get_option( self::DISMISSED_OPTION, array() );
  223. $dismissed = array_diff( $dismissed, array( $this->get_id() ) );
  224. $update = update_option( self::DISMISSED_OPTION, $dismissed );
  225. if ( $update ) {
  226. $this->record_tracks_event( 'undo_dismiss_task', array( 'task_name' => $this->get_id() ) );
  227. }
  228. return $update;
  229. }
  230. /**
  231. * Check if a task is snoozeable.
  232. *
  233. * @return bool
  234. */
  235. public function is_snoozeable() {
  236. return false;
  237. }
  238. /**
  239. * Get the snoozed until datetime.
  240. *
  241. * @return string
  242. */
  243. public function get_snoozed_until() {
  244. $snoozed_tasks = get_option( self::SNOOZED_OPTION, array() );
  245. if ( isset( $snoozed_tasks[ $this->get_id() ] ) ) {
  246. return $snoozed_tasks[ $this->get_id() ];
  247. }
  248. return null;
  249. }
  250. /**
  251. * Bool for task snoozed.
  252. *
  253. * @return bool
  254. */
  255. public function is_snoozed() {
  256. if ( ! $this->is_snoozeable() ) {
  257. return false;
  258. }
  259. $snoozed = get_option( self::SNOOZED_OPTION, array() );
  260. return isset( $snoozed[ $this->get_id() ] ) && $snoozed[ $this->get_id() ] > ( time() * 1000 );
  261. }
  262. /**
  263. * Snooze the task.
  264. *
  265. * @param string $duration Duration to snooze. day|hour|week.
  266. * @return bool
  267. */
  268. public function snooze( $duration = 'day' ) {
  269. if ( ! $this->is_snoozeable() ) {
  270. return false;
  271. }
  272. $snoozed = get_option( self::SNOOZED_OPTION, array() );
  273. $snoozed_until = $this->duration_to_ms[ $duration ] + ( time() * 1000 );
  274. $snoozed[ $this->get_id() ] = $snoozed_until;
  275. $update = update_option( self::SNOOZED_OPTION, $snoozed );
  276. if ( $update ) {
  277. if ( $update ) {
  278. $this->record_tracks_event( 'remindmelater_task', array( 'task_name' => $this->get_id() ) );
  279. }
  280. }
  281. return $update;
  282. }
  283. /**
  284. * Undo task snooze.
  285. *
  286. * @return bool
  287. */
  288. public function undo_snooze() {
  289. $snoozed = get_option( self::SNOOZED_OPTION, array() );
  290. unset( $snoozed[ $this->get_id() ] );
  291. $update = update_option( self::SNOOZED_OPTION, $snoozed );
  292. if ( $update ) {
  293. $this->record_tracks_event( 'undo_remindmelater_task', array( 'task_name' => $this->get_id() ) );
  294. }
  295. return $update;
  296. }
  297. /**
  298. * Check if a task list has previously been marked as complete.
  299. *
  300. * @return bool
  301. */
  302. public function has_previously_completed() {
  303. $complete = get_option( self::COMPLETED_OPTION, array() );
  304. return in_array( $this->get_id(), $complete, true );
  305. }
  306. /**
  307. * Track task completion if task is viewable.
  308. */
  309. public function possibly_track_completion() {
  310. if ( ! $this->is_complete() ) {
  311. return;
  312. }
  313. if ( $this->has_previously_completed() ) {
  314. return;
  315. }
  316. $completed_tasks = get_option( self::COMPLETED_OPTION, array() );
  317. $completed_tasks[] = $this->get_id();
  318. update_option( self::COMPLETED_OPTION, $completed_tasks );
  319. $this->record_tracks_event( 'task_completed', array( 'task_name' => $this->get_id() ) );
  320. }
  321. /**
  322. * Set this as the active task across page loads.
  323. */
  324. public function set_active() {
  325. if ( $this->is_complete() ) {
  326. return;
  327. }
  328. set_transient(
  329. self::ACTIVE_TASK_TRANSIENT,
  330. $this->get_id(),
  331. DAY_IN_SECONDS
  332. );
  333. }
  334. /**
  335. * Check if this is the active task.
  336. */
  337. public function is_active() {
  338. return get_transient( self::ACTIVE_TASK_TRANSIENT ) === $this->get_id();
  339. }
  340. /**
  341. * Check if the store is capable of viewing the task.
  342. *
  343. * @return bool
  344. */
  345. public function can_view() {
  346. return true;
  347. }
  348. /**
  349. * Check if task is disabled.
  350. *
  351. * @return bool
  352. */
  353. public function is_disabled() {
  354. return false;
  355. }
  356. /**
  357. * Check if the task is complete.
  358. *
  359. * @return bool
  360. */
  361. public function is_complete() {
  362. return self::is_actioned();
  363. }
  364. /**
  365. * Check if the task has been visited.
  366. *
  367. * @return bool
  368. */
  369. public function is_visited() {
  370. $user_id = get_current_user_id();
  371. $response = WCAdminUser::get_user_data_field( $user_id, 'task_list_tracked_started_tasks' );
  372. $tracked_tasks = $response ? json_decode( $response, true ) : array();
  373. return isset( $tracked_tasks[ $this->get_id() ] ) && $tracked_tasks[ $this->get_id() ] > 0;
  374. }
  375. /**
  376. * Get the task as JSON.
  377. *
  378. * @return array
  379. */
  380. public function get_json() {
  381. $this->possibly_track_completion();
  382. return array(
  383. 'id' => $this->get_id(),
  384. 'parentId' => $this->get_parent_id(),
  385. 'title' => $this->get_title(),
  386. 'canView' => $this->can_view(),
  387. 'content' => $this->get_content(),
  388. 'additionalInfo' => $this->get_additional_info(),
  389. 'actionLabel' => $this->get_action_label(),
  390. 'actionUrl' => $this->get_action_url(),
  391. 'isComplete' => $this->is_complete(),
  392. 'time' => $this->get_time(),
  393. 'level' => $this->get_level(),
  394. 'isActioned' => $this->is_actioned(),
  395. 'isDismissed' => $this->is_dismissed(),
  396. 'isDismissable' => $this->is_dismissable(),
  397. 'isSnoozed' => $this->is_snoozed(),
  398. 'isSnoozeable' => $this->is_snoozeable(),
  399. 'isVisited' => $this->is_visited(),
  400. 'isDisabled' => $this->is_disabled(),
  401. 'snoozedUntil' => $this->get_snoozed_until(),
  402. 'additionalData' => self::convert_object_to_camelcase( $this->get_additional_data() ),
  403. 'eventPrefix' => $this->prefix_event( '' ),
  404. );
  405. }
  406. /**
  407. * Convert object keys to camelcase.
  408. *
  409. * @param array $data Data to convert.
  410. * @return object
  411. */
  412. public static function convert_object_to_camelcase( $data ) {
  413. if ( ! is_array( $data ) ) {
  414. return $data;
  415. }
  416. $new_object = (object) array();
  417. foreach ( $data as $key => $value ) {
  418. $new_key = lcfirst( implode( '', array_map( 'ucfirst', explode( '_', $key ) ) ) );
  419. $new_object->$new_key = $value;
  420. }
  421. return $new_object;
  422. }
  423. /**
  424. * Mark a task as actioned. Used to verify an action has taken place in some tasks.
  425. *
  426. * @return bool
  427. */
  428. public function mark_actioned() {
  429. $actioned = get_option( self::ACTIONED_OPTION, array() );
  430. $actioned[] = $this->get_id();
  431. $update = update_option( self::ACTIONED_OPTION, array_unique( $actioned ) );
  432. if ( $update ) {
  433. $this->record_tracks_event( 'actioned_task', array( 'task_name' => $this->get_id() ) );
  434. }
  435. return $update;
  436. }
  437. /**
  438. * Check if a task has been actioned.
  439. *
  440. * @return bool
  441. */
  442. public function is_actioned() {
  443. return self::is_task_actioned( $this->get_id() );
  444. }
  445. /**
  446. * Check if a provided task ID has been actioned.
  447. *
  448. * @param string $id Task ID.
  449. * @return bool
  450. */
  451. public static function is_task_actioned( $id ) {
  452. $actioned = get_option( self::ACTIONED_OPTION, array() );
  453. return in_array( $id, $actioned, true );
  454. }
  455. /**
  456. * Sorting function for tasks.
  457. *
  458. * @param Task $a Task a.
  459. * @param Task $b Task b.
  460. * @param array $sort_by list of columns with sort order.
  461. * @return int
  462. */
  463. public static function sort( $a, $b, $sort_by = array() ) {
  464. $result = 0;
  465. foreach ( $sort_by as $data ) {
  466. $key = $data['key'];
  467. $a_val = $a->$key ?? false;
  468. $b_val = $b->$key ?? false;
  469. if ( 'asc' === $data['order'] ) {
  470. $result = $a_val <=> $b_val;
  471. } else {
  472. $result = $b_val <=> $a_val;
  473. }
  474. if ( 0 !== $result ) {
  475. break;
  476. }
  477. }
  478. return $result;
  479. }
  480. }