Skip to main content
OpenEyes is designed to support multiple institutions and sites within a single deployment, enabling shared infrastructure while maintaining data segregation and institution-specific configurations.

Institution Architecture

The multi-institution system is built on a hierarchical structure:
Installation (System-wide)
  └── Institution 1
      ├── Site A
      ├── Site B
      └── Site C
  └── Institution 2
      ├── Site D
      └── Site E

Institution Model

File: protected/models/Institution.php
id
integer
Unique institution identifier
name
string
required
Full institution name (e.g., “Moorfields Eye Hospital”)
short_name
string
required
Abbreviated name for display (e.g., “MEH”)
remote_id
integer
required
External system reference ID
pas_key
string
Patient Administration System key (max 10 characters)
contact_id
integer
Link to Contact record with address and contact details
logo_id
integer
Institution logo for correspondence and reports
any_number_search_allowed
boolean
Whether to allow searching across different number types

Institution Relations

// protected/models/Institution.php:96
public function relations()
{
    return array(
        'contact' => array(self::BELONGS_TO, 'Contact', 'contact_id'),
        'sites' => array(
            self::HAS_MANY, 
            'Site', 
            'institution_id',
            'condition' => 'sites.active = 1',
        ),
        'authenticationMethods' => array(
            self::HAS_MANY,
            'InstitutionAuthentication',
            'institution_id',
            'condition' => 'authenticationMethods.active = 1',
        ),
        'logo' => array(self::BELONGS_TO, 'SiteLogo', 'logo_id'),
    );
}

Creating an Institution

1

Access Institution Admin

Navigate to Admin → Institutions (typically /admin/institutions)
2

Add Institution Details

  • Enter full name and short name
  • Set remote ID for external system integration
  • Add PAS key if using a Patient Administration System
3

Configure Contact Information

Link or create a Contact record with:
  • Address
  • Phone/fax numbers
  • Email address
4

Set Up Authentication

Create at least one InstitutionAuthentication method
5

Add Sites

Create one or more sites within the institution
Once an institution has users or patient data associated with it, it should not be deleted. Instead, mark it as inactive.

Multi-Tenancy & Authentication

Institutions can be “tenanted” - meaning they have their own authentication system and user base.

Checking if Institution is Tenanted

// protected/models/Institution.php:146
public function isTenanted(): bool
{
    return count($this->authenticationMethods) > 0;
}

Getting Tenanted Institutions

// Get all tenanted institutions
$institutions = Institution::model()->getTenanted();

// Get tenanted institutions user is member of
$userInstitutions = Institution::model()->getTenanted(
    '', 
    [], 
    $user_must_be_member = true
);

// Get as array for dropdown
$institutionList = Institution::model()->getTenantedList(
    $current_institution_only = false,
    $user_must_be_member = true
);

InstitutionAuthentication

Each institution can have multiple authentication methods configured:
class InstitutionAuthentication extends BaseActiveRecord
{
    public $institution_id;              // Link to institution
    public $user_authentication_method;  // 'BASIC', 'LDAP', 'SAML', 'OIDC'
    public $description;                 // Display name
    public $active;                      // Whether method is active
    public $site_id;                     // Optional site restriction
}

Authentication Flow

1

User Attempts Login

Enters username on login page
2

System Identifies Institution

Looks up username in user_authentication tableJoins to institution_authentication to determine institution and auth method
3

Apply Auth Method

  • BASIC: Verify against local password hash
  • LDAP: Forward to LDAP server
  • SAML: Redirect to SAML IdP
  • OIDC: Redirect to OIDC provider
4

Set Session Context

On success, set:
  • Selected institution
  • Selected site (default for institution)
  • Selected firm (user’s last or default)

Site Management

Sites represent physical locations within an institution where clinical services are delivered.

Site Model

class Site extends BaseActiveRecord
{
    public $id;
    public $name;                    // Site name
    public $short_name;             // Abbreviated name
    public $institution_id;         // Parent institution
    public $contact_id;             // Site contact details
    public $telephone;              // Main phone
    public $fax;                    // Fax number
    public $active;                 // Whether site is active
}

Site-Specific Settings

Settings can be configured at the site level using the setting_site table:
// Example: Site-specific correspondence letterhead
SettingMetadata::model()->getSetting(
    'correspondence_letterhead',
    $element_type = null,
    $return_object = false,
    $allowed_classes = ['SettingSite']
);

Current Institution and Site

Getting Current Context

// Get current institution
$institution = Institution::model()->getCurrent();
// or from session
$institutionId = Yii::app()->session['selected_institution_id'];

// Get current site  
$site = Site::model()->getCurrent();
// or from session
$siteId = Yii::app()->session['selected_site_id'];

// Get current firm/context
$firm = Firm::model()->findByPk(
    Yii::app()->session['selected_firm_id']
);
The current institution context is stored in the session and used throughout the application to filter data and apply institution-specific settings.

Changing Institution Context

