<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package clearFusionCMS
 * @copyright Copyright (c) 2012 - 2016 clearFusionCMS. All rights reserved.
 * @link http://clearfusioncms.com
 */

/**
 * cfBackendSetthemeAction Class
 *
 * The action class to set the site theme.
 *
 * @package clearFusionCMS
 */
class cfBackendSetthemeAction extends cfBackendControllerAction {
	/**
	 * Unpack a theme zip file.
	 *
	 * @param string $fileName The name of the file holding the theme to unpack.
	 * @return string The result string to show to the user.
	 */
	protected function unpackTheme($fileName) {
		$zip = new ZipArchive();
		if($zip->open($fileName) === true && $zip->numFiles > 1) {

			// Get the theme name
			$themeName = rtrim($zip->getNameIndex(0), '/');

			// Calc path and name of theme info
			$themeInfo = "$themeName/{$themeName}.ini";

			// Test if the theme info is available in the archive
			$isTheme = false;
			for($i = 0; $i < $zip->numFiles; $i++) {
				if($themeInfo == $zip->getNameIndex($i)) {
					$isTheme = true;
					break;
				}
			}

			// If theme in archive then unpack it
			if($isTheme) {
				if($zip->extractTo(FLTHEMEPATH)) {
					$msg = $this->alertMessage('Success', __('The theme archive has been extracted.', 'cfBackend'));

					$tplMgr = new flTemplateManager($this->db);
					$info = $tplMgr->getThemeInfo($themeName);
					cfThemeAudit::installed($info->name, FLTHEMEPATH . $themeName . '/');
				}
				else
					$msg = $this->alertMessage('Error', __('Unable to extract the theme archive.', 'cfBackend'));
			}
			else
				$msg = $this->alertMessage('Error', __('The file does not appear to be a valid theme archive.', 'cfBackend'));

			$zip->close();
		}
		else
			$msg = $this->alertMessage('Error', __('The file does not appear to be a valid container for a theme.', 'cfBackend'));

		return $msg;
	}

	/**
	 * Delete a folder and all child folders and files.
	 *
	 * @param string $folder The folder to delete
	 */
	protected function delFolder($folder) {
		$files = glob($folder . '/*');

		foreach($files as $f) {
			if(is_dir($f)) {
				$this->delFolder($f);
				rmdir($f);
			}
			else
				unlink($f);
		}
	}

