PageRenderTime 28ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/recess/recess/apps/tools/controllers/RecessToolsAppsController.class.php

http://github.com/recess/recess
PHP | 452 lines | 374 code | 56 blank | 22 comment | 58 complexity | 40dba2e0270edd347356bd59a7776082 MD5 | raw file
Possible License(s): MIT, GPL-2.0
  1. <?php
  2. Library::import('recess.framework.controllers.Controller');
  3. Library::import('recess.database.pdo.RecessType');
  4. /**
  5. * !RespondsWith Layouts, Json
  6. * !Prefix apps/
  7. */
  8. class RecessToolsAppsController extends Controller {
  9. public function init() {
  10. if(RecessConf::$mode == RecessConf::PRODUCTION) {
  11. throw new RecessResponseException('Recess Tools are available only during development. Please disable the application in a production environment.', ResponseCodes::HTTP_NOT_FOUND, array());
  12. }
  13. }
  14. /** !Route GET */
  15. public function home() {
  16. $this->apps = RecessConf::$applications;
  17. if(isset($this->request->get->flash)) {
  18. $this->flash = $this->request->get['flash'];
  19. }
  20. }
  21. /** !Route GET, uninstall/$appClass */
  22. public function uninstall($appClass) {
  23. //Library::getFullyQualifiedClassName($appClass);
  24. $this->app = new $appClass;
  25. }
  26. /** !Route GET, new */
  27. public function newApp() {
  28. $writeable = is_writable($_ENV['dir.apps']);
  29. $this->appsDirWriteable = $writeable;
  30. if($this->appsDirWriteable) {
  31. $this->form = $this->getNewAppForm();
  32. return $this->ok('newAppWizard');
  33. } else {
  34. return $this->ok('newAppInstructions');
  35. }
  36. }
  37. /** !Route POST, new */
  38. public function newAppPost() {
  39. $form = $this->getNewAppForm($this->request->post);
  40. $form->assertNotEmpty('appName');
  41. $form->assertNotEmpty('programmaticName');
  42. if($form->hasErrors()) {
  43. $this->form = $form;
  44. return $this->conflict('newAppWizard');
  45. } else {
  46. Library::import('recess.lang.Inflector');
  47. $this->form = $this->getNewAppStep2Form($this->request->post);
  48. $this->form->routingPrefix->setValue(Inflector::toCamelCaps($this->form->programmaticName->getValue()) . '/');
  49. return $this->ok('newAppWizardStep2');
  50. }
  51. }
  52. /** !Route POST, new/step2 */
  53. function newAppStep2 () {
  54. $form = $this->getNewAppStep2Form($this->request->post);
  55. $this->generateApp();
  56. return $this->ok('newAppWizardComplete');
  57. }
  58. private function generateApp() {
  59. Library::import('recess.lang.Inflector');
  60. $appName = $this->request->post['appName'];
  61. $programmaticName = Inflector::toProperCaps($this->request->post['programmaticName']);
  62. $camelProgrammaticName = Inflector::toCamelCaps($programmaticName);
  63. $this->applicationClass = $programmaticName . 'Application';
  64. $this->applicationFullClass = $camelProgrammaticName . '.' . $this->applicationClass;
  65. $this->appName = $appName;
  66. $routesPrefix = $this->request->post['routingPrefix'];
  67. if(substr($routesPrefix,-1) != '/') { $routesPrefix .= '/'; }
  68. if($routesPrefix{0} === '/') { $routesPrefix = substr($routesPrefix,1); }
  69. $appDir = $_ENV['dir.apps'] . $camelProgrammaticName;
  70. $this->messages = array();
  71. $this->messages[] = $this->tryCreatingDirectory($appDir, 'application');
  72. $appReplacements = array('appName' => $appName, 'programmaticName' => $programmaticName, 'camelProgrammaticName' => $camelProgrammaticName, 'routesPrefix' => $routesPrefix);
  73. $this->messages[] = $this->tryGeneratingFile('Application Class', $this->application->codeTemplatesDir . 'Application.template.php', $appDir . '/' . $programmaticName . 'Application.class.php', $appReplacements);
  74. $this->messages[] = $this->tryCreatingDirectory($appDir . '/models', 'models');
  75. $this->messages[] = $this->tryCreatingDirectory($appDir . '/controllers', 'controllers');
  76. $this->messages[] = $this->tryGeneratingFile('Home Controller', $this->application->codeTemplatesDir . 'scaffolding/controllers/HomeController.template.php', $appDir . '/controllers/' . $programmaticName . 'HomeController.class.php', $appReplacements);
  77. $this->messages[] = $this->tryCreatingDirectory($appDir . '/views', 'views');
  78. $this->messages[] = $this->tryCreatingDirectory($appDir . '/views/parts', 'common parts');
  79. $this->messages[] = $this->tryGeneratingFile('Navigation Part', $this->application->codeTemplatesDir . 'scaffolding/views/parts/navigation.part.template.php', $appDir . '/views/parts/navigation.part.php', $appReplacements);
  80. $this->messages[] = $this->tryGeneratingFile('Style Part', $this->application->codeTemplatesDir . 'scaffolding/views/parts/style.part.template.php', $appDir . '/views/parts/style.part.php', $appReplacements);
  81. $this->messages[] = $this->tryCreatingDirectory($appDir . '/views/home', 'home views');
  82. $this->messages[] = $this->tryCreatingDirectory($appDir . '/views/layouts', 'layouts');
  83. $this->messages[] = $this->tryGeneratingFile('Home Template', $this->application->codeTemplatesDir . 'scaffolding/views/home/index.template.php', $appDir . '/views/home/index.html.php', $appReplacements);
  84. $this->messages[] = $this->tryGeneratingFile('Master Layout', $this->application->codeTemplatesDir . 'scaffolding/views/master.layout.template.php', $appDir . '/views/layouts/master.layout.php', $appReplacements);
  85. $scaffolding_dir = $this->application->codeTemplatesDir . 'scaffolding';
  86. $this->messages[] = $this->tryCopyDirectory($scaffolding_dir . '/public', $appDir . '/public');
  87. }
  88. private function tryCreatingDirectory($path, $name) {
  89. $message = '';
  90. try {
  91. $message = 'Creating ' . $name . ' dir "' . $path . '" ... ';
  92. mkdir($path);
  93. $message .= 'ok.';
  94. } catch (Exception $e) {
  95. if(file_exists($path)) $message .= ' already exists.';
  96. else $message .= 'failed.';
  97. }
  98. return $message;
  99. }
  100. /**
  101. * Copy all files and directories (recursive) to another directory
  102. */
  103. private function tryCopyDirectory($src, $dst) {
  104. $message = '';
  105. try {
  106. $message = 'Copying ' . $src . ' dir to ' . $dst . ' ... ';
  107. $dir = opendir($src);
  108. mkdir($dst);
  109. while(false !== ( $file = readdir($dir))) {
  110. if (( $file != '.' ) && ( $file != '..' )) {
  111. if ( is_dir($src . '/' . $file) ) {
  112. self::tryCopyDirectory($src . '/' . $file,$dst . '/' . $file);
  113. } else {
  114. copy($src . '/' . $file,$dst . '/' . $file);
  115. }
  116. }
  117. }
  118. closedir($dir);
  119. } catch (Exception $e) {
  120. if(file_exists($dst)) $message .= ' already exists.';
  121. else if(!is_dir($src)) $message .= ', source directory does not exist.';
  122. else $message .= 'failed.';
  123. }
  124. return $message;
  125. }
  126. private function tryGeneratingFile($name, $template, $outputFile, $values, $allowSlashes = false) {
  127. $templateContents = file_get_contents($template);
  128. $search = array_keys($values);
  129. foreach($search as $key => $value) {
  130. $search[$key] = '/\{\{' . $value . '\}\}/';
  131. }
  132. $replace = array_values($values);
  133. foreach($replace as $key => $value) {
  134. if(!$allowSlashes) {
  135. $value = addSlashes($value);
  136. }
  137. $replace[$key] = $value;
  138. }
  139. $output = preg_replace($search,$replace,$templateContents);
  140. $message = '';
  141. try {
  142. $message = 'Generating ' . $name . ' at "' . $outputFile . '" ... ';
  143. if(file_exists($outputFile)) {
  144. throw new Exception('file exists');
  145. }
  146. file_put_contents($outputFile, $output);
  147. $message .= 'ok.';
  148. } catch(Exception $e) {
  149. if(file_exists($outputFile)) $message .= ' already exists. Not overwriting.';
  150. else $message .= 'failed.';
  151. }
  152. return $message;
  153. }
  154. private function getNewAppForm($fillValues = array()) {
  155. Library::import('recess.framework.forms.Form');
  156. $form = new Form('');
  157. $form->method = "POST";
  158. $form->flash = "";
  159. $form->action = $this->urlTo('newApp');
  160. $form->inputs['appName'] = new TextInput('appName', '', '','');
  161. $form->inputs['programmaticName'] = new TextInput('programmaticName', '', '','');
  162. $form->fill($fillValues);
  163. return $form;
  164. }
  165. private function getNewAppStep2Form($fillValues = array()) {
  166. Library::import('recess.framework.forms.Form');
  167. $form = new Form('');
  168. $form->method = "POST";
  169. $form->flash = "";
  170. $form->action = $this->urlTo('newAppStep2');
  171. $form->inputs['appName'] = new HiddenInput('appName', '');
  172. $form->inputs['programmaticName'] = new HiddenInput('programmaticName', '');
  173. $form->inputs['routingPrefix'] = new TextInput('routingPrefix', '','','');
  174. $form->fill($fillValues);
  175. return $form;
  176. }
  177. /** !Route GET, $appClass */
  178. public function app($appClass) {
  179. $application = $this->getApplication($appClass);
  180. if(!$application instanceof Application) {
  181. return $application; // App not found
  182. }
  183. $this->app = $application;
  184. }
  185. /** !Route GET, app/$app/model/gen */
  186. public function createModel($app) {
  187. $this->sources = Databases::getSources();
  188. $this->tables = Databases::getDefaultSource()->getTables();
  189. $this->app = $app;
  190. }
  191. /** !Route POST, app/$app/model/gen */
  192. public function generateModel($app) {
  193. $values = $this->request->post;
  194. $modelName = $values['modelName'];
  195. $tableExists = $values['tableExists'] == 'yes' ? true : false;
  196. if($tableExists) {
  197. $dataSource = $values['existingDataSource'];
  198. $createTable = false;
  199. $tableName = $values['existingTableName'];
  200. } else {
  201. $dataSource = $values['dataSource'];
  202. $createTable = $values['createTable'] == 'Yes' ? true : false;
  203. $tableName = $values['tableName'];
  204. }
  205. $propertyNames = $values['fields'];
  206. $primaryKey = $values['primaryKey'];
  207. $types = $values['types'];
  208. Library::import('recess.database.orm.Model', true);
  209. // Forcing b/c ModelDescriptor is in Model
  210. $modelDescriptor = new ModelDescriptor($modelName, false);
  211. $modelDescriptor->setSource($dataSource);
  212. $modelDescriptor->setTable($tableName, false);
  213. $pkFound = false;
  214. foreach($propertyNames as $i => $name) {
  215. if($name == "") continue;
  216. $property = new ModelProperty();
  217. $property->name = trim($name);
  218. if($name == $primaryKey) {
  219. $property->isPrimaryKey = true;
  220. }
  221. if($types[$i] == 'Integer Autoincrement') {
  222. if($property->isPrimaryKey) {
  223. $property->type = RecessType::INTEGER;
  224. $property->isAutoIncrement = true;
  225. } else {
  226. $property->type = RecessType::INTEGER;
  227. }
  228. } else {
  229. $property->type = $types[$i];
  230. }
  231. $modelDescriptor->properties[] = $property;
  232. }
  233. Library::import('recess.database.orm.ModelGen');
  234. $this->modelCode = ModelGen::toCode($modelDescriptor, $_ENV['dir.temp'] . 'Model.class.php');
  235. $app = new $app;
  236. if(strpos($app->modelsPrefix,'recess.apps.') !== false) {
  237. $base = $_ENV['dir.recess'];
  238. } else {
  239. $base = $_ENV['dir.apps'];
  240. }
  241. $path = $base . str_replace(Library::dotSeparator,Library::pathSeparator,$app->modelsPrefix);
  242. $path .= $modelName . '.class.php';
  243. $this->path = $path;
  244. $this->modelWasSaved = false;
  245. $this->codeGenMessage = '';
  246. try {
  247. if(file_exists($this->path)) {
  248. if(file_get_contents($this->path) == $this->modelCode) {
  249. $this->modelWasSaved = true;
  250. } else {
  251. $this->codeGenMessage = 'File already exists!';
  252. }
  253. } else {
  254. file_put_contents($this->path, $this->modelCode);
  255. $this->modelWasSaved = true;
  256. }
  257. } catch(Exception $e) {
  258. $this->codeGenMessage = 'File could not be saved. Is models directory writeable?';
  259. $this->modelWasSaved = false;
  260. }
  261. $this->modelName = $modelName;
  262. $this->appName = get_class($app);
  263. $this->tableGenAttempted = $createTable;
  264. $this->tableWasCreated = false;
  265. $this->tableSql = '';
  266. if($createTable) {
  267. $modelSource = Databases::getSource($dataSource);
  268. $this->tableSql = $modelSource->createTableSql($modelDescriptor);
  269. try {
  270. $modelSource->exec($this->tableSql);
  271. $this->tableWasCreated = true;
  272. } catch(Exception $e) {
  273. $this->tableWasCreated = false;
  274. }
  275. }
  276. return $this->ok('createModelComplete');
  277. }
  278. /** !Route GET, $app/model/$model/scaffolding */
  279. public function generateScaffolding($app, $model) {
  280. $app = new $app;
  281. if(strpos($app->controllersPrefix,'recess.apps.') !== false) {
  282. $base = $_ENV['dir.recess'];
  283. } else {
  284. $base = $_ENV['dir.apps'];
  285. }
  286. Library::import('recess.lang.Inflector');
  287. $controllersDir = $base . str_replace(Library::dotSeparator,Library::pathSeparator,$app->controllersPrefix);
  288. $viewsDir = $app->viewsDir;
  289. Library::import($app->modelsPrefix . $model);
  290. $replacements =
  291. array( 'modelName' => $model,
  292. 'modelNameLower' => Inflector::toCamelCaps($model),
  293. 'fullyQualifiedModel' => $app->modelsPrefix . $model,
  294. 'primaryKey' => Model::primaryKeyName($model),
  295. 'viewsPrefix' => Inflector::toCamelCaps($model),
  296. 'routesPrefix' => Inflector::toCamelCaps($model),);
  297. $this->messages[] = $this->tryGeneratingFile('RESTful ' . $model . ' Controller', $this->application->codeTemplatesDir . 'scaffolding/controllers/ResourceController.template.php', $controllersDir . $model . 'Controller.class.php', $replacements);
  298. $indexFieldTemplate = $this->getTemplate($this->application->codeTemplatesDir . 'scaffolding/views/resource/indexField.template.php');
  299. $indexDateFieldTemplate = $this->getTemplate($this->application->codeTemplatesDir . 'scaffolding/views/resource/indexDateField.template.php');
  300. $editFormInputTemplate = $this->getTemplate($this->application->codeTemplatesDir . 'scaffolding/views/resource/editFormInput.template.php');
  301. $indexFields = '';
  302. $formFields = '';
  303. foreach(Model::getProperties($model) as $property) {
  304. if($property->isPrimaryKey) continue;
  305. $values = array(
  306. 'fieldName' => $property->name,
  307. 'primaryKey' => Model::primaryKeyName($model),
  308. 'modelName' => $model,
  309. 'modelNameLower' => Inflector::toCamelCaps($model),
  310. 'fieldNameEnglish' => Inflector::toEnglish($property->name) );
  311. switch($property->type) {
  312. case RecessType::DATE:
  313. case RecessType::DATETIME:
  314. case RecessType::TIME:
  315. case RecessType::TIMESTAMP:
  316. $template = $indexDateFieldTemplate;
  317. break;
  318. default:
  319. $template = $indexFieldTemplate;
  320. break;
  321. }
  322. $formFields .= $this->fillTemplate($editFormInputTemplate, $values);
  323. $indexFields .= $this->fillTemplate($template, $values);
  324. }
  325. $replacements['fields'] = $indexFields;
  326. $replacements['editFields'] = $formFields;
  327. $viewsDir = $app->viewsDir . $replacements['viewsPrefix'] . '/';
  328. $this->messages[] = $this->tryCreatingDirectory($viewsDir, $model . ' views dir');
  329. $this->messages[] = $this->tryGeneratingFile('resource layout', $this->application->codeTemplatesDir . 'scaffolding/views/resource/resource.layout.template.php', $viewsDir . '../layouts/' . $replacements['viewsPrefix'] . '.layout.php', $replacements);
  330. $this->messages[] = $this->tryGeneratingFile('index view', $this->application->codeTemplatesDir . 'scaffolding/views/resource/index.template.php', $viewsDir . 'index.html.php', $replacements);
  331. $this->messages[] = $this->tryGeneratingFile('editForm view', $this->application->codeTemplatesDir . 'scaffolding/views/resource/editForm.template.php', $viewsDir . 'editForm.html.php', $replacements, true);
  332. $this->messages[] = $this->tryGeneratingFile('form part', $this->application->codeTemplatesDir . 'scaffolding/views/resource/form.part.template.php', $viewsDir . 'form.part.php', $replacements, true);
  333. $this->messages[] = $this->tryGeneratingFile('static details', $this->application->codeTemplatesDir . 'scaffolding/views/resource/details.template.php', $viewsDir . 'details.html.php', $replacements);
  334. $this->messages[] = $this->tryGeneratingFile('details part', $this->application->codeTemplatesDir . 'scaffolding/views/resource/details.part.template.php', $viewsDir . 'details.part.php', $replacements);
  335. $this->appName = get_class($app);
  336. $this->modelName = $model;
  337. }
  338. protected function getTemplate($templateFile) {
  339. try {
  340. return file_get_contents($templateFile);
  341. } catch (Exception $e) {
  342. return '';
  343. }
  344. }
  345. protected function fillTemplate($template, $values) {
  346. $search = array_keys($values);
  347. foreach($search as $key => $value) {
  348. $search[$key] = '/\{\{' . $value . '\}\}/';
  349. }
  350. $replace = array_values($values);
  351. foreach($replace as $key => $value) {
  352. $replace[$key] = addslashes($value);
  353. }
  354. return preg_replace($search,$replace,$template);
  355. }
  356. /** !Route GET, model/gen/analyzeModelName/$modelName */
  357. public function analyzeModelName($modelName) {
  358. Library::import('recess.lang.Inflector');
  359. $this->tableName = Inflector::toPlural(Inflector::toUnderscores($modelName));
  360. $this->isValid = preg_match('/^[a-zA-Z][_a-zA-z0-9]*$/', $modelName) == 1;
  361. }
  362. /** !Route GET, model/gen/getTables/$sourceName */
  363. public function getTables($sourceName) {
  364. $this->tables = Databases::getSource($sourceName)->getTables();
  365. }
  366. /** !Route GET, model/gen/getTableProps/$sourceName/$tableName */
  367. public function getTableProps($sourceName, $tableName) {
  368. $source = Databases::getSource($sourceName);
  369. if($source == null) {
  370. return $this->redirect($this->urlTo('home'));
  371. } else {
  372. $this->source = $source;
  373. }
  374. $this->sourceName = $sourceName;
  375. $this->table = $tableName;
  376. $this->columns = $this->source->getTableDescriptor($tableName)->getColumns();
  377. }
  378. /** !Route GET, $app/controller/gen */
  379. public function createController($app) {
  380. $application = $this->getApplication($app);
  381. if(!$application instanceof Application) {
  382. return $application; // App not found
  383. }
  384. $this->app = $application;
  385. return $this->ok('genController');
  386. }
  387. private function getApplication($appClass) {
  388. foreach(RecessConf::$applications as $app) {
  389. if(get_class($app) == $appClass) {
  390. return $app;
  391. }
  392. }
  393. return $this->forwardNotFound($this->urlTo('home'), 'Application ' . $appClass . ' does not exist or is not enabled.');
  394. }
  395. }
  396. ?>