diff --git a/potx.admin.inc b/potx.admin.inc
deleted file mode 100644
index e5fc13d..0000000
--- a/potx.admin.inc
+++ /dev/null
@@ -1,274 +0,0 @@
-<?php
-
-/**
- * @file
- *   Administrative interface for the module.
- */
-
-/**
- * Component selection interface.
- */
-function potx_select_component_form() {
-
-  $form = array();
-  $components = _potx_component_list();
-  _potx_component_selector($form, $components);
-
-  // Generate translation file for a specific language if possible.
-  $languages = language_list();
-  if (count($languages) > 1 || !isset($languages['en'])) {
-    // We have more languages, or the single language we have is not English.
-    $options = array('n/a' => t('Language independent template'));
-    foreach ($languages as $langcode => $language) {
-      // Skip English, as we should not have translations for this language.
-      if ($langcode == 'en') {
-        continue;
-      }
-      $options[$langcode] = t('Template file for !langname translations', array('!langname' => t($language->name)));
-    }
-    $form['langcode'] = array(
-      '#type' => 'radios',
-      '#title' => t('Template language'),
-      '#default_value' => 'n/a',
-      '#options' => $options,
-      '#description' => t('Export a language independent or language dependent (plural forms, language team name, etc.) template.'),
-    );
-    $form['translations'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Include translations'),
-      '#description' => t('Include translations of strings in the file generated. Not applicable for language independent templates.')
-    );
-  }
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Extract'),
-  );
-
-  return $form;
-}
-
-/**
- * Validation handler for potx component selection form.
- */
-function potx_select_component_form_validate($form, &$form_state) {
-  if (empty($form_state['values']['component'])) {
-    form_set_error('', t('You should select a component to export.'));
-  }
-}
-
-/**
- * Generate translation template or translation file for the requested component.
- */
-function potx_select_component_form_submit($form, &$form_state) {
-  global $devel_shutdown;
-
-  // Avoid devel.module putting extra output to the end of files exported.
-  $devel_shutdown = FALSE;
-
-  // This could take some time.
-  @set_time_limit(0);
-  module_load_include('inc', 'potx');
-  module_load_include('inc', 'potx', 'potx.local');
-
-  // Silence status messages.
-  potx_status('set', POTX_STATUS_MESSAGE);
-
-  // $form_state['values']['component'] either contains a specific file name
-  // with path, or a directory name for a module/theme or a module suite.
-  // Examples:
-  //   modules/watchdog
-  //   sites/all/modules/coder
-  //   sites/all/modules/i18n/i18n.module
-  //   themes/garland
-
-  $component = $form_state['values']['component'];
-  $pathinfo = pathinfo($component);
-  if (!isset($pathinfo['filename'])) {
-    // The filename key is only available in PHP 5.2.0+
-    $pathinfo['filename'] = substr($pathinfo['basename'], 0, strrpos($pathinfo['basename'], '.'));
-  }
-
-  if (isset($pathinfo['extension'])) {
-    // A specific module or theme file was requested (otherwise there should be no extension).
-    potx_local_init($pathinfo['dirname']);
-    $files = _potx_explore_dir($pathinfo['dirname'] .'/', $pathinfo['filename']);
-    $strip_prefix = 1 + strlen($pathinfo['dirname']);
-    $outputname = $pathinfo['filename'];
-  }
-  // A directory name was requested.
-  else {
-    potx_local_init($component);
-    $files = _potx_explore_dir($component .'/');
-    $strip_prefix = 1 + strlen($component);
-    $outputname = $pathinfo['basename'];
-  }
-
-  // Decide on template or translation file generation.
-  $template_langcode = $translation_langcode = NULL;
-  if (isset($form_state['values']['langcode']) && ($form_state['values']['langcode'] != 'n/a')) {
-    $template_langcode = $form_state['values']['langcode'];
-    $outputname .= '.'. $template_langcode;
-    if (!empty($form_state['values']['translations'])) {
-      $translation_langcode = $template_langcode;
-      $outputname .= '.po';
-    }
-    else {
-      $outputname .= '.pot';
-    }
-  }
-  else {
-    $outputname .= '.pot';
-  }
-
-  // Collect every string in affected files. Installer related strings are discared.
-  foreach ($files as $file) {
-    _potx_process_file($file, $strip_prefix);
-  }
-  potx_finish_processing('_potx_save_string');
-
-  // Need to include full parameter list to get to passing the language codes.
-  _potx_build_files(POTX_STRING_RUNTIME, POTX_BUILD_SINGLE, 'general', '_potx_save_string', '_potx_save_version', '_potx_get_header', $template_langcode, $translation_langcode);
-
-  _potx_write_files($outputname, 'attachment');
-
-  exit;
-}
-
-/**
- * Build a chunk of the component selection form.
- *
- * @param $form
- *   Form to populate with fields.
- * @param $components
- *   Structured array with components as returned by _potx_component_list().
- * @param $dirname
- *   Name of directory handled.
- */
-function _potx_component_selector(&$form, &$components, $dirname = '') {
-
-  // Pop off count of components in this directory.
-  if (isset($components['#-count'])) {
-    $component_count = $components['#-count'];
-    unset($components['#-count']);
-  }
-
-  //ksort($components);
-  $dirkeys = array_keys($components);
-
-  // A directory with one component.
-  if (isset($component_count) && (count($components) == 1)) {
-    $component = array_shift($components);
-    $dirname = dirname($component->filename);
-    $form[_potx_form_id('dir', $dirname)] = array(
-      '#type' => 'radio',
-      '#title' => t('Extract from %name in the %directory directory', array('%directory' => $dirname, '%name' => $component->name)),
-      '#description' => t('Generates output from all files found in this directory.'),
-      '#default_value' => 0,
-      '#return_value' => $dirname,
-      // Get all radio buttons into the same group.
-      '#parents' => array('component'),
-    );
-    return;
-  }
-
-  // A directory with multiple components in it.
-  if (preg_match('!/(modules|themes)\\b(/.+)?!', $dirname, $pathmatch)) {
-    $t_args = array('@directory' => substr($dirname, 1));
-    if (isset($pathmatch[2])) {
-      $form[_potx_form_id('dir', $dirname)] = array(
-        '#type' => 'radio',
-        '#title' => t('Extract from all in directory "@directory"', $t_args),
-        '#description' => t('To extract from a single component in this directory, choose the desired entry in the fieldset below.'),
-        '#default_value' => 0,
-        '#return_value' => substr($dirname, 1),
-        // Get all radio buttons into the same group.
-        '#parents' => array('component'),
-      );
-    }
-    $element = array(
-      '#type' => 'fieldset',
-      '#title' => t('Directory "@directory"', $t_args),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-    );
-    $form[_potx_form_id('fs', $dirname)] =& $element;
-  }
-  else {
-    $element =& $form;
-  }
-
-  foreach ($dirkeys as $entry) {
-    // A component in this directory with multiple components.
-    if ($entry[0] == '#') {
-      // Component entry.
-      $t_args = array(
-        '%directory' => dirname($components[$entry]->filename),
-        '%name'      => $components[$entry]->name,
-        '%pattern'   => $components[$entry]->name .'.*',
-      );
-      $element[_potx_form_id('com', $components[$entry]->basename)] = array(
-        '#type' => 'radio',
-        '#title' => t('Extract from %name', $t_args),
-        '#description' => t('Extract from files named %pattern in the %directory directory.', $t_args),
-        '#default_value' => 0,
-        '#return_value' => $components[$entry]->filename,
-        // Get all radio buttons into the same group.
-        '#parents' => array('component'),
-      );
-    }
-    // A subdirectory we need to look into.
-    else {
-      _potx_component_selector($element, $components[$entry], "$dirname/$entry");
-    }
-  }
-
-  return count($components);
-}
-
-/**
- * Generate a sane form element ID for the current radio button.
- *
- * @param $type
- *   Type of ID generated: 'fs' for fieldset, 'dir' for directory, 'com' for component.
- * @param $path
- *   Path of file we generate an ID for.
- * @return string
- *   The generated ID.
- */
-function _potx_form_id($type, $path) {
-  return 'potx-'. $type .'-'. preg_replace('/[^a-zA-Z0-9]+/', '-', $path);
-}
-
-/**
- * Generate a hierarchical structured list of components.
- *
- * @return array
- *  Array in the directory structure identified.
- *    - 'normal'  keyed elements being subfolders
- *    - '#name'   elements being component objects for the 'name' component
- *    - '#-count' being the file count of all components in the directory
- */
-function _potx_component_list() {
-  $components = array();
-  // Get a list of all enabled modules and themes.
-  $result = db_query("SELECT name, filename, type, status FROM {system} WHERE type IN ('module', 'theme') ORDER BY filename ASC");
-  foreach ($result as $component) {
-    // Build directory tree structure.
-    $path_parts = explode('/', dirname($component->filename));
-    $dir =& $components;
-    foreach ($path_parts as $dirname) {
-      if (!isset($dir[$dirname])) {
-        $dir[$dirname] = array();
-      }
-      $dir =& $dir[$dirname];
-    }
-
-    // Information about components in this directory.
-    $component->basename = basename($component->filename);
-    $dir['#'. $component->basename] = $component;
-    $dir['#-count'] = isset($dir['#-count']) ? $dir['#-count'] + 1 : 1;
-  }
-
-  return $components;
-}
diff --git a/potx.inc b/potx.inc
index 64baccc..6bbd8cd 100644
--- a/potx.inc
+++ b/potx.inc
@@ -33,7 +33,7 @@ use Symfony\Component\Yaml\Exception\ParseException;
  *
  * This should be the only difference between different branches of potx.inc
  */
