filefield_paths.module 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. <?php
  2. /**
  3. * @file
  4. * Contains core functions for the FileField Paths module.
  5. */
  6. /**
  7. * Include additional files.
  8. */
  9. foreach (module_list() as $module) {
  10. if (file_exists($file = dirname(__FILE__) . "/modules/{$module}.inc")) {
  11. require_once $file;
  12. }
  13. }
  14. /**
  15. * Implements hook_form_alter().
  16. */
  17. function filefield_paths_form_alter(&$form, $form_state, $form_id) {
  18. $ffp = array();
  19. // Invoke hook_filefield_paths_form_alter().
  20. foreach (module_implements('filefield_paths_form_alter') as $module) {
  21. $function = "{$module}_filefield_paths_form_alter";
  22. $function($form, $ffp);
  23. }
  24. // If supporting module enabled, show FileField Paths settings form.
  25. if (count($ffp) > 0) {
  26. $entity_info = entity_get_info($form['#instance']['entity_type']);
  27. $fields = module_invoke_all('filefield_paths_field_settings');
  28. foreach ($ffp as $field_name => $field_data) {
  29. $active_updating = 0;
  30. $results = db_select('filefield_paths', 'f')
  31. ->fields('f')
  32. ->condition('type', $field_data['type'])
  33. ->condition('field', $field_name)
  34. ->execute()
  35. ->fetchAllAssoc('type');
  36. if (!empty($results[$field_data['type']])) {
  37. $result = $results[$field_data['type']];
  38. foreach ($fields as &$field) {
  39. $field['settings'] = unserialize($result->{$field['sql']});
  40. }
  41. unset($field);
  42. $active_updating = $result->active_updating;
  43. }
  44. $count = 0;
  45. foreach ($fields as $name => $field) {
  46. $count++;
  47. if (isset($field['form']) && is_array($field['form'])) {
  48. $keys = array_keys($field['form']);
  49. for ($i = 1; $i < count($field['form']); $i++) {
  50. $field['form'][$keys[$i]]['#weight'] = ($count - 1) * 3 + 2 + $i;
  51. $field['form'][$keys[$i]]['#element_validate'] = array('token_element_validate');
  52. $field['form'][$keys[$i]]['#token_types'] = array('file', $entity_info['token type']);
  53. }
  54. unset($keys);
  55. $field_data['form_path'] = array_merge_recursive($field_data['form_path'], $field['form']);
  56. }
  57. $field_data['form_path']['#tree'] = TRUE;
  58. $field_data['form_path'][$name]['#weight'] = ($count - 1) * 3;
  59. // Set defualt value for patterns.
  60. if (isset($field['settings']['value'])) {
  61. $field_data['form_path'][$name]['#default_value'] = $field['settings']['value'];
  62. if (isset($field['data'])) {
  63. foreach ($field['data'] as $key => $value) {
  64. $field_data['form_path'][$value]['#default_value'] = $field['settings'][$key];
  65. }
  66. }
  67. }
  68. $field_data['form_path']["{$name}_cleanup"] = array(
  69. '#type' => 'fieldset',
  70. '#title' => t('@title cleanup settings', array('@title' => $field['title'])),
  71. '#collapsible' => TRUE,
  72. '#collapsed' => TRUE,
  73. '#weight' => ($count - 1) * 3 + 1,
  74. '#attributes' => array(
  75. 'class' => array("{$name} cleanup")
  76. )
  77. );
  78. // Cleanup field with Pathauto module.
  79. $field_data['form_path']["{$name}_cleanup"]["{$name}_pathauto"] = array(
  80. '#type' => 'checkbox',
  81. '#title' => t('Cleanup using Pathauto') . '.',
  82. '#default_value' => isset($field['settings']['pathauto'])
  83. ? $field['settings']['pathauto']
  84. : 0
  85. ,
  86. '#description' => t('Cleanup @title using', array('@title' => $field['title'])) . ' ' . l(t('Pathauto settings'), 'admin/config/search/path/settings'),
  87. );
  88. if (!module_exists('pathauto')) {
  89. $field_data['form_path']["{$name}_cleanup"]["{$name}_pathauto"]['#disabled'] = TRUE;
  90. $field_data['form_path']["{$name}_cleanup"]["{$name}_pathauto"]['#default_value'] = 0;
  91. }
  92. // Convert field to lower case.
  93. $field_data['form_path']["{$name}_cleanup"]["{$name}_tolower"] = array(
  94. '#type' => 'checkbox',
  95. '#title' => t('Convert to lower case') . '.',
  96. '#default_value' => isset($field['settings']['tolower'])
  97. ? $field['settings']['tolower']
  98. : 0
  99. ,
  100. '#description' => t('Convert @title to lower case', array('@title' => $field['title'])) . '.'
  101. );
  102. // Transliterate field with Transliteration module.
  103. $field_data['form_path']["{$name}_cleanup"]["{$name}_transliterate"] = array(
  104. '#type' => 'checkbox',
  105. '#title' => t('Transliterate') . '.',
  106. '#default_value' => isset($field['settings']['transliterate'])
  107. ? $field['settings']['transliterate']
  108. : 0
  109. ,
  110. '#description' => t('Transliterate @title', array('@title' => $field['title'])) . '.'
  111. );
  112. if (!module_exists('transliteration')) {
  113. $field_data['form_path']["{$name}_cleanup"]["{$name}_transliterate"]['#disabled'] = TRUE;
  114. $field_data['form_path']["{$name}_cleanup"]["{$name}_transliterate"]['#default_value'] = 0;
  115. }
  116. }
  117. // Replacement patterns for field.
  118. $field_data['form_path']['token_tree'] = array(
  119. '#type' => 'fieldset',
  120. '#title' => t('Replacement patterns'),
  121. '#collapsible' => TRUE,
  122. '#collapsed' => TRUE,
  123. '#description' => theme('token_tree', array('token_types' => array('file', $entity_info['token type']))),
  124. '#weight' => 10,
  125. );
  126. // Retroactive updates.
  127. $field_data['form_path']['retroactive_update'] = array(
  128. '#type' => 'checkbox',
  129. '#title' => t('Retroactive update'),
  130. '#description' => t('Move and rename previously uploaded files') . '.' .
  131. '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
  132. t('This feature should only be used on developmental servers or with extreme caution') . '.',
  133. '#weight' => 11
  134. );
  135. // Active updating.
  136. $field_data['form_path']['active_updating'] = array(
  137. '#type' => 'checkbox',
  138. '#title' => t('Active updating'),
  139. '#default_value' => $active_updating,
  140. '#description' => t('Actively move and rename previously uploaded files as required') . '.' .
  141. '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
  142. t('This feature should only be used on developmental servers or with extreme caution') . '.',
  143. '#weight' => 12
  144. );
  145. if (!in_array('filefield_paths_form_submit', $form['#submit'])) {
  146. $form['#submit'][] = 'filefield_paths_form_submit';
  147. }
  148. }
  149. }
  150. }
  151. /**
  152. * Implements hook_form_submit().
  153. */
  154. function filefield_paths_form_submit($form, &$form_state) {
  155. $ffp = array();
  156. // Invoke hook_filefield_paths_form_submit().
  157. foreach (module_implements('filefield_paths_form_submit') as $module) {
  158. $function = "{$module}_filefield_paths_form_submit";
  159. $function($form_state, $ffp);
  160. }
  161. if (count($ffp) > 0) {
  162. $retroactive_update = FALSE;
  163. $fields = module_invoke_all('filefield_paths_field_settings');
  164. foreach ($ffp as $field_name => $field_data) {
  165. $cols = array(
  166. 'type' => $field_data['type'],
  167. 'field' => $field_name,
  168. 'active_updating' => $form_state['values']["ffp_{$field_name}"]['active_updating'],
  169. );
  170. foreach ($fields as $name => &$field) {
  171. $cols[$field['sql']] = array(
  172. 'value' => $form_state['values']["ffp_{$field_name}"][$name],
  173. 'tolower' => $form_state['values']["ffp_{$field_name}"]["{$name}_cleanup"]["{$name}_tolower"],
  174. 'pathauto' => $form_state['values']["ffp_{$field_name}"]["{$name}_cleanup"]["{$name}_pathauto"],
  175. 'transliterate' => $form_state['values']["ffp_{$field_name}"]["{$name}_cleanup"]["{$name}_transliterate"],
  176. );
  177. // Store additional settings from add-on modules.
  178. if (isset($field['data'])) {
  179. foreach ($field['data'] as $key => $value) {
  180. $cols[$field['sql']][$key] = $form_state['values']["ffp_{$field_name}"][$value];
  181. }
  182. }
  183. }
  184. $results = db_select('filefield_paths', 'f')
  185. ->fields('f')
  186. ->condition('type', $cols['type'])
  187. ->condition('field', $cols['field'])
  188. ->execute()
  189. ->fetchAll();
  190. // Update existing entry.
  191. if (!empty($results)) {
  192. drupal_write_record('filefield_paths', $cols, array('type', 'field'));
  193. }
  194. // Create new entry.
  195. else {
  196. drupal_write_record('filefield_paths', $cols);
  197. }
  198. if ($form_state['values']["ffp_{$field_name}"]['retroactive_update']) {
  199. $retroactive_update = TRUE;
  200. $module = isset($form['#field']) ? $form['#field']['module'] : $field_name;
  201. filefield_paths_batch_update($form_state['values']['instance']);
  202. }
  203. }
  204. if ($retroactive_update) {
  205. // Run batch.
  206. batch_process($form_state['redirect']);
  207. }
  208. }
  209. }
  210. /**
  211. * Set batch process to update FileField Paths.
  212. *
  213. * @param $instance
  214. */
  215. function filefield_paths_batch_update($instance) {
  216. $objects = array();
  217. // Invoke hook_filefield_paths_batch_update().
  218. if (function_exists($function = "{$instance['widget']['module']}_filefield_paths_batch_update")) {
  219. $function($instance, $objects);
  220. }
  221. // Create batch.
  222. $batch = array(
  223. 'title' => t('Updating FileField Paths'),
  224. 'operations' => array(
  225. array('_filefield_paths_batch_update_process', array($objects, $instance))
  226. ),
  227. );
  228. batch_set($batch);
  229. }
  230. function _filefield_paths_batch_update_process($objects, $instance, &$context) {
  231. if (!isset($context['sandbox']['progress'])) {
  232. $context['sandbox']['progress'] = 0;
  233. $context['sandbox']['max'] = count($objects);
  234. $context['sandbox']['objects'] = $objects;
  235. }
  236. // Process nodes by groups of 5.
  237. $count = min(5, count($context['sandbox']['objects']));
  238. for ($i = 1; $i <= $count; $i++) {
  239. // For each oid, load the object, update the files and save it.
  240. $oid = array_shift($context['sandbox']['objects']);
  241. // Invoke hook_filefield_paths_update().
  242. if (function_exists($function = "{$instance['widget']['module']}_filefield_paths_update")) {
  243. $function($oid, $instance);
  244. }
  245. // Update our progress information.
  246. $context['sandbox']['progress']++;
  247. }
  248. // Inform the batch engine that we are not finished,
  249. // and provide an estimation of the completion level we reached.
  250. if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
  251. $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  252. }
  253. }
  254. /**
  255. * Implements hook_entity_insert().
  256. */
  257. function filefield_paths_entity_insert($entity, $type) {
  258. filefield_paths_entity_update($entity, $type);
  259. }
  260. /**
  261. * Implements hook_entity_update().
  262. */
  263. function filefield_paths_entity_update($entity, $type) {
  264. if (($ffp = filefield_paths_get_fields($entity, $type)) !== FALSE) {
  265. $update = new stdClass;
  266. $update->entity = FALSE;
  267. // Process files.
  268. foreach ($ffp['#files'] as &$file) {
  269. // Invoke hook_filefield_paths_process_file().
  270. foreach (module_implements('filefield_paths_process_file') as $module) {
  271. $function = "{$module}_filefield_paths_process_file";
  272. $settings = isset($ffp['#settings'][$file['name']]) ? $ffp['#settings'][$file['name']] : NULL;
  273. if (!is_null($settings)) {
  274. $function(($file['new'] || $settings['active_updating']), $file, $settings, $entity, $type, $update);
  275. }
  276. }
  277. }
  278. if ($update->entity == TRUE) {
  279. field_attach_update($type, $entity);
  280. }
  281. // Cleanup temporary paths.
  282. if ($ffp['#settings']) {
  283. foreach ($ffp['#settings'] as $name => $field) {
  284. // Invoke hook_filefield_paths_cleanup().
  285. foreach (module_implements('filefield_paths_cleanup') as $module) {
  286. $function = "{$module}_filefield_paths_cleanup";
  287. $function($ffp, $name);
  288. }
  289. }
  290. }
  291. }
  292. }
  293. /**
  294. * Implements hook_file_presave().
  295. */
  296. function filefield_paths_file_presave($file) {
  297. // Store original filename in the database.
  298. if (empty($file->origname)) {
  299. $file->origname = $file->filename;
  300. }
  301. }
  302. /**
  303. * Implements hook_field_delete_instance().
  304. */
  305. function filefield_paths_field_delete_instance($instance) {
  306. db_delete('filefield_paths')
  307. ->condition('type', "{$instance['entity_type']}::{$instance['bundle']}")
  308. ->condition('field', $instance['field_name'])
  309. ->execute();
  310. }
  311. /**
  312. *
  313. */
  314. function filefield_paths_get_fields(&$entity, $type, $op = NULL) {
  315. $ffp = array();
  316. $entity_info = entity_get_info();
  317. // Invoke hook_filefield_paths_get_fields().
  318. foreach (module_implements('filefield_paths_get_fields') as $module) {
  319. $function = "{$module}_filefield_paths_get_fields";
  320. $function($entity, $ffp);
  321. }
  322. if (count($ffp) == 0 || (isset($ffp['#types']) && !is_array($ffp['#types']))) {
  323. return FALSE;
  324. }
  325. $fields = module_invoke_all('filefield_paths_field_settings');
  326. // Load fields settings
  327. foreach (array_keys($ffp['#types']) as $name) {
  328. $field_type = isset($entity->{$entity_info[$type]['entity keys']['bundle']}) ? "{$type}::{$entity->{$entity_info[$type]['entity keys']['bundle']}}" : "{$type}::{$type}";
  329. $results = db_select('filefield_paths', 'f')
  330. ->fields('f')
  331. ->condition('type', $field_type)
  332. ->condition('field', $name)
  333. ->execute()
  334. ->fetchAllAssoc('type');
  335. if (!empty($results[$field_type])) {
  336. $result = $results[$field_type];
  337. foreach ($fields as $field) {
  338. $ffp['#settings'][$name][$field['sql']] = unserialize($result->$field['sql']);
  339. }
  340. $ffp['#settings'][$name]['active_updating'] = $result->active_updating;
  341. }
  342. }
  343. return $ffp;
  344. }
  345. /**
  346. * Run regular expression over all available text-based fields.
  347. */
  348. function _filefield_paths_replace_path($old, $new, &$entity, &$update) {
  349. // Build regular expression.
  350. $info = parse_url($old);
  351. $info['path'] = !empty($info['path']) ? $info['path'] : '';
  352. $absolute = str_replace("{$info['host']}{$info['path']}", '', file_create_url($old));
  353. $relative = parse_url($absolute, PHP_URL_PATH);
  354. $regex = str_replace('/', '\/', "({$absolute}|{$relative}|{$info['scheme']}://)(styles/.*?/{$info['scheme']}/|)({$info['host']}{$info['path']})");
  355. // Build replacement.
  356. $info = parse_url($new);
  357. $replacement = "\$1\$2{$info['host']}{$info['path']}";
  358. $fields = field_info_fields();
  359. foreach ($fields as $name => $field) {
  360. if ($field['module'] == 'text' && isset($entity->{$field['field_name']}) && is_array($entity->{$field['field_name']})) {
  361. foreach ($entity->{$field['field_name']} as &$language) {
  362. foreach ($language as &$item) {
  363. $item['value'] = preg_replace("/$regex/", $replacement, $item['value']);
  364. }
  365. }
  366. }
  367. }
  368. }
  369. /**
  370. * Process and cleanup strings.
  371. */
  372. function filefield_paths_process_string($value, $data, $settings = array()) {
  373. $transliterate = module_exists('transliteration') && isset($settings['transliterate']) && $settings['transliterate'];
  374. $pathauto = module_exists('pathauto') && isset($settings['pathauto']) && $settings['pathauto'] == TRUE;
  375. if ($pathauto == TRUE) {
  376. module_load_include('inc', 'pathauto');
  377. }
  378. $paths = explode('/', $value);
  379. foreach ($paths as &$path) {
  380. // Process string tokens.
  381. $path = token_replace($path, $data, array('clear' => TRUE));
  382. // Cleanup with pathauto.
  383. if ($pathauto == TRUE) {
  384. $path_parts = explode('.', $path);
  385. foreach ($path_parts as &$path_part) {
  386. $path_part = pathauto_cleanstring($path_part);
  387. }
  388. $path = implode('.', $path_parts);
  389. }
  390. // Transliterate string.
  391. if ($transliterate == TRUE) {
  392. $path = transliteration_clean_filename($path);
  393. }
  394. }
  395. $value = implode('/', $paths);
  396. // Convert string to lower case.
  397. if (isset($settings['tolower']) && $settings['tolower'] == TRUE) {
  398. $value = drupal_strtolower($value);
  399. }
  400. // Ensure that there are no double-slash sequences due to empty token values.
  401. $value = preg_replace('/\/+/', '/', $value);
  402. return $value;
  403. }