views_natural_sort.module 14.2 KB
Newer Older
1
<?php
2

3 4
/**
 * @file
5 6 7 8
 * Views Natural Sort module.
 */

/*
9
 * Provides a views filter that sorts titles by a more natural manner by
10
 * ignoring articles like "The" and "A.".
11 12 13 14 15 16
 *
 * Normal sort:
 * A Chorus Line
 * All American
 * Fiddler on the Roof
 * Oklahoma!
17
 * The King And I.
18 19 20 21 22 23 24 25 26 27
 *
 * Natural sort:
 * All American
 * A Chorus Line
 * Fiddler on the Roof
 * The King And I
 * Oklahoma!
 */

/**
28
 * Implements hook_menu().
29 30 31
 */
function views_natural_sort_menu() {
  $items = array();
32
  $items['admin/structure/views/settings/views_natural_sort'] = array(
33
    'title' => 'Natural Sort',
34 35
    'description' => 'Set the settings for how particular transformations should behave.',
    'page callback' => 'views_natural_sort_settings_page',
36 37 38
    'access callback' => 'user_access',
    'access arguments' => array('administer views'),
    'file' => 'views_natural_sort.admin.inc',
39
    'type' => MENU_LOCAL_TASK,
40
  );
41 42 43
  return $items;
}

44
/**
45
 * Implements hook_views_api().
46 47 48
 */
function views_natural_sort_views_api() {
  return array(
49
    'api' => 3.0,
50 51
  );
}
52 53

/**
54
 * Implements hook_implements_alter().
55
 */
56 57 58 59 60 61 62 63
function views_natural_sort_module_implements_alter(&$implements, $hook) {
  if ($hook == 'views_data_alter') {
    // Make views natural sort always last so we get all the up to date info.
    $group = $implements['views_natural_sort'];
    unset($implements['views_natural_sort']);
    $implements['views_natural_sort'] = $group;
  }
}
64

65
/**
66
 * Implements hook_views_natural_sort_get_entry_types().
67
 */
68
function views_natural_sort_views_natural_sort_get_entry_types() {
69
  $supported_entity_properties = views_natural_sort_get_views_configurable_properties();
70 71 72 73 74 75 76 77 78 79 80 81
  $entry_types = array();
  foreach ($supported_entity_properties as $entity_type => $properties) {
    foreach ($properties as $property => $schema_info) {
      $entry_types[] = array(
        'entity_type' => $entity_type,
        'field' => $property,
      );
    }
  }
  return $entry_types;
}

82
/**
83
 * Implements hook_views_natural_sort_queue_rebuild_data().
84
 */
85
function views_natural_sort_views_natural_sort_queue_rebuild_data($entry_type) {
86
  $supported_entity_properties = views_natural_sort_get_views_configurable_properties();
87 88
  if (empty($supported_entity_properties[$entry_type['entity_type']]) ||
    empty($supported_entity_properties[$entry_type['entity_type']][$entry_type['field']])) {
89 90
    return array();
  }
91
  $queue = views_natural_sort_get_queue();
92 93

  $query = new EntityFieldQuery();
94 95
  $result = $query->entityCondition('entity_type', $entry_type['entity_type'])
    ->execute();
96 97 98 99 100 101 102
  $entity_ids = array();

  if (isset($result[$entry_type['entity_type']])) {
    $entity_ids = array_keys($result[$entry_type['entity_type']]);
  }

  foreach ($entity_ids as $entity_id) {
103 104
    $results = entity_load($entry_type['entity_type'], array($entity_id));
    $entity = reset($results);
105
    $queue->createItem(array(
106
      'eid' => $entity_id,
107 108 109 110
      'entity_type' => $entry_type['entity_type'],
      'field' => $entry_type['field'],
      'delta' => 0,
      'content' => $entity->$entry_type['field'],
111
    ));
112 113 114 115
  }
}

/**
116
 * Implements hook_entity_insert().
117 118 119
 *
 * This keeps our natural sort index up to date.
 */
