1: <?php
2: /**
3: * @package OpenCart
4: *
5: * @author Daniel Kerr
6: * @copyright Copyright (c) 2005 - 2022, OpenCart, Ltd. (https://www.opencart.com/)
7: * @license https://opensource.org/licenses/GPL-3.0
8: *
9: * @see https://www.opencart.com
10: */
11: namespace Opencart\System\Engine;
12: /**
13: * Class Loader
14: *
15: * @mixin \Opencart\System\Engine\Registry
16: */
17: class Loader {
18: /**
19: * @var \Opencart\System\Engine\Registry
20: */
21: protected \Opencart\System\Engine\Registry $registry;
22:
23: /**
24: * Constructor
25: *
26: * @param \Opencart\System\Engine\Registry $registry
27: */
28: public function __construct(\Opencart\System\Engine\Registry $registry) {
29: $this->registry = $registry;
30: }
31:
32: /**
33: * __get
34: *
35: * https://www.php.net/manual/en/language.oop5.overloading.php#object.get
36: *
37: * @param string $key
38: *
39: * @return object
40: */
41: public function __get(string $key): object {
42: return $this->registry->get($key);
43: }
44:
45: /**
46: * __set
47: *
48: * https://www.php.net/manual/en/language.oop5.overloading.php#object.set
49: *
50: * @param string $key
51: * @param object $value
52: *
53: * @return void
54: */
55: public function __set(string $key, object $value): void {
56: $this->registry->set($key, $value);
57: }
58:
59: /**
60: * Controller
61: *
62: * https://wiki.php.net/rfc/variadics
63: *
64: * @param string $route
65: * @param mixed $args
66: *
67: * @return mixed
68: */
69: public function controller(string $route, ...$args) {
70: // Sanitize the call
71: $route = preg_replace('/[^a-zA-Z0-9_|\/\.]/', '', str_replace('|', '.', $route));
72:
73: $trigger = $route;
74:
75: // Trigger the pre events
76: $this->event->trigger('controller/' . $trigger . '/before', [&$route, &$args]);
77:
78: // Keep the original trigger
79: $action = new \Opencart\System\Engine\Action($route);
80:
81: while ($action) {
82: // Execute action
83: $output = $action->execute($this->registry, $args);
84:
85: // Make action a non-object so it's not infinitely looping
86: $action = '';
87:
88: // Action object returned then we keep the loop going
89: if ($output instanceof \Opencart\System\Engine\Action) {
90: $action = $output;
91: }
92: }
93:
94: // Trigger the post events
95: $this->event->trigger('controller/' . $trigger . '/after', [&$route, &$args, &$output]);
96:
97: return $output;
98: }
99:
100: /**
101: * Model
102: *
103: * @param string $route
104: *
105: * @return void
106: */
107: public function model(string $route): void {
108: // Sanitize the call
109: $route = preg_replace('/[^a-zA-Z0-9_\/]/', '', $route);
110:
111: // Create a new key to store the model object
112: $key = 'model_' . str_replace('/', '_', $route);
113:
114: if (!$this->registry->has($key)) {
115: // Converting a route path to a class name
116: $class = 'Opencart\\' . $this->config->get('application') . '\Model\\' . str_replace(['_', '/'], ['', '\\'], ucwords($route, '_/'));
117:
118: if (class_exists($class)) {
119: $proxy = new \Opencart\System\Engine\Proxy();
120:
121: foreach (get_class_methods($class) as $method) {
122: $reflection = new \ReflectionMethod($class, $method);
123:
124: if ((substr($method, 0, 2) != '__') && $reflection->isPublic()) {
125: // https://wiki.php.net/rfc/variadics
126: $proxy->{$method} = function(&...$args) use ($route, $method) {
127: $route = $route . '/' . $method;
128:
129: $trigger = $route;
130:
131: // Trigger the pre events
132: $this->event->trigger('model/' . $trigger . '/before', [&$route, &$args]);
133:
134: // Find last `/` so we can remove and find the method
135: $pos = strrpos($route, '/');
136:
137: $class = substr($route, 0, $pos);
138: $method = substr($route, $pos + 1);
139:
140: // Create a new key to store the model object
141: $key = 'callback_' . str_replace('/', '_', $class);
142:
143: if (!$this->registry->has($key)) {
144: // Initialize the class
145: $model = $this->factory->model($class);
146:
147: // Store object
148: $this->registry->set($key, $model);
149: } else {
150: $model = $this->registry->get($key);
151: }
152:
153: $callable = [$model, $method];
154:
155: if (is_callable($callable)) {
156: $output = $callable(...$args);
157: } else {
158: throw new \Exception('Error: Could not call model/' . $route . '!');
159: }
160:
161: // Trigger the post events
162: $this->event->trigger('model/' . $trigger . '/after', [&$route, &$args, &$output]);
163:
164: return $output;
165: };
166: }
167: }
168:
169: $this->registry->set($key, $proxy);
170: } else {
171: throw new \Exception('Error: Could not load model ' . $class . '!');
172: }
173: }
174: }
175:
176: /**
177: * View
178: *
179: * Loads the template file and generates the html code.
180: *
181: * @param string $route
182: * @param array<string, mixed> $data
183: * @param string $code
184: *
185: * @return string
186: */
187: public function view(string $route, array $data = [], string $code = ''): string {
188: // Sanitize the call
189: $route = preg_replace('/[^a-zA-Z0-9_\/]/', '', $route);
190:
191: $trigger = $route;
192:
193: $output = '';
194:
195: // Trigger the pre events
196: $this->event->trigger('view/' . $trigger . '/before', [&$route, &$data, &$code, &$output]);
197:
198: if (!$output) {
199: // Make sure it's only the last event that returns an output if required.
200: $output = $this->template->render($route, $data, $code);
201: }
202:
203: // Trigger the post events
204: $this->event->trigger('view/' . $trigger . '/after', [&$route, &$data, &$output]);
205:
206: return $output;
207: }
208:
209: /**
210: * Language
211: *
212: * @param string $route
213: * @param string $prefix
214: * @param string $code
215: *
216: * @return array<string, string>
217: */
218: public function language(string $route, string $prefix = '', string $code = ''): array {
219: // Sanitize the call
220: $route = preg_replace('/[^a-zA-Z0-9_\-\/]/', '', $route);
221:
222: $trigger = $route;
223:
224: // Trigger the pre events
225: $this->event->trigger('language/' . $trigger . '/before', [&$route, &$prefix, &$code]);
226:
227: $output = $this->language->load($route, $prefix, $code);
228:
229: // Trigger the post events
230: $this->event->trigger('language/' . $trigger . '/after', [&$route, &$prefix, &$code, &$output]);
231:
232: return $output;
233: }
234:
235: /**
236: * Library
237: *
238: * @param string $route
239: * @param array<mixed> $args
240: *
241: * @return object
242: */
243: public function library(string $route, &...$args): object {
244: // Sanitize the call
245: $route = preg_replace('/[^a-zA-Z0-9_\/]/', '', $route);
246:
247: // Create a new key to store the model object
248: $key = 'library_' . str_replace('/', '_', $route);
249:
250: if (!$this->registry->has($key)) {
251: // Initialize the class
252: $library = $this->factory->library($route, $args);
253:
254: // Store object
255: $this->registry->set($key, $library);
256: } else {
257: $library = $this->registry->get($key);
258: }
259:
260: return $library;
261: }
262:
263: /**
264: * Config
265: *
266: * @param string $route
267: *
268: * @return array<string, string>
269: */
270: public function config(string $route): array {
271: // Sanitize the call
272: $route = preg_replace('/[^a-zA-Z0-9_\-\/]/', '', $route);
273:
274: $trigger = $route;
275:
276: // Trigger the pre events
277: $this->event->trigger('config/' . $trigger . '/before', [&$route]);
278:
279: $output = $this->config->load($route);
280:
281: // Trigger the post events
282: $this->event->trigger('config/' . $trigger . '/after', [&$route, &$output]);
283:
284: return $output;
285: }
286:
287: /**
288: * Helper
289: *
290: * @param string $route
291: *
292: * @return void
293: */
294: public function helper(string $route): void {
295: $route = preg_replace('/[^a-zA-Z0-9_\/]/', '', $route);
296:
297: if (!str_starts_with($route, 'extension/')) {
298: $file = DIR_SYSTEM . 'helper/' . $route . '.php';
299: } else {
300: $parts = explode('/', substr($route, 10));
301:
302: $code = array_shift($parts);
303:
304: $file = DIR_EXTENSION . $code . '/system/helper/' . implode('/', $parts) . '.php';
305: }
306:
307: if (is_file($file)) {
308: include_once($file);
309: } else {
310: throw new \Exception('Error: Could not load helper ' . $route . '!');
311: }
312: }
313: }
314: