uc_catalog.module 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. <?php
  2. /**
  3. * @file
  4. * Ubercart Catalog module.
  5. *
  6. * Provides classification and navigation product nodes using taxonomy. When
  7. * installed, this module creates a vocabulary named "Product Catalog" and
  8. * stores the vocabulary id for future use. The user is responsible for
  9. * maintaining the terms in the taxonomy, though the Catalog will find products
  10. * not listed in it.
  11. */
  12. /**
  13. * Implements hook_help().
  14. */
  15. function uc_catalog_help($path, $arg) {
  16. switch ($path) {
  17. case 'admin/store/products/orphans':
  18. return '<p>' . t('Orphaned products are products that you have created but not yet assigned to a category in your product catalog. All such products will appear as links below that you can follow to edit the product listings to assign them to categories.') . '</p>';
  19. case 'admin/store/settings/catalog':
  20. if (!module_exists('views_ui')) {
  21. return '<p>' . t('<a href="@modules">Enable the Views UI module</a> to edit the catalog display.', array('@modules' => url('admin/modules', array('fragment' => 'edit-modules-views')))) . '</p>';
  22. }
  23. break;
  24. }
  25. }
  26. /**
  27. * Implements hook_menu().
  28. */
  29. function uc_catalog_menu() {
  30. global $user;
  31. $items = array();
  32. $items['catalog'] = array(
  33. 'title' => 'Catalog',
  34. 'page callback' => 'uc_catalog_browse',
  35. 'access arguments' => array('view catalog'),
  36. 'type' => MENU_SUGGESTED_ITEM,
  37. );
  38. $items['admin/store/settings/catalog'] = array(
  39. 'title' => 'Catalog',
  40. 'description' => 'Configure the product catalog pages.',
  41. 'page callback' => 'drupal_get_form',
  42. 'page arguments' => array('uc_catalog_settings_form'),
  43. 'access arguments' => array('administer catalog'),
  44. 'file' => 'uc_catalog.admin.inc',
  45. );
  46. if (module_exists('views_ui')) {
  47. $items['admin/store/settings/catalog/edit'] = array(
  48. 'title' => 'Edit catalog display',
  49. 'description' => 'Configure the catalog display in Views.',
  50. 'page callback' => 'drupal_goto',
  51. 'page arguments' => array('admin/structure/views/view/uc_catalog/edit'),
  52. 'access callback' => 'user_access',
  53. 'access arguments' => array('administer views'),
  54. 'type' => MENU_LOCAL_ACTION,
  55. );
  56. }
  57. $items['admin/store/settings/catalog/repair'] = array(
  58. 'title' => 'Repair catalog field',
  59. 'description' => 'Ensures the catalog taxonomy field is attached to product content types.',
  60. 'page callback' => 'uc_catalog_repair_field',
  61. 'access arguments' => array('administer catalog'),
  62. 'file' => 'uc_catalog.admin.inc',
  63. );
  64. $items['admin/store/products/orphans'] = array(
  65. 'title' => 'Find orphaned products',
  66. 'description' => 'Find products that have not been categorized.',
  67. 'page callback' => 'uc_catalog_orphaned_products',
  68. 'access arguments' => array('administer catalog'),
  69. 'weight' => -4,
  70. 'file' => 'uc_catalog.admin.inc',
  71. );
  72. return $items;
  73. }
  74. /**
  75. * Implements hook_permission().
  76. */
  77. function uc_catalog_permission() {
  78. return array(
  79. 'administer catalog' => array(
  80. 'title' => t('Administer catalog'),
  81. ),
  82. 'view catalog' => array(
  83. 'title' => t('View catalog'),
  84. ),
  85. );
  86. }
  87. /**
  88. * Implements hook_image_default_styles().
  89. */
  90. function uc_catalog_image_default_styles() {
  91. $styles = array();
  92. $styles['uc_category'] = array(
  93. 'effects' => array(
  94. array(
  95. 'name' => 'image_scale',
  96. 'data' => array(
  97. 'width' => '100',
  98. 'height' => '100',
  99. 'upscale' => 0,
  100. ),
  101. 'weight' => '0',
  102. ),
  103. ),
  104. );
  105. return $styles;
  106. }
  107. /**
  108. * Implements hook_theme().
  109. */
  110. function uc_catalog_theme() {
  111. return array(
  112. 'uc_catalog_block' => array(
  113. 'variables' => array('menu_tree' => NULL),
  114. 'file' => 'uc_catalog.theme.inc',
  115. ),
  116. 'uc_catalog_item' => array(
  117. 'variables' => array(
  118. 'here' => NULL,
  119. 'link' => NULL,
  120. 'lis' => NULL,
  121. 'expand' => NULL,
  122. 'inpath' => NULL,
  123. 'count_children' => NULL,
  124. ),
  125. 'file' => 'uc_catalog.theme.inc',
  126. ),
  127. );
  128. }
  129. /**
  130. * Implements hook_node_view().
  131. */
  132. function uc_catalog_node_view($node, $view_mode) {
  133. static $parents = array();
  134. if (uc_product_is_product($node->type) && isset($node->taxonomy_catalog)) {
  135. if ($view_mode == 'full' && variable_get('uc_catalog_breadcrumb', TRUE)) {
  136. $crumbs = array();
  137. if (variable_get('site_frontpage', 'node') != 'catalog') {
  138. $crumbs[] = l(t('Home'), '');
  139. }
  140. if ($terms = field_get_items('node', $node, 'taxonomy_catalog')) {
  141. $crumbs[] = l(t('Catalog'), 'catalog');
  142. $used_tids = array();
  143. foreach ($terms as $term) {
  144. if (!isset($parents[$term['tid']])) {
  145. $parents[$term['tid']] = taxonomy_get_parents_all($term['tid']);
  146. }
  147. foreach (array_reverse($parents[$term['tid']]) as $parent) {
  148. if (!in_array($parent->tid, $used_tids)) {
  149. $crumbs[] = l($parent->name, uc_catalog_path($parent));
  150. $used_tids[] = $parent->tid;
  151. }
  152. }
  153. }
  154. }
  155. drupal_set_breadcrumb($crumbs);
  156. }
  157. }
  158. }
  159. /**
  160. * Implements hook_taxonomy_vocabulary_delete().
  161. */
  162. function uc_catalog_taxonomy_vocabulary_delete($vocabulary) {
  163. if ($vocabulary->vid == variable_get('uc_catalog_vid', 0)) {
  164. variable_del('uc_catalog_vid');
  165. }
  166. }
  167. /**
  168. * Implements hook_taxonomy_term_insert().
  169. */
  170. function uc_catalog_taxonomy_term_insert($term) {
  171. if (module_exists('pathauto')) {
  172. if ($term->name) {
  173. module_load_include('inc', 'uc_catalog', 'uc_catalog.pathauto');
  174. $count = _uc_catalog_pathauto_alias($term, 'insert');
  175. }
  176. }
  177. }
  178. /**
  179. * Implements hook_taxonomy_term_update().
  180. */
  181. function uc_catalog_taxonomy_term_update($term) {
  182. if (module_exists('pathauto')) {
  183. if ($term->name) {
  184. module_load_include('inc', 'uc_catalog', 'uc_catalog.pathauto');
  185. $count = _uc_catalog_pathauto_alias($term, 'update');
  186. }
  187. }
  188. }
  189. /**
  190. * Implements hook_taxonomy_term_delete().
  191. */
  192. function uc_catalog_taxonomy_term_delete($term) {
  193. path_delete(array('source' => uc_catalog_path($term)));
  194. }
  195. /**
  196. * Implements hook_preprocess_link().
  197. *
  198. * Rewrites taxonomy term links to point to the catalog.
  199. */
  200. function uc_catalog_preprocess_link(&$vars) {
  201. // Link back to the catalog and not the taxonomy term page.
  202. if (isset($vars['options']['entity_type']) && $vars['options']['entity_type'] == 'taxonomy_term') {
  203. $term = $vars['options']['entity'];
  204. if ($term->vid == variable_get('uc_catalog_vid', 0)) {
  205. $vars['path'] = uc_catalog_path($term);
  206. }
  207. }
  208. }
  209. /**
  210. * Implements hook_block_info().
  211. *
  212. * Displays a menu for navigating the "Product Catalog"
  213. */
  214. function uc_catalog_block_info() {
  215. $blocks['catalog'] = array(
  216. 'info' => t('Catalog'),
  217. 'cache' => DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE,
  218. );
  219. return $blocks;
  220. }
  221. /**
  222. * Implements hook_block_view().
  223. */
  224. function uc_catalog_block_view($delta = '') {
  225. if ($delta == 'catalog') {
  226. $block = array();
  227. if (user_access('view catalog')) {
  228. drupal_add_css(drupal_get_path('module', 'uc_catalog') . '/uc_catalog.css');
  229. // Get the vocabulary tree information.
  230. $vid = variable_get('uc_catalog_vid', 0);
  231. $tree = taxonomy_get_tree($vid);
  232. // Then convert it into an actual tree structure.
  233. $seq = 0;
  234. $menu_tree = new UcTreeNode();
  235. $level = array();
  236. $curr_depth = -1;
  237. foreach ($tree as $knot) {
  238. $seq++;
  239. $knot->sequence = $seq;
  240. $knothole = new UcTreeNode($knot);
  241. // Begin at the root of the tree and find the proper place.
  242. $menu_tree->add_child($knothole);
  243. }
  244. // Now, create a structured menu, separate from Drupal's menu.
  245. $content = theme('uc_catalog_block', array('menu_tree' => $menu_tree));
  246. $block = array('subject' => t('Catalog'), 'content' => $content);
  247. }
  248. return $block;
  249. }
  250. }
  251. /**
  252. * Implements hook_block_configure().
  253. *
  254. * Builds the settings form used by the catalog block.
  255. */
  256. function uc_catalog_block_configure($delta = '') {
  257. if ($delta == 'catalog') {
  258. $form['uc_catalog_block_title'] = array(
  259. '#type' => 'checkbox',
  260. '#title' => t('Make the block title a link to the top-level catalog page.'),
  261. '#default_value' => variable_get('uc_catalog_block_title', FALSE),
  262. );
  263. $form['uc_catalog_expand_categories'] = array(
  264. '#type' => 'checkbox',
  265. '#title' => t('Always expand categories.'),
  266. '#default_value' => variable_get('uc_catalog_expand_categories', FALSE),
  267. );
  268. $form['uc_catalog_block_nodecount'] = array(
  269. '#type' => 'checkbox',
  270. '#title' => t('Display product counts.'),
  271. '#default_value' => variable_get('uc_catalog_block_nodecount', TRUE),
  272. );
  273. return $form;
  274. }
  275. }
  276. /**
  277. * Implements hook_block_save().
  278. *
  279. * Saves the catalog block settings.
  280. */
  281. function uc_catalog_block_save($delta = '', $edit = array()) {
  282. if ($delta == 'catalog') {
  283. variable_set('uc_catalog_block_title', $edit['uc_catalog_block_title']);
  284. variable_set('uc_catalog_expand_categories', $edit['uc_catalog_expand_categories']);
  285. variable_set('uc_catalog_block_nodecount', $edit['uc_catalog_block_nodecount']);
  286. }
  287. }
  288. /**
  289. * Preprocesses the catalog block output.
  290. */
  291. function uc_catalog_preprocess_block(&$variables) {
  292. $block = &$variables['block'];
  293. if ($block->module == 'uc_catalog' && $block->delta == 'catalog' && $block->subject && variable_get('uc_catalog_block_title', FALSE)) {
  294. $block->subject = l($block->subject, 'catalog');
  295. }
  296. }
  297. /**
  298. * Implements hooks_views_api().
  299. */
  300. function uc_catalog_views_api() {
  301. return array(
  302. 'api' => '2.0',
  303. 'path' => drupal_get_path('module', 'uc_catalog') . '/views',
  304. );
  305. }
  306. /**
  307. * Implements hook_uc_store_status().
  308. *
  309. * Provides status information about the "Product Catalog" and products not
  310. * listed in the catalog.
  311. */
  312. function uc_catalog_uc_store_status() {
  313. $field = field_info_field('taxonomy_catalog');
  314. if (!$field) {
  315. return array(array(
  316. 'status' => 'error',
  317. 'title' => t('Catalog field'),
  318. 'desc' => t('The catalog taxonomy reference field is missing. <a href="!url">Click here to create it</a>.', array('!url' => url('admin/store/settings/catalog/repair'))),
  319. ));
  320. }
  321. $statuses = array();
  322. $cat_id = variable_get('uc_catalog_vid', 0);
  323. $catalog = taxonomy_vocabulary_load($cat_id);
  324. if ($catalog) {
  325. // Don't display a status if the taxonomy_index table has no data.
  326. if (variable_get('taxonomy_maintain_index_table', TRUE)) {
  327. $statuses[] = array('status' => 'ok', 'title' => t('Catalog vocabulary'),
  328. 'desc' => t('Vocabulary !name has been identified as the Ubercart catalog.', array('!name' => l($catalog->name, 'admin/structure/taxonomy/' . $catalog->machine_name)))
  329. );
  330. $product_types = uc_product_types();
  331. $types = array_intersect($product_types, $field['bundles']['node']);
  332. $excluded = 0;
  333. $result = db_query("SELECT COUNT(DISTINCT n.nid) FROM {node} n LEFT JOIN {taxonomy_index} ti ON n.nid = ti.nid LEFT JOIN {taxonomy_term_data} td ON ti.tid = td.tid WHERE n.type IN (:types) AND ti.tid IS NULL AND td.vid = :vid", array(':types' => $types, ':vid' => $catalog->vid));
  334. if ($excluded = $result->fetchField()) {
  335. $description = format_plural($excluded, 'There is 1 product not listed in the catalog.', 'There are @count products not listed in the catalog.')
  336. . t('Products are listed by assigning a category from the <a href="!cat_url">Product Catalog</a> vocabulary to them.', array('!cat_url' => url('admin/structure/taxonomy/' . $catalog->machine_name)));
  337. $terms = db_query("SELECT COUNT(1) FROM {taxonomy_term_data} WHERE vid = :vid", array(':vid' => $catalog->vid))->fetchField();
  338. if ($terms) {
  339. $description .= ' ' . l(t('Find orphaned products here.'), 'admin/store/products/orphans');
  340. }
  341. else {
  342. $description .= ' ' . l(t('Add terms for the products to inhabit.'), 'admin/structure/taxonomy/' . $catalog->machine_name . '/add/term');
  343. }
  344. $statuses[] = array(
  345. 'status' => 'warning',
  346. 'title' => t('Unlisted products'),
  347. 'desc' => $description,
  348. );
  349. }
  350. }
  351. }
  352. else {
  353. $statuses[] = array(
  354. 'status' => 'error',
  355. 'title' => t('Catalog vocabulary'),
  356. 'desc' => t('No vocabulary has been recognized as the Ubercart catalog. Choose one on <a href="!admin_catalog">this page</a> or add one on the <a href="!admin_vocab">taxonomy admin page</a> first, if needed.', array('!admin_catalog' => url('admin/store/settings/catalog'), '!admin_vocab' => url('admin/structure/taxonomy'))),
  357. );
  358. }
  359. return $statuses;
  360. }
  361. /**
  362. * Implements hook_uc_product_class().
  363. *
  364. * Adds product node types to the catalog vocabulary as they are created.
  365. */
  366. function uc_catalog_uc_product_class($type, $op) {
  367. if ($op == 'insert') {
  368. uc_catalog_add_node_type($type);
  369. }
  370. }
  371. /**
  372. * Shows the catalog page of the given category.
  373. */
  374. function uc_catalog_browse($tid = 0) {
  375. $build = array();
  376. if ($terms = views_get_view('uc_catalog_terms')) {
  377. $build['terms'] = array(
  378. '#markup' => $terms->preview('default', array($tid)),
  379. );
  380. }
  381. if ($products = views_get_view('uc_catalog')) {
  382. $display = variable_get('uc_catalog_display', 'catalog');
  383. // Force the breadcrumb path to this page.
  384. $products->override_path = 'catalog';
  385. $build['products'] = array(
  386. '#markup' => $products->execute_display($display, array($tid)),
  387. );
  388. }
  389. return $build;
  390. }
  391. /**
  392. * Emulates Drupal's menu system, but based around the catalog taxonomy.
  393. *
  394. * @param $branch
  395. * A treeNode object. Determines if the URL points to itself, or possibly one
  396. * of its children, if present. If the URL points to itself or one of its
  397. * products, it displays its name and expands to show its children, otherwise
  398. * displays a link and a count of the products in it. If the URL points to
  399. * one of its children, it still displays a link and product count, but must
  400. * still be expanded. Otherwise, it is collapsed and a link.
  401. *
  402. * @return
  403. * An array whose first element is true if the treeNode is in hierarchy of
  404. * the URL path. The second element is the HTML of the list item of itself
  405. * and its children.
  406. */
  407. function _uc_catalog_navigation($branch) {
  408. static $terms;
  409. static $breadcrumb;
  410. static $types;
  411. if (empty($types)) {
  412. $types = uc_product_types();
  413. }
  414. $query = new EntityFieldQuery();
  415. $query->entityCondition('entity_type', 'node')
  416. ->entityCondition('bundle', $types)
  417. ->propertyCondition('status', 1) // Don't include unpublished products.
  418. ->fieldCondition('taxonomy_catalog', 'tid', $branch->tid)
  419. ->count();
  420. $num = $query->execute();
  421. $branch_path = uc_catalog_path($branch);
  422. if (!isset($breadcrumb)) {
  423. $breadcrumb = drupal_get_breadcrumb();
  424. }
  425. $vid = variable_get('uc_catalog_vid', 0);
  426. if ($_GET['q'] == $branch_path) {
  427. // The URL points to this term.
  428. $here = TRUE;
  429. }
  430. else {
  431. $here = FALSE;
  432. }
  433. if (!isset($terms)) {
  434. $node = menu_get_object('node', 1);
  435. $terms = array();
  436. if ($node && $field_data = field_get_items('node', $node, 'taxonomy_catalog')) {
  437. foreach ($field_data as $term) {
  438. $terms[$term['tid']] = $term['tid'];
  439. }
  440. }
  441. }
  442. // Determine whether to expand menu item.
  443. if ((arg(0) == 'node' && array_key_exists($branch->tid, $terms))) {
  444. $inpath = FALSE;
  445. foreach ($breadcrumb as $link) {
  446. if (strpos(urldecode($link), drupal_get_path_alias($branch_path)) !== FALSE) {
  447. $inpath = TRUE;
  448. }
  449. }
  450. }
  451. else {
  452. $inpath = $here;
  453. }
  454. $lis = array();
  455. $expand = variable_get('uc_catalog_expand_categories', FALSE);
  456. if ($expand || count($branch->children)) {
  457. foreach ($branch->children as $twig) {
  458. // Expand if children are in the menu path. Capture their output.
  459. list($child_in_path, $lis[], $child_num) = _uc_catalog_navigation($twig);
  460. $num += $child_num;
  461. if ($child_in_path) {
  462. $inpath = $child_in_path;
  463. }
  464. }
  465. }
  466. // No nodes in category or descendants. Not in path and display nothing.
  467. if (!$num) {
  468. return array(FALSE, '', 0);
  469. }
  470. // Checks to see if node counts are desired in navigation.
  471. $num_text = '';
  472. if (variable_get('uc_catalog_block_nodecount', TRUE)) {
  473. $num_text = ' (' . $num . ')';
  474. }
  475. $link = l($branch->name . $num_text, $branch_path);
  476. $output = theme('uc_catalog_item', array(
  477. 'here' => $here,
  478. 'link' => $link,
  479. 'lis' => $lis,
  480. 'expand' => $expand,
  481. 'inpath' => $inpath,
  482. 'count_children' => count($branch->children),
  483. ));
  484. // Tell parent category your status, and pass on output.
  485. return array($inpath, $output, $num);
  486. }
  487. /**
  488. * Creates paths to the catalog from taxonomy term.
  489. */
  490. function uc_catalog_path($term) {
  491. return 'catalog/' . $term->tid;
  492. }
  493. /**
  494. * Adds a catalog taxonomy reference field to the specified node type.
  495. */
  496. function uc_catalog_add_node_type($type) {
  497. $vid = variable_get('uc_catalog_vid', 0);
  498. if (!$vid) {
  499. $vid = db_query("SELECT vid FROM {taxonomy_vocabulary} WHERE machine_name = 'catalog'")->fetchField();
  500. if (!$vid) {
  501. $vocabulary = new stdClass();
  502. $vocabulary->name = t('Catalog');
  503. $vocabulary->description = '';
  504. $vocabulary->hierarchy = 1;
  505. $vocabulary->module = 'uc_catalog';
  506. $vocabulary->machine_name = 'catalog';
  507. taxonomy_vocabulary_save($vocabulary);
  508. $vid = $vocabulary->vid;
  509. }
  510. variable_set('uc_catalog_vid', $vid);
  511. }
  512. $vocabulary = taxonomy_vocabulary_load($vid);
  513. $field = field_info_field('taxonomy_catalog');
  514. if (!$field) {
  515. $field = array(
  516. 'field_name' => 'taxonomy_catalog',
  517. 'type' => 'taxonomy_term_reference',
  518. 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
  519. 'settings' => array(
  520. 'allowed_values' => array(
  521. array(
  522. 'vocabulary' => $vocabulary->machine_name,
  523. 'parent' => 0,
  524. ),
  525. ),
  526. ),
  527. );
  528. field_create_field($field);
  529. }
  530. $instance = field_info_instance('node', 'taxonomy_catalog', $type);
  531. if (!$instance) {
  532. $instance = array(
  533. 'field_name' => 'taxonomy_catalog',
  534. 'entity_type' => 'node',
  535. 'bundle' => $type,
  536. 'label' => t('Catalog'),
  537. );
  538. field_create_instance($instance);
  539. }
  540. }
  541. /**
  542. * Sets up a default image field on the Catalog vocabulary.
  543. */
  544. function uc_catalog_add_image_field() {
  545. $field = field_info_field('uc_catalog_image');
  546. if (!$field) {
  547. $field = array(
  548. 'field_name' => 'uc_catalog_image',
  549. 'type' => 'image',
  550. );
  551. field_create_field($field);
  552. }
  553. $instance = field_info_instance('taxonomy_term', 'uc_catalog_image', 'catalog');
  554. // Only add the instance if it doesn't exist. Don't overwrite any changes.
  555. if (!$instance) {
  556. $label = t('Image');
  557. $instance = array(
  558. 'field_name' => 'uc_catalog_image',
  559. 'entity_type' => 'taxonomy_term',
  560. 'bundle' => 'catalog',
  561. 'label' => $label,
  562. 'widget' => array(
  563. 'type' => 'image_image',
  564. ),
  565. 'display' => array(
  566. 'full' => array(
  567. 'label' => 'hidden',
  568. 'type' => 'image',
  569. 'settings' => array(
  570. 'image_link' => 'content',
  571. 'image_style' => 'uc_category',
  572. ),
  573. ),
  574. ),
  575. );
  576. field_create_instance($instance);
  577. }
  578. }