-define('POTX_API_CURRENT', 7);
+define('POTX_API_CURRENT', 8);
 
 /**
  * Silence status reports.
diff --git a/potx.info b/potx.info
deleted file mode 100644
index 3b29dfd..0000000
--- a/potx.info
+++ /dev/null
@@ -1,9 +0,0 @@
-name = Translation template extractor
-description = Provides a web interface and an API to extract translatable text from the sources of installed components.
-dependencies[] = locale
-files[] = potx.inc
-files[] = potx.local.inc
-files[] = tests/potx.test
-core = 7.x
-package = Multilingual
-php = 5.1.2
diff --git a/potx.info.yml b/potx.info.yml
new file mode 100644
index 0000000..4670bd9
--- /dev/null
+++ b/potx.info.yml
@@ -0,0 +1,8 @@
+name: Translation template extractor
+description: Provides a web interface and an API to extract translatable text from the sources of installed components.
+core: 8.x
+package: Multilingual
+version: 8.x-1.x-dev
+dependencies:
+  - locale
+type: module
diff --git a/potx.install b/potx.install
index 0cbf947..16184c1 100644
--- a/potx.install
+++ b/potx.install
@@ -16,13 +16,12 @@
  */
 function potx_requirements($phase) {
   $requirements = array();
-  $t = get_t();
 
   if (!function_exists('token_get_all')) {
     $requirements['potx_tokenizer'] = array(
-      'title' => $t('PHP tokenizer for Translation template extractor'),
-      'value' => $t('Not available'),
-      'description' => $t('The <a href="@tokenizer">PHP tokenizer functions</a> are required.', array('@tokenizer' => 'http://php.net/tokenizer')),
+      'title' => t('PHP tokenizer for Translation template extractor'),
+      'value' => t('Not available'),
+      'description' => t('The <a href="@tokenizer">PHP tokenizer functions</a> are required.', array('@tokenizer' => 'http://php.net/tokenizer')),
       'severity' => REQUIREMENT_ERROR,
     );
   }
diff --git a/potx.links.task.yml b/potx.links.task.yml
new file mode 100644
index 0000000..d71f1ef
--- /dev/null
+++ b/potx.links.task.yml
@@ -0,0 +1,5 @@
+potx.extract_translation:
+  route_name: potx.extract_translation
+  base_route: locale.translate_page
+  title: Extract
+  weight: 40
diff --git a/potx.module b/potx.module
index 945a2bc..524f96b 100644
--- a/potx.module
+++ b/potx.module
@@ -13,30 +13,14 @@
 /**
  * Implementation of hook_help().
  */
-function potx_help($path, $arg) {
-  switch ($path) {
-    case 'admin/config/regional/translate/extract':
+function potx_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'potx.extract_translation':
       return '<p>'. t('This page allows you to generate translation templates for module and theme files. Select the module or theme you wish to generate a template file for. A single Gettext Portable Object (Template) file is generated, so you can easily save it and start translation.') .'</p>';
   }
 }
 
 /**
- * Implementation of hook_menu().
- */
-function potx_menu() {
-  $items['admin/config/regional/translate/extract'] = array(
-    'title' => 'Extract',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('potx_select_component_form'),
-    'access arguments' => array('translate interface'),
-    'weight' => 200,
-    'type' => MENU_LOCAL_TASK,
-    'file' => 'potx.admin.inc',
-  );
-  return $items;
-}
-
-/**
  * Implementation of hook_reviews(). Coder module integration.
  *
  * Provides the list of reviews provided by this module.
diff --git a/potx.routing.yml b/potx.routing.yml
new file mode 100644
index 0000000..9a74e20
--- /dev/null
+++ b/potx.routing.yml
@@ -0,0 +1,7 @@
+potx.extract_translation:
+  path: '/admin/config/regional/translate/extract'
+  defaults:
+    _form: '\Drupal\potx\Form\PotxExtractTranslationForm'
+    _title: 'Extract'
+  requirements:
+    _permission: 'translate interface'
diff --git a/src/Form/PotxExtractTranslationForm.php b/src/Form/PotxExtractTranslationForm.php
new file mode 100644
index 0000000..36a2047
--- /dev/null
+++ b/src/Form/PotxExtractTranslationForm.php
@@ -0,0 +1,334 @@
+<?php
+
+namespace Drupal\potx\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Extension\ThemeHandlerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+
+class PotxExtractTranslationForm extends FormBase {
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\language\ConfigurableLanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The theme handler.
+   *
+   * @var \Drupal\Core\Extension\ThemeHandlerInterface
+   */
+  protected $themeHandler;
+
+  /**
+   * Constructs a new PotxExtractTranslationForm object.
+   *
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager service.
+   */
+  public function __construct(LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
+    $this->languageManager = $language_manager;
+    $this->moduleHandler = $module_handler;
+    $this->themeHandler = $theme_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('language_manager'),
+      $container->get('module_handler'),
+      $container->get('theme_handler')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'potx_extract_transation';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $components = $this->generateComponentList();
+    $this->buildComponentSelector($form, $components);
+
+    // Generate translation file for a specific language if possible.
+    $languages = $this->languageManager->getLanguages();
+    if (count($languages) > 1 || !isset($languages['en'])) {
+      // We have more languages, or the single language we have is not English.
+      $options = array('n/a' => $this->t('Language independent template'));
+      foreach ($languages as $langcode => $language) {
+        // Skip English, as we should not have translations for this language.
+        if ($langcode == 'en') {
+          continue;
+        }
+
+        $options[$langcode] = $this->t('Template file for @langname translations', array('@langname' => $this->t($language->getName())));
+      }
+      $form['langcode'] = array(
+        '#type' => 'radios',
+        '#title' => $this->t('Template language'),
+        '#default_value' => 'n/a',
+        '#options' => $options,
+        '#description' => $this->t('Export a language independent or language dependent (plural forms, language team name, etc.) template.'),
+      );
+      $form['translations'] = array(
+        '#type' => 'checkbox',
+        '#title' => $this->t('Include translations'),
+        '#description' => $this->t('Include translations of strings in the file generated. Not applicable for language independent templates.')
+      );
+    }
+
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Extract'),
+    );
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    if (empty($form_state->getValue('component'))) {
+      $form_state->setErrorByName('component', $this->t('You should select a component to export.'));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    global $devel_shutdown;
+
+    // Avoid devel.module putting extra output to the end of files exported.
+    $devel_shutdown = FALSE;
+
+    // This could take some time.
+    @set_time_limit(0);
+    $this->moduleHandler->loadInclude('potx', 'inc');
+    $this->moduleHandler->loadInclude('potx', 'inc', 'potx.local');
+
+    // Silence status messages.
+    potx_status('set', POTX_STATUS_MESSAGE);
+
+    // $form_state->getValue('component') either contains a specific file name
+    // with path, or a directory name for a module/theme or a module suite.
+    // Examples:
+    //   modules/watchdog
+    //   sites/all/modules/coder
+    //   sites/all/modules/i18n/i18n.module
+    //   themes/garland
+
+    $component = $form_state->getValue('component');
+    $pathinfo = pathinfo($component);
+    if (!isset($pathinfo['filename'])) {
+      // The filename key is only available in PHP 5.2.0+
+      $pathinfo['filename'] = substr($pathinfo['basename'], 0, strrpos($pathinfo['basename'], '.'));
+    }
+
+    if (isset($pathinfo['extension'])) {
+      // A specific module or theme file was requested (otherwise there should be no extension).
+      potx_local_init($pathinfo['dirname']);
+      $files = _potx_explore_dir($pathinfo['dirname'] .'/', $pathinfo['filename']);
+      $strip_prefix = 1 + strlen($pathinfo['dirname']);
+      $outputname = $pathinfo['filename'];
+    }
+    // A directory name was requested.
+    else {
+      potx_local_init($component);
+      $files = _potx_explore_dir($component .'/');
+      $strip_prefix = 1 + strlen($component);
+      $outputname = $pathinfo['basename'];
+    }
+
+    // Decide on template or translation file generation.
+    $template_langcode = $translation_langcode = NULL;
+    if ($form_state->hasValue('langcode') && ($form_state->getValue('langcode') != 'n/a')) {
+      $template_langcode = $form_state->getValue('langcode');
+      $outputname .= '.'. $template_langcode;
+      if (!empty($form_state->getValue('translations'))) {
+        $translation_langcode = $template_langcode;
+        $outputname .= '.po';
+      }
+      else {
+        $outputname .= '.pot';
+      }
+    }
+    else {
+      $outputname .= '.pot';
+    }
+
+    // Collect every string in affected files. Installer related strings are discared.
+    foreach ($files as $file) {
+      _potx_process_file($file, $strip_prefix);
+    }
+    potx_finish_processing('_potx_save_string');
+
+    // Need to include full parameter list to get to passing the language codes.
+    _potx_build_files(POTX_STRING_RUNTIME, POTX_BUILD_SINGLE, 'general', '_potx_save_string', '_potx_save_version', '_potx_get_header', $template_langcode, $translation_langcode);
+
+    _potx_write_files($outputname, 'attachment');
+
+    exit;
+  }
+
+  /**
+   * Generate a hierarchical structured list of components.
+   *
+   * @return array
+   *  Array in the directory structure identified.
+   *    - 'normal'  keyed elements being subfolders
+   *    - '#name'   elements being component objects for the 'name' component
+   *    - '#-count' being the file count of all components in the directory
+   */
+  private function generateComponentList() {
+
+    $components = array();
+
+    // Get a list of all enabled modules and themes.
+    $modules = $this->moduleHandler->getModuleList();
+    $themes = $this->themeHandler->listInfo();
+    $result = array_merge($modules, $themes);
+    foreach ($result as $component) {
+      // Build directory tree structure.
+      $path_parts = explode('/', $component->getPath());
+      $dir =& $components;
+      foreach ($path_parts as $dirname) {
+        if (!isset($dir[$dirname])) {
+          $dir[$dirname] = array();
+        }
+        $dir =& $dir[$dirname];
+      }
+
+      // Information about components in this directory.
+      $dir['#' . $component->getExtensionFilename()] = $component;
+      $dir['#-count'] = isset($dir['#-count']) ? $dir['#-count'] + 1 : 1;
+    }
+
+    return $components;
+  }
+
+  /**
+   * Build a chunk of the component selection form.
+   *
+   * @param $form
+   *   Form to populate with fields.
+   * @param $components
+   *   Structured array with components as returned by $this->generateComponentList().
+   * @param $dirname
+   *   Name of directory handled.
+   */
+  function buildComponentSelector(&$form, &$components, $dirname = '') {
+
+    // Pop off count of components in this directory.
+    if (isset($components['#-count'])) {
+      $component_count = $components['#-count'];
+      unset($components['#-count']);
+    }
+
+    //ksort($components);
+    $dirkeys = array_keys($components);
+
+    // A directory with one component.
+    if (isset($component_count) && (count($components) == 1)) {
+      $component = array_shift($components);
+      $dirname = $component->getPath();
+      $form[$this->getFormElementID('dir', $dirname)] = array(
+        '#type' => 'radio',
+        '#title' => t('Extract from %name in the %directory directory', array('%directory' => $dirname, '%name' => $component->getName())),
+        '#description' => t('Generates output from all files found in this directory.'),
+        '#default_value' => 0,
+        '#return_value' => $dirname,
+        // Get all radio buttons into the same group.
+        '#parents' => array('component'),
+      );
+      return;
+    }
+
+    // A directory with multiple components in it.
+    if (preg_match('!/(modules|themes)\\b(/.+)?!', $dirname, $pathmatch)) {
+      $t_args = array('@directory' => substr($dirname, 1));
+      if (isset($pathmatch[2])) {
+        $form[$this->getFormElementID('dir', $dirname)] = array(
+          '#type' => 'radio',
+          '#title' => t('Extract from all in directory "@directory"', $t_args),
+          '#description' => t('To extract from a single component in this directory, choose the desired entry in the fieldset below.'),
+          '#default_value' => 0,
+          '#return_value' => substr($dirname, 1),
+          // Get all radio buttons into the same group.
+          '#parents' => array('component'),
+        );
+      }
+      $element = array(
+        '#type' => 'fieldset',
+        '#title' => t('Directory "@directory"', $t_args),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+      );
+      $form[$this->getFormElementID('fs', $dirname)] =& $element;
+    }
+    else {
+      $element =& $form;
+    }
+
+    foreach ($dirkeys as $entry) {
+      // A component in this directory with multiple components.
+      if ($entry[0] == '#') {
+        // Component entry.
+        $t_args = array(
+          '%directory' => $components[$entry]->getPath(),
+          '%name'      => $components[$entry]->getName(),
+          '%pattern'   => $components[$entry]->getName() .'.*',
+        );
+        $element[$this->getFormElementID('com', $components[$entry]->getExtensionFilename())] = array(
+          '#type' => 'radio',
+          '#title' => $this->t('Extract from %name', $t_args),
+          '#description' => $this->t('Extract from files named %pattern in the %directory directory.', $t_args),
+          '#default_value' => 0,
+          '#return_value' => $components[$entry]->getExtensionPathname(),
+          // Get all radio buttons into the same group.
+          '#parents' => array('component'),
+        );
+      }
+      // A subdirectory we need to look into.
+      else {
+        $this->buildComponentSelector($element, $components[$entry], "$dirname/$entry");
+      }
+    }
+
+    return count($components);
+  }
+
+  /**
+   * Generate a sane form element ID for the current radio button.
+   *
+   * @param $type
+   *   Type of ID generated: 'fs' for fieldset, 'dir' for directory, 'com' for component.
+   * @param $path
+   *   Path of file we generate an ID for.
+   * @return string
+   *   The generated ID.
+   */
+  function getFormElementID($type, $path) {
+    return 'potx-'. $type .'-'. preg_replace('/[^a-zA-Z0-9]+/', '-', $path);
+  }
+}
