<?php
/**
* @brief themeEditor, a plugin for Dotclear 2
*
* @package Dotclear
* @subpackage Plugins
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
if (!defined('DC_RC_PATH')) {
return;
}
class dcThemeEditor
{
protected $core;
protected $user_theme;
protected $parent_theme;
protected $tplset_theme;
protected $parent_name;
protected $tplset_name;
protected $tpl_model;
public $tpl = [];
public $css = [];
public $js = [];
public $po = [];
public $php = [];
public function __construct($core)
{
$this->core = &$core;
$this->user_theme = path::real($this->core->blog->themes_path . '/' . $this->core->blog->settings->system->theme);
$this->tplset_theme = DC_ROOT . '/inc/public/default-templates/' . DC_DEFAULT_TPLSET;
$this->tplset_name = DC_DEFAULT_TPLSET;
if (null !== $this->core->themes) {
$parent_theme = $this->core->themes->moduleInfo($this->core->blog->settings->system->theme, 'parent');
if ($parent_theme) {
$this->parent_theme = path::real($this->core->blog->themes_path . '/' . $parent_theme);
$this->parent_name = $parent_theme;
}
$tplset = $this->core->themes->moduleInfo($this->core->blog->settings->system->theme, 'tplset');
if ($tplset) {
$this->tplset_theme = DC_ROOT . '/inc/public/default-templates/' . $tplset;
$this->tplset_name = $tplset;
}
}
$this->findTemplates();
$this->findStyles();
$this->findScripts();
$this->findLocales();
$this->findCodes();
}
public function filesList($type, $item = '%1$s', $split = true)
{
$files = $this->getFilesFromType($type);
if (empty($files)) {
return '<p>' . __('No file') . '</p>';
}
$list = '';
if ($split) {
$list_theme = ''; // Files from current theme
$list_parent = ''; // Files from parent of current theme
$list_tpl = ''; // Files from template set used by current theme
foreach ($files as $k => $v) {
if (strpos($v, $this->user_theme) === 0) {
$li = sprintf('<li class="default-file">%s</li>', $item);
$list_theme .= sprintf($li, $k, html::escapeHTML($k));
} elseif ($this->parent_theme && strpos($v, $this->parent_theme) === 0) {
$li = sprintf('<li class="parent-file">%s</li>', $item);
$list_parent .= sprintf($li, $k, html::escapeHTML($k));
} else {
$li = sprintf('<li>%s</li>', $item);
$list_tpl .= sprintf($li, $k, html::escapeHTML($k));
}
}
$list .= ($list_theme != '' ? sprintf('<li class="group-file">' . __('From theme:') . '<ul>%s</ul></li>', $list_theme) : '');
$list .= ($list_parent != '' ? sprintf(
'<li class="group-file">' . __('From parent:') . ' %s<ul>%s</ul></li>',
$this->parent_name,
$list_parent
) : '');
$list .= ($list_tpl != '' ? sprintf(
'<li class="group-file">' . __('From template set:') . ' %s<ul>%s</ul></li>',
$this->tplset_name,
$list_tpl
) : '');
} else {
foreach ($files as $k => $v) {
if (strpos($v, $this->user_theme) === 0) {
$li = sprintf('<li class="default-file">%s</li>', $item);
} elseif ($this->parent_theme && strpos($v, $this->parent_theme) === 0) {
$li = sprintf('<li class="parent-file">%s</li>', $item);
} else {
$li = sprintf('<li>%s</li>', $item);
}
$list .= sprintf($li, $k, html::escapeHTML($k));
}
}
return sprintf('<ul>%s</ul>', $list);
}
public function getFileContent($type, $f)
{
$files = $this->getFilesFromType($type);
if (!isset($files[$f])) {
throw new Exception(__('File does not exist.'));
}
$F = $files[$f];
if (!is_readable($F)) {
throw new Exception(sprintf(__('File %s is not readable'), $f));
}
return [
'c' => file_get_contents($F),
'w' => $this->getDestinationFile($type, $f) !== false,
'type' => $type,
'f' => $f,
];
}
public function writeFile($type, $f, $content)
{
$files = $this->getFilesFromType($type);
if (!isset($files[$f])) {
throw new Exception(__('File does not exist.'));
}
try {
$dest = $this->getDestinationFile($type, $f);
if ($dest == false) {
throw new Exception();
}
if ($type == 'tpl' && !is_dir(dirname($dest))) {
files::makeDir(dirname($dest));
}
if ($type == 'po' && !is_dir(dirname($dest))) {
files::makeDir(dirname($dest));
}
$fp = @fopen($dest, 'wb');
if (!$fp) {
throw new Exception('tocatch');
}
$content = preg_replace('/(\r?\n)/m', "\n", $content);
$content = preg_replace('/\r/m', "\n", $content);
fwrite($fp, $content);
fclose($fp);
# Updating inner files list
$this->updateFileInList($type, $f, $dest);
} catch (Exception $e) {
throw new Exception(sprintf(__('Unable to write file %s. Please check your theme files and folders permissions.'), $f));
}
}
public function deletableFile($type, $f)
{
if ($type != 'tpl') {
// Only tpl files may be deleted
return false;
}
$files = $this->getFilesFromType($type);
if (isset($files[$f])) {
$dest = $this->getDestinationFile($type, $f);
if ($dest) {
if (file_exists($dest) && is_writable($dest)) {
// Is there a model (parent theme or template set) ?
if (isset($this->tpl_model[$f])) {
return true;
}
}
}
}
return false;
}
public function deleteFile($type, $f)
{
if ($type != 'tpl') {
// Only tpl files may be deleted
return;
}
$files = $this->getFilesFromType($type);
if (!isset($files[$f])) {
throw new Exception(__('File does not exist.'));
}
try {
$dest = $this->getDestinationFile($type, $f);
if ($dest) {
// File exists and may be deleted
unlink($dest);
// Updating template files list
$this->findTemplates();
}
} catch (Exception $e) {
throw new Exception(sprintf(__('Unable to delete file %s. Please check your theme files and folders permissions.'), $f));
}
}
protected function getDestinationFile($type, $f)
{
if ($type == 'tpl') {
$dest = $this->user_theme . '/tpl/' . $f;
} elseif ($type == 'po') {
$dest = $this->user_theme . '/locales/' . $f;
} else {
$dest = $this->user_theme . '/' . $f;
}
if (file_exists($dest) && is_writable($dest)) {
return $dest;
}
if ($type == 'tpl' && !is_dir(dirname($dest))) {
if (is_writable($this->user_theme)) {
return $dest;
}
}
if ($type == 'po' && !is_dir(dirname($dest))) {
if (is_writable($this->user_theme)) {
return $dest;
}
}
if (is_writable(dirname($dest))) {
return $dest;
}
return false;
}
protected function getFilesFromType($type)
{
switch ($type) {
case 'tpl':
return $this->tpl;
case 'css':
return $this->css;
case 'js':
return $this->js;
case 'po':
return $this->po;
case 'php':
return $this->php;
default:
return [];
}
}
protected function updateFileInList($type, $f, $file)
{
switch ($type) {
case 'tpl':
$list = &$this->tpl;
break;
case 'css':
$list = &$this->css;
break;
case 'js':
$list = &$this->js;
break;
case 'po':
$list = &$this->po;
break;
case 'php':
$list = &$this->php;
break;
default:
return;
}
$list[$f] = $file;
}
protected function findTemplates()
{
$this->tpl = array_merge(
$this->getFilesInDir($this->tplset_theme),
$this->getFilesInDir($this->parent_theme . '/tpl')
);
$this->tpl_model = $this->tpl;
$this->tpl = array_merge($this->tpl, $this->getFilesInDir($this->user_theme . '/tpl'));
# Then we look in 'default-templates' plugins directory
$plugins = $this->core->plugins->getModules();
foreach ($plugins as $p) {
// Looking in default-templates directory
$this->tpl = array_merge($this->getFilesInDir($p['root'] . '/default-templates'), $this->tpl);
$this->tpl_model = array_merge($this->getFilesInDir($p['root'] . '/default-templates'), $this->tpl_model);
// Looking in default-templates/tplset directory
$this->tpl = array_merge($this->getFilesInDir($p['root'] . '/default-templates/' . $this->tplset_name), $this->tpl);
$this->tpl_model = array_merge($this->getFilesInDir($p['root'] . '/default-templates/' . $this->tplset_name), $this->tpl_model);
}
uksort($this->tpl, [$this, 'sortFilesHelper']);
}
protected function findStyles()
{
$this->css = $this->getFilesInDir($this->user_theme, 'css');
$this->css = array_merge($this->css, $this->getFilesInDir($this->user_theme . '/style', 'css', 'style/'));
$this->css = array_merge($this->css, $this->getFilesInDir($this->user_theme . '/css', 'css', 'css/'));
}
protected function findScripts()
{
$this->js = $this->getFilesInDir($this->user_theme, 'js');
$this->js = array_merge($this->js, $this->getFilesInDir($this->user_theme . '/js', 'js', 'js/'));
}
protected function findLocales()
{
$langs = l10n::getISOcodes(true, true);
foreach ($langs as $k => $v) {
if ($this->parent_theme) {
$this->po = array_merge($this->po, $this->getFilesInDir($this->parent_theme . '/locales/' . $v, 'po', $v . '/'));
}
$this->po = array_merge($this->po, $this->getFilesInDir($this->user_theme . '/locales/' . $v, 'po', $v . '/'));
}
}
protected function findCodes()
{
$this->php = $this->getFilesInDir($this->user_theme, 'php');
}
protected function getFilesInDir($dir, $ext = null, $prefix = '', $model = null)
{
$dir = path::real($dir);
if (!$dir || !is_dir($dir) || !is_readable($dir)) {
return [];
}
$d = dir($dir);
$res = [];
while (($f = $d->read()) !== false) {
if (is_file($dir . '/' . $f) && !preg_match('/^\./', $f) && (!$ext || preg_match('/\.' . preg_quote($ext) . '$/i', $f))) {
if (!$model || preg_match('/^' . preg_quote($model) . '$/i', $f)) {
$res[$prefix . $f] = $dir . '/' . $f;
}
}
}
return $res;
}
protected function sortFilesHelper($a, $b)
{
if ($a == $b) {
return 0;
}
$ext_a = files::getExtension($a);
$ext_b = files::getExtension($b);
return strcmp($ext_a . '.' . $a, $ext_b . '.' . $b);
}
}