AJenbo/agcms

View on GitHub
application/inc/Models/Category.php

Summary

Maintainability
B
6 hrs
Test Coverage
B
89%
<?php

namespace App\Models;

use App\Exceptions\InvalidInput;
use App\Render;
use App\Services\DbService;
use App\Services\OrmService;

class Category extends AbstractRenderable
{
    use HasIcon;

    /** Table name in database. */
    public const TABLE_NAME = 'kat';
    /** Do not show category. */
    public const HIDDEN = 0;
    /** Gallery rendering of pages. */
    public const GALLERY = 1;
    /** List rendering of pages. */
    public const LIST = 2;

    // Backed by DB

    private ?int $parentId;

    /** @var int Render mode for page list. */
    private int $renderMode = 1;
    /** @var string Contact email. */
    private string $email = '';
    /** @var bool Are children to be fetched by weight. */
    private bool $weightedChildren = false;
    /** @var int Sorting weight. */
    private int $weight = 0;

    // Runtime

    /** @var ?bool Cache if category is visible or not. */
    private ?bool $visible = null;

    /**
     * @param array<string, mixed> $data The entity data
     *
     * @throws InvalidInput
     */
    public function __construct(array $data = [])
    {
        $this->setRenderMode(intval($data['render_mode']))
            ->setEmail(strval($data['email']))
            ->setWeightedChildren(boolval($data['weighted_children']))
            ->setWeight(intval($data['weight']))
            ->setTitle(strval($data['title']))
            ->setId(intOrNull($data['id'] ?? null));

        $this->iconId = intOrNull($data['icon_id'] ?? null);
        $this->parentId = intOrNull($data['parent_id'] ?? null);
        if (null === $this->parentId && $this->id > 0) {
            throw new InvalidInput(_('Cannot create root categories.'), 423);
        }
    }

    public static function mapFromDB(array $data): array
    {
        return [
            'id'                => $data['id'],
            'title'             => $data['navn'],
            'parent_id'         => $data['bind'],
            'icon_id'           => $data['icon_id'],
            'render_mode'       => $data['vis'],
            'email'             => $data['email'],
            'weighted_children' => $data['custom_sort_subs'],
            'weight'            => $data['order'],
        ];
    }

    /**
     * Delete category and all of it's content.
     *
     * Not that this deletes all pages, even if they aren't exclusive to this category.
     */
    public function delete(): bool
    {
        foreach ($this->getChildren() as $child) {
            $child->delete();
        }

        foreach ($this->getPages() as $page) {
            $page->delete();
        }

        return parent::delete();
    }

    /**
     * Set parent.
     *
     * @throws InvalidInput
     *
     * @return $this
     */
    public function setParent(?self $parent): self
    {
        if (null === $this->parentId && $parent) {
            throw new InvalidInput(_('You\'r not allowed to move root categories.'), 423);
        }
        if (null !== $this->parentId && !$parent) {
            throw new InvalidInput(_('You cannot create new root categories.'), 423);
        }

        $this->parentId = $parent ? $parent->getId() : null;

        return $this;
    }

    /**
     * Set render mode.
     *
     * @param int $renderMode The render mode for displaying pages
     *
     * @return $this
     */
    public function setRenderMode(int $renderMode): self
    {
        $this->renderMode = $renderMode;

        return $this;
    }

    /**
     * Get the page list rendermode for this category.
     */
    public function getRenderMode(): int
    {
        return $this->renderMode;
    }

    /**
     * Set contact email.
     *
     * @return $this
     */
    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    /**
     * Get the contact email address for pages in this category.
     */
    public function getEmail(): string
    {
        return $this->email;
    }

    /**
     * Set if children should be manually ordered.
     *
     * @param bool $weightedChildren Weather child categories should be ordered by weight
     *
     * @return $this
     */
    public function setWeightedChildren(bool $weightedChildren): self
    {
        $this->weightedChildren = $weightedChildren;

        return $this;
    }

    /**
     * Are the children of this category be manually ordered.
     */
    public function hasWeightedChildren(): bool
    {
        return $this->weightedChildren;
    }

    /**
     * Set weight.
     *
     * @param int $weight Order-by weight
     *
     * @return $this
     */
    public function setWeight(int $weight): self
    {
        $this->weight = $weight;

        return $this;
    }

    // General methods

    /**
     * Should the category be visible on the website (is it empty or hidden).
     */
    public function isVisible(): bool
    {
        if (self::HIDDEN === $this->renderMode) {
            return false;
        }

        if (null === $this->visible) {
            if (!$this->isInactive() && ($this->hasPages() || $this->hasVisibleChildren())) {
                return true;
            }

            $this->visible = false;
        }

        return $this->visible;
    }

    /**
     * Get the url slug.
     */
    public function getSlug(): string
    {
        $title = $this->getTitle();
        if (!$title && $icon = $this->getIcon()) {
            $title = $icon->getDescription();
            if (!$title) {
                $title = pathinfo($icon->getPath(), PATHINFO_FILENAME);
            }
        }

        if (!$this->getId()) {
            return '';
        }

        return 'kat' . $this->getId() . '-' . cleanFileName($title) . '/';
    }

