/
Layout.class.php
231 lines (204 loc) · 5.96 KB
/
Layout.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
<?php
Library::import('recess.framework.AbstractHelper');
/**
* The Layout helper enables template inheritance with a system of slots
* filled by blocks. Parent templates define slots that can be filled by
* child templates with blocks. Slots and blocks are named with strings.
* Content in a child template that appears outside of any blocks will
* implicitly create a 'body' block.
*
* <code>
* <?php
* // master.php
* Layout::slot('foo');
* echo 'This is the slot\'s default content.';
* Layout::slotEnd();
*
* Layout::slot('body');
* echo 'This is the default body content.';
* Layout::slotEnd();
* ?>
*
* <?php
* // child.php
* Layout::extend('master');
* Layout::block('foo');
* echo 'This overrides the parent\'s foo slot content.';
* Layout::blockEnd();
*
* echo 'By default this fills the body slot.';
* ?>
* </code>
* @author Kris Jordan
* @author Joshua Paine
*/
class Layout extends AbstractHelper {
const DEFAULT_BLOCK = 'body';
protected static $extendStack = array();
protected static $blockStack = array();
protected static $blockMap = array();
protected static $slotStack = array();
protected static $app;
/**
* Initialize the Layout helper with a View.
*
* @param AbstractView The view this helper is helping.
*/
public static function init(AbstractView $view) {
$response = $view->getResponse();
self::$app = $response->meta->app;
}
/**
* Extend a parent layout. Referenced from the views/ directory in
* the current application.
*
* @param string The layout to extend.
*/
public static function extend($layout) {
if(!empty(self::$extendStack)) {
throw new RecessFrameworkException('Nesting extends is not allowed.', 1);
}
$layout = self::$app->getViewsDir() . $layout;
if(strpos($layout, '.php') === false) {
$layout .= '.php';
}
if(!file_exists($layout)) {
throw new RecessFrameworkException('Extended layout does not exist. Expected location: ' . $layout, 1);
}
array_push(self::$extendStack, $layout);
ob_start();
}
/**
* Open a block that will fill a slot on a parent template. Any content sent
* to the output buffer between block/blockEnd will be used to fill the slot.
*
* @param string The name of the slot this block fills.
*/
public static function block($title) {
if(empty(self::$extendStack)) {
throw new RecessFrameworkException('Blocks are only valid when extending a layout using Layout::extend()', 1);
}
if(!empty(self::$blockStack)) {
throw new RecessFrameworkException('Nesting blocks is not allowed. You must end a block with Layout::blockEnd() before starting a new block.', 1);
}
array_push(self::$blockStack, $title);
ob_start();
}
/**
* Mark the end of a slot. Must be called after an opening block().
*/
public static function blockEnd() {
if(empty(self::$blockStack)) {
throw new RecessFrameworkException('Block end encountered without a preceding Layout::block() to open the block.', 1);
}
$blockName = array_pop(self::$blockStack);
if(!isset(self::$blockMap[$blockName])) {
self::$blockMap[$blockName] = ob_get_clean();
} else {
ob_end_clean();
}
}
/**
* Helper method for blocks whose content is a single string. Prints
* string between a block/blockEnd pair to avoid verbosity in a child
* layout.
*
* @param string The name of the slot this block fills.
* @param string The value of the block.
*/
public static function blockAssign($title, $value) {
self::block($title);
echo $value;
self::blockEnd();
}
/**
* Marks the beging of a slot in a parent template to be optionally
* filled by a child. Any content output between this slot open and
* slotEnd will be used as the default content of a slot should the
* extending template not fill it with a block.
*
* @param string The name of the slot.
*/
public static function slot($title) {
if(!empty(self::$slotStack)) {
throw new RecessFrameworkException('Nesting slots is not allowed. You must end a slot with Layout::slotEnd() before starting a new slot.', 1);
}
array_push(self::$slotStack, $title);
ob_start();
}
/**
* Marks the end of a slot. In effect this outputs the content of a slot.
*/
public static function slotEnd() {
if(empty(self::$slotStack)) {
throw new RecessFrameworkException('Slot end encountered without a preceding Layout::slot() to open the slot.', 1);
}
$slotName = array_pop(self::$slotStack);
if(isset(self::$blockMap[$slotName])) {
ob_end_clean();
echo self::$blockMap[$slotName];
unset(self::$blockMap[$slotName]);
} else {
ob_end_flush();
}
}
/**
* Helper method for a common scenario of a 'middle'
* template filling some additional information in a slot
* and re-opening the slot for a child to append to. Example:
*
* <code>
* // master.php
* <html>
* <head>
* <title>Master<?php Layout::slot('Title') ?><?php Layout::slotEnd(); ?></title>
* </head>
* </html>
* </code>
*
* <code>
* <?php
* // section.php
* Layout::extend('master');
* Layout::slotAppend('title', ' > Section');
* ?>
* </code>
*
* <code>
* <?php
* // page.php
* Layout::extend('section');
* Layout::slotAppend('title', ' > Page');
* ?>
* </code>
*
* Result is: <title>Master > Section > Page</title>
*
* @param string The slot to append to.
* @param string The value to append to the slot.
*/
public static function slotAppend($title, $value) {
self::block($title);
echo $value;
self::slot($title);
self::slotEnd();
self::blockEnd();
}
/**
* End a template extension and process the parent.
* Called automatically by RecessView but can be called manually.
*/
public static function extendEnd() {
if(!empty(self::$extendStack)) {
if(!isset(self::$blockMap[Layout::DEFAULT_BLOCK])) {
self::$blockMap[Layout::DEFAULT_BLOCK] = ob_get_clean();
} else {
ob_end_clean();
}
$parent = array_pop(self::$extendStack);
include($parent);
self::extendEnd();
}
}
}
?>