views_plugin_query_default.inc 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663
  1. <?php
  2. /**
  3. * @file
  4. * Defines the default query object.
  5. */
  6. /**
  7. * Object used to create a SELECT query.
  8. *
  9. * @ingroup views_query_plugins
  10. */
  11. class views_plugin_query_default extends views_plugin_query {
  12. /**
  13. * A list of tables in the order they should be added, keyed by alias.
  14. */
  15. var $table_queue = array();
  16. /**
  17. * Holds an array of tables and counts added so that we can create aliases
  18. */
  19. var $tables = array();
  20. /**
  21. * Holds an array of relationships, which are aliases of the primary
  22. * table that represent different ways to join the same table in.
  23. */
  24. var $relationships = array();
  25. /**
  26. * An array of sections of the WHERE query. Each section is in itself
  27. * an array of pieces and a flag as to whether or not it should be AND
  28. * or OR.
  29. */
  30. var $where = array();
  31. /**
  32. * An array of sections of the HAVING query. Each section is in itself
  33. * an array of pieces and a flag as to whether or not it should be AND
  34. * or OR.
  35. */
  36. var $having = array();
  37. /**
  38. * The default operator to use when connecting the WHERE groups. May be
  39. * AND or OR.
  40. */
  41. var $group_operator = 'AND';
  42. /**
  43. * A simple array of order by clauses.
  44. */
  45. var $orderby = array();
  46. /**
  47. * A simple array of group by clauses.
  48. */
  49. var $groupby = array();
  50. /**
  51. * An array of fields.
  52. */
  53. var $fields = array();
  54. /**
  55. * The table header to use for tablesort. This matters because tablesort
  56. * needs to modify the query and needs the header.
  57. */
  58. var $header = array();
  59. /**
  60. * A flag as to whether or not to make the primary field distinct.
  61. */
  62. var $distinct = FALSE;
  63. var $has_aggregate = FALSE;
  64. /**
  65. * Should this query be optimized for counts, for example no sorts.
  66. */
  67. var $get_count_optimized = NULL;
  68. /**
  69. * The current used pager plugin.
  70. *
  71. * @var views_plugin_pager
  72. */
  73. var $pager = NULL;
  74. /**
  75. * An array mapping table aliases and field names to field aliases.
  76. */
  77. var $field_aliases = array();
  78. /**
  79. * Query tags which will be passed over to the dbtng query object.
  80. */
  81. var $tags = array();
  82. /**
  83. * Is the view marked as not distinct.
  84. *
  85. * @var bool
  86. */
  87. var $no_distinct;
  88. /**
  89. * Defines the distinct type.
  90. * - FALSE if it's distinct by base field.
  91. * - TRUE if it just adds the sql distinct keyword.
  92. *
  93. * @var bool
  94. */
  95. public $pure_distinct = FALSE;
  96. /**
  97. * Constructor; Create the basic query object and fill with default values.
  98. */
  99. function init($base_table = 'node', $base_field = 'nid', $options) {
  100. parent::init($base_table, $base_field, $options);
  101. $this->base_table = $base_table; // Predefine these above, for clarity.
  102. $this->base_field = $base_field;
  103. $this->relationships[$base_table] = array(
  104. 'link' => NULL,
  105. 'table' => $base_table,
  106. 'alias' => $base_table,
  107. 'base' => $base_table
  108. );
  109. // init the table queue with our primary table.
  110. $this->table_queue[$base_table] = array(
  111. 'alias' => $base_table,
  112. 'table' => $base_table,
  113. 'relationship' => $base_table,
  114. 'join' => NULL,
  115. );
  116. // init the tables with our primary table
  117. $this->tables[$base_table][$base_table] = array(
  118. 'count' => 1,
  119. 'alias' => $base_table,
  120. );
  121. /**
  122. * -- we no longer want the base field to appear automatigically.
  123. if ($base_field) {
  124. $this->fields[$base_field] = array(
  125. 'table' => $base_table,
  126. 'field' => $base_field,
  127. 'alias' => $base_field,
  128. );
  129. }
  130. */
  131. $this->count_field = array(
  132. 'table' => $base_table,
  133. 'field' => $base_field,
  134. 'alias' => $base_field,
  135. 'count' => TRUE,
  136. );
  137. }
  138. // ----------------------------------------------------------------
  139. // Utility methods to set flags and data.
  140. /**
  141. * Set the view to be distinct.
  142. *
  143. * There are either distinct per base field or distinct in the pure sql way,
  144. * based on $pure_distinct.
  145. *
  146. * @param bool $value
  147. * Should the view by distincted.
  148. * @param bool $pure_distinct
  149. * Should only the sql keyword be added.
  150. */
  151. function set_distinct($value = TRUE, $pure_distinct = FALSE) {
  152. if (!(isset($this->no_distinct) && $value)) {
  153. $this->distinct = $value;
  154. $this->pure_distinct = $pure_distinct;
  155. }
  156. }
  157. /**
  158. * Set what field the query will count() on for paging.
  159. */
  160. function set_count_field($table, $field, $alias = NULL) {
  161. if (empty($alias)) {
  162. $alias = $table . '_' . $field;
  163. }
  164. $this->count_field = array(
  165. 'table' => $table,
  166. 'field' => $field,
  167. 'alias' => $alias,
  168. 'count' => TRUE,
  169. );
  170. }
  171. /**
  172. * Set the table header; used for click-sorting because it's needed
  173. * info to modify the ORDER BY clause.
  174. */
  175. function set_header($header) {
  176. $this->header = $header;
  177. }
  178. function option_definition() {
  179. $options = parent::option_definition();
  180. $options['disable_sql_rewrite'] = array(
  181. 'default' => FALSE,
  182. 'translatable' => FALSE,
  183. 'bool' => TRUE,
  184. );
  185. $options['distinct'] = array(
  186. 'default' => FALSE,
  187. 'bool' => TRUE,
  188. );
  189. $options['pure_distinct'] = array(
  190. 'default' => FALSE,
  191. 'bool' => TRUE,
  192. );
  193. $options['slave'] = array(
  194. 'default' => FALSE,
  195. 'bool' => TRUE,
  196. );
  197. $options['query_comment'] = array(
  198. 'default' => '',
  199. );
  200. $options['query_tags'] = array(
  201. 'default' => array(),
  202. );
  203. return $options;
  204. }
  205. /**
  206. * Add settings for the ui.
  207. */
  208. function options_form(&$form, &$form_state) {
  209. parent::options_form($form, $form_state);
  210. $form['disable_sql_rewrite'] = array(
  211. '#title' => t('Disable SQL rewriting'),
  212. '#description' => t('Disabling SQL rewriting will disable node_access checks as well as other modules that implement hook_query_alter().'),
  213. '#type' => 'checkbox',
  214. '#default_value' => !empty($this->options['disable_sql_rewrite']),
  215. '#suffix' => '<div class="messages warning sql-rewrite-warning js-hide">' . t('WARNING: Disabling SQL rewriting means that node access security is disabled. This may allow users to see data they should not be able to see if your view is misconfigured. Please use this option only if you understand and accept this security risk.') . '</div>',
  216. );
  217. $form['distinct'] = array(
  218. '#type' => 'checkbox',
  219. '#title' => t('Distinct'),
  220. '#description' => t('This will make the view display only distinct items. If there are multiple identical items, each will be displayed only once. You can use this to try and remove duplicates from a view, though it does not always work. Note that this can slow queries down, so use it with caution.'),
  221. '#default_value' => !empty($this->options['distinct']),
  222. );
  223. $form['pure_distinct'] = array(
  224. '#type' => 'checkbox',
  225. '#title' => t('Pure Distinct'),
  226. '#description' => t('This will prevent views from adding the base column to the distinct field. If this is not selected and the base column is a primary key, then a non-pure distinct will not function properly because the primary key is always unique.'),
  227. '#default_value' => !empty($this->options['pure_distinct']),
  228. '#dependency' => array('edit-query-options-distinct' => '1'),
  229. );
  230. $form['slave'] = array(
  231. '#type' => 'checkbox',
  232. '#title' => t('Use Slave Server'),
  233. '#description' => t('This will make the query attempt to connect to a slave server if available. If no slave server is defined or available, it will fall back to the default server.'),
  234. '#default_value' => !empty($this->options['slave']),
  235. );
  236. $form['query_comment'] = array(
  237. '#type' => 'textfield',
  238. '#title' => t('Query Comment'),
  239. '#description' => t('If set, this comment will be embedded in the query and passed to the SQL server. This can be helpful for logging or debugging.'),
  240. '#default_value' => $this->options['query_comment'],
  241. );
  242. $form['query_tags'] = array(
  243. '#type' => 'textfield',
  244. '#title' => t('Query Tags'),
  245. '#description' => t('If set, these tags will be appended to the query and can be used to identify the query in a module. This can be helpful for altering queries.'),
  246. '#default_value' => implode(', ', $this->options['query_tags']),
  247. '#element_validate' => array('views_element_validate_tags'),
  248. );
  249. }
  250. /**
  251. * Special submit handling.
  252. */
  253. function options_submit(&$form, &$form_state) {
  254. $element = array('#parents' => array('query', 'options', 'query_tags'));
  255. $value = explode(',', drupal_array_get_nested_value($form_state['values'], $element['#parents']));
  256. $value = array_filter(array_map('trim', $value));
  257. form_set_value($element, $value, $form_state);
  258. }
  259. // ----------------------------------------------------------------
  260. // Table/join adding
  261. /**
  262. * A relationship is an alternative endpoint to a series of table
  263. * joins. Relationships must be aliases of the primary table and
  264. * they must join either to the primary table or to a pre-existing
  265. * relationship.
  266. *
  267. * An example of a relationship would be a nodereference table.
  268. * If you have a nodereference named 'book_parent' which links to a
  269. * parent node, you could set up a relationship 'node_book_parent'
  270. * to 'node'. Then, anything that links to 'node' can link to
  271. * 'node_book_parent' instead, thus allowing all properties of
  272. * both nodes to be available in the query.
  273. *
  274. * @param $alias
  275. * What this relationship will be called, and is also the alias
  276. * for the table.
  277. * @param views_join $join
  278. * A views_join object (or derived object) to join the alias in.
  279. * @param $base
  280. * The name of the 'base' table this relationship represents; this
  281. * tells the join search which path to attempt to use when finding
  282. * the path to this relationship.
  283. * @param $link_point
  284. * If this relationship links to something other than the primary
  285. * table, specify that table here. For example, a 'track' node
  286. * might have a relationship to an 'album' node, which might
  287. * have a relationship to an 'artist' node.
  288. */
  289. function add_relationship($alias, $join, $base, $link_point = NULL) {
  290. if (empty($link_point)) {
  291. $link_point = $this->base_table;
  292. }
  293. elseif (!array_key_exists($link_point, $this->relationships)) {
  294. return FALSE;
  295. }
  296. // Make sure $alias isn't already used; if it, start adding stuff.
  297. $alias_base = $alias;
  298. $count = 1;
  299. while (!empty($this->relationships[$alias])) {
  300. $alias = $alias_base . '_' . $count++;
  301. }
  302. // Make sure this join is adjusted for our relationship.
  303. if ($link_point && isset($this->relationships[$link_point])) {
  304. $join = $this->adjust_join($join, $link_point);
  305. }
  306. // Add the table directly to the queue to avoid accidentally marking
  307. // it.
  308. $this->table_queue[$alias] = array(
  309. 'table' => $join->table,
  310. 'num' => 1,
  311. 'alias' => $alias,
  312. 'join' => $join,
  313. 'relationship' => $link_point,
  314. );
  315. $this->relationships[$alias] = array(
  316. 'link' => $link_point,
  317. 'table' => $join->table,
  318. 'base' => $base,
  319. );
  320. $this->tables[$this->base_table][$alias] = array(
  321. 'count' => 1,
  322. 'alias' => $alias,
  323. );
  324. return $alias;
  325. }
  326. /**
  327. * Add a table to the query, ensuring the path exists.
  328. *
  329. * This function will test to ensure that the path back to the primary
  330. * table is valid and exists; if you do not wish for this testing to
  331. * occur, use $query->queue_table() instead.
  332. *
  333. * @param $table
  334. * The name of the table to add. It needs to exist in the global table
  335. * array.
  336. * @param $relationship
  337. * An alias of a table; if this is set, the path back to this table will
  338. * be tested prior to adding the table, making sure that all intermediary
  339. * tables exist and are properly aliased. If set to NULL the path to
  340. * the primary table will be ensured. If the path cannot be made, the
  341. * table will NOT be added.
  342. * @param views_join $join
  343. * In some join configurations this table may actually join back through
  344. * a different method; this is most likely to be used when tracing
  345. * a hierarchy path. (node->parent->parent2->parent3). This parameter
  346. * will specify how this table joins if it is not the default.
  347. * @param $alias
  348. * A specific alias to use, rather than the default alias.
  349. *
  350. * @return $alias
  351. * The alias of the table; this alias can be used to access information
  352. * about the table and should always be used to refer to the table when
  353. * adding parts to the query. Or FALSE if the table was not able to be
  354. * added.
  355. */
  356. function add_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
  357. if (!$this->ensure_path($table, $relationship, $join)) {
  358. return FALSE;
  359. }
  360. if ($join && $relationship) {
  361. $join = $this->adjust_join($join, $relationship);
  362. }
  363. return $this->queue_table($table, $relationship, $join, $alias);
  364. }
  365. /**
  366. * Add a table to the query without ensuring the path.
  367. *
  368. * This is a pretty internal function to Views and add_table() or
  369. * ensure_table() should be used instead of this one, unless you are
  370. * absolutely sure this is what you want.
  371. *
  372. * @param $table
  373. * The name of the table to add. It needs to exist in the global table
  374. * array.
  375. * @param $relationship
  376. * The primary table alias this table is related to. If not set, the
  377. * primary table will be used.
  378. * @param views_join $join
  379. * In some join configurations this table may actually join back through
  380. * a different method; this is most likely to be used when tracing
  381. * a hierarchy path. (node->parent->parent2->parent3). This parameter
  382. * will specify how this table joins if it is not the default.
  383. * @param $alias
  384. * A specific alias to use, rather than the default alias.
  385. *
  386. * @return $alias
  387. * The alias of the table; this alias can be used to access information
  388. * about the table and should always be used to refer to the table when
  389. * adding parts to the query. Or FALSE if the table was not able to be
  390. * added.
  391. */
  392. function queue_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
  393. // If the alias is set, make sure it doesn't already exist.
  394. if (isset($this->table_queue[$alias])) {
  395. return $alias;
  396. }
  397. if (empty($relationship)) {
  398. $relationship = $this->base_table;
  399. }
  400. if (!array_key_exists($relationship, $this->relationships)) {
  401. return FALSE;
  402. }
  403. if (!$alias && $join && $relationship && !empty($join->adjusted) && $table != $join->table) {
  404. if ($relationship == $this->base_table) {
  405. $alias = $table;
  406. }
  407. else {
  408. $alias = $relationship . '_' . $table;
  409. }
  410. }
  411. // Check this again to make sure we don't blow up existing aliases for already
  412. // adjusted joins.
  413. if (isset($this->table_queue[$alias])) {
  414. return $alias;
  415. }
  416. $alias = $this->mark_table($table, $relationship, $alias);
  417. // If no alias is specified, give it the default.
  418. if (!isset($alias)) {
  419. $alias = $this->tables[$relationship][$table]['alias'] . $this->tables[$relationship][$table]['count'];
  420. }
  421. // If this is a relationship based table, add a marker with
  422. // the relationship as a primary table for the alias.
  423. if ($table != $alias) {
  424. $this->mark_table($alias, $this->base_table, $alias);
  425. }
  426. // If no join is specified, pull it from the table data.
  427. if (!isset($join)) {
  428. $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
  429. if (empty($join)) {
  430. return FALSE;
  431. }
  432. $join = $this->adjust_join($join, $relationship);
  433. }
  434. $this->table_queue[$alias] = array(
  435. 'table' => $table,
  436. 'num' => $this->tables[$relationship][$table]['count'],
  437. 'alias' => $alias,
  438. 'join' => $join,
  439. 'relationship' => $relationship,
  440. );
  441. return $alias;
  442. }
  443. function mark_table($table, $relationship, $alias) {
  444. // Mark that this table has been added.
  445. if (empty($this->tables[$relationship][$table])) {
  446. if (!isset($alias)) {
  447. $alias = '';
  448. if ($relationship != $this->base_table) {
  449. // double underscore will help prevent accidental name
  450. // space collisions.
  451. $alias = $relationship . '__';
  452. }
  453. $alias .= $table;
  454. }
  455. $this->tables[$relationship][$table] = array(
  456. 'count' => 1,
  457. 'alias' => $alias,
  458. );
  459. }
  460. else {
  461. $this->tables[$relationship][$table]['count']++;
  462. }
  463. return $alias;
  464. }
  465. /**
  466. * Ensure a table exists in the queue; if it already exists it won't
  467. * do anything, but if it doesn't it will add the table queue. It will ensure
  468. * a path leads back to the relationship table.
  469. *
  470. * @param $table
  471. * The unaliased name of the table to ensure.
  472. * @param $relationship
  473. * The relationship to ensure the table links to. Each relationship will
  474. * get a unique instance of the table being added. If not specified,
  475. * will be the primary table.
  476. * @param views_join $join
  477. * A views_join object (or derived object) to join the alias in.
  478. *
  479. * @return
  480. * The alias used to refer to this specific table, or NULL if the table
  481. * cannot be ensured.
  482. */
  483. function ensure_table($table, $relationship = NULL, $join = NULL) {
  484. // ensure a relationship
  485. if (empty($relationship)) {
  486. $relationship = $this->base_table;
  487. }
  488. // If the relationship is the primary table, this actually be a relationship
  489. // link back from an alias. We store all aliases along with the primary table
  490. // to detect this state, because eventually it'll hit a table we already
  491. // have and that's when we want to stop.
  492. if ($relationship == $this->base_table && !empty($this->tables[$relationship][$table])) {
  493. return $this->tables[$relationship][$table]['alias'];
  494. }
  495. if (!array_key_exists($relationship, $this->relationships)) {
  496. return FALSE;
  497. }
  498. if ($table == $this->relationships[$relationship]['base']) {
  499. return $relationship;
  500. }
  501. // If we do not have join info, fetch it.
  502. if (!isset($join)) {
  503. $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
  504. }
  505. // If it can't be fetched, this won't work.
  506. if (empty($join)) {
  507. return;
  508. }
  509. // Adjust this join for the relationship, which will ensure that the 'base'
  510. // table it links to is correct. Tables adjoined to a relationship
  511. // join to a link point, not the base table.
  512. $join = $this->adjust_join($join, $relationship);
  513. if ($this->ensure_path($table, $relationship, $join)) {
  514. // Attempt to eliminate redundant joins. If this table's
  515. // relationship and join exactly matches an existing table's
  516. // relationship and join, we do not have to join to it again;
  517. // just return the existing table's alias. See
  518. // http://groups.drupal.org/node/11288 for details.
  519. //
  520. // This can be done safely here but not lower down in
  521. // queue_table(), because queue_table() is also used by
  522. // add_table() which requires the ability to intentionally add
  523. // the same table with the same join multiple times. For
  524. // example, a view that filters on 3 taxonomy terms using AND
  525. // needs to join taxonomy_term_data 3 times with the same join.
  526. // scan through the table queue to see if a matching join and
  527. // relationship exists. If so, use it instead of this join.
  528. // TODO: Scanning through $this->table_queue results in an
  529. // O(N^2) algorithm, and this code runs every time the view is
  530. // instantiated (Views 2 does not currently cache queries).
  531. // There are a couple possible "improvements" but we should do
  532. // some performance testing before picking one.
  533. foreach ($this->table_queue as $queued_table) {
  534. // In PHP 4 and 5, the == operation returns TRUE for two objects
  535. // if they are instances of the same class and have the same
  536. // attributes and values.
  537. if ($queued_table['relationship'] == $relationship && $queued_table['join'] == $join) {
  538. return $queued_table['alias'];
  539. }
  540. }
  541. return $this->queue_table($table, $relationship, $join);
  542. }
  543. }
  544. /**
  545. * Make sure that the specified table can be properly linked to the primary
  546. * table in the JOINs. This function uses recursion. If the tables
  547. * needed to complete the path back to the primary table are not in the
  548. * query they will be added, but additional copies will NOT be added
  549. * if the table is already there.
  550. */
  551. function ensure_path($table, $relationship = NULL, $join = NULL, $traced = array(), $add = array()) {
  552. if (!isset($relationship)) {
  553. $relationship = $this->base_table;
  554. }
  555. if (!array_key_exists($relationship, $this->relationships)) {
  556. return FALSE;
  557. }
  558. // If we do not have join info, fetch it.
  559. if (!isset($join)) {
  560. $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
  561. }
  562. // If it can't be fetched, this won't work.
  563. if (empty($join)) {
  564. return FALSE;
  565. }
  566. // Does a table along this path exist?
  567. if (isset($this->tables[$relationship][$table]) ||
  568. ($join && $join->left_table == $relationship) ||
  569. ($join && $join->left_table == $this->relationships[$relationship]['table'])) {
  570. // Make sure that we're linking to the correct table for our relationship.
  571. foreach (array_reverse($add) as $table => $path_join) {
  572. $this->queue_table($table, $relationship, $this->adjust_join($path_join, $relationship));
  573. }
  574. return TRUE;
  575. }
  576. // Have we been this way?
  577. if (isset($traced[$join->left_table])) {
  578. // we looped. Broked.
  579. return FALSE;
  580. }
  581. // Do we have to add this table?
  582. $left_join = $this->get_join_data($join->left_table, $this->relationships[$relationship]['base']);
  583. if (!isset($this->tables[$relationship][$join->left_table])) {
  584. $add[$join->left_table] = $left_join;
  585. }
  586. // Keep looking.
  587. $traced[$join->left_table] = TRUE;
  588. return $this->ensure_path($join->left_table, $relationship, $left_join, $traced, $add);
  589. }
  590. /**
  591. * Fix a join to adhere to the proper relationship; the left table can vary
  592. * based upon what relationship items are joined in on.
  593. */
  594. function adjust_join($join, $relationship) {
  595. if (!empty($join->adjusted)) {
  596. return $join;
  597. }
  598. if (empty($relationship) || empty($this->relationships[$relationship])) {
  599. return $join;
  600. }
  601. // Adjusts the left table for our relationship.
  602. if ($relationship != $this->base_table) {
  603. // If we're linking to the primary table, the relationship to use will
  604. // be the prior relationship. Unless it's a direct link.
  605. // Safety! Don't modify an original here.
  606. $join = clone $join;
  607. // Do we need to try to ensure a path?
  608. if ($join->left_table != $this->relationships[$relationship]['table'] &&
  609. $join->left_table != $this->relationships[$relationship]['base'] &&
  610. !isset($this->tables[$relationship][$join->left_table]['alias'])) {
  611. $this->ensure_table($join->left_table, $relationship);
  612. }
  613. // First, if this is our link point/anchor table, just use the relationship
  614. if ($join->left_table == $this->relationships[$relationship]['table']) {
  615. $join->left_table = $relationship;
  616. }
  617. // then, try the base alias.
  618. elseif (isset($this->tables[$relationship][$join->left_table]['alias'])) {
  619. $join->left_table = $this->tables[$relationship][$join->left_table]['alias'];
  620. }
  621. // But if we're already looking at an alias, use that instead.
  622. elseif (isset($this->table_queue[$relationship]['alias'])) {
  623. $join->left_table = $this->table_queue[$relationship]['alias'];
  624. }
  625. }
  626. $join->adjusted = TRUE;
  627. return $join;
  628. }
  629. /**
  630. * Retrieve join data from the larger join data cache.
  631. *
  632. * @param $table
  633. * The table to get the join information for.
  634. * @param $base_table
  635. * The path we're following to get this join.
  636. *
  637. * @return views_join
  638. * A views_join object or child object, if one exists.
  639. */
  640. function get_join_data($table, $base_table) {
  641. // Check to see if we're linking to a known alias. If so, get the real
  642. // table's data instead.
  643. if (!empty($this->table_queue[$table])) {
  644. $table = $this->table_queue[$table]['table'];
  645. }
  646. return views_get_table_join($table, $base_table);
  647. }
  648. /**
  649. * Get the information associated with a table.
  650. *
  651. * If you need the alias of a table with a particular relationship, use
  652. * ensure_table().
  653. */
  654. function get_table_info($table) {
  655. if (!empty($this->table_queue[$table])) {
  656. return $this->table_queue[$table];
  657. }
  658. // In rare cases we might *only* have aliased versions of the table.
  659. if (!empty($this->tables[$this->base_table][$table])) {
  660. $alias = $this->tables[$this->base_table][$table]['alias'];
  661. if (!empty($this->table_queue[$alias])) {
  662. return $this->table_queue[$alias];
  663. }
  664. }
  665. }
  666. /**
  667. * Add a field to the query table, possibly with an alias. This will
  668. * automatically call ensure_table to make sure the required table
  669. * exists, *unless* $table is unset.
  670. *
  671. * @param $table
  672. * The table this field is attached to. If NULL, it is assumed this will
  673. * be a formula; otherwise, ensure_table is used to make sure the
  674. * table exists.
  675. * @param $field
  676. * The name of the field to add. This may be a real field or a formula.
  677. * @param $alias
  678. * The alias to create. If not specified, the alias will be $table_$field
  679. * unless $table is NULL. When adding formulae, it is recommended that an
  680. * alias be used.
  681. * @param $params
  682. * An array of parameters additional to the field that will control items
  683. * such as aggregation functions and DISTINCT.
  684. *
  685. * @return $name
  686. * The name that this field can be referred to as. Usually this is the alias.
  687. */
  688. function add_field($table, $field, $alias = '', $params = array()) {
  689. // We check for this specifically because it gets a special alias.
  690. if ($table == $this->base_table && $field == $this->base_field && empty($alias)) {
  691. $alias = $this->base_field;
  692. }
  693. if ($table && empty($this->table_queue[$table])) {
  694. $this->ensure_table($table);
  695. }
  696. if (!$alias && $table) {
  697. $alias = $table . '_' . $field;
  698. }
  699. // Make sure an alias is assigned
  700. $alias = $alias ? $alias : $field;
  701. // PostgreSQL truncates aliases to 63 characters: http://drupal.org/node/571548
  702. // We limit the length of the original alias up to 60 characters
  703. // to get a unique alias later if its have duplicates
  704. $alias = strtolower(substr($alias, 0, 60));
  705. // Create a field info array.
  706. $field_info = array(
  707. 'field' => $field,
  708. 'table' => $table,
  709. 'alias' => $alias,
  710. ) + $params;
  711. // Test to see if the field is actually the same or not. Due to
  712. // differing parameters changing the aggregation function, we need
  713. // to do some automatic alias collision detection:
  714. $base = $alias;
  715. $counter = 0;
  716. while (!empty($this->fields[$alias]) && $this->fields[$alias] != $field_info) {
  717. $field_info['alias'] = $alias = $base . '_' . ++$counter;
  718. }
  719. if (empty($this->fields[$alias])) {
  720. $this->fields[$alias] = $field_info;
  721. }
  722. // Keep track of all aliases used.
  723. $this->field_aliases[$table][$field] = $alias;
  724. return $alias;
  725. }
  726. /**
  727. * Remove all fields that may've been added; primarily used for summary
  728. * mode where we're changing the query because we didn't get data we needed.
  729. */
  730. function clear_fields() {
  731. $this->fields = array();
  732. }
  733. /**
  734. * Add a simple WHERE clause to the query. The caller is responsible for
  735. * ensuring that all fields are fully qualified (TABLE.FIELD) and that
  736. * the table already exists in the query.
  737. *
  738. * @param $group
  739. * The WHERE group to add these to; groups are used to create AND/OR
  740. * sections. Groups cannot be nested. Use 0 as the default group.
  741. * If the group does not yet exist it will be created as an AND group.
  742. * @param $field
  743. * The name of the field to check.
  744. * @param $value
  745. * The value to test the field against. In most cases, this is a scalar. For more
  746. * complex options, it is an array. The meaning of each element in the array is
  747. * dependent on the $operator.
  748. * @param $operator
  749. * The comparison operator, such as =, <, or >=. It also accepts more complex
  750. * options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is an array
  751. * = otherwise. If $field is a string you have to use 'formula' here.
  752. *
  753. * The $field, $value and $operator arguments can also be passed in with a
  754. * single DatabaseCondition object, like this:
  755. * @code
  756. * $this->query->add_where(
  757. * $this->options['group'],
  758. * db_or()
  759. * ->condition($field, $value, 'NOT IN')
  760. * ->condition($field, $value, 'IS NULL')
  761. * );
  762. * @endcode
  763. *
  764. * @see QueryConditionInterface::condition()
  765. * @see DatabaseCondition
  766. */
  767. function add_where($group, $field, $value = NULL, $operator = NULL) {
  768. // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
  769. // the default group.
  770. if (empty($group)) {
  771. $group = 0;
  772. }
  773. // Check for a group.
  774. if (!isset($this->where[$group])) {
  775. $this->set_where_group('AND', $group);
  776. }
  777. $this->where[$group]['conditions'][] = array(
  778. 'field' => $field,
  779. 'value' => $value,
  780. 'operator' => $operator,
  781. );
  782. }
  783. /**
  784. * Add a complex WHERE clause to the query.
  785. *
  786. * The caller is reponsible for ensuring that all fields are fully qualified
  787. * (TABLE.FIELD) and that the table already exists in the query.
  788. * Internally the dbtng method "where" is used.
  789. *
  790. * @param $group
  791. * The WHERE group to add these to; groups are used to create AND/OR
  792. * sections. Groups cannot be nested. Use 0 as the default group.
  793. * If the group does not yet exist it will be created as an AND group.
  794. * @param $snippet
  795. * The snippet to check. This can be either a column or
  796. * a complex expression like "UPPER(table.field) = 'value'"
  797. * @param $args
  798. * An associative array of arguments.
  799. *
  800. * @see QueryConditionInterface::where()
  801. */
  802. function add_where_expression($group, $snippet, $args = array()) {
  803. // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
  804. // the default group.
  805. if (empty($group)) {
  806. $group = 0;
  807. }
  808. // Check for a group.
  809. if (!isset($this->where[$group])) {
  810. $this->set_where_group('AND', $group);
  811. }
  812. $this->where[$group]['conditions'][] = array(
  813. 'field' => $snippet,
  814. 'value' => $args,
  815. 'operator' => 'formula',
  816. );
  817. }
  818. /**
  819. * Add a simple HAVING clause to the query.
  820. *
  821. * The caller is responsible for ensuring that all fields are fully qualified
  822. * (TABLE.FIELD) and that the table and an appropriate GROUP BY already exist in the query.
  823. * Internally the dbtng method "havingCondition" is used.
  824. *
  825. * @param $group
  826. * The HAVING group to add these to; groups are used to create AND/OR
  827. * sections. Groups cannot be nested. Use 0 as the default group.
  828. * If the group does not yet exist it will be created as an AND group.
  829. * @param $field
  830. * The name of the field to check.
  831. * @param $value
  832. * The value to test the field against. In most cases, this is a scalar. For more
  833. * complex options, it is an array. The meaning of each element in the array is
  834. * dependent on the $operator.
  835. * @param $operator
  836. * The comparison operator, such as =, <, or >=. It also accepts more complex
  837. * options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is an array
  838. * = otherwise. If $field is a string you have to use 'formula' here.
  839. *
  840. * @see SelectQueryInterface::havingCondition()
  841. */
  842. function add_having($group, $field, $value = NULL, $operator = NULL) {
  843. // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
  844. // the default group.
  845. if (empty($group)) {
  846. $group = 0;
  847. }
  848. // Check for a group.
  849. if (!isset($this->having[$group])) {
  850. $this->set_where_group('AND', $group, 'having');
  851. }
  852. // Add the clause and the args.
  853. $this->having[$group]['conditions'][] = array(
  854. 'field' => $field,
  855. 'value' => $value,
  856. 'operator' => $operator,
  857. );
  858. }
  859. /**
  860. * Add a complex HAVING clause to the query.
  861. * The caller is responsible for ensuring that all fields are fully qualified
  862. * (TABLE.FIELD) and that the table and an appropriate GROUP BY already exist in the query.
  863. * Internally the dbtng method "having" is used.
  864. *
  865. * @param $group
  866. * The HAVING group to add these to; groups are used to create AND/OR
  867. * sections. Groups cannot be nested. Use 0 as the default group.
  868. * If the group does not yet exist it will be created as an AND group.
  869. * @param $snippet
  870. * The snippet to check. This can be either a column or
  871. * a complex expression like "COUNT(table.field) > 3"
  872. * @param $args
  873. * An associative array of arguments.
  874. *
  875. * @see QueryConditionInterface::having()
  876. */
  877. function add_having_expression($group, $snippet, $args = array()) {
  878. // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
  879. // the default group.
  880. if (empty($group)) {
  881. $group = 0;
  882. }
  883. // Check for a group.
  884. if (!isset($this->having[$group])) {
  885. $this->set_where_group('AND', $group, 'having');
  886. }
  887. // Add the clause and the args.
  888. $this->having[$group]['conditions'][] = array(
  889. 'field' => $snippet,
  890. 'value' => $args,
  891. 'operator' => 'formula',
  892. );
  893. }
  894. /**
  895. * Add an ORDER BY clause to the query.
  896. *
  897. * @param $table
  898. * The table this field is part of. If a formula, enter NULL.
  899. * If you want to orderby random use "rand" as table and nothing else.
  900. * @param $field
  901. * The field or formula to sort on. If already a field, enter NULL
  902. * and put in the alias.
  903. * @param $order
  904. * Either ASC or DESC.
  905. * @param $alias
  906. * The alias to add the field as. In SQL, all fields in the order by
  907. * must also be in the SELECT portion. If an $alias isn't specified
  908. * one will be generated for from the $field; however, if the
  909. * $field is a formula, this alias will likely fail.
  910. * @param $params
  911. * Any params that should be passed through to the add_field.
  912. */
  913. function add_orderby($table, $field = NULL, $order = 'ASC', $alias = '', $params = array()) {
  914. // Only ensure the table if it's not the special random key.
  915. // @todo: Maybe it would make sense to just add a add_orderby_rand or something similar.
  916. if ($table && $table != 'rand') {
  917. $this->ensure_table($table);
  918. }
  919. // Only fill out this aliasing if there is a table;
  920. // otherwise we assume it is a formula.
  921. if (!$alias && $table) {
  922. $as = $table . '_' . $field;
  923. }
  924. else {
  925. $as = $alias;
  926. }
  927. if ($field) {
  928. $as = $this->add_field($table, $field, $as, $params);
  929. }
  930. $this->orderby[] = array(
  931. 'field' => $as,
  932. 'direction' => strtoupper($order)
  933. );
  934. /**
  935. * -- removing, this should be taken care of by field adding now.
  936. * -- leaving commented because I am unsure.
  937. // If grouping, all items in the order by must also be in the
  938. // group by clause. Check $table to ensure that this is not a
  939. // formula.
  940. if ($this->groupby && $table) {
  941. $this->add_groupby($as);
  942. }
  943. */
  944. }
  945. /**
  946. * Add a simple GROUP BY clause to the query. The caller is responsible
  947. * for ensuring that the fields are fully qualified and the table is properly
  948. * added.
  949. */
  950. function add_groupby($clause) {
  951. // Only add it if it's not already in there.
  952. if (!in_array($clause, $this->groupby)) {
  953. $this->groupby[] = $clause;
  954. }
  955. }
  956. /**
  957. * Returns the alias for the given field added to $table.
  958. *
  959. * @see views_plugin_query_default::add_field()
  960. */
  961. function get_field_alias($table_alias, $field) {
  962. return isset($this->field_aliases[$table_alias][$field]) ? $this->field_aliases[$table_alias][$field] : FALSE;
  963. }
  964. /**
  965. * Adds a query tag to the sql object.
  966. *
  967. * @see SelectQuery::addTag()
  968. */
  969. function add_tag($tag) {
  970. $this->tags[] = $tag;
  971. }
  972. /**
  973. * Generates a unique placeholder used in the db query.
  974. */
  975. function placeholder($base = 'views') {
  976. static $placeholders = array();
  977. if (!isset($placeholders[$base])) {
  978. $placeholders[$base] = 0;
  979. return ':' . $base;
  980. }
  981. else {
  982. return ':' . $base . ++$placeholders[$base];
  983. }
  984. }
  985. /**
  986. * Construct the "WHERE" or "HAVING" part of the query.
  987. *
  988. * As views has to wrap the conditions from arguments with AND, a special
  989. * group is wrapped around all conditions. This special group has the ID 0.
  990. * There is other code in filters which makes sure that the group IDs are
  991. * higher than zero.
  992. *
  993. * @param $where
  994. * 'where' or 'having'.
  995. */
  996. function build_condition($where = 'where') {
  997. $has_condition = FALSE;
  998. $has_arguments = FALSE;
  999. $has_filter = FALSE;
  1000. $main_group = db_and();
  1001. $filter_group = $this->group_operator == 'OR' ? db_or() : db_and();
  1002. foreach ($this->$where as $group => $info) {
  1003. if (!empty($info['conditions'])) {
  1004. $sub_group = $info['type'] == 'OR' ? db_or() : db_and();
  1005. foreach ($info['conditions'] as $key => $clause) {
  1006. // DBTNG doesn't support to add the same subquery twice to the main
  1007. // query and the count query, so clone the subquery to have two instances
  1008. // of the same object. - http://drupal.org/node/1112854
  1009. if (is_object($clause['value']) && $clause['value'] instanceof SelectQuery) {
  1010. $clause['value'] = clone $clause['value'];
  1011. }
  1012. if ($clause['operator'] == 'formula') {
  1013. $has_condition = TRUE;
  1014. $sub_group->where($clause['field'], $clause['value']);
  1015. }
  1016. else {
  1017. $has_condition = TRUE;
  1018. $sub_group->condition($clause['field'], $clause['value'], $clause['operator']);
  1019. }
  1020. }
  1021. // Add the item to the filter group.
  1022. if ($group != 0) {
  1023. $has_filter = TRUE;
  1024. $filter_group->condition($sub_group);
  1025. }
  1026. else {
  1027. $has_arguments = TRUE;
  1028. $main_group->condition($sub_group);
  1029. }
  1030. }
  1031. }
  1032. if ($has_filter) {
  1033. $main_group->condition($filter_group);
  1034. }
  1035. if (!$has_arguments && $has_condition) {
  1036. return $filter_group;
  1037. }
  1038. if ($has_arguments && $has_condition) {
  1039. return $main_group;
  1040. }
  1041. }
  1042. /**
  1043. * Build fields array.
  1044. */
  1045. function compile_fields($fields_array, $query) {
  1046. $non_aggregates = array();
  1047. foreach ($fields_array as $field) {
  1048. $string = '';
  1049. if (!empty($field['table'])) {
  1050. $string .= $field['table'] . '.';
  1051. }
  1052. $string .= $field['field'];
  1053. $fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
  1054. if (!empty($field['distinct'])) {
  1055. throw new Exception("Column-level distinct is not supported anymore.");
  1056. }
  1057. if (!empty($field['count'])) {
  1058. // Retained for compatibility
  1059. $field['function'] = 'count';
  1060. // It seems there's no way to abstract the table+column reference
  1061. // without adding a field, aliasing, and then using the alias.
  1062. }
  1063. if (!empty($field['function'])) {
  1064. $info = $this->get_aggregation_info();
  1065. if (!empty($info[$field['function']]['method']) && function_exists($info[$field['function']]['method'])) {
  1066. $string = $info[$field['function']]['method']($field['function'], $string);
  1067. $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : array();
  1068. $query->addExpression($string, $fieldname, $placeholders);
  1069. }
  1070. $this->has_aggregate = TRUE;
  1071. }
  1072. // This is a formula, using no tables.
  1073. elseif (empty($field['table'])) {
  1074. $non_aggregates[] = $fieldname;
  1075. $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : array();
  1076. $query->addExpression($string, $fieldname, $placeholders);
  1077. }
  1078. elseif ($this->distinct && !in_array($fieldname, $this->groupby)) {
  1079. // d7cx: This code was there, apparently needed for PostgreSQL
  1080. // $string = db_driver() == 'pgsql' ? "FIRST($string)" : $string;
  1081. $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
  1082. }
  1083. elseif (empty($field['aggregate'])) {
  1084. $non_aggregates[] = $fieldname;
  1085. $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
  1086. }
  1087. // @TODO Remove this old code.
  1088. if (!empty($field['distinct']) && empty($field['function'])) {
  1089. $distinct[] = $string;
  1090. }
  1091. else {
  1092. $fields[] = $string;
  1093. }
  1094. if ($this->get_count_optimized) {
  1095. // We only want the first field in this case.
  1096. break;
  1097. }
  1098. }
  1099. return array(
  1100. $non_aggregates,
  1101. );
  1102. }
  1103. /**
  1104. * Generate a query and a countquery from all of the information supplied
  1105. * to the object.
  1106. *
  1107. * @param $get_count
  1108. * Provide a countquery if this is true, otherwise provide a normal query.
  1109. */
  1110. function query($get_count = FALSE) {
  1111. // Check query distinct value.
  1112. if (empty($this->no_distinct) && $this->distinct && !empty($this->fields)) {
  1113. if ($this->pure_distinct === FALSE){
  1114. $base_field_alias = $this->add_field($this->base_table, $this->base_field);
  1115. $this->add_groupby($base_field_alias);
  1116. }
  1117. $distinct = TRUE;
  1118. }
  1119. /**
  1120. * An optimized count query includes just the base field instead of all the fields.
  1121. * Determine of this query qualifies by checking for a groupby or distinct.
  1122. */
  1123. $fields_array = $this->fields;
  1124. if ($get_count && !$this->groupby) {
  1125. foreach ($fields_array as $field) {
  1126. if (!empty($field['distinct']) || !empty($field['function'])) {
  1127. $this->get_count_optimized = FALSE;
  1128. break;
  1129. }
  1130. }
  1131. }
  1132. else {
  1133. $this->get_count_optimized = FALSE;
  1134. }
  1135. if (!isset($this->get_count_optimized)) {
  1136. $this->get_count_optimized = TRUE;
  1137. }
  1138. $options = array();
  1139. $target = 'default';
  1140. $key = 'default';
  1141. // Detect an external database and set the
  1142. if (isset($this->view->base_database)) {
  1143. $key = $this->view->base_database;
  1144. }
  1145. // Set the slave target if the slave option is set
  1146. if (!empty($this->options['slave'])) {
  1147. $target = 'slave';
  1148. }
  1149. // Go ahead and build the query.
  1150. // db_select doesn't support to specify the key, so use getConnection directly.
  1151. $query = Database::getConnection($target, $key)
  1152. ->select($this->base_table, $this->base_table, $options)
  1153. ->addTag('views')
  1154. ->addTag('views_' . $this->view->name);
  1155. // Add the tags added to the view itself.
  1156. foreach ($this->tags as $tag) {
  1157. $query->addTag($tag);
  1158. }
  1159. if (!empty($distinct)) {
  1160. $query->distinct();
  1161. }
  1162. $joins = $where = $having = $orderby = $groupby = '';
  1163. $fields = $distinct = array();
  1164. // Add all the tables to the query via joins. We assume all LEFT joins.
  1165. foreach ($this->table_queue as $table) {
  1166. if (is_object($table['join'])) {
  1167. $table['join']->build_join($query, $table, $this);
  1168. }
  1169. }
  1170. $this->has_aggregate = FALSE;
  1171. $non_aggregates = array();
  1172. list($non_aggregates) = $this->compile_fields($fields_array, $query);
  1173. if (count($this->having)) {
  1174. $this->has_aggregate = TRUE;
  1175. }
  1176. elseif (!$this->has_aggregate) {
  1177. // Allow 'GROUP BY' even no aggregation function has been set.
  1178. $this->has_aggregate = $this->view->display_handler->get_option('group_by');
  1179. }
  1180. if ($this->has_aggregate && (!empty($this->groupby) || !empty($non_aggregates))) {
  1181. $groupby = array_unique(array_merge($this->groupby, $non_aggregates));
  1182. foreach ($groupby as $field) {
  1183. $query->groupBy($field);
  1184. }
  1185. if (!empty($this->having) && $condition = $this->build_condition('having')) {
  1186. $query->havingCondition($condition);
  1187. }
  1188. }
  1189. if (!$this->get_count_optimized) {
  1190. // we only add the orderby if we're not counting.
  1191. if ($this->orderby) {
  1192. foreach ($this->orderby as $order) {
  1193. if ($order['field'] == 'rand_') {
  1194. $query->orderRandom();
  1195. }
  1196. else {
  1197. $query->orderBy($order['field'], $order['direction']);
  1198. }
  1199. }
  1200. }
  1201. }
  1202. if (!empty($this->where) && $condition = $this->build_condition('where')) {
  1203. $query->condition($condition);
  1204. }
  1205. // Add a query comment.
  1206. if (!empty($this->options['query_comment'])) {
  1207. $query->comment($this->options['query_comment']);
  1208. }
  1209. // Add the query tags.
  1210. if (!empty($this->options['query_tags'])) {
  1211. foreach ($this->options['query_tags'] as $tag) {
  1212. $query->addTag($tag);
  1213. }
  1214. }
  1215. // Add all query substitutions as metadata.
  1216. $query->addMetaData('views_substitutions', module_invoke_all('views_query_substitutions', $this));
  1217. if (!$get_count) {
  1218. if (!empty($this->limit) || !empty($this->offset)) {
  1219. // We can't have an offset without a limit, so provide a very large limit
  1220. // instead.
  1221. $limit = intval(!empty($this->limit) ? $this->limit : 999999);
  1222. $offset = intval(!empty($this->offset) ? $this->offset : 0);
  1223. $query->range($offset, $limit);
  1224. }
  1225. }
  1226. return $query;
  1227. }
  1228. /**
  1229. * Get the arguments attached to the WHERE and HAVING clauses of this query.
  1230. */
  1231. function get_where_args() {
  1232. $args = array();
  1233. foreach ($this->where as $group => $where) {
  1234. $args = array_merge($args, $where['args']);
  1235. }
  1236. foreach ($this->having as $group => $having) {
  1237. $args = array_merge($args, $having['args']);
  1238. }
  1239. return $args;
  1240. }
  1241. /**
  1242. * Let modules modify the query just prior to finalizing it.
  1243. */
  1244. function alter(&$view) {
  1245. foreach (module_implements('views_query_alter') as $module) {
  1246. $function = $module . '_views_query_alter';
  1247. $function($view, $this);
  1248. }
  1249. }
  1250. /**
  1251. * Builds the necessary info to execute the query.
  1252. */
  1253. function build(&$view) {
  1254. // Make the query distinct if the option was set.
  1255. if (!empty($this->options['distinct'])) {
  1256. $this->set_distinct(TRUE, !empty($this->options['pure_distinct']));
  1257. }
  1258. // Store the view in the object to be able to use it later.
  1259. $this->view = $view;
  1260. $view->init_pager();
  1261. // Let the pager modify the query to add limits.
  1262. $this->pager->query();
  1263. $view->build_info['query'] = $this->query();
  1264. $view->build_info['count_query'] = $this->query(TRUE);
  1265. }
  1266. /**
  1267. * Executes the query and fills the associated view object with according
  1268. * values.
  1269. *
  1270. * Values to set: $view->result, $view->total_rows, $view->execute_time,
  1271. * $view->current_page.
  1272. */
  1273. function execute(&$view) {
  1274. $external = FALSE; // Whether this query will run against an external database.
  1275. $query = $view->build_info['query'];
  1276. $count_query = $view->build_info['count_query'];
  1277. $query->addMetaData('view', $view);
  1278. $count_query->addMetaData('view', $view);
  1279. if (empty($this->options['disable_sql_rewrite'])) {
  1280. $base_table_data = views_fetch_data($this->base_table);
  1281. if (isset($base_table_data['table']['base']['access query tag'])) {
  1282. $access_tag = $base_table_data['table']['base']['access query tag'];
  1283. $query->addTag($access_tag);
  1284. $count_query->addTag($access_tag);
  1285. }
  1286. }
  1287. $items = array();
  1288. if ($query) {
  1289. $additional_arguments = module_invoke_all('views_query_substitutions', $view);
  1290. // Count queries must be run through the preExecute() method.
  1291. // If not, then hook_query_node_access_alter() may munge the count by
  1292. // adding a distinct against an empty query string
  1293. // (e.g. COUNT DISTINCT(1) ...) and no pager will return.
  1294. // See pager.inc > PagerDefault::execute()
  1295. // http://api.drupal.org/api/drupal/includes--pager.inc/function/PagerDefault::execute/7
  1296. // See http://drupal.org/node/1046170.
  1297. $count_query->preExecute();
  1298. // Build the count query.
  1299. $count_query = $count_query->countQuery();
  1300. // Add additional arguments as a fake condition.
  1301. // XXX: this doesn't work... because PDO mandates that all bound arguments
  1302. // are used on the query. TODO: Find a better way to do this.
  1303. if (!empty($additional_arguments)) {
  1304. // $query->where('1 = 1', $additional_arguments);
  1305. // $count_query->where('1 = 1', $additional_arguments);
  1306. }
  1307. $start = microtime(TRUE);
  1308. try {
  1309. if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
  1310. $this->pager->execute_count_query($count_query);
  1311. }
  1312. $this->pager->pre_execute($query);
  1313. $result = $query->execute();
  1314. $view->result = array();
  1315. foreach ($result as $item) {
  1316. $view->result[] = $item;
  1317. }
  1318. $this->pager->post_execute($view->result);
  1319. if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
  1320. $view->total_rows = $this->pager->get_total_items();
  1321. }
  1322. }
  1323. catch (Exception $e) {
  1324. $view->result = array();
  1325. if (!empty($view->live_preview)) {
  1326. drupal_set_message($e->getMessage(), 'error');
  1327. }
  1328. else {
  1329. vpr('Exception in @human_name[@view_name]: @message', array('@human_name' => $view->human_name, '@view_name' => $view->name, '@message' => $e->getMessage()));
  1330. }
  1331. }
  1332. }
  1333. else {
  1334. $start = microtime(TRUE);
  1335. }
  1336. $view->execute_time = microtime(TRUE) - $start;
  1337. }
  1338. function add_signature(&$view) {
  1339. $view->query->add_field(NULL, "'" . $view->name . ':' . $view->current_display . "'", 'view_name');
  1340. }
  1341. function get_aggregation_info() {
  1342. // @todo -- need a way to get database specific and customized aggregation
  1343. // functions into here.
  1344. return array(
  1345. 'group' => array(
  1346. 'title' => t('Group results together'),
  1347. 'is aggregate' => FALSE,
  1348. ),
  1349. 'count' => array(
  1350. 'title' => t('Count'),
  1351. 'method' => 'views_query_default_aggregation_method_simple',
  1352. 'handler' => array(
  1353. 'argument' => 'views_handler_argument_group_by_numeric',
  1354. 'field' => 'views_handler_field_numeric',
  1355. 'filter' => 'views_handler_filter_group_by_numeric',
  1356. 'sort' => 'views_handler_sort_group_by_numeric',
  1357. ),
  1358. ),
  1359. 'count_distinct' => array(
  1360. 'title' => t('Count DISTINCT'),
  1361. 'method' => 'views_query_default_aggregation_method_distinct',
  1362. 'handler' => array(
  1363. 'argument' => 'views_handler_argument_group_by_numeric',
  1364. 'field' => 'views_handler_field_numeric',
  1365. 'filter' => 'views_handler_filter_group_by_numeric',
  1366. 'sort' => 'views_handler_sort_group_by_numeric',
  1367. ),
  1368. ),
  1369. 'sum' => array(
  1370. 'title' => t('Sum'),
  1371. 'method' => 'views_query_default_aggregation_method_simple',
  1372. 'handler' => array(
  1373. 'argument' => 'views_handler_argument_group_by_numeric',
  1374. 'filter' => 'views_handler_filter_group_by_numeric',
  1375. 'sort' => 'views_handler_sort_group_by_numeric',
  1376. ),
  1377. ),
  1378. 'avg' => array(
  1379. 'title' => t('Average'),
  1380. 'method' => 'views_query_default_aggregation_method_simple',
  1381. 'handler' => array(
  1382. 'argument' => 'views_handler_argument_group_by_numeric',
  1383. 'filter' => 'views_handler_filter_group_by_numeric',
  1384. 'sort' => 'views_handler_sort_group_by_numeric',
  1385. ),
  1386. ),
  1387. 'min' => array(
  1388. 'title' => t('Minimum'),
  1389. 'method' => 'views_query_default_aggregation_method_simple',
  1390. 'handler' => array(
  1391. 'argument' => 'views_handler_argument_group_by_numeric',
  1392. 'filter' => 'views_handler_filter_group_by_numeric',
  1393. 'sort' => 'views_handler_sort_group_by_numeric',
  1394. ),
  1395. ),
  1396. 'max' => array(
  1397. 'title' => t('Maximum'),
  1398. 'method' => 'views_query_default_aggregation_method_simple',
  1399. 'handler' => array(
  1400. 'argument' => 'views_handler_argument_group_by_numeric',
  1401. 'filter' => 'views_handler_filter_group_by_numeric',
  1402. 'sort' => 'views_handler_sort_group_by_numeric',
  1403. ),
  1404. ),
  1405. 'stddev_pop' => array(
  1406. 'title' => t('Standard deviation'),
  1407. 'method' => 'views_query_default_aggregation_method_simple',
  1408. 'handler' => array(
  1409. 'argument' => 'views_handler_argument_group_by_numeric',
  1410. 'filter' => 'views_handler_filter_group_by_numeric',
  1411. 'sort' => 'views_handler_sort_group_by_numeric',
  1412. ),
  1413. )
  1414. );
  1415. }
  1416. /**
  1417. * Returns the according entity objects for the given query results.
  1418. *
  1419. */
  1420. function get_result_entities($results, $relationship = NULL) {
  1421. $base_table = $this->base_table;
  1422. $base_table_alias = $base_table;
  1423. if (!empty($relationship)) {
  1424. foreach ($this->view->relationship as $current) {
  1425. if ($current->alias == $relationship) {
  1426. $base_table = $current->definition['base'];
  1427. $base_table_alias = $relationship;
  1428. break;
  1429. }
  1430. }
  1431. }
  1432. $table_data = views_fetch_data($base_table);
  1433. // Bail out if the table has not specified the according entity-type.
  1434. if (!isset($table_data['table']['entity type'])) {
  1435. return FALSE;
  1436. }
  1437. $entity_type = $table_data['table']['entity type'];
  1438. $info = entity_get_info($entity_type);
  1439. $id_alias = $this->get_field_alias($base_table_alias, $info['entity keys']['id']);
  1440. // Assemble the ids of the entities to load.
  1441. $ids = array();
  1442. foreach ($results as $key => $result) {
  1443. if (isset($result->$id_alias)) {
  1444. $ids[$key] = $result->$id_alias;
  1445. }
  1446. }
  1447. $entities = entity_load($entity_type, $ids);
  1448. // Re-key the array by row-index.
  1449. $result = array();
  1450. foreach ($ids as $key => $id) {
  1451. $result[$key] = isset($entities[$id]) ? $entities[$id] : FALSE;
  1452. }
  1453. return array($entity_type, $result);
  1454. }
  1455. }
  1456. function views_query_default_aggregation_method_simple($group_type, $field) {
  1457. return strtoupper($group_type) . '(' . $field . ')';
  1458. }
  1459. function views_query_default_aggregation_method_distinct($group_type, $field) {
  1460. $group_type = str_replace('_distinct', '', $group_type);
  1461. return strtoupper($group_type) . '(DISTINCT ' . $field . ')';
  1462. }
  1463. /**
  1464. * Validation callback for query tags.
  1465. */
  1466. function views_element_validate_tags($element, &$form_state) {
  1467. $values = array_map('trim', explode(',', $element['#value']));
  1468. foreach ($values as $value) {
  1469. if (preg_match("/[^a-z_]/", $value)) {
  1470. form_error($element, t('The query tags may only contain lower-case alphabetical characters and underscores.'));
  1471. return;
  1472. }
  1473. }
  1474. }