120
function views_natural_sort_entity_insert($entity, $type) {
121
  $supported_entity_properties = views_natural_sort_get_views_configurable_properties();
122 123 124 125
  if (empty($supported_entity_properties[$type])) {
    return;
  }
  foreach ($supported_entity_properties[$type] as $property => $property_info) {
126
    // Proposed by hgoto in #2672538. Made it in before he got credit.
generalredneck's avatar
generalredneck committed
127 128 129
    if (isset($entity->{$property})) {
      views_natural_sort_store(views_natural_sort_entity_to_vns($entity, $type, $property));
    }
130
  }
131 132 133
}

/**
134
 * Implements hook_entity_update().
135 136 137
 *
 * This keeps our natural sort index up to date.
 */
138 139
function views_natural_sort_entity_update($entity, $type) {
  views_natural_sort_entity_insert($entity, $type);
140 141 142
}

/**
143
 * Implements hook_entity_delete().
144 145 146
 *
 * This keep sour natural sort index clean.
 */
147
function views_natural_sort_entity_delete($entity, $type) {
148 149
  $entity_info = entity_get_info($type);
  $id_field = $entity_info['entity keys']['id'];
150 151 152 153
  views_natural_sort_remove($entry = array(
    'eid' => $entity->$id_field,
    'entity_type' => $type,
  ));
154 155 156
}

/**
157
 * Store Multiple views_natural_sort entries.
158
 *
159 160
 * @param array $index_entries
 *   An array of entries to store in the views_natural_sort table.
161
 *
162
 * @see views_natural_sort_store
163
 */
164
function views_natural_sort_store_multiple(array $index_entries) {
165
  foreach ($index_entries as $entry) {
166 167
    views_natural_sort_store($entry);
  }
168 169
}

170
/**
171
 * Save an entry to the database that represents a views_natural_sort index.
172 173 174 175 176 177 178 179 180 181 182
 *
 * @param array $index_entry
 *   Mirrors the views_natural_sort table
 *     $eid - Entity Id of the item referenced
 *     $entity_type - The Entity Type. Ex. node
 *     $field - reference to the property or field name
 *     $delta - the item number in that field or property
 *     $content - The transformed data that a field will
 *                be sorted by.
 */
function views_natural_sort_store(array $index_entry) {
183 184
  // This should take a formatted object and store it into the
  // views_natural_sort table.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
  $string = views_natural_sort_transform($index_entry);

  // The size limit on the content field for views_natual_sort is sometimes not
  // enough. Lets truncate all data down to that size. I personally feel the
  // inaccuracy is an acceptable loss, as the bigger the string gets, the less
  // permanent the sort.
  //
  // TODO: Have this pick up off of the schema so if someone does a
  // hook_schema_alter() on me.
  return db_merge('views_natural_sort')
    ->key(array(
      'eid' => $index_entry['eid'],
      'entity_type' => $index_entry['entity_type'],
      'field' => $index_entry['field'],
      'delta' => $index_entry['delta'],
    ))
    ->fields(array(
      'eid' => $index_entry['eid'],
      'entity_type' => $index_entry['entity_type'],
      'field' => $index_entry['field'],
      'delta' => $index_entry['delta'],
      'content' => substr($string, 0, 255),
    ))
    ->execute();
}

/**
212
 * Remove a views_natural_sort index entry based on keys.
213 214 215 216 217
 *
 * @param array $index_entry
 *   Mirrors the views_natural_sort table
 *     $eid - Entity Id of the item referenced
 *     $entity_type - The Entity Type. Ex. node
218 219 220
 *     $field - (optional) reference to the property or field name
 *     $delta - (optional)the item number in that field or property
 *   If an optional parameter doesn't exist, this is treated as a wild care
221
 *   delete.
222
 */
223
function views_natural_sort_remove(array $index_entry) {
224
  $query = db_delete('views_natural_sort')
225
    ->condition('eid', $index_entry['eid'])
226 227 228 229 230 231 232 233
    ->condition('entity_type', $index_entry['entity_type']);
  if (isset($index_entry['field'])) {
    $query->condition('field', $index_entry['field']);
  }
  if (isset($index_entry['delta'])) {
    $query->condition('delta', $index_entry['delta']);
  }
  $query->execute();
234 235 236
}

