i18n_select.module 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <?php
  2. /**
  3. * @file
  4. * Multilingual content selection module.
  5. *
  6. * Alters content queries to add language conditions.
  7. *
  8. * Queries tagged with 'i18n_select' or that already have a language condition will not be altered.
  9. */
  10. // No language selection
  11. define('I18N_SELECT_NONE', 0);
  12. // Content with current language and undefined language
  13. define('I18N_SELECT_NORMAL', 1);
  14. // Select default language when current language is missing
  15. define('I18N_SELECT_MISSING', 2);
  16. /**
  17. * Enable on every page except the listed pages.
  18. */
  19. define('I18N_SELECT_PAGE_NOTLISTED', 0);
  20. /**
  21. * Enable on only the listed pages.
  22. */
  23. define('I18N_SELECT_PAGE_LISTED', 1);
  24. /**
  25. * Enable if the associated PHP code returns TRUE.
  26. */
  27. define('I18N_SELECT_PAGE_PHP', 2);
  28. /**
  29. * Implements hook_init().
  30. */
  31. function i18n_select_init() {
  32. // Determine selection mode for this page
  33. i18n_select(i18n_select_page());
  34. }
  35. /**
  36. * Implements hook_block_list_alter().
  37. *
  38. * Dirty trick to enable selection for blocks though it may be disabled for the current page.
  39. */
  40. function i18n_select_block_list_alter(&$blocks) {
  41. // Still, skip for form submission. There are pages like the ones produced
  42. // by overlay that render the blocks before the page.
  43. // See overlay_init(), overlay_overlay_child_initialize()
  44. if (empty($_POST) && !isset($_GET['token']) && variable_get('i18n_select_page_block', TRUE)) {
  45. i18n_select(TRUE);
  46. }
  47. }
  48. /**
  49. * Implements hook_menu().
  50. */
  51. function i18n_select_menu() {
  52. $items['admin/config/regional/i18n/select'] = array(
  53. 'title' => 'Selection',
  54. 'description' => 'Configure extended options for multilingual content and translations.',
  55. 'page callback' => 'drupal_get_form',
  56. 'page arguments' => array('i18n_select_admin_settings'),
  57. 'access arguments' => array('administer site configuration'),
  58. 'file' => 'i18n_select.admin.inc',
  59. 'type' => MENU_LOCAL_TASK,
  60. );
  61. return $items;
  62. }
  63. /**
  64. * Get current mode for i18n selection
  65. *
  66. * @param $type
  67. * Selection type: 'nodes', 'taxonomy', etc..
  68. */
  69. function i18n_select_mode($type = NULL) {
  70. if (i18n_select() && (!$type || variable_get('i18n_select_' . $type, TRUE))) {
  71. return I18N_SELECT_NORMAL;
  72. }
  73. else {
  74. return I18N_SELECT_NONE;
  75. }
  76. }
  77. /**
  78. * Check current path to enable selection.
  79. *
  80. * This works pretty much like block visibility.
  81. *
  82. * @return bool
  83. * TRUE if content selection should be enabled for this page.
  84. */
  85. function i18n_select_page() {
  86. static $mode;
  87. if (!isset($mode)) {
  88. $mode = &drupal_static(__FUNCTION__);
  89. $visibility = variable_get('i18n_select_page_mode', I18N_SELECT_PAGE_NOTLISTED);
  90. if ($pages = variable_get('i18n_select_page_list', 'admin/*')) {
  91. // Convert path to lowercase. This allows comparison of the same path
  92. // with different case. Ex: /Page, /page, /PAGE.
  93. $pages = drupal_strtolower($pages);
  94. if ($visibility < I18N_SELECT_PAGE_PHP) {
  95. // @see views_ajax()
  96. // @see I18NSelectAdminViewsAjax::testViewsAjaxWithoutSkippingTags()
  97. $path = isset($_REQUEST['view_path']) ? $_REQUEST['view_path'] : $_GET['q'];
  98. // Convert the Drupal path to lowercase.
  99. $path = drupal_strtolower(drupal_get_path_alias($path));
  100. // Compare the lowercase internal and lowercase path alias (if any).
  101. $page_match = drupal_match_path($path, $pages);
  102. if ($path != $_GET['q']) {
  103. $page_match = $page_match || drupal_match_path($_GET['q'], $pages);
  104. }
  105. // When $visibility has a value of 0 (I18N_SELECT_PAGE_NOTLISTED),
  106. // the block is displayed on all pages except those listed in $pages.
  107. // When set to 1 (I18N_SELECT_PAGE_LISTED), it is displayed only on
  108. // those pages listed in $pages.
  109. $mode = !($visibility xor $page_match);
  110. }
  111. elseif (module_exists('php')) {
  112. $mode = php_eval($pages);
  113. }
  114. else {
  115. $mode = FALSE;
  116. }
  117. }
  118. else {
  119. // No pages defined, still respect the setting (unlike blocks).
  120. $mode = $visibility == I18N_SELECT_PAGE_NOTLISTED;
  121. }
  122. }
  123. return $mode;
  124. }
  125. /**
  126. * Implementation of hook_query_node_access_alter().
  127. *
  128. * Rewrite node queries so language selection options are enforced.
  129. */
  130. function i18n_select_query_node_access_alter(QueryAlterableInterface $query) {
  131. if (i18n_select_mode('nodes') && i18n_select_check_query($query, 'nid') &&
  132. ($table_alias = i18n_select_check_table($query, 'node', 'nid'))) {
  133. $query->condition($table_alias . '.language', i18n_select_langcodes());
  134. // Mark query as altered
  135. $query->addTag('i18n_select');
  136. }
  137. }
  138. /**
  139. * Implementation of hook_query_term_access_alter().
  140. *
  141. * Rewrite taxonomy term queries so language selection options are enforced.
  142. */
  143. function i18n_select_query_term_access_alter(QueryAlterableInterface $query) {
  144. if (module_exists('i18n_taxonomy') && i18n_select_mode('taxonomy') && i18n_select_check_query($query, 'tid') &&
  145. ($table_alias = i18n_select_check_table($query, 'taxonomy_term_data', 'tid'))) {
  146. $query->condition($table_alias . '.language', i18n_select_langcodes());
  147. // Mark query as altered
  148. $query->addTag('i18n_select');
  149. }
  150. }
  151. /**
  152. * Check table exists in query and get alias for it.
  153. *
  154. * @param $query
  155. * Query object
  156. * @param $table_name
  157. * Table name to find.
  158. * @param $field_name
  159. * field to join the table if needed.
  160. *
  161. * @return
  162. * Table alias if found, none if not.
  163. */
  164. function i18n_select_check_table($query, $table_name, $field_name) {
  165. $tables = $query->getTables();
  166. foreach ($tables as $table) {
  167. if (!($table instanceof SelectQueryInterface) && $table['table'] == $table_name) {
  168. return _i18n_select_table_alias($table);
  169. }
  170. }
  171. // Join the table if we can find the key field on any of the tables
  172. // And all the conditions have a table alias (or there's a unique table).
  173. if (count($tables) == 1) {
  174. $table = reset($tables);
  175. $table_alias = _i18n_select_table_alias($table);
  176. if (i18n_select_check_conditions($query, $table_alias)) {
  177. $join_table = $table_alias;
  178. }
  179. }
  180. elseif (i18n_select_check_conditions($query)) {
  181. // Try to find the right field in the table schema.
  182. foreach ($tables as $table) {
  183. $schema = drupal_get_schema($table['table']);
  184. if ($schema && !empty($schema['fields'][$field_name])) {
  185. $join_table = _i18n_select_table_alias($table);
  186. break;
  187. }
  188. }
  189. }
  190. if (!empty($join_table)) {
  191. return $query->join($table_name, $table_name, $join_table . '.' . $field_name . ' = %alias.' . $field_name);
  192. }
  193. else {
  194. return FALSE;
  195. }
  196. }
  197. /**
  198. * Get table alias
  199. */
  200. function _i18n_select_table_alias($table) {
  201. return !empty($table['alias']) ? $table['alias'] : $table['table'];
  202. }
  203. /**
  204. * Check all query conditions have a table alias.
  205. *
  206. * @param $table_alias
  207. * Optional table alias for fields without table.
  208. *
  209. * @return boolean
  210. * TRUE if table conditions are ok, FALSE otherwise.
  211. */
  212. function i18n_select_check_conditions($query, $table_alias = NULL) {
  213. $conditions =& $query->conditions();
  214. foreach ($conditions as $index => $condition) {
  215. if (is_array($condition) && !empty($condition['field'])) {
  216. if (strpos($condition['field'], '.') === FALSE) {
  217. if ($table_alias) {
  218. // Change the condition to include a table alias.
  219. $conditions[$index]['field'] = $table_alias . '.' . $condition['field'];
  220. }
  221. else {
  222. // We won't risk joining anything here.
  223. return FALSE;
  224. }
  225. }
  226. }
  227. }
  228. return TRUE;
  229. }
  230. /**
  231. * Check whether we should apply language conditions here:
  232. * - The query has not been tagged with 'i18n_select'
  233. * - The query doesn't have already a language condition
  234. * - All the conditions have a table alias or there's only one table.
  235. * - We are not loading specific objects (no condition for index field).
  236. *
  237. * @param $query
  238. * Query object.
  239. * @param $index_field
  240. * Object index field to check we don't have 'IN' conditions for it.
  241. */
  242. function i18n_select_check_query($query, $index_field = NULL) {
  243. static $tags;
  244. // Skip queries with certain tags
  245. if (!isset($tags)) {
  246. $tags = ($skip = variable_get('i18n_select_skip_tags', 'views')) ? array_map('trim', explode(',', $skip)) : array();
  247. $tags[] = 'i18n_select';
  248. }
  249. foreach ($tags as $tag) {
  250. if ($query->hasTag($tag)) {
  251. return FALSE;
  252. }
  253. }
  254. // Check all the conditions to see whether the query is suitable for altering.
  255. foreach ($query->conditions() as $condition) {
  256. if (is_array($condition)) {
  257. // @todo For some complex queries, like search ones, field is a DatabaseCondition object
  258. if (!isset($condition['field']) || !is_string($condition['field'])) {
  259. // There's a weird condition field, we won't take any chances.
  260. return FALSE;
  261. }
  262. else {
  263. // Just check the condition doesn't include the language field
  264. if (strpos($condition['field'], '.') === FALSE) {
  265. $field = $condition['field'];
  266. }
  267. else {
  268. list($table, $field) = explode('.', $condition['field']);
  269. }
  270. if ($field == 'language') {
  271. return FALSE;
  272. }
  273. // Check 'IN' conditions for index field, usually entity loading for specific objects.
  274. if ($field == $index_field && $condition['operator'] == 'IN') {
  275. return FALSE;
  276. }
  277. }
  278. }
  279. }
  280. // If the language field is present we don't want to do any filtering.
  281. $fields = $query->getFields();
  282. if (isset($fields['language'])) {
  283. return FALSE;
  284. }
  285. return TRUE;
  286. }
  287. /**
  288. * Get main language for content selection
  289. */
  290. function i18n_select_language() {
  291. return $GLOBALS[LANGUAGE_TYPE_CONTENT];
  292. }
  293. /**
  294. * Get language codes for content selection to use in query conditions
  295. */
  296. function i18n_select_langcodes() {
  297. return array(i18n_select_language()->language, LANGUAGE_NONE);
  298. }