Update X12_5010_837P.php (#2599)
[openemr.git] / src / Core / Header.php
blob18850b2fc05f21cabc2400f3e840b3013c634150
1 <?php
2 /**
3 * OpenEMR <https://open-emr.org>.
5 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
6 */
8 namespace OpenEMR\Core;
10 use Symfony\Component\Yaml\Yaml;
11 use Symfony\Component\Yaml\Exception\ParseException;
13 /**
14 * Class Header.
16 * Helper class to generate some `<script>` and `<link>` elements based on a
17 * configuration file. This file would be a good place to include other helpers
18 * for creating a `<head>` element, but for now it sufficently handles the
19 * `setupHeader()`
21 * @package OpenEMR
22 * @subpackage Core
23 * @author Robert Down <robertdown@live.com>
24 * @copyright Copyright (c) 2017 Robert Down
26 class Header
28 private static $scripts;
29 private static $links;
31 /**
32 * Setup various <head> elements.
34 * See root_dir/config/config.yaml for available assets
36 * Example usage in a PHP view script:
37 * ```php
38 * // Top of script with require_once statements
39 * use OpenEMR\Core\Header;
41 * // Inside of <head>
42 * // If no special assets are needed:
43 * Header::setupHeader();
45 * // If 1 special asset is needed:
46 * Header::setupHeader('key-of-asset');
48 * // If 2 or more assets are needed:
49 * Header::setupHeader(['array', 'of', 'keys']);
51 * // If wish to not include a normally autoloaded asset
52 * Header::setupHeader('no_main-theme');
53 * ```
55 * Inside of a smarty template, use | (pipe) delimited string of key names
56 * ```php
57 * {headerTemplate}
58 * {headerTemplate assets='key-of-asset'} (1 optional assets)
59 * {headerTemplate assets='array|of|keys'} (multiple optional assets. ie. via | delimiter)
60 * ```
62 * The above example will render `<script>` tags and `<link>` tag which
63 * bring in the requested assets from config.yaml
65 * @param array|string $assets Asset(s) to include
66 * @throws ParseException If unable to parse the config file
67 * @return string
69 public static function setupHeader($assets = [])
71 try {
72 echo self::includeAsset($assets);
73 } catch (\InvalidArgumentException $e) {
74 error_log(errorLogEscape($e->getMessage()));
78 /**
79 * Include an asset from a config file.
81 * Static function to read in a YAML file into an array, check if the
82 * $assets keys are in the config file, and from the config file generate
83 * the HTML for a `<script>` or `<link>` tag.
85 * This is a private function, use Header::setupHeader() instead
87 * @param array|string $assets Asset(s) to include
88 * @throws ParseException If unable to parse the config file
89 * @return string
91 private static function includeAsset($assets = [])
94 if (is_string($assets)) {
95 $assets = [$assets];
98 // @TODO Hard coded the path to the config file, not good RD 2017-05-27
99 $map = self::readConfigFile("{$GLOBALS['fileroot']}/config/config.yaml");
100 self::$scripts = [];
101 self::$links = [];
103 self::parseConfigFile($map, $assets);
105 /* adding custom assets in addition */
106 if (is_file("{$GLOBALS['fileroot']}/custom/assets/custom.yaml")) {
107 $customMap = self::readConfigFile("{$GLOBALS['fileroot']}/custom/assets/custom.yaml");
108 self::parseConfigFile($customMap);
111 $linksStr = implode("", self::$links);
112 $scriptsStr = implode("", self::$scripts);
113 return "\n{$linksStr}\n{$scriptsStr}\n";
117 * Parse assets from config file
119 * @param array $map Assets to parse into self::$scripts and self::$links
120 * @param array $selectedAssets
121 * @return void
123 private static function parseConfigFile($map, $selectedAssets = array())
125 foreach ($map as $k => $opts) {
126 $autoload = (isset($opts['autoload'])) ? $opts['autoload'] : false;
127 $allowNoLoad= (isset($opts['allowNoLoad'])) ? $opts['allowNoLoad'] : false;
128 $alreadyBuilt = (isset($opts['alreadyBuilt'])) ? $opts['alreadyBuilt'] : false;
129 $loadInFile = (isset($opts['loadInFile'])) ? $opts['loadInFile'] : false;
130 $rtl = (isset($opts['rtl'])) ? $opts['rtl'] : false;
132 if ($autoload === true || in_array($k, $selectedAssets) || ($loadInFile && $loadInFile === self::getCurrentFile())) {
133 if ($allowNoLoad === true) {
134 if (in_array("no_" . $k, $selectedAssets)) {
135 continue;
139 $tmp = self::buildAsset($opts, $alreadyBuilt);
141 foreach ($tmp['scripts'] as $s) {
142 self::$scripts[] = $s;
145 foreach ($tmp['links'] as $l) {
146 self::$links[] = $l;
149 if ($rtl && $_SESSION['language_direction'] == 'rtl') {
150 $tmpRtl = self::buildAsset($rtl, $alreadyBuilt);
151 foreach ($tmpRtl['scripts'] as $s) {
152 self::$scripts[] = $s;
155 foreach ($tmpRtl['links'] as $l) {
156 self::$links[] = $l;
164 * Build an html element from config options.
166 * @var array $opts Options
167 * @var boolean $alreadyBuilt - This means the path with cache busting segment has already been built
168 * @return array Array with `scripts` and `links` keys which contain arrays of elements
170 private static function buildAsset($opts = array(), $alreadyBuilt = false)
172 $script = (isset($opts['script'])) ? $opts['script'] : false;
173 $link = (isset($opts['link'])) ? $opts['link'] : false;
174 $path = (isset($opts['basePath'])) ? $opts['basePath'] : '';
175 $basePath = self::parsePlaceholders($path);
177 $scripts = [];
178 $links = [];
180 if ($script) {
181 $script = self::parsePlaceholders($script);
182 if ($alreadyBuilt) {
183 $path = $script;
184 } else {
185 $path = self::createFullPath($basePath, $script);
187 $scripts[] = self::createElement($path, 'script', $alreadyBuilt);
190 if ($link) {
191 if (!is_string($link) && !is_array($link)) {
192 throw new \InvalidArgumentException("Link must be of type string or array");
195 if (is_string($link)) {
196 $link = [$link];
199 foreach ($link as $l) {
200 $l = self::parsePlaceholders($l);
201 if ($alreadyBuilt) {
202 $path = $l;
203 } else {
204 $path = self::createFullPath($basePath, $l);
206 $links[] = self::createElement($path, 'link', $alreadyBuilt);
210 return ['scripts' => $scripts, 'links' => $links];
214 * Parse a string for $GLOBAL key placeholders %key-name%.
216 * Perform a regex match all in the given subject for anything wrapped in
217 * percent signs `%some-key%` and if that string exists in the $GLOBALS
218 * array, will replace the occurence with the value of that key.
220 * @param string $subject String containing placeholders (%key-name%)
221 * @return string The new string with properly replaced keys
223 public static function parsePlaceholders($subject)
225 $re = '/%(.*)%/';
226 $matches = [];
227 preg_match_all($re, $subject, $matches, PREG_SET_ORDER, 0);
229 foreach ($matches as $match) {
230 if (array_key_exists($match[1], $GLOBALS)) {
231 $subject = str_replace($match[0], $GLOBALS["{$match[1]}"], $subject);
235 return $subject;
239 * Create the actual HTML element.
241 * @param string $path File path to load
242 * @param string $type Must be `script` or `link`
243 * @return string mixed HTML element
245 private static function createElement($path, $type, $alreadyBuilt)
248 $script = "<script type=\"text/javascript\" src=\"%path%\"></script>\n";
249 $link = "<link rel=\"stylesheet\" href=\"%path%\" type=\"text/css\">\n";
251 $template = ($type == 'script') ? $script : $link;
252 if (!$alreadyBuilt) {
253 $v = $GLOBALS['v_js_includes'];
254 $path = $path . "?v={$v}";
256 return str_replace("%path%", $path, $template);
260 * Create a full path from given parts.
262 * @param string $base Base path
263 * @param string $path specific path / filename
264 * @return string The full path
266 private static function createFullPath($base, $path)
268 return $base . $path;
272 * Read a config file and turn it into an array.
274 * @param string $file Full path to filename
275 * @return array Array of assets
277 private static function readConfigFile($file)
279 try {
280 $config = Yaml::parse(file_get_contents($file));
281 return $config['assets'];
282 } catch (ParseException $e) {
283 error_log(errorLogEscape($e->getMessage()));
284 // @TODO need to handle this better. RD 2017-05-24
289 * Return relative path to current file
291 * @return string The current file
293 private static function getCurrentFile()
295 //remove web root and query string
296 return str_replace($GLOBALS['webroot'].'/', '', strtok($_SERVER["REQUEST_URI"], '?'));