/**
237 238 239 240
 * Encodes a string into an ascii-sortable string.
 *
 * Encoding rquires a set of transformations. Those transformations perform
 * functionality such as:
241 242
 *  - Leading articles in common languages are ingored: The A An El La Le Il
 *  - Unimportant punctuation is ignored: # ' " ( )
243
 *  - Unimportant words are ignored: and of or.
244
 *
245 246 247 248 249
 * @param array $index_entry
 *   Mirrors the views_natural_sort table
 *     $eid - Entity Id of the item referenced
 *     $entity_type - The Entity Type. Ex. node
 *     $field - reference to the property or field name
250
 *     $delta - the item number in that field or property.
251
 *
252 253
 * @return string
 *   The transformed string
254
 */
255
function views_natural_sort_transform(array $index_entry) {
256 257 258 259 260 261 262
  // Get copy the original string.
  $string = $index_entry['content'];
  module_load_include('inc', 'views_natural_sort', 'views_natural_sort');
  foreach (views_natural_sort_get_transformations($index_entry) as $transformation_method) {
    $string = $transformation_method($string);
  }
  return $string;
263 264
}

265 266
/**
 * Get the full list of transformations to run when saving a natural sort entry.
267
 *
268 269 270 271 272 273 274 275 276 277 278 279
 * @param array $index_entry
 *   The original entry to be written to the views_natural_sort table.
 *     $eid - Entity Id of the item referenced
 *     $entity_type - The Entity Type. Ex. node
 *     $field - reference to the property or field name
 *     $delta - the item number in that field or property
 *     $content - The transformed data that a field will
 *                be sorted by.
 *
 * @return array
 *   The final list of transformations.
 */
280
function views_natural_sort_get_transformations(array $index_entry) {
281 282 283 284 285 286
  $transformations = array(
    'views_natural_sort_remove_beginning_words',
    'views_natural_sort_remove_words',
    'views_natural_sort_remove_symbols',
    'views_natural_sort_numbers',
  );
287 288 289 290 291 292

  if (variable_get('views_natural_sort_days_of_the_week_enabled', FALSE)) {
    $transformations[] = "views_natural_sort_days_of_the_week_sort_days";
  }
  // Allow other modules to modify the transformation that happens here if
  // needed.
293 294 295
  drupal_alter('views_natural_sort_transformations', $transformations, $index_entry);
  return $transformations;
}
296

297 298 299 300
/**
 * Retrieve the full list of entities and properties that can be supported.
 *
 * @return array
301 302
 *   An array of property information keyed by entity machine name. Example:
 *   array (
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
 *    'node' => array (
 *      'type' => array (
 *        'base_table' => 'node',
 *        'schema_field' => 'type',
 *      ),
 *      'title' => array (
 *        'base_table' => 'node',
 *        'schema_field' => 'title',
 *      ),
 *      'language' => array (
 *        'base_table' => 'node',
 *        'schema_field' => 'language',
 *      ),
 *    ),
 *    'user' => array (
 *      'name' => array (
 *        'base_table' => 'users',
 *        'schema_field' => 'name',
 *      ),
 *      'mail' => array (
 *        'base_table' => 'users',
 *        'schema_field' => 'mail',
 *      ),
 *      'theme' => array (
 *        'base_table' => 'users',
 *        'schema_field' => 'theme',
 *      ),
 *    ),
 *    'file' => array (
 *      'name' => array (
 *        'base_table' => 'file_managed',
 *        'schema_field' => 'filename',
 *      ),
 *      'mime' => array (
 *        'base_table' => 'file_managed',
 *        'schema_field' => 'filemime',
 *      ),
 *    ),
341
 *   )
342
 */
