3
* © Copyright 2007, 2008 IntraHealth International, Inc.
5
* This File is part of iHRIS
7
* iHRIS is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation; either version 3 of the License, or
10
* (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21
* The I2CE_Template class that all display pages use.
24
* @author Luke Duncan <lduncan@intrahealth.org>
25
* @copyright Copyright © 2007, 2008 IntraHealth International, Inc.
29
if (!class_exists('I2CE_Template',false)) {
30
class I2CE_Template extends I2CE_TemplateMeister {
34
* Helper method. Load a file into the spectified document as HTML
35
* @param DOMDocument $doc
36
* @param string $contentfile the file to load
37
* @returns boolean False on failure,
39
protected function _loadFile($doc,$contentfile) {
40
$file = self::findTemplate( $contentfile );
41
if ( !$file || $file == "" ) {
42
self::raiseError( "Unable to find template file: $contentfile.", E_USER_WARNING );
45
libxml_clear_errors();
46
$sucess= $doc->loadHTMLFile($file);
48
self::raiseError("Could not load HTML file $file:\n" . print_r(libxml_get_last_error(),true));
49
libxml_clear_errors();
56
* Helper method. Load text into the spectified document as XML
57
* @param DOMDocument $doc
58
* @param string $contentfile the file to load
59
* @param string $setEncoding Defaults to true. If true, set the encoding to be that of the docuemnt. Only useful as false for loading root template files
60
* @returns boolean False on failure,
62
protected function _loadText($doc,$text,$setEncoding = true) {
63
libxml_use_internal_errors(true);
64
libxml_clear_errors();
65
if ($setEncoding && $this->doc instanceof DOMDocument && $this->doc->encoding ) {
66
$header = "<meta http-equiv='content-type' content='text/html; charset={$this->doc->encoding}'>";
67
$text = $header . $text;
69
if (!$doc->loadHTML($text)) {
70
if ($this->verboseErrors) {
71
self::raiseError("Could not load HTML");
72
$errors = libxml_get_errors();
73
foreach($errors as $error) {
74
$this->xmlError($error);
77
self::raiseError("Could not load HTML:\n" . print_r(libxml_get_last_error(),true));
79
libxml_clear_errors();
80
libxml_use_internal_errors(false);
83
libxml_clear_errors();
84
libxml_use_internal_errors(false);
91
* This method finds the location of a template file. If the file is not an absolute path searches the class path 'TEMPLATES'
93
* This method searches the template directory path from the global configuration array
94
* for the given template. If it exists it returns the full path to the file and if not
95
* it returns false. It seaches the path backwards so that later directories
96
* can override package versions of files.
97
* @param string $template The name of the template file.
100
static protected function findTemplate( $template ) {
101
if (I2CE_FileSearch::isAbsolut($template)) {
104
$template_file = I2CE::getFileSearch()->search( 'TEMPLATES', $template );
105
if ( $template_file ) {
106
return $template_file;
108
self::raiseError( "Couldn't find template file: $template.\nSearch Path is:\n"
109
. print_r(I2CE::getFileSearch()->getSearchPath('TEMPLATES'), true), E_USER_NOTICE );
117
* An array of files that have been loaded so they're aren't added in twice.
118
* The keys are the file names, the value is true if it has been loaded, unset otherwise
121
protected $files_loaded;
127
* I2CE_Template constructor method.
129
* This constructor sets up the basic variables for all I2CE_Template objects.
130
* $loadOptions is set to zero.
134
public function __construct() {
135
parent::__construct($options);
136
$this->files_loaded = array();
137
$this->headers = array('Content-type: text/html; charset=utf-8');
138
$this->loadOptions = 0;
142
* Fixes any href's starting with # so that they work properly
143
* Also makes sure that any relative URL include an index.php if .htaccess is not used.
145
protected function fixupAnchors() {
147
if (isset($_SERVER['PATH_INFO'])) {
148
$pageURL = $_SERVER['PATH_INFO'];
149
if ($pageURL[0] == '/') {
150
$pageURL = substr($pageURL,1);
153
if (isset($_SERVER['QUERY_STRING']) && strlen($_SERVER['QUERY_STRING']) > 0) {
154
$pageURL .= '?' . $_SERVER['QUERY_STRING'];
156
$anchors = $this->query("//*[starts-with(@href,'#')]");
157
for( $i = 0; $i < $anchors->length; $i++ ) {
158
$anchor = $anchors->item($i);
159
$href= $pageURL . $anchor->getAttribute('href');
160
$anchor->setAttribute('href',$href);
162
if (I2CE_Page::rewrittenURLs()) {
163
//there is (presumably) a .htaccess that is rewrite SITE_UR/a/blah/c?darp to SITE_URL/index.php/a/blah/c?darp
164
//so we don't need to do anything.
167
// $urls = $this->query("//*[not(matches(@href,'^([a-zA-Z]+:\/\/)|\/'))]"); //get all non-absolute urls. -- this should work but I get a : xmlXPathCompOpEval: function matches not found
168
//so iwill do it by hand below
169
$links = array('src','href','action');
170
foreach($links as $link) {
171
$urls = $this->query("//*[@$link]"); //get all non-absolute urls.
172
for( $i = 0; $i < $urls->length; $i++ ) {
173
$url = $urls->item($i);
174
$ref = self::ensureURL($url->getAttribute($link));
175
$url->setAttribute($link,$ref);
178
//now fix any @import url's for style sheets
179
$styles = $this->query("//style");
180
for ($i=0; $i < $styles->length; $i++) {
181
$style = $styles->item($i);
182
$replace = self::ensureCSSURLs($style->textContent);
183
while($style->hasChildNodes()) {
184
$style->removeChild($style->lastChild);
186
$style->appendChild($this->doc->createTextNode( $replace));
188
//now fixed ie hack type comments e.g
189
//<!--[if lt IE 7]><link href="file/iehacks.css?newman" rel="stylesheet" type="text/css" media="screen" /><![endif]-->
190
$comments = $this->query('//comment()');
191
foreach ($comments as $comment ) {
192
$data = $comment->data;
193
$comment->deleteData(0,$comment->length);
194
$data = preg_replace('/href=([\'"])([a-zA-Z0-9])/','href=$1index.php/$2',$data,-1);
195
$comment->appendData($data);
199
public static function ensureCSSURLs($css) {
200
$css = preg_replace('/@import\s+\"(.*?)\"/','@import url("\1")',$css); //in case the url is not wrapped in parens
201
$css = preg_replace_callback(
202
'/@import\s+url\(\s*(\"?)(.*?)\1\s*\)/',
203
create_function('$matches','return "@import url(\"" . I2CE_Template::ensureURL($matches[2]) . "\")";')
208
public static function ensureURL($url) {
209
if (preg_match('/(^[a-zA-Z]+:\/\/)|(^\/)|(^index\.php\/)/',$url)) {
212
return "index.php/$url";
218
* Called to prepare the display.
220
public function prepareDisplay() {
221
// Check role initially to skip processing any form elements that won't remain.
222
$this->checkRolesTasksAndPermissions();
223
I2CE_ModuleFactory::callHooks('template_display',array('template'=>$this,'user'=>$this->user));
224
$this->checkRolesTasksAndPermissions();
226
$this->fixupAnchors();
230
* Returns the displayed page as a string
233
public function getDisplay() {
234
return $this->doc->saveHTML();
238
protected function setBase() {
239
$head = $this->getElementByTagName('head',0);
240
if (!$head instanceof DOMNode) {
243
$script = substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], "/"));
244
$site_url = 'http://' . $_SERVER['HTTP_HOST'] . $script . '/';
245
$base = $this->createElement('base',array('href'=>$site_url));
246
$head->insertBefore($base,$head->firstChild);
252
* Remove any restricted access elements of the page.
254
* This method processes all elements in the document that have a role attribute.
255
* If there is no known user or the user doesn't have access to that role then the
256
* entire node will be removed from the document.
257
* @param I2CE_User $user
258
* @param DOMNode $node. Defaults to null, meaning we check the whole document. Otherwise, we check
259
* relaive to that role
261
public function checkRolesTasksAndPermissions( $node =null) {
262
if ($node instanceof DOMNode) {
263
$results = $this->query( ".//*[@role!='' or @task!='' or @permission!='']", $node );
265
$results = $this->query( "//*[@role!='' or @task!='' or @permission!='']" );
267
$permissionParser = new I2CE_PermissionParser($this->template,$this->user);
268
for( $i = 0; $i < $results->length; $i++ ) {
269
$node = $results->item($i);
270
if (!$node instanceof DOMElement) {
271
return true; //not removed.
273
$role = trim($node->getAttribute('role'));
274
$task = trim($node->getAttribute('task'));
275
$permission = trim($node->getAttribute('permission'));
277
$role = 'role(' . $role .')';
279
$permission = $permission . ' or ' . $role;
285
$task = 'task(' . $task .')';
287
$permission = $permission . ' or ' . $task;
292
$node->removeAttribute( "role" );
293
$node->removeAttribute( "task" );
294
$node->removeAttribute( "permission" );
295
$permission = trim($permission);
296
if ($permission && !$permissionParser->hasPermission($permission)) {
297
$this->removeNode( $node );
304
* Process any arguments sent to the page
305
* @returns boolean true on sucess. false on failure
307
public function processArgs($args) {
308
$i2ce_config = I2CE::getConfig()->I2CE;
309
$prefix_title = 'iHRIS';
310
$i2ce_config->__setIfIsSet($prefix_title,"template/prefix_title");
311
if ($args['title']) {
312
$this->setTitle( $prefix_title . ": " . $args['title']);
314
$this->setTitle( $prefix_title);
317
if (!isset($args['attributes'])) {
318
$args['attributes'] = array();
320
foreach( $args['attributes'] as $attr ) {
321
list($attr_name,$value) = explode('=',$attr);
322
if ( !($value === null || ( is_string($value) && strlen($value) == 0))
323
&& !($attr_name === null || ( is_string($attr_name) && strlen($attr_name) == 0))) {
324
$this->setBodyAttr( $attr_name, $value );
333
* Set the HTML title of the page.
335
* This will find the first title element of the page and replace
336
* the text in it with the given title. There should only be one
337
* title element in a valid HTML page.
338
* @param string $title
340
public function setTitle( $title ) {
341
$titletag = $this->getElementByTagName( "title" ,0 );
342
if ( ! $titletag instanceof DOMNode) {
345
$newtitle = $this->createTextNode( $title );
346
if ( $titletag->hasChildNodes() ) {
347
// Should just have one child node of the previous text
348
$oldtitle = $titletag->childNodes->item( 0 );
349
$titletag->replaceChild( $newtitle, $oldtitle );
351
$titletag->appendChild( $newtitle );
356
* Adds an attribute to the body tag of this document.
358
* This will find the body tag of the document and add or replace
359
* the given attribute with the given value.
360
* @param string $attr
361
* @param string $value
363
public function setBodyAttr( $attr, $value ) {
364
$bodytag = $this->doc->getElementsByTagName( "body" )->item( 0 );
365
if (!$bodytag instanceof DOMElement) {
366
self::raiseError("Trying to set body attribute, but body not found",E_USER_NOTICE);
368
$bodytag->setAttribute( $attr, $value );
372
* Sets the id attribute for the body tag.
374
* This will find the body tag of the document and add or replace the
375
* id attribute with the given id.
378
public function setBodyId( $id ) {
379
$this->setBodyAttr( "id", $id );
387
* Recursively process all child nodes that are being added in a loop to replace the designator text.
388
* @param DOMNode $node
389
* @param string $designator
390
* @param integer $count
393
protected function processLoopCount( $node, $designator, $count ) {
394
$this->replaceCount( $node, $designator, $count );
395
if ( $node->hasChildNodes() ) {
396
foreach( $node->childNodes as $child ) {
397
$this->processLoopCount( $child, $designator, $count );
402
* Replace the given designator text with the loop count of the template file being added.
404
* If the node is a DOM element then the designator text with the number of the count.
405
* @param DOMNode $node
406
* @param string $designator
407
* @param integer $count
409
protected function replaceCount( $node, $designator, $count ) {
410
if ( $node->nodeType != XML_ELEMENT_NODE ) return;
411
$attrs = array( "name", "id" );
412
foreach( $attrs as $attr ) {
413
if ( $node->hasAttribute( $attr ) ) {
414
$value = $node->getAttribute( $attr );
415
$value = str_replace( $designator, $count, $value );
416
$node->setAttribute( $attr, $value );
425
* Add text to the header of the document.
427
* @param string $text
428
* @param string $type -- one of 'css',javascipt','vbscript'
429
* @param mixed $as_serpate_node whether or not we want add the node as a seperate node or append it (default to false-- append).
430
* It will append it to the last node if so.
431
* If it is a string, then we create a new node and set the id of a node to the given string, or append to an existing node with that
432
* id. If it is a DOMNode then we just append to that node
433
* @return DOMNode -- the node just created or appended to
434
* Note the text does not have to be wrapped in <$tag> node. If not, it will put it in a <$tag> node with reasonalbe
437
public function addHeaderText( $text,$type, $as_separate_node = false ) {
438
$this->wrapHeaderTextInTag($text,$type); //$type should now be a valid tag
439
$imported_node = $this->importText( $text, $type, LIBXML_NOENT);
440
$head_node = $this->doc->getElementsByTagName( "head" )->item( 0 );
441
if ($as_separate_node === false ) {
442
$append_node = $this->doc->getElementsByTagName( $type, -1,$head_node );
444
$append_node = $this->getElementById($as_separate_node);
445
//note: we don't want to do this relative to head_node as script tags loaded
446
//from templates may not have been moved to the header yet
448
if ($append_node instanceof DOMNode) {
449
//we need to append it to an existing header text node
450
foreach( $imported_node->childNodes as $child ) {
451
$append_node->appendChild( $child );
454
} else {//we need to append to the header node
455
if (is_string($as_separate_node) && $imported_node instanceof DOMElement) {
456
$imported_node->setAttribute('id',$as_separate_node);
458
return $head_node->appendChild($imported_node);
463
* No validation occcurs so always returns true
466
public function validate() {
471
* Add a script or css to the header as a link
473
* @param string $file
474
* @param array $attr -- an array of attribute/value pairs. Defaults to the empty array.
476
* @param boolean $use_filedump -- default to true meaning that we should use the filedump utility
477
* when looking for the script/css
478
* @returm DOMNode -- the node just created or appended to
479
* You might use this by calling
480
* addHeaderLink("printer.css",'',array('media'=>'print'));
481
* Note: Uses the file's extension to determine the proper behavior. Valid ones are 'js', 'css', and 'vb'
483
public function addHeaderLink( $file,$attr = array(),$use_filedump = true) {
484
if ( $this->files_loaded[$file]) {
485
// Check to make sure this file hasn't already been loaded and ignore it if so.
488
// we have not loaded the file so start loading it
489
$this->files_loaded[$file] = true;
490
$ext = strtolower(substr(strrchr($file, "."), 1));
492
$file = 'file/' . $file;
494
$head_node = $this->doc->getElementsByTagName( "head" )->item( 0 );
497
$node = $this->doc->createElement('script','');
498
$node->setAttribute('src',$file);
499
$node->setAttribute('type','text/javascript');
502
$node = $this->doc->createElement('script','');
503
$node->setAttribute('src',$file);
504
$node->setAttribute('type','text/vbscript');
507
$style = $this->query( "style[@type='text/css' and @media='screen']", $head_node );
508
if ( array_key_exists('ie6', $attr) && $attr['ie6'] ) {
509
$node = $this->doc->createComment( '[if lt IE 7]><link href="' . $file . '" rel="stylesheet" type="text/css" media="screen" /><![endif]' );
511
} elseif ( $style->length > 0 && (!array_key_exists('media', $attr) || $attr['media'] == "screen" ) ) {
513
$head_node = $style->item(0);
514
$node = $this->doc->createTextNode( " @import url(" . $file . ");\n" );
516
$node = $this->doc->createElement('link','');
517
$node->setAttribute('rel','stylesheet');
518
$node->setAttribute('media','screen');
519
$node->setAttribute('href',$file);
520
$node->setAttribute('type','text/css');
524
self::raiseError("Unknown file type ($ext) for linking in the header", E_USER_NOTICE);
527
if (is_Array($attr)) {
528
foreach ($attr as $a=>$v) {
529
$node->setAttribute($a,$v);
532
return $this->appendNode($node,$head_node); //append the node to the begining of the header
540
protected function wrapHeaderTextInTag(&$text,&$tag) {
541
$tag = strtolower($tag);
543
if ( preg_match('/^\s*<\s*([a-zA-Z]+)/',$text,$matches)) {
544
//we probably started with a tag. otherwise it is malformed
545
//but i am not going to care
546
$first_tag = strtolower($matches[1]);
548
//did not start with a tag, so wrap it
551
if ((strlen($text) > 20) || (preg_match("/\n/",$text))) {
556
switch ($first_tag) {
557
case 'script': //assume a 'script' is really a 'javascript'
561
$text = '<script type=\'text/javascript\'>' . $newline . $text . $newline . '</script>';
566
$text = '<style type=\'text/css\' media=\'screen\'>' . $newline. $text . $newline . '</style>';
570
$text = '<script type=\'application/ecmascript\'>' . $newline . $text . $newline . '</script>';
574
$text = '<script type=\'text/vbscript\'>' . $newline . $text . $newline .'</script>';
577
$text= htmlspecialchars($text);
578
if ($first_tag != $tag) {
579
//they don't agree so wrap it
580
$text = '<' . $tag . '>' . $newline . $text . $newline . '</' .$tag . '>';
597
# c-default-style: "bsd"
598
# indent-tabs-mode: nil