> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/appertafoundation/openeyes/llms.txt
> Use this file to discover all available pages before exploring further.

# System Settings

> Managing system-wide settings and customization in OpenEyes

OpenEyes provides a comprehensive settings system that allows administrators to customize behavior at multiple levels, from installation-wide defaults to user-specific preferences.

## Settings Architecture

The settings system uses a hierarchical approach with multiple levels of specificity:

```
User Settings (highest priority)
  ↓
Firm Settings
  ↓
Institution + Subspecialty Settings
  ↓
Subspecialty Settings
  ↓  
Specialty Settings
  ↓
Site Settings
  ↓
Institution Settings
  ↓
Installation Settings (lowest priority, system defaults)
```

<Info>
  When retrieving a setting, the system checks each level in order (most specific first) until a value is found.
</Info>

## Settings Database Schema

The system uses 9 related tables:

### Setting Metadata

**Table:** `setting_metadata`

Defines available settings and their properties:

<ParamField path="id" type="integer">
  Unique identifier
</ParamField>

<ParamField path="key" type="string" required>
  Unique setting key (used in code)
</ParamField>

<ParamField path="name" type="string" required>
  Display name for admin interface
</ParamField>

<ParamField path="field_type_id" type="integer" required>
  Type of input field (text, dropdown, checkbox, HTML, etc.)
</ParamField>

<ParamField path="data" type="text">
  Serialized data for field configuration (e.g., dropdown options)
</ParamField>

<ParamField path="default_value" type="text" required>
  Default value when no override exists
</ParamField>

<ParamField path="element_type_id" type="integer">
  Optional: Link to specific element type if setting is element-specific
</ParamField>

<ParamField path="description" type="text">
  Help text for administrators
</ParamField>

<ParamField path="group_id" type="integer" required>
  Setting group for organization in admin UI
</ParamField>

<ParamField path="lowest_setting_level" type="string">
  Minimum level at which this setting can be overridden
</ParamField>

### Setting Instance Tables

Each level has its own table for storing setting values:

* `setting_installation` - System-wide defaults
* `setting_institution` - Institution-specific values
* `setting_site` - Site-specific values
* `setting_specialty` - Specialty-specific values
* `setting_subspecialty` - Subspecialty-specific values
* `setting_institution_subspecialty` - Institution + subspecialty combination
* `setting_firm` - Firm/context-specific values
* `setting_user` - User-specific values

All instance tables share this structure:

```sql theme={null}
CREATE TABLE setting_[level] (
    id INT PRIMARY KEY AUTO_INCREMENT,
    key VARCHAR(64) NOT NULL,
    value TEXT,
    element_type_id INT,
    [level]_id INT NOT NULL,
    last_modified_date DATETIME,
    created_date DATETIME,
    UNIQUE KEY (key, element_type_id, [level]_id)
);
```

## Accessing Settings

### In PHP Code

```php theme={null}
// Get a setting value
$value = SettingMetadata::model()->getSetting('setting_key');

// Get setting with element type context
$value = SettingMetadata::model()->getSetting(
    'examination_view_mode',
    ElementType::model()->findByAttributes(['class_name' => 'Element_OphCiExamination_History'])
);

// Get setting display name (applies data mapping)
$displayValue = SettingMetadata::model()->getSettingName('setting_key');

// Get setting for specific institution (e.g., in admin)
$value = SettingMetadata::model()->getSetting(
    'setting_key',
    $element_type = null,
    $return_object = false,
    $allowed_classes = ['SettingInstitution', 'SettingInstallation'],
    $institution_id = 123,
    $is_setting_page = true
);

// Check if setting has specific value
if (SettingMetadata::checkSetting('feature_enabled', 'on')) {
    // Feature is enabled
}
```

### Setting Context

The system automatically determines context from the current session:

**File:** `protected/models/SettingMetadata.php:219`