343 344 345 346 347 348 349 350 351 352
function views_natural_sort_get_supported_entity_properties() {
  $supported_properties = &drupal_static(__FUNCTION__, array());
  if (empty($supported_properties)) {
    $entity_property_info = entity_get_property_info();
    $entity_info = entity_get_info();
    foreach ($entity_property_info as $entity_type => $info) {
      if (empty($supported_properties[$entity_type])) {
        $supported_properties[$entity_type] = array();
      }
      $properties = $info['properties'];
353
      foreach ($properties as $property => $property_info) {
354
        $property_info += entity_property_info_defaults();
355 356 357 358
        if ($property_info['type'] != 'text' || empty($property_info['schema field'])) {
          continue;
        }
        $schema = drupal_get_schema($entity_info[$entity_type]['base table']);
359
        $schema_field = $property_info['schema field'];
360
        if (empty($schema['fields'][$schema_field]) || $schema['fields'][$schema_field]['type'] != 'varchar') {
361 362 363 364 365 366 367 368 369 370 371 372
          continue;
        }
        $supported_properties[$entity_type][$property] = array(
          'base_table' => $entity_info[$entity_type]['base table'],
          'schema_field' => $property_info['schema field'],
        );
      }
    }
  }
  return $supported_properties;
}

373 374 375
/**
 * Returns a list of properties that we know views will allow us to alter.
 *
376 377 378 379
 * This list of properties is more realistic than "supported properties" because
 * it factors in what views actually contains handlers for. This is used by
 * all the administration functions to determine what properties need to be
 * affected by VNS.
380 381
 *
 * @return mixed
382 383 384
 *   Returns an array formatted as
 *   views_natural_sort_get_supported_entity_properties or FALSE when views
 *   hasn't initianalized yet.
385
 *
386
 * @see views_natural_sort_get_supported_entity_properties
387 388 389 390 391 392 393 394 395
 */
function views_natural_sort_get_views_configurable_properties() {
  $views_configurable_properties = &drupal_static(__FUNCTION__, array());
  if (empty($supported_properties)) {
    $supported_entity_properties = views_natural_sort_get_supported_entity_properties();
    $views_data = views_fetch_data();
    if (empty($views_data)) {
      return FALSE;
    }
396 397
    foreach ($supported_entity_properties as $entity => $properties) {
      foreach ($properties as $property => $schema_info) {
398 399 400 401 402 403
        if (!empty($views_data[$schema_info['base_table']][$schema_info['schema_field']]) &&
          !empty($views_data[$schema_info['base_table']][$schema_info['schema_field']]['sort']) &&
          !empty($views_data[$schema_info['base_table']][$schema_info['schema_field']]['sort']['handler']) &&
          in_array($views_data[$schema_info['base_table']][$schema_info['schema_field']]['sort']['handler'], array('views_natural_sort_handler_sort', 'views_handler_sort'))) {
          $views_configurable_properties[$entity][$property] = $schema_info;
        }
404 405 406 407 408 409
      }
    }
  }
  return $views_configurable_properties;
}

410 411 412
/**
 * A helper function for creating a VNS record for storage.
 *
413
 * @param object $entity
414
 *   An object representing an entity.
415
 * @param string $entity_type
416
 *   The machine name for an entity type.
417
 * @param string $field
418
 *   The machine name for the field the data belongs to.
419 420 421 422
 *
 * @return array
 *   An array that represents the VNS table row to be inserted.
 */
423
function views_natural_sort_entity_to_vns($entity, $entity_type, $field) {
424
  $supported_entity_properties = views_natural_sort_get_views_configurable_properties();
425 426 427 428 429 430 431 432 433 434 435 436 437 438
  if (empty($supported_entity_properties[$entity_type]) ||
    empty($supported_entity_properties[$entity_type][$field])) {
    throw new Exception("$entity_type -> $field doesn't exist. Cannot create Views Natural Sort record");
  }
  $entity_info = entity_get_info($entity_type);
  $id_field = $entity_info['entity keys']['id'];
  return array(
    'eid' => $entity->$id_field,
    'entity_type' => $entity_type,
    'field' => $field,
    'delta' => 0,
    'content' => $entity->$field,
  );
}
439 440

/**
441
 * Get the queue used to store natural sort index jobs.
442 443 444 445
 */
function views_natural_sort_get_queue() {
  return DrupalQueue::get('views_natural_sort_index_queue');
}