1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of michaelbutler/phposh. |
5
|
|
|
* Source: https://github.com/michaelbutler/phposh |
6
|
|
|
* |
7
|
|
|
* (c) Michael Butler <[email protected]> |
8
|
|
|
* |
9
|
|
|
* This source file is subject to the MIT license that is bundled |
10
|
|
|
* with this source code in the file named LICENSE. |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
namespace PHPosh\Provider\Poshmark; |
14
|
|
|
|
15
|
|
|
use Symfony\Component\DomCrawler\Crawler; |
16
|
|
|
|
17
|
|
|
class DataParser |
18
|
|
|
{ |
19
|
|
|
/** |
20
|
|
|
* Auto-detect the item id given a listing URL. |
21
|
|
|
* |
22
|
|
|
* @param string $listingUrl Listing URL such as /listing/Red-Pants-Gap-Jeans-5eaa834be23448c3438d... |
23
|
|
|
* |
24
|
|
|
* @return string Just the item id, such as 5eaa834be23448c3438d |
25
|
|
|
*/ |
26
|
3 |
|
public static function parseItemIdFromUrl(string $listingUrl): string |
27
|
|
|
{ |
28
|
3 |
|
$parts = explode('-', $listingUrl); |
29
|
|
|
|
30
|
3 |
|
return array_pop($parts) ?: ''; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @return array [Price, Price, Price, Price] (orderTotal, poshmarkFee, earnings, tax) |
35
|
|
|
*/ |
36
|
2 |
|
public static function parseOrderPrices(Crawler $contentNode): array |
37
|
|
|
{ |
38
|
|
|
// Parse out price information using a regex |
39
|
2 |
|
$pricesInfo = trim($contentNode->filter('.price-details')->text()); |
40
|
2 |
|
$matches = []; |
41
|
2 |
|
preg_match( |
42
|
2 |
|
'/[\D.]+([\d.]+)[\D.]+([\d.]+)[\D.]+([\d.]+)[\D]+([\d]+\.[\d]+)/i', |
43
|
|
|
$pricesInfo, |
44
|
|
|
$matches |
45
|
|
|
); |
46
|
2 |
|
$orderTotal = $matches[1] ?? '0.00'; |
47
|
2 |
|
$position = mb_strpos($pricesInfo, $orderTotal); |
48
|
2 |
|
$symbol = mb_substr($pricesInfo, $position - 1, 1); |
49
|
2 |
|
$orderTotal = $symbol . $orderTotal; |
50
|
2 |
|
$poshmarkFee = $symbol . $matches[2] ?? '0.00'; |
51
|
2 |
|
$earnings = $symbol . $matches[3] ?? '0.00'; |
52
|
2 |
|
$tax = $symbol . $matches[4] ?? '0.00'; |
53
|
|
|
|
54
|
2 |
|
$orderTotal = Price::fromString($orderTotal); |
55
|
2 |
|
$poshmarkFee = Price::fromString($poshmarkFee); |
56
|
2 |
|
$earnings = Price::fromString($earnings); |
57
|
2 |
|
$tax = Price::fromString($tax); |
58
|
|
|
|
59
|
2 |
|
return [$orderTotal, $poshmarkFee, $earnings, $tax]; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Convert the JSON item data into an Item object. |
64
|
|
|
* |
65
|
|
|
* @param array $data Full JSON web response as a data array |
66
|
|
|
*/ |
67
|
3 |
|
public function parseOneItemResponseJson(array $data): Item |
68
|
|
|
{ |
69
|
3 |
|
if (!isset($data['title']) && isset($data['data'])) { |
70
|
2 |
|
$itemData = $data['data']; |
71
|
|
|
} else { |
72
|
1 |
|
$itemData = $data; |
73
|
|
|
} |
74
|
3 |
|
$base_url = PoshmarkService::BASE_URL; |
75
|
3 |
|
$newItem = new Item(); |
76
|
|
|
|
77
|
|
|
try { |
78
|
3 |
|
$dt = new \DateTime($itemData['created_at']); |
79
|
1 |
|
} catch (\Exception $e) { |
80
|
1 |
|
$dt = new \DateTime(); |
81
|
|
|
} |
82
|
|
|
|
83
|
3 |
|
$currentPrice = new Price(); |
84
|
3 |
|
$currentPrice->setCurrencyCode($itemData['price_amount']['currency_code'] ?? 'USD') |
85
|
3 |
|
->setAmount($itemData['price_amount']['val'] ?? '0.00') |
86
|
|
|
; |
87
|
|
|
|
88
|
3 |
|
$origPrice = new Price(); |
89
|
3 |
|
$origPrice->setCurrencyCode($itemData['original_price_amount']['currency_code'] ?? 'USD') |
90
|
3 |
|
->setAmount($itemData['original_price_amount']['val'] ?? '0.00') |
91
|
|
|
; |
92
|
|
|
|
93
|
3 |
|
$newItem->setBrand($itemData['brand'] ?? '') |
94
|
3 |
|
->setCreatedAt($dt) |
95
|
3 |
|
->setPrice($currentPrice) |
96
|
3 |
|
->setOrigPrice($origPrice) |
97
|
3 |
|
->setSize($itemData['size'] ?: '') |
98
|
3 |
|
->setId($itemData['id'] ?: '') |
99
|
3 |
|
->setTitle($itemData['title'] ?: 'Unknown') |
100
|
3 |
|
->setDescription($itemData['description']) |
101
|
3 |
|
->setExternalUrl($base_url . '/listing/item-' . $itemData['id']) |
102
|
3 |
|
->setImageUrl($itemData['picture_url'] ?: '') |
103
|
3 |
|
->setRawData($itemData) |
104
|
|
|
; |
105
|
|
|
|
106
|
3 |
|
return $newItem; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* @return Order[] |
111
|
|
|
*/ |
112
|
1 |
|
public function parseOrdersPagePartialResponse(string $html): array |
113
|
|
|
{ |
114
|
1 |
|
$crawler = new Crawler($html); |
115
|
1 |
|
$items = $crawler->filter('a.item'); |
116
|
|
|
$retItems = $items->each(static function (Crawler $node, $i) { |
|
|
|
|
117
|
1 |
|
$order = new Order(); |
118
|
1 |
|
$price = Price::fromString($node->filter('.price .value')->first()->text()); |
119
|
1 |
|
$path = $node->attr('href'); |
120
|
1 |
|
$parts = explode('/', $path); |
121
|
1 |
|
$id = array_pop($parts); |
122
|
|
|
// Multi-item orders will not have a size here |
123
|
1 |
|
$sizeNode = $node->filter('.size .value'); |
124
|
1 |
|
$count = 1; |
125
|
1 |
|
$badge = $node->filter('.badge-con .badge'); |
126
|
1 |
|
if ($badge->count() > 0) { |
127
|
1 |
|
$count = $badge->first()->text(); |
128
|
|
|
} |
129
|
1 |
|
$order->setTitle($node->filter('.title')->eq(0)->text()) |
130
|
1 |
|
->setId($id) |
131
|
1 |
|
->setUrl(PoshmarkService::BASE_URL . $path) |
132
|
1 |
|
->setImageUrl($node->filter('img.item-pic')->first()->attr('src')) |
|
|
|
|
133
|
1 |
|
->setSize($sizeNode->count() > 0 ? $sizeNode->first()->text() : '') |
134
|
1 |
|
->setBuyerUsername($node->filter('.seller .value')->first()->text()) |
135
|
1 |
|
->setOrderTotal($price) |
136
|
1 |
|
->setOrderStatus($node->filter('.status .value')->first()->text()) |
137
|
1 |
|
->setItemCount($count) |
|
|
|
|
138
|
|
|
; |
139
|
|
|
|
140
|
1 |
|
return $order; |
141
|
1 |
|
}); |
142
|
|
|
|
143
|
1 |
|
return $retItems; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* This parses the full order details and also makes HTTP requests for the full individual item details. |
148
|
|
|
* |
149
|
|
|
* @param string $html HTML content of the order details page, or at least the main content block of it |
150
|
|
|
* @param Item[] $items Items to assign to the order |
151
|
|
|
*/ |
152
|
1 |
|
public function parseFullOrderResponseHtml(string $orderId, string $html, array $items): Order |
153
|
|
|
{ |
154
|
1 |
|
$crawler = new Crawler($html); |
155
|
1 |
|
$contentNode = $crawler->filter('.order-main-con'); |
156
|
|
|
|
157
|
1 |
|
$order = new Order(); |
158
|
1 |
|
$order->setItems($items); |
159
|
|
|
|
160
|
1 |
|
[$orderTotal, $poshmarkFee, $earnings, $tax] = self::parseOrderPrices($contentNode); |
161
|
|
|
|
162
|
1 |
|
$count = count($items); |
163
|
1 |
|
$multiItemOrder = $count > 1; |
164
|
|
|
|
165
|
1 |
|
$title = $multiItemOrder ? |
166
|
1 |
|
sprintf('Order %s (%d items)', $orderId, count($items)) : |
167
|
1 |
|
$items[0]->getTitle(); |
168
|
|
|
|
169
|
1 |
|
$dateAndUser = $contentNode->filter('.order-details')->html(''); |
170
|
1 |
|
$dateAndUser = str_replace("\n", ' ', $this->dumbHtmlTagReplacement($dateAndUser)); |
171
|
1 |
|
$matches = []; |
172
|
1 |
|
preg_match( |
173
|
1 |
|
'/Date: *([\S]+) *Order #: *([\S]+) *Buyer: *([\S]+)\b/i', |
174
|
|
|
$dateAndUser, |
175
|
|
|
$matches |
176
|
|
|
); |
177
|
1 |
|
$orderDate = trim($matches[1] ?? null); |
178
|
1 |
|
$buyerName = trim($matches[3] ?? 'Unknown'); |
179
|
|
|
|
180
|
1 |
|
$orderDate = new \DateTime($orderDate); |
181
|
|
|
|
182
|
1 |
|
$orderStatus = $contentNode->filter('.status-desc')->text(); |
183
|
|
|
|
184
|
1 |
|
$matches = []; |
185
|
1 |
|
preg_match('/Status:([A-Z ]+)/i', $orderStatus, $matches); |
186
|
1 |
|
$orderStatus = trim($matches[1] ?? 'Unknown'); |
187
|
|
|
|
188
|
1 |
|
$order->setTitle($title) |
189
|
1 |
|
->setId($orderId) |
190
|
1 |
|
->setUrl(PoshmarkService::BASE_URL . '/order/sales/' . $orderId) |
191
|
1 |
|
->setImageUrl($items[0]->getImageUrl()) |
192
|
1 |
|
->setSize('') |
193
|
1 |
|
->setBuyerUsername($buyerName) |
194
|
1 |
|
->setOrderTotal($orderTotal) |
195
|
1 |
|
->setEarnings($earnings) |
196
|
1 |
|
->setPoshmarkFee($poshmarkFee) |
197
|
1 |
|
->setTaxes($tax) |
198
|
1 |
|
->setOrderStatus($orderStatus) |
199
|
1 |
|
->setItemCount($count) |
200
|
1 |
|
->setOrderDate($orderDate) |
201
|
|
|
; |
202
|
|
|
|
203
|
1 |
|
$order->setShippingLabelPdf( |
204
|
1 |
|
sprintf('%s/order/sales/%s/download_shipping_label_link', PoshmarkService::BASE_URL, $orderId) |
205
|
|
|
); |
206
|
|
|
|
207
|
1 |
|
return $order; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Like the built-in strip_tags(), except it replaces each tag with a whitespace char " ". |
212
|
|
|
* This makes it easier to find individual words. |
213
|
|
|
* Example: |
214
|
|
|
* Input: <p>Hello</p><div>World!</div> |
215
|
|
|
* This method output: Hello World! |
216
|
|
|
* strip_tags output: HelloWorld! |
217
|
|
|
*/ |
218
|
1 |
|
private function dumbHtmlTagReplacement(string $string): string |
219
|
|
|
{ |
220
|
1 |
|
$string = str_replace('>', '> ', $string); |
221
|
|
|
|
222
|
1 |
|
return strip_tags($string); |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.