```php theme={null}
public function getSetting($key = null, $element_type = null, ...)
{
    // Gather context from session
    $user_id = Yii::app()->session['user']->id ?? null;
    $firm_id = $firm ? $firm->id : null;
    $subspecialty_id = /* from firm */;
    $specialty_id = /* from subspecialty */;
    $site_id = /* from session */;
    $institution_id = $site->institution_id ?? null;
    
    // Check each level in order
    foreach (self::$CONTEXT_CLASSES as $class => $field) {
        if ($setting = $this->getSettingValue($class, $key, ...)) {
            return $this->parseSetting($setting, $metadata);
        }
    }
    
    // Return default if no override found
    return $metadata->default_value;
}
```

## Setting Field Types

### Available Field Types

**Table:** `setting_field_type`

<Tabs>
  <Tab title="Text">
    Simple text input field.

    ```php theme={null}
    array(
        'field_type_id' => 1,  // Text
        'default_value' => 'Default text'
    )
    ```
  </Tab>

  <Tab title="Textarea">
    Multi-line text area.

    ```php theme={null}
    array(
        'field_type_id' => 2,  // Textarea
        'default_value' => 'Multi-line\ndefault text'
    )
    ```
  </Tab>

  <Tab title="Checkbox">
    Boolean on/off toggle.

    ```php theme={null}
    array(
        'field_type_id' => 3,  // Checkbox
        'default_value' => 'on'  // or 'off'
    )
    ```
  </Tab>

  <Tab title="Dropdown">
    Select from predefined options.

    ```php theme={null}
    array(
        'field_type_id' => 4,  // Dropdown
        'data' => serialize(array(
            'option1' => 'Display Label 1',
            'option2' => 'Display Label 2',
            'option3' => 'Display Label 3'
        )),
        'default_value' => 'option1'
    )
    ```
  </Tab>

  <Tab title="Radio">
    Radio button group.

    ```php theme={null}
    array(
        'field_type_id' => 5,  // Radio
        'data' => serialize(array(
            'yes' => 'Yes',
            'no' => 'No'
        )),
        'default_value' => 'no'
    )
    ```
  </Tab>

  <Tab title="HTML">
    Rich text editor with HTML support.

    ```php theme={null}
    array(
        'field_type_id' => 6,  // HTML
        'default_value' => '<p>Default HTML content</p>'
    )
    ```

    Supports substitution placeholders for dynamic content.
  </Tab>
</Tabs>

## HTML Settings with Substitutions

HTML field types can include dynamic substitutions:

### Available Substitutions

**Session Substitutions** (user/firm/site context):

* `[user_name]` - Current user's full name
* `[user_title]` - User's professional title
* `[firm_name]` - Current firm name
* `[site_name]` - Current site name
* `[site_address]` - Site address
* `[site_phone]` - Site phone number
* `[site_fax]` - Site fax number
* `[site_email]` - Site email
* `[current_date]` - Today's date
* `[primary_logo]` - Institution primary logo
* `[secondary_logo]` - Institution secondary logo

**Patient Substitutions:**

* `[patient_full_name]`
* `[patient_first_name]`
* `[patient_last_name]`
* `[patient_date_of_birth]`
* `[patient_gender]`
* `[patient_nhs_num]`
* `[patient_hos_num]`
* `[patient_last_exam_date]`

### Using Substitutions

```php theme={null}
// In setting metadata
array(
    'key' => 'correspondence_footer',
    'field_type_id' => 6,  // HTML
    'data' => serialize(array(
        'substitutions' => array(
            'site_name',
            'site_address',
            'site_phone'
        )
    )),
    'default_value' => '<p><span data-substitution="site_name">[site_name]</span><br>
                        <span data-substitution="site_address">[site_address]</span><br>
                        Tel: <span data-substitution="site_phone">[site_phone]</span></p>'
)

// Performing substitutions at runtime
$html = SettingMetadata::model()->getSetting('correspondence_footer');
// Substitutions are automatically applied when retrieved
```

<Note>
  Substitutions are processed automatically by the `parseSetting()` method in `SettingMetadata.php:384`.
</Note>

## Managing Settings in Admin Interface

### Accessing System Settings

Navigate to: **Admin → System Settings**

Settings are organized into groups:

