PageRenderTime 26ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/application/controller/backend/ThemeController.php

http://github.com/integry/livecart
PHP | 550 lines | 432 code | 78 blank | 40 comment | 36 complexity | 1ed21f2b091ce0c14d7655a5849ba9bc MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. ClassLoader::import('application.controller.backend.abstract.StoreManagementController');
  3. ClassLoader::import('application.model.template.Theme');
  4. ClassLoader::import('application.model.template.EditedCssFile');
  5. /**
  6. * Manage design themes
  7. *
  8. * @package application.controller.backend
  9. * @author Integry Systems
  10. */
  11. class ThemeController extends StoreManagementController
  12. {
  13. // for theme copying
  14. private $fromTheme;
  15. private $toTheme;
  16. public function index()
  17. {
  18. $themes = array_merge(array('barebone' => 'barebone'), array_diff($this->application->getRenderer()->getThemeList(), array('barebone')));
  19. unset($themes['default'], $themes['default-3column'], $themes['light'], $themes['light-3column']);
  20. $response = new ActionResponse();
  21. $response->set('themes', json_encode($themes));
  22. $response->set('addForm', $this->buildForm());
  23. $response->set('maxSize', ini_get('upload_max_filesize'));
  24. $response->set('importForm', $this->buildImportForm());
  25. $response->set('copyForm', $this->buildCopyForm());
  26. return $response;
  27. }
  28. public function edit()
  29. {
  30. $theme = new Theme($this->request->get('id'), $this->application);
  31. $arr = $theme->toArray();
  32. $form = $this->buildSettingsForm();
  33. $form->setData($arr);
  34. foreach ($theme->getParentThemes() as $key => $parent)
  35. {
  36. $form->set('parent_' . ($key + 1), $parent);
  37. }
  38. $response = new ActionResponse();
  39. $response->set('theme', $arr);
  40. $response->set('form', $form);
  41. $response->set('themes', $this->application->getRenderer()->getThemeList());
  42. return $response;
  43. }
  44. public function saveSettings()
  45. {
  46. $themes = array();
  47. for ($k = 1; $k <= 3; $k++)
  48. {
  49. if ($theme = $this->request->get('parent_' . $k))
  50. {
  51. $themes[] = $theme;
  52. }
  53. }
  54. $inst = new Theme($this->request->get('id'), $this->application);
  55. $inst->setParentThemes($themes);
  56. $inst->saveConfig();
  57. return new JSONResponse(false, 'success', $this->translate('_theme_saved'));
  58. }
  59. public function add()
  60. {
  61. $inst = new Theme($this->request->get('name'), $this->application);
  62. $errors = array();
  63. $validator = $this->buildValidator();
  64. $validator->isValid();
  65. if ($inst->isExistingTheme())
  66. {
  67. $validator->triggerError('name', $this->translate('_err_theme_exists'));
  68. }
  69. if ($errors = $validator->getErrorList())
  70. {
  71. return new JSONResponse(array('errors' => $errors));
  72. }
  73. else
  74. {
  75. $inst->create();
  76. return new JSONResponse($inst->toArray(), 'success', $this->translate('_theme_created'));
  77. }
  78. }
  79. public function delete()
  80. {
  81. $inst = new Theme($this->request->get('id'), $this->application);
  82. if ($inst->isCoreTheme())
  83. {
  84. return new JSONResponse($inst->toArray(), 'failure', $this->translate('_err_cannot_delete_core_theme'));
  85. }
  86. else
  87. {
  88. $inst->delete();
  89. return new JSONResponse($inst->toArray(), 'success', $this->maketext('_theme_deleted', array($inst->getName())));
  90. }
  91. }
  92. public function colors()
  93. {
  94. $inst = new Theme($this->request->get('id'), $this->application);
  95. $response = new ActionResponse();
  96. $response->set('config', $this->getParsedStyleConfig($inst));
  97. $response->set('form', $this->buildColorsForm($inst));
  98. $response->set('measurements', $this->getSelectOptions(array('', 'auto', 'px', '%', 'em')));
  99. $response->set('borderStyles', $this->getSelectOptions(array('', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset')));
  100. $response->set('textStyles', $this->getSelectOptions(array('', 'none', 'underline')));
  101. $response->set('bgRepeat', $this->getSelectOptions(array('repeat', 'no-repeat', 'repeat-x', 'repeat-y')));
  102. $response->set('bgPosition', $this->getSelectOptions(array('left top', 'left center', 'left bottom', 'center top', 'center center', 'center bottom', 'right top', 'right center', 'right bottom')));
  103. $response->set('theme', $this->request->get('id'));
  104. return $response;
  105. }
  106. private function getSelectOptions($options)
  107. {
  108. $out = array();
  109. foreach ($options as $opt)
  110. {
  111. $out[$opt] = $this->translate($opt);
  112. }
  113. return $out;
  114. }
  115. public function saveColors()
  116. {
  117. $theme = $this->request->get('id');
  118. $css = new EditedCssFile($theme);
  119. $code = $this->request->get('css');
  120. // process uploaded files
  121. $filePath = ClassLoader::getRealPath('public.upload.theme.' . $theme . '.');
  122. if (!file_exists($filePath))
  123. {
  124. mkdir($filePath, 0777, true);
  125. chmod($filePath, 0777);
  126. }
  127. foreach ($_FILES as $var => $file)
  128. {
  129. if (!$file['name'])
  130. {
  131. continue;
  132. }
  133. $name = $var . '_' . $file['name'];
  134. move_uploaded_file($file['tmp_name'], $filePath . $name);
  135. $code = str_replace('url("' . $var . '")', 'url(\'../theme/' . $theme .'/' . $name . '\')', $code);
  136. }
  137. $code = preg_replace('/rgb\((\d+),\s*(\d+),\s*(\d+)\)/e', '"#" . str_pad(dechex(\\1), 2, "0", STR_PAD_LEFT) . str_pad(dechex(\\2), 2, "0", STR_PAD_LEFT) . str_pad(dechex(\\3), 2, "0", STR_PAD_LEFT)', $code);
  138. $css->setCode($code);
  139. $res = $css->save();
  140. return new ActionRedirectResponse('backend.theme', 'cssIframe', array('query' => array('theme' => $theme, 'saved' => true)));
  141. }
  142. public function cssIframe()
  143. {
  144. $this->setLayout('empty');
  145. $theme = $this->request->get('theme');
  146. $css = new EditedCssFile($theme);
  147. if (!$css->getCode())
  148. {
  149. $css->setCode(' ');
  150. $css->save();
  151. }
  152. $response = new ActionResponse();
  153. $response->set('theme', $theme);
  154. $response->set('file', $css->getFileName());
  155. return $response;
  156. }
  157. private function getParsedStyleConfig(Theme $theme)
  158. {
  159. $themeName = $theme->getName();
  160. $conf = array();
  161. foreach ($theme->getStyleConfig() as $name => $sectionData)
  162. {
  163. $section = array();
  164. $open = true;
  165. if ('-' == $name[0])
  166. {
  167. $open = false;
  168. $name = substr($name, 1);
  169. }
  170. $section['name'] = $this->translate($name);
  171. $section['open'] = $open;
  172. $properties = array();
  173. foreach ($sectionData as $name => $value)
  174. {
  175. $property = array('var' => $name, 'name' => $this->translate($name), 'id' => $themeName . '_' . $name);
  176. $parts = explode(' _ ', $value);
  177. $property['type'] = array_shift($parts);
  178. $property['selector'] = array_shift($parts);
  179. $property['append'] = str_replace('__', ';', array_shift($parts));
  180. if ($property['append'])
  181. {
  182. // determines whether the auto-append properties need to be set
  183. $property['append'] .= '; richness: 100;';
  184. }
  185. $properties[] = $property;
  186. }
  187. $section['properties'] = $properties;
  188. $conf[] = $section;
  189. }
  190. return $conf;
  191. }
  192. public function copyTheme()
  193. {
  194. $res = $this->doCopyTheme();
  195. return new JSONResponse
  196. (
  197. array_key_exists('id', $res) ? array('id' => $res['id']) : null,
  198. array_key_exists('status', $res) ? $res['status'] : 'failure',
  199. array_key_exists('message', $res) ? $res['message'] : null
  200. );
  201. }
  202. private function doCopyTheme()
  203. {
  204. ClassLoader::importNow('application.helper.CopyRecursive');
  205. $request = $this->getRequest();
  206. $this->fromTheme = $request->get('id');
  207. $this->toTheme = $request->get('name');
  208. $files = $this->getThemeFiles($this->fromTheme);
  209. $copyFiles = $this->getThemeFiles($this->toTheme, false);
  210. $baseDir = ClassLoader::getBaseDir();
  211. foreach ($files as $key => $orginalFileName)
  212. {
  213. if (array_key_exists($key, $copyFiles))
  214. {
  215. $copyToFileName = $copyFiles[$key];
  216. }
  217. else if (preg_match('/public.?upload.?css.?delete/',$orginalFileName))
  218. {
  219. // orginal theme files matching glob('public/upload/css/delete/<theme>-*.php')
  220. // get copyTo file name by replacing
  221. $copyToFileName = str_replace('public/upload/css/delete/'.$this->fromTheme.'-', 'public/upload/css/delete/'.$this->toTheme.'-', $orginalFileName);
  222. $copyFiles[] = $copyToFileName;
  223. }
  224. else
  225. {
  226. continue; // only if new type of files added in themes
  227. }
  228. copyRecursive($baseDir.DIRECTORY_SEPARATOR.$orginalFileName,
  229. $baseDir.DIRECTORY_SEPARATOR.$copyToFileName, array($this, 'onThemeFileCopied'));
  230. }
  231. return array('status'=>'success', 'id'=>$this->toTheme,
  232. 'message'=>$this->maketext('_theme_copied', array($this->fromTheme, $this->toTheme)));
  233. }
  234. public function onThemeFileCopied($file)
  235. {
  236. if (preg_match('/\.(tpl|css)$/i',$file))
  237. {
  238. file_put_contents($file,
  239. str_replace(
  240. '/'.$this->fromTheme.'/',
  241. '/'.$this->toTheme.'/',
  242. file_get_contents($file)
  243. )
  244. );
  245. }
  246. }
  247. public function import()
  248. {
  249. $this->setLayout('iframeJs');
  250. $response = new ActionResponse();
  251. $res = $this->doImport();
  252. foreach($res as $key=>$value)
  253. {
  254. $response->set($key, $value);
  255. }
  256. return $response;
  257. }
  258. private function doImport()
  259. {
  260. require_once(ClassLoader::getRealPath('library.pclzip') . '/pclzip.lib.php');
  261. $request = $this->getRequest();
  262. $validator = $this->buildImportValidator($request);
  263. if($validator->isValid() == false)
  264. {
  265. return array('status'=>'failure');
  266. }
  267. $file = $_FILES['theme'];
  268. if (!$file['name'] || $file['error'] != 0)
  269. {
  270. return array('status'=>'failure');
  271. }
  272. do
  273. {
  274. $path = ClassLoader::getRealPath('cache.tmp.theme_import_' . rand(1, 10000000));
  275. } while(is_dir($path));
  276. mkdir($path, 0777, true);
  277. $zipFilePath = $path.'_archive.zip';
  278. move_uploaded_file($file['tmp_name'], $zipFilePath);
  279. $archive = new PclZip($zipFilePath);
  280. $archive->extract($path);
  281. if (file_exists($path.DIRECTORY_SEPARATOR.'theme.conf') == false)
  282. {
  283. $this->clearImportFiles($path);
  284. return array('status'=>'failure');
  285. }
  286. $ini = parse_ini_file($path.DIRECTORY_SEPARATOR.'theme.conf', true);
  287. $id = trim($ini['Theme']['name']);
  288. if (preg_match('/^[\_\-a-zA-Z0-9]{1,}$/', $id) == false) // todo: how to reuse validator for theme name?
  289. {
  290. return array('status'=>'failure');
  291. }
  292. $files = array_merge(
  293. array(
  294. $path . DIRECTORY_SEPARATOR. 'public'.DIRECTORY_SEPARATOR.'upload'.DIRECTORY_SEPARATOR.'theme'.DIRECTORY_SEPARATOR.$id,
  295. $path . DIRECTORY_SEPARATOR. 'public'.DIRECTORY_SEPARATOR.'upload'.DIRECTORY_SEPARATOR.'css'.DIRECTORY_SEPARATOR.$id.'.css',
  296. $path . DIRECTORY_SEPARATOR. 'storage'.DIRECTORY_SEPARATOR.'customize'.DIRECTORY_SEPARATOR.'view'.DIRECTORY_SEPARATOR.'theme'.DIRECTORY_SEPARATOR.$id
  297. ),
  298. glob($path . DIRECTORY_SEPARATOR.'public'.DIRECTORY_SEPARATOR.'upload'.DIRECTORY_SEPARATOR.'css'.DIRECTORY_SEPARATOR.'delete'.DIRECTORY_SEPARATOR.$id.'-*.php')
  299. );
  300. //print_r($files);
  301. $baseDir = ClassLoader::getBaseDir();
  302. $len = strlen($path.DIRECTORY_SEPARATOR);
  303. foreach ($files as $fn)
  304. {
  305. if(file_exists($fn))
  306. {
  307. $fn = substr($fn, $len);
  308. $to = $baseDir.DIRECTORY_SEPARATOR.$fn;
  309. if (is_dir($to))
  310. {
  311. $this->application->rmdir_recurse($to);
  312. }
  313. else if (file_exists($to))
  314. {
  315. unlink($to);
  316. }
  317. if (!file_exists(dirname($to)))
  318. {
  319. mkdir(dirname($to), 0777, true);
  320. }
  321. rename($path.DIRECTORY_SEPARATOR.$fn, $to);
  322. }
  323. }
  324. $this->clearImportFiles($path);
  325. return array('id'=>$id, 'status'=>'success');
  326. }
  327. private function clearImportFiles($path)
  328. {
  329. $this->application->rmdir_recurse($path);
  330. unlink($path.'_archive.zip');
  331. }
  332. private function getThemeFiles($id, $onlyExistingFiles=true)
  333. {
  334. if (strlen($id) == 0)
  335. {
  336. return null;
  337. }
  338. $files = array_merge(
  339. array(
  340. 'A' => ClassLoader::getRealPath('public.upload.theme.'.$id),
  341. 'B' => ClassLoader::getRealPath('public.upload.css.'.$id).'.css',
  342. 'C' => ClassLoader::getRealPath('storage.customize.view.theme.'.$id)
  343. ),
  344. glob(ClassLoader::getRealPath('public.upload.css.delete.').$id.'-*.php')
  345. );
  346. // Make paths relative
  347. // ClassLoader::getRealPath(<p.a.t.h>) returns <base dir> + <path>
  348. // $len is required to chop off <base dir> part
  349. $len = strlen(ClassLoader::getBaseDir());
  350. if ($onlyExistingFiles)
  351. {
  352. foreach ($files as &$fn)
  353. {
  354. $fn = file_exists($fn) ? substr($fn, $len) : null;
  355. }
  356. $files = array_filter($files);
  357. if (count($files) == 0)
  358. {
  359. return null;
  360. }
  361. }
  362. else
  363. {
  364. foreach ($files as &$fn)
  365. {
  366. $fn = substr($fn, $len);
  367. }
  368. }
  369. return $files;
  370. }
  371. public function export()
  372. {
  373. require_once(ClassLoader::getRealPath('library.pclzip') . '/pclzip.lib.php');
  374. $id = $this->getRequest()->get('id');
  375. $files = $this->getThemeFiles($id);
  376. if ($files === null)
  377. {
  378. return new ActionRedirectResponse('backend.theme', 'index');
  379. }
  380. do
  381. {
  382. $path = ClassLoader::getRealPath('cache.tmp.theme_export_' . rand(1, 10000000));
  383. } while(file_exists($path));
  384. $zipFilePath = $path.'_archive.zip';
  385. $confFilePath = $path.DIRECTORY_SEPARATOR .'theme.conf';
  386. foreach(array($zipFilePath, $confFilePath) as $fp)
  387. {
  388. if (!is_dir(dirname($fp)))
  389. {
  390. mkdir(dirname($fp), 0777, true);
  391. }
  392. }
  393. file_put_contents($confFilePath, sprintf(
  394. '[Theme]'."\n".
  395. 'name = %s', $id)
  396. );
  397. $archive = new PclZip($zipFilePath);
  398. chdir(ClassLoader::getBaseDir());
  399. $files[] = $confFilePath;
  400. $archive->add($files, PCLZIP_OPT_REMOVE_PATH, $path);
  401. $this->application->rmdir_recurse($path);
  402. $response = new ObjectFileResponse(ObjectFile::getNewInstance('ObjectFile', $zipFilePath, $id.'.zip'));
  403. $response->deleteFileOnComplete();
  404. return $response;
  405. }
  406. /**
  407. * @return RequestValidator
  408. */
  409. private function buildValidator()
  410. {
  411. $validator = $this->getValidator("theme", $this->request);
  412. $validator->addCheck("name", new IsNotEmptyCheck($this->translate('_err_theme_name_empty')));
  413. $validator->addFilter("name", new RegexFilter('[^_-a-zA-Z0-9]'));
  414. return $validator;
  415. }
  416. /**
  417. * @return Form
  418. */
  419. private function buildColorsForm(Theme $theme)
  420. {
  421. return new Form($this->buildColorsValidator($theme));
  422. }
  423. /**
  424. * @return RequestValidator
  425. */
  426. private function buildColorsValidator($theme)
  427. {
  428. $validator = $this->getValidator("themeColors", $this->request);
  429. return $validator;
  430. }
  431. /**
  432. * @return Form
  433. */
  434. private function buildForm()
  435. {
  436. return new Form($this->buildValidator());
  437. }
  438. private function buildSettingsForm()
  439. {
  440. return new Form($this->getValidator("foo", $this->request));
  441. }
  442. /**
  443. * Builds an theme import form validator
  444. *
  445. * @return RequestValidator
  446. */
  447. protected function buildImportValidator()
  448. {
  449. $validator = $this->getValidator('themeImportValidator', $this->request);
  450. $uploadCheck = new IsFileUploadedCheck($this->translate(!empty($_FILES['theme']['name']) ? '_err_too_large' :'_err_not_uploaded'));
  451. $uploadCheck->setFieldName('theme');
  452. $validator->addCheck('theme', $uploadCheck);
  453. return $validator;
  454. }
  455. /**
  456. * Builds a import theme form instance
  457. *
  458. * @return Form
  459. */
  460. protected function buildImportForm()
  461. {
  462. return new Form($this->buildImportValidator());
  463. }
  464. /**
  465. * @return Form
  466. */
  467. protected function buildCopyForm()
  468. {
  469. return new Form($this->buildValidator()); // copy and add actions use the same validator
  470. }
  471. }
  472. ?>