PageRenderTime 27ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/APIModule.php

http://github.com/modolabs/Kurogo-Mobile-Web
PHP | 435 lines | 281 code | 59 blank | 95 comment | 38 complexity | 00db35b41c12d875ea0cb1d1bba8de41 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1
  1. <?php
  2. /*
  3. * Copyright © 2010 - 2013 Modo Labs Inc. All rights reserved.
  4. *
  5. * The license governing the contents of this file is located in the LICENSE
  6. * file located at the root directory of this distribution. If the LICENSE file
  7. * is missing, please contact sales@modolabs.com.
  8. *
  9. */
  10. abstract class APIModule extends Module
  11. {
  12. protected $responseVersion;
  13. protected $requestedVersion;
  14. protected $requestedVmin;
  15. protected $clientVersion;
  16. protected $clientPagetype;
  17. protected $clientPlatform;
  18. protected $clientBrowser;
  19. protected $vmin;
  20. protected $vmax;
  21. protected $command = '';
  22. protected $response; //response object
  23. protected $warnings = array();
  24. public function getVmin() {
  25. return $this->vmin;
  26. }
  27. public function getVmax() {
  28. return $this->vmax;
  29. }
  30. public function getPayload() {
  31. return null;
  32. }
  33. public function getWebBridgeConfig($platform=null) {
  34. return KurogoWebBridge::getHelloMessageForModule($this->configModule, $platform);
  35. }
  36. protected function getAllModuleNavigationData() {
  37. $modules = $moduleNavData = array(
  38. 'primary' => array(),
  39. 'secondary'=> array(),
  40. );
  41. $navModules = Kurogo::getSiteSections('navigation', Config::APPLY_CONTEXTS_NAVIGATION);
  42. foreach ($navModules as $moduleID=>$moduleData) {
  43. $type = Kurogo::arrayVal($moduleData, 'type', 'primary');
  44. if (isset($moduleData['minAPIClientVersion'])) {
  45. if (version_compare($this->clientVersion, $moduleData['minAPIClientVersion'], 'lt')) {
  46. continue;
  47. }
  48. }
  49. if (isset($moduleData['maxAPIClientVersion'])) {
  50. if (version_compare($this->clientVersion, $moduleData['maxAPIClientVersion'], 'gt')) {
  51. continue;
  52. }
  53. }
  54. if (isset($moduleData['pagetype'])) {
  55. if (!is_array($moduleData['pagetype'])) {
  56. $moduleData['pagetype'] = array($moduleData['pagetype']);
  57. }
  58. if (!in_array($this->clientPagetype, $moduleData['pagetype'])) {
  59. continue;
  60. }
  61. }
  62. if (isset($moduleData['browser'])) {
  63. if (!is_array($moduleData['browser'])) {
  64. $moduleData['browser'] = array($moduleData['browser']);
  65. }
  66. if (!in_array($this->clientBrowser, $moduleData['browser'])) {
  67. continue;
  68. }
  69. }
  70. if (isset($moduleData['platform'])) {
  71. if (!is_array($moduleData['platform'])) {
  72. $moduleData['platform'] = array($moduleData['platform']);
  73. }
  74. if (!in_array($this->clientPlatform, $moduleData['platform'])) {
  75. continue;
  76. }
  77. }
  78. $moduleNavData[$type][$moduleID] = $moduleData;
  79. }
  80. return $moduleNavData;
  81. }
  82. public function warningHandler($errno, $str, $file, $line) {
  83. if (!(error_reporting() & $errno)) {
  84. // This error code is not included in error_reporting
  85. return;
  86. }
  87. $this->loadResponseIfNeeded();
  88. $this->response->addWarning(new KurogoWarning($errno, $str, $file, $line));
  89. return true;
  90. }
  91. /**
  92. * Set the command
  93. * @param string the command
  94. */
  95. protected function setCommand($command) {
  96. $this->command = $command;
  97. }
  98. /**
  99. * The module is disabled.
  100. */
  101. protected function moduleDisabled() {
  102. header("HTTP/1.1 500 Internal Server Error");
  103. $error = new KurogoError(2, 'Module Disabled', 'This module has been disabled');
  104. $this->throwError($error);
  105. }
  106. /**
  107. * The module must be run securely (https)
  108. */
  109. protected function secureModule() {
  110. $secure_host = Kurogo::getOptionalSiteVar('SECURE_HOST', $_SERVER['SERVER_NAME']);
  111. if (empty($secure_host)) {
  112. $secure_host = $_SERVER['SERVER_NAME'];
  113. }
  114. $secure_port = Kurogo::getOptionalSiteVar('SECURE_PORT', 443);
  115. if (empty($secure_port)) {
  116. $secure_port = 443;
  117. }
  118. $redirect= sprintf("https://%s%s%s", $secure_host, $secure_port == 443 ? '': ":$secure_port", $_SERVER['REQUEST_URI']);
  119. Kurogo::log(LOG_DEBUG, "Redirecting to secure url $redirect",'module');
  120. Kurogo::redirectToURL($redirect, Kurogo::REDIRECT_PERMANENT);
  121. }
  122. protected function unsecureModule() {
  123. $host = Kurogo::getOptionalSiteVar('HOST', $_SERVER['SERVER_NAME'], 'site settings');
  124. if (empty($host)) {
  125. $host = $_SERVER['SERVER_NAME'];
  126. }
  127. $port = Kurogo::getOptionalSiteVar('PORT', 80, 'site settings');
  128. if (empty($port)) {
  129. $port = 80;
  130. }
  131. $redirect= sprintf("http://%s%s%s", $host, $port == 80 ? '': ":$port", $_SERVER['REQUEST_URI']);
  132. Kurogo::log(LOG_DEBUG, "Redirecting to non-secure url $redirect",'module');
  133. Kurogo::redirectToURL($redirect);
  134. }
  135. /**
  136. * The user cannot access this module
  137. */
  138. protected function unauthorizedAccess() {
  139. header("HTTP/1.1 403 Forbidden");
  140. $error = new KurogoError(4, 'Unauthorized', 'You are not permitted to use the '.$this->getModuleVar('title', 'module').' module');
  141. $this->throwError($error);
  142. }
  143. /**
  144. * An unrecognized command was requested
  145. */
  146. protected function invalidCommand() {
  147. header("HTTP/1.1 500 Internal Server Error");
  148. $error = new KurogoError(5, 'Invalid Command', "The $this->id module does not understand $this->command");
  149. $this->throwError($error);
  150. }
  151. /**
  152. * The module was unable to load the version requested
  153. */
  154. protected function noVersionAvailable() {
  155. header("HTTP/1.1 500 Internal Server Error");
  156. $error = new KurogoError(6, 'No version available', "A command matching the specified version could not be processed");
  157. $this->throwError($error);
  158. }
  159. /**
  160. * Sets the error portion of the response. Some messages can return both a response and an error
  161. * @param KurogoError $error an error object
  162. */
  163. protected function setResponseError(KurogoError $error) {
  164. $this->loadResponseIfNeeded();
  165. $this->response->setError($error);
  166. }
  167. /**
  168. * Set the response text. Typically an object or associate array (a dictionary)
  169. */
  170. protected function setResponse($response) {
  171. $this->loadResponseIfNeeded();
  172. $this->response->setResponse($response);
  173. }
  174. /**
  175. * Throw a fatal error in the API. Used for user created errors like invalid parameters. Stops
  176. * execution and displays the error
  177. * @param KurogoError $user a valid error object
  178. */
  179. protected function throwError(KurogoError $error) {
  180. $this->loadResponseIfNeeded();
  181. $this->setResponseError($error);
  182. if (is_null($this->responseVersion)) {
  183. $this->setResponseVersion(0);
  184. }
  185. $this->response->display();
  186. exit();
  187. }
  188. protected function redirectTo($command, $args=array()) {
  189. $url = URL_BASE . API_URL_PREFIX . "/$this->id/$command";
  190. if (count($args)) {
  191. $url .= http_build_query($args);
  192. }
  193. //error_log('Redirecting to: '.$url);
  194. Kurogo::redirectToURL($url);
  195. }
  196. /**
  197. * Factory method
  198. * @param string $id the module id to load
  199. * @param string $command the command to execute
  200. * @param array $args an array of arguments
  201. * @return APIModule
  202. */
  203. public static function factory($id, $command='', $args=array()) {
  204. if (!$module = parent::factory($id, 'api')) {
  205. return false;
  206. }
  207. if ($command) {
  208. $module->init($command, $args);
  209. }
  210. return $module;
  211. }
  212. public static function getAllModules() {
  213. $dirs = array(
  214. SITE_CONFIG_DIR,
  215. SHARED_CONFIG_DIR,
  216. );
  217. $modules = array();
  218. foreach ($dirs as $dir) {
  219. $configFiles = glob($dir . "/*/module.ini");
  220. foreach ($configFiles as $file) {
  221. if (preg_match("#" . preg_quote($dir,"#") . "/([^/]+)/module.ini$#", $file, $bits)) {
  222. $id = $bits[1];
  223. try {
  224. if ($module = APIModule::factory($id)) {
  225. $modules[$id] = $module;
  226. }
  227. } catch (KurogoException $e) {
  228. }
  229. }
  230. }
  231. }
  232. ksort($modules);
  233. return $modules;
  234. }
  235. /**
  236. * Lazy load the response object
  237. */
  238. private function loadResponseIfNeeded() {
  239. if (!isset($this->response)) {
  240. $this->response = new APIResponse($this->id, $this->configModule, $this->command);
  241. }
  242. }
  243. /**
  244. * Sets the requested version and minimum accepted version
  245. */
  246. private function setRequestedVersion($requestedVersion, $minimumVersion) {
  247. if ($requestedVersion) {
  248. $this->requestedVersion = intval($requestedVersion);
  249. if ($minimumVersion) {
  250. $this->requestedVmin = intval($minimumVersion);
  251. } else {
  252. $this->requestedVmin = $this->requestedVersion;
  253. }
  254. } else {
  255. $this->requestedVersion = null;
  256. $this->requestedVmin = null;
  257. }
  258. }
  259. /**
  260. * Called by the modules to set what version we are returning to the client_info
  261. * @param int $version the version we are returning
  262. */
  263. protected function setResponseVersion($version) {
  264. $this->loadResponseIfNeeded();
  265. $this->response->setVersion($version);
  266. $this->responseVersion = $this->response->getVersion();
  267. }
  268. protected function setContext($context) {
  269. $this->loadResponseIfNeeded();
  270. $this->context = $context;
  271. $this->response->setContext($context);
  272. }
  273. protected function setClientVersion($version) {
  274. $this->clientVersion = $version;
  275. }
  276. protected function setClient($clientString) {
  277. if ($clientString) {
  278. $parts = explode('-', $clientString);
  279. $this->clientPagetype = Kurogo::arrayVal($parts, 0);
  280. $this->clientPlatform = Kurogo::arrayVal($parts, 1);
  281. $this->clientBrowser = Kurogo::arrayVal($parts, 2);
  282. Kurogo::deviceClassifier()->setDevice($clientString);
  283. }
  284. }
  285. /**
  286. * Initialize the request
  287. */
  288. protected function init($command='', $args=array()) {
  289. set_error_handler(array($this, 'warningHandler'), E_WARNING | E_NOTICE | E_STRICT);
  290. $this->setArgs($args);
  291. $this->setRequestedVersion($this->getArg('v', null), $this->getArg('vmin', null));
  292. $this->setClientVersion($this->getArg('clientv', null));
  293. $this->setClient($this->getArg('client'));
  294. if ($context = $this->getArg('context', null)) {
  295. $this->setContext($context);
  296. }
  297. $this->setCommand($command);
  298. parent::init();
  299. }
  300. /**
  301. * Execute the command. Will call initializeForCommand() which should set the version, error and response
  302. * values appropriately
  303. */
  304. public function executeCommand() {
  305. if (empty($this->command)) {
  306. throw new KurogoException("Command not specified");
  307. }
  308. $this->loadResponseIfNeeded();
  309. if ($this->clientBrowser == 'native') {
  310. if ($appData = Kurogo::getAppData($this->clientPlatform)) {
  311. if ($minversion = Kurogo::arrayVal($appData, 'minversion')) {
  312. if (version_compare($this->clientVersion, $minversion)<0) {
  313. $data = array(
  314. 'url'=>Kurogo::arrayVal($appData, 'url'),
  315. 'version'=>Kurogo::arrayVal($appData, 'version')
  316. );
  317. $error = new KurogoError(7, 'Upgrade Required', 'You must upgrade your application');
  318. $error->setData($data);
  319. $this->throwError($error);
  320. }
  321. }
  322. }
  323. }
  324. $this->initializeForCommand();
  325. $json = $this->response->getJSONOutput();
  326. $size = strlen($json);
  327. if ($this->logView) {
  328. $this->logCommand($size);
  329. }
  330. header("Content-Length: " . $size);
  331. header("Content-Type: application/json; charset=utf-8");
  332. echo $json;
  333. exit();
  334. }
  335. protected function logCommand($size=null) {
  336. KurogoStats::logView('api', $this->configModule, $this->command, $this->logData, $this->logDataLabel, $size);
  337. }
  338. /**
  339. * All modules must implement this method to handle the logic of each command.
  340. */
  341. abstract protected function initializeForCommand();
  342. /**
  343. * Implement if your app supports the buildNative command for native app shim modules.
  344. * Return a list of pages supported by your native app shim templates.
  345. */
  346. protected function getNativePagelist() {
  347. return array();
  348. }
  349. protected function getTabsForTabKeys($tabKeys, $page) {
  350. // method similar to WebModule's enableTabs()
  351. // first checks for configured title in pages.ini for $page
  352. // if not found will simply uppercase the first letter of the key in $tabKeys
  353. $tabs = array();
  354. $section = $this->getModuleSection($page, "pages");
  355. foreach ($tabKeys as $key) {
  356. $pagesKey = "tab_".$key;
  357. if (isset($section[$pagesKey])) {
  358. // first reads tab titles if configured in pages.ini $page section
  359. $tabs[] = array(
  360. 'id' => $key,
  361. 'title' => $section[$pagesKey],
  362. );
  363. } else {
  364. // if no configured title in pages.ini, will simply uppercase the first letter in the key
  365. $tabs[] = array(
  366. 'id' => $key,
  367. 'title' => ucwords($key)
  368. );
  369. }
  370. }
  371. return $tabs;
  372. }
  373. }