@@ -124,8 +124,15 @@ public function update_transaction_queue( $source, $args, $context, $info ) {
124124 return ;
125125 }
126126
127- // Initialize transaction ID.
128- if ( is_null ( $ this ->transaction_id ) ) {
127+
128+ // Bail if transaction has already been completed. There are times when the underlying action runs twice.
129+ if ( ! is_null ( $ this ->transaction_id ) ) {
130+ $ transaction_queue = get_transient ( "woo_session_transactions_queue_ {$ this ->session_handler ->get_customer_id ()}" );
131+ if ( in_array ( $ this ->transaction_id , array_column ( $ transaction_queue , 'transaction_id ' ), true ) ) {
132+ return ;
133+ }
134+ } else {
135+ // Initialize transaction ID.
129136 $ mutation = $ info ->fieldName ; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
130137 $ this ->transaction_id = \uniqid ( "wooSession_ {$ mutation }_ " );
131138 }
@@ -136,6 +143,9 @@ public function update_transaction_queue( $source, $args, $context, $info ) {
136143 $ this ->update_transaction_queue ( $ source , $ args , $ context , $ info );
137144 } else {
138145 $ this ->session_handler ->reload_data ();
146+
147+ // Set a timestamp on the transaction, which will allow us to check for any stale transactions that accidentally get left behind.
148+ $ this ->set_timestamp ();
139149 }
140150 }
141151
@@ -156,6 +166,10 @@ public function next_transaction() {
156166 // If current transaction is the lead exit loop.
157167 } elseif ( $ this ->transaction_id === $ transaction_queue [0 ]['transaction_id ' ] ) {
158168 return true ;
169+ } elseif ( true === $ this ->did_transaction_expire ( $ transaction_queue ) ) {
170+ // If transaction has expired, remove it from the queue array and continue loop
171+ array_shift ( $ transaction_queue );
172+ $ this ->save_transaction_queue ( $ transaction_queue );
159173 }
160174
161175 return false ;
@@ -169,15 +183,16 @@ public function next_transaction() {
169183 */
170184 public function get_transaction_queue () {
171185 // Get transaction queue.
172- $ transaction_queue = get_transient ( "woo_session_transactions_queue_ {$ this ->_customer_id }" );
186+ $ transaction_queue = get_transient ( "woo_session_transactions_queue_ {$ this ->session_handler -> get_customer_id () }" );
173187 if ( ! $ transaction_queue ) {
174188 $ transaction_queue = array ();
175189 }
176190
177191 // If transaction ID not in queue, add it, and start transaction.
178- if ( false === array_search ( $ this ->transaction_id , array_column ( $ transaction_queue , 'transaction_id ' ), true ) ) {
179- $ transaction_id = $ this ->transaction_id ;
180- $ snapshot = $ this ->_data ;
192+ if ( ! in_array ( $ this ->transaction_id , array_column ( $ transaction_queue , 'transaction_id ' ), true ) ) {
193+ $ transaction_id = $ this ->transaction_id ;
194+ $ snapshot = $ this ->session_handler ->get_session_data ();
195+
181196 $ transaction_queue [] = compact ( 'transaction_id ' , 'snapshot ' );
182197
183198 // Update queue.
@@ -199,7 +214,7 @@ public function pop_transaction_id() {
199214 }
200215
201216 // Get transaction queue.
202- $ transaction_queue = get_transient ( "woo_session_transactions_queue_ {$ this ->_customer_id }" );
217+ $ transaction_queue = get_transient ( "woo_session_transactions_queue_ {$ this ->session_handler -> get_customer_id () }" );
203218
204219 // Throw if transaction ID not on top.
205220 if ( $ this ->transaction_id !== $ transaction_queue [0 ]['transaction_id ' ] ) {
@@ -221,11 +236,60 @@ public function pop_transaction_id() {
221236 public function save_transaction_queue ( $ queue = array () ) {
222237 // If queue empty delete transient and bail.
223238 if ( empty ( $ queue ) ) {
224- delete_transient ( "woo_session_transactions_queue_ {$ this ->_customer_id }" );
239+ delete_transient ( "woo_session_transactions_queue_ {$ this ->session_handler -> get_customer_id () }" );
225240 return ;
226241 }
227242
228243 // Save transaction queue.
229- set_transient ( "woo_session_transactions_queue_ {$ this ->_customer_id }" , $ queue );
244+ set_transient ( "woo_session_transactions_queue_ {$ this ->session_handler ->get_customer_id ()}" , $ queue );
245+ }
246+
247+ public function set_timestamp () {
248+ $ transaction_queue = $ this ->get_transaction_queue ();
249+
250+ // Bail if we don't have a queue to add a timestamp against.
251+ if ( empty ( $ transaction_queue [0 ] ) ) {
252+ return ;
253+ }
254+
255+ $ transaction_queue [0 ]['timestamp ' ] = time ();
256+
257+ $ this ->save_transaction_queue ( $ transaction_queue );
258+ }
259+
260+ /**
261+ * The length of time in seconds a transaction should stay in the queue
262+ *
263+ * @return mixed|void
264+ */
265+ public function get_timestamp_threshold () {
266+ return apply_filters ( 'woographql_session_transaction_timeout ' , 30 );
267+ }
268+
269+ /**
270+ * Whether the transaction has expired. This helps prevent infinite loops while searching through the transaction
271+ * queue.
272+ *
273+ * @param $transaction_queue
274+ *
275+ * @return bool
276+ */
277+ public function did_transaction_expire ( $ transaction_queue ) {
278+ // Guard against empty transaction queue. We assume that it is invalid since we cannot calculate.
279+ if ( empty ( $ transaction_queue ) ) {
280+ return true ;
281+ }
282+
283+ // Guard against empty timestamp. We assume that it is invalid since we cannot calculate.
284+ if ( empty ( $ transaction_queue [0 ] ) || empty ( $ transaction_queue [0 ]['timestamp ' ] ) ) {
285+ return true ;
286+ }
287+
288+ $ now = time ();
289+ $ stamp = $ transaction_queue [0 ]['timestamp ' ];
290+ $ threshold = $ this ->get_timestamp_threshold ();
291+ $ difference = $ now - $ stamp ;
292+
293+ return $ difference > $ threshold ;
230294 }
231295}
0 commit comments