<?php

/**
 * Class ToolsController
 * A set of interesting tools.
 */
class ToolsController extends AppBackendController {

	/** @inheritdoc No model. */
	public $uses = false;

	/** @var string[] Actions allowed without logging in to CMS. */
	public $front = [ 'thumb', 'upload' ];

	/** @inheritdoc */
	public function beforeFilter() {
		parent::beforeFilter();

		// Set the base crumbs
		$this->crumbs[] = [
			__('Tools'), [ C => $this->request->params[C], A => 'phpinfo' ]
		];
	}

	/**
	 * Action: Clear the complete cache.
	 */
	public function clear() {
		$this->Administrator->clearCache();
		$this->backtrack('<a href="/clear" title="' . __('click to clear again') . '">' . __('Cache cleared') . '</a>');
	}

	/**
	 * Action: Refresh the access control lists.
	 */
	public function acl() {
		$this->Administrator->refreshAcl();
		$this->backtrack(__('Access control lists refreshed'));
	}

	/**
	 * Action: Show the PHP info.
	 *
	 * @param bool $echo True to print the info directly, false to show it inside CMS.
	 */
	public function phpinfo($echo = false) {
		if ($echo) {
			phpinfo();
			exit;
		}

		$this->crumbs[] = 'phpinfo';
	}

	/**
	 * Action: Show the phpMyAdmin.
	 */
	public function phpMyAdmin() {

		// Validate token
		if (!preg_match('~\bphpMyAdmin-' . SECURITY_PMA_TOKEN . '\b~', $this->request->url)) {
			$this->notFound();
		}

		$this->set('db', ConnectionManager::$config->default['database']);
		$this->crumbs[] = 'phpMyAdmin';
	}

	/**
	 * Action: Repair the MysQL table.
	 *
	 * @param string $model The name of the model whose table should be fixed.
	 */
	public function repair($model) {
		ClassRegistry::init($model)->repair();
		$this->backtrack(__('Table repaired'));
	}

	/**
	 * Action: Refresh upload cache fields.
	 *
	 * @param string $model The name of the model to refresh the image cache.
	 */
	public function repairImages($model) {
		ClassRegistry::init($model)->refreshUploadCache();
		$this->backtrack(__('Image cache refreshed'));
	}

	/**
	 * Action: Request a MySQL dump.
	 * The dump will be stored in the backups folder.
	 *
	 * @param bool $archive Set to true to skip the download of the dump, just archive it.
	 */
	public function dump($archive = false) {
		Cache::clear();
		$dbase = $this->Administrator->getDataSource()->config['database'];
		$result = $this->Administrator->query("SHOW TABLES FROM `{$dbase}`");
		$tables = Set::extract('/0', Set::classicExtract($result, '{n}.TABLE_NAMES.{s}'));
		$commands = [];
		foreach ($tables as $table) {
			$result = $this->Administrator->query("SHOW CREATE TABLE `{$table}`", false);
			$create = "DROP TABLE IF EXISTS `{$table}`;\n{$result[0][0]['Create Table']};";

			$result = $this->Administrator->query("SHOW COLUMNS FROM `{$table}`", false);
			$columns = Set::extract('/COLUMNS/Field', $result);

			$values = [];
			$result = $this->Administrator->query("SELECT * FROM `{$table}`", false);
			foreach ($result as $i => $name) {
				$row = [];
				$table = key($name);
				$fields = reset($name);
				foreach ($fields as $field => $value) {
					$row[] = $value === null ? 'NULL' : "'" . mysql_escape_string($value) . "'";
				}
				$values[] = '(' . implode(', ', $row) . ')';
			}

			if (!empty($values)) {
				$insert = "INSERT INTO `{$table}` (`" . implode('`, `', $columns) . '`) VALUES';
				$values = implode(",\n", $values) . ';';
			} else {
				$insert = $values = '';
			}

			$commands[] = $create . "\n\n/*!40000 ALTER TABLE `{$table}` DISABLE KEYS */;\n" . $insert . "\n" . $values . "\n/*!40000 ALTER TABLE `{$table}` ENABLE KEYS */;";
		}
		$time = date('Y-m-d_His');
		$dump = implode("\n\n\n", $commands);
		$folder = 'Backups';
		$filename = $folder . "/db_dump_{$time}";

		// Check if directory is writable
		if (!is_writable(APP . $folder)) {
			$this->backtrack(__('Backup folder is not writable'), 'danger', false);
		}

		// Ensure UTF8 is used and temporaly disable foreign key checks
		$dump = "
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET FOREIGN_KEY_CHECKS=0 */;

{$dump}

/*!40014 SET FOREIGN_KEY_CHECKS=1 */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;";

		// Compress the dump
		file_put_contents(APP . $filename . '.sql', $dump);
		$file = APP . $filename . '.zip';
		$zip = new ZipArchive();
		$zip->open($file, ZipArchive::OVERWRITE);
		$zip->addFile(APP . $filename . '.sql', basename($filename . '.sql'));
		$zip->close();
		unlink(APP . $filename . '.sql');

		// Download the compressed dump
		if (!$archive) {
			header('Content-Type: application/zip');
			header('Content-Length: ' . filesize($file));
			header('Content-Disposition: attachment; filename="' . basename($file) . '"');
			echo file_get_contents($file);
			exit;
		}

		return;
	}

