* The offset for the query.
* @param int $rowCount
* The number of rows to return.
- * @param string $sort
+ * @param string|CRM_Utils_Sort $sort
* The order by string.
* @param bool $count
* Is this a count only query ?.
$order = $orderBy = $limit = '';
if (!$count) {
- $config = CRM_Core_Config::singleton();
- if ($config->includeOrderByClause ||
- isset($this->_distinctComponentClause)
- ) {
- if ($sort) {
- if (is_string($sort)) {
- $orderBy = $sort;
- }
- else {
- $orderBy = trim($sort->orderBy());
- }
- // Deliberately remove the backticks again, as they mess up the evil
- // string munging below. This balanced by re-escaping before use.
- $orderBy = str_replace('`', '', $orderBy);
-
- if (!empty($orderBy)) {
- // this is special case while searching for
- // change log CRM-1718
- if (preg_match('/sort_name/i', $orderBy)) {
- $orderBy = str_replace('sort_name', 'contact_a.sort_name', $orderBy);
- }
-
- $order = " ORDER BY $orderBy";
-
- if ($sortOrder) {
- $order .= " $sortOrder";
- }
-
- // always add contact_a.id to the ORDER clause
- // so the order is deterministic
- if (strpos('contact_a.id', $order) === FALSE) {
- $order .= ", contact_a.id";
- }
- }
- }
- elseif ($sortByChar) {
- $order = " ORDER BY UPPER(LEFT(contact_a.sort_name, 1)) asc";
- }
- else {
- $order = " ORDER BY contact_a.sort_name asc, contact_a.id";
- }
- }
-
- // hack for order clause
- if ($order) {
- $fieldStr = trim(str_replace('ORDER BY', '', $order));
- $fieldOrder = explode(' ', $fieldStr);
- $field = $fieldOrder[0];
-
- if ($field) {
- switch ($field) {
- case 'city':
- case 'postal_code':
- $this->_tables["civicrm_address"] = $this->_whereTables["civicrm_address"] = 1;
- $order = str_replace($field, "civicrm_address.{$field}", $order);
- break;
-
- case 'country':
- case 'state_province':
- $this->_tables["civicrm_{$field}"] = $this->_whereTables["civicrm_{$field}"] = 1;
- if (is_array($this->_returnProperties) && empty($this->_returnProperties)) {
- $additionalFromClause .= " LEFT JOIN civicrm_{$field} ON civicrm_{$field}.id = civicrm_address.{$field}_id";
- }
- $order = str_replace($field, "civicrm_{$field}.name", $order);
- break;
-
- case 'email':
- $this->_tables["civicrm_email"] = $this->_whereTables["civicrm_email"] = 1;
- $order = str_replace($field, "civicrm_email.{$field}", $order);
- break;
-
- default:
- //CRM-12565 add "`" around $field if it is a pseudo constant
- foreach ($this->_pseudoConstantsSelect as $key => $value) {
- if (!empty($value['element']) && $value['element'] == $field) {
- $order = str_replace($field, "`{$field}`", $order);
- }
- }
- }
- $this->_fromClause = self::fromClause($this->_tables, NULL, NULL, $this->_primaryLocation, $this->_mode);
- $this->_simpleFromClause = self::fromClause($this->_whereTables, NULL, NULL, $this->_primaryLocation, $this->_mode);
- }
- }
-
- // The above code relies on crazy brittle string manipulation of a peculiarly-encoded ORDER BY
- // clause. But this magic helper which forgivingly reescapes ORDER BY.
- // Note: $sortByChar implies that $order was hard-coded/trusted, so it can do funky things.
- if ($order && !$sortByChar) {
- $order = ' ORDER BY ' . CRM_Utils_Type::escape(preg_replace('/^\s*ORDER BY\s*/', '', $order), 'MysqlOrderBy');
- }
+ list($order, $additionalFromClause) = $this->prepareOrderBy($sort, $sortByChar, $sortOrder, $additionalFromClause);
if ($rowCount > 0 && $offset >= 0) {
$offset = CRM_Utils_Type::escape($offset, 'Int');
}
}
+ /**
+ * Parse and assimilate the various sort options.
+ *
+ * Side-effect: if sorting on a common column from a related table (`city`, `postal_code`,
+ * `email`), the related table may be joined automatically.
+ *
+ * At time of writing, this code is deeply flawed and should be rewritten. For the moment,
+ * it's been extracted to a standalone function.
+ *
+ * @param string|CRM_Utils_Sort $sort
+ * The order by string.
+ * @param bool $sortByChar
+ * If true returns the distinct array of first characters for search results.
+ * @param null $sortOrder
+ * Who knows? Hu knows. He who knows Hu knows who.
+ * @param string $additionalFromClause
+ * Should be clause with proper joins, effective to reduce where clause load.
+ * @return array
+ * list(string $orderByClause, string $additionalFromClause).
+ */
+ protected function prepareOrderBy($sort, $sortByChar, $sortOrder, $additionalFromClause) {
+ $config = CRM_Core_Config::singleton();
+ if ($config->includeOrderByClause ||
+ isset($this->_distinctComponentClause)
+ ) {
+ if ($sort) {
+ if (is_string($sort)) {
+ $orderBy = $sort;
+ }
+ else {
+ $orderBy = trim($sort->orderBy());
+ }
+ // Deliberately remove the backticks again, as they mess up the evil
+ // string munging below. This balanced by re-escaping before use.
+ $orderBy = str_replace('`', '', $orderBy);
+
+ if (!empty($orderBy)) {
+ // this is special case while searching for
+ // change log CRM-1718
+ if (preg_match('/sort_name/i', $orderBy)) {
+ $orderBy = str_replace('sort_name', 'contact_a.sort_name', $orderBy);
+ }
+
+ $order = " ORDER BY $orderBy";
+
+ if ($sortOrder) {
+ $order .= " $sortOrder";
+ }
+
+ // always add contact_a.id to the ORDER clause
+ // so the order is deterministic
+ if (strpos('contact_a.id', $order) === FALSE) {
+ $order .= ", contact_a.id";
+ }
+ }
+ }
+ elseif ($sortByChar) {
+ $order = " ORDER BY UPPER(LEFT(contact_a.sort_name, 1)) asc";
+ }
+ else {
+ $order = " ORDER BY contact_a.sort_name asc, contact_a.id";
+ }
+ }
+
+ // hack for order clause
+ if ($order) {
+ $fieldStr = trim(str_replace('ORDER BY', '', $order));
+ $fieldOrder = explode(' ', $fieldStr);
+ $field = $fieldOrder[0];
+
+ if ($field) {
+ switch ($field) {
+ case 'city':
+ case 'postal_code':
+ $this->_tables["civicrm_address"] = $this->_whereTables["civicrm_address"] = 1;
+ $order = str_replace($field, "civicrm_address.{$field}", $order);
+ break;
+
+ case 'country':
+ case 'state_province':
+ $this->_tables["civicrm_{$field}"] = $this->_whereTables["civicrm_{$field}"] = 1;
+ if (is_array($this->_returnProperties) && empty($this->_returnProperties)) {
+ $additionalFromClause .= " LEFT JOIN civicrm_{$field} ON civicrm_{$field}.id = civicrm_address.{$field}_id";
+ }
+ $order = str_replace($field, "civicrm_{$field}.name", $order);
+ break;
+
+ case 'email':
+ $this->_tables["civicrm_email"] = $this->_whereTables["civicrm_email"] = 1;
+ $order = str_replace($field, "civicrm_email.{$field}", $order);
+ break;
+
+ default:
+ //CRM-12565 add "`" around $field if it is a pseudo constant
+ foreach ($this->_pseudoConstantsSelect as $key => $value) {
+ if (!empty($value['element']) && $value['element'] == $field) {
+ $order = str_replace($field, "`{$field}`", $order);
+ }
+ }
+ }
+ $this->_fromClause = self::fromClause($this->_tables, NULL, NULL, $this->_primaryLocation, $this->_mode);
+ $this->_simpleFromClause = self::fromClause($this->_whereTables, NULL, NULL, $this->_primaryLocation, $this->_mode);
+ }
+ }
+
+ // The above code relies on crazy brittle string manipulation of a peculiarly-encoded ORDER BY
+ // clause. But this magic helper which forgivingly reescapes ORDER BY.
+ // Note: $sortByChar implies that $order was hard-coded/trusted, so it can do funky things.
+ if ($order && !$sortByChar) {
+ $order = ' ORDER BY ' . CRM_Utils_Type::escape(preg_replace('/^\s*ORDER BY\s*/', '', $order), 'MysqlOrderBy');
+ return array($order, $additionalFromClause);
+ }
+ return array($order, $additionalFromClause);
+ }
+
}