Merge pull request #5667 from JKingsnorth/CRM-16328
[civicrm-core.git] / Civi / Angular / Manager.php
1 <?php
2 namespace Civi\Angular;
3
4 /**
5 * Manage Angular resources.
6 *
7 * @package Civi\Angular
8 */
9 class Manager {
10
11 /**
12 * @var \CRM_Core_Resources
13 */
14 protected $res = NULL;
15
16 /**
17 * @var array|NULL
18 * Each item has some combination of these keys:
19 * - ext: string
20 * The Civi extension which defines the Angular module.
21 * - js: array(string $relativeFilePath)
22 * List of JS files (relative to the extension).
23 * - css: array(string $relativeFilePath)
24 * List of CSS files (relative to the extension).
25 * - partials: array(string $relativeFilePath)
26 * A list of partial-HTML folders (relative to the extension).
27 * This will be mapped to "~/moduleName" by crmResource.
28 * - settings: array(string $key => mixed $value)
29 * List of settings to preload.
30 */
31 protected $modules = NULL;
32
33 /**
34 * @param \CRM_Core_Resources $res
35 * The resource manager.
36 */
37 public function __construct($res) {
38 $this->res = $res;
39 }
40
41 /**
42 * Get a list of AngularJS modules which should be autoloaded.
43 *
44 * @return array
45 * Each item has some combination of these keys:
46 * - ext: string
47 * The Civi extension which defines the Angular module.
48 * - js: array(string $relativeFilePath)
49 * List of JS files (relative to the extension).
50 * - css: array(string $relativeFilePath)
51 * List of CSS files (relative to the extension).
52 * - partials: array(string $relativeFilePath)
53 * A list of partial-HTML folders (relative to the extension).
54 * This will be mapped to "~/moduleName" by crmResource.
55 * - settings: array(string $key => mixed $value)
56 * List of settings to preload.
57 */
58 public function getModules() {
59 if ($this->modules === NULL) {
60 $config = \CRM_Core_Config::singleton();
61
62 $angularModules = array();
63 $angularModules['angularFileUpload'] = array(
64 'ext' => 'civicrm',
65 'js' => array('bower_components/angular-file-upload/angular-file-upload.min.js'),
66 );
67 $angularModules['crmApp'] = array(
68 'ext' => 'civicrm',
69 'js' => array('ang/crmApp.js'),
70 );
71 $angularModules['crmAttachment'] = array(
72 'ext' => 'civicrm',
73 'js' => array('ang/crmAttachment.js'),
74 'css' => array('ang/crmAttachment.css'),
75 'partials' => array('ang/crmAttachment'),
76 'settings' => array(
77 'token' => \CRM_Core_Page_AJAX_Attachment::createToken(),
78 ),
79 );
80 $angularModules['crmAutosave'] = array(
81 'ext' => 'civicrm',
82 'js' => array('ang/crmAutosave.js'),
83 );
84 //$angularModules['crmExample'] = array(
85 // 'ext' => 'civicrm',
86 // 'js' => array('ang/crmExample.js'),
87 // 'partials' => array('ang/crmExample'),
88 //);
89 $angularModules['crmResource'] = array(
90 'ext' => 'civicrm',
91 // 'js' => array('js/angular-crmResource/byModule.js'), // One HTTP request per module.
92 'js' => array('js/angular-crmResource/all.js'), // One HTTP request for all modules.
93 );
94 $angularModules['crmUi'] = array(
95 'ext' => 'civicrm',
96 'js' => array('ang/crmUi.js'),
97 'partials' => array('ang/crmUi'),
98 'settings' => array(
99 'browseUrl' => $config->userFrameworkResourceURL . 'packages/kcfinder/browse.php',
100 'uploadUrl' => $config->userFrameworkResourceURL . 'packages/kcfinder/upload.php',
101 ),
102 );
103 $angularModules['crmUtil'] = array(
104 'ext' => 'civicrm',
105 'js' => array('ang/crmUtil.js'),
106 );
107 // https://github.com/jwstadler/angular-jquery-dialog-service
108 $angularModules['dialogService'] = array(
109 'ext' => 'civicrm',
110 'js' => array('bower_components/angular-jquery-dialog-service/dialog-service.js'),
111 );
112 $angularModules['ngRoute'] = array(
113 'ext' => 'civicrm',
114 'js' => array('bower_components/angular-route/angular-route.min.js'),
115 );
116 $angularModules['ui.utils'] = array(
117 'ext' => 'civicrm',
118 'js' => array('bower_components/angular-ui-utils/ui-utils.min.js'),
119 );
120 $angularModules['ui.sortable'] = array(
121 'ext' => 'civicrm',
122 'js' => array('bower_components/angular-ui-sortable/sortable.min.js'),
123 );
124 $angularModules['unsavedChanges'] = array(
125 'ext' => 'civicrm',
126 'js' => array('bower_components/angular-unsavedChanges/dist/unsavedChanges.min.js'),
127 );
128
129 foreach (\CRM_Core_Component::getEnabledComponents() as $component) {
130 $angularModules = array_merge($angularModules, $component->getAngularModules());
131 }
132 \CRM_Utils_Hook::angularModules($angularModules);
133 $this->modules = $this->resolvePatterns($angularModules);
134 }
135
136 return $this->modules;
137 }
138
139 /**
140 * Get the descriptor for an Angular module.
141 *
142 * @param string $name
143 * Module name.
144 * @return array
145 * Details about the module:
146 * - ext: string, the name of the Civi extension which defines the module
147 * - js: array(string $relativeFilePath).
148 * - css: array(string $relativeFilePath).
149 * - partials: array(string $relativeFilePath).
150 * @throws \Exception
151 */
152 public function getModule($name) {
153 $modules = $this->getModules();
154 if (!isset($modules[$name])) {
155 throw new \Exception("Unrecognized Angular module");
156 }
157 return $modules[$name];
158 }
159
160 /**
161 * Convert any globs in an Angular module to file names.
162 *
163 * @param array $modules
164 * List of Angular modules.
165 * @return array
166 * Updated list of Angular modules
167 */
168 protected function resolvePatterns($modules) {
169 $newModules = array();
170
171 foreach ($modules as $moduleKey => $module) {
172 foreach (array('js', 'css', 'partials') as $fileset) {
173 if (!isset($module[$fileset])) {
174 continue;
175 }
176 $module[$fileset] = $this->res->glob($module['ext'], $module[$fileset]);
177 }
178 $newModules[$moduleKey] = $module;
179 }
180
181 return $newModules;
182 }
183
184 /**
185 * Get the partial HTML documents for a module.
186 *
187 * @param string $name
188 * Angular module name.
189 * @return array
190 * Array(string $extFilePath => string $html)
191 * @throws \Exception
192 * Invalid partials configuration.
193 */
194 public function getPartials($name) {
195 $module = $this->getModule($name);
196 $result = array();
197 if (isset($module['partials'])) {
198 foreach ($module['partials'] as $partialDir) {
199 $partialDir = $this->res->getPath($module['ext']) . '/' . $partialDir;
200 $files = \CRM_Utils_File::findFiles($partialDir, '*.html', TRUE);
201 foreach ($files as $file) {
202 $filename = '~/' . $name . '/' . $file;
203 $result[$filename] = file_get_contents($partialDir . '/' . $file);
204 }
205 }
206 }
207 return $result;
208 }
209
210 /**
211 * Get list of translated strings for a module.
212 *
213 * @param string $name
214 * Angular module name.
215 * @return array
216 * Translated strings: array(string $orig => string $translated).
217 */
218 public function getTranslatedStrings($name) {
219 $module = $this->getModule($name);
220 $result = array();
221 $strings = $this->getStrings($name);
222 foreach ($strings as $string) {
223 // TODO: should we pass translation domain based on $module[ext] or $module[tsDomain]?
224 // It doesn't look like client side really supports the domain right now...
225 $translated = ts($string, array(
226 'domain' => array($module['ext'], NULL),
227 ));
228 if ($translated != $string) {
229 $result[$string] = $translated;
230 }
231 }
232 return $result;
233 }
234
235 /**
236 * Get list of translatable strings for a module.
237 *
238 * @param string $name
239 * Angular module name.
240 * @return array
241 * Translatable strings.
242 */
243 public function getStrings($name) {
244 $module = $this->getModule($name);
245 $result = array();
246 if (isset($module['js'])) {
247 foreach ($module['js'] as $file) {
248 $strings = $this->res->getStrings()->get(
249 $module['ext'],
250 $this->res->getPath($module['ext'], $file),
251 'text/javascript'
252 );
253 $result = array_unique(array_merge($result, $strings));
254 }
255 }
256 if (isset($module['partials'])) {
257 foreach ($module['partials'] as $partialDir) {
258 $partialDir = $this->res->getPath($module['ext']) . '/' . $partialDir;
259 $files = \CRM_Utils_File::findFiles($partialDir, '*.html');
260 foreach ($files as $file) {
261 $strings = $this->res->getStrings()->get(
262 $module['ext'],
263 $file,
264 'text/html'
265 );
266 $result = array_unique(array_merge($result, $strings));
267 }
268 }
269 }
270 return $result;
271 }
272
273 /**
274 * Get resources for one or more modules.
275 *
276 * @param string|array $moduleNames
277 * List of module names.
278 * @param string $resType
279 * Type of resource ('js', 'css', 'settings').
280 * @param string $refType
281 * Type of reference to the resource ('cacheUrl', 'rawUrl', 'path', 'settings').
282 * @return array
283 * List of URLs or paths.
284 * @throws \CRM_Core_Exception
285 */
286 public function getResources($moduleNames, $resType, $refType) {
287 $result = array();
288 $moduleNames = (array) $moduleNames;
289 foreach ($moduleNames as $moduleName) {
290 $module = $this->getModule($moduleName);
291 if (isset($module[$resType])) {
292 foreach ($module[$resType] as $file) {
293 switch ($refType) {
294 case 'path':
295 $result[] = $this->res->getPath($module['ext'], $file);
296 break;
297
298 case 'rawUrl':
299 $result[] = $this->res->getUrl($module['ext'], $file);
300 break;
301
302 case 'cacheUrl':
303 $result[] = $this->res->getUrl($module['ext'], $file, TRUE);
304 break;
305
306 case 'settings':
307 if (!empty($module[$resType])) {
308 $result[$moduleName] = $module[$resType];
309 }
310 break;
311
312 default:
313 throw new \CRM_Core_Exception("Unrecognized resource format");
314 }
315 }
316 }
317 }
318 return $result;
319 }
320
321 }