xref: /dokuwiki/inc/Remote/Api.php (revision 104a3b7cee0d6263c9886c95da6cd5d929f79a06)
1dd87735dSAndreas Gohr<?php
2dd87735dSAndreas Gohr
3dd87735dSAndreas Gohrnamespace dokuwiki\Remote;
4dd87735dSAndreas Gohr
5*104a3b7cSAndreas Gohruse dokuwiki\Extension\PluginInterface;
6*104a3b7cSAndreas Gohruse dokuwiki\Input\Input;
7cbb44eabSAndreas Gohruse dokuwiki\Extension\Event;
8e1d9dcc8SAndreas Gohruse dokuwiki\Extension\RemotePlugin;
9dd87735dSAndreas Gohr
10dd87735dSAndreas Gohr/**
11dd87735dSAndreas Gohr * This class provides information about remote access to the wiki.
12dd87735dSAndreas Gohr *
13dd87735dSAndreas Gohr * == Types of methods ==
14dd87735dSAndreas Gohr * There are two types of remote methods. The first is the core methods.
15dd87735dSAndreas Gohr * These are always available and provided by dokuwiki.
16dd87735dSAndreas Gohr * The other is plugin methods. These are provided by remote plugins.
17dd87735dSAndreas Gohr *
18dd87735dSAndreas Gohr * == Information structure ==
19dd87735dSAndreas Gohr * The information about methods will be given in an array with the following structure:
20dd87735dSAndreas Gohr * array(
21dd87735dSAndreas Gohr *     'method.remoteName' => array(
22dd87735dSAndreas Gohr *          'args' => array(
23dd87735dSAndreas Gohr *              'type eg. string|int|...|date|file',
24dd87735dSAndreas Gohr *          )
25dd87735dSAndreas Gohr *          'name' => 'method name in class',
26dd87735dSAndreas Gohr *          'return' => 'type',
27dd87735dSAndreas Gohr *          'public' => 1/0 - method bypass default group check (used by login)
28dd87735dSAndreas Gohr *          ['doc' = 'method documentation'],
29dd87735dSAndreas Gohr *     )
30dd87735dSAndreas Gohr * )
31dd87735dSAndreas Gohr *
32dd87735dSAndreas Gohr * plugin names are formed the following:
33dd87735dSAndreas Gohr *   core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself.
34dd87735dSAndreas Gohr *   i.e.: dokuwiki.version or wiki.getPage
35dd87735dSAndreas Gohr *
36dd87735dSAndreas Gohr * plugin methods are formed like 'plugin.<plugin name>.<method name>'.
37dd87735dSAndreas Gohr * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime
38dd87735dSAndreas Gohr */
39dd87735dSAndreas Gohrclass Api
40dd87735dSAndreas Gohr{
41dd87735dSAndreas Gohr
42dd87735dSAndreas Gohr    /**
43dd87735dSAndreas Gohr     * @var ApiCore
44dd87735dSAndreas Gohr     */
45*104a3b7cSAndreas Gohr    private $coreMethods;
46dd87735dSAndreas Gohr
47dd87735dSAndreas Gohr    /**
48dd87735dSAndreas Gohr     * @var array remote methods provided by dokuwiki plugins - will be filled lazy via
49dd87735dSAndreas Gohr     * {@see dokuwiki\Remote\RemoteAPI#getPluginMethods}
50dd87735dSAndreas Gohr     */
51*104a3b7cSAndreas Gohr    private $pluginMethods;
52dd87735dSAndreas Gohr
53dd87735dSAndreas Gohr    /**
54dd87735dSAndreas Gohr     * @var array contains custom calls to the api. Plugins can use the XML_CALL_REGISTER event.
55dd87735dSAndreas Gohr     * The data inside is 'custom.call.something' => array('plugin name', 'remote method name')
56dd87735dSAndreas Gohr     *
57dd87735dSAndreas Gohr     * The remote method name is the same as in the remote name returned by _getMethods().
58dd87735dSAndreas Gohr     */
59*104a3b7cSAndreas Gohr    private $pluginCustomCalls;
60dd87735dSAndreas Gohr
61dd87735dSAndreas Gohr    private $dateTransformation;
62dd87735dSAndreas Gohr    private $fileTransformation;
63dd87735dSAndreas Gohr
64dd87735dSAndreas Gohr    /**
65dd87735dSAndreas Gohr     * constructor
66dd87735dSAndreas Gohr     */
67dd87735dSAndreas Gohr    public function __construct()
68dd87735dSAndreas Gohr    {
69*104a3b7cSAndreas Gohr        $this->dateTransformation = [$this, 'dummyTransformation'];
70*104a3b7cSAndreas Gohr        $this->fileTransformation = [$this, 'dummyTransformation'];
71dd87735dSAndreas Gohr    }
72dd87735dSAndreas Gohr
73dd87735dSAndreas Gohr    /**
74dd87735dSAndreas Gohr     * Get all available methods with remote access.
75dd87735dSAndreas Gohr     *
76dd87735dSAndreas Gohr     * @return array with information to all available methods
77dd87735dSAndreas Gohr     * @throws RemoteException
78dd87735dSAndreas Gohr     */
79dd87735dSAndreas Gohr    public function getMethods()
80dd87735dSAndreas Gohr    {
81dd87735dSAndreas Gohr        return array_merge($this->getCoreMethods(), $this->getPluginMethods());
82dd87735dSAndreas Gohr    }
83dd87735dSAndreas Gohr
84dd87735dSAndreas Gohr    /**
85dd87735dSAndreas Gohr     * Call a method via remote api.
86dd87735dSAndreas Gohr     *
87dd87735dSAndreas Gohr     * @param string $method name of the method to call.
88dd87735dSAndreas Gohr     * @param array $args arguments to pass to the given method
89dd87735dSAndreas Gohr     * @return mixed result of method call, must be a primitive type.
90dd87735dSAndreas Gohr     * @throws RemoteException
91dd87735dSAndreas Gohr     */
92*104a3b7cSAndreas Gohr    public function call($method, $args = [])
93dd87735dSAndreas Gohr    {
94dd87735dSAndreas Gohr        if ($args === null) {
95*104a3b7cSAndreas Gohr            $args = [];
96dd87735dSAndreas Gohr        }
97056bf31fSDamien Regad        // Ensure we have at least one '.' in $method
98*104a3b7cSAndreas Gohr        [$type, $pluginName, ] = sexplode('.', $method . '.', 3, '');
99dd87735dSAndreas Gohr        if ($type === 'plugin') {
100dd87735dSAndreas Gohr            return $this->callPlugin($pluginName, $method, $args);
101dd87735dSAndreas Gohr        }
102dd87735dSAndreas Gohr        if ($this->coreMethodExist($method)) {
103dd87735dSAndreas Gohr            return $this->callCoreMethod($method, $args);
104dd87735dSAndreas Gohr        }
105dd87735dSAndreas Gohr        return $this->callCustomCallPlugin($method, $args);
106dd87735dSAndreas Gohr    }
107dd87735dSAndreas Gohr
108dd87735dSAndreas Gohr    /**
109dd87735dSAndreas Gohr     * Check existance of core methods
110dd87735dSAndreas Gohr     *
111dd87735dSAndreas Gohr     * @param string $name name of the method
112dd87735dSAndreas Gohr     * @return bool if method exists
113dd87735dSAndreas Gohr     */
114dd87735dSAndreas Gohr    private function coreMethodExist($name)
115dd87735dSAndreas Gohr    {
116dd87735dSAndreas Gohr        $coreMethods = $this->getCoreMethods();
117dd87735dSAndreas Gohr        return array_key_exists($name, $coreMethods);
118dd87735dSAndreas Gohr    }
119dd87735dSAndreas Gohr
120dd87735dSAndreas Gohr    /**
121dd87735dSAndreas Gohr     * Try to call custom methods provided by plugins
122dd87735dSAndreas Gohr     *
123dd87735dSAndreas Gohr     * @param string $method name of method
124dd87735dSAndreas Gohr     * @param array $args
125dd87735dSAndreas Gohr     * @return mixed
1261cdd0090SAndreas Gohr     * @throws RemoteException if method not exists
127dd87735dSAndreas Gohr     */
128dd87735dSAndreas Gohr    private function callCustomCallPlugin($method, $args)
129dd87735dSAndreas Gohr    {
130dd87735dSAndreas Gohr        $customCalls = $this->getCustomCallPlugins();
131dd87735dSAndreas Gohr        if (!array_key_exists($method, $customCalls)) {
1321cdd0090SAndreas Gohr            throw new RemoteException('Method does not exist', -32603);
133dd87735dSAndreas Gohr        }
134*104a3b7cSAndreas Gohr        [$plugin, $method] = $customCalls[$method];
135f7acdb5eSAndreas Gohr        $fullMethod = "plugin.$plugin.$method";
136f7acdb5eSAndreas Gohr        return $this->callPlugin($plugin, $fullMethod, $args);
137dd87735dSAndreas Gohr    }
138dd87735dSAndreas Gohr
139dd87735dSAndreas Gohr    /**
140dd87735dSAndreas Gohr     * Returns plugin calls that are registered via RPC_CALL_ADD action
141dd87735dSAndreas Gohr     *
142dd87735dSAndreas Gohr     * @return array with pairs of custom plugin calls
143dd87735dSAndreas Gohr     * @triggers RPC_CALL_ADD
144dd87735dSAndreas Gohr     */
145dd87735dSAndreas Gohr    private function getCustomCallPlugins()
146dd87735dSAndreas Gohr    {
147dd87735dSAndreas Gohr        if ($this->pluginCustomCalls === null) {
148*104a3b7cSAndreas Gohr            $data = [];
149cbb44eabSAndreas Gohr            Event::createAndTrigger('RPC_CALL_ADD', $data);
150dd87735dSAndreas Gohr            $this->pluginCustomCalls = $data;
151dd87735dSAndreas Gohr        }
152dd87735dSAndreas Gohr        return $this->pluginCustomCalls;
153dd87735dSAndreas Gohr    }
154dd87735dSAndreas Gohr
155dd87735dSAndreas Gohr    /**
156dd87735dSAndreas Gohr     * Call a plugin method
157dd87735dSAndreas Gohr     *
158dd87735dSAndreas Gohr     * @param string $pluginName
159dd87735dSAndreas Gohr     * @param string $method method name
160dd87735dSAndreas Gohr     * @param array $args
161dd87735dSAndreas Gohr     * @return mixed return of custom method
1621cdd0090SAndreas Gohr     * @throws RemoteException
163dd87735dSAndreas Gohr     */
164dd87735dSAndreas Gohr    private function callPlugin($pluginName, $method, $args)
165dd87735dSAndreas Gohr    {
166dd87735dSAndreas Gohr        $plugin = plugin_load('remote', $pluginName);
167dd87735dSAndreas Gohr        $methods = $this->getPluginMethods();
168*104a3b7cSAndreas Gohr        if (!$plugin instanceof PluginInterface) {
1691cdd0090SAndreas Gohr            throw new RemoteException('Method does not exist', -32603);
170dd87735dSAndreas Gohr        }
171dd87735dSAndreas Gohr        $this->checkAccess($methods[$method]);
172dd87735dSAndreas Gohr        $name = $this->getMethodName($methods, $method);
173e1215f13SPhy        try {
174*104a3b7cSAndreas Gohr            set_error_handler([$this, "argumentWarningHandler"], E_WARNING); // for PHP <7.1
175*104a3b7cSAndreas Gohr            return call_user_func_array([$plugin, $name], $args);
176e1215f13SPhy        } catch (\ArgumentCountError $th) {
177e1215f13SPhy            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
1786d7829a7SPhy        } finally {
1796d7829a7SPhy            restore_error_handler();
180e1215f13SPhy        }
181dd87735dSAndreas Gohr    }
182dd87735dSAndreas Gohr
183dd87735dSAndreas Gohr    /**
184dd87735dSAndreas Gohr     * Call a core method
185dd87735dSAndreas Gohr     *
186dd87735dSAndreas Gohr     * @param string $method name of method
187dd87735dSAndreas Gohr     * @param array $args
188dd87735dSAndreas Gohr     * @return mixed
1891cdd0090SAndreas Gohr     * @throws RemoteException if method not exist
190dd87735dSAndreas Gohr     */
191dd87735dSAndreas Gohr    private function callCoreMethod($method, $args)
192dd87735dSAndreas Gohr    {
193dd87735dSAndreas Gohr        $coreMethods = $this->getCoreMethods();
194dd87735dSAndreas Gohr        $this->checkAccess($coreMethods[$method]);
195dd87735dSAndreas Gohr        if (!isset($coreMethods[$method])) {
196dd87735dSAndreas Gohr            throw new RemoteException('Method does not exist', -32603);
197dd87735dSAndreas Gohr        }
198dd87735dSAndreas Gohr        $this->checkArgumentLength($coreMethods[$method], $args);
199e1215f13SPhy        try {
200*104a3b7cSAndreas Gohr            set_error_handler([$this, "argumentWarningHandler"], E_WARNING); // for PHP <7.1
201*104a3b7cSAndreas Gohr            return call_user_func_array([$this->coreMethods, $this->getMethodName($coreMethods, $method)], $args);
202e1215f13SPhy        } catch (\ArgumentCountError $th) {
203e1215f13SPhy            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
2046d7829a7SPhy        } finally {
2056d7829a7SPhy            restore_error_handler();
206e1215f13SPhy        }
207dd87735dSAndreas Gohr    }
208dd87735dSAndreas Gohr
209dd87735dSAndreas Gohr    /**
210dd87735dSAndreas Gohr     * Check if access should be checked
211dd87735dSAndreas Gohr     *
212dd87735dSAndreas Gohr     * @param array $methodMeta data about the method
213dd87735dSAndreas Gohr     * @throws AccessDeniedException
214dd87735dSAndreas Gohr     */
215dd87735dSAndreas Gohr    private function checkAccess($methodMeta)
216dd87735dSAndreas Gohr    {
217dd87735dSAndreas Gohr        if (!isset($methodMeta['public'])) {
218dd87735dSAndreas Gohr            $this->forceAccess();
219*104a3b7cSAndreas Gohr        } elseif ($methodMeta['public'] == '0') {
220dd87735dSAndreas Gohr            $this->forceAccess();
221dd87735dSAndreas Gohr        }
222dd87735dSAndreas Gohr    }
223dd87735dSAndreas Gohr
224dd87735dSAndreas Gohr    /**
225dd87735dSAndreas Gohr     * Check the number of parameters
226dd87735dSAndreas Gohr     *
227dd87735dSAndreas Gohr     * @param array $methodMeta data about the method
228dd87735dSAndreas Gohr     * @param array $args
229dd87735dSAndreas Gohr     * @throws RemoteException if wrong parameter count
230dd87735dSAndreas Gohr     */
231dd87735dSAndreas Gohr    private function checkArgumentLength($methodMeta, $args)
232dd87735dSAndreas Gohr    {
233dd87735dSAndreas Gohr        if (count($methodMeta['args']) < count($args)) {
234dd87735dSAndreas Gohr            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
235dd87735dSAndreas Gohr        }
236dd87735dSAndreas Gohr    }
237dd87735dSAndreas Gohr
238dd87735dSAndreas Gohr    /**
239dd87735dSAndreas Gohr     * Determine the name of the real method
240dd87735dSAndreas Gohr     *
241dd87735dSAndreas Gohr     * @param array $methodMeta list of data of the methods
242dd87735dSAndreas Gohr     * @param string $method name of method
243dd87735dSAndreas Gohr     * @return string
244dd87735dSAndreas Gohr     */
245dd87735dSAndreas Gohr    private function getMethodName($methodMeta, $method)
246dd87735dSAndreas Gohr    {
247dd87735dSAndreas Gohr        if (isset($methodMeta[$method]['name'])) {
248dd87735dSAndreas Gohr            return $methodMeta[$method]['name'];
249dd87735dSAndreas Gohr        }
250dd87735dSAndreas Gohr        $method = explode('.', $method);
251dd87735dSAndreas Gohr        return $method[count($method) - 1];
252dd87735dSAndreas Gohr    }
253dd87735dSAndreas Gohr
254dd87735dSAndreas Gohr    /**
255dd87735dSAndreas Gohr     * Perform access check for current user
256dd87735dSAndreas Gohr     *
257dd87735dSAndreas Gohr     * @return bool true if the current user has access to remote api.
258dd87735dSAndreas Gohr     * @throws AccessDeniedException If remote access disabled
259dd87735dSAndreas Gohr     */
260dd87735dSAndreas Gohr    public function hasAccess()
261dd87735dSAndreas Gohr    {
262dd87735dSAndreas Gohr        global $conf;
263dd87735dSAndreas Gohr        global $USERINFO;
264*104a3b7cSAndreas Gohr        /** @var Input $INPUT */
265dd87735dSAndreas Gohr        global $INPUT;
266dd87735dSAndreas Gohr
267dd87735dSAndreas Gohr        if (!$conf['remote']) {
268dd87735dSAndreas Gohr            throw new AccessDeniedException('server error. RPC server not enabled.', -32604);
269dd87735dSAndreas Gohr        }
270dd87735dSAndreas Gohr        if (trim($conf['remoteuser']) == '!!not set!!') {
271dd87735dSAndreas Gohr            return false;
272dd87735dSAndreas Gohr        }
273dd87735dSAndreas Gohr        if (!$conf['useacl']) {
274dd87735dSAndreas Gohr            return true;
275dd87735dSAndreas Gohr        }
276dd87735dSAndreas Gohr        if (trim($conf['remoteuser']) == '') {
277dd87735dSAndreas Gohr            return true;
278dd87735dSAndreas Gohr        }
279dd87735dSAndreas Gohr
280dd87735dSAndreas Gohr        return auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps']);
281dd87735dSAndreas Gohr    }
282dd87735dSAndreas Gohr
283dd87735dSAndreas Gohr    /**
284dd87735dSAndreas Gohr     * Requests access
285dd87735dSAndreas Gohr     *
286dd87735dSAndreas Gohr     * @return void
287dd87735dSAndreas Gohr     * @throws AccessDeniedException On denied access.
288dd87735dSAndreas Gohr     */
289dd87735dSAndreas Gohr    public function forceAccess()
290dd87735dSAndreas Gohr    {
291dd87735dSAndreas Gohr        if (!$this->hasAccess()) {
292dd87735dSAndreas Gohr            throw new AccessDeniedException('server error. not authorized to call method', -32604);
293dd87735dSAndreas Gohr        }
294dd87735dSAndreas Gohr    }
295dd87735dSAndreas Gohr
296dd87735dSAndreas Gohr    /**
297dd87735dSAndreas Gohr     * Collects all the methods of the enabled Remote Plugins
298dd87735dSAndreas Gohr     *
299dd87735dSAndreas Gohr     * @return array all plugin methods.
300dd87735dSAndreas Gohr     * @throws RemoteException if not implemented
301dd87735dSAndreas Gohr     */
302dd87735dSAndreas Gohr    public function getPluginMethods()
303dd87735dSAndreas Gohr    {
304dd87735dSAndreas Gohr        if ($this->pluginMethods === null) {
305*104a3b7cSAndreas Gohr            $this->pluginMethods = [];
306dd87735dSAndreas Gohr            $plugins = plugin_list('remote');
307dd87735dSAndreas Gohr
308dd87735dSAndreas Gohr            foreach ($plugins as $pluginName) {
309e1d9dcc8SAndreas Gohr                /** @var RemotePlugin $plugin */
310dd87735dSAndreas Gohr                $plugin = plugin_load('remote', $pluginName);
311e1d9dcc8SAndreas Gohr                if (!is_subclass_of($plugin, 'dokuwiki\Extension\RemotePlugin')) {
31227f63a23SAndreas Gohr                    throw new RemoteException(
31327f63a23SAndreas Gohr                        "Plugin $pluginName does not implement dokuwiki\Plugin\DokuWiki_Remote_Plugin"
31427f63a23SAndreas Gohr                    );
315dd87735dSAndreas Gohr                }
316dd87735dSAndreas Gohr
317dd87735dSAndreas Gohr                try {
318dd87735dSAndreas Gohr                    $methods = $plugin->_getMethods();
319dd87735dSAndreas Gohr                } catch (\ReflectionException $e) {
320dd87735dSAndreas Gohr                    throw new RemoteException('Automatic aggregation of available remote methods failed', 0, $e);
321dd87735dSAndreas Gohr                }
322dd87735dSAndreas Gohr
323dd87735dSAndreas Gohr                foreach ($methods as $method => $meta) {
324dd87735dSAndreas Gohr                    $this->pluginMethods["plugin.$pluginName.$method"] = $meta;
325dd87735dSAndreas Gohr                }
326dd87735dSAndreas Gohr            }
327dd87735dSAndreas Gohr        }
328dd87735dSAndreas Gohr        return $this->pluginMethods;
329dd87735dSAndreas Gohr    }
330dd87735dSAndreas Gohr
331dd87735dSAndreas Gohr    /**
332dd87735dSAndreas Gohr     * Collects all the core methods
333dd87735dSAndreas Gohr     *
334dd87735dSAndreas Gohr     * @param ApiCore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore
335dd87735dSAndreas Gohr     *                         instance. (for mocking)
336dd87735dSAndreas Gohr     * @return array all core methods.
337dd87735dSAndreas Gohr     */
338dd87735dSAndreas Gohr    public function getCoreMethods($apiCore = null)
339dd87735dSAndreas Gohr    {
340*104a3b7cSAndreas Gohr        if (!$this->coreMethods instanceof ApiCore) {
341*104a3b7cSAndreas Gohr            if (!$apiCore instanceof ApiCore) {
342dd87735dSAndreas Gohr                $this->coreMethods = new ApiCore($this);
343dd87735dSAndreas Gohr            } else {
344dd87735dSAndreas Gohr                $this->coreMethods = $apiCore;
345dd87735dSAndreas Gohr            }
346dd87735dSAndreas Gohr        }
347e6a9d76fSSyntaxseed        return $this->coreMethods->getRemoteInfo();
348dd87735dSAndreas Gohr    }
349dd87735dSAndreas Gohr
350dd87735dSAndreas Gohr    /**
351dd87735dSAndreas Gohr     * Transform file to xml
352dd87735dSAndreas Gohr     *
353dd87735dSAndreas Gohr     * @param mixed $data
354dd87735dSAndreas Gohr     * @return mixed
355dd87735dSAndreas Gohr     */
356dd87735dSAndreas Gohr    public function toFile($data)
357dd87735dSAndreas Gohr    {
358dd87735dSAndreas Gohr        return call_user_func($this->fileTransformation, $data);
359dd87735dSAndreas Gohr    }
360dd87735dSAndreas Gohr
361dd87735dSAndreas Gohr    /**
362dd87735dSAndreas Gohr     * Transform date to xml
363dd87735dSAndreas Gohr     *
364dd87735dSAndreas Gohr     * @param mixed $data
365dd87735dSAndreas Gohr     * @return mixed
366dd87735dSAndreas Gohr     */
367dd87735dSAndreas Gohr    public function toDate($data)
368dd87735dSAndreas Gohr    {
369dd87735dSAndreas Gohr        return call_user_func($this->dateTransformation, $data);
370dd87735dSAndreas Gohr    }
371dd87735dSAndreas Gohr
372dd87735dSAndreas Gohr    /**
373dd87735dSAndreas Gohr     * A simple transformation
374dd87735dSAndreas Gohr     *
375dd87735dSAndreas Gohr     * @param mixed $data
376dd87735dSAndreas Gohr     * @return mixed
377dd87735dSAndreas Gohr     */
378dd87735dSAndreas Gohr    public function dummyTransformation($data)
379dd87735dSAndreas Gohr    {
380dd87735dSAndreas Gohr        return $data;
381dd87735dSAndreas Gohr    }
382dd87735dSAndreas Gohr
383dd87735dSAndreas Gohr    /**
384dd87735dSAndreas Gohr     * Set the transformer function
385dd87735dSAndreas Gohr     *
386dd87735dSAndreas Gohr     * @param callback $dateTransformation
387dd87735dSAndreas Gohr     */
388dd87735dSAndreas Gohr    public function setDateTransformation($dateTransformation)
389dd87735dSAndreas Gohr    {
390dd87735dSAndreas Gohr        $this->dateTransformation = $dateTransformation;
391dd87735dSAndreas Gohr    }
392dd87735dSAndreas Gohr
393dd87735dSAndreas Gohr    /**
394dd87735dSAndreas Gohr     * Set the transformer function
395dd87735dSAndreas Gohr     *
396dd87735dSAndreas Gohr     * @param callback $fileTransformation
397dd87735dSAndreas Gohr     */
398dd87735dSAndreas Gohr    public function setFileTransformation($fileTransformation)
399dd87735dSAndreas Gohr    {
400dd87735dSAndreas Gohr        $this->fileTransformation = $fileTransformation;
401dd87735dSAndreas Gohr    }
4026d7829a7SPhy
4036d7829a7SPhy    /**
4046d7829a7SPhy     * The error handler that catches argument-related warnings
4056d7829a7SPhy     */
4066d7829a7SPhy    public function argumentWarningHandler($errno, $errstr)
4076d7829a7SPhy    {
4086d7829a7SPhy        if (substr($errstr, 0, 17) == 'Missing argument ') {
4096d7829a7SPhy            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
4106d7829a7SPhy        }
4116d7829a7SPhy    }
412dd87735dSAndreas Gohr}
413