summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-06-11 19:26:25 +0100
committerLester Caine <lester@lsces.co.uk>2026-06-11 19:26:25 +0100
commit64aae1e41d9288a6d9709781af29b06fe6adb5ca (patch)
treed5d5e74420cee938d7e8ff90b9e6792d5f44cc77
parent27c615a0a26edb985543e520587e3043d91489f6 (diff)
downloadcontact-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>
-rw-r--r--add_business.php11
-rw-r--r--add_person.php6
-rwxr-xr-xadmin/admin_contact_inc.php5
-rwxr-xr-xadmin/schema_inc.php39
-rw-r--r--admin/upgrades/5.0.3.php112
-rwxr-xr-xdisplay_contact.php1
-rwxr-xr-xedit.php23
-rw-r--r--export_contacts.php4
-rw-r--r--import/ImportContactCSV.php12
-rw-r--r--import/load_contacts_csv.php2
-rwxr-xr-xincludes/bit_setup_inc.php17
-rwxr-xr-xincludes/classes/Contact.php85
-rw-r--r--includes/classes/ContactBusiness.php28
-rw-r--r--includes/classes/ContactPerson.php29
-rwxr-xr-xincludes/classes/ContactType.php41
-rw-r--r--includes/lookup_contact.php2
-rwxr-xr-xincludes/lookup_contact_inc.php10
-rwxr-xr-xlist1.php3
-rwxr-xr-xlist2.php3
-rw-r--r--list_businesses.php36
-rwxr-xr-xlist_contacts.php27
-rw-r--r--list_people.php30
-rwxr-xr-xtemplates/display_contact.tpl2
-rwxr-xr-xtemplates/display_type_header.tpl4
-rwxr-xr-xtemplates/edit.tpl6
-rwxr-xr-xtemplates/edit_type_header.tpl19
-rwxr-xr-xtemplates/list.tpl2
-rwxr-xr-xtemplates/menu_contact.tpl6
28 files changed, 449 insertions, 116 deletions
diff --git a/add_business.php b/add_business.php
index 4164768..685423d 100644
--- a/add_business.php
+++ b/add_business.php
@@ -4,7 +4,7 @@
* @subpackage functions
*/
-use Bitweaver\Contact\Contact;
+use Bitweaver\Contact\ContactBusiness;
use Bitweaver\KernelTools;
require_once '../kernel/includes/setup_inc.php';
@@ -14,7 +14,7 @@ global $gBitSystem, $gBitSmarty, $gBitUser;
$gBitSystem->verifyPackage( 'contact' );
$gBitSystem->verifyPermission( 'p_contact_update' );
-$gContent = new Contact();
+$gContent = new ContactBusiness();
if( !empty( $_REQUEST['fCancel'] ) ) {
KernelTools::bit_redirect( CONTACT_PKG_URL );
@@ -37,12 +37,11 @@ if( !empty( $_REQUEST['fSaveContact'] ) ) {
}
}
-// Load all business-relevant types ($01 and above) for optional checkboxes
-$allTypes = $gContent->getXrefSourceList();
-$businessTypes = array_filter( $allTypes, fn($t) => $t['item'] !== '$00' && $t['item'] !== '$01' );
+// ContactBusiness type markers are $02+ only ($00/$01 live under contactperson/deprecated)
+$businessTypes = $gContent->getXrefSourceList();
$gBitSmarty->assign( 'gContent', $gContent );
-$gBitSmarty->assign( 'businessTypes', array_values( $businessTypes ) );
+$gBitSmarty->assign( 'businessTypes', array_values( (array)$businessTypes ) );
$gBitSmarty->assign( 'errors', $gContent->mErrors );
$gBitSystem->display( 'bitpackage:contact/add_business.tpl', KernelTools::tra( 'Add Business' ), [ 'display_mode' => 'edit' ] );
diff --git a/add_person.php b/add_person.php
index eda69c7..68e3599 100644
--- a/add_person.php
+++ b/add_person.php
@@ -4,7 +4,7 @@
* @subpackage functions
*/
-use Bitweaver\Contact\Contact;
+use Bitweaver\Contact\ContactPerson;
use Bitweaver\KernelTools;
require_once '../kernel/includes/setup_inc.php';
@@ -14,7 +14,7 @@ global $gBitSystem, $gBitSmarty, $gBitUser;
$gBitSystem->verifyPackage( 'contact' );
$gBitSystem->verifyPermission( 'p_contact_update' );
-$gContent = new Contact();
+$gContent = new ContactPerson();
if( !empty( $_REQUEST['fCancel'] ) ) {
KernelTools::bit_redirect( CONTACT_PKG_URL );
@@ -22,7 +22,7 @@ if( !empty( $_REQUEST['fCancel'] ) ) {
}
if( !empty( $_REQUEST['fSaveContact'] ) ) {
- $_REQUEST['contact_types'] = [ '$00' ];
+ $_REQUEST['contact_types'] = [ 'P01' ];
if( $gContent->store( $_REQUEST ) ) {
KernelTools::bit_redirect( CONTACT_PKG_URL.'edit.php?content_id='.$gContent->mContentId );
die;
diff --git a/admin/admin_contact_inc.php b/admin/admin_contact_inc.php
index 5ea1ae2..dd47e5d 100755
--- a/admin/admin_contact_inc.php
+++ b/admin/admin_contact_inc.php
@@ -7,8 +7,7 @@
use Bitweaver\Contact\ContactType;
-$mTypes = new ContactType();
-$mTypes->setup();
+$contactTypeMarkers = ContactType::getTypeMarkerList();
$formContactListFeatures = [
"contact_list_id" => [
@@ -41,7 +40,7 @@ $formContactListFeatures = [
];
$gBitSmarty->assign( 'formContactListFeatures',$formContactListFeatures );
-foreach( $mTypes->mContactType as $key => $type ) {
+foreach( $contactTypeMarkers as $key => $type ) {
$option = 'contact_default_'.$key;
$contactChecks[] = $option;
$contactTypeDefaults[$option] = $type;
diff --git a/admin/schema_inc.php b/admin/schema_inc.php
index 50fc188..0fd9ebf 100755
--- a/admin/schema_inc.php
+++ b/admin/schema_inc.php
@@ -64,6 +64,7 @@ $gBitInstaller->registerPackageInfo( CONTACT_PKG_NAME, [
'dependencies' => 'liberty',
] );
+
// ### Indexes
$indices = [
'contact_parent_id_idx' => [ 'table' => 'contact', 'cols' => 'parent_id', 'opts' => null ],
@@ -75,24 +76,32 @@ $gBitInstaller->registerSchemaIndexes( CONTACT_PKG_NAME, $indices );
$gBitInstaller->registerSchemaSequences( CONTACT_PKG_NAME, [] );
// ### Defaults
-// xref configuration now lives in liberty_xref_group and liberty_xref_item (content_type_guid='contact').
-// These replace the old contact_xref_type and contact_xref_source table defaults.
+// Xref schema: shared groups/items at content_type_guid='contact'; type markers split by sub-type.
+// contactperson: 'type' group + $00 item. contactbusiness: 'type' group + $02-$05 items.
$gBitInstaller->registerSchemaDefault( CONTACT_PKG_NAME, [
- // --- liberty_xref_group (formerly contact_xref_type: integer xref_type → sort_order, source text → x_group) ---
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('type', 'contact','Contact Type List', 0,3,'')",
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('contact','contact','General Contact Details', 1,3,'')",
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('links', 'contact','Linked Contact Items', 2,3,'')",
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('account','contact','Account Details', 3,3,'')",
+ // --- liberty_content_types — sub-type handlers for person and business ---
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_content_types` (`content_type_guid`,`content_name`,`content_name_plural`,`handler_class`,`handler_package`,`handler_file`,`maintainer_url`) VALUES ('contactperson','Person Contact','Person Contacts','ContactPerson','contact','ContactPerson.php','http://lsces.co.uk')",
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_content_types` (`content_type_guid`,`content_name`,`content_name_plural`,`handler_class`,`handler_package`,`handler_file`,`maintainer_url`) VALUES ('contactbusiness','Business Contact','Business Contacts','ContactBusiness','contact','ContactBusiness.php','http://lsces.co.uk')",
+
+ // --- liberty_xref_group ---
+ // 'type' group split: one per sub-type (sort_order=0 = type-marker group, excluded from loadXrefInfo display)
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('type','contactperson', 'Person Type', 0,3,'')",
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('type','contactbusiness','Business Type List',0,3,'')",
+ // shared groups stay at 'contact' level (loaded via dual-guid IN filter)
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('contact','contact','General Contact Details',1,3,'')",
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('links', 'contact','Linked Contact Items', 2,3,'')",
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_group` (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`) VALUES ('account','contact','Account Details', 3,3,'')",
- // --- liberty_xref_item (formerly contact_xref_source) ---
- // group: type
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('\$00','contact','type','Personal', 0,3,'/contact/?type=0', NULL)",
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('\$01','contact','type','Business', 0,3,'/contact/?type=1', NULL)",
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('\$02','contact','type','Manufacturer', 0,3,'/contact/?type=2', NULL)",
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('\$03','contact','type','Distributor', 0,3,'/contact/?type=3', NULL)",
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('\$04','contact','type','Supplier', 0,3,'/contact/?type=4', NULL)",
- "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('\$05','contact','type','MERG Kit Elf', 0,3,'/contact/?type=5', NULL)",
+ // --- liberty_xref_item ---
+ // group: type — person types
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('P01','contactperson','type','Personal', 0,3,'',NULL)",
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('P02','contactperson','type','MERG Kit Elf', 0,3,'',NULL)",
+ // group: type — business subtypes
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('B01','contactbusiness','type','Service', 0,3,'',NULL)",
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('B02','contactbusiness','type','Manufacturer', 0,3,'',NULL)",
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('B03','contactbusiness','type','Distributor', 0,3,'',NULL)",
+ "INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('B04','contactbusiness','type','Supplier', 0,3,'',NULL)",
// group: contact
"INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('#C','contact','contact','Contact Address', 0,3,'../nlpg/?uprn=', 'address')",
"INSERT INTO `" . BIT_DB_PREFIX . "liberty_xref_item` (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`) VALUES ('#E','contact','contact','eMail Address', 1,3,'../contact/?contact_id=','text' )",
diff --git a/admin/upgrades/5.0.3.php b/admin/upgrades/5.0.3.php
new file mode 100644
index 0000000..87c4989
--- /dev/null
+++ b/admin/upgrades/5.0.3.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * 5.0.3 — Introduce ContactPerson and ContactBusiness subclasses.
+ *
+ * Steps:
+ * 1. Insert liberty_content_types rows for contactperson and contactbusiness.
+ * 2. Insert liberty_xref_group 'type' rows at contactperson and contactbusiness level.
+ * 3. Insert xref_item rows with new P01/P02 and B01-B04 codes.
+ * 4. Delete the now-superseded 'type' group and $0x items from content_type_guid='contact'.
+ * 5. Migrate liberty_content: records with a $00 xref → contactperson; remainder → contactbusiness.
+ * 6. Rename existing liberty_xref type-tag rows from $0x to P0x/B0x.
+ *
+ * @package contact
+ */
+
+global $gBitInstaller;
+
+$X = BIT_DB_PREFIX;
+
+$gBitInstaller->registerPackageUpgrade(
+ [
+ 'package' => 'contact',
+ 'version' => '5.0.3',
+ 'description' => 'Introduce ContactPerson and ContactBusiness content type subclasses.',
+ ],
+ [
+ // --- Step 1: register content types ---
+ [ 'QUERY' => [ 'SQL92' => [
+ "UPDATE OR INSERT INTO `{$X}liberty_content_types`
+ (`content_type_guid`,`content_name`,`content_name_plural`,`handler_class`,`handler_package`,`handler_file`,`maintainer_url`)
+ VALUES ('contactperson','Person Contact','Person Contacts','ContactPerson','contact','ContactPerson.php','http://lsces.co.uk')
+ MATCHING (`content_type_guid`)",
+ "UPDATE OR INSERT INTO `{$X}liberty_content_types`
+ (`content_type_guid`,`content_name`,`content_name_plural`,`handler_class`,`handler_package`,`handler_file`,`maintainer_url`)
+ VALUES ('contactbusiness','Business Contact','Business Contacts','ContactBusiness','contact','ContactBusiness.php','http://lsces.co.uk')
+ MATCHING (`content_type_guid`)",
+ ]]],
+
+ // --- Step 2: xref_group 'type' rows ---
+ [ 'QUERY' => [ 'SQL92' => [
+ "UPDATE OR INSERT INTO `{$X}liberty_xref_group`
+ (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`)
+ VALUES ('type','contactperson','Person Type',0,3,'')
+ MATCHING (`x_group`,`content_type_guid`)",
+ "UPDATE OR INSERT INTO `{$X}liberty_xref_group`
+ (`x_group`,`content_type_guid`,`title`,`sort_order`,`role_id`,`type_href`)
+ VALUES ('type','contactbusiness','Business Type List',0,3,'')
+ MATCHING (`x_group`,`content_type_guid`)",
+ ]]],
+
+ // --- Step 3: xref_item rows with new codes ---
+ [ 'QUERY' => [ 'SQL92' => [
+ // person types
+ "UPDATE OR INSERT INTO `{$X}liberty_xref_item`
+ (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`)
+ VALUES ('P01','contactperson','type','Personal',0,3,'',NULL)
+ MATCHING (`item`,`content_type_guid`)",
+ "UPDATE OR INSERT INTO `{$X}liberty_xref_item`
+ (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`)
+ VALUES ('P02','contactperson','type','MERG Kit Elf',0,3,'',NULL)
+ MATCHING (`item`,`content_type_guid`)",
+ // business subtypes
+ "UPDATE OR INSERT INTO `{$X}liberty_xref_item`
+ (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`)
+ VALUES ('B01','contactbusiness','type','Service',0,3,'',NULL)
+ MATCHING (`item`,`content_type_guid`)",
+ "UPDATE OR INSERT INTO `{$X}liberty_xref_item`
+ (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`)
+ VALUES ('B02','contactbusiness','type','Manufacturer',0,3,'',NULL)
+ MATCHING (`item`,`content_type_guid`)",
+ "UPDATE OR INSERT INTO `{$X}liberty_xref_item`
+ (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`)
+ VALUES ('B03','contactbusiness','type','Distributor',0,3,'',NULL)
+ MATCHING (`item`,`content_type_guid`)",
+ "UPDATE OR INSERT INTO `{$X}liberty_xref_item`
+ (`item`,`content_type_guid`,`x_group`,`cross_ref_title`,`multiple`,`role_id`,`cross_ref_href`,`template`)
+ VALUES ('B04','contactbusiness','type','Supplier',0,3,'',NULL)
+ MATCHING (`item`,`content_type_guid`)",
+ ]]],
+
+ // --- Step 4: remove old 'type' group and $0x items from contact ---
+ [ 'QUERY' => [ 'SQL92' => [
+ "DELETE FROM `{$X}liberty_xref_item` WHERE `content_type_guid` = 'contact' AND `x_group` = 'type'",
+ "DELETE FROM `{$X}liberty_xref_group` WHERE `content_type_guid` = 'contact' AND `x_group` = 'type'",
+ ]]],
+
+ // --- Step 5: migrate liberty_content records ---
+ // Person detection uses old $00 item (still present in liberty_xref at this point)
+ [ 'QUERY' => [ 'SQL92' => [
+ "UPDATE `{$X}liberty_content` SET `content_type_guid` = 'contactperson'
+ WHERE `content_type_guid` = 'contact'
+ AND `content_id` IN (
+ SELECT `content_id` FROM `{$X}liberty_xref` WHERE `item` = '\$00'
+ )",
+ "UPDATE `{$X}liberty_content` SET `content_type_guid` = 'contactbusiness'
+ WHERE `content_type_guid` = 'contact'",
+ ]]],
+
+ // --- Step 6: rename existing liberty_xref type-tag rows ---
+ // $00 (Personal name tag) → P01; $05 (Kit Elf) → P02
+ // $02 (Manufacturer) → B02; $03 (Distributor) → B03; $04 (Supplier) → B04
+ // $01 (deprecated Business) — deleted; B01 (Service) is new, no existing data
+ [ 'QUERY' => [ 'SQL92' => [
+ "UPDATE `{$X}liberty_xref` SET `item` = 'P01' WHERE `item` = '\$00'",
+ "UPDATE `{$X}liberty_xref` SET `item` = 'P02' WHERE `item` = '\$05'",
+ "UPDATE `{$X}liberty_xref` SET `item` = 'B02' WHERE `item` = '\$02'",
+ "UPDATE `{$X}liberty_xref` SET `item` = 'B03' WHERE `item` = '\$03'",
+ "UPDATE `{$X}liberty_xref` SET `item` = 'B04' WHERE `item` = '\$04'",
+ "DELETE FROM `{$X}liberty_xref` WHERE `item` = '\$01'",
+ ]]],
+ ]
+);
diff --git a/display_contact.php b/display_contact.php
index 891cd16..6082ccb 100755
--- a/display_contact.php
+++ b/display_contact.php
@@ -51,6 +51,7 @@ if ($gContent->isCommentable()) {
}
}
+$gBitSmarty->assign( 'isPerson', $gContent instanceof \Bitweaver\Contact\ContactPerson );
$gBitSmarty->assign( 'gXrefInfo', $gContent->mXrefInfo );
$gBitSystem->setBrowserTitle( $gContent->mInfo['title'] );
diff --git a/edit.php b/edit.php
index 6bea6aa..4b47446 100755
--- a/edit.php
+++ b/edit.php
@@ -73,15 +73,28 @@ if( empty( $formInfo ) ) {
$formInfo = &$gContent->mInfo;
}
-$isPerson = !empty( $gContent->mInfo['contact_types'][0]['content_id'] );
+$isPerson = $gContent instanceof \Bitweaver\Contact\ContactPerson;
$gContent->loadXrefInfo();
$gBitSmarty->assign( 'gXrefInfo', $gContent->mXrefInfo );
$gBitSmarty->assign( 'isPerson', $isPerson );
-$allTypes = $gContent->getXrefSourceList();
-$formInfo['contact_type_list'] = $isPerson
- ? []
- : array_values( array_filter( $allTypes, fn($t) => $t['item'] > '$01' ) );
+// Build type toggle list: available options from schema (or hard-coded pre-upgrade fallback),
+// checked state from contact_types (currently set items in liberty_xref).
+$setItems = [];
+foreach ( $gContent->mInfo['contact_types'] ?? [] as $ct ) {
+ if ( !empty( $ct['content_id'] ) ) {
+ $setItems[ $ct['item'] ] = true;
+ }
+}
+$typeToggle = [];
+foreach ( $gContent->getAvailableTypeItems() as $m ) {
+ $typeToggle[] = [
+ 'item' => $m['item'],
+ 'name' => $m['name'],
+ 'checked' => isset( $setItems[ $m['item'] ] ),
+ ];
+}
+$gContent->mInfo['contact_type_list'] = $typeToggle;
$gBitSmarty->assign( 'pageInfo', $formInfo );
$gBitSmarty->assign( 'errors', $gContent->mErrors );
diff --git a/export_contacts.php b/export_contacts.php
index 380d94c..608988c 100644
--- a/export_contacts.php
+++ b/export_contacts.php
@@ -27,7 +27,7 @@ $sql = "SELECT lc.`content_id`, lc.`title`,
FROM `" . BIT_DB_PREFIX . "liberty_content` lc
LEFT JOIN `" . BIT_DB_PREFIX . "liberty_xref` x00 ON x00.`content_id` = lc.`content_id` AND x00.`item` = '\$00'
LEFT JOIN `" . BIT_DB_PREFIX . "liberty_xref` xsc ON xsc.`content_id` = lc.`content_id` AND xsc.`item` = 'SCREF'
- WHERE lc.`content_type_guid` = 'contact'
+ WHERE lc.`content_type_guid` IN ('contactperson','contactbusiness')
ORDER BY lc.`title`";
$result = $gBitDb->query( $sql );
@@ -56,7 +56,7 @@ $sql = "SELECT x.`content_id`, x.`item`, xi.`cross_ref_title`, xi.`template`,
x.`xkey`, x.`xkey_ext`, x.`data`, x.`xorder`,
ap.`add1`, ap.`add2`, ap.`add3`, ap.`add4`, ap.`town`, ap.`county`
FROM `" . BIT_DB_PREFIX . "liberty_xref` x
- JOIN `" . BIT_DB_PREFIX . "liberty_xref_item` xi ON xi.`item` = x.`item` AND xi.`content_type_guid` = 'contact'
+ JOIN `" . BIT_DB_PREFIX . "liberty_xref_item` xi ON xi.`item` = x.`item` AND xi.`content_type_guid` IN ('contact','contactperson','contactbusiness')
LEFT JOIN `" . BIT_DB_PREFIX . "address_postcode` ap ON ap.`postcode` = x.`xkey`
WHERE ( x.`end_date` IS NULL OR x.`end_date` > CURRENT_TIMESTAMP )
AND x.`content_id` IN (" . implode( ',', array_keys( $contacts ) ) . ")
diff --git a/import/ImportContactCSV.php b/import/ImportContactCSV.php
index ada5732..a6e6b1d 100644
--- a/import/ImportContactCSV.php
+++ b/import/ImportContactCSV.php
@@ -24,6 +24,8 @@
namespace Bitweaver\Liberty;
use Bitweaver\Contact\Contact;
+use Bitweaver\Contact\ContactPerson;
+use Bitweaver\Contact\ContactBusiness;
/**
* Delete any existing xref for ($contentId, $item) then re-insert if either $xkey or
@@ -95,14 +97,16 @@ function contactCsvImportRow( array $row, int $rowNum ): array {
return $result;
}
- // --- Find existing or create new via Contact class ---
+ // --- Find existing or create new via Contact subclass ---
+ $isPerson = ( $type === '$00' );
+
$contentId = $gBitDb->getOne(
"SELECT `content_id` FROM `" . BIT_DB_PREFIX . "liberty_content`
- WHERE `content_type_guid` = 'contact' AND `title` = ?",
+ WHERE `content_type_guid` IN ('contactperson','contactbusiness','contact') AND `title` = ?",
[ $title ]
);
- $contact = new Contact( null, $contentId ?: null );
+ $contact = $isPerson ? new ContactPerson( null, $contentId ?: null ) : new ContactBusiness( null, $contentId ?: null );
if( $contentId ) {
$contact->load();
}
@@ -118,7 +122,7 @@ function contactCsvImportRow( array $row, int $rowNum ): array {
if( !empty( $type ) && $type[0] === '$' ) {
$pHash['contact_types'] = [ $type ];
- if( $type === '$00' ) {
+ if( $isPerson ) {
$pHash['name'] = $personName;
}
}
diff --git a/import/load_contacts_csv.php b/import/load_contacts_csv.php
index 3a75030..4e8adc7 100644
--- a/import/load_contacts_csv.php
+++ b/import/load_contacts_csv.php
@@ -52,7 +52,7 @@ if( !file_exists( $csvFile ) ) {
if( empty( $title ) ) continue;
$contentId = $gBitDb->getOne(
"SELECT `content_id` FROM `" . BIT_DB_PREFIX . "liberty_content`
- WHERE `content_type_guid` = 'contact' AND `title` = ?",
+ WHERE `content_type_guid` IN ('contactperson','contactbusiness') AND `title` = ?",
[ $title ]
);
if( $contentId ) {
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();
}
diff --git a/list1.php b/list1.php
index 77d1678..64d35b7 100755
--- a/list1.php
+++ b/list1.php
@@ -16,6 +16,7 @@
require_once '../kernel/includes/setup_inc.php';
use Bitweaver\Contact\Contact;
+use Bitweaver\Contact\ContactType;
use Bitweaver\KernelTools;
$gBitSystem->verifyPackage( 'contact' );
@@ -24,7 +25,7 @@ $gBitSystem->verifyPermission( 'p_contact_view' );
$gContent = new Contact( );
$gContent->invokeServices( 'content_list_function', $_REQUEST );
-// Handle the request hash storing into the session.
+$gBitSmarty->assign( 'contContactTypes', ContactType::getTypeMarkerList() );
$gContent->mTypes->processRequestHash($_REQUEST, $_SESSION['contact']);
$listHash = $_REQUEST;
diff --git a/list2.php b/list2.php
index d4d6bd4..563dfad 100755
--- a/list2.php
+++ b/list2.php
@@ -16,6 +16,7 @@
require_once '../kernel/includes/setup_inc.php';
use Bitweaver\Contact\Contact;
+use Bitweaver\Contact\ContactType;
use Bitweaver\KernelTools;
$gBitSystem->verifyPackage( 'contact' );
@@ -24,7 +25,7 @@ $gBitSystem->verifyPermission( 'p_contact_view' );
$gContent = new Contact( );
$gContent->invokeServices( 'content_list_function', $_REQUEST );
-// Handle the request hash storing into the session.
+$gBitSmarty->assign( 'contContactTypes', ContactType::getTypeMarkerList() );
$gContent->mTypes->processRequestHash($_REQUEST, $_SESSION['contact']);
$listHash = $_REQUEST;
diff --git a/list_businesses.php b/list_businesses.php
new file mode 100644
index 0000000..b4dd4b1
--- /dev/null
+++ b/list_businesses.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @package contact
+ */
+
+require_once '../kernel/includes/setup_inc.php';
+
+use Bitweaver\Contact\ContactBusiness;
+use Bitweaver\Contact\ContactType;
+use Bitweaver\KernelTools;
+use Bitweaver\Liberty\LibertyXrefType;
+
+$gBitSystem->verifyPackage( 'contact' );
+$gBitSystem->verifyPermission( 'p_contact_view' );
+
+$gContent = new ContactBusiness();
+$gContent->invokeServices( 'content_list_function', $_REQUEST );
+$gContent->mTypes->processRequestHash( $_REQUEST, $_SESSION['contact'] );
+
+// Business type filter: contactbusiness type markers only (never mix with contactperson)
+$businessTypes = ( new LibertyXrefType( CONTACTBUSINESS_CONTENT_TYPE_GUID ) )->getTypeMarkers();
+$gBitSmarty->assign( 'contContactTypes', array_column( $businessTypes, 'name', 'item' ) );
+
+$listHash = $_REQUEST;
+$listcontacts = $gContent->getList( $listHash );
+
+if( $listHash['listInfo']['count'] == 1 ) {
+ KernelTools::bit_redirect( CONTACT_PKG_URL . "display_contact.php?content_id=" . $listcontacts[0]['content_id'] );
+}
+
+$gBitSmarty->assign( 'listcontacts', $listcontacts );
+$gBitSmarty->assign( 'listInfo', $listHash['listInfo'] );
+$gBitSmarty->assign( 'listTitle', KernelTools::tra( 'Businesses' ) );
+
+$gBitSystem->setBrowserTitle( KernelTools::tra( 'Businesses' ) );
+$gBitSystem->display( 'bitpackage:contact/list.tpl', NULL, [ 'display_mode' => 'list' ] );
diff --git a/list_contacts.php b/list_contacts.php
index 9aa8ca9..0d1899e 100755
--- a/list_contacts.php
+++ b/list_contacts.php
@@ -6,19 +6,34 @@
require_once '../kernel/includes/setup_inc.php';
-use Bitweaver\Contact\Contact;
+use Bitweaver\Contact\ContactPerson;
+use Bitweaver\Contact\ContactBusiness;
use Bitweaver\KernelTools;
$gBitSystem->verifyPackage( 'contact' );
$gBitSystem->verifyPermission( 'p_contact_view' );
-$gContent = new Contact();
-$gContent->invokeServices( 'content_list_function', $_REQUEST );
+// Persons and businesses are separate types — each has its own getList().
+// The combined display is a view-layer concern: merge, sort, and let the
+// template select the row template by content_type_guid.
+$personContent = new ContactPerson();
+$businessContent = new ContactBusiness();
-$listHash = $_REQUEST;
-$listcontacts = $gContent->getList( $listHash );
+$personHash = $_REQUEST;
+$businessHash = $_REQUEST;
-if( $listHash['listInfo']['count'] == 1 ) {
+$persons = $personContent->getList( $personHash );
+$businesses = $businessContent->getList( $businessHash );
+
+$listcontacts = array_merge( $persons, $businesses );
+usort( $listcontacts, fn( $a, $b ) => strcasecmp( $a['title'] ?? '', $b['title'] ?? '' ) );
+
+// listInfo: sum the two counts; use personHash's pagination metadata as base
+$listHash = $personHash;
+$listHash['cant'] = ( $personHash['cant'] ?? 0 ) + ( $businessHash['cant'] ?? 0 );
+$listHash['listInfo']['count'] = $listHash['cant'];
+
+if( $listHash['cant'] == 1 ) {
KernelTools::bit_redirect( CONTACT_PKG_URL."display_contact.php?content_id=".$listcontacts[0]['content_id'] );
}
diff --git a/list_people.php b/list_people.php
new file mode 100644
index 0000000..56ba8ad
--- /dev/null
+++ b/list_people.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @package contact
+ */
+
+require_once '../kernel/includes/setup_inc.php';
+
+use Bitweaver\Contact\ContactPerson;
+use Bitweaver\KernelTools;
+
+$gBitSystem->verifyPackage( 'contact' );
+$gBitSystem->verifyPermission( 'p_contact_view' );
+
+$gContent = new ContactPerson();
+$gContent->invokeServices( 'content_list_function', $_REQUEST );
+$gContent->mTypes->processRequestHash( $_REQUEST, $_SESSION['contact'] );
+
+$listHash = $_REQUEST;
+$listcontacts = $gContent->getList( $listHash );
+
+if( $listHash['listInfo']['count'] == 1 ) {
+ KernelTools::bit_redirect( CONTACT_PKG_URL . "display_contact.php?content_id=" . $listcontacts[0]['content_id'] );
+}
+
+$gBitSmarty->assign( 'listcontacts', $listcontacts );
+$gBitSmarty->assign( 'listInfo', $listHash['listInfo'] );
+$gBitSmarty->assign( 'listTitle', KernelTools::tra( 'People' ) );
+
+$gBitSystem->setBrowserTitle( KernelTools::tra( 'People' ) );
+$gBitSystem->display( 'bitpackage:contact/list.tpl', NULL, [ 'display_mode' => 'list' ] );
diff --git a/templates/display_contact.tpl b/templates/display_contact.tpl
index 8512d33..2a3ba39 100755
--- a/templates/display_contact.tpl
+++ b/templates/display_contact.tpl
@@ -9,7 +9,7 @@
<div class="clear"></div>
</div>
{/if}
- {if $gContent->mInfo.contact_types.0.content_id}
+ {if $isPerson}
<div class="form-group">
{formlabel label="Name"}
{forminput}
diff --git a/templates/display_type_header.tpl b/templates/display_type_header.tpl
index 350ce1c..f8647a7 100755
--- a/templates/display_type_header.tpl
+++ b/templates/display_type_header.tpl
@@ -1,8 +1,8 @@
<div class="form-group">
- {formlabel label="{if $gContent->mInfo.contact_types.0.content_id}Personal Contact{else}Business Contact{/if}"}
+ {formlabel label="{if $isPerson}Personal Contact{else}Business Contact{/if}"}
{forminput}
{foreach from=$gContent->mInfo.contact_types key=type_id item=type}
- {if isset($type.content_id) && $type.item gt '$01'}{$type.cross_ref_title}<br/>{/if}
+ {if isset($type.content_id) && $type.item neq 'P01'}{$type.cross_ref_title}<br/>{/if}
{/foreach}
{/forminput}
<div class="clear"></div>
diff --git a/templates/edit.tpl b/templates/edit.tpl
index 9caf1d9..e164291 100755
--- a/templates/edit.tpl
+++ b/templates/edit.tpl
@@ -51,11 +51,9 @@
<div class="clear"></div>
</div>
- {if !$isPerson}
- {include file="bitpackage:contact/edit_type_header.tpl"}
- {/if}
+ {include file="bitpackage:contact/edit_type_header.tpl"}
- {if $gContent->mInfo.name || $gContent->mInfo.contact_types.0.content_id || !isset( $gContent->mInfo.contact_types ) }
+ {if $isPerson}
<div class="form-group">
{formlabel label="Title" for="prefix"}
{forminput}
diff --git a/templates/edit_type_header.tpl b/templates/edit_type_header.tpl
index 3d42abb..c4f6756 100755
--- a/templates/edit_type_header.tpl
+++ b/templates/edit_type_header.tpl
@@ -1,19 +1,12 @@
<div class="form-group">
- {formlabel label="Contact Types" for=content_types}
+ {formlabel label="Contact Types"}
{forminput}
- {if isset( $gContent->mInfo.contact_types ) }
- {foreach from=$gContent->mInfo.contact_types key=type_id item=type}
- {if $type.item gt '$01'}
- <input type="checkbox" name="contact_types[{$type_id}]" value="{$type.item}" {if isset($type.content_id) } checked="checked"{/if} /> {$type.cross_ref_title}<br/>
- {/if}
- {/foreach}
- {else}
- {foreach from=$gContent->mInfo.contact_type_list key=type_id item=type}
- <input type="checkbox" name="contact_types[$type_id]" value="{$type.item}" />{$type.name}<br/>
- {/foreach}
- {/if}
+ {foreach from=$gContent->mInfo.contact_type_list item=type}
+ <label class="checkbox-inline">
+ <input type="checkbox" name="contact_types[]" value="{$type.item|escape}"{if $type.checked} checked="checked"{/if} /> {$type.name|escape}
+ </label>
+ {/foreach}
{/forminput}
- {formhelp note=""}
<div class="clear"></div>
</div>
diff --git a/templates/list.tpl b/templates/list.tpl
index bba6f3c..b8cd94e 100755
--- a/templates/list.tpl
+++ b/templates/list.tpl
@@ -4,7 +4,7 @@
<div class="listing contacts">
<div class="header">
- <h1>{tr}Contacts{/tr}</h1>
+ <h1>{if $listTitle}{$listTitle|escape}{else}{tr}Contacts{/tr}{/if}</h1>
</div>
<div class="body">
diff --git a/templates/menu_contact.tpl b/templates/menu_contact.tpl
index 56913cf..c53c197 100755
--- a/templates/menu_contact.tpl
+++ b/templates/menu_contact.tpl
@@ -1,8 +1,10 @@
{strip}
{if $packageMenuTitle}<a class="dropdown-toggle" data-toggle="dropdown" href="#"> {tr}{$packageMenuTitle}{/tr} <b class="caret"></b></a>{/if}
<ul class="{$packageMenuClass}">
- <li><a class="item" href="{$smarty.const.CONTACT_PKG_URL}list_contacts.php">{biticon ipackage="icons" iname="view-list" iexplain="List contacts" ilocation=menu}</a></li>
- {if $gBitUser->isAdmin() || $gBitUser->hasPermission( 'p_contact_edit' ) }
+ <li><a class="item" href="{$smarty.const.CONTACT_PKG_URL}list_people.php">{biticon ipackage="icons" iname="view-list" iexplain="List People" ilocation=menu}</a></li>
+ <li><a class="item" href="{$smarty.const.CONTACT_PKG_URL}list_businesses.php">{biticon ipackage="icons" iname="view-list" iexplain="List Businesses" ilocation=menu}</a></li>
+ <li><a class="item" href="{$smarty.const.CONTACT_PKG_URL}list_contacts.php">{biticon ipackage="icons" iname="system-search" iexplain="All Contacts" ilocation=menu}</a></li>
+ {if $gBitUser->isAdmin() || $gBitUser->hasPermission( 'p_contact_update' ) }
<li><a class="item" href="{$smarty.const.CONTACT_PKG_URL}add_person.php">{biticon ipackage="icons" iname="contact-new-symbolic" iexplain="Add Person" ilocation=menu}</a></li>
<li><a class="item" href="{$smarty.const.CONTACT_PKG_URL}add_business.php">{biticon ipackage="icons" iname="address-book-new-symbolic" iexplain="Add Business" ilocation=menu}</a></li>
{/if}