diff --git a/include/class.staff.php b/include/class.staff.php
index c2c344544944682fe25c579ea235bfd44e6892ba..57b35bd02a22ef325a5ab4f6352cb627a4425a86 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -36,6 +36,9 @@ implements AuthenticatedUser {
             'group' => array(
                 'constraint' => array('group_id' => 'Group.group_id'),
             ),
+            'teams' => array(
+                'constraint' => array('staff_id' => 'StaffTeamMember.staff_id'),
+            ),
         ),
     );
 
@@ -45,6 +48,7 @@ implements AuthenticatedUser {
     var $timezone;
     var $stats = array();
     var $_extra;
+    var $passwd_change;
 
     function __onload() {
         // WE have to patch info here to support upgrading from old versions.
@@ -527,9 +531,9 @@ implements AuthenticatedUser {
 
     function updateTeams($team_ids) {
         if ($team_ids && is_array($team_ids)) {
-            $teams = TeamMember::object()
+            $teams = StaffTeamMember::objects()
                 ->filter(array('staff_id' => $this->getId()));
-            foreach ($team_ids as $member) {
+            foreach ($teams as $member) {
                 if ($idx = array_search($member->team_id, $team_ids)) {
                     // XXX: Do we really need to track the time of update?
                     $member->updated = SqlFunction::NOW();
@@ -541,7 +545,7 @@ implements AuthenticatedUser {
                 }
             }
             foreach ($team_ids as $id) {
-                TeamMember::create(array(
+                StaffTeamMember::create(array(
                     'updated'=>SqlFunction::NOW(),
                     'staff_id'=>$this->getId(), 'team_id'=>$id
                 ))->save();
@@ -814,6 +818,9 @@ class StaffTeamMember extends VerySimpleModel {
             'staff' => array(
                 'constraint' => array('staff_id' => 'Staff.staff_id'),
             ),
+            'team' => array(
+                'constraint' => array('team_id' => 'Team.team_id'),
+            ),
         ),
     );
 }
diff --git a/include/class.team.php b/include/class.team.php
index c0a9b8a1d4c20fba116a535a60e658d427493700..8ba31af52bc5ee2800dae7936875149df6003e5f 100644
--- a/include/class.team.php
+++ b/include/class.team.php
@@ -14,43 +14,23 @@
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
 
-class Team {
-
-    var $id;
-    var $ht;
+class Team extends VerySimpleModel {
+
+    static $meta = array(
+        'table' => TEAM_TABLE,
+        'pk' => array('team_id'),
+        'joins' => array(
+            'staffmembers' => array(
+                'reverse' => 'StaffTeamMember.team'
+            ),
+            'lead' => array(
+                'constraint' => array('lead_id' => 'Staff.staff_id'),
+            ),
+        ),
+    );
 
     var $members;
 
-    function Team($id) {
-
-        return $this->load($id);
-    }
-
-    function load($id=0) {
-
-        if(!$id && !($id=$this->getId()))
-            return false;
-
-        $sql='SELECT team.*,count(member.staff_id) as members '
-            .' FROM '.TEAM_TABLE.' team '
-            .' LEFT JOIN '.TEAM_MEMBER_TABLE.' member USING(team_id) '
-            .' WHERE team.team_id='.db_input($id)
-            .' GROUP BY team.team_id ';
-
-        if(!($res=db_query($sql)) || !db_num_rows($res))
-            return false;
-
-        $this->ht=db_fetch_array($res);
-        $this->id=$this->ht['team_id'];
-        $this->members=array();
-
-        return $this->id;
-    }
-
-    function reload() {
-        return $this->load($this->getId());
-    }
-
     function asVar() {
         return $this->__toString();
     }
@@ -60,49 +40,37 @@ class Team {
     }
 
     function getId() {
-        return $this->id;
+        return $this->team_id;
     }
 
     function getName() {
-        return $this->ht['name'];
+        return $this->name;
     }
 
     function getNumMembers() {
-        return $this->ht['members'];
+        return $this->members->count();
     }
 
     function getMembers() {
-
-        if(!$this->members && $this->getNumMembers()) {
-            $sql='SELECT m.staff_id FROM '.TEAM_MEMBER_TABLE.' m '
-                .'LEFT JOIN '.STAFF_TABLE.' s USING(staff_id) '
-                .'WHERE m.team_id='.db_input($this->getId()).' AND s.staff_id IS NOT NULL '
-                .'ORDER BY s.lastname, s.firstname';
-            if(($res=db_query($sql)) && db_num_rows($res)) {
-                while(list($id)=db_fetch_row($res))
-                    if(($staff= Staff::lookup($id)))
-                        $this->members[]= $staff;
-            }
+        if (!isset($this->members)) {
+            $this->members = Staff::objects()
+                ->filter(array('teams__team_id'=>$this->getId()))
+                ->order_by('lastname', 'firstname');
         }
-
         return $this->members;
     }
 
     function hasMember($staff) {
-        return db_count(
-             'SELECT COUNT(*) FROM '.TEAM_MEMBER_TABLE
-            .' WHERE team_id='.db_input($this->getId())
-            .'   AND staff_id='.db_input($staff->getId())) !== 0;
+        return $this->getMembers()
+            ->filter(array('staff_id'=>$staff->getId()))
+            ->count() !== 0;
     }
 
     function getLeadId() {
-        return $this->ht['lead_id'];
+        return $this->lead_id;
     }
 
     function getTeamLead() {
-        if(!$this->lead && $this->getLeadId())
-            $this->lead=Staff::lookup($this->getLeadId());
-
         return $this->lead;
     }
 
@@ -111,7 +79,9 @@ class Team {
     }
 
     function getHashtable() {
-        return $this->ht;
+        $base = $this->ht;
+        unset($base['staffmembers']);
+        return $base;
     }
 
     function getInfo() {
@@ -119,7 +89,7 @@ class Team {
     }
 
     function isEnabled() {
-        return ($this->ht['isenabled']);
+        return $this->isenabled;
     }
 
     function isActive() {
@@ -127,11 +97,11 @@ class Team {
     }
 
     function alertsEnabled() {
-        return !$this->ht['noalerts'];
+        return !$this->noalerts;
     }
 
     function getTranslateTag($subtag) {
-        return _H(sprintf('team.%s.%s', $subtag, $this->id));
+        return _H(sprintf('team.%s.%s', $subtag, $this->getId()));
     }
     function getLocal($subtag) {
         $tag = $this->getTranslateTag($subtag);
@@ -144,49 +114,30 @@ class Team {
         return $T != $tag ? $T : $default;
     }
 
-    function update($vars, &$errors) {
-
-        //reset team lead if they're being deleted
-        if($this->getLeadId()==$vars['lead_id']
-                && $vars['remove'] && in_array($this->getLeadId(), $vars['remove']))
-            $vars['lead_id']=0;
-
-        //Save the changes...
-        if(!Team::save($this->getId(), $vars, $errors))
-            return false;
+    function updateMembership($vars) {
 
-        //Delete staff marked for removal...
-        if($vars['remove']) {
-            $sql='DELETE FROM '.TEAM_MEMBER_TABLE
-                .' WHERE team_id='.db_input($this->getId())
-                .' AND staff_id IN ('
-                    .implode(',', db_input($vars['remove']))
-                .')';
-            db_query($sql);
+        // Delete staff marked for removal...
+        if ($vars['remove']) {
+            $this->staffmembers
+                ->filter(array(
+                    'staff_id__in' => $vars['remove']))
+                ->delete();
         }
-
-        //Reload.
-        $this->reload();
-
         return true;
     }
 
     function delete() {
         global $thisstaff;
 
-        if(!$thisstaff || !($id=$this->getId()))
+        if (!$thisstaff || !($id=$this->getId()))
             return false;
 
         # Remove the team
-        $res = db_query(
-            'DELETE FROM '.TEAM_TABLE.' WHERE team_id='.db_input($id)
-          .' LIMIT 1');
-        if (db_affected_rows($res) != 1)
+        if (!parent::delete())
             return false;
 
         # Remove members of this team
-        db_query('DELETE FROM '.TEAM_MEMBER_TABLE
-               .' WHERE team_id='.db_input($id));
+        $this->staffmembers->delete();
 
         # Reset ticket ownership for tickets owned by this team
         db_query('UPDATE '.TICKET_TABLE.' SET team_id=0 WHERE team_id='
@@ -195,91 +146,105 @@ class Team {
         return true;
     }
 
-    /* ----------- Static function ------------------*/
-    function lookup($id) {
-        return ($id && is_numeric($id) && ($team= new Team($id)) && $team->getId()==$id)?$team:null;
+    function save($refetch=false) {
+        if ($this->dirty)
+            $this->updated = SqlFunction::NOW();
+        return parent::save($refetch || $this->dirty);
     }
 
+    /* ----------- Static function ------------------*/
 
-    function getIdbyName($name) {
-
-        $sql='SELECT team_id FROM '.TEAM_TABLE.' WHERE name='.db_input($name);
-        if(($res=db_query($sql)) && db_num_rows($res))
-            list($id)=db_fetch_row($res);
+    static function getIdbyName($name) {
+        $row = static::objects()
+            ->filter(array('name'=>$name))
+            ->values_flat('team_id')
+            ->first();
 
-        return $id;
+        return $row ? $row[0] : null;
     }
 
-    function getTeams( $availableOnly=false ) {
+    static function getTeams( $availableOnly=false ) {
+        static $names;
+
+        if (isset($names))
+            return $names;
+
+        $names = array();
+        $teams = static::objects()
+            ->values_flat('team_id', 'name', 'isenabled');
 
-        $teams=array();
-        $sql='SELECT team_id, name, isenabled FROM '.TEAM_TABLE;
-        if($availableOnly) {
+        if ($availableOnly) {
             //Make sure the members are active...TODO: include group check!!
-            $sql='SELECT t.team_id, t.name, count(m.staff_id) as members '
-                .' FROM '.TEAM_TABLE.' t '
-                .' LEFT JOIN '.TEAM_MEMBER_TABLE.' m ON(m.team_id=t.team_id) '
-                .' INNER JOIN '.STAFF_TABLE.' s ON(s.staff_id=m.staff_id AND s.isactive=1 AND onvacation=0) '
-                .' INNER JOIN '.GROUP_TABLE.' g ON(g.group_id=s.group_id AND g.group_enabled=1) '
-                .' WHERE t.isenabled=1 '
-                .' GROUP BY t.team_id '
-                .' HAVING members>0'
-                .' ORDER by t.name ';
+            $teams->annotate(array('members'=>Aggregate::COUNT('staffmembers')))
+                ->filter(array(
+                    'isenabled'=>1,
+                    'staffmembers__staff__isactive'=>1,
+                    'staffmembers__staff__onvacation'=>0,
+                    'staffmembers__staff__group__group_enabled'=>1,
+                ))
+                ->filter(array('members__gt'=>0))
+                ->order_by('name');
         }
-        if(($res = db_query($sql)) && db_num_rows($res)) {
-            while(list($id, $name, $isenabled) = db_fetch_row($res)) {
-                $teams[$id] = self::getLocalById($id, 'name', $name);
-                if (!$isenabled)
-                    $teams[$id] .= ' ' . __('(disabled)');
-            }
+
+        foreach ($teams as $row) {
+            list($id, $name, $isenabled) = $row;
+            $names[$id] = self::getLocalById($id, 'name', $name);
+            if (!$isenabled)
+                $names[$id] .= ' ' . __('(disabled)');
         }
 
-        return $teams;
+        return $names;
     }
 
-    function getActiveTeams() {
+    static function getActiveTeams() {
         return self::getTeams(true);
     }
 
-    function create($vars, &$errors) {
-        return self::save(0, $vars, $errors);
+    static function create($vars=array()) {
+        $team = parent::create($vars);
+        $team->created = SqlFunction::NOW();
+        return $team;
     }
 
-    function save($id, $vars, &$errors) {
-        if($id && $vars['id']!=$id)
+    function update($vars, &$errors) {
+        if (isset($this->team_id) && $this->getId() != $vars['id'])
             $errors['err']=__('Missing or invalid team');
 
         if(!$vars['name']) {
             $errors['name']=__('Team name is required');
         } elseif(strlen($vars['name'])<3) {
             $errors['name']=__('Team name must be at least 3 chars.');
-        } elseif(($tid=Team::getIdByName($vars['name'])) && $tid!=$id) {
+        } elseif(($tid=static::getIdByName($vars['name']))
+                && (!isset($this->team_id) || $tid!=$this->getId())) {
             $errors['name']=__('Team name already exists');
         }
 
-        if($errors) return false;
+        if ($errors)
+            return false;
 
-        $sql='SET updated=NOW(),isenabled='.db_input($vars['isenabled']).
-             ',name='.db_input($vars['name']).
-             ',noalerts='.db_input(isset($vars['noalerts'])?$vars['noalerts']:0).
-             ',notes='.db_input(Format::sanitize($vars['notes']));
+        $this->isenabled = $vars['isenabled'];
+        $this->name = $vars['name'];
+        $this->noalerts = isset($vars['noalerts'])?$vars['noalerts']:0;
+        $this->notes = Format::sanitize($vars['notes']);
+        if (isset($vars['lead_id']))
+            $this->lead_id = $vars['lead_id'];
 
-        if($id) {
-            $sql='UPDATE '.TEAM_TABLE.' '.$sql.',lead_id='.db_input($vars['lead_id']).' WHERE team_id='.db_input($id);
-            if(db_query($sql) && db_affected_rows())
-                return true;
+        // reset team lead if they're being removed from the team
+        if ($this->getLeadId() == $vars['lead_id']
+                && $vars['remove'] && in_array($this->getLeadId(), $vars['remove']))
+            $this->lead_id = 0;
 
-            $errors['err']=sprintf(__('Unable to update %s.'), __('this team'))
-               .' '.__('Internal error occurred');
-        } else {
-            $sql='INSERT INTO '.TEAM_TABLE.' '.$sql.',created=NOW()';
-            if(db_query($sql) && ($id=db_insert_id()))
-                return $id;
+        if ($this->save())
+            return $this->updateMembership($vars);
 
+        if ($this->__new__) {
             $errors['err']=sprintf(__('Unable to create %s.'), __('this team'))
                .' '.__('Internal error occurred');
         }
-
+        else {
+            $errors['err']=sprintf(__('Unable to update %s.'), __('this team'))
+               .' '.__('Internal error occurred');
+        }
         return false;
     }
 }
diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php
index d8748d0feb161ad9f1c8d449d7b361da58676833..0d5fdd2d37e231408761e92d70adcd41b9dd531b 100644
--- a/include/staff/team.inc.php
+++ b/include/staff/team.inc.php
@@ -26,7 +26,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
  <input type="hidden" name="do" value="<?php echo $action; ?>">
  <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
  <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
- <h2><?php echo __('Team');?></h2>
+ <h2><?php echo __('Team');?>
     <i class="help-tip icon-question-sign" href="#teams"></i>
     </h2>
  <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
diff --git a/scp/teams.php b/scp/teams.php
index 215e9fb8788154f34ed6947a05bbf051292e690a..42fb2cbdd1bd8d38b6b215f4220db5c395558ff8 100644
--- a/scp/teams.php
+++ b/scp/teams.php
@@ -33,7 +33,8 @@ if($_POST){
             }
             break;
         case 'create':
-            if(($id=Team::create($_POST,$errors))){
+            $team = Team::create();
+            if ($team->update($_POST,$errors)) {
                 $msg=sprintf(__('Successfully added %s'),Format::htmlchars($_POST['team']));
                 $_REQUEST['a']=null;
             }elseif(!$errors['err']){