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