mirror of
https://codeberg.org/artfulrobot/contactcats.git
synced 2025-06-25 12:58:05 +02:00
checkin (broken)
This commit is contained in:
parent
a01abba787
commit
06fec5daf7
21 changed files with 2025 additions and 376 deletions
|
@ -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;
|
||||
class CRM_Contactcats_DAO_ContactCategory extends CRM_Contactcats_DAO_Base {
|
||||
|
||||
/**
|
||||
* Database access object for the ContactCategory entity.
|
||||
*/
|
||||
class CRM_Contactcats_DAO_ContactCategory extends CRM_Core_DAO {
|
||||
const EXT = E::LONG_NAME;
|
||||
const TABLE_ADDED = '';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
24
CRM/Contactcats/DAO/ContactCategoryDescription.php
Normal file
24
CRM/Contactcats/DAO/ContactCategoryDescription.php
Normal 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';
|
||||
|
||||
}
|
13
Civi/Api4/ContactCategoryDefinition.php
Normal file
13
Civi/Api4/ContactCategoryDefinition.php
Normal 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 {
|
||||
|
||||
}
|
|
@ -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().
|
||||
*
|
||||
|
|
8
info.xml
8
info.xml
|
@ -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>
|
||||
|
|
40
mixin/entity-types-php@2.0.0.mixin.php
Normal file
40
mixin/entity-types-php@2.0.0.mixin.php
Normal 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;
|
||||
}
|
||||
});
|
||||
|
||||
};
|
28
mixin/lib/civimix-schema@5.80.2/pathload.main.php
Normal file
28
mixin/lib/civimix-schema@5.80.2/pathload.main.php
Normal 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');
|
||||
}
|
181
mixin/lib/civimix-schema@5.80.2/src/AutomaticUpgrader.php
Normal file
181
mixin/lib/civimix-schema@5.80.2/src/AutomaticUpgrader.php
Normal 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)");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
46
mixin/lib/civimix-schema@5.80.2/src/CiviMixSchema.php
Normal file
46
mixin/lib/civimix-schema@5.80.2/src/CiviMixSchema.php
Normal 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];
|
||||
}
|
||||
|
||||
};
|
350
mixin/lib/civimix-schema@5.80.2/src/DAO.php
Normal file
350
mixin/lib/civimix-schema@5.80.2/src/DAO.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
87
mixin/lib/civimix-schema@5.80.2/src/SchemaHelper.php
Normal file
87
mixin/lib/civimix-schema@5.80.2/src/SchemaHelper.php
Normal 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;
|
||||
}
|
||||
|
||||
};
|
|
@ -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 {
|
||||
|
||||
}
|
232
mixin/lib/civimix-schema@5.80.2/src/SqlGenerator.php
Normal file
232
mixin/lib/civimix-schema@5.80.2/src/SqlGenerator.php
Normal 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
711
mixin/lib/pathload-0.php
Normal 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();
|
||||
}
|
78
mixin/smarty-v2@1.0.3.mixin.php
Normal file
78
mixin/smarty-v2@1.0.3.mixin.php
Normal 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);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
67
schema/ContactCategory.entityType.php
Normal file
67
schema/ContactCategory.entityType.php
Normal 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',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
85
schema/ContactCategoryDescription.entityType.php
Normal file
85
schema/ContactCategoryDescription.entityType.php
Normal 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,
|
||||
],
|
||||
],
|
||||
];
|
|
@ -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;
|
|
@ -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;
|
|
@ -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',
|
||||
],
|
||||
];
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue