/** * Constructor * Makes sure the cache directory is set * * @return object */ public function __construct() { $this->cacheDir = $this->_zula->getDir('tmp') . '/cache'; if (!is_dir($this->cacheDir)) { zula_make_dir($this->cacheDir); } if (!zula_is_writable($this->cacheDir)) { throw new Cache_Exception($this->cacheDir . ' is not writable'); } }
/** * Main method that is called on all loggers from * the Log class. * * @param string $message * @param int $level * @param string $file * @param int $line * @return bool */ public function logMessage($msg, $level, $file = 'unknown', $line = 0) { $fileName = $this->makeFileName($level); $filePath = $this->logDir . '/' . $fileName; if (!zula_is_writable($this->logDir)) { return false; } $uid = Registry::has('session') ? $this->_session->getUserId() : 'unknown'; $msgFormat = '[%1$s] [%2$s | uid %3$s] [%4$s] -- (%5$s:%6$d) %7$s' . "\r\n"; $entry = sprintf($msgFormat, date('c'), zula_get_client_ip(), $uid, $this->levelName($level), basename($file), $line, $msg); return error_log($entry, 3, $filePath); }
/** * Check the environment the installer is running in to * ensure all required PHP extensions exist and the needed * files/directories are writable * * @return bool|string */ public function indexSection() { $this->setTitle(t('Pre-installation checks')); $checks = array('exts' => array('title' => t('Required PHP extensions'), 'passed' => false, 'values' => array('ctype', 'date', 'dom', 'filter', 'hash', 'pdo', 'pdo_mysql', 'pcre', 'session', 'json')), 'optExts' => array('title' => t('Optional PHP extensions'), 'passed' => true, 'values' => array('gd', 'FileInfo')), 'files' => array('title' => t('Writable files'), 'passed' => false, 'values' => array($this->_zula->getDir('config') . '/config.ini.php', $this->_zula->getDir('config') . '/layouts/admin-default.xml', $this->_zula->getDir('config') . '/layouts/main-default.xml', $this->_zula->getDir('config') . '/layouts/fpsc-admin.xml', $this->_zula->getDir('config') . '/layouts/fpsc-main.xml')), 'dirs' => array('title' => t('Writable directories'), 'passed' => false, 'values' => array($this->_zula->getDir('config') . '/layouts', $this->_zula->getDir('logs'), $this->_zula->getDir('tmp'), $this->_zula->getDir('uploads'), $this->_zula->getDir('locale')))); // Run the various checks $passed = true; foreach ($checks as $name => $details) { $results = array(); foreach ($details['values'] as $val) { switch ($name) { case 'exts': case 'optExts': $results[$val] = extension_loaded($val); break; case 'files': case 'dirs': $results[$val] = zula_is_writable($val); } } if ($name != 'optExts' && in_array(false, $results, true)) { $passed = false; $checks[$name]['passed'] = false; } else { $checks[$name]['passed'] = true; } $checks[$name]['values'] = $results; } if ($passed) { if ($this->_zula->getMode() !== 'cli') { $_SESSION['installStage'] = 2; } $this->_event->success(t('Pre-installation checks were successful')); return zula_redirect($this->_router->makeUrl('install', 'sql')); } else { if ($this->_zula->getMode() == 'cli') { $this->_zula->setExitCode(3); } $this->_event->error(t('Sorry, your server environment does not meet our requirements')); $view = $this->loadView('checks' . ($this->_zula->getMode() == 'cli' ? '-cli.txt' : '.html')); $view->assign(array('checks' => $checks, 'passed' => $passed)); return $view->getOutput(); } }
/** * Checks which directories and files are writable to the user * the script is running as * * @return string */ public function writableSection() { $this->setTitle(t('Writable files/directories')); $this->setOutputType(self::_OT_INFORMATIVE); // Generate array of all files/dirs $items = array(); $directoryIterator = new RecursiveDirectoryIterator($this->_zula->getDir('config')); foreach (new RecursiveIteratorIterator($directoryIterator) as $file) { if (substr($file->getFileName(), 0, 1) != '.' && $file->isFile()) { $items['files'][] = array('path' => $file->getPathName(), 'result' => zula_is_writable($file->getPathName())); } } // Dirs $dirs = array($this->_zula->getDir('config') . '/layouts', $this->_zula->getDir('logs'), $this->_zula->getDir('tmp'), $this->_zula->getDir('uploads'), $this->_zula->getDir('locale')); foreach ($dirs as $dir) { $items['dirs'][] = array('path' => $dir, 'result' => zula_is_writable($dir)); } $view = $this->loadView('index/writable.html'); $view->assign(array('DIRS' => $items['dirs'], 'FILES' => $items['files'])); return $view->getOutput(); }
/** * Gets all available cache types that can be used with * the current environment * * @return array */ public static function getAvailable() { $types = array(); foreach (new DirectoryIterator(Registry::get('zula')->getDir('libs') . '/Cache') as $file) { if (!$file->isDot() && substr($file, 0, 1) != '.') { $extension = null; $type = basename($file, '.php'); // Ensure all extensions are there to be used switch ($type) { case 'Disabled': case 'Tmp': break; case 'Apc': $extension = 'apc'; break; case 'Eaccelerator': $extension = 'eaccelerator'; if (!function_exists('eaccelerator_put')) { continue 2; } break; case 'File': if (!zula_is_writable(Registry::get('zula')->getDir('tmp') . '/cache')) { continue 2; } break; case 'Memcached': $extension = 'memcached'; break; default: continue 2; } if (!isset($extension) || extension_loaded($extension)) { $types[] = $type; } } } return $types; }
/** * Pre-upgrade checks to ensure the environment is how we * require it. * * @return bool|string */ public function indexSection() { $this->setTitle(t('Pre-upgrade checks')); if ($this->_zula->getMode() != 'cli' && (!isset($_SESSION['upgradeStage']) || $_SESSION['upgradeStage'] !== 3)) { return zula_redirect($this->_router->makeUrl('upgrade', 'version')); } /** * All the checks that need to be run, and then actualy run the needed checks */ $tests = array('files' => array($this->_zula->getConfigPath() => ''), 'dirs' => array($this->_zula->getDir('config') => '')); $passed = true; foreach ($tests as $type => &$items) { foreach ($items as $itemName => $status) { $writable = zula_is_writable($itemName); $items[$itemName] = $writable; if ($writable === false) { $passed = false; } } } if ($passed) { if (isset($_SESSION['upgradeStage'])) { ++$_SESSION['upgradeStage']; } $this->_event->success(t('Pre-upgrade checks were successful')); return zula_redirect($this->_router->makeUrl('upgrade', 'migrate')); } else { if ($this->_zula->getMode() == 'cli') { $this->_zula->setExitCode(3); } $this->_event->error(t('Sorry, your server environment does not meet our requirements')); $view = $this->loadView('checks' . ($this->_zula->getMode() == 'cli' ? '-cli.txt' : '.html')); $view->assign(array('file_results' => $tests['files'], 'dir_results' => $tests['dirs'], 'passed' => $passed)); return $view->getOutput(); } }
/** * Update the settings based on the post-data provided * * @param string $name * @param array $args * @return string */ public function __call($name, $args) { $name = substr($name, 0, -7); if (!$this->_acl->check('settings_update')) { throw new Module_NoPermission(); } else { if (!in_array($name, $this->categories)) { throw new Module_ControllerNoExist(); } else { if (!$this->_input->checkToken()) { $this->_event->error(Input::csrfMsg()); return zula_redirect($this->_router->makeUrl('settings', $name)); } } } $this->setTitle(t('Update settings')); // Update all of the provided settings, or insert if they don't exist foreach ($this->_input->post('setting') as $key => $val) { if (strpos($key, 'cache') !== 0) { if (substr($key, 8, 9) == 'mail/smtp' && !$this->_acl->check('settings_access_smtp')) { continue; } try { $this->_config_sql->update($key, $val); } catch (Config_KeyNoExist $e) { $this->_sql->insert('config', array('name' => $key, 'value' => $val)); } } } /** * Category specific things to do when updating * the settings or other things (ACL forms etc). */ switch ($name) { case 'general': $this->_cache->delete('view_default_tags'); break; case 'cache': try { $this->_config_ini->update('cache/type', $this->_input->post('setting/cache\\/type')); $this->_config_ini->update('cache/ttl', $this->_input->post('setting/cache\\/ttl')); $this->_config_ini->update('cache/js_aggregate', $this->_input->post('setting/cache\\/js_aggregate')); $this->_config_ini->update('cache/google_cdn', $this->_input->post('setting/cache\\/google_cdn')); $this->_config_ini->writeIni(); // Clear cache if needbe if ($this->_input->post('cache_purge')) { $this->_cache->purge(); } } catch (Exception $e) { $this->_event->error($e->getMessage()); $this->_log->message($e->getMessage(), Log::L_WARNING); } break; case 'locale': try { $this->_config_ini->update('locale/default', $this->_input->post('setting/locale\\/default')); $this->_config_ini->writeIni(); } catch (Exception $e) { $this->_event->error($e->getMessage()); $this->_log->message($e->getMessage(), Log::L_WARNING); } if (($pkg = $this->_input->post('lang_pkg')) !== 'none') { // Download and install a new locale if (!zula_supports('zipExtraction')) { $this->_event->error(t('Cannot install locale, server does not support zip extraction')); } else { if (!preg_match('#^[a-z]{2}_[A-Z]{2}$#', $pkg)) { $this->_event->error(t('Provided locale is invalid, unable to install')); } else { if (!zula_is_writable($this->_zula->getDir('locale'))) { $this->_event->error(t('Locale directory is not writable, unable to install')); } else { $version = str_replace('-', '/', zula_version_map(_PROJECT_VERSION)); $zipDest = $this->_zula->getDir('tmp') . '/i18n-' . $pkg . '.zip'; $copyResult = @copy('http://releases.tangocms.org/' . $version . '/i18n/' . $pkg . '.zip', $zipDest); if ($copyResult) { // Extract the archive to the locale dir $zip = new ZipArchive(); if ($zip->open($zipDest)) { $zip->extractTo($this->_zula->getDir('locale')); $zip->close(); $this->_event->success(t('Locale successfully installed')); } else { $this->_event->error(t('Could not install locale, zip extraction failed')); } unlink($zipDest); } else { $this->_event->error(t('Failed to get remote language archive')); } } } } } break; } $this->_event->success(t('Updated settings')); return zula_redirect($this->_router->makeUrl('settings', $name)); }
/** * Allows for shorter URLs. Selects a CSS file to edit, will use * the first CSS file if none is specified * * @param string $name * @param array $args * @return string|bool */ public function __call($name, $args) { if (!$this->_acl->check('theme_view_css')) { throw new Module_NoPermission(); } $this->setTitle(t('Edit CSS files')); $this->setOutputType(self::_OT_CONFIG); /** * Gather details of the theme and check if the CSS file exists */ try { $theme = new Theme(substr($name, 0, -7)); $this->setTitle(sprintf(t('"%s" stylesheets'), $theme->getDetail('title'))); // All CSS files for this theme $themeCssFiles = $theme->getAllCss(); if (empty($themeCssFiles)) { $this->_event->error(t('There are no CSS files available')); return zula_redirect($this->_router->makeUrl('theme')); } else { try { $selectedCss = $this->_router->getArgument('file'); } catch (Router_ArgNoExist $e) { $cssDetails = reset($themeCssFiles); $selectedCss = $cssDetails['name']; } if (isset($themeCssFiles[$selectedCss])) { $cssDetails = $themeCssFiles[$selectedCss]; } else { // Eek, the CSS file does not exist! $this->_event->error(sprintf(t('Stylesheet "%1$s" does not exist'), $selectedCss)); return zula_redirect($this->_router->makeUrl('theme', 'css', $theme->getDetail('name'))); } } } catch (Theme_NoExist $e) { $this->_event->error(t('Theme does not exist')); return zula_redirect($this->_router->makeUrl('theme')); } /** * Setup the form and validation for the contents */ $form = new View_form('css.html', 'theme'); $form->addElement('theme/body', file_get_contents($cssDetails['path']), t('Stylesheet'), new Validator_length(0, 10000)); if ($form->hasInput() && $form->isValid()) { if (!$this->_acl->check('theme_edit_css')) { throw new Module_NoPermission(); } else { if (zula_is_writable($cssDetails['path'])) { file_put_contents($cssDetails['path'], $form->getValues('theme/body')); $this->_event->success(t('Stylesheet updated')); return zula_redirect($this->_router->makeUrl('theme', 'css', $theme->getDetail('name'), null, array('file' => $cssDetails['name']))); } else { $this->_event->error(sprintf(t('CSS file "%s" is not writable'), $cssDetails['path'])); } } } // Assign other data to be provided to the view file $this->addAsset('js/editor.js'); $this->_theme->addCssFile('jquery.linedtextarea.css'); $this->_theme->addJsFile('jquery.linedtextarea'); $form->assign(array('CSS_FILES' => $themeCssFiles, 'CSS_NAME' => $selectedCss, 'THEME' => array('NAME' => $theme->getDetail('name')))); return $form->getOutput(); }
/** * Zula Exception Handler for all uncaught exceptions * * @param object $exception * @return bool */ public function exceptionHandler(Exception $e) { $formatCode = sprintf('ZULA-%03d', $e->getCode()); if (!Registry::has('error')) { try { $this->loadLib('error'); } catch (Exception $e) { } } /** * Create the correct title for the exception */ if ($e instanceof Zula_Exception) { $title = 'Uncaught internal (Zula) exception'; } else { if ($e instanceof PDOException) { $title = 'Uncaught SQL (PDO) exception'; } else { $title = 'Uncaught application exception'; } } // Attempt to write a dump log file $logDir = $this->getDir('logs'); if (zula_is_writable($logDir)) { $i = 1; do { $file = $logDir . '/zula-dump.' . $i . '.log'; $i++; } while (file_exists($file)); $body = 'Uncaught exception in "' . $e->getFile() . '" on line ' . $e->getLine() . ' with code "' . $formatCode . '"'; $body .= "\n\nProject Version: " . (defined('_PROJECT_VERSION') ? _PROJECT_VERSION : 'unknown'); $body .= "\nTime & Date: " . date('c'); $body .= "\nRequest Method: " . (PHP_SAPI == 'cli' ? 'cli' : $_SERVER['REQUEST_METHOD']); if (Registry::has('router')) { $body .= "\nRequest Path: " . Registry::get('router')->getRequestPath(); $body .= "\nRaw Request Path: " . Registry::get('router')->getRawRequestPath(); } $body .= "\nException Thrown: " . get_class($e); $body .= "\nMessage: " . $e->getMessage(); $body .= "\n\nStack Trace:\n"; $body .= $e->getTraceAsString(); file_put_contents($file, $body); } if (Registry::has('error')) { return Registry::get('error')->report($e->getMessage(), E_USER_ERROR, $e->getFile(), $e->getLine(), $title); } else { trigger_error($e->getMessage(), E_USER_ERROR); } }
/** * Checks if a file can be deleted * * @param string $file * @return bool */ function zula_is_deletable($file) { return zula_is_writable(dirname($file)); }
/** * Takes the provided destination and checks that it does not exist first * if so, remove it - and check that directory is writable * * @param string $destination * @return string */ protected function prepareDestination($destination) { if ($destination == false) { $destination = $this->dirname . '/' . $this->filename; switch ($this->mime) { case 'image/jpeg': case 'image/jpg': $destination .= '.jpg'; break; case '': case 'image/png': $destination .= '.png'; break; case 'image/gif': $destination .= '.gif'; break; default: $destination .= '.' . $this->extension; } } $directory = dirname($destination); if (file_exists($destination)) { if (!@unlink($destination)) { throw new Image_SaveFailed($destination . ' already exists and could not be removed'); } } else { if (!zula_make_dir($directory)) { throw new Image_SaveFailed($directory . ' directory could not be created'); } } if (!zula_is_writable($directory)) { throw new Image_SaveFailed($directory . ' is not writable'); } return $destination; }
/** * Save the layout file and updates/inserts SQL entry if needed * * @param string $path * @return bool */ public function save($path = null) { $path = trim($path) ? $path : $this->path; if ((file_exists($path) && zula_is_writable($path) || !file_exists($path) && zula_is_writable(dirname($path))) && $this->dom->save($path)) { if (Registry::has('sql')) { if ($regex = $this->getRegex()) { $pdoSt = $this->_sql->prepare('INSERT INTO {PREFIX}layouts (name, regex) VALUES (?, ?) ON DUPLICATE KEY UPDATE regex = VALUES(regex)'); $pdoSt->execute(array($this->name, $this->getRegex())); } else { $pdoSt = $this->_sql->prepare('DELETE FROM {PREFIX}layouts WHERE name = ?'); $pdoSt->execute(array($this->name)); } } return true; } else { return false; } }
/** * Rewrites the configuration ini file back, leaving it as * in-take as possible (ie, keeping all comments) in place * and same sort of structure. * * @return bool */ public function writeIni() { if (!zula_is_writable($this->iniFile)) { throw new Config_ini_FileNotWriteable($this->iniFile . ' is not writeable'); } $iniContent = ''; /** * Open the file and read line by line, rewriting it as * it goes a long */ $fHandle = fopen($this->iniFile, 'rb'); $sections = array(); # Store the sections and values which have been written while (!feof($fHandle)) { $line = trim(fgets($fHandle)) . "\n"; if (zula_substr($line, 0, 1) == ';') { // Line is a comment $iniContent .= $line; } else { if (zula_substr($line, 0, 1) == '[') { preg_match('#\\[(.*?)\\]#', $line, $matches); try { $values = $this->get($matches[1]); $sections[] = $matches[1]; $iniContent .= $matches[0] . "\n"; foreach ($values as $key => $val) { if (preg_match('#[^A-Z0-9_\\-./]#i', $val)) { $val = '"' . $val . '"'; } $iniContent .= $key . ' = ' . $val . "\n"; } // Add a spacer to the bottom $iniContent .= "\n"; } catch (Config_KeyNoExist $e) { continue; } } } } /** * Add on the extra values that need to be added to the ini file */ foreach ($this->getAll() as $section => $values) { if (empty($values)) { continue; # No need to add empty sections in } else { if (!in_array($section, $sections)) { $iniContent .= '[' . $section . "]\n"; foreach ($values as $key => $val) { $val = (string) $val; if (preg_match('#[^A-Z0-9_\\-./]#i', $val)) { $val = '"' . $val . '"'; } $iniContent .= $key . ' = ' . $val . "\n"; } } } } file_put_contents($this->iniFile, trim($iniContent)); return true; }