imponeer/smarty-xo

View on GitHub
src/XOPageNavFunction.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php

namespace Imponeer\Smarty\Extensions\XO;

use Smarty_Internal_Template;

/**
 * Describes {xoPageNav} function
 *
 * @package Imponeer\Smarty\Extensions\XO
 */
class XOPageNavFunction implements \Imponeer\Contracts\Smarty\Extension\SmartyFunctionInterface
{
    /**
     * @var callable
     */
    private $urlGeneratorCallable;

    /**
     * @var bool
     */
    private $oldSchoolUrlMode;
    /**
     * @var string
     */
    private $strPreviousPage;
    /**
     * @var string
     */
    private $strNextPage;

    /**
     * XOPageNavFunction constructor
     * .
     * @param callable $urlGenerator Function that can be used for URL generation
     * @param string $strPreviousPage String to be displayed for previous page links
     * @param string $strNextPage String to be displayed to next page links
     * @param bool $oldSchoolUrlMode Should parameters in URL be replaced or as array given to URL generation function?
     */
    public function __construct(callable $urlGenerator, $strPreviousPage = '<', $strNextPage = '>', bool $oldSchoolUrlMode = true)
    {
        $this->urlGeneratorCallable = $urlGenerator;
        $this->oldSchoolUrlMode = $oldSchoolUrlMode;
        $this->strPreviousPage = $strPreviousPage;
        $this->strNextPage = $strNextPage;
    }

    /**
     * @inheritDoc
     */
    public function getName(): string
    {
        return 'xoPageNav';
    }

    /**
     * @inheritDoc
     */
    public function execute($params, Smarty_Internal_Template &$template)
    {
        $data = $this->calculateDataFromParams($params);

        if ($data['current'] === null) {
            return '';
        }

        return $this->buildHTMLTag('nav', ['class' => $data['class']]) .
            $this->buildHTMLTag('ul', ['class' => $data['ulClass']]) .
            $this->buildPreviousPageLink($data['current'], $data['offset'], $data['pageSize'], $data['url'], $data['liClass'], $data['linkClass']) .
            $this->buildIndividualPageLinks($data['current'], $data['linksCount'], $data['last'], $data['pageSize'], $data['url'], $data['liClass'], $data['linkClass']) .
            $this->buildNextPageLink($data['current'], $data['last'], $data['offset'], $data['pageSize'], $data['url'], $data['liClass'], $data['linkClass']) .
            '</ul></nav>';
    }

    /**
     * Calculate data from function params
     *
     * @param array $params Params used to call function
     *
     * @return array
     */
    protected function calculateDataFromParams(array $params): array
    {
        $ret = [
            'pageSize' => (int)abs($params['pageSize'] ?? 10),
            'itemsCount' => $params['itemsCount'] ?? 10,
            'offset' => $params['offset'] ?? 0,
            'linksCount' => $params['linksCount'] ?? 0,
            'url' => $params['url'] ?? '#',
            'class' => $params['class'] ?? 'pagenav',
            'ulClass' => $params['ulClass'] ?? 'pagination',
            'liClass' => $params['liClass'] ?? 'page-item',
            'linkClass' => $params['linkClass'] ?? 'page-link',
        ];

        if (
            ($ret['itemsCount'] <= $ret['pageSize']) ||
            ((int)($ret['itemsCount'] / $ret['pageSize']) < 2)
        ) {
            $ret['current'] = null;
            $ret['last'] = null;
        } else {
            $ret['current'] = (int)($ret['offset'] / $ret['pageSize']) + 1;
            $ret['last'] = (int)($ret['itemsCount'] / $ret['pageSize']) + 1;
        }

        return $ret;
    }

    /**
     * Builds HTML tag
     *
     * @param string $name Tag name
     * @param array $attributes Dictionary of tag attributes
     *
     * @return string
     */
    private function buildHTMLTag(string $name, array $attributes): string
    {
        $ret = '<' . $name;

        foreach ($attributes as $attrName => $attrValue) {
            $ret .= ' ' . $attrName . '=' . json_encode((string)$attrValue);
        }

        return $ret . '>';
    }

    /**
     * Builds Previous page link
     *
     * @param int $current Current page no
     * @param int $offset How many items from first page?
     * @param int $size How many items per page?
     * @param string $url URL
     * @param string $liClass HTML LI element class
     * @param string $aClass HTML a element class
     *
     * @return string
     */
    protected function buildPreviousPageLink($current, $offset, $size, $url, $liClass, $aClass): string
    {
        if ($current > 1) {
            return $this->buildHTMLTag('li', ['class' => $liClass]) .
                $this->buildAPageTag($offset - $size, $url, $this->strPreviousPage, [$aClass]) .
                '</li>';
        }

        return $this->buildHTMLTag('li', ['class' => $liClass]) . '<span>' . $this->strPreviousPage . '</span></li>';
    }

    /**
     * Generates A HTML tag for moving to specific page
     *
     * @param int $page Page for link
     * @param string $url Url to use for link
     * @param string $title Title for link
     * @param string[] $class Some extra classes for element
     *
     * @return string
     */
    protected function buildAPageTag($page, $url, $title, array $class = []): string
    {
        if ($this->oldSchoolUrlMode) {
            $href = call_user_func(
                $this->urlGeneratorCallable,
                str_replace('%s', $page, $url)
            );
        } else {
            $href = call_user_func_array($url, $page);
        }
        $class = implode(' ', $class);
        return $this->buildHTMLTag('a', compact('href', 'class')) . $title . '</a>';
    }

    /**
     * Builds individual page links
     *
     * @param int $current Current page no
     * @param int $linksCount Links count
     * @param int $last Last page no
     * @param int $size How many items per page?
     * @param string $url URL
     * @param string $liClass HTML LI element class
     * @param string $aClass HTML a element class
     *
     * @return string
     */
    protected function buildIndividualPageLinks($current, $linksCount, $last, $size, $url, $liClass, $aClass): string
    {
        $ret = '';

        $min = min(1, ceil($current - $linksCount / 2));
        $max = max($last, floor($current + $linksCount / 2));

        for ($i = $min; $i <= $max; $i++) {
            $ret .= $this->buildHTMLTag('li', ['class' => $liClass]) .
                $this->buildAPageTag(($i - 1) * $size, $url, $i, [$aClass]) .
                '</a>';
        }

        return $ret;
    }

    /**
     * Builds Next page link
     *
     * @param int $current Current page no
     * @param int $last Last page no
     * @param int $offset How many items from first page?
     * @param int $size How many items per page?
     * @param string $url URL
     * @param string $liClass HTML LI element class
     * @param string $aClass HTML a element class
     *
     * @return string
     */
    protected function buildNextPageLink($current, $last, $offset, $size, $url, $liClass, $aClass): string
    {
        if ($current < $last) {
            return $this->buildHTMLTag('li', ['class' => $liClass]) .
                $this->buildAPageTag($offset + $size, $url, $this->strNextPage, [$aClass]) .
                '</li>';
        }

        return $this->buildHTMLTag('li', ['class' => $liClass]) . '<span>' . $this->strNextPage . '</span></li>';
    }
}