<?php
/**
 * @file
 * Contains core functions for the FileField Paths module.
 */

/**
 * Include additional files.
 */
foreach (module_list() as $module) {
  if (file_exists($file = dirname(__FILE__) . "/modules/{$module}.inc")) {
    require_once $file;
  }
}

/**
 * Implements hook_form_alter().
 */
function filefield_paths_form_alter(&$form, $form_state, $form_id) {
  $ffp = array();

  // Invoke hook_filefield_paths_form_alter().
  foreach (module_implements('filefield_paths_form_alter') as $module) {
    $function = "{$module}_filefield_paths_form_alter";
    $function($form, $ffp);
  }

  // If supporting module enabled, show FileField Paths settings form.
  if (count($ffp) > 0) {
    $entity_info = entity_get_info($form['#instance']['entity_type']);
    $fields = module_invoke_all('filefield_paths_field_settings');
    foreach ($ffp as $field_name => $field_data) {
      $active_updating = 0;

      $results = db_select('filefield_paths', 'f')
        ->fields('f')
        ->condition('type', $field_data['type'])
        ->condition('field', $field_name)
        ->execute()
        ->fetchAllAssoc('type');
      if (!empty($results[$field_data['type']])) {
        $result = $results[$field_data['type']];
        foreach ($fields as &$field) {
          $field['settings'] = unserialize($result->{$field['sql']});
        }
        unset($field);
        $active_updating = $result->active_updating;
      }

      $count = 0;
      foreach ($fields as $name => $field) {
        $count++;

        if (isset($field['form']) && is_array($field['form'])) {
          $keys = array_keys($field['form']);
          for ($i = 1; $i < count($field['form']); $i++) {
            $field['form'][$keys[$i]]['#weight'] = ($count - 1) * 3 + 2 + $i;
            $field['form'][$keys[$i]]['#element_validate'] = array('token_element_validate');
            $field['form'][$keys[$i]]['#token_types'] = array('file', $entity_info['token type']);
          }
          unset($keys);

          $field_data['form_path'] = array_merge_recursive($field_data['form_path'], $field['form']);
        }

        $field_data['form_path']['#tree'] = TRUE;
        $field_data['form_path'][$name]['#weight'] = ($count - 1) * 3;

        // Set defualt value for patterns.
        if (isset($field['settings']['value'])) {
          $field_data['form_path'][$name]['#default_value'] = $field['settings']['value'];

          if (isset($field['data'])) {
            foreach ($field['data'] as $key => $value) {
              $field_data['form_path'][$value]['#default_value'] = $field['settings'][$key];
            }
          }
        }

        $field_data['form_path']["{$name}_cleanup"] = array(
          '#type' => 'fieldset',
          '#title' => t('@title cleanup settings', array('@title' => $field['title'])),
          '#collapsible' => TRUE,
          '#collapsed' => TRUE,
          '#weight' => ($count - 1) * 3 + 1,
          '#attributes' => array(
            'class' => array("{$name} cleanup")
          )
        );

        // Cleanup field with Pathauto module.
        $field_data['form_path']["{$name}_cleanup"]["{$name}_pathauto"] = array(
          '#type' => 'checkbox',
          '#title' => t('Cleanup using Pathauto') . '.',
          '#default_value' => isset($field['settings']['pathauto'])
            ? $field['settings']['pathauto']
            : 0
          ,
          '#description' => t('Cleanup @title using', array('@title' => $field['title'])) . ' ' . l(t('Pathauto settings'), 'admin/config/search/path/settings'),
        );
        if (!module_exists('pathauto')) {
          $field_data['form_path']["{$name}_cleanup"]["{$name}_pathauto"]['#disabled'] = TRUE;
          $field_data['form_path']["{$name}_cleanup"]["{$name}_pathauto"]['#default_value'] = 0;
        }

        // Convert field to lower case.
        $field_data['form_path']["{$name}_cleanup"]["{$name}_tolower"] = array(
          '#type' => 'checkbox',
          '#title' => t('Convert to lower case') . '.',
          '#default_value' => isset($field['settings']['tolower'])
            ? $field['settings']['tolower']
            : 0
          ,
          '#description' => t('Convert @title to lower case', array('@title' => $field['title'])) . '.'
        );

        // Transliterate field with Transliteration module.
        $field_data['form_path']["{$name}_cleanup"]["{$name}_transliterate"] = array(
          '#type' => 'checkbox',
          '#title' => t('Transliterate') . '.',
          '#default_value' => isset($field['settings']['transliterate'])
            ? $field['settings']['transliterate']
            : 0
          ,
          '#description' => t('Transliterate @title', array('@title' => $field['title'])) . '.'
        );
        if (!module_exists('transliteration')) {
          $field_data['form_path']["{$name}_cleanup"]["{$name}_transliterate"]['#disabled'] = TRUE;
          $field_data['form_path']["{$name}_cleanup"]["{$name}_transliterate"]['#default_value'] = 0;
        }
      }

      // Replacement patterns for field.
      $field_data['form_path']['token_tree'] = array(
        '#type' => 'fieldset',
        '#title' => t('Replacement patterns'),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#description' => theme('token_tree', array('token_types' => array('file', $entity_info['token type']))),
        '#weight' => 10,
      );

      // Retroactive updates.
      $field_data['form_path']['retroactive_update'] = array(
        '#type' => 'checkbox',
        '#title' => t('Retroactive update'),
        '#description' => t('Move and rename previously uploaded files') . '.' .
          '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
          t('This feature should only be used on developmental servers or with extreme caution') . '.',
        '#weight' => 11
      );

      // Active updating.
      $field_data['form_path']['active_updating'] = array(
        '#type' => 'checkbox',
        '#title' => t('Active updating'),
        '#default_value' => $active_updating,
        '#description' => t('Actively move and rename previously uploaded files as required') . '.' .
          '<br /> <strong style="color: #FF0000;">' . t('Warning') . ':</strong> ' .
          t('This feature should only be used on developmental servers or with extreme caution') . '.',
        '#weight' => 12
      );

      if (!in_array('filefield_paths_form_submit', $form['#submit'])) {
        $form['#submit'][] = 'filefield_paths_form_submit';
      }
    }
  }
}

