PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 1.2.0 All 47 releases
fluent-cart / database / Seeder / OrderOperationSeeder.php

OrderOperationSeeder.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at database/Seeder/OrderOperationSeeder.php

252 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\Database\Seeder;
4
5 use FluentCart\Faker\Factory;
6 use FluentCart\App\Models\Order;
7 use FluentCart\App\Models\OrderOperation;
8
9 class OrderOperationSeeder
10 {
11 public static $socialMedias = [
12 'facebook',
13 'instagram',
14 'youtube',
15 'linkedin',
16 'twitter',
17 'pinterest',
18 'reddit'
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 */
30 public static $utmParameters = [
31 [
32 'source' => 'google',
33 'medium' => 'cpc',
34 'campaign' => 'summer_sale',
35 'term' => 'buy_online',
36 'content' => 'search_ad_a'
37 ],
38 [
39 'source' => 'google',
40 'medium' => 'cpc',
41 'campaign' => 'brand_defense',
42 'term' => 'brand_name',
43 'content' => 'search_ad_b'
44 ],
45 [
46 'source' => 'google',
47 'medium' => 'organic',
48 'campaign' => 'evergreen_seo',
49 'term' => 'best_price',
50 'content' => 'landing_page'
51 ],
52 [
53 'source' => 'google',
54 'medium' => 'display',
55 'campaign' => 'retargeting',
56 'term' => 'cart_abandoners',
57 'content' => 'display_banner'
58 ],
59 [
60 'source' => 'facebook',
61 'medium' => 'cpc',
62 'campaign' => 'black_friday',
63 'term' => 'doorbuster',
64 'content' => 'carousel_ad'
65 ],
66 [
67 'source' => 'facebook',
68 'medium' => 'social',
69 'campaign' => 'product_launch',
70 'term' => 'new_release',
71 'content' => 'page_post'
72 ],
73 [
74 'source' => 'instagram',
75 'medium' => 'social',
76 'campaign' => 'creator_collab',
77 'term' => 'influencer',
78 'content' => 'story_ad'
79 ],
80 [
81 'source' => 'instagram',
82 'medium' => 'cpc',
83 'campaign' => 'flash_sale',
84 'term' => '24hr_special',
85 'content' => 'reel_ad'
86 ],
87 [
88 'source' => 'newsletter',
89 'medium' => 'email',
90 'campaign' => 'weekly_digest',
91 'term' => 'latest_news',
92 'content' => 'header_link'
93 ],
94 [
95 'source' => 'newsletter',
96 'medium' => 'email',
97 'campaign' => 'abandoned_cart',
98 'term' => 'come_back',
99 'content' => 'recovery_link'
100 ],
101 [
102 'source' => 'twitter',
103 'medium' => 'social',
104 'campaign' => 'brand_awareness',
105 'term' => 'spread_the_word',
106 'content' => 'tweet_link'
107 ],
108 [
109 'source' => 'youtube',
110 'medium' => 'video',
111 'campaign' => 'tutorial_series',
112 'term' => 'how_to',
113 'content' => 'description_link'
114 ],
115 [
116 'source' => 'bing',
117 'medium' => 'cpc',
118 'campaign' => 'summer_sale',
119 'term' => 'buy_online',
120 'content' => 'search_ad_a'
121 ],
122 [
123 'source' => 'affiliate',
124 'medium' => 'referral',
125 'campaign' => 'partner_program',
126 'term' => 'review_post',
127 'content' => 'inline_link'
128 ],
129 [
130 'source' => 'linkedin',
131 'medium' => 'social',
132 'campaign' => 'b2b_outreach',
133 'term' => 'team_plan',
134 'content' => 'infeed_ad'
135 ],
136 [
137 'source' => 'reddit',
138 'medium' => 'social',
139 'campaign' => 'community_awareness',
140 'term' => 'ama_thread',
141 'content' => 'comment_link'
142 ],
143 [
144 'source' => 'direct',
145 'medium' => 'none',
146 'campaign' => 'direct_visit',
147 'term' => '',
148 'content' => ''
149 ]
150 ];
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;
160
161 public static function seed($count, $assoc_args = [])
162 {
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
188 $operationData = [];
189 $faker = Factory::create();
190
191 if (defined('WP_CLI') && WP_CLI) {
192 $progress = \WP_CLI\Utils\make_progress_bar('%CSeeding Order Operations', count($orders));
193 }
194
195 foreach ($orders as $order) {
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'".
217 $operationData[] = [
218 'order_id' => $order->id,
219 'created_via' => 'web',
220 'emails_sent' => wp_rand(0, 1),
221 'sales_recorded' => wp_rand(0, 1),
222 'utm_campaign' => $utmParameter['campaign'],
223 'utm_term' => $utmParameter['term'],
224 'utm_source' => $utmParameter['source'],
225 'utm_content' => $utmParameter['content'],
226 'utm_medium' => $utmParameter['medium'],
227 'utm_id' => $hasUtm ? (string) wp_rand(1, 7) : '',
228 'cart_hash' => wp_rand(100, 2345),
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,
234 ];
235
236 if (defined('WP_CLI') && WP_CLI) {
237 $progress->tick();
238 }
239 }
240
241 if ($operationData) {
242 OrderOperation::query()->insert($operationData);
243 }
244
245 if (defined('WP_CLI') && WP_CLI) {
246 $progress->finish();
247 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
248 echo \WP_CLI::colorize('%n');
249 }
250 }
251 }
252