<?php
if (!defined("ROOT_PATH"))
{
	header("HTTP/1.1 403 Forbidden");
	exit;
}
class pjFront extends pjAppController
{
	public $defaultForm = 'SCart_Form';
	
	public $defaultCaptcha = 'SCart_Captcha';
	
	public $defaultUser = 'SCart_Client';
	
	public $defaultVoucher = 'SCart_Voucher';
	
	public $defaultCookie = 'SCart_Cookie';
	
	public $defaultTax = 'SCart_Tax';
	
	public $defaultLocale = 'SCart_LocaleId';
	
	public $defaultHash = 'SCart_Hash';
	
	public $defaultLangMenu = 'SCart_LangMenu';
	
	public $defaultCategoryMenu = 'SCart_CategoryMenu';
	
	public $cart = NULL;

	public function __construct()
	{
		$this->setLayout('pjActionFront');
		
		if (!isset($_SESSION[$this->defaultHash]))
		{
			if ($this->isLoged())
			{
				$_SESSION[$this->defaultHash] = md5(PJ_SALT . $this->getUserId());
			} else {
				$_SESSION[$this->defaultHash] = md5(uniqid(rand(), true));
			}
		}
		
		$this->setModel('Cart', pjCartModel::factory());
		$this->cart = new pjShoppingCart($this->getModel('Cart'), $_SESSION[$this->defaultHash]);
		$this->pjPruneOrphanCartItems();
		$this->set('cart_arr', $this->cart->getAll());

		self::allowCORS();
	}

	/**
	 * Remove cart items whose stock row no longer exists - e.g. the product was
	 * re-imported or updated, which rebuilds its stock rows with new ids. Such
	 * orphaned entries would otherwise inflate the header item count and make
	 * the price calculation fail (showing a 0.00 total) while the cart page
	 * silently skips them. Pruning keeps the count, total and page consistent.
	 */
	protected function pjPruneOrphanCartItems()
	{
		$items = $this->cart->getAll();
		if (empty($items)) { return; }
		$stock_ids = array();
		foreach ($items as $it)
		{
			if ((int) $it['stock_id'] > 0) { $stock_ids[(int) $it['stock_id']] = true; }
		}
		if (empty($stock_ids)) { return; }
		$existing = pjStockModel::factory()->whereIn('t1.id', array_keys($stock_ids))->findAll()->getDataPair('id');
		foreach ($items as $it)
		{
			$sid = (int) $it['stock_id'];
			if ($sid > 0 && !isset($existing[$sid]))
			{
				$this->cart->remove(md5($it['key_data']));
			}
		}
	}
	
	public function afterFilter()
	{		
		if ($this->_get->check('locale') && $this->_get->toInt('locale') > 0)
	    {
	        $this->pjActionSetLocale($this->_get->toInt('locale'));
	    }
	    
	    if ($this->pjActionGetLocale() === FALSE)
	    {
	        $locale_arr = pjLocaleModel::factory()->where('is_default', 1)->limit(1)->findAll()->getData();
	        if (count($locale_arr) === 1)
	        {
	            $this->pjActionSetLocale($locale_arr[0]['id']);
	        }
	    }
	    if ($this->_get->check('action') && !in_array($this->_get->toArray('action'), array('pjActionLoadCss')))
	    {
	        $this->loadSetFields(true);
	    }
	    
		if (!$this->_get->check('hide') || ($this->_get->check('hide') && $this->_get->toInt('hide') !== 1) &&
            in_array($this->_get->toString('action'), array('pjActionLogin', 'pjActionForgot', 'pjActionRegister',
				'pjActionProfile', 'pjActionFavs', 'pjActionProducts', 'pjActionProduct',
				'pjActionCart', 'pjActionCheckout', 'pjActionPreview', 'pjActionGetPaymentForm')))
        {
            $locale_arr = pjLocaleModel::factory()->select('t1.*, t2.file, t2.title')
            ->join('pjBaseLocaleLanguage', 't2.iso=t1.language_iso', 'left')
            ->where('t2.file IS NOT NULL')
            ->orderBy('t1.sort ASC')->findAll()->getData();
            
            $this->set('locale_arr', $locale_arr);
        }
		$this->set('hidden_ids_arr', pjProductModel::factory()->where('t1.status <>', 1)->findAll()->getDataPair('id', 'id'));
		$this->set('demo_product_ids_arr', pjUtil::getDemoProductIds());
	}
	
	public function beforeFilter()
	{
		return parent::beforeFilter();
	}

	/**
	 * Block checkout pricing when the cart contains demo catalogue items.
	 */
	protected function pjActionRejectDemoCartItems()
	{
		if (pjUtil::areDemoPurchasesAllowed($this->option_arr)) {
			return null;
		}

		$cart_arr = $this->get('cart_arr');
		if (empty($cart_arr)) {
			return null;
		}
		foreach ($cart_arr as $cart_item) {
			if (!empty($cart_item['product_id']) && pjUtil::shouldBlockDemoProduct($cart_item['product_id'], $this->option_arr)) {
				return array(
					'status' => 'ERR',
					'code'   => 120,
					'text'   => pjUtil::getDemoProductOrderBlockedMessage(),
				);
			}
		}

		return null;
	}

	/**
	 * Reject add-to-cart AJAX when the request targets a demo product.
	 */
	protected function pjActionRejectDemoCartPost($post)
	{
		if (pjUtil::areDemoPurchasesAllowed($this->option_arr)) {
			return null;
		}

		if (!is_array($post)) {
			return null;
		}

		$productId = 0;
		if (!empty($post['id'])) {
			$productId = (int) $post['id'];
		} elseif (!empty($post['product_id'])) {
			$productId = (int) $post['product_id'];
		}

		if ($productId > 0 && pjUtil::shouldBlockDemoProduct($productId, $this->option_arr)) {
			return array(
				'status' => 'ERR',
				'code'   => 120,
				'text'   => pjUtil::getDemoProductOrderBlockedMessage(),
			);
		}

		if (!empty($post['stock_id']) && pjUtil::shouldBlockDemoStock($post['stock_id'], $this->option_arr)) {
			return array(
				'status' => 'ERR',
				'code'   => 120,
				'text'   => pjUtil::getDemoProductOrderBlockedMessage(),
			);
		}

		return null;
	}

