PageRenderTime 26ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/cms/controllers/Index.php

https://gitlab.com/tonycodes/october
PHP | 458 lines | 336 code | 89 blank | 33 comment | 52 complexity | ff33fb412a9b77022c55e67369ff3939 MD5 | raw file
  1. <?php namespace Cms\Controllers;
  2. use URL;
  3. use Lang;
  4. use Flash;
  5. use Event;
  6. use Config;
  7. use Request;
  8. use Response;
  9. use Exception;
  10. use BackendMenu;
  11. use Backend\Classes\Controller;
  12. use Backend\Classes\WidgetManager;
  13. use Cms\Widgets\AssetList;
  14. use Cms\Widgets\TemplateList;
  15. use Cms\Widgets\ComponentList;
  16. use Cms\Classes\Page;
  17. use Cms\Classes\Theme;
  18. use Cms\Classes\Router;
  19. use Cms\Classes\Layout;
  20. use Cms\Classes\Partial;
  21. use Cms\Classes\Content;
  22. use Cms\Classes\CmsCompoundObject;
  23. use Cms\Classes\ComponentManager;
  24. use Cms\Classes\ComponentPartial;
  25. use System\Classes\ApplicationException;
  26. use Backend\Traits\InspectableContainer;
  27. use October\Rain\Router\Router as RainRouter;
  28. /**
  29. * CMS index
  30. *
  31. * @package october\cms
  32. * @author Alexey Bobkov, Samuel Georges
  33. */
  34. class Index extends Controller
  35. {
  36. use InspectableContainer;
  37. protected $theme;
  38. public $requiredPermissions = ['cms.*'];
  39. /**
  40. * Constructor.
  41. */
  42. public function __construct()
  43. {
  44. parent::__construct();
  45. BackendMenu::setContext('October.Cms', 'cms', true);
  46. try {
  47. if (!($theme = Theme::getEditTheme())) {
  48. throw new ApplicationException(Lang::get('cms::lang.theme.edit.not_found'));
  49. }
  50. $this->theme = $theme;
  51. new TemplateList($this, 'pageList', function () use ($theme) {
  52. return Page::listInTheme($theme, true);
  53. });
  54. new TemplateList($this, 'partialList', function () use ($theme) {
  55. return Partial::listInTheme($theme, true);
  56. });
  57. new TemplateList($this, 'layoutList', function () use ($theme) {
  58. return Layout::listInTheme($theme, true);
  59. });
  60. new TemplateList($this, 'contentList', function () use ($theme) {
  61. return Content::listInTheme($theme, true);
  62. });
  63. new ComponentList($this, 'componentList');
  64. new AssetList($this, 'assetList');
  65. }
  66. catch (Exception $ex) {
  67. $this->handleError($ex);
  68. }
  69. }
  70. //
  71. // Pages
  72. //
  73. public function index()
  74. {
  75. $this->addJs('/modules/cms/assets/js/october.cmspage.js', 'core');
  76. $this->addJs('/modules/cms/assets/js/october.dragcomponents.js', 'core');
  77. $this->addJs('/modules/cms/assets/js/october.tokenexpander.js', 'core');
  78. $this->addJs('/modules/backend/formwidgets/codeeditor/assets/js/codeeditor.js', 'core');
  79. $this->addCss('/modules/cms/assets/css/october.components.css', 'core');
  80. // Preload Ace editor modes explicitly, because they could be changed dynamically
  81. // depending on a content block type
  82. $this->addJs('/modules/backend/formwidgets/codeeditor/assets/vendor/ace/ace.js', 'core');
  83. $aceModes = ['markdown', 'plain_text', 'html', 'less', 'css', 'scss', 'sass', 'javascript'];
  84. foreach ($aceModes as $mode) {
  85. $this->addJs('/modules/backend/formwidgets/codeeditor/assets/vendor/ace/mode-'.$mode.'.js', 'core');
  86. }
  87. $this->bodyClass = 'compact-container side-panel-not-fixed';
  88. $this->pageTitle = 'cms::lang.cms.menu_label';
  89. $this->pageTitleTemplate = '%s CMS';
  90. }
  91. public function index_onOpenTemplate()
  92. {
  93. $this->validateRequestTheme();
  94. $type = Request::input('type');
  95. $template = $this->loadTemplate($type, Request::input('path'));
  96. $widget = $this->makeTemplateFormWidget($type, $template);
  97. $this->vars['templatePath'] = Request::input('path');
  98. if ($type == 'page') {
  99. $router = new RainRouter;
  100. $this->vars['pageUrl'] = $router->urlFromPattern($template->url);
  101. }
  102. return [
  103. 'tabTitle' => $this->getTabTitle($type, $template),
  104. 'tab' => $this->makePartial('form_page', [
  105. 'form' => $widget,
  106. 'templateType' => $type,
  107. 'templateTheme' => $this->theme->getDirName(),
  108. 'templateMtime' => $template->mtime
  109. ])
  110. ];
  111. }
  112. public function onSave()
  113. {
  114. $this->validateRequestTheme();
  115. $type = Request::input('templateType');
  116. $templatePath = trim(Request::input('templatePath'));
  117. $template = $templatePath ? $this->loadTemplate($type, $templatePath) : $this->createTemplate($type);
  118. $settings = Request::input('settings') ?: [];
  119. $settings = $this->upgradeSettings($settings);
  120. $templateData = [];
  121. if ($settings) {
  122. $templateData['settings'] = $settings;
  123. }
  124. $fields = ['markup', 'code', 'fileName', 'content'];
  125. foreach ($fields as $field) {
  126. if (array_key_exists($field, $_POST)) {
  127. $templateData[$field] = Request::input($field);
  128. }
  129. }
  130. if (!empty($templateData['markup']) && Config::get('cms.convertLineEndings', false) === true) {
  131. $templateData['markup'] = $this->convertLineEndings($templateData['markup']);
  132. }
  133. if (!Request::input('templateForceSave') && $template->mtime) {
  134. if (Request::input('templateMtime') != $template->mtime) {
  135. throw new ApplicationException('mtime-mismatch');
  136. }
  137. }
  138. $template->fill($templateData);
  139. $template->save();
  140. /*
  141. * Extensibility
  142. */
  143. Event::fire('cms.template.save', [$this, $template, $type]);
  144. $this->fireEvent('template.save', [$template, $type]);
  145. Flash::success(Lang::get('cms::lang.template.saved'));
  146. $result = [
  147. 'templatePath' => $template->fileName,
  148. 'templateMtime' => $template->mtime,
  149. 'tabTitle' => $this->getTabTitle($type, $template)
  150. ];
  151. if ($type == 'page') {
  152. $result['pageUrl'] = URL::to($template->url);
  153. $router = new Router($this->theme);
  154. $router->clearCache();
  155. CmsCompoundObject::clearCache($this->theme);
  156. }
  157. return $result;
  158. }
  159. public function onOpenConcurrencyResolveForm()
  160. {
  161. return $this->makePartial('concurrency_resolve_form');
  162. }
  163. public function onCreateTemplate()
  164. {
  165. $type = Request::input('type');
  166. $template = $this->createTemplate($type);
  167. if ($type == 'asset') {
  168. $template->setInitialPath($this->widget->assetList->getCurrentRelativePath());
  169. }
  170. $widget = $this->makeTemplateFormWidget($type, $template);
  171. $this->vars['templatePath'] = '';
  172. return [
  173. 'tabTitle' => $this->getTabTitle($type, $template),
  174. 'tab' => $this->makePartial('form_page', [
  175. 'form' => $widget,
  176. 'templateType' => $type,
  177. 'templateTheme' => $this->theme->getDirName(),
  178. 'templateMtime' => null
  179. ])
  180. ];
  181. }
  182. public function onDeleteTemplates()
  183. {
  184. $this->validateRequestTheme();
  185. $type = Request::input('type');
  186. $templates = Request::input('template');
  187. $error = null;
  188. $deleted = [];
  189. try {
  190. foreach ($templates as $path => $selected) {
  191. if ($selected) {
  192. $this->loadTemplate($type, $path)->delete();
  193. $deleted[] = $path;
  194. }
  195. }
  196. }
  197. catch (Exception $ex) {
  198. $error = $ex->getMessage();
  199. }
  200. /*
  201. * Extensibility
  202. */
  203. Event::fire('cms.template.delete', [$this, $type]);
  204. $this->fireEvent('template.delete', [$type]);
  205. return [
  206. 'deleted' => $deleted,
  207. 'error' => $error,
  208. 'theme' => Request::input('theme')
  209. ];
  210. }
  211. public function onDelete()
  212. {
  213. $this->validateRequestTheme();
  214. $type = Request::input('templateType');
  215. $this->loadTemplate($type, trim(Request::input('templatePath')))->delete();
  216. /*
  217. * Extensibility
  218. */
  219. Event::fire('cms.template.delete', [$this, $type]);
  220. $this->fireEvent('template.delete', [$type]);
  221. }
  222. public function onGetTemplateList()
  223. {
  224. $this->validateRequestTheme();
  225. $page = new Page($this->theme);
  226. return [
  227. 'layouts' => $page->getLayoutOptions()
  228. ];
  229. }
  230. public function onExpandMarkupToken()
  231. {
  232. if (!$alias = post('tokenName')) {
  233. throw new ApplicationException(trans('cms::lang.component.no_records'));
  234. }
  235. // Can only expand components at this stage
  236. if ((!$type = post('tokenType')) && $type != 'component') {
  237. return;
  238. }
  239. if (!($names = (array) post('component_names')) || !($aliases = (array) post('component_aliases'))) {
  240. throw new ApplicationException(trans('cms::lang.component.not_found', ['name' => $alias]));
  241. }
  242. if (($index = array_get(array_flip($aliases), $alias, false)) === false) {
  243. throw new ApplicationException(trans('cms::lang.component.not_found', ['name' => $alias]));
  244. }
  245. if (!$componentName = array_get($names, $index)) {
  246. throw new ApplicationException(trans('cms::lang.component.not_found', ['name' => $alias]));
  247. }
  248. $manager = ComponentManager::instance();
  249. $componentObj = $manager->makeComponent($componentName);
  250. $partial = ComponentPartial::load($componentObj, 'default');
  251. $content = $partial->getContent();
  252. $content = str_replace('__SELF__', $alias, $content);
  253. return $content;
  254. }
  255. //
  256. // Methods for the internal use
  257. //
  258. protected function validateRequestTheme()
  259. {
  260. if ($this->theme->getDirName() != Request::input('theme')) {
  261. throw new ApplicationException(trans('cms::lang.theme.edit.not_match'));
  262. }
  263. }
  264. protected function resolveTypeClassName($type)
  265. {
  266. $types = [
  267. 'page' => '\Cms\Classes\Page',
  268. 'partial' => '\Cms\Classes\Partial',
  269. 'layout' => '\Cms\Classes\Layout',
  270. 'content' => '\Cms\Classes\Content',
  271. 'asset' => '\Cms\Classes\Asset',
  272. ];
  273. if (!array_key_exists($type, $types)) {
  274. throw new ApplicationException(trans('cms::lang.template.invalid_type'));
  275. }
  276. return $types[$type];
  277. }
  278. protected function loadTemplate($type, $path)
  279. {
  280. $class = $this->resolveTypeClassName($type);
  281. if (!($template = call_user_func(array($class, 'load'), $this->theme, $path))) {
  282. throw new ApplicationException(trans('cms::lang.template.not_found'));
  283. }
  284. return $template;
  285. }
  286. protected function createTemplate($type)
  287. {
  288. $class = $this->resolveTypeClassName($type);
  289. if (!($template = new $class($this->theme))) {
  290. throw new ApplicationException(trans('cms::lang.template.not_found'));
  291. }
  292. return $template;
  293. }
  294. protected function getTabTitle($type, $template)
  295. {
  296. if ($type == 'page') {
  297. $result = $template->title ?: $template->getFileName();
  298. if (!$result) {
  299. $result = trans('cms::lang.page.new');
  300. }
  301. return $result;
  302. }
  303. if ($type == 'partial' || $type == 'layout' || $type == 'content' || $type == 'asset') {
  304. $result = in_array($type, ['asset', 'content']) ? $template->getFileName() : $template->getBaseFileName();
  305. if (!$result) {
  306. $result = trans('cms::lang.'.$type.'.new');
  307. }
  308. return $result;
  309. }
  310. return $template->getFileName();
  311. }
  312. protected function makeTemplateFormWidget($type, $template)
  313. {
  314. $formConfigs = [
  315. 'page' => '@/modules/cms/classes/page/fields.yaml',
  316. 'partial' => '@/modules/cms/classes/partial/fields.yaml',
  317. 'layout' => '@/modules/cms/classes/layout/fields.yaml',
  318. 'content' => '@/modules/cms/classes/content/fields.yaml',
  319. 'asset' => '@/modules/cms/classes/asset/fields.yaml',
  320. ];
  321. if (!array_key_exists($type, $formConfigs)) {
  322. throw new ApplicationException(trans('cms::lang.template.not_found'));
  323. }
  324. $widgetConfig = $this->makeConfig($formConfigs[$type]);
  325. $widgetConfig->model = $template;
  326. $widgetConfig->alias = 'form'.studly_case($type).md5($template->getFileName()).uniqid();
  327. $widget = $this->makeWidget('Backend\Widgets\Form', $widgetConfig);
  328. return $widget;
  329. }
  330. protected function upgradeSettings($settings)
  331. {
  332. if (!array_key_exists('component_properties', $_POST)) {
  333. return $settings;
  334. }
  335. if (!array_key_exists('component_names', $_POST) || !array_key_exists('component_aliases', $_POST)) {
  336. throw new ApplicationException(trans('cms::lang.component.invalid_request'));
  337. }
  338. $count = count($_POST['component_properties']);
  339. if (count($_POST['component_names']) != $count || count($_POST['component_aliases']) != $count) {
  340. throw new ApplicationException(trans('cms::lang.component.invalid_request'));
  341. }
  342. for ($index = 0; $index < $count; $index ++) {
  343. $componentName = $_POST['component_names'][$index];
  344. $componentAlias = $_POST['component_aliases'][$index];
  345. $section = $componentName;
  346. if ($componentAlias != $componentName) {
  347. $section .= ' '.$componentAlias;
  348. }
  349. $properties = json_decode($_POST['component_properties'][$index], true);
  350. unset($properties['oc.alias']);
  351. unset($properties['inspectorProperty']);
  352. unset($properties['inspectorClassName']);
  353. $settings[$section] = $properties;
  354. }
  355. return $settings;
  356. }
  357. /**
  358. * Replaces Windows style (/r/n) line endings with unix style (/n)
  359. * line endings.
  360. * @param string $markup The markup to convert to unix style endings
  361. * @return string
  362. */
  363. protected function convertLineEndings($markup)
  364. {
  365. $markup = str_replace("\r\n", "\n", $markup);
  366. $markup = str_replace("\r", "\n", $markup);
  367. return $markup;
  368. }
  369. }