summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xincludes/classes/LibertyContent.php18
-rw-r--r--includes/classes/LibertyXref.php35
-rw-r--r--includes/classes/LibertyXrefContent.php25
-rw-r--r--includes/classes/LibertyXrefGroup.php78
-rw-r--r--includes/classes/LibertyXrefInfo.php98
-rw-r--r--includes/classes/LibertyXrefType.php96
6 files changed, 166 insertions, 184 deletions
diff --git a/includes/classes/LibertyContent.php b/includes/classes/LibertyContent.php
index 839c652..4f94a0d 100755
--- a/includes/classes/LibertyContent.php
+++ b/includes/classes/LibertyContent.php
@@ -89,7 +89,7 @@ define( 'LIBERTY_SPLIT_REGEX', "!\.[3]split\.[3][\t ]*\n?!" );
* ## XRef System
*
* LibertyContent is the gateway into the xref system for content subclasses:
- * - `loadXrefInfo()` — populate `$this->mXrefInfo` (a LibertyXrefInfo) with all
+ * - `loadXrefInfo()` — populate `$this->mXrefInfo` (a LibertyXrefContent) with all
* display groups and their rows for this content item. Call this from page files
* before assigning `gXrefInfo` to Smarty. Package classes override this to
* enrich rows (e.g. resolving contact titles from xref content_ids).
@@ -149,8 +149,8 @@ class LibertyContent extends LibertyBase implements BitCacheable {
*/
public $mType;
- /** Populated by loadXrefInfo() — LibertyXrefInfo instance for this content item */
- public ?LibertyXrefInfo $mXrefInfo = null;
+ /** Populated by loadXrefInfo() — LibertyXrefContent instance for this content item */
+ public ?LibertyXrefContent $mXrefInfo = null;
/**
*Permissions hash specific to the user accessing this LibertyContent object
@@ -3920,7 +3920,7 @@ class LibertyContent extends LibertyBase implements BitCacheable {
// =========================================================================
// Xref methods — delegates to LibertyXrefType (query logic) and
- // LibertyXref / LibertyXrefInfo (data logic).
+ // LibertyXref / LibertyXrefContent (data logic).
// =========================================================================
/**
@@ -3935,7 +3935,7 @@ class LibertyContent extends LibertyBase implements BitCacheable {
* All public xref query methods on LibertyContent delegate here — do not
* construct LibertyXrefType directly in page or subclass code.
*/
- private function xrefType(): LibertyXrefType {
+ protected function xrefType(): LibertyXrefType {
return $this->mXrefType ??= new LibertyXrefType( $this->mContentTypeGuid, $this->mPackageGuid ?: null );
}
@@ -3975,14 +3975,14 @@ class LibertyContent extends LibertyBase implements BitCacheable {
}
/**
- * Populate mXrefInfo with a LibertyXrefInfo instance for this content item.
+ * Populate mXrefInfo with a LibertyXrefContent for this content item.
* Creates one LibertyXrefGroup per display group (sort_order > 0), each holding
- * its raw xref data rows.
+ * LibertyXref instances for its active rows, plus a synthetic 'history' group
+ * when expired rows exist.
*/
public function loadXrefInfo(): void {
if( $this->isValid() && !empty( $this->mContentTypeGuid ) ) {
- $this->mXrefInfo = new LibertyXrefInfo( $this->mContentTypeGuid );
- $this->mXrefInfo->load( $this->mContentId );
+ $this->mXrefInfo = $this->xrefType()->loadContent( $this->mContentId );
}
}
diff --git a/includes/classes/LibertyXref.php b/includes/classes/LibertyXref.php
index 328b07e..6c5b821 100644
--- a/includes/classes/LibertyXref.php
+++ b/includes/classes/LibertyXref.php
@@ -27,13 +27,13 @@ use Bitweaver\BitDate;
* last_update_date — last write timestamp
*
* This class handles load/verify/store for a single row. For bulk loading of all
- * xref rows belonging to a content item, use LibertyXrefGroup / LibertyXrefInfo.
+ * xref rows belonging to a content item, use LibertyXrefGroup / LibertyXrefContent.
*
* The stepXref() method implements an audit-trail pattern: instead of updating a row
* in place it closes the current row (sets end_date) and opens a new one, preserving
- * history. Expired rows are swept into the synthetic 'history' group by LibertyXrefInfo.
+ * history. Expired rows are swept into the synthetic 'history' group by LibertyXrefType::loadContent().
*/
-class LibertyXref extends BitBase {
+class LibertyXref extends BitBase implements \ArrayAccess {
/** x_group value of the loaded xref's item definition */
public $mType;
/** item key of the loaded row (matches liberty_xref_item.item) */
@@ -46,6 +46,8 @@ class LibertyXref extends BitBase {
public $mDate;
/** when set, scopes liberty_xref_item lookups to this content type */
public $mContentTypeGuid = '';
+ /** flat row data — populated by load() and fromRow(); underpins ArrayAccess */
+ protected array $mRow = [];
public function __construct( $iXrefId = NULL ) {
$this->mXrefId = NULL;
@@ -59,6 +61,30 @@ class LibertyXref extends BitBase {
$this->mDate->get_display_offset();
}
+ /**
+ * Construct a LibertyXref instance from a bulk-loaded result row.
+ *
+ * Used by LibertyXrefType::loadContent() to populate LibertyXrefGroup::mXrefs
+ * without going through load(). The row is stored flat in $mRow; ArrayAccess
+ * exposes it so templates continue to use {$xrefInfo.xkey} syntax unchanged.
+ *
+ * @param array $row result row from the LibertyXrefType::loadContent() query
+ */
+ public static function fromRow( array $row ): self {
+ $xref = new self();
+ $xref->mRow = $row;
+ $xref->mXrefId = $row['xref_id'] ?? null;
+ $xref->mItem = $row['item'] ?? null;
+ $xref->mContentId = $row['content_id'] ?? null;
+ return $xref;
+ }
+
+ // ArrayAccess — lets templates use {$xrefInfo.key} dot notation on bulk-loaded rows.
+ public function offsetExists( mixed $offset ): bool { return isset( $this->mRow[$offset] ); }
+ public function offsetGet( mixed $offset ): mixed { return $this->mRow[$offset] ?? null; }
+ public function offsetSet( mixed $offset, mixed $value ): void { $this->mRow[$offset] = $value; }
+ public function offsetUnset( mixed $offset ): void { unset( $this->mRow[$offset] ); }
+
/** @return bool true if a row has been loaded (mXrefId is a valid integer) */
public function isValid() {
return $this->verifyId( $this->mXrefId );
@@ -96,11 +122,12 @@ class LibertyXref extends BitBase {
$this->mXrefId = $pXrefId;
$this->mContentId = $result['content_id'];
$this->mType = $result["x_group"];
- $this->mItem = $result['item'];
+ $this->mItem = $result['item'];
$this->mInfo['title'] = $result['source_title'];
$this->mInfo['format_guid'] = 'text';
unset( $result['source_title'] );
$this->mInfo['data'] = $result;
+ $this->mRow = $result;
}
}
}
diff --git a/includes/classes/LibertyXrefContent.php b/includes/classes/LibertyXrefContent.php
new file mode 100644
index 0000000..7e1acdd
--- /dev/null
+++ b/includes/classes/LibertyXrefContent.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * @package liberty
+ * @subpackage classes
+ */
+
+namespace Bitweaver\Liberty;
+
+/**
+ * Data container for all xref groups loaded for a specific content item.
+ *
+ * Produced by LibertyXrefType::loadContent(). Holds one LibertyXrefGroup per
+ * display group (sort_order > 0) plus a synthetic 'history' group when expired
+ * rows exist. Groups are keyed by x_group name.
+ *
+ * Packages that need to enrich rows (e.g. resolving contact titles from xref
+ * content_ids) should override loadXrefInfo() in their own class, call the
+ * parent, then walk $this->mXrefInfo->mGroups and mutate as needed.
+ *
+ * Template access: {foreach $gXrefInfo->mGroups as $xrefGroup}
+ */
+class LibertyXrefContent {
+ /** @var LibertyXrefGroup[] Loaded groups, keyed by x_group name */
+ public array $mGroups = [];
+}
diff --git a/includes/classes/LibertyXrefGroup.php b/includes/classes/LibertyXrefGroup.php
index 15beef7..58e4ec6 100644
--- a/includes/classes/LibertyXrefGroup.php
+++ b/includes/classes/LibertyXrefGroup.php
@@ -15,11 +15,11 @@ namespace Bitweaver\Liberty;
*
* loadXrefs() separates rows into two buckets:
* - active rows → stored in mXrefs, rendered by the group's template
- * - expired rows → returned to LibertyXrefInfo, which collects them into
+ * - expired rows → returned to the caller, which collects them into
* a synthetic 'history' group (sort_order 999)
*
- * Instances are created by LibertyXrefInfo::load(); do not instantiate directly
- * except in tests or package-level loadXrefInfo() overrides.
+ * Instances are created by LibertyXrefType::loadContent(); do not instantiate
+ * directly except in tests or package-level loadXrefInfo() overrides.
*
* Template access: group templates receive the whole object as $xrefGroup and
* iterate $xrefGroup->mXrefs. The first two lines of every group template must be:
@@ -27,7 +27,7 @@ namespace Bitweaver\Liberty;
* {assign var=xrefAllowEdit value=$allow_edit|default:false}
* {assign var=isHistory value=($xrefGroup->mXGroup eq 'history')}
*/
-class LibertyXrefGroup extends LibertyBase {
+class LibertyXrefGroup {
/** x_group key (e.g. 'address', 'reference', 'quantity', 'history') */
public string $mXGroup;
/** content_type_guid this group belongs to (e.g. 'contact', 'stockmovement') */
@@ -42,7 +42,7 @@ class LibertyXrefGroup extends LibertyBase {
public int $mRoleId;
/** optional package-level guid — xref_item rows with this guid are also matched */
public ?string $mPackageGuid;
- /** @var array[] active liberty_xref data rows for the current content item */
+ /** @var LibertyXref[] active xref rows for the current content item */
public array $mXrefs = [];
/**
@@ -51,7 +51,6 @@ class LibertyXrefGroup extends LibertyBase {
* @param string|null $packageGuid optional package-level guid (e.g. 'stock')
*/
public function __construct( array $groupRow, string $contentTypeGuid, ?string $packageGuid = null ) {
- parent::__construct();
$this->mXGroup = $groupRow['x_group'];
$this->mContentTypeGuid = $contentTypeGuid;
$this->mPackageGuid = $packageGuid;
@@ -61,71 +60,4 @@ class LibertyXrefGroup extends LibertyBase {
$this->mRoleId = (int)( $groupRow['role_id'] ?? 0 );
}
- /**
- * Load liberty_xref rows for this group under a given content item.
- *
- * Queries liberty_xref joined to liberty_xref_item (scoped to this group and
- * content type) and address_postcode (for address groups). Role filtering is
- * applied against the current user's roles; anonymous gets role_id -1.
- *
- * Rows whose end_date is in the past are classified as 'history' and returned
- * separately so LibertyXrefInfo can accumulate them into the synthetic history
- * group. Active rows are stored directly in $this->mXrefs.
- *
- * Packages that need to enrich rows (e.g. resolving contact titles from xref
- * content_ids) should do so by overriding loadXrefInfo() in their own class
- * after calling parent::loadXrefInfo(), not by modifying this method.
- *
- * @param int $contentId liberty_content.content_id to fetch rows for
- * @return array[] expired rows (type_source='history') for the caller to collect
- */
- public function loadXrefs( int $contentId ): array {
- global $gBitUser;
-
- if( empty( $this->mContentTypeGuid ) ) {
- $this->mErrors[] = 'LibertyXrefGroup::load() requires mContentTypeGuid — cannot scope liberty_xref_item query without it.';
- return [];
- }
-
- $roles = array_keys( $gBitUser->mRoles ?? [] ) ?: [-1];
- $userId = $gBitUser->mUserId;
- $bindVars = array_merge( [ $this->mDb->NOW(), $contentId ], $roles, [ $userId ] );
-
- $guidFilter = $this->mPackageGuid
- ? "IN ('{$this->mContentTypeGuid}', '{$this->mPackageGuid}')"
- : "= '{$this->mContentTypeGuid}'";
-
- $sql = "SELECT x.`xref_id`, x.`item`, x.`xref`, x.`xkey`, x.`xkey_ext`,
- x.`xorder`, x.`data`, x.`start_date`, x.`end_date`, x.`last_update_date`,
- s.`template`, s.`cross_ref_href`,
- CASE WHEN x.`xorder` = 0 THEN s.`cross_ref_title`
- ELSE s.`cross_ref_title` || '-' || x.`xorder` END AS xref_title,
- CASE WHEN x.`end_date` IS NOT NULL AND x.`end_date` < ? THEN 'history'
- ELSE s.`x_group` END AS type_source,
- pc.`add1` || ',' || pc.`add2` || ',' || pc.`add4` || ',' || pc.`town` AS address
- FROM `" . BIT_DB_PREFIX . "liberty_xref` x
- JOIN `" . BIT_DB_PREFIX . "liberty_xref_item` s
- ON s.`item` = x.`item`
- AND s.`content_type_guid` $guidFilter
- AND s.`x_group` = '{$this->mXGroup}'
- LEFT JOIN `" . BIT_DB_PREFIX . "address_postcode` pc ON pc.`postcode` = x.`xkey`
- LEFT OUTER JOIN `" . BIT_DB_PREFIX . "users_roles_map` purm
- ON purm.`user_id` = $userId AND purm.`role_id` = s.`role_id`
- WHERE x.`content_id` = ?
- AND (s.`role_id` IN(" . implode( ',', array_fill( 0, count( $roles ), '?' ) ) . ") OR purm.`user_id` = ?)
- ORDER BY x.`item`, x.`xorder`";
-
- $historyRows = [];
- $result = $this->mDb->query( $sql, $bindVars );
- if( $result ) {
- while( $row = $result->fetchRow() ) {
- if( $row['type_source'] === 'history' ) {
- $historyRows[] = $row;
- } else {
- $this->mXrefs[] = $row;
- }
- }
- }
- return $historyRows;
- }
}
diff --git a/includes/classes/LibertyXrefInfo.php b/includes/classes/LibertyXrefInfo.php
deleted file mode 100644
index 0285968..0000000
--- a/includes/classes/LibertyXrefInfo.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-/**
- * @package liberty
- * @subpackage classes
- */
-
-namespace Bitweaver\Liberty;
-
-/**
- * Top-level container for all xref groups belonging to one content item.
- *
- * LibertyXrefInfo::load() is the single entry point for fetching the complete
- * xref picture for a content item. It:
- * 1. Queries liberty_xref_group for all groups defined for the content type
- * (sort_order > 0 only — sort_order 0 is reserved for the 'type' group
- * which is loaded separately by loadXrefTypeList()).
- * 2. Applies the current user's role filter on each group's role_id gate.
- * 3. Creates a LibertyXrefGroup for each row and calls loadXrefs() on it.
- * 4. Collects any expired rows returned by loadXrefs() and, if any exist,
- * synthesises a 'history' group (sort_order 999) to hold them.
- *
- * After load(), $mGroups is a keyed array of LibertyXrefGroup objects ordered
- * by sort_order. Templates iterate this directly:
- *
- * {foreach $gXrefInfo->mGroups as $xrefGroup}
- * {include file=$gContent->getXrefListTemplate($xrefGroup->mTemplate) xrefGroup=$xrefGroup}
- * {/foreach}
- *
- * Package-level enrichment (resolving contact titles, component details, etc.)
- * is done in the package class's loadXrefInfo() override after calling
- * parent::loadXrefInfo(), by walking $mXrefInfo->mGroups and mutating rows.
- */
-class LibertyXrefInfo {
- /** content_type_guid this info object was built for */
- public string $mContentTypeGuid;
- /** optional package-level guid whose xref_group/xref_item rows are also loaded (e.g. 'stock') */
- public ?string $mPackageGuid;
- /** @var LibertyXrefGroup[] keyed by x_group, ordered by sort_order */
- public array $mGroups = [];
-
- /**
- * @param string $contentTypeGuid e.g. 'stockassembly', 'contact'
- * @param string|null $packageGuid e.g. 'stock' — additional guid whose xref_group rows are merged in
- */
- public function __construct( string $contentTypeGuid, ?string $packageGuid = null ) {
- $this->mContentTypeGuid = $contentTypeGuid;
- $this->mPackageGuid = $packageGuid;
- }
-
- /**
- * Load all visible xref groups and their rows for a content item.
- *
- * Populates $this->mGroups. Safe to call multiple times — each call
- * rebuilds mGroups from scratch.
- *
- * @param int $contentId liberty_content.content_id
- */
- public function load( int $contentId ): void {
- global $gBitDb, $gBitUser;
-
- $roles = array_keys( $gBitUser->mRoles ?? [] ) ?: [-1];
- $userId = $gBitUser->mUserId;
- $bindVars = array_merge( $roles, [ $userId ] );
-
- $guidFilter = $this->mPackageGuid
- ? "g.`content_type_guid` IN ('{$this->mContentTypeGuid}', '{$this->mPackageGuid}')"
- : "g.`content_type_guid` = '{$this->mContentTypeGuid}'";
-
- $sql = "SELECT g.`x_group`, g.`title`, g.`sort_order`, g.`template`, g.`role_id`
- FROM `" . BIT_DB_PREFIX . "liberty_xref_group` g
- LEFT OUTER JOIN `" . BIT_DB_PREFIX . "users_roles_map` purm
- ON purm.`user_id` = $userId AND purm.`role_id` = g.`role_id`
- WHERE $guidFilter
- AND g.`sort_order` > 0
- AND (g.`role_id` IN(" . implode( ',', array_fill( 0, count( $roles ), '?' ) ) . ") OR purm.`user_id` = ?)
- ORDER BY g.`sort_order`";
-
- $result = $gBitDb->query( $sql, $bindVars );
- if( !$result ) return;
-
- $allHistory = [];
- while( $row = $result->fetchRow() ) {
- $group = new LibertyXrefGroup( $row, $this->mContentTypeGuid, $this->mPackageGuid );
- $allHistory = array_merge( $allHistory, $group->loadXrefs( $contentId ) );
- $this->mGroups[$row['x_group']] = $group;
- }
-
- if( !empty( $allHistory ) ) {
- $historyGroup = new LibertyXrefGroup(
- [ 'x_group' => 'history', 'title' => 'History', 'sort_order' => 999, 'template' => null, 'role_id' => 0 ],
- $this->mContentTypeGuid,
- $this->mPackageGuid
- );
- $historyGroup->mXrefs = $allHistory;
- $this->mGroups['history'] = $historyGroup;
- }
- }
-}
diff --git a/includes/classes/LibertyXrefType.php b/includes/classes/LibertyXrefType.php
index 5ed5fac..1ef71ca 100644
--- a/includes/classes/LibertyXrefType.php
+++ b/includes/classes/LibertyXrefType.php
@@ -199,6 +199,102 @@ class LibertyXrefType {
}
/**
+ * Load all xref groups and their rows for a specific content item.
+ *
+ * Replaces the former LibertyXrefInfo::load() + LibertyXrefGroup::loadXrefs()
+ * two-class pattern. Returns a LibertyXrefContent whose mGroups array is keyed
+ * by x_group name. A synthetic 'history' group (sort_order 999) is appended
+ * when any expired rows are found.
+ *
+ * Each group's mXrefs array holds LibertyXref instances built via
+ * LibertyXref::fromRow(). Templates continue to use {$xrefInfo.xkey} dot
+ * notation — LibertyXref::ArrayAccess maps this transparently.
+ *
+ * Packages that enrich rows after loading (e.g. resolving contact titles)
+ * should override loadXrefInfo() in their class, call the parent, then walk
+ * $this->mXrefInfo->mGroups and mutate as needed.
+ *
+ * @param int $contentId liberty_content.content_id to load rows for
+ * @return LibertyXrefContent
+ */
+ public function loadContent( int $contentId ): LibertyXrefContent {
+ global $gBitSystem, $gBitUser;
+
+ $db = $gBitSystem->mDb;
+ $roles = array_keys( $gBitUser->mRoles ?? [] ) ?: [-1];
+ $userId = (int)( $gBitUser->mUserId ?? 0 );
+ $guidFilter = $this->packageGuid
+ ? "IN ('$this->contentTypeGuid', '$this->packageGuid')"
+ : "= '$this->contentTypeGuid'";
+ $rolePlaceholders = implode( ',', array_fill( 0, count( $roles ), '?' ) );
+
+ $content = new LibertyXrefContent();
+ $allHistory = [];
+
+ $groupResult = $db->query(
+ "SELECT g.`x_group`, g.`title`, g.`sort_order`, g.`template`, g.`role_id`
+ FROM `".BIT_DB_PREFIX."liberty_xref_group` g
+ LEFT OUTER JOIN `".BIT_DB_PREFIX."users_roles_map` purm
+ ON purm.`user_id` = $userId AND purm.`role_id` = g.`role_id`
+ WHERE g.`content_type_guid` $guidFilter AND g.`sort_order` > 0
+ AND (g.`role_id` IN($rolePlaceholders) OR purm.`user_id` = ?)
+ ORDER BY g.`sort_order`",
+ array_merge( $roles, [ $userId ] )
+ );
+ if( !$groupResult ) return $content;
+
+ while( $groupRow = $groupResult->fetchRow() ) {
+ $group = new LibertyXrefGroup( $groupRow, $this->contentTypeGuid, $this->packageGuid );
+ $xGroup = $groupRow['x_group'];
+ $rowResult = $db->query(
+ "SELECT x.`xref_id`, x.`item`, x.`xref`, x.`xkey`, x.`xkey_ext`,
+ x.`xorder`, x.`data`, x.`start_date`, x.`end_date`, x.`last_update_date`,
+ s.`template`, s.`cross_ref_href`,
+ CASE WHEN x.`xorder` = 0 THEN s.`cross_ref_title`
+ ELSE s.`cross_ref_title` || '-' || x.`xorder` END AS xref_title,
+ CASE WHEN x.`end_date` IS NOT NULL AND x.`end_date` < ? THEN 'history'
+ ELSE s.`x_group` END AS type_source,
+ pc.`add1` || ',' || pc.`add2` || ',' || pc.`add4` || ',' || pc.`town` AS address
+ FROM `".BIT_DB_PREFIX."liberty_xref` x
+ JOIN `".BIT_DB_PREFIX."liberty_xref_item` s
+ ON s.`item` = x.`item`
+ AND s.`content_type_guid` $guidFilter
+ AND s.`x_group` = '$xGroup'
+ LEFT JOIN `".BIT_DB_PREFIX."address_postcode` pc ON pc.`postcode` = x.`xkey`
+ LEFT OUTER JOIN `".BIT_DB_PREFIX."users_roles_map` purm
+ ON purm.`user_id` = $userId AND purm.`role_id` = s.`role_id`
+ WHERE x.`content_id` = ?
+ AND (s.`role_id` IN($rolePlaceholders) OR purm.`user_id` = ?)
+ ORDER BY x.`item`, x.`xorder`",
+ array_merge( [ $db->NOW(), $contentId ], $roles, [ $userId ] )
+ );
+ if( $rowResult ) {
+ while( $row = $rowResult->fetchRow() ) {
+ $xref = LibertyXref::fromRow( $row );
+ if( $row['type_source'] === 'history' ) {
+ $allHistory[] = $xref;
+ } else {
+ $group->mXrefs[] = $xref;
+ }
+ }
+ }
+ $content->mGroups[$xGroup] = $group;
+ }
+
+ if( !empty( $allHistory ) ) {
+ $historyGroup = new LibertyXrefGroup(
+ [ 'x_group' => 'history', 'title' => 'History', 'sort_order' => 999, 'template' => null, 'role_id' => 0 ],
+ $this->contentTypeGuid,
+ $this->packageGuid
+ );
+ $historyGroup->mXrefs = $allHistory;
+ $content->mGroups['history'] = $historyGroup;
+ }
+
+ return $content;
+ }
+
+ /**
* Return the distinct template format names defined across all item slots for
* this content type, filtered to the current user's roles.
*