* **Core** - Essential system settings
* **User Interface** - Display and behavior settings
* **Clinical** - Clinical workflow settings
* **Correspondence** - Letter and document settings
* **Examination** - Examination module settings
* **Module-specific** - Settings for each active module

### Setting Groups

**Table:** `setting_group`

```php theme={null}
class SettingGroup extends BaseActiveRecord
{
    public $id;
    public $name;           // Display name
    public $description;    // Help text
    public $display_order;  // Sort order
}
```

### Editing Settings

<Steps>
  <Step title="Select Setting Group">
    Choose the group containing the setting to edit
  </Step>

  <Step title="Choose Setting Level">
    Select whether to edit at:

    * Installation (system default)
    * Institution
    * Site
    * Subspecialty
    * etc.
  </Step>

  <Step title="Modify Value">
    Change the setting value using the appropriate input field
  </Step>

  <Step title="Save Changes">
    Click "Save" to apply the new value
  </Step>
</Steps>

<Warning>
  Changing installation-level settings affects all institutions and users unless overridden at a more specific level.
</Warning>

## Common System Settings

### Core Settings

<ParamField path="hos_num_label" type="string" default="Hospital Number">
  Label for hospital number field
</ParamField>

<ParamField path="nhs_num_label" type="string" default="NHS Number">
  Label for NHS number field
</ParamField>

<ParamField path="show_version" type="boolean" default="on">
  Display version number in footer
</ParamField>

<ParamField path="helpdesk_email" type="string">
  Email address for helpdesk/support
</ParamField>

<ParamField path="helpdesk_phone" type="string">
  Phone number for helpdesk/support
</ParamField>

### User Interface Settings

<ParamField path="recent_context_firm_limit" type="integer" default="6">
  Number of recent firms to show in context menu
</ParamField>

<ParamField path="default_rows_per_page" type="integer" default="30">
  Default pagination size for lists
</ParamField>

<ParamField path="patient_summary_id_widgets" type="text">
  Configure which ID numbers to display in patient summary
</ParamField>

### Clinical Settings

<ParamField path="default_site_id" type="integer">
  Default site for new events
</ParamField>

<ParamField path="auto_generate_event_name" type="boolean">
  Automatically generate event descriptions
</ParamField>

<ParamField path="event_print_method" type="string" default="pdf">
  Method for printing events: `pdf` or `html`
</ParamField>

### Correspondence Settings

<ParamField path="correspondence_sort_order" type="string">
  Default sort order for correspondence list
</ParamField>

<ParamField path="default_letter_address" type="string">
  Default address type for letters
</ParamField>

<ParamField path="letter_footer" type="html">
  Default letter footer content (supports substitutions)
</ParamField>

## Custom Text Settings

OpenEyes allows customizing display text for event types and element types:

### Event Type Custom Text

```php theme={null}
// protected/controllers/AdminController.php:307
public function actionEditEventTypeCustomText()
{
    foreach ($_POST['EventType'] as $event_type_form) {
        $event_type = EventType::model()->findByPk($event_type_form['id']);
        $event_type->custom_event_name = $form['custom_event_name'];
        $event_type->save();
    }
}
```

### Element Type Custom Text

```php theme={null}
// protected/controllers/AdminController.php:334  
public function actionEditElementTypeCustomText()
{
    foreach ($_POST['ElementType'] as $element_type_form) {
        $element_type = ElementType::model()->findByPk(
            $element_type_form['id']
        );
        $element_type->custom_hint_text = $form['custom_hint_text'];
        $element_type->save();
    }
}
```

## Caching

The settings system implements aggressive caching for performance:

**File:** `protected/models/SettingMetadata.php:250-270`

```php theme={null}
// Check for settings table updates
$debounce_val = Yii::app()->settingCache->get('SettingMetaDebounce');
if ($debounce_val === false) {
    // Query last update time of all setting tables
    $dependency_sql = "SELECT sha1(GROUP_CONCAT(UPDATE_TIME))
                       FROM information_schema.tables
                       WHERE TABLE_NAME IN (
                           'setting_metadata',
                           'setting_installation',
                           'setting_institution',
                           ...
                       )";
    $debounce_val = Yii::app()->db->createCommand($dependency_sql)
                                   ->queryScalar();
    // Cache for 5 seconds to prevent excessive queries
    Yii::app()->settingCache->set('SettingMetaDebounce', $debounce_val, 5);
}
```

