MS_Model_Transactionlog

Transaction Log Model.

Defined (1)

The class is defined in the following location(s).

/app/model/class-ms-model-transactionlog.php  
  1. class MS_Model_Transactionlog extends MS_Model_CustomPostType { 
  2.  
  3. /** 
  4. * Model custom post type. 
  5. * Both static and class property are used to handle php 5.2 limitations. 
  6. * @since 1.0.1.0 
  7. * @var string 
  8. */ 
  9. protected static $POST_TYPE = 'ms_transaction_log'; 
  10.  
  11. /** 
  12. * Timestamp of the transaction. 
  13. * @since 1.0.1. 
  14. * @var string 
  15. */ 
  16. protected $date = ''; 
  17.  
  18. /** 
  19. * The gateway that made the call. 
  20. * @since 1.0.1. 
  21. * @var string 
  22. */ 
  23. protected $gateway_id = ''; 
  24.  
  25. /** 
  26. * The transaction method. 
  27. * Possible methods are: 
  28. * "handle": IPN response 
  29. * "process": Process order (i.e. user comes from Payment screen) 
  30. * "request": Automatically request recurring payment 
  31. * @since 1.0.1.0 
  32. * @var string 
  33. */ 
  34. protected $method = ''; 
  35.  
  36. /** 
  37. * Indicator if the transaction was successfully processed by M2 
  38. * True means that an invoice was marked paid. 
  39. * False indicates an error or unknown input. 
  40. * NULL indicates a message that was processed but irrelevant. 
  41. * @since 1.0.1.0 
  42. * @var bool|null 
  43. */ 
  44. protected $success = null; 
  45.  
  46. /** 
  47. * Transaction state, similar to $success but as string. 
  48. * 'ok' means that an invoice was marked paid. 
  49. * 'err' indicates an error or unknown input. 
  50. * 'ignore' indicates a message that was processed but irrelevant. 
  51. * Note: This is the state of the original transaction, not the state that 
  52. * is displayed to the user. {@see $manual_state} 
  53. * @since 1.0.1.0 
  54. * @var string $state Access via $item->state (not $item->_state!) 
  55. */ 
  56. protected $_state = null; 
  57.  
  58. /** 
  59. * The subscription linked with the transaction. 
  60. * @since 1.0.1.0 
  61. * @var int 
  62. */ 
  63. protected $subscription_id = 0; 
  64.  
  65. /** 
  66. * The invoice linked with the transaction. 
  67. * @since 1.0.1.0 
  68. * @var int 
  69. */ 
  70. protected $invoice_id = 0; 
  71.  
  72. /** 
  73. * The member associated with the transaction. 
  74. * @since 1.0.1.0 
  75. * @var int 
  76. */ 
  77. protected $member_id = 0; 
  78.  
  79. /** 
  80. * The transaction amount reported by the gateway. 
  81. * @since 1.0.1.0 
  82. * @var int 
  83. */ 
  84. protected $amount = 0; 
  85.  
  86. /** 
  87. * The URL used to report the transaction. 
  88. * This is especially relevant for IPN messages (method "handle") 
  89. * @since 1.0.1.0 
  90. * @var string 
  91. */ 
  92. protected $url = ''; 
  93.  
  94. /** 
  95. * A collection of all POST parameters passed to the $url. 
  96. * @since 1.0.1.0 
  97. * @var array 
  98. */ 
  99. protected $post = null; 
  100.  
  101. /** 
  102. * A collection of all HTTP headers passed to the $url. 
  103. * @since 1.0.2.0 
  104. * @var array 
  105. */ 
  106. protected $headers = null; 
  107.  
  108. /** 
  109. * The manually overwritten state value. 
  110. * If this is empty then $state is the effective state value, otherwise this 
  111. * flag indicates the state that is displayed to the user. 
  112. * @since 1.0.1.0 
  113. * @var string 
  114. */ 
  115. protected $manual_state = ''; 
  116.  
  117. /** 
  118. * Timestamp of setting the $manual_state value. 
  119. * @since 1.0.1.0 
  120. * @var string 
  121. */ 
  122. protected $manual_date = ''; 
  123.  
  124. /** 
  125. * User who changed the $manual_state value. 
  126. * @since 1.0.1.0 
  127. * @var int 
  128. */ 
  129. protected $manual_user = 0; 
  130.  
  131. /** 
  132. * The external transaction ID provided by the gateway. 
  133. * @since 1.0.2.0 
  134. * @var string 
  135. */ 
  136. protected $external_id = ''; 
  137.  
  138.  
  139. // 
  140. // 
  141. // 
  142. // -------------------------------------------------------------- COLLECTION 
  143.  
  144.  
  145. /** 
  146. * Returns the post-type of the current object. 
  147. * @since 1.0.1.0 
  148. * @return string The post-type name. 
  149. */ 
  150. public static function get_post_type() { 
  151. return parent::_post_type( self::$POST_TYPE ); 
  152.  
  153. /** 
  154. * Get custom register post type args for this model. 
  155. * @since 1.0.1.0 
  156. * @return array Post Type details. 
  157. */ 
  158. public static function get_register_post_type_args() { 
  159. $args = array( 
  160. 'label' => __( 'Membership2 Transaction Logs', 'membership2' ),  
  161. 'supports' => array(),  
  162. 'hierarchical' => false,  
  163. 'public' => false,  
  164. 'show_ui' => false,  
  165. 'show_in_menu' => false,  
  166. 'show_in_admin_bar' => false,  
  167. 'show_in_nav_menus' => false,  
  168. 'can_export' => false,  
  169. 'has_archive' => false,  
  170. 'exclude_from_search' => true,  
  171. 'publicly_queryable' => false,  
  172. ); 
  173.  
  174. return apply_filters( 
  175. 'ms_customposttype_register_args',  
  176. $args,  
  177. self::get_post_type() 
  178. ); 
  179.  
  180. /** 
  181. * Get the total number of log entries. 
  182. * For list table pagination. 
  183. * @since 1.0.1.0 
  184. * @param array $args The default query args. 
  185. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  186. * @return int The total count. 
  187. */ 
  188. public static function get_item_count( $args = null ) { 
  189. $args = lib3()->array->get( $args ); 
  190. $args['posts_per_page'] = -1; 
  191. $items = self::get_items( $args ); 
  192.  
  193. $count = count( $items ); 
  194.  
  195. return apply_filters( 
  196. 'ms_model_transactionlog_get_item_count',  
  197. $count,  
  198. $args 
  199. ); 
  200.  
  201. /** 
  202. * Get transaction log items. 
  203. * @since 1.0.1.0 
  204. * @param $args The query post args. 
  205. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  206. * @return array List of transaction log items. 
  207. */ 
  208. public static function get_items( $args = null ) { 
  209. MS_Factory::select_blog(); 
  210. $args = self::get_query_args( $args ); 
  211. $query = new WP_Query( $args ); 
  212. MS_Factory::revert_blog(); 
  213.  
  214. $items = array(); 
  215.  
  216. foreach ( $query->posts as $post_id ) { 
  217. if ( ! get_post_meta( $post_id, 'method', true ) ) { 
  218. // The log entry is incomplete. Do not load it. 
  219. continue; 
  220.  
  221. $items[] = MS_Factory::load( 'MS_Model_Transactionlog', $post_id ); 
  222.  
  223. return apply_filters( 
  224. 'ms_model_transactionlog_get_items',  
  225. $items,  
  226. $args 
  227. ); 
  228.  
  229. /** 
  230. * Get WP_Query object arguments. 
  231. * Default search arguments for this custom post_type. 
  232. * @since 1.0.1.0 
  233. * @param $args The query post args 
  234. * @see @link http://codex.wordpress.org/Class_Reference/WP_Query 
  235. * @return array $args The parsed args. 
  236. */ 
  237. public static function get_query_args( $args ) { 
  238. $defaults = array( 
  239. 'post_type' => self::get_post_type(),  
  240. 'post_status' => 'any',  
  241. 'fields' => 'ids',  
  242. 'order' => 'DESC',  
  243. 'orderby' => 'ID',  
  244. 'posts_per_page' => 20,  
  245. ); 
  246.  
  247. if ( ! empty( $args['state'] ) ) { 
  248. $ids = self::get_state_ids( $args['state'] ); 
  249. if ( ! empty( $args['post__in'] ) ) { 
  250. $ids = array_intersect( $args['post__in'], $ids ); 
  251.  
  252. if ( $ids ) { 
  253. $args['post__in'] = $ids; 
  254. } else { 
  255. $args['post__in'] = array( 0 ); 
  256.  
  257. if ( ! empty( $args['source'] ) ) { 
  258. $ids = self::get_matched_ids( $args['source'][0], $args['source'][1] ); 
  259. if ( ! empty( $args['post__in'] ) ) { 
  260. $ids = array_intersect( $args['post__in'], $ids ); 
  261.  
  262. if ( $ids ) { 
  263. $args['post__in'] = $ids; 
  264. } else { 
  265. $args['post__in'] = array( 0 ); 
  266.  
  267. $args = wp_parse_args( $args, $defaults ); 
  268.  
  269. return apply_filters( 
  270. 'ms_model_transactionlog_get_item_args',  
  271. $args 
  272. ); 
  273.  
  274. /** 
  275. * Returns a list of post_ids that have the specified Transaction State. 
  276. * @since 1.0.1.0 
  277. * @param string $state A valid transaction state [err|ok|ignore]. 
  278. * @return array List of post_ids. 
  279. */ 
  280. static public function get_state_ids( $state ) { 
  281. global $wpdb; 
  282.  
  283. $sql = " 
  284. SELECT p.ID 
  285. FROM 
  286. {$wpdb->posts} p 
  287. LEFT JOIN {$wpdb->postmeta} state1 ON 
  288. state1.post_id = p.ID AND state1.meta_key = 'success' 
  289. LEFT JOIN {$wpdb->postmeta} state2 ON 
  290. state2.post_id = p.ID AND state2.meta_key = 'manual_state' 
  291. INNER JOIN {$wpdb->postmeta} method ON 
  292. method.post_id = p.ID AND method.meta_key = 'method' 
  293. WHERE 
  294. p.post_type = %s 
  295. AND LENGTH( method.meta_value ) > 0 
  296. "; 
  297.  
  298. switch ( $state ) { 
  299. case 'err': 
  300. $sql .= " 
  301. AND (state1.meta_value IS NULL OR state1.meta_value IN ('', '0', 'err')) 
  302. AND (state2.meta_value IS NULL OR state2.meta_value IN ('')) 
  303. "; 
  304. break; 
  305.  
  306. case 'ok': 
  307. $sql .= " 
  308. AND ( 
  309. state1.meta_value IN ('1', 'ok') 
  310. OR state2.meta_value IN ('1', 'ok') 
  311. "; 
  312. break; 
  313.  
  314. case 'ignore': 
  315. $sql .= " 
  316. AND ( 
  317. state1.meta_value IN ('ignore') 
  318. OR state2.meta_value IN ('ignore') 
  319. "; 
  320. break; 
  321.  
  322. $sql = $wpdb->prepare( $sql, self::get_post_type() ); 
  323. $ids = $wpdb->get_col( $sql ); 
  324.  
  325. if ( ! count( $ids ) ) { 
  326. $ids = array( 0 ); 
  327.  
  328. return $ids; 
  329.  
  330. /** 
  331. * Returns a list of post_ids that have the specified source_id. 
  332. * This tries to find transactions for imported subscriptions. 
  333. * @since 1.0.1.2 
  334. * @param string $source_id Subscription ID before import; i.e. original ID. 
  335. * @param string $source The import source. Currently supported: 'm1'. 
  336. * @return array List of post_ids. 
  337. */ 
  338. static public function get_matched_ids( $source_id, $source ) { 
  339. global $wpdb; 
  340.  
  341. $sql = " 
  342. SELECT p.ID 
  343. FROM 
  344. {$wpdb->posts} p 
  345. LEFT JOIN {$wpdb->postmeta} form ON 
  346. form.post_id = p.ID AND form.meta_key = 'post' 
  347. LEFT JOIN {$wpdb->postmeta} gateway ON 
  348. gateway.post_id = p.ID AND gateway.meta_key = 'gateway_id' 
  349. WHERE 
  350. p.post_type = %s 
  351. "; 
  352.  
  353. $source_int = intval( $source_id ); 
  354. $int_len = strlen( $source_int ); 
  355.  
  356. switch ( $source ) { 
  357. case 'm1': 
  358. $sql .= " 
  359. AND gateway.meta_value = 'paypalstandard' 
  360. AND form.meta_value LIKE '%%s:6:\"custom\";s:%%' 
  361. AND form.meta_value LIKE '%%:{$source_int}:%%' 
  362. "; 
  363. break; 
  364.  
  365. case 'pay_btn': 
  366. $sql .= " 
  367. AND gateway.meta_value = 'paypalstandard' 
  368. AND form.meta_value LIKE '%%s:6:\"btn_id\";s:{$int_len}:\"{$source_int}\";%%' 
  369. AND form.meta_value LIKE '%%s:11:\"payer_email\";%%' 
  370. "; 
  371. break; 
  372.  
  373. $sql = $wpdb->prepare( $sql, self::get_post_type() ); 
  374. $ids = $wpdb->get_col( $sql ); 
  375.  
  376. if ( ! count( $ids ) ) { 
  377. $ids = array( 0 ); 
  378.  
  379. return $ids; 
  380.  
  381. /** 
  382. * Checks if the specified transaction was already successfully processed 
  383. * to avoid duplicate payments. 
  384. * @since 1.0.2.0 
  385. * @param string $gateway The payment gateway ID. 
  386. * @param string $external_id The external transaction ID. 
  387. * @return bool True if the transaction was processed/paid already. 
  388. */ 
  389. static public function was_processed( $gateway, $external_id ) { 
  390. global $wpdb; 
  391.  
  392. $sql = " 
  393. SELECT COUNT(1) 
  394. FROM {$wpdb->posts} p 
  395. INNER JOIN {$wpdb->postmeta} gateway ON 
  396. gateway.post_id=p.ID AND gateway.meta_key='gateway_id' 
  397. INNER JOIN {$wpdb->postmeta} ext_id ON 
  398. ext_id.post_id=p.ID AND ext_id.meta_key='external_id' 
  399. LEFT JOIN {$wpdb->postmeta} state1 ON 
  400. state1.post_id = p.ID AND state1.meta_key = 'success' 
  401. LEFT JOIN {$wpdb->postmeta} state2 ON 
  402. state2.post_id = p.ID AND state2.meta_key = 'manual_state' 
  403. WHERE 
  404. p.post_type = %s 
  405. AND gateway.meta_value = %s 
  406. AND ext_id.meta_value = %s 
  407. AND ( 
  408. state1.meta_value IN ('1', 'ok') 
  409. OR state2.meta_value IN ('1', 'ok') 
  410. "; 
  411. $sql = $wpdb->prepare( 
  412. $sql,  
  413. self::get_post_type(),  
  414. $gateway,  
  415. $external_id 
  416. ); 
  417. $res = intval( $wpdb->get_var( $sql ) ); 
  418.  
  419. return $res > 0; 
  420.  
  421.  
  422. // 
  423. // 
  424. // 
  425. // ------------------------------------------------------------- SINGLE ITEM 
  426.  
  427.  
  428. /** 
  429. * Initializes variables right before saving the model. 
  430. * @since 1.0.1.0 
  431. */ 
  432. public function before_save() { 
  433. // Translate a boolean success value to a string. 
  434. if ( true === $this->success ) { 
  435. $this->success = 'ok'; 
  436. } elseif ( null === $this->success ) { 
  437. $this->success = 'ignore'; 
  438. } elseif ( false === $this->success ) { 
  439. $this->success = 'err'; 
  440.  
  441. if ( ! $this->id ) { 
  442. $this->url = lib3()->net->current_url(); 
  443. $this->post = $_POST; 
  444. $this->headers = $this->get_headers(); 
  445. $this->user_id = get_current_user_id(); 
  446. $this->title = 'Transaction Log'; 
  447.  
  448. /** 
  449. * Prepares an object after it was loaded from database. 
  450. * @since 1.0.1.0 
  451. */ 
  452. public function prepare_obj() { 
  453. $this->set_state( $this->success ); 
  454.  
  455. if ( ! $this->member_id ) { 
  456. if ( $this->invoice_id ) { 
  457. $invoice = MS_Factory::load( 'MS_Model_Invoice', $this->invoice_id ); 
  458. $this->member_id = $invoice->user_id; 
  459. } elseif ( MS_Gateway_Paypalstandard::ID == $this->gateway ) { 
  460. /** 
  461. * Migration logic for M1 IPN messages: 
  462. * M1 did use the "custom" field to link the transaction to a 
  463. * subscription. The custom field contains these details: 
  464. * timestamp : user_id : subscription_id : key 
  465. */ 
  466. if ( is_array( $this->post ) && ! empty( $this->post['custom'] ) ) { 
  467. $infos = explode( ':', $this->post['custom'] ); 
  468. if ( count( $infos ) > 2 && is_numeric( $infos[1] ) ) { 
  469. $this->member_id = intval( $infos[1] ); 
  470.  
  471. /** 
  472. * Populate custom fields from the wp_posts table. 
  473. * @since 1.0.1.0 
  474. * @param WP_Post $post The post object. 
  475. */ 
  476. public function load_post_data( $post ) { 
  477. $this->date = $post->post_date; 
  478.  
  479. /** 
  480. * Returns a list of all HTTP headers. 
  481. * @since 1.0.1.2 
  482. * @return array List of all incoming HTTP headers. 
  483. */ 
  484. protected function get_headers() { 
  485. $headers = array(); 
  486.  
  487. if ( function_exists( 'getallheaders' ) ) { 
  488. $headers = getallheaders(); 
  489. } else { 
  490. foreach ( $_SERVER as $key => $value ) { 
  491. if ( 'HTTP_' == substr( $key, 0, 5 ) ) { 
  492. $key = str_replace( '_', ' ', substr( $key, 5 ) ); 
  493. $key = str_replace( ' ', '-', ucwords( strtolower( $key ) ) ); 
  494. $headers[ $key ] = $value; 
  495.  
  496. return $headers; 
  497.  
  498. /** 
  499. * Sets the manual-state value of the transaction log entry. 
  500. * @since 1.0.0 
  501. * @param string $state The new state of the item. 
  502. * @return bool True on success. 
  503. */ 
  504. public function manual_state( $state ) { 
  505. if ( 'err' != $this->_state ) { 
  506. // Not allowed: Only error state can be manually corrected. 
  507. return false; 
  508.  
  509. switch ( $state ) { 
  510. case 'ignore': 
  511. if ( $this->manual_state ) { 
  512. // Not allowed: Manual state was defined already. 
  513. return false; 
  514. break; 
  515.  
  516. case 'clear': 
  517. if ( 'ignore' != $this->manual_state ) { 
  518. // Not allowed: Only "ingored" state can be cleared. 
  519. return false; 
  520. break; 
  521.  
  522. case 'ok': 
  523. if ( $this->manual ) { 
  524. // Not allowed: Manual state is already defined. 
  525. return false; 
  526. if ( ! $this->invoice_id || ! $this->subscription_id ) { 
  527. // Not allowed: Required data is missing for OK status. 
  528. return false; 
  529. break; 
  530.  
  531. default: 
  532. // Not allowed: Unknown state. 
  533. return false; 
  534.  
  535. if ( 'clear' == $state ) { 
  536. $this->manual_state = ''; 
  537. $this->manual_date = ''; 
  538. $this->manual_user = 0; 
  539. } else { 
  540. $this->manual_state = $state; 
  541. $this->manual_date = MS_Helper_Period::current_time(); 
  542. $this->manual_user = get_current_user_id(); 
  543. return true; 
  544.  
  545. /** 
  546. * Returns the WP_User object for the manual user. 
  547. * @since 1.0.1.0 
  548. * @return WP_User 
  549. */ 
  550. public function get_manual_user() { 
  551. $user = new WP_User( $this->manual_user ); 
  552. return $user; 
  553.  
  554. /** 
  555. * Returns the Member object associated with this transaction. 
  556. * @since 1.0.1.0 
  557. * @return MS_Model_Member 
  558. */ 
  559. public function get_member() { 
  560. return MS_Factory::load( 'MS_Model_Member', $this->member_id ); 
  561.  
  562. /** 
  563. * Returns the Invoice model linked with the transaction. 
  564. * @since 1.0.1.0 
  565. * @return bool|MS_Model_Invoice 
  566. */ 
  567. public function get_invoice() { 
  568. $result = false; 
  569.  
  570. if ( $this->invoice_id ) { 
  571. $invoice = MS_Factory::load( 'MS_Model_Invoice', $this->invoice_id ); 
  572. if ( $invoice->id == $this->invoice_id ) { 
  573. $result = $invoice; 
  574.  
  575. return $result; 
  576.  
  577. /** 
  578. * Updates the subscription_id and member_id based on the specified ID. 
  579. * @since 1.0.1.0 
  580. * @param int $id A valid subscription ID. 
  581. */ 
  582. protected function set_subscription( $id ) { 
  583. $subscription = MS_Factory::load( 'MS_Model_Relationship', $id ); 
  584. $this->subscription_id = $subscription->id; 
  585. $this->member_id = $subscription->user_id; 
  586.  
  587. /** 
  588. * Updates the invoice_id, subscription_id and member_id based on the 
  589. * specified ID. 
  590. * @since 1.0.1.0 
  591. * @param int $id A valid invoice ID. 
  592. */ 
  593. protected function set_invoice( $id ) { 
  594. $invoice = MS_Factory::load( 'MS_Model_Invoice', $id ); 
  595. $this->invoice_id = $invoice->id; 
  596. $this->subscription_id = $invoice->ms_relationship_id; 
  597. $this->member_id = $invoice->user_id; 
  598.  
  599. /** 
  600. * Updates the state and success properties of the object. 
  601. * @since 1.0.1.0 
  602. * @param mixed $value The new state value 
  603. */ 
  604. protected function set_state( $value ) { 
  605. switch ( $value ) { 
  606. case 'ok': 
  607. $this->_state = $value; 
  608. $this->success = true; 
  609. break; 
  610.  
  611. case 'ignore': 
  612. $this->_state = $value; 
  613. $this->success = null; 
  614. break; 
  615.  
  616. case 'err': 
  617. $this->_state = $value; 
  618. $this->success = false; 
  619. break; 
  620.  
  621. case true: 
  622. $this->_state = 'ok'; 
  623. $this->success = $value; 
  624. break; 
  625.  
  626. case false: 
  627. $this->_state = 'err'; 
  628. $this->success = $value; 
  629. break; 
  630.  
  631. case null: 
  632. $this->_state = 'ignore'; 
  633. $this->success = 'ignore'; 
  634. break; 
  635.  
  636. default: 
  637. // Unrecognized values are not saved. 
  638.  
  639. /** 
  640. * Returns property associated with the render. 
  641. * @since 1.0.1.0 
  642. * @internal 
  643. * @param string $property The name of a property. 
  644. * @return mixed Returns mixed value of a property or NULL if a property doesn't exist. 
  645. */ 
  646. public function __get( $property ) { 
  647. $value = null; 
  648.  
  649. switch ( $property ) { 
  650. case 'state': 
  651. if ( $this->manual_state ) { 
  652. $value = $this->manual_state; 
  653. } else { 
  654. $value = $this->_state; 
  655. break; 
  656.  
  657. case 'is_manual': 
  658. $value = ! ! $this->manual_state; 
  659. break; 
  660.  
  661. default: 
  662. if ( property_exists( $this, $property ) ) { 
  663. $value = $this->$property; 
  664. break; 
  665.  
  666. return apply_filters( 
  667. 'ms_model_transactionlog__get',  
  668. $value,  
  669. $property,  
  670. $this 
  671. ); 
  672.  
  673. /** 
  674. * Set specific property. 
  675. * @since 1.0.1.0 
  676. * @internal 
  677. * @param string $property The name of a property to associate. 
  678. * @param mixed $value The value of a property. 
  679. */ 
  680. public function __set( $property, $value ) { 
  681. switch ( $property ) { 
  682. case 'state': 
  683. case 'success': 
  684. $this->set_state( $value ); 
  685. break; 
  686.  
  687. case 'invoice_id': 
  688. $this->set_invoice( $value ); 
  689. break; 
  690.  
  691. case 'subscription_id': 
  692. $this->set_subscription( $value ); 
  693. break; 
  694.  
  695. default: 
  696. if ( property_exists( $this, $property ) ) { 
  697. $this->$property = $value; 
  698. break; 
  699.  
  700. do_action( 
  701. 'ms_model_transactionlog__set_after',  
  702. $property,  
  703. $value,  
  704. $this 
  705. );