checkin (broken)

This commit is contained in:
Rich Lott / Artful Robot 2025-02-18 17:33:27 +00:00
parent a01abba787
commit 06fec5daf7
21 changed files with 2025 additions and 376 deletions

View file

@ -1,239 +1,24 @@
<?php
/**
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
* DAOs provide an OOP-style facade for reading and writing database records.
*
* Generated from contactcats/xml/schema/CRM/Contactcats/ContactCategory.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
* (GenCodeChecksum:222d053743f68a2678d92b8a75b46316)
* DAOs are a primary source for metadata in older versions of CiviCRM (<5.74)
* and are required for some subsystems (such as APIv3).
*
* This stub provides compatibility. It is not intended to be modified in a
* substantive way. Property annotations may be added, but are not required.
* @property string $id
* @property string $contact_id
* @property string $category
* @property string $next_category
*/
use CRM_Contactcats_ExtensionUtil as E;
/**
* Database access object for the ContactCategory entity.
*/
class CRM_Contactcats_DAO_ContactCategory extends CRM_Core_DAO {
const EXT = E::LONG_NAME;
const TABLE_ADDED = '';
class CRM_Contactcats_DAO_ContactCategory extends CRM_Contactcats_DAO_Base {
/**
* Static instance to hold the table name.
*
* Required by older versions of CiviCRM (<5.74).
* @var string
*/
public static $_tableName = 'civicrm_contact_category';
/**
* Should CiviCRM log any modifications to this table in the civicrm_log table.
*
* @var bool
*/
public static $_log = FALSE;
/**
* Unique ID, corresponds to contact id
*
* @var int|string|null
* (SQL type: int unsigned)
* Note that values will be retrieved from the database as a string.
*/
public $id;
/**
* Same as id but for FormBuilder
*
* @var int|string
* (SQL type: int unsigned)
* Note that values will be retrieved from the database as a string.
*/
public $contact_id;
/**
* @var int|string
* (SQL type: int unsigned)
* Note that values will be retrieved from the database as a string.
*/
public $category;
/**
* @var int|string
* (SQL type: int unsigned)
* Note that values will be retrieved from the database as a string.
*/
public $next_category;
/**
* Class constructor.
*/
public function __construct() {
$this->__table = 'civicrm_contact_category';
parent::__construct();
}
/**
* Returns localized title of this entity.
*
* @param bool $plural
* Whether to return the plural version of the title.
*/
public static function getEntityTitle($plural = FALSE) {
return $plural ? E::ts('Contact Categories') : E::ts('Contact Category');
}
/**
* Returns all the column names of this table
*
* @return array
*/
public static function &fields() {
if (!isset(Civi::$statics[__CLASS__]['fields'])) {
Civi::$statics[__CLASS__]['fields'] = [
'id' => [
'name' => 'id',
'type' => CRM_Utils_Type::T_INT,
'title' => E::ts('ID'),
'description' => E::ts('Unique ID, corresponds to contact id'),
'required' => TRUE,
'usage' => [
'import' => FALSE,
'export' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
'where' => 'civicrm_contact_category.id',
'table_name' => 'civicrm_contact_category',
'entity' => 'ContactCategory',
'bao' => 'CRM_Contactcats_DAO_ContactCategory',
'localizable' => 0,
'html' => [
'type' => 'Number',
],
'readonly' => TRUE,
'add' => NULL,
],
'contact_id' => [
'name' => 'contact_id',
'type' => CRM_Utils_Type::T_INT,
'title' => E::ts('Contact ID'),
'description' => E::ts('Same as id but for FormBuilder'),
'required' => TRUE,
'usage' => [
'import' => FALSE,
'export' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
'where' => 'civicrm_contact_category.contact_id',
'table_name' => 'civicrm_contact_category',
'entity' => 'ContactCategory',
'bao' => 'CRM_Contactcats_DAO_ContactCategory',
'localizable' => 0,
'FKClassName' => 'CRM_Contact_DAO_Contact',
'html' => [
'type' => 'EntityRef',
'label' => E::ts("Contact"),
],
'add' => NULL,
],
'category' => [
'name' => 'category',
'type' => CRM_Utils_Type::T_INT,
'title' => E::ts('Category'),
'required' => TRUE,
'usage' => [
'import' => FALSE,
'export' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
'where' => 'civicrm_contact_category.category',
'default' => '0',
'table_name' => 'civicrm_contact_category',
'entity' => 'ContactCategory',
'bao' => 'CRM_Contactcats_DAO_ContactCategory',
'localizable' => 0,
'html' => [
'type' => 'Select',
],
'pseudoconstant' => [
'optionGroupName' => 'contact_categories',
'optionEditPath' => 'civicrm/admin/options/contact_categories',
],
'add' => NULL,
],
'next_category' => [
'name' => 'next_category',
'type' => CRM_Utils_Type::T_INT,
'title' => E::ts('Next Category'),
'required' => TRUE,
'usage' => [
'import' => FALSE,
'export' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
'where' => 'civicrm_contact_category.next_category',
'default' => '0',
'table_name' => 'civicrm_contact_category',
'entity' => 'ContactCategory',
'bao' => 'CRM_Contactcats_DAO_ContactCategory',
'localizable' => 0,
'pseudoconstant' => [
'optionGroupName' => 'contact_categories',
'optionEditPath' => 'civicrm/admin/options/contact_categories',
],
'add' => NULL,
],
];
CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
}
return Civi::$statics[__CLASS__]['fields'];
}
/**
* Returns the list of fields that can be imported
*
* @param bool $prefix
*
* @return array
*/
public static function &import($prefix = FALSE) {
$r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contact_category', $prefix, []);
return $r;
}
/**
* Returns the list of fields that can be exported
*
* @param bool $prefix
*
* @return array
*/
public static function &export($prefix = FALSE) {
$r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contact_category', $prefix, []);
return $r;
}
/**
* Returns the list of indices
*
* @param bool $localize
*
* @return array
*/
public static function indices($localize = TRUE) {
$indices = [
'index_category' => [
'name' => 'index_category',
'field' => [
0 => 'category',
],
'localizable' => FALSE,
'sig' => 'civicrm_contact_category::0::category',
],
];
return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* DAOs provide an OOP-style facade for reading and writing database records.
*
* DAOs are a primary source for metadata in older versions of CiviCRM (<5.74)
* and are required for some subsystems (such as APIv3).
*
* This stub provides compatibility. It is not intended to be modified in a
* substantive way. Property annotations may be added, but are not required.
* @property string $id
* @property string $contact_id
* @property string $category
* @property string $next_category
*/
class CRM_Contactcats_DAO_ContactCategoryDescription extends CRM_Contactcats_DAO_Base {
/**
* Required by older versions of CiviCRM (<5.74).
* @var string
*/
public static $_tableName = 'civicrm_contact_category_description';
}

View file

@ -0,0 +1,13 @@
<?php
namespace Civi\Api4;
/**
* ContactCategoryDefinition entity.
*
* Provided by the Contact Categories extension.
*
* @package Civi\Api4
*/
class ContactCategoryDefinition extends Generic\DAOEntity {
}

View file

@ -75,10 +75,47 @@ class CRM_Contactcats_ExtensionUtil {
return self::CLASS_PREFIX . '_' . str_replace('\\', '_', $suffix);
}
/**
* @return \CiviMix\Schema\SchemaHelperInterface
*/
public static function schema() {
if (!isset($GLOBALS['CiviMixSchema'])) {
pathload()->loadPackage('civimix-schema@5', TRUE);
}
return $GLOBALS['CiviMixSchema']->getHelper(static::LONG_NAME);
}
}
use CRM_Contactcats_ExtensionUtil as E;
($GLOBALS['_PathLoad'][0] ?? require __DIR__ . '/mixin/lib/pathload-0.php');
pathload()->addSearchDir(__DIR__ . '/mixin/lib');
spl_autoload_register('_contactcats_civix_class_loader', TRUE, TRUE);
function _contactcats_civix_class_loader($class) {
if ($class === 'CRM_Contactcats_DAO_Base') {
if (version_compare(CRM_Utils_System::version(), '5.74.beta', '>=')) {
class_alias('CRM_Core_DAO_Base', 'CRM_Contactcats_DAO_Base');
// ^^ Materialize concrete names -- encourage IDE's to pick up on this association.
}
else {
$realClass = 'CiviMix\\Schema\\Contactcats\\DAO';
class_alias($realClass, $class);
// ^^ Abstract names -- discourage IDE's from picking up on this association.
}
return;
}
// This allows us to tap-in to the installation process (without incurring real file-reads on typical requests).
if (strpos($class, 'CiviMix\\Schema\\Contactcats\\') === 0) {
// civimix-schema@5 is designed for backported use in download/activation workflows,
// where new revisions may become dynamically available.
pathload()->loadPackage('civimix-schema@5', TRUE);
CiviMix\Schema\loadClass($class);
}
}
/**
* (Delegated) Implements hook_civicrm_config().
*

View file

@ -27,18 +27,18 @@
</classloader>
<civix>
<namespace>CRM/Contactcats</namespace>
<format>23.02.1</format>
<format>25.01.1</format>
<angularModule>crmContactcats</angularModule>
</civix>
<mixins>
<mixin>mgd-php@1.0.0</mixin>
<mixin>scan-classes@1.0.0</mixin>
<mixin>setting-php@1.0.0</mixin>
<mixin>smarty-v2@1.0.1</mixin>
<mixin>entity-types-php@1.0.0</mixin>
<mixin>smarty-v2@1.0.3</mixin>
<mixin>mgd-php@1.0.0</mixin>
<mixin>ang-php@1.0.0</mixin>
<mixin>menu-xml@1.0.0</mixin>
<mixin>entity-types-php@2.0.0</mixin>
</mixins>
<upgrader>CRM_Contactcats_Upgrader</upgrader>
<upgrader>CiviMix\Schema\Contactcats\AutomaticUpgrader</upgrader>
</extension>

View file

@ -0,0 +1,40 @@
<?php
/**
* Auto-register entity declarations from `schema/*.entityType.php`.
*
* @mixinName entity-types-php
* @mixinVersion 2.0.0
* @since 5.73
*
* Changelog:
* - v2.0 scans /schema directory instead of /xml/schema/*
* - v2.0 supports only one entity per file
* - v2.0 adds 'module' key to each entity
*
* @param CRM_Extension_MixInfo $mixInfo
* On newer deployments, this will be an instance of MixInfo. On older deployments, Civix may polyfill with a work-a-like.
* @param \CRM_Extension_BootCache $bootCache
* On newer deployments, this will be an instance of BootCache. On older deployments, Civix may polyfill with a work-a-like.
*/
return function ($mixInfo, $bootCache) {
/**
* @param \Civi\Core\Event\GenericHookEvent $e
* @see CRM_Utils_Hook::entityTypes()
*/
Civi::dispatcher()->addListener('hook_civicrm_entityTypes', function ($e) use ($mixInfo) {
// When deactivating on a polyfill/pre-mixin system, listeners may not cleanup automatically.
if (!$mixInfo->isActive() || !is_dir($mixInfo->getPath('schema'))) {
return;
}
$files = (array) glob($mixInfo->getPath('schema/*.entityType.php'));
foreach ($files as $file) {
$entity = include $file;
$entity['module'] = $mixInfo->longName;
$e->entityTypes[$entity['name']] = $entity;
}
});
};

View file

@ -0,0 +1,28 @@
<?php
namespace CiviMix\Schema;
\pathload()->activatePackage('civimix-schema@5', __DIR__, [
'reloadable' => TRUE,
// The civimix-schema library specifically supports installation processes. From a
// bootstrap/service-availability POV, this is a rough environment which leads to
// the "Multi-Activation Issue" and "Multi-Download Issue". To adapt to them,
// civimix-schema follows "Reloadable Library" patterns.
// More information: https://github.com/totten/pathload-poc/blob/master/doc/issues.md
]);
// When reloading, we make newer instance of the Facade object.
$GLOBALS['CiviMixSchema'] = require __DIR__ . '/src/CiviMixSchema.php';
if (!interface_exists(__NAMESPACE__ . '\SchemaHelperInterface')) {
require __DIR__ . '/src/SchemaHelperInterface.php';
}
// \CiviMix\Schema\loadClass() is a facade. The facade should remain identical across versions.
if (!function_exists(__NAMESPACE__ . '\loadClass')) {
function loadClass(string $class) {
return $GLOBALS['CiviMixSchema']->loadClass($class);
}
spl_autoload_register(__NAMESPACE__ . '\loadClass');
}

View file

@ -0,0 +1,181 @@
<?php
namespace CiviMix\Schema;
use Civi\Test\Invasive;
/**
* The "AutomaticUpgrader" will create and destroy the SQL tables
* using schema files (`SchemaHelper`). It also calls-out to any custom
* upgrade code (eg `CRM_Myext_Upgrader`).
*
* To simplify backport considerations, `AutomaticUpgrader` does not have formal name.
* It is accessed via aliases like "CiviMix\Schema\*\AutomaticUpgrader".
*
* Target: CiviCRM v5.38+
*/
return new class() implements \CRM_Extension_Upgrader_Interface {
use \CRM_Extension_Upgrader_IdentityTrait {
init as initIdentity;
}
/**
* Optionally delegate to "CRM_Myext_Upgrader" or "Civi\Myext\Upgrader".
*
* @var \CRM_Extension_Upgrader_Interface|null
*/
private $customUpgrader;
public function init(array $params) {
$this->initIdentity($params);
if ($info = $this->getInfo()) {
if ($class = $this->getDelegateUpgraderClass($info)) {
$this->customUpgrader = new $class();
$this->customUpgrader->init($params);
if ($errors = $this->checkDelegateCompatibility($this->customUpgrader)) {
throw new \CRM_Core_Exception("AutomaticUpgrader is not compatible with $class:\n" . implode("\n", $errors));
}
}
}
}
public function notify(string $event, array $params = []) {
$info = $this->getInfo();
if (!$info) {
return;
}
if ($event === 'install') {
$GLOBALS['CiviMixSchema']->getHelper($this->getExtensionKey())->install();
}
if ($this->customUpgrader) {
$result = $this->customUpgrader->notify($event, $params);
// for upgrade checks, we need to pass check results up to the caller
// (for now - could definitely be more elegant!)
if ($event === 'upgrade') {
return $result;
}
}
if ($event === 'uninstall') {
$GLOBALS['CiviMixSchema']->getHelper($this->getExtensionKey())->uninstall();
}
}
/**
* Civix-based extensions have a conventional name for their upgrader class ("CRM_Myext_Upgrader"
* or "Civi\Myext\Upgrader"). Figure out if this class exists.
*
* @param \CRM_Extension_Info $info
* @return string|null
* Ex: 'CRM_Myext_Upgrader' or 'Civi\Myext\Upgrader'
*/
public function getDelegateUpgraderClass(\CRM_Extension_Info $info): ?string {
$candidates = [];
if (!empty($info->civix['namespace'])) {
$namespace = $info->civix['namespace'];
$candidates[] = sprintf('%s_Upgrader', str_replace('/', '_', $namespace));
$candidates[] = sprintf('%s\\Upgrader', str_replace('/', '\\', $namespace));
}
foreach ($candidates as $candidate) {
if (class_exists($candidate)) {
return $candidate;
}
}
return NULL;
}
public function getInfo(): ?\CRM_Extension_Info {
try {
return \CRM_Extension_System::singleton()->getMapper()->keyToInfo($this->extensionName);
}
catch (\CRM_Extension_Exception_ParseException $e) {
\Civi::log()->error("Parse error in extension " . $this->extensionName . ": " . $e->getMessage());
return NULL;
}
}
/**
* @param \CRM_Extension_Upgrader_Interface $upgrader
* @return array
* List of error messages.
*/
public function checkDelegateCompatibility($upgrader): array {
$class = get_class($upgrader);
$errors = [];
if (!($upgrader instanceof \CRM_Extension_Upgrader_Base)) {
$errors[] = "$class is not based on CRM_Extension_Upgrader_Base.";
return $errors;
}
// In the future, we will probably modify AutomaticUpgrader to build its own
// sequence of revisions (based on other sources of data). AutomaticUpgrader
// is only regarded as compatible with classes that strictly follow the standard revision-model.
$methodNames = [
'appendTask',
'onUpgrade',
'getRevisions',
'getCurrentRevision',
'setCurrentRevision',
'enqueuePendingRevisions',
'hasPendingRevisions',
];
foreach ($methodNames as $methodName) {
$method = new \ReflectionMethod($upgrader, $methodName);
if ($method->getDeclaringClass()->getName() !== 'CRM_Extension_Upgrader_Base') {
$errors[] = "To ensure future interoperability, AutomaticUpgrader only supports {$class}::{$methodName}() if it's inherited from CRM_Extension_Upgrader_Base";
}
}
return $errors;
}
public function __set($property, $value) {
switch ($property) {
// _queueAdapter() needs these properties.
case 'ctx':
case 'queue':
if (!$this->customUpgrader) {
throw new \RuntimeException("AutomaticUpgrader($this->extensionName): Cannot assign delegated property: $property (No custom-upgrader found)");
}
// "Invasive": unlike QueueTrait, we are not in the same class as the recipient. And we can't replace previously-published QueueTraits.
Invasive::set([$this->customUpgrader, $property], $value);
return;
}
throw new \RuntimeException("AutomaticUpgrader($this->extensionName): Cannot assign unknown property: $property");
}
public function __get($property) {
switch ($property) {
// _queueAdapter() needs these properties.
case 'ctx':
case 'queue':
if (!$this->customUpgrader) {
throw new \RuntimeException("AutomaticUpgrader($this->extensionName): Cannot read delegated property: $property (No custom-upgrader found)");
}
// "Invasive": Unlike QueueTrait, we are not in the same class as the recipient. And we can't replace previously-published QueueTraits.
return Invasive::get([$this->customUpgrader, $property]);
}
throw new \RuntimeException("AutomaticUpgrader($this->extensionName): Cannot read unknown property: $property");
}
public function __call($name, $arguments) {
if ($this->customUpgrader) {
return call_user_func_array([$this->customUpgrader, $name], $arguments);
}
else {
throw new \RuntimeException("AutomaticUpgrader($this->extensionName): Cannot delegate method $name (No custom-upgrader found)");
}
}
};

View file

@ -0,0 +1,46 @@
<?php
namespace CiviMix\Schema;
/**
* This object is known as $GLOBALS['CiviMixSchema']. It is a reloadable service-object.
* (It may be reloaded if you enable a new extension that includes an upgraded copy.)
*/
return new class() {
/**
* @var string
* Regular expression. Note the 2 groupings. $m[1] identifies a per-extension namespace. $m[2] identifies the actual class.
*/
private $regex = ';^CiviMix\\\Schema\\\(\w+)\\\(AutomaticUpgrader|DAO)$;';
/**
* If someone requests a class like:
*
* CiviMix\Schema\MyExt\AutomaticUpgrader
*
* then load the latest version of:
*
* civimix-schema/src/Helper.php
*/
public function loadClass(string $class) {
if (preg_match($this->regex, $class, $m)) {
$absPath = __DIR__ . DIRECTORY_SEPARATOR . $m[2] . '.php';
class_alias(get_class(require $absPath), $class);
}
}
/**
* @param string $extensionKey
* Ex: 'org.civicrm.flexmailer'
* @return \CiviMix\Schema\SchemaHelperInterface
*/
public function getHelper(string $extensionKey) {
$store = &\Civi::$statics['CiviMixSchema-helpers'];
if (!isset($store[$extensionKey])) {
$class = get_class(require __DIR__ . '/SchemaHelper.php');
$store[$extensionKey] = new $class($extensionKey);
}
return $store[$extensionKey];
}
};

View file

@ -0,0 +1,350 @@
<?php
namespace CiviMix\Schema;
/**
* To simplify backport considerations, `DAO` does not have formal name.
* It is accessed via aliases like "CiviMix\Schema\*\DAO".
*
* Target: TBD (5.38+? 5.51+)
*/
return new class() extends \CRM_Core_DAO {
public function __construct() {
if (strpos(static::class, '@') !== FALSE) {
// Template instance. Fake news!
return;
}
parent::__construct();
// Historically a generated DAO would have one class variable per field.
// To prevent undefined property warnings, this dynamic DAO mimics that by
// initializing the object with a property for each field.
foreach (static::getEntityDefinition()['getFields']() as $name => $field) {
$this->$name = NULL;
}
}
/**
* @inheritDoc
*/
public function keys(): array {
$keys = [];
foreach (static::getEntityDefinition()['getFields']() as $name => $field) {
if (!empty($field['primary_key'])) {
$keys[] = $name;
}
}
return $keys;
}
public static function getEntityTitle($plural = FALSE) {
$info = static::getEntityInfo();
return ($plural && isset($info['title_plural'])) ? $info['title_plural'] : $info['title'];
}
/**
* @inheritDoc
*/
public static function getEntityPaths(): array {
$definition = static::getEntityDefinition();
if (isset($definition['getPaths'])) {
return $definition['getPaths']();
}
return [];
}
public static function getLabelField(): ?string {
return static::getEntityInfo()['label_field'] ?? NULL;
}
/**
* @inheritDoc
*/
public static function getEntityDescription(): ?string {
return static::getEntityInfo()['description'] ?? NULL;
}
/**
* @inheritDoc
*/
public static function getTableName() {
return static::getEntityDefinition()['table'];
}
/**
* @inheritDoc
*/
public function getLog(): bool {
return static::getEntityInfo()['log'] ?? FALSE;
}
/**
* @inheritDoc
*/
public static function getEntityIcon(string $entityName, ?int $entityId = NULL): ?string {
return static::getEntityInfo()['icon'] ?? NULL;
}
/**
* @inheritDoc
*/
protected static function getTableAddVersion(): string {
return static::getEntityInfo()['add'] ?? '1.0';
}
/**
* @inheritDoc
*/
public static function getExtensionName(): ?string {
return static::getEntityDefinition()['module'];
}
/**
* @inheritDoc
*/
public static function &fields() {
$fields = [];
foreach (static::getSchemaFields() as $field) {
$key = $field['uniqueName'] ?? $field['name'];
unset($field['uniqueName']);
$fields[$key] = $field;
}
return $fields;
}
private static function getSchemaFields(): array {
if (!isset(\Civi::$statics[static::class]['fields'])) {
\Civi::$statics[static::class]['fields'] = static::loadSchemaFields();
}
return \Civi::$statics[static::class]['fields'];
}
private static function loadSchemaFields(): array {
$fields = [];
$entityDef = static::getEntityDefinition();
$baoName = \CRM_Core_DAO_AllCoreTables::getBAOClassName(static::class);
foreach ($entityDef['getFields']() as $fieldName => $fieldSpec) {
$field = [
'name' => $fieldName,
'type' => !empty($fieldSpec['data_type']) ? \CRM_Utils_Type::getValidTypes()[$fieldSpec['data_type']] : static::getCrmTypeFromSqlType($fieldSpec['sql_type']),
'title' => $fieldSpec['title'],
'description' => $fieldSpec['description'] ?? NULL,
];
if (!empty($fieldSpec['required'])) {
$field['required'] = TRUE;
}
if (strpos($fieldSpec['sql_type'], 'decimal(') === 0) {
$precision = self::getFieldLength($fieldSpec['sql_type']);
$field['precision'] = array_map('intval', explode(',', $precision));
}
foreach (['maxlength', 'size', 'rows', 'cols'] as $attr) {
if (isset($fieldSpec['input_attrs'][$attr])) {
$field[$attr] = $fieldSpec['input_attrs'][$attr];
unset($fieldSpec['input_attrs'][$attr]);
}
}
if (strpos($fieldSpec['sql_type'], 'char(') !== FALSE) {
$length = self::getFieldLength($fieldSpec['sql_type']);
if (!isset($field['size'])) {
$field['size'] = constant(static::getDefaultSize($length));
}
if (!isset($field['maxlength'])) {
$field['maxlength'] = $length;
}
}
$usage = $fieldSpec['usage'] ?? [];
$field['usage'] = [
'import' => in_array('import', $usage),
'export' => in_array('export', $usage),
'duplicate_matching' => in_array('duplicate_matching', $usage),
'token' => in_array('token', $usage),
];
if ($field['usage']['import']) {
$field['import'] = TRUE;
}
$field['where'] = $entityDef['table'] . '.' . $field['name'];
if ($field['usage']['export'] || (!$field['usage']['export'] && $field['usage']['import'])) {
$field['export'] = $field['usage']['export'];
}
if (!empty($fieldSpec['contact_type'])) {
$field['contactType'] = $fieldSpec['contact_type'];
}
if (!empty($fieldSpec['permission'])) {
$field['permission'] = $fieldSpec['permission'];
}
if (array_key_exists('default', $fieldSpec)) {
$field['default'] = isset($fieldSpec['default']) ? (string) $fieldSpec['default'] : NULL;
if (is_bool($fieldSpec['default'])) {
$field['default'] = $fieldSpec['default'] ? '1' : '0';
}
}
$field['table_name'] = $entityDef['table'];
$field['entity'] = $entityDef['name'];
$field['bao'] = $baoName;
$field['localizable'] = intval($fieldSpec['localizable'] ?? 0);
if (!empty($fieldSpec['localize_context'])) {
$field['localize_context'] = (string) $fieldSpec['localize_context'];
}
if (!empty($fieldSpec['entity_reference'])) {
if (!empty($fieldSpec['entity_reference']['entity'])) {
$field['FKClassName'] = static::getDAONameForEntity($fieldSpec['entity_reference']['entity']);
}
if (!empty($fieldSpec['entity_reference']['dynamic_entity'])) {
$field['DFKEntityColumn'] = $fieldSpec['entity_reference']['dynamic_entity'];
}
$field['FKColumnName'] = $fieldSpec['entity_reference']['key'] ?? 'id';
}
if (!empty($fieldSpec['component'])) {
$field['component'] = $fieldSpec['component'];
}
if (!empty($fieldSpec['serialize'])) {
$field['serialize'] = $fieldSpec['serialize'];
}
if (!empty($fieldSpec['unique_name'])) {
$field['uniqueName'] = $fieldSpec['unique_name'];
}
if (!empty($fieldSpec['unique_title'])) {
$field['unique_title'] = $fieldSpec['unique_title'];
}
if (!empty($fieldSpec['deprecated'])) {
$field['deprecated'] = TRUE;
}
if (!empty($fieldSpec['input_attrs'])) {
$field['html'] = \CRM_Utils_Array::rekey($fieldSpec['input_attrs'], function($str) {
return \CRM_Utils_String::convertStringToCamel($str, FALSE);
});
}
if (!empty($fieldSpec['input_type'])) {
$field['html']['type'] = $fieldSpec['input_type'];
}
if (!empty($fieldSpec['pseudoconstant'])) {
$field['pseudoconstant'] = \CRM_Utils_Array::rekey($fieldSpec['pseudoconstant'], function($str) {
return \CRM_Utils_String::convertStringToCamel($str, FALSE);
});
if (!isset($field['pseudoconstant']['optionEditPath']) && !empty($field['pseudoconstant']['optionGroupName'])) {
$field['pseudoconstant']['optionEditPath'] = 'civicrm/admin/options/' . $field['pseudoconstant']['optionGroupName'];
}
}
if (!empty($fieldSpec['primary_key']) || !empty($fieldSpec['readonly'])) {
$field['readonly'] = TRUE;
}
$field['add'] = $fieldSpec['add'] ?? NULL;
$fields[$fieldName] = $field;
}
\CRM_Core_DAO_AllCoreTables::invoke(static::class, 'fields_callback', $fields);
return $fields;
}
private static function getFieldLength($sqlType): ?string {
$open = strpos($sqlType, '(');
if ($open) {
return substr($sqlType, $open + 1, -1);
}
return NULL;
}
/**
* @inheritDoc
*/
public static function indices(bool $localize = TRUE): array {
$definition = static::getEntityDefinition();
$indices = [];
if (isset($definition['getIndices'])) {
$fields = $definition['getFields']();
foreach ($definition['getIndices']() as $name => $info) {
$index = [
'name' => $name,
'field' => [],
'localizable' => FALSE,
];
foreach ($info['fields'] as $fieldName => $length) {
if (!empty($fields[$fieldName]['localizable'])) {
$index['localizable'] = TRUE;
}
if (is_int($length)) {
$fieldName .= "($length)";
}
$index['field'][] = $fieldName;
}
if (!empty($info['unique'])) {
$index['unique'] = TRUE;
}
$index['sig'] = ($definition['table']) . '::' . intval($info['unique'] ?? 0) . '::' . implode('::', $index['field']);
$indices[$name] = $index;
}
}
return ($localize && $indices) ? \CRM_Core_DAO_AllCoreTables::multilingualize(static::class, $indices) : $indices;
}
public static function getEntityDefinition(): array {
if (!isset(\Civi::$statics[static::class]['definition'])) {
$class = new \ReflectionClass(static::class);
$file = substr(basename($class->getFileName()), 0, -4) . '.entityType.php';
$folder = dirname($class->getFileName(), 4) . '/schema/';
$path = $folder . $file;
\Civi::$statics[static::class]['definition'] = include $path;
}
return \Civi::$statics[static::class]['definition'];
}
private static function getEntityInfo(): array {
return static::getEntityDefinition()['getInfo']();
}
private static function getDefaultSize($length) {
// Infer from <length> tag if <size> was not explicitly set or was invalid
// This map is slightly different from CRM_Core_Form_Renderer::$_sizeMapper
// Because we usually want fields to render as smaller than their maxlength
$sizes = [
2 => 'TWO',
4 => 'FOUR',
6 => 'SIX',
8 => 'EIGHT',
16 => 'TWELVE',
32 => 'MEDIUM',
64 => 'BIG',
];
foreach ($sizes as $size => $name) {
if ($length <= $size) {
return "CRM_Utils_Type::$name";
}
}
return 'CRM_Utils_Type::HUGE';
}
private static function getCrmTypeFromSqlType(string $sqlType): int {
[$type] = explode('(', $sqlType);
switch ($type) {
case 'varchar':
case 'char':
return \CRM_Utils_Type::T_STRING;
case 'datetime':
return \CRM_Utils_Type::T_DATE + \CRM_Utils_Type::T_TIME;
case 'decimal':
return \CRM_Utils_Type::T_MONEY;
case 'double':
return \CRM_Utils_Type::T_FLOAT;
case 'int unsigned':
case 'tinyint':
return \CRM_Utils_Type::T_INT;
default:
return constant('CRM_Utils_Type::T_' . strtoupper($type));
}
}
private static function getDAONameForEntity($entity) {
if (is_callable(['CRM_Core_DAO_AllCoreTables', 'getDAONameForEntity'])) {
return \CRM_Core_DAO_AllCoreTables::getDAONameForEntity($entity);
}
else {
return \CRM_Core_DAO_AllCoreTables::getFullName($entity);
}
}
};

View file

@ -0,0 +1,87 @@
<?php
namespace CiviMix\Schema;
/**
* The "SchemaHelper" class provides helper methods for an extension to manage its schema.
*
* Target: CiviCRM v5.38+
*/
return new class() implements SchemaHelperInterface {
/**
* @var string
*
* Ex: 'org.civicrm.flexmailer'
*/
private $key;
private $sqlGenerator;
public function __construct(?string $key = NULL) {
$this->key = $key;
}
public function install(): void {
$this->runSqls([$this->generateInstallSql()]);
}
public function uninstall(): void {
$this->runSqls([$this->generateUninstallSql()]);
}
public function generateInstallSql(): ?string {
return $this->getSqlGenerator()->getCreateTablesSql();
}
public function generateUninstallSql(): string {
return $this->getSqlGenerator()->getDropTablesSql();
}
public function hasSchema(): bool {
return file_exists($this->getExtensionDir() . '/schema');
}
public function arrayToSql(array $entityDefn): string {
$generator = $this->getSqlGenerator();
return $generator->generateCreateTableWithConstraintSql($entityDefn);
}
// FIXME: You can add more utility methods here
// public function addTables(array $names): void {
// throw new \RuntimeException("TODO: Install a single tables");
// }
//
// public function addColumn(string $table, string $column): void {
// throw new \RuntimeException("TODO: Install a single tables");
// }
/**
* @param array $sqls
* List of SQL scripts.
*/
private function runSqls(array $sqls): void {
foreach ($sqls as $sql) {
\CRM_Utils_File::runSqlQuery(CIVICRM_DSN, $sql);
}
}
protected function getExtensionDir(): string {
if ($this->key === 'civicrm') {
$r = new \ReflectionClass('CRM_Core_ClassLoader');
return dirname($r->getFileName(), 3);
}
$system = \CRM_Extension_System::singleton();
return $system->getMapper()->keyToBasePath($this->key);
}
private function getSqlGenerator() {
if ($this->sqlGenerator === NULL) {
$gen = require __DIR__ . '/SqlGenerator.php';
$this->sqlGenerator = $gen::createFromFolder($this->key, $this->getExtensionDir() . '/schema', $this->key === 'civicrm');
}
return $this->sqlGenerator;
}
};

View file

@ -0,0 +1,30 @@
<?php
namespace CiviMix\Schema;
/**
* The SchemaHelperInterface provides utility methods for managing the schema
* in an extension (e.g. installing or uninstalling all SQL tables).
*
* The interface is implemented by the reloadable library (civimix-schema@5). To ensure
* newer revisions of the library can be loaded, the implementation is an anonymous-class,
* and the interface uses soft type-hints.
*
* @method bool hasSchema()
*
* @method void install()
* @method void uninstall()
*
* @method string generateInstallSql()
* @method string generateUninstallSql()
*
* TODO: void addTables(string[] $tables)
* TODO: void addColumn(string $table, string $column)
*
* To see the latest implementation:
*
* @see ./SchemaHelper.php
*/
interface SchemaHelperInterface {
}

View file

@ -0,0 +1,232 @@
<?php
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
return new class() {
/**
* @var array
*/
private $entities;
/**
* @var callable
*/
private $findExternalTable;
/**
* @param string $module
* Ex: 'civicrm' or 'org.example.mymodule'
* @param string $path
* Ex: '/var/www/sites/all/modules/civicrm/schema'
* @param bool $isolated
* TRUE if these entities should be a self-sufficient (i.e. no external references).
* FALSE if these entities may include references to other tables.
* TRUE would make sense in (eg) civicrm-core, before installation or bootstrap
* FALSE would make sense in (eg) an extension on an active system.
*
* @return static
*/
public static function createFromFolder(string $module, string $path, bool $isolated) {
$files = \CRM_Utils_File::findFiles($path, '*.entityType.php');
$entities = [];
foreach ($files as $file) {
$entity = include $file;
$entity['module'] = $module;
$entities[$entity['name']] = $entity;
}
$findExternalTable = $isolated ? NULL : (['CRM_Core_DAO_AllCoreTables', 'getTableForEntityName']);
return new static($entities, $findExternalTable);
}
public function __construct(array $entities = [], ?callable $findExternalTable = NULL) {
// Filter out entities without a sql table (e.g. Afform)
$this->entities = array_filter($entities, function($entity) {
return !empty($entity['table']);
});
$this->findExternalTable = $findExternalTable ?: function() {
return NULL;
};
}
public function getEntities(): array {
return $this->entities;
}
public function getCreateTablesSql(): string {
$sql = '';
foreach ($this->entities as $entity) {
$sql .= $this->generateCreateTableSql($entity);
}
foreach ($this->entities as $entity) {
$sql .= $this->generateConstraintsSql($entity);
}
return $sql;
}
public function getCreateTableSql(string $entityName): string {
$sql = $this->generateCreateTableSql($this->entities[$entityName]);
$sql .= $this->generateConstraintsSql($this->entities[$entityName]);
return $sql;
}
public function getDropTablesSql(): string {
$sql = "SET FOREIGN_KEY_CHECKS=0;\n";
foreach ($this->entities as $entity) {
$sql .= "DROP TABLE IF EXISTS `{$entity['table']}`;\n";
}
$sql .= "SET FOREIGN_KEY_CHECKS=1;\n";
return $sql;
}
public function generateCreateTableWithConstraintSql(array $entity): string {
$definition = $this->getTableDefinition($entity);
$constraints = $this->getTableConstraints($entity);
$sql = "CREATE TABLE IF NOT EXISTS `{$entity['table']}` (\n " .
implode(",\n ", $definition);
if ($constraints) {
$sql .= ",\n " . implode(",\n ", $constraints);
}
$sql .= "\n)\n" . $this->getTableOptions() . ";\n";
return $sql;
}
private function generateCreateTableSql(array $entity): string {
$definition = $this->getTableDefinition($entity);
$sql = "CREATE TABLE `{$entity['table']}` (\n " .
implode(",\n ", $definition) .
"\n)\n" .
$this->getTableOptions() . ";\n";
return $sql;
}
private function getTableDefinition(array $entity): array {
$definition = [];
$primaryKeys = [];
foreach ($entity['getFields']() as $fieldName => $field) {
if (!empty($field['primary_key'])) {
$primaryKeys[] = "`$fieldName`";
}
$definition[] = "`$fieldName` " . self::generateFieldSql($field);
}
if ($primaryKeys) {
$definition[] = 'PRIMARY KEY (' . implode(', ', $primaryKeys) . ')';
}
$indices = isset($entity['getIndices']) ? $entity['getIndices']() : [];
foreach ($indices as $indexName => $index) {
$indexFields = [];
foreach ($index['fields'] as $fieldName => $length) {
$indexFields[] = "`$fieldName`" . (is_int($length) ? "($length)" : '');
}
$definition[] = (!empty($index['unique']) ? 'UNIQUE ' : '') . "INDEX `$indexName`(" . implode(', ', $indexFields) . ')';
}
return $definition;
}
private function generateConstraintsSql(array $entity): string {
$constraints = $this->getTableConstraints($entity);
$sql = '';
if ($constraints) {
$sql .= "ALTER TABLE `{$entity['table']}`\n ";
$sql .= 'ADD ' . implode(",\n ADD ", $constraints) . ";\n";
}
return $sql;
}
private function getTableConstraints(array $entity): array {
$constraints = [];
foreach ($entity['getFields']() as $fieldName => $field) {
if (!empty($field['entity_reference']['entity'])) {
$constraint = "CONSTRAINT `FK_{$entity['table']}_$fieldName` FOREIGN KEY (`$fieldName`)" .
" REFERENCES `" . $this->getTableForEntity($field['entity_reference']['entity']) . "`(`{$field['entity_reference']['key']}`)";
if (!empty($field['entity_reference']['on_delete'])) {
$constraint .= " ON DELETE {$field['entity_reference']['on_delete']}";
}
$constraints[] = $constraint;
}
}
return $constraints;
}
public static function generateFieldSql(array $field) {
$fieldSql = $field['sql_type'];
if (!empty($field['collate'])) {
$fieldSql .= " COLLATE {$field['collate']}";
}
// Required fields and booleans cannot be null
// FIXME: For legacy support this doesn't force boolean fields to be NOT NULL... but it really should.
if (!empty($field['required'])) {
$fieldSql .= ' NOT NULL';
}
else {
$fieldSql .= ' NULL';
}
if (!empty($field['auto_increment'])) {
$fieldSql .= " AUTO_INCREMENT";
}
$fieldSql .= self::getDefaultSql($field);
if (!empty($field['description'])) {
$fieldSql .= " COMMENT '" . \CRM_Core_DAO::escapeString($field['description']) . "'";
}
return $fieldSql;
}
private static function getDefaultSql(array $field): string {
// Booleans always have a default
if ($field['sql_type'] === 'boolean') {
$field += ['default' => FALSE];
}
if (!array_key_exists('default', $field)) {
return '';
}
if (is_null($field['default'])) {
$default = 'NULL';
}
elseif (is_bool($field['default'])) {
$default = $field['default'] ? 'TRUE' : 'FALSE';
}
elseif (!is_string($field['default']) || str_starts_with($field['default'], 'CURRENT_TIMESTAMP')) {
$default = $field['default'];
}
else {
$default = "'" . \CRM_Core_DAO::escapeString($field['default']) . "'";
}
return ' DEFAULT ' . $default;
}
private function getTableForEntity(string $entityName): string {
return $this->entities[$entityName]['table'] ?? call_user_func($this->findExternalTable, $entityName);
}
/**
* Get general/default options for use in CREATE TABLE (eg character set, collation).
*/
private function getTableOptions(): string {
if (!Civi\Core\Container::isContainerBooted()) {
// Pre-installation environment ==> aka new install
$collation = CRM_Core_BAO_SchemaHandler::DEFAULT_COLLATION;
}
else {
// What character-set is used for CiviCRM core schema? What collation?
// This depends on when the DB was *initialized*:
// - civicrm-core >= 5.33 has used `CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`
// - civicrm-core 4.3-5.32 has used `CHARACTER SET utf8 COLLATE utf8_unicode_ci`
// - civicrm-core <= 4.2 -- I haven't checked, but it's probably the same.
// Some systems have migrated (eg APIv3's `System.utf8conversion`), but (as of Feb 2024)
// we haven't made any effort to push to this change.
$collation = \CRM_Core_BAO_SchemaHandler::getInUseCollation();
}
$characterSet = (stripos($collation, 'utf8mb4') !== FALSE) ? 'utf8mb4' : 'utf8';
return "ENGINE=InnoDB DEFAULT CHARACTER SET {$characterSet} COLLATE {$collation} ROW_FORMAT=DYNAMIC";
}
};

711
mixin/lib/pathload-0.php Normal file
View file

@ -0,0 +1,711 @@
<?php
//phpcs:disable
/*
PHP PathLoad (MIT License)
Copyright (c) 2022-2024 CiviCRM LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
namespace {
if (isset($GLOBALS['_PathLoad'][0])) {
return $GLOBALS['_PathLoad'][0];
}
if (!interface_exists('PathLoadInterface')) {
/**
* The PathLoad interface is defined via soft signatures ("duck-typing") rather than hard signatures.
* This matters when multiple parties inject PathLoad support onto a pre-existing framework.
* In the event of future language changes or contract changes, the soft signatures
* give wiggle-room to address interoperability/conversion.
*
* ==== PACKAGE CONSUMER APIS ===
*
* (PathLoad v0) Enable autoloading of `*.phar`, `*.php`, and folders from a search directory.
*
* @method PathLoadInterface addSearchDir(string $baseDir)
*
* (Pathload v0) Enable autoloading of a specific `*.phar`, `*.php`, or folder.
* Useful for non-standard file-layout.
*
* @method PathLoadInterface addSearchItem(string $name, string $version, string $file, ?string $type = NULL)
*
* (PathLoad v0) Add auto-loading hints. If someone requests a class in $namespace, then we load $package.
*
* Consecutive/identical calls to addNamespace() are de-duplicated.
*
* @method PathLoadInterface addNamespace(string $package, $namespaces)
*
* (Pathload v0) When you need resources from a package, call loadPackage().
* This locates the relevant files and loads them.
* If you use namespace-autoloading, then this shouldn't be necessary.
*
* @method PathLoadInterface loadPackage(string $majorName)
*
* ==== PACKAGE PROVIDER APIS ====
*
* (PathLoad v0) Activate your package. This allows you to add metadata about activating
* your own package. In particular, this may be necessary if you have transitive
* dependencies. This would be appropriate for single-file PHP package (`cloud-io@1.0.0.php`)
* which lack direct support for `pathload.json`.
*
* @method PathLoadInterface activatePackage(string $majorName, string $dir, array $config)
*/
interface PathLoadInterface {
}
}
}
namespace PathLoad\V0 {
if (!class_exists('PathLoad')) {
function doRequire(string $file) {
require_once $file;
}
/**
* Locate version-compliant instances of PathLoad.
*/
class Versions implements \ArrayAccess {
public $top;
public function __construct($top) {
$this->top = $top;
}
public function offsetExists($version): bool {
return ($version === 'top' || $version <= $this->top->version);
}
public function offsetGet($version): ?\PathLoadInterface {
if ($version === 'top' || $version <= $this->top->version) {
return $this->top;
}
return NULL;
}
public function offsetSet($offset, $value): void {
error_log("Cannot overwrite PathLoad[$offset]");
}
public function offsetUnset($offset): void {
error_log("Cannot remove PathLoad[$offset]");
}
}
class Package {
/**
* Split a package identifier into its parts.
*
* @param string $package
* Ex: 'foobar@1.2.3'
* @return array
* Tuple: [$majorName, $name, $version]
* Ex: 'foobar@1', 'foobar', '1.2.3'
*/
public static function parseExpr(string $package): array {
if (strpos($package, '@') === FALSE) {
throw new \RuntimeException("Malformed package name: $package");
}
[$prefix, $suffix] = explode('@', $package, 2);
$prefix = str_replace('/', '~', $prefix);
[$major] = explode('.', $suffix, 2);
return ["$prefix@$major", $prefix, $suffix];
}
public static function parseFileType(string $file): array {
if (substr($file, -4) === '.php') {
return ['php', substr(basename($file), 0, -4)];
}
elseif (substr($file, '-5') === '.phar') {
return ['phar', substr(basename($file), 0, -5)];
}
elseif (is_dir($file)) {
return ['dir', basename($file)];
}
else {
return [NULL, NULL];
}
}
/**
* @param string $file
* Ex: '/var/www/app-1/lib/foobar@.1.2.3.phar'
* @return \PathLoad\Vn\Package|null
*/
public static function create(string $file): ?Package {
[$type, $base] = self::parseFileType($file);
if ($type === NULL) {
return NULL;
}
$self = new Package();
[$self->majorName, $self->name, $self->version] = static::parseExpr($base);
$self->file = $file;
$self->type = $type;
return $self;
}
/**
* @var string
* Ex: '/var/www/app-1/lib/cloud-file-io@1.2.3.phar'
*/
public $file;
/**
* @var string
* Ex: 'cloud-file-io'
*/
public $name;
/**
* @var string
* Ex: 'cloud-file-io@1'
*/
public $majorName;
/**
* @var string
* Ex: '1.2.3'
*/
public $version;
/**
* @var string
* Ex: 'php' or 'phar' or 'dir'
*/
public $type;
public $reloadable = FALSE;
}
class Scanner {
/**
* @var array
* Array(string $id => [package => string, glob => string])
* @internal
*/
public $allRules = [];
/**
* @var array
* Array(string $id => [package => string, glob => string])
* @internal
*/
public $newRules = [];
/**
* @param array $rule
* Ex: ['package' => '*', 'glob' => '/var/www/lib/*@*']
* Ex: ['package' => 'cloud-file-io@1', 'glob' => '/var/www/lib/cloud-io@1*.phar'])
* @return void
*/
public function addRule(array $rule): void {
$id = static::id($rule);
$this->newRules[$id] = $this->allRules[$id] = $rule;
}
public function reset(): void {
$this->newRules = $this->allRules;
}
/**
* Evaluate any rules that have a chance of finding $packageHint.
*
* @param string $packageHint
* Give a hint about what package we're looking for.
* The scanner will try to target packages based on this hint.
* Ex: '*' or 'cloud-file-io'
* @return \Generator
* A list of packages. These may not be the exact package you're looking for.
* You should assimilate knowledge of all outputs because you may not get them again.
*/
public function scan(string $packageHint): \Generator {
yield from [];
foreach (array_keys($this->newRules) as $id) {
$searchRule = $this->newRules[$id];
if ($searchRule['package'] === '*' || $searchRule['package'] === $packageHint) {
unset($this->newRules[$id]);
if (isset($searchRule['glob'])) {
foreach ((array) glob($searchRule['glob']) as $file) {
if (($package = Package::create($file)) !== NULL) {
yield $package;
}
}
}
if (isset($searchRule['file'])) {
$package = new Package();
$package->file = $searchRule['file'];
$package->name = $searchRule['package'];
$package->majorName = $searchRule['package'] . '@' . explode('.', $searchRule['version'])[0];
$package->version = $searchRule['version'];
$package->type = $searchRule['type'] ?: Package::parseFileType($searchRule['file'])[0];
yield $package;
}
}
}
}
protected static function id(array $rule): string {
if (isset($rule['glob'])) {
return $rule['glob'];
}
elseif (isset($rule['file'])) {
return md5(implode(' ', [$rule['file'], $rule['package'], $rule['version']]));
}
else {
throw new \RuntimeException("Cannot identify rule: " . json_encode($rule));
}
}
}
class Psr0Loader {
/**
* @var array
* Ex: $paths['F']['Foo_'][0] = '/var/www/app/lib/foo@1.0.0/src/';
* @internal
*/
public $paths = [];
/**
* @param string $dir
* @param array $config
* Ex: ['Foo_' => ['src/']] or ['Foo_' => ['Foo_']]
*/
public function addAll(string $dir, array $config) {
$dir = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
foreach ($config as $prefix => $relPaths) {
$bucket = $prefix[0];
foreach ((array) $relPaths as $relPath) {
$this->paths[$bucket][$prefix][] = $dir . $relPath;
}
}
}
/**
* Loads the class file for a given class name.
*
* @param string $class The fully-qualified class name.
* @return mixed The mapped file name on success, or boolean false on failure.
*/
public function loadClass(string $class) {
$bucket = $class[0];
if (!isset($this->paths[$bucket])) {
return FALSE;
}
$file = DIRECTORY_SEPARATOR . str_replace(['_', '\\'], [DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR], $class) . '.php';
foreach ($this->paths[$bucket] as $prefix => $paths) {
if ($prefix === substr($class, 0, strlen($prefix))) {
foreach ($paths as $path) {
$fullFile = $path . $file;
if (file_exists($fullFile)) {
doRequire($fullFile);
return $fullFile;
}
}
}
}
return FALSE;
}
}
class Psr4Loader {
/**
* @var array
* Ex: $prefixes['Foo\\'][0] = '/var/www/app/lib/foo@1.0.0/src/']
* @internal
*/
public $prefixes = [];
public function addAll(string $dir, array $config) {
foreach ($config as $prefix => $relPaths) {
foreach ($relPaths as $relPath) {
$this->addNamespace($prefix, $dir . '/' . $relPath);
}
}
}
/**
* Adds a base directory for a namespace prefix.
*
* @param string $prefix
* The namespace prefix.
* @param string $baseDir
* A base directory for class files in the namespace.
* @return void
*/
private function addNamespace($prefix, $baseDir) {
$prefix = trim($prefix, '\\') . '\\';
$baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
if (isset($this->prefixes[$prefix]) === FALSE) {
$this->prefixes[$prefix] = [];
}
array_push($this->prefixes[$prefix], $baseDir);
}
/**
* Loads the class file for a given class name.
*
* @param string $class The fully-qualified class name.
* @return mixed The mapped file name on success, or boolean false on failure.
*/
public function loadClass(string $class) {
$prefix = $class;
while (FALSE !== $pos = strrpos($prefix, '\\')) {
$prefix = substr($class, 0, $pos + 1);
$relativeClass = substr($class, $pos + 1);
if ($mappedFile = $this->findRelativeClass($prefix, $relativeClass)) {
doRequire($mappedFile);
return $mappedFile;
}
$prefix = rtrim($prefix, '\\');
}
return FALSE;
}
/**
* Load the mapped file for a namespace prefix and relative class.
*
* @param string $prefix
* The namespace prefix.
* @param string $relativeClass
* The relative class name.
* @return string|FALSE
* Matched file name, or FALSE if none found.
*/
private function findRelativeClass($prefix, $relativeClass) {
if (isset($this->prefixes[$prefix]) === FALSE) {
return FALSE;
}
$relFile = str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php';
foreach ($this->prefixes[$prefix] as $baseDir) {
$file = $baseDir . $relFile;
if (file_exists($file)) {
return $file;
}
}
return FALSE;
}
}
class PathLoad implements \PathLoadInterface {
/**
* @var null|int
*/
public $version;
/**
* @var Scanner
* @internal
*/
public $scanner;
/**
* List of best-known versions for each package.
*
* Packages are loaded lazily. Once loaded, the data is moved to $loadedPackages.
*
* @var Package[]
* Ex: ['cloud-file-io@1' => new Package('/usr/share/php-pathload/cloud-file-io@1.2.3.phar',
* ...)]
* @internal
*/
public $availablePackages = [];
/**
* List of packages that have already been resolved.
*
* @var Package[]
* Ex: ['cloud-file-io@1' => new Package('/usr/share/php-pathload/cloud-file-io@1.2.3.phar',
* ...)] Note: If PathLoad version is super-ceded, then the loadedPackages may be instances of
* an old `Package` class. Be mindful of duck-type compatibility. We don't strictly need to
* retain this data, but it feels it'd be handy for debugging.
* @internal
*/
public $loadedPackages = [];
/**
* Log of package activations. Used to re-initialize class-loader if we upgrade.
*
* @var array
* @internal
*/
public $activatedPackages = [];
/**
* List of hints for class-loading. If someone tries to use a matching class, then
* load the corresponding package.
*
* Namespace-rules are evaluated lazily. Once evaluated, the data is removed.
*
* @var array
* Array(string $prefix => [string $package => string $package])
* Ex: ['Super\Cloud\IO\' => ['cloud-io@1' => 'cloud-io@1']
* @internal
*/
public $availableNamespaces;
/**
* @var \PathLoad\Vn\Psr0Loader
* @internal
*/
public $psr0;
/**
* @var \PathLoad\Vn\Psr4Loader
* @internal
*/
public $psr4;
/**
* @param int $version
* Identify the version being instantiated.
* @param \PathLoadInterface|null $old
* If this instance is a replacement for an older instance, then it will be passed in.
* @return \ArrayAccess
* Versioned work-a-like array.
*/
public static function create(int $version, ?\PathLoadInterface $old = NULL) {
if ($old !== NULL) {
$old->unregister();
}
$new = new static();
$new->version = $version;
$new->scanner = new Scanner();
$new->psr0 = new Psr0Loader();
$new->psr4 = new Psr4Loader();
$new->register();
// The exact protocol for assimilating $old instances may need change.
// This seems like a fair guess as long as old properties are forward-compatible.
if ($old === NULL) {
$baseDirs = getenv('PHP_PATHLOAD') ? explode(PATH_SEPARATOR, getenv('PHP_PATHLOAD')) : [];
foreach ($baseDirs as $baseDir) {
$new->addSearchDir($baseDir);
}
}
else {
// TIP: You might use $old->version to decide what to use.
foreach ($old->scanner->allRules as $rule) {
$new->scanner->addRule($rule);
}
$new->loadedPackages = $old->loadedPackages;
$new->availableNamespaces = $old->availableNamespaces;
foreach ($old->activatedPackages as $activatedPackage) {
$new->activatePackage($activatedPackage['name'], $activatedPackage['dir'], $activatedPackage['config']);
}
}
return new Versions($new);
}
public function register(): \PathLoadInterface {
spl_autoload_register([$this, 'loadClass']);
return $this;
}
public function unregister(): \PathLoadInterface {
spl_autoload_unregister([$this, 'loadClass']);
return $this;
}
public function reset(): \PathLoadInterface {
$this->scanner->reset();
return $this;
}
/**
* Append a directory (with many packages) to the search-path.
*
* @param string $baseDir
* The path to a base directory (e.g. `/var/www/myapp/lib`) which contains many packages (e.g.
* `foo@1.2.3.phar` or `bar@4.5.6/autoload.php`).
*/
public function addSearchDir(string $baseDir): \PathLoadInterface {
$this->scanner->addRule(['package' => '*', 'glob' => "$baseDir/*@*"]);
return $this;
}
/**
* Append one specific item to the search list.
*
* @param string $name
* Ex: 'cloud-file-io'
* @param string $version
* Ex: '1.2.3'
* @param string $file
* Full path to the file or folder.
* @param string|null $type
* One of: 'php', 'phar', or 'dir'. NULL will auto-detect.
*
* @return \PathLoadInterface
*/
public function addSearchItem(string $name, string $version, string $file, ?string $type = NULL): \PathLoadInterface {
$this->scanner->addRule(['package' => $name, 'version' => $version, 'file' => $file, 'type' => $type]);
return $this;
}
/**
* Add auto-loading hints. If someone requests a class in $namespace, then we load $package.
*
* Consecutive/identical calls to addNamespace() are de-duplicated.
*
* @param string $package
* Ex: 'cloud-io@1'
* @param string|string[] $namespaces
* Ex: 'Super\Cloud\IO\'
*/
public function addNamespace(string $package, $namespaces): \PathLoadInterface {
foreach ((array) $namespaces as $namespace) {
$this->availableNamespaces[$namespace][$package] = $package;
}
return $this;
}
public function loadClass(string $class) {
if (strpos($class, '\\') !== FALSE) {
$this->loadPackagesByNamespace('\\', explode('\\', $class));
}
elseif (strpos($class, '_') !== FALSE) {
$this->loadPackagesByNamespace('_', explode('_', $class));
}
return $this->psr4->loadClass($class) || $this->psr0->loadClass($class);
}
/**
* If the application requests class "Foo\Bar\Whiz\Bang", then you should load
* any packages related to "Foo\*", "Foo\Bar\*", or "Foo\Bar\Whiz\*".
*
* @param string $delim
* Ex: '\\' or '_'
* @param string[] $classParts
* Ex: ['Symfony', 'Components', 'Filesystem', 'Filesystem']
*/
private function loadPackagesByNamespace(string $delim, array $classParts): void {
array_pop($classParts);
do {
$foundPackages = FALSE;
$namespace = '';
foreach ($classParts as $nsPart) {
$namespace .= $nsPart . $delim;
if (isset($this->availableNamespaces[$namespace])) {
$packages = $this->availableNamespaces[$namespace];
foreach ($packages as $package) {
unset($this->availableNamespaces[$namespace][$package]);
if ($this->loadPackage($package)) {
$foundPackages = TRUE;
}
else {
trigger_error("PathLoad: Failed to locate package \"$package\" required for namespace \"$namespace\"", E_USER_WARNING);
$this->availableNamespaces[$namespace][$package] = $package; /* Maybe some other time */
}
}
}
}
} while ($foundPackages);
// Loading a package could produce metadata about other packages. Assimilate those too.
}
/**
* Load the content of a package.
*
* @param string $majorName
* Ex: 'cloud-io@1'
* @param bool $reload
* @return string|NULL
* The version# of the loaded package. Otherwise, NULL
*/
public function loadPackage(string $majorName, bool $reload = FALSE): ?string {
if (isset($this->loadedPackages[$majorName])) {
if ($reload && $this->loadedPackages[$majorName]->reloadable) {
$this->scanner->reset();
}
else {
if ($reload) {
trigger_error("PathLoad: Declined to reload \"$majorName\". Package is not reloadable.", E_USER_WARNING);
}
return $this->loadedPackages[$majorName]->version;
}
}
$this->scanAvailablePackages(explode('@', $majorName, 2)[0], $this->availablePackages);
if (!isset($this->availablePackages[$majorName])) {
return NULL;
}
$package = $this->loadedPackages[$majorName] = $this->availablePackages[$majorName];
unset($this->availablePackages[$majorName]);
switch ($package->type ?? NULL) {
case 'php':
doRequire($package->file);
return $package->version;
case 'phar':
doRequire($package->file);
$this->useMetadataFiles($package, 'phar://' . $package->file);
return $package->version;
case 'dir':
$this->useMetadataFiles($package, $package->file);
return $package->version;
default:
\error_log("PathLoad: Package (\"$majorName\") appears malformed.");
return NULL;
}
}
private function scanAvailablePackages(string $hint, array &$avail): void {
foreach ($this->scanner->scan($hint) as $package) {
/** @var Package $package */
if (!isset($avail[$package->majorName]) || \version_compare($package->version, $avail[$package->majorName]->version, '>')) {
$avail[$package->majorName] = $package;
}
}
}
/**
* When loading a package, execute metadata files like "pathload.main.php" or "pathload.json".
*
* @param Package $package
* @param string $dir
* Ex: '/var/www/lib/cloud-io@1.2.0'
* Ex: 'phar:///var/www/lib/cloud-io@1.2.0.phar'
*/
private function useMetadataFiles(Package $package, string $dir): void {
$phpFile = "$dir/pathload.main.php";
$jsonFile = "$dir/pathload.json";
if (file_exists($phpFile)) {
require $phpFile;
}
elseif (file_exists($jsonFile)) {
$jsonData = json_decode(file_get_contents($jsonFile), TRUE);
$id = $package->name . '@' . $package->version;
$this->activatePackage($id, $dir, $jsonData);
}
}
/**
* Given a configuration for the package, activate the correspond autoloader rules.
*
* @param string $majorName
* Ex: 'cloud-io@1'
* @param string|null $dir
* Used for applying the 'autoload' rules.
* Ex: '/var/www/lib/cloud-io@1.2.3'
* @param array $config
* Ex: ['autoload' => ['psr4' => ...], 'require-namespace' => [...], 'require-package' => [...]]
* @return \PathLoadInterface
*/
public function activatePackage(string $majorName, ?string $dir, array $config): \PathLoadInterface {
if (isset($config['reloadable'])) {
$this->loadedPackages[$majorName]->reloadable = $config['reloadable'];
}
if (!isset($config['autoload'])) {
return $this;
}
if ($dir === NULL) {
throw new \RuntimeException("Cannot activate package $majorName. The 'autoload' property requires a base-directory.");
}
$this->activatedPackages[] = ['name' => $majorName, 'dir' => $dir, 'config' => $config];
if (!empty($config['autoload']['include'])) {
foreach ($config['autoload']['include'] as $file) {
doRequire($dir . DIRECTORY_SEPARATOR . $file);
}
}
if (isset($config['autoload']['psr-0'])) {
$this->psr0->addAll($dir, $config['autoload']['psr-0']);
}
if (isset($config['autoload']['psr-4'])) {
$this->psr4->addAll($dir, $config['autoload']['psr-4']);
}
foreach ($config['require-namespace'] ?? [] as $nsRule) {
foreach ((array) $nsRule['package'] as $package) {
foreach ((array) $nsRule['prefix'] as $prefix) {
$this->availableNamespaces[$prefix][$package] = $package;
}
}
}
foreach ($config['require-package'] ?? [] as $package) {
$this->loadPackage($package);
}
return $this;
}
}
}
}
namespace {
// New or upgraded instance.
$GLOBALS['_PathLoad'] = \PathLoad\V0\PathLoad::create(0, $GLOBALS['_PathLoad']['top'] ?? NULL);
if (!function_exists('pathload')) {
/**
* Get a reference the PathLoad manager.
*
* @param int|string $version
* @return \PathLoadInterface
*/
function pathload($version = 'top') {
return $GLOBALS['_PathLoad'][$version];
}
}
return pathload();
}

View file

@ -0,0 +1,78 @@
<?php
/**
* Auto-register "templates/" folder.
*
* @mixinName smarty-v2
* @mixinVersion 1.0.3
* @since 5.59
*
* @deprecated - it turns out that the mixin is not version specific so the 'smarty'
* mixin is preferred over smarty-v2 (they are the same but not having the version
* in the name is less misleading.)
*
* @param CRM_Extension_MixInfo $mixInfo
* On newer deployments, this will be an instance of MixInfo. On older deployments, Civix may polyfill with a work-a-like.
* @param \CRM_Extension_BootCache $bootCache
* On newer deployments, this will be an instance of MixInfo. On older deployments, Civix may polyfill with a work-a-like.
*/
return function ($mixInfo, $bootCache) {
$dir = $mixInfo->getPath('templates');
if (!file_exists($dir)) {
return;
}
$register = function($newDirs) {
$smarty = CRM_Core_Smarty::singleton();
$v2 = isset($smarty->_version) && version_compare($smarty->_version, 3, '<');
$templateDirs = (array) ($v2 ? $smarty->template_dir : $smarty->getTemplateDir());
$templateDirs = array_merge($newDirs, $templateDirs);
$templateDirs = array_unique(array_map(function($v) {
$v = str_replace(DIRECTORY_SEPARATOR, '/', $v);
$v = rtrim($v, '/') . '/';
return $v;
}, $templateDirs));
if ($v2) {
$smarty->template_dir = $templateDirs;
}
else {
$smarty->setTemplateDir($templateDirs);
}
};
// Let's figure out what environment we're in -- so that we know the best way to call $register().
if (!empty($GLOBALS['_CIVIX_MIXIN_POLYFILL'])) {
// Polyfill Loader (v<=5.45): We're already in the middle of firing `hook_config`.
if ($mixInfo->isActive()) {
$register([$dir]);
}
return;
}
if (CRM_Extension_System::singleton()->getManager()->extensionIsBeingInstalledOrEnabled($mixInfo->longName)) {
// New Install, Standard Loader: The extension has just been enabled, and we're now setting it up.
// System has already booted. New templates may be needed for upcoming installation steps.
$register([$dir]);
return;
}
// Typical Pageview, Standard Loader: Defer the actual registration for a moment -- to ensure that Smarty is online.
// We need to bundle-up all dirs -- Smarty 3/4/5 is inefficient with processing repeated calls to `getTemplateDir()`+`setTemplateDir()`
if (!isset(Civi::$statics[__FILE__]['event'])) {
Civi::$statics[__FILE__]['event'] = 'civi.smarty-v2.addPaths.' . md5(__FILE__);
Civi::dispatcher()->addListener('hook_civicrm_config', function() use ($register) {
$dirs = [];
$event = \Civi\Core\Event\GenericHookEvent::create(['dirs' => &$dirs]);
Civi::dispatcher()->dispatch(Civi::$statics[__FILE__]['event'], $event);
$register($dirs);
});
}
Civi::dispatcher()->addListener(Civi::$statics[__FILE__]['event'], function($event) use ($mixInfo, $dir) {
if ($mixInfo->isActive()) {
array_unshift($event->dirs, $dir);
}
});
};

View file

@ -0,0 +1,67 @@
<?php
use CRM_Contactcats_ExtensionUtil as E;
return [
'name' => 'ContactCategory',
'table' => 'civicrm_contact_category',
'class' => 'CRM_Contactcats_DAO_ContactCategory',
'getInfo' => fn() => [
'title' => E::ts('Contact Category'),
'title_plural' => E::ts('Contact Categories'),
'description' => E::ts('Stores a category for each contact'),
'log' => FALSE,
],
'getIndices' => fn() => [
'index_category' => [
'fields' => [
'category' => TRUE,
],
],
],
'getFields' => fn() => [
'id' => [
'title' => E::ts('ID'),
'sql_type' => 'int unsigned',
'input_type' => 'Number',
'required' => TRUE,
'description' => E::ts('Unique ID, corresponds to contact id'),
'primary_key' => TRUE,
'auto_increment' => TRUE,
],
'contact_id' => [
'title' => E::ts('Contact ID'),
'sql_type' => 'int unsigned',
'input_type' => 'EntityRef',
'required' => TRUE,
'description' => E::ts('Same as id but for FormBuilder'),
'input_attrs' => [
'label' => E::ts('Contact'),
],
'entity_reference' => [
'entity' => 'Contact',
'key' => 'id',
'on_delete' => 'CASCADE',
],
],
'category' => [
'title' => E::ts('Category'),
'sql_type' => 'int unsigned',
'input_type' => 'Select',
'required' => TRUE,
'default' => 0,
'pseudoconstant' => [
'option_group_name' => 'contact_categories',
],
],
'next_category' => [
'title' => E::ts('Next Category'),
'sql_type' => 'int unsigned',
'input_type' => 'Select',
'required' => TRUE,
'default' => 0,
'pseudoconstant' => [
'option_group_name' => 'contact_categories',
],
],
],
];

View file

@ -0,0 +1,85 @@
<?php
use CRM_Contactcats_ExtensionUtil as E;
return [
'name' => 'ContactCategoryDescription',
'table' => 'civicrm_contact_category_description',
'class' => 'CRM_Contactcats_DAO_ContactCategoryDescription',
'getInfo' => fn() => [
'title' => E::ts('Contact Category Description'),
'title_plural' => E::ts('Contact Category Descriptions'),
'description' => E::ts('Holds definition of a "Contact category"'),
'log' => FALSE,
],
'getIndices' => fn() => [
'index_category' => [
'fields' => [
'category' => TRUE,
],
],
],
'getFields' => fn() => [
'id' => [
'title' => E::ts('ID'),
'sql_type' => 'int unsigned',
'input_type' => 'Number',
'required' => TRUE,
'description' => E::ts('Unique Contact Category Description ID'),
'primary_key' => TRUE,
'auto_increment' => TRUE,
],
'label' => [
'title' => E::ts('Category name'),
'sql_type' => 'varchar(255)',
'input_type' => 'Text',
'required' => TRUE,
'description' => E::ts('Same as id but for FormBuilder'),
'input_attrs' => [
'text_length' => 255,
'label' => E::ts('Category name'),
],
],
'search_type' => [
'title' => E::ts('What defines this search?'),
'sql_type' => 'varchar(12)',
'input_type' => 'Select',
'required' => TRUE,
'default' => 'search',
'pseudoconstant' => [
'callback' => fn() => [
// 'name' => 'title',
'search' => E::ts('Search Kit search'),
'group' => E::ts('Group'),
// Future:
// 'sql' => E::ts('SQL template'),
],
],
],
'search_data' => [
'title' => E::ts('JSON blob specifies particulars to for the search_type'),
'description' => E::ts('Holds data specific to the search specification (e.g. a group ID when Group is selected).'),
'sql_type' => 'text',
'serialize' => 'JSON',
],
'color' => [
'title' => E::ts('Colour for category label'),
'sql_type' => 'varchar(7)',
'default' => '',
'required' => TRUE,
],
'icon' => [
'title' => E::ts('Icon'),
'sql_type' => 'varchar(64)',
'default' => '',
'required' => TRUE,
'description' => E::ts('The name of a Font Awesome icon to use to represent this, e.g. fa-trophy'),
],
'execution_order' => [
'title' => E::ts('Execution order'),
'description' => E::ts('The first category to match is assigned. Lower numbers are tested first.'),
'sql_type' => 'tinyint unsigned',
'default' => '10',
'required' => TRUE,
],
],
];

View file

@ -1,45 +0,0 @@
-- +--------------------------------------------------------------------+
-- | Copyright CiviCRM LLC. All rights reserved. |
-- | |
-- | This work is published under the GNU AGPLv3 license with some |
-- | permitted exceptions and without any warranty. For full license |
-- | and copyright information, see https://civicrm.org/licensing |
-- +--------------------------------------------------------------------+
--
-- Generated from schema.tpl
-- DO NOT EDIT. Generated by CRM_Core_CodeGen
--
-- /*******************************************************
-- *
-- * Clean up the existing tables - this section generated from drop.tpl
-- *
-- *******************************************************/
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `civicrm_contact_category`;
SET FOREIGN_KEY_CHECKS=1;
-- /*******************************************************
-- *
-- * Create new tables
-- *
-- *******************************************************/
-- /*******************************************************
-- *
-- * civicrm_contact_category
-- *
-- * Stores a category for each contact
-- *
-- *******************************************************/
CREATE TABLE `civicrm_contact_category` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique ID, corresponds to contact id',
`contact_id` int unsigned NOT NULL COMMENT 'Same as id but for FormBuilder',
`category` int unsigned NOT NULL DEFAULT 0,
`next_category` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `index_category`(category),
CONSTRAINT FK_civicrm_contact_category_contact_id FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE
)
ENGINE=InnoDB;

View file

@ -1,22 +0,0 @@
-- +--------------------------------------------------------------------+
-- | Copyright CiviCRM LLC. All rights reserved. |
-- | |
-- | This work is published under the GNU AGPLv3 license with some |
-- | permitted exceptions and without any warranty. For full license |
-- | and copyright information, see https://civicrm.org/licensing |
-- +--------------------------------------------------------------------+
--
-- Generated from drop.tpl
-- DO NOT EDIT. Generated by CRM_Core_CodeGen
--
-- /*******************************************************
-- *
-- * Clean up the existing tables
-- *
-- *******************************************************/
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `civicrm_contact_category`;
SET FOREIGN_KEY_CHECKS=1;

View file

@ -1,10 +0,0 @@
<?php
// This file declares a new entity type. For more details, see "hook_civicrm_entityTypes" at:
// https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes
return [
[
'name' => 'ContactCategory',
'class' => 'CRM_Contactcats_DAO_ContactCategory',
'table' => 'civicrm_contact_category',
],
];

View file

@ -1,68 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1" ?>
<table>
<base>CRM/Contactcats</base>
<class>ContactCategory</class>
<name>civicrm_contact_category</name>
<comment>Stores a category for each contact</comment>
<log>false</log>
<field>
<name>id</name>
<type>int unsigned</type>
<required>true</required>
<comment>Unique ID, corresponds to contact id</comment>
<html>
<type>Number</type>
</html>
</field>
<primaryKey>
<name>id</name>
<autoincrement>true</autoincrement>
</primaryKey>
<field>
<name>contact_id</name>
<type>int unsigned</type>
<required>true</required>
<comment>Same as id but for FormBuilder</comment>
<html>
<type>EntityRef</type>
<label>Contact</label>
</html>
</field>
<foreignKey>
<name>contact_id</name>
<table>civicrm_contact</table>
<key>id</key>
<onDelete>CASCADE</onDelete>
</foreignKey>
<field>
<name>category</name>
<type>int unsigned</type>
<required>true</required>
<default>0</default>
<pseudoconstant>
<optionGroupName>contact_categories</optionGroupName>
</pseudoconstant>
<html>
<type>Select</type>
</html>
</field>
<index>
<name>index_category</name>
<fieldName>category</fieldName>
</index>
<field>
<name>next_category</name>
<type>int unsigned</type>
<required>true</required>
<default>0</default>
<pseudoconstant>
<optionGroupName>contact_categories</optionGroupName>
</pseudoconstant>
</field>
</table>