5
5
use CodeIgniter \Debug \Toolbar \Collectors \Views ;
6
6
use CodeIgniter \Filters \DebugToolbar ;
7
7
use CodeIgniter \View \Exceptions \ViewException ;
8
+ use CodeIgniter \View \RendererInterface ;
8
9
use CodeIgniter \View \View as BaseView ;
9
10
use Config \Toolbar ;
10
11
use RuntimeException ;
15
16
class View extends BaseView
16
17
{
17
18
/**
18
- * Holds the sections and their data .
19
+ * Show fragments tags or not .
19
20
*/
20
- protected array $ fragments = [] ;
21
+ protected bool $ showFragments = false ;
21
22
22
23
/**
23
24
* The name of the current section being rendered,
@@ -27,6 +28,116 @@ class View extends BaseView
27
28
*/
28
29
protected array $ fragmentStack = [];
29
30
31
+ /**
32
+ * Starts holds content for a fragment within the layout.
33
+ *
34
+ * @param string $name Fragment name
35
+ */
36
+ public function fragment (string $ name ): void
37
+ {
38
+ $ this ->fragmentStack [] = $ name ;
39
+
40
+ if ($ this ->showFragments ) {
41
+ echo sprintf ('@[[fragmentStart="%s"]] ' , $ name );
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Captures the last fragment
47
+ *
48
+ * @throws RuntimeException
49
+ */
50
+ public function endFragment (): void
51
+ {
52
+ if ($ this ->fragmentStack === []) {
53
+ ob_end_clean ();
54
+
55
+ throw new RuntimeException ('View themes, no current fragment. ' );
56
+ }
57
+
58
+ $ name = array_pop ($ this ->fragmentStack );
59
+
60
+ if ($ this ->showFragments ) {
61
+ echo sprintf ('@[[fragmentEnd="%s"]] ' , $ name );
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Whether we should display fragments tags or not.
67
+ */
68
+ protected function showFragments (bool $ display = true ): RendererInterface
69
+ {
70
+ $ this ->showFragments = $ display ;
71
+
72
+ return $ this ;
73
+ }
74
+
75
+ /**
76
+ * Render fragments.
77
+ */
78
+ public function renderFragments (string $ name , ?array $ options = null , ?bool $ saveData = null ): string
79
+ {
80
+ $ fragments = $ options ['fragments ' ] ?? [];
81
+ $ output = $ this ->showFragments ()->render ($ name , $ options , $ saveData );
82
+
83
+ if ($ fragments === []) {
84
+ return preg_replace ('/@\[\[fragmentStart="[^"]+"\]\]|@\[\[fragmentEnd="[^"]+"\]\]/ ' , '' , $ output );
85
+ }
86
+
87
+ $ result = $ this ->showFragments (false )->parseFragments ($ output , $ fragments );
88
+ $ output = '' ;
89
+
90
+ foreach ($ result as $ contents ) {
91
+ $ output .= implode ('' , $ contents );
92
+ }
93
+
94
+ return $ output ;
95
+ }
96
+
97
+ /**
98
+ * Parse output to retrieve fragments.
99
+ */
100
+ protected function parseFragments (string $ output , array $ fragments ): array
101
+ {
102
+ $ results = [];
103
+ $ stack = [];
104
+
105
+ // Match all fragment start and end tags at once
106
+ preg_match_all ('/@\[\[fragmentStart="([^"]+)"\]\]|@\[\[fragmentEnd="([^"]+)"\]\]/ ' , $ output , $ matches , PREG_OFFSET_CAPTURE );
107
+
108
+ // Return empty array if no matches
109
+ if (count ($ matches [0 ]) === 0 ) {
110
+ return $ results ;
111
+ }
112
+
113
+ foreach ($ matches [0 ] as $ index => $ match ) {
114
+ $ pos = $ match [1 ];
115
+ $ isStart = isset ($ matches [1 ][$ index ]) && $ matches [1 ][$ index ][0 ] !== '' ;
116
+ $ name = $ isStart ? $ matches [1 ][$ index ][0 ] : (isset ($ matches [2 ][$ index ]) ? $ matches [2 ][$ index ][0 ] : '' );
117
+
118
+ if ($ isStart ) {
119
+ $ stack [] = ['name ' => $ name , 'start ' => $ pos ];
120
+ } elseif ($ stack !== [] && end ($ stack )['name ' ] === $ name ) {
121
+ $ info = array_pop ($ stack );
122
+
123
+ // Calculate the position of the fragment content
124
+ $ fragmentStart = $ info ['start ' ] + strlen ($ matches [0 ][array_search ($ info ['name ' ], array_column ($ matches [1 ], 0 ), true )][0 ]);
125
+ $ fragmentEnd = $ pos ;
126
+
127
+ // Extract the content between the tags
128
+ $ content = substr ($ output , $ fragmentStart , $ fragmentEnd - $ fragmentStart );
129
+ // Clean the fragment content by removing the tags
130
+ $ content = preg_replace ('/@\[\[fragmentStart="[^"]+"\]\]|@\[\[fragmentEnd="[^"]+"\]\]/ ' , '' , $ content );
131
+
132
+ if (in_array ($ info ['name ' ], $ fragments , true )) {
133
+ $ results [$ info ['name ' ]][] = $ content ;
134
+ }
135
+ }
136
+ }
137
+
138
+ return $ results ;
139
+ }
140
+
30
141
/**
31
142
* Builds the output based upon a file name and any
32
143
* data that has already been set.
@@ -35,13 +146,13 @@ class View extends BaseView
35
146
* - cache Number of seconds to cache for
36
147
* - cache_name Name to use for cache
37
148
*
38
- * @param string $view File name of the view source
39
- * @param array|null $options Reserved for 3rd-party uses since
40
- * it might be needed to pass additional info
41
- * to other template engines.
42
- * @param bool|null $saveData If true, saves data for subsequent calls,
43
- * if false, cleans the data after displaying,
44
- * if null, uses the config setting.
149
+ * @param string $view File name of the view source
150
+ * @param array<string, mixed> |null $options Reserved for 3rd-party uses since
151
+ * it might be needed to pass additional info
152
+ * to other template engines.
153
+ * @param bool|null $saveData If true, saves data for subsequent calls,
154
+ * if false, cleans the data after displaying,
155
+ * if null, uses the config setting.
45
156
*/
46
157
public function render (string $ view , ?array $ options = null , ?bool $ saveData = null ): string
47
158
{
@@ -58,7 +169,7 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n
58
169
59
170
// Was it cached?
60
171
if (isset ($ this ->renderVars ['options ' ]['cache ' ])) {
61
- $ cacheName = $ this ->renderVars ['options ' ]['cache_name ' ] ?? str_replace ('.php ' , '' , $ this ->renderVars ['view ' ]);
172
+ $ cacheName = $ this ->renderVars ['options ' ]['cache_name ' ] ?? str_replace ('.php ' , '' , $ this ->renderVars ['view ' ]) . ( empty ( $ this -> renderVars [ ' options ' ][ ' fragments ' ]) ? '' : implode ( '' , $ this -> renderVars [ ' options ' ][ ' fragments ' ])) ;
62
173
$ cacheName = str_replace (['\\' , '/ ' ], '' , $ cacheName );
63
174
64
175
$ this ->renderVars ['cacheName ' ] = $ cacheName ;
@@ -109,13 +220,6 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n
109
220
$ output = $ this ->render ($ layoutView , $ options , $ saveData );
110
221
// Get back current vars
111
222
$ this ->renderVars = $ renderVars ;
112
- } elseif (! empty ($ this ->renderVars ['options ' ]['fragments ' ]) && $ this ->fragmentStack === []) {
113
- $ output = '' ;
114
-
115
- foreach ($ this ->renderVars ['options ' ]['fragments ' ] as $ fragmentName ) {
116
- $ output .= $ this ->renderFragment ($ fragmentName );
117
- unset($ this ->fragments [$ fragmentName ]);
118
- }
119
223
}
120
224
121
225
$ output = $ this ->decorateOutput ($ output );
@@ -148,74 +252,4 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n
148
252
149
253
return $ output ;
150
254
}
151
-
152
- /**
153
- * Starts holds content for a fragment within the layout.
154
- *
155
- * @param string $name Fragment name
156
- *
157
- * @return void
158
- */
159
- public function fragment (string $ name )
160
- {
161
- $ this ->fragmentStack [] = $ name ;
162
-
163
- ob_start ();
164
- }
165
-
166
- /**
167
- * Captures the last fragment
168
- *
169
- * @throws RuntimeException
170
- */
171
- public function endFragment ()
172
- {
173
- $ contents = ob_get_clean ();
174
-
175
- if ($ this ->fragmentStack === []) {
176
- throw new RuntimeException ('View themes, no current fragment. ' );
177
- }
178
-
179
- $ fragmentName = array_pop ($ this ->fragmentStack );
180
-
181
- // Ensure an array exists, so we can store multiple entries for this.
182
- if (! array_key_exists ($ fragmentName , $ this ->fragments )) {
183
- $ this ->fragments [$ fragmentName ] = [];
184
- }
185
-
186
- $ this ->fragments [$ fragmentName ][] = $ contents ;
187
-
188
- echo $ contents ;
189
- }
190
-
191
- /**
192
- * Renders a fragment's contents.
193
- */
194
- protected function renderFragment (string $ fragmentName )
195
- {
196
- if (! isset ($ this ->fragments [$ fragmentName ])) {
197
- return '' ;
198
- }
199
-
200
- foreach ($ this ->fragments [$ fragmentName ] as $ contents ) {
201
- return $ contents ;
202
- }
203
- }
204
-
205
- /**
206
- * Used within layout views to include additional views.
207
- *
208
- * @param bool $saveData
209
- */
210
- public function include (string $ view , ?array $ options = null , $ saveData = true ): string
211
- {
212
- if ($ this ->fragmentStack !== [] && ! empty ($ this ->renderVars ['options ' ]['fragments ' ])) {
213
- $ options ['fragments ' ] = $ this ->renderVars ['options ' ]['fragments ' ];
214
- echo $ this ->render ($ view , $ options , $ saveData );
215
-
216
- return '' ;
217
- }
218
-
219
- return $ this ->render ($ view , $ options , $ saveData );
220
- }
221
255
}
0 commit comments