<?php
// $Id: user_relationship_implications.module,v 1.17.4.11 2008/03/14 06:10:27 sprsquish Exp $

/**
 * Drupal Module: User Relationship Implications
 *
 * @author: Jeff Smick <sprsquish [at] gmail [dot] com>
 * @file
 * Allows admins to create implied relationships (eg: Manager implies Coworker)
 */


/**
 * Public API to load an implied relationship
 *
 * @param $riid
 *    integer of the implied relationship ID
 *
 * @return
 *    object with the relationship_type object and implied relationship_type object
 */
function user_relationship_implication_load($param = array()) {
  $implied_relationships = user_relationship_implications_load();

  if (is_numeric($param)) {
    return $implied_relationships[$param];
  }

  foreach ($implied_relationships as $implied) {
    $found = TRUE;

    foreach($param as $column => $value) {
      $column = strtolower($column);

      if ($column == 'name' || $column == 'plural_name') {
        $value = strtolower($value);
        $col_val = strtolower($implied->$column);
      }
      else {
        $col_val = $implied->$column;
      }

      // mismatch, move to the next type
      if ($col_val != $value) {
        $found = FALSE;
        break;
      }
    }

    if ($found) {
      return $type;
    }
  }
}


/**
 * Public API to load all implied relationships
 *
 * @return
 *    array of relationship implications
 */
function user_relationship_implications_load($reset = FALSE) {
  static $implications = array();

  if ($reset || !$implications) {
    $results = db_query("SELECT * FROM {user_relationship_implications}");

    while ($implication = db_fetch_object($results)) {
      $implication->relationship_type         = user_relationships_type_load($implication->rtid);
      $implication->implies_relationship_type = user_relationships_type_load($implication->implies_rtid);
      $implications[$implication->riid] = $implication;
    }
  }

  return $implications;
}


/**
 * hook_form_alter()
 */
