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|array $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. if ( ! is_array( $state ) ) { $state = array( $state ); } 
  299. $state_cond = array(); 
  300.  
  301. foreach ( $state as $key ) { 
  302. switch ( $key ) { 
  303. case 'err': 
  304. $state_cond[] = "( 
  305. (state1.meta_value IS NULL OR state1.meta_value IN ('', '0', 'err')) 
  306. AND (state2.meta_value IS NULL OR state2.meta_value IN ('')) 
  307. )"; 
  308. break; 
  309.  
  310. case 'ok': 
  311. $state_cond[] = "( 
  312. state1.meta_value IN ('1', 'ok') 
  313. OR state2.meta_value IN ('1', 'ok') 
  314. )"; 
  315. break; 
  316.  
  317. case 'ignore': 
  318. $state_cond[] = "( 
  319. state1.meta_value IN ('ignore') 
  320. OR state2.meta_value IN ('ignore') 
  321. )"; 
  322. break; 
  323. $sql .= 'AND (' . implode( ' OR ', $state_cond ) . ')'; 
  324.  
  325. $sql = $wpdb->prepare( $sql, self::get_post_type() ); 
  326. $ids = $wpdb->get_col( $sql ); 
  327.  
  328. if ( ! count( $ids ) ) { 
  329. $ids = array( 0 ); 
  330.  
  331. return $ids; 
  332.  
  333. /** 
  334. * Returns a list of post_ids that have the specified source_id. 
  335. * This tries to find transactions for imported subscriptions. 
  336. * @since 1.0.1.2 
  337. * @param string $source_id Subscription ID before import; i.e. original ID. 
  338. * @param string $source The import source. Currently supported: 'm1'. 
  339. * @return array List of post_ids. 
  340. */ 
  341. static public function get_matched_ids( $source_id, $source ) { 
  342. global $wpdb; 
  343.  
  344. $sql = " 
  345. SELECT p.ID 
  346. FROM 
  347. {$wpdb->posts} p 
  348. LEFT JOIN {$wpdb->postmeta} form ON 
  349. form.post_id = p.ID AND form.meta_key = 'post' 
  350. LEFT JOIN {$wpdb->postmeta} gateway ON 
  351. gateway.post_id = p.ID AND gateway.meta_key = 'gateway_id' 
  352. WHERE 
  353. p.post_type = %s 
  354. "; 
  355.  
  356. $source_int = intval( $source_id ); 
  357. $int_len = strlen( $source_int ); 
  358.  
  359. switch ( $source ) { 
  360. case 'm1': 
  361. $sql .= " 
  362. AND gateway.meta_value = 'paypalstandard' 
  363. AND form.meta_value REGEXP 's:6:\"custom\";s:[0-9]+:\"[0-9]+:[0-9]+:{$source_int}:' 
  364. "; 
  365. break; 
  366.  
  367. case 'pay_btn': 
  368. $sql .= " 
  369. AND gateway.meta_value = 'paypalstandard' 
  370. AND form.meta_value LIKE '%%s:6:\"btn_id\";s:{$int_len}:\"{$source_int}\";%%' 
  371. AND form.meta_value LIKE '%%s:11:\"payer_email\";%%' 
  372. "; 
  373. break; 
  374.  
  375. $sql = $wpdb->prepare( $sql, self::get_post_type() ); 
  376. $ids = $wpdb->get_col( $sql ); 
  377.  
  378. if ( ! count( $ids ) ) { 
  379. $ids = array( 0 ); 
  380.  
  381. return $ids; 
  382.  
  383. /** 
  384. * Checks if the specified transaction was already successfully processed 
  385. * to avoid duplicate payments. 
  386. * @since 1.0.2.0 
  387. * @param string $gateway The payment gateway ID. 
  388. * @param string $external_id The external transaction ID. 
  389. * @return bool True if the transaction was processed/paid already. 
  390. */ 
  391. static public function was_processed( $gateway, $external_id ) { 
  392. global $wpdb; 
  393.  
  394. $sql = " 
  395. SELECT COUNT(1) 
  396. FROM {$wpdb->posts} p 
  397. INNER JOIN {$wpdb->postmeta} gateway ON 
  398. gateway.post_id=p.ID AND gateway.meta_key='gateway_id' 
  399. INNER JOIN {$wpdb->postmeta} ext_id ON 
  400. ext_id.post_id=p.ID AND ext_id.meta_key='external_id' 
  401. LEFT JOIN {$wpdb->postmeta} state1 ON 
  402. state1.post_id = p.ID AND state1.meta_key = 'success' 
  403. LEFT JOIN {$wpdb->postmeta} state2 ON 
  404. state2.post_id = p.ID AND state2.meta_key = 'manual_state' 
  405. WHERE 
  406. p.post_type = %s 
  407. AND gateway.meta_value = %s 
  408. AND ext_id.meta_value = %s 
  409. AND ( 
  410. state1.meta_value IN ('1', 'ok') 
  411. OR state2.meta_value IN ('1', 'ok') 
  412. "; 
  413. $sql = $wpdb->prepare( 
  414. $sql,  
  415. self::get_post_type(),  
  416. $gateway,  
  417. $external_id 
  418. ); 
  419. $res = intval( $wpdb->get_var( $sql ) ); 
  420.  
  421. return $res > 0; 
  422.  
  423.  
  424. // 
  425. // 
  426. // 
  427. // ------------------------------------------------------------- SINGLE ITEM 
  428.  
  429.  
  430. /** 
  431. * Initializes variables right before saving the model. 
  432. * @since 1.0.1.0 
  433. */ 
  434. public function before_save() { 
  435. // Translate a boolean success value to a string. 
  436. if ( true === $this->success ) { 
  437. $this->success = 'ok'; 
  438. } elseif ( null === $this->success ) { 
  439. $this->success = 'ignore'; 
  440. } elseif ( false === $this->success ) { 
  441. $this->success = 'err'; 
  442.  
  443. if ( ! $this->id ) { 
  444. $this->url = lib3()->net->current_url(); 
  445. $this->post = $_POST; 
  446. $this->headers = $this->get_headers(); 
  447. $this->user_id = get_current_user_id(); 
  448. $this->title = 'Transaction Log'; 
  449.  
  450. /** 
  451. * Prepares an object after it was loaded from database. 
  452. * @since 1.0.1.0 
  453. */ 
  454. public function prepare_obj() { 
  455. $this->set_state( $this->success ); 
  456.  
  457. if ( ! $this->member_id ) { 
  458. if ( $this->invoice_id ) { 
  459. $invoice = MS_Factory::load( 'MS_Model_Invoice', $this->invoice_id ); 
  460. $this->member_id = $invoice->user_id; 
  461. } elseif ( MS_Gateway_Paypalstandard::ID == $this->gateway ) { 
  462. /** 
  463. * Migration logic for M1 IPN messages: 
  464. * M1 did use the "custom" field to link the transaction to a 
  465. * subscription. The custom field contains these details: 
  466. * timestamp : user_id : subscription_id : key 
  467. */ 
  468. if ( is_array( $this->post ) && ! empty( $this->post['custom'] ) ) { 
  469. $infos = explode( ':', $this->post['custom'] ); 
  470. if ( count( $infos ) > 2 && is_numeric( $infos[1] ) ) { 
  471. $this->member_id = intval( $infos[1] ); 
  472.  
  473. /** 
  474. * Populate custom fields from the wp_posts table. 
  475. * @since 1.0.1.0 
  476. * @param WP_Post $post The post object. 
  477. */ 
  478. public function load_post_data( $post ) { 
  479. $this->date = $post->post_date; 
  480.  
  481. /** 
  482. * Returns a list of all HTTP headers. 
  483. * @since 1.0.1.2 
  484. * @return array List of all incoming HTTP headers. 
  485. */ 
  486. protected function get_headers() { 
  487. $headers = array(); 
  488.  
  489. if ( function_exists( 'getallheaders' ) ) { 
  490. $headers = getallheaders(); 
  491. } else { 
  492. foreach ( $_SERVER as $key => $value ) { 
  493. if ( 'HTTP_' == substr( $key, 0, 5 ) ) { 
  494. $key = str_replace( '_', ' ', substr( $key, 5 ) ); 
  495. $key = str_replace( ' ', '-', ucwords( strtolower( $key ) ) ); 
  496. $headers[ $key ] = $value; 
  497.  
  498. return $headers; 
  499.  
  500. /** 
  501. * Sets the manual-state value of the transaction log entry. 
  502. * @since 1.0.0 
  503. * @param string $state The new state of the item. 
  504. * @return bool True on success. 
  505. */ 
  506. public function manual_state( $state ) { 
  507. if ( 'err' != $this->_state ) { 
  508. // Not allowed: Only error state can be manually corrected. 
  509. return false; 
  510.  
  511. switch ( $state ) { 
  512. case 'ignore': 
  513. if ( $this->manual_state ) { 
  514. // Not allowed: Manual state was defined already. 
  515. return false; 
  516. break; 
  517.  
  518. case 'clear': 
  519. if ( 'ignore' != $this->manual_state ) { 
  520. // Not allowed: Only "ingored" state can be cleared. 
  521. return false; 
  522. break; 
  523.  
  524. case 'ok': 
  525. if ( $this->manual ) { 
  526. // Not allowed: Manual state is already defined. 
  527. return false; 
  528. if ( ! $this->invoice_id || ! $this->subscription_id ) { 
  529. // Not allowed: Required data is missing for OK status. 
  530. return false; 
  531. break; 
  532.  
  533. default: 
  534. // Not allowed: Unknown state. 
  535. return false; 
  536.  
  537. if ( 'clear' == $state ) { 
  538. $this->manual_state = ''; 
  539. $this->manual_date = ''; 
  540. $this->manual_user = 0; 
  541. } else { 
  542. $this->manual_state = $state; 
  543. $this->manual_date = MS_Helper_Period::current_time(); 
  544. $this->manual_user = get_current_user_id(); 
  545. return true; 
  546.  
  547. /** 
  548. * Returns the WP_User object for the manual user. 
  549. * @since 1.0.1.0 
  550. * @return WP_User 
  551. */ 
  552. public function get_manual_user() { 
  553. $user = new WP_User( $this->manual_user ); 
  554. return $user; 
  555.  
  556. /** 
  557. * Returns the Member object associated with this transaction. 
  558. * @since 1.0.1.0 
  559. * @return MS_Model_Member 
  560. */ 
  561. public function get_member() { 
  562. return MS_Factory::load( 'MS_Model_Member', $this->member_id ); 
  563.  
  564. /** 
  565. * Returns the Invoice model linked with the transaction. 
  566. * @since 1.0.1.0 
  567. * @return bool|MS_Model_Invoice 
  568. */ 
  569. public function get_invoice() { 
  570. $result = false; 
  571.  
  572. if ( $this->invoice_id ) { 
  573. $invoice = MS_Factory::load( 'MS_Model_Invoice', $this->invoice_id ); 
  574. if ( $invoice->id == $this->invoice_id ) { 
  575. $result = $invoice; 
  576.  
  577. return $result; 
  578.  
  579. /** 
  580. * Updates the subscription_id and member_id based on the specified ID. 
  581. * @since 1.0.1.0 
  582. * @param int $id A valid subscription ID. 
  583. */ 
  584. protected function set_subscription( $id ) { 
  585. $subscription = MS_Factory::load( 'MS_Model_Relationship', $id ); 
  586. $this->subscription_id = $subscription->id; 
  587. $this->member_id = $subscription->user_id; 
  588.  
  589. /** 
  590. * Updates the invoice_id, subscription_id and member_id based on the 
  591. * specified ID. 
  592. * @since 1.0.1.0 
  593. * @param int $id A valid invoice ID. 
  594. */ 
  595. protected function set_invoice( $id ) { 
  596. $invoice = MS_Factory::load( 'MS_Model_Invoice', $id ); 
  597. $this->invoice_id = $invoice->id; 
  598. $this->subscription_id = $invoice->ms_relationship_id; 
  599. $this->member_id = $invoice->user_id; 
  600.  
  601. /** 
  602. * Updates the state and success properties of the object. 
  603. * @since 1.0.1.0 
  604. * @param mixed $value The new state value 
  605. */ 
  606. protected function set_state( $value ) { 
  607. switch ( $value ) { 
  608. case 'ok': 
  609. $this->_state = $value; 
  610. $this->success = true; 
  611. break; 
  612.  
  613. case 'ignore': 
  614. $this->_state = $value; 
  615. $this->success = null; 
  616. break; 
  617.  
  618. case 'err': 
  619. $this->_state = $value; 
  620. $this->success = false; 
  621. break; 
  622.  
  623. case true: 
  624. $this->_state = 'ok'; 
  625. $this->success = $value; 
  626. break; 
  627.  
  628. case false: 
  629. $this->_state = 'err'; 
  630. $this->success = $value; 
  631. break; 
  632.  
  633. case null: 
  634. $this->_state = 'ignore'; 
  635. $this->success = 'ignore'; 
  636. break; 
  637.  
  638. default: 
  639. // Unrecognized values are not saved. 
  640.  
  641. /** 
  642. * Returns property associated with the render. 
  643. * @since 1.0.1.0 
  644. * @internal 
  645. * @param string $property The name of a property. 
  646. * @return mixed Returns mixed value of a property or NULL if a property doesn't exist. 
  647. */ 
  648. public function __get( $property ) { 
  649. $value = null; 
  650.  
  651. switch ( $property ) { 
  652. case 'state': 
  653. if ( $this->manual_state ) { 
  654. $value = $this->manual_state; 
  655. } else { 
  656. $value = $this->_state; 
  657. break; 
  658.  
  659. case 'is_manual': 
  660. $value = ! ! $this->manual_state; 
  661. break; 
  662.  
  663. default: 
  664. if ( property_exists( $this, $property ) ) { 
  665. $value = $this->$property; 
  666. break; 
  667.  
  668. return apply_filters( 
  669. 'ms_model_transactionlog__get',  
  670. $value,  
  671. $property,  
  672. $this 
  673. ); 
  674.  
  675. /** 
  676. * Set specific property. 
  677. * @since 1.0.1.0 
  678. * @internal 
  679. * @param string $property The name of a property to associate. 
  680. * @param mixed $value The value of a property. 
  681. */ 
  682. public function __set( $property, $value ) { 
  683. switch ( $property ) { 
  684. case 'state': 
  685. case 'success': 
  686. $this->set_state( $value ); 
  687. break; 
  688.  
  689. case 'invoice_id': 
  690. $this->set_invoice( $value ); 
  691. break; 
  692.  
  693. case 'subscription_id': 
  694. $this->set_subscription( $value ); 
  695. break; 
  696.  
  697. default: 
  698. if ( property_exists( $this, $property ) ) { 
  699. $this->$property = $value; 
  700. break; 
  701.  
  702. do_action( 
  703. 'ms_model_transactionlog__set_after',  
  704. $property,  
  705. $value,  
  706. $this 
  707. );