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) { |
||||
0 ignored issues
–
show
|
|||||
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')) |
|||
0 ignored issues
–
show
It seems like
$node->filter('img.item-...)->first()->attr('src') can also be of type null ; however, parameter $imageUrl of PHPosh\Provider\Poshmark\Order::setImageUrl() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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) |
|||
0 ignored issues
–
show
It seems like
$count can also be of type string ; however, parameter $itemCount of PHPosh\Provider\Poshmark\Order::setItemCount() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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.