AfformScanner - Just use the long cache
[civicrm-core.git] / ext / afform / core / CRM / Afform / AfformScanner.php
CommitLineData
a6db0d80
TO
1<?php
2
3/**
4 * Class CRM_Afform_AfformScanner
5 *
6 * The AfformScanner searches the extensions and `civicrm.files` for subfolders
7 * named `afform`. Each item in there is interpreted as a form instance.
8 *
9 * To reduce file-scanning, we keep a cache of file paths.
10 */
11class CRM_Afform_AfformScanner {
12
87dde5eb 13 const METADATA_FILE = 'aff.json';
a6db0d80 14
a87c70ce
TO
15 const LAYOUT_FILE = 'aff.html';
16
17 const FILE_REGEXP = '/\.aff\.(json|html)$/';
18
c1b9955f 19 const DEFAULT_REQUIRES = 'afCore';
a6db0d80
TO
20
21 /**
22 * @var CRM_Utils_Cache_Interface
23 */
24 protected $cache;
25
26 /**
27 * CRM_Afform_AfformScanner constructor.
28 */
29 public function __construct() {
ab06ec41 30 $this->cache = Civi::cache('long');
a6db0d80
TO
31 }
32
33 /**
145fc64b
TO
34 * Get a list of all forms and their file paths.
35 *
a6db0d80
TO
36 * @return array
37 * Ex: ['view-individual' => ['/var/www/foo/afform/view-individual']]
38 */
39 public function findFilePaths() {
40 if (!CRM_Core_Config::singleton()->debug) {
41 // FIXME: Use a separate setting. Maybe use the asset-builder cache setting?
ab06ec41 42 $paths = $this->cache->get('afformAllPaths');
a6db0d80
TO
43 if ($paths !== NULL) {
44 return $paths;
45 }
46 }
47
5591cfbf 48 $paths = [];
a6db0d80
TO
49
50 $mapper = CRM_Extension_System::singleton()->getMapper();
51 foreach ($mapper->getModules() as $module) {
52 /** @var $module CRM_Core_Module */
8ad30bee 53 try {
54 if ($module->is_active) {
55 $this->appendFilePaths($paths, dirname($mapper->keyToPath($module->name)) . DIRECTORY_SEPARATOR . 'ang', 20);
56 }
57 }
58 catch (CRM_Extension_Exception_MissingException $e) {
59 // If the extension is missing skip & continue.
a6db0d80
TO
60 }
61 }
62
63 $this->appendFilePaths($paths, $this->getSiteLocalPath(), 10);
64
ab06ec41 65 $this->cache->set('afformAllPaths', $paths);
a6db0d80
TO
66 return $paths;
67 }
68
69 /**
70 * Get the full path to the given file.
71 *
72 * @param string $formName
73 * Ex: 'view-individual'
87dde5eb
TO
74 * @param string $suffix
75 * Ex: 'aff.json'
a6db0d80 76 * @return string|NULL
87dde5eb 77 * Ex: '/var/www/sites/default/files/civicrm/afform/view-individual.aff.json'
a6db0d80 78 */
87dde5eb 79 public function findFilePath($formName, $suffix) {
a6db0d80
TO
80 $paths = $this->findFilePaths();
81
82 if (isset($paths[$formName])) {
83 foreach ($paths[$formName] as $path) {
87dde5eb
TO
84 if (file_exists($path . '.' . $suffix)) {
85 return $path . '.' . $suffix;
a6db0d80
TO
86 }
87 }
88 }
89
90 return NULL;
91 }
92
93 /**
94 * Determine the path where we can write our own customized/overriden
95 * version of a file.
96 *
97 * @param string $formName
98 * Ex: 'view-individual'
99 * @param string $file
87dde5eb 100 * Ex: 'aff.json'
a6db0d80 101 * @return string|NULL
87dde5eb 102 * Ex: '/var/www/sites/default/files/civicrm/afform/view-individual.aff.json'
a6db0d80
TO
103 */
104 public function createSiteLocalPath($formName, $file) {
87dde5eb 105 return $this->getSiteLocalPath() . DIRECTORY_SEPARATOR . $formName . '.' . $file;
a6db0d80
TO
106 }
107
108 public function clear() {
ab06ec41 109 $this->cache->delete('afformAllPaths');
a6db0d80
TO
110 }
111
112 /**
113 * Get the effective metadata for a form.
114 *
115 * @param string $name
116 * Ex: 'view-individual'
117 * @return array
faece79d 118 * An array with some mix of the following keys: name, title, description, server_route, requires, is_public.
87dde5eb 119 * NOTE: This is only data available in *.aff.json. It does *NOT* include layout.
a6db0d80
TO
120 * Ex: [
121 * 'name' => 'view-individual',
122 * 'title' => 'View an individual contact',
6f175b5c 123 * 'server_route' => 'civicrm/view-individual',
a6db0d80
TO
124 * 'requires' => ['afform'],
125 * ]
126 */
127 public function getMeta($name) {
128 // FIXME error checking
a6db0d80
TO
129
130 $defaults = [
131 'name' => $name,
77dccccb 132 'requires' => [],
a6db0d80
TO
133 'title' => '',
134 'description' => '',
e17912d6 135 'is_public' => FALSE,
17535d7f 136 'permission' => 'access CiviCRM',
a6db0d80
TO
137 ];
138
a87c70ce
TO
139 $metaFile = $this->findFilePath($name, self::METADATA_FILE);
140 if ($metaFile !== NULL) {
141 return array_merge($defaults, json_decode(file_get_contents($metaFile), 1));
142 }
143 elseif ($this->findFilePath($name, self::LAYOUT_FILE)) {
144 return $defaults;
145 }
146 else {
147 return NULL;
148 }
a6db0d80
TO
149 }
150
10fd70a3
TO
151 public function getComputedFields($name) {
152 // Ex: $allPaths['viewIndividual'][0] == '/var/www/foo/afform/view-individual'].
153 $allPaths = $this->findFilePaths()[$name];
154 // $activeLayoutPath = $this->findFilePath($name, self::LAYOUT_FILE);
155 // $activeMetaPath = $this->findFilePath($name, self::METADATA_FILE);
156 $localLayoutPath = $this->createSiteLocalPath($name, self::LAYOUT_FILE);
157 $localMetaPath = $this->createSiteLocalPath($name, self::METADATA_FILE);
158
159 $fields = [];
160 $fields['has_local'] = file_exists($localLayoutPath) || file_exists($localMetaPath);
2d4bfef1 161 $fields['has_base'] = ($fields['has_local'] && count($allPaths) > 1)
10fd70a3
TO
162 || (!$fields['has_local'] && count($allPaths) > 0);
163 return $fields;
164 }
165
bebb3e59
TO
166 /**
167 * @param string $formName
168 * Ex: 'view-individual'
169 * @return string|NULL
170 * Ex: '<em>Hello world!</em>'
171 * NULL if no layout exists
172 */
173 public function getLayout($formName) {
174 $filePath = $this->findFilePath($formName, self::LAYOUT_FILE);
175 return $filePath === NULL ? NULL : file_get_contents($filePath);
176 }
177
145fc64b
TO
178 /**
179 * Get the effective metadata for all forms.
180 *
181 * @return array
182 * A list of all forms, keyed by form name.
87dde5eb 183 * NOTE: This is only data available in *.aff.json. It does *NOT* include layout.
145fc64b
TO
184 * Ex: ['view-individual' => ['title' => 'View an individual contact', ...]]
185 */
186 public function getMetas() {
5591cfbf 187 $result = [];
145fc64b
TO
188 foreach (array_keys($this->findFilePaths()) as $name) {
189 $result[$name] = $this->getMeta($name);
190 }
191 return $result;
192 }
193
a6db0d80
TO
194 /**
195 * @param array $formPaths
196 * List of all form paths.
87dde5eb 197 * Ex: ['foo' => [0 => '/var/www/org.example.foobar/ang']]
a6db0d80
TO
198 * @param string $parent
199 * Ex: '/var/www/org.example.foobar/afform/'
200 * @param int $priority
201 * Lower priority files override higher priority files.
202 */
203 private function appendFilePaths(&$formPaths, $parent, $priority) {
a87c70ce
TO
204 $files = preg_grep(self::FILE_REGEXP, (array) glob("$parent/*"));
205
87dde5eb 206 foreach ($files as $file) {
a87c70ce 207 $fileBase = preg_replace(self::FILE_REGEXP, '', $file);
1787c8cd 208 $name = basename($fileBase);
a87c70ce 209 $formPaths[$name][$priority] = $fileBase;
87dde5eb 210 ksort($formPaths[$name]);
a6db0d80
TO
211 }
212 }
213
214 /**
215 * Get the path where site-local form customizations are stored.
216 *
217 * @return mixed|string
218 * Ex: '/var/www/sites/default/files/civicrm/afform'.
219 */
220 private function getSiteLocalPath() {
221 // TODO Allow a setting override.
222 // return Civi::paths()->getPath(Civi::settings()->get('afformPath'));
87dde5eb 223 return Civi::paths()->getPath('[civicrm.files]/ang');
a6db0d80
TO
224 }
225
a87c70ce
TO
226 /**
227 * @return string
228 */
229 private function getMarkerRegexp() {
230 static $v;
231 if ($v === NULL) {
232 $v = '/\.(' . preg_quote(self::LAYOUT_FILE, '/') . '|' . preg_quote(self::METADATA_FILE, '/') . ')$/';
233 }
234 return $v;
235 }
236
a6db0d80 237}