	/**
	 * Process the page.
	 */
	function process() {
		$this->testPermission('cfContent.elemtheme');
		$this->breadcrumbTrail->addCrumb(__('Set Theme', 'cfBackend'), $this->assemble('', 'settheme'));

		// Build form for uploading themes
		$uploadMsg = '';
		$formUpld = new flForm($this->request, 'uploadthemeform', $this->assemble('', 'settheme'), true);
		if(is_writable(FLTHEMEPATH) && is_writable(sys_get_temp_dir())) {
			$file = new flFormElementFile($formUpld, 'zipfile');
			$file->setLabel(__('Theme ZIP File', 'cfBackend'))->setCSS('primary')
				->setDescription(__('Choose the theme ZIP file to upload.', 'cfBackend'));

			// Calc max upload size
			$maxUpload = (int)ini_get('upload_max_filesize');
			$maxPost = (int)ini_get('post_max_size');
			$memoryLimit = (int)ini_get('memory_limit');
			$uploadSize = min($maxUpload, $maxPost, $memoryLimit);

			$uri = new flFormElementUri($formUpld, 'uri');
			$uri->setLabel(__('Download Theme From', 'cfBackend'))
				->setDescription(__('The web address to download the theme ZIP file from.', 'cfBackend'));

			$submit = new flFormElementSubmit($formUpld, 'Upload');
			$submit->setCaption(__('Install Theme', 'cfBackend'))->setCSS('primary')
				->setDescription(sprintf(__('Maximum file upload size is %dMBytes.', 'cfBackend'), $uploadSize));
		}
		else
			new flFormElementPane($formUpld, 'info', $this->alertMessage('Info', __('You do not have permission to write to the theme folder via PHP therefore the install feature has been disabled.', 'cfBackend')));

		if($formUpld->isSubmitted() && $formUpld->isValid()) {
			if($file->fileUploaded())
				$uploadMsg = $this->unpackTheme($file->getTmpFileName());
			else if($uri->getValue() != '') {
				$client = new flHttpClient($uri->getValue());
				if($client->sendRequest() && $client->getResponseCode() == 200) {
					$fileName = tempnam(sys_get_temp_dir(), 'cf_');

					// Save the file
					if(file_put_contents($fileName, $client->getBody())) {
						$uploadMsg = $this->unpackTheme($fileName);
						unlink($fileName);
					}
					else
						$uploadMsg = $this->alertMessage('Error', __('Failed to write theme archive to disk.', 'cfBackend'));
				}
				else
					$uploadMsg = $this->alertMessage('Error', __('Theme archive failed to download.', 'cfBackend'));
			}
			else
				$uploadMsg = $this->alertMessage('Error', __('Please select a theme ZIP file from your computer or enter the address to download the theme from and try again.', 'cfBackend'));
		}

		$cfg = clearFusionCMS::getSiteConfig('cfSystemSite');
		$tplMgr = new flTemplateManager($this->db);

		// Handle enable/disable
		$isShowingForm = false;
		$data = $this->request->getParam('*');
		if(count($data) == 2 && preg_match('/^[a-z0-9_.]+$/', $data[1])) {
			if($data[0] == 'enable') {
				$info = $tplMgr->getThemeInfo($data[1]);
				cfThemeAudit::enabled($info->name, [
					'theme' => $cfg->siteTheme,
					'childTheme' => $cfg->siteChildTheme
				]);
				$cfg->siteTheme = $data[1];
				$cfg->siteChildTheme = '';

				$info = $tplMgr->getThemeInfo($data[1], false);
				$cfg->siteThemeCSSFramework = trim($info->get('cssframework', ''));

				// Add any custom fields to the system (Note we don't remove them just in case)
				$fields = new cfCustomFields($this->db);
				foreach($tplMgr->getThemeFields($data[1]) as $template => $flds) {
					foreach($flds as $def) {
						$f = $fields->getField($def['name']);
						// Field exists so add to template
						if($f) {
							if(!in_array($def['name'], $f['templates'])) {
								$f['templates'][] = $template;
								$fields->updateField($f['placeholder'], $f['placeholder'], $f['fldtype'], $f['caption'], $f['description'], $f['editperm'], $f['catid'], $f['disporder'], $f['templates'], $f['validation'], $f['ignoregroup']);
							}
						}
						// Create the field
						else
							$fields->createField($def['name'], $def['type'], $def['caption'], '', '', 0, 0, array($template), array(), false);
					}
				}
				$this->cache->flush();
				$this->response->redirectTo($this->assemble('', 'settheme', array(), true));
			}
			else if($data[0] == 'disable') {
				$info = $tplMgr->getThemeInfo($cfg->siteTheme);
				cfThemeAudit::disabled($info->name, [
					'theme' => $cfg->siteTheme,
					'childTheme' => $cfg->siteChildTheme
				]);
				if($cfg->siteTheme == $data[1])
					$cfg->siteTheme = $cfg->siteChildTheme = '';
				$this->cache->flush();
				$this->response->redirectTo($this->assemble('', 'settheme', array(), true));
			}
			else if($data[0] == 'uninstall' && is_writable(FLTHEMEPATH)) {
				$this->breadcrumbTrail->addCrumb(__('Uninstall', 'cfBackend'), $this->assemble('', 'settheme', array('uninstall', $data[1])));
				$isShowingForm = $data[1];

				$info = $tplMgr->getThemeInfo($data[1]);

				$form = new flForm($this->request, 'removetheme', $this->assemble('', 'settheme', array('uninstall', $data[1])), true);
				$modname = new flFormElementText($form, 'themename', $info->name);
				$modname->setLabel(__('Theme Name', 'cfBackend'))
					->setReadonly()
					->setDisabled();

				$delSubmit = new flFormElementSubmit($form, 'deletetheme');
				$delSubmit->setCaption(__('Uninstall Theme &amp; Permanently Delete All Files', 'cfBackend'))
					->setCSS('danger')
					->setDescription($this->alertMessage('Warning', __('This action cannot be undone and will delete all files associated with the theme!', 'cfBackend')));

				$msg = '';
				if($form->isSubmitted() && $form->isValid()) {
					cfThemeAudit::removed($info->name, FLTHEMEPATH . $data[1]);

					if($cfg->siteTheme == $data[1])
						$cfg->siteTheme = $cfg->siteChildTheme = '';
					$this->delFolder(FLTHEMEPATH . $data[1]);
					@rmdir(FLTHEMEPATH . $data[1]);
					$this->cache->flush();
					$this->response->redirectTo($this->assemble('', 'settheme', array(), true));
				}

				$content = $this->renderBlock(__('Uninstall Theme', 'cfBackend'), $form->render($this->tpl->getCodeBuffer()), '', $msg);
			}
			else if($data[0] == 'view') {
				$this->breadcrumbTrail->addCrumb(__('View Files', 'cfBackend'), $this->assemble('', 'settheme', array('view', $data[1])));
				$isShowingForm = true;

				// Build a list of the files within the template
				$list = array();
				$l = strlen(realpath(FLTHEMEPATH . $data[1])) + 1;
				$files = glob(FLTHEMEPATH . "$data[1]/*");
				foreach($files as $f) {
					if(is_dir($f)) {
						$files2 = glob("$f/*");
						foreach($files2 as $f2) {
							if(!is_dir($f2) && preg_match('/\.(css|html|txt|ini|php|less)$/', $f2))
								$list[] = substr($f2, $l);
						}
					}
					else if(preg_match('/\.(css|html|txt|ini|php|less)$/', $f))
						$list[] = substr($f, $l);
				}

				$form = new flForm($this->request, 'themefile', $this->assemble('', 'settheme', array('view', $data[1])), true, null, new flFormDecoratorList(new flFormDecoratorDl()));
				$file = new flFormElementSelect($form, 'file', array_search('README.txt', $list), $list);
				$file->setLabel(__('File', 'cfBackend'))
					->setJavaScript('$fl("#file").bind("change", function() {
	$fl("#themefile").submit();
})');

				$code = new flFormElementWYSIWYG($form, 'code');
				$code->setLabel(__('File Contents', 'cfBackend'))
					->setReadonly()
					->setRows(20)
					->editorSize('100%', '450')
					->setDB($this->db)
					->editorModule($this->getCodeEditorModule())
					->editorContentType('text');

				$submit = new flFormElementSubmit($form, 'view');
				$submit->setCaption(__('View File', 'cfBackend'))->setCSS('primary');

				$form->isSubmitted() && $form->isValid();
				if(is_readable($f = FLTHEMEPATH . "$data[1]/" . ($t = $list[$file->getValue()]))) {
					$code->setValue(file_get_contents($f));
					if(preg_match('/\.html$/', $t))
						$code->editorContentType('chunk');
					else if(preg_match('/\.php$/', $t))
						$code->editorContentType('snippet');
					else if(preg_match('/\.css/', $t) || preg_match('/\.less/', $t))
						$code->editorContentType('css');
				}

				$info = $tplMgr->getThemeInfo($data[1]);
				$content = $this->renderBlock(sprintf(__('View Files in Theme %s', 'cfBackend'), $info->name), $form->render($this->tpl->getCodeBuffer()), '', '');
			}
		}

