diff options
| author | Lester Caine <lester@lsces.co.uk> | 2026-06-11 19:26:25 +0100 |
|---|---|---|
| committer | Lester Caine <lester@lsces.co.uk> | 2026-06-11 19:26:25 +0100 |
| commit | 64aae1e41d9288a6d9709781af29b06fe6adb5ca (patch) | |
| tree | d5d5e74420cee938d7e8ff90b9e6792d5f44cc77 /includes | |
| parent | 27c615a0a26edb985543e520587e3043d91489f6 (diff) | |
| download | contact-64aae1e41d9288a6d9709781af29b06fe6adb5ca.tar.gz contact-64aae1e41d9288a6d9709781af29b06fe6adb5ca.tar.bz2 contact-64aae1e41d9288a6d9709781af29b06fe6adb5ca.zip | |
Introduce ContactPerson and ContactBusiness subclasses
Splits the Contact class into ContactPerson (content_type_guid='contactperson')
and ContactBusiness (content_type_guid='contactbusiness'), each using 'contact'
as the shared package-level xref schema. Replaces the $isPerson/$00 xref hack
with proper class identity via instanceof.
- ContactPerson.php, ContactBusiness.php: new subclasses
- Contact.php: loadXrefTypeList() reads type tags directly from liberty_xref;
getAvailableTypeItems() for edit form (schema-driven with pre-upgrade fallback);
getDisplayUrl() now points to display_contact.php
- Type item codes: P01/P02 (person), B01-B04 (business, B01=Service new)
- list_people.php, list_businesses.php: separate list pages per type
- list_contacts.php: combined display-layer merge of both types
- 5.0.3.php: upgrade script migrating existing data to new content types and codes
- Templates: isPerson flag from instanceof; horizontal type checkboxes; list.tpl
accepts $listTitle; menu adds People/Businesses entries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'includes')
| -rwxr-xr-x | includes/bit_setup_inc.php | 17 | ||||
| -rwxr-xr-x | includes/classes/Contact.php | 85 | ||||
| -rw-r--r-- | includes/classes/ContactBusiness.php | 28 | ||||
| -rw-r--r-- | includes/classes/ContactPerson.php | 29 | ||||
| -rwxr-xr-x | includes/classes/ContactType.php | 41 | ||||
| -rw-r--r-- | includes/lookup_contact.php | 2 | ||||
| -rwxr-xr-x | includes/lookup_contact_inc.php | 10 |
7 files changed, 166 insertions, 46 deletions
diff --git a/includes/bit_setup_inc.php b/includes/bit_setup_inc.php index 8d2b144..c973854 100755 --- a/includes/bit_setup_inc.php +++ b/includes/bit_setup_inc.php @@ -21,6 +21,23 @@ define( 'CONTACT_IMPORT_PATH', STORAGE_PKG_PATH . 'contact/' ); $gBitSystem->registerPackage( $pRegisterHash ); if( $gBitSystem->isPackageActive( 'contact' ) ) { + // Register sub-type content types at startup so getLibertyObject() can resolve them. + // registerContentType() is a no-op in memory once the row exists in the DB. + $gLibertySystem->registerContentType( 'contactperson', [ + 'content_type_guid' => 'contactperson', + 'content_name' => 'Person Contact', + 'handler_class' => 'ContactPerson', + 'handler_package' => 'contact', + 'handler_file' => 'ContactPerson.php', + ] ); + $gLibertySystem->registerContentType( 'contactbusiness', [ + 'content_type_guid' => 'contactbusiness', + 'content_name' => 'Business Contact', + 'handler_class' => 'ContactBusiness', + 'handler_package' => 'contact', + 'handler_file' => 'ContactBusiness.php', + ] ); + $menuHash = [ 'package_name' => CONTACT_PKG_NAME, 'index_url' => CONTACT_PKG_URL . 'index.php', diff --git a/includes/classes/Contact.php b/includes/classes/Contact.php index f5bc6c4..43ae07f 100755 --- a/includes/classes/Contact.php +++ b/includes/classes/Contact.php @@ -15,6 +15,8 @@ use Bitweaver\Liberty\LibertyContent; // Contact base class require_once CONTACT_PKG_PATH.'lib/phpcoord-2.3.php'; define( 'CONTACT_CONTENT_TYPE_GUID', 'contact' ); +defined( 'CONTACTPERSON_CONTENT_TYPE_GUID' ) || define( 'CONTACTPERSON_CONTENT_TYPE_GUID', 'contactperson' ); +defined( 'CONTACTBUSINESS_CONTENT_TYPE_GUID' ) || define( 'CONTACTBUSINESS_CONTENT_TYPE_GUID', 'contactbusiness' ); class Contact extends LibertyContent { @@ -53,7 +55,64 @@ class Contact extends LibertyContent { $this->mAdminContentPerm = 'p_contact_admin'; $this->mTypes = new ContactType(); - $this->mTypes->setup(); + } + + /** + * Load type-tag xref rows (P01/P02/B01–B04) directly from liberty_xref. + * + * The schema-driven getContentTypeMarkers() requires liberty_xref_item rows to + * exist at the contactperson/contactbusiness level — they don't exist until the + * 5.0.3 upgrade runs. Reading liberty_xref directly makes type tags visible in + * both pre- and post-upgrade states. Schema labels are enriched where available. + */ + public function loadXrefTypeList(): void { + if ( !$this->isValid() || !empty( $this->mInfo[$this->mXrefTypeKey] ) ) return; + + $result = $this->mDb->query( + "SELECT x.`item`, x.`xkey_ext`, x.`content_id`, + COALESCE( (SELECT FIRST 1 i2.`cross_ref_title` + FROM `".BIT_DB_PREFIX."liberty_xref_item` i2 + WHERE i2.`item` = x.`item`), + x.`item` ) AS `cross_ref_title` + FROM `".BIT_DB_PREFIX."liberty_xref` x + WHERE x.`content_id` = ? + AND ( x.`item` STARTING WITH 'P' + OR x.`item` STARTING WITH 'B' + OR x.`item` STARTING WITH '\$' ) + ORDER BY x.`item`", + [ $this->mContentId ] + ); + + $this->mInfo[$this->mXrefTypeKey] = []; + while ( $row = $result->fetchRow() ) { + $this->mInfo[$this->mXrefTypeKey][] = $row; + } + } + + /** + * Return all available type-tag options for this contact's edit form. + * + * Uses the schema (liberty_xref_item via getTypeMarkers) post-upgrade. + * Falls back to a hard-coded list pre-upgrade so edit checkboxes always appear. + * P01 is always excluded — it is implied for every person and is not a user choice. + * + * @return array[] Each element: ['item' => string, 'name' => string] + */ + public function getAvailableTypeItems(): array { + $markers = $this->xrefType()->getTypeMarkers(); + if ( !empty( $markers ) ) { + return array_values( array_filter( $markers, fn( $m ) => $m['item'] !== 'P01' ) ); + } + // Pre-upgrade fallback — schema rows not yet migrated + if ( $this->mContentTypeGuid === CONTACTPERSON_CONTENT_TYPE_GUID ) { + return [ [ 'item' => 'P02', 'name' => 'MERG Kit Elf' ] ]; + } + return [ + [ 'item' => 'B01', 'name' => 'Service' ], + [ 'item' => 'B02', 'name' => 'Manufacturer' ], + [ 'item' => 'B03', 'name' => 'Distributor' ], + [ 'item' => 'B04', 'name' => 'Supplier' ], + ]; } /** @@ -77,7 +136,7 @@ class Contact extends LibertyContent { LEFT JOIN `".BIT_DB_PREFIX."users_users` uue ON (uue.`user_id` = lc.`modifier_user_id`) LEFT JOIN `".BIT_DB_PREFIX."users_users` uuc ON (uuc.`user_id` = lc.`user_id`) LEFT JOIN `".BIT_DB_PREFIX."liberty_xref` img ON img.`content_id` = con.`content_id` AND img.`item` = 'IMG' - LEFT JOIN `".BIT_DB_PREFIX."liberty_xref` x00 ON x00.`content_id` = con.`content_id` AND x00.`item` = '$00' + LEFT JOIN `".BIT_DB_PREFIX."liberty_xref` x00 ON x00.`content_id` = con.`content_id` AND x00.`item` = 'P01' LEFT JOIN `".BIT_DB_PREFIX."liberty_xref` xhA ON xhA.`content_id` = con.`content_id` AND xhA.`item` = '#S' AND ( xhA.`end_date` IS NULL OR xhA.`end_date` > CURRENT_TIMESTAMP ) LEFT JOIN `".BIT_DB_PREFIX."liberty_xref` xhL ON xhL.`content_id` = con.`content_id` AND xhL.`item` = '#L' AND ( xhL.`end_date` IS NULL OR xhL.`end_date` > CURRENT_TIMESTAMP ) LEFT JOIN `".BIT_DB_PREFIX."address_postcode` ap ON ap.`postcode` = xhA.`xkey` @@ -203,20 +262,17 @@ class Contact extends LibertyContent { $result = $this->mDb->associateInsert( $atable, $pParamHash['contact_store'] ); } if( !empty( $pParamHash['contact_types'] ) ) { - $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_xref` WHERE `content_id` = ? AND `item` LIKE '$%'"; - $result = $this->mDb->query($query, [$this->mContentId ] ); - foreach ( $pParamHash['contact_types'] as $key => $source ) { - if ( $source == '$00' ) { + $query = "DELETE FROM `".BIT_DB_PREFIX."liberty_xref` WHERE `content_id` = ? AND (`item` STARTING WITH 'P' OR `item` STARTING WITH 'B')"; + $result = $this->mDb->query($query, [ $this->mContentId ] ); + foreach ( $pParamHash['contact_types'] as $key => $source ) { + if ( $source === 'P01' ) { $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_xref` (`xref_id`, `content_id`, `item`, `xkey_ext`, `last_update_date`) VALUES ( ?, ?, ?, ?, NULL )"; $result = $this->mDb->query($query, [ $this->mDb->GenID('liberty_xref_seq'), $this->mContentId, $source, $pParamHash['name'] ] ); - } else if ( $source == '$01' ) { - $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_xref` (`xref_id`, `content_id`, `item`, `xkey_ext`, `last_update_date`) VALUES ( ?, ?, ?, ?, NULL )"; - $result = $this->mDb->query($query, [ $this->mDb->GenID('liberty_xref_seq'), $this->mContentId, $source, $pParamHash['organisation'] ] ); } else { $query = "INSERT INTO `".BIT_DB_PREFIX."liberty_xref` (`xref_id`, `content_id`, `item`, `last_update_date`) VALUES ( ?, ?, ?, NULL )"; $result = $this->mDb->query($query, [ $this->mDb->GenID('liberty_xref_seq'), $this->mContentId, $source ] ); } - } + } } // load before completing transaction as firebird isolates results $this->load(); @@ -267,7 +323,7 @@ class Contact extends LibertyContent { $pContentId = $this->mContentId; } - return CONTACT_PKG_URL.'index.php?content_id='.$pContentId; + return CONTACT_PKG_URL.'display_contact.php?content_id='.$pContentId; } /** @@ -444,12 +500,7 @@ class Contact extends LibertyContent { return $ret; } - /** @return array Map of item code → label from the ContactType setup. */ - public function getContactTypes() { - return $this->mTypes->mContactType; - } - - /** +/** * Load contacts that reference this one via the '#A' xref item into $this->mInfo['client_list']. * * Used for contacts handled by a third party (e.g. alarm maintainer, call centre). diff --git a/includes/classes/ContactBusiness.php b/includes/classes/ContactBusiness.php new file mode 100644 index 0000000..1b75b0e --- /dev/null +++ b/includes/classes/ContactBusiness.php @@ -0,0 +1,28 @@ +<?php +/** + * Business contact — extends Contact with content_type_guid='contactbusiness'. + * + * Business-specific xref types ($02+ subtypes) are registered at the 'contactbusiness' + * level; shared contact fields live at the 'contact' package level. + * + * @package contact + */ +namespace Bitweaver\Contact; + +class ContactBusiness extends Contact { + + public function __construct( $pContactId = NULL, $pContentId = NULL ) { + parent::__construct( $pContactId, $pContentId ); + $this->mContentTypeGuid = CONTACTBUSINESS_CONTENT_TYPE_GUID; + $this->registerContentType( CONTACTBUSINESS_CONTENT_TYPE_GUID, [ + 'content_type_guid' => CONTACTBUSINESS_CONTENT_TYPE_GUID, + 'content_name' => 'Business Contact', + 'handler_class' => 'ContactBusiness', + 'handler_package' => 'contact', + 'handler_file' => 'ContactBusiness.php', + 'maintainer_url' => 'http://lsces.co.uk', + ] ); + // mPackageGuid='contact' is set automatically by registerContentType() + // because handler_package('contact') != content_type_guid('contactbusiness'). + } +} diff --git a/includes/classes/ContactPerson.php b/includes/classes/ContactPerson.php new file mode 100644 index 0000000..5393355 --- /dev/null +++ b/includes/classes/ContactPerson.php @@ -0,0 +1,29 @@ +<?php +/** + * Person contact — extends Contact with content_type_guid='contactperson'. + * + * Person-specific xref items ($00 type etc.) are registered at the 'contactperson' + * level; shared contact fields (addresses, SCREF etc.) live at the 'contact' + * package level and are picked up via the dual-guid xref pattern. + * + * @package contact + */ +namespace Bitweaver\Contact; + +class ContactPerson extends Contact { + + public function __construct( $pContactId = NULL, $pContentId = NULL ) { + parent::__construct( $pContactId, $pContentId ); + $this->mContentTypeGuid = CONTACTPERSON_CONTENT_TYPE_GUID; + $this->registerContentType( CONTACTPERSON_CONTENT_TYPE_GUID, [ + 'content_type_guid' => CONTACTPERSON_CONTENT_TYPE_GUID, + 'content_name' => 'Person Contact', + 'handler_class' => 'ContactPerson', + 'handler_package' => 'contact', + 'handler_file' => 'ContactPerson.php', + 'maintainer_url' => 'http://lsces.co.uk', + ] ); + // mPackageGuid='contact' is set automatically by registerContentType() + // because handler_package('contact') != content_type_guid('contactperson'). + } +} diff --git a/includes/classes/ContactType.php b/includes/classes/ContactType.php index 47b837f..45607d1 100755 --- a/includes/classes/ContactType.php +++ b/includes/classes/ContactType.php @@ -8,6 +8,7 @@ namespace Bitweaver\Contact; use Bitweaver\BitBase; +use Bitweaver\Liberty\LibertyXrefType; class ContactType extends BitBase { public $mContactType; @@ -17,31 +18,21 @@ class ContactType extends BitBase { } /** - * Populate $this->mContactType from liberty_xref_item (sort_order=0 groups) - * and assign 'contContactTypes' to Smarty for use in list/filter templates. + * Return all contact type markers (person + business) as item => title array. + * + * Each sub-type is queried independently via LibertyXrefType so the two sets + * of items are never mixed in a single query. + * + * @return array<string,string> e.g. ['$00' => 'Personal', '$04' => 'Supplier', ...] */ - public function setup() { - global $gBitUser, $gBitSmarty; - - $roles = array_keys($gBitUser->mRoles ?? []) ?: [-1]; - $bindVars = []; - $bindVars = array_merge( $bindVars, $roles, [ $gBitUser->mUserId ] ); - - $sql = "SELECT r.`item`, r.`cross_ref_title` - FROM `".BIT_DB_PREFIX."liberty_xref_item` r - JOIN `".BIT_DB_PREFIX."liberty_xref_group` t ON t.`x_group` = r.`x_group` AND t.`content_type_guid` = r.`content_type_guid` - LEFT OUTER JOIN `".BIT_DB_PREFIX."users_roles_map` purm ON ( purm.`user_id`=".(int)($gBitUser->mUserId ?? 0)." ) AND ( purm.`role_id`=r.`role_id` ) - WHERE r.`content_type_guid` = 'contact' AND t.`sort_order` = 0 AND (r.`role_id` IN(". implode(',', array_fill(0, count($roles), '?')) ." ) OR purm.`user_id`=?) - ORDER BY r.`item`"; - - $result = $this->mDb->query( $sql, $bindVars ); - - while( $res = $result->fetchRow() ) { - $this->mContactType[ $res['item']] = $res['cross_ref_title']; + public static function getTypeMarkerList(): array { + $ret = []; + foreach ( [ 'contactperson', 'contactbusiness' ] as $guid ) { + foreach ( ( new LibertyXrefType( $guid ) )->getTypeMarkers() as $m ) { + $ret[ $m['item'] ] = $m['name']; + } } - -// asort($this->mContactType); - $gBitSmarty->assign( 'contContactTypes', $this->mContactType ); + return $ret; } /** @@ -87,7 +78,7 @@ class ContactType extends BitBase { $bindVars[] = $pOptionHash['title']; } - $guidWhere = " cxt.`content_type_guid` = 'contact' "; + $guidWhere = " cxt.`content_type_guid` IN ('contact','contactperson','contactbusiness') "; $where = $where ? $where . " AND $guidWhere" : " WHERE $guidWhere"; $query = "SELECT cxt.* @@ -100,7 +91,7 @@ class ContactType extends BitBase { while( $res = $result->fetchRow() ) { $res["num_types"] = $gBitSystem->mDb->getOne( - "SELECT COUNT(*) FROM `".BIT_DB_PREFIX."liberty_xref_item` WHERE `x_group` = ? AND `content_type_guid` = 'contact'", + "SELECT COUNT(*) FROM `".BIT_DB_PREFIX."liberty_xref_item` WHERE `x_group` = ? AND `content_type_guid` IN ('contact','contactperson','contactbusiness')", [ $res["x_group"] ] ); $ret[] = $res; diff --git a/includes/lookup_contact.php b/includes/lookup_contact.php index e477fb9..098bca1 100644 --- a/includes/lookup_contact.php +++ b/includes/lookup_contact.php @@ -31,7 +31,7 @@ $rows = $gBitDb->getArray( (SELECT FIRST 1 sx.xkey FROM ".BIT_DB_PREFIX."liberty_xref sx WHERE sx.content_id=lc.content_id AND sx.item='SCREF') AS scref FROM ".BIT_DB_PREFIX."liberty_content lc - WHERE lc.content_type_guid='contact' + WHERE lc.content_type_guid IN ('contactperson','contactbusiness') AND (LOWER(lc.title) LIKE ? OR EXISTS ( SELECT 1 FROM ".BIT_DB_PREFIX."liberty_xref sx WHERE sx.content_id=lc.content_id AND sx.item='SCREF' AND LOWER(sx.xkey) LIKE ? diff --git a/includes/lookup_contact_inc.php b/includes/lookup_contact_inc.php index 1c77c99..6804d9d 100755 --- a/includes/lookup_contact_inc.php +++ b/includes/lookup_contact_inc.php @@ -10,13 +10,17 @@ */ use Bitweaver\BitBase; use Bitweaver\Contact\Contact; -//require_once( TASKS_PKG_PATH.'Tasks.php'); +use Bitweaver\Liberty\LibertyContent; // if we already have a gContent, we assume someone else created it for us, and has properly loaded everything up. if( empty( $gContent ) || !is_object( $gContent ) ) { if( BitBase::verifyId( $_REQUEST['content_id'] ?? 0 ) ) { - $gContent = new Contact( NULL, $_REQUEST['content_id'] ); - $gContent->load(); + // getLibertyObject returns ContactPerson or ContactBusiness (already loaded) + $gContent = LibertyContent::getLibertyObject( (int)$_REQUEST['content_id'] ); + if( !( $gContent instanceof Contact ) ) { + // Fallback: content_id exists but is not a contact type + $gContent = new Contact( NULL, (int)$_REQUEST['content_id'] ); + } } else { $gContent = new Contact(); } |