/**
 * Implements hook_form_submit().
 */
function filefield_paths_form_submit($form, &$form_state) {
  $ffp = array();

  // Invoke hook_filefield_paths_form_submit().
  foreach (module_implements('filefield_paths_form_submit') as $module) {
    $function = "{$module}_filefield_paths_form_submit";
    $function($form_state, $ffp);
  }

  if (count($ffp) > 0) {
    $retroactive_update = FALSE;
    $fields = module_invoke_all('filefield_paths_field_settings');
    foreach ($ffp as $field_name => $field_data) {
      $cols = array(
        'type' => $field_data['type'],
        'field' => $field_name,
        'active_updating' => $form_state['values']["ffp_{$field_name}"]['active_updating'],
      );

      foreach ($fields as $name => &$field) {
        $cols[$field['sql']] = array(
          'value' => $form_state['values']["ffp_{$field_name}"][$name],
          'tolower' => $form_state['values']["ffp_{$field_name}"]["{$name}_cleanup"]["{$name}_tolower"],
          'pathauto' => $form_state['values']["ffp_{$field_name}"]["{$name}_cleanup"]["{$name}_pathauto"],
          'transliterate' => $form_state['values']["ffp_{$field_name}"]["{$name}_cleanup"]["{$name}_transliterate"],
        );

        // Store additional settings from add-on modules.
        if (isset($field['data'])) {
          foreach ($field['data'] as $key => $value) {
            $cols[$field['sql']][$key] = $form_state['values']["ffp_{$field_name}"][$value];
          }
        }
      }

      $results = db_select('filefield_paths', 'f')
        ->fields('f')
        ->condition('type', $cols['type'])
        ->condition('field', $cols['field'])
        ->execute()
        ->fetchAll();

      // Update existing entry.
      if (!empty($results)) {
        drupal_write_record('filefield_paths', $cols, array('type', 'field'));
      }

      // Create new entry.
      else {
        drupal_write_record('filefield_paths', $cols);
      }

      if ($form_state['values']["ffp_{$field_name}"]['retroactive_update']) {
        $retroactive_update = TRUE;
        $module = isset($form['#field']) ? $form['#field']['module'] : $field_name;
        filefield_paths_batch_update($form_state['values']['instance']);
      }
    }

    if ($retroactive_update) {
      // Run batch.
      batch_process($form_state['redirect']);
    }
  }
}