	public function beforeRender()
	{
		$this->set('price_arr', $this->pjActionGetPrice());
	}
	
	protected function pjActionGetPrice()
	{
		if ($this->cart->isEmpty())
		{
			return array('status' => 'ERR', 'code' => 105, 'text' => 'Empty cart.');
		}
		$demo_block = $this->pjActionRejectDemoCartItems();
		if ($demo_block !== null) {
			return $demo_block;
		}
		$data = $stock_id = $stocks = $product_id = array();
		$cart_arr = $this->get('cart_arr');
		foreach ($cart_arr as $cart_item)
		{
			if (isset($cart_item['stock_id']) && (int) $cart_item['stock_id'] > 0)
			{
				$stock_id[] = $cart_item['stock_id'];
			}
			$product_id[] = $cart_item['product_id'];
		}
		if (empty($stock_id))
		{
			return array('status' => 'ERR', 'code' => 105, 'text' => 'Empty cart.');
		}
		$stocks = pjStockModel::factory()
			->whereIn('t1.id', $stock_id)
			->findAll()
			->getDataPair('id');

		if (empty($stocks))
		{
			return array('status' => 'ERR', 'code' => 106, 'text' => 'Stocks in cart not found into the database.');
		}
		
		$pjExtraItemModel = pjExtraItemModel::factory();
		$extra_arr = pjExtraModel::factory()->whereIn('t1.product_id', $product_id)->findAll()->getDataPair('id', 'price');
		foreach ($extra_arr as $e_id => $e_price)
		{
			$extra_arr[$e_id] = array(
					'price' => $e_price,
					'extra_items' => $pjExtraItemModel->reset()
					->join('pjExtra', "t2.id=t1.extra_id AND t2.type='multi'", 'inner')
					->where('t1.extra_id', $e_id)->findAll()->getDataPair('id', 'price')
			);
		}
		
		$calc_price = pjAppController::pjActionCalcPrices($product_id, $extra_arr, $cart_arr, $stocks, @$_SESSION[$this->defaultVoucher], $this->option_arr, isset($_SESSION[$this->defaultTax]) ? $_SESSION[$this->defaultTax] : null,  'front');
		if($calc_price == false)
		{
			return array('status' => 'ERR', 'code' => 108, 'text' => __('system_118', true));
		}
		
		$data['price'] = $calc_price['price'];
		$data['discount'] = $calc_price['discount'];
		$data['insurance'] = $calc_price['insurance'];
		$data['shipping'] = $calc_price['shipping'];
		$data['tax'] = $calc_price['tax'];
		$data['total'] = $calc_price['total'];
		$data['total'] = $data['total'] > 0 ? $data['total'] : 0;
		
		return array('status' => 'OK', 'code' => 200, 'text' => 'Success', 'data' => $data);
	}
	
	protected function pjActionGetCart()
	{
		# Find out what qty is in current shopping cart for each stock
		$order_arr = $product_id = $stock_id = array();
		$cart_arr = $this->get('cart_arr');
		foreach ($cart_arr as $cart_item)
		{
			if (!isset($order_arr[$cart_item['stock_id']]))
			{
				$order_arr[$cart_item['stock_id']] = 0;
			}
			$order_arr[$cart_item['stock_id']] += $cart_item['qty'];
		
			$product_id[] = $cart_item['product_id'];
			if (!empty($cart_item['stock_id']))
			{
				$stock_id[] = $cart_item['stock_id'];
			}
		}
		
		$arr = pjProductModel::factory()
			->select(sprintf("t1.*, t2.content AS name,
				(SELECT GROUP_CONCAT(`category_id`) FROM `%1\$s` WHERE `product_id` = `t1`.`id` LIMIT 1) AS `category_ids`",
				pjProductCategoryModel::factory()->getTable()
			))
			->join('pjMultiLang', "t2.model='pjProduct' AND t2.foreign_id=t1.id AND t2.locale='".$this->getLocaleId()."' AND t2.field='name'", 'left outer')
			->join('pjStock', 't3.product_id=t1.id', 'inner')
			->whereIn('t1.id', $product_id)
			->findAll()
			->toArray('category_ids', ',')
			->getData();

		$tax_arr = pjTaxModel::factory()
			->select('t1.*, t2.content AS location')
			->join('pjMultiLang', "t2.model='pjTax' AND t2.foreign_id=t1.id AND t2.field='location' AND t2.locale='".$this->getLocaleId()."'", 'left outer')
			->orderBy('`location` ASC')
			->findAll()
			->getData();
			
		$_stock_arr = pjStockModel::factory()
			->whereIn('t1.id', $stock_id)
			->findAll()
			->getData();
		$stock_arr = array();
		foreach ($_stock_arr as $stock)
		{
			$stock_arr[$stock['id']] = $stock;
		}
		
		$image_arr = pjStockModel::factory()
			->select('t1.id, t2.small_path')
			->join('pjGallery', 't2.id=t1.image_id', 'left outer')
			->whereIn('t1.id', $stock_id)
			->findAll()
			->getDataPair('id', 'small_path');
		foreach($image_arr as $id => $img)
		{
			if(empty($img))
			{
				$gallery_arr = pjGalleryModel::factory()
					->select('t1.*')
					->where("`foreign_id` = (SELECT TS.`product_id` FROM `".pjStockModel::factory()->getTable()."` AS TS WHERE TS.id='$id')")
					->limit(1)
					->findAll()
					->getData();
				if(!empty($gallery_arr))
				{
					$image_arr[$id] = $gallery_arr[0]['small_path'];
				}
			}
		}

		$extra_arr = pjAppController::pjActionGetExtrasList($product_id, $this->getLocaleId());
		$attr_arr = pjAppController::pjActionGetAttr($product_id, $this->getLocaleId());
				
		return compact('arr', 'extra_arr', 'order_arr', 'attr_arr', 'stock_arr', 'tax_arr', 'image_arr');
	}
	
