diff --git a/CRM/Volunteer/Form/IncludeProfile.php b/CRM/Volunteer/Form/IncludeProfile.php
index 52966426..d06c988e 100644
--- a/CRM/Volunteer/Form/IncludeProfile.php
+++ b/CRM/Volunteer/Form/IncludeProfile.php
@@ -1,7 +1,5 @@
_elements as $element) {
$groupName = $this->getGroupName($element);
-
$label = $element->getLabel();
- if (!empty($label)) {
+ if (!empty($label)) {
if (!array_key_exists($groupName, $elementGroups)) {
$elementGroups[$groupName] = array();
}
@@ -403,8 +400,8 @@ private function getGroupName(HTML_QuickForm_element $element) {
// otherwise fallback to settings metadata
if (empty($groupName)) {
- $setting = CRM_Utils_Array::value($element->getName(), $this->_settingsMetadata);
- $groupName = $setting['group_name'];
+ $elName = $element->getName();
+ $groupName = $this->_settingsMetadata[$elName]['group_name'] ?? NULL;
}
return $groupName;
diff --git a/CRM/Volunteer/Form/VolunteerSignUp.php b/CRM/Volunteer/Form/VolunteerSignUp.php
index c9b3b831..3b200e02 100644
--- a/CRM/Volunteer/Form/VolunteerSignUp.php
+++ b/CRM/Volunteer/Form/VolunteerSignUp.php
@@ -25,8 +25,6 @@
+--------------------------------------------------------------------+
*/
-require_once 'CRM/Core/Form.php';
-
/**
* Form controller class
*
diff --git a/CRM/Volunteer/Upgrader.php b/CRM/Volunteer/Upgrader.php
index 266bd06b..1ca368e5 100644
--- a/CRM/Volunteer/Upgrader.php
+++ b/CRM/Volunteer/Upgrader.php
@@ -35,7 +35,7 @@ class CRM_Volunteer_Upgrader extends CRM_Extension_Upgrader_Base {
const customContactTypeName = 'Volunteer';
const skillLevelOptionGroupName = 'skill_level';
- public function install() {
+ public function postInstall() {
$volActivityTypeId = $this->createActivityType(CRM_Volunteer_BAO_Assignment::CUSTOM_ACTIVITY_TYPE);
$smarty = CRM_Core_Smarty::singleton();
$smarty->assign('volunteer_custom_activity_type_name', CRM_Volunteer_BAO_Assignment::CUSTOM_ACTIVITY_TYPE);
@@ -62,6 +62,17 @@ public function install() {
// uncomment the next line to insert sample data
// $this->executeSqlFile('sql/volunteer_sample.mysql');
+
+ // See VOL-237. Avoid order of operation problems by assigning a value to the
+ // slider_widget_fields setting after the install, which is responsible for
+ // creating both the setting and the custom field whose ID is used in the
+ // initial value.
+ $customFieldId = civicrm_api3('customField', 'getvalue', array(
+ 'custom_group_id' => 'Volunteer_Information',
+ 'name' => 'camera_skill_level',
+ 'return' => 'id',
+ ));
+ _volunteer_update_slider_fields(array(CRM_Core_Action::ADD => $customFieldId));
}
/**
@@ -507,23 +518,6 @@ public function uninstall() {
}
}
- /**
- * Perform post-install tasks.
- *
- * See VOL-237. Avoid order of operation problems by assigning a value to the
- * slider_widget_fields setting after the install, which is responsible for
- * creating both the setting and the custom field whose ID is used in the
- * initial value.
- */
- public function postInstall() {
- $customFieldId = civicrm_api3('customField', 'getvalue', array(
- 'custom_group_id' => 'Volunteer_Information',
- 'name' => 'camera_skill_level',
- 'return' => 'id',
- ));
- _volunteer_update_slider_fields(array(CRM_Core_Action::ADD => $customFieldId));
- }
-
/**
* Example: Run a simple query when a module is enabled
*
@@ -893,7 +887,6 @@ public function executeCustomDataTemplateFile($relativePath) {
$xmlCode = $smarty->fetch($relativePath);
$xml = simplexml_load_string($xmlCode);
- require_once 'CRM/Utils/Migrate/Import.php';
$import = new CRM_Utils_Migrate_Import();
$import->runXmlElement($xml);
return TRUE;
diff --git a/info.xml b/info.xml
index 1a7bd26b..fbf9eb0a 100644
--- a/info.xml
+++ b/info.xml
@@ -11,14 +11,14 @@
2024-02-10
2.4.5
- 5.60
+ 5.71
Developed by Ginkgo Street Labs and CiviCRM, LLC with contributions from the community. Special thanks to Friends of Georgia State Parks & Historic Sites for funding the initial release, and to The Manhattan Neighborhood Network for funding the 1.4 release.
CRM/Volunteer
- 23.02.1
+ 24.09.1
https://docs.civicrm.org/volunteer/en/latest/
@@ -31,8 +31,8 @@
menu-xml@1.0.0
mgd-php@1.0.0
setting-php@1.0.0
- smarty-v2@1.0.0
entity-types-php@1.0.0
+ smarty@1.0.3
diff --git a/mixin/lib/civimix-schema@5.78.beta1.phar b/mixin/lib/civimix-schema@5.78.beta1.phar
new file mode 100644
index 00000000..079ec362
Binary files /dev/null and b/mixin/lib/civimix-schema@5.78.beta1.phar differ
diff --git a/mixin/lib/pathload-0.php b/mixin/lib/pathload-0.php
new file mode 100644
index 00000000..76d64254
--- /dev/null
+++ b/mixin/lib/pathload-0.php
@@ -0,0 +1,711 @@
+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();
+}
diff --git a/mixin/smarty@1.0.3.mixin.php b/mixin/smarty@1.0.3.mixin.php
new file mode 100644
index 00000000..f6401978
--- /dev/null
+++ b/mixin/smarty@1.0.3.mixin.php
@@ -0,0 +1,74 @@
+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.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);
+ }
+ });
+
+};
diff --git a/volunteer.civix.php b/volunteer.civix.php
index 982c6332..283660c2 100644
--- a/volunteer.civix.php
+++ b/volunteer.civix.php
@@ -75,10 +75,47 @@ public static function findClass($suffix) {
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_Volunteer_ExtensionUtil as E;
+($GLOBALS['_PathLoad'][0] ?? require __DIR__ . '/mixin/lib/pathload-0.php');
+pathload()->addSearchDir(__DIR__ . '/mixin/lib');
+spl_autoload_register('_volunteer_civix_class_loader', TRUE, TRUE);
+
+function _volunteer_civix_class_loader($class) {
+ if ($class === 'CRM_Volunteer_DAO_Base') {
+ if (version_compare(CRM_Utils_System::version(), '5.74.beta', '>=')) {
+ class_alias('CRM_Core_DAO_Base', 'CRM_Volunteer_DAO_Base');
+ // ^^ Materialize concrete names -- encourage IDE's to pick up on this association.
+ }
+ else {
+ $realClass = 'CiviMix\\Schema\\Volunteer\\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\\Volunteer\\') === 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().
*