1<?php
2
3namespace dokuwiki\template\twigstarter;
4
5use BadFunctionCallException;
6use BadMethodCallException;
7use dokuwiki\Menu\MenuInterface;
8use Exception;
9use RuntimeException;
10use Twig\Environment;
11
12require_once __DIR__ . '/vendor/autoload.php';
13
14/**
15 * Controls all functionality of the TwigStarter Template
16 */
17class TemplateController
18{
19    protected $view;
20    protected $twig;
21
22    /**
23     * TemplateController constructor.
24     * @param string $view The current view (main, detail, mediamanager)
25     */
26    public function __construct($view)
27    {
28        global $conf;
29
30        // better compatibility
31        header('X-UA-Compatible: IE=edge,chrome=1');
32
33        // what view is currently displayed?
34        $this->view = $view;
35
36        // lookup templates in the twigstarter and the actual template
37        $paths = [];
38        if (is_dir(tpl_incdir() . 'templates')) {
39            $paths[] = tpl_incdir() . 'templates';
40        }
41        $paths[] = __DIR__ . '/templates';
42        $loader = new TwigStarterLoader($paths);
43        $cache = $conf['cachedir'] . '/twig';
44        io_mkdir_p($cache);
45        $this->twig = new Environment($loader, [
46            'cache' => $conf['allowdebug'] ? false : $cache,
47            'debug' => $conf['allowdebug'],
48            'auto_reload' => true,
49        ]);
50
51    }
52
53    /**
54     * Render the template for the current view
55     *
56     * @param array $vars optional additional variables to set
57     */
58    public function render($vars = [])
59    {
60        global $conf;
61
62        // register all globals to be available in twig
63        $data = $GLOBALS;
64        $data['_SERVER'] = $_SERVER;
65
66        // make this controller available in twig as TPL
67        $data['TPL'] = $this;
68
69        // autoregister a custom controller as a Twig variable
70        $classname = '\\dokuwiki\\template\\' . $conf['template'] . '\\CustomController';
71        if (class_exists($classname) && is_a($classname, CustomControllerInterface::class, true)) {
72            $data['SELF'] = new $classname($this);
73        }
74
75        // add user supplied data
76        $data = array_merge($data, $vars);
77
78        // render the current view template
79        try {
80            echo $this->twig->render($this->view . '.twig', $data);
81        } catch (Exception $e) {
82            $msg = hsc($e->getMessage());
83            if ($conf['allowdebug']) {
84                $msg .= '<pre>' . hsc($e->getTraceAsString()) . '</pre>';
85            }
86
87            nice_die($msg);
88        }
89    }
90
91    /**
92     * Initializes and returns one of the menus
93     *
94     * @param string $type
95     * @return MenuInterface
96     */
97    public function menu($type)
98    {
99        $class = '\\dokuwiki\\Menu\\' . ucfirst($type) . 'Menu';
100        if (class_exists($class)) {
101            return new $class();
102        }
103
104        throw new BadMethodCallException("No such menu $type");
105    }
106
107    /**
108     * Initializes a new object
109     *
110     * This basically exposes the 'new' keyword to Twig. If the given class can't be found
111     * the current template's namespace is prepended and the lookup is tried again.
112     *
113     * @param string $class
114     * @param array $arguments
115     * @return Object
116     */
117    public function newObj($class, $arguments = [])
118    {
119        global $conf;
120        if (class_exists($class)) {
121            $classname = $class;
122        } else {
123            $classname = '\\dokuwiki\\template\\' . $conf['template'] . '\\' . $class;
124        }
125        if (!class_exists($classname)) {
126            throw new RuntimeException("No such class $class");
127        }
128
129        return new $classname(...$arguments);
130    }
131
132    /**
133     * Calls a static method on the given class
134     *
135     * This exposes any static method to Twig. If the given class can't be found
136     * the current template's namespace is prepended and the lookup is tried again.
137     *
138     * @param string $class
139     * @param string $function
140     * @param array $arguments
141     * @return mixed
142     */
143    public function callStatic($class, $function, $arguments = [])
144    {
145        global $conf;
146        if (class_exists($class)) {
147            $classname = $class;
148        } else {
149            $classname = '\\dokuwiki\\template\\' . $conf['template'] . '\\' . $class;
150        }
151        if (!class_exists($classname)) {
152            throw new RuntimeException("No such class $class");
153        }
154
155        if (!is_callable([$classname, $function])) {
156            throw new BadMethodCallException("No such method $class::$function");
157        }
158
159        return call_user_func_array([$classname, $function], $arguments);
160    }
161
162    /**
163     * Return the current view as set in the constructor
164     *
165     * @return string
166     */
167    public function getView()
168    {
169        return $this->view;
170    }
171
172    /**
173     * We make all our functions available to the template as methods of this object
174     *
175     * We always need the functions to return their data, not print it so we use output buffering to
176     * catch any possible output.
177     *
178     * @param string $name
179     * @param array $arguments
180     * @return mixed
181     */
182    public function __call($name, $arguments)
183    {
184        if (function_exists($name)) {
185            ob_start();
186            $return = call_user_func_array($name, $arguments);
187            $output = ob_get_clean();
188            if ($output !== '' && $output !== false) {
189                return $output;
190            }
191            return $return;
192        }
193
194        throw new BadFunctionCallException("Function $name() does not exist");
195    }
196
197}
198