	/**
	 * Action: Show the version history information.
	 *
	 * @param string $action Set to 'history' to show the version log, or to 'sql' to show the
	 *                       complete SQL log.
	 */
	public function version($action = 'history') {

		// Load the versions
		$this->set('versionLog', CMSVersion::getContents());

		// Default
		switch ($action) {
			case 'history':

				// Actions depends on the role of the user
				if ((int) $this->Auth->user('group_id') === GROUP_SUPERADMINS) {
					$this->addAction('database', __('Show complete SQL changes'), [ 'sql' ]);
				} else {
					$this->addAction('envelope', __('Request a feature'), 'mailto:support@intellex.rs');
				}

				// Show the view
				$this->crumbs[] = __('Version history');
				break;

			case 'sql':

				// Actions
				$this->addAction('history', __('History of changes'), [ '' ]);

				// Load the view
				$this->crumbs[] = [ __('Version history'), [ '' ] ];
				$this->crumbs[] = __('SQL log');
				$this->render('version_sql');
				break;

			default:
				$this->notFound();
		}
	}

	/**
	 * Action: Test push notifications from this server.
	 */
	public function push() {

		// Define crumbs and actions
		$this->addAction('users', __('Push subscribers'), [ P => null, C => 'push_subscribers', A => 'index' ]);
		$this->addAction('sliders', __('Configuration'), [ P => null, C => 'configs', A => 'index', '#' => 'Push' ]);
		$this->crumbs[] = __('Push notifications');

		// Get active
		$hasIos = (bool) Configure::read('Push.apple_gateway') ? Configure::read('Push.apple_production_certificate') : Configure::read('Push.apple_dev_certificate');
		$hasAndroid = (bool) Configure::read('Push.gcm_secret');
		$this->set(compact('hasIos', 'hasAndroid'));

		// Define special keys
		$keys = [ 'time', 'os', 'sandbox', 'registration_keys', 'time_to_live', 'collapse_key', 'badge', 'sound', 'alert', 'save-btn', $this->request->here ];
		foreach ($keys as $key) {
			$this->set($key, '');
		}

		// Default is android
		$os = $hasAndroid ? 'android' : 'ios';
		$this->set('os', $os);

		// Send
		if (!empty($_GET['registration_keys'])) {
			try {

				// Store special keys
				foreach ($keys as $key)
					if (isset($_GET[$key])) {
						$this->set($key, $$key = $_GET[$key]);
					}

				// Create message
				$payload = [];
				foreach ($_GET as $key => $value) {
					if (!in_array($key, $keys)) {
						$payload[$key] = $value;
					}
				}
				$this->set('payload', $payload);

				// Define push class
				$this->Push = ClassRegistry::init('PushSubscriber');

				// Sent to proper devices
				switch ($os) {
					case 'android':

						// Get the response
						$response = $this->Push->sendNotificationsToAndroid($payload, array_filter(explode("\n", trim($registration_keys))));
						break;

					case 'ios':

						// Handle defaults for alert, sound and badge
						if (empty($alert))
							$alert = null;
						if (empty($sound))
							$sound = null;
						if (empty($badge))
							$badge = null;

						// Send push
						$response = $this->Push->sendNotificationsToIOS($alert, $payload, array_filter(explode("\n", trim($registration_keys))), $sound, $badge);
						break;

					default:
						throw new Exception('Unknown device type');
				}

				$this->set('response', $response);

			} catch (Exception $e) {
				$this->set('error', $e->getMessage());
			}
		}
	}

	/**
	 * Action: Test the email templates.
	 */
	public function renderEmails() {

		// Make sure it is local
		if (!LOCALHOST) {
			$this->backtrack(__('Only available on localhost.'), 'danger', false);
			exit;
		}

		// If pushed
		$data = $this->request->data;
		if (!empty($data)) {

			// Validate template
			if (empty($data['template'])) {
				echo __('Please select a template');
				exit(1);
			}

			// Make sure the template exists
			$path = ROOT . DS . 'app' . DS . 'View' . DS . 'Emails' . DS . 'html' . DS . $data['template'] . '.ctp';
			if (!is_readable($path)) {
				echo __('Selected template does not exists: %s', $path);
				exit(2);
			}

			// Save to database
			ClassRegistry::init('ToolRenderEmail')->write($data['template'], $data['expression']);

			// Error reporting
			$reporting = "ini_set('display_errors', true);\nerror_reporting(E_ALL);\nConfigure::write('debug', true);\n";

			// Get the template
			$file = tempnam(sys_get_temp_dir(), "tre");
			file_put_contents($file, "<?php define('RENDER_EMAIL_TEST', true);\n\n{$reporting}\n\n{$data['expression']}\n\n?>\n" . str_replace('error_reporting', 'date', file_get_contents($path)));

			// Evaluate
			ini_set('display_errors', true);
			error_reporting(E_ALL);
			require $file;
			@unlink($file);
			exit(0);
		}

		// Get the list of email templates
		$path = DS . 'app' . DS . 'View' . DS . 'Emails' . DS . 'html' . DS;
		$templates = glob(ROOT . $path . '*.ctp');
		foreach ($templates as $i => $template) {
			$templates[$i] = pathinfo($template);
			$templates[$i]['expression'] = ClassRegistry::init('ToolRenderEmail')->getExpressionForTemplate($templates[$i]['filename']);
		}

		$this->addAction('home', __('Dasbboard'), [ P => null, C => 'dashboard' ]);
		$this->set(compact('path', 'templates'));
	}

}
