Issues (16)

src/Provider/Poshmark/DataParser.php (3 issues)

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
The parameter $i is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

116
        $retItems = $items->each(static function (Crawler $node, /** @scrutinizer ignore-unused */ $i) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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 ignore-type  annotation

132
                ->setImageUrl(/** @scrutinizer ignore-type */ $node->filter('img.item-pic')->first()->attr('src'))
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 ignore-type  annotation

137
                ->setItemCount(/** @scrutinizer ignore-type */ $count)
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