CORE-POS/IS4C

View on GitHub
fannie/item/ItemEditorPage.php

Summary

Maintainability
F
6 days
Test Coverage
F
55%
<?php
/*******************************************************************************

    Copyright 2013 Whole Foods Community Co-op

    This file is part of CORE-POS.

    CORE-POS is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    CORE-POS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    in the file license.txt along with IT CORE; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*********************************************************************************/

use COREPOS\Fannie\API\lib\Store;

if (!class_exists('FannieAPI')) {
    include(dirname(__FILE__) . '/../classlib2.0/FannieAPI.php');
}
if (!function_exists('updateAllLanes')) {
    include('laneUpdates_WEFC_Toronto.php');
}

class ItemEditorPage extends FanniePage 
{
    private $mode = 'search';
    private $msgs = '';

    public $description = '[Item Editor] is the primary item editing tool.';
    protected $enable_linea = true;

    public function readinessCheck()
    {
        if ($this->config->get('STORE_MODE') != 'HQ') {
            return true;
        } else {
            if ($this->config->get('STORE_ID') === '') {
                $this->error_msg = 'In HQ Mode store must have an ID!';
                return false;
            } elseif (!is_numeric($this->config->get('STORE_ID'))) {
                $this->error_msg = 'Invalid store ID: ' . $this->config->get('STORE_ID');
                return false;
            } else {
                $this->connection->selectDB($this->config->get('OP_DB'));
                $prep = $this->connection->prepare('
                    SELECT storeID
                    FROM Stores
                    WHERE storeID=?');
                $res = $this->connection->execute($prep, array($this->config->get('STORE_ID')));
                if ($res === false || $this->connection->numRows($res) == 0) {
                    $this->error_msg = 'No record exists for this store';
                    return false;
                }
            }

            return true;
        }
    }

    public function errorContent()
    {
        return '<div class="alert alert-danger">'
            . $this->error_msg . '</div>'
            . '<p><a href="../install/InstallStoresPage.php">Adjust Store Settings</a></p>';
    }

    private $config_converted = false;

    private function getConfiguredModules()
    {
        $FANNIE_PRODUCT_MODULES = $this->config->get('PRODUCT_MODULES');
        if (!is_array($FANNIE_PRODUCT_MODULES)) {
            $FANNIE_PRODUCT_MODULES = array('BaseItemModule' => array('seq'=>0, 'show'=>1, 'expand'=>1));
        }

        if ($this->config_converted === false) {
            /*
              Convert old settings to new format.
            */
            $legacy_indexes = array();
            $replacement_values = array();
            foreach ($FANNIE_PRODUCT_MODULES as $id => $m) {
                if (preg_match('/^\d+$/', $id)) {
                    // old setting. convert to new.
                    $legacy_indexes[] = $id;
                    $replacement_values[$m] = array(
                        'seq' => $id,
                        'show' => 1,
                        'expand' => 1,
                    );
                }
            }
            foreach ($legacy_indexes as $index) {
                unset($FANNIE_PRODUCT_MODULES[$index]);
            }
            foreach ($replacement_values as $name => $params) {
                $FANNIE_PRODUCT_MODULES[$name] = $params;
            }

            // verify modules exist
            foreach (array_keys($FANNIE_PRODUCT_MODULES) as $name) {
                if (class_exists($name)) {
                    continue;
                }
                $file = dirname(__FILE__) . '/modules/' . $name . '.php';
                if (!file_exists($file)) {
                    unset($FANNIE_PRODUCT_MODULES[$name]);
                } else {
                    include_once($file);
                    if (!class_exists($name)) {
                        unset($FANNIE_PRODUCT_MODULES[$name]);
                    }
                }
            }

            $this->config_converted = true;
        }

        return $FANNIE_PRODUCT_MODULES;
    }

    function preprocess()
    {
        $mods = $this->getConfiguredModules();

        $this->title = _('Fannie') . ' - ' . _('Item Maintenance');
        $this->header = '';//_('Item Maintenance');

        if (FormLib::get_form_value('searchupc') !== '') {
            $this->mode = 'searchResults';
        }
        if (FormLib::get('superFilter', false) !== false) {
            $this->session->__superFilter = FormLib::get('superFilter');
        }

        if (FormLib::get_form_value('createBtn') !== ''){
            $this->msgs = $this->saveItem(true);
        } else if (FormLib::get_form_value('updateBtn') !== '') {
            $this->msgs = $this->saveItem(false);
        }

        return true;
    }

    function body_content()
    {
        switch($this->mode){
            case 'searchResults':
                return $this->searchResults();
            case 'search':
            default:
                return $this->searchForm();
        }
    }

    private function searchForm()
    {
        $FANNIE_URL = $this->config->get('URL');
        $ret = '';
        $vars = array(
            'enter' => _('Enter'),
            'orName' => _('or product name here'),
            'advancedSearch' => _('Advanced Search'),
            'openPLU' => _('Find Open PLU Range'),
            'self' => filter_input(INPUT_SERVER, 'PHP_SELF'),
            'msgs' => '',
        );
        if (!empty($this->msgs)) {
            $vars['msgs'] = '<blockquote style="border:solid 1px black;">'
                    . $this->msgs
                    . '</blockquote>';
        }
        $model = new SuperDeptNamesModel($this->connection);
        $sOpts = $model->toOptions($this->session->__superFilter);
        if ($this->session->__superFilter !== '') {
            $this->addOnloadCommand("EXTRA_AUTO_COMPLETE_PARAMS = { superID: " . $this->session->__superFilter . " };");
        }
        $ret = <<<HTML
{$vars['msgs']}
<form action="{$vars['self']}" name="searchform" method=get>
    <div class="container-fluid">
        <div class="row form-group form-inline">
            <input name=searchupc type=text id=upc class="form-control" /> 
            {$vars['enter']}
            <select name="ntype" id="searchselect" class="form-control">
                <option>UPC</option>
                <option>SKU</option>
                <option>Brand Prefix</option>
                <option>Batch ID</option>
                <option>Vendor ID</option>
                <option>Product Physical Location</option>
                <option>Owner Number</option>
            </select> 
            {$vars['orName']}
        </div>
    </div>
    <p>
        <a name=searchBtn id="searchbtn" type=submit class="btn btn-default">Go</a>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <label>
            <input type="checkbox" name="inUse" value="1" />
            Include items that are not inUse
        </label>
    </p>
    <p class="form-inline">
        <label>Filter</label>:
        <select class="form-control input-sm" name="superFilter"
            onchange="if (this.value == '') { EXTRA_AUTO_COMPLETE_PARAMS = {}; } else { EXTRA_AUTO_COMPLETE_PARAMS = { superID: this.value }; }">
            <option value="">No Filter</option>
            {$sOpts}
        </select>
    </p>
</form>
<p><a href="AdvancedItemSearch.php">{$vars['advancedSearch']}</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="PluRangePage.php">{$vars['openPLU']}</a>
</p>
HTML;
        
        $this->addScript('autocomplete.js');
        $wsUrl = $FANNIE_URL . 'ws/';
        $this->addOnloadCommand("bindAutoComplete('#upc', '$wsUrl', 'item');\n");
        $this->addOnloadCommand('$(\'#upc\').focus();');

        $this->addScript($FANNIE_URL . 'src/javascript/fancybox/jquery.fancybox-1.3.4.js?v=1');
        $this->addCssFile($FANNIE_URL . 'src/javascript/fancybox/jquery.fancybox-1.3.4.css');
        $this->addOnloadCommand('$(\'.fancyboxLink\').fancybox({\'width\':\'85%;\'});');

        // bind scanner to UPC field
        $this->addOnloadCommand("enableLinea('#upc');\n");

        return $ret;
    }

    private function searchQuery($upc, $numType, $inUseFlag, $store_id)
    {
        $query = '';
        $args = array();
        if (is_numeric($upc)) {
            switch($numType) {
                case 'SKU':
                    $query = "SELECT p.*,n.vendorName AS distributor,p.brand AS manufacturer 
                        FROM products as p inner join 
                        vendorItems as v ON p.upc=v.upc 
                        left join vendors AS n ON p.default_vendor_id=n.vendorID
                        WHERE v.sku LIKE ? ";
                    $args[] = '%'.$upc;
                    break;
                case 'Brand Prefix':
                    $query = "SELECT p.*,n.vendorName AS distributor,p.brand AS manufacturer 
                        FROM products as p 
                        left join vendors AS n ON p.default_vendor_id=n.vendorID
                        WHERE p.upc like ? ";
                    $args[] = '%'.$upc.'%';
                    break;
                case 'UPC':
                default:
                    $upc = BarcodeLib::padUPC($upc);
                    $query = "SELECT p.*,n.vendorName AS distributor,p.brand AS manufacturer 
                        FROM products as p
                        left join vendors AS n ON p.default_vendor_id=n.vendorID
                        WHERE p.upc = ? ";
                    $args[] = $upc;
                    $inUseFlag = 1; // exact matches should be allowed
                    break;
            }
        } else {
            $superFilter = isset($this->session->__superFilter) && $this->session->__superFilter !== '';
            $superJoin = $superFilter ? ' left join superdepts AS s ON p.department=s.dept_ID ' : '';
            $query = "SELECT p.*,n.vendorName AS distributor,p.brand AS manufacturer 
                FROM products AS p
                    left join vendors AS n ON p.default_vendor_id=n.vendorID
                    {$superJoin}
                WHERE (description LIKE ? 
                    OR n.vendorName LIKE ?
                    OR p.brand LIKE ?)";
            $args[] = '%'.$upc.'%';
            $args[] = '%'.$upc.'%';
            $args[] = '%'.$upc.'%';
            if ($superFilter) {
                $query .= " AND s.superID=? ";
                $args[] = $this->session->__superFilter;
            }
        }
        if (!$inUseFlag) {
            $query .= ' AND inUse=1 ';
        }
        if ($this->config->get('STORE_MODE') == 'HQ') {
            $query .= " AND p.store_id=? ";
            $args[] = $store_id;
        }

        return array($query, $args);
    }

    private function newResultToUpc($dbc, $upc, $numType)
    {
        if (strlen($upc) > 13) {
            $upc = ltrim($upc, '0');
        }
        $actualUPC = BarcodeLib::padUPC($upc);
        $this->mode = 'new'; // mode drives appropriate help text
        if ($numType == 'SKU') {
            $prep = $dbc->prepare('
                SELECT upc
                FROM vendorItems
                WHERE sku LIKE ?
            ');
            $skuR = $dbc->execute($prep, array('%'.$upc));
            if ($skuR && $dbc->numRows($skuR)) {
                $skuW = $dbc->fetchRow($skuR);
                $actualUPC = BarcodeLib::padUPC($skuW['upc']);
            }
        }

        return $actualUPC;
    }

    private function searchResults()
    {
        $authorized = false;
        if (FannieAuth::validateUserQuiet('admin')) {
            $authorized = true;
        }
        $dbc = $this->connection;
        $dbc->selectDB($this->config->get('OP_DB'));
        $upc = trim(FormLib::get_form_value('searchupc'));
        $numType = FormLib::get_form_value('ntype','UPC');
        $inUseFlag = FormLib::get('inUse', false);
        $store_id = $this->config->get('STORE_ID');
        if ($this->config->get('STORE_MODE') == 'HQ') {
            $store_id = COREPOS\Fannie\API\lib\Store::getIdByIp(); 
        }

        $query = "";
        $args = array();
        list($query, $args) = $this->searchQuery($upc, $numType, $inUseFlag, $store_id);
        $query = $dbc->addSelectLimit($query, 500);
        $prep = $dbc->prepare($query);
        $result = $dbc->execute($query,$args);

        /**
          Query somehow failed. Unlikely. Show error and search box again.
        */
        if ($result === false) {
            $this->msgs = '<div class="alert alert-danger">' . _('Error searching for') 
                . ' ' . $upc . '</div>';
            $this->mode = 'search'; // mode drives appropriate help text
            return $this->searchForm();
        }

        if ($this->config->get('COOP_ID') == 'WFC_Duluth') {
            $lnUPCs = array();
            $lnP = $dbc->prepare("SELECT code FROM AlternateCodes");
            $lnR = $dbc->execute($lnP);
            while ($lnW = $dbc->fetchRow($lnR)) {
                $lnUPCs[] = BarcodeLib::padUPC($lnW['code']);
            }
            if (in_array($upc, $lnUPCs)) {
                $this->msgs = '<div class="alert alert-danger"> PLU ' . $upc . ' is being used as a linked PLU.
                    This item cannot exist in POS.</div>';
                return $this->searchForm();
            }

            if (substr(BarcodeLib::padUPC($upc), 0, 1) != '0' && $authorized == false) {
                $this->msgs = '<div class="alert alert-danger">A check digit is incuded in the UPC that was entered. There must be at least one leading zero to be a valid item.</div>';
                return $this->searchForm();
            }

        }

        $num = $dbc->numRows($result);

        /**
          No match for text input. Can't create a new item w/o numeric UPC,
          so show error and search box again.
        */
        if ($num == 0 && !is_numeric($upc)) {
            $this->msgs = '<div class="alert alert-danger">' . _('No results for') 
                . ' ' . $upc . '</div>';
            $this->mode = 'search'; // mode drives appropriate help text
            return $this->searchForm();
        }

        /**
          List multiple results
        */
        if ($num > 1) {
            $items = array();
            while ($row = $dbc->fetchRow($result)) {
                $items[$row['upc']] = $row;
            }
            $this->mode = 'many'; // mode drives appropriate help text
            return $this->multipleResults($items);
        }

        /**
          Only remaining possibility is a new item or
          editing an existing item
        */
        $actualUPC = '';
        if ($num == 0) {
            $actualUPC = $this->newResultToUpc($dbc, $upc, $numType);
        } else {
            $row = $dbc->fetchRow($result);
            $actualUPC = $row['upc'];
            $this->mode = 'edit'; // mode drives appropriate help text
        }

        return $this->editForm($actualUPC, $this->mode === 'new' ? true : false);
    }

    private function multipleResults($results)
    {
        $FANNIE_URL = $this->config->get('URL');
        $ret = '<table id="itemSearchResults" class="tablesorter">';
        $ret .= '<thead><tr>
            <th>UPC</th><th>Description</th><th>Brand</th><th>Reg. Price</th><th>Sale Price</th><th>Modified</th>
            </tr></thead>';
        $ret .= '<tbody>';
        foreach ($results as $upc => $data) {
            $ret .= sprintf('<tr>
                            <td><a href="%s?searchupc=%s">%s</a></td>
                            <td>%s</td>
                            <td>%s</td>
                            <td>%.2f</td>
                            <td>%s</td>
                            <td>%s</td>
                            </tr>',
                            filter_input(INPUT_SERVER, 'PHP_SELF'),
                            $upc, $upc, 
                            $data['description'],
                            $data['manufacturer'],
                            $data['normal_price'],
                            ($data['discounttype'] > 0 ? $data['special_price'] : 'n/a'),
                            $data['modified']
            );
        }
        $ret .= '</tbody></table>';

        $this->addCssFile($FANNIE_URL . 'src/javascript/tablesorter/themes/blue/style.css');
        $this->addScript($FANNIE_URL . 'src/javascript/tablesorter/jquery.tablesorter.min.js');
        $this->addOnloadCommand('$(\'#itemSearchResults\').tablesorter();');

        return $ret;
    }

    public static function sortModules($a, $b)
    {
        if ($a['seq'] < $b['seq']) {
            return -1;
        } else if ($a['seq'] > $b['seq']) {
            return 1;
        } else {
            return 0;
        }
    }

    private function userCanEdit($upc, $isNew)
    {
        $authorized = false;
        if (FannieAuth::validateUserQuiet('pricechange') || FannieAuth::validateUserQuiet('audited_pricechange')) {
            $authorized = true;
        } elseif (($range=FannieAuth::validateUserLimited('pricechange')) !== false) {
            /**
              Check if user is authorized to edit a subset of items
            */
            if ($isNew) {
                $authorized = true;
            } else {
                $this->connection->selectDB($this->config->OP_DB);
                $prep = $this->connection->prepare("
                    SELECT upc
                    FROM products AS p
                        INNER JOIN superdepts AS s ON p.department=s.dept_ID
                    WHERE p.upc=?
                        AND s.superID BETWEEN ? AND ?");
                $args = array(BarcodeLib::padUPC($upc), $range[0], $range[1]);
                $result = $this->connection->execute($prep, $args);
                if ($result && $this->connection->numRows($result) > 0) {
                    $authorized = true;
                }
            }
        //} elseif (substr($upc, 0, 3) == '002' && $this->config->get('COOP_ID') == 'WFC_Duluth') {
         //   $authorized = true;
        }

        return $authorized;
    }

    private function editorLinksArea($upc, $isNew, $authorized)
    {
        $url = $this->config->get('URL');
        $self = filter_input(INPUT_SERVER, 'PHP_SELF');
        $ret = '<p>';
        if (!$authorized) {
            $ret .= sprintf('<a class="btn btn-danger"
                        href="%sauth/ui/loginform.php?redirect=%s?searchupc=%s">Login
                        to edit</a>', $url, $self, $upc);
            $this->addOnloadCommand("\$(':input').prop('disabled', true).prop('title','Login to edit');\n");
        } elseif ($isNew) {
            $ret .= '<button type="submit" name="createBtn" value="1"
                        class="btn btn-default">Create Item</button>';
        } else {
            $ret .= '<button type="submit" name="updateBtn" value="1"
                        class="btn btn-default">Update Item</button>';
        }
        $ret .= '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            <a class="btn btn-default btn-sm" href="' . $self . '">Back</a>';
        $this->addScript($url . 'src/javascript/fancybox/jquery.fancybox-1.3.4.js?v=1');
        $this->addCssFile($url . 'src/javascript/fancybox/jquery.fancybox-1.3.4.css');
        if (!$isNew) {
            $ret .= <<<HTML
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="DeleteItemPage.php?id={$upc}" class="btn btn-danger btn-sm">Delete this item</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<label class="badge">History</label> <span class="btn-group">
<a class="btn btn-default btn-sm iframe fancyboxLink" 
    href="{$url}reports/PriceHistory/?upc={$upc}" title="Price History">Price</a>
<a class="btn btn-default btn-sm iframe fancyboxLink" 
    href="{$url}reports/CostHistory/?upc={$upc}" title="Cost History">Cost</a>
<a class="btn btn-default btn-sm iframe fancyboxLink" 
    href="{$url}reports/RecentSales/?upc={$upc}" title="Sales History">Sales</a>
<a class="btn btn-default btn-sm iframe fancyboxLink" 
    href="{$url}reports/ItemOrderHistory/ItemOrderHistoryReport.php?upc={$upc}" 
    title="Order History">Orders</a>
<a class="btn btn-default btn-sm iframe fancyboxLink" 
    href="{$url}reports/ItemBatches/ItemBatchesReport.php?upc={$upc}" 
    title="Batch History">Batches</a>
<a class="btn btn-default btn-sm iframe fancyboxLink"
    href="{$url}batches/batchhistory/BatchHistoryPage.php?upc={$upc}&nomenu=1"
    title="Extended Histocal Batch Data">Batch Historical</a>
<a class="btn btn-default btn-sm iframe fancyboxLink"
    href="{$url}reports/DDD/SingleItemDDDReport.php?upc=$upc">Loss</a>
</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a class="btn btn-default btn-sm iframe fancyboxLink" 
    href="{$url}item/addShelfTag.php?upc={$upc}" title="Queue a tag for this item">Shelf Tag</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a class="btn btn-default btn-sm" 
    href="{$url}item/CloneItemPage.php?id={$upc}" 
    title="Create a duplicate item with a different UPC">Clone Item</a>
HTML;
        }
        $ret .= '</p>';
        
        return $ret;
    }
    
    private function editForm($upc,$isNew)
    {
        $FANNIE_PRODUCT_MODULES = $this->getConfiguredModules();
        $FANNIE_URL = $this->config->get('URL');
        $shown = array();

        $this->addScript('autocomplete.js');
        $this->addScript($FANNIE_URL . 'src/javascript/chosen/chosen.jquery.min.js');
        $this->addCssFile($FANNIE_URL . 'src/javascript/chosen/bootstrap-chosen.css');
        $wsUrl = $FANNIE_URL . 'ws/';

        $authorized = $this->userCanEdit($upc, $isNew);

        // remove action so form cannot be submitted by pressing enter
        $ret = '<form id="item-editor-form" action="' . ($authorized ? filter_input(INPUT_SERVER, 'PHP_SELF') : '') . '" 
            enctype="multipart/form-data" method="post">';
        $ret .= '<div class="container"><div id="alert-area">';

        uasort($FANNIE_PRODUCT_MODULES, array('ItemEditorPage', 'sortModules'));
        $count = 0;
        $mod_js = '';
        $current_width = 100;
        foreach ($FANNIE_PRODUCT_MODULES as $class => $params) {
            $mod = new $class();
            if ($current_width + $mod->width() > 100) {
                $ret .= '</div><div class="row">';
                $current_width = 0;
                $count++;
            }
            switch ($mod->width()) {
                case \COREPOS\Fannie\API\item\ItemModule::META_WIDTH_THIRD:
                    $ret .= '<div class="col-sm-4">' . "\n";
                    break;
                case \COREPOS\Fannie\API\item\ItemModule::META_WIDTH_HALF:
                    $ret .= '<div class="col-sm-6">' . "\n";
                    break;
                case \COREPOS\Fannie\API\item\ItemModule::META_WIDTH_FULL:
                default:
                    $ret .= '<div class="col-sm-12">' . "\n";
                    break;
            }
            $ret .= $mod->ShowEditForm($upc, $params['show'], $params['expand']);
            $ret .= '</div>' . "\n";
            $shown[$class] = true;
            $mod_js .= $mod->getFormJavascript($upc);

            if ($count == 1 && $current_width == 0) { // show links after first mod
                $ret .= $this->editorLinksArea($upc, $isNew, $authorized);
            }

            $current_width += $mod->width();
        }
        $ret .= '</div>'; // close last row
        $ret .= '</div>'; // close fluid-container

        if (isset($shown['BaseItemModule'])) {
            $this->add_onload_command("bindAutoComplete('.brand-field', '$wsUrl', 'brand');\n");
            $this->add_onload_command("bindAutoComplete('input.vendor_field', '$wsUrl', 'vendor');\n");
            $this->add_onload_command("bindAutoComplete('.unit-of-measure', '$wsUrl', 'unit');\n");
            $this->add_onload_command("\$('.unit-of-measure').autocomplete('option', 'minLength', 1);\n");
            $this->add_onload_command("baseItem.addVendorDialog();\n");
            if ($this->config->get('STORE_MODE') == 'HQ') {
                $this->addOnloadCommand("\$('#item-editor-form').submit(baseItem.syncStoreTabs);\n");
                $this->addOnloadCommand("\$('.syncable-input').change(baseItem.syncStoreTabs);\n");
                $this->addOnloadCommand("\$('.syncable-checkbox').change(baseItem.syncStoreTabs);\n");
                $this->addOnloadCommand("baseItem.markUnSynced();\n");
            }
        }

        if (isset($shown['ItemMarginModule'])) {
            $this->add_onload_command('$(\'.price-input\').change(updateMarginMod)');
            $this->add_onload_command('$(\'.cost-input\').change(updateMarginMod)');
        }

        if (isset($shown['ProdUserModule'])) {
            $this->add_onload_command("bindAutoComplete('#lf_brand', '$wsUrl', 'long_brand');\n");
        }

        if (isset($shown['LikeCodeModule'])) {
            $this->add_onload_command("addLcDialog();\n");
        }
        /**
          Chosen initializes incorrectly if the <select> is not displayed
          Reapplying each time a new Bootstrap tab is shown fixes this
        */
        $this->add_onload_command('$(\'.chosen-select:visible\').chosen();');
        $this->add_onload_command('$(\'#store-tabs a\').on(\'shown.bs.tab\', function(){$(\'.chosen-select:visible\').chosen();});');

        $ret .= '</form>';

        if ($mod_js != '') {
            $ret .= '<script type="text/javascript">' . "\n";
            $ret .= $mod_js;
            $ret .= "\n</script>\n";
        }

        $this->add_onload_command('$(\'.fancyboxLink\').fancybox({\'width\':\'85%;\',\'titlePosition\':\'inside\'});');
        if ($this->mode == 'new') {
            $this->add_onload_command('$(\'.descript-input:visible:first\').focus();');
        }
        
        return $ret;
    }

    private function saveItem($isNew)
    {
        $FANNIE_PRODUCT_MODULES = $this->getConfiguredModules();
        $FANNIE_URL = $this->config->get('URL');

        $upc = FormLib::get_form_value('upc','');
        if ($upc === '' || !is_numeric($upc)) {
            return '<span style="color:red;">Error: bad UPC:</span> '.$upc;
        }
        $upc = BarcodeLib::padUPC($upc);

        $authorized = $this->userCanEdit($upc, $isNew);
        $audited = FannieAuth::validateUserQuiet('audited_pricechange') ? true : false;
        if ($authorized !== true) {
            // not authorized to make edits
            return '<span style="color:red;">Error: Log in to edit</span>';
        }

        uasort($FANNIE_PRODUCT_MODULES, array('ItemEditorPage', 'sortModules'));
        $this->saveModules($FANNIE_PRODUCT_MODULES, $upc);

        /* push updates to the lanes */
        $dbc = $this->connection;
        $dbc->selectDB($this->config->get('OP_DB'));
        $FANNIE_COOP_ID = $this->config->get('COOP_ID');
        if (isset($FANNIE_COOP_ID) && $FANNIE_COOP_ID == 'WEFC_Toronto') {
            updateAllLanes($upc, array('products','productUser'));
        } elseif (isset($FANNIE_COOP_ID) && $FANNIE_COOP_ID == 'WFC_Duluth') {
            $queue = new COREPOS\Fannie\API\jobs\QueueManager();
            $queue->add(array(
                'class' => 'COREPOS\\Fannie\\API\\jobs\\SyncItem',
                'data' => array(
                    'upc' => $upc,
                ),
            ));
        } else {
            COREPOS\Fannie\API\data\ItemSync::sync($upc);
        }

        if ($audited) {
            $likecode = FormLib::get('likeCode', -1);
            $no_update = FormLib::get('LikeCodeNoUpdate', false);
            if ($likecode != -1 && !$no_update) {
                \COREPOS\Fannie\API\lib\AuditLib::itemUpdate($upc, $likecode);
            } else {
                \COREPOS\Fannie\API\lib\AuditLib::itemUpdate($upc);
            }
        }

        return $this->modulesResult($FANNIE_PRODUCT_MODULES, $upc);
    }

    private function saveModules($mods, $upc)
    {
        $form = new \COREPOS\common\mvc\FormValueContainer();
        $this->connection->startTransaction();
        foreach ($mods as $class => $params) {
            $mod = new $class();
            $mod->setConnection($this->connection);
            $mod->setConfig($this->config);
            $mod->setForm($form);
            $mod->SaveFormData($upc);
        }
        $this->connection->commitTransaction();
    }

    private function modulesResult($mods, $upc)
    {
        $ret = "<table class=\"table\">";
        foreach ($mods as $class => $params) {
            $mod = new $class();
            $rows = $mod->summaryRows($upc);
            foreach ($rows as $row) {
                $ret .= '<tr>' . $row . '</tr>';
            }
        }
        $ret .= '</table>';

        return $ret;
    }

    public function helpContent()
    {
        $ret = '<p>This tool is for adding or editing an item</p>';
        if ($this->mode == 'search') {
            $ret .= '<p>
                To create a new item, simply enter the UPC. To search for an
                existing item, enter the UPC or part of the description. You
                can also search by vendor SKU or manufacturer UPC prefix by
                changing the UPC dropdown.
                </p>';
        } elseif ($this->mode == 'many') {
            $ret .= '<p>Multiple results found. Click the UPC to edit that item
                or use the browser\'s back button to try a different search</p>';
        } elseif ($this->mode == 'new') {
            $ret .= '<p>
                Creating a <strong>new</strong> item. Minimum required fields are
                description, price, and department (dept). Tax, foodstamp, and scale
                settings will be automatically assigned based on the depatment\'s
                defaults.
                </p>';
        } elseif ($this->mode == 'edit') {
            $ret .= '<p>
                Editing an <strong>existing</strong> item. Changes made here will be
                sent to the lanes immediately.
                </p>';
        }

        if ($this->mode == 'new' || $this->mode == 'edit') {
            $ret .= '<ul>
                    <li>Description appears on the lane screen & receipt</li>
                    <li>Price is the current retail price</li>
                    <li>Brand is solely for backend reporting and organization</li>
                    <li>Vendor indicates the default supplier for the item</li>
                    <li>Department (Dept) is the primary item categorization system.</li> 
                    <li>Tax sets sale tax rate</li>
                    <li>Checking FS indicates the item is eligible for purchase with foodstamps</li>
                    <li>Checking Scale indicates the item should be weighed at checkout.</li>
                    <li>Checking QtyFrc causes the lane to prompt the cashier for a quantity
                        when the item is entered</li>
                    <li>Discount controls which type of discounts apply to the item:
                        <ul>
                        <li>Trxn only means the item is only eligible for discounts that apply to
                        the entire transaction such as a member\'s discount</li>
                        <li>Line only means the item is only eligible for percent discount
                        explictly applied by the cashier as they ring in the item.</li>
                        <li>Yes means the item is eligible for both discounts above</li>
                        <li>No means the item is not eligible for either discount above</li>
                        </ul>
                    </li>
                </ul>';
        }

        return $ret;
    }

    public function javascript_content()
    {
        return <<<JAVASCRIPT
$(document).ready(function(){
    alterTable();

    // don't use default keyup on inputs
    $('#upc').keyup(function(e){
        e.preventDefault();
        if (e.keyCode == 13) {
            $('#searchbtn').trigger('click');
        }
    });
    $('#searchselect').keyup(function(e){
        if (e.keyCode == 13) {
            $('#searchbtn').trigger('click');
        }
    });
    $('#searchbtn').on('click', function(){
        var opt = $('#searchselect option:selected').text();
        var val = $('#upc').val();
        switch (opt) {
            case 'UPC':
            case 'SKU':
            case 'Brand Prefix':
                document.forms['searchform'].submit();
                break;
            case 'Batch ID':
                window.location = '../batches/newbatch/EditBatchPage.php?id='+val;
                break;
            case 'Vendor ID':
                window.location = 'vendors/VendorIndexPage.php?vid='+val;
                break;
            case 'Product Physical Location':
                window.location = 'ProdLocationEditor.php?store_id=&upc='+val+'&batchCheck=&searchupc=Update+Locations+by+UPC';
                break;
            case 'Owner Number':
                window.location = '../mem/MemRedirect.php?id='+val;
                break;
        }
    });
});
$(window).on("resize", function(event){
    alterTable();
});
function alterTable()
{
    var windowWidth = $(window).width();
    if (windowWidth < 768) {
        $('table').find('td').each(function(){
            var html = $(this).html();
            $(this).replaceWith('<div class="form-inline">'+html+'</div>');
        });
        $('table').find('th').each(function(){
            var html = $(this).html();
            $(this).replaceWith('<div class="th-alt"><strong>'+html+'</strong></div>');
        });
    } else {
        $('table').find('div.form-inline').each(function(){
            var html = $(this).html();
            $(this).replaceWith('<td class="form-inline">'+html+'</td>');
        });
        $('table').find('div.th-alt').each(function(){
            var html = $(this).html();
            $(this).replaceWith('<th>'+html+'</th>');
        });
   } 
}
JAVASCRIPT;
    }
    
    public function unitTest($phpunit)
    {
        $this->error_msg = '';
        $upc = '0000000004011';
        $phpunit->assertNotEquals(0, strlen($this->errorContent()));
        $phpunit->assertNotEquals(0, strlen($this->searchForm()));
        foreach (array('SKU','Brand Prefix','UPC') as $type) {
            $phpunit->assertInternalType('array', $this->searchQuery($upc, $type, 1, 1));
        }

        list($query, $args) = $this->searchQuery('banana', $type, 0, 1);
        $prep = $this->connection->prepare($query);
        $res = $this->connection->execute($prep, $args);
        $results = array();
        while ($row = $this->connection->fetchRow($res)) {
            $results[$row['upc']] = $row;
        }
        $phpunit->assertNotEquals(0, strlen($this->multipleResults($results)));

        $phpunit->assertInternalType('boolean', $this->userCanEdit($upc, false));
        $phpunit->assertInternalType('boolean', $this->userCanEdit($upc, true));

        $phpunit->assertNotEquals(0, strlen($this->editorLinksArea($upc, false, false)));
        $phpunit->assertNotEquals(0, strlen($this->editorLinksArea($upc, false, true)));
        $phpunit->assertNotEquals(0, strlen($this->editorLinksArea($upc, true, true)));

        $phpunit->assertNotEquals(0, strlen($this->editForm($upc, false)));
        $phpunit->assertNotEquals(0, strlen($this->editForm($upc, true)));

        $phpunit->assertNotEquals(0, strlen($this->modulesResult(array('BaseItemModule'=>'irrelevant'), $upc)));
    }
}

FannieDispatch::conditionalExec();