PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 4.7.1
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v4.7.1
4.7.2 4.7.1 4.7.0 4.6.6 4.6.5 4.6.4 4.6.3 4.6.2 4.6.1 4.6.0 4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.11.0 1.11.1 1.11.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.9.0 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.10.0 2.10.1 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.12.0 2.13.0 2.14.0 2.14.1 2.15.0 2.15.1 2.16.0 2.16.1 2.16.2 2.16.3 2.17.0 2.17.1 2.17.2 2.18.0 2.19.0 2.19.2 2.19.3 2.19.4 2.2.0 2.2.1 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.22.0 2.22.1 2.23.0 2.24.0 2.25.0 2.25.1 2.25.2 2.26.0 2.27.0 2.27.1 2.28.0 2.29.0 2.29.1 2.29.2 2.29.3 2.29.4 2.3.0 2.3.1 2.30.0 2.31.0 2.31.1 2.31.2 2.31.3 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.40.0 2.40.1 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.9.0 3.0.0 3.0.0-RC1 3.0.0-RC2 3.0.0-beta1 3.0.0-beta2 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.10.0 3.10.1 3.11.0 3.12.0 3.13.0 3.13.1 3.13.2 3.13.3 3.13.4 3.14.0 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.16.6 3.16.7 3.16.8 3.17.0 3.17.1 3.17.2 3.17.3 3.17.4 3.17.5 3.17.6 3.18.0 3.19.0 3.19.1 3.19.2 3.2.0 3.2.1 3.2.2 3.20.0 3.20.1 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 4.0.0 4.0.1 4.0.2 4.0.3 trunk 4.1.0 0.2.19.1 4.1.1 1.0.0 4.2.0 1.0.1 1.0.2
surecart / app / src / Database / CartLineItemDetailsMigrationService.php
surecart / app / src / Database Last commit date
Tables 1 year ago CartLineItemDetailsMigrationService.php 1 day ago GeneralMigration.php 1 year ago MigrationsServiceProvider.php 1 month ago ProductPageMigrationService.php 1 day ago RewriteRulesMigrationService.php 1 year ago Table.php 4 years ago ThemeMigrationService.php 5 months ago UpdateMigrationServiceProvider.php 2 months ago UserMetaMigrationsService.php 4 years ago VersionMigration.php 1 day ago WebhookMigrationsService.php 1 year ago
CartLineItemDetailsMigrationService.php
187 lines
1 <?php
2
3 namespace SureCart\Database;
4
5 /**
6 * Migrates saved cart templates: wraps the standalone `cart-line-item-variant`
7 * block inside the new `cart-line-item-details` container so bundle line-items
8 * get the collapsible "+N more" region on upgrade.
9 *
10 * The `cart-line-item-note` block is intentionally left standalone — in
11 * released carts it already sits right after the variant, so once the variant
12 * is wrapped the note naturally ends up after the details container.
13 */
14 class CartLineItemDetailsMigrationService extends VersionMigration {
15 /**
16 * The key for the migration.
17 *
18 * @var string
19 */
20 protected $migration_key = 'surecart_cart_line_item_details_migration_version';
21
22 /**
23 * Default variant attributes — matches the default cart template.
24 *
25 * @var string
26 */
27 const DEFAULT_CHILD_ATTRS = '{"style":{"typography":{"fontSize":"14px","lineHeight":"1.4"}}}';
28
29 /**
30 * Matches the details container form: opener (with or without attrs),
31 * inner blocks, closer. Its presence means the cart is already migrated.
32 *
33 * @var string
34 */
35 const DETAILS_WRAPPER_PATTERN = '/<!--\s*wp:surecart\/cart-line-item-details\b.*?-->.*?<!--\s*\/wp:surecart\/cart-line-item-details\s*-->/s';
36
37 /**
38 * Matches the standalone self-closing variant block the container now owns.
39 *
40 * @var string
41 */
42 const VARIANT_BLOCK_PATTERN = '/[ \t]*<!--\s*wp:surecart\/cart-line-item-variant\b.*?\/-->\n?/s';
43
44 /**
45 * Run the migration.
46 *
47 * @return void
48 */
49 public function run(): void {
50 foreach ( $this->getCartPosts() as $cart_post ) {
51 $content = $cart_post->post_content ?? '';
52 if ( empty( $content ) ) {
53 continue;
54 }
55
56 $migrated = $this->consolidateDetails( $content );
57 if ( $migrated === $content ) {
58 continue;
59 }
60
61 $result = $this->updatePostContent( $cart_post->ID, $migrated );
62
63 // a failed update defers completion so the next admin_init retries (see VersionMigration::complete()).
64 if ( is_wp_error( $result ) || empty( $result ) ) {
65 $this->failed = true;
66 }
67 }
68 }
69
70 /**
71 * Collect the cart posts to migrate.
72 *
73 * Covers both storage locations:
74 * - wp_template_part with post_name 'cart' (newer installs).
75 * - sc_cart post type (legacy installs whose cart hasn't been moved yet).
76 *
77 * @return array
78 */
79 protected function getCartPosts(): array {
80 $cart_part_query = new \WP_Query(
81 array(
82 'post_type' => 'wp_template_part',
83 'post_status' => array( 'auto-draft', 'draft', 'publish' ),
84 'posts_per_page' => -1,
85 'name' => 'cart',
86 'lazy_load_term_meta' => false,
87 )
88 );
89
90 $sc_cart_query = new \WP_Query(
91 array(
92 'post_type' => 'sc_cart',
93 'post_status' => array( 'auto-draft', 'draft', 'publish' ),
94 'posts_per_page' => -1,
95 'lazy_load_term_meta' => false,
96 )
97 );
98
99 return array_merge( $cart_part_query->posts ?? array(), $sc_cart_query->posts ?? array() );
100 }
101
102 /**
103 * Wrap the standalone variant block in a details container, preserving the
104 * merchant's variant styling. The note is left untouched.
105 *
106 * Deterministic, so re-running on migrated content is a no-op.
107 *
108 * @param string $content Post content.
109 * @return string Migrated content (unchanged if nothing matched).
110 */
111 protected function consolidateDetails( string $content ): string {
112 // Idempotent: a details container already present means the cart is in
113 // the target shape (container owns the variant; note stays standalone).
114 if ( preg_match( self::DETAILS_WRAPPER_PATTERN, $content ) ) {
115 return $content;
116 }
117
118 // Preserve the existing variant attributes (merchant styling).
119 $variant_attrs = $this->blockAttrs( $content, 'cart-line-item-variant' ) ?? self::DEFAULT_CHILD_ATTRS;
120
121 $details = "<!-- wp:surecart/cart-line-item-details -->\n"
122 . '<!-- wp:surecart/cart-line-item-variant ' . $variant_attrs . " /-->\n"
123 . '<!-- /wp:surecart/cart-line-item-details -->';
124
125 // Wrap the first standalone variant in place. The note (a separate
126 // standalone block) is deliberately not matched, so it stays exactly
127 // where it is — after the variant, hence after the new container.
128 if ( preg_match( self::VARIANT_BLOCK_PATTERN, $content ) ) {
129 $migrated = preg_replace( self::VARIANT_BLOCK_PATTERN, $details . "\n", $content, 1 );
130 return is_string( $migrated ) ? $migrated : $content;
131 }
132
133 // No variant block at all: inject the container before the first
134 // sensible anchor so bundle items still show for older carts. Prefer
135 // the note so the container lands before it (details, then note).
136 $anchor = $this->firstExistingAnchor(
137 $content,
138 array(
139 'surecart/cart-line-item-note',
140 'surecart/cart-line-item-status',
141 'surecart/cart-line-item-quantity',
142 'surecart/cart-line-item-remove',
143 )
144 );
145
146 if ( ! $anchor ) {
147 return $content;
148 }
149
150 return str_replace(
151 '<!-- wp:' . $anchor,
152 $details . "\n" . '<!-- wp:' . $anchor,
153 $content
154 );
155 }
156
157 /**
158 * Get the attribute JSON of the first occurrence of a self-closing block.
159 *
160 * @param string $content Post content to scan.
161 * @param string $block Block name without the `surecart/` prefix.
162 * @return string|null Attribute JSON, or null when absent/attribute-less.
163 */
164 protected function blockAttrs( string $content, string $block ): ?string {
165 if ( preg_match( '/<!--\s*wp:surecart\/' . preg_quote( $block, '/' ) . '\s+(\{.*?\})\s*\/-->/s', $content, $matches ) ) {
166 return $matches[1];
167 }
168 return null;
169 }
170
171 /**
172 * Pick the first anchor block from a preferred list that exists in the content.
173 *
174 * @param string $content Post content to scan.
175 * @param array $candidates Block names, in order of preference.
176 * @return string|null Block name to anchor against, or null if none match.
177 */
178 protected function firstExistingAnchor( string $content, array $candidates ): ?string {
179 foreach ( $candidates as $block_name ) {
180 if ( has_block( $block_name, $content ) ) {
181 return $block_name;
182 }
183 }
184 return null;
185 }
186 }
187