Skip to content

Locations Developer Guide

Ed Mozley edited this page Nov 14, 2026 · 1 revision

πŸ› οΈ Locations - Developer Guide

Since 5.3.0 Β· #311 Β· User guide: Locations Β· Related: Shared asset locations - Developer Guide, Forms lookup fields - Developer Guide

#311 moved the locations list from Assets to System without moving any data. This page covers the four pieces: one editor in two places, the gate that lets both in, the Forms lookup source, and the signposting.


1. πŸ“ Files

File Role
asset_locations Unchanged. id, name, parent_id, display_order, tenant_id, is_shared. Still the one table
assets/js/locations-tree.js The editor, moved out of the Assets settings page. LocationsTree.mount(el, {apiBase}) draws the tree, builds its own modal, and returns {reload, add}
assets/css/locations-tree.css Its styles, including the row's icon buttons (scoped to .loc-tree) and the .loc-signpost note
system/locations/index.php System β†’ Locations. Admin-only through system/includes/page_gate.php
asset-management/settings/index.php The Locations tab: the same editor, plus the signpost
includes/asset_locations.php locationsRequireAccessJson(bool $manage) - the gate - beside the shared-location rules
api/assets/get_asset_locations.php, save_asset_location.php, delete_asset_location.php The same three endpoints for both screens, now gated by locationsRequireAccessJson()
includes/services/forms.php The location lookup source, shared_col, path_parent_col, lookupColumnExists(), lookupPathLabels()
forms/edit/index.php LOOKUP_SOURCES in the designer gains location
api/forms/ai_generate.php The AI form builder's prompt names the new source
system/includes/areas.php, system/help/_registry.php, system/help/locations.php The System card, its icon and its help
tests/locations-system.php 23 checks: the lookup's scope and labels, the gate, both screens in headless Chrome

2. πŸ”‘ Why the Assets tab stayed

System is admin-only (page_gate.php refuses anyone else). The Assets tab is open to anyone holding Cap::ASSETS_LOCATIONS (Manage locations). Moving the list outright would have taken it away from every asset manager who is not an administrator. So both screens mount the same component, and the gate accepts either audience.

The strings stay under asset-management.settings.* - they were already translated into every language, and moving them would have lost that. System β†’ Locations loads the asset-management namespace for them.

3. πŸ”’ The gate

function locationsRequireAccessJson(bool $manage): void
{
    // signed in, or 401
    if (analystIsAdmin(connectToDatabase(), (int)$_SESSION['analyst_id'])) return;
    requireModuleAccessJson('assets');
    if ($manage) requireCapabilityJson(Cap::ASSETS_LOCATIONS);
}
  • An administrator is let in even with the Assets module switched off - the list now serves forms too, so turning Assets off must not strand it.
  • Anyone else gets exactly the rule from before #311: Assets to read, plus Manage locations to change.
  • The company and shared-location rules inside the endpoints are untouched; see Shared asset locations - Developer Guide.

tests/module-access-coverage.php recognises locationsRequireAccessJson as a guard (it scans for guard names), so a new endpoint using it is not reported as unguarded.

4. βš™οΈ The Forms location source

'location' => [
    'table'           => 'asset_locations',
    'id_col'          => 'id',
    'label_col'       => 'name',
    'search_cols'     => ['name'],
    'tenant_col'      => 'tenant_id',
    'shared_col'      => 'is_shared',
    'path_parent_col' => 'parent_id',
    'portal_safe'     => true,
],

Two new optional keys, so the next tree-shaped or shareable source needs no new code:

  • shared_col - lookupTenantClause() adds OR is_shared = 1, so a shared location is in scope for every company. Because the search and the submit-time guard share that clause, they cannot disagree. lookupColumnExists() checks the column first (cached per request), so an install that has not run Database Verification since 2.6.0 still works.
  • path_parent_col - lookupPathLabels() turns Floor 2 into UK > London > Floor 2, for the search results and for the label stored with an answer (lookupRecordLabel()). It reads the whole table once (a locations tree is small) and walks up from each id; a cycle or a missing parent ends the path. The ids it is given have already passed the scope check, and a location's parent is always in the same company or shared, so a path never shows another company's name.

The search still matches on the location's own name only - typing London finds London, not the floors inside it.

5. 🧭 The signposting

  • Assets β†’ Settings β†’ Locations: a .loc-signpost note (asset-management.settings.locations_system_note) and, for an administrator only, a link to system/locations/.
  • Assets help: a note in section 1 (#locations, asset-management.help.locations_lead / locations_body).
  • System β†’ Locations says, under its heading, what uses the list and that Assets edits the same one (system.locations.uses).

6. πŸ§ͺ Verifying

FREEITSM_TEST_DATABASE=freeitsm_prtest FREEITSM_TEST_URL=http://localhost/freeitsm-prtest php tests/locations-system.php

A ZZ second company and a small tree (own, shared, the other company's). Every refusal has a positive control beside it. The screen checks need Chrome and are skipped without it. tests/forms-lookup/run.php still checks that every source the AI prompt names exists.

7. ⬜ Next

Per #311, one at a time: a location on people (perhaps mapping users.office), tickets, contracts and projects; a Location reference type for CMDB properties (#312). rota_locations is a different thing (where a shift is worked) and stays separate.

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Responses to feedback

Blue sky thinking

Bugs resolved

Pull requests

Links

Clone this wiki locally