		if($isShowingForm === false) {
			// Build sub theme select form
			$themeList = array('' => __('[default]', 'cfBackend')) + $tplMgr->getChildThemeList($cfg->siteTheme);
			$msg = '';

			$form = new flForm($this->request, 'themeform', $this->assemble('', 'settheme'), true);

			if(empty($cfg->siteTheme)) {
				$cssFramework = new flFormElementSelect($form, 'cssframework', $cfg->get('siteThemeCSSFramework', ''), array(
					'' => 'fusionCSS / Other',
					'bootstrap3' => 'Bootstrap 3',
					'foundation5' => 'Foundation 5 or 6'
				));
				$cssFramework->setLabel('Using CSS Framework');
			}

			$theme = new flFormElementSelect($form, 'theme', $cfg->get('siteChildTheme', ''), $themeList);
			$theme->setLabel(__('Use Child Theme', 'cfBackend'));

			$submit = new flFormElementSubmit($form, 'update');
			$submit->setCaption(empty($cfg->siteTheme) ? __('Update', 'cfBackend') : __('Set Child Theme', 'cfBackend'))->setCSS('primary');

			$form->focusElement($theme);

			// Build the list of available templates
			$installed = new flWidgetTable();
			$installed->setClass('tablewidget bordered striped rowhover responsive')
				->setHeadings('', __('Description', 'cfBackend'), __('Operation', 'cfBackend'))
				->setColData('icon', 'description', 'ops')
				->skipEncode('ops', 'icon', 'description');

			// If no themes then running off internal templates
			$foundTheme = false;
			if($cfg->siteTheme == '') {
				$row['icon'] = '';
				$row['description'] = '<strong>' . fusionLib::htmlChars(__('Using Internal Templates Only', 'cfBackend')) . '</strong><br />' . fusionLib::htmlChars(__('The system is only using the templates defined under Elements.', 'cfBackend')) . $form->render($this->tpl->getCodeBuffer());
				$row['ops'] = '';
				$installed->addRow($row);
				$foundTheme = true;
			}

			// Move active theme first
			$tl = $tplMgr->getThemeList();
			if(isset($tl[$cfg->siteTheme]))
				$tl = array($cfg->siteTheme => $tl[$cfg->siteTheme]) + $tl;

			// Draw list of installed
			foreach($tl as $themeCode => $themeName) {
				$info = $tplMgr->getThemeInfo($themeCode);

				$row['icon'] = "<img src=\"system/themes/$themeCode/$themeCode.png\" alt=\"thumbnail\" />";
				$row['description'] = '<strong>' . fusionLib::htmlChars($themeName . ' ' . $info->get('version', '')) . '</strong><br />' . fusionLib::htmlChars($info->get('description', ''));
				$row['description'] .= '<br /><br />CSS Framework: ' . $info->get('cssframework', 'fusionCSS');
				if($cfg->siteTheme == $themeCode) {
					$foundTheme = true;
					$row['ops'] = '<a href="' . $this->assemble('', 'settheme', array('disable', $themeCode)) . '" class="modulelistuninstall danger">' . __('Disable', 'cfBackend') . '</a>';
					$row['description'] .= $form->render($this->tpl->getCodeBuffer());
				}
				else {
					$row['ops'] = '<a href="' . $this->assemble('', 'settheme', array('enable', $themeCode)) . '" class="modulelistinstall">' . __('Enable', 'cfBackend') . '</a>';
					if(is_writable(FLTHEMEPATH) && is_writable(sys_get_temp_dir()))
						$row['ops'] .= ' <a href="' . $this->assemble('', 'settheme', array('uninstall', $themeCode)) . '" class="modulelistuninstall danger">' . __('Uninstall', 'cfBackend') . '</a>';
				}
				$row['ops'] .= ' <a href="' . $this->assemble('', 'settheme', array('view', $themeCode)) . '">' . __('View Files', 'cfBackend') . '</a>';
				$installed->addRow($row);
			}

			if(!$foundTheme) {
				$cfg->siteTheme = $cfg->siteChildTheme = '';
				$this->response->redirectTo($this->assemble('', 'settheme', array(), true));
			}

			if($form->isSubmitted() && $form->isValid()) {
				cfThemeAudit::childThemeChanged($theme->getValue(), $cfg->siteChildTheme);
				$cfg->siteChildTheme = $theme->getValue();
				if(isset($cssFramework)) {
					cfThemeAudit::cssChanged($cssFramework->getValue(), $cfg->siteThemeCSSFramework);
					$cfg->siteThemeCSSFramework = $cssFramework->getValue();
				}

				$this->cache->flush();
				$msg = $this->alertMessage('Success', __('The new theme has been set.', 'cfBackend'));
			}

			$content = $this->renderBlock(__('Select the Theme to Use', 'cfBackend'), $installed->render(), '', $msg);
		}

		// Draw the page
		$this->renderPage(__('Set Theme', 'cfBackend'),
			$content,
			$this->renderBlock(__('Install Theme', 'cfBackend'), $formUpld->render($this->tpl->getCodeBuffer()), '', $uploadMsg));
	}
}