function user_relationship_implications_form_alter($form_id, &$form) {
  switch ($form_id) {
  case 'user_relationships_type_edit':
    $rtid = $form['rtid']['#value'];
    $relationship_type = user_relationships_type_load($rtid);
    $relationship_types = user_relationships_types_load();

    $implied_by = array();
    if ($relationship_type) {      
      foreach ($relationship_type->implies as $rtid => $implies) {
        $values[$rtid]['strict'] = $implies->strict;
        $values[$rtid]['reverse'] = $implies->reverse;
      }
      foreach ($relationship_type->implied_by as $implied) {
        if(!$implied->reverse) {
          $implied_by[] = $implied->rtid;
        }
      }
    }

    $form['implications'] = array(
      '#title'          => t('This relationship implies'),
      '#type'           => 'fieldset',
      '#weight'         => 0,
      '#tree'           => TRUE,
      '#theme'          => 'user_relationship_implications_form_table',
      '#description'    => t('
        <ul>
          <li>Users will automatically have these relationships created between them when the implying relationship is created. (ex: Manager implies Coworker).</li>
          <li>When the implied relationship is removed the implying relationship will not be removed. (ex: removing Coworker WILL NOT remove Manager)</li>
          <li>If "strict" is set the implying relationship will be removed when the implied relationship is removed. (ex: removing Coworker WILL remove Manager)</li>
          <li>Reverse is really only useful for one-way relationships. It allows things like Parent implies Offspring to work in the right direction</li>
        </ul>
      '),
    );

    $list = FALSE;
    foreach ($relationship_types as $type) {
      if ($type->rtid != $relationship_type->rtid && !in_array($type->rtid, $implied_by)) {
        $imp_name = "implies_{$type->rtid}";
        $form['implications']['opts'][$type->rtid][$imp_name] = array(
          '#title'          => t($type->name),
          '#type'           => 'checkbox',
          '#return_value'   => $type->rtid,
          '#default_value'  => isset($form['#post'][$imp_name]) || isset($values[$type->rtid]),
        );

        $strict_name = "implied_{$type->rtid}_strict";
        $form['implications']['opts'][$type->rtid][$strict_name] = array(
          '#type'           => 'checkbox',
          '#return_value'   => $type->rtid,
          '#default_value'  => isset($form['#post'][$strict_name]) || $values[$type->rtid]['strict'],
        );

        $opp_name = "implied_{$type->rtid}_reverse";
        $form['implications']['opts'][$type->rtid][$opp_name] = array(
          '#type'           => 'checkbox',
          '#return_value'   => $type->rtid,
          '#default_value'  => isset($form['#post'][$opp_name]) || $values[$type->rtid]['reverse'],
        );

        $list = TRUE;
      }
    }

    if ($list) {
      $form['#submit']['user_relationship_implications_edit_submit'] = array();
    }
    else {
      unset($form['implications']);
    }
    break;
  }
}


/**
 * Edit relationship type submission processor
 */
function user_relationship_implications_edit_submit($form_id, &$form_values) {
  // the rtid is in a different place when adding a new type vs. editing an existing type
  if (isset($form_values['relationship_type']) && !is_null($form_values['relationship_type'])) {
    // editing an existing relationship type
    $rtid = $form_values['relationship_type']->rtid;
  }
  else {
    // adding a new relationship type - go figure
    $rtid = $form_values['rtid'];
  }

  db_query("DELETE FROM {user_relationship_implications} WHERE rtid = %d", $rtid);

  foreach ($form_values['implications']['opts'] as $implied_rtid => $elements) {
    if ($elements["implies_{$implied_rtid}"]) {
      $strict   = $elements["implied_{$implied_rtid}_strict"] ? 1 : 0;
      $reverse  = $elements["implied_{$implied_rtid}_reverse"] ? 1 : 0;

      db_query(
        "INSERT INTO {user_relationship_implications} (riid, rtid, implies_rtid, strict, reverse) VALUES (%d, %d, %d, %d, %d)", 
        db_next_id('{user_relationship_implications}_id'), $rtid, $implied_rtid, $strict, $reverse
      );
    }
  }
}

/**
 * hook_user_relationships()
 */
function user_relationship_implications_user_relationships($type, $relationship, $category = NULL) {
  switch ($type) {
  case 'load':
    if ($category == 'type' && $relationship->rtid) {
      static $loaded_relationship_implications = array();

      if (!is_array($loaded_relationship_implications[$relationship->rtid])) {
        $loaded_relationship_implications[$relationship->rtid] = array();
        $results = db_query(
          "SELECT * FROM {user_relationship_implications} WHERE rtid = %d OR implies_rtid = %d", 
          $relationship->rtid, $relationship->rtid
        );
        while ($implication = db_fetch_object($results)) {
          $loaded_relationship_implications[$relationship->rtid][] = $implication;
        }
      }

      $relationship->implies    = array();
      $relationship->implied_by = array();
      foreach ($loaded_relationship_implications[$relationship->rtid] as $implication) {
        if ($implication->rtid == $relationship->rtid) {
          $relationship->implies[$implication->implies_rtid] = $implication;
        }
        else {
          $relationship->implied_by[$implication->rtid] = $implication;
        }
      }
    }
    break;

  case 'delete':
    if ($category == 'type') {
      if (!$relationship->rtid) { break; }

      $results = db_query(
        "DELETE FROM {user_relationship_implications} WHERE rtid = %d OR implies_rtid = %d", 
        $relationship->rtid, $relationship->rtid
      );
  
      // clean out the implications cache
      static $loaded_relationship_implications;
      unset($loaded_relationship_implications);
    }

    else if ($category != 'type') {
      $current_type = user_relationships_type_load($relationship->rtid);

      // nothing else matters if there aren't implications involved
      $reversed = array_filter($current_type->implies, '_user_relationship_implications_filter_for_reverse');
      if (!$current_type->implied_by && !$reversed) { break; }

      // load relationships that imply this relationship
      $rids = array_merge(array_keys($current_type->implied_by), array_keys($reversed));
      $relationships = user_relationships_load(array(
        'between' => array($relationship->requester_id, $relationship->requestee_id),
        'rtid'    => $rids
      ), FALSE, 'rtid');

      foreach ($relationships as $rtid => $relationship) {
        $relationship = array_shift($relationship);

        // the relationship being deleted (current_type) is implied by this relationship (only matters if "strict" is set)
        if ($current_type->implied_by[$rtid]->strict || $reversed[$rtid]->strict) {
          user_relationships_delete_relationship($relationship, $current_type->deleted_by, $category);
          drupal_set_message(user_relationships_get_message($category, $relationship));
        }
      }
    }    
    break;

  case 'post-save':
    $type = user_relationships_type_load($relationship->rtid);
    if ($type->implies) {
      // if the type of the relationship we're inserting or updating implies other relationship type(s),
      // loop through the implied relationship types and do the right thing
      foreach ($type->implies as $implied) {
        // load all the pre-existing relationships between these two users of the implied relationship type
        $relationships = user_relationships_load(array(
          'between' => array($relationship->requester_id, $relationship->requestee_id),
          'rtid'    => $implied->implies_rtid,
        ));

        // if there aren't any, create one with the same approved status as the relationship we're inserting/updateing
        if (sizeof($relationships) == 0) {
          $users = array($relationship->requester_id, $relationship->requestee_id);
          if ($implied->reverse) { $users = array_reverse($users); }

          $implied = user_relationships_type_load($implied->implies_rtid);
          user_relationships_request_relationship($users[0], $users[1], $implied, $relationship->approved);
        }
        // if there are some, then if we're approving this relationship, approve the pre-existing one(s) too
        else {
          foreach ($relationships as $existing) {
            if ($relationship->approved && !$existing->approved) {
              // approve the relationship
              $approved = $existing;
              $approved->approved = TRUE;
              user_relationships_update_relationship($existing, $approved);
              // set the message informing the user (use requester and requestee from original relationship)
              drupal_set_message(user_relationships_get_message('accepted', $approved));
            }
          }
        }
      }
    }
    break;

  }
}

/**
 * hook_user_relationships_page_alter()
 */
function user_relationship_implications_user_relationships_page_alter($page_id, &$page, &$table) {
  switch ($page_id) {
  case 'types list':
    array_splice($table['headers'], 2, 0, t('Implies'));

    foreach ($table['data'] as $key => $relationship) {
      $relationship = user_relationships_type_load($relationship->rtid);
      array_splice($table['rows'][$key], 2, 0, '&nbsp;');

      $names = array();
      if (is_array($relationship->implies)) {
        foreach ($relationship->implies as $implied) {
          $implied = user_relationship_implication_load($implied->riid);
          $names[] = $implied->implies_relationship_type->name;
        }
      }
      $table['rows'][$key][2] = implode(', ', $names);
    }
    break;
  }
}

/**
 * Categorized list of relationships for a given user
 */
function theme_user_relationship_implications_page($uid = NULL, $relationship = NULL) {
  global $user;

  if (empty($uid)) {
    $viewed_user =& $user;
  }
  else {
    $viewed_user = user_load(array('uid' => $uid));
  }

  // Check that the uid is valid, not the anonymous user, and the user exists
  if ($viewed_user->uid == 0) {
    drupal_not_found();
    exit();
  }

  $params = array('user' => $viewed_user->uid);
  if (isset($relationship->rtid)) {
    $params['rtid'] = $relationship->rtid;
  }

  $query = _user_relationships_generate_query($params);

  if ($relationships_per_page = variable_get('user_relationships_relationships_per_page', 16)) {
    $results = pager_query($query['query'], $relationships_per_page, 0, $query['count'], $query['arguments']);
  }
  else {
    $results = db_query($query['query'], $query['arguments']);
  }

  if (db_num_rows($results)) {
    $edit_access = ($user->uid == $uid && user_access('maintain relationships')) || user_access('administer users');
    $online_interval = time() - variable_get('user_block_seconds_online', 180);

    while ($relation = db_fetch_object($results)) {
      $this_user = $viewed_user->uid == $relation->requestee_id ? 'requester_id' : 'requestee_id';
      $this_user = user_load(array('uid' => $relation->$this_user));
      $relations = array();

      $this_users_relationships = user_relationships_load(array('user' => $this_user->uid));
      $rows[] = array(
        theme('username', $this_user),
        theme('item_list', _user_relationship_implications_load_relationship_names($this_users_relationships, $viewed_user->uid)),
        $this_user->access > $online_interval ? t('online') : t('not online'),
        $edit_access ? theme('user_relationships_remove_link', $viewed_user->uid, $relation) : '&nbsp;',
      );
    }
    $output .= theme('table', array(), $rows);
  }
  else {
    $output .= t('No relationships found');
  }

  $output .= theme('pager', NULL, $relationships_per_page);

  drupal_set_title(t("%username's %relationships", array(
    '%username' => $viewed_user->name, 
    '%relationships' => $relationship->plural_name ? $relationship->plural_name : t('relationships')
  )));

  return $output;
}


/**
 * Theme function to create a table of checkboxes for the implications
 */
function theme_user_relationship_implications_form_table(&$form) {
  $headers = array(t('Relationship Type'), t('Strict'), t('Reverse'));
  $rows = array();

  foreach ($form['opts'] as $rtid => $elements) {
    if (!is_numeric($rtid)) { continue; }
    $rows[$rtid] = array(
      drupal_render(array_shift($elements)),
      drupal_render(array_shift($elements)),
      drupal_render(array_shift($elements)),
    );
  }
  return theme('table', $headers, $rows);
}


/**
 * Helper functions (not for general use!!)
 */
function _user_relationship_implications_load_relationship_names($relationships, $uid) {
  $output = array();
  foreach ($relationships as $relationship) {
    if ($relationship->requester_id == $uid || $relationship->requestee_id == $uid) {
      $output[] = $relationship->name;
    }
  }
  return $output;
}

function _user_relationship_implications_filter_for_reverse($implication) {
  $implication = (array)$implication;
  return $implication['reverse'];
}
