| @@ -10,8 +10,9 @@ | ||
| 10 | 10 | */ |
| 11 | 11 | |
| 12 | 12 | namespace SpringDevs\Subscription\Admin; |
| 13 | 13 | |
| 14 | +use SpringDevs\Subscription\Illuminate\Helper; | |
| 14 | 15 | use SpringDevs\Subscription\Illuminate\Plans\PlanRepository; |
| 15 | 16 | |
| 16 | 17 | /** |
| 17 | 18 | * Plan presenter. |
| @@ -46,8 +47,14 @@ | ||
| 46 | 47 | if ( ! $tree ) { |
| 47 | 48 | return null; |
| 48 | 49 | } |
| 49 | 50 | |
| 51 | + $order = is_array( $tree['data'] ) ? $tree['data'] : array(); | |
| 52 | + | |
| 53 | + // Saved drag-and-drop order. Sorting the tree's plans here also orders | |
| 54 | + // the duration rows inside every product's price table. | |
| 55 | + $tree['plans'] = self::apply_order( $tree['plans'], $order['term_order'] ?? array(), false ); | |
| 56 | + | |
| 50 | 57 | $type_key = $tree['type_key']; |
| 51 | 58 | $terms = array(); |
| 52 | 59 | |
| 53 | 60 | foreach ( $tree['plans'] as $plan ) { |
| @@ -67,13 +74,50 @@ | ||
| 67 | 74 | 'status' => $tree['status'], |
| 68 | 75 | 'created' => self::ago( $tree['created_at'] ), |
| 69 | 76 | 'edited' => self::ago( $tree['updated_at'] ), |
| 70 | 77 | 'terms' => $terms, |
| 71 | - 'products' => self::products( $tree, $type_key ), | |
| 78 | + 'products' => self::apply_order( self::products( $tree, $type_key ), $order['product_order'] ?? array(), true ), | |
| 72 | 79 | ); |
| 73 | 80 | } |
| 74 | 81 | |
| 75 | 82 | /** |
| 83 | + * Reorder items by a saved list of ids (the detail page's drag-and-drop). | |
| 84 | + * | |
| 85 | + * Items missing from the list keep their default relative order and go | |
| 86 | + * after the listed ones — or before them when $unlisted_first, so a newly | |
| 87 | + * attached product still shows at the top as it did before any reorder. | |
| 88 | + * | |
| 89 | + * @param array $items Items, each with an `id`. | |
| 90 | + * @param array $ids Saved order (ids). | |
| 91 | + * @param bool $unlisted_first Put unlisted items first. | |
| 92 | + * | |
| 93 | + * @return array | |
| 94 | + */ | |
| 95 | + protected static function apply_order( $items, $ids, $unlisted_first ) { | |
| 96 | + if ( empty( $ids ) || ! is_array( $ids ) ) { | |
| 97 | + return $items; | |
| 98 | + } | |
| 99 | + | |
| 100 | + $position = array_flip( array_map( 'intval', array_values( $ids ) ) ); | |
| 101 | + $listed = array(); | |
| 102 | + $unlisted = array(); | |
| 103 | + | |
| 104 | + foreach ( $items as $item ) { | |
| 105 | + $id = (int) $item['id']; | |
| 106 | + if ( isset( $position[ $id ] ) ) { | |
| 107 | + $listed[ $position[ $id ] ] = $item; | |
| 108 | + } else { | |
| 109 | + $unlisted[] = $item; | |
| 110 | + } | |
| 111 | + } | |
| 112 | + | |
| 113 | + ksort( $listed ); | |
| 114 | + $listed = array_values( $listed ); | |
| 115 | + | |
| 116 | + return $unlisted_first ? array_merge( $unlisted, $listed ) : array_merge( $listed, $unlisted ); | |
| 117 | + } | |
| 118 | + | |
| 119 | + /** | |
| 76 | 120 | * Group relations by product into the products[] contract (read-only). |
| 77 | 121 | * |
| 78 | 122 | * @param array $tree Group tree (plans → relations). |
| 79 | 123 | * @param string $type_key Plan type key. |
| @@ -107,8 +151,9 @@ | ||
| 107 | 151 | 'base_price' => ( $product && ! $is_variable ) ? self::money( (float) $product->get_price() ) : '', |
| 108 | 152 | 'one_time_on' => $product ? ( 'yes' === $product->get_meta( '_subscrpt_one_time_enabled' ) ) : false, |
| 109 | 153 | 'ot_regular' => ( $product && ! $is_variable ) ? self::one_time_price( $product ) : '', |
| 110 | 154 | 'ot_offer' => ( $product && ! $is_variable ) ? (string) $product->get_sale_price() : '', |
| 155 | + 'user_cancel' => $product ? Helper::can_user_cancel( $product->get_meta( '_subscrpt_user_cancel' ) ) : true, | |
| 111 | 156 | 'edit_url' => get_edit_post_link( $oid, 'raw' ), |
| 112 | 157 | 'view_url' => get_permalink( $oid ), |
| 113 | 158 | 'rows' => array(), |
| 114 | 159 | 'variations' => array(), |