Index: product/product.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/ecommerce/product/product.module,v retrieving revision 1.110 diff -u -u -F^f -r1.110 product.module --- product/product.module 18 May 2006 12:42:24 -0000 1.110 +++ product/product.module 18 May 2006 19:01:25 -0000 @@ -296,28 +296,31 @@ function product_menu($may_cache) { ); } else { - if (arg(0) == 'node' && is_numeric(arg(1)) && user_access('administer products')) { - // Only add the product-tab for non-product pages: - if (db_result(db_query(db_rewrite_sql("SELECT COUNT(n.nid) FROM {node} n WHERE n.nid = %d AND n.type != 'product'"), arg(1))) > 0) { - $items[] = array( - 'path' => 'node/'. arg(1) .'/product', - 'title' => t('product'), - 'callback' => 'product_to_product', - 'access' => user_access('administer products'), - 'type' => MENU_LOCAL_TASK, - 'weight' => 2 - ); - $items[] = array( - 'path' => 'node/'. arg(1) .'/product/delete', - 'title' => t('delete product'), - 'callback' => 'product_to_product_delete', - 'access' => user_access('administer products'), - 'type' => MENU_CALLBACK, + // These items are for transforming non-product nodes into products + if (arg(0) == 'node' && is_numeric(arg(1))) { + $node = node_load(arg(1)); + $settings = _product_transform_get_settings($node->type); + if (count($settings['product_types'])) { + // Node can be a product. + $items[] = array('path' => 'node/'. arg(1) .'/product', + 'title' => t('product'), + 'callback' => 'product_transform_to_product', + 'callback arguments' => array($node->nid), + 'access' => (user_access('administer products') || + user_access("administer $node->type products")), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2); + $items[] = array('path' => 'node/'. arg(1) .'/product/delete', + 'title' => t('delete product'), + 'callback' => 'product_transform_to_product_delete', + 'access' => (user_access('administer products') || + user_access("administer $node->type products")), + 'type' => MENU_CALLBACK, ); } } } - + return $items; } @@ -337,15 +340,109 @@ function product_node_name($node) { /** * Implementation of hook_form_alter() + * Adds node-type settings to admin/settings/node-types/ + * Adds product fields to node edit forms, where applicable */ function product_form_alter($form_id, &$form) { - if (isset($form['type']) && $form_id == $form['type']['#value'] .'_node_form') { - foreach (array('ptype') as $key) { - $form[$key] = array('#type' => 'value', '#value' => $form['#node']->$key); + if (isset($form['type'])) { + $ntype = $form['type']['#value']; + $defaults = _product_transform_get_settings($ntype); + if (isset($form['#node']) && isset($form['#node']->ptype)) { + // ptype has been set for this node + $ptype = $form['#node']->ptype; + } + else if (count($defaults['product_types']) == 1) { + // this node type can be only one product type + $ptype = array_shift($defaults['product_types']); + } + + if ($form_id == $ntype . '_node_settings' && + $ntype != 'product') { + // admin/settings/content-types form + if (user_access('administer products')) { + $form['product_transform'] = + array('#type' => 'fieldset', + '#title' => t('Product'), + '#collapsible' => true, + '#collapsed' => (!count($defaults['product_types'])), + '#tree' => true, + ); + $options = array_merge(product_get_ptypes()); + $form['product_transform']['product_types'] = + array('#type' => 'checkboxes', + '#title' => t('Product type'), + '#multiple' => TRUE, + '#options' => $options, + '#default_value' => $defaults['product_types'], + '#description' => t('Select the product type(s), if any, that apply to %ntype nodes', array('%ntype' => $ntype)), + ); + + // Allow user to configure defaults + $form['product_transform']['product_defaults'] = + array('#type' => 'fieldset', + '#title' => t('Product defaults'), + ); + $product_defaults = (object)$defaults['product_defaults']['values']; + $form['product_transform']['product_defaults']['values'] = product_base_form_elements($product_defaults); + unset($form['product_transform']['product_defaults']['values']['price']['#required']); + + // system_settings_form should do this for us: + $form['buttons']['#weight'] = 99; + + // make sure we are called when form is submitted + $form['#submit']['product_transform_node_settings_submit'] = + array($ntype); + } + } + else if ($form_id == $ntype . '_node_form') { + foreach (array('ptype') as $key) { + $form[$key] = array('#type' => 'value', '#value' => $form['#node']->$key); + } + if ($ntype != 'product') { + // node/add/$ntype form. or node/$nid/edit form + if (user_access('administer products') || + user_access('administer '. $ntype . ' products')) { + $node = $form['#node']; + if ($ptype) { + $form['product_transform'] = + array('#type' => 'fieldset', + '#title' => t('Product'), + '#collapsible' => true, + '#collapsed' => false, + ); + $form['product_transform']['content'] = _product_transform_node_form($node, $ptype); + + if ($node->nid) { + $form['product_transform']['description'] = + array('#type' => 'markup', + '#value' => t('To delete product settings, see the product tab'), + ); + } + + $form['#validate']['product_transform_node_form_validate'] = + array($ptype); + } + else if (count($defaults['product_types']) && !$node->nid) { + // User may choose from several product types. They must do this on + // the product tab, after node has been created. + $form['product_transform'] = + array('#type' => 'fieldset', + '#title' => t('Product'), + '#collapsible' => FALSE, //collapse does not work with markup? + ); + $form['product_transform']['content'] = + array('#type' => 'markup', + '#value' => t('To make this node a product: First submit your changes here. Then see the product tab.'), + ); + } + } + } } } } + + /** * Implementation of hook_nodeapi(). * @@ -368,17 +465,9 @@ function product_nodeapi(&$node, $op, $a case 'insert': case 'update': - // copy the product to the next revision. - if ($node->type != 'product' && $node->revision) { - $last_vid = db_result(db_query('SELECT p.vid AS pvid FROM {node_revisions} r LEFT JOIN {ec_product} p ON r.vid = p.vid WHERE r.nid = %d AND r.vid <> %d ORDER BY r.vid DESC LIMIT 1', $node->nid, $node->vid)); - if ($last_vid) { - $product->nid = $node->nid; - $product->vid = $last_vid; - if ($product = product_load($product)) { - $product->vid = $node->vid; - product_save($product); - } - } + // product fields included in node form + if ($node->product_transform_make_product || $node->ptype) { + product_save($node); } break; @@ -399,9 +488,25 @@ function product_nodeapi(&$node, $op, $a /** * Implementation of hook_perm(). + * + * Creates 'administer TYPE products' permission for each node type + * which is also a product type. Granting a user that permission will + * allow them to edit the product settings for any node of that type. + * + * Granting 'administer products' allows + * the user to edit product settings for all node types. */ function product_perm() { - return array('administer products'); + $items[] = 'administer products'; + + // one perm for each node type that may be converted into a product + $settings = _product_transform_get_settings(); + foreach ($settings as $ntype => $ntype_settings) { + if (count($ntype_settings['product_types'])) { + $items[] = "administer $ntype products"; + } + } + return $items; } /** @@ -530,15 +635,25 @@ function product_add() { return $output; } -function product_types_listing($link = 'node/add/product') { +/** + * @param $link + * base path when generating links + * @param $filter + * optional array of product types to display. + * Hide those types not included in $filter. + */ +function product_types_listing($link = 'node/add/product', $filter = NULL) { // If no (valid) product type has been provided, display a product type overview. $node = new StdClass(); foreach (product_get_ptypes() as $ptype => $name) { - $node->ptype = $ptype; - if (product_access('create', $node)) { - $out = '
'. l($name, "$link/$ptype", array('title' => t('Add a %s.', array('%s' => $name)))) .'
'; - $out .= '
'. implode("\n", module_invoke_all('help', 'node/add/product#'. $ptype)) .'
'; - $item[$name] = $out; + if ($filter == NULL || + in_array($ptype, $filter)) { + $node->ptype = $ptype; + if (product_access('create', $node)) { + $out = '
'. l($name, "$link/$ptype", array('title' => t('Add a %s.', array('%s' => $name)))) .'
'; + $out .= '
'. implode("\n", module_invoke_all('help', 'node/add/product#'. $ptype)) .'
'; + $item[$name] = $out; + } } } @@ -635,107 +750,12 @@ function product_page() { return theme('product_view_collection'); } -/** - * Handles all product operations for non-product nodes. - */ -function product_to_product() { - $op = $_POST['op']; - $node = node_load(arg(1)); - - if ($node->nid) { - drupal_set_title(t('%node-title (product)', array('%node-title' => $node->title))); - $product = db_fetch_object(db_query('SELECT * FROM {ec_product} WHERE vid = %d', $node->vid)); - $ptypes = product_get_ptypes(); - if ($product || (arg(3) && in_array(arg(3), array_keys($ptypes)))) { - $node->ptype = $node->ptype ? $node->ptype : arg(3); - $form = product_base_form_elements($node); - $form['#node'] = $node; - foreach (array('nid', 'vid', 'ptype') as $key) { - $form[$key] = array('#type' => 'value', '#value' => $node->$key); - } - - $form = array_merge($form, (array)module_invoke($node->ptype, 'productapi', $node, 'form')); - if ($product) { - $form['update'] = array('#type' => 'submit', '#value' => t('Update product'), '#weight' => 40); - $form['remove'] = array('#type' => 'submit', '#value' => t('Remove product'), '#weight' => 45); - } - else { - $form['create'] = array('#type' => 'submit', '#value' => t('Create product'), '#weight' => 40); - } - return drupal_get_form('product_to_product', $form); - } - else { - return product_types_listing("node/$node->nid/product"); - } - } -} - -function product_to_product_validate($form_id, $form_values) { - $op = $_POST['op']; - $edit = (object) $form_values; - - switch ($op) { - case t('Create product'): - case t('Update product'): - product_form_validate($edit); - break; - } -} - -function product_to_product_submit($form_id, $form_values) { - $op = $_POST['op']; - $edit = (object) $form_values; - - switch ($op) { - case t('Create product'): - case t('Update product'): - product_save($edit); - drupal_set_message(t('The product has been saved.')); - return "node/$edit->nid"; - break; - case t('Remove product'): - //product_delete($edit, true); - //drupal_set_message(t('Removed the post from the product listings.')); - return "node/$edit->nid/product/delete"; - break; - } -} - -function product_to_product_delete() { - $node = node_load(arg(1)); - - $form['node'] = array('#type' => 'value', '#value' => $node); - - $output.= confirm_form( - 'product_to_product_delete', - $form, - t('Are you sure that you want to delete the product information for %title', array('%title' => $node->title)), - "node/{$node->nid}/product", - t('The product information will be permenantly removed.'), - t('Delete Product'), - t('Cancel') - ); - return $output; -} - -function product_to_product_delete_submit($form_id, $form_values) { - if ($form_values['confirm']) { - product_delete($form_values['node'], true); - drupal_set_message(t('Removed the post from the product listings.')); - return "node/{$form_values['node']->nid}"; - } - return "node/{$form_values['node']->nid}/product"; -} function product_form_validate(&$edit) { $errors = array(); - /* Remove the currency symbol at the beginning of the price if it exists */ + /* Make sure price is a number */ if (isset($edit->price)) { - if (substr($edit->price, 0, 1) == variable_get('payment_symbol', '$')) { - $edit->price = substr($edit->price, count(variable_get('payment_symbol', '$'))); - } - $edit->price = str_replace(',', '', $edit->price); if (!is_numeric($edit->price)) { $errors['price'] = t('Please enter a numeric value for the product price.'); } @@ -1197,3 +1217,222 @@ function product_views_tables() { function product_views_handler_filter_product_type() { return product_get_ptypes(); } + + +/**************************************************************** + * Product transformation functions. Convert non-products to products + * See also hook_menu and hook_form_alter and hook_nodeapi + ****************************************************************/ + +/** + * Return the product type settings. + * Settings are stored as a drupal variable, named product_transform_settings + * + * @param + * $ntype a node type (optional) + * + * @return + * If no node type provided, an associative array in which the keys + * are node type names and values are associative arrays. The values + * array contains settings specific to the node type (key). These + * nested settings include the product_type. + * If node type provided, the settings for that specific node type are + * returned. + */ +function _product_transform_get_settings($ntype = NULL) { + $settings = variable_get('product_transform_settings', array()); + if (!$ntype) + return $settings; + else { + $ntype_settings = $settings[$ntype]; + if (!$ntype_settings) { + // default node-type settings here: + $ntype_settings = array('product_types' => array()); + } + return $ntype_settings; + } +} + +/** + * Called when node-type settings form is submitted. + * + * We add the settings for this particular node type to the + * product_transform settings variable. + */ +function product_transform_node_settings_submit($form_id, $form, $ntype) { + // clean up data structure returned from checkboxes + foreach ($form['product_transform']['product_types'] as $ptype => $value) { + if (!$value) { + unset($form['product_transform']['product_types'][$ptype]); + } + } + + $settings = _product_transform_get_settings(); + $settings[$ntype] = $form['product_transform']; + variable_set('product_transform_settings', $settings); + + // because of the way the settings form works, it saves a bogus variable. + // here we clean up after it + variable_del('product_transform'); +} + + +/** + * Build the form fields for product details. + * + * The returned form may be included in node edit form, or product tab. + */ +function _product_transform_node_form($node, $ptype) { + // If node is not yet a product, provide default values + if (!$node->ptype && !count($_POST)) { + $settings = _product_transform_get_settings($node->type); + if (count($settings['product_defaults']['values'])) { + foreach ($settings['product_defaults']['values'] as $key => $value) { + $node->$key = $value; + } + } + } + + // prime the form + $form = product_base_form_elements($node); + + if (!$node->nid) { + // when creating a new node, include a "make product" checkbox + if (isset($node->product_transform_make_product)) { + $make_product = $node->product_transform_make_product; + } + else { + $make_product = TRUE; + } + + $form['product_transform_make_product'] = + array('#type' => 'checkbox', + '#title' => t('Include this item in the product list.'), + '#default_value' => $make_product, + '#weight' => -99); + } + + $form['ptype'] = array('#type' => 'value', '#value' => $ptype); + // fields specific to this product type + $form = array_merge($form, (array)module_invoke($ptype, 'productapi', $node, 'form')); + + if (!$node->nid) { + // when creating a new node, price is only required if make_product is checked + unset($form['price']['#required']); + } + + return $form; +} + +/** + * Validate node form. + * + * Invokes product_form_validate() for both general validation, and + * product-type-specific validation. + */ +function product_transform_node_form_validate($form_id, $node, &$form, $ptype) { + if ($node->product_transform) { + $node_object = (object) $node; + product_form_validate($node_object); + } +} + +/** + * Build product tab on node view. This appears after a node has been saved. + * The pages shown here enable functionality that is difficult to include in + * the node form. Including deleting product settings and changing product + * type. + */ +function product_transform_to_product($nid) { + $node = node_load($nid); + $settings = _product_transform_get_settings($node->type); + $ptype = $node->ptype; + if (!$ptype && (count($settings['product_types']) == 1)) + $ptype = array_shift($settings['product_types']); + else if (arg(3) && in_array(arg(3), $settings['product_types'])) { + $ptype = arg(3); + } + + if ($ptype) { + $already_product = $node->ptype; + + $form = _product_transform_node_form($node, $ptype); + + foreach (array('nid', 'vid') as $key) { + $form[$key] = array('#type' => 'value', '#value' => $node->$key); + } + + // submit buttons + if ($already_product) { + $form['update'] = array('#type' => 'submit', '#value' => t('Update product'), '#weight' => 40); + $form['remove'] = array('#type' => 'submit', '#value' => t('Remove product'), '#weight' => 45); + } + else { + $form['create'] = array('#type' => 'submit', '#value' => t('Create product'), '#weight' => 40); + } + + $form['#validate']['product_transform_node_form_validate'] = + array($ptype); + return drupal_get_form('product_transform_to_product', $form); + } + else if (count($settings['product_types']) > 1) { + // Let user choose amoung several product types + return product_types_listing("node/$node->nid/product", + $settings['product_types']); + } +} + +/** + * Save data from product tab. + * + * Allows arbitrary node types to become products. Or, delete product + * information about a specific node. + */ +function product_transform_to_product_submit($form_id, $form_values) { + $op = $_POST['op']; + $edit = (object) $form_values; + + switch ($op) { + case t('Create product'): + case t('Update product'): + product_save($edit); + drupal_set_message(t('The product has been saved.')); + return "node/$edit->nid"; + break; + case t('Remove product'): + return "node/$edit->nid/product/delete"; + break; + } +} + +/** + * Callback to confirm deletion of product info from non-product nodes. + */ +function product_transform_to_product_delete() { + $node = node_load(arg(1)); + + $form['node'] = array('#type' => 'value', '#value' => $node); + + $output.= confirm_form( + 'product_transform_to_product_delete', + $form, + t('Are you sure that you want to delete the product information for %title', array('%title' => $node->title)), + "node/{$node->nid}/product", + t('The product information will be permenantly removed.'), + t('Delete Product'), + t('Cancel') + ); + return $output; +} + +/** + * Delete product information from non-product nodes. + */ +function product_transform_to_product_delete_submit($form_id, $form_values) { + if ($form_values['confirm']) { + product_delete($form_values['node'], true); + drupal_set_message(t('Removed the post from the product listings.')); + return "node/{$form_values['node']->nid}"; + } + return "node/{$form_values['node']->nid}/product_transform"; +}