    /**
     * Get parent category.
     *
     * @return ?static
     */
    public function getParent(): ?self
    {
        $cetegory = null;
        if (null !== $this->parentId) {
            $cetegory = app(OrmService::class)->getOne(static::class, $this->parentId);
        }

        return $cetegory;
    }

    /**
     * Get attached categories.
     *
     * @todo natsort when sorted by title
     *
     * @param bool $onlyVisible Only return visible
     *
     * @return self[]
     */
    public function getChildren(bool $onlyVisible = false): array
    {
        $orderBy = 'navn';
        if ($this->hasWeightedChildren()) {
            $orderBy = '`order`, navn';
        }

        $children = app(OrmService::class)->getByQuery(
            self::class,
            '
            SELECT * FROM kat
            WHERE bind = ' . $this->getId() . '
            ORDER BY ' . $orderBy
        );

        if (!$onlyVisible) {
            return $children;
        }

        foreach ($children as $key => $child) {
            if (!$child->isVisible()) {
                unset($children[$key]);
            }
        }

        return array_values($children);
    }

    /**
     * Get children that are suitable for displaying.
     *
     * @return self[]
     */
    public function getVisibleChildren(): array
    {
        return $this->getChildren(true);
    }

    /**
     * Check if it has attached categories.
     *
     * @param bool $onlyVisible Only check visible
     */
    public function hasChildren(bool $onlyVisible = false): bool
    {
        $children = $this->getChildren($onlyVisible);
        if ($children) {
            if ($onlyVisible) {
                $this->visible = true;
            }

            return true;
        }

        return false;
    }

    /**
     * Check if there are children that are appropriate for displaying.
     */
    public function hasVisibleChildren(): bool
    {
        return $this->hasChildren(true);
    }

    /**
     * Return attache pages.
     *
     * @param string $order What column to order by
     *
     * @return Page[]
     */
    public function getPages(string $order = 'navn', bool $reverseOrder = false): array
    {
        app(DbService::class)->addLoadedTable('bind');

        if (!in_array($order, ['navn', 'for', 'pris', 'varenr'], true)) {
            $order = 'navn';
        }

        $pages = app(OrmService::class)->getByQuery(
            Page::class,
            '
            SELECT * FROM sider
            WHERE id IN(SELECT side FROM bind WHERE kat = ' . $this->getId() . ')
            ORDER BY `' . $order . '` ' . ($reverseOrder ? 'DESC' : 'ASC')
        );

        $pageMap = [];
        $objectArray = [];
        foreach ($pages as $page) {
            $pageMap[$page->getId()] = $page;
            $objectArray[] = [
                'id'     => $page->getId(),
                'navn'   => $page->getTitle(),
                'for'    => $page->getOldPrice(),
                'pris'   => $page->getPrice(),
                'varenr' => $page->getSku(),
            ];
        }
        $objectArray = arrayNatsort($objectArray, $order, $reverseOrder ? 'desc' : '');

        $pages = [];
        foreach ($objectArray as $item) {
            $pages[] = $pageMap[$item['id']];
        }

        return $pages;
    }

    /**
     * Chekc if there are andy attached pages.
     */
    public function hasPages(): bool
    {
        $db = app(DbService::class);

        $db->addLoadedTable('bind');

        $hasPages = (bool)$db->fetchOne('SELECT kat FROM `bind` WHERE `kat` = ' . $this->getId());
        if ($hasPages) {
            $this->visible = true;
        }

        return $hasPages;
    }

    /**
     * Check if there is any content for this category.
     */
    public function hasContent(): bool
    {
        return $this->hasChildren() || $this->hasPages();
    }

    /**
     * Is page currently not placed on the website.
     */
    public function isInactive(): bool
    {
        return (bool)$this->getRoot()->getId();
    }

    /**
     * Find the root category.
     */
    public function getRoot(): self
    {
        return $this->getBranch()[0];
    }

    /**
     * Get the full list of categories leading to the root element.
     *
     * @return self[]
     */
    public function getBranch(): array
    {
        $nodes = [];
        $category = $this;
        do {
            $nodes[] = $category;
        } while ($category = $category->getParent());

        return array_values(array_reverse($nodes));
    }

    /**
     * Get display path.
     */
    public function getPath(): string
    {
        $path = '/';
        foreach ($this->getBranch() as $category) {
            $path .= $category->getTitle() . '/';
        }

        return $path;
    }

    // ORM related functions

    public function getDbArray(): array
    {
        $db = app(DbService::class);

        return [
            'navn'             => $db->quote($this->title),
            'bind'             => null !== $this->parentId ? (string)$this->parentId : 'NULL',
            'icon_id'          => null !== $this->iconId ? (string)$this->iconId : 'NULL',
            'vis'              => (string)$this->renderMode,
            'email'            => $db->quote($this->email),
            'custom_sort_subs' => (string)(int)$this->weightedChildren,
            'order'            => (string)$this->weight,
            'access'           => '""',
        ];
    }
}