Users can switch between institutions they have access to:
// In controller
public function actionChangeSiteAndFirm()
{
    $institutionId = $_POST['institution_id'];
    $siteId = $_POST['site_id'];
    $firmId = $_POST['firm_id'];
    
    // Validate user has access
    // Update session
    Yii::app()->session['selected_institution_id'] = $institutionId;
    Yii::app()->session['selected_site_id'] = $siteId;
    Yii::app()->session['selected_firm_id'] = $firmId;
    
    // Trigger session change event
    SessionSiteChangedSystemEvent::dispatch();
}

Institution-Specific Data

Many models support institution-specific instances:

ReferenceData Model

Used by models like CommonOphthalmicDisorder, this provides multi-level data support:
// Level constants (protected/models/ReferenceData.php)
const LEVEL_INSTALLATION = 1;
const LEVEL_INSTITUTION = 2;
const LEVEL_SITE = 4;
const LEVEL_SUBSPECIALTY = 8;
const LEVEL_SPECIALTY = 16;
const LEVEL_FIRM = 32;

// Finding data for current institution
$disorders = CommonOphthalmicDisorder::model()->findAllAtLevels(
    ReferenceData::LEVEL_INSTITUTION | ReferenceData::LEVEL_SUBSPECIALTY,
    $criteria,
    $current_institution,
    null,  // site
    null,  // specialty  
    $subspecialty
);

Managing Institution-Specific Data

// protected/controllers/AdminController.php:50
public function actionEditCommonOphthalmicDisorderGroups()
{
    // Get current institution or use installation level
    $current_institution = $this->request->getParam('institution_id')
        ? Institution::model()->find('id = :id', [
            ':id' => $this->request->getParam('institution_id')
          ])
        : (!$this->checkAccess('admin') 
            ? Institution::model()->getCurrent() 
            : null);
    
    // Determine level mask
    $level_mask = $current_institution 
        ? ReferenceData::LEVEL_INSTITUTION 
        : ReferenceData::LEVEL_INSTALLATION;
    
    // Query data at appropriate level
    $groups = CommonOphthalmicDisorderGroup::model()->findAllAtLevels(
        $level_mask,
        $criteria,
        $current_institution
    );
}

User-Institution Relationships

Finding Institution Users

// Get all users for an institution
$institution_user_ids = Yii::app()->db->createCommand()
    ->selectDistinct('ua.user_id')
    ->from('institution_authentication ia')
    ->join('user_authentication ua', 
           'ua.institution_authentication_id = ia.id')
    ->where('ia.institution_id = :institution_id')
    ->bindValue(':institution_id', $institutionId)
    ->queryColumn();

// Get users from current institution only
$users = User::model()->getUsersFromCurrentInstitution();

User Access Control

Non-admin users are typically restricted to their institution:
// protected/controllers/AdminController.php:881
if (!$this->checkAccess('admin')) {
    // Get institution-specific users only
    $institution = Yii::app()->session['selected_institution_id'];
    $institution_user_ids = /* query above */;
    
    // Exclude installation admins
    $admin_user_ids = /* admin query */;
    $user_ids = array_diff($institution_user_ids, $admin_user_ids);
    
    $criteria->addInCondition('t.id', $user_ids);
}

Institution Settings

Institution-level settings override installation defaults:
-- setting_institution table
CREATE TABLE setting_institution (
    id INT PRIMARY KEY,
    institution_id INT NOT NULL,
    key VARCHAR(64) NOT NULL,
    value TEXT,
    element_type_id INT,
    FOREIGN KEY (institution_id) 
        REFERENCES institution(id),
    UNIQUE KEY (institution_id, key, element_type_id)
);
Accessing institution settings:
// Get setting for current institution
$value = SettingMetadata::model()->getSetting(
    'setting_key',
    $element_type = null,
    $return_object = false,
    $allowed_classes = ['SettingInstitution', 'SettingInstallation']
);

Patient Numbers

Patients can have different identification numbers at different institutions:
class PatientIdentifier extends BaseActiveRecord
{
    public $patient_id;
    public $num_type_id;      // NHS Number, Hospital Number, etc.
    public $value;            // The actual number
    public $institution_id;   // Institution where number applies
}

// Get patient hospital number for current institution
$hos_num = $patient->getHos(
    Yii::app()->session['selected_institution_id']
);

// Get NHS number
$nhs_num = $patient->getNhs(
    Yii::app()->session['selected_institution_id']
);

API Reference

Institution Methods

  • getCurrent() - Get current institution from session
  • isTenanted() - Check if institution has authentication
  • getTenanted($condition, $params, $user_must_be_member) - Get tenanted institutions
  • getTenantedList($current_only, $user_member, $json) - Get as list
  • getList($current_institution_only) - Get institutions as array

AdminController Institution Methods

File: protected/controllers/AdminController.php
  • actionEditCommonOphthalmicDisorderGroups() - Line 46
  • actionEditCommonOphthalmicDisorder() - Line 366
  • actionAddMapping() - Line 243
  • actionRemoveMapping() - Line 274

Best Practices

Plan Your Structure

Design institution/site hierarchy before deployment

Use Tenancy

Enable multi-tenancy for true institution isolation

Test Data Segregation

Verify users can only access their institution’s data

Configure Settings Hierarchically

Use institution settings for institution-specific customization

Build docs developers (and LLMs) love