* @license PHP License * @package WB * @subpackage content */ /** * Load classes */ WBClass::load('WBContent' , 'WBHtml_JS' , 'WBShop' , 'WBShop_Cart' ); /** * Content component: Shop Cart * * @version 0.3.1 * @package WB * @subpackage content */ class WBContent_Shop_Cart extends WBContent { /** * my parameter list * * - mode can be: "gauge", "display" or "admin" * - action: list, edit, ... * - cart: select individual cart * - tmpl: use alternate template (for gauge mode) * - csvdelimiter: for export * - csvenclosure: for export * - captcha: mode for form captcha * - requiredgroup: authorisation * - usefilter (0) filter lists, display filter form, use form definition * in filter.xml and filter.tmpl to render form. * * @var array */ protected $config = array( 'mode' => 'display', 'action' => 'list', 'status' => '', 'cart' => '__active', 'tmpl' => '', 'goto' => 0, 'csvdelimiter' => ',', 'csvenclosure' => '"', 'captcha' => 'auto', 'requiredgroup' => parent::GROUP_ANON, 'usefilter' => 0 ); /** * cart * @var WBShop_Cart */ private $cart; /** * article * @var WBShop_Article */ private $article; /** * shopping-cart is not cachable * @var */ protected $cachable = false; /** * Current form to change status * @var string */ private $formStatus = ''; /** * run * * Dispatch action config parameter and call method that actually fills * HTML templates. * * @return array parameter list */ public function run() { $this->addConfigAsGlobalVars(); if (is_array($this->config['status'])) { $this->tmpl->addGlobalVar('config_status', implode(',', $this->config['status'])); } if (!$this->isUserInGroup($this->config['requiredgroup'])) { $this->loadTemplates('anon'); return $this->config; } $this->tmpl->addGlobalVar('cart_mode', $this->config['mode']); $this->tmpl->addGlobalVar('cart_uid', $this->user->getId()); $this->article = WBClass::create('WBShop_Article'); $params = array(); if ('admin' == $this->config['mode']) { $params['mode'] = WBShop_Cart::CART_MODE_ADMIN; } $this->cart = WBClass::create('WBShop_Cart', $params); $this->cart->setUser($this->user->getId()); $this->cart->setNamespace($this->req->get('namespace', '')); $this->config['action'] = strtolower($this->config['action']); $nextAction = strtolower($this->req->get('nextaction', 'list')); if (!in_array($nextAction, array('display', 'edit', 'list'))) { $nextAction = 'list'; } switch ($this->config['action']) { case 'create': $this->create(); break; case 'copy': $this->copy(); break; case 'copypublic': $this->copypublic(); break; case 'merge': $this->merge(); break; case 'update': $this->updateItem(); break; case 'delete': $this->delete(); break; case 'import': $this->import(); break; case 'export': $this->export(); return $this->config; break; case 'setattributes': $this->setAttributes(); break; case 'notify': $this->notify(); break; case 'setstatus': if ($this->setStatus()) { return $this->config; } break; case 'additems': $nextAction = 'edit'; $article = $this->req->get('article', ''); if (is_array($article)) { $this->req->set('article', '__list'); $this->addItems($article); } break; case 'edit': case 'add': $nextAction = 'edit'; break; case 'display': $nextAction = 'display'; break; case 'displaypublic': $nextAction = 'displaypublic'; break; case 'list': default: $nextAction = 'list'; break; } switch ($nextAction) { case 'edit': $this->edit(); break; case 'display': $this->display(); break; case 'displaypublic': $this->display(true); break; case 'list': default: $this->listCarts(); break; } return $this->config; } /** * Update items in cart * * Update quantity and comment */ private function updateItem() { $this->load(); $item = $this->req->get('item', ''); if (empty($item)) { $this->req->set('save'); $this->edit(); return; } $quantity = intval($this->req->get('quantity', '-1')); if (0 > $quantity) { $this->req->set('save'); $this->edit(); return; } // $comment = $this->req->get('comment', ''); $comment = NULL; $data = $this->req->export(); if (isset($data['comment'])) { $comment = $data['comment']; } $this->tmpl->addGlobalVar('message', 'item_saved'); $this->cart->set($item, $quantity, $comment); // set attributes $this->cart->setItemAttributes($item, $data); $this->cart->save(); $this->req->set('save'); $this->edit(); } /** * create a new cart */ private function create() { if (!$this->user->getId()) { return; } $this->cart->save(); } /** * Create a new cart * * Copy cart to my own carts. If in admin mode, simply clone cart including owner's id. * Current cart will become new cart */ private function copy() { if (!$this->user->getId()) { return; } $this->load(); // make unique title $fmt = $this->cart->getTitle() . ' (%d)'; $cnt = 2; $title = sprintf($fmt, $cnt); $unique = false; $list = $this->cart->getList(); while (!$unique) { $found = false; foreach ($list as $l) { if ($l['title'] == $title) { $found = true; break; } } $unique = true; if ($found) { $unique = false; ++$cnt; $title = sprintf($fmt, $cnt); } } $params = array(); $uid = $this->user->getId(); if ('admin' == $this->config['mode']) { $params['mode'] = WBShop_Cart::CART_MODE_ADMIN; $uid = $this->cart->getUser(); } $copy = WBClass::create('WBShop_Cart', $params); $copy->setUser($uid); $copy->merge($this->cart); $copy->setTitle($title); $copy->save(); $this->cart = $copy; } /** * Copy public cart * * Copy cart to my own carts. * Current cart will become new cart */ private function copypublic() { if (!$this->user->getId()) { return; } $public = WBClass::create('WBShop_Cart'); /** @var $public WBShop_Cart */ $public->load($this->config['cart'], true); $copy = WBClass::create('WBShop_Cart'); /** @var $copy WBShop_Cart */ $copy->setUser($this->user->getId()); $copy->merge($public); $copy->save(); $this->config['cart'] = $copy->getId(); } /** * Merge one cart with other * * Select other cart and merge with current (loaded). * In case no other cart is selected yet, display list of * selectable carts */ private function merge() { if (!$this->user->getId()) { return; } $this->load(); $other = $this->req->get('other', ''); // try to load other cart if (!empty($other)) { $params = array(); $uid = $this->user->getId(); if ('admin' == $this->config['mode']) { $params['mode'] = WBShop_Cart::CART_MODE_ADMIN; $uid = $this->cart->getUser(); } $cart = WBClass::create('WBShop_Cart', $params); $cart->setUser($uid); $cart->load($other); if ('__new' == $cart->getId() || $thiy->cart->getId() == $cart->getId()) { $other = ''; } } if (empty($other)) { $this->loadTemplates('merge'); $this->tmpl->addGlobalVars($this->cart->get(), 'ORG_'); $status = array('saved', 'active'); $list = $this->cart->getList($status, $this->cart->getId()); $this->tmpl->addRows('cart_entry', $list); return; } // actual merge $this->cart->merge($cart); $this->cart->save(); } /** * Notify * * Simply trigger event */ private function notify() { $event = $this->req->get('event', ''); if (empty($event)) { return; } $this->load(); $this->cart->notify($event); $this->tmpl->addGlobalVar('message', 'cart_notify_' . $event); return; } /** * Set status of cart * * */ private function setStatus() { $this->load(); $new = $this->req->get('statusnew'); if (empty($new)) { return false; } $this->formStatus = $this->cart->getRequiredForm($new); $this->tmpl->addGlobalVar('status_form_name', $this->formStatus); $this->tmpl->addGlobalVar('statusnew', $new); $this->tmpl->addGlobalVar('id', $this->cart->getId()); if (empty($this->formStatus)) { if ($this->cart->setStatus($new)) { $this->tmpl->addGlobalVar('message', 'cart_status'); } return false; } $cart = $this->cart->get(); $this->tmpl->addGlobalVars($cart); // copy default values from user data if ($this->user->isAuthenticated()) { $userData = $this->user->getData(); // name if (isset($cart['customername']) && !empty($cart['customername'])) { $cart['customername'] = $userData['forename'] . ' ' . $userData['surname']; } // e-mail if (isset($cart['customeremailurlid']) && empty($cart['customeremailurlid'])) { $dict = WBClass::create('WBDictionary_URL'); $dict->addWord($userData['email']); $cart['customeremailurlid'] = $dict->getId(); } // generic copy of user data if form element's name startes with "customer" foreach ($cart as $key => &$value) { // only non-empty values if (!empty($value)) { continue; } // those fields are well known if (in_array($key, array('customername', 'customeremailurlid'))) { continue; } // use prefix if (0 != strncmp($key, 'customer', 8)) { continue; } $suffix = substr($key, 8); if (isset($userData[$suffix])) { $value = $userData[$suffix]; } } } $this->processForm('status', $cart); $this->formStatus = ''; return true; } /** * Update Status And Set Attributes * * Update cart after form was filled * * @param patForms $form * @param array $values * @return bool always true */ public function onStatusValid($form, $values) { $this->cart->setAttributes($values); $this->cart->setStatus($this->req->get('statusnew')); $this->cart->save(); return true; } /** * Set Cart's Attributes * */ private function setAttributes() { $this->load(); $this->cart->setAttributes($this->req->export()); $this->cart->save(); $this->tmpl->addGlobalVar('message', 'attributes_saved'); } /** * Edit cart attributes * * Process form "edit" * @param bool */ private function edit() { $this->parseFormTmpl = false; $this->load(); $cart = $this->cart->get(); $this->tmpl->addGlobalVars($cart); // two forms: add item and edit $this->processForm('additem', $cart, 'additemsave'); $this->processForm('edit', $cart); $stat = $this->req->get('status', 'saved'); if ('__new' == $this->config['cart'] && 'active' == $stat ) { $this->cart->setStatus('active'); } $cart = $this->cart->get(); $this->tmpl->addGlobalVars($cart); if ($this->tmpl->exists('item_entry')) { $items = $this->getItems(); $this->tmpl->addRows('item_entry', $items); } $this->parseFormTmpl = true; } private function addItems($items) { $this->load(); if (!is_array($items) || empty($items)) { return; } foreach ($items as $id => $item) { $this->cart->add($id, $item['quantity']); } // update discount if ($this->req->has('discount')) { $this->cart->setDiscount($this->req->get('discount')); } $this->cart->save(); } /** * Add items to cart * * Plainly add items using article-no * * @param patForms $form * @param array $values * @return bool always false */ public function onAdditemValid($form, $values) { // Nothing to add? if (empty($values['article']) || '__list' == $values['article']) { $this->req->set('additemsave'); $this->processForm('additem', $this->cart->get()); return false; } // Quantity $quantity = 1; if (isset($values['quantity'])) { $quantity = $values['quantity']; } // Use namespace $namespace = ''; $atts = array(); if (isset($values['namespace']) && !empty($values['namespace'])) { $namespace = $values['namespace']; if ('__NULL' == $namespace) { $namespace = ''; } // copy attributes from item with same namespace $items = $this->cart->getItems(); foreach ($items as $i) { if ($i['namespace'] == $namespace) { $atts = $i; break; } } } $this->cart->setNamespace($namespace); $this->cart->add($values['article'], $quantity); if (!empty($atts)) { $this->cart->setItemAttributes($values['article'], $atts); } $this->cart->save(); $this->req->set('additemsave'); $this->processForm('additem', $this->cart->get()); return false; } /** * Save cart entry * * Store record in database and add user id for new records. * Return false to avoid loading "editValid" template. Instead, * remove "save" parameter from request and redisplay the form * * @param patForms $form * @param array $values * @return bool always false */ public function onEditValid($form, $values) { if (!isset($values['public'])) { $values['public'] = 0; } if ($this->req->has('title')) { $this->cart->setTitle($values['title']); } if ($this->req->has('note')) { $this->cart->setNote($values['note']); } if ($this->req->has('public')) { $this->cart->setPublic($values['public']); } if ($this->req->has('discount')) { $this->cart->setDiscount($values['discount']); } $this->cart->setAttributes($values); $this->cart->save(); // redisplay form $this->tmpl->addGlobalVars($this->cart->get()); $this->req->set('save'); $this->processForm('edit', $this->cart->get()); $this->tmpl->addGlobalVar('message', 'cart_saved'); return false; } /** * Remova cart * */ private function delete() { if (!$this->user->getId()) { return; } $this->load(); $this->cart->delete(); } /** * List Carts * * List all carts owned by current user. */ private function listCarts() { $this->tmpl->addGlobalVar('id', ''); $tmpl = $this->getSubTmplName('list'); $this->loadTemplates($tmpl); $status = array('saved', 'active'); if (!empty($this->config['status'])) { $status = $this->config['status']; if (!is_array($status)) { $status = explode(',', $status); } } $this->tmpl->addGlobalVar('cart_status', implode(',', $status)); if (WBShop_Cart::CART_MODE_ADMIN == $this->config['mode']) { $pager = array(); $list = $this->cart->browseList($this->config['goto'], $pager, $status); $this->tmpl->addGlobalVars($pager, 'pager_'); } else { $list = $this->cart->getList($status); } $this->tmpl->addGlobalVar('cart_count', count($list)); $this->tmpl->addRows('cart_entry', $list); } /** * Display cart's detailstatus * * Load cart by id (or active one) */ private function display() { $tmpl = $this->getSubTmplName('display'); $this->load(); $this->loadTemplates($tmpl); if ('gauge' == strtolower($this->config['mode'])) { WBHtml_JS::add('WB/Ajax/Shop'); WBHtml_JS::add('WB/Ajax/Shop/Cart'); WBHtml_JS::add(sprintf('WB.Ajax.Shop.Cart.useGauge("%s", "%s");', $this->part, $this->config['tmpl']), WBHtml_JS::AREA_FOOT); } if ($this->tmpl->exists('item_entry')) { $items = $this->getItems(); $this->tmpl->addRows('item_entry', $items); } $this->tmpl->addGlobalVars($this->cart->get()); } /** * Import CSV file as new cart * * Upload CSV file, either in "standard" format (same as export format) or as simple list of * articles and quantities. */ private function import() { $this->loadTemplates('import'); if (!isset($_FILES) || empty($_FILES)) { return; } $error = array(); $import = array(); foreach ($_FILES as $file) { $file['errormsg'] = ''; if ($file['error'] || empty($file['tmp_name']) || (1 > $file['size'])) { $file['errormsg'] = 'upload'; $error[] = $file; continue; } if ('text/csv' != $file['type']) { $file['errormsg'] = 'type'; $error[] = $file; continue; } $imp = $this->importUpload($file); if (empty($imp)) { $file['errormsg'] = 'format'; $error[] = $file; continue; } $import[] = $imp; } if (!empty($error)) { $this->tmpl->addGlobalVar('error_countx', count($error)); $this->tmpl->addRows('error_entry', $error); } if (!empty($import)) { $this->tmpl->addGlobalVar('import_count', count($import)); $this->tmpl->addRows('import_entry', $import); } } /** * Export cart as CSV-table * * Load selected cart. Transform to CSV format and start download * * CAUTION. This method does not return. CSV-Download will be initiated */ private function export() { $this->load(); $file = WBClass::create('WBFile'); /** @var WBFile $file */ $file = $file->tempnam('cart'); $csv = fopen($file->realpath(), 'w'); $data = $this->cart->get(); $head = $this->cart->getConfig()->get('cart/export/cart/head', array('title' => 'name', 'note' => 'note', 'date' => 'date')); fputcsv($csv, array($head['title'], $data['title']), $this->config['csvdelimiter'], $this->config['csvenclosure']); fputcsv($csv, array($head['note'], $data['note']), $this->config['csvdelimiter'], $this->config['csvenclosure']); fputcsv($csv, array($head['date'], gmdate('Y-m-d H:i:s')), $this->config['csvdelimiter'], $this->config['csvenclosure']); fputcsv($csv, array(), $this->config['csvdelimiter'], $this->config['csvenclosure']); $colConf = $this->cart->getConfig()->get('cart/export/cartitem/column', array()); $headConf = $this->cart->getConfig()->get('cart/export/cartitem/head', array()); $cols = array(); $head = array(); foreach ($colConf as $i => $c) { if (!is_array($c) && empty($c['requiredgroup'])) { $cols[] = $c; if (empty($headConf[$i])) { $headConf[$i] = $c; } $head[] = $headConf[$i]; continue; } if (!$this->isUserInGroup($c['requiredgroup'])) { continue; } $cols[] = $c['column']; if (empty($headConf[$i])) { $headConf[$i] = $c['column']; } $head[] = $headConf[$i]; } unset($colConf); unset($headConf); fputcsv($csv, $head, $this->config['csvdelimiter'], $this->config['csvenclosure']); $items = $this->getItems(); foreach ($items as $i) { $row = array(); foreach ($cols as $c) { if (isset($i[$c])) { $row[$c] = $i[$c]; } } fputcsv($csv, $row, $this->config['csvdelimiter'], $this->config['csvenclosure']); } fclose($csv); // switch off compression - otherwise complete download files are loaded to RAM WBParam::set('wb/response/compress', 0); $res = WBClass::create('WBResponse'); /** @var $res WBResponse */ $res->add(file_get_contents($file->realpath())); $file->unlink(); $res->addHeader('Content-Type', 'text/csv'); $res->addHeader('Content-Disposition', 'filename="cart-' . $this->cart->getId() . '.csv"'); $res->send($this->req); exit(0); } /** * load cart object * */ private function load() { $this->tmpl->addGlobalVar('cart_load', $this->config['cart']); if ('__active' == $this->config['cart']) { $this->cart->loadActive(); return ; } $this->cart->load($this->config['cart']); if ($this->config['cart'] != $this->cart->getId()) { $this->tmpl->addGlobalVar('cart_load', 'failed'); } } /** * Fetch and enrich items * * Get items from current cart and enrich them with article data * Auxilliary method. * * @param bool $enrich * @return array $items */ private function getItems($enrich = true) { $items = $this->cart->getItems(); if (!$enrich) { return $items; } $primary = $this->article->getIdentifer(); foreach ($items as &$i) { $this->article->load($i[$primary]); $i = array_merge($this->article->get(), $i); } return $items; } /** * Get template name in sub dir * * @param string $tmpl */ private function getSubTmplName($tmpl) { if (empty($this->config['tmpl'])) { return $tmpl; } return $tmpl .= '/' . $this->config['tmpl']; } /** * receive output * * Display parsed template * * @return string */ public function getString() { return $this->tmpl->getParsedTemplate(); } /** * location of form config * * Return sub directory where form element definitions are located * * @return string folder */ protected function getFormConfigDir() { return 'shop/cart/form'; } /** * Import uploaded CSV file * * Upload poarameter represents on uploaded file, see: $_FILES * Detect format of CSV file and import it, if possible. * * @param array $upload */ private function importUpload($upload) { $csv = fopen($upload['tmp_name'], 'r'); $fmt = $this->detectCSVFormat($csv); if (!$fmt['ok']) { fclose($csv); unlink($upload['tmp_name']); return null; } $this->importUploadCSV($csv, $fmt, $upload); fclose($csv); unlink($upload['tmp_name']); return $upload; } /** * Try to detect CSV file's format * * Detect delimiter, enclosure of CSV file. * Furthermore detects whether the contains standard format (same as export format) * * @param stream $csv * @return array $fmt */ private function detectCSVFormat($csv) { $delimiter = array(';', ',', "\t"); $enclosure = array("'", '"'); $fmt = array( 'ok' => false, 'std' => false, 'del' => '', 'enc' => '' ); foreach ($delimiter as $d) { foreach ($enclosure as $e) { fseek($csv, 0); $row1 = fgetcsv($csv, 0, $d, $e); $row2 = array(); if (feof($csv)) { fseek($csv, 0); } $row2 = fgetcsv($csv, 0, $d, $e); // require two columns if (2 > min(count($row1), count($row2))) { continue; } if ('name' == strtolower($row1[0]) && 'note' == strtolower($row2[0])) { $fmt['std'] = true; } $oe = '"'; if ('"' == $e) { $oe = "'"; } // does enclsure work as expected? foreach ($row1 as $c) { // onclosure does not seem to match, try next if ($c != trim($c, $oe)) { continue 2; } } $fmt['ok'] = true; $fmt['enc'] = $e; $fmt['del'] = $d; break 2; } } fseek($csv, 0); return $fmt; } /** * Import CSV file * * Create new cart, set title and note (for standard CSV-files) and add all the items * * @param stream $csv * @param array $fmt * @param array $upload */ private function importUploadCSV($csv, $fmt, &$upload) { // new cart $cart = WBClass::create('WBShop_Cart'); $cart->setUser($this->user->getId()); // standard format? if ($fmt['std']) { $row = fgetcsv($csv, 0, $fmt['del'], $fmt['enc']); array_map('trim', $row); $cart->setTitle($row[1]); $upload['title'] = $row[1]; $row = fgetcsv($csv, 0, $fmt['del'], $fmt['enc']); array_map('trim', $row); $cart->setNote($row[1]); } // default map: article: column 0, quantity: column 1 $mapFound = false; $map = array( 'article' => 0, 'quantity' => 1 ); $upload['item_count'] = 0; // try to find better map using headline while (!feof($csv)) { $row = fgetcsv($csv, 0, $fmt['del'], $fmt['enc']); if (empty($row)) { continue; } array_map('trim', $row); $row = array_map('strtolower', $row); if (!in_array('article', $row)) { continue; } $map = array_flip($row); $mapFound = true; break; } if (!$mapFound) { fseek($csv, 0); } $upload['item_count'] = $this->importItems2Cart($csv, $fmt, $map, $cart); $cart->save(); $upload['id'] = $cart->getId(); } /** * Import items from CSV file * * Use column map to load article ids from CSV and add articles with quantity and comment * * @param stream $csv * @param array $fmt * @param array $map * @param array $upload */ private function importItems2Cart($csv, $fmt, $map, $cart) { $count = 0; // add items to cart $art = WBClass::create('WBShop_Article'); while (!feof($csv)) { $row = fgetcsv($csv, 0, $fmt['del'], $fmt['enc']); if (empty($row)) { continue; } array_map('trim', $row); $a = $row[$map['article']]; $art->load($a); // invalid article id if ($art->getId() != $a) { continue; } $q = 1; if (isset($map['quantity'])) { $q = intval($row[$map['quantity']]); } $c = ''; if (isset($map['comment'])) { $c = $row[$map['comment']]; } ++$count; $cart->add($a, $q, $c); } return $count; } /** * Fetch list of form elements * * Use different file regardless of actual form name. * * @param string name of form * @return array $elements * @todo see how to implement with cart-filter */ protected function getFormElementList($name) { if ('status' != $name) { // $this->formTmplName = 'snippet'; return parent::getFormElementList($name); } // $this->formTmplName = 'snippet-status-' . $status; $name = $this->formStatus; $list = parent::getFormElementList($name); $this->injectElement4Captcha($list, $this->user->isAuthenticated(), $this->config['captcha']); return $list; } }