summaryrefslogtreecommitdiff
path: root/includes
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 /includes
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>
Diffstat (limited to 'includes')
-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
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();
}