PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/library/core/src/Header.php

https://github.com/tmccormi/openemr
PHP | 269 lines | 127 code | 29 blank | 113 comment | 19 complexity | 05fd6fbaea853a5caaa0d4d9b29c8d91 MD5 | raw file
  1. <?php
  2. /**
  3. * OpenEMR <http://open-emr.org>.
  4. *
  5. * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
  6. */
  7. namespace OpenEMR\Core;
  8. use Symfony\Component\Yaml\Yaml;
  9. use Symfony\Component\Yaml\Exception\ParseException;
  10. /**
  11. * Class Header.
  12. *
  13. * Helper class to generate some `<script>` and `<link>` elements based on a
  14. * configuration file. This file would be a good place to include other helpers
  15. * for creating a `<head>` element, but for now it sufficently handles the
  16. * `setupHeader()`
  17. *
  18. * @package OpenEMR
  19. * @subpackage Core
  20. * @author Robert Down <robertdown@live.com>
  21. * @copyright Copyright (c) 2017 Robert Down
  22. */
  23. class Header
  24. {
  25. /**
  26. * Setup various <head> elements.
  27. *
  28. * See root_dir/config/config.yaml for available assets
  29. *
  30. * Example usage in a PHP view script:
  31. * ```php
  32. * // Top of script with require_once statements
  33. * use OpenEMR\Core\Header;
  34. *
  35. * // Inside of <head>
  36. * // If no special assets are needed:
  37. * Header::setupHeader();
  38. *
  39. * // If 1 special asset is needed:
  40. * Header::setupHeader('key-of-asset');
  41. *
  42. * // If 2 or more assets are needed:
  43. * Header::setupHeader(['array', 'of', 'keys']);
  44. *
  45. * // If wish to not include a normally autoloaded asset
  46. * Header::setupHeader('no_main-theme');
  47. * ```
  48. *
  49. * Inside of a twig template (Parameters same as before):
  50. * ```html
  51. * {{ includeAsset() }}
  52. * ```
  53. *
  54. * Inside of a smarty template, use | (pipe) delimited string of key names
  55. * ```php
  56. * {headerTemplate}
  57. * {headerTemplate assets='key-of-asset'} (1 optional assets)
  58. * {headerTemplate assets='array|of|keys'} (multiple optional assets. ie. via | delimiter)
  59. * ```
  60. *
  61. * The above example will render `<script>` tags and `<link>` tag which
  62. * bring in the requested assets from config.yaml
  63. *
  64. * @param array|string $assets Asset(s) to include
  65. * @throws ParseException If unable to parse the config file
  66. * @return string
  67. */
  68. public static function setupHeader($assets = [])
  69. {
  70. try {
  71. html_header_show();
  72. echo self::includeAsset($assets);
  73. } catch (\InvalidArgumentException $e) {
  74. error_log($e->getMessage());
  75. }
  76. }
  77. /**
  78. * Include an asset from a config file.
  79. *
  80. * Static function to read in a YAML file into an array, check if the
  81. * $assets keys are in the config file, and from the config file generate
  82. * the HTML for a `<script>` or `<link>` tag.
  83. *
  84. * This is a private function, use Header::setupHeader() instead
  85. *
  86. * @param array|string $assets Asset(s) to include
  87. * @throws ParseException If unable to parse the config file
  88. * @return string
  89. */
  90. private static function includeAsset($assets = [])
  91. {
  92. if (is_string($assets)) {
  93. $assets = [$assets];
  94. }
  95. // @TODO Hard coded the path to the config file, not good RD 2017-05-27
  96. $map = self::readConfigFile("{$GLOBALS['fileroot']}/config/config.yaml");
  97. $scripts = [];
  98. $links = [];
  99. foreach ($map as $k => $opts) {
  100. $autoload = (isset($opts['autoload'])) ? $opts['autoload'] : false;
  101. $allowNoLoad= (isset($opts['allowNoLoad'])) ? $opts['allowNoLoad'] : false;
  102. $alreadyBuilt = (isset($opts['alreadyBuilt'])) ? $opts['alreadyBuilt'] : false;
  103. $rtl = (isset($opts['rtl'])) ? $opts['rtl'] : false;
  104. if ($autoload === true || in_array($k, $assets)) {
  105. if ($allowNoLoad === true) {
  106. if (in_array("no_" . $k, $assets)) {
  107. continue;
  108. }
  109. }
  110. $tmp = self::buildAsset($opts, $alreadyBuilt);
  111. foreach ($tmp['scripts'] as $s) {
  112. $scripts[] = $s;
  113. }
  114. foreach ($tmp['links'] as $l) {
  115. $links[] = $l;
  116. }
  117. if ($rtl && $_SESSION['language_direction'] == 'rtl') {
  118. $tmpRtl = self::buildAsset($rtl, $alreadyBuilt);
  119. foreach ($tmpRtl['scripts'] as $s) {
  120. $scripts[] = $s;
  121. }
  122. foreach ($tmpRtl['links'] as $l) {
  123. $links[] = $l;
  124. }
  125. }
  126. }
  127. }
  128. $linksStr = implode("", $links);
  129. $scriptsStr = implode("", $scripts);
  130. return "\n{$linksStr}\n{$scriptsStr}\n";
  131. }
  132. /**
  133. * Build an html element from config options.
  134. *
  135. * @var array $opts Options
  136. * @var boolean $alreadyBuilt - This means the path with cache busting segment has already been built
  137. * @return array Array with `scripts` and `links` keys which contain arrays of elements
  138. */
  139. private static function buildAsset($opts = array(), $alreadyBuilt = false)
  140. {
  141. $script = (isset($opts['script'])) ? $opts['script'] : false;
  142. $link = (isset($opts['link'])) ? $opts['link'] : false;
  143. $path = (isset($opts['basePath'])) ? $opts['basePath'] : '';
  144. $basePath = self::parsePlaceholders($path);
  145. $scripts = [];
  146. $links = [];
  147. if ($script) {
  148. $script = self::parsePlaceholders($script);
  149. if ($alreadyBuilt) {
  150. $path = $script;
  151. } else {
  152. $path = self::createFullPath($basePath, $script);
  153. }
  154. $scripts[] = self::createElement($path, 'script');
  155. }
  156. if ($link) {
  157. if (!is_string($link) && !is_array($link)) {
  158. throw new \InvalidArgumentException("Link must be of type string or array");
  159. }
  160. if (is_string($link)) {
  161. $link = [$link];
  162. }
  163. foreach ($link as $l) {
  164. $l = self::parsePlaceholders($l);
  165. if ($alreadyBuilt) {
  166. $path = $l;
  167. } else {
  168. $path = self::createFullPath($basePath, $l);
  169. }
  170. $links[] = self::createElement($path, 'link');
  171. }
  172. }
  173. return ['scripts' => $scripts, 'links' => $links];
  174. }
  175. /**
  176. * Parse a string for $GLOBAL key placeholders %key-name%.
  177. *
  178. * Perform a regex match all in the given subject for anything wrapped in
  179. * percent signs `%some-key%` and if that string exists in the $GLOBALS
  180. * array, will replace the occurence with the value of that key.
  181. *
  182. * @param string $subject String containing placeholders (%key-name%)
  183. * @return string The new string with properly replaced keys
  184. */
  185. public static function parsePlaceholders($subject)
  186. {
  187. $re = '/%(.*)%/';
  188. $matches = [];
  189. preg_match_all($re, $subject, $matches, PREG_SET_ORDER, 0);
  190. foreach ($matches as $match) {
  191. if (array_key_exists($match[1], $GLOBALS)) {
  192. $subject = str_replace($match[0], $GLOBALS["{$match[1]}"], $subject);
  193. }
  194. }
  195. return $subject;
  196. }
  197. /**
  198. * Create the actual HTML element.
  199. *
  200. * @param string $path File path to load
  201. * @param string $type Must be `script` or `link`
  202. * @return string mixed HTML element
  203. */
  204. private static function createElement($path, $type)
  205. {
  206. $script = "<script type=\"text/javascript\" src=\"%path%\"></script>\n";
  207. $link = "<link rel=\"stylesheet\" href=\"%path%\" type=\"text/css\">\n";
  208. $template = ($type == 'script') ? $script : $link;
  209. $v = $GLOBALS['v_js_includes'];
  210. $path = $path . "?v={$v}";
  211. return str_replace("%path%", $path, $template);
  212. }
  213. /**
  214. * Create a full path from given parts.
  215. *
  216. * @param string $base Base path
  217. * @param string $path specific path / filename
  218. * @return string The full path
  219. */
  220. private static function createFullPath($base, $path)
  221. {
  222. return $base . $path;
  223. }
  224. /**
  225. * Read a config file and turn it into an array.
  226. *
  227. * @param string $file Full path to filename
  228. * @return array Array of assets
  229. */
  230. private static function readConfigFile($file)
  231. {
  232. try {
  233. $config = Yaml::parse(file_get_contents($file));
  234. return $config['assets'];
  235. } catch (ParseException $e) {
  236. error_log($e->getMessage());
  237. // @TODO need to handle this better. RD 2017-05-24
  238. }
  239. }
  240. }