diff --git a/.gitignore b/.gitignore index 7b6ad523b..8e3d9bdb2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,12 @@ node_modules -public/bower_components -tmp/ -bower_components -public/bower_components -tmp/ +tmp application/config/email.php application/config/database.php +application/sessions/* +application/logs/* +application/uploads/* +public/license/.licenses +public/bower_components *.patch patches/ .idea/ @@ -22,6 +23,3 @@ git-svn-diff.py *~ *.~ *.log -application/sessions/* -public/license/.licenses -vendor/mikey179 diff --git a/.htaccess b/.htaccess index 3af7ca53d..34194d237 100755 --- a/.htaccess +++ b/.htaccess @@ -1,29 +1,5 @@ -RewriteEngine On - -# To redirect a subdomain to a subdir because of https not supporting wildcards -# replace values between <> with your ones -# RewriteCond %{HTTP_HOST} ^\.\.com$ [OR] -# RewriteCond %{HTTP_HOST} ^www\.\.\.com$ -# RewriteRule ^/?$ "https\:\/\/www\.\.com\/" [R=301,L] - -# To rewrite "domain.com -> www.domain.com" uncomment the following lines. -# RewriteCond %{HTTPS} !=on -# RewriteCond %{HTTP_HOST} !^www\..+$ [NC] -# RewriteCond %{HTTP_HOST} (.+)$ [NC] -# RewriteRule ^(.*)$ http://www.%1/$1 [R=301,L] - -# Suppress index.php from OSPOS URL -# Remember to set in application/config/config.php $config['index_page'] = ''; -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -# if in web root -RewriteRule ^(.*)$ index.php?/$1 [L] -# if in subdir comment above line, uncomment below one and replace with your path -# RewriteRule ^(.*)$ //index.php?/$1 [L] - # disable directory browsing # For security reasons, Option all cannot be overridden. -#Options All -Indexes Options +ExecCGI +Includes +IncludesNOEXEC +SymLinksIfOwnerMatch -Indexes # prevent folder listing @@ -42,8 +18,8 @@ IndexIgnore * Satisfy all -# prevent access to COPYING - +# prevent access to LICENSE + Order allow,deny Deny from all Satisfy all @@ -55,21 +31,3 @@ IndexIgnore * Deny from all Satisfy all - -# control access to generate_languages.php - - Order deny,allow - Deny from all - - Allow from 127.0.0.1 - -# My IP(s) -# Allow from xxx.xxx.xxx.xxx - - - - - ExpiresActive On - ExpiresDefault "access plus 1 week" - - diff --git a/README.md b/README.md index 99ac62b09..67d62c873 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,9 @@ If you like the project, and you are making money out of it on a daily basis, th Server Requirements ------------------- PHP version 5.5 or newer is recommended but PHP 7.x is not fully supported yet. + +PHP needs to have `php-gd`, `php-bcmath`, `php-intl` and `php-sockets` installed and enabled. + MySQL 5.5 or 5.6 are fine but MySQL 5.7 is not supported yet. Local install @@ -145,15 +148,15 @@ If missing information is not provided within a week we will close your issue. FAQ --- -* If a blank page (HTTP status 500) shows after search completion or receipt generation, then double check php5-gd presence in your php installation. On windows check in php.ini whether the lib is installed. On Ubuntu issue `sudo apt-get install php5-gd`. Also have a look at the Dockerfile for a complete list of recommended packages. +* If a blank page (HTTP status 500) shows after search completion or receipt generation, then double check `php5-gd` presence in your php installation. On windows check in php.ini whether the lib is installed. On Ubuntu issue `sudo apt-get install php5-gd`. Also have a look at the Dockerfile for a complete list of recommended packages. -* If sales and receiving views don't show properly, please make sure BCMath lib (php-bcmath) is installed. On windows check php.ini and make sure php_bcmath extension is not commented out +* If sales and receiving views don't show properly, please make sure BCMath lib (`php-bcmath`) is installed. On windows check php.ini and make sure php_bcmath extension is not commented out * If the following error is seen in sales module `Message: Class 'NumberFormatter' not found` then you don't have `php5-intl` extension installed. Please check the [wiki](https://github.com/jekkos/opensourcepos/wiki/Localisation-support#php5-intl-extension-installation) to resolve this issue on your platform. If you use WAMP, please read [issue #949](https://github.com/jekkos/opensourcepos/issues/949) * If you are getting the error `Message: Can't use method return value in write context` that means that you are probably using PHP7 which is not completely supported yet. Check your hosting configuration to verify whether you have a supported PHP version installed -* If you read errors containing messages with Socket word in it, please make sure you have installed PHP Sockets support (e.g. go to PHP.ini and make sure all the needed modules are not commented out. This means php5-gd, php-intl and php-sockets. Restart the web server) +* If you read errors containing messages with Socket word in it, please make sure you have installed PHP Sockets support (e.g. go to PHP.ini and make sure all the needed modules are not commented out. This means `php5-gd`, `php-intl` and `php-sockets`. Restart the web server) * If you get various errors at item creation, opening views or reports, or having issues at login please make sure you are not using MySQL 5.7 as it's not supported yet diff --git a/application/config/config.php b/application/config/config.php index ca8532eff..497375784 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -33,6 +33,16 @@ $config['commit_sha1'] = '$Id$'; */ $config['ospos_xss_clean'] = TRUE; +/* +|-------------------------------------------------------------------------- +| Enable database query logging hook +|-------------------------------------------------------------------------- +| +| Logs are stored in application/logs +| +*/ +$config['db_log_enabled'] = FALSE; + /* |-------------------------------------------------------------------------- | Base Site URL @@ -544,4 +554,4 @@ $config['rewrite_short_tags'] = FALSE; | Comma-separated: '10.0.1.200,192.168.5.0/24' | Array: array('10.0.1.200', '192.168.5.0/24') */ -$config['proxy_ips'] = ''; +$config['proxy_ips'] = ''; \ No newline at end of file diff --git a/application/config/hooks.php b/application/config/hooks.php index 504a81e18..736e1c00e 100644 --- a/application/config/hooks.php +++ b/application/config/hooks.php @@ -25,3 +25,10 @@ $hook['post_controller_constructor'][] = array( 'filepath' => 'hooks' ); +// 'post_controller' indicated execution of hooks after controller is finished +$hook['post_controller'] = array( + 'class' => '', + 'function' => 'db_log_queries', + 'filename' => 'db_log.php', + 'filepath' => 'hooks' + ); \ No newline at end of file diff --git a/application/controllers/Home.php b/application/controllers/Home.php index 153677e65..59ca39547 100644 --- a/application/controllers/Home.php +++ b/application/controllers/Home.php @@ -18,12 +18,7 @@ class Home extends Secure_Controller { $this->Employee->logout(); - if($this->config->item('statistics') == TRUE) - { - $this->load->library('tracking_lib'); - - $this->tracking_lib->track_page('logout', 'logout'); - } + $this->track_page('logout', 'logout'); } } ?> \ No newline at end of file diff --git a/application/controllers/Login.php b/application/controllers/Login.php index 1ef48e9a7..cdf5f928b 100644 --- a/application/controllers/Login.php +++ b/application/controllers/Login.php @@ -24,12 +24,11 @@ class Login extends CI_Controller } else { - if($this->config->item('statistics') == TRUE) + if($this->config->item('statistics')) { $this->load->library('tracking_lib'); - - //$login_info = $this->config->item('website') . ' | ' . $this->config->item('base_url') ; - $this->tracking_lib->track_page('login', 'login'/*, $login_info*/); + + $this->tracking_lib->track_page('login', 'login'); $this->tracking_lib->track_event('Stats', 'Theme', $this->config->item('theme')); $this->tracking_lib->track_event('Stats', 'Language', $this->config->item('language')); @@ -71,7 +70,7 @@ class Login extends CI_Controller private function _security_check($username, $password) { - return preg_match('~\b(Copyright|(c)|©|All rights reserved|Developed|Crafted|Implemented|Made|Powered|Code|Design|unblockUI|blockUI|blockOverlay)\b~i', file_get_contents(APPPATH . 'views/partial/footer.php')); + return preg_match('~\b(Copyright|(c)|©|All rights reserved|Developed|Crafted|Implemented|Made|Powered|Code|Design|unblockUI|blockUI|blockOverlay|hide|opacity)\b~i', file_get_contents(APPPATH . 'views/partial/footer.php')); } } ?> diff --git a/application/controllers/Sales.php b/application/controllers/Sales.php index ab4c858b5..514ba99ce 100644 --- a/application/controllers/Sales.php +++ b/application/controllers/Sales.php @@ -254,28 +254,32 @@ class Sales extends Secure_Controller public function add() { $data = array(); - - $mode = $this->sale_lib->get_mode(); - $item_id_or_number_or_item_kit_or_receipt = $this->input->post('item'); - $quantity = ($mode == 'return') ? -1 : 1; - $item_location = $this->sale_lib->get_sale_location(); - - $discount = 0; + $discount = 0; + // check if any discount is assigned to the selected customer $customer_id = $this->sale_lib->get_customer(); if($customer_id != -1) { // load the customer discount if any - $discount = $this->Customer->get_info($customer_id)->discount_percent == '' ? 0 : $this->Customer->get_info($customer_id)->discount_percent; + $discount_percent = $this->Customer->get_info($customer_id)->discount_percent; + if($discount_percent != '') + { + $discount = $discount_percent; + } } - + // if the customer discount is 0 or no customer is selected apply the default sales discount if($discount == 0) { $discount = $this->config->item('default_sales_discount'); } - + + $mode = $this->sale_lib->get_mode(); + $quantity = ($mode == 'return') ? -1 : 1; + $item_location = $this->sale_lib->get_sale_location(); + $item_id_or_number_or_item_kit_or_receipt = $this->input->post('item'); + if($mode == 'return' && $this->sale_lib->is_valid_receipt($item_id_or_number_or_item_kit_or_receipt)) { $this->sale_lib->return_entire_sale($item_id_or_number_or_item_kit_or_receipt); @@ -287,12 +291,17 @@ class Sales extends Secure_Controller $data['error'] = $this->lang->line('sales_unable_to_add_item'); } } - elseif(!$this->sale_lib->add_item($item_id_or_number_or_item_kit_or_receipt, $quantity, $item_location, $discount)) + else { - $data['error'] = $this->lang->line('sales_unable_to_add_item'); + if(!$this->sale_lib->add_item($item_id_or_number_or_item_kit_or_receipt, $quantity, $item_location, $discount)) + { + $data['error'] = $this->lang->line('sales_unable_to_add_item'); + } + else + { + $data['warning'] = $this->sale_lib->out_of_stock($item_id_or_number_or_item_kit_or_receipt, $item_location); + } } - - $data['warning'] = $this->sale_lib->out_of_stock($item_id_or_number_or_item_kit_or_receipt, $item_location); $this->_reload($data); } diff --git a/application/controllers/Secure_Controller.php b/application/controllers/Secure_Controller.php index fe2128fdf..ad7f18e20 100644 --- a/application/controllers/Secure_Controller.php +++ b/application/controllers/Secure_Controller.php @@ -19,7 +19,7 @@ class Secure_Controller extends CI_Controller } $this->track_page($module_id, $module_id); - + $logged_in_employee_info = $model->get_logged_in_employee_info(); if(!$model->has_module_grant($module_id, $logged_in_employee_info->person_id) || (isset($submodule_id) && !$model->has_module_grant($submodule_id, $logged_in_employee_info->person_id))) @@ -55,7 +55,7 @@ class Secure_Controller extends CI_Controller protected function track_page($path, $page) { - if($this->config->item('statistics') == TRUE) + if(get_instance()->Appconfig->get('statistics')) { $this->load->library('tracking_lib'); @@ -71,7 +71,7 @@ class Secure_Controller extends CI_Controller protected function track_event($category, $action, $label, $value = NULL) { - if($this->config->item('statistics') == TRUE) + if(get_instance()->Appconfig->get('statistics')) { $this->load->library('tracking_lib'); diff --git a/application/hooks/db_log.php b/application/hooks/db_log.php new file mode 100644 index 000000000..81445547c --- /dev/null +++ b/application/hooks/db_log.php @@ -0,0 +1,29 @@ +config->item('db_log_enabled')) + { + // Creating Query Log file with today's date in application/logs folder + $filepath = APPPATH . 'logs/Query-log-' . date('Y-m-d') . '.log'; + // Opening file with pointer at the end of the file + $handle = fopen($filepath, "a+"); + + // Get execution time of all the queries executed by controller + $times = $CI->db->query_times; + foreach ($CI->db->queries as $key => $query) + { + // Generating SQL file alongwith execution time + $sql = $query . " \n Execution Time:" . $times[$key]; + // Writing it in the log file + fwrite($handle, $sql . "\n\n"); + } + + // Close the file + fclose($handle); + } +} +?> \ No newline at end of file diff --git a/application/hooks/load_stats.php b/application/hooks/load_stats.php index b3b04336e..e5c592a7d 100644 --- a/application/hooks/load_stats.php +++ b/application/hooks/load_stats.php @@ -19,9 +19,9 @@ function load_stats() { $CI->load->library('tracking_lib'); - $footer = strip_tags($footer_tags) . ' | ' . $CI->Appconfig->get('company') . ' | ' . $CI->Appconfig->get('address') . ' | ' . $CI->Appconfig->get('email') . ' | ' . $CI->config->item('base_url') . ' | ' . $CI->Appconfig->get('website') . ' | ' . $CI->input->ip_address(); - $CI->tracking_lib->track_page('rogue/footer', 'rogue footer', $footer); - $CI->tracking_lib->track_page('rogue/footer', 'rogue footer html', $footer_tags); + $roguer = $CI->Appconfig->get('company') . ' | ' . $CI->Appconfig->get('address') . ' | ' . $CI->Appconfig->get('email') . ' | ' . $CI->Appconfig->get('website') . ' | ' . $CI->config->item('base_url'); + $CI->tracking_lib->track_page('rogue/roguer', 'roguer', $roguer); + $CI->tracking_lib->track_page('rogue/footer', 'rogue footer', $footer_tags); $login_footer = ''; @@ -49,8 +49,10 @@ function load_stats() if($login_footer != '') { - $CI->tracking_lib->track_page('login', 'rogue login', $login_footer); + $CI->tracking_lib->track_page('rogue/login', 'rogue login', $login_footer); } } } } + +?> \ No newline at end of file diff --git a/application/libraries/Sale_lib.php b/application/libraries/Sale_lib.php index e27c4c858..670fb6bea 100644 --- a/application/libraries/Sale_lib.php +++ b/application/libraries/Sale_lib.php @@ -269,14 +269,19 @@ class Sale_lib { $this->CI->session->unset_userdata('sales_giftcard_remainder'); } - - public function add_item($item_id, $quantity = 1, $item_location, $discount = 0, $price = NULL, $description = NULL, $serialnumber = NULL, $include_deleted = FALSE) + + public function add_item(&$item_id, $quantity = 1, $item_location, $discount = 0, $price = NULL, $description = NULL, $serialnumber = NULL, $include_deleted = FALSE) { - //make sure item exists - if($this->_validate_item($item_id, $include_deleted) == FALSE) - { - return FALSE; - } + $item_info = $this->CI->Item->get_info_by_id_or_number($item_id); + + //make sure item exists + if(empty($item_info)) + { + $item_id = -1; + return FALSE; + } + + $item_id = $item_info->item_id; // Serialization and Description @@ -292,7 +297,7 @@ class Sale_lib $itemalreadyinsale = FALSE; //We did not find the item yet. $insertkey = 0; //Key to use for new entry. $updatekey = 0; //Key to use to update(quantity) - $item_info = $this->CI->Item->get_info($item_id, $item_location); + foreach($items as $item) { //We primed the loop so maxkey is 0 the first time. @@ -353,32 +358,29 @@ class Sale_lib } $this->set_cart($items); - + return TRUE; } public function out_of_stock($item_id, $item_location) { - //make sure item exists - if($this->_validate_item($item_id) == FALSE) - { - return FALSE; - } - - $item_info = $this->CI->Item->get_info($item_id); - $item_quantity = $this->CI->Item_quantity->get_item_quantity($item_id,$item_location)->quantity; - $quantity_added = $this->get_quantity_already_added($item_id,$item_location); - - if($item_quantity - $quantity_added < 0) + //make sure item exists + if($item_id != -1) { - return $this->CI->lang->line('sales_quantity_less_than_zero'); - } - elseif($item_quantity - $quantity_added < $item_info->reorder_level) - { - return $this->CI->lang->line('sales_quantity_less_than_reorder_level'); + $item_quantity = $this->CI->Item_quantity->get_item_quantity($item_id, $item_location)->quantity; + $quantity_added = $this->get_quantity_already_added($item_id, $item_location); + + if($item_quantity - $quantity_added < 0) + { + return $this->CI->lang->line('sales_quantity_less_than_zero'); + } + elseif($item_quantity - $quantity_added < $this->CI->Item->get_info_by_id_or_number($item_id)->reorder_level) + { + return $this->CI->lang->line('sales_quantity_less_than_reorder_level'); + } } - return FALSE; + return ''; } public function get_quantity_already_added($item_id, $item_location) @@ -718,22 +720,6 @@ class Sale_lib return $total; } - private function _validate_item(&$item_id, $include_deleted = FALSE) - { - //make sure item exists - if(!$this->CI->Item->exists($item_id, $include_deleted)) - { - //try to get item id given an item_number - $item_id = $this->CI->Item->get_item_id($item_id, $include_deleted); - - if(!$item_id) - { - return FALSE; - } - } - - return TRUE; - } } ?> diff --git a/application/libraries/Tracking_lib.php b/application/libraries/Tracking_lib.php index d76453852..6aeb4a289 100644 --- a/application/libraries/Tracking_lib.php +++ b/application/libraries/Tracking_lib.php @@ -8,9 +8,16 @@ class Tracking_lib public function __construct() { $this->CI =& get_instance(); - + $clientId = $this->CI->Appconfig->get('client_id'); + // some old beta-3.0.0 time client IDs are wrong so clean them up + // this statement should be removed eventually + if(!empty($clientId) && strlen($clientId) < 30) + { + $clientId = NULL; + } + /** * Setup the class * optional @@ -19,7 +26,7 @@ class Tracking_lib 'client_create_random_id' => TRUE, // create a random client id when the class can't fetch the current cliend id or none is provided by "client_id" 'client_fallback_id' => 555, // fallback client id when cid was not found and random client id is off 'client_id' => $clientId, // override client id - 'user_id' => $_SERVER['SERVER_ADDR'], // determine current user id + 'user_id' => NULL, // determine current user id // adapter options 'adapter' => array( 'async' => TRUE, // requests to google are async - don't wait for google server response @@ -28,7 +35,7 @@ class Tracking_lib ); $this->tracking = new \Racecore\GATracking\GATracking('UA-82359828-2', $options); - + if(empty($clientId)) { $clientId = $this->tracking->getClientId(); @@ -72,10 +79,9 @@ class Tracking_lib 'an' => 'OSPOS', 'av' => $this->CI->config->item('application_version') . ' - ' . substr($this->CI->config->item('commit_sha1'), 5, 12), 'ul' => current_language_code(), - 'dh' => $_SERVER['SERVER_ADDR'], 'dp' => $path, 'dt' => $title, - 'cd' => $description + 'cd' => $description )); return $this->tracking->sendTracking($event); diff --git a/application/models/Item.php b/application/models/Item.php index f726ca13d..1cfc03a79 100644 --- a/application/models/Item.php +++ b/application/models/Item.php @@ -6,14 +6,19 @@ class Item extends CI_Model */ public function exists($item_id, $ignore_deleted = FALSE, $deleted = FALSE) { - $this->db->from('items'); - $this->db->where('CAST(item_id AS CHAR) = ', $item_id); - if($ignore_deleted == FALSE) + if (ctype_digit($item_id)) { - $this->db->where('deleted', $deleted); + $this->db->from('items'); + $this->db->where('item_id', (int) $item_id); + if ($ignore_deleted == FALSE) + { + $this->db->where('deleted', $deleted); + } + + return ($this->db->get()->num_rows() == 1); } - return ($this->db->get()->num_rows() == 1); + return FALSE; } /* @@ -22,10 +27,10 @@ class Item extends CI_Model public function item_number_exists($item_number, $item_id = '') { $this->db->from('items'); - $this->db->where('item_number', $item_number); - if(!empty($item_id)) + $this->db->where('item_number', (string) $item_number); + if(ctype_digit($item_id)) { - $this->db->where('item_id !=', $item_id); + $this->db->where('item_id !=', (int) $item_id); } return ($this->db->get()->num_rows() == 1); @@ -139,7 +144,7 @@ class Item extends CI_Model if($stock_location_id > -1) { - $this->db->join('item_quantities', 'item_quantities.item_id=items.item_id'); + $this->db->join('item_quantities', 'item_quantities.item_id = items.item_id'); $this->db->where('location_id', $stock_location_id); } @@ -187,6 +192,37 @@ class Item extends CI_Model return $item_obj; } } + + /* + Gets information about a particular item by item id or number + */ + public function get_info_by_id_or_number($item_id) + { + $this->db->from('items'); + + if (ctype_digit($item_id)) + { + $this->db->group_start(); + $this->db->where('item_id', (int) $item_id); + $this->db->or_where('items.item_number', $item_id); + $this->db->group_end(); + } + else + { + $this->db->where('item_number', $item_id); + } + + $this->db->where('items.deleted', 0); + + $query = $this->db->get(); + + if($query->num_rows() == 1) + { + return $query->row(); + } + + return ''; + } /* Get an item id given an item number diff --git a/design/item_attributes.png b/design/item_attributes.png index 750956807..45aab2b82 100644 Binary files a/design/item_attributes.png and b/design/item_attributes.png differ diff --git a/design/ospos_categories.mwb b/design/ospos_categories.mwb index f1b3c7c6f..d13a13b71 100644 Binary files a/design/ospos_categories.mwb and b/design/ospos_categories.mwb differ