-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathPHPView.php
239 lines (205 loc) · 6.3 KB
/
PHPView.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
232
233
234
235
236
237
238
239
<?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\GoogleListingsAndAds\View;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\View;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\ViewFactory;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Exception;
defined( 'ABSPATH' ) || exit;
/**
* Class PHPView
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\View
*/
class PHPView implements View {
use PluginHelper;
/**
* Extension to use for view files.
*/
protected const VIEW_EXTENSION = 'php';
/**
* Path to the view file to render.
*
* @var string
*/
protected $path;
/**
* Internal storage for passed-in context.
*
* @var array
*/
protected $context = [];
/**
* @var ViewFactory
*/
protected $view_factory;
/**
* PHPView constructor.
*
* @param string $path Path to the view file to render.
* @param ViewFactory $view_factory View factory instance to use.
*
* @throws ViewException If an invalid path was passed into the View.
*/
public function __construct( string $path, ViewFactory $view_factory ) {
$this->path = $this->validate( $path );
$this->view_factory = $view_factory;
}
/**
* Render the current view with a given context.
*
* @param array $context Context in which to render.
*
* @return string Rendered HTML.
*
* @throws ViewException If the view could not be loaded.
*/
public function render( array $context = [] ): string {
// Add entire context as array to the current instance to pass onto
// partial views.
$this->context = $context;
// Save current buffering level so we can backtrack in case of an error.
// This is needed because the view itself might also add an unknown
// number of output buffering levels.
$buffer_level = ob_get_level();
ob_start();
try {
include $this->path;
} catch ( Exception $exception ) {
// Remove whatever levels were added up until now.
while ( ob_get_level() > $buffer_level ) {
ob_end_clean();
}
do_action( 'woocommerce_gla_exception', $exception, __METHOD__ );
throw ViewException::invalid_view_exception(
$this->path,
$exception
);
}
return ob_get_clean() ?: '';
}
/**
* Render a partial view.
*
* This can be used from within a currently rendered view, to include
* nested partials.
*
* The passed-in context is optional, and will fall back to the parent's
* context if omitted.
*
* @param string $path Path of the partial to render.
* @param array|null $context Context in which to render the partial.
*
* @return string Rendered HTML.
*
* @throws ViewException If the view could not be loaded or the provided path was not valid.
*/
public function render_partial( string $path, array $context = null ): string {
return $this->view_factory->create( $path )->render( $context ?: $this->context );
}
/**
* Return the raw value of a context property.
*
* By default, properties are automatically escaped when accessing them
* within the view. This method allows direct access to the raw value
* instead to bypass this automatic escaping.
*
* @param string $property Property for which to return the raw value.
*
* @return mixed Raw context property value.
*
* @throws ViewException If a requested property is not recognized (only in debugging mode).
*/
public function raw( string $property ) {
if ( array_key_exists( $property, $this->context ) ) {
return $this->context[ $property ];
}
do_action( 'woocommerce_gla_error', sprintf( 'View property "%s" is missing or undefined.', $property ), __METHOD__ );
/*
* We only throw an exception here if we are in debugging mode, as we
* don't want to take the server down when trying to render a missing
* property.
*/
if ( $this->is_debug_mode() ) {
throw ViewException::invalid_context_property( $property );
}
return null;
}
/**
* Validate a path.
*
* @param string $path Path to validate.
*
* @return string Validated path.
*
* @throws ViewException If an invalid path was passed into the View.
*/
protected function validate( string $path ): string {
$path = $this->check_extension( $path, static::VIEW_EXTENSION );
$path = path_join( $this->get_views_base_path(), $path );
if ( ! is_readable( $path ) ) {
do_action( 'woocommerce_gla_error', sprintf( 'View not found in path "%s".', $path ), __METHOD__ );
throw ViewException::invalid_path( $path );
}
return $path;
}
/**
* Check that the path has the correct extension.
*
* Optionally adds the extension if none was detected.
*
* @param string $path Path to check the extension of.
* @param string $extension Extension to use.
*
* @return string Path with correct extension.
*/
protected function check_extension( string $path, string $extension ): string {
$detected_extension = pathinfo( $path, PATHINFO_EXTENSION );
if ( $extension !== $detected_extension ) {
$path .= '.' . $extension;
}
return $path;
}
/**
* Use magic getter to provide automatic escaping by default.
*
* Use the raw() method to skip automatic escaping.
*
* @param string $property Property to get.
*
* @return mixed
*
* @throws ViewException If a requested property is not recognized (only in debugging mode).
*/
public function __get( string $property ) {
if ( array_key_exists( $property, $this->context ) ) {
return $this->sanitize_context_variable( $this->context[ $property ] );
}
do_action( 'woocommerce_gla_error', sprintf( 'View property "%s" is missing or undefined.', $property ), __METHOD__ );
/*
* We only throw an exception here if we are in debugging mode, as we
* don't want to take the server down when trying to render a missing
* property.
*/
if ( $this->is_debug_mode() ) {
throw ViewException::invalid_context_property( $property );
}
return null;
}
/**
* @param mixed $variable
*/
protected function sanitize_context_variable( $variable ) {
if ( is_array( $variable ) ) {
return array_map( [ $this, 'sanitize_context_variable' ], $variable );
} else {
return ! is_bool( $variable ) && is_scalar( $variable ) ? sanitize_text_field( $variable ) : $variable;
}
}
/**
* @return string
*/
protected function get_views_base_path(): string {
return path_join( dirname( __DIR__, 2 ), 'views' );
}
}