michaelbutler /
phposh
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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.