1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
class Templates |
|
|
|
|
4
|
|
|
{ |
5
|
|
|
protected static $instance = null; |
6
|
|
|
|
7
|
|
|
public $dirs = array(); |
8
|
|
|
protected $delayed = array(); |
9
|
|
|
protected $templates = array(); |
10
|
|
|
protected $default_loaded = false; |
11
|
|
|
|
12
|
7 |
|
public static function getInstance() |
13
|
|
|
{ |
14
|
7 |
|
if (self::$instance === null) |
15
|
7 |
|
{ |
16
|
|
|
self::$instance = new Templates; |
17
|
|
|
} |
18
|
|
|
|
19
|
7 |
|
return self::$instance; |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
protected function __construct() |
23
|
|
|
{ |
24
|
|
|
// We want to be able to figure out any errors... |
25
|
|
|
@ini_set('track_errors', '1'); |
|
|
|
|
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
public function setDirectories(array $dirs) |
29
|
|
|
{ |
30
|
|
|
$this->dirs = $dirs; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Load a template - if the theme doesn't include it, use the default. |
35
|
|
|
* |
36
|
|
|
* What it does: |
37
|
|
|
* - loads a template file with the name template_name from the current, default, or base theme. |
38
|
|
|
* - detects a wrong default theme directory and tries to work around it. |
39
|
|
|
* - can be used to only load style sheets by using false as the template name |
40
|
|
|
* loading of style sheets with this function is deprecated, use loadCSSFile instead |
41
|
|
|
* - if $this->dirs is empty, it delays the loading of the template |
42
|
|
|
* |
43
|
|
|
* @uses $this->requireTemplate() to actually load the file. |
44
|
|
|
* @param string|false $template_name |
45
|
|
|
* @param string[]|string $style_sheets any style sheets to load with the template |
46
|
|
|
* @param bool $fatal = true if fatal is true, dies with an error message if the template cannot be found |
47
|
|
|
* |
48
|
|
|
* @return boolean|null |
49
|
|
|
*/ |
50
|
1 |
|
public function load($template_name, $style_sheets = array(), $fatal = true) |
51
|
|
|
{ |
52
|
|
|
// If we don't know yet the default theme directory, let's wait a bit. |
53
|
1 |
|
if (empty($this->dirs)) |
54
|
1 |
|
{ |
55
|
|
|
$this->delayed[] = array( |
56
|
|
|
$template_name, |
57
|
|
|
$style_sheets, |
58
|
|
|
$fatal |
59
|
|
|
); |
60
|
|
|
|
61
|
|
|
return; |
62
|
|
|
} |
63
|
|
|
// If instead we know the default theme directory and we have delayed something, it's time to process |
64
|
1 |
|
elseif (!empty($this->delayed)) |
65
|
|
|
{ |
66
|
|
|
foreach ($this->delayed as $val) |
67
|
|
|
{ |
68
|
|
|
$this->requireTemplate($val[0], $val[1], $val[2]); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
// Forget about them (load them only once) |
72
|
|
|
$this->delayed = array(); |
73
|
|
|
} |
74
|
|
|
|
75
|
1 |
|
$this->requireTemplate($template_name, $style_sheets, $fatal); |
76
|
1 |
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* <b>Internal function! Do not use it, use loadTemplate instead</b> |
80
|
|
|
* |
81
|
|
|
* What it does: |
82
|
|
|
* - loads a template file with the name template_name from the current, default, or base theme. |
83
|
|
|
* - detects a wrong default theme directory and tries to work around it. |
84
|
|
|
* - can be used to only load style sheets by using false as the template name |
85
|
|
|
* loading of style sheets with this function is deprecated, use loadCSSFile instead |
86
|
|
|
* |
87
|
|
|
* @uses $this->templateInclude() to include the file. |
88
|
|
|
* @param string|false $template_name |
89
|
|
|
* @param string[]|string $style_sheets any style sheets to load with the template |
90
|
|
|
* @param bool $fatal = true if fatal is true, dies with an error message if the template cannot be found |
91
|
|
|
* |
92
|
|
|
* @return boolean|null |
93
|
|
|
*/ |
94
|
1 |
|
protected function requireTemplate($template_name, $style_sheets, $fatal) |
|
|
|
|
95
|
|
|
{ |
96
|
1 |
|
global $context, $settings, $txt, $scripturl, $db_show_debug; |
97
|
|
|
|
98
|
1 |
|
if (!is_array($style_sheets)) |
99
|
1 |
|
$style_sheets = array($style_sheets); |
100
|
|
|
|
101
|
1 |
|
if ($this->default_loaded === false) |
102
|
1 |
|
{ |
103
|
1 |
|
loadCSSFile('index.css'); |
104
|
1 |
|
$this->default_loaded = true; |
105
|
1 |
|
} |
106
|
|
|
|
107
|
|
|
// Any specific template style sheets to load? |
108
|
1 |
|
if (!empty($style_sheets)) |
109
|
1 |
|
{ |
110
|
|
|
trigger_error('Use of loadTemplate to add style sheets to the head is deprecated.', E_USER_DEPRECATED); |
111
|
|
|
$sheets = array(); |
112
|
|
|
foreach ($style_sheets as $sheet) |
113
|
|
|
{ |
114
|
|
|
$sheets[] = stripos('.css', $sheet) !== false ? $sheet : $sheet . '.css'; |
115
|
|
|
if ($sheet == 'admin' && !empty($context['theme_variant'])) |
116
|
|
|
$sheets[] = $context['theme_variant'] . '/admin' . $context['theme_variant'] . '.css'; |
117
|
|
|
} |
118
|
|
|
loadCSSFile($sheets); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
// No template to load? |
122
|
1 |
|
if ($template_name === false) |
123
|
1 |
|
return true; |
124
|
|
|
|
125
|
1 |
|
$loaded = false; |
126
|
1 |
|
foreach ($this->dirs as $template_dir) |
127
|
|
|
{ |
128
|
1 |
|
if (file_exists($template_dir . '/' . $template_name . '.template.php')) |
129
|
1 |
|
{ |
130
|
1 |
|
$loaded = true; |
131
|
1 |
|
$this->templateInclude($template_dir . '/' . $template_name . '.template.php', true); |
132
|
1 |
|
break; |
133
|
|
|
} |
134
|
1 |
|
} |
135
|
|
|
|
136
|
|
|
if ($loaded) |
137
|
1 |
|
{ |
138
|
1 |
|
if ($db_show_debug === true) |
139
|
1 |
|
Debug::get()->add('templates', $template_name . ' (' . basename($template_dir) . ')'); |
|
|
|
|
140
|
|
|
|
141
|
|
|
// If they have specified an initialization function for this template, go ahead and call it now. |
142
|
1 |
|
if (function_exists('template_' . $template_name . '_init')) |
143
|
1 |
|
call_user_func('template_' . $template_name . '_init'); |
144
|
1 |
|
} |
145
|
|
|
// Hmmm... doesn't exist?! I don't suppose the directory is wrong, is it? |
146
|
|
|
elseif (!file_exists($settings['default_theme_dir']) && file_exists(BOARDDIR . '/themes/default')) |
147
|
|
|
{ |
148
|
|
|
$settings['default_theme_dir'] = BOARDDIR . '/themes/default'; |
149
|
|
|
$this->addDirectory($settings['default_theme_dir']); |
150
|
|
|
|
151
|
|
|
if (!empty($context['user']['is_admin']) && !isset($_GET['th'])) |
152
|
|
|
{ |
153
|
|
|
loadLanguage('Errors'); |
154
|
|
|
|
155
|
|
|
if (!isset($context['security_controls_files']['title'])) |
156
|
|
|
$context['security_controls_files']['title'] = $txt['generic_warning']; |
157
|
|
|
|
158
|
|
|
$context['security_controls_files']['errors']['theme_dir'] = '<a href="' . $scripturl . '?action=admin;area=theme;sa=list;th=1;' . $context['session_var'] . '=' . $context['session_id'] . '">' . $txt['theme_dir_wrong'] . '</a>'; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
loadTemplate($template_name); |
162
|
|
|
} |
163
|
|
|
// Cause an error otherwise. |
164
|
|
|
elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal) |
165
|
|
|
Errors::instance()->fatal_lang_error('theme_template_error', 'template', array((string) $template_name)); |
166
|
|
|
elseif ($fatal) |
167
|
|
|
die(Errors::instance()->log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load themes/default/%s.template.php!', (string) $template_name), 'template')); |
|
|
|
|
168
|
|
|
else |
169
|
|
|
return false; |
170
|
1 |
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Load a sub-template. |
174
|
|
|
* |
175
|
|
|
* What it does: |
176
|
|
|
* - loads the sub template specified by sub_template_name, which must be in an already-loaded template. |
177
|
|
|
* - if ?debug is in the query string, shows administrators a marker after every sub template |
178
|
|
|
* for debugging purposes. |
179
|
|
|
* |
180
|
|
|
* @todo get rid of reading $_REQUEST directly |
181
|
|
|
* @param string $sub_template_name |
182
|
|
|
* @param bool|string $fatal = false, $fatal = true is for templates that |
183
|
|
|
* shouldn't get a 'pretty' error screen 'ignore' to skip |
184
|
|
|
*/ |
185
|
|
|
public function loadSubTemplate($sub_template_name, $fatal = false) |
186
|
|
|
{ |
187
|
|
|
global $txt, $db_show_debug; |
188
|
|
|
|
189
|
|
|
if ($db_show_debug === true) |
190
|
|
|
Debug::get()->add('sub_templates', $sub_template_name); |
191
|
|
|
|
192
|
|
|
// Figure out what the template function is named. |
193
|
|
|
$theme_function = 'template_' . $sub_template_name; |
194
|
|
|
|
195
|
|
|
if (function_exists($theme_function)) |
196
|
|
|
$theme_function(); |
197
|
|
|
elseif ($fatal === false) |
198
|
|
|
Errors::instance()->fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name)); |
199
|
|
|
elseif ($fatal !== 'ignore') |
200
|
|
|
die(Errors::instance()->log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load the %s sub template!', (string) $sub_template_name), 'template')); |
|
|
|
|
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Load the template/language file using eval or require? (with eval we can show an error message!) |
205
|
|
|
* |
206
|
|
|
* What it does: |
207
|
|
|
* - loads the template or language file specified by filename. |
208
|
|
|
* - uses eval unless disableTemplateEval is enabled. |
209
|
|
|
* - outputs a parse error if the file did not exist or contained errors. |
210
|
|
|
* - attempts to detect the error and line, and show detailed information. |
211
|
|
|
* |
212
|
|
|
* @param string $filename |
213
|
|
|
* @param bool $once = false, if true only includes the file once (like include_once) |
214
|
|
|
*/ |
215
|
7 |
|
public function templateInclude($filename, $once = false) |
216
|
|
|
{ |
217
|
|
|
// I know this looks weird but this is used to include $txt files. If the parent doesn't declare them global |
218
|
|
|
// the scope will be local to this function. IOW, don't remove this line! |
219
|
7 |
|
global $txt; |
220
|
|
|
|
221
|
|
|
// Don't include the file more than once, if $once is true. |
222
|
7 |
|
if ($once && in_array($filename, $this->templates)) |
223
|
7 |
|
{ |
224
|
|
|
return; |
225
|
|
|
} |
226
|
|
|
// Add this file to the include list, whether $once is true or not. |
227
|
|
|
else |
228
|
|
|
{ |
229
|
7 |
|
$this->templates[] = $filename; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
// Load it if we find it |
233
|
7 |
|
$file_found = file_exists($filename); |
234
|
|
|
|
235
|
7 |
|
if ($once && $file_found) |
236
|
7 |
|
require_once($filename); |
237
|
6 |
|
elseif ($file_found) |
238
|
6 |
|
require($filename); |
239
|
|
|
|
240
|
7 |
|
if ($file_found !== true) |
241
|
7 |
|
$this->templateNotFound($filename); |
242
|
7 |
|
} |
243
|
|
|
|
244
|
|
|
protected function templateNotFound($filename) |
|
|
|
|
245
|
|
|
{ |
246
|
|
|
global $context, $txt, $scripturl, $modSettings, $boardurl; |
247
|
|
|
global $maintenance, $mtitle, $mmessage; |
248
|
|
|
|
249
|
|
|
@ob_end_clean(); |
|
|
|
|
250
|
|
|
if (!empty($modSettings['enableCompressedOutput'])) |
251
|
|
|
ob_start('ob_gzhandler'); |
252
|
|
|
else |
253
|
|
|
ob_start(); |
254
|
|
|
|
255
|
|
|
if (isset($_GET['debug'])) |
256
|
|
|
header('Content-Type: application/xhtml+xml; charset=UTF-8'); |
257
|
|
|
|
258
|
|
|
// Don't cache error pages!! |
259
|
|
|
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); |
260
|
|
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); |
261
|
|
|
header('Cache-Control: no-cache'); |
262
|
|
|
|
263
|
|
|
if (!isset($txt['template_parse_error'])) |
264
|
|
|
{ |
265
|
|
|
$txt['template_parse_error'] = 'Template Parse Error!'; |
266
|
|
|
$txt['template_parse_error_message'] = 'It seems something has gone sour on the forum with the template system. This problem should only be temporary, so please come back later and try again. If you continue to see this message, please contact the administrator.<br /><br />You can also try <a href="javascript:location.reload();">refreshing this page</a>.'; |
267
|
|
|
$txt['template_parse_error_details'] = 'There was a problem loading the <span style="font-family: monospace;"><strong>%1$s</strong></span> template or language file. Please check the syntax and try again - remember, single quotes (<span style="font-family: monospace;">\'</span>) often have to be escaped with a slash (<span style="font-family: monospace;">\\</span>). To see more specific error information from PHP, try <a href="%2$s%1$s" class="extern">accessing the file directly</a>.<br /><br />You may want to try to <a href="javascript:location.reload();">refresh this page</a> or <a href="%3$s">use the default theme</a>.'; |
268
|
|
|
$txt['template_parse_undefined'] = 'An undefined error occurred during the parsing of this template'; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
// First, let's get the doctype and language information out of the way. |
272
|
|
|
echo '<!DOCTYPE html> |
273
|
|
|
<html ', !empty($context['right_to_left']) ? 'dir="rtl"' : '', '> |
274
|
|
|
<head> |
275
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; |
276
|
|
|
|
277
|
|
|
if (!empty($maintenance) && !allowedTo('admin_forum')) { |
278
|
|
|
echo ' |
279
|
|
|
<title>', $mtitle, '</title> |
280
|
|
|
</head> |
281
|
|
|
<body> |
282
|
|
|
<h3>', $mtitle, '</h3> |
283
|
|
|
', $mmessage, ' |
284
|
|
|
</body> |
285
|
|
|
</html>'; |
286
|
|
|
} |
287
|
|
|
elseif (!allowedTo('admin_forum')) |
288
|
|
|
{ |
289
|
|
|
echo ' |
290
|
|
|
<title>', $txt['template_parse_error'], '</title> |
291
|
|
|
</head> |
292
|
|
|
<body> |
293
|
|
|
<h3>', $txt['template_parse_error'], '</h3> |
294
|
|
|
', $txt['template_parse_error_message'], ' |
295
|
|
|
</body> |
296
|
|
|
</html>'; |
297
|
|
|
} |
298
|
|
|
else |
299
|
|
|
{ |
300
|
|
|
require_once(SUBSDIR . '/Package.subs.php'); |
301
|
|
|
|
302
|
|
|
$error = fetch_web_data($boardurl . strtr($filename, array(BOARDDIR => '', strtr(BOARDDIR, '\\', '/') => ''))); |
303
|
|
|
if (empty($error) && ini_get('track_errors') && !empty($php_errormsg)) |
304
|
|
|
$error = $php_errormsg; |
305
|
|
|
elseif (empty($error)) |
306
|
|
|
$error = $txt['template_parse_undefined']; |
307
|
|
|
|
308
|
|
|
$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>')); |
309
|
|
|
|
310
|
|
|
echo ' |
311
|
|
|
<title>', $txt['template_parse_error'], '</title> |
312
|
|
|
</head> |
313
|
|
|
<body> |
314
|
|
|
<h3>', $txt['template_parse_error'], '</h3> |
315
|
|
|
', sprintf($txt['template_parse_error_details'], strtr($filename, array(BOARDDIR => '', strtr(BOARDDIR, '\\', '/') => '')), $boardurl, $scripturl . '?theme=1'); |
316
|
|
|
|
317
|
|
|
if (!empty($error)) |
318
|
|
|
echo ' |
319
|
|
|
<hr /> |
320
|
|
|
|
321
|
|
|
<div style="margin: 0 20px;"><span style="font-family: monospace;">', strtr(strtr($error, array('<strong>' . BOARDDIR => '<strong>...', '<strong>' . strtr(BOARDDIR, '\\', '/') => '<strong>...')), '\\', '/'), '</span></div>'; |
322
|
|
|
|
323
|
|
|
// I know, I know... this is VERY COMPLICATED. Still, it's good. |
324
|
|
|
if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0) |
325
|
|
|
{ |
326
|
|
|
$data = file($filename); |
327
|
|
|
$data2 = highlight_php_code(implode('', $data)); |
328
|
|
|
$data2 = preg_split('~\<br( /)?\>~', $data2); |
329
|
|
|
|
330
|
|
|
// Fix the PHP code stuff... |
331
|
|
|
if (!isBrowser('gecko')) |
332
|
|
|
$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2); |
333
|
|
|
else |
334
|
|
|
$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2); |
335
|
|
|
|
336
|
|
|
// Now we get to work around a bug in PHP where it doesn't escape <br />s! |
337
|
|
|
$j = -1; |
338
|
|
|
foreach ($data as $line) |
339
|
|
|
{ |
340
|
|
|
$j++; |
341
|
|
|
|
342
|
|
|
if (substr_count($line, '<br />') == 0) |
343
|
|
|
continue; |
344
|
|
|
|
345
|
|
|
$n = substr_count($line, '<br />'); |
346
|
|
|
for ($i = 0; $i < $n; $i++) |
347
|
|
|
{ |
348
|
|
|
$data2[$j] .= '<br />' . $data2[$j + $i + 1]; |
349
|
|
|
unset($data2[$j + $i + 1]); |
350
|
|
|
} |
351
|
|
|
$j += $n; |
352
|
|
|
} |
353
|
|
|
$data2 = array_values($data2); |
354
|
|
|
array_unshift($data2, ''); |
355
|
|
|
|
356
|
|
|
echo ' |
357
|
|
|
<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">'; |
358
|
|
|
|
359
|
|
|
// Figure out what the color coding was before... |
360
|
|
|
$line = max($match[1] - 9, 1); |
361
|
|
|
$last_line = ''; |
362
|
|
|
for ($line2 = $line - 1; $line2 > 1; $line2--) |
363
|
|
|
if (strpos($data2[$line2], '<') !== false) |
364
|
|
|
{ |
365
|
|
|
if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0) |
366
|
|
|
$last_line = $color_match[1]; |
367
|
|
|
break; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
// Show the relevant lines... |
371
|
|
|
for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++) |
372
|
|
|
{ |
373
|
|
|
if ($line == $match[1]) |
374
|
|
|
echo '</pre><div style="background: #ffb0b5;"><pre style="margin: 0;">'; |
375
|
|
|
|
376
|
|
|
echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> '; |
377
|
|
|
if (isset($data2[$line]) && $data2[$line] != '') |
378
|
|
|
echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line]; |
379
|
|
|
|
380
|
|
|
if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0) |
381
|
|
|
{ |
382
|
|
|
$last_line = $color_match[1]; |
383
|
|
|
echo '</', substr($last_line, 1, 4), '>'; |
384
|
|
|
} |
385
|
|
|
elseif ($last_line != '' && strpos($data2[$line], '<') !== false) |
386
|
|
|
$last_line = ''; |
387
|
|
|
elseif ($last_line != '' && $data2[$line] != '') |
388
|
|
|
echo '</', substr($last_line, 1, 4), '>'; |
389
|
|
|
|
390
|
|
|
if ($line == $match[1]) |
391
|
|
|
echo '</pre></div><pre style="margin: 0;">'; |
392
|
|
|
else |
393
|
|
|
echo "\n"; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
echo '</pre></div>'; |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
echo ' |
400
|
|
|
</body> |
401
|
|
|
</html>'; |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
die; |
|
|
|
|
405
|
|
|
} |
406
|
|
|
|
407
|
1 |
|
public function addDirectory($dir) |
408
|
|
|
{ |
409
|
1 |
|
$this->dirs[] = (string) $dir; |
410
|
1 |
|
return $this; |
411
|
|
|
} |
412
|
|
|
|
413
|
1 |
|
public function reloadDirectories(array $settings) |
414
|
|
|
{ |
415
|
1 |
|
$this->dirs = array(); |
416
|
|
|
|
417
|
1 |
|
if (!empty($settings['theme_dir'])) |
418
|
1 |
|
{ |
419
|
1 |
|
$this->addDirectory($settings['theme_dir']); |
420
|
1 |
|
} |
421
|
|
|
|
422
|
|
|
// Based on theme (if there is one). |
423
|
1 |
|
if (!empty($settings['base_theme_dir'])) |
424
|
1 |
|
{ |
425
|
|
|
$this->addDirectory($settings['base_theme_dir']); |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
// Lastly the default theme. |
429
|
1 |
|
if ($settings['theme_dir'] !== $settings['default_theme_dir']) |
430
|
1 |
|
{ |
431
|
|
|
$this->addDirectory($settings['default_theme_dir']); |
432
|
|
|
} |
433
|
1 |
|
} |
434
|
|
|
|
435
|
|
|
public function hasDirectories() |
436
|
|
|
{ |
437
|
|
|
return !empty($this->dirs); |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
public function getTemplateDirectories() |
441
|
|
|
{ |
442
|
|
|
return $this->dirs; |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
public function getIncludedTemplates() |
446
|
|
|
{ |
447
|
|
|
return $this->templates; |
448
|
|
|
} |
449
|
|
|
} |
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.