diff --git a/flag.info b/flag.info
index 0c3b65a..a5c4055 100644
--- a/flag.info
+++ b/flag.info
@@ -15,9 +15,11 @@ files[] = includes/flag.cookie_storage.inc
 
 files[] = includes/flag.entity.inc
 files[] = flag.rules.inc
-files[] = includes/flag_handler_argument_content_id.inc
-files[] = includes/flag_handler_field_ops.inc
-files[] = includes/flag_handler_filter_flagged.inc
-files[] = includes/flag_handler_relationships.inc
-files[] = includes/flag_plugin_argument_validate_flaggability.inc
+
+; Views
+files[] = includes/views/flag_handler_argument_content_id.inc
+files[] = includes/views/flag_handler_field_ops.inc
+files[] = includes/views/flag_handler_filter_flagged.inc
+files[] = includes/views/flag_handler_relationships.inc
+files[] = includes/views/flag_plugin_argument_validate_flaggability.inc
 files[] = tests/flag.test
diff --git a/includes/flag.views.inc b/includes/flag.views.inc
deleted file mode 100644
index 2c3402d..0000000
--- a/includes/flag.views.inc
+++ /dev/null
@@ -1,252 +0,0 @@
-<?php
-
-/**
- * @file
- * Provides support for the Views module.
- */
-
-/**
- * Implements hook_views_plugins().
- */
-function flag_views_plugins() {
-  return array(
-    'argument validator' => array(
-      'flag_flaggable_node' => array(
-        'title' => t('Flaggable node'),
-        'flag type' => 'node',
-        'handler' => 'flag_plugin_argument_validate_flaggability',
-        'path' => drupal_get_path('module', 'flag') . '/includes',
-      ),
-      'flag_flaggable_user' => array(
-        'title' => t('Flaggable user'),
-        'flag type' => 'user',
-        'handler' => 'flag_plugin_argument_validate_flaggability',
-        'path' => drupal_get_path('module', 'flag') . '/includes',
-      ),
-      // A comment validator won't be very useful. Moreover, having it will
-      // contribute to the argument object's $options polution, so let's skip
-      // it.
-    ),
-  );
-}
-/**
- * Implements hook_views_data().
- */
-function flag_views_data() {
-  $data = array();
-
-  $data['flag_content']['table']['group'] = t('Flags');
-  $data['flag_counts']['table']['group'] = t('Flags');
-
-  $data['flag_content']['uid'] = array(
-    'title' => t('User uid'),
-    'help' => t('The user that flagged an item. If you need more fields than the uid add the "Flags: User" relationship.'),
-    'relationship' => array(
-      'base' => 'users',
-      'title' => t('User'),
-      'help' => t('Relate an item to the user that flagged it.'),
-      'handler' => 'views_handler_relationship',
-      'label' => t('Flag user'),
-    ),
-    'filter' => array(
-      'handler' => 'views_handler_filter_user_name',
-    ),
-    'argument' => array(
-      'handler' => 'views_handler_argument_numeric',
-    ),
-    'field' => array(
-      'handler' => 'views_handler_field_user',
-    ),
-  );
-
-  $data['flag_content']['timestamp'] = array(
-    'title' => t('Flagged time'),
-    'help' => t('Display the time the content was flagged by a user.'),
-    'field' => array(
-      'handler' => 'views_handler_field_date',
-      'click sortable' => TRUE,
-    ),
-    'sort' => array(
-      'handler' => 'views_handler_sort_date',
-    ),
-    'filter' => array(
-      'handler' => 'views_handler_filter_date',
-    ),
-    'argument' => array(
-      'handler' => 'views_handler_argument_date',
-    ),
-  );
-
-  // Argument for content ID, used for "Who's flagged this" views.
-  $data['flag_content']['content_id'] = array(
-    'title' => t('Content ID'),
-    'help' => t('The unique ID of the object that has been flagged.'),
-    'argument' => array(
-      'handler' => 'flag_handler_argument_content_id',
-    ),
-  );
-
-  // Specialized is null/is not null filter.
-  $data['flag_content']['flagged'] = array(
-    'title' => t('Flagged'),
-    'help' => t('Filter to ensure content has or has not been flagged.'),
-    'real field' => 'uid',
-    'filter' => array(
-      'handler' => 'flag_handler_filter_flagged',
-      'label' => t('Flagged'),
-    ),
-  );
-
-  // Flag content links.
-  $data['flag_content']['ops'] = array(
-    'title' => t('Flag link'),
-    'help' => t('Display flag/unflag link.'),
-    'field' => array(
-      'handler' => 'flag_handler_field_ops',
-    ),
-  );
-
-  $data['flag_counts']['count'] = array(
-    'title' => t('Flag counter'),
-    'help' => t('The number of times a piece of content is flagged by any user.'),
-    'field' => array(
-      'handler' => 'views_handler_field_numeric',
-      'click sortable' => TRUE,
-    ),
-    'sort' => array(
-      'handler' => 'views_handler_sort',
-    ),
-    'filter' => array(
-      'handler' => 'views_handler_filter_numeric',
-    ),
-    'argument' => array(
-      'handler' => 'views_handler_argument_numeric',
-    ),
-  );
-
-  $data['flag_counts']['last_updated'] = array(
-    'title' => t('Time last flagged'),
-    'help' => t('The time a piece of content was most recently flagged by any user.'),
-    'field' => array(
-      'handler' => 'views_handler_field_date',
-      'click sortable' => TRUE,
-    ),
-    'sort' => array(
-      'handler' => 'views_handler_sort_date',
-    ),
-    'filter' => array(
-      'handler' => 'views_handler_filter_date',
-    ),
-    'argument' => array(
-      'handler' => 'views_handler_argument_date',
-    ),
-  );
-
-  return $data;
-}
-
-/**
- * Implements hook_views_data_alter().
- */
-function flag_views_data_alter(&$data) {
-
-  foreach (array_keys(flag_fetch_definition()) as $flag_type) {
-    $flag = flag_flag::factory_by_content_type($flag_type);
-    $info = $flag->get_views_info();
-
-    if (!empty($info['join field'])) {
-      // Add the flag relationship.
-      $data[$info['views table']]['flag_content_rel'] = array(
-        'group' => t('Flags'),
-        'title' => $info['title'],
-        'help' => $info['help'],
-        'relationship' => array(
-          'flag type' => $flag_type,
-          'handler' => 'flag_handler_relationship_content',
-          'label' => t('flag'),
-          'base' => 'flag_content',
-          'base field' => 'content_id',
-          'relationship field' => $info['join field'],
-        ),
-      );
-
-      // Add the flag counter relationship.
-      $data[$info['views table']]['flag_count_rel'] = array(
-        'group' => t('Flags'),
-        'title' => $info['counter title'],
-        'help' => $info['counter help'],
-        'relationship' => array(
-          'flag type' => $flag_type,
-          'handler' => 'flag_handler_relationship_counts',
-          'label' => t('counter'),
-          'base' => 'flag_counts',
-          'base field' => 'content_id',
-          'relationship field' => $info['join field'],
-        ),
-      );
-    }
-  }
-
-  // Add a relationship for the user that flagged any type of content.
-  $data['users']['flag_user_content_rel'] = array(
-    'group' => t('Flags'),
-    'title' => t("User's flagged content"),
-    'help' => t('Limit results to users that have flagged certain content.'),
-    'relationship' => array(
-      'base' => 'flag_content',
-      'base field' => 'uid',
-      'relationship field' => 'uid',
-      'handler' => 'flag_handler_relationship_user_content',
-      'label' => t('user flagged content'),
-    ),
-  );
-}
-
-/**
- * Implements hook_views_query_substitutions().
- *
- * Allow replacement of current user's session id so we can cache these queries.
- */
-function flag_views_query_substitutions() {
-  return array(
-    '***FLAG_CURRENT_USER_SID***' => flag_get_sid(),
-  );
-}
-/**
- * A helper function that creates a radio list of available flags.
- *
- * This function is used to select the desired flag when setting up flag
- * relationships and fields.
- */
-function flag_views_flag_config_form($form_type, $content_type, $current_flag) {
-  $flags = flag_get_flags($content_type);
-
-  $options = array();
-  foreach ($flags as $flag) {
-    $options[$flag->name] = $flag->get_title();
-  }
-
-  $form = array(
-    '#type' => $form_type,
-    '#title' => t('Flag'),
-    '#options' => $options,
-    '#default_value' => $current_flag,
-    '#required' => TRUE,
-  );
-
-  return $form;
-}
-
-/**
- * Helper function that gets the first defined flag and returns its name.
- */
-function flag_views_flag_default($content_type) {
-  static $default_flag = array();
-
-  if (!array_key_exists($content_type, $default_flag)) {
-    $flag = array_shift(flag_get_flags($content_type));
-    $default_flag[$content_type] = $flag ? $flag->name : NULL;
-  }
-
-  return $default_flag[$content_type];
-}
diff --git a/includes/flag.views_convert.inc b/includes/flag.views_convert.inc
deleted file mode 100644
index d7e0d07..0000000
--- a/includes/flag.views_convert.inc
+++ /dev/null
@@ -1,235 +0,0 @@
-<?php
-
-/**
- * @file
- * Update Views 1 views provided by both views_bookmark and flag modules.
- */
-
-/**
- * Implements hook_views_convert().
- *
- * Intervene to convert field values from the Views 1 format to the
- * Views 2 format. Intervene only if $view->add_item() won't produce
- * the right results, usually needed to set field options or values.
- */
-function flag_views_convert($display, $type, &$view, $field, $id) {
-  static $flag_name;
-
-  // First, replace any sign of views_bookmark with flag's Views 1 equivelant.
-  $key_search = array(
-    '^views_bookmark_ops_([0-9]+)' => 'flag_ops_',
-    '^views_bookmark_nodes_([0-9]+)' => 'flag_content_',
-    '^views_bookmark_users_([0-9]+)' => 'flag_users_',
-    '^views_bookmark_node_count_([0-9]+)' => 'flag_counts_',
-  );
-  foreach ($field as $property => $value) {
-    foreach ($key_search as $search => $replace) {
-      if (!empty($value) && is_string($value) && preg_match('/' . $search . '/', $value, $matches)) {
-        $flag = flag_get_flag(NULL, $matches[1]);
-        $replace = $replace . $flag->name;
-        $field[$property] = preg_replace('/' . $search . '/', $replace, $value);
-      }
-    }
-  }
-
-  // Create a table/field identifier for this field.
-  foreach (array('flag_ops_', 'flag_content_', 'flag_users_', 'flag_counts_') as $table) {
-    if (strpos($field['tablename'], $table) === 0) {
-      $name = str_replace($table, '', $field['tablename']);
-      if (!empty($name) && !isset($flag_name)) {
-        $flag_name = $name;
-      }
-    }
-  }
-
-  // Now update values, options, etc. to those selected in the imported view.
-  switch ($type) {
-    case 'field':
-      switch ($id) {
-        case 'ops':
-          $new_field = array(
-            'label' => $field['label'],
-            'id' => 'ops',
-            'table' => 'flag_content',
-            'field' => 'ops',
-            'relationship' => 'flag_content_rel',
-          );
-          $new_rel = 'flag_content_rel';
-          $flag_content_rel_user = 'current';
-          break;
-        case 'count':
-          $new_field = array(
-            'label' => $field['label'],
-            'id' => 'count',
-            'table' => 'flag_counts',
-            'field' => 'count',
-            'relationship' => 'flag_counts_rel',
-          );
-          $new_rel = 'flag_counts_rel';
-          break;
-        case 'name':
-          $new_field = array(
-            'label' => $field['label'],
-            'link_to_user' => 1,
-            'id' => 'name',
-            'table' => 'users',
-            'field' => 'name',
-            'relationship' => 'uid',
-          );
-          $new_rel = 'uid';
-          break;
-      }
-      break;
-
-    case 'filter':
-    case 'exposed_filter':
-      switch ($id) {
-        case 'uid':
-          // The flag_content uid filter means "Include content only flagged by
-          // the current user". In D6, this is no longer a filter, but instead
-          // part of the relationship. Make the relationship join on the uid.
-          if ($field['value'] == '***CURRENT_USER***') {
-            $new_rel = 'flag_content_rel';
-            $flag_content_rel_user = 'current';
-          }
-          // Remove the old filter.
-          $view->set_item('default', $type, $id, NULL);
-          break;
-        case 'timestamp':
-          $new_field = array(
-            'operator' => $field['operator'],
-            'value' => flag_views_convert_timestamp_value($field['value']),
-            'group' => 0,
-            'id' => 'timestamp',
-            'table' => 'flag_content',
-            'field' => 'timestamp',
-            'relationship' => 'flag_content_rel',
-            'exposed' => $type == 'exposed_filter' ? 1 : 0,
-          );
-          $new_rel = 'flag_content_rel';
-          drupal_set_message(t('Flag is not able to convert the <em>Flagged time</em> filter. It\'s value is currently empty, but needs to be populated to work properly.'), 'warning');
-          break;
-        case 'count':
-          $new_field = array(
-            'operator' => $field['operator'],
-            'value' => array('value' => $field['value']),
-            'group' => 0,
-            'id' => 'count',
-            'table' => 'flag_counts',
-            'field' => 'count',
-            'relationship' => 'flag_counts_rel',
-            'exposed' => $type == 'exposed_filter' ? 1 : 0,
-          );
-          $new_rel = 'flag_counts_rel';
-          break;
-      }
-      break;
-
-    case 'argument':
-      // Flag in Drupal 5 only provides one argument, and in Views 1 arguments
-      // weren't given and ID, so we use the field type.
-      if (strpos($field['type'], 'flag_content_') === 0) {
-        $new_field = array(
-          'id' => 'uid',
-          'table' => 'users',
-          'field' => 'uid',
-          'relationship' => 'uid',
-        );
-        $new_rel = 'uid';
-      }
-      break;
-
-    case 'sort':
-      switch ($id) {
-        case 'count':
-          $new_field = array(
-            'order' => $field['sortorder'],
-            'id' => 'count',
-            'table' => 'flag_counts',
-            'field' => 'count',
-            'relationship' => 'flag_counts_rel',
-          );
-          $new_rel = 'flag_counts_rel';
-          break;
-        case 'timestamp':
-          $new_field = array(
-            'order' => $field['sortorder'],
-            'id' => 'timestamp',
-            'table' => 'flag_content',
-            'field' => 'timestamp',
-            'relationship' => 'flag_content_rel',
-          );
-          $new_rel = 'flag_content_rel';
-          break;
-      }
-      break;
-  }
-
-  // Add any new fields.
-  if (isset($new_field)) {
-    // Exposed filters are now wrapped into filters.
-    $type = $type == 'exposed_filter' ? 'filter' : $type;
-    // Update the type in the view options.
-    $view->set_item('default', $type, $id, $new_field);
-  }
-
-  // Add any new relationships (but only once per view).
-  if (isset($new_rel) && !isset($view->display[$display]->display_options['relationships'][$new_rel])) {
-    if ($new_rel == 'flag_content_rel') {
-      $view->display[$display]->display_options['relationships'][$new_rel] = array(
-        'label' => $flag_name,
-        'required' => 1,
-        'flag' => $flag_name,
-        'user_scope' => 'any',
-        'id' => 'flag_content_rel',
-        'table' => 'node',
-        'field' => 'flag_content_rel',
-        'relationship' => 'none',
-      );
-    }
-    elseif ($new_rel == 'flag_counts_rel') {
-      $view->display[$display]->display_options['relationships'][$new_rel] = array(
-        'label' => $flag_name . ' counts',
-        'required' => 0,
-        'flag' => $flag_name,
-        'id' => 'flag_counts_rel',
-        'table' => 'node',
-        'field' => 'flag_counts_rel',
-        'relationship' => 'none',
-      );
-    }
-    elseif ($new_rel == 'uid') {
-      $view->display[$display]->display_options['relationships'][$new_rel] = array(
-        'label' => isset($flag_name) ? $flag_name . ' user' : 'user',
-        'required' => 0,
-        'id' => 'uid',
-        'table' => 'flag_content',
-        'field' => 'uid',
-        'relationship' => 'flag_content_rel',
-      );
-    }
-  }
-
-  // Modify relationships if needed.
-  if (isset($flag_content_rel_user) && isset($view->display[$display]->display_options['relationships']['flag_content_rel'])) {
-    $view->display[$display]->display_options['relationships']['flag_content_rel']['user_scope'] = 'current';
-  }
-}
-
-/**
- * In Views 1, dates were simply stored as a single string. In Views 2, the
- * value is stored as an array of options.
- */
-function flag_views_convert_timestamp_value($value) {
-  $type = 'date';
-  if (stripos($value, 'now') !== FALSE) {
-    $type = 'offset';
-    $value = trim(str_ireplace('now', '', $value));
-  }
-  return array(
-    'type' => $type,
-    'value' => $value,
-    'min' => '',
-    'max' => '',
-  );
-}
diff --git a/includes/flag_handler_argument_content_id.inc b/includes/flag_handler_argument_content_id.inc
deleted file mode 100644
index ee18743..0000000
--- a/includes/flag_handler_argument_content_id.inc
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains the content ID argument handler.
- */
-
-/**
- * Handler to accept an argument of the content ID of any object.
- *
- * @ingroup views
- */
-class flag_handler_argument_content_id extends views_handler_argument_numeric {
-
-  /**
-   * Returns the flag object associated with our argument.
-   *
-   * An argument is in some relationship. This function reaches out for this
-   * relationship and reads its 'flag' option, which holds the flag name.
-   */
-  function get_flag() {
-    return $this->view->relationship[$this->options['relationship']]->get_flag();
-  }
-
-  /**
-   * Override the behavior of title(). Get the title of the appropriate objects.
-   */
-  function title_query() {
-    if (!($flag = $this->get_flag())) {
-      return array(); // Error message is printed by get_flag().
-    }
-    $views_info = $flag->get_views_info();
-
-    $titles = array();
-    $placeholders = implode(', ', array_fill(0, sizeof($this->value), '%d'));
-
-    $result = db_select($views_info['views table'], 'o')
-      ->fields('o', array($views_info['title field']))
-      ->condition('o.' . $views_info['join field'], $this->value, 'IN')
-      ->execute();
-    foreach ($result as $title) {
-      $titles[] = check_plain($title->$views_info['title field']);
-    }
-    return $titles;
-  }
-}
diff --git a/includes/flag_handler_field_ops.inc b/includes/flag_handler_field_ops.inc
deleted file mode 100644
index aaa8f2b..0000000
--- a/includes/flag_handler_field_ops.inc
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains the flag Ops field handler.
- */
-
-/**
- * Views field handler for the Flag operations links (flag/unflag).
- *
- * @ingroup views
- */
-class flag_handler_field_ops extends views_handler_field {
-
-  /**
-   * Returns the flag object associated with our field.
-   *
-   * A field is in some relationship. This function reaches out for this
-   * relationship and reads its 'flag' option, which holds the flag name.
-   */
-  function get_flag() {
-    // When editing a view it's possible to delete the relationship (either by
-    // error or to later recreate it), so we have to guard against a missing
-    // one.
-    if (isset($this->view->relationship[$this->options['relationship']])) {
-      return $this->view->relationship[$this->options['relationship']]->get_flag();
-    }
-  }
-
-  /**
-   * Return the the relationship we're linked to. That is, the alias for its
-   * table (which is suitbale for use with the various methods of the 'query'
-   * object).
-   */
-  function get_parent_relationship() {
-    $parent = $this->view->relationship[$this->options['relationship']]->options['relationship'];
-    if (!$parent || $parent == 'none') {
-      return NULL; // Base query table.
-    }
-    else {
-      return $this->view->relationship[$parent]->alias;
-    }
-  }
-
-  function option_definition() {
-    $options = parent::option_definition();
-    $options['link_type'] = array('default' => '');
-    return $options;
-  }
-
-  function options_form(&$form, &$form_state) {
-    parent::options_form($form, $form_state);
-
-    $form['link_type'] = array(
-      '#type' => 'radios',
-      '#title' => t('Link type'),
-      '#options' => array('' => t('Use flag link settings')) + _flag_link_type_options(),
-      '#default_value' => $this->options['link_type'],
-    );
-  }
-
-  /**
-   * Override base ::query(). The purpose here is to make it possible for the
-   * render() method to know two things: what's the content ID, and whether
-   * it's flagged.
-   */
-  function query() {
-    if (!($flag = $this->get_flag())) {
-      return; // Error message is printed in render().
-    }
-    $info = $flag->get_views_info();
-    $parent = $this->get_parent_relationship();
-
-    // Find out if the content is flagged. We can't just peek at some field in
-    // our loaded table because it doesn't always reflect the user browsing the
-    // page. So we explicitly add the flag_content table to find this out.
-    $join = new views_join();
-    $join->construct('flag_content', $info['views table'], $info['join field'], 'content_id');
-    $join->extra[] = array(
-      'field' => 'fid',
-      'value' => $flag->fid,
-      'numeric' => TRUE,
-    );
-    if (!$flag->global) {
-      $join->extra[] = array(
-        'field' => 'uid',
-        'value' => '***CURRENT_USER***',
-        'numeric' => TRUE,
-      );
-      $join->extra[] = array(
-        'field' => 'sid',
-        'value' => '***FLAG_CURRENT_USER_SID***',
-        'numeric' => TRUE,
-      );
-    }
-    $flag_table = $this->query->add_table('flag_content', $parent, $join);
-    $this->aliases['is_flagged'] = $this->query->add_field($flag_table, 'content_id');
-
-    // Next, find out the content ID. We can't add_field() on this table
-    // (flag_content), because its content_id may be NULL (in case no user has
-    // flagged this content, and it's a LEFT JOIN). So we reach to the parent
-    // relationship and add_field() *its* content ID column.
-    $left_table = $this->view->relationship[$this->options['relationship']]->table_alias;
-    $this->aliases['content_id'] = $this->query->add_field($left_table, $info['join field']);
-  }
-
-  /**
-   * Find out if the flag applies to each item seen on the page. It's done in a
-   * separate DB query to to avoid complexity and to make 'many to one' tests
-   * (e.g. checking user roles) possible without causing duplicate rows.
-   */
-  function pre_render(&$values) {
-    if (!($flag = $this->get_flag())) {
-      return; // Error message is printed in render().
-    }
-
-    $ids = array();
-    foreach ($values as $row) {
-      $content_id = $row->{$this->aliases['content_id']};
-      $is_flagged = $row->{$this->aliases['is_flagged']};
-      if (isset($content_id)) {
-        $ids[$content_id] = $is_flagged ? 'unflag' : 'flag';
-      }
-    }
-    $this->flag_applies = $ids ? $flag->access_multiple($ids) : array();
-  }
-
-  function render($values) {
-    if (!($flag = $this->get_flag())) {
-      return t('Missing flag'); // get_flag() itself will print a more detailed message.
-    }
-
-    $content_id = $values->{$this->aliases['content_id']};
-    $is_flagged = $values->{$this->aliases['is_flagged']};
-
-    if (empty($this->flag_applies[$content_id])) {
-      // Flag does not apply to this content.
-      return;
-    }
-
-    if (!empty($this->options['link_type'])) {
-      $flag->link_type = $this->options['link_type'];
-    }
-    return $flag->theme($is_flagged ? 'unflag' : 'flag', $content_id);
-  }
-}
diff --git a/includes/flag_handler_filter_flagged.inc b/includes/flag_handler_filter_flagged.inc
deleted file mode 100644
index d22c40b..0000000
--- a/includes/flag_handler_filter_flagged.inc
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains the flagged content filter handler.
- */
-
-/**
- * Handler to filter for content that has not been flagged.
- *
- * @ingroup views
- */
-class flag_handler_filter_flagged extends views_handler_filter_boolean_operator {
-  function option_definition() {
-    $options = parent::option_definition();
-    $options['value'] = array('default' => 1);
-    return $options;
-  }
-
-  function options_form(&$form, &$form_state) {
-    parent::options_form($form, $form_state);
-    $form['value']['#type'] = 'radios';
-    $form['value']['#title'] = t('Status');
-    $form['value']['#options'] = array(1 => t('Flagged'), 0 => t('Not flagged'), 'All' => t('All'));
-    $form['value']['#default_value'] = empty($this->options['value']) ? '0' : $this->options['value'];
-    $form['value']['#description'] = '<p>' . t('This filter is only needed if the relationship used has the "Include only flagged content" option <strong>unchecked</strong>. Otherwise, this filter is useless, because all records are already limited to flagged content.') . '</p><p>' . t('By choosing <em>Not flagged</em>, it is possible to create a list of content <a href="@unflagged-url">that is specifically not flagged</a>.', array('@unflagged-url' => 'http://drupal.org/node/299335')) . '</p>';
-  }
-
-  function query() {
-    $operator = $this->value ? 'IS NOT' : 'IS';
-    $this->query->add_where($this->options['group'], $this->relationship . '.uid', NULL, $operator . ' NULL');
-  }
-}
diff --git a/includes/flag_handler_relationships.inc b/includes/flag_handler_relationships.inc
deleted file mode 100644
index a2fa516..0000000
--- a/includes/flag_handler_relationships.inc
+++ /dev/null
@@ -1,266 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains various relationship handlers.
- */
-
-/**
- * Base class for all our relationship classes.
- *
- * @ingroup views
- */
-abstract class flag_handler_relationship extends views_handler_relationship {
-
-  /**
-   * Every relationship has a 'flag' option.
-   */
-  function option_definition() {
-    $options = parent::option_definition();
-    $options['flag'] = array('default' => NULL);
-    $options['required'] = array('default' => 1);
-    return $options;
-  }
-
-  /**
-   * Make sure the flag exists.
-   *
-   * When importing views, or when deleting flags, inconsistent views may
-   * result. This validator is called by Views before saving or previewing a
-   * view.
-   */
-  function validate() {
-    $errors = array();
-    $tokens = array(
-      '@relationship-name' => $this->ui_name() . ' ' . $this->admin_summary(),
-      '@flag-name' => $this->options['flag'],
-    );
-    if (!$this->options['flag']) {
-      $errors[] = t('You must pick a flag to use for the relationship "@relationship-name".', $tokens);
-    }
-    elseif (!flag_get_flag($this->options['flag'])) {
-      $errors[] = t('This view is looking for a flag by the name "@flag-name", but there is no such flag. Perhaps it was deleted. Please update the relationship "@relationship-name" in this view to use an existing flag.', $tokens);
-    }
-    return $errors;
-  }
-
-  function get_flag_type() {
-    return isset($this->definition['flag type']) ? $this->definition['flag type'] : NULL;
-  }
-
-  /**
-   * Returns the flag object.
-   */
-  function get_flag() {
-
-    // Backward compatibility: There may exist old views on the system whose
-    // 'flag' option isn't set. (This happens if the admin had skippped
-    // clicking the 'Update' button.) When run, these views should behave as
-    // if the first flag was selected.
-    if (!isset($this->options['flag'])) {
-      $this->options['flag'] = flag_views_flag_default($this->get_flag_type());
-    }
-
-    // Validation: Since validate() is called only when in Views's
-    // administrative UI, we need to do validation at "run time" ourselves.
-    if (($errors = $this->validate())) {
-      foreach ($errors as $error) {
-        drupal_set_message($error, 'error');
-      }
-    }
-
-    return flag_get_flag($this->options['flag']);
-  }
-
-  // @todo: It's logical that this class should also implement options_form(),
-  // to show the flag selector, and query(), to filter on the flag.
-}
-
-/**
- * Specialized relationship handler associating flags and content.
- *
- * @ingroup views
- */
-class flag_handler_relationship_content extends flag_handler_relationship {
-
-  function option_definition() {
-    $options = parent::option_definition();
-    $options['user_scope'] = array('default' => 'current');
-    return $options;
-  }
-
-  function options_form(&$form, &$form_state) {
-    parent::options_form($form, $form_state);
-    $content_type = $this->definition['flag type'];
-    $form['label']['#description'] .= ' ' . t('The name of the selected flag makes a good label.');
-    $form['flag'] = flag_views_flag_config_form('radios', $content_type, $this->options['flag']);
-
-    $form['user_scope'] = array(
-      '#type' => 'radios',
-      '#title' => t('By'),
-      '#options' => array('current' => t('Current user'), 'any' => t('Any user')),
-      '#default_value' => $this->options['user_scope'],
-    );
-
-    $form['required']['#title'] = t('Include only flagged content');
-    $form['required']['#description'] = t('If checked, only content that has this flag will be included. Leave unchecked to include all content; or, in combination with the <em>Flagged</em> filter, <a href="@unflagged-url">to limit the results to specifically unflagged content</a>.', array('@unflagged-url' => 'http://drupal.org/node/299335'));
-
-    if (!$form['flag']['#options']) {
-      $form = array(
-        'error' => array(
-          '#markup' => '<p class="error form-item">' . t('No %type flags exist. You must first <a href="@create-url">create a %type flag</a> before being able to use this relationship type.', array('%type' => $content_type, '@create-url' => url(FLAG_ADMIN_PATH))) . '</p>',
-        ),
-      );
-      $form_state['no flags exist'] = TRUE;
-    }
-    if (module_exists('session_api')) {
-      $form['session_warning'] = array(
-        '#markup' => '<p class="warning form-item">' . t('<strong>Warning</strong>: Adding this relationship for any flag that contains <strong>anonymous flagging access</strong> will disable page caching for anonymous users when this view is executed. (But this is only true when the relationship is constrained to "Current user", not to "Any user".) It is recommended to create a dedicated page for views containing anonymous user data.') . '</p>',
-      );
-    }
-  }
-
-  function options_validate(&$form, &$form_state) {
-    if (!empty($form_state['no flags exist'])) {
-      form_error($form, t('You must first create a flag'));
-    }
-  }
-
-  function admin_summary() {
-    return $this->options['user_scope'] == 'current' ? t('by current user') : t('by any user');
-  }
-
-  function ui_name($short = FALSE) {
-    // We put the bookmark name in the UI string to save space.
-    return t('!group: !title', array('!group' => $this->definition['group'], '!title' => empty($this->options['flag']) ? t('(Please select a flag)') : $this->options['flag']));
-  }
-
-  /**
-   * Called to implement a relationship in a query.
-   */
-  function query() {
-    if (!($flag = $this->get_flag())) {
-      return;
-    }
-
-    $this->definition['extra'][] = array(
-      'field' => 'fid',
-      'value' => $flag->fid,
-      'numeric' => TRUE,
-    );
-    if ($this->options['user_scope'] == 'current' && !$flag->global) {
-      $this->definition['extra'][] = array(
-        'field' => 'uid',
-        'value' => '***CURRENT_USER***',
-        'numeric' => TRUE,
-      );
-      if (array_search(DRUPAL_ANONYMOUS_RID, $flag->roles['flag']) !== FALSE) {
-        // Disable page caching for anonymous users.
-        drupal_page_is_cacheable(FALSE);
-
-        // Add in the SID from Session API for anonymous users.
-        $this->definition['extra'][] = array(
-          'field' => 'sid',
-          'value' => '***FLAG_CURRENT_USER_SID***',
-          'numeric' => TRUE,
-        );
-      }
-    }
-    parent::query();
-  }
-}
-
-/**
- * Specialized relationship handler associating flag counts and content.
- *
- * @ingroup views
- */
-class flag_handler_relationship_counts extends flag_handler_relationship {
-
-  function options_form(&$form, &$form_state) {
-    parent::options_form($form, $form_state);
-    $content_type = $this->definition['flag type'];
-    $form['flag'] = flag_views_flag_config_form('radios', $content_type, $this->options['flag']);
-
-    $form['required']['#title'] = t('Include only flagged content');
-    $form['required']['#description'] = t('If checked, only content that is flagged will be included.');
-  }
-
-  function admin_summary() {
-    // Nothing to show.
-  }
-
-  function ui_name($short = FALSE) {
-    // We put the bookmark name in the UI string to save space.
-    return t('!group: !title counter', array('!group' => $this->definition['group'], '!title' => $this->options['flag']));
-  }
-
-  /**
-   * Called to implement a relationship in a query.
-   */
-  function query() {
-    if (!($flag = $this->get_flag())) {
-      return;
-    }
-
-    $this->definition['extra'][] = array(
-      'field' => 'fid',
-      'value' => $flag->fid,
-      'numeric' => TRUE,
-    );
-    if (!empty($this->options['required'])) {
-      // Unfortunately, we may have zeros in our table, so having
-      // parent::query() do INNER JOIN doesn't suffice. We need to filter these
-      // zeros out.
-      // @todo Make sure zero records aren't written in the first place, and
-      // remove this code.
-      $this->definition['extra'][] = array(
-        'field' => 'count',
-        'operator' => '>',
-        'value' => '0',
-        'numeric' => TRUE,
-      );
-    }
-    parent::query();
-  }
-}
-
-/**
- * Specialized relationship handler associating flags and users.
- *
- * @ingroup views
- */
-class flag_handler_relationship_user_content extends flag_handler_relationship {
-
-  function options_form(&$form, &$form_state) {
-    parent::options_form($form, $form_state);
-    $form['label']['#description'] .= ' ' . t('Including name of the selected flag helps identify this relationship.');
-
-    $form['flag'] = flag_views_flag_config_form('radios', NULL, $this->options['flag']);
-    $form['flag']['#title'] = t('Flagged');
-
-    $form['required']['#title'] = t('Include only users who have flagged content.');
-    $form['required']['#description'] = t('If checked, only users that have flagged any content with this flag will be included.');
-  }
-
-  function admin_summary() {
-    return $this->options['flag'];
-  }
-
-  /**
-   * Called to implement a relationship in a query.
-   */
-  function query() {
-    if (!($flag = $this->get_flag())) {
-      return;
-    }
-
-    $this->definition['extra'][] = array(
-      'field' => 'fid',
-      'value' => $flag->fid,
-      'numeric' => TRUE,
-    );
-    parent::query();
-  }
-}
-
diff --git a/includes/flag_plugin_argument_validate_flaggability.inc b/includes/flag_plugin_argument_validate_flaggability.inc
deleted file mode 100644
index cb3587a..0000000
--- a/includes/flag_plugin_argument_validate_flaggability.inc
+++ /dev/null
@@ -1,238 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains the flaggability validator handler.
- */
-
-/**
- * Validates whether an argument is a flaggable/flagged object.
- *
- * @ingroup views
- */
-class flag_plugin_argument_validate_flaggability extends views_plugin_argument_validate {
-
-  function construct() {
-    parent::construct();
-    $this->flag_type = $this->definition['flag type'];
-  }
-
-  function option_definition() {
-    $options = parent::option_definition();
-    $options['flag_name'] = array('default' => '*relationship*');
-    $options['flag_test'] = array('default' => 'flaggable');
-    $options['flag_id_type'] = array('default' => 'id');
-    return $options;
-  }
-
-  function options_form(&$form, &$form_state) {
-    // If there are no flag relationships set, skip this form of validation.
-    $flags = flag_get_flags($this->flag_type);
-    if (!$flags) {
-      return array();
-    }
-    $options = $this->flags_options();
-
-    $form[$this->_option_name('flag_name')] = array(
-      '#type' => 'radios',
-      '#title' => t('Flag'),
-      '#options' => $options,
-      '#default_value' => $this->_get_option('flag_name', '*relationship*'),
-      '#description' => t('Select the flag to validate against.'),
-    );
-    if (!$options) {
-      $form[$this->_option_name('flag_name')]['#description'] = '<p class="warning">' . t('No %type flags exist. You must first <a href="@create-url">create a %type flag</a> before being able to use one.', array('%type' => $this->flag_type, '@create-url' => FLAG_ADMIN_PATH)) . '</p>';
-    }
-
-    $form[$this->_option_name('flag_test')] = array(
-      '#type' => 'select',
-      '#title' => t('Validate the @type only if', array('@type' => $this->flag_type)),
-      '#options' => $this->tests_options(),
-      '#default_value' => $this->_get_option('flag_test', 'flaggable'),
-    );
-
-    // This validator supports the "multiple IDs" syntax. It may not be
-    // extremely useful, but similar validators do support this and it's a good
-    // thing to be compatible.
-    $form[$this->_option_name('flag_id_type')] = array(
-      '#type' => 'select',
-      '#title' => t('Argument type'),
-      '#options' => array(
-        'id' => t('ID'),
-        'ids' => t('IDs separated by , or +'),
-      ),
-      '#default_value' => $this->_get_option('flag_id_type', 'id'),
-    );
-  }
-
-  /**
-   * Returns form #options for the flags. Returns empty array if no flags were
-   * found.
-   */
-  function flags_options() {
-    $flags = flag_get_flags($this->flag_type);
-    if (!$flags) {
-      return array();
-    }
-    else {
-      foreach ($flags as $flag) {
-        $options[$flag->name] = $flag->get_title();
-      }
-      $options['*relationship*'] = t('<em>Pick the first flag mentioned in the relationships.</em>');
-      return $options;
-    }
-  }
-
-  // This function seems useless, but in the Drupal 6 branch it allows for
-  // compatibility between Views 3 and Views 2.
-  function _get_option($option, $default) {
-    return $this->options[$option];
-  }
-
-  // This function seems useless, but in the Drupal 6 branch it allows for
-  // compatibility between Views 3 and Views 2.
-  function _option_name($option) {
-    return $option;
-  }
-
-  /**
-   * Migrate existing Views 2 options to Views 3.
-   */
-  function convert_options(&$options) {
-    $prefix = 'validate_argument_' . $this->flag_type . '_';
-    if (!isset($options['flag_name']) && !empty($this->argument->options[$prefix . 'flag_name'])) {
-      $options['flag_name'] = $this->argument->options[$prefix . 'flag_name'];
-      $options['flag_test'] = $this->argument->options[$prefix . 'flag_test'];
-      $options['flag_id_type'] = $this->argument->options[$prefix . 'flag_id_type'];
-    }
-  }
-
-  /**
-   * Declares all tests. This scheme makes it easy for derived classes to add
-   * and remove tests.
-   */
-  function tests_info($which = NULL) {
-    return array(
-      'flaggable' => array(
-        'title' => t('It is flaggable'),
-        'callback' => 'test_flaggable',
-      ),
-      'flagged' => array(
-        'title' => t('It is flagged at least once'),
-        'callback' => 'test_flagged',
-      ),
-      'flagged_by_current_user' => array(
-        'title' => t('It is flagged by the current user'),
-        'callback' => 'test_flagged_by_current_user',
-      ),
-    );
-  }
-
-  function tests_options() {
-    $options = array();
-    foreach ($this->tests_info() as $id => $info) {
-      $options[$id] = $info['title'];
-    }
-    return $options;
-  }
-
-  function get_flag() {
-    $flag_name = $this->_get_option('flag_name', '*relationship*');
-
-    if ($flag_name == '*relationship*') {
-      // Pick the first flag mentioned in the relationships.
-      foreach ($this->view->relationship as $id => $handler) {
-        // Note: we can't do $handler->field, because the relationship handler's init() may overwrite it.
-        if (strpos($handler->options['field'], 'flag') !== FALSE && !empty($handler->options['flag'])) {
-          $flag = flag_get_flag($handler->options['flag']);
-          if ($flag && $flag->content_type == $this->flag_type) {
-            return $flag;
-          }
-        }
-      }
-    }
-
-    return flag_get_flag($flag_name);
-  }
-
-  /**
-   * Tests whether the argument is flaggable, or flagged, or flagged by current
-   * user. These are three possible tests, and which of the three to actually
-   * carry out is determined by 'flag_test'.
-   */
-  function validate_argument($argument) {
-    $flag_test = $this->_get_option('flag_test', 'flaggable');
-    $id_type   = $this->_get_option('flag_id_type', 'id');
-
-    $flag = $this->get_flag();
-    if (!$flag) {
-      // Validator is misconfigured somehow.
-      return TRUE;
-    }
-
-    if ($id_type == 'id') {
-      if (!is_numeric($argument)) {
-        // If a user is being smart and types several IDs where only one is
-        // expected, we invalidate this.
-        return FALSE;
-      }
-    }
-
-    $ids = views_break_phrase($argument, $this);
-    if ($ids->value == array(-1)) {
-      // Malformed argument syntax. Invalidate.
-      return FALSE;
-    }
-    $ids = $ids->value;
-
-    // Delegate the testing to the particual test method. $passed then
-    // holds the IDs that passed the test.
-    $tests_info = $this->tests_info();
-    $method = $tests_info[$flag_test]['callback'];
-    if (method_exists($this, $method)) {
-      $passed = $this->$method($ids, $flag);
-    }
-    else {
-      $passed = array();
-    }
-
-    // If some IDs exist that haven't $passed our test then validation fails.
-    $failed = array_diff($ids, $passed);
-    return empty($failed);
-  }
-
-  //
-  // The actual tests. They return the IDs that passed.
-  //
-
-  function test_flaggable($ids, $flag) {
-    return array_filter($ids, array($flag, 'applies_to_content_id'));
-  }
-
-  function test_flagged($ids, $flag) {
-    // view_break_phrase() is guaranteed to return only integers, so this is SQL safe.
-    $flattened_ids = implode(',', $ids);
-    return $this->_test_by_sql("SELECT content_id FROM {flag_counts} WHERE fid = :fid AND content_id IN ($flattened_ids) AND count > 0", array(':fid' => $flag->fid));
-  }
-
-  function test_flagged_by_current_user($ids, $flag) {
-    global $user;
-    if (!$user->uid) {
-      // Anonymous user
-      return array();
-    }
-    $flattened_ids = implode(',', $ids);
-    return $this->_test_by_sql("SELECT content_id FROM {flag_content} WHERE fid = :fid AND content_id IN ($flattened_ids) AND uid = :uid", array(':fid' => $flag->fid, ':uid' => $user->uid));
-  }
-
-  // Helper: executes an SQL query and returns all the content_id's.
-  function _test_by_sql($sql, $args) {
-    $passed = array();
-    $result = db_query($sql, $args);
-    foreach ($result as $row) {
-      $passed[] = $row->content_id;
-    }
-    return $passed;
-  }
-}
-
diff --git a/includes/views/flag.views.inc b/includes/views/flag.views.inc
new file mode 100644
index 0000000..2c3402d
--- /dev/null
+++ b/includes/views/flag.views.inc
@@ -0,0 +1,252 @@
+<?php
+
+/**
+ * @file
+ * Provides support for the Views module.
+ */
+
+/**
+ * Implements hook_views_plugins().
+ */
+function flag_views_plugins() {
+  return array(
+    'argument validator' => array(
+      'flag_flaggable_node' => array(
+        'title' => t('Flaggable node'),
+        'flag type' => 'node',
+        'handler' => 'flag_plugin_argument_validate_flaggability',
+        'path' => drupal_get_path('module', 'flag') . '/includes',
+      ),
+      'flag_flaggable_user' => array(
+        'title' => t('Flaggable user'),
+        'flag type' => 'user',
+        'handler' => 'flag_plugin_argument_validate_flaggability',
+        'path' => drupal_get_path('module', 'flag') . '/includes',
+      ),
+      // A comment validator won't be very useful. Moreover, having it will
+      // contribute to the argument object's $options polution, so let's skip
+      // it.
+    ),
+  );
+}
+/**
+ * Implements hook_views_data().
+ */
+function flag_views_data() {
+  $data = array();
+
+  $data['flag_content']['table']['group'] = t('Flags');
+  $data['flag_counts']['table']['group'] = t('Flags');
+
+  $data['flag_content']['uid'] = array(
+    'title' => t('User uid'),
+    'help' => t('The user that flagged an item. If you need more fields than the uid add the "Flags: User" relationship.'),
+    'relationship' => array(
+      'base' => 'users',
+      'title' => t('User'),
+      'help' => t('Relate an item to the user that flagged it.'),
+      'handler' => 'views_handler_relationship',
+      'label' => t('Flag user'),
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_user_name',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_numeric',
+    ),
+    'field' => array(
+      'handler' => 'views_handler_field_user',
+    ),
+  );
+
+  $data['flag_content']['timestamp'] = array(
+    'title' => t('Flagged time'),
+    'help' => t('Display the time the content was flagged by a user.'),
+    'field' => array(
+      'handler' => 'views_handler_field_date',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort_date',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_date',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_date',
+    ),
+  );
+
+  // Argument for content ID, used for "Who's flagged this" views.
+  $data['flag_content']['content_id'] = array(
+    'title' => t('Content ID'),
+    'help' => t('The unique ID of the object that has been flagged.'),
+    'argument' => array(
+      'handler' => 'flag_handler_argument_content_id',
+    ),
+  );
+
+  // Specialized is null/is not null filter.
+  $data['flag_content']['flagged'] = array(
+    'title' => t('Flagged'),
+    'help' => t('Filter to ensure content has or has not been flagged.'),
+    'real field' => 'uid',
+    'filter' => array(
+      'handler' => 'flag_handler_filter_flagged',
+      'label' => t('Flagged'),
+    ),
+  );
+
+  // Flag content links.
+  $data['flag_content']['ops'] = array(
+    'title' => t('Flag link'),
+    'help' => t('Display flag/unflag link.'),
+    'field' => array(
+      'handler' => 'flag_handler_field_ops',
+    ),
+  );
+
+  $data['flag_counts']['count'] = array(
+    'title' => t('Flag counter'),
+    'help' => t('The number of times a piece of content is flagged by any user.'),
+    'field' => array(
+      'handler' => 'views_handler_field_numeric',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_numeric',
+    ),
+  );
+
+  $data['flag_counts']['last_updated'] = array(
+    'title' => t('Time last flagged'),
+    'help' => t('The time a piece of content was most recently flagged by any user.'),
+    'field' => array(
+      'handler' => 'views_handler_field_date',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort_date',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_date',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_date',
+    ),
+  );
+
+  return $data;
+}
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function flag_views_data_alter(&$data) {
+
+  foreach (array_keys(flag_fetch_definition()) as $flag_type) {
+    $flag = flag_flag::factory_by_content_type($flag_type);
+    $info = $flag->get_views_info();
+
+    if (!empty($info['join field'])) {
+      // Add the flag relationship.
+      $data[$info['views table']]['flag_content_rel'] = array(
+        'group' => t('Flags'),
+        'title' => $info['title'],
+        'help' => $info['help'],
+        'relationship' => array(
+          'flag type' => $flag_type,
+          'handler' => 'flag_handler_relationship_content',
+          'label' => t('flag'),
+          'base' => 'flag_content',
+          'base field' => 'content_id',
+          'relationship field' => $info['join field'],
+        ),
+      );
+
+      // Add the flag counter relationship.
+      $data[$info['views table']]['flag_count_rel'] = array(
+        'group' => t('Flags'),
+        'title' => $info['counter title'],
+        'help' => $info['counter help'],
+        'relationship' => array(
+          'flag type' => $flag_type,
+          'handler' => 'flag_handler_relationship_counts',
+          'label' => t('counter'),
+          'base' => 'flag_counts',
+          'base field' => 'content_id',
+          'relationship field' => $info['join field'],
+        ),
+      );
+    }
+  }
+
+  // Add a relationship for the user that flagged any type of content.
+  $data['users']['flag_user_content_rel'] = array(
+    'group' => t('Flags'),
+    'title' => t("User's flagged content"),
+    'help' => t('Limit results to users that have flagged certain content.'),
+    'relationship' => array(
+      'base' => 'flag_content',
+      'base field' => 'uid',
+      'relationship field' => 'uid',
+      'handler' => 'flag_handler_relationship_user_content',
+      'label' => t('user flagged content'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_views_query_substitutions().
+ *
+ * Allow replacement of current user's session id so we can cache these queries.
+ */
+function flag_views_query_substitutions() {
+  return array(
+    '***FLAG_CURRENT_USER_SID***' => flag_get_sid(),
+  );
+}
+/**
+ * A helper function that creates a radio list of available flags.
+ *
+ * This function is used to select the desired flag when setting up flag
+ * relationships and fields.
+ */
+function flag_views_flag_config_form($form_type, $content_type, $current_flag) {
+  $flags = flag_get_flags($content_type);
+
+  $options = array();
+  foreach ($flags as $flag) {
+    $options[$flag->name] = $flag->get_title();
+  }
+
+  $form = array(
+    '#type' => $form_type,
+    '#title' => t('Flag'),
+    '#options' => $options,
+    '#default_value' => $current_flag,
+    '#required' => TRUE,
+  );
+
+  return $form;
+}
+
+/**
+ * Helper function that gets the first defined flag and returns its name.
+ */
+function flag_views_flag_default($content_type) {
+  static $default_flag = array();
+
+  if (!array_key_exists($content_type, $default_flag)) {
+    $flag = array_shift(flag_get_flags($content_type));
+    $default_flag[$content_type] = $flag ? $flag->name : NULL;
+  }
+
+  return $default_flag[$content_type];
+}
diff --git a/includes/views/flag.views_convert.inc b/includes/views/flag.views_convert.inc
new file mode 100644
index 0000000..d7e0d07
--- /dev/null
+++ b/includes/views/flag.views_convert.inc
@@ -0,0 +1,235 @@
+<?php
+
+/**
+ * @file
+ * Update Views 1 views provided by both views_bookmark and flag modules.
+ */
+
+/**
+ * Implements hook_views_convert().
+ *
+ * Intervene to convert field values from the Views 1 format to the
+ * Views 2 format. Intervene only if $view->add_item() won't produce
+ * the right results, usually needed to set field options or values.
+ */
+function flag_views_convert($display, $type, &$view, $field, $id) {
+  static $flag_name;
+
+  // First, replace any sign of views_bookmark with flag's Views 1 equivelant.
+  $key_search = array(
+    '^views_bookmark_ops_([0-9]+)' => 'flag_ops_',
+    '^views_bookmark_nodes_([0-9]+)' => 'flag_content_',
+    '^views_bookmark_users_([0-9]+)' => 'flag_users_',
+    '^views_bookmark_node_count_([0-9]+)' => 'flag_counts_',
+  );
+  foreach ($field as $property => $value) {
+    foreach ($key_search as $search => $replace) {
+      if (!empty($value) && is_string($value) && preg_match('/' . $search . '/', $value, $matches)) {
+        $flag = flag_get_flag(NULL, $matches[1]);
+        $replace = $replace . $flag->name;
+        $field[$property] = preg_replace('/' . $search . '/', $replace, $value);
+      }
+    }
+  }
+
+  // Create a table/field identifier for this field.
+  foreach (array('flag_ops_', 'flag_content_', 'flag_users_', 'flag_counts_') as $table) {
+    if (strpos($field['tablename'], $table) === 0) {
+      $name = str_replace($table, '', $field['tablename']);
+      if (!empty($name) && !isset($flag_name)) {
+        $flag_name = $name;
+      }
+    }
+  }
+
+  // Now update values, options, etc. to those selected in the imported view.
+  switch ($type) {
+    case 'field':
+      switch ($id) {
+        case 'ops':
+          $new_field = array(
+            'label' => $field['label'],
+            'id' => 'ops',
+            'table' => 'flag_content',
+            'field' => 'ops',
+            'relationship' => 'flag_content_rel',
+          );
+          $new_rel = 'flag_content_rel';
+          $flag_content_rel_user = 'current';
+          break;
+        case 'count':
+          $new_field = array(
+            'label' => $field['label'],
+            'id' => 'count',
+            'table' => 'flag_counts',
+            'field' => 'count',
+            'relationship' => 'flag_counts_rel',
+          );
+          $new_rel = 'flag_counts_rel';
+          break;
+        case 'name':
+          $new_field = array(
+            'label' => $field['label'],
+            'link_to_user' => 1,
+            'id' => 'name',
+            'table' => 'users',
+            'field' => 'name',
+            'relationship' => 'uid',
+          );
+          $new_rel = 'uid';
+          break;
+      }
+      break;
+
+    case 'filter':
+    case 'exposed_filter':
+      switch ($id) {
+        case 'uid':
+          // The flag_content uid filter means "Include content only flagged by
+          // the current user". In D6, this is no longer a filter, but instead
+          // part of the relationship. Make the relationship join on the uid.
+          if ($field['value'] == '***CURRENT_USER***') {
+            $new_rel = 'flag_content_rel';
+            $flag_content_rel_user = 'current';
+          }
+          // Remove the old filter.
+          $view->set_item('default', $type, $id, NULL);
+          break;
+        case 'timestamp':
+          $new_field = array(
+            'operator' => $field['operator'],
+            'value' => flag_views_convert_timestamp_value($field['value']),
+            'group' => 0,
+            'id' => 'timestamp',
+            'table' => 'flag_content',
+            'field' => 'timestamp',
+            'relationship' => 'flag_content_rel',
+            'exposed' => $type == 'exposed_filter' ? 1 : 0,
+          );
+          $new_rel = 'flag_content_rel';
+          drupal_set_message(t('Flag is not able to convert the <em>Flagged time</em> filter. It\'s value is currently empty, but needs to be populated to work properly.'), 'warning');
+          break;
+        case 'count':
+          $new_field = array(
+            'operator' => $field['operator'],
+            'value' => array('value' => $field['value']),
+            'group' => 0,
+            'id' => 'count',
+            'table' => 'flag_counts',
+            'field' => 'count',
+            'relationship' => 'flag_counts_rel',
+            'exposed' => $type == 'exposed_filter' ? 1 : 0,
+          );
+          $new_rel = 'flag_counts_rel';
+          break;
+      }
+      break;
+
+    case 'argument':
+      // Flag in Drupal 5 only provides one argument, and in Views 1 arguments
+      // weren't given and ID, so we use the field type.
+      if (strpos($field['type'], 'flag_content_') === 0) {
+        $new_field = array(
+          'id' => 'uid',
+          'table' => 'users',
+          'field' => 'uid',
+          'relationship' => 'uid',
+        );
+        $new_rel = 'uid';
+      }
+      break;
+
+    case 'sort':
+      switch ($id) {
+        case 'count':
+          $new_field = array(
+            'order' => $field['sortorder'],
+            'id' => 'count',
+            'table' => 'flag_counts',
+            'field' => 'count',
+            'relationship' => 'flag_counts_rel',
+          );
+          $new_rel = 'flag_counts_rel';
+          break;
+        case 'timestamp':
+          $new_field = array(
+            'order' => $field['sortorder'],
+            'id' => 'timestamp',
+            'table' => 'flag_content',
+            'field' => 'timestamp',
+            'relationship' => 'flag_content_rel',
+          );
+          $new_rel = 'flag_content_rel';
+          break;
+      }
+      break;
+  }
+
+  // Add any new fields.
+  if (isset($new_field)) {
+    // Exposed filters are now wrapped into filters.
+    $type = $type == 'exposed_filter' ? 'filter' : $type;
+    // Update the type in the view options.
+    $view->set_item('default', $type, $id, $new_field);
+  }
+
+  // Add any new relationships (but only once per view).
+  if (isset($new_rel) && !isset($view->display[$display]->display_options['relationships'][$new_rel])) {
+    if ($new_rel == 'flag_content_rel') {
+      $view->display[$display]->display_options['relationships'][$new_rel] = array(
+        'label' => $flag_name,
+        'required' => 1,
+        'flag' => $flag_name,
+        'user_scope' => 'any',
+        'id' => 'flag_content_rel',
+        'table' => 'node',
+        'field' => 'flag_content_rel',
+        'relationship' => 'none',
+      );
+    }
+    elseif ($new_rel == 'flag_counts_rel') {
+      $view->display[$display]->display_options['relationships'][$new_rel] = array(
+        'label' => $flag_name . ' counts',
+        'required' => 0,
+        'flag' => $flag_name,
+        'id' => 'flag_counts_rel',
+        'table' => 'node',
+        'field' => 'flag_counts_rel',
+        'relationship' => 'none',
+      );
+    }
+    elseif ($new_rel == 'uid') {
+      $view->display[$display]->display_options['relationships'][$new_rel] = array(
+        'label' => isset($flag_name) ? $flag_name . ' user' : 'user',
+        'required' => 0,
+        'id' => 'uid',
+        'table' => 'flag_content',
+        'field' => 'uid',
+        'relationship' => 'flag_content_rel',
+      );
+    }
+  }
+
+  // Modify relationships if needed.
+  if (isset($flag_content_rel_user) && isset($view->display[$display]->display_options['relationships']['flag_content_rel'])) {
+    $view->display[$display]->display_options['relationships']['flag_content_rel']['user_scope'] = 'current';
+  }
+}
+
+/**
+ * In Views 1, dates were simply stored as a single string. In Views 2, the
+ * value is stored as an array of options.
+ */
+function flag_views_convert_timestamp_value($value) {
+  $type = 'date';
+  if (stripos($value, 'now') !== FALSE) {
+    $type = 'offset';
+    $value = trim(str_ireplace('now', '', $value));
+  }
+  return array(
+    'type' => $type,
+    'value' => $value,
+    'min' => '',
+    'max' => '',
+  );
+}
diff --git a/includes/views/flag_handler_argument_content_id.inc b/includes/views/flag_handler_argument_content_id.inc
new file mode 100644
index 0000000..ee18743
--- /dev/null
+++ b/includes/views/flag_handler_argument_content_id.inc
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains the content ID argument handler.
+ */
+
+/**
+ * Handler to accept an argument of the content ID of any object.
+ *
+ * @ingroup views
+ */
+class flag_handler_argument_content_id extends views_handler_argument_numeric {
+
+  /**
+   * Returns the flag object associated with our argument.
+   *
+   * An argument is in some relationship. This function reaches out for this
+   * relationship and reads its 'flag' option, which holds the flag name.
+   */
+  function get_flag() {
+    return $this->view->relationship[$this->options['relationship']]->get_flag();
+  }
+
+  /**
+   * Override the behavior of title(). Get the title of the appropriate objects.
+   */
+  function title_query() {
+    if (!($flag = $this->get_flag())) {
+      return array(); // Error message is printed by get_flag().
+    }
+    $views_info = $flag->get_views_info();
+
+    $titles = array();
+    $placeholders = implode(', ', array_fill(0, sizeof($this->value), '%d'));
+
+    $result = db_select($views_info['views table'], 'o')
+      ->fields('o', array($views_info['title field']))
+      ->condition('o.' . $views_info['join field'], $this->value, 'IN')
+      ->execute();
+    foreach ($result as $title) {
+      $titles[] = check_plain($title->$views_info['title field']);
+    }
+    return $titles;
+  }
+}
diff --git a/includes/views/flag_handler_field_ops.inc b/includes/views/flag_handler_field_ops.inc
new file mode 100644
index 0000000..aaa8f2b
--- /dev/null
+++ b/includes/views/flag_handler_field_ops.inc
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Contains the flag Ops field handler.
+ */
+
+/**
+ * Views field handler for the Flag operations links (flag/unflag).
+ *
+ * @ingroup views
+ */
+class flag_handler_field_ops extends views_handler_field {
+
+  /**
+   * Returns the flag object associated with our field.
+   *
+   * A field is in some relationship. This function reaches out for this
+   * relationship and reads its 'flag' option, which holds the flag name.
+   */
+  function get_flag() {
+    // When editing a view it's possible to delete the relationship (either by
+    // error or to later recreate it), so we have to guard against a missing
+    // one.
+    if (isset($this->view->relationship[$this->options['relationship']])) {
+      return $this->view->relationship[$this->options['relationship']]->get_flag();
+    }
+  }
+
+  /**
+   * Return the the relationship we're linked to. That is, the alias for its
+   * table (which is suitbale for use with the various methods of the 'query'
+   * object).
+   */
+  function get_parent_relationship() {
+    $parent = $this->view->relationship[$this->options['relationship']]->options['relationship'];
+    if (!$parent || $parent == 'none') {
+      return NULL; // Base query table.
+    }
+    else {
+      return $this->view->relationship[$parent]->alias;
+    }
+  }
+
+  function option_definition() {
+    $options = parent::option_definition();
+    $options['link_type'] = array('default' => '');
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+
+    $form['link_type'] = array(
+      '#type' => 'radios',
+      '#title' => t('Link type'),
+      '#options' => array('' => t('Use flag link settings')) + _flag_link_type_options(),
+      '#default_value' => $this->options['link_type'],
+    );
+  }
+
+  /**
+   * Override base ::query(). The purpose here is to make it possible for the
+   * render() method to know two things: what's the content ID, and whether
+   * it's flagged.
+   */
+  function query() {
+    if (!($flag = $this->get_flag())) {
+      return; // Error message is printed in render().
+    }
+    $info = $flag->get_views_info();
+    $parent = $this->get_parent_relationship();
+
+    // Find out if the content is flagged. We can't just peek at some field in
+    // our loaded table because it doesn't always reflect the user browsing the
+    // page. So we explicitly add the flag_content table to find this out.
+    $join = new views_join();
+    $join->construct('flag_content', $info['views table'], $info['join field'], 'content_id');
+    $join->extra[] = array(
+      'field' => 'fid',
+      'value' => $flag->fid,
+      'numeric' => TRUE,
+    );
+    if (!$flag->global) {
+      $join->extra[] = array(
+        'field' => 'uid',
+        'value' => '***CURRENT_USER***',
+        'numeric' => TRUE,
+      );
+      $join->extra[] = array(
+        'field' => 'sid',
+        'value' => '***FLAG_CURRENT_USER_SID***',
+        'numeric' => TRUE,
+      );
+    }
+    $flag_table = $this->query->add_table('flag_content', $parent, $join);
+    $this->aliases['is_flagged'] = $this->query->add_field($flag_table, 'content_id');
+
+    // Next, find out the content ID. We can't add_field() on this table
+    // (flag_content), because its content_id may be NULL (in case no user has
+    // flagged this content, and it's a LEFT JOIN). So we reach to the parent
+    // relationship and add_field() *its* content ID column.
+    $left_table = $this->view->relationship[$this->options['relationship']]->table_alias;
+    $this->aliases['content_id'] = $this->query->add_field($left_table, $info['join field']);
+  }
+
+  /**
+   * Find out if the flag applies to each item seen on the page. It's done in a
+   * separate DB query to to avoid complexity and to make 'many to one' tests
+   * (e.g. checking user roles) possible without causing duplicate rows.
+   */
+  function pre_render(&$values) {
+    if (!($flag = $this->get_flag())) {
+      return; // Error message is printed in render().
+    }
+
+    $ids = array();
+    foreach ($values as $row) {
+      $content_id = $row->{$this->aliases['content_id']};
+      $is_flagged = $row->{$this->aliases['is_flagged']};
+      if (isset($content_id)) {
+        $ids[$content_id] = $is_flagged ? 'unflag' : 'flag';
+      }
+    }
+    $this->flag_applies = $ids ? $flag->access_multiple($ids) : array();
+  }
+
+  function render($values) {
+    if (!($flag = $this->get_flag())) {
+      return t('Missing flag'); // get_flag() itself will print a more detailed message.
+    }
+
+    $content_id = $values->{$this->aliases['content_id']};
+    $is_flagged = $values->{$this->aliases['is_flagged']};
+
+    if (empty($this->flag_applies[$content_id])) {
+      // Flag does not apply to this content.
+      return;
+    }
+
+    if (!empty($this->options['link_type'])) {
+      $flag->link_type = $this->options['link_type'];
+    }
+    return $flag->theme($is_flagged ? 'unflag' : 'flag', $content_id);
+  }
+}
diff --git a/includes/views/flag_handler_filter_flagged.inc b/includes/views/flag_handler_filter_flagged.inc
new file mode 100644
index 0000000..d22c40b
--- /dev/null
+++ b/includes/views/flag_handler_filter_flagged.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains the flagged content filter handler.
+ */
+
+/**
+ * Handler to filter for content that has not been flagged.
+ *
+ * @ingroup views
+ */
+class flag_handler_filter_flagged extends views_handler_filter_boolean_operator {
+  function option_definition() {
+    $options = parent::option_definition();
+    $options['value'] = array('default' => 1);
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $form['value']['#type'] = 'radios';
+    $form['value']['#title'] = t('Status');
+    $form['value']['#options'] = array(1 => t('Flagged'), 0 => t('Not flagged'), 'All' => t('All'));
+    $form['value']['#default_value'] = empty($this->options['value']) ? '0' : $this->options['value'];
+    $form['value']['#description'] = '<p>' . t('This filter is only needed if the relationship used has the "Include only flagged content" option <strong>unchecked</strong>. Otherwise, this filter is useless, because all records are already limited to flagged content.') . '</p><p>' . t('By choosing <em>Not flagged</em>, it is possible to create a list of content <a href="@unflagged-url">that is specifically not flagged</a>.', array('@unflagged-url' => 'http://drupal.org/node/299335')) . '</p>';
+  }
+
+  function query() {
+    $operator = $this->value ? 'IS NOT' : 'IS';
+    $this->query->add_where($this->options['group'], $this->relationship . '.uid', NULL, $operator . ' NULL');
+  }
+}
diff --git a/includes/views/flag_handler_relationships.inc b/includes/views/flag_handler_relationships.inc
new file mode 100644
index 0000000..a2fa516
--- /dev/null
+++ b/includes/views/flag_handler_relationships.inc
@@ -0,0 +1,266 @@
+<?php
+
+/**
+ * @file
+ * Contains various relationship handlers.
+ */
+
+/**
+ * Base class for all our relationship classes.
+ *
+ * @ingroup views
+ */
+abstract class flag_handler_relationship extends views_handler_relationship {
+
+  /**
+   * Every relationship has a 'flag' option.
+   */
+  function option_definition() {
+    $options = parent::option_definition();
+    $options['flag'] = array('default' => NULL);
+    $options['required'] = array('default' => 1);
+    return $options;
+  }
+
+  /**
+   * Make sure the flag exists.
+   *
+   * When importing views, or when deleting flags, inconsistent views may
+   * result. This validator is called by Views before saving or previewing a
+   * view.
+   */
+  function validate() {
+    $errors = array();
+    $tokens = array(
+      '@relationship-name' => $this->ui_name() . ' ' . $this->admin_summary(),
+      '@flag-name' => $this->options['flag'],
+    );
+    if (!$this->options['flag']) {
+      $errors[] = t('You must pick a flag to use for the relationship "@relationship-name".', $tokens);
+    }
+    elseif (!flag_get_flag($this->options['flag'])) {
+      $errors[] = t('This view is looking for a flag by the name "@flag-name", but there is no such flag. Perhaps it was deleted. Please update the relationship "@relationship-name" in this view to use an existing flag.', $tokens);
+    }
+    return $errors;
+  }
+
+  function get_flag_type() {
+    return isset($this->definition['flag type']) ? $this->definition['flag type'] : NULL;
+  }
+
+  /**
+   * Returns the flag object.
+   */
+  function get_flag() {
+
+    // Backward compatibility: There may exist old views on the system whose
+    // 'flag' option isn't set. (This happens if the admin had skippped
+    // clicking the 'Update' button.) When run, these views should behave as
+    // if the first flag was selected.
+    if (!isset($this->options['flag'])) {
+      $this->options['flag'] = flag_views_flag_default($this->get_flag_type());
+    }
+
+    // Validation: Since validate() is called only when in Views's
+    // administrative UI, we need to do validation at "run time" ourselves.
+    if (($errors = $this->validate())) {
+      foreach ($errors as $error) {
+        drupal_set_message($error, 'error');
+      }
+    }
+
+    return flag_get_flag($this->options['flag']);
+  }
+
+  // @todo: It's logical that this class should also implement options_form(),
+  // to show the flag selector, and query(), to filter on the flag.
+}
+
+/**
+ * Specialized relationship handler associating flags and content.
+ *
+ * @ingroup views
+ */
+class flag_handler_relationship_content extends flag_handler_relationship {
+
+  function option_definition() {
+    $options = parent::option_definition();
+    $options['user_scope'] = array('default' => 'current');
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $content_type = $this->definition['flag type'];
+    $form['label']['#description'] .= ' ' . t('The name of the selected flag makes a good label.');
+    $form['flag'] = flag_views_flag_config_form('radios', $content_type, $this->options['flag']);
+
+    $form['user_scope'] = array(
+      '#type' => 'radios',
+      '#title' => t('By'),
+      '#options' => array('current' => t('Current user'), 'any' => t('Any user')),
+      '#default_value' => $this->options['user_scope'],
+    );
+
+    $form['required']['#title'] = t('Include only flagged content');
+    $form['required']['#description'] = t('If checked, only content that has this flag will be included. Leave unchecked to include all content; or, in combination with the <em>Flagged</em> filter, <a href="@unflagged-url">to limit the results to specifically unflagged content</a>.', array('@unflagged-url' => 'http://drupal.org/node/299335'));
+
+    if (!$form['flag']['#options']) {
+      $form = array(
+        'error' => array(
+          '#markup' => '<p class="error form-item">' . t('No %type flags exist. You must first <a href="@create-url">create a %type flag</a> before being able to use this relationship type.', array('%type' => $content_type, '@create-url' => url(FLAG_ADMIN_PATH))) . '</p>',
+        ),
+      );
+      $form_state['no flags exist'] = TRUE;
+    }
+    if (module_exists('session_api')) {
+      $form['session_warning'] = array(
+        '#markup' => '<p class="warning form-item">' . t('<strong>Warning</strong>: Adding this relationship for any flag that contains <strong>anonymous flagging access</strong> will disable page caching for anonymous users when this view is executed. (But this is only true when the relationship is constrained to "Current user", not to "Any user".) It is recommended to create a dedicated page for views containing anonymous user data.') . '</p>',
+      );
+    }
+  }
+
+  function options_validate(&$form, &$form_state) {
+    if (!empty($form_state['no flags exist'])) {
+      form_error($form, t('You must first create a flag'));
+    }
+  }
+
+  function admin_summary() {
+    return $this->options['user_scope'] == 'current' ? t('by current user') : t('by any user');
+  }
+
+  function ui_name($short = FALSE) {
+    // We put the bookmark name in the UI string to save space.
+    return t('!group: !title', array('!group' => $this->definition['group'], '!title' => empty($this->options['flag']) ? t('(Please select a flag)') : $this->options['flag']));
+  }
+
+  /**
+   * Called to implement a relationship in a query.
+   */
+  function query() {
+    if (!($flag = $this->get_flag())) {
+      return;
+    }
+
+    $this->definition['extra'][] = array(
+      'field' => 'fid',
+      'value' => $flag->fid,
+      'numeric' => TRUE,
+    );
+    if ($this->options['user_scope'] == 'current' && !$flag->global) {
+      $this->definition['extra'][] = array(
+        'field' => 'uid',
+        'value' => '***CURRENT_USER***',
+        'numeric' => TRUE,
+      );
+      if (array_search(DRUPAL_ANONYMOUS_RID, $flag->roles['flag']) !== FALSE) {
+        // Disable page caching for anonymous users.
+        drupal_page_is_cacheable(FALSE);
+
+        // Add in the SID from Session API for anonymous users.
+        $this->definition['extra'][] = array(
+          'field' => 'sid',
+          'value' => '***FLAG_CURRENT_USER_SID***',
+          'numeric' => TRUE,
+        );
+      }
+    }
+    parent::query();
+  }
+}
+
+/**
+ * Specialized relationship handler associating flag counts and content.
+ *
+ * @ingroup views
+ */
+class flag_handler_relationship_counts extends flag_handler_relationship {
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $content_type = $this->definition['flag type'];
+    $form['flag'] = flag_views_flag_config_form('radios', $content_type, $this->options['flag']);
+
+    $form['required']['#title'] = t('Include only flagged content');
+    $form['required']['#description'] = t('If checked, only content that is flagged will be included.');
+  }
+
+  function admin_summary() {
+    // Nothing to show.
+  }
+
+  function ui_name($short = FALSE) {
+    // We put the bookmark name in the UI string to save space.
+    return t('!group: !title counter', array('!group' => $this->definition['group'], '!title' => $this->options['flag']));
+  }
+
+  /**
+   * Called to implement a relationship in a query.
+   */
+  function query() {
+    if (!($flag = $this->get_flag())) {
+      return;
+    }
+
+    $this->definition['extra'][] = array(
+      'field' => 'fid',
+      'value' => $flag->fid,
+      'numeric' => TRUE,
+    );
+    if (!empty($this->options['required'])) {
+      // Unfortunately, we may have zeros in our table, so having
+      // parent::query() do INNER JOIN doesn't suffice. We need to filter these
+      // zeros out.
+      // @todo Make sure zero records aren't written in the first place, and
+      // remove this code.
+      $this->definition['extra'][] = array(
+        'field' => 'count',
+        'operator' => '>',
+        'value' => '0',
+        'numeric' => TRUE,
+      );
+    }
+    parent::query();
+  }
+}
+
+/**
+ * Specialized relationship handler associating flags and users.
+ *
+ * @ingroup views
+ */
+class flag_handler_relationship_user_content extends flag_handler_relationship {
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $form['label']['#description'] .= ' ' . t('Including name of the selected flag helps identify this relationship.');
+
+    $form['flag'] = flag_views_flag_config_form('radios', NULL, $this->options['flag']);
+    $form['flag']['#title'] = t('Flagged');
+
+    $form['required']['#title'] = t('Include only users who have flagged content.');
+    $form['required']['#description'] = t('If checked, only users that have flagged any content with this flag will be included.');
+  }
+
+  function admin_summary() {
+    return $this->options['flag'];
+  }
+
+  /**
+   * Called to implement a relationship in a query.
+   */
+  function query() {
+    if (!($flag = $this->get_flag())) {
+      return;
+    }
+
+    $this->definition['extra'][] = array(
+      'field' => 'fid',
+      'value' => $flag->fid,
+      'numeric' => TRUE,
+    );
+    parent::query();
+  }
+}
+
diff --git a/includes/views/flag_plugin_argument_validate_flaggability.inc b/includes/views/flag_plugin_argument_validate_flaggability.inc
new file mode 100644
index 0000000..cb3587a
--- /dev/null
+++ b/includes/views/flag_plugin_argument_validate_flaggability.inc
@@ -0,0 +1,238 @@
+<?php
+
+/**
+ * @file
+ * Contains the flaggability validator handler.
+ */
+
+/**
+ * Validates whether an argument is a flaggable/flagged object.
+ *
+ * @ingroup views
+ */
+class flag_plugin_argument_validate_flaggability extends views_plugin_argument_validate {
+
+  function construct() {
+    parent::construct();
+    $this->flag_type = $this->definition['flag type'];
+  }
+
+  function option_definition() {
+    $options = parent::option_definition();
+    $options['flag_name'] = array('default' => '*relationship*');
+    $options['flag_test'] = array('default' => 'flaggable');
+    $options['flag_id_type'] = array('default' => 'id');
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    // If there are no flag relationships set, skip this form of validation.
+    $flags = flag_get_flags($this->flag_type);
+    if (!$flags) {
+      return array();
+    }
+    $options = $this->flags_options();
+
+    $form[$this->_option_name('flag_name')] = array(
+      '#type' => 'radios',
+      '#title' => t('Flag'),
+      '#options' => $options,
+      '#default_value' => $this->_get_option('flag_name', '*relationship*'),
+      '#description' => t('Select the flag to validate against.'),
+    );
+    if (!$options) {
+      $form[$this->_option_name('flag_name')]['#description'] = '<p class="warning">' . t('No %type flags exist. You must first <a href="@create-url">create a %type flag</a> before being able to use one.', array('%type' => $this->flag_type, '@create-url' => FLAG_ADMIN_PATH)) . '</p>';
+    }
+
+    $form[$this->_option_name('flag_test')] = array(
+      '#type' => 'select',
+      '#title' => t('Validate the @type only if', array('@type' => $this->flag_type)),
+      '#options' => $this->tests_options(),
+      '#default_value' => $this->_get_option('flag_test', 'flaggable'),
+    );
+
+    // This validator supports the "multiple IDs" syntax. It may not be
+    // extremely useful, but similar validators do support this and it's a good
+    // thing to be compatible.
+    $form[$this->_option_name('flag_id_type')] = array(
+      '#type' => 'select',
+      '#title' => t('Argument type'),
+      '#options' => array(
+        'id' => t('ID'),
+        'ids' => t('IDs separated by , or +'),
+      ),
+      '#default_value' => $this->_get_option('flag_id_type', 'id'),
+    );
+  }
+
+  /**
+   * Returns form #options for the flags. Returns empty array if no flags were
+   * found.
+   */
+  function flags_options() {
+    $flags = flag_get_flags($this->flag_type);
+    if (!$flags) {
+      return array();
+    }
+    else {
+      foreach ($flags as $flag) {
+        $options[$flag->name] = $flag->get_title();
+      }
+      $options['*relationship*'] = t('<em>Pick the first flag mentioned in the relationships.</em>');
+      return $options;
+    }
+  }
+
+  // This function seems useless, but in the Drupal 6 branch it allows for
+  // compatibility between Views 3 and Views 2.
+  function _get_option($option, $default) {
+    return $this->options[$option];
+  }
+
+  // This function seems useless, but in the Drupal 6 branch it allows for
+  // compatibility between Views 3 and Views 2.
+  function _option_name($option) {
+    return $option;
+  }
+
+  /**
+   * Migrate existing Views 2 options to Views 3.
+   */
+  function convert_options(&$options) {
+    $prefix = 'validate_argument_' . $this->flag_type . '_';
+    if (!isset($options['flag_name']) && !empty($this->argument->options[$prefix . 'flag_name'])) {
+      $options['flag_name'] = $this->argument->options[$prefix . 'flag_name'];
+      $options['flag_test'] = $this->argument->options[$prefix . 'flag_test'];
+      $options['flag_id_type'] = $this->argument->options[$prefix . 'flag_id_type'];
+    }
+  }
+
+  /**
+   * Declares all tests. This scheme makes it easy for derived classes to add
+   * and remove tests.
+   */
+  function tests_info($which = NULL) {
+    return array(
+      'flaggable' => array(
+        'title' => t('It is flaggable'),
+        'callback' => 'test_flaggable',
+      ),
+      'flagged' => array(
+        'title' => t('It is flagged at least once'),
+        'callback' => 'test_flagged',
+      ),
+      'flagged_by_current_user' => array(
+        'title' => t('It is flagged by the current user'),
+        'callback' => 'test_flagged_by_current_user',
+      ),
+    );
+  }
+
+  function tests_options() {
+    $options = array();
+    foreach ($this->tests_info() as $id => $info) {
+      $options[$id] = $info['title'];
+    }
+    return $options;
+  }
+
+  function get_flag() {
+    $flag_name = $this->_get_option('flag_name', '*relationship*');
+
+    if ($flag_name == '*relationship*') {
+      // Pick the first flag mentioned in the relationships.
+      foreach ($this->view->relationship as $id => $handler) {
+        // Note: we can't do $handler->field, because the relationship handler's init() may overwrite it.
+        if (strpos($handler->options['field'], 'flag') !== FALSE && !empty($handler->options['flag'])) {
+          $flag = flag_get_flag($handler->options['flag']);
+          if ($flag && $flag->content_type == $this->flag_type) {
+            return $flag;
+          }
+        }
+      }
+    }
+
+    return flag_get_flag($flag_name);
+  }
+
+  /**
+   * Tests whether the argument is flaggable, or flagged, or flagged by current
+   * user. These are three possible tests, and which of the three to actually
+   * carry out is determined by 'flag_test'.
+   */
+  function validate_argument($argument) {
+    $flag_test = $this->_get_option('flag_test', 'flaggable');
+    $id_type   = $this->_get_option('flag_id_type', 'id');
+
+    $flag = $this->get_flag();
+    if (!$flag) {
+      // Validator is misconfigured somehow.
+      return TRUE;
+    }
+
+    if ($id_type == 'id') {
+      if (!is_numeric($argument)) {
+        // If a user is being smart and types several IDs where only one is
+        // expected, we invalidate this.
+        return FALSE;
+      }
+    }
+
+    $ids = views_break_phrase($argument, $this);
+    if ($ids->value == array(-1)) {
+      // Malformed argument syntax. Invalidate.
+      return FALSE;
+    }
+    $ids = $ids->value;
+
+    // Delegate the testing to the particual test method. $passed then
+    // holds the IDs that passed the test.
+    $tests_info = $this->tests_info();
+    $method = $tests_info[$flag_test]['callback'];
+    if (method_exists($this, $method)) {
+      $passed = $this->$method($ids, $flag);
+    }
+    else {
+      $passed = array();
+    }
+
+    // If some IDs exist that haven't $passed our test then validation fails.
+    $failed = array_diff($ids, $passed);
+    return empty($failed);
+  }
+
+  //
+  // The actual tests. They return the IDs that passed.
+  //
+
+  function test_flaggable($ids, $flag) {
+    return array_filter($ids, array($flag, 'applies_to_content_id'));
+  }
+
+  function test_flagged($ids, $flag) {
+    // view_break_phrase() is guaranteed to return only integers, so this is SQL safe.
+    $flattened_ids = implode(',', $ids);
+    return $this->_test_by_sql("SELECT content_id FROM {flag_counts} WHERE fid = :fid AND content_id IN ($flattened_ids) AND count > 0", array(':fid' => $flag->fid));
+  }
+
+  function test_flagged_by_current_user($ids, $flag) {
+    global $user;
+    if (!$user->uid) {
+      // Anonymous user
+      return array();
+    }
+    $flattened_ids = implode(',', $ids);
+    return $this->_test_by_sql("SELECT content_id FROM {flag_content} WHERE fid = :fid AND content_id IN ($flattened_ids) AND uid = :uid", array(':fid' => $flag->fid, ':uid' => $user->uid));
+  }
+
+  // Helper: executes an SQL query and returns all the content_id's.
+  function _test_by_sql($sql, $args) {
+    $passed = array();
+    $result = db_query($sql, $args);
+    foreach ($result as $row) {
+      $passed[] = $row->content_id;
+    }
+    return $passed;
+  }
+}
+
