+ $this->allFuncFiles = NULL;
+
+ return [$this->liveFuncFiles, $this->mixInfos];
+ }
+
+ /**
+ * @param CRM_Extension_MixInfo $mix
+ * @return static
+ * @throws \CRM_Extension_Exception_ParseException
+ */
+ public function addMixInfo(CRM_Extension_MixInfo $mix) {
+ $this->mixInfos[$mix->longName] = $mix;
+ return $this;
+ }
+
+ /**
+ * @param array|string $files
+ * Ex: 'path/to/some/file@1.0.0.mixin.php'
+ * @param bool $deepRead
+ * If TRUE, then the file will be read to find metadata.
+ * @return $this
+ */
+ public function addFunctionFiles($files, $deepRead = FALSE) {
+ $files = (array) $files;
+ foreach ($files as $file) {
+ if (preg_match(';^([^@]+)@([^@]+)\.mixin\.php$;', basename($file), $m)) {
+ $this->allFuncFiles[$m[1]][$m[2]] = $file;
+ continue;
+ }
+
+ if ($deepRead) {
+ $header = $this->loadFunctionFileHeader($file);
+ if (isset($header['mixinName'], $header['mixinVersion'])) {
+ $this->allFuncFiles[$header['mixinName']][$header['mixinVersion']] = $file;
+ continue;
+ }
+ else {
+ error_log(sprintf('MixinLoader: Invalid mixin header for "%s". @mixinName and @mixinVersion required.', $file));
+ continue;
+ }
+ }
+
+ error_log(sprintf('MixinLoader: File \"%s\" cannot be parsed.', $file));
+ }
+ return $this;
+ }
+
+ private function loadFunctionFileHeader($file) {
+ $php = file_get_contents($file, TRUE);
+ foreach (token_get_all($php) as $token) {
+ if (is_array($token) && in_array($token[0], [T_DOC_COMMENT, T_COMMENT, T_FUNC_C, T_METHOD_C, T_TRAIT_C, T_CLASS_C])) {
+ return \Civi\Api4\Utils\ReflectionUtils::parseDocBlock($token[1]);
+ }
+ }
+ return [];