/**
 * Set batch process to update FileField Paths.
 *
 * @param $instance
 */
function filefield_paths_batch_update($instance) {
  $objects = array();

  // Invoke hook_filefield_paths_batch_update().
  if (function_exists($function = "{$instance['widget']['module']}_filefield_paths_batch_update")) {
    $function($instance, $objects);
  }

  // Create batch.
  $batch = array(
    'title' => t('Updating FileField Paths'),
    'operations' => array(
      array('_filefield_paths_batch_update_process', array($objects, $instance))
    ),
  );
  batch_set($batch);
}

function _filefield_paths_batch_update_process($objects, $instance, &$context) {
  if (!isset($context['sandbox']['progress'])) {
    $context['sandbox']['progress'] = 0;
    $context['sandbox']['max'] = count($objects);
    $context['sandbox']['objects'] = $objects;
  }

  // Process nodes by groups of 5.
  $count = min(5, count($context['sandbox']['objects']));
  for ($i = 1; $i <= $count; $i++) {
    // For each oid, load the object, update the files and save it.
    $oid = array_shift($context['sandbox']['objects']);

    // Invoke hook_filefield_paths_update().
    if (function_exists($function = "{$instance['widget']['module']}_filefield_paths_update")) {
      $function($oid, $instance);
    }

    // Update our progress information.
    $context['sandbox']['progress']++;
  }

  // Inform the batch engine that we are not finished,
  // and provide an estimation of the completion level we reached.
  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  }
}

/**
 * Implements hook_entity_insert().
 */
function filefield_paths_entity_insert($entity, $type) {
  filefield_paths_entity_update($entity, $type);
}

/**
 * Implements hook_entity_update().
 */
function filefield_paths_entity_update($entity, $type) {
  if (($ffp = filefield_paths_get_fields($entity, $type)) !== FALSE) {
    $update = new stdClass;
    $update->entity = FALSE;

    // Process files.
    foreach ($ffp['#files'] as &$file) {
      // Invoke hook_filefield_paths_process_file().
      foreach (module_implements('filefield_paths_process_file') as $module) {
        $function = "{$module}_filefield_paths_process_file";
        $settings = isset($ffp['#settings'][$file['name']]) ? $ffp['#settings'][$file['name']] : NULL;
        if (!is_null($settings)) {
          $function(($file['new'] || $settings['active_updating']), $file, $settings, $entity, $type, $update);
        }
      }
    }

    if ($update->entity == TRUE) {
      field_attach_update($type, $entity);
    }

    // Cleanup temporary paths.
    if ($ffp['#settings']) {
      foreach ($ffp['#settings'] as $name => $field) {
        // Invoke hook_filefield_paths_cleanup().
        foreach (module_implements('filefield_paths_cleanup') as $module) {
          $function = "{$module}_filefield_paths_cleanup";
          $function($ffp, $name);
        }
      }
    }
  }
}

/**
 * Implements hook_file_presave().
 */
function filefield_paths_file_presave($file) {
  // Store original filename in the database.
  if (empty($file->origname)) {
    $file->origname = $file->filename;
  }
}

/**
 * Implements hook_field_delete_instance().
 */
function filefield_paths_field_delete_instance($instance) {
  db_delete('filefield_paths')
    ->condition('type', "{$instance['entity_type']}::{$instance['bundle']}")
    ->condition('field', $instance['field_name'])
    ->execute();
}

