diff --git a/uc_attribute/uc_attribute.info b/uc_attribute/uc_attribute.info index 8f55108..e86b94d 100644 --- a/uc_attribute/uc_attribute.info +++ b/uc_attribute/uc_attribute.info @@ -7,3 +7,10 @@ core = 7.x ; Test cases files[] = tests/uc_attribute.test files[] = tests/uc_attribute_checkout.test + +; Views handlers +files[] = views/uc_attribute_handler_field_combination.inc +files[] = views/uc_attribute_handler_field_combination_price.inc +files[] = views/uc_attribute_handler_field_order_product_attribute.inc +files[] = views/uc_attribute_handler_filter_attr.inc +files[] = views/uc_attribute_handler_filter_product_attr.inc diff --git a/uc_attribute/uc_attribute.module b/uc_attribute/uc_attribute.module index fd63d64..3a88ec9 100644 --- a/uc_attribute/uc_attribute.module +++ b/uc_attribute/uc_attribute.module @@ -11,6 +11,15 @@ * instead of listing each combination separately. */ +/** + * Implementation of hook_views_api(). + */ +function uc_attribute_views_api() { + return array( + 'api' => '2.0', + 'path' => drupal_get_path('module', 'uc_attribute') .'/views', + ); +} /** * Implements hook_help(). diff --git a/uc_attribute/views/uc_attribute.views.inc b/uc_attribute/views/uc_attribute.views.inc new file mode 100644 index 0000000..9fb4b5d --- /dev/null +++ b/uc_attribute/views/uc_attribute.views.inc @@ -0,0 +1,185 @@ +aid); + $option = uc_attribute_option_load($row->oid); + $data['uc_product_options_' . $row->aid . '_' . $row->oid]['table']['group'] = t('Product attributes'); + $data['uc_product_options_' . $row->aid . '_' . $row->oid]['table']['join']['node'] = array( + 'table' => 'uc_product_options', + 'left_field' => 'nid', + 'field' => 'nid', + 'extra' => array( + array( + 'field' => 'oid', + 'value' => $row->oid, + ), + ), + ); + $data['uc_product_options_' . $row->aid . '_' . $row->oid]['table']['group'] = t('Product attributes'); + $data['uc_product_options_' . $row->aid . '_' . $row->oid]['price'] = array( + 'title' => t('Price for !aid/!oid', array('!aid' => $attr->label ? $attr->label : $attr->name, '!oid' => $option->name)), + 'help' => 'Price of the product with the selected option.', + 'field' => array( + 'handler' => 'uc_product_handler_field_price', + 'click sortable' => TRUE, + 'float' => TRUE, + ), + ); + $data['uc_product_options_' . $row->aid . '_' . $row->oid]['cost'] = array( + 'title' => t('Cost for !aid/!oid', array('!aid' => $attr->label ? $attr->label : $attr->name, '!oid' => $option->name)), + 'help' => 'Cost of the product with the selected option.', + 'field' => array( + 'handler' => 'uc_product_handler_field_price', + 'click sortable' => TRUE, + 'float' => TRUE, + ), + ); + $data['uc_product_options_' . $row->aid . '_' . $row->oid]['weight'] = array( + 'title' => t('Weight for !aid/!oid', array('!aid' => $attr->label ? $attr->label : $attr->name, '!oid' => $option->name)), + 'help' => 'Weight of the product with the selected option.', + 'field' => array( + 'additional fields' => array( + 'weight_units' => array( + 'table' => 'uc_products', + 'field' => 'weight_units', + ), + ), + 'handler' => 'uc_product_handler_field_weight', + 'click sortable' => TRUE, + 'float' => TRUE, + ), + ); + } + + $uc_product_attributes = drupal_get_schema_unprocessed('uc_attribute', 'uc_product_attributes'); + + //Create a filter for each product attribute + $result = db_query("SELECT aid, name, description FROM {uc_attributes}"); + foreach ($result as $row) { + + $data['uc_order_products']['attr_'.$row->aid] = array( + 'title' => 'Attribute: '. $row->name, + 'help' => 'Attribute desc: '. $row->description, + 'filter' => array( + 'handler' => 'uc_attribute_handler_filter_attr', + ), + 'aid' => $row->aid, + ); + + // Create a filter for each product attribute + $data['uc_product_adjustments']['pattr_'.$row->aid] = array( + 'title' => /*'Attribute: '.*/ $row->name, + 'help' => 'Attribute desc: '.$row->description, + 'filter' => array( + 'handler' => 'uc_attribute_handler_filter_product_attr', + ), + ); + } + + $data['uc_product_adjustments']['table']['group'] = t('Product attributes'); + + $uc_product_adjustments = drupal_get_schema_unprocessed('uc_attribute', 'uc_product_adjustments'); + + $data['uc_product_adjustments']['table']['join']['node'] = array( + 'left_field' => 'nid', + 'field' => 'nid', + ); + + $data['uc_product_adjustments']['model'] = array( + 'title' => t('Model'), + 'help' => $uc_product_adjustments['fields']['model']['description'], + 'field' => array( + 'handler' => 'views_handler_field', + ), + 'relationship' => array( + 'base' => 'uc_product_stock', + 'base field' => 'sku', + 'relationship field' => 'model', + 'handler' => 'views_handler_relationship', + 'title' => t('Stock from adjustments'), + 'help' => t('Product\'s stock from the SKUs in the adjustments table.'), + ) + ); + + $data['uc_product_adjustments']['combination'] = array( + 'title' => t('Attributes'), + 'help' => t('Product combination of attributes.'), + 'field' => array( + 'handler' => 'uc_attribute_handler_field_combination', + ), + ); + + $data['uc_product_adjustments']['combination_sell_price'] = array( + 'title' => t('Sell price w/attributes adjustment'), + 'help' => t('The sell price of the product with all the price adjustments from its combination of attributes.'), + 'field' => array( + 'additional fields' => array( + 'combination', + ), + 'handler' => 'uc_attribute_handler_field_combination_price', + 'price' => 'sell_price', + 'float' => TRUE, + ), + ); + + $data['uc_product_adjustments']['combination_cost_price'] = array( + 'title' => t('Cost price w/attributes adjustment'), + 'help' => t('The cost price of the product with all the price adjustments from its combination of attributes.'), + 'field' => array( + 'additional fields' => array( + 'combination', + ), + 'handler' => 'uc_attribute_handler_field_combination_price', + 'price' => 'cost', + 'float' => TRUE, + ), + ); + + return $data; +} + + +/** + * Implements hook_views_data_alter(). + * + * Additions to other ubercart related module views' handlers. + * + * They are added here so that they don't take any preference in a new view + * building process. + */ +function uc_attribute_views_data_alter(&$data) { + + $data['uc_order_products']['attributes'] = array( + 'title' => t('Ordered product attributes'), + 'help' => t('List of attribute selection for the ordered product.'), + 'group' => t('Ordered product'), + 'field' => array( + 'table' => 'uc_order_products', + 'field' => 'data', + 'handler' => 'uc_attribute_handler_field_order_product_attribute', + ), + ); + + // Add stock relationship to the adjustments table + $data['uc_product_stock']['sku']['relationship'] = array( + 'base' => 'uc_product_adjustments', + 'base field' => 'model', + 'relationship field' => 'sku', + 'handler' => 'views_handler_relationship', + 'title' => t('Adjustments from stock'), + 'help' => t('Product\'s adjustments from the SKUs in the stock table.'), + 'group' => t('Product attributes'), + ); +} diff --git a/uc_attribute/views/uc_attribute_handler_field_combination.inc b/uc_attribute/views/uc_attribute_handler_field_combination.inc new file mode 100644 index 0000000..4e55f50 --- /dev/null +++ b/uc_attribute/views/uc_attribute_handler_field_combination.inc @@ -0,0 +1,165 @@ + attr_option according to the combination + * + * @see uc_product_adjustments_form() + */ + function _uc_stock_extra_combinations($nid) { + $combinations = array(); + $query_select = "SELECT DISTINCT"; + $query_from = " FROM"; + $query_where = " WHERE"; + $query_order = " ORDER BY"; + $result = db_query("SELECT pa.nid, pa.aid, pa.ordering, a.name, a.ordering, ao.aid, COUNT(po.oid) FROM {uc_product_attributes} AS pa LEFT JOIN {uc_attributes} AS a ON pa.aid = a.aid LEFT JOIN {uc_attribute_options} AS ao ON a.aid = ao.aid LEFT JOIN {uc_product_options} AS po ON ao.oid = po.oid AND po.nid = :nid WHERE pa.nid = :nid GROUP BY ao.aid, pa.aid, a.name, pa.ordering, a.ordering, pa.nid HAVING count(po.oid) > 0 ORDER BY pa.ordering, a.ordering", array('nid' => $nid)); + $i = 1; + $values = array(); + $values['nid'] = $nid; + + $query_select_count = $query_select . " COUNT(*),"; + $attribute_ids = array(); + foreach ($result as $prod_attr) { + $query_select .= " ao$i.aid AS aid$i, ao$i.name AS name$i, ao$i.oid AS oid$i, po$i.ordering,"; + $query_from .= " ({uc_product_options} AS po$i LEFT JOIN {uc_attribute_options} AS ao$i ON po$i.oid = ao$i.oid AND po$i.nid = :nid),"; + $query_where .= " ao$i.aid = ". $prod_attr->aid ." AND"; + $query_order .= " po$i.ordering, ao$i.name,"; + ++$i; + $attribute_ids[] = $prod_attr->aid; + } + $num_prod_attr = count($attribute_ids); + + // Remove last connecting parts (commas, "AND") + $query_select = rtrim($query_select, ','); + $query_select_count = rtrim($query_select_count, ','); + $query_from = rtrim($query_from, ','); + $query_where = substr($query_where, 0, strlen($query_where) - 4); + $query_order = rtrim($query_order, ','); + + if ($num_prod_attr) { + // Count because this array can get huge, so do not process if over the configured amount + $count = db_query($query_select_count . $query_from . $query_where . $query_order, $values)->fetchField(); + if ($count > $this->options['fetch_limit']) { + return FALSE; + } + $result = db_query($query_select . $query_from . $query_where . $query_order, $values); + foreach ($result as $row) { + $attrs_info = array(); + $attrs = array(); + for ($i = 1 ; $i <= $num_prod_attr ; $i++) { + $aid_key = "aid$i"; + $oid_key = "oid$i"; + $name_key = "name$i"; + $aid = $row->$aid_key; + $attr = db_query("SELECT name FROM {uc_attributes} WHERE aid = :aid", array('aid' => $aid))->fetchField(); + $attrs_info[$attr] = $row->$name_key; + $attrs[$aid] = $row->$oid_key; + } + asort($attrs); + $combinations[serialize($attrs)] = $attrs_info; + } + } + + // Exclude configured combinations + $result = db_query("SELECT * FROM {uc_product_adjustments} WHERE nid = :nid", array('nid' => $nid)); + foreach ($result as $row) { + // TODO: Remove unserialize/asort/serialize because rc6+ already sorts it + $c = unserialize($row->combination); + asort($c); + $c = serialize($c); + unset($combinations[$c]); + } + + return $combinations; + } + + /** + * Defines a few default options for the combination field + */ + function option_definition() { + $options = parent::option_definition(); + + $options['fetch_if_null'] = array('default' => FALSE); + $options['fetch_limit'] = array('default' => 10); + + return $options; + } + + /** + * Adds form elements for a few options + */ + function options_form(&$form, &$form_state) { + // TODO: Add an option for grouping fields + parent::options_form($form, $form_state); + $form['fetch_if_null'] = array( + '#type' => 'checkbox', + '#title' => t('Fetch attributes if there is no combination'), + '#description' => t('If checked, and if the combination is not found for a specific SKU/model, get the attributes and options from other tables.'), + '#default_value' => $this->options['fetch_if_null'], + ); + $form['fetch_limit'] = array( + '#type' => 'textfield', + '#title' => t('Fetch limit'), + '#description' => t('Attempting to build the full combination of attributes could led to a huge amount of items. To prevent ever-lasting queries and a big output, we encourage to limit the amount of it or not to use this feature.'), + '#default_value' => $this->options['fetch_limit'], + '#dependency' => array( + 'edit-options-fetch-if-null' => array(1) + ), + ); + } + + function render($values) { + $items = array(); + $combination = $values->{$this->field_alias}; + if (is_string($combination)) { + $combination = unserialize($combination); + $attributes = array(); + foreach($combination as $aid => $oid) { + $attribute = uc_attribute_load($aid); + $option = uc_attribute_option_load($oid); + $attributes[] = "$attribute->name: $option->name"; + } + $items[] = implode($attributes, ', '); + $variables = array( + 'items' => $items + ); + return theme('item_list', $variables); + } + // combination information is not found, render other node's attribute and options + else if ($this->options['fetch_if_null']) { + $nid = $values->nid; + $combinations = $this->_uc_stock_extra_combinations($nid); + if ($combinations === FALSE) { + return t('Too many attributes.'); + } + if (!empty($combinations)) { + foreach ($combinations as $combination => $attr_info) { + $attributes = array(); + foreach ($attr_info as $attr => $option) { + $attributes[] = "$attr: $option"; + } + $items[] = implode($attributes, ', '); + } + $variables = array( + 'items' => $items + ); + return theme('item_list', $variables); + } + } + return parent::render($values); + } +} diff --git a/uc_attribute/views/uc_attribute_handler_field_combination_price.inc b/uc_attribute/views/uc_attribute_handler_field_combination_price.inc new file mode 100644 index 0000000..c19cda0 --- /dev/null +++ b/uc_attribute/views/uc_attribute_handler_field_combination_price.inc @@ -0,0 +1,57 @@ +ensure_my_table(); + $this->add_additional_fields(); + // Is this the proper way to access vid? + $this->aliases['vid'] = $this->query->add_field('node', 'vid'); + } + + function render($values) { + switch ($this->definition['price']) { + case 'sell_price': + $uc_products_field = 'sell_price'; + $uc_product_options_field = 'price'; + $field_alias = 'combination_sell_price'; + break; + + case 'cost': + $uc_products_field = 'cost'; + $uc_product_options_field = 'cost'; + $field_alias = 'combination_cost_price'; + break; + } + // I wonder if we can have the price as additional field + // Because this can be used in very complex queries, I rather take that + // price out from the db directly here. + // I did try to add it through + // additional_fields => array('product_price' => array('table' => 'uc_products', 'field' => 'sell_price')) + // but it didn't work. + $price = db_query("SELECT $uc_products_field FROM {uc_products} WHERE nid = :nid AND vid = :vid", array('nid' => $values->nid, 'vid' => $values->{$this->aliases['vid']}))->fetchField(); + $offset_total = 0; + + $combination =$values->{$this->aliases['combination']}; + if ($combination) { + $combination = unserialize($combination); + foreach ($combination as $aid => $oid) { + $offset = db_query("SELECT $uc_product_options_field FROM {uc_product_options} WHERE nid = :nid AND oid = :oid", array('nid' => $values->nid, 'oid' => $oid))->fetchField(); + $offset_total += $offset; + } + } + + // Setting this internal variables so that the parent render works. + $this->field_alias = $field_alias; + $values->{$this->field_alias} = $price + $offset_total; + return parent::render($values); + } +} diff --git a/uc_attribute/views/uc_attribute_handler_field_order_product_attribute.inc b/uc_attribute/views/uc_attribute_handler_field_order_product_attribute.inc new file mode 100644 index 0000000..64c3a05 --- /dev/null +++ b/uc_attribute/views/uc_attribute_handler_field_order_product_attribute.inc @@ -0,0 +1,37 @@ +{$this->field_alias}); + $result = ""; + if (is_array($data['attributes'])) { + $result = ''; + $rows = array(); + foreach ($data['attributes'] as $attribute => $option) { + $rows[] = t('@attribute: @option', array('@attribute' => $attribute, '@option' => implode(', ', (array)$option))); + if (count($rows)) { + $variables = array( + 'items' => $rows, + 'type' => 'ul', + 'attributes' => array('class' => 'product-description'), + ); + $result = theme('item_list', $variables); + } + } + } + return $result; + } +} diff --git a/uc_attribute/views/uc_attribute_handler_filter_attr.inc b/uc_attribute/views/uc_attribute_handler_filter_attr.inc new file mode 100644 index 0000000..daee658 --- /dev/null +++ b/uc_attribute/views/uc_attribute_handler_filter_attr.inc @@ -0,0 +1,63 @@ +value_options)) { + $aid = explode('_', $this->field); + $aid = $aid[1]; + + $this->value_title = t('Options'); + $result = db_query("SELECT name, oid FROM {uc_attribute_options} WHERE aid = :aid ORDER BY ordering", array('aid' => $aid)); + + foreach ($result as $row) { + $options[$row->oid] = $row->name; + } + if (count($options) == 0) { + $options[] = t('No options found.'); + } + $this->value_options = $options; + } + } + + function query() { + $aid = explode('_',$this->field); + $aid = $aid[1]; + + $key = db_query('SELECT name FROM {uc_attributes} WHERE aid = :aid', array('aid' => $aid))->fetchField(); + + $this->ensure_my_table(); + $this->real_field = 'data'; + $field = "$this->table_alias.$this->real_field"; + + if (!empty($this->value)) { + if ($this->operator == 'not in') { + $not = 'NOT'; + $condition = db_and(); + } + else { + $condition = db_or(); + } + + foreach ($this->value as &$value) { + $option = db_query('SELECT name FROM {uc_attribute_options} WHERE oid = :oid', array('oid' => $value))->fetchField(); + $var = array($key => array($value => $option)); + $servar = serialize($var); + $l = strpos($servar, '{') + 1; + $r = strpos($servar, '}') - $l; + $value = substr($servar, $l, $r); + $condition->condition($field, "%{$value}%", "$not LIKE"); + } + + $this->query->add_where($this->options['group'], $condition); + } + } +} + diff --git a/uc_attribute/views/uc_attribute_handler_filter_product_attr.inc b/uc_attribute/views/uc_attribute_handler_filter_product_attr.inc new file mode 100644 index 0000000..9e80d09 --- /dev/null +++ b/uc_attribute/views/uc_attribute_handler_filter_product_attr.inc @@ -0,0 +1,70 @@ +value_options)) { + $aid = explode('_', $this->field); + $aid = $aid[1]; + + $this->value_title = t('Options'); + $result = db_query("SELECT name, oid FROM {uc_attribute_options} WHERE aid = :aid ORDER BY ordering", array('aid' => $aid)); + + foreach ($result as $row) { + $options[$row->oid] = $row->name; + } + if (count($options) == 0) { + $options[] = t('No options found.'); + } + $this->value_options = $options; + } + } + + function query() { + $this->ensure_my_table(); + $this->real_field = 'combination'; + + if ($this->operator != 'not in' && $this->operator != 'in') { + return parent::query(); + } + + $aid = explode('_', $this->field); + $aid = $aid[1]; + + $field = "{$this->table_alias}.{$this->real_field}"; + + if(!is_array($this->value)) { + $this->value = array($this->value); + } + + if (!empty($this->value)) { + if ($this->operator == 'not in') { + $not = 'NOT'; + $condition = db_and(); + } + else { + $not = ''; + $condition = db_or(); + } + + foreach ($this->value as $value) { + + $var = array($aid => $value); + $servar = serialize($var); + $l = strpos($servar, '{') + 1; + $r = strpos($servar, '}') - $l; + $value = substr($servar, $l, $r); + $condition->condition($field, "%{$value}%", "$not LIKE"); + } + + $this->query->add_where($this->options['group'], $condition); + } + } +}