/** * Discoverer constructor. * @param Config $config */ public function __construct(Config $config) { $paths = $config->getDiscovererPaths(); if (count($paths) == 0) { throw new \DomainException('The Config object has no discoverable paths'); } //@TODO check mandatory properties of API declaration (url, type) foreach ($paths as $path) { if (!is_dir($path)) { throw new \InvalidArgumentException(sprintf('%s is not a directory', $path)); } if (!is_readable($path)) { throw new \DomainException(sprintf('%s is not a readable directory', $path)); } } $this->config = $config; }
/** * * @param ServerRequestInterface|null $request * @param ResponseInterface|null $response * @param CacheProvider|null $cache * @return array */ public function route(ServerRequestInterface $request = null, ResponseInterface $response = null, CacheProvider $cache = null) { $cacheDir = $this->config->getCacheDirectory(); $cacheKey = $this->config->getApiProperty('id'); $cacheLifetime = $this->config->getCacheLifetime(); $request = $request ?: ServerRequestFactory::fromGlobals(); $response = $response ?: new Response(); $cache = $cache ?: new FilesystemCache($cacheDir); if (count($request->getHeader('Ext-Direct-Token1')) == 0) { throw new \InvalidArgumentException('The Token1 is invalid'); } if (count($request->getHeader('Ext-Direct-Token2')) == 0) { throw new \InvalidArgumentException('The Token2 is invalid'); } $token1 = $request->getHeader('Ext-Direct-Token1')[0]; $token2 = $request->getHeader('Ext-Direct-Token2')[0]; session_id($token1); session_start(); if (!$_SESSION['Ext-Direct-Token2']) { throw new \InvalidArgumentException('The session data is invalid'); } if (strcmp($_SESSION['Ext-Direct-Token2'], $token2) != 0) { throw new \InvalidArgumentException('Token2 verification failed'); } if ($cache->contains($cacheKey)) { $classMap = $cache->fetch($cacheKey); } else { $discoverer = new Discoverer($this->config); $classMap = $discoverer->mapClasses(); $cache->save($cacheKey, $classMap, $cacheLifetime); } $actionsResults = []; $actions = $this->getActions($request, $classMap); $upload = false; foreach ($actions as $action) { $actionsResults[] = $action->run(); if ($action->isUpload()) { $upload = true; } } if ($upload) { $result = sprintf('<html><body><textarea>%s</textarea></body></html>', preg_replace('/"/', '\\"', json_encode($actionsResults[0], \JSON_UNESCAPED_UNICODE))); $response->getBody()->write($result); $this->response = $response->withHeader('Content-Type', 'text/html'); } else { if (count($actionsResults) == 1) { $response->getBody()->write(json_encode($actionsResults[0], \JSON_UNESCAPED_UNICODE)); } else { $response->getBody()->write(json_encode($actionsResults, \JSON_UNESCAPED_UNICODE)); } $this->response = $response->withHeader('Content-Type', 'application/json'); } }
/** * Start discovery process * * @param ResponseInterface|null $response * @param CacheProvider|null $cache * @return array */ public function start(ResponseInterface $response = null, CacheProvider $cache = null) { $cacheDir = $this->config->getCacheDirectory(); $cacheKey = $this->config->getApiProperty('id'); $cacheLifetime = $this->config->getCacheLifetime(); $response = $response ?: new Response(); $cache = $cache ?: new FilesystemCache($cacheDir); if ($cache->contains($cacheKey)) { $classMap = $cache->fetch($cacheKey); } else { $classMap = $this->mapClasses(); $cache->save($cacheKey, $classMap, $cacheLifetime); } $api = $this->buildApi($classMap); $body = sprintf('%s=%s;', $this->config->getApiDescriptor(), json_encode($api, \JSON_UNESCAPED_UNICODE)); $response->getBody()->write($body); if (function_exists('openssl_random_pseudo_bytes')) { $token1 = bin2hex(openssl_random_pseudo_bytes(16)); $token2 = bin2hex(openssl_random_pseudo_bytes(16)); } else { $token1 = uniqid(); $token2 = uniqid(); } if (isset($_COOKIE['Ext-Direct-Token1'])) { $token1 = $_COOKIE['Ext-Direct-Token1']; } else { session_id($token1); } session_start(); if (isset($_SESSION['Ext-Direct-Token2'])) { $token2 = $_SESSION['Ext-Direct-Token2']; } $_SESSION['Ext-Direct-Token2'] = $token2; setcookie('Ext-Direct-Token1', $token1, 0, '/', session_get_cookie_params()['domain']); $response->getBody()->write(sprintf('Ext.define(\'Ext.overrides.data.Connection\',{' . 'override:\'Ext.data.Connection\',request:function(o){o=Ext.apply(o||{},{' . 'withCredentials:true,cors:true,' . 'headers:{\'Ext-Direct-Token1\':\'%s\',\'Ext-Direct-Token2\':\'%s\'}});' . 'this.callParent([o]);}});', $token1, $token2)); $this->response = $response->withHeader('Content-Type', 'text/javascript')->withHeader('Set-Ext-Direct-Token1', $token1)->withHeader('Set-Ext-Direct-Token2', $token2); }
/** * Scan discoverable paths and get actions * * @return array */ public function mapClasses() { $paths = $this->config->getDiscovererPaths(); $files = $classMap = []; foreach ($paths as $path) { $files = array_merge($files, $this->loadDir($path)); } foreach ($files as $file) { $fileContent = file_get_contents($file); $classes = array_keys(AnnotationsParser::parsePhp($fileContent)); Config::includeFile($file); foreach ($classes as $className) { $class = new \ReflectionClass($className); if (!$class->isInstantiable()) { continue; } $classAnnotations = AnnotationsParser::getAll($class); if (!isset($classAnnotations['ExtDirect'])) { continue; } $methods = $this->getMethods($class); $classAlias = null; if (isset($classAnnotations['ExtDirect\\Alias'])) { if (is_array($classAnnotations['ExtDirect\\Alias']) && is_string($classAnnotations['ExtDirect\\Alias'][0])) { $classAlias = $classAnnotations['ExtDirect\\Alias'][0]; } } $actionName = $classAlias ?: $className; $classMap[$actionName]['action'] = $actionName; $classMap[$actionName]['class'] = $className; $classMap[$actionName]['file'] = $file; $classMap[$actionName]['methods'] = $methods; } } return $classMap; }