Skip to content
Code-Schnipsel Gruppen Projekte
Handler.php 14,8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Patrick Müller's avatar
    Patrick Müller committed
    <?php
    
    namespace QUI\ERP\Coupons;
    
    
    use DateInterval;
    use DateTime;
    use Exception;
    use PDO;
    
    Patrick Müller's avatar
    Patrick Müller committed
    use QUI;
    
    use QUI\ERP\Discount\Handler as DiscountHandler;
    
    Patrick Müller's avatar
    Patrick Müller committed
    use QUI\Utils\Grid;
    use QUI\Utils\Security\Orthos;
    
    
    use function current;
    use function explode;
    use function implode;
    use function is_null;
    use function json_encode;
    use function preg_replace;
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    /**
     * Class Handler
     *
     * Main CouponCode Code handler
     */
    class Handler
    {
        /**
         * Permissions
         */
    
        const PERMISSION_VIEW = 'quiqqer.couponcode.view';
    
    Patrick Müller's avatar
    Patrick Müller committed
        const PERMISSION_CREATE = 'quiqqer.couponcode.create';
    
        const PERMISSION_EDIT = 'quiqqer.couponcode.edit';
    
    Patrick Müller's avatar
    Patrick Müller committed
        const PERMISSION_DELETE = 'quiqqer.couponcode.delete';
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
        /**
         * Valur for `maxUsage`
         */
        const MAX_USAGE_ONCE_PER_USER = 'oncePerUser';
    
        const MAX_USAGE_ONCE = 'once';
        const MAX_USAGE_UNLIMITED = 'unlimited';
    
    Patrick Müller's avatar
    Patrick Müller committed
        /**
    
    Patrick Müller's avatar
    Patrick Müller committed
         * CouponCode runtime cache
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
    
    Patrick Müller's avatar
    Patrick Müller committed
         * @var CouponCode[]
    
    Patrick Müller's avatar
    Patrick Müller committed
         */
    
        protected static array $couponCodes = [];
    
    Patrick Müller's avatar
    Patrick Müller committed
    
        /**
    
    Patrick Müller's avatar
    Patrick Müller committed
         * Get CouponCode
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
         * @param int $id
    
    Patrick Müller's avatar
    Patrick Müller committed
         * @return CouponCode
         * @throws CouponCodeException
    
    Patrick Müller's avatar
    Patrick Müller committed
         */
    
        public static function getCouponCode(int $id): CouponCode
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
    Patrick Müller's avatar
    Patrick Müller committed
            if (isset(self::$couponCodes[$id])) {
                return self::$couponCodes[$id];
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
    
    Patrick Müller's avatar
    Patrick Müller committed
            self::$couponCodes[$id] = new CouponCode($id);
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
    Patrick Müller's avatar
    Patrick Müller committed
            return self::$couponCodes[$id];
    
    Patrick Müller's avatar
    Patrick Müller committed
        }
    
        /**
    
    Patrick Müller's avatar
    Patrick Müller committed
         * Get CouponCode by its actual code
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
         * @param string $code
    
    Patrick Müller's avatar
    Patrick Müller committed
         * @return CouponCode
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
    
    Patrick Müller's avatar
    Patrick Müller committed
         * @throws CouponCodeException
    
         * @throws QUI\Database\Exception
    
    Patrick Müller's avatar
    Patrick Müller committed
         */
    
        public static function getCouponCodeByCode(string $code): CouponCode
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
    Patrick Müller's avatar
    Patrick Müller committed
            $result = QUI::getDataBase()->fetch([
                'select' => [
    
    Patrick Müller's avatar
    Patrick Müller committed
                    'id'
    
    Patrick Müller's avatar
    Patrick Müller committed
                ],
    
                'from' => self::getTable(),
                'where' => [
    
    Patrick Müller's avatar
    Patrick Müller committed
                    'code' => $code
    
    Patrick Müller's avatar
    Patrick Müller committed
                ],
    
                'limit' => 1
    
    Patrick Müller's avatar
    Patrick Müller committed
            ]);
    
    Patrick Müller's avatar
    Patrick Müller committed
    
            if (empty($result)) {
    
    Patrick Müller's avatar
    Patrick Müller committed
                throw new CouponCodeException([
    
                    'quiqqer/coupons',
    
    Patrick Müller's avatar
    Patrick Müller committed
                    'exception.Handler.code_not_found',
    
    Patrick Müller's avatar
    Patrick Müller committed
                    [
    
    Patrick Müller's avatar
    Patrick Müller committed
                        'code' => $code
    
    Patrick Müller's avatar
    Patrick Müller committed
                    ]
                ], 404);
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
    
    Patrick Müller's avatar
    Patrick Müller committed
            return self::getCouponCode($result[0]['id']);
    
    Patrick Müller's avatar
    Patrick Müller committed
        }
    
        /**
    
    Patrick Müller's avatar
    Patrick Müller committed
         * Create new CouponCode
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
    
         * @param array $discountIds - IDs of the discounts that are linked to this CouponCode
         * @param array $settings (optional) - If omitted a random default CouponCode is generated
    
    Patrick Müller's avatar
    Patrick Müller committed
         * @return CouponCode
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
    
         * @throws Exception
    
    Patrick Müller's avatar
    Patrick Müller committed
         */
    
        public static function createCouponCode(array $discountIds, array $settings = []): CouponCode
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
            $DiscountHandler = DiscountHandler::getInstance();
    
            if (empty($discountIds)) {
                throw new CouponCodeException([
                    'quiqqer/coupons',
                    'exception.Handler.no_discounts_linked'
                ]);
            }
    
            // check if all given discounts exist
            foreach ($discountIds as $discountId) {
                try {
                    $DiscountHandler->getChild($discountId);
    
                } catch (Exception $Exception) {
    
                    throw new CouponCodeException([
                        'quiqqer/coupons',
                        'exception.Handler.discount_error',
                        [
                            'discountId' => $discountId,
    
                            'error' => $Exception->getMessage()
    
            $Now = new DateTime();
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
            if (!empty($settings['code'])) {
                if (self::existsCode($settings['code'])) {
    
    Patrick Müller's avatar
    Patrick Müller committed
                    throw new CouponCodeException([
                        'quiqqer/coupons',
                        'exception.Handler.code_already_exists',
                        [
    
                            'code' => $settings['code']
    
    Patrick Müller's avatar
    Patrick Müller committed
                $code = self::sanitizeCode($settings['code']);
    
    Patrick Müller's avatar
    Patrick Müller committed
            } else {
                $code = CodeGenerator::generate();
            }
    
    
            if (empty($settings['maxUsages'])) {
                $maxUsages = self::MAX_USAGE_ONCE_PER_USER;
            } else {
                $maxUsages = $settings['maxUsages'];
            }
    
    
    Patrick Müller's avatar
    Patrick Müller committed
            $couponCode = [
    
                'title' => empty($settings['title']) ? null : $settings['title'],
                'createDate' => $Now->format('Y-m-d H:i:s'),
                'code' => $code,
                'maxUsages' => $maxUsages,
                'discountIds' => json_encode($discountIds)
    
    Patrick Müller's avatar
    Patrick Müller committed
            ];
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
            if (!empty($settings['validUntilDate'])) {
    
                $ValidUntil = new DateTime($settings['validUntilDate']);
    
    Patrick Müller's avatar
    Patrick Müller committed
                $couponCode['validUntilDate'] = $ValidUntil->format('Y-m-d H:i:s');
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
    
            if (!empty($settings['userIds'])) {
    
                $couponCode['userIds'] = json_encode(explode(",", $settings['userIds']));
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
            if (!empty($settings['groupIds'])) {
    
                $couponCode['groupIds'] = json_encode(explode(",", $settings['groupIds']));
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
    
            try {
                QUI::getDataBase()->insert(
                    self::getTable(),
                    $couponCode
                );
            } catch (QUI\Database\Exception $e) {
                throw new CouponCodeException([
                    $e->getMessage(),
                    $e->getCode()
                ]);
            }
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
    Patrick Müller's avatar
    Patrick Müller committed
            return self::getCouponCode(QUI::getPDO()->lastInsertId());
    
    Patrick Müller's avatar
    Patrick Müller committed
        }
    
    
        /**
         * Edit a CouponCode
         *
         * @param int $id - CouponCode ID
         * @param array $discountIds - IDs of the discounts that are linked to this CouponCode
         * @param array $settings (optional) - If omitted a random default CouponCode is generated
         * @return CouponCode
         *
    
         * @throws Exception
    
        public static function editCouponCode(int $id, array $discountIds, array $settings = []): CouponCode
    
        {
            QUI\Permissions\Permission::checkPermission(self::PERMISSION_EDIT);
    
            // check if CouponCode exists
            $CouponCode = self::getCouponCode($id);
    
            // Check settings
            $DiscountHandler = DiscountHandler::getInstance();
    
            if (empty($discountIds)) {
                throw new CouponCodeException([
                    'quiqqer/coupons',
                    'exception.Handler.no_discounts_linked'
                ]);
            }
    
            // check if all given discounts exist
            foreach ($discountIds as $discountId) {
                try {
                    $DiscountHandler->getChild($discountId);
    
                } catch (Exception $Exception) {
    
                    throw new CouponCodeException([
                        'quiqqer/coupons',
                        'exception.Handler.discount_error',
                        [
                            'discountId' => $discountId,
    
                            'error' => $Exception->getMessage()
    
            $Now = new DateTime();
    
    
            if (!empty($settings['code'])) {
    
                if (
                    $CouponCode->getCode() !== $settings['code']
                    && self::existsCode($settings['code'])
                ) {
    
                    throw new CouponCodeException([
                        'quiqqer/coupons',
                        'exception.Handler.code_already_exists',
                        [
                            'code' => $settings['code']
                        ]
                    ]);
                }
    
    
    Patrick Müller's avatar
    Patrick Müller committed
                $code = self::sanitizeCode($settings['code']);
    
            } else {
                $code = CodeGenerator::generate();
            }
    
    
            if (empty($settings['maxUsages'])) {
                $maxUsages = self::MAX_USAGE_ONCE_PER_USER;
            } else {
                $maxUsages = $settings['maxUsages'];
            }
    
    
            $couponCode = [
    
                'title' => empty($settings['title']) ? null : $settings['title'],
                'createDate' => $Now->format('Y-m-d H:i:s'),
                'code' => $code,
                'maxUsages' => $maxUsages,
                'discountIds' => json_encode($discountIds)
    
            ];
    
            if (!empty($settings['validUntilDate'])) {
    
                $ValidUntil = new DateTime($settings['validUntilDate']);
    
                $couponCode['validUntilDate'] = $ValidUntil->format('Y-m-d H:i:s');
            } else {
                $couponCode['validUntilDate'] = null;
            }
    
            if (!empty($settings['userIds'])) {
    
                $couponCode['userIds'] = json_encode(explode(",", $settings['userIds']));
    
            } else {
                $couponCode['userIds'] = null;
            }
    
            if (!empty($settings['groupIds'])) {
    
                $couponCode['groupIds'] = json_encode(explode(",", $settings['groupIds']));
    
            } else {
                $couponCode['groupIds'] = null;
            }
    
            QUI::getDataBase()->update(
                self::getTable(),
                $couponCode,
    
            );
    
            return self::getCouponCode($id);
        }
    
    
    Patrick Müller's avatar
    Patrick Müller committed
        /**
    
    Patrick Müller's avatar
    Patrick Müller committed
         * Search CouponCodes
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
         * @param array $searchParams
         * @param bool $countOnly (optional) - get result count only [default: false]
    
    Patrick Müller's avatar
    Patrick Müller committed
         * @return CouponCode[]|int
    
         * @throws CouponCodeException|QUI\Exception
    
    Patrick Müller's avatar
    Patrick Müller committed
         */
    
        public static function search(array $searchParams, bool $countOnly = false): array|int
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
    Patrick Müller's avatar
    Patrick Müller committed
            $couponCodes = [];
    
            $Grid = new Grid($searchParams);
            $gridParams = $Grid->parseDBParams($searchParams);
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
    Patrick Müller's avatar
    Patrick Müller committed
            $binds = [];
            $where = [];
    
    Patrick Müller's avatar
    Patrick Müller committed
    
            if ($countOnly) {
                $sql = "SELECT COUNT(*)";
            } else {
                $sql = "SELECT id";
            }
    
    
            $sql .= " FROM `" . self::getTable() . "`";
    
    Patrick Müller's avatar
    Patrick Müller committed
    
            if (!empty($searchParams['search'])) {
    
    Patrick Müller's avatar
    Patrick Müller committed
                $searchColumns = [
    
    Patrick Müller's avatar
    Patrick Müller committed
                    'id',
                    'code',
                    'email'
    
    Patrick Müller's avatar
    Patrick Müller committed
                ];
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
    Patrick Müller's avatar
    Patrick Müller committed
                $whereOr = [];
    
    Patrick Müller's avatar
    Patrick Müller committed
    
                foreach ($searchColumns as $searchColumn) {
    
                    $whereOr[] = '`' . $searchColumn . '` LIKE :search';
    
    Patrick Müller's avatar
    Patrick Müller committed
                }
    
                if (!empty($whereOr)) {
    
                    $where[] = '(' . implode(' OR ', $whereOr) . ')';
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
    Patrick Müller's avatar
    Patrick Müller committed
                    $binds['search'] = [
    
                        'value' => '%' . $searchParams['search'] . '%',
                        'type' => PDO::PARAM_STR
    
    Patrick Müller's avatar
    Patrick Müller committed
                    ];
    
    Patrick Müller's avatar
    Patrick Müller committed
                }
            }
    
            // build WHERE query string
            if (!empty($where)) {
    
                $sql .= " WHERE " . implode(" AND ", $where);
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
            // ORDER
    
            if (!empty($searchParams['sortOn'])) {
    
    Patrick Müller's avatar
    Patrick Müller committed
                $sortOn = Orthos::clear($searchParams['sortOn']);
    
                $order = "ORDER BY " . $sortOn;
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
                if (!empty($searchParams['sortBy'])) {
                    $order .= " " . Orthos::clear($searchParams['sortBy']);
    
    Patrick Müller's avatar
    Patrick Müller committed
                } else {
                    $order .= " ASC";
                }
    
    
                $sql .= " " . $order;
    
    Patrick Müller's avatar
    Patrick Müller committed
            } else {
                $sql .= " ORDER BY id DESC";
            }
    
            // LIMIT
    
            if (!empty($gridParams['limit']) && !$countOnly) {
    
                $sql .= " LIMIT " . $gridParams['limit'];
    
    Patrick Müller's avatar
    Patrick Müller committed
            } else {
                if (!$countOnly) {
    
                    $sql .= " LIMIT " . 20;
    
    Patrick Müller's avatar
    Patrick Müller committed
                }
            }
    
            $Stmt = QUI::getPDO()->prepare($sql);
    
            // bind search values
            foreach ($binds as $var => $bind) {
    
                $Stmt->bindValue(':' . $var, $bind['value'], $bind['type']);
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
            try {
                $Stmt->execute();
    
                $result = $Stmt->fetchAll(PDO::FETCH_ASSOC);
            } catch (Exception $Exception) {
    
    Patrick Müller's avatar
    Patrick Müller committed
                QUI\System\Log::addError(
    
                    self::class . ' :: search() -> ' . $Exception->getMessage()
    
    Patrick Müller's avatar
    Patrick Müller committed
                );
    
    
    Patrick Müller's avatar
    Patrick Müller committed
                return [];
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
            if ($countOnly) {
    
                return (int)current(current($result));
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
            foreach ($result as $row) {
    
    Patrick Müller's avatar
    Patrick Müller committed
                $couponCodes[] = self::getCouponCode($row['id']);
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
    
    Patrick Müller's avatar
    Patrick Müller committed
            return $couponCodes;
    
    Patrick Müller's avatar
    Patrick Müller committed
        }
    
        /**
    
         * Check if a CouponCode exists by its code
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
    
         * @param string $code The code of the CouponCode
         * @return bool Returns true if the CouponCode exists, false otherwise
         *
         * @throws QUI\Database\Exception
    
    Patrick Müller's avatar
    Patrick Müller committed
         */
    
        public static function existsCode(string $code): bool
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
    Patrick Müller's avatar
    Patrick Müller committed
            $result = QUI::getDataBase()->fetch([
    
    Patrick Müller's avatar
    Patrick Müller committed
                'select' => 'id',
    
                'from' => self::getTable(),
                'where' => [
    
    Patrick Müller's avatar
    Patrick Müller committed
                    'code' => $code
    
    Patrick Müller's avatar
    Patrick Müller committed
                ],
    
                'limit' => 1
    
    Patrick Müller's avatar
    Patrick Müller committed
            ]);
    
    Patrick Müller's avatar
    Patrick Müller committed
    
            return !empty($result);
        }
    
        /**
         * Get Registration site
         *
         * @return QUI\Projects\Site|false
         */
    
        public static function getRegistrationSite(): bool|QUI\Projects\Site
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
                $Conf = QUI::getPackage('quiqqer/coupons')->getConfig();
    
                $regSite = $Conf->get('settings', 'registrationSite');
            } catch (QUI\Exception $Exception) {
                QUI\System\Log::writeDebugException($Exception);
    
                return false;
            }
    
    Patrick Müller's avatar
    Patrick Müller committed
    
            if (empty($regSite)) {
                return false;
            }
    
            try {
                return QUI\Projects\Site\Utils::getSiteByLink($regSite);
    
    Patrick Müller's avatar
    Patrick Müller committed
                return false;
            }
        }
    
        /**
    
    Patrick Müller's avatar
    Patrick Müller committed
         * Deletes all CouponCodes that are expired
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
    
         * @param int|null $days (optional) - Delete expired Codes that are older than X days [default: delete all]
    
    Patrick Müller's avatar
    Patrick Müller committed
         * @return void
         *
    
         * @throws Exception
    
    Patrick Müller's avatar
    Patrick Müller committed
         */
    
        public static function deleteExpiredCouponCodes(int $days = null): void
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
            $Now = new DateTime();
    
    Patrick Müller's avatar
    Patrick Müller committed
            $where = [
                'validUntilDate' => [
    
                    'type' => '<=',
    
    Patrick Müller's avatar
    Patrick Müller committed
                    'value' => $Now->format('Y-m-d H:i:s')
    
    Patrick Müller's avatar
    Patrick Müller committed
                ]
            ];
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
            if (!is_null($days)) {
                $OldDate = new DateTime();
                $OldDate->sub(new DateInterval('P' . $days . 'D'));
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
    Patrick Müller's avatar
    Patrick Müller committed
                $where['validUntilDate'] = [
    
                    'type' => '<=',
    
    Patrick Müller's avatar
    Patrick Müller committed
                    'value' => $OldDate->format('Y-m-d H:i:s')
    
    Patrick Müller's avatar
    Patrick Müller committed
                ];
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
            QUI::getDataBase()->delete(
                self::getTable(),
                $where
            );
        }
    
        /**
    
    Patrick Müller's avatar
    Patrick Müller committed
         * Deletes all CouponCodes that have been redeemed
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
    
         * @param int|null $days (optional) - Delete redeemed Codes that are older than X days [default: delete all]
    
    Patrick Müller's avatar
    Patrick Müller committed
         * @return void
         *
    
         * @throws Exception
    
    Patrick Müller's avatar
    Patrick Müller committed
         */
    
        public static function deleteRedeemedCouponCodes(int $days = null): void
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
    Patrick Müller's avatar
    Patrick Müller committed
            $where = [
                'useDate' => [
    
                    'type' => 'NOT',
    
    Patrick Müller's avatar
    Patrick Müller committed
                    'value' => null
    
    Patrick Müller's avatar
    Patrick Müller committed
                ]
            ];
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
            if (!is_null($days)) {
                $OldDate = new DateTime();
                $OldDate->sub(new DateInterval('P' . $days . 'D'));
    
    Patrick Müller's avatar
    Patrick Müller committed
    
    
    Patrick Müller's avatar
    Patrick Müller committed
                $where['useDate'] = [
    
                    'type' => '<=',
    
    Patrick Müller's avatar
    Patrick Müller committed
                    'value' => $OldDate->format('Y-m-d H:i:s')
    
    Patrick Müller's avatar
    Patrick Müller committed
                ];
    
    Patrick Müller's avatar
    Patrick Müller committed
            }
    
            QUI::getDataBase()->delete(
                self::getTable(),
                $where
            );
        }
    
    
    Patrick Müller's avatar
    Patrick Müller committed
        /**
         * Sanitize coupon code and allow only certain characters
         *
         * @param string $code
         * @return string
         */
    
        public static function sanitizeCode(string $code): string
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
            return preg_replace('#[^A-Za-z0-9\.\-_\*&$% ]#i', '', $code);
    
    Patrick Müller's avatar
    Patrick Müller committed
        /**
    
    Patrick Müller's avatar
    Patrick Müller committed
         * Get CouponCode table
    
    Patrick Müller's avatar
    Patrick Müller committed
         *
         * @return string
         */
    
        public static function getTable(): string
    
    Patrick Müller's avatar
    Patrick Müller committed
        {
    
    Patrick Müller's avatar
    Patrick Müller committed
            return QUI::getDBTableName('quiqqer_coupons');
    
    Patrick Müller's avatar
    Patrick Müller committed
        }
    }