SettingsController.php 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088
  1. <?php
  2. namespace Typemill\Controllers;
  3. use \Symfony\Component\Yaml\Yaml;
  4. use Typemill\Models\Write;
  5. use Typemill\Models\Fields;
  6. use Typemill\Models\Validation;
  7. use Typemill\Models\User;
  8. use Typemill\Models\ProcessFile;
  9. use Typemill\Models\ProcessImage;
  10. use Typemill\Events\OnUserfieldsLoaded;
  11. use Typemill\Events\OnSystemnaviLoaded;
  12. class SettingsController extends Controller
  13. {
  14. public function showBlank($request, $response, $args)
  15. {
  16. $user = new User();
  17. $settings = $this->c->get('settings');
  18. $route = $request->getAttribute('route');
  19. $navigation = $this->getNavigation();
  20. $content = '<h1>Hello</h1><p>I am the showBlank method from the settings controller</p><p>In most cases I have been called from a plugin. But if you see this content, then the plugin does not work or has forgotten to inject its own content.</p>';
  21. return $this->render($response, 'settings/blank.twig', array(
  22. 'settings' => $settings,
  23. 'acl' => $this->c->acl,
  24. 'navigation' => $navigation,
  25. 'content' => $content,
  26. 'route' => $route->getName()
  27. ));
  28. }
  29. /*********************
  30. ** BASIC SETTINGS **
  31. *********************/
  32. public function showSettings($request, $response, $args)
  33. {
  34. $user = new User();
  35. $settings = $this->c->get('settings');
  36. $defaultSettings = \Typemill\Settings::getDefaultSettings();
  37. $copyright = $this->getCopyright();
  38. $languages = $this->getLanguages();
  39. $locale = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2) : 'en';
  40. $route = $request->getAttribute('route');
  41. $navigation = $this->getNavigation();
  42. # set navigation active
  43. $navigation['System']['active'] = true;
  44. # set option for registered website
  45. $options = ['' => 'all', 'registered' => 'registered users only'];
  46. return $this->render($response, 'settings/system.twig', array(
  47. 'settings' => $settings,
  48. 'acl' => $this->c->acl,
  49. 'navigation' => $navigation,
  50. 'copyright' => $copyright,
  51. 'languages' => $languages,
  52. 'locale' => $locale,
  53. 'formats' => $defaultSettings['formats'],
  54. 'access' => $options,
  55. 'route' => $route->getName()
  56. ));
  57. }
  58. public function saveSettings($request, $response, $args)
  59. {
  60. if($request->isPost())
  61. {
  62. $settings = \Typemill\Settings::getUserSettings();
  63. $defaultSettings = \Typemill\Settings::getDefaultSettings();
  64. $params = $request->getParams();
  65. $files = $request->getUploadedFiles();
  66. $newSettings = isset($params['settings']) ? $params['settings'] : false;
  67. $validate = new Validation();
  68. $processFiles = new ProcessFile();
  69. if($newSettings)
  70. {
  71. # check for image settings
  72. $imgwidth = isset($newSettings['images']['live']['width']) ? $newSettings['images']['live']['width'] : false;
  73. $imgheight = isset($newSettings['images']['live']['height']) ? $newSettings['images']['live']['height'] : false;
  74. # make sure only allowed fields are stored
  75. $newSettings = array(
  76. 'title' => $newSettings['title'],
  77. 'author' => $newSettings['author'],
  78. 'copyright' => $newSettings['copyright'],
  79. 'year' => $newSettings['year'],
  80. 'language' => $newSettings['language'],
  81. 'langattr' => $newSettings['langattr'],
  82. 'editor' => $newSettings['editor'],
  83. 'access' => $newSettings['access'],
  84. 'formats' => $newSettings['formats'],
  85. 'headlineanchors' => isset($newSettings['headlineanchors']) ? $newSettings['headlineanchors'] : null,
  86. 'displayErrorDetails' => isset($newSettings['displayErrorDetails']) ? true : null,
  87. 'twigcache' => isset($newSettings['twigcache']) ? true : null,
  88. 'proxy' => isset($newSettings['proxy']) ? true : null,
  89. 'trustedproxies' => $newSettings['trustedproxies'],
  90. 'headersoff' => isset($newSettings['headersoff']) ? true : null,
  91. 'urlschemes' => $newSettings['urlschemes'],
  92. );
  93. # https://www.slimframework.com/docs/v3/cookbook/uploading-files.html;
  94. $copyright = $this->getCopyright();
  95. $validate->settings($newSettings, $copyright, $defaultSettings['formats'], 'settings');
  96. # use custom image settings
  97. if( $imgwidth && ctype_digit($imgwidth) && (strlen($imgwidth) < 5) )
  98. {
  99. $newSettings['images']['live']['width'] = $imgwidth;
  100. }
  101. if( $imgheight && ctype_digit($imgheight) && (strlen($imgheight) < 5) )
  102. {
  103. $newSettings['images']['live']['height'] = $imgheight;
  104. }
  105. }
  106. else
  107. {
  108. $this->c->flash->addMessage('error', 'Wrong Input');
  109. return $response->withRedirect($this->c->router->pathFor('settings.show'));
  110. }
  111. if(isset($_SESSION['errors']))
  112. {
  113. $this->c->flash->addMessage('error', 'Please correct the errors');
  114. return $response->withRedirect($this->c->router->pathFor('settings.show'));
  115. }
  116. if(!$processFiles->checkFolders())
  117. {
  118. $this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.');
  119. return $response->withRedirect($this->c->router->pathFor('settings.show'));
  120. }
  121. # handle single input with single file upload
  122. $logo = $files['settings']['logo'];
  123. if($logo->getError() === UPLOAD_ERR_OK)
  124. {
  125. $allowed = ['jpg', 'jpeg', 'png', 'svg'];
  126. $extension = pathinfo($logo->getClientFilename(), PATHINFO_EXTENSION);
  127. if(!in_array(strtolower($extension), $allowed))
  128. {
  129. $_SESSION['errors']['settings']['logo'] = array('Only jpg, jpeg, png and svg allowed');
  130. $this->c->flash->addMessage('error', 'Please correct the errors');
  131. return $response->withRedirect($this->c->router->pathFor('settings.show'));
  132. }
  133. $processFiles->deleteFileWithName('logo');
  134. $newSettings['logo'] = $processFiles->moveUploadedFile($logo, $overwrite = true, $name = 'logo');
  135. }
  136. elseif(isset($params['settings']['deletelogo']) && $params['settings']['deletelogo'] == 'delete')
  137. {
  138. $processFiles->deleteFileWithName('logo');
  139. $newSettings['logo'] = '';
  140. }
  141. else
  142. {
  143. $newSettings['logo'] = isset($settings['logo']) ? $settings['logo'] : '';
  144. }
  145. # handle single input with single file upload
  146. $favicon = $files['settings']['favicon'];
  147. if ($favicon->getError() === UPLOAD_ERR_OK)
  148. {
  149. $extension = pathinfo($favicon->getClientFilename(), PATHINFO_EXTENSION);
  150. if(strtolower($extension) != 'png')
  151. {
  152. $_SESSION['errors']['settings']['favicon'] = array('Only .png-files allowed');
  153. $this->c->flash->addMessage('error', 'Please correct the errors');
  154. return $response->withRedirect($this->c->router->pathFor('settings.show'));
  155. }
  156. $processImage = new ProcessImage([
  157. '16' => ['width' => 16, 'height' => 16],
  158. '32' => ['width' => 32, 'height' => 32],
  159. '72' => ['width' => 72, 'height' => 72],
  160. '114' => ['width' => 114, 'height' => 114],
  161. '144' => ['width' => 144, 'height' => 144],
  162. '180' => ['width' => 180, 'height' => 180],
  163. ]);
  164. $favicons = $processImage->generateSizesFromImageFile('favicon.png', $favicon->file);
  165. foreach($favicons as $key => $favicon)
  166. {
  167. imagepng( $favicon, $processFiles->fileFolder . 'favicon-' . $key . '.png' );
  168. # $processFiles->moveUploadedFile($favicon, $overwrite = true, $name = 'favicon-' . $key);
  169. }
  170. $newSettings['favicon'] = 'favicon';
  171. }
  172. elseif(isset($params['settings']['deletefav']) && $params['settings']['deletefav'] == 'delete')
  173. {
  174. $processFiles->deleteFileWithName('favicon');
  175. $newSettings['favicon'] = '';
  176. }
  177. else
  178. {
  179. $newSettings['favicon'] = isset($settings['favicon']) ? $settings['favicon'] : '';
  180. }
  181. # store updated settings
  182. \Typemill\Settings::updateSettings(array_merge($settings, $newSettings));
  183. $this->c->flash->addMessage('info', 'Settings are stored');
  184. return $response->withRedirect($this->c->router->pathFor('settings.show'));
  185. }
  186. }
  187. /*********************
  188. ** THEME SETTINGS **
  189. *********************/
  190. public function showThemes($request, $response, $args)
  191. {
  192. $userSettings = $this->c->get('settings');
  193. $themes = $this->getThemes();
  194. $themedata = array();
  195. $fieldsModel = new Fields();
  196. foreach($themes as $themeName)
  197. {
  198. /* if theme is active, list it first */
  199. if($userSettings['theme'] == $themeName)
  200. {
  201. $themedata = array_merge(array($themeName => null), $themedata);
  202. }
  203. else
  204. {
  205. $themedata[$themeName] = null;
  206. }
  207. $themeSettings = \Typemill\Settings::getObjectSettings('themes', $themeName);
  208. # add standard-textarea for custom css
  209. $themeSettings['forms']['fields']['customcss'] = ['type' => 'textarea', 'label' => 'Custom CSS', 'rows' => 10, 'class' => 'codearea', 'description' => 'You can overwrite the theme-css with your own css here.'];
  210. # load custom css-file
  211. $write = new write();
  212. $customcss = $write->getFile('cache', $themeName . '-custom.css');
  213. $themeSettings['settings']['customcss'] = $customcss;
  214. if($themeSettings)
  215. {
  216. /* store them as default theme data with author, year, default settings and field-definitions */
  217. $themedata[$themeName] = $themeSettings;
  218. }
  219. if(isset($themeSettings['forms']['fields']))
  220. {
  221. $fields = $fieldsModel->getFields($userSettings, 'themes', $themeName, $themeSettings);
  222. /* overwrite original theme form definitions with enhanced form objects */
  223. $themedata[$themeName]['forms']['fields'] = $fields;
  224. }
  225. /* add the preview image */
  226. $img = getcwd() . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeName . DIRECTORY_SEPARATOR . $themeName;
  227. $image = false;
  228. if(file_exists($img . '.jpg'))
  229. {
  230. $image = $themeName . '.jpg';
  231. }
  232. if(file_exists($img . '.png'))
  233. {
  234. $image = $themeName . '.png';
  235. }
  236. $themedata[$themeName]['img'] = $image;
  237. }
  238. /* add the users for navigation */
  239. $route = $request->getAttribute('route');
  240. $navigation = $this->getNavigation();
  241. # set navigation active
  242. $navigation['Themes']['active'] = true;
  243. return $this->render($response, 'settings/themes.twig', array(
  244. 'settings' => $userSettings,
  245. 'acl' => $this->c->acl,
  246. 'navigation' => $navigation,
  247. 'themes' => $themedata,
  248. 'route' => $route->getName()
  249. ));
  250. }
  251. public function showPlugins($request, $response, $args)
  252. {
  253. $userSettings = $this->c->get('settings');
  254. $plugins = array();
  255. $fieldsModel = new Fields();
  256. $fields = array();
  257. /* iterate through the plugins in the stored user settings */
  258. foreach($userSettings['plugins'] as $pluginName => $pluginUserSettings)
  259. {
  260. /* add plugin to plugin Data, if active, set it first */
  261. /* if plugin is active, list it first */
  262. if($userSettings['plugins'][$pluginName]['active'] == true)
  263. {
  264. $plugins = array_merge(array($pluginName => null), $plugins);
  265. }
  266. else
  267. {
  268. $plugins[$pluginName] = Null;
  269. }
  270. /* Check if the user has deleted a plugin. Then delete it in the settings and store the updated settings. */
  271. if(!is_dir($userSettings['rootPath'] . 'plugins' . DIRECTORY_SEPARATOR . $pluginName))
  272. {
  273. /* remove the plugin settings and store updated settings */
  274. \Typemill\Settings::removePluginSettings($pluginName);
  275. continue;
  276. }
  277. /* load the original plugin definitions from the plugin folder (author, version and stuff) */
  278. $pluginOriginalSettings = \Typemill\Settings::getObjectSettings('plugins', $pluginName);
  279. if($pluginOriginalSettings)
  280. {
  281. /* store them as default plugin data with plugin author, plugin year, default settings and field-definitions */
  282. $plugins[$pluginName] = $pluginOriginalSettings;
  283. }
  284. /* check, if the plugin has been disabled in the form-session-data */
  285. if(isset($_SESSION['old']) && !isset($_SESSION['old'][$pluginName]['active']))
  286. {
  287. $plugins[$pluginName]['settings']['active'] = false;
  288. }
  289. /* if the plugin defines forms and fields, so that the user can edit the plugin settings in the frontend */
  290. if(isset($pluginOriginalSettings['forms']['fields']))
  291. {
  292. # if the plugin defines frontend fields
  293. if(isset($pluginOriginalSettings['public']))
  294. {
  295. $pluginOriginalSettings['forms']['fields']['recaptcha'] = ['type' => 'checkbox', 'label' => 'Google Recaptcha', 'checkboxlabel' => 'Activate Recaptcha' ];
  296. $pluginOriginalSettings['forms']['fields']['recaptcha_webkey'] = ['type' => 'text', 'label' => 'Recaptcha Website Key', 'help' => 'Add the recaptcha website key here. You can get the key from the recaptcha website.', 'description' => 'The website key is mandatory if you activate the recaptcha field'];
  297. $pluginOriginalSettings['forms']['fields']['recaptcha_secretkey'] = ['type' => 'text', 'label' => 'Recaptcha Secret Key', 'help' => 'Add the recaptcha secret key here. You can get the key from the recaptcha website.', 'description' => 'The secret key is mandatory if you activate the recaptcha field'];
  298. }
  299. /* get all the fields and prefill them with the dafault-data, the user-data or old input data */
  300. $fields = $fieldsModel->getFields($userSettings, 'plugins', $pluginName, $pluginOriginalSettings);
  301. /* overwrite original plugin form definitions with enhanced form objects */
  302. $plugins[$pluginName]['forms']['fields'] = $fields;
  303. }
  304. }
  305. $route = $request->getAttribute('route');
  306. $navigation = $this->getNavigation();
  307. # set navigation active
  308. $navigation['Plugins']['active'] = true;
  309. return $this->render($response, 'settings/plugins.twig', array(
  310. 'settings' => $userSettings,
  311. 'acl' => $this->c->acl,
  312. 'navigation' => $navigation,
  313. 'plugins' => $plugins,
  314. 'route' => $route->getName()
  315. ));
  316. }
  317. /*************************************
  318. ** SAVE THEME- AND PLUGIN-SETTINGS **
  319. *************************************/
  320. public function saveThemes($request, $response, $args)
  321. {
  322. if($request->isPost())
  323. {
  324. $userSettings = \Typemill\Settings::getUserSettings();
  325. $params = $request->getParams();
  326. $themeName = isset($params['theme']) ? $params['theme'] : false;
  327. $userInput = isset($params[$themeName]) ? $params[$themeName] : false;
  328. $validate = new Validation();
  329. $themeSettings = \Typemill\Settings::getObjectSettings('themes', $themeName);
  330. if(isset($themeSettings['settings']['images']))
  331. {
  332. # get the default settings
  333. $defaultSettings = \Typemill\Settings::getDefaultSettings();
  334. # merge the default image settings with the theme image settings, delete all others (image settings from old theme)
  335. $userSettings['images'] = array_merge($defaultSettings['images'], $themeSettings['settings']['images']);
  336. }
  337. /* set theme name and delete theme settings from user settings for the case, that the new theme has no settings */
  338. $userSettings['theme'] = $themeName;
  339. # extract the custom css from user input
  340. $customcss = isset($userInput['customcss']) ? $userInput['customcss'] : false;
  341. # delete custom css from userinput
  342. unset($userInput['customcss']);
  343. $write = new write();
  344. # make sure no file is set if there is no custom css
  345. if(!$customcss OR $customcss == '')
  346. {
  347. # delete the css file if exists
  348. $write->deleteFileWithPath('cache' . DIRECTORY_SEPARATOR . $themeName . '-custom.css');
  349. }
  350. else
  351. {
  352. if ( $customcss != strip_tags($customcss) )
  353. {
  354. $_SESSION['errors'][$themeName]['customcss'][] = 'custom css contains html';
  355. }
  356. else
  357. {
  358. # store css
  359. $write = new write();
  360. $write->writeFile('cache', $themeName . '-custom.css', $customcss);
  361. }
  362. }
  363. if($userInput)
  364. {
  365. # validate the user-input and return image-fields if they are defined
  366. $imageFields = $this->validateInput('themes', $themeName, $userInput, $validate);
  367. /* set user input as theme settings */
  368. $userSettings['themes'][$themeName] = $userInput;
  369. }
  370. # handle images
  371. $images = $request->getUploadedFiles();
  372. if(!isset($_SESSION['errors']) && isset($images[$themeName]))
  373. {
  374. $userInput = $this->saveImages($imageFields, $userInput, $userSettings, $images[$themeName]);
  375. # set user input as theme settings
  376. $userSettings['themes'][$themeName] = $userInput;
  377. }
  378. /* check for errors and redirect to path, if errors found */
  379. if(isset($_SESSION['errors']))
  380. {
  381. $this->c->flash->addMessage('error', 'Please correct the errors');
  382. return $response->withRedirect($this->c->router->pathFor('themes.show'));
  383. }
  384. /* store updated settings */
  385. \Typemill\Settings::updateSettings($userSettings);
  386. $this->c->flash->addMessage('info', 'Settings are stored');
  387. return $response->withRedirect($this->c->router->pathFor('themes.show'));
  388. }
  389. }
  390. public function savePlugins($request, $response, $args)
  391. {
  392. if($request->isPost())
  393. {
  394. $userSettings = \Typemill\Settings::getUserSettings();
  395. $pluginSettings = array();
  396. $userInput = $request->getParams();
  397. $validate = new Validation();
  398. /* use the stored user settings and iterate over all original plugin settings, so we do not forget any... */
  399. foreach($userSettings['plugins'] as $pluginName => $pluginUserSettings)
  400. {
  401. /* if there are no input-data for this plugin, then use the stored plugin settings */
  402. if(!isset($userInput[$pluginName]))
  403. {
  404. $pluginSettings[$pluginName] = $pluginUserSettings;
  405. }
  406. else
  407. {
  408. /* validate the user-input */
  409. $imageFields = $this->validateInput('plugins', $pluginName, $userInput[$pluginName], $validate);
  410. /* use the input data */
  411. $pluginSettings[$pluginName] = $userInput[$pluginName];
  412. }
  413. # handle images
  414. $images = $request->getUploadedFiles();
  415. if(!isset($_SESSION['errors']) && isset($images[$pluginName]))
  416. {
  417. $userInput[$pluginName] = $this->saveImages($imageFields, $userInput[$pluginName], $userSettings, $images[$pluginName]);
  418. # set user input as theme settings
  419. $pluginSettings[$pluginName] = $userInput[$pluginName];
  420. }
  421. /* deactivate the plugin, if there is no active flag */
  422. if(!isset($userInput[$pluginName]['active']))
  423. {
  424. $pluginSettings[$pluginName]['active'] = false;
  425. }
  426. }
  427. if(isset($_SESSION['errors']))
  428. {
  429. $this->c->flash->addMessage('error', 'Please correct the errors below');
  430. }
  431. else
  432. {
  433. /* if everything is valid, add plugin settings to base settings again */
  434. $userSettings['plugins'] = $pluginSettings;
  435. /* store updated settings */
  436. \Typemill\Settings::updateSettings($userSettings);
  437. $this->c->flash->addMessage('info', 'Settings are stored');
  438. }
  439. return $response->withRedirect($this->c->router->pathFor('plugins.show'));
  440. }
  441. }
  442. /***********************
  443. ** USER MANAGEMENT **
  444. ***********************/
  445. public function showAccount($request, $response, $args)
  446. {
  447. $username = $_SESSION['user'];
  448. $validate = new Validation();
  449. if($validate->username($username))
  450. {
  451. # get settings
  452. $settings = $this->c->get('settings');
  453. # get user with userdata
  454. $user = new User();
  455. $userdata = $user->getSecureUser($username);
  456. # instantiate field-builder
  457. $fieldsModel = new Fields();
  458. # get the field-definitions
  459. $fieldDefinitions = $this->getUserFields($userdata['userrole']);
  460. # prepare userdata for field-builder
  461. $userSettings['users']['user'] = $userdata;
  462. # generate the input form
  463. $userform = $fieldsModel->getFields($userSettings, 'users', 'user', $fieldDefinitions);
  464. $route = $request->getAttribute('route');
  465. $navigation = $this->getNavigation();
  466. # set navigation active
  467. $navigation['Account']['active'] = true;
  468. return $this->render($response, 'settings/user.twig', array(
  469. 'settings' => $settings,
  470. 'acl' => $this->c->acl,
  471. 'navigation' => $navigation,
  472. 'usersettings' => $userSettings, // needed for image url in form, will overwrite settings for field-template
  473. 'userform' => $userform, // field model, needed to generate frontend-field
  474. 'userdata' => $userdata, // needed to fill form with data
  475. # 'userrole' => false, // not needed ?
  476. # 'username' => $args['username'], // not needed ?
  477. 'route' => $route->getName() // needed to set link active
  478. ));
  479. }
  480. $this->c->flash->addMessage('error', 'User does not exists');
  481. return $response->withRedirect($this->c->router->pathFor('home'));
  482. }
  483. public function showUser($request, $response, $args)
  484. {
  485. # if user has no rights to watch userlist, then redirect to
  486. if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'view') && $_SESSION['user'] !== $args['username'] )
  487. {
  488. return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] ));
  489. }
  490. # get settings
  491. $settings = $this->c->get('settings');
  492. # get user with userdata
  493. $user = new User();
  494. $userdata = $user->getSecureUser($args['username']);
  495. if(!$userdata)
  496. {
  497. $this->c->flash->addMessage('error', 'User does not exists');
  498. return $response->withRedirect($this->c->router->pathFor('user.account'));
  499. }
  500. # instantiate field-builder
  501. $fieldsModel = new Fields();
  502. # get the field-definitions
  503. $fieldDefinitions = $this->getUserFields($userdata['userrole']);
  504. # prepare userdata for field-builder
  505. $userSettings['users']['user'] = $userdata;
  506. # generate the input form
  507. $userform = $fieldsModel->getFields($userSettings, 'users', 'user', $fieldDefinitions);
  508. $route = $request->getAttribute('route');
  509. $navigation = $this->getNavigation();
  510. # set navigation active
  511. $navigation['Users']['active'] = true;
  512. return $this->render($response, 'settings/user.twig', array(
  513. 'settings' => $settings,
  514. 'acl' => $this->c->acl,
  515. 'navigation' => $navigation,
  516. 'usersettings' => $userSettings, // needed for image url in form, will overwrite settings for field-template
  517. 'userform' => $userform, // field model, needed to generate frontend-field
  518. 'userdata' => $userdata, // needed to fill form with data
  519. 'route' => $route->getName() // needed to set link active
  520. ));
  521. }
  522. public function listUser($request, $response)
  523. {
  524. $user = new User();
  525. $users = $user->getUsers();
  526. $userdata = array();
  527. $route = $request->getAttribute('route');
  528. $settings = $this->c->get('settings');
  529. $navigation = $this->getNavigation();
  530. # set navigation active
  531. $navigation['Users']['active'] = true;
  532. foreach($users as $username)
  533. {
  534. $userdata[] = $user->getUser($username);
  535. }
  536. return $this->render($response, 'settings/userlist.twig', array(
  537. 'settings' => $settings,
  538. 'acl' => $this->c->acl,
  539. 'navigation' => $navigation,
  540. 'users' => $users,
  541. 'userdata' => $userdata,
  542. 'route' => $route->getName()
  543. ));
  544. }
  545. public function newUser($request, $response, $args)
  546. {
  547. $user = new User();
  548. $users = $user->getUsers();
  549. $userroles = $this->c->acl->getRoles();
  550. $route = $request->getAttribute('route');
  551. $settings = $this->c->get('settings');
  552. $navigation = $this->getNavigation();
  553. # set navigation active
  554. $navigation['Users']['active'] = true;
  555. return $this->render($response, 'settings/usernew.twig', array(
  556. 'settings' => $settings,
  557. 'acl' => $this->c->acl,
  558. 'navigation' => $navigation,
  559. 'users' => $users,
  560. 'userrole' => $userroles,
  561. 'route' => $route->getName()
  562. ));
  563. }
  564. public function createUser($request, $response, $args)
  565. {
  566. if($request->isPost())
  567. {
  568. $params = $request->getParams();
  569. $user = new User();
  570. $validate = new Validation();
  571. $userroles = $this->c->acl->getRoles();
  572. if($validate->newUser($params, $userroles))
  573. {
  574. $userdata = array(
  575. 'username' => $params['username'],
  576. 'email' => $params['email'],
  577. 'userrole' => $params['userrole'],
  578. 'password' => $params['password']);
  579. $user->createUser($userdata);
  580. $this->c->flash->addMessage('info', 'Welcome, there is a new user!');
  581. return $response->withRedirect($this->c->router->pathFor('user.list'));
  582. }
  583. $this->c->flash->addMessage('error', 'Please correct your input');
  584. return $response->withRedirect($this->c->router->pathFor('user.new'));
  585. }
  586. }
  587. public function updateUser($request, $response, $args)
  588. {
  589. if($request->isPost())
  590. {
  591. $params = $request->getParams();
  592. $userdata = $params['user'];
  593. $user = new User();
  594. $validate = new Validation();
  595. $userroles = $this->c->acl->getRoles();
  596. $redirectRoute = ($userdata['username'] == $_SESSION['user']) ? $this->c->router->pathFor('user.account') : $this->c->router->pathFor('user.show', ['username' => $userdata['username']]);
  597. # check if user is allowed to view (edit) userlist and other users
  598. if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'write'))
  599. {
  600. # if an editor tries to update other userdata than its own */
  601. if($_SESSION['user'] !== $userdata['username'])
  602. {
  603. return $response->withRedirect($this->c->router->pathFor('user.account'));
  604. }
  605. # non admins cannot change their userrole, so set it to session-value
  606. $userdata['userrole'] = $_SESSION['role'];
  607. }
  608. # validate standard fields for users
  609. if($validate->existingUser($userdata, $userroles))
  610. {
  611. # validate custom input fields and return images
  612. $userfields = $this->getUserFields($userdata['userrole']);
  613. $imageFields = $this->validateInput('users', 'user', $userdata, $validate, $userfields);
  614. if(!empty($imageFields))
  615. {
  616. $images = $request->getUploadedFiles();
  617. if(isset($images['user']))
  618. {
  619. # set image size
  620. $settings = $this->c->get('settings');
  621. $imageSizes = $settings['images'];
  622. $imageSizes['live'] = ['width' => 500, 'height' => 500];
  623. $settings->replace(['images' => $imageSizes]);
  624. $imageresult = $this->saveImages($imageFields, $userdata, $settings, $images['user']);
  625. if(isset($_SESSION['slimFlash']['error']))
  626. {
  627. return $response->withRedirect($redirectRoute);
  628. }
  629. elseif(isset($imageresult['username']))
  630. {
  631. $userdata = $imageresult;
  632. }
  633. }
  634. }
  635. # check for errors and redirect to path, if errors found */
  636. if(isset($_SESSION['errors']))
  637. {
  638. $this->c->flash->addMessage('error', 'Please correct the errors');
  639. return $response->withRedirect($redirectRoute);
  640. }
  641. if(empty($userdata['password']) AND empty($userdata['newpassword']))
  642. {
  643. # make sure no invalid passwords go into model
  644. unset($userdata['password']);
  645. unset($userdata['newpassword']);
  646. $user->updateUser($userdata);
  647. $this->c->flash->addMessage('info', 'Saved all changes');
  648. return $response->withRedirect($redirectRoute);
  649. }
  650. elseif($validate->newPassword($userdata))
  651. {
  652. $userdata['password'] = $userdata['newpassword'];
  653. unset($userdata['newpassword']);
  654. $user->updateUser($userdata);
  655. $this->c->flash->addMessage('info', 'Saved all changes');
  656. return $response->withRedirect($redirectRoute);
  657. }
  658. }
  659. # change error-array for formbuilder
  660. $errors = $_SESSION['errors'];
  661. unset($_SESSION['errors']);
  662. $_SESSION['errors']['user'] = $errors;#
  663. $this->c->flash->addMessage('error', 'Please correct your input');
  664. return $response->withRedirect($redirectRoute);
  665. }
  666. }
  667. public function deleteUser($request, $response, $args)
  668. {
  669. if($request->isPost())
  670. {
  671. $params = $request->getParams();
  672. $validate = new Validation();
  673. $user = new User();
  674. # check if user is allowed to view (edit) userlist and other users
  675. if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'write'))
  676. {
  677. # if an editor tries to delete other user than its own
  678. if($_SESSION['user'] !== $params['username'])
  679. {
  680. return $response->withRedirect($this->c->router->pathFor('user.account'));
  681. }
  682. }
  683. if($validate->username($params['username']))
  684. {
  685. $user->deleteUser($params['username']);
  686. # if user deleted his own account
  687. if($_SESSION['user'] == $params['username'])
  688. {
  689. session_destroy();
  690. return $response->withRedirect($this->c->router->pathFor('auth.show'));
  691. }
  692. $this->c->flash->addMessage('info', 'Say goodbye, the user is gone!');
  693. return $response->withRedirect($this->c->router->pathFor('user.list'));
  694. }
  695. $this->c->flash->addMessage('error', 'Ups, we did not find that user');
  696. return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']]));
  697. }
  698. }
  699. public function clearCache($request, $response, $args)
  700. {
  701. $settings = $this->c->get('settings');
  702. $dir = $settings['basePath'] . 'cache';
  703. $iterator = new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS);
  704. $files = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
  705. $error = false;
  706. foreach($files as $file)
  707. {
  708. if ($file->isDir())
  709. {
  710. if(!rmdir($file->getRealPath()))
  711. {
  712. $error = 'Could not delete some folders.';
  713. }
  714. }
  715. elseif($file->getExtension() !== 'css')
  716. {
  717. if(!unlink($file->getRealPath()) )
  718. {
  719. $error = 'Could not delete some files.';
  720. }
  721. }
  722. }
  723. if($error)
  724. {
  725. return $response->withJson(['errors' => $error], 500);
  726. }
  727. return $response->withJson(array('errors' => false));
  728. }
  729. private function getUserFields($role)
  730. {
  731. $fields = [];
  732. $fields['username'] = ['label' => 'Username (read only)', 'type' => 'text', 'readonly' => true];
  733. $fields['firstname'] = ['label' => 'First Name', 'type' => 'text'];
  734. $fields['lastname'] = ['label' => 'Last Name', 'type' => 'text'];
  735. $fields['email'] = ['label' => 'E-Mail', 'type' => 'text', 'required' => true];
  736. $fields['userrole'] = ['label' => 'Role', 'type' => 'text', 'readonly' => true];
  737. $fields['password'] = ['label' => 'Actual Password', 'type' => 'password'];
  738. $fields['newpassword'] = ['label' => 'New Password', 'type' => 'password'];
  739. # dispatch fields;
  740. $fields = $this->c->dispatcher->dispatch('onUserfieldsLoaded', new OnUserfieldsLoaded($fields))->getData();
  741. # only roles who can edit content need profile image and description
  742. if($this->c->acl->isAllowed($role, 'mycontent', 'create'))
  743. {
  744. $newfield['image'] = ['label' => 'Profile-Image', 'type' => 'image'];
  745. $newfield['description'] = ['label' => 'Author-Description (Markdown)', 'type' => 'textarea'];
  746. $fields = array_slice($fields, 0, 1, true) + $newfield + array_slice($fields, 1, NULL, true);
  747. # array_splice($fields,1,0,$newfield);
  748. }
  749. # Only admin can change userroles
  750. if($this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'write'))
  751. {
  752. $userroles = $this->c->acl->getRoles();
  753. $options = [];
  754. # we need associative array to make select-field with key/value work
  755. foreach($userroles as $userrole)
  756. {
  757. $options[$userrole] = $userrole;
  758. }
  759. $fields['userrole'] = ['label' => 'Role', 'type' => 'select', 'options' => $options];
  760. }
  761. $userform = [];
  762. $userform['forms']['fields'] = $fields;
  763. return $userform;
  764. }
  765. private function getThemes()
  766. {
  767. $themeFolder = $this->c->get('settings')['rootPath'] . $this->c->get('settings')['themeFolder'];
  768. $themeFolderC = scandir($themeFolder);
  769. $themes = array();
  770. foreach ($themeFolderC as $key => $theme)
  771. {
  772. if (!in_array($theme, array(".","..")))
  773. {
  774. if (is_dir($themeFolder . DIRECTORY_SEPARATOR . $theme))
  775. {
  776. $themes[] = $theme;
  777. }
  778. }
  779. }
  780. return $themes;
  781. }
  782. private function getCopyright()
  783. {
  784. return array(
  785. "©",
  786. "CC-BY",
  787. "CC-BY-NC",
  788. "CC-BY-NC-ND",
  789. "CC-BY-NC-SA",
  790. "CC-BY-ND",
  791. "CC-BY-SA",
  792. "None"
  793. );
  794. }
  795. private function getLanguages()
  796. {
  797. return array(
  798. 'en' => 'English',
  799. 'ru' => 'Russian',
  800. 'nl' => 'Dutch, Flemish',
  801. 'de' => 'German',
  802. 'it' => 'Italian',
  803. 'fr' => 'French',
  804. );
  805. }
  806. private function getNavigation()
  807. {
  808. $navigation = [
  809. 'System' => ['routename' => 'settings.show', 'icon' => 'icon-wrench', 'aclresource' => 'system', 'aclprivilege' => 'view'],
  810. 'Themes' => ['routename' => 'themes.show', 'icon' => 'icon-paint-brush', 'aclresource' => 'system', 'aclprivilege' => 'view'],
  811. 'Plugins' => ['routename' => 'plugins.show', 'icon' => 'icon-plug', 'aclresource' => 'system', 'aclprivilege' => 'view'],
  812. 'Account' => ['routename' => 'user.account', 'icon' => 'icon-user', 'aclresource' => 'user', 'aclprivilege' => 'view'],
  813. 'Users' => ['routename' => 'user.list', 'icon' => 'icon-group', 'aclresource' => 'userlist', 'aclprivilege' => 'view']
  814. ];
  815. # dispatch fields;
  816. $navigation = $this->c->dispatcher->dispatch('onSystemnaviLoaded', new OnSystemnaviLoaded($navigation))->getData();
  817. return $navigation;
  818. }
  819. private function validateInput($objectType, $objectName, $userInput, $validate, $originalSettings = NULL)
  820. {
  821. if(!$originalSettings)
  822. {
  823. # fetch the original settings from the folder (plugin or theme) to get the field definitions
  824. $originalSettings = \Typemill\Settings::getObjectSettings($objectType, $objectName);
  825. }
  826. # images get special treatment
  827. $imageFieldDefinitions = array();
  828. if(isset($originalSettings['forms']['fields']))
  829. {
  830. /* flaten the multi-dimensional array with fieldsets to a one-dimensional array */
  831. $originalFields = array();
  832. foreach($originalSettings['forms']['fields'] as $fieldName => $fieldValue)
  833. {
  834. if(isset($fieldValue['fields']))
  835. {
  836. foreach($fieldValue['fields'] as $subFieldName => $subFieldValue)
  837. {
  838. $originalFields[$subFieldName] = $subFieldValue;
  839. }
  840. }
  841. else
  842. {
  843. $originalFields[$fieldName] = $fieldValue;
  844. }
  845. }
  846. # if the plugin defines frontend fields
  847. if(isset($originalSettings['public']))
  848. {
  849. $originalFields['recaptcha'] = ['type' => 'checkbox', 'label' => 'Google Recaptcha', 'checkboxlabel' => 'Activate Recaptcha' ];
  850. $originalFields['recaptcha_webkey'] = ['type' => 'text', 'label' => 'Recaptcha Website Key', 'help' => 'Add the recaptcha website key here. You can get the key from the recaptcha website.', 'description' => 'The website key is mandatory if you activate the recaptcha field'];
  851. $originalFields['recaptcha_secretkey'] = ['type' => 'text', 'label' => 'Recaptcha Secret Key', 'help' => 'Add the recaptcha secret key here. You can get the key from the recaptcha website.', 'description' => 'The secret key is mandatory if you activate the recaptcha field'];
  852. }
  853. # if plugin is not active, then skip required
  854. $skiprequired = false;
  855. if($objectType == 'plugins' && !isset($userInput['active']))
  856. {
  857. $skiprequired = true;
  858. }
  859. /* take the user input data and iterate over all fields and values */
  860. foreach($userInput as $fieldName => $fieldValue)
  861. {
  862. /* get the corresponding field definition from original plugin settings */
  863. $fieldDefinition = isset($originalFields[$fieldName]) ? $originalFields[$fieldName] : false;
  864. if($fieldDefinition)
  865. {
  866. /* validate user input for this field */
  867. $validate->objectField($fieldName, $fieldValue, $objectName, $fieldDefinition, $skiprequired);
  868. if($fieldDefinition['type'] == 'image')
  869. {
  870. # we want to return all images-fields for further processing
  871. $imageFieldDefinitions[$fieldName] = $fieldDefinition;
  872. }
  873. }
  874. if(!$fieldDefinition && $objectType != 'users' && $fieldName != 'active')
  875. {
  876. $_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!');
  877. }
  878. }
  879. }
  880. return $imageFieldDefinitions;
  881. }
  882. protected function saveImages($imageFields, $userInput, $userSettings, $files)
  883. {
  884. # initiate image processor with standard image sizes
  885. $processImages = new ProcessImage($userSettings['images']);
  886. if(!$processImages->checkFolders('images'))
  887. {
  888. $this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.');
  889. return false;
  890. }
  891. foreach($imageFields as $fieldName => $imageField)
  892. {
  893. if(isset($userInput[$fieldName]))
  894. {
  895. # handle single input with single file upload
  896. $image = $files[$fieldName];
  897. if($image->getError() === UPLOAD_ERR_OK)
  898. {
  899. # not the most elegant, but createImage expects a base64-encoded string.
  900. $imageContent = $image->getStream()->getContents();
  901. $imageData = base64_encode($imageContent);
  902. $imageSrc = 'data: ' . $image->getClientMediaType() . ';base64,' . $imageData;
  903. if($processImages->createImage($imageSrc, $image->getClientFilename(), $userSettings['images'], $overwrite = NULL))
  904. {
  905. # returns image path to media library
  906. $userInput[$fieldName] = $processImages->publishImage();
  907. }
  908. }
  909. }
  910. }
  911. return $userInput;
  912. }
  913. }