nat.module

Tracking 6.x-2.x branch
  1. drupal
    1. 6 contributions/nat/nat.module

NAT - node auto term - is a helper module that automatically creates a term using the same title as a node.

@author Karthik Kumar / Zen [ http://drupal.org/user/21209 ]. @internal There are a number of cases to be considered (dev notes): o Term adds/updates/deletes: i.e. should this be a 2-way module? o Vocabulary deletes. o Dissociation of node type and vocabulary in nat_config - how should this be handled? o Filter handling for body/description fields. o Duplicate handling?

Features to be added: o Node deletes: Optionally delete child nodes associated via NAT. o Maintain hierarchy on unassociated vocabularies (on a best effort basis?)

Functions & methods

NameDescription
nat_form_alterImplementation of hook_form_alter().
nat_get_childrenRetrieve all all the children of a node via its NAT association. Note: taxonomy_select_nodes is a rather resource hungry function.
nat_get_nidsGets node IDs/nodes associated with a term.
nat_get_termRetrieve the first / single NAT term associated with a node optionally restricted by vocabulary.
nat_get_termsGets terms associated with a node.
nat_get_terms_by_vocabularyRetrieve the NAT terms associated with a node restricted by vocabulary.
nat_helpImplementation of hook_help().
nat_link_alterImplementation of hook_link_alter().
nat_menuImplementation of hook_menu().
nat_nodeapiImplementation of hook_nodeapi().
nat_permImplementation of hook_perm().
nat_set_configUpdate the NAT config to include node->vocabulary associations and related settings. Commonly used in .install files to register associations and save the admin some work.
nat_views_apiImplementation of hook_views_api().
_nat_add_termsAdd node titles as terms into the taxonomy system. @todo Ideas are welcome to allow retaining the hierarchy for vocabularies not present in the node form.
_nat_delete_associationDelete node-term associations from the NAT table.
_nat_delete_termsDelete associated terms from the taxonomy system. @todo Options to delete child nodes as well etc.
_nat_get_vocabulariesRetrieve all vocabularies.
_nat_save_associationSave node-term associations in the NAT table.
_nat_taxonomy_term_childrenGiven a taxonomy term, recursively list all its children.
_nat_update_termsUpdate saved node-terms.
_nat_variable_getReturn a NAT module variable.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * NAT - node auto term - is a helper module that automatically creates a
  5. * term using the same title as a node.
  6. *
  7. * @author Karthik Kumar / Zen [ http://drupal.org/user/21209 ].
  8. * @internal There are a number of cases to be considered (dev notes):
  9. * o Term adds/updates/deletes: i.e. should this be a 2-way module?
  10. * o Vocabulary deletes.
  11. * o Dissociation of node type and vocabulary in nat_config - how should this
  12. * be handled?
  13. * o Filter handling for body/description fields.
  14. * o Duplicate handling?
  15. *
  16. * Features to be added:
  17. * o Node deletes: Optionally delete child nodes associated via NAT.
  18. * o Maintain hierarchy on unassociated vocabularies (on a best effort basis?)
  19. */
  20. /**
  21. * Implementation of hook_help().
  22. */
  23. function nat_help($path, $arg) {
  24. switch ($path) {
  25. case 'admin/help#nat':
  26. return t('NAT - node auto term - is a helper module that automatically creates a term using the same title as a node.');
  27. }
  28. }
  29. /**
  30. * Implementation of hook_menu().
  31. */
  32. function nat_menu() {
  33. $items = array();
  34. $items['admin/settings/nat'] = array(
  35. 'title' => 'NAT',
  36. 'description' => 'Establish node - node relationships via the taxonomy module.',
  37. 'page callback' => 'drupal_get_form',
  38. 'page arguments' => array('nat_settings_form'),
  39. 'access arguments' => array('administer NAT configuration'),
  40. 'file' => 'nat.admin.inc'
  41. );
  42. $items['admin/settings/nat/settings'] = array(
  43. 'title' => 'Settings',
  44. 'page callback' => 'drupal_get_form',
  45. 'page arguments' => array('nat_settings_form'),
  46. 'access arguments' => array('administer NAT configuration'),
  47. 'type' => MENU_DEFAULT_LOCAL_TASK,
  48. 'file' => 'nat.admin.inc'
  49. );
  50. $items['admin/settings/nat/sync'] = array(
  51. 'title' => 'Sync',
  52. 'page callback' => 'drupal_get_form',
  53. 'page arguments' => array('nat_sync_form'),
  54. 'access arguments' => array('administer NAT configuration'),
  55. 'type' => MENU_LOCAL_TASK,
  56. 'file' => 'nat.admin.inc'
  57. );
  58. return $items;
  59. }
  60. /**
  61. * Implementation of hook_perm().
  62. */
  63. function nat_perm() {
  64. return array('administer NAT configuration');
  65. }
  66. /**
  67. * Implementation of hook_nodeapi().
  68. */
  69. function nat_nodeapi(&$node, $op, $teaser, $page) {
  70. $nat_config = _nat_variable_get();
  71. if (!isset($nat_config['types'][$node->type]) || empty($nat_config['types'][$node->type])) {
  72. return;
  73. }
  74. switch ($op) {
  75. case 'load':
  76. $node->nat = nat_get_terms($node->nid);
  77. break;
  78. case 'insert':
  79. // Add term(s).
  80. $terms = _nat_add_terms($node);
  81. // Save node-term association in the NAT table.
  82. _nat_save_association($node->nid, $terms);
  83. break;
  84. case 'update':
  85. // Ensure that this is a node form submission and not a direct node_save
  86. // operation. @see http://drupal.org/node/197532 and
  87. // http://drupal.org/node/188377 .
  88. if (isset($node->form_id)) {
  89. _nat_update_terms($node);
  90. }
  91. break;
  92. case 'delete':
  93. // Deleting the associated term when a node is deleted is optional.
  94. if (isset($nat_config['delete'][$node->type])) {
  95. _nat_delete_terms($node->nid);
  96. }
  97. // Delete node-term association from the NAT table.
  98. _nat_delete_association($node->nid);
  99. break;
  100. }
  101. }
  102. /**
  103. * Implementation of hook_form_alter().
  104. */
  105. function nat_form_alter(&$form, $form_state, $form_id) {
  106. if ($form['#id'] == 'node-form') {
  107. $config = _nat_variable_get();
  108. foreach ($config['types'] as $type => $associations) {
  109. if (count($associations) && $form_id == $type .'_node_form') {
  110. $nat_terms = array();
  111. // If this is a node update, remove this node's associated terms from
  112. // its associated vocabularies.
  113. // N.B. Free-tag vocabularies are unaffected by this.
  114. if (isset($form['#node']->nid)) {
  115. foreach ($form['#node']->nat as $tid => $term) {
  116. $nat_terms[$term->vid] = $tid;
  117. // Cull associated terms and their children from the taxonomy form.
  118. if (isset($form['taxonomy'])) {
  119. foreach ($form['taxonomy'] as $vid => $values) {
  120. if ($term->vid == $vid) {
  121. $children = _nat_taxonomy_term_children($tid, $vid);
  122. foreach ($values['#options'] as $id => $option) {
  123. // Discount the -None- entry.
  124. if (is_object($option)) {
  125. $option_id = array_pop(array_keys($option->option));
  126. if (in_array($option_id, $children)) {
  127. unset($form['taxonomy'][$vid]['#options'][$id]);
  128. }
  129. }
  130. }
  131. break;
  132. }
  133. }
  134. }
  135. }
  136. }
  137. // Related terms.
  138. if (isset($config['related'][$type])) {
  139. $form['nat'] = array(
  140. '#type' => 'fieldset',
  141. '#title' => t('Term information'),
  142. '#tree' => TRUE,
  143. '#weight' => -2,
  144. '#collapsible' => TRUE,
  145. '#collapsed' => FALSE
  146. );
  147. foreach ($associations as $vocabulary_id) {
  148. $vocabulary = taxonomy_vocabulary_load($vocabulary_id);
  149. if ($vocabulary->relations) {
  150. if (isset($nat_terms[$vocabulary_id])) {
  151. $default = array_keys(taxonomy_get_related($nat_terms[$vocabulary_id]));
  152. $exclude = array($nat_terms[$vocabulary_id]);
  153. }
  154. else {
  155. $default = $exclude = array();
  156. }
  157. $form['nat']['related'][$vocabulary_id] = _taxonomy_term_select(
  158. t('Related @terms', array('@terms' => $vocabulary->name)),
  159. 'relations',
  160. $default,
  161. $vocabulary_id,
  162. NULL,
  163. 1,
  164. t('<none>'),
  165. $exclude
  166. );
  167. }
  168. }
  169. // Synonyms: It is assumed that the synonyms for NAT terms in
  170. // different vocabularies are the same.
  171. if (!empty($nat_terms)) {
  172. $tid = array_pop($nat_terms);
  173. $default = implode("\n", taxonomy_get_synonyms($tid));
  174. }
  175. else {
  176. $default = '';
  177. }
  178. $form['nat']['synonyms'] = array(
  179. '#type' => 'textarea',
  180. '#title' => t('Synonyms'),
  181. '#default_value' => $default,
  182. '#description' => t('Synonyms of this term; one synonym per line.')
  183. );
  184. }
  185. // This can only match once, so we just break the foreach.
  186. break;
  187. }
  188. }
  189. }
  190. }
  191. /**
  192. * Implementation of hook_link_alter().
  193. */
  194. function nat_link_alter(&$links, $node) {
  195. $nat_config = _nat_variable_get();
  196. if (isset($nat_config['node_links'][$node->type])) {
  197. foreach ($links as $module => $link) {
  198. // Extract the term ID from the module indicator.
  199. $tid = str_replace('taxonomy_term_', '', $module, $count);
  200. // $link['title'] will be empty during node previews at which point
  201. // taxonomy links do not work.
  202. if ($count && $link['title']) {
  203. $nids = array_keys(nat_get_nids(array($tid), FALSE));
  204. if (!empty($nids)) {
  205. // Link back to the NAT node and not the taxonomy term page.
  206. $links[$module]['href'] = "node/$nids[0]";
  207. }
  208. }
  209. }
  210. }
  211. }
  212. /**
  213. * Implementation of hook_views_api().
  214. *
  215. * This one is used as the base to reduce errors when updating.
  216. */
  217. function nat_views_api() {
  218. return array(
  219. 'api' => 2,
  220. 'path' => drupal_get_path('module', 'nat') .'/includes'
  221. );
  222. }
  223. /**
  224. * Gets terms associated with a node.
  225. *
  226. * @param $nid
  227. * The nid of the node whose NAT terms are to be retrieved.
  228. * @return $return
  229. * An associative array of NAT-associated term objects.
  230. */
  231. function nat_get_terms($nid) {
  232. static $term_cache = NULL;
  233. if (isset($term_cache[$nid])) {
  234. return $term_cache[$nid];
  235. }
  236. $return = array();
  237. $result = db_query("SELECT td.* FROM {nat} n INNER JOIN {term_data} td USING (tid) WHERE n.nid = %d", $nid);
  238. while ($term = db_fetch_object($result)) {
  239. $return[$term->tid] = $term;
  240. }
  241. // Cache result.
  242. $term_cache[$nid] = $return;
  243. return $return;
  244. }
  245. /**
  246. * Retrieve the NAT terms associated with a node restricted by vocabulary.
  247. *
  248. * @param $nid
  249. * The node ID of the node whose NAT-terms are to be retrieved.
  250. * @param $vocabularies
  251. * An array of vocabulary IDs used to optionally retrict the retrieved terms
  252. * to a defined set of vocabularies.
  253. */
  254. function nat_get_terms_by_vocabulary($nid, $vocabularies = array()) {
  255. $terms = nat_get_terms($nid);
  256. if (!empty($vocabularies)) {
  257. foreach ($terms as $tid => $term) {
  258. if (!in_array($term->vid, $vocabularies)) {
  259. unset($terms[$tid]);
  260. }
  261. }
  262. }
  263. return $terms;
  264. }
  265. /**
  266. * Retrieve the first / single NAT term associated with a node optionally
  267. * restricted by vocabulary.
  268. *
  269. * @param $nid
  270. * The node ID of the node whose NAT-term is to be retrieved.
  271. * @param $vocabularies
  272. * An optional array of vocabulary IDs used to optionally retrict the
  273. * retrieved term to a defined set of vocabularies.
  274. */
  275. function nat_get_term($nid, $vocabularies = array()) {
  276. $terms = empty($vocabularies) ? nat_get_terms($nid) : nat_get_terms_by_vocabulary($nid, $vocabularies);
  277. return array_shift($terms);
  278. }
  279. /**
  280. * Retrieve all all the children of a node via its NAT association.
  281. * Note: taxonomy_select_nodes is a rather resource hungry function.
  282. *
  283. * @param $nid
  284. * The node ID of the node whose child nodes are to be retrieved.
  285. * @param $types
  286. * Unknown.
  287. * @param $vocabularies
  288. * Retrict children to nodes categorised using provided vocabularies.
  289. * @return
  290. * The resource identifier returned by taxonomy_select_nodes.
  291. */
  292. function nat_get_children($nid, $types = array(), $vocabularies = array()) {
  293. $terms = nat_get_terms_by_vocabulary($nid, $vocabularies);
  294. $tids = array_keys($terms);
  295. return taxonomy_select_nodes($tids);
  296. }
  297. /**
  298. * Gets node IDs/nodes associated with a term.
  299. *
  300. * @param $tids
  301. * An array of term IDs whose associated nodes are to be retrived.
  302. * @param $get_nodes
  303. * A boolean indicating if node_load operations are to be performed on the
  304. * associated nodes.
  305. * @return $return
  306. * An associative array of (nid => node) or (nid => title) depending on the
  307. * value of $get_nodes.
  308. */
  309. function nat_get_nids($tids, $get_nodes = FALSE) {
  310. static $nid_cache = NULL;
  311. static $node_cache = NULL;
  312. $return = array();
  313. // Keep processing to a minimum for empty tid arrays.
  314. if (!empty($tids)) {
  315. // Sort tid array to ensure that the cache_string never suffers from order
  316. // issues.
  317. sort($tids);
  318. $cache_string = implode('+', $tids);
  319. if ($get_nodes) {
  320. if (isset($node_cache[$cache_string])) {
  321. return $node_cache[$cache_string];
  322. }
  323. elseif (isset($nid_cache[$cache_string])) {
  324. // If the nid cache stores the same string, node_load() each nid and
  325. // return them.
  326. $return = array();
  327. foreach (array_keys($nid_cache[$cache_string]) as $nid) {
  328. $return[$nid] = node_load($nid);
  329. }
  330. $node_cache[$cache_string] = $return;
  331. return $return;
  332. }
  333. }
  334. else {
  335. if (isset($nid_cache[$cache_string])) {
  336. return $nid_cache[$cache_string];
  337. }
  338. elseif (isset($node_cache[$cache_string])) {
  339. // If the node cache stores the same string, retrieve only the nids and
  340. // return them.
  341. foreach ($node_cache[$cache_string] as $nid => $node) {
  342. $return[$nid] = $node->name;
  343. }
  344. // Cache extracted results.
  345. $nid_cache[$cache_string] = $return;
  346. return $return;
  347. }
  348. }
  349. // Results have not been cached.
  350. $result = db_query("SELECT n.nid, t.name FROM {nat} n INNER JOIN {term_data} t USING (tid) WHERE n.tid IN (". db_placeholders($tids) .")", $tids);
  351. while ($node = db_fetch_object($result)) {
  352. if ($get_nodes) {
  353. $return[$node->nid] = node_load($node->nid);
  354. }
  355. else {
  356. $return[$node->nid] = $node->name;
  357. }
  358. }
  359. if ($get_nodes) {
  360. $node_cache[$cache_string] = $return;
  361. }
  362. else {
  363. $nid_cache[$cache_string] = $return;
  364. }
  365. }
  366. return $return;
  367. }
  368. /**
  369. * Update the NAT config to include node->vocabulary associations and related
  370. * settings. Commonly used in .install files to register associations and save
  371. * the admin some work.
  372. *
  373. * @param $type
  374. * The node type.
  375. * @param $vids
  376. * Array of vocabulary IDs that the above node type is to be associated with
  377. * via NAT.
  378. * @param $delete
  379. * Boolean to indicate if associated term should be deleted when a node is
  380. * deleted.
  381. * @param $links
  382. * Boolean to indicate if links to NAT terms should point to the associated
  383. * nodes instead.
  384. * @param $body
  385. * Boolean to indicated if the node body should be in sync with the term
  386. * description field.
  387. * @param $related
  388. * Boolean to indicate if related terms and synonyms can be set during node
  389. * creation.
  390. */
  391. function nat_set_config($type, $vids, $delete = TRUE, $links = TRUE, $body = FALSE, $related = FALSE) {
  392. $nat_config = _nat_variable_get();
  393. if (!isset($nat_config['types'][$type])) {
  394. $nat_config['types'][$type] = array();
  395. }
  396. foreach ($vids as $vid) {
  397. $nat_config['types'][$type][$vid] = $vid;
  398. }
  399. if ($delete) {
  400. $nat_config['delete'][$type] = TRUE;
  401. }
  402. if ($links) {
  403. $nat_config['links'][$type] = TRUE;
  404. }
  405. if ($body) {
  406. $nat_config['body'][$type] = TRUE;
  407. }
  408. if ($related) {
  409. $nat_config['related'][$type] = TRUE;
  410. }
  411. variable_set('nat_config', $nat_config);
  412. }
  413. /**
  414. * Retrieve all vocabularies.
  415. *
  416. * @return $vocabularies
  417. * An associative array of vocabulary IDs to vocabulary names.
  418. */
  419. function _nat_get_vocabularies() {
  420. $vocabularies = taxonomy_get_vocabularies();
  421. foreach ($vocabularies as $id => $vocabulary) {
  422. $vocabularies[$id] = check_plain($vocabulary->name);
  423. }
  424. return $vocabularies;
  425. }
  426. /**
  427. * Add node titles as terms into the taxonomy system.
  428. * @todo Ideas are welcome to allow retaining the hierarchy for vocabularies not
  429. * present in the node form.
  430. *
  431. * @param Object $node
  432. * The node object to associate and update.
  433. * @param Array $vids
  434. * An array of vocabulary IDs to restrict associations to; useful for
  435. * operations such as NAT sync ...
  436. *
  437. * @return Array $tids
  438. * An array of term objects.
  439. */
  440. function _nat_add_terms($node, $vids = array()) {
  441. $nat_config = _nat_variable_get();
  442. $edit = array(
  443. 'name' => $node->title,
  444. 'description' => isset($nat_config['body'][$node->type]) ? $node->body : '',
  445. 'weight' => 0
  446. );
  447. $tids = array();
  448. $hierarchy = isset($node->taxonomy) ? $node->taxonomy : array();
  449. $vids = empty($vids) ? $nat_config['types'][$node->type] : $vids;
  450. foreach ($vids as $vid) {
  451. // $edit is passed by reference and 'tid' is set with the tid of the new
  452. // term.
  453. unset($edit['tid']);
  454. $edit['vid'] = $vid;
  455. // Save hierarchy for vocabularies also present in the node form.
  456. if (isset($hierarchy[$vid])) {
  457. $edit['parent'] = $hierarchy[$vid];
  458. }
  459. else {
  460. $edit['parent'] = array();
  461. }
  462. $edit['relations'] = isset($node->nat) && isset($node->nat['related'][$vid]) ? $node->nat['related'][$vid] : array();
  463. $edit['synonyms'] = isset($node->nat) ? $node->nat['synonyms'] : '';
  464. taxonomy_save_term($edit);
  465. $tids[] = $edit;
  466. }
  467. return $tids;
  468. }
  469. /**
  470. * Update saved node-terms.
  471. *
  472. * @param Object $node
  473. * The node object to associate and update.
  474. */
  475. function _nat_update_terms($node) {
  476. $nat_config = _nat_variable_get();
  477. $edit = array('name' => $node->title);
  478. $hierarchy = isset($node->taxonomy) ? $node->taxonomy : array();
  479. $terms = nat_get_terms($node->nid);
  480. foreach ($terms as $term) {
  481. $edit['tid'] = $term->tid;
  482. $edit['vid'] = $term->vid;
  483. $edit['description'] = isset($nat_config['body'][$node->type]) ? $node->body : $term->description;
  484. $edit['weight'] = $term->weight;
  485. $edit['parent'] = isset($hierarchy[$term->vid]) ? $hierarchy[$term->vid] : array();
  486. // If $node->nat is set, then so is $nat_config['related'][$node->type].
  487. if (isset($node->nat)) {
  488. $edit['relations'] = isset($node->nat['related'][$term->vid]) ? $node->nat['related'][$term->vid] : array();
  489. $edit['synonyms'] = $node->nat['synonyms'];
  490. }
  491. taxonomy_save_term($edit);
  492. }
  493. }
  494. /**
  495. * Delete associated terms from the taxonomy system.
  496. * @todo Options to delete child nodes as well etc.
  497. *
  498. * @param $nid
  499. * Node ID of the node whose NAT-terms are to be deleted.
  500. */
  501. function _nat_delete_terms($nid) {
  502. $terms = nat_get_terms($nid);
  503. foreach ($terms as $term) {
  504. taxonomy_del_term($term->tid);
  505. }
  506. }
  507. /**
  508. * Save node-term associations in the NAT table.
  509. *
  510. * @param Integer $nid
  511. * Node ID of the node.
  512. * @param Array $terms
  513. * NAT-term objects of the above node.
  514. */
  515. function _nat_save_association($nid, $terms) {
  516. foreach ($terms as $term) {
  517. $term['nid'] = $nid;
  518. drupal_write_record('nat', $term);
  519. }
  520. }
  521. /**
  522. * Delete node-term associations from the NAT table.
  523. *
  524. * @param $nid
  525. * Node ID of the node which is to be removed from the NAT table.
  526. */
  527. function _nat_delete_association($nid) {
  528. db_query("DELETE FROM {nat} WHERE nid = %d", $nid);
  529. }
  530. /**
  531. * Return a NAT module variable.
  532. *
  533. * @param $name
  534. * The name of the variable to retrieve.
  535. * @return
  536. * The value of the variable requested.
  537. */
  538. function _nat_variable_get($name = NULL) {
  539. static $variables = array();
  540. if (empty($variables)) {
  541. $defaults = array(
  542. 'types' => array(),
  543. 'body' => array(),
  544. 'delete' => array(),
  545. 'related' => array(),
  546. 'node_links' => array()
  547. );
  548. $variables = variable_get('nat_config', array());
  549. $variables = array_merge($defaults, $variables);
  550. }
  551. return $name ? $variables[$name] : $variables;
  552. }
  553. /**
  554. * Given a taxonomy term, recursively list all its children.
  555. *
  556. * @param $tid
  557. * Term ID.
  558. * @param $vid
  559. * Vocabulary ID.
  560. * @return
  561. * An array of term IDs including the parent term.
  562. */
  563. function _nat_taxonomy_term_children($tid, $vid) {
  564. $tids = array($tid);
  565. $terms = taxonomy_get_children($tid, $vid);
  566. foreach ($terms as $term) {
  567. $tids = array_merge($tids, _nat_taxonomy_term_children($term->tid, $vid));
  568. }
  569. return $tids;
  570. }