	public function pjActionCaptcha()
	{
	    $this->setAjax(true);
	    
	    header("Cache-Control: max-age=3600, private");
	    $rand = $this->_get->toInt('rand') ? $this->_get->toInt('rand') : rand(1, 9999);
	    $patterns = 'app/web/img/button.png';
	    if(!empty($this->option_arr['o_captcha_background_front']) && $this->option_arr['o_captcha_background_front'] != 'plain')
	    {
	        $patterns = PJ_INSTALL_PATH . $this->getConstant('pjBase', 'PLUGIN_IMG_PATH') . 'captcha_patterns/' . $this->option_arr['o_captcha_background_front'];
	    }
	    $Captcha = new pjCaptcha(PJ_INSTALL_PATH . $this->getConstant('pjBase', 'PLUGIN_WEB_PATH') . 'obj/arialbd.ttf', $this->defaultCaptcha, (int) $this->option_arr['o_captcha_length_front']);
	    $Captcha->setImage($patterns)->setMode($this->option_arr['o_captcha_mode_front'])->init($rand);
	    exit;
	}
	
	public function pjActionCheckCaptcha()
	{
	    $this->setAjax(true);
	    if (!$this->_get->check('captcha') || !$this->_get->toString('captcha') || !pjCaptcha::validate($this->_get->toString('captcha'), $_SESSION[$this->defaultCaptcha])){
	        echo 'false';
	    }else{
	        echo 'true';
	    }
	    exit;
	}
	
	public function pjActionCheckReCaptcha()
	{
	    $this->setAjax(true);
	    $verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='.$this->option_arr['o_captcha_secret_key_front'].'&response='.$this->_get->toString('recaptcha'));
	    $responseData = json_decode($verifyResponse);
	    echo $responseData->success ? 'true': 'false';
	    exit;
	}
	
	public static function pjActionConfirmSend($option_arr, $data, $salt, $opt, $locale)
	{
		$Email = self::getMailer($option_arr);
	    
	    $pjMultiLangModel = pjMultiLangModel::factory();
	    
	    $admin_email = pjAppController::getAdminEmail();
	    $admin_phone = pjAppController::getAdminPhone();
	    $locale_id = isset($data['locale_id']) && (int) $data['locale_id'] > 0 ? (int) $data['locale_id'] : $locale;
	    $data['products'] = preg_replace('/\r\n|\n/', '<br />', pjAppController::pjActionGetProductsString($data['id'], $locale_id));
		$tokens = pjAppController::getTokens($data, $option_arr);
		
	    $pjNotificationModel = pjNotificationModel::factory();
		/*Confirmation sent to clients*/
		$notification = $pjNotificationModel->reset()->where('recipient', 'client')->where('transport', 'email')->where('variant', $opt)->findAll()->getDataIndex(0);
		if(is_array($notification) && (int) $notification['id'] > 0 && $notification['is_active'] == 1)
		{
			$resp = pjFront::pjActionGetSubjectMessage($notification, $locale_id);
			$lang_message = $resp['lang_message'];
			$lang_subject = $resp['lang_subject'];
			if (count($lang_message) === 1 && count($lang_subject) === 1)
			{
				$subject = str_replace($tokens['search'], $tokens['replace'], $lang_subject[0]['content']);
				$message = str_replace($tokens['search'], $tokens['replace'], $lang_message[0]['content']);
				$Email
				->setTo($data['email'])
				->setSubject($subject)
				->send(pjUtil::textToHtml($message));
			}
		}
		/*Confirmation sent to admin*/
		$notification = $pjNotificationModel->reset()->where('recipient', 'admin')->where('transport', 'email')->where('variant', $opt)->findAll()->getDataIndex(0);
		if(is_array($notification) && (int) $notification['id'] > 0 && $notification['is_active'] == 1)
		{
			$resp = pjFront::pjActionGetSubjectMessage($notification, $locale_id);
			$lang_message = $resp['lang_message'];
			$lang_subject = $resp['lang_subject'];
			if (count($lang_message) === 1 && count($lang_subject) === 1)
			{
				$subject = str_replace($tokens['search'], $tokens['replace'], $lang_subject[0]['content']);
				$message = str_replace($tokens['search'], $tokens['replace'], $lang_message[0]['content']);
				foreach ($admin_email as $a_email) {
					$Email
					->setTo($a_email)
					->setSubject($subject)
					->send(pjUtil::textToHtml($message));
				}
			}
		}
		/*SMS sent to client*/
		if(!empty($data['phone']))
		{
			$notification = $pjNotificationModel->reset()->where('recipient', 'client')->where('transport', 'sms')->where('variant', $opt)->findAll()->getDataIndex(0);
			if(is_array($notification) && (int) $notification['id'] > 0 && $notification['is_active'] == 1)
			{
				$variant = $notification['variant'] == 'confirmation' ? 'confirm' : $notification['variant'];
        		$field = $variant . '_sms_tokens_' . $notification['recipient'];
				$lang_message = $pjMultiLangModel
				->reset()
				->select('t1.*')
				->where('t1.foreign_id', $notification['id'])
				->where('t1.model','pjNotification')
				->where('t1.locale', $locale_id)
				->where('t1.field', $field)
				->limit(0, 1)
				->findAll()
				->getData();
				if (count($lang_message) === 1)
				{
					$message = str_replace($tokens['search'], $tokens['replace'], $lang_message[0]['content']);
					$params = array(
						'text' => $message,
						'type' => 'unicode',
						'key' => md5($option_arr['private_key'] . PJ_SALT)
					);
					$params['number'] = $data['phone'];
					pjBaseSms::init($params)->pjActionSend();
				}
			}
		}
		/*SMS sent to admin*/
		if(!empty($admin_phone))
		{
			$notification = $pjNotificationModel->reset()->where('recipient', 'admin')->where('transport', 'sms')->where('variant', $opt)->findAll()->getDataIndex(0);
			if(is_array($notification) && (int) $notification['id'] > 0 && $notification['is_active'] == 1)
			{
				$variant = $notification['variant'] == 'confirmation' ? 'confirm' : $notification['variant'];
        		$field = $variant . '_sms_tokens_' . $notification['recipient'];
				$lang_message = $pjMultiLangModel
				->reset()
				->select('t1.*')
				->where('t1.foreign_id', $notification['id'])
				->where('t1.model','pjNotification')
				->where('t1.locale', $locale_id)
				->where('t1.field', $field)
				->limit(0, 1)
				->findAll()
				->getData();
				if (count($lang_message) === 1)
				{
					$message = str_replace($tokens['search'], $tokens['replace'], $lang_message[0]['content']);
					foreach ($admin_phone as $a_phone) {
						$params = array(
							'text' => $message,
							'type' => 'unicode',
							'key' => md5($option_arr['private_key'] . PJ_SALT)
						);
						$params['number'] = $a_phone;
						pjBaseSms::init($params)->pjActionSend();
					}
				}
			}
		}
	}
	
