1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Licensed under The GPL-3.0 License |
4
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
5
|
|
|
* Redistributions of files must retain the above copyright notice. |
6
|
|
|
* |
7
|
|
|
* @since 2.0.0 |
8
|
|
|
* @author Christopher Castro <[email protected]> |
9
|
|
|
* @link http://www.quickappscms.org |
10
|
|
|
* @license http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
use Cake\Cache\Cache; |
14
|
|
|
use Cake\Core\Configure; |
15
|
|
|
use Cake\Datasource\ConnectionManager; |
16
|
|
|
use Cake\Error\Debugger; |
17
|
|
|
use Cake\Error\FatalErrorException; |
18
|
|
|
use Cake\Event\EventManager; |
19
|
|
|
use Cake\Filesystem\File; |
20
|
|
|
use Cake\Filesystem\Folder; |
21
|
|
|
use Cake\I18n\I18n; |
22
|
|
|
use Cake\ORM\Entity; |
23
|
|
|
use Cake\ORM\TableRegistry; |
24
|
|
|
use Cake\Routing\Router; |
25
|
|
|
use Cake\Utility\Inflector; |
26
|
|
|
use CMS\Core\Plugin; |
27
|
|
|
|
28
|
|
|
if (!function_exists('snapshot')) { |
29
|
|
|
/** |
30
|
|
|
* Stores some bootstrap-handy information into a persistent file. |
31
|
|
|
* |
32
|
|
|
* Information is stored in `TMP/snapshot.php` file, it contains |
33
|
|
|
* useful information such as enabled languages, content types slugs, installed |
34
|
|
|
* plugins, etc. |
35
|
|
|
* |
36
|
|
|
* You can read this information using `Configure::read()` as follow: |
37
|
|
|
* |
38
|
|
|
* ```php |
39
|
|
|
* Configure::read('QuickApps.<option>'); |
40
|
|
|
* ``` |
41
|
|
|
* |
42
|
|
|
* Or using the `quickapps()` global function: |
43
|
|
|
* |
44
|
|
|
* ```php |
45
|
|
|
* quickapps('<option>'); |
46
|
|
|
* ``` |
47
|
|
|
* |
48
|
|
|
* @return void |
49
|
|
|
*/ |
50
|
|
|
function snapshot() |
51
|
|
|
{ |
52
|
|
|
if (Cache::config('default')) { |
|
|
|
|
53
|
|
|
Cache::clear(false, 'default'); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
if (Cache::config('_cake_core_')) { |
|
|
|
|
57
|
|
|
Cache::clear(false, '_cake_core_'); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
if (Cache::config('_cake_model_')) { |
|
|
|
|
61
|
|
|
Cache::clear(false, '_cake_model_'); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
$versionPath = QUICKAPPS_CORE . 'VERSION.txt'; |
65
|
|
|
$snapshot = [ |
66
|
|
|
'version' => null, |
67
|
|
|
'content_types' => [], |
68
|
|
|
'plugins' => [], |
69
|
|
|
'options' => [], |
70
|
|
|
'languages' => [], |
71
|
|
|
'aspects' => [], |
72
|
|
|
]; |
73
|
|
|
|
74
|
|
|
if (is_readable($versionPath)) { |
75
|
|
|
$versionFile = file($versionPath); |
76
|
|
|
$snapshot['version'] = trim(array_pop($versionFile)); |
77
|
|
|
} else { |
78
|
|
|
die(sprintf('Missing file: %s', $versionPath)); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
if (ConnectionManager::config('default')) { |
|
|
|
|
82
|
|
|
if (!TableRegistry::exists('SnapshotPlugins')) { |
83
|
|
|
$PluginTable = TableRegistry::get('SnapshotPlugins', ['table' => 'plugins']); |
84
|
|
|
} else { |
85
|
|
|
$PluginTable = TableRegistry::get('SnapshotPlugins'); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
if (!TableRegistry::exists('SnapshotContentTypes')) { |
89
|
|
|
$ContentTypesTable = TableRegistry::get('SnapshotContentTypes', ['table' => 'content_types']); |
90
|
|
|
} else { |
91
|
|
|
$ContentTypesTable = TableRegistry::get('SnapshotContentTypes'); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
if (!TableRegistry::exists('SnapshotLanguages')) { |
95
|
|
|
$LanguagesTable = TableRegistry::get('SnapshotLanguages', ['table' => 'languages']); |
96
|
|
|
} else { |
97
|
|
|
$LanguagesTable = TableRegistry::get('SnapshotLanguages'); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
if (!TableRegistry::exists('SnapshotOptions')) { |
101
|
|
|
$OptionsTable = TableRegistry::get('SnapshotOptions', ['table' => 'options']); |
102
|
|
|
} else { |
103
|
|
|
$OptionsTable = TableRegistry::get('SnapshotOptions'); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
$PluginTable->schema(['value' => 'serialized']); |
|
|
|
|
107
|
|
|
$OptionsTable->schema(['value' => 'serialized']); |
|
|
|
|
108
|
|
|
|
109
|
|
|
$plugins = $PluginTable->find() |
110
|
|
|
->select(['name', 'package', 'status']) |
111
|
|
|
->order([ |
112
|
|
|
'ordering' => 'ASC', |
113
|
|
|
'name' => 'ASC', |
114
|
|
|
]) |
115
|
|
|
->all(); |
116
|
|
|
$contentTypes = $ContentTypesTable->find() |
117
|
|
|
->select(['slug']) |
118
|
|
|
->all(); |
119
|
|
|
$languages = $LanguagesTable->find() |
120
|
|
|
->where(['status' => 1]) |
121
|
|
|
->order(['ordering' => 'ASC']) |
122
|
|
|
->all(); |
123
|
|
|
$options = $OptionsTable->find() |
124
|
|
|
->select(['name', 'value']) |
125
|
|
|
->where(['autoload' => 1]) |
126
|
|
|
->all(); |
127
|
|
|
|
128
|
|
|
foreach ($contentTypes as $contentType) { |
129
|
|
|
$snapshot['content_types'][] = $contentType->slug; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
foreach ($options as $option) { |
133
|
|
|
$snapshot['options'][$option->name] = $option->value; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
foreach ($languages as $language) { |
137
|
|
|
list($languageCode, $countryCode) = localeSplit($language->code); |
138
|
|
|
$snapshot['languages'][$language->code] = [ |
139
|
|
|
'name' => $language->name, |
140
|
|
|
'locale' => $language->code, |
141
|
|
|
'code' => $languageCode, |
142
|
|
|
'country' => $countryCode, |
143
|
|
|
'direction' => $language->direction, |
144
|
|
|
'icon' => $language->icon, |
145
|
|
|
]; |
146
|
|
|
} |
147
|
|
|
} else { |
148
|
|
|
$plugins = []; |
149
|
|
|
foreach (Plugin::scan() as $plugin => $path) { |
150
|
|
|
$plugins[] = new Entity([ |
151
|
|
|
'name' => $plugin, |
152
|
|
|
'status' => true, |
153
|
|
|
'package' => 'quickapps-plugins', |
154
|
|
|
]); |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
$folder = new Folder(QUICKAPPS_CORE . 'src/Aspect/'); |
159
|
|
|
foreach ($folder->read(false, false, true)[1] as $classFile) { |
160
|
|
|
$className = basename(preg_replace('/\.php$/', '', $classFile)); |
161
|
|
|
if (!in_array($className, ['AppAspect', 'Aspect'])) { |
162
|
|
|
$snapshot['aspects'][] = "CMS\\Aspect\\{$className}"; |
163
|
|
|
} |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
foreach ($plugins as $plugin) { |
167
|
|
|
$pluginPath = false; |
168
|
|
|
|
169
|
|
|
if (isset(Plugin::scan()[$plugin->name])) { |
170
|
|
|
$pluginPath = Plugin::scan()[$plugin->name]; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
if ($pluginPath === false) { |
174
|
|
|
Debugger::log(sprintf('Plugin "%s" was found in DB but QuickAppsCMS was unable to locate its root directory.', $plugin->name)); |
175
|
|
|
continue; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
if (!Plugin::validateJson("{$pluginPath}/composer.json")) { |
179
|
|
|
Debugger::log(sprintf('Plugin "%s" has a corrupt "composer.json" file (%s).', $plugin->name, "{$pluginPath}/composer.json")); |
180
|
|
|
continue; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
$aspectsPath = "{$pluginPath}/src/Aspect/"; |
184
|
|
|
$eventsPath = "{$pluginPath}/src/Event/"; |
185
|
|
|
$fieldsPath = "{$pluginPath}/src/Field/"; |
186
|
|
|
$helpFiles = glob($pluginPath . '/src/Template/Element/Help/help*.ctp'); |
187
|
|
|
$isTheme = str_ends_with($plugin->name, 'Theme'); |
188
|
|
|
$status = (bool)$plugin->status; |
189
|
|
|
$humanName = ''; |
190
|
|
|
$aspects = []; |
191
|
|
|
$eventListeners = []; |
192
|
|
|
$fields = []; |
193
|
|
|
|
194
|
|
|
$subspaces = [ |
195
|
|
|
$aspectsPath => 'Aspect', |
196
|
|
|
$eventsPath => 'Event', |
197
|
|
|
$fieldsPath => 'Field', |
198
|
|
|
]; |
199
|
|
|
$varnames = [ |
200
|
|
|
$aspectsPath => 'aspects', |
201
|
|
|
$eventsPath => 'eventListeners', |
202
|
|
|
$fieldsPath => 'fields', |
203
|
|
|
]; |
204
|
|
|
foreach ([$aspectsPath, $eventsPath, $fieldsPath] as $path) { |
205
|
|
|
if (is_dir($path)) { |
206
|
|
|
$Folder = new Folder($path); |
207
|
|
|
foreach ($Folder->read(false, false, true)[1] as $classFile) { |
208
|
|
|
$className = basename(preg_replace('/\.php$/', '', $classFile)); |
209
|
|
|
$subspace = $subspaces[$path]; |
210
|
|
|
$varname = $varnames[$path]; |
211
|
|
|
$namespace = "{$plugin->name}\\{$subspace}\\"; |
212
|
|
|
${$varname}[] = $namespace . $className; |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
if (is_readable("{$pluginPath}composer.json")) { |
218
|
|
|
$json = (array)json_decode(file_get_contents("{$pluginPath}composer.json"), true); |
219
|
|
|
if (!empty($json['extra']['human-name'])) { |
220
|
|
|
$humanName = $json['extra']['human-name']; |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
if (empty($humanName)) { |
225
|
|
|
$humanName = (string)Inflector::humanize((string)Inflector::underscore($plugin->name)); |
226
|
|
|
if ($isTheme) { |
227
|
|
|
$humanName = trim(str_replace_last('Theme', '', $humanName)); |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
$snapshot['plugins'][$plugin->name] = [ |
232
|
|
|
'name' => $plugin->name, |
233
|
|
|
'humanName' => $humanName, |
234
|
|
|
'package' => $plugin->package, |
235
|
|
|
'isTheme' => $isTheme, |
236
|
|
|
'hasHelp' => !empty($helpFiles), |
237
|
|
|
'hasSettings' => is_readable($pluginPath . '/src/Template/Element/settings.ctp'), |
238
|
|
|
'aspects' => $aspects, |
239
|
|
|
'eventListeners' => $eventListeners, |
240
|
|
|
'fields' => $fields, |
241
|
|
|
'status' => $status, |
242
|
|
|
'path' => $pluginPath, |
243
|
|
|
]; |
244
|
|
|
|
245
|
|
|
if ($status) { |
246
|
|
|
$snapshot['aspects'] = array_merge($snapshot['aspects'], $aspects); |
247
|
|
|
} |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
Configure::write('QuickApps', $snapshot); |
251
|
|
|
if (!Configure::dump('snapshot', 'QuickApps', ['QuickApps'])) { |
252
|
|
|
die('QuickAppsCMS was unable to create a snapshot file, check that PHP have permission to write to the "/tmp" directory.'); |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
if (!function_exists('normalizePath')) { |
258
|
|
|
/** |
259
|
|
|
* Normalizes the given file system path, makes sure that all DIRECTORY_SEPARATOR |
260
|
|
|
* are the same according to current OS, so you won't get a mix of "/" and "\" in |
261
|
|
|
* your paths. |
262
|
|
|
* |
263
|
|
|
* ### Example: |
264
|
|
|
* |
265
|
|
|
* ```php |
266
|
|
|
* normalizePath('/path\to/filename\with\backslash.zip'); |
267
|
|
|
* // output LINUX: /path/to/filename\with\backslashes.zip |
268
|
|
|
* // output WINDOWS: /path/to/filename/with/backslashes.zip |
269
|
|
|
* ``` |
270
|
|
|
* |
271
|
|
|
* You can indicate which "directory separator" symbol to use using the second |
272
|
|
|
* argument: |
273
|
|
|
* |
274
|
|
|
* ```php |
275
|
|
|
* normalizePath('/path\to/filename\with\backslash.zip', '\'); |
276
|
|
|
* // output LINUX & WIDNOWS: \path\to\filename\with\backslash.zip |
277
|
|
|
* ``` |
278
|
|
|
* |
279
|
|
|
* By defaults uses DIRECTORY_SEPARATOR as symbol. |
280
|
|
|
* |
281
|
|
|
* @param string $path The path to normalize |
282
|
|
|
* @param string $ds Directory separator character, defaults to DIRECTORY_SEPARATOR |
283
|
|
|
* @return string Normalized $path |
284
|
|
|
*/ |
285
|
|
|
function normalizePath($path, $ds = DIRECTORY_SEPARATOR) |
286
|
|
|
{ |
287
|
|
|
$tail = ''; |
288
|
|
|
$base = $path; |
289
|
|
|
|
290
|
|
|
if (DIRECTORY_SEPARATOR === '/') { |
291
|
|
|
$lastDS = strrpos($path, $ds); |
292
|
|
|
$tail = $lastDS !== false && $lastDS !== strlen($path) - 1 ? substr($path, $lastDS + 1) : ''; |
293
|
|
|
$base = $tail ? substr($path, 0, $lastDS + 1) : $path; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
$path = str_replace(['/', '\\', "{$ds}{$ds}"], $ds, $base); |
297
|
|
|
$path = str_replace("{$ds}{$ds}", $ds, $path); |
298
|
|
|
$path .= $tail; |
299
|
|
|
|
300
|
|
|
return $path; |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
if (!function_exists('quickapps')) { |
305
|
|
|
/** |
306
|
|
|
* Shortcut for reading QuickApps's snapshot configuration. |
307
|
|
|
* |
308
|
|
|
* For example, `quickapps('variables');` maps to |
309
|
|
|
* `Configure::read('QuickApps.variables');`. If this function is used with |
310
|
|
|
* no arguments, `quickapps()`, the entire snapshot will be returned. |
311
|
|
|
* |
312
|
|
|
* @param string $key The key to read from snapshot, or null to read the whole |
313
|
|
|
* snapshot's info |
314
|
|
|
* @return mixed |
315
|
|
|
*/ |
316
|
|
|
function quickapps($key = null) |
317
|
|
|
{ |
318
|
|
|
if ($key !== null) { |
319
|
|
|
return Configure::read("QuickApps.{$key}"); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
return Configure::read('QuickApps'); |
323
|
|
|
} |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
if (!function_exists('option')) { |
327
|
|
|
/** |
328
|
|
|
* Shortcut for getting an option value from "options" DB table. |
329
|
|
|
* |
330
|
|
|
* The second arguments, $default, is used as default value to return if no |
331
|
|
|
* value is found. If not value is found and not default values was given this |
332
|
|
|
* function will return `false`. |
333
|
|
|
* |
334
|
|
|
* ### Example: |
335
|
|
|
* |
336
|
|
|
* ```php |
337
|
|
|
* option('site_slogan'); |
338
|
|
|
* ``` |
339
|
|
|
* |
340
|
|
|
* @param string $name Name of the option to retrieve. e.g. `front_theme`, |
341
|
|
|
* `default_language`, `site_slogan`, etc |
342
|
|
|
* @param mixed $default The default value to return if no value is found |
343
|
|
|
* @return mixed Current value for the specified option. If the specified option |
344
|
|
|
* does not exist, returns boolean FALSE |
345
|
|
|
*/ |
346
|
|
|
function option($name, $default = false) |
347
|
|
|
{ |
348
|
|
|
if (Configure::check("QuickApps.options.{$name}")) { |
349
|
|
|
return Configure::read("QuickApps.options.{$name}"); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
if (ConnectionManager::config('default')) { |
|
|
|
|
353
|
|
|
$option = TableRegistry::get('Options') |
354
|
|
|
->find() |
355
|
|
|
->where(['Options.name' => $name]) |
356
|
|
|
->first(); |
357
|
|
|
if ($option) { |
358
|
|
|
return $option->value; |
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
return $default; |
363
|
|
|
} |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
if (!function_exists('plugin')) { |
367
|
|
|
/** |
368
|
|
|
* Shortcut for "Plugin::get()". |
369
|
|
|
* |
370
|
|
|
* ### Example: |
371
|
|
|
* |
372
|
|
|
* ```php |
373
|
|
|
* $specialSetting = plugin('MyPlugin')->settings['special_setting']; |
374
|
|
|
* ``` |
375
|
|
|
* |
376
|
|
|
* @param string $plugin Plugin name to get, or null to get a collection of |
377
|
|
|
* all plugin objects |
378
|
|
|
* @return \CMS\Core\Package\PluginPackage|\Cake\Collection\Collection |
379
|
|
|
* @throws \Cake\Error\FatalErrorException When requested plugin was not found |
380
|
|
|
* @see \CMS\Core\Plugin::get() |
381
|
|
|
*/ |
382
|
|
|
function plugin($plugin = null) |
383
|
|
|
{ |
384
|
|
|
return Plugin::get($plugin); |
385
|
|
|
} |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
if (!function_exists('theme')) { |
389
|
|
|
/** |
390
|
|
|
* Gets the given (or in use) theme as a package object. |
391
|
|
|
* |
392
|
|
|
* ### Example: |
393
|
|
|
* |
394
|
|
|
* ```php |
395
|
|
|
* // current theme |
396
|
|
|
* $bgColor = theme()->settings['background_color']; |
397
|
|
|
* |
398
|
|
|
* // specific theme |
399
|
|
|
* $bgColor = theme('BlueTheme')->settings['background_color']; |
400
|
|
|
* ``` |
401
|
|
|
* |
402
|
|
|
* @param string|null $name Name of the theme to get, or null to get the theme |
403
|
|
|
* being used in current request |
404
|
|
|
* @return \CMS\Core\Package\PluginPackage |
405
|
|
|
* @throws \Cake\Error\FatalErrorException When theme could not be found |
406
|
|
|
*/ |
407
|
|
|
function theme($name = null) |
408
|
|
|
{ |
409
|
|
|
if ($name === null) { |
410
|
|
|
$option = Router::getRequest()->isAdmin() ? 'back_theme' : 'front_theme'; |
411
|
|
|
$name = option($option); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
$theme = Plugin::get() |
415
|
|
|
->filter(function ($plugin) use ($name) { |
416
|
|
|
return $plugin->isTheme && $plugin->name == $name; |
417
|
|
|
}) |
418
|
|
|
->first(); |
419
|
|
|
|
420
|
|
|
if ($theme) { |
421
|
|
|
return $theme; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
throw new FatalErrorException(__d('cms', 'Theme "{0}" was not found', $name)); |
425
|
|
|
} |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
if (!function_exists('listeners')) { |
429
|
|
|
/** |
430
|
|
|
* Returns a list of all registered event listeners within the provided event |
431
|
|
|
* manager, or within the global manager if not provided. |
432
|
|
|
* |
433
|
|
|
* @param \Cake\Event\EventManager\null $manager Event manager instance, or null |
434
|
|
|
* to use global manager instance. |
435
|
|
|
* @return array |
436
|
|
|
*/ |
437
|
|
|
function listeners(EventManager $manager = null) |
438
|
|
|
{ |
439
|
|
|
if ($manager === null) { |
440
|
|
|
$manager = EventManager::instance(); |
441
|
|
|
} |
442
|
|
|
$class = new \ReflectionClass($manager); |
443
|
|
|
$property = $class->getProperty('_listeners'); |
444
|
|
|
$property->setAccessible(true); |
445
|
|
|
$listeners = array_keys($property->getValue($manager)); |
446
|
|
|
|
447
|
|
|
return $listeners; |
448
|
|
|
} |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
if (!function_exists('packageSplit')) { |
452
|
|
|
/** |
453
|
|
|
* Splits a composer package syntax into its vendor and package name. |
454
|
|
|
* |
455
|
|
|
* Commonly used like `list($vendor, $package) = packageSplit($name);` |
456
|
|
|
* |
457
|
|
|
* ### Example: |
458
|
|
|
* |
459
|
|
|
* ```php |
460
|
|
|
* list($vendor, $package) = packageSplit('some-vendor/this-package', true); |
461
|
|
|
* echo "{$vendor} : {$package}"; |
462
|
|
|
* // prints: SomeVendor : ThisPackage |
463
|
|
|
* ``` |
464
|
|
|
* |
465
|
|
|
* @param string $name Package name. e.g. author-name/package-name |
466
|
|
|
* @param bool $camelize Set to true to Camelize each part |
467
|
|
|
* @return array Array with 2 indexes. 0 => vendor name, 1 => package name. |
468
|
|
|
*/ |
469
|
|
|
function packageSplit($name, $camelize = false) |
470
|
|
|
{ |
471
|
|
|
$pos = strrpos($name, '/'); |
472
|
|
|
if ($pos === false) { |
473
|
|
|
$parts = ['', $name]; |
474
|
|
|
} else { |
475
|
|
|
$parts = [substr($name, 0, $pos), substr($name, $pos + 1)]; |
476
|
|
|
} |
477
|
|
|
if ($camelize) { |
478
|
|
|
$parts[0] = Inflector::camelize(str_replace('-', '_', $parts[0])); |
479
|
|
|
if (!empty($parts[1])) { |
480
|
|
|
$parts[1] = Inflector::camelize(str_replace('-', '_', $parts[1])); |
481
|
|
|
} |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
return $parts; |
485
|
|
|
} |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
if (!function_exists('normalizeLocale')) { |
489
|
|
|
/** |
490
|
|
|
* Normalizes the given locale code. |
491
|
|
|
* |
492
|
|
|
* @param string $locale The locale code to normalize. e.g. `en-US` |
493
|
|
|
* @return string Normalized code. e.g. `en_US` |
494
|
|
|
*/ |
495
|
|
|
function normalizeLocale($locale) |
496
|
|
|
{ |
497
|
|
|
list($language, $region) = localeSplit($locale); |
498
|
|
|
|
499
|
|
|
return !empty($region) ? "{$language}_{$region}" : $language; |
500
|
|
|
} |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
if (!function_exists('aspects')) { |
504
|
|
|
/** |
505
|
|
|
* Gets a list of all active aspect classes. |
506
|
|
|
* |
507
|
|
|
* @return array |
508
|
|
|
*/ |
509
|
|
|
function aspects() |
510
|
|
|
{ |
511
|
|
|
return quickapps('aspects'); |
512
|
|
|
} |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
if (!function_exists('localeSplit')) { |
516
|
|
|
/** |
517
|
|
|
* Parses and splits the given locale code and returns its parts: language and |
518
|
|
|
* regional codes. |
519
|
|
|
* |
520
|
|
|
* ### Example: |
521
|
|
|
* |
522
|
|
|
* ```php |
523
|
|
|
* list($language, $region) = localeSplit('en_NZ'); |
524
|
|
|
* ``` |
525
|
|
|
* |
526
|
|
|
* IMPORTANT: Note that region code may be an empty string. |
527
|
|
|
* |
528
|
|
|
* @param string $localeId Locale code. e.g. "en_NZ" (or "en-NZ") for |
529
|
|
|
* "English New Zealand" |
530
|
|
|
* @return array Array with 2 indexes. 0 => language code, 1 => country code. |
531
|
|
|
*/ |
532
|
|
|
function localeSplit($localeId) |
533
|
|
|
{ |
534
|
|
|
$localeId = str_replace('-', '_', $localeId); |
535
|
|
|
$parts = explode('_', $localeId); |
536
|
|
|
$country = isset($parts[1]) ? strtoupper($parts[1]) : ''; |
537
|
|
|
$language = strtolower($parts[0]); |
538
|
|
|
|
539
|
|
|
return [$language, $country]; |
540
|
|
|
} |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
if (!function_exists('array_move')) { |
544
|
|
|
/** |
545
|
|
|
* Moves up or down the given element by index from a list array of elements. |
546
|
|
|
* |
547
|
|
|
* If item could not be moved, the original list will be returned. Valid values |
548
|
|
|
* for $direction are `up` or `down`. |
549
|
|
|
* |
550
|
|
|
* ### Example: |
551
|
|
|
* |
552
|
|
|
* ```php |
553
|
|
|
* array_move(['a', 'b', 'c'], 1, 'up'); |
554
|
|
|
* // returns: ['a', 'c', 'b'] |
555
|
|
|
* ``` |
556
|
|
|
* |
557
|
|
|
* @param array $list Numeric indexed array list of elements |
558
|
|
|
* @param int $index The index position of the element you want to move |
559
|
|
|
* @param string $direction Direction, 'up' or 'down' |
560
|
|
|
* @return array Reordered original list. |
561
|
|
|
*/ |
562
|
|
|
function array_move(array $list, $index, $direction) |
563
|
|
|
{ |
564
|
|
|
$maxIndex = count($list) - 1; |
565
|
|
|
if ($direction == 'down') { |
566
|
|
|
if (0 < $index && $index <= $maxIndex) { |
567
|
|
|
$item = $list[$index]; |
568
|
|
|
$list[$index] = $list[$index - 1]; |
569
|
|
|
$list[$index - 1] = $item; |
570
|
|
|
} |
571
|
|
|
} elseif ($direction == 'up') { |
572
|
|
|
if ($index >= 0 && $maxIndex > $index) { |
573
|
|
|
$item = $list[$index]; |
574
|
|
|
$list[$index] = $list[$index + 1]; |
575
|
|
|
$list[$index + 1] = $item; |
576
|
|
|
|
577
|
|
|
return $list; |
578
|
|
|
} |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
return $list; |
582
|
|
|
} |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
if (!function_exists('php_eval')) { |
586
|
|
|
/** |
587
|
|
|
* Evaluate a string of PHP code. |
588
|
|
|
* |
589
|
|
|
* This is a wrapper around PHP's eval(). It uses output buffering to capture both |
590
|
|
|
* returned and printed text. Unlike eval(), we require code to be surrounded by |
591
|
|
|
* <?php ?> tags; in other words, we evaluate the code as if it were a stand-alone |
592
|
|
|
* PHP file. |
593
|
|
|
* |
594
|
|
|
* Using this wrapper also ensures that the PHP code which is evaluated can not |
595
|
|
|
* overwrite any variables in the calling code, unlike a regular eval() call. |
596
|
|
|
* |
597
|
|
|
* ### Usage: |
598
|
|
|
* |
599
|
|
|
* ```php |
600
|
|
|
* echo php_eval('<?php return "Hello {$world}!"; ?>', ['world' => 'WORLD']); |
601
|
|
|
* // output: Hello WORLD |
602
|
|
|
* ``` |
603
|
|
|
* |
604
|
|
|
* @param string $code The code to evaluate |
605
|
|
|
* @param array $args Array of arguments as `key` => `value` pairs, evaluated |
606
|
|
|
* code can access this variables |
607
|
|
|
* @return string |
608
|
|
|
*/ |
609
|
|
|
function php_eval($code, $args = []) |
610
|
|
|
{ |
611
|
|
|
ob_start(); |
612
|
|
|
extract($args); |
613
|
|
|
print eval('?>' . $code); |
614
|
|
|
$output = ob_get_contents(); |
615
|
|
|
ob_end_clean(); |
616
|
|
|
|
617
|
|
|
return $output; |
618
|
|
|
} |
619
|
|
|
} |
620
|
|
|
|
621
|
|
|
if (!function_exists('get_this_class_methods')) { |
622
|
|
|
/** |
623
|
|
|
* Return only the methods for the given object. It will strip out inherited |
624
|
|
|
* methods. |
625
|
|
|
* |
626
|
|
|
* @param string $class Class name |
627
|
|
|
* @return array List of methods |
628
|
|
|
*/ |
629
|
|
|
function get_this_class_methods($class) |
630
|
|
|
{ |
631
|
|
|
$primary = get_class_methods($class); |
632
|
|
|
|
633
|
|
|
if ($parent = get_parent_class($class)) { |
634
|
|
|
$secondary = get_class_methods($parent); |
635
|
|
|
$methods = array_diff($primary, $secondary); |
636
|
|
|
} else { |
637
|
|
|
$methods = $primary; |
638
|
|
|
} |
639
|
|
|
|
640
|
|
|
return $methods; |
641
|
|
|
} |
642
|
|
|
} |
643
|
|
|
|
644
|
|
View Code Duplication |
if (!function_exists('str_replace_once')) { |
645
|
|
|
/** |
646
|
|
|
* Replace the first occurrence only. |
647
|
|
|
* |
648
|
|
|
* ### Example: |
649
|
|
|
* |
650
|
|
|
* ```php |
651
|
|
|
* echo str_replace_once('A', 'a', 'AAABBBCCC'); |
652
|
|
|
* // out: aAABBBCCC |
653
|
|
|
* ``` |
654
|
|
|
* |
655
|
|
|
* @param string|array $search The value being searched for |
656
|
|
|
* @param string $replace The replacement value that replaces found search value |
657
|
|
|
* @param string $subject The string being searched and replaced on |
658
|
|
|
* @return string A string with the replaced value |
659
|
|
|
*/ |
660
|
|
|
function str_replace_once($search, $replace, $subject) |
661
|
|
|
{ |
662
|
|
|
if (!is_array($search)) { |
663
|
|
|
$search = [$search]; |
664
|
|
|
} |
665
|
|
|
|
666
|
|
|
foreach ($search as $s) { |
667
|
|
|
if ($s !== '' && strpos($subject, $s) !== false) { |
668
|
|
|
return substr_replace($subject, $replace, strpos($subject, $s), strlen($s)); |
669
|
|
|
} |
670
|
|
|
} |
671
|
|
|
|
672
|
|
|
return $subject; |
673
|
|
|
} |
674
|
|
|
} |
675
|
|
|
|
676
|
|
View Code Duplication |
if (!function_exists('str_replace_last')) { |
677
|
|
|
/** |
678
|
|
|
* Replace the last occurrence only. |
679
|
|
|
* |
680
|
|
|
* ### Example: |
681
|
|
|
* |
682
|
|
|
* ```php |
683
|
|
|
* echo str_replace_once('A', 'a', 'AAABBBCCC'); |
684
|
|
|
* // out: AAaBBBCCC |
685
|
|
|
* ``` |
686
|
|
|
* |
687
|
|
|
* @param string|array $search The value being searched for |
688
|
|
|
* @param string $replace The replacement value that replaces found search value |
689
|
|
|
* @param string $subject The string being searched and replaced on |
690
|
|
|
* @return string A string with the replaced value |
691
|
|
|
*/ |
692
|
|
|
function str_replace_last($search, $replace, $subject) |
693
|
|
|
{ |
694
|
|
|
if (!is_array($search)) { |
695
|
|
|
$search = [$search]; |
696
|
|
|
} |
697
|
|
|
|
698
|
|
|
foreach ($search as $s) { |
699
|
|
|
if ($s !== '' && strrpos($subject, $s) !== false) { |
700
|
|
|
$subject = substr_replace($subject, $replace, strrpos($subject, $s), strlen($s)); |
701
|
|
|
} |
702
|
|
|
} |
703
|
|
|
|
704
|
|
|
return $subject; |
705
|
|
|
} |
706
|
|
|
} |
707
|
|
|
|
708
|
|
|
if (!function_exists('str_starts_with')) { |
709
|
|
|
/** |
710
|
|
|
* Check if $haystack string starts with $needle string. |
711
|
|
|
* |
712
|
|
|
* ### Example: |
713
|
|
|
* |
714
|
|
|
* ```php |
715
|
|
|
* str_starts_with('lorem ipsum', 'lo'); // true |
716
|
|
|
* str_starts_with('lorem ipsum', 'ipsum'); // false |
717
|
|
|
* ``` |
718
|
|
|
* |
719
|
|
|
* @param string $haystack The string to search in |
720
|
|
|
* @param string $needle The string to look for |
721
|
|
|
* @return bool |
722
|
|
|
*/ |
723
|
|
|
function str_starts_with($haystack, $needle) |
724
|
|
|
{ |
725
|
|
|
return |
726
|
|
|
$needle === '' || |
727
|
|
|
strpos($haystack, $needle) === 0; |
728
|
|
|
} |
729
|
|
|
} |
730
|
|
|
|
731
|
|
|
if (!function_exists('str_ends_with')) { |
732
|
|
|
/** |
733
|
|
|
* Check if $haystack string ends with $needle string. |
734
|
|
|
* |
735
|
|
|
* ### Example: |
736
|
|
|
* |
737
|
|
|
* ```php |
738
|
|
|
* str_ends_with('lorem ipsum', 'm'); // true |
739
|
|
|
* str_ends_with('dolorem sit amet', 'at'); // false |
740
|
|
|
* ``` |
741
|
|
|
* |
742
|
|
|
* @param string $haystack The string to search in |
743
|
|
|
* @param string $needle The string to look for |
744
|
|
|
* @return bool |
745
|
|
|
*/ |
746
|
|
|
function str_ends_with($haystack, $needle) |
747
|
|
|
{ |
748
|
|
|
return |
749
|
|
|
$needle === '' || |
750
|
|
|
substr($haystack, - strlen($needle)) === $needle; |
751
|
|
|
} |
752
|
|
|
} |
753
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.