diff --git a/admin/controller/editor/editor.php b/admin/controller/editor/editor.php index ecd7d22..6137b93 100755 --- a/admin/controller/editor/editor.php +++ b/admin/controller/editor/editor.php @@ -36,6 +36,8 @@ use Vvveb\System\Sites; class Editor extends Base { + use GlobalTrait; + protected $revisionDateFormat = 'Y-m-d_H:i:s'; private $themeConfig = []; @@ -62,13 +64,15 @@ function oEmbedProxy() { $this->response->output($result); } - function getThemeFolder() { - $theme = $this->request->get['theme'] ?? Sites::getTheme() ?? 'default'; + private function getTheme() { + return $theme = sanitizeFileName($this->request->get['theme'] ?? Sites::getTheme() ?? 'default'); + } - return DIR_THEMES . DS . $theme; + private function getThemeFolder() { + return DIR_THEMES . $this->getTheme(); } - function loadThemeConfig() { + private function loadThemeConfig() { $config = $this->getThemeFolder() . DS . 'theme.php'; if (file_exists($config)) { @@ -78,16 +82,16 @@ function loadThemeConfig() { } } - function loadTemplateList() { + private function loadTemplateList($theme = null) { $list = $this->themeConfig['pages'] ?? []; - $pages = $list + \Vvveb\getTemplateList(); + $pages = $list + \Vvveb\getTemplateList($theme); list($pages) = Event::trigger(__CLASS__, __FUNCTION__, $pages); return $pages; } - function loadEditorData() { + private function loadEditorData() { $data = []; //menu list @@ -104,7 +108,7 @@ function loadEditorData() { /* Load theme sections, components and inputs */ - function loadThemeAssets() { + private function loadThemeAssets() { $themeFolder = $this->getThemeFolder(); $view = &$this->view; $themeJs = []; @@ -136,9 +140,11 @@ function loadThemeAssets() { } function index() { + $theme = sanitizeFileName($this->request->get['theme'] ?? false); + $themeParam = ($theme ? '&theme=' . $theme : ''); $view = View::getInstance(); - $view->themeBaseUrl = PUBLIC_PATH . 'themes/' . (Sites::getTheme() ?? 'default') . '/'; - $view->pages = $this->loadTemplateList(); + $view->themeBaseUrl = PUBLIC_PATH . 'themes/' . ($this->getTheme() ?? 'default') . '/'; + $view->pages = $this->loadTemplateList($theme); $this->loadThemeAssets(); @@ -155,45 +161,51 @@ function index() { $name = 'homepage-live'; } - $current_page = ['name' => $name, 'file' => $file, 'url' => $url, 'title' => $title, 'folder' => '', 'className' => 'page']; + $current_page = ['name' => $name, + 'file' => $file, + 'url' => $url . ($theme ? '?theme=' . $theme : ''), + 'title' => $title, + 'folder' => '', + 'className' => 'page', ]; + $view->pages = [$name => $current_page] + $view->pages; } $admin_path = \Vvveb\adminPath(); $mediaControllerPath = $admin_path . 'index.php?module=media/media'; - $controllerPath = $admin_path . 'index.php?module=editor/editor'; - $revisionsPath = $admin_path . 'index.php?module=editor/revisions'; + $controllerPath = $admin_path . 'index.php?module=editor/editor' . $themeParam; + $revisionsPath = $admin_path . 'index.php?module=editor/revisions' . $themeParam; + $reusablePath = $admin_path . 'index.php?module=editor/reusable' . $themeParam; $this->view->scanUrl = "$mediaControllerPath&action=scan"; $this->view->uploadUrl = "$mediaControllerPath&action=upload"; $this->view->saveUrl = "$controllerPath&action=save"; $this->view->deleteUrl = "$controllerPath&action=delete"; $this->view->renameUrl = "$controllerPath&action=rename"; - $this->view->saveReusableUrl = "$controllerPath&action=saveReusable"; + $this->view->saveReusableUrl = "$reusablePath&action=save"; $this->view->oEmbedProxyUrl = "$controllerPath&action=oEmbedProxy"; $this->view->revisionsUrl = "$revisionsPath&action=revisions"; - $this->view->revisionLoadUrl = "$revisionsPath&action=revisionLoad"; - $this->view->revisionDeleteUrl = "$revisionsPath&action=revisionDelete"; + $this->view->revisionLoadUrl = "$revisionsPath&action=load"; + $this->view->revisionDeleteUrl = "$revisionsPath&action=delete"; - $view->templates = \Vvveb\getTemplateList(); - $view->folders = \Vvveb\getThemeFolderList(); + $view->templates = \Vvveb\getTemplateList($theme); + $view->folders = \Vvveb\getThemeFolderList($theme); $view->data = $this->loadEditorData(); } - function getComponent($html, $options) { - } - - function backup($page) { + private function backup($page) { $themeFolder = $this->getThemeFolder() . DS; $backupFolder = $themeFolder . 'backup' . DS; $page = str_replace('.html', '', sanitizeFileName($page)); - $backupName = str_replace(DS, '-', $page) . '|' . date($this->revisionDateFormat) . '.html'; + $backupName = str_replace(DS, '-', $page) . '@' . str_replace(':',';', date($this->revisionDateFormat)) . '.html'; $file = $themeFolder . $page . '.html'; if (is_dir($backupFolder)) { if (file_exists($file)) { $content = file_get_contents($themeFolder . $page . '.html'); + $base = str_replace('/admin', '', PUBLIC_THEME_PATH) . 'themes/' . $this->getTheme() . '/'; + $content = preg_replace('//', '', $content); return file_put_contents($backupFolder . $backupName, $content); } @@ -202,7 +214,7 @@ function backup($page) { return false; } - function saveElements($elements) { + private function saveElements($elements) { $products = new ProductSQL(); $posts = new PostSQL(); $components = []; @@ -313,36 +325,16 @@ function rename() { $this->response->output($message); } - function saveReusable() { - $name = slugify(sanitizeFileName($this->request->post['name'])); - $type = $this->request->post['type']; - $html = $this->request->post['html']; - - $themeFolder = $this->getThemeFolder(); - $folder = $themeFolder . DS . $type . 's' . DS . 'reusable' . DS; - $file = "$name.html"; - - @mkdir($folder); - - if (file_put_contents($folder . $file, $html)) { - $message = ['success' => true, 'message' => __('Element saved!')]; - } else { - $message = ['success' => false, 'message' => __('Error saving!')]; - } - - $this->response->setType('json'); - $this->response->output($message); - } - function save() { $file = $this->request->post['file'] ?? ''; $folder = $this->request->post['folder'] ?? ''; $startTemplateUrl = $this->request->post['startTemplateUrl'] ?? ''; $name = $this->request->post['name'] ?? ''; $content = $this->request->post['content'] ?? 'Lorem ipsum'; - $type = $this->request->post['type'] ?? false; - $addMenu = $this->request->post['add-menu'] ?? false; - $menu_id = $this->request->post['menu_id'] ?? false; + $type = $this->request->post['type'] ?? false; + $addMenu = $this->request->post['add-menu'] ?? false; + $menu_id = $this->request->post['menu_id'] ?? false; + $theme = sanitizeFileName($this->request->get['theme'] ?? false); $url = ''; $file = sanitizeFileName(str_replace('.html', '', $file)) . '.html'; @@ -369,7 +361,8 @@ function save() { 'content' => $content, 'language_id' => $this->global['language_id'], ]], - ] + $this->global, ] + $this->global); + ] + $this->global, + 'site_id' => [$this->global['site_id']], ] + $this->global); if ($result['post']) { $success = true; @@ -398,7 +391,8 @@ function save() { 'content' => $content, 'language_id' => $this->global['language_id'], ]], - ] + $this->global, ] + $this->global); + ] + $this->global, + 'site_id' => [$this->global['site_id']], ] + $this->global); if ($result['product']) { $success = true; @@ -419,8 +413,8 @@ function save() { $success = false; $text = ''; + $baseUrl = '/themes/' . $this->getTheme() . '/' . ($folder ? $folder . '/' : ''); $themeFolder = $this->getThemeFolder(); - $baseUrl = '/themes/' . (Sites::getTheme() ?? 'default') . '/' . ($folder ? $folder . '/' : ''); if ($startTemplateUrl) { $content = file_get_contents($themeFolder . DS . $startTemplateUrl); @@ -478,6 +472,8 @@ function save() { $fileName = $themeFolder . DS . ($folder ? $folder . DS : '') . $file; } + $this->saveGlobalElements($content); + if (file_put_contents($fileName, $content)) { $success = true; $text .= __('File saved!'); diff --git a/admin/controller/editor/global-trait.php b/admin/controller/editor/global-trait.php new file mode 100644 index 0000000..52659a0 --- /dev/null +++ b/admin/controller/editor/global-trait.php @@ -0,0 +1,102 @@ +. + * + */ + +namespace Vvveb\Controller\Editor; + +use Vvveb\System\Core\View; + +trait GlobalTrait { + private function saveGlobalElements($content) { + $document = new \DomDocument(); + $document->preserveWhiteSpace = false; + $document->recover = true; + $document->strictErrorChecking = false; + $document->formatOutput = false; + $document->resolveExternals = false; + $document->validateOnParse = false; + $document->xmlStandalone = true; + + $view = View::getInstance(); + libxml_use_internal_errors(true); + + @$document->loadHTML($content); + + $xpath = new \DOMXpath($document); + + $elements = $xpath->query('//*[ @data-v-save-global ]'); + + if ($elements && $elements->length) { + $toDocument = new \DomDocument(); + $toDocument->preserveWhiteSpace = false; + $toDocument->recover = true; + $toDocument->strictErrorChecking = false; + $toDocument->formatOutput = false; + $toDocument->resolveExternals = false; + $toDocument->validateOnParse = false; + $toDocument->xmlStandalone = true; + + $themeFolder = $this->getThemeFolder(); + + foreach ($elements as $element) { + $attribute = $element->getAttribute('data-v-save-global'); + + if (strpos($attribute, ',') !== false) { + list($file, $selector) = explode(',',$attribute); + + $file = html_entity_decode($file); + $selector = html_entity_decode($selector); + $file = $themeFolder . DS . $file; + + $toDocument->loadHTMLFile($file); + + $toXpath = new \DOMXpath($toDocument); + + $toElements = $toXpath->query(\Vvveb\cssToXpath($selector)); + + $count = 0; + + if ($elements && $elements->length) { + foreach ($toElements as $externalNode) { + $parent = $externalNode->parentNode; + + $importedNode = $toDocument->importNode($element, true); + + if ($parent) { + if ($count) { + $parent->appendChild($importedNode); + } else { + $parent->replaceChild($importedNode, $externalNode); + } + $externalNode = $importedNode; + $parent = $externalNode->parentNode; + $count++; + } + } + + $html= $toDocument->saveHTML(); + file_put_contents($file, $html); + } + } + } + } + } +} diff --git a/admin/controller/editor/reusable.php b/admin/controller/editor/reusable.php new file mode 100644 index 0000000..23a1a22 --- /dev/null +++ b/admin/controller/editor/reusable.php @@ -0,0 +1,133 @@ +. + * + */ + +namespace Vvveb\Controller\Editor; + +use function Vvveb\__; +use Vvveb\Controller\Base; +use function Vvveb\humanReadable; +use function Vvveb\sanitizeFileName; +use function Vvveb\slugify; +use Vvveb\System\Sites; + +class Reusable extends Base { + private $sectionTemplate = <<request->get['theme'] ?? Sites::getTheme() ?? 'default'); + } + + private function regenerate($type) { + $themeFolder = $this->getThemeFolder(); + + $folder = $themeFolder . DS . $type . 's' . DS; + $htmlFolder = $folder . 'reusable' . DS; + $template = ($type == 'section') ? $this->sectionTemplate : $this->blockTemplate; + + $js = ''; + + foreach (scandir($htmlFolder) as $file) { + if (strpos($file, '.html') === false) { + continue; + } + $name = str_replace('.html','', $file); + $title = humanReadable($name); + $content = file_get_contents($htmlFolder . DS . $file); + + $data = compact('name', 'title', 'content'); + $reusable = $template; + + foreach ($data as $key => $value) { + $reusable = str_replace('{' . $key . '}', $value, $reusable); + } + + $js .= $reusable; + } + + $jsFile = $folder . $type . 's.js'; + $typeName = ucfirst($type); + $jsContent = ''; + + if (file_exists($jsFile)) { + $jsContent = file_get_contents($jsFile); + + //remove old reusable + $regex = '\s*Vvveb\.\w+?\.add\([\'"]reusable.+?`\s*\}\);?|' . + '\s*Vvveb\.\w+?Group\[["\']Reusable["\']\]\s*\.push\([^\)]+\);?\s*|' . + '\s*Vvveb\.\w+?Group\[["\']Reusable["\']\]\s*=\s*\[[^\]]*?\];?\s*'; + + $jsContent = preg_replace("/$regex/ms", '', $jsContent); + } + + //append new reusable + $jsContent = $jsContent . "\n\nVvveb." . $typeName . "sGroup[\"Reusable\"] = [];\n" . $js; + + return file_put_contents($jsFile, $jsContent); + } + + function save() { + $name = slugify(sanitizeFileName($this->request->post['name'])); + $type = $this->request->post['type']; + $html = $this->request->post['html']; + $message = ['success' => false, 'message' => __('Error saving!')]; + + if (($type == 'section' || $type == 'block') && $html) { + $themeFolder = $this->getThemeFolder(); + $folder = $themeFolder . DS . $type . 's' . DS . 'reusable' . DS; + $file = "$name.html"; + + @mkdir($folder, 0755 & ~umask(), true); + + if (file_put_contents($folder . $file, $html)) { + if ($this->regenerate($type)) { + $message = ['success' => true, 'message' => __('Element saved!')]; + } + } else { + } + } + + $this->response->setType('json'); + $this->response->output($message); + } +} diff --git a/admin/controller/editor/revisions.php b/admin/controller/editor/revisions.php index 8821425..7d96807 100644 --- a/admin/controller/editor/revisions.php +++ b/admin/controller/editor/revisions.php @@ -22,52 +22,94 @@ namespace Vvveb\Controller\Editor; +use function Vvveb\__; use Vvveb\Controller\Base; use function Vvveb\friendlyDate; use function Vvveb\sanitizeFileName; use Vvveb\System\Sites; class Revisions extends Base { - private function backupFolder() { + function getThemeFolder() { $theme = $this->request->get['theme'] ?? Sites::getTheme() ?? 'default'; - return DIR_THEMES . DS . $theme . DS . 'backup' . DS; + return $theme; + } + + private function sanitizeBackupFileName($fileName) { + return str_replace(['.', '/', '\\'], '', $fileName); + } + + private function backupFolder() { + $theme = $this->getThemeFolder(); + + return DIR_THEMES . $theme . DS . 'backup' . DS; } function delete() { + $file = $this->request->post['file'] ?? false; + + if ($file) { + $file = $this->backupFolder() . $this->sanitizeBackupFileName($file) . '.html'; + + $text = __('Error deleting file!' . $file); + $success = false; + + if (file_exists($file)) { + $success = unlink($file); + + if ($success) { + $text = __('File deleted!'); + } + } + + $data = ['success' => $success, 'message' => $text]; + + $this->response->setType('json'); + $this->response->output($data); + } } function load() { - $template = $this->request->get['template'] ?? false; + $file = $this->request->post['file'] ?? false; - $this->view->text = $text; - $this->response->setType('text'); - } + if ($file) { + $file = $this->backupFolder() . $this->sanitizeBackupFileName($file) . '.html'; - function preview() { + if (file_exists($file)) { + $this->response->setType('text'); + $this->response->output(file_get_contents($file)); + } + } } function revisions() { + $theme = $this->getThemeFolder(); $template = $this->request->get['template'] ?? false; $backupFolder = $this->backupFolder(); $revisions = []; if ($template) { - $template = str_replace(['/', '.html'], ['-', ''], sanitizeFileName($template)); + $templateName = str_replace(['/', '.html'], ['-', ''], sanitizeFileName($template)); + $path = '//' . $_SERVER['HTTP_HOST'] . PUBLIC_PATH . "/themes/$theme/"; - $glob = glob("$backupFolder/$template|*.html"); + $glob = glob("$backupFolder/$templateName@*.html"); foreach ($glob as &$file) { $file = basename($file, '.html'); - list($name, $date) = explode('|', $file); - $date = str_replace('_', ' ', $date); + $url = $path . "backup/$file.html"; + list($name, $date) = explode('@', $file); + $date = str_replace(['_', ';'], [' ', ':'], $date); $date_friendly = friendlyDate($date); /* $time = date_parse_from_format($this->revisionDateFormat, $date); unset($time['warnings'], $time['warning_count'], $time['errors'], $time['error_count']); */ - $revisions[] = compact(['file', 'date', 'date_friendly', 'name'/*, 'time'*/]); + $revisions[$date] = compact(['url', 'file', 'date', 'date_friendly', 'name'/*, 'time'*/]); } + + krsort($revisions); + + //$revisions[0] = ['url' => $path . $template, 'file' => $template, '' => '', 'date_friendly' => 'Live', 'date' => '', 'name' => '']; } $this->response->setType('json');