taxonomy_manager.module 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121
  1. <?php
  2. /**
  3. *
  4. * @file
  5. * Taxonomy Manager
  6. *
  7. * Administration interface for managing taxonomy vocabularies
  8. *
  9. */
  10. // Default value for the tree pager. Value can be overridden with the variable
  11. // taxonomy_manager_pager_tree_page_size.
  12. define('TAXONOMY_MANAGER_PAGER_TREE_PAGE_SIZE_DEFAULT', 100);
  13. /**
  14. * Implements hook_menu().
  15. */
  16. function taxonomy_manager_menu() {
  17. $items['admin/structure/taxonomy_manager/voc'] = array(
  18. 'title' => 'Taxonomy Manager',
  19. 'description' => 'Administer vocabularies with the Taxonomy Manager',
  20. 'page callback' => 'taxonomy_manager_voc_list',
  21. 'access arguments' => array('administer taxonomy'),
  22. 'file' => 'taxonomy_manager.admin.inc',
  23. );
  24. $items['admin/structure/taxonomy_manager/voc/%taxonomy_vocabulary_machine_name'] = array(
  25. 'title callback' => 'taxonomy_admin_vocabulary_title_callback',
  26. 'title arguments' => array(4),
  27. 'page callback' => 'drupal_get_form',
  28. 'page arguments' => array('taxonomy_manager_form', 4),
  29. 'access arguments' => array('administer taxonomy'),
  30. 'file' => 'taxonomy_manager.admin.inc',
  31. );
  32. $items['admin/structure/taxonomy_manager/childform'] = array(
  33. 'page callback' => 'taxonomy_manager_tree_build_child_form',
  34. 'access arguments' => array('administer taxonomy'),
  35. 'type' => MENU_CALLBACK,
  36. );
  37. $items['admin/structure/taxonomy_manager/weight'] = array(
  38. 'page callback' => 'taxonomy_manager_update_weights',
  39. 'access arguments' => array('administer taxonomy'),
  40. 'type' => MENU_CALLBACK,
  41. 'file' => 'taxonomy_manager.admin.inc',
  42. );
  43. $items['admin/structure/taxonomy_manager/siblingsform'] = array(
  44. 'page callback' => 'taxonomy_manager_tree_build_siblings_form',
  45. 'access arguments' => array('administer taxonomy'),
  46. 'type' => MENU_CALLBACK,
  47. );
  48. $items['admin/structure/taxonomy_manager/export'] = array(
  49. 'page callback' => 'taxonomy_manager_export',
  50. 'access arguments' => array('administer taxonomy'),
  51. 'type' => MENU_CALLBACK,
  52. 'file' => 'taxonomy_manager.admin.inc',
  53. );
  54. $items['admin/structure/taxonomy_manager/double-tree/%taxonomy_vocabulary_machine_name/%taxonomy_vocabulary_machine_name'] = array(
  55. 'title' => 'Taxonomy Manager',
  56. 'page callback' => 'drupal_get_form',
  57. 'page arguments' => array('taxonomy_manager_double_tree_form', 4, 5),
  58. 'access arguments' => array('administer taxonomy'),
  59. 'file' => 'taxonomy_manager.admin.inc',
  60. );
  61. $items['admin/config/user-interface/taxonomy-manager-settings'] = array(
  62. 'title' => 'Taxonomy Manager',
  63. 'description' => 'Advanced settings for the Taxonomy Manager',
  64. 'page callback' => 'drupal_get_form',
  65. 'page arguments' => array('taxonomy_manager_settings'),
  66. 'access arguments' => array('administer site configuration'),
  67. 'type' => MENU_NORMAL_ITEM,
  68. 'file' => 'taxonomy_manager.admin.inc',
  69. );
  70. $items['taxonomy_manager/autocomplete'] = array(
  71. 'title' => 'Taxonomy Manager Autocomplete',
  72. 'page callback' => 'taxonomy_manager_autocomplete_load',
  73. 'access arguments' => array('administer taxonomy'),
  74. 'type' => MENU_CALLBACK,
  75. 'file' => 'taxonomy_manager.admin.inc',
  76. );
  77. return $items;
  78. }
  79. /**
  80. * Implements hook_admin_menu_map().
  81. */
  82. function taxonomy_manager_admin_menu_map() {
  83. if (!user_access('administer taxonomy')) {
  84. return;
  85. }
  86. $map['admin/structure/taxonomy_manager/voc/%taxonomy_vocabulary_machine_name'] = array(
  87. 'parent' => 'admin/structure/taxonomy_manager/voc',
  88. 'arguments' => array(
  89. array('%taxonomy_vocabulary_machine_name' => array_keys(taxonomy_vocabulary_get_names())),
  90. ),
  91. );
  92. return $map;
  93. }
  94. /**
  95. * Implements hook_theme().
  96. */
  97. function taxonomy_manager_theme() {
  98. return array(
  99. 'taxonomy_manager_form' => array(
  100. 'render element' => 'form',
  101. ),
  102. 'taxonomy_manager_double_tree_form' => array(
  103. 'render element' => 'form',
  104. ),
  105. 'no_submit_button' => array(
  106. 'render element' => 'element',
  107. ),
  108. 'taxonomy_manager_image_button' => array(
  109. 'render element' => 'element',
  110. ),
  111. 'taxonomy_manager_tree' => array(
  112. 'render element' => 'element',
  113. ),
  114. 'taxonomy_manager_tree_elements' => array(
  115. 'render element' => 'element',
  116. ),
  117. 'taxonomy_manager_tree_checkbox' => array(
  118. 'render element' => 'element',
  119. ),
  120. 'taxonomy_manager_tree_radio' => array(
  121. 'render element' => 'element',
  122. ),
  123. 'taxonomy_manager_term_data_extra' => array(
  124. 'render element' => 'element',
  125. ),
  126. );
  127. }
  128. /**
  129. * Implements hook_help().
  130. */
  131. function taxonomy_manager_help($path, $arg) {
  132. switch ($path) {
  133. case 'admin/help#taxonomy_manager':
  134. $output = t('The Taxonomy Manager provides an additional interface for managing vocabularies of the taxonomy module. It\'s especially very useful for long sets of terms.
  135. The vocabulary is represented in a dynamic tree view.
  136. It supports operation like mass adding and deleting of terms, fast weight editing, moving of terms in hierarchies, merging of terms and fast term data editing.
  137. For more information on how to use please read the readme file included in the taxonomy_manager directory.');
  138. return $output;
  139. }
  140. }
  141. /**
  142. * function gets called by the taxonomy_manager_tree form type ('taxonomy_manager_'. form_id .'_operations')
  143. * return an form array with values to show next to every term value
  144. */
  145. function taxonomy_manager_taxonomy_manager_tree_operations($term) {
  146. $form = array();
  147. if (!variable_get('taxonomy_manager_disable_mouseover', 0)) {
  148. $module_path = drupal_get_path('module', 'taxonomy_manager') . '/';
  149. if (_taxonomy_manager_tree_term_children_count($term->tid) > 0) {
  150. $form['select_all'] = array('#weight' => -1, '#markup' => '<span class="select-all-children" title="' . t("Select all children") . '">&nbsp;&nbsp;&nbsp;&nbsp;</span>');
  151. }
  152. $form['up'] = array('#markup' => theme("image", array('path' => $module_path . "images/go-up-small.png", 'alt' => "go up", 'title' => t("Move up"), 'attributes' => array('class' => 'term-up'))));
  153. $form['down'] = array('#markup' => theme("image", array('path' => $module_path . "images/go-down-small.png", 'alt' => "go down", 'title' => t("Move down"), 'attributes' => array('class' => 'term-down'))));
  154. $link_img = theme("image", array('path' => $module_path . "images/link-small.png", 'alt' => "link to term page"));
  155. $link = l('&nbsp;' . $link_img, 'taxonomy/term/' . $term->tid, array('attributes' => array('rel' => 'tag', 'title' => t("Go to term page"), 'target' => '_blank'), 'html' => TRUE));
  156. $form['link'] = array('#markup' => $link, '#weight' => 10);
  157. }
  158. return $form;
  159. }
  160. /**
  161. * function gets called by taxonomy_manager_tree form type ('taxonomy_manager_'. form_id .'_link')
  162. * and returns an link, where to go, when a term gets clicked
  163. *
  164. * @param $vid vocabulary id
  165. */
  166. function taxonomy_manager_taxonomy_manager_tree_link($term) {
  167. return "admin/structure/taxonomy_manager/termdata/" . $term->tid;
  168. }
  169. function taxonomy_manager_taxonomy2_manager_tree_operations($term) {
  170. return taxonomy_manager_taxonomy_manager_tree_operations($term);
  171. }
  172. function taxonomy_manager_taxonomy2_manager_tree_link($term) {
  173. return taxonomy_manager_taxonomy_manager_tree_link($term);
  174. }
  175. /******************************************
  176. * TAXONOMY TREE FORM ELEMENT DEFINITION
  177. *
  178. * how to use:
  179. * $form['name'] = array(
  180. * '#type' => 'taxonomy_manager_tree',
  181. * '#vid' => $vid,
  182. * );
  183. *
  184. * additional parameter:
  185. * #pager: TRUE / FALSE,
  186. * whether to use pagers (drupal pager, load of nested children, load of siblings)
  187. * or to load the whole tree on page generation
  188. * #parent: only children on this parent will be loaded
  189. * #terms_to_expand: loads and opens the first path of given term ids
  190. * #siblings_page: current page for loading pf next siblings, internal use
  191. * #default_value: an array of term ids, which get selected by default
  192. * #render_whole_tree: set this option to TRUE, if you have defined a parent for the tree and you want
  193. * the the tree is fully rendered
  194. * #add_term_info: if TRUE, hidden form values with the term id and weight are going to be added
  195. * #expand_all: if TRUE, all elements are going to be expanded by default
  196. * #multiple: if TRUE the tree will contain checkboxes, otherwise radio buttons
  197. * #tree_is_required: use #tree_is_required instead of #required if you are using the tree within an other
  198. * element and don't want that both are internally required, because it might cause that
  199. * error messages are shown twice (see content_taxonomy_tree)
  200. * #language lang code if i18n is enabled and multilingual vocabulary
  201. *
  202. * defining term operations:
  203. * to add values (operations,..) to each term, add a function, which return a form array
  204. * 'taxonomy_manager_'. $tree_form_id .'_operations'
  205. *
  206. * how to retrieve selected values:
  207. * selected terms ids are available in validate / submit function in
  208. * $form_values['name']['selected_terms'];
  209. *
  210. ******************************************/
  211. /**
  212. * Implements hook_elements().
  213. */
  214. function taxonomy_manager_element_info() {
  215. $type['taxonomy_manager_tree'] = array(
  216. '#input' => TRUE,
  217. '#process' => array('taxonomy_manager_tree_process_elements'),
  218. '#tree' => TRUE,
  219. '#element_validate' => array('taxonomy_manager_tree_validate'),
  220. '#theme' => 'taxonomy_manager_tree',
  221. '#parent' => 0,
  222. '#siblings_page' => 0,
  223. '#operations' => "",
  224. '#default_value' => array(),
  225. '#multiple' => TRUE,
  226. '#add_term_info' => TRUE,
  227. '#required' => FALSE,
  228. '#expand_all' => FALSE,
  229. '#render_whole_tree' => FALSE,
  230. '#search_string' => '',
  231. '#terms_to_expand' => array(),
  232. '#terms_to_highlight' => array(),
  233. '#language' => NULL,
  234. '#pager' => FALSE,
  235. );
  236. return $type;
  237. }
  238. /**
  239. * Processes the tree form element
  240. *
  241. * @param $element
  242. * @return the tree element
  243. */
  244. function taxonomy_manager_tree_process_elements($element) {
  245. global $_taxonomy_manager_existing_ids; //TEMP: seems like this functions gets called twice in preview and cause problem because of adding the settings to js twice
  246. $_taxonomy_manager_existing_ids = is_array($_taxonomy_manager_existing_ids) ? $_taxonomy_manager_existing_ids : array();
  247. $module_path = drupal_get_path('module', 'taxonomy_manager') . '/';
  248. $id = drupal_clean_css_identifier(implode('-', $element['#parents']));
  249. $element['#id'] = $id;
  250. $vid = $element['#vid'];
  251. if (!$element['#siblings_page'] && !in_array($id, $_taxonomy_manager_existing_ids)) {
  252. $_taxonomy_manager_existing_ids[$id] = $id;
  253. drupal_add_css($module_path . 'css/taxonomy_manager.css');
  254. drupal_add_js($module_path . 'js/tree.js');
  255. drupal_add_js(array('siblingsForm' => array('url' => url('admin/structure/taxonomy_manager/siblingsform'), 'modulePath' => $module_path)), 'setting');
  256. drupal_add_js(array('childForm' => array('url' => url('admin/structure/taxonomy_manager/childform'), 'modulePath' => $module_path)), 'setting');
  257. drupal_add_js(array('taxonomytree' => array(array('id' => $id, 'vid' => $vid, 'parents' => $element['#parents']))), 'setting');
  258. }
  259. if (empty($element['#operations'])) {
  260. $opertions_callback = 'taxonomy_manager_' . implode('_', $element['#parents']) . '_operations';
  261. if (function_exists($opertions_callback)) {
  262. $element['#operations_callback'] = $opertions_callback;
  263. }
  264. }
  265. if (!isset($element['#link'])) {
  266. $link_callback = 'taxonomy_manager_' . implode('_', $element['#parents']) . '_link';
  267. if (function_exists($link_callback)) {
  268. $element['#link_callback'] = $link_callback;
  269. }
  270. }
  271. $element['#elements'] = array();
  272. $tree = _taxonomy_manager_tree_get_item($element['#vid'], $element['#parent'], $element['#pager'], $element['#siblings_page'], $element['#search_string'], $element['#language']);
  273. if (count($tree)) {
  274. if ($element['#pager'] && !($element['#parent'] || $element['#siblings_page'])) {
  275. $element['pager'] = array('#value' => theme('pager'));
  276. }
  277. $terms_to_expand = array();
  278. if (isset($element['#terms_to_expand'])) {
  279. // allow multiple terms to be expanded
  280. $requested_terms_to_expand = is_array($element['#terms_to_expand']) ? $element['#terms_to_expand'] : array($element['#terms_to_expand']);
  281. foreach ($requested_terms_to_expand as $term_to_expand) {
  282. // Multiple term version
  283. _taxonomy_manager_tree_get_first_path($term_to_expand, $tree, $terms_to_expand);
  284. }
  285. $terms_to_expand = taxonomy_manager_tree_get_terms_to_expand($tree, $requested_terms_to_expand, TRUE);
  286. }
  287. if (count($element['#default_value']) && !$element['#expand_all']) {
  288. $terms_to_expand = taxonomy_manager_tree_get_terms_to_expand($tree, $element['#default_value'], $element['#multiple']);
  289. }
  290. if (!empty($element['#language'])) {
  291. $element['#elements']['language'] = array('#type' => 'hidden', '#value' => $element['#language'], '#attributes' => array('class' => 'tree-lang'));
  292. _taxonomy_manager_tree_element_set_params($element['#parents'], $element['#elements']);
  293. }
  294. if (!is_array($element['#terms_to_highlight'])) {
  295. $element['#terms_to_highlight'] = array($element['#terms_to_highlight']);
  296. }
  297. $index = 0;
  298. taxonomy_manager_tree_build_form($index, $tree, $element['#elements'], $element, $element['#parents'], $element['#siblings_page'], $element['#default_value'], $element['#multiple'], $terms_to_expand, $element['#terms_to_highlight']);
  299. }
  300. return $element;
  301. }
  302. /**
  303. * loads tree with terms (depending on various settings)
  304. *
  305. * @param $vid
  306. * @param $parent
  307. * @param $pager
  308. * @param $siblings_page
  309. * @return array with term elements
  310. */
  311. function _taxonomy_manager_tree_get_item($vid, $parent = 0, $pager = FALSE, $siblings_page = 0, $search_string = NULL, $language_code = NULL) {
  312. $tree = array();
  313. if (module_exists('i18n_taxonomy') && $language_code != "") {
  314. return _taxonomy_manager_tree_get_translated_item($vid, $parent, $pager, $siblings_page, $search_string, $language_code);
  315. }
  316. if ($pager) {
  317. if ($parent || $siblings_page) {
  318. $start = ($siblings_page-1) * variable_get('taxonomy_manager_pager_tree_page_size', TAXONOMY_MANAGER_PAGER_TREE_PAGE_SIZE_DEFAULT);
  319. $result = db_query_range("SELECT t.* FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} h ON t.tid = h.tid WHERE vid = :vid AND h.parent = :parent ORDER BY t.weight, t.name", $start, variable_get('taxonomy_manager_pager_tree_page_size', 100), array(':vid' => $vid, ':parent' => $parent));
  320. }
  321. else {
  322. $query = db_select('taxonomy_term_data', 't')->extend('PagerDefault');
  323. $query->fields('t');
  324. $table_alias = $query->join('taxonomy_term_hierarchy', 'h', 't.tid = h.tid AND h.parent = 0');
  325. $query->condition('t.vid', $vid)
  326. ->orderBy('t.weight', 'ASC')
  327. ->orderBy('t.name', 'ASC')
  328. ->limit(variable_get('taxonomy_manager_pager_tree_page_size', TAXONOMY_MANAGER_PAGER_TREE_PAGE_SIZE_DEFAULT));
  329. if ($search_string) {
  330. $query->condition('name', '%' . db_like($search_string) . '%', 'LIKE');
  331. }
  332. $result = $query->execute();
  333. }
  334. foreach ($result as $term) {
  335. $term->depth = 0;
  336. $tree[] = $term;
  337. }
  338. }
  339. else {
  340. $tree = taxonomy_get_tree($vid, $parent);
  341. }
  342. return $tree;
  343. }
  344. /**
  345. * loads translated tree with terms (depending on various settings)
  346. *
  347. * @param $vid
  348. * @param $parent
  349. * @param $pager
  350. * @param $siblings_page
  351. * @return array with term elements
  352. */
  353. function _taxonomy_manager_tree_get_translated_item($vid, $parent = 0, $pager = FALSE, $siblings_page = 0, $search_string = NULL, $language_code = NULL) {
  354. //TODO merge with function above
  355. $tree = array();
  356. if ($language_code == "no language") {
  357. $language_code = ""; //get terms where no language is specified
  358. }
  359. if ($pager) {
  360. if ($parent || $siblings_page) {
  361. $start = ($siblings_page-1) * variable_get('taxonomy_manager_pager_tree_page_size', TAXONOMY_MANAGER_PAGER_TREE_PAGE_SIZE_DEFAULT);
  362. $result = db_query_range("SELECT t.* FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} h ON t.tid = h.tid WHERE vid = :vid AND h.parent = :parent AND t.language = :language ORDER BY t.weight, t.name", $start, variable_get('taxonomy_manager_pager_tree_page_size', 50), array(':vid' => $vid, ':parent' => $parent, ':language' => $language_code));
  363. }
  364. else {
  365. $query = db_select('taxonomy_term_data', 't')->extend('PagerDefault');
  366. $query->fields('t');
  367. $table_alias = $query->join('taxonomy_term_hierarchy', 'h', 't.tid = h.tid AND h.parent = 0');
  368. $query->condition('t.vid', $vid)
  369. ->condition('t.language', $language_code)
  370. ->orderBy('t.weight', 'ASC')
  371. ->orderBy('t.name', 'ASC')
  372. ->limit(variable_get('taxonomy_manager_pager_tree_page_size', TAXONOMY_MANAGER_PAGER_TREE_PAGE_SIZE_DEFAULT));
  373. if ($search_string) {
  374. $query->condition('name', '%' . db_like($search_string) . '%', 'LIKE');
  375. }
  376. $result = $query->execute();
  377. }
  378. foreach ($result as $term) {
  379. $term->depth = 0;
  380. $tree[] = $term;
  381. }
  382. }
  383. else {
  384. $tree = i18n_taxonomy_get_tree($vid, $language_code, $parent);
  385. }
  386. return $tree;
  387. }
  388. /**
  389. * marks parent terms to expand if a child terms is selected by default
  390. */
  391. function taxonomy_manager_tree_get_terms_to_expand($tree, $default_values, $multiple) {
  392. $terms = array();
  393. foreach (array_reverse($tree) as $term) {
  394. if (in_array($term->tid, array_values($default_values)) || in_array($term->tid, $terms)) {
  395. if (isset($term->parents) && is_array($term->parents)) {
  396. foreach ($term->parents as $parent) {
  397. if ($parent) {
  398. $terms[$parent] = $parent;
  399. }
  400. if (!$multiple) {
  401. break;
  402. }
  403. }
  404. }
  405. }
  406. }
  407. return $terms;
  408. }
  409. /**
  410. * calculates a path to a certain term and merges it into the tree
  411. */
  412. function _taxonomy_manager_tree_get_first_path($tid, &$tree, &$terms_to_expand) {
  413. $path = array();
  414. $next_tid = $tid;
  415. $i = 0;
  416. while ($i < 100) { //prevent infinite loop if inconsistent hierarchy
  417. $parents = taxonomy_get_parents($next_tid);
  418. if (count($parents)) {
  419. // Takes first parent.
  420. $parent = array_shift($parents);
  421. $path[] = $parent;
  422. $next_tid = $parent->tid;
  423. if (taxonomy_manager_term_is_root($next_tid)) {
  424. break;
  425. }
  426. }
  427. else {
  428. break;
  429. }
  430. $i++;
  431. }
  432. $path = array_reverse($path);
  433. $path[] = taxonomy_term_load($tid);
  434. $root_term = $path[0];
  435. $root_term_index;
  436. // build a map of the path keyed on tid - helps in avoiding duplicates when merging multiple paths to the tree
  437. $term_map = array();
  438. if (count($path) > 1) {
  439. foreach ($tree as $index => $term) {
  440. $term_map[$term->tid] = $index;
  441. if ($term->tid == $root_term->tid) {
  442. $root_term_index = $index;
  443. }
  444. }
  445. reset($tree);
  446. }
  447. if (isset($root_term_index)) {
  448. $path_tree = taxonomy_manager_get_partial_tree($path);
  449. // build map of path tree keyed on tids
  450. $path_term_map = array();
  451. foreach ($path_tree as $index => $term) {
  452. $path_term_map[$term->tid] = $index;
  453. }
  454. reset($path_tree);
  455. // first find the set of new terms that we need to add - new terms should be contiguous within $path_tree
  456. $new_path_terms_map = array_diff_key($path_term_map, $term_map);
  457. if (!empty($new_path_terms_map)) {
  458. // something to add
  459. $insert_term_index = reset($new_path_terms_map);
  460. if ($insert_term_index > 0) {
  461. // use previous term as insertion point
  462. $previous_tid = $path_tree[$insert_term_index-1]->tid;
  463. $insertion_index = $term_map[$previous_tid];
  464. }
  465. else {
  466. // use root index as insertion point
  467. $insertion_index = $root_term_index;
  468. }
  469. // get the new terms to add from the path tree
  470. $new_path_tree = array_slice($path_tree, $insert_term_index, count($new_path_terms_map));
  471. // stick the new terms into the tree at the insertion point
  472. array_splice($tree, $insertion_index+1, 0, $new_path_tree);
  473. }
  474. }
  475. }
  476. /**
  477. * helper function to check whether a given term is a root term
  478. */
  479. function taxonomy_manager_term_is_root($tid) {
  480. $is_root = (bool) db_query_range("SELECT 1 FROM {taxonomy_term_hierarchy} h WHERE h.tid = :tid AND h.parent = 0", 0, 1, array(':tid' => $tid))->fetchField();
  481. if ($is_root) {
  482. return TRUE;
  483. }
  484. return FALSE;
  485. }
  486. /**
  487. * returns partial tree for a given path
  488. */
  489. function taxonomy_manager_get_partial_tree($path, $depth = 0) {
  490. $tree = array();
  491. $root_term = $path[$depth];
  492. $children = taxonomy_get_children($root_term->tid);
  493. if (isset($path[++$depth])) {
  494. $next_term = $path[$depth];
  495. }
  496. foreach ($children as $key => $child) {
  497. $child->depth = $depth;
  498. $child->parents = array(0 => $root_term->tid);
  499. $tree[] = $child;
  500. if (isset($next_term) && $child->tid == $next_term->tid) {
  501. $tree = array_merge($tree, taxonomy_manager_get_partial_tree($path, $depth));
  502. }
  503. }
  504. return $tree;
  505. }
  506. /**
  507. * recursive function for building nested form array
  508. * with checkboxes and weight forms for each term
  509. *
  510. * nested form array are allways appended to parent-form['children']
  511. *
  512. * @param $index current index in tree, start with 0
  513. * @param $tree of terms (generated by taxonomy_get_tree)
  514. * @return a form array
  515. */
  516. function taxonomy_manager_tree_build_form(&$index, $tree, &$form, $root_form, $parents = array(), $page = 0, $default_value = array(), $multiple = TRUE, $terms_to_expand = array(), $terms_to_highlight = array()) {
  517. $current_depth = $tree[$index]->depth;
  518. while ($index < count($tree) && $tree[$index]->depth >= $current_depth) {
  519. $term = $tree[$index];
  520. $attributes = array();
  521. $this_parents = $parents;
  522. $this_parents[] = $term->tid;
  523. $value = in_array($term->tid, $default_value) ? 1 : 0;
  524. if ($value && !$multiple) {
  525. // Find our direct parent
  526. $newindex = $index;
  527. while ($newindex >= 0 && $tree[$newindex]->depth >= $current_depth) {
  528. $newindex--;
  529. }
  530. if ($newindex >= 0) {
  531. $value = in_array($tree[$newindex]->tid, $terms_to_expand) ? 1 : 0;
  532. }
  533. }
  534. $form[$term->tid]['checkbox'] = array(
  535. '#type' => ($multiple) ? 'checkbox' : 'radio',
  536. '#title' => $term->name, // Escaping is done in theme wrappers.
  537. '#value' => $value,
  538. '#return_value' => $term->tid,
  539. '#required' => FALSE,
  540. '#theme_wrappers' => ($multiple) ? array('taxonomy_manager_tree_checkbox') : array('taxonomy_manager_tree_radio'),
  541. '#highlight' => in_array($term->tid, $terms_to_highlight) ? TRUE : FALSE,
  542. );
  543. $form[$term->tid]['#attributes'] = array();
  544. if (!empty($root_form['#link_callback'])) {
  545. $link_callback = $root_form['#link_callback'];
  546. if (function_exists($link_callback)) {
  547. $form[$term->tid]['checkbox']['#link'] = $link_callback($term);
  548. }
  549. }
  550. if ($root_form['#add_term_info']) {
  551. $form[$term->tid]['weight'] = array('#type' => 'hidden', '#value' => $term->weight, '#attributes' => array('class' => array('weight-form')));
  552. $form[$term->tid]['tid'] = array('#type' => 'hidden', '#value' => $term->tid, '#attributes' => array('class' => array('term-id')));
  553. $form[$term->tid]['checkbox']['#extra_info'] = taxonomy_manager_tree_term_extra_info($term);
  554. }
  555. if (!empty($root_form['#operations_callback'])) {
  556. $opertions_callback = $root_form['#operations_callback'];
  557. if (function_exists($opertions_callback)) {
  558. $form[$term->tid]['operations'] = $opertions_callback($term);
  559. }
  560. }
  561. if ($page) {
  562. if ($index == (variable_get('taxonomy_manager_pager_tree_page_size', TAXONOMY_MANAGER_PAGER_TREE_PAGE_SIZE_DEFAULT) - 1) && !isset($tree[$index+1])) {
  563. $form[$term->tid]['page'] = array(
  564. '#type' => 'hidden',
  565. '#value' => $page,
  566. '#attributes' => array('class' => 'page'),
  567. );
  568. $next_count = _taxonomy_manager_tree_get_next_siblings_count($term->vid, $page, $root_form['#parent']);
  569. $form[$term->tid]['next_count'] = array('#markup' => $next_count);
  570. $form[$term->tid]['#attributes']['class'][] = 'has-more-siblings';
  571. }
  572. }
  573. _taxonomy_manager_tree_element_set_params($this_parents, $form[$term->tid]);
  574. _taxonomy_manager_tree_term_set_class($form[$term->tid]['#attributes']['class'], $index, $tree, ($root_form['#expand_all'] || in_array($term->tid, $terms_to_expand)));
  575. $index++;
  576. if (isset($tree[$index]) && $tree[$index]->depth > $current_depth) {
  577. taxonomy_manager_tree_build_form($index, $tree, $form[$term->tid]['children'], $root_form, array_merge($this_parents, array('children')), $page, $default_value, $multiple, $terms_to_expand, $terms_to_highlight);
  578. }
  579. }
  580. }
  581. /**
  582. * adds #id and #name to all form elements
  583. *
  584. * @param $parents
  585. * @param $form
  586. */
  587. function _taxonomy_manager_tree_element_set_params($parents, &$form) {
  588. foreach (element_children($form) as $field_name) {
  589. $field_parents = array_merge($parents, array($field_name));
  590. $form[$field_name]['#tree'] = TRUE;
  591. $form[$field_name]['#post'] = array();
  592. $form[$field_name]['#parents'] = $field_parents;
  593. $form[$field_name]['#id'] = drupal_clean_css_identifier('edit- ' . implode('-', $field_parents));
  594. $form[$field_name]['#name'] = array_shift($field_parents) . '[' . implode('][', $field_parents) . ']';
  595. }
  596. }
  597. /**
  598. * calculates class type (expandable, lastExpandable) for current element
  599. *
  600. * @param $current_index in tree array
  601. * @param $tree array with terms
  602. */
  603. function _taxonomy_manager_tree_term_set_class(&$class, $current_index, $tree, $expand) {
  604. $term = $tree[$current_index];
  605. $next_index = ++$current_index;
  606. $next = isset($tree[$next_index]) ? $tree[$next_index] : NULL;
  607. $children = FALSE;
  608. if (!empty($next) && $next->depth > $term->depth) {
  609. $children = TRUE;
  610. }
  611. if ($children) {
  612. if (!empty($next->depth) && $next->depth == $term->depth) {
  613. $class[] = ($expand) ? 'collapsable' : 'expandable';
  614. }
  615. else {
  616. $class[] = ($expand) ? 'lastCollapsable' : 'lastExpandable';
  617. }
  618. }
  619. elseif (_taxonomy_manager_tree_term_children_count($term->tid) > 0) {
  620. $class[] = 'has-children';
  621. if ($current_index == count($tree)) {
  622. $class[] = 'lastExpandable';
  623. }
  624. else {
  625. $class[] = 'expandable';
  626. }
  627. }
  628. elseif ((count($tree) == $current_index) || (!empty($next) && $term->depth > $next->depth)) {
  629. $class[] = 'last';
  630. }
  631. return $class;
  632. }
  633. /**
  634. * @param $tid
  635. * @return children count
  636. */
  637. function _taxonomy_manager_tree_term_children_count($tid) {
  638. static $tids = array();
  639. if (!isset($tids[$tid])) {
  640. $query = db_select('taxonomy_term_hierarchy', 'h');
  641. $query->condition('h.parent', $tid);
  642. $tids[$tid] = $query->countQuery()->execute()->fetchField();
  643. }
  644. return $tids[$tid];
  645. }
  646. /**
  647. * returns some additional information about the term which gets added to the link title
  648. */
  649. function taxonomy_manager_tree_term_extra_info($term) {
  650. $extra_info = "";
  651. $term_children_count = _taxonomy_manager_tree_term_children_count($term->tid);
  652. $term_parents = taxonomy_get_parents($term->tid);
  653. if ($term_children_count > 0) {
  654. $extra_info = t('Children Count:') . ' ' . $term_children_count;
  655. }
  656. if (count($term_parents) >= 1) {
  657. $extra_info .= !empty($extra_info) ? ' | ' : '';
  658. $extra_info .= t('Direct Parents:') . ' ';
  659. $p_names = array();
  660. foreach ($term_parents as $p) {
  661. if ($p->tid != $term->tid) {
  662. $p_names[] = $p->name;
  663. }
  664. }
  665. $extra_info .= implode(', ', $p_names);
  666. }
  667. return $extra_info;
  668. }
  669. /**
  670. * calculates number of next siblings if using paging
  671. *
  672. * @param $vid
  673. * @param $page
  674. * @param $parent
  675. * @return next page size
  676. */
  677. function _taxonomy_manager_tree_get_next_siblings_count($vid, $page, $parent = 0) {
  678. $query = db_select('taxonomy_term_data', 't');
  679. $query->join('taxonomy_term_hierarchy', 'h', 't.tid = h.tid');
  680. $query->condition('t.vid', $vid);
  681. $query->condition('h.parent', $parent);
  682. $count = $query->countQuery()->execute()->fetchField();
  683. $current_count = variable_get('taxonomy_manager_pager_tree_page_size', TAXONOMY_MANAGER_PAGER_TREE_PAGE_SIZE_DEFAULT) * $page;
  684. $diff = $count - $current_count;
  685. if ($diff > variable_get('taxonomy_manager_pager_tree_page_size', TAXONOMY_MANAGER_PAGER_TREE_PAGE_SIZE_DEFAULT)) {
  686. $diff = variable_get('taxonomy_manager_pager_tree_page_size', TAXONOMY_MANAGER_PAGER_TREE_PAGE_SIZE_DEFAULT);
  687. }
  688. return $diff;
  689. }
  690. /**
  691. * callback for generating and rendering nested child forms (AHAH)
  692. *
  693. * @param $tree_id
  694. * @param $parent term id of parent, that is expanded and of which children have to be loaded
  695. */
  696. function taxonomy_manager_tree_build_child_form($tree_id, $vid, $parent) {
  697. $params = $_GET;
  698. $form_state = form_state_defaults();
  699. $form_state['method'] = 'get';
  700. $form_state['values'] = array();
  701. $form_state['process_input'] = TRUE;
  702. $form_state['input'] = array();
  703. $form_state['complete form'] = array();
  704. $tids = array();
  705. if (isset($params['#terms_to_expand']) && $params['#terms_to_expand'] != 0) {
  706. // convert to array
  707. $tids = explode(',', $params['#terms_to_expand']);
  708. }
  709. if (count($tids) == 1) {
  710. $language = _taxonomy_manager_term_get_lang($tids[0]);
  711. }
  712. else {
  713. $language = $params['language'];
  714. }
  715. $child_form = array(
  716. '#type' => 'taxonomy_manager_tree',
  717. '#vid' => $vid,
  718. '#parent' => $parent,
  719. '#pager' => TRUE,
  720. '#language' => $language,
  721. '#terms_to_expand' => $tids,
  722. '#siblings_page' => 1,
  723. );
  724. $opertions_callback = 'taxonomy_manager_' . str_replace('-', '_', $tree_id) . '_operations';
  725. if (function_exists($opertions_callback)) {
  726. $child_form['#operations_callback'] = $opertions_callback;
  727. }
  728. $link_callback = 'taxonomy_manager_' . str_replace('-', '_', $tree_id) . '_link';
  729. if (function_exists($link_callback)) {
  730. $child_form['#link_callback'] = $link_callback;
  731. }
  732. _taxonomy_manager_tree_sub_forms_set_parents($params['form_parents'], $parent, $child_form);
  733. $child_form = form_builder('taxonomy_manager_form', $child_form, $form_state);
  734. print drupal_json_output(array('data' => drupal_render($child_form)));
  735. ajax_footer();
  736. }
  737. /**
  738. * callback for generating and rendering next siblings terms form (AHAH)
  739. *
  740. * @param $tree_id
  741. * @param $page current page
  742. * @param $prev_tid last sibling, that appears
  743. * @param $parent if in hierarchies, parent id
  744. */
  745. function taxonomy_manager_tree_build_siblings_form($tree_id, $page, $prev_tid, $parent = 0) {
  746. $params = $_GET;
  747. $form_state = form_state_defaults();
  748. $form_state['method'] = 'get';
  749. $form_state['values'] = array();
  750. $form_state['process_input'] = TRUE;
  751. $form_state['input'] = array();
  752. $form_state['complete form'] = array();
  753. $vid = db_query("SELECT vid FROM {taxonomy_term_data} WHERE tid = :tid", array(':tid' => $prev_tid))->fetchField();
  754. $siblings_form = array(
  755. '#type' => 'taxonomy_manager_tree',
  756. '#vid' => $vid,
  757. '#parent' => $parent,
  758. '#pager' => TRUE,
  759. '#siblings_page' => $page+1,
  760. '#language' => $params['language'],
  761. );
  762. $opertions_callback = 'taxonomy_manager_' . str_replace('-', '_', $tree_id) . '_operations';
  763. if (function_exists($opertions_callback)) {
  764. $siblings_form['#operations_callback'] = $opertions_callback;
  765. }
  766. $link_callback = 'taxonomy_manager_' . str_replace('-', '_', $tree_id) . '_link';
  767. if (function_exists($link_callback)) {
  768. $siblings_form['#link_callback'] = $link_callback;
  769. }
  770. _taxonomy_manager_tree_sub_forms_set_parents($params['form_parents'], $parent, $siblings_form);
  771. $siblings_form = form_builder('taxonomy_manager_form', $siblings_form, $form_state);
  772. $output = drupal_render($siblings_form);
  773. //cutting of <ul> and ending </ul> ... can this be done cleaner?
  774. $output = drupal_substr($output, 21, -5);
  775. print drupal_json_output(array('data' => $output));
  776. ajax_footer();
  777. }
  778. /**
  779. * sets parents depending on form_id and hierarchical parents
  780. *
  781. * @param $tree_id
  782. * @param $parent term id
  783. * @param $form
  784. */
  785. function _taxonomy_manager_tree_sub_forms_set_parents($form_parents, $parent, &$form) {
  786. $tree_ids = $form_parents;
  787. foreach ($tree_ids as $key => $id) {
  788. $form['#parents'][] = $id;
  789. }
  790. if ($parent) {
  791. $form['#parents'][] = $parent;
  792. $form['#parents'][] = 'children';
  793. /*$all_parents = taxonomy_get_parents_all($parent);
  794. for ($i=count($all_parents)-1; $i >= 0; $i--) {
  795. $form['#parents'][] = $all_parents[$i]->tid;
  796. $form['#parents'][] = 'children';
  797. }*/
  798. }
  799. }
  800. /**
  801. * validates submitted form values
  802. * checks if selected terms really belong to initial voc, if not --> form_set_error
  803. *
  804. * if all is valid, selected values get added to 'selected_terms' for easy use in submit
  805. *
  806. * @param $form
  807. */
  808. function taxonomy_manager_tree_validate($form, &$form_state) {
  809. $selected = array();
  810. //this can be useful for more complex processing, where the parent of the selecte term in the tree view is need (releveant if multi parent)
  811. //used in double tree
  812. $direct_parents = array();
  813. $selected = _taxonomy_manager_tree_get_selected_terms($form['#value'], $direct_parents);
  814. if (!$form['#multiple'] && count($selected) > 1) {
  815. // There should only be one selected term here. Make sure this is the case.
  816. $selected = array(key($selected) => key($selected));
  817. }
  818. $vid = $form['#vid'];
  819. foreach ($selected as $tid) {
  820. if (!_taxonomy_manager_tree_term_valid($tid, $vid)) {
  821. form_set_error('', t('An illegal choice has been detected. Please contact the site administrator.'));
  822. }
  823. }
  824. form_set_value($form, array('selected_terms' => $selected, 'selected_terms_direct_parents' => $direct_parents), $form_state);
  825. }
  826. /**
  827. * checks if term id belongs to vocabulary
  828. *
  829. * @param $tid term id
  830. * @param $vid voc id
  831. * @return true, if term belongs to voc, else false
  832. */
  833. function _taxonomy_manager_tree_term_valid($tid, $vid) {
  834. $term = taxonomy_term_load($tid);
  835. if ($term->vid != $vid) return FALSE;
  836. return TRUE;
  837. }
  838. /**
  839. * returns term ids of selected checkboxes
  840. *
  841. * goes through nested form array recursivly
  842. *
  843. * @param $form_values
  844. * @return an array with ids of selected terms
  845. */
  846. function _taxonomy_manager_tree_get_selected_terms($form_values, &$direct_parents = array(), $direct_parent = 0) {
  847. $tids = array();
  848. if (is_array($form_values)) {
  849. foreach ($form_values as $tid => $form_value) {
  850. if (isset($form_value['checkbox']) && $tid && ($tid == $form_value['checkbox'])) {
  851. $tids[$tid] = $tid;
  852. if ($direct_parent) {
  853. $direct_parents[$tid] = $direct_parent;
  854. }
  855. }
  856. if (isset($form_value['children']) && is_array($form_value['children'])) {
  857. $tids += _taxonomy_manager_tree_get_selected_terms($form_value['children'], $direct_parents, $tid);
  858. }
  859. }
  860. }
  861. return $tids;
  862. }
  863. /**
  864. * returns language of a term (multilingual voc), if i18ntaxonomy enabled
  865. */
  866. function _taxonomy_manager_term_get_lang($tid) {
  867. if (module_exists('i18n_taxonomy')) {
  868. $term = taxonomy_term_load($tid);
  869. if ($term && isset($term->language)) {
  870. return $term->language;
  871. }
  872. }
  873. return "";
  874. }
  875. /**
  876. * theme function for root element
  877. *
  878. * @param $element
  879. * @return html output
  880. */
  881. function theme_taxonomy_manager_tree($variables) {
  882. $element = $variables['element'];
  883. $tree = theme('taxonomy_manager_tree_elements', array('element' => $element['#elements']));
  884. if ((!$element['#parent'] && !$element['#siblings_page']) || $element['#render_whole_tree']) {
  885. $element['#children'] = '<div id="' . $element['#id'] . '">';
  886. $element['#children'] .= $tree;
  887. $element['#children'] .= '</div>';
  888. $element['#title_display'] = 'none';
  889. return theme('form_element', array('element' => $element));
  890. }
  891. return $tree;
  892. }
  893. /**
  894. * recursive theme function for term elements
  895. *
  896. * @param $element
  897. * @return html lists
  898. */
  899. function theme_taxonomy_manager_tree_elements($variables) {
  900. $element = $variables['element'];
  901. $output = '<ul class="treeview">';
  902. if (is_array($element)) {
  903. foreach (element_children($element) as $tid) {
  904. if (is_numeric($tid)) {
  905. $output .= '<li' . drupal_attributes($element[$tid]['#attributes']) . '>';
  906. if ((is_array($element[$tid]['#attributes']['class']) && in_array('has-children', $element[$tid]['#attributes']['class'])) || (isset($element[$tid]['children']) && is_array($element[$tid]['children']))) {
  907. $output .= '<div class="hitArea"></div>';
  908. }
  909. $output .= '<div class="term-line' . (($element[$tid]['checkbox']['#highlight']) ? ' highlightActiveTerm' : '') . '">';
  910. $output .= drupal_render($element[$tid]['checkbox']);
  911. $output .= '<div class="term-operations" style="display: none;">';
  912. $output .= drupal_render($element[$tid]['operations']);
  913. $output .= '</div>';
  914. if (is_array($element[$tid]['weight']) && is_array($element[$tid]['tid'])) {
  915. $output .= drupal_render($element[$tid]['weight']);
  916. $output .= drupal_render($element[$tid]['tid']);
  917. }
  918. $output .= '</div>';
  919. // Siblings Pager.
  920. if (isset($element[$tid]['next_count'])) {
  921. $output .= '<div class="term-has-more-siblings">';
  922. $output .= '<div class="term-next-count">' . t('next') . " " . drupal_render($element[$tid]['next_count']) . '</div>';
  923. $output .= drupal_render($element[$tid]['page']);
  924. $output .= '</div>';
  925. }
  926. if (isset($element[$tid]['children']) && is_array($element[$tid]['children'])) {
  927. $output .= theme('taxonomy_manager_tree_elements', array('element' => $element[$tid]['children']));
  928. }
  929. $output .='</li>';
  930. }
  931. }
  932. }
  933. $output .= "</ul>";
  934. if (isset($element['language'])) {
  935. $output .= drupal_render($element['language']);
  936. }
  937. return $output;
  938. }
  939. /**
  940. * themes a checkbox, where a label can optional contain a link
  941. */
  942. function theme_taxonomy_manager_tree_checkbox($variables) {
  943. $element = $variables['element'];
  944. $element['#attributes']['type'] = 'checkbox';
  945. element_set_attributes($element, array('id', 'name', '#return_value' => 'value'));
  946. // Unchecked checkbox has #value of integer 0.
  947. if (!empty($element['#checked'])) {
  948. $element['#attributes']['checked'] = 'checked';
  949. }
  950. _form_set_class($element, array('form-checkbox'));
  951. $output = '<input' . drupal_attributes($element['#attributes']) . ' />';
  952. $title = $element['#title'];
  953. if (isset($element['#link'])) {
  954. $attr = array();
  955. $attr["class"][] = "term-data-link";
  956. $attr["class"][] = "term-data-link-id-" . $element['#return_value'];
  957. if (isset($element['#extra_info'])) {
  958. $attr["title"] = $element['#extra_info'];
  959. }
  960. $title = l($title, $element['#link'], array('attributes' => $attr));
  961. }
  962. else {
  963. $title = check_plain($title);
  964. }
  965. $element['#children'] = '<label class="option">' . $output . ' ' . $title . '</label>';
  966. $element['#title_display'] = 'none';
  967. return theme('form_element', array('element' => $element));
  968. }
  969. /**
  970. * themes a radio, where a label can optional contain a link
  971. */
  972. function theme_taxonomy_manager_tree_radio($variables) {
  973. $element = $variables['element'];
  974. $element['#attributes']['type'] = 'radio';
  975. element_set_attributes($element, array('id', 'name', '#return_value' => 'value'));
  976. if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) {
  977. $element['#attributes']['checked'] = 'checked';
  978. }
  979. _form_set_class($element, array('form-radio'));
  980. $output = '<input' . drupal_attributes($element['#attributes']) . ' />';
  981. $title = $element['#title'];
  982. if (isset($element['#link'])) {
  983. $title = l($title, $element['#link'], array('attributes' => array("class" => "term-data-link term-data-link-id-" . $element['#return_value'])));
  984. }
  985. else {
  986. $title = check_plain($title);
  987. }
  988. $element['#children'] = '<label class="option">' . $output . ' ' . $title . '</label>';
  989. $element['#title_display'] = 'none';
  990. return theme('form_element', array('element' => $element));
  991. }