PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
← All changes | database/Seeder/OrderOperationSeeder.php +152 -181 1.3.26 → 1.6.5 View file →
@@ -10,244 +10,214 @@
10 10 {
11 11 public static $socialMedias = [
12 12 'facebook',
13 13 'instagram',
14 - 'google',
15 14 'youtube',
16 15 'linkedin',
17 - 'twitter'
16 + 'twitter',
17 + 'pinterest',
18 + 'reddit'
18 19 ];
19 20
21 + /**
22 + * Attribution presets the Order Sources report is meant to be read through.
23 + *
24 + * The report groups by utm_campaign + utm_source + utm_medium, so every entry
25 + * below becomes one row. They are deliberately a small, plausible pool rather
26 + * than random strings: a human reading the report should recognise
27 + * "google / cpc / summer_sale" as a channel, and popular sources repeat across
28 + * entries so the spread looks like a real store's traffic mix.
29 + */
20 30 public static $utmParameters = [
21 31 [
22 - 'source' => 'social_media',
32 + 'source' => 'google',
23 33 'medium' => 'cpc',
24 - 'campaign' => 'launch2024',
25 - 'term' => 'discount_code',
26 - 'content' => 'banner1'
34 + 'campaign' => 'summer_sale',
35 + 'term' => 'buy_online',
36 + 'content' => 'search_ad_a'
27 37 ],
28 38 [
29 - 'source' => 'newsletter',
30 - 'medium' => 'email',
31 - 'campaign' => 'spring_sale',
32 - 'term' => 'special_offer',
33 - 'content' => 'top_banner'],
34 - [
35 - 'source' => 'affiliate',
36 - 'medium' => 'referral',
37 - 'campaign' => 'affiliate_promo',
38 - 'term' => 'product_launch',
39 - 'content' => 'sidebar_ad'
39 + 'source' => 'google',
40 + 'medium' => 'cpc',
41 + 'campaign' => 'brand_defense',
42 + 'term' => 'brand_name',
43 + 'content' => 'search_ad_b'
40 44 ],
41 - ['source' => 'google',
42 - 'medium' => 'organic',
43 - 'campaign' => 'summer_discounts',
44 - 'term' => 'new_product',
45 - 'content' => 'footer_link'],
46 45 [
47 - 'source' => 'facebook',
48 - 'medium' => 'social',
49 - 'campaign' => 'holiday_specials',
50 - 'term' => 'limited_time_offer',
51 - 'content' => 'video_ad'
46 + 'source' => 'google',
47 + 'medium' => 'organic',
48 + 'campaign' => 'evergreen_seo',
49 + 'term' => 'best_price',
50 + 'content' => 'landing_page'
52 51 ],
53 52 [
54 - 'source' => 'youtube',
55 - 'medium' => 'video',
56 - 'campaign' => '2024_trends',
57 - 'term' => 'feature_highlight',
58 - 'content' => 'watch_demo'
59 - ],
60 - [
61 - 'source' => 'instagram',
62 - 'medium' => 'paid',
63 - 'campaign' => 'exclusive_deal',
64 - 'term' => 'top_sellers',
65 - 'content' => 'image_post'
66 - ],
67 - [
68 - 'source' => 'linkedin',
53 + 'source' => 'google',
69 54 'medium' => 'display',
70 - 'campaign' => 'professional_gear',
71 - 'term' => 'upgrade',
72 - 'content' => 'infeed_ad'
55 + 'campaign' => 'retargeting',
56 + 'term' => 'cart_abandoners',
57 + 'content' => 'display_banner'
73 58 ],
74 59 [
75 - 'source' => 'twitter',
76 - 'medium' => 'social',
77 - 'campaign' => 'winter_sale',
78 - 'term' => 'must_have',
79 - 'content' => 'tweet_link'
60 + 'source' => 'facebook',
61 + 'medium' => 'cpc',
62 + 'campaign' => 'black_friday',
63 + 'term' => 'doorbuster',
64 + 'content' => 'carousel_ad'
80 65 ],
81 66 [
82 - 'source' => 'blog',
83 - 'medium' => 'banner',
84 - 'campaign' => 'new_arrivals',
85 - 'term' => 'just_arrived',
86 - 'content' => 'read_more_button'
87 - ],
88 - [
89 - 'source' => 'email_campaign',
90 - 'medium' => 'newsletter',
91 - 'campaign' => 'mid_year_sale',
92 - 'term' => 'promo_july',
93 - 'content' => 'email_link1',
94 - ],
95 - [
96 - 'source' => 'search_engine',
97 - 'medium' => 'seo',
98 - 'campaign' => 'organic_growth',
99 - 'term' => 'keyword_optimization',
100 - 'content' => 'organic_link',
101 - ],
102 - [
103 - 'source' => 'partner_site',
104 - 'medium' => 'affiliate',
105 - 'campaign' => 'partner_discount',
106 - 'term' => 'affiliate2024',
107 - 'content' => 'partner_banner',
108 - ],
109 - [
110 - 'source' => 'linkedin',
111 - 'medium' => 'ad',
112 - 'campaign' => 'b2b_leads',
113 - 'term' => 'lead_gen',
114 - 'content' => 'profile_ad',
115 - ],
116 - [
117 - 'source' => 'twitter',
118 - 'medium' => 'tweet',
119 - 'campaign' => 'brand_awareness',
120 - 'term' => 'spread_the_word',
121 - 'content' => 'tweet1',
122 - ],
123 - [
124 67 'source' => 'facebook',
125 - 'medium' => 'post',
68 + 'medium' => 'social',
126 69 'campaign' => 'product_launch',
127 70 'term' => 'new_release',
128 - 'content' => 'post_link',
71 + 'content' => 'page_post'
129 72 ],
130 73 [
131 - 'source' => 'google_ads',
132 - 'medium' => 'ppc',
133 - 'campaign' => 'adwords_boost',
134 - 'term' => 'click_through',
135 - 'content' => 'ad_variation1',
74 + 'source' => 'instagram',
75 + 'medium' => 'social',
76 + 'campaign' => 'creator_collab',
77 + 'term' => 'influencer',
78 + 'content' => 'story_ad'
136 79 ],
137 80 [
138 - 'source' => 'youtube',
139 - 'medium' => 'channel',
140 - 'campaign' => 'video_campaign',
141 - 'term' => 'tutorial_series',
142 - 'content' => 'video1',
143 - ],
144 - [
145 81 'source' => 'instagram',
146 - 'medium' => 'story',
82 + 'medium' => 'cpc',
147 83 'campaign' => 'flash_sale',
148 84 'term' => '24hr_special',
149 - 'content' => 'story_ad',
85 + 'content' => 'reel_ad'
150 86 ],
151 87 [
152 - 'source' => 'pinterest',
153 - 'medium' => 'pin',
154 - 'campaign' => 'home_decor',
155 - 'term' => 'interior_tips',
156 - 'content' => 'pin_post',
88 + 'source' => 'newsletter',
89 + 'medium' => 'email',
90 + 'campaign' => 'weekly_digest',
91 + 'term' => 'latest_news',
92 + 'content' => 'header_link'
157 93 ],
158 94 [
159 - 'source' => 'email_blast',
160 - 'medium' => 'direct',
161 - 'campaign' => 'exclusive_offer',
162 - 'term' => 'vip_list',
163 - 'content' => 'email1',
95 + 'source' => 'newsletter',
96 + 'medium' => 'email',
97 + 'campaign' => 'abandoned_cart',
98 + 'term' => 'come_back',
99 + 'content' => 'recovery_link'
164 100 ],
165 101 [
166 - 'source' => 'webinar',
167 - 'medium' => 'live_event',
168 - 'campaign' => 'educational_series',
169 - 'term' => 'session1',
170 - 'content' => 'signup_page',
171 - ],
172 - [
173 - 'source' => 'google',
174 - 'medium' => 'display',
175 - 'campaign' => 'retargeting',
176 - 'term' => 'visitor_retarget',
177 - 'content' => 'display_ad1',
178 - ],
179 - [
180 102 'source' => 'twitter',
181 - 'medium' => 'promotion',
182 - 'campaign' => 'tweet_special',
183 - 'term' => 'limited_offer',
184 - 'content' => 'promo_tweet',
103 + 'medium' => 'social',
104 + 'campaign' => 'brand_awareness',
105 + 'term' => 'spread_the_word',
106 + 'content' => 'tweet_link'
185 107 ],
186 108 [
187 - 'source' => 'facebook',
188 - 'medium' => 'video_ad',
189 - 'campaign' => 'video_promotion',
190 - 'term' => 'watch_now',
191 - 'content' => 'video_banner',
109 + 'source' => 'youtube',
110 + 'medium' => 'video',
111 + 'campaign' => 'tutorial_series',
112 + 'term' => 'how_to',
113 + 'content' => 'description_link'
192 114 ],
193 115 [
194 - 'source' => 'instagram',
195 - 'medium' => 'photo_ad',
196 - 'campaign' => 'image_campaign',
197 - 'term' => 'product_photo',
198 - 'content' => 'product_image',
116 + 'source' => 'bing',
117 + 'medium' => 'cpc',
118 + 'campaign' => 'summer_sale',
119 + 'term' => 'buy_online',
120 + 'content' => 'search_ad_a'
199 121 ],
200 122 [
201 - 'source' => 'pinterest',
202 - 'medium' => 'image',
203 - 'campaign' => 'diy_projects',
204 - 'term' => 'do_it_yourself',
205 - 'content' => 'diy_pin',
123 + 'source' => 'affiliate',
124 + 'medium' => 'referral',
125 + 'campaign' => 'partner_program',
126 + 'term' => 'review_post',
127 + 'content' => 'inline_link'
206 128 ],
207 129 [
208 - 'source' => 'newsletter',
209 - 'medium' => 'email',
210 - 'campaign' => 'weekly_update',
211 - 'term' => 'latest_news',
212 - 'content' => 'newsletter_link',
130 + 'source' => 'linkedin',
131 + 'medium' => 'social',
132 + 'campaign' => 'b2b_outreach',
133 + 'term' => 'team_plan',
134 + 'content' => 'infeed_ad'
213 135 ],
214 136 [
215 - 'source' => 'google',
216 - 'medium' => 'organic',
217 - 'campaign' => 'seo_boost',
218 - 'term' => 'search_term',
219 - 'content' => 'landing_page',
137 + 'source' => 'reddit',
138 + 'medium' => 'social',
139 + 'campaign' => 'community_awareness',
140 + 'term' => 'ama_thread',
141 + 'content' => 'comment_link'
220 142 ],
221 143 [
222 - 'source' => 'email',
223 - 'medium' => 'retargeting_mail',
224 - 'campaign' => 'customer_followup',
225 - 'term' => 'follow_up',
226 - 'content' => 'second_email',
144 + 'source' => 'direct',
145 + 'medium' => 'none',
146 + 'campaign' => 'direct_visit',
147 + 'term' => '',
148 + 'content' => ''
227 149 ]
228 -
229 150 ];
230 151
152 + /**
153 + * Share of seeded orders that carry no attribution at all.
154 + *
155 + * Real stores always have unattributed orders, and the Sources report guards
156 + * against them with whereNotNull('oo.utm_source'), so the seed data has to
157 + * contain some or that guard is never exercised.
158 + */
159 + const NO_UTM_RATIO = 20;
231 160
232 161 public static function seed($count, $assoc_args = [])
233 162 {
234 - $orders = Order::query()->limit($count)->get();
163 + // Newest first: DBSeeder runs this straight after OrderSeeder, so the
164 + // orders that still need an operation row are the ones just inserted.
165 + // (The old query took the OLDEST $count orders, which duplicated rows on
166 + // any store that had been seeded before and left new orders with none.)
167 + $orders = Order::query()
168 + ->select(['id', 'created_at'])
169 + ->orderBy('id', 'desc')
170 + ->limit($count)
171 + ->get();
172 +
173 + if ($orders->isEmpty()) {
174 + return;
175 + }
176 +
177 + $orderIds = $orders->pluck('id')->toArray();
178 +
179 + // Bounded by $count on both sides — never insert a second operation row
180 + // for an order that already has one.
181 + $alreadySeeded = OrderOperation::query()
182 + ->whereIn('order_id', $orderIds)
183 + ->pluck('order_id')
184 + ->toArray();
185 +
186 + $alreadySeeded = array_flip($alreadySeeded);
187 +
235 188 $operationData = [];
236 189 $faker = Factory::create();
237 190
238 191 if (defined('WP_CLI') && WP_CLI) {
239 - $progress = \WP_CLI\Utils\make_progress_bar('%CSeeding Order Operations', $count);
192 + $progress = \WP_CLI\Utils\make_progress_bar('%CSeeding Order Operations', count($orders));
240 193 }
241 194
242 195 foreach ($orders as $order) {
243 - $utmParameter = $faker->randomElement(self::$utmParameters);
196 + if (isset($alreadySeeded[$order->id])) {
197 + if (defined('WP_CLI') && WP_CLI) {
198 + $progress->tick();
199 + }
200 + continue;
201 + }
202 +
203 + $hasUtm = wp_rand(1, 100) > self::NO_UTM_RATIO;
204 +
205 + $utmParameter = $hasUtm
206 + ? $faker->randomElement(self::$utmParameters)
207 + : ['source' => '', 'medium' => '', 'campaign' => '', 'term' => '', 'content' => ''];
208 +
209 + // The report filters orders by o.created_at, so the operation row is
210 + // dated with its own order rather than a random date since 1970.
211 + $createdAt = (string) $order->created_at;
212 +
213 + // has_tax / has_discount / coupons_counted are still listed in the
214 + // OrderOperation model's $fillable, but OrderOperationsMigrator never
215 + // creates those columns — writing them killed every seed run with
216 + // "Unknown column 'coupons_counted' in 'field list'".
244 217 $operationData[] = [
245 218 'order_id' => $order->id,
246 219 'created_via' => 'web',
247 - 'has_tax' => wp_rand(0, 1),
248 - 'has_discount' => wp_rand(0, 1),
249 - 'coupons_counted' => wp_rand(1, 3),
250 220 'emails_sent' => wp_rand(0, 1),
251 221 'sales_recorded' => wp_rand(0, 1),
252 222 'utm_campaign' => $utmParameter['campaign'],
253 223 'utm_term' => $utmParameter['term'],
@@ -253,14 +223,15 @@
253 223 'utm_term' => $utmParameter['term'],
254 224 'utm_source' => $utmParameter['source'],
255 225 'utm_content' => $utmParameter['content'],
256 226 'utm_medium' => $utmParameter['medium'],
257 - 'utm_id' => wp_rand(1, 7),
227 + 'utm_id' => $hasUtm ? (string) wp_rand(1, 7) : '',
258 228 'cart_hash' => wp_rand(100, 2345),
259 - 'refer_url' => in_array($utmParameter['source'], self::$socialMedias, '') ?
260 - 'https://www.' . $utmParameter['source'] . '.com' : '',
261 - 'created_at' => $faker->date('Y-m-d H:i:s'),
262 - 'updated_at' => $faker->date('Y-m-d H:i:s'),
229 + 'refer_url' => in_array($utmParameter['source'], self::$socialMedias, true)
230 + ? 'https://www.' . $utmParameter['source'] . '.com'
231 + : '',
232 + 'created_at' => $createdAt,
233 + 'updated_at' => $createdAt,
263 234 ];
264 235
265 236 if (defined('WP_CLI') && WP_CLI) {
266 237 $progress->tick();
@@ -266,13 +237,13 @@
266 237 $progress->tick();
267 238 }
268 239 }
269 240
241 + if ($operationData) {
242 + OrderOperation::query()->insert($operationData);
243 + }
270 244
271 - OrderOperation::query()->insert($operationData);
272 -
273 245 if (defined('WP_CLI') && WP_CLI) {
274 - $progress->tick();
275 246 $progress->finish();
276 247 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
277 248 echo \WP_CLI::colorize('%n');
278 249 }