PageRenderTime 324ms CodeModel.GetById 101ms app.highlight 125ms RepoModel.GetById 90ms app.codeStats 0ms

/public/external/pydio/core/classes/class.AJXP_Controller.php

https://github.com/costinu/cms
PHP | 579 lines | 365 code | 19 blank | 195 comment | 128 complexity | d5eeddf4360c65934757e58d4f532a7f MD5 | raw file
  1<?php
  2/*
  3 * Copyright 2007-2013 Charles du Jeu - Abstrium SAS <team (at) pyd.io>
  4 * This file is part of Pydio.
  5 *
  6 * Pydio is free software: you can redistribute it and/or modify
  7 * it under the terms of the GNU Affero General Public License as published by
  8 * the Free Software Foundation, either version 3 of the License, or
  9 * (at your option) any later version.
 10 *
 11 * Pydio is distributed in the hope that it will be useful,
 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14 * GNU Affero General Public License for more details.
 15 *
 16 * You should have received a copy of the GNU Affero General Public License
 17 * along with Pydio.  If not, see <http://www.gnu.org/licenses/>.
 18 *
 19 * The latest code can be found at <http://pyd.io/>.
 20 */
 21defined('AJXP_EXEC') or die( 'Access not allowed');
 22/**
 23 * Core controller for dispatching the actions.
 24 * It uses the XML Registry (simple version, not extended) to search all the <action> tags and find the action.
 25 * @package Pydio
 26 * @subpackage Core
 27 */
 28class AJXP_Controller
 29{
 30    /**
 31     * @var DOMXPath
 32     */
 33    private static $xPath;
 34    /**
 35     * @var bool
 36     */
 37    public static $lastActionNeedsAuth = false;
 38    /**
 39     * @var array
 40     */
 41    private static $includeHooks = array();
 42
 43    /**
 44     * Initialize the queryable xPath object
 45     * @static
 46     * @return DOMXPath
 47     */
 48    private static function initXPath()
 49    {
 50        if (!isSet(self::$xPath)) {
 51
 52            $registry = AJXP_PluginsService::getXmlRegistry( false );
 53            $changes = self::filterRegistryFromRole($registry);
 54            if($changes) AJXP_PluginsService::updateXmlRegistry($registry);
 55            self::$xPath = new DOMXPath($registry);
 56        }
 57        return self::$xPath;
 58    }
 59
 60    /**
 61     * Check the current user "specificActionsRights" and filter the full registry actions with these.
 62     * @static
 63     * @param DOMDocument $registry
 64     * @return bool
 65     */
 66    public static function filterRegistryFromRole(&$registry)
 67    {
 68        if(!AuthService::usersEnabled()) return false ;
 69        $loggedUser = AuthService::getLoggedUser();
 70        if($loggedUser == null) return false;
 71        $crtRepo = ConfService::getRepository();
 72        $crtRepoId = AJXP_REPO_SCOPE_ALL; // "ajxp.all";
 73        if ($crtRepo != null && is_a($crtRepo, "Repository")) {
 74            $crtRepoId = $crtRepo->getId();
 75        }
 76        $actionRights = $loggedUser->mergedRole->listActionsStatesFor($crtRepo);
 77        $changes = false;
 78        $xPath = new DOMXPath($registry);
 79        foreach ($actionRights as $pluginName => $actions) {
 80            foreach ($actions as $actionName => $enabled) {
 81                if($enabled !== false) continue;
 82                $actions = $xPath->query("actions/action[@name='$actionName']");
 83                if (!$actions->length) {
 84                    continue;
 85                }
 86                $action = $actions->item(0);
 87                $action->parentNode->removeChild($action);
 88                $changes = true;
 89            }
 90        }
 91        $parameters = $loggedUser->mergedRole->listParameters();
 92        foreach ($parameters as $scope => $paramsPlugs) {
 93            if ($scope == AJXP_REPO_SCOPE_ALL || $scope == $crtRepoId || ($crtRepo->hasParent() && $scope == AJXP_REPO_SCOPE_SHARED)) {
 94                foreach ($paramsPlugs as $plugId => $params) {
 95                    foreach ($params as $name => $value) {
 96                        // Search exposed plugin_configs, replace if necessary.
 97                        $searchparams = $xPath->query("plugins/*[@id='$plugId']/plugin_configs/property[@name='$name']");
 98                        if(!$searchparams->length) continue;
 99                        $param = $searchparams->item(0);
100                        $newCdata = $registry->createCDATASection(json_encode($value));
101                        $param->removeChild($param->firstChild);
102                        $param->appendChild($newCdata);
103                    }
104                }
105            }
106        }
107        return $changes;
108    }
109
110
111    /**
112     * @param $actionName
113     * @param $path
114     * @return bool
115     */
116    public static function findRestActionAndApply($actionName, $path)
117    {
118        $xPath = self::initXPath();
119        $actions = $xPath->query("actions/action[@name='$actionName']");
120        if (!$actions->length) {
121            self::$lastActionNeedsAuth = true;
122            return false;
123        }
124        $action = $actions->item(0);
125        $restPathList = $xPath->query("processing/serverCallback/@restParams", $action);
126        if (!$restPathList->length) {
127            self::$lastActionNeedsAuth = true;
128            return false;
129        }
130        $restPath = $restPathList->item(0)->nodeValue;
131        $paramNames = explode("/", trim($restPath, "/"));
132        $path = array_shift(explode("?", $path));
133        $paramValues = array_map("urldecode", explode("/", trim($path, "/"), count($paramNames)));
134        foreach ($paramNames as $i => $pName) {
135            if (strpos($pName, "+") !== false) {
136                $paramNames[$i] = str_replace("+", "", $pName);
137                $paramValues[$i] = "/" . $paramValues[$i];
138            }
139        }
140        if (count($paramValues) < count($paramNames)) {
141            $paramNames = array_slice($paramNames, 0, count($paramValues));
142        }
143        $httpVars = array_merge($_GET, $_POST, array_combine($paramNames, $paramValues));
144        return self::findActionAndApply($actionName, $httpVars, $_FILES, $action);
145
146    }
147
148    /**
149     * @static
150     * @param Array $parameters
151     * @param DOMNode $callbackNode
152     * @param DOMXPath $xPath
153     * @throws Exception
154     */
155    public static function checkParams(&$parameters, $callbackNode, $xPath)
156    {
157        if (!$callbackNode->attributes->getNamedItem('checkParams') || $callbackNode->attributes->getNamedItem('checkParams')->nodeValue != "true") {
158            return;
159        }
160        $inputParams = $xPath->query("input_param", $callbackNode);
161        $declaredParams = array();
162        foreach ($inputParams as $param) {
163            $name = $param->attributes->getNamedItem("name")->nodeValue;
164            $type = $param->attributes->getNamedItem("type")->nodeValue;
165            $defaultNode = $param->attributes->getNamedItem("default");
166            $mandatory = ($param->attributes->getNamedItem("mandatory")->nodeValue == "true");
167            if ($mandatory && !isSet($parameters[$name])) {
168                throw new Exception("Missing parameter '".$name."' of type '$type'");
169            }
170            if ($defaultNode != null && !isSet($parameters[$name])) {
171                $parameters[$name] = $defaultNode->nodeValue;
172            }
173            $declaredParams[] = $name;
174        }
175        foreach ($parameters as $k => $n) {
176            if(!in_array($k, $declaredParams)) unset($parameters[$k]);
177        }
178    }
179
180    /**
181     * Main method for querying the XML registry, find an action and all its associated processors,
182     * and apply all the callbacks.
183     * @static
184     * @param String $actionName
185     * @param array $httpVars
186     * @param array $fileVars
187     * @param DOMNode $action
188     * @return bool
189     */
190    public static function findActionAndApply($actionName, $httpVars, $fileVars, &$action = null)
191    {
192        $actionName = AJXP_Utils::sanitize($actionName, AJXP_SANITIZE_EMAILCHARS);
193        if ($actionName == "cross_copy") {
194            $pService = AJXP_PluginsService::getInstance();
195            $actives = $pService->getActivePlugins();
196            $accessPlug = $pService->getPluginsByType("access");
197            if (count($accessPlug)) {
198                foreach ($accessPlug as $key=>$objbect) {
199                    if ($actives[$objbect->getId()] === true) {
200                        call_user_func(array($pService->getPluginById($objbect->getId()), "crossRepositoryCopy"), $httpVars);
201                        break;
202                    }
203                }
204            }
205            self::$lastActionNeedsAuth = true;
206            return ;
207        }
208        $xPath = self::initXPath();
209        if ($action == null) {
210            $actions = $xPath->query("actions/action[@name='$actionName']");
211            if (!$actions->length) {
212                self::$lastActionNeedsAuth = true;
213                return false;
214            }
215            $action = $actions->item(0);
216        }
217        //Check Rights
218        if (AuthService::usersEnabled()) {
219            $loggedUser = AuthService::getLoggedUser();
220            if( AJXP_Controller::actionNeedsRight($action, $xPath, "adminOnly") &&
221                ($loggedUser == null || !$loggedUser->isAdmin())){
222                    $mess = ConfService::getMessages();
223                    AJXP_XMLWriter::header();
224                    AJXP_XMLWriter::sendMessage(null, $mess[207]);
225                    AJXP_XMLWriter::requireAuth();
226                    AJXP_XMLWriter::close();
227                    exit(1);
228                }
229            if( AJXP_Controller::actionNeedsRight($action, $xPath, "read") &&
230                ($loggedUser == null || !$loggedUser->canRead(ConfService::getCurrentRepositoryId().""))){
231                    AJXP_XMLWriter::header();
232                    if($actionName == "ls" & $loggedUser!=null
233                        && $loggedUser->canWrite(ConfService::getCurrentRepositoryId()."")){
234                        // Special case of "write only" right : return empty listing, no auth error.
235                        AJXP_XMLWriter::close();
236                        exit(1);
237                    }
238                    $mess = ConfService::getMessages();
239                    AJXP_XMLWriter::sendMessage(null, $mess[208]);
240                    AJXP_XMLWriter::requireAuth();
241                    AJXP_XMLWriter::close();
242                    exit(1);
243                }
244            if( AJXP_Controller::actionNeedsRight($action, $xPath, "write") &&
245                ($loggedUser == null || !$loggedUser->canWrite(ConfService::getCurrentRepositoryId().""))){
246                    $mess = ConfService::getMessages();
247                    AJXP_XMLWriter::header();
248                    AJXP_XMLWriter::sendMessage(null, $mess[207]);
249                    AJXP_XMLWriter::requireAuth();
250                    AJXP_XMLWriter::close();
251                    exit(1);
252                }
253        }
254
255        $preCalls = self::getCallbackNode($xPath, $action, 'pre_processing/serverCallback', $actionName, $httpVars, $fileVars, true);
256        $postCalls = self::getCallbackNode($xPath, $action, 'post_processing/serverCallback[not(@capture="true")]', $actionName, $httpVars, $fileVars, true);
257        $captureCalls = self::getCallbackNode($xPath, $action, 'post_processing/serverCallback[@capture="true"]', $actionName, $httpVars, $fileVars, true);
258        $mainCall = self::getCallbackNode($xPath, $action, "processing/serverCallback",$actionName, $httpVars, $fileVars, false);
259        if ($mainCall != null) {
260            self::checkParams($httpVars, $mainCall, $xPath);
261        }
262
263        if ($captureCalls !== false) {
264            // Make sure the ShutdownScheduler has its own OB started BEFORE, as it will presumabily be
265            // executed AFTER the end of this one.
266            AJXP_ShutdownScheduler::getInstance();
267            ob_start();
268            $params = array("pre_processor_results" => array(), "post_processor_results" => array());
269        }
270        if ($preCalls !== false) {
271            foreach ($preCalls as $preCall) {
272                // A Preprocessing callback can modify its input arguments (passed by ref)
273                $preResult = self::applyCallback($xPath, $preCall, $actionName, $httpVars, $fileVars);
274                if (isSet($params)) {
275                    $params["pre_processor_results"][$preCall->getAttribute("pluginId")] = $preResult;
276                }
277            }
278        }
279        if ($mainCall) {
280            $result = self::applyCallback($xPath, $mainCall, $actionName, $httpVars, $fileVars);
281            if (isSet($params)) {
282                $params["processor_result"] = $result;
283            }
284        }
285        if ($postCalls !== false) {
286            foreach ($postCalls as $postCall) {
287                // A Preprocessing callback can modify its input arguments (passed by ref)
288                $postResult = self::applyCallback($xPath, $postCall, $actionName, $httpVars, $fileVars);
289                if (isSet($params)) {
290                    $params["post_processor_results"][$postCall->getAttribute("pluginId")] = $postResult;
291                }
292            }
293        }
294        if ($captureCalls !== false) {
295            $params["ob_output"] = ob_get_contents();
296            ob_end_clean();
297            foreach ($captureCalls as $captureCall) {
298                self::applyCallback($xPath, $captureCall, $actionName, $httpVars, $params);
299            }
300        } else {
301            if(isSet($result)) return $result;
302        }
303    }
304
305    /**
306     * Launch a command-line version of the framework by passing the actionName & parameters as arguments.
307     * @static
308     * @param String $currentRepositoryId
309     * @param String $actionName
310     * @param Array $parameters
311     * @param string $user
312     * @param string $statusFile
313     * @return null|UnixProcess
314     */
315    public static function applyActionInBackground($currentRepositoryId, $actionName, $parameters, $user ="", $statusFile = "")
316    {
317        $token = md5(time());
318        $logDir = AJXP_CACHE_DIR."/cmd_outputs";
319        if(!is_dir($logDir)) mkdir($logDir, 0755);
320        $logFile = $logDir."/".$token.".out";
321        if (empty($user)) {
322            if(AuthService::usersEnabled() && AuthService::getLoggedUser() !== null) $user = AuthService::getLoggedUser()->getId();
323            else $user = "shared";
324        }
325        if (AuthService::usersEnabled()) {
326            $user = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256,  md5($token."\1CDAFx¨op#"), $user, MCRYPT_MODE_ECB));
327        }
328        $robustInstallPath = str_replace("/", DIRECTORY_SEPARATOR, AJXP_INSTALL_PATH);
329        $cmd = ConfService::getCoreConf("CLI_PHP")." ".$robustInstallPath.DIRECTORY_SEPARATOR."cmd.php -u=$user -t=$token -a=$actionName -r=$currentRepositoryId";
330        /* Inserted next 3 lines to quote the command if in windows - rmeske*/
331        if (PHP_OS == "WIN32" || PHP_OS == "WINNT" || PHP_OS == "Windows") {
332            $cmd = ConfService::getCoreConf("CLI_PHP")." ".chr(34).$robustInstallPath.DIRECTORY_SEPARATOR."cmd.php".chr(34)." -u=$user -t=$token -a=$actionName -r=$currentRepositoryId";
333        }
334        if ($statusFile != "") {
335            $cmd .= " -s=".$statusFile;
336        }
337        foreach ($parameters as $key=>$value) {
338            if($key == "action" || $key == "get_action") continue;
339            if(is_array($value)){
340                $index = 0;
341                foreach($value as $v){
342                    $cmd .= " --file_".$index."=".escapeshellarg($v);
343                    $index++;
344                }
345            }else{
346                $cmd .= " --$key=".escapeshellarg($value);
347            }
348        }
349
350        return self::runCommandInBackground($cmd, $logFile);
351        /*
352        if (PHP_OS == "WIN32" || PHP_OS == "WINNT" || PHP_OS == "Windows") {
353            if(AJXP_SERVER_DEBUG) $cmd .= " > ".$logFile;
354            if (class_exists("COM") && ConfService::getCoreConf("CLI_USE_COM")) {
355                $WshShell   = new COM("WScript.Shell");
356                $oExec      = $WshShell->Run("cmd /C $cmd", 0, false);
357            } else {
358                $tmpBat = implode(DIRECTORY_SEPARATOR, array( $robustInstallPath, "data","tmp", md5(time()).".bat"));
359                $cmd .= "\n DEL ".chr(34).$tmpBat.chr(34);
360                AJXP_Logger::debug("Writing file $cmd to $tmpBat");
361                file_put_contents($tmpBat, $cmd);
362                pclose(popen('start /b "CLI" "'.$tmpBat.'"', 'r'));
363            }
364        } else {
365            $process = new UnixProcess($cmd, (AJXP_SERVER_DEBUG?$logFile:null));
366            AJXP_Logger::debug("Starting process and sending output dev null");
367            return $process;
368        }
369        */
370    }
371
372    /**
373     * @param $cmd
374     * @param $logFile
375     * @return UnixProcess|null
376     */
377    public static function runCommandInBackground($cmd, $logFile)
378    {
379        if (PHP_OS == "WIN32" || PHP_OS == "WINNT" || PHP_OS == "Windows") {
380              if(AJXP_SERVER_DEBUG) $cmd .= " > ".$logFile;
381              if (class_exists("COM") && ConfService::getCoreConf("CLI_USE_COM")) {
382                  $WshShell   = new COM("WScript.Shell");
383                  $oExec      = $WshShell->Run("cmd /C $cmd", 0, false);
384              } else {
385                  $basePath = str_replace("/", DIRECTORY_SEPARATOR, AJXP_INSTALL_PATH);
386                  $tmpBat = implode(DIRECTORY_SEPARATOR, array( $basePath, "data","tmp", md5(time()).".bat"));
387                  $cmd .= "\n DEL ".chr(34).$tmpBat.chr(34);
388                  AJXP_Logger::debug("Writing file $cmd to $tmpBat");
389                  file_put_contents($tmpBat, $cmd);
390                  pclose(popen('start /b "CLI" "'.$tmpBat.'"', 'r'));
391              }
392        } else {
393            $process = new UnixProcess($cmd, (AJXP_SERVER_DEBUG?$logFile:null));
394            AJXP_Logger::debug("Starting process and sending output dev null");
395            return $process;
396        }
397    }
398
399    /**
400     * Find a callback node by its xpath query, filtering with the applyCondition if the xml attribute exists.
401     * @static
402     * @param DOMXPath $xPath
403     * @param DOMNode $actionNode
404     * @param string $query
405     * @param string $actionName
406     * @param array $httpVars
407     * @param array $fileVars
408     * @param bool $multiple
409     * @return DOMNode|bool
410     */
411    private static function getCallbackNode($xPath, $actionNode, $query ,$actionName, $httpVars, $fileVars, $multiple = true)
412    {
413        $callbacks = $xPath->query($query, $actionNode);
414        if(!$callbacks->length) return false;
415        if ($multiple) {
416            $cbArray = array();
417            foreach ($callbacks as $callback) {
418                if (self::appliesCondition($callback, $actionName, $httpVars, $fileVars)) {
419                    $cbArray[] = $callback;
420                }
421            }
422            if(!count($cbArray)) return  false;
423            return $cbArray;
424        } else {
425            $callback=$callbacks->item(0);
426            if(!self::appliesCondition($callback, $actionName, $httpVars, $fileVars)) return false;
427            return $callback;
428        }
429    }
430
431    /**
432     * Check in the callback node if an applyCondition XML attribute exists, and eval its content.
433     * The content must set an $apply boolean as result
434     * @static
435     * @param DOMNode $callback
436     * @param string $actionName
437     * @param array $httpVars
438     * @param array $fileVars
439     * @return bool
440     */
441    private static function appliesCondition($callback, $actionName, $httpVars, $fileVars)
442    {
443        if ($callback->getAttribute("applyCondition")!="") {
444            $apply = false;
445            eval($callback->getAttribute("applyCondition"));
446            if(!$apply) return false;
447        }
448        return true;
449    }
450
451    /**
452     * Applies a callback node
453     * @static
454     * @param DOMXPath $xPath
455     * @param DOMNode $callback
456     * @param String $actionName
457     * @param Array $httpVars
458     * @param Array $fileVars
459     * @param null $variableArgs
460     * @throw AJXP_Exception
461     * @return void
462     */
463    private static function applyCallback($xPath, $callback, &$actionName, &$httpVars, &$fileVars, &$variableArgs = null, $defer = false)
464    {
465        //Processing
466        $plugId = $xPath->query("@pluginId", $callback)->item(0)->value;
467        $methodName = $xPath->query("@methodName", $callback)->item(0)->value;
468        $plugInstance = AJXP_PluginsService::findPluginById($plugId);
469        //return call_user_func(array($plugInstance, $methodName), $actionName, $httpVars, $fileVars);
470        // Do not use call_user_func, it cannot pass parameters by reference.
471        if (method_exists($plugInstance, $methodName)) {
472            if ($variableArgs == null) {
473                return $plugInstance->$methodName($actionName, $httpVars, $fileVars);
474            } else {
475                if ($defer == true) {
476                    AJXP_ShutdownScheduler::getInstance()->registerShutdownEventArray(array($plugInstance, $methodName), $variableArgs);
477                } else {
478                    call_user_func_array(array($plugInstance, $methodName), $variableArgs);
479                }
480            }
481        } else {
482            throw new AJXP_Exception("Cannot find method $methodName for plugin $plugId!");
483        }
484    }
485
486    /**
487     * Find all callbacks registered for a given hook and apply them
488     * @static
489     * @param string $hookName
490     * @param array $args
491     * @return
492     */
493    public static function applyHook($hookName, $args, $forceNonDefer = false)
494    {
495        $xPath = self::initXPath();
496        $callbacks = $xPath->query("hooks/serverCallback[@hookName='$hookName']");
497        if(!$callbacks->length) return ;
498        $callback = new DOMNode();
499        foreach ($callbacks as $callback) {
500            if ($callback->getAttribute("applyCondition")!="") {
501                $apply = false;
502                eval($callback->getAttribute("applyCondition"));
503                if(!$apply) continue;
504              }
505            //$fake1; $fake2; $fake3;
506            $defer = ($callback->attributes->getNamedItem("defer") != null && $callback->attributes->getNamedItem("defer")->nodeValue == "true");
507            if($defer && $forceNonDefer) $defer = false;
508            self::applyCallback($xPath, $callback, $fake1, $fake2, $fake3, $args, $defer);
509        }
510    }
511
512    /**
513     * Find the statically defined callbacks for a given hook and apply them
514     * @static
515     * @param $hookName
516     * @param $args
517     * @return
518     */
519    public static function applyIncludeHook($hookName, &$args)
520    {
521        if(!isSet(self::$includeHooks[$hookName])) return;
522        foreach (self::$includeHooks[$hookName] as $callback) {
523            call_user_func_array($callback, $args);
524        }
525    }
526
527    /**
528     * Register a hook statically when it must be defined before the XML registry construction.
529     * @static
530     * @param $hookName
531     * @param $callback
532     * @return void
533     */
534    public static function registerIncludeHook($hookName, $callback)
535    {
536        if (!isSet(self::$includeHooks[$hookName])) {
537            self::$includeHooks[$hookName] = array();
538        }
539        self::$includeHooks[$hookName][] = $callback;
540    }
541
542    /**
543     * Check the rightsContext node of an action.
544     * @static
545     * @param DOMNode $actionNode
546     * @param DOMXPath $xPath
547     * @param string $right
548     * @return bool
549     */
550    public static function actionNeedsRight($actionNode, $xPath, $right)
551    {
552        $rights = $xPath->query("rightsContext", $actionNode);
553        if(!$rights->length) return false;
554        $rightNode =  $rights->item(0);
555        $rightAttr = $xPath->query("@".$right, $rightNode);
556        if ($rightAttr->length && $rightAttr->item(0)->value == "true") {
557            self::$lastActionNeedsAuth = true;
558            return true;
559        }
560        return false;
561    }
562
563    /**
564     * Utilitary used by the postprocesors to forward previously computed data
565     * @static
566     * @param array $postProcessData
567     * @return void
568     */
569    public static function passProcessDataThrough($postProcessData)
570    {
571        if (isSet($postProcessData["pre_processor_results"]) && is_array($postProcessData["pre_processor_results"])) {
572            print(implode("", $postProcessData["pre_processor_results"]));
573        }
574        if (isSet($postProcessData["processor_result"])) {
575            print($postProcessData["processor_result"]);
576        }
577        if(isSet($postProcessData["ob_output"])) print($postProcessData["ob_output"]);
578    }
579}