HeadScript.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_View
  17. * @subpackage Helper
  18. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @version $Id$
  20. * @license http://framework.zend.com/license/new-bsd New BSD License
  21. */
  22. /** Zend_View_Helper_Placeholder_Container_Standalone */
  23. require_once 'Zend/View/Helper/Placeholder/Container/Standalone.php';
  24. /**
  25. * Helper for setting and retrieving script elements for HTML head section
  26. *
  27. * @uses Zend_View_Helper_Placeholder_Container_Standalone
  28. * @package Zend_View
  29. * @subpackage Helper
  30. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  31. * @license http://framework.zend.com/license/new-bsd New BSD License
  32. * @method $this appendFile($src, $type = 'text/javascript', array $attrs = array())
  33. * @method $this appendScript($script, $type = 'text/javascript', array $attrs = array())
  34. * @method $this offsetSetFile($index, $src, $type = 'text/javascript', array $attrs = array())
  35. * @method $this offsetSetScript($index, $script, $type = 'text/javascript', array $attrs = array())
  36. * @method $this prependFile($src, $type = 'text/javascript', array $attrs = array())
  37. * @method $this prependScript($script, $type = 'text/javascript', array $attrs = array())
  38. * @method $this setFile($src, $type = 'text/javascript', array $attrs = array())
  39. * @method $this setScript($script, $type = 'text/javascript', array $attrs = array())
  40. */
  41. class Zend_View_Helper_HeadScript extends Zend_View_Helper_Placeholder_Container_Standalone
  42. {
  43. /**#@+
  44. * Script type contants
  45. * @const string
  46. */
  47. const FILE = 'FILE';
  48. const SCRIPT = 'SCRIPT';
  49. /**#@-*/
  50. /**
  51. * Registry key for placeholder
  52. * @var string
  53. */
  54. protected $_regKey = 'Zend_View_Helper_HeadScript';
  55. /**
  56. * Are arbitrary attributes allowed?
  57. * @var bool
  58. */
  59. protected $_arbitraryAttributes = false;
  60. /**#@+
  61. * Capture type and/or attributes (used for hinting during capture)
  62. * @var string
  63. */
  64. protected $_captureLock;
  65. protected $_captureScriptType = null;
  66. protected $_captureScriptAttrs = null;
  67. protected $_captureType;
  68. /**#@-*/
  69. /**
  70. * Optional allowed attributes for script tag
  71. * @var array
  72. */
  73. protected $_optionalAttributes = array(
  74. 'charset', 'defer', 'language', 'src'
  75. );
  76. /**
  77. * Required attributes for script tag
  78. * @var string
  79. */
  80. protected $_requiredAttributes = array('type');
  81. /**
  82. * Whether or not to format scripts using CDATA; used only if doctype
  83. * helper is not accessible
  84. * @var bool
  85. */
  86. public $useCdata = false;
  87. /**
  88. * Constructor
  89. *
  90. * Set separator to PHP_EOL.
  91. *
  92. * @return void
  93. */
  94. public function __construct()
  95. {
  96. parent::__construct();
  97. $this->setSeparator(PHP_EOL);
  98. }
  99. /**
  100. * Return headScript object
  101. *
  102. * Returns headScript helper object; optionally, allows specifying a script
  103. * or script file to include.
  104. *
  105. * @param string $mode Script or file
  106. * @param string $spec Script/url
  107. * @param string $placement Append, prepend, or set
  108. * @param array $attrs Array of script attributes
  109. * @param string $type Script type and/or array of script attributes
  110. * @return Zend_View_Helper_HeadScript
  111. */
  112. public function headScript($mode = Zend_View_Helper_HeadScript::FILE, $spec = null, $placement = 'APPEND', array $attrs = array(), $type = 'text/javascript')
  113. {
  114. if ((null !== $spec) && is_string($spec)) {
  115. $action = ucfirst(strtolower($mode));
  116. $placement = strtolower($placement);
  117. switch ($placement) {
  118. case 'set':
  119. case 'prepend':
  120. case 'append':
  121. $action = $placement . $action;
  122. break;
  123. default:
  124. $action = 'append' . $action;
  125. break;
  126. }
  127. $this->$action($spec, $type, $attrs);
  128. }
  129. return $this;
  130. }
  131. /**
  132. * Start capture action
  133. *
  134. * @param mixed $captureType
  135. * @param string $typeOrAttrs
  136. * @return void
  137. */
  138. public function captureStart($captureType = Zend_View_Helper_Placeholder_Container_Abstract::APPEND, $type = 'text/javascript', $attrs = array())
  139. {
  140. if ($this->_captureLock) {
  141. require_once 'Zend/View/Helper/Placeholder/Container/Exception.php';
  142. $e = new Zend_View_Helper_Placeholder_Container_Exception('Cannot nest headScript captures');
  143. $e->setView($this->view);
  144. throw $e;
  145. }
  146. $this->_captureLock = true;
  147. $this->_captureType = $captureType;
  148. $this->_captureScriptType = $type;
  149. $this->_captureScriptAttrs = $attrs;
  150. ob_start();
  151. }
  152. /**
  153. * End capture action and store
  154. *
  155. * @return void
  156. */
  157. public function captureEnd()
  158. {
  159. $content = ob_get_clean();
  160. $type = $this->_captureScriptType;
  161. $attrs = $this->_captureScriptAttrs;
  162. $this->_captureScriptType = null;
  163. $this->_captureScriptAttrs = null;
  164. $this->_captureLock = false;
  165. switch ($this->_captureType) {
  166. case Zend_View_Helper_Placeholder_Container_Abstract::SET:
  167. case Zend_View_Helper_Placeholder_Container_Abstract::PREPEND:
  168. case Zend_View_Helper_Placeholder_Container_Abstract::APPEND:
  169. $action = strtolower($this->_captureType) . 'Script';
  170. break;
  171. default:
  172. $action = 'appendScript';
  173. break;
  174. }
  175. $this->$action($content, $type, $attrs);
  176. }
  177. /**
  178. * Overload method access
  179. *
  180. * Allows the following method calls:
  181. * - appendFile($src, $type = 'text/javascript', $attrs = array())
  182. * - offsetSetFile($index, $src, $type = 'text/javascript', $attrs = array())
  183. * - prependFile($src, $type = 'text/javascript', $attrs = array())
  184. * - setFile($src, $type = 'text/javascript', $attrs = array())
  185. * - appendScript($script, $type = 'text/javascript', $attrs = array())
  186. * - offsetSetScript($index, $src, $type = 'text/javascript', $attrs = array())
  187. * - prependScript($script, $type = 'text/javascript', $attrs = array())
  188. * - setScript($script, $type = 'text/javascript', $attrs = array())
  189. *
  190. * @param string $method
  191. * @param array $args
  192. * @return Zend_View_Helper_HeadScript
  193. * @throws Zend_View_Exception if too few arguments or invalid method
  194. */
  195. public function __call($method, $args)
  196. {
  197. if (preg_match('/^(?P<action>set|(ap|pre)pend|offsetSet)(?P<mode>File|Script)$/', $method, $matches)) {
  198. if (1 > count($args)) {
  199. require_once 'Zend/View/Exception.php';
  200. $e = new Zend_View_Exception(sprintf('Method "%s" requires at least one argument', $method));
  201. $e->setView($this->view);
  202. throw $e;
  203. }
  204. $action = $matches['action'];
  205. $mode = strtolower($matches['mode']);
  206. $type = 'text/javascript';
  207. $attrs = array();
  208. if ('offsetSet' == $action) {
  209. $index = array_shift($args);
  210. if (1 > count($args)) {
  211. require_once 'Zend/View/Exception.php';
  212. $e = new Zend_View_Exception(sprintf('Method "%s" requires at least two arguments, an index and source', $method));
  213. $e->setView($this->view);
  214. throw $e;
  215. }
  216. }
  217. $content = $args[0];
  218. if (isset($args[1])) {
  219. $type = (string) $args[1];
  220. }
  221. if (isset($args[2])) {
  222. $attrs = (array) $args[2];
  223. }
  224. switch ($mode) {
  225. case 'script':
  226. $item = $this->createData($type, $attrs, $content);
  227. if ('offsetSet' == $action) {
  228. $this->offsetSet($index, $item);
  229. } else {
  230. $this->$action($item);
  231. }
  232. break;
  233. case 'file':
  234. default:
  235. if (!$this->_isDuplicate($content) || $action=='set') {
  236. $attrs['src'] = $content;
  237. $item = $this->createData($type, $attrs);
  238. if ('offsetSet' == $action) {
  239. $this->offsetSet($index, $item);
  240. } else {
  241. $this->$action($item);
  242. }
  243. }
  244. break;
  245. }
  246. return $this;
  247. }
  248. return parent::__call($method, $args);
  249. }
  250. /**
  251. * Is the file specified a duplicate?
  252. *
  253. * @param string $file
  254. * @return bool
  255. */
  256. protected function _isDuplicate($file)
  257. {
  258. foreach ($this->getContainer() as $item) {
  259. if (($item->source === null)
  260. && array_key_exists('src', $item->attributes)
  261. && ($file == $item->attributes['src']))
  262. {
  263. return true;
  264. }
  265. }
  266. return false;
  267. }
  268. /**
  269. * Is the script provided valid?
  270. *
  271. * @param mixed $value
  272. * @param string $method
  273. * @return bool
  274. */
  275. protected function _isValid($value)
  276. {
  277. if ((!$value instanceof stdClass)
  278. || !isset($value->type)
  279. || (!isset($value->source) && !isset($value->attributes)))
  280. {
  281. return false;
  282. }
  283. return true;
  284. }
  285. /**
  286. * Override append
  287. *
  288. * @param string $value
  289. * @return void
  290. */
  291. public function append($value)
  292. {
  293. if (!$this->_isValid($value)) {
  294. require_once 'Zend/View/Exception.php';
  295. $e = new Zend_View_Exception('Invalid argument passed to append(); please use one of the helper methods, appendScript() or appendFile()');
  296. $e->setView($this->view);
  297. throw $e;
  298. }
  299. return $this->getContainer()->append($value);
  300. }
  301. /**
  302. * Override prepend
  303. *
  304. * @param string $value
  305. * @return void
  306. */
  307. public function prepend($value)
  308. {
  309. if (!$this->_isValid($value)) {
  310. require_once 'Zend/View/Exception.php';
  311. $e = new Zend_View_Exception('Invalid argument passed to prepend(); please use one of the helper methods, prependScript() or prependFile()');
  312. $e->setView($this->view);
  313. throw $e;
  314. }
  315. return $this->getContainer()->prepend($value);
  316. }
  317. /**
  318. * Override set
  319. *
  320. * @param string $value
  321. * @return void
  322. */
  323. public function set($value)
  324. {
  325. if (!$this->_isValid($value)) {
  326. require_once 'Zend/View/Exception.php';
  327. $e = new Zend_View_Exception('Invalid argument passed to set(); please use one of the helper methods, setScript() or setFile()');
  328. $e->setView($this->view);
  329. throw $e;
  330. }
  331. return $this->getContainer()->set($value);
  332. }
  333. /**
  334. * Override offsetSet
  335. *
  336. * @param string|int $index
  337. * @param mixed $value
  338. * @return void
  339. */
  340. public function offsetSet($index, $value)
  341. {
  342. if (!$this->_isValid($value)) {
  343. require_once 'Zend/View/Exception.php';
  344. $e = new Zend_View_Exception('Invalid argument passed to offsetSet(); please use one of the helper methods, offsetSetScript() or offsetSetFile()');
  345. $e->setView($this->view);
  346. throw $e;
  347. }
  348. return $this->getContainer()->offsetSet($index, $value);
  349. }
  350. /**
  351. * Set flag indicating if arbitrary attributes are allowed
  352. *
  353. * @param bool $flag
  354. * @return Zend_View_Helper_HeadScript
  355. */
  356. public function setAllowArbitraryAttributes($flag)
  357. {
  358. $this->_arbitraryAttributes = (bool) $flag;
  359. return $this;
  360. }
  361. /**
  362. * Are arbitrary attributes allowed?
  363. *
  364. * @return bool
  365. */
  366. public function arbitraryAttributesAllowed()
  367. {
  368. return $this->_arbitraryAttributes;
  369. }
  370. /**
  371. * Create script HTML
  372. *
  373. * @param string $type
  374. * @param array $attributes
  375. * @param string $content
  376. * @param string|int $indent
  377. * @return string
  378. */
  379. public function itemToString($item, $indent, $escapeStart, $escapeEnd)
  380. {
  381. $attrString = '';
  382. if (!empty($item->attributes)) {
  383. foreach ($item->attributes as $key => $value) {
  384. if ((!$this->arbitraryAttributesAllowed() && !in_array($key, $this->_optionalAttributes))
  385. || in_array($key, array('conditional', 'noescape')))
  386. {
  387. continue;
  388. }
  389. if ('defer' == $key) {
  390. $value = 'defer';
  391. }
  392. $attrString .= sprintf(' %s="%s"', $key, ($this->_autoEscape) ? $this->_escape($value) : $value);
  393. }
  394. }
  395. $addScriptEscape = !(isset($item->attributes['noescape']) && filter_var($item->attributes['noescape'], FILTER_VALIDATE_BOOLEAN));
  396. $type = ($this->_autoEscape) ? $this->_escape($item->type) : $item->type;
  397. $html = '<script type="' . $type . '"' . $attrString . '>';
  398. if (!empty($item->source)) {
  399. $html .= PHP_EOL ;
  400. if ($addScriptEscape) {
  401. $html .= $indent . ' ' . $escapeStart . PHP_EOL;
  402. }
  403. $html .= $indent . ' ' . $item->source;
  404. if ($addScriptEscape) {
  405. $html .= $indent . ' ' . $escapeEnd . PHP_EOL;
  406. }
  407. $html .= $indent;
  408. }
  409. $html .= '</script>';
  410. if (isset($item->attributes['conditional'])
  411. && !empty($item->attributes['conditional'])
  412. && is_string($item->attributes['conditional']))
  413. {
  414. // inner wrap with comment end and start if !IE
  415. if (str_replace(' ', '', $item->attributes['conditional']) === '!IE') {
  416. $html = '<!-->' . $html . '<!--';
  417. }
  418. $html = $indent . '<!--[if ' . $item->attributes['conditional'] . ']>' . $html . '<![endif]-->';
  419. } else {
  420. $html = $indent . $html;
  421. }
  422. return $html;
  423. }
  424. /**
  425. * Retrieve string representation
  426. *
  427. * @param string|int $indent
  428. * @return string
  429. */
  430. public function toString($indent = null)
  431. {
  432. $indent = (null !== $indent)
  433. ? $this->getWhitespace($indent)
  434. : $this->getIndent();
  435. if ($this->view) {
  436. $useCdata = $this->view->doctype()->isXhtml() ? true : false;
  437. } else {
  438. $useCdata = $this->useCdata ? true : false;
  439. }
  440. $escapeStart = ($useCdata) ? '//<![CDATA[' : '//<!--';
  441. $escapeEnd = ($useCdata) ? '//]]>' : '//-->';
  442. $items = array();
  443. $this->getContainer()->ksort();
  444. foreach ($this as $item) {
  445. if (!$this->_isValid($item)) {
  446. continue;
  447. }
  448. $items[] = $this->itemToString($item, $indent, $escapeStart, $escapeEnd);
  449. }
  450. $return = implode($this->getSeparator(), $items);
  451. return $return;
  452. }
  453. /**
  454. * Create data item containing all necessary components of script
  455. *
  456. * @param string $type
  457. * @param array $attributes
  458. * @param string $content
  459. * @return stdClass
  460. */
  461. public function createData($type, array $attributes, $content = null)
  462. {
  463. $data = new stdClass();
  464. $data->type = $type;
  465. $data->attributes = $attributes;
  466. $data->source = $content;
  467. return $data;
  468. }
  469. }