xref: /plugin/amazonlight/syntax.php (revision 38539ccd682f85599dbcb8a235827aba0053a3a6)
11c1d842eSAndreas Gohr<?php
2*38539ccdSAndreas Gohr
3*38539ccdSAndreas Gohruse dokuwiki\HTTP\DokuHTTPClient;
4*38539ccdSAndreas Gohruse DOMWrap\Document;
5*38539ccdSAndreas Gohr
61c1d842eSAndreas Gohr/**
71c1d842eSAndreas Gohr * DokuWiki Plugin amazonlight (Syntax Component)
81c1d842eSAndreas Gohr *
91c1d842eSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
101c1d842eSAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
111c1d842eSAndreas Gohr */
121c1d842eSAndreas Gohrclass syntax_plugin_amazonlight extends DokuWiki_Syntax_Plugin
131c1d842eSAndreas Gohr{
141c1d842eSAndreas Gohr
151c1d842eSAndreas Gohr    /** @var array what regions to use for the different countries */
161c1d842eSAndreas Gohr    const REGIONS = [
17*38539ccdSAndreas Gohr        'us' => 'www.amazon.com',
18*38539ccdSAndreas Gohr        'ca' => 'www.amazon.ca',
19*38539ccdSAndreas Gohr        'de' => 'www.amazon.de',
20*38539ccdSAndreas Gohr        'gb' => 'www.amazon.co.uk',
21*38539ccdSAndreas Gohr        'fr' => 'www.amazon.fr',
22*38539ccdSAndreas Gohr        'jp' => 'www.amazon.co.jp',
231c1d842eSAndreas Gohr    ];
241c1d842eSAndreas Gohr
251c1d842eSAndreas Gohr    /** @inheritDoc */
261c1d842eSAndreas Gohr    public function getType()
271c1d842eSAndreas Gohr    {
281c1d842eSAndreas Gohr        return 'substition';
291c1d842eSAndreas Gohr    }
301c1d842eSAndreas Gohr
311c1d842eSAndreas Gohr    /** @inheritDoc */
321c1d842eSAndreas Gohr    public function getPType()
331c1d842eSAndreas Gohr    {
341c1d842eSAndreas Gohr        return 'block';
351c1d842eSAndreas Gohr    }
361c1d842eSAndreas Gohr
371c1d842eSAndreas Gohr    /** @inheritDoc */
381c1d842eSAndreas Gohr    public function getSort()
391c1d842eSAndreas Gohr    {
401c1d842eSAndreas Gohr        return 160;
411c1d842eSAndreas Gohr    }
421c1d842eSAndreas Gohr
431c1d842eSAndreas Gohr    /**
441c1d842eSAndreas Gohr     * Connect lookup pattern to lexer.
451c1d842eSAndreas Gohr     *
461c1d842eSAndreas Gohr     * @param string $mode Parser mode
471c1d842eSAndreas Gohr     */
481c1d842eSAndreas Gohr    public function connectTo($mode)
491c1d842eSAndreas Gohr    {
501c1d842eSAndreas Gohr        $this->Lexer->addSpecialPattern('\{\{amazon>[\w:\\- =]+\}\}', $mode, 'plugin_amazonlight');
511c1d842eSAndreas Gohr    }
521c1d842eSAndreas Gohr
531c1d842eSAndreas Gohr    /** @inheritDoc */
541c1d842eSAndreas Gohr    public function handle($match, $state, $pos, Doku_Handler $handler)
551c1d842eSAndreas Gohr    {
561c1d842eSAndreas Gohr        $match = substr($match, 9, -2);
571c1d842eSAndreas Gohr        list($ctry, $asin) = explode(':', $match, 2);
581c1d842eSAndreas Gohr
591c1d842eSAndreas Gohr        // no country given?
601c1d842eSAndreas Gohr        if (empty($asin)) {
611c1d842eSAndreas Gohr            $asin = $ctry;
621c1d842eSAndreas Gohr            $ctry = 'us';
631c1d842eSAndreas Gohr        }
641c1d842eSAndreas Gohr
651c1d842eSAndreas Gohr        // default parameters...
661c1d842eSAndreas Gohr        $params = array(
671c1d842eSAndreas Gohr            'imgw' => $this->getConf('imgw'),
681c1d842eSAndreas Gohr            'imgh' => $this->getConf('imgh'),
691c1d842eSAndreas Gohr            'price' => $this->getConf('showprice'),
701c1d842eSAndreas Gohr        );
711c1d842eSAndreas Gohr        // ...can be overridden
721c1d842eSAndreas Gohr        list($asin, $more) = explode(' ', $asin, 2);
731c1d842eSAndreas Gohr        $params['asin'] = $asin;
741c1d842eSAndreas Gohr
751c1d842eSAndreas Gohr        if (preg_match('/(\d+)x(\d+)/i', $more, $match)) {
761c1d842eSAndreas Gohr            $params['imgw'] = $match[1];
771c1d842eSAndreas Gohr            $params['imgh'] = $match[2];
781c1d842eSAndreas Gohr        }
791c1d842eSAndreas Gohr        if (preg_match('/noprice/i', $more, $match)) {
801c1d842eSAndreas Gohr            $params['price'] = false;
811c1d842eSAndreas Gohr        } elseif (preg_match('/(show)?price/i', $more, $match)) {
821c1d842eSAndreas Gohr            $params['price'] = true;
831c1d842eSAndreas Gohr        }
841c1d842eSAndreas Gohr
851c1d842eSAndreas Gohr        // correct country given?
861c1d842eSAndreas Gohr        if ($ctry === 'uk') $ctry = 'gb';
871c1d842eSAndreas Gohr        if (!preg_match('/^(us|gb|jp|de|fr|ca)$/', $ctry)) {
881c1d842eSAndreas Gohr            $ctry = 'us';
891c1d842eSAndreas Gohr        }
901c1d842eSAndreas Gohr        $params['country'] = $ctry;
911c1d842eSAndreas Gohr
921c1d842eSAndreas Gohr        return $params;
931c1d842eSAndreas Gohr    }
941c1d842eSAndreas Gohr
951c1d842eSAndreas Gohr    /** @inheritDoc */
961c1d842eSAndreas Gohr    public function render($mode, Doku_Renderer $renderer, $data)
971c1d842eSAndreas Gohr    {
981c1d842eSAndreas Gohr        if ($mode !== 'xhtml') {
991c1d842eSAndreas Gohr            return false;
1001c1d842eSAndreas Gohr        }
1011c1d842eSAndreas Gohr
1021c1d842eSAndreas Gohr        $html = $this->output($data);
1032681f07aSAndreas Gohr        if (!$html) {
1042681f07aSAndreas Gohr            if ($data['country'] == 'de') {
1052681f07aSAndreas Gohr                $renderer->interwikilink('Amazon', 'Amazon.de', 'amazon.de', $data['asin']);
1062681f07aSAndreas Gohr            } else {
1072681f07aSAndreas Gohr                $renderer->interwikilink('Amazon', 'Amazon', 'amazon', $data['asin']);
1082681f07aSAndreas Gohr            }
1092681f07aSAndreas Gohr        }
1102681f07aSAndreas Gohr
1111c1d842eSAndreas Gohr        $renderer->doc .= $html;
1121c1d842eSAndreas Gohr
1131c1d842eSAndreas Gohr        return true;
1141c1d842eSAndreas Gohr    }
1151c1d842eSAndreas Gohr
1161c1d842eSAndreas Gohr    /**
1171c1d842eSAndreas Gohr     * @param array $param
1181c1d842eSAndreas Gohr     * @return string
1191c1d842eSAndreas Gohr     */
1201c1d842eSAndreas Gohr    protected function output($param)
1211c1d842eSAndreas Gohr    {
1221c1d842eSAndreas Gohr        global $conf;
1231c1d842eSAndreas Gohr
1241c1d842eSAndreas Gohr        try {
1251c1d842eSAndreas Gohr            $data = $this->fetchData($param['asin'], $param['country']);
126*38539ccdSAndreas Gohr        } catch (Exception $e) {
1271c1d842eSAndreas Gohr            msg(hsc($e->getMessage()), -1);
1281c1d842eSAndreas Gohr            return false;
1291c1d842eSAndreas Gohr        }
1301c1d842eSAndreas Gohr
1311c1d842eSAndreas Gohr        $img = ml($data['img'], array('w' => $param['imgw'], 'h' => $param['imgh']));
1321c1d842eSAndreas Gohr
1331c1d842eSAndreas Gohr        ob_start();
1341c1d842eSAndreas Gohr        echo '<div class="amazon">';
1351c1d842eSAndreas Gohr        echo '<a href="' . $data['url'] . '"';
1361c1d842eSAndreas Gohr        if ($conf['target']['extern']) echo ' target="' . $conf['target']['extern'] . '"';
1371c1d842eSAndreas Gohr        echo '>';
1381c1d842eSAndreas Gohr        echo '<img src="' . $img . '" width="' . $param['imgw'] . '" height="' . $param['imgh'] . '" alt="" />';
1391c1d842eSAndreas Gohr        echo '</a>';
1401c1d842eSAndreas Gohr
1411c1d842eSAndreas Gohr        echo '<div class="amazon_title">';
1421c1d842eSAndreas Gohr        echo '<a href="' . $data['url'] . '"';
1431c1d842eSAndreas Gohr        if ($conf['target']['extern']) echo ' target="' . $conf['target']['extern'] . '"';
1441c1d842eSAndreas Gohr        echo '>';
1451c1d842eSAndreas Gohr        echo hsc($data['title']);
1461c1d842eSAndreas Gohr        echo '</a>';
1471c1d842eSAndreas Gohr        echo '</div>';
1481c1d842eSAndreas Gohr
149*38539ccdSAndreas Gohr        echo '<div class="amazon_author">';
150*38539ccdSAndreas Gohr        echo hsc($data['author']);
151*38539ccdSAndreas Gohr        echo '</div>';
152*38539ccdSAndreas Gohr
153e20d6e78SAndreas Gohr        echo '<div class="amazon_isbn">';
154e20d6e78SAndreas Gohr        echo hsc($data['isbn']);
155e20d6e78SAndreas Gohr        echo '</div>';
156e20d6e78SAndreas Gohr
1571c1d842eSAndreas Gohr        if ($param['price'] && $data['price']) {
1581c1d842eSAndreas Gohr            echo '<div class="amazon_price">' . hsc($data['price']) . '</div>';
1591c1d842eSAndreas Gohr        }
1601c1d842eSAndreas Gohr        echo '</div>';
1611c1d842eSAndreas Gohr
1621c1d842eSAndreas Gohr        return ob_get_clean();
1631c1d842eSAndreas Gohr    }
1641c1d842eSAndreas Gohr
1651c1d842eSAndreas Gohr    /**
1661c1d842eSAndreas Gohr     * Fetch the meta data
1671c1d842eSAndreas Gohr     *
1681c1d842eSAndreas Gohr     * @param string $asin
1691c1d842eSAndreas Gohr     * @param string $country
1701c1d842eSAndreas Gohr     * @return array
1711c1d842eSAndreas Gohr     * @throws Exception
1721c1d842eSAndreas Gohr     */
1731c1d842eSAndreas Gohr    protected function fetchData($asin, $country)
1741c1d842eSAndreas Gohr    {
1751c1d842eSAndreas Gohr        $partner = $this->getConf('partner_' . $country);
1761c1d842eSAndreas Gohr        if (!$partner) $partner = 'none';
1771c1d842eSAndreas Gohr        $region = self::REGIONS[$country];
1781c1d842eSAndreas Gohr
179*38539ccdSAndreas Gohr        $url = 'https://' . $region . '/dp/' . $asin;
1801c1d842eSAndreas Gohr
1811c1d842eSAndreas Gohr        $http = new DokuHTTPClient();
182*38539ccdSAndreas Gohr        $http->headers['User-Agent'] = 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36';
1831c1d842eSAndreas Gohr        $html = $http->get($url);
1841c1d842eSAndreas Gohr        if (!$html) {
185*38539ccdSAndreas Gohr            throw new Exception('Failed to fetch data. Status ' . $http->status);
1861c1d842eSAndreas Gohr        }
1871c1d842eSAndreas Gohr
1881c1d842eSAndreas Gohr
189*38539ccdSAndreas Gohr        $doc = new Document();
190*38539ccdSAndreas Gohr        $doc->html($html);
191*38539ccdSAndreas Gohr
192*38539ccdSAndreas Gohr        $result = [
193*38539ccdSAndreas Gohr            'title' => $this->extract($doc, '#productTitle'),
194*38539ccdSAndreas Gohr            'author' => $this->extract($doc, '#bylineInfo a'),
195*38539ccdSAndreas Gohr            'rating' => $this->extract($doc, '#averageCustomerReviews span.a-declarative a > span'),
196*38539ccdSAndreas Gohr            'price' => $this->extract($doc, '.priceToPay'),
197*38539ccdSAndreas Gohr            'isbn' => $this->extract($doc, '#rpi-attribute-book_details-isbn10 .rpi-attribute-value'),
198*38539ccdSAndreas Gohr            'img' => $this->extract($doc, '#imgTagWrapperId img', 'src'),
199*38539ccdSAndreas Gohr            'url' => $url . '?tag=' . $partner,
200*38539ccdSAndreas Gohr        ];
201*38539ccdSAndreas Gohr
202*38539ccdSAndreas Gohr        if (!$result['title']) {
203*38539ccdSAndreas Gohr            $result['title'] = $this->extract($doc, 'title');
2041c1d842eSAndreas Gohr        }
205*38539ccdSAndreas Gohr        if (!$result['title']) {
206*38539ccdSAndreas Gohr            throw new Exception('Could not find title in data');
2071c1d842eSAndreas Gohr        }
2081c1d842eSAndreas Gohr
2091c1d842eSAndreas Gohr        return $result;
2101c1d842eSAndreas Gohr    }
2111c1d842eSAndreas Gohr
2121c1d842eSAndreas Gohr    /**
213*38539ccdSAndreas Gohr     * Extract text or attribute from a selector
214*38539ccdSAndreas Gohr     *
215*38539ccdSAndreas Gohr     * @param Document $doc
216*38539ccdSAndreas Gohr     * @param string $selector
217*38539ccdSAndreas Gohr     * @param string|null $attr attribute to extract, omit for text
2181c1d842eSAndreas Gohr     * @return string
2191c1d842eSAndreas Gohr     */
220*38539ccdSAndreas Gohr    protected function extract(Document $doc, string $selector, $attr = null): string
2211c1d842eSAndreas Gohr    {
222*38539ccdSAndreas Gohr        $element = $doc->find($selector)->first();
223*38539ccdSAndreas Gohr        if($element === null) {
224*38539ccdSAndreas Gohr            return '';
2251c1d842eSAndreas Gohr        }
226*38539ccdSAndreas Gohr        if ($attr) {
227*38539ccdSAndreas Gohr            return $element->attr($attr);
228*38539ccdSAndreas Gohr        } else {
229*38539ccdSAndreas Gohr            return $element->text();
230*38539ccdSAndreas Gohr        }
231*38539ccdSAndreas Gohr    }
2321c1d842eSAndreas Gohr}
2331c1d842eSAndreas Gohr
234