### Cache Management

```php theme={null}
// Clear settings cache
SettingMetadata::resetCache();
// or via CLI
./yiic cache flush settingCache

// Clear all caches
./yiic cache flush all
```

<Info>
  Settings are cached with a dependency on the last update time of setting tables. The cache automatically invalidates when settings are modified.
</Info>

## Creating New Settings

### Via Database Migration

```php theme={null}
class m240101_120000_add_new_setting extends OEMigration
{
    public function up()
    {
        // Get or create setting group
        $group_id = $this->getIdOfSettingGroupByName('Core');
        
        // Get field type
        $field_type_id = $this->getIdOfSettingFieldTypeByName('Checkbox');
        
        // Insert setting metadata
        $this->insert('setting_metadata', [
            'element_type_id' => null,
            'display_order' => 10,
            'field_type_id' => $field_type_id,
            'key' => 'my_new_setting',
            'name' => 'My New Setting',
            'data' => null,
            'default_value' => 'off',
            'description' => 'Description of what this setting does',
            'group_id' => $group_id,
            'lowest_setting_level' => 'INSTALLATION',
        ]);
        
        // Optionally set installation-level value
        $this->insert('setting_installation', [
            'key' => 'my_new_setting',
            'value' => 'on',
        ]);
    }
    
    public function down()
    {
        $this->delete('setting_installation', 
                     "`key` = 'my_new_setting'");
        $this->delete('setting_metadata', 
                     "`key` = 'my_new_setting'");
    }
}
```

### Via Admin SQL

```sql theme={null}
-- Insert setting metadata
INSERT INTO setting_metadata (
    element_type_id,
    display_order,
    field_type_id,
    `key`,
    name,
    data,
    default_value,
    description,
    group_id
) VALUES (
    NULL,
    10,
    (SELECT id FROM setting_field_type WHERE name = 'Checkbox'),
    'feature_enabled',
    'Enable Feature',
    NULL,
    'off',
    'Enable the new feature functionality',
    (SELECT id FROM setting_group WHERE name = 'Core')
);

-- Set installation default
INSERT INTO setting_installation (`key`, value) 
VALUES ('feature_enabled', 'on');
```

## API Reference

### SettingMetadata Methods

**File:** `protected/models/SettingMetadata.php`

* `getSetting($key, $element_type, $return_object, $allowed_classes, $institution_id, $is_setting_page)` - Retrieve setting value
* `getSettingName($key, $allowed_classes, $institution_id, $is_setting_page)` - Get display name with data mapping
* `checkSetting($key, $value)` - Check if setting has specific value
* `parseSetting($setting, $metadata)` - Process setting value (apply substitutions, etc.)
* `performSubstitutions($html, $substitutions, $preserve_empty)` - Apply substitutions to HTML
* `getSessionSubstitutions()` - Get available session-based substitutions
* `getPatientSubstitutions($patient, $event)` - Get patient-specific substitutions
* `resetCache()` - Clear settings cache

### AdminController Settings Methods

**File:** `protected/controllers/AdminController.php`

* `actionEditEventTypeCustomText()` - Line 307
* `actionEditElementTypeCustomText()` - Line 334

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Appropriate Levels" icon="layer-group">
    Set settings at the most appropriate level for your use case
  </Card>

  <Card title="Document Custom Settings" icon="book">
    Add clear descriptions to help future administrators
  </Card>

  <Card title="Test Before Production" icon="flask">
    Test setting changes in a development environment first
  </Card>

  <Card title="Version Control Migrations" icon="code-branch">
    Use database migrations for new settings in code
  </Card>
</CardGroup>

## Related Documentation

* [System Configuration](/admin/configuration)
* [User Management](/admin/users-permissions)
* [Institution Management](/admin/institutions)