/**
 *
 */
function filefield_paths_get_fields(&$entity, $type, $op = NULL) {
  $ffp = array();
  $entity_info = entity_get_info();

  // Invoke hook_filefield_paths_get_fields().
  foreach (module_implements('filefield_paths_get_fields') as $module) {
    $function = "{$module}_filefield_paths_get_fields";
    $function($entity, $ffp);
  }

  if (count($ffp) == 0 || (isset($ffp['#types']) && !is_array($ffp['#types']))) {
    return FALSE;
  }

  $fields = module_invoke_all('filefield_paths_field_settings');

  // Load fields settings
  foreach (array_keys($ffp['#types']) as $name) {
    $field_type = isset($entity->{$entity_info[$type]['entity keys']['bundle']}) ? "{$type}::{$entity->{$entity_info[$type]['entity keys']['bundle']}}" : "{$type}::{$type}";
    $results = db_select('filefield_paths', 'f')
      ->fields('f')
      ->condition('type', $field_type)
      ->condition('field', $name)
      ->execute()
      ->fetchAllAssoc('type');

    if (!empty($results[$field_type])) {
      $result = $results[$field_type];
      foreach ($fields as $field) {
        $ffp['#settings'][$name][$field['sql']] = unserialize($result->$field['sql']);
      }
      $ffp['#settings'][$name]['active_updating'] = $result->active_updating;
    }
  }

  return $ffp;
}

/**
 * Run regular expression over all available text-based fields.
 */
function _filefield_paths_replace_path($old, $new, &$entity, &$update) {
  // Build regular expression.
  $info = parse_url($old);
  $info['path'] = !empty($info['path']) ? $info['path'] : '';
  $absolute = str_replace("{$info['host']}{$info['path']}", '', file_create_url($old));
  $relative = parse_url($absolute, PHP_URL_PATH);
  $regex = str_replace('/', '\/', "({$absolute}|{$relative}|{$info['scheme']}://)(styles/.*?/{$info['scheme']}/|)({$info['host']}{$info['path']})");

  // Build replacement.
  $info = parse_url($new);
  $replacement = "\$1\$2{$info['host']}{$info['path']}";

  $fields = field_info_fields();
  foreach ($fields as $name => $field) {
    if ($field['module'] == 'text' && isset($entity->{$field['field_name']}) && is_array($entity->{$field['field_name']})) {
      foreach ($entity->{$field['field_name']} as &$language) {
        foreach ($language as &$item) {
          $item['value'] = preg_replace("/$regex/", $replacement, $item['value']);
        }
      }
    }
  }
}


/**
 * Process and cleanup strings.
 */
function filefield_paths_process_string($value, $data, $settings = array()) {
  $transliterate = module_exists('transliteration') && isset($settings['transliterate']) && $settings['transliterate'];
  $pathauto = module_exists('pathauto') && isset($settings['pathauto']) && $settings['pathauto'] == TRUE;
  if ($pathauto == TRUE) {
    module_load_include('inc', 'pathauto');
  }

  $paths = explode('/', $value);
  foreach ($paths as &$path) {

    // Process string tokens.
    $path = token_replace($path, $data, array('clear' => TRUE));

    // Cleanup with pathauto.
    if ($pathauto == TRUE) {
      $path_parts = explode('.', $path);
      foreach ($path_parts as &$path_part) {
        $path_part = pathauto_cleanstring($path_part);
      }
      $path = implode('.', $path_parts);
    }

    // Transliterate string.
    if ($transliterate == TRUE) {
      $path = transliteration_clean_filename($path);
    }
  }
  $value = implode('/', $paths);

  // Convert string to lower case.
  if (isset($settings['tolower']) && $settings['tolower'] == TRUE) {
    $value = drupal_strtolower($value);
  }

  // Ensure that there are no double-slash sequences due to empty token values.
  $value = preg_replace('/\/+/', '/', $value);

  return $value;
}