	public function pjActionDigitalDownload()
	{
		$this->setLayout('pjActionEmpty');
		
		if (!$this->_get->check('uuid') || $this->_get->toString('uuid')  == '' || !$this->_get->check('hash') || $this->_get->toString('uuid') == '' || md5($this->_get->toString('uuid') . PJ_SALT) != $this->_get->toString('hash'))
		{
			$this->set('status', 1);
			return;
		}
		
		$order = pjOrderModel::factory()->where('t1.uuid', $this->_get->toString('uuid'))->limit(1)->findAll()->getData();
		if (empty($order))
		{
			$this->set('status', 2);
			return;
		}
		
		$order = $order[0];
		if ($order['status'] != 'completed')
		{
			$this->set('status', 3);
			return;
		}
		
		$os_arr = pjOrderStockModel::factory()
			->select('t3.digital_file, t3.digital_name, t3.digital_expire, t2.processed_on,
				DATE_ADD(t2.processed_on, INTERVAL t3.digital_expire HOUR_SECOND) AS `expire_at`,
				IF(DATE_ADD(t2.processed_on, INTERVAL t3.digital_expire HOUR_SECOND) < NOW(), 1, 0) AS `is_expired`')
			->join('pjOrder', 't2.id=t1.order_id', 'inner')
			->join('pjProduct', "t3.id=t1.product_id AND t3.is_digital='1'", 'inner')
			->where('t1.order_id', $order['id'])
			->findAll()
			->getData();

		if (empty($os_arr))
		{
			$this->set('status', 4);
			return;
		}
		
		$digitals = $expired = array();
		foreach ($os_arr as $item)
		{
			if ((int) $item['is_expired'] === 0 || $item['digital_expire'] == '00:00:00')
			{
				$digitals[] = $item;
			} else {
				$expired[] = $item;
			}
		}
		
		if (empty($digitals))
		{
			$this->set('status', 5);
			return;
		}
		
		$zip = new pjZipStream();
		foreach ($digitals as $file)
		{
			if (empty($file['digital_file']) || !is_file($file['digital_file']))
			{
				continue;
			}
			$handle = @fopen($file['digital_file'], "rb");
			if ($handle)
			{
				$zip->addLargeFile($handle, $file['digital_name']);
				fclose($handle);
			}
		}
		$zip->finalize();
		$zip->sendZip(sprintf("%s.zip", $order['uuid']));
		exit;
	}
	
	public function pjActionGetStocks()
	{
		$this->setAjax(true);
	
		if ($this->isXHR())
		{
			if ($this->_get->check('id') && $this->_get->toInt('id') > 0)
			{
				# Find out what qty is in current shopping cart for each stock
				$order_arr = array();
				$cart_arr = $this->get('cart_arr');
				foreach ($cart_arr as $cart_item)
				{
					if (!isset($order_arr[$cart_item['stock_id']]))
					{
						$order_arr[$cart_item['stock_id']] = 0;
					}
					$order_arr[$cart_item['stock_id']] += $cart_item['qty'];
				}
				
				$pjStockModel = pjStockModel::factory();
				$pjStockAttributeModel = pjStockAttributeModel::factory();
				$pjAttributeModel = pjAttributeModel::factory();
				
				$stock_arr = $pjStockModel
					->join('pjProduct', 't1.product_id=t2.id', 'left')
					->where('t1.product_id', $this->_get->toInt('id'))
					->where("(t1.qty > 0 OR t2.is_digital='1')")
					->findAll()->getData();
				
				$stocks = $stock_ids = $qty = $price = array();
				foreach ($stock_arr as $k => $stock)
				{
					$_qty = $stock['qty'];
					if (isset($order_arr[$stock['id']]))
					{
						$_qty -= $order_arr[$stock['id']];
						if ($_qty < 1)
						{
							continue;
						}
					}
					$stock_ids[] = $stock['id'];
					$stocks[] = $pjStockAttributeModel
						->reset()
						->where('t1.stock_id', $stock['id'])
						->where("t1.attribute_id IN (SELECT TA.id FROM `".$pjAttributeModel->getTable()."` AS `TA` WHERE `TA`.product_id='". $this->_get->toInt('id')."')")
						->orderBy('t1.attribute_id ASC')
						->findAll()
						->getDataPair('attribute_parent_id', 'attribute_id');
						
					$qty[] = $_qty;
					$price[] = $stock['price'];
				}

				# -- Fix for empty values in stocks
				$attr_arr = $pjAttributeModel
					->where('t1.product_id', $this->_get->toInt('id'))
					->where(sprintf("(CONCAT_WS('_', t1.id, t1.parent_id) IN (
							SELECT CONCAT_WS('_', TSA.attribute_id, TSA.attribute_parent_id)
							FROM `%s` AS `TSA`
							INNER JOIN `%s` AS `TS` ON TS.id = TSA.stock_id AND TS.qty > 0
							WHERE TSA.product_id = t1.product_id
						) OR t1.parent_id IS NULL OR t1.parent_id = '0')", $pjStockAttributeModel->getTable(), $pjStockModel->getTable()))
					->findAll()
					->getDataPair('id', 'parent_id');
				
				foreach ($stocks as $k => $stock)
				{
					foreach ($stock as $_k => $_v)
					{
						if ((int) $_v === 0)
						{
							$stokkk = $stock;
							pjUtil::reArrange($stocks, $qty, $price, $stokkk, $attr_arr, $_k, $k);
						}
					}
				}
				# -- End fix
				
				# Attributes --
				$attr_arr = array();
				// Do not change col_name, direction
				$a_arr = $pjAttributeModel
					->reset()
					->select('t1.id, t1.product_id, t1.parent_id, t2.content AS name')
					->join('pjMultiLang', "t2.model='pjAttribute' AND t2.foreign_id=t1.id AND t2.field='name' AND t2.locale='".$this->pjActionGetLocale()."'", 'left outer')
					->where('t1.product_id', $this->_get->toInt('id'))
					->where(sprintf("(CONCAT_WS('_', t1.id, t1.parent_id) IN (
							SELECT CONCAT_WS('_', TSA.attribute_id, TSA.attribute_parent_id)
							FROM `%s` AS `TSA`
							INNER JOIN `%s` AS `TS` ON TS.id = TSA.stock_id AND TS.qty > 0
							WHERE TSA.product_id = t1.product_id
						) OR t1.parent_id IS NULL OR t1.parent_id = '0')", $pjStockAttributeModel->getTable(), $pjStockModel->getTable()))
					->orderBy('t1.parent_id ASC, `name` ASC')
					->findAll()
					->getData();

				foreach ($a_arr as $attr)
				{
					if ((int) $attr['parent_id'] === 0)
					{
						$attr_arr[$attr['id']] = $attr;
					} else {
						if (!isset($attr_arr[$attr['parent_id']]['child']))
						{
							$attr_arr[$attr['parent_id']]['child'] = array();
						}
						$attr_arr[$attr['parent_id']]['child'][] = $attr;
					}
				}
				$attributes = array_values($attr_arr);
				# Attributes --

				# Fix for no-stock
				if (isset($stocks[0]) && empty($stocks[0]))
				{
					$stocks = array();
				}
				pjAppController::jsonResponse(compact('stocks', 'qty', 'price', 'stock_ids', 'attributes'));
			}
		}
		exit;
	}

	public function pjActionSendToFriend()
	{
		$this->setAjax(true);
		
		if ($this->isXHR())
		{
			if (!$this->_post->check('id') || $this->_post->toInt('id') <= 0 || !$this->_post->check('url') || $this->_post->toString('url') == '' ||
				!$this->_post->check('your_email') || $this->_post->toString('your_email') == '' || !pjValidation::pjActionEmail($this->_post->toString('your_email')) ||
				!$this->_post->check('your_name') || $this->_post->toString('your_name') == '' ||
				!$this->_post->check('friend_email') || $this->_post->toString('friend_email') == '' || !pjValidation::pjActionEmail($this->_post->toString('friend_email')) ||
				!$this->_post->check('friend_name') || $this->_post->toString('friend_name') == '' ||
				!$this->_post->check('captcha') || $this->_post->toString('captcha') == ''
				|| !isset($_SESSION[$this->defaultCaptcha])
				|| !pjCaptcha::validate($this->_post->toString('captcha'), $_SESSION[$this->defaultCaptcha])
			)
			{
				pjAppController::jsonResponse(array('status' => 'ERR', 'code' => 100, 'text' => __('system_100', true)));
			}
			
			$_SESSION[$this->defaultCaptcha] = NULL;
			unset($_SESSION[$this->defaultCaptcha]);
			
			$notification = pjNotificationModel::factory()->where('recipient', 'client')->where('transport', 'email')->where('variant', 'send_to_friend')->findAll()->getDataIndex(0);
			if(is_array($notification) && (int) $notification['id'] > 0 && $notification['is_active'] == 1)
			{
				$resp = pjAppController::pjActionGetSubjectMessage($notification, $this->getLocaleId());
				$lang_message = $resp['lang_message'];
				$lang_subject = $resp['lang_subject'];
				if (count($lang_message) === 1 && count($lang_subject) === 1)
				{
					$search = array(
						'{FriendName}', 
						'{FriendEmail}', 
						'{YourName}', 
						'{YourEmail}', 
						'{URL}'
					);
					$replace = array(
						pjSanitize::html($this->_post->toString('friend_name')), 
						pjSanitize::html($this->_post->toString('friend_email')), 
						pjSanitize::html($this->_post->toString('your_name')), 
						pjSanitize::html($this->_post->toString('your_email')), 
						pjSanitize::html($this->_post->toString('url'))
					);
					$subject_client = str_replace($search, $replace, $lang_subject[0]['content']);
					$message_client = str_replace($search, $replace, $lang_message[0]['content']);
										
					$Email = self::getMailer($this->option_arr);
	    			$r = $Email
		    			->setFrom($this->_post->toString('your_email'))
		    			->setTo($this->_post->toString('friend_email'))
		    			->setSubject($subject_client)
		    			->send(pjUtil::textToHtml($message_client));
	    				
	    			if (isset($r) && $r)
	    			{
	    				pjAppController::jsonResponse(array('status' => 'OK', 'code' => 200, 'text' => __('system_200', true)));
	    			}
	    			pjAppController::jsonResponse(array('status' => 'ERR', 'code' => 101, 'text' => __('system_101', true)));
				}
			}
		}
		exit;
	}
	
	public function pjActionLoad()
	{
		ob_start();
		header("Content-type: text/javascript");
		
		$_SESSION[$this->defaultLangMenu] = 'show';		
		if($this->_get->check('locale') && $this->_get->toInt('locale') > 0)
		{
			$this->loadSetFields(true);
		}
		if($this->_get->check('category_id') && $this->_get->toInt('category_id') > 0)
		{
			$_SESSION[$this->defaultCategoryMenu] = $this->_get->toInt('category_id');
		}else{
			$_SESSION[$this->defaultCategoryMenu] = 0;
		}
	}
	
	public function pjActionLoadCss()
	{
	    $dm = new pjDependencyManager(PJ_INSTALL_PATH, PJ_THIRD_PARTY_PATH);
	    $dm->load(PJ_CONFIG_PATH . 'dependencies.php')->resolve();
		
		$layout = $this->_get->check('layout') && in_array($this->_get->toInt('layout'), $this->getLayoutRange()) ?
			$this->_get->toInt('layout') : (int) $this->option_arr['o_layout'];

		$theme = 'theme1';
	    
		$arr = array(
			array('file' => 'ShoppingCart'.$layout.'.css', 'path' => PJ_CSS_PATH),
		    array('file' => 'pjQuery.fancybox.css', 'path' => $dm->getPath('pj_fancybox')),
		    array('file' => 'assets/owl.carousel.min.css', 'path' => $dm->getPath('pj_owlcarousel')),
		    array('file' => 'css/swiper-bundle.min.css', 'path' => $dm->getPath('pj_swiper'), 'replace' => false),
			array('file' => $theme.'.css', 'path' => PJ_CSS_PATH)
		);
		header("Content-Type: text/css; charset=utf-8");

		// Load the selected Google font FIRST (an @import must precede all other
		// rules). Without this the storefront only had "font-family: 'X'" with the
		// font never loaded, so it fell back to sans-serif even though the admin
		// live preview (which loads the font) showed it correctly.
		$font = isset($this->option_arr['o_font_family']) ? $this->option_arr['o_font_family'] : '';
		$safeFont = !empty($font) ? preg_replace('/[^a-zA-Z0-9\s]/', '', $font) : '';
		if (!empty($safeFont))
		{
			echo "@import url('https://fonts.googleapis.com/css2?family=" . str_replace(' ', '+', $safeFont) . ":wght@400;500;600;700&display=swap');\n";
		}

		foreach ($arr as $item)
		{
			$string = FALSE;
			if ($stream = fopen($item['path'] . $item['file'], 'rb'))
			{
				$string = stream_get_contents($stream);
				fclose($stream);
			}
			
			if ($string !== FALSE)
			{
			    if (!isset($item['replace'])) {
    				echo str_replace(
    					array("url('","pjWrapper"),
    					array(
    						"url('" . PJ_INSTALL_URL . $dm->getPath('pj_fancybox'),
    						"pjWrapperShoppingCart_" . $theme),
    					$string
    				) . "\n";
			    } else {
			        echo $string . "\n";
			    }
			}
		}

		require_once PJ_APP_PATH . 'classes/pjColorHelper.class.php';

		$brand = isset($this->option_arr['o_brand_color']) && pjColorHelper::isValidHex($this->option_arr['o_brand_color']) ? $this->option_arr['o_brand_color'] : '#ff6600';
		$accent = isset($this->option_arr['o_accent_color']) && pjColorHelper::isValidHex($this->option_arr['o_accent_color']) ? $this->option_arr['o_accent_color'] : '#f3f4f6';
		$text = isset($this->option_arr['o_text_color']) && pjColorHelper::isValidHex($this->option_arr['o_text_color']) ? $this->option_arr['o_text_color'] : '#111827';
		$bg = isset($this->option_arr['o_background_color']) && pjColorHelper::isValidHex($this->option_arr['o_background_color']) ? $this->option_arr['o_background_color'] : '#ffffff';
		$price = isset($this->option_arr['o_price_color']) && pjColorHelper::isValidHex($this->option_arr['o_price_color']) ? $this->option_arr['o_price_color'] : '#ff6600';
		$pageBg = isset($this->option_arr['o_page_background_color']) && pjColorHelper::isValidHex($this->option_arr['o_page_background_color']) ? $this->option_arr['o_page_background_color'] : '#ffffff';

		echo pjColorHelper::generateCssVariables($brand, $accent, $text, $bg, $price, $pageBg);

		// $safeFont was computed (and the font @import emitted) at the top of this method.
		if (!empty($safeFont))
		{
			echo "\n/* Custom font */\n";
			echo "#pjWrapperShoppingCart_" . $theme . ", #pjWrapperShoppingCart_" . $theme . " * { font-family: '" . $safeFont . "', sans-serif; }\n";
			echo "#pjWrapperShoppingCart_" . $theme . " .fa, #pjWrapperShoppingCart_" . $theme . " .fa:before, #pjWrapperShoppingCart_" . $theme . " .fa:after { font-family: FontAwesome !important; }\n";
			echo "#pjWrapperShoppingCart_" . $theme . " .glyphicon, #pjWrapperShoppingCart_" . $theme . " .glyphicon:before, #pjWrapperShoppingCart_" . $theme . " .glyphicon:after { font-family: 'Glyphicons Halflings' !important; }\n";
		}
		exit;
	}
	
	public function pjActionLogout()
	{
		$this->setAjax(true);
		
		if ($this->isXHR())
		{
			if ($this->isLoged())
			{
				$_SESSION[$this->defaultUser] = NULL;
				unset($_SESSION[$this->defaultUser]);
				
				$_SESSION[$this->defaultHash] = NULL;
				unset($_SESSION[$this->defaultHash]);
			}
			pjAppController::jsonResponse(array('status' => 'OK', 'code' => 201, 'text' => __('system_201', true)));
		}
		exit;
	}
	
	public function pjActionLocale()
	{
		$this->setAjax(true);
	
		if ($this->isXHR())
		{
			if ($this->_get->check('locale_id'))
			{
				$this->pjActionSetLocale($this->_get->toInt('locale_id'));
				$this->loadSetFields(true);
			}
		}
		exit;
	}
	
	private function pjActionSetLocale($locale)
	{
		if ((int) $locale > 0)
		{
			$_SESSION[$this->defaultLocale] = (int) $locale;
		}
		return $this;
	}
	
	public function pjActionGetLocale()
	{
		return isset($_SESSION[$this->defaultLocale]) && (int) $_SESSION[$this->defaultLocale] > 0 ? (int) $_SESSION[$this->defaultLocale] : FALSE;
	}

	public function pjActionShowShipping()
	{
		$cart_arr = $this->get('cart_arr');
		foreach ($cart_arr as $cart_item)
		{
			$item = unserialize($cart_item['key_data']);
			if ((int) $item['is_digital'] === 0)
			{
				return true;
				break;
			}
		}
		
		return false;
	}
	
	protected function pjActionSaveToAddressBook($client_id, $data, $prefix='b_')
	{
		return pjAddressModel::factory()->setAttributes(array(
			'client_id' => $client_id,
			'country_id' => @$data[$prefix.'country_id'],
			'state' => @$data[$prefix.'state'],
			'city' => @$data[$prefix.'city'],
			'zip' => @$data[$prefix.'zip'],
			'address_1' => @$data[$prefix.'address_1'],
			'address_2' => @$data[$prefix.'address_2'],
			'name' => @$data[$prefix.'name']
		))->insert()->getInsertId();
	}
	
	public function isXHR()
	{
		return parent::isXHR() || isset($_SERVER['HTTP_ORIGIN']);
	}
	
	protected static function allowCORS()
    {
        $install_url = parse_url(PJ_INSTALL_URL);
        if($install_url['scheme'] == 'https'){
            header('Set-Cookie: '.session_name().'='.session_id().'; SameSite=None; Secure');
        }
        $origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*';
        header('P3P: CP="ALL DSP COR CUR ADM TAI OUR IND COM NAV INT"');
        header("Access-Control-Allow-Origin: $origin");
        header("Access-Control-Allow-Credentials: true");
        header("Access-Control-Allow-Methods: POST, GET, OPTIONS");
        header("Access-Control-Allow-Headers: Origin, X-Requested-With");
        if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS')
        {
            exit;
        }
    }
    
	protected function pjActionSyncInvoicePayment($order_uuid, $status, $payment_method=null, $txn_id=null)
	{
		if (!pjAppController::isInvoicePluginAvailable() || !class_exists('pjInvoiceModel')) {
			return;
		}
		if (empty($order_uuid) || !in_array($status, array('paid', 'cancelled'))) {
			return;
		}

		$data = array(
			'status' => $status,
			'processed_on' => ':NOW()'
		);
		if ($payment_method !== null) {
			$data['payment_method'] = $payment_method;
		}
		if ($txn_id !== null && $txn_id !== '') {
			$data['txn_id'] = $txn_id;
		}

		pjInvoiceModel::factory()
			->where('order_id', $order_uuid)
			->modifyAll($data);
	}

	protected function pjActionGetPaymentReturnUrl()
	{
		$fallback = !empty($this->option_arr['o_install_url']) ? $this->option_arr['o_install_url'] : PJ_INSTALL_URL;
		return pjPayments::normalizeReturnUrl(isset($this->option_arr['o_thankyou_page']) ? $this->option_arr['o_thankyou_page'] : null, $fallback);
	}

	public function pjActionConfirm()
	{
	    $this->setAjax(true);
	    
	    if (pjObject::getPlugin('pjPayments') === NULL)
	    {
	        $this->log('pjPayments plugin not installed');
	        exit;
	    }
	    
	    $pjPayments = new pjPayments();
	    $post = $this->_post->raw();
	    $get = $this->_get->raw();
	    $request = array();
	    if(isset($get['payment_method']))
	    {
	        $request = $get;
	    }
	    if(isset($post['payment_method']))
	    {
	        $request = $post;
	    }
	    if($pjPlugin = $pjPayments->getPaymentPlugin($request))
	    {
	        if($uuid = $this->requestAction(array('controller' => $pjPlugin, 'action' => 'pjActionGetCustom', 'params' => $request), array('return')))
	        {
	            $pjOrderModel = pjOrderModel::factory();
	            $order_arr = $pjOrderModel
					->select(sprintf("t1.*,
						AES_DECRYPT(t1.cc_type, '%1\$s') AS `cc_type`,
						AES_DECRYPT(t1.cc_num, '%1\$s') AS `cc_num`,
						AES_DECRYPT(t1.cc_exp_month, '%1\$s') AS `cc_exp_month`,
						AES_DECRYPT(t1.cc_exp_year, '%1\$s') AS `cc_exp_year`,
						AES_DECRYPT(t1.cc_code, '%1\$s') AS `cc_code`,
						t2.content AS b_country, t3.content AS s_country,
						t4.email, t4.client_name, t4.phone, t4.url, AES_DECRYPT(t4.password, '%1\$s') AS `password`", PJ_SALT))
					->join('pjMultiLang', "t2.model='pjBaseCountry' AND t2.foreign_id=t1.b_country_id AND t2.locale=t1.locale_id AND t2.field='name'", 'left outer')
					->join('pjMultiLang', "t3.model='pjBaseCountry' AND t3.foreign_id=t1.s_country_id AND t3.locale=t1.locale_id AND t3.field='name'", 'left outer')
					->join('pjClient', 't4.id=t1.client_id', 'left outer')
					->where('t1.uuid', $uuid)
					->limit(1)
					->findAll()
					->getDataIndex(0);
	        	if (!empty($order_arr))
	            {
	                $params = array(
	                    'request'		=> $request,
	                    'payment_method' => $request['payment_method'],
	                    'foreign_id'	 => $this->getForeignId(),
	                    'amount'		 => $order_arr['total'],
	                    'txn_id'		 => $order_arr['txn_id'],
	                    'order_id'	   => $order_arr['id'],
	                    'cancel_hash'	=> sha1($order_arr['uuid'].strtotime($order_arr['created']).PJ_SALT),
	                    'key'			=> md5($this->option_arr['private_key'] . PJ_SALT)
	                );
	                $response = $this->requestAction(array('controller' => $pjPlugin, 'action' => 'pjActionConfirm', 'params' => $params), array('return'));
	                if($response['status'] == 'OK')
	                {
	                    $this->log("Payments | {$pjPlugin} plugin<br>Order was confirmed. UUID: {$uuid}");
	                    
	                    $pjOrderModel->reset()
		                    ->setAttributes(array('id' => $order_arr['id']))
		                    ->modify(array('status' => 'completed', 'txn_id' => @$response['txn_id'], 'processed_on' => ':NOW()'));
						$this->pjActionSyncInvoicePayment($order_arr['uuid'], 'paid', isset($request['payment_method']) ? $request['payment_method'] : null, @$response['txn_id']);
	                    
	                    $order_arr['has_digital'] = pjAppController::pjActionCheckDigital($order_arr['id']);
	                	$locale_id = $this->getLocaleId();
	                    if((int) $order_arr['locale_id'])
	                    {
	                        $locale_id = $order_arr['locale_id'];
	                    }
						pjFront::pjActionConfirmSend($this->option_arr, $order_arr, PJ_SALT, 'payment', $locale_id);
	                    
	                    echo $this->pjActionGetPaymentReturnUrl();
	                    exit;
	                }elseif($response['status'] == 'CANCEL'){
	                    $this->log("Payments | {$pjPlugin} plugin<br>Payment was cancelled. UUID: {$uuid}");
	                    
	                    $pjOrderModel->reset()
		                    ->setAttributes(array('id' => $order_arr['id']))
		                    ->modify(array('status' => 'cancelled', 'processed_on' => ':NOW()'));
						$this->pjActionSyncInvoicePayment($order_arr['uuid'], 'cancelled', isset($request['payment_method']) ? $request['payment_method'] : null, @$response['txn_id']);
	                    
	                    $order_arr['has_digital'] = pjAppController::pjActionCheckDigital($order_arr['id']);
	                	$locale_id = $this->getLocaleId();
	                    if((int) $order_arr['locale_id'])
	                    {
	                        $locale_id = $order_arr['locale_id'];
	                    }
						pjFront::pjActionConfirmSend($this->option_arr, $order_arr, PJ_SALT, 'cancel', $locale_id);

	                    // Honour the Cancel URL configured for this payment method
	                    // (stored as the option's failure_url). Falls back to the
	                    // thank-you / return page when it is left blank.
	                    $cancelUrl = $this->pjActionGetPaymentReturnUrl();
	                    if (class_exists('pjPaymentOptionModel') && !empty($order_arr['payment_method']))
	                    {
	                        $payOpts = pjPaymentOptionModel::factory()->getOptions($this->getForeignId(), $order_arr['payment_method']);
	                        if (!empty($payOpts['failure_url']))
	                        {
	                            $cancelUrl = pjPayments::normalizeReturnUrl($payOpts['failure_url'], $cancelUrl);
	                        }
	                    }
	                    echo $cancelUrl;
	                    exit;
	                }else{
	                    $this->log("Payments | {$pjPlugin} plugin<br>Order confirmation was failed. UUID: {$uuid}");
	                }
	                
	                if(isset($response['redirect']) && $response['redirect'] == true)
	                {
	                    echo $this->pjActionGetPaymentReturnUrl();
	                    exit;
	                }
	            }else{
	                $this->log("Payments | {$pjPlugin} plugin<br>Reservation with UUID {$uuid} not found.");
	            }
				echo $this->pjActionGetPaymentReturnUrl();
				exit;
	        }
	    }
	    
	    echo $this->pjActionGetPaymentReturnUrl();
	    exit;
	}
    public function pjActionLoadInvoice()
    {
        ob_start();
        header("Content-type: text/javascript");

        $_SESSION[$this->defaultLangMenu] = 'show';

        if ($this->_get->check('locale') && $this->_get->toInt('locale') > 0) {
            $this->loadSetFields(true);
        }

        if ($this->_get->check('category_id')) {
            // Get the raw input like "2,4,5,6,7"
            $category_ids_raw = $this->_get->raw('category_id');

            // Convert to array and sanitize
            $category_ids = array_filter(array_map('intval', explode(',', $category_ids_raw)));

            // Optional: Store as array or string depending on usage
            $_SESSION[$this->defaultCategoryMenu] = $category_ids;
        } else {
            $_SESSION[$this->defaultCategoryMenu] = [];
        }

        // Popup teksten laden (zelfde fallback als pjActionLoad)
        $popup_all = pjBaseMultiLangModel::factory()->getMultiLang($this->getForeignId(), 'pjPaymentNotification');
        $locale_id = $this->getLocaleId();
        $default_locale_id = null;
        $locale_rows = pjBaseLocaleModel::factory()->where('is_default', 1)->limit(1)->findAll()->getData();
        if (!empty($locale_rows)) $default_locale_id = $locale_rows[0]['id'];
        $pi = isset($popup_all[$locale_id]) ? $popup_all[$locale_id] : array();
        if ($default_locale_id && $default_locale_id != $locale_id && isset($popup_all[$default_locale_id])) {
            foreach ($popup_all[$default_locale_id] as $k => $v) {
                if (empty($pi[$k])) $pi[$k] = $v;
            }
        }
        $this->set('popup_i18n', $pi);
    }
    public function generatePdf()
    {
        $uuid     = $this->_get->toString('id');
        $order_id = $this->_get->toString('uuid');

        if (empty($uuid) || empty($order_id)) {
            http_response_code(404);
            exit;
        }

        // Load the pjInvoice controller using the same pattern as attachInvoiceIfConfigured.
        if (!class_exists('pjInvoice')) {
            $ctrlPath = PJ_INSTALL_PATH . 'plugins/pjInvoice/controllers/pjInvoice.controller.php';
            if (!is_file($ctrlPath)) {
                http_response_code(500);
                exit;
            }
            require_once $ctrlPath;
        }
        if (!class_exists('pjInvoice')) {
            http_response_code(500);
            exit;
        }

        try {
            $invoiceCtrl = new pjInvoice();
            $pdfBytes = $invoiceCtrl->renderInvoicePdf($uuid, $order_id);
        } catch (Exception $e) {
            http_response_code(500);
            exit;
        }

        if ($pdfBytes === false || $pdfBytes === '') {
            http_response_code(404);
            exit;
        }

        if (ob_get_length()) {
            ob_end_clean();
        }

        $filename = 'invoice_' . $uuid . '.pdf';
        header('Content-Type: application/pdf');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Content-Length: ' . strlen($pdfBytes));
        echo $pdfBytes;
        exit;
    }

}
?>
