55use CodeIgniter \Debug \Toolbar \Collectors \Views ;
66use CodeIgniter \Filters \DebugToolbar ;
77use CodeIgniter \View \Exceptions \ViewException ;
8+ use CodeIgniter \View \RendererInterface ;
89use CodeIgniter \View \View as BaseView ;
910use Config \Toolbar ;
1011use RuntimeException ;
1516class View extends BaseView
1617{
1718 /**
18- * Holds the sections and their data .
19+ * Show fragments tags or not .
1920 */
20- protected array $ fragments = [] ;
21+ protected bool $ showFragments = false ;
2122
2223 /**
2324 * The name of the current section being rendered,
@@ -27,6 +28,116 @@ class View extends BaseView
2728 */
2829 protected array $ fragmentStack = [];
2930
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 ($ matches [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+
30141 /**
31142 * Builds the output based upon a file name and any
32143 * data that has already been set.
@@ -35,13 +146,13 @@ class View extends BaseView
35146 * - cache Number of seconds to cache for
36147 * - cache_name Name to use for cache
37148 *
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.
45156 */
46157 public function render (string $ view , ?array $ options = null , ?bool $ saveData = null ): string
47158 {
@@ -58,7 +169,7 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n
58169
59170 // Was it cached?
60171 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 ' ])) ;
62173 $ cacheName = str_replace (['\\' , '/ ' ], '' , $ cacheName );
63174
64175 $ this ->renderVars ['cacheName ' ] = $ cacheName ;
@@ -109,13 +220,6 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n
109220 $ output = $ this ->render ($ layoutView , $ options , $ saveData );
110221 // Get back current vars
111222 $ 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- }
119223 }
120224
121225 $ output = $ this ->decorateOutput ($ output );
@@ -148,74 +252,4 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n
148252
149253 return $ output ;
150254 }
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- }
221255}
0 commit comments