* Copyright (C) 2024 Frédéric France * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file htdocs/core/lib/website2.lib.php * \ingroup website * \brief Library for website module (rare functions not required for execution of website) */ /** * Save content of a page on disk * * @param string $filemaster Full path of filename master.inc.php for website to generate * @return boolean True if OK */ function dolSaveMasterFile($filemaster) { // Now generate the master.inc.php page dol_syslog("We regenerate the master.inc.php file"); dol_delete_file($filemaster); $mastercontent = ''."\n"; $result = file_put_contents($filemaster, $mastercontent); dolChmod($filemaster); return $result; } /** * Save an alias page on disk (A page that include the reference page). * It saves file into the root directory but also into language subdirectory. * * @param string $filealias Full path of filename to generate * @param Website $object Object website * @param WebsitePage $objectpage Object websitepage * @return boolean True if OK * @see dolSavePageContent() */ function dolSavePageAlias($filealias, $object, $objectpage) { // Now create the .tpl file dol_syslog("dolSavePageAlias We regenerate the alias page filealias=".$filealias." and a wrapper into all language subdirectories"); $aliascontent = 'id.'.tpl.php\'; '; $aliascontent .= 'else require $dolibarr_main_data_root.\'/website/\'.$website->ref.\'/page'.$objectpage->id.'.tpl.php\';'."\n"; $aliascontent .= '?>'."\n"; $result = file_put_contents($filealias, $aliascontent); if ($result === false) { dol_syslog("Failed to write file ".$filealias, LOG_WARNING); } dolChmod($filealias); // Save also alias into language subdirectory if it is not a main language if ($objectpage->lang && in_array($objectpage->lang, explode(',', $object->otherlang))) { $dirname = dirname($filealias); $filename = basename($filealias); $filealiassub = $dirname.'/'.$objectpage->lang.'/'.$filename; dol_mkdir($dirname.'/'.$objectpage->lang, DOL_DATA_ROOT); $aliascontent = 'id.'.tpl.php\'; '; $aliascontent .= 'else require $dolibarr_main_data_root.\'/website/\'.$website->ref.\'/page'.$objectpage->id.'.tpl.php\';'."\n"; $aliascontent .= '?>'."\n"; $result = file_put_contents($filealiassub, $aliascontent); if ($result === false) { dol_syslog("Failed to write file ".$filealiassub, LOG_WARNING); } dolChmod($filealiassub); } elseif (empty($objectpage->lang) || !in_array($objectpage->lang, explode(',', $object->otherlang))) { // Save also alias into all language subdirectories if it is a main language if (!getDolGlobalString('WEBSITE_DISABLE_MAIN_LANGUAGE_INTO_LANGSUBDIR') && !empty($object->otherlang)) { $dirname = dirname($filealias); $filename = basename($filealias); foreach (explode(',', $object->otherlang) as $sublang) { // Avoid to erase main alias file if $sublang is empty string if (empty(trim($sublang))) { continue; } $filealiassub = $dirname.'/'.$sublang.'/'.$filename; $aliascontent = 'id.'.tpl.php\'; '; $aliascontent .= 'else require $dolibarr_main_data_root.\'/website/\'.$website->ref.\'/page'.$objectpage->id.'.tpl.php\';'."\n"; $aliascontent .= '?>'."\n"; dol_mkdir($dirname.'/'.$sublang); $result = file_put_contents($filealiassub, $aliascontent); if ($result === false) { dol_syslog("Failed to write file ".$filealiassub, LOG_WARNING); } dolChmod($filealiassub); } } } return ($result ? true : false); } /** * Save content of a page on disk (page name is generally ID_of_page.php). * Page contents are always saved into "root" directory. Only aliases pages saved with dolSavePageAlias() can be in root or language subdir. * * @param string $filetpl Full path of filename to generate * @param Website $object Object website * @param WebsitePage $objectpage Object websitepage * @param int $backupold 1=Make a backup of old page * @return boolean True if OK * @see dolSavePageAlias() */ function dolSavePageContent($filetpl, Website $object, WebsitePage $objectpage, $backupold = 0) { global $db; // Now create the .tpl file (duplicate code with actions updatesource or updatecontent but we need this to save new header) dol_syslog("dolSavePageContent We regenerate the tpl page filetpl=".$filetpl); include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; if (dol_is_file($filetpl)) { if ($backupold) { $result = archiveOrBackupFile($filetpl); if (! $result) { return false; } } else { dol_delete_file($filetpl); } } $shortlangcode = ''; if ($objectpage->lang) { $shortlangcode = substr($objectpage->lang, 0, 2); // en_US or en-US -> en } if (empty($shortlangcode)) { $shortlangcode = substr($object->lang, 0, 2); // en_US or en-US -> en } if (!empty($objectpage->type_container) && in_array($objectpage->type_container, array('library', 'service'))) { $originalcontentonly = 1; } $tplcontent = ''; if (!isset($originalcontentonly)) { $tplcontent .= "\n"; if (getDolGlobalString('WEBSITE_FORCE_DOCTYPE_HTML5')) { $tplcontent .= "\n"; } $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''.dol_string_nohtmltag($objectpage->title, 0, 'UTF-8').''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; // Add favicon if ($objectpage->id == $object->fk_default_home) { $tplcontent .= ''."\n"; } // Add canonical reference if ($object->virtualhost) { $tplcontent .= ''."\n"; } // Add translation reference (main language) if ($object->isMultiLang()) { // Add page "translation of" $translationof = $objectpage->fk_page; if ($translationof) { $tmppage = new WebsitePage($db); $tmppage->fetch($translationof); if ($tmppage->id > 0) { $tmpshortlangcode = ''; if ($tmppage->lang) { $tmpshortlangcode = preg_replace('/[_-].*$/', '', $tmppage->lang); // en_US or en-US -> en } if (empty($tmpshortlangcode)) { $tmpshortlangcode = preg_replace('/[_-].*$/', '', $object->lang); // en_US or en-US -> en } if ($tmpshortlangcode != $shortlangcode) { $tplcontent .= ''."\n"; } } } // Add "has translation pages" $sql = "SELECT rowid as id, lang, pageurl from ".MAIN_DB_PREFIX.'website_page where fk_page IN ('.$db->sanitize($objectpage->id.($translationof ? ", ".$translationof : '')).")"; $resql = $db->query($sql); if ($resql) { $num_rows = $db->num_rows($resql); if ($num_rows > 0) { while ($obj = $db->fetch_object($resql)) { $tmpshortlangcode = ''; if ($obj->lang) { $tmpshortlangcode = preg_replace('/[_-].*$/', '', $obj->lang); // en_US or en-US -> en } if ($tmpshortlangcode != $shortlangcode) { $tplcontent .= ''."\n"; } } } } else { dol_print_error($db); } // Add myself $tplcontent .= 'fk_default_home == $objectpage->id) ? '/' : (($shortlangcode != substr($object->lang, 0, 2)) ? '/'.$shortlangcode : '')).'/'.$objectpage->pageurl.'.php") { ?>'."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; } // Add manifest.json. Do we have to add it only on home page ? $tplcontent .= 'use_manifest) { print \'\'."\n"; } ?>'."\n"; $tplcontent .= ''."\n"; // Add js $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; // Add headers $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= preg_replace('/<\/?html>/ims', '', $objectpage->htmlheader)."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= $objectpage->content."\n"; $tplcontent .= ''."\n"; $tplcontent .= ''."\n"; $tplcontent .= 'content, '$__PAGE__TITLE__') !== false) { $tplcontent .= '$tmp = preg_replace("/.*?<\/title>/s", "<title>" . dol_escape_htmltag($__PAGE__TITLE__) . "", $tmp);'."\n"; $tplcontent .= '$tmp = preg_replace("//s", "", $tmp);'; } if (strpos($objectpage->content, '$__PAGE__KEYWORDS__') !== false) { $tplcontent .= '$tmp = preg_replace("//s", "", $tmp);'; } if (strpos($objectpage->content, '$__PAGE__DESC__') !== false) { $tplcontent .= '$tmp = preg_replace("//s", "", $tmp);'; } $tplcontent .= 'dolWebsiteOutput($tmp, "html", '.$objectpage->id.'); dolWebsiteIncrementCounter('.$object->id.', "'.$objectpage->type_container.'", '.$objectpage->id.');'."\n"; $tplcontent .= "// END PHP ?>\n"; } else { $tplcontent .= "\n"; $tplcontent .= $objectpage->content; } //var_dump($filetpl);exit; $result = file_put_contents($filetpl, $tplcontent); dolChmod($filetpl); return $result; } /** * Save content of the index.php and/or the wrapper.php page * * @param string $pathofwebsite Path of website root * @param string $fileindex Full path of file index.php * @param string $filetpl File tpl the index.php page redirect to (used only if $fileindex is provided) * @param string $filewrapper Full path of file wrapper.php * @param Website $object Object website * @return boolean True if OK */ function dolSaveIndexPage($pathofwebsite, $fileindex, $filetpl, $filewrapper, $object = null) { global $db; $result1 = false; $result2 = false; dol_mkdir($pathofwebsite); if ($fileindex) { dol_delete_file($fileindex); $indexcontent = ''."\n"; $result1 = file_put_contents($fileindex, $indexcontent); dolChmod($fileindex); if (is_object($object) && $object->fk_default_home > 0) { $objectpage = new WebsitePage($db); $objectpage->fetch($object->fk_default_home); // Create a version for sublanguages if (empty($objectpage->lang) || !in_array($objectpage->lang, explode(',', $object->otherlang))) { if (!getDolGlobalString('WEBSITE_DISABLE_MAIN_LANGUAGE_INTO_LANGSUBDIR') && is_object($object) && !empty($object->otherlang)) { $dirname = dirname($fileindex); foreach (explode(',', $object->otherlang) as $sublang) { // Avoid to erase main alias file if $sublang is empty string if (empty(trim($sublang))) { continue; } $fileindexsub = $dirname.'/'.$sublang.'/index.php'; // Same indexcontent than previously but with ../ instead of ./ for master and tpl file include/require_once. $relpath = '..'; $indexcontent = ''."\n"; $result = file_put_contents($fileindexsub, $indexcontent); if ($result === false) { dol_syslog("Failed to write file ".$fileindexsub, LOG_WARNING); } dolChmod($fileindexsub); } } } } } else { $result1 = true; } if ($filewrapper) { dol_delete_file($filewrapper); $wrappercontent = file_get_contents(DOL_DOCUMENT_ROOT.'/website/samples/wrapper.php'); $result2 = file_put_contents($filewrapper, $wrappercontent); dolChmod($filewrapper); } else { $result2 = true; } return ($result1 && $result2); } /** * Save content of a page on disk * * @param string $filehtmlheader Full path of filename to generate * @param string $htmlheadercontent Content of file * @return boolean True if OK */ function dolSaveHtmlHeader($filehtmlheader, $htmlheadercontent) { global $pathofwebsite; dol_syslog("Save html header into ".$filehtmlheader); dol_mkdir($pathofwebsite); $result = file_put_contents($filehtmlheader, $htmlheadercontent); dolChmod($filehtmlheader); return $result; } /** * Save content of a page on disk * * @param string $filecss Full path of filename to generate * @param string $csscontent Content of file * @return boolean True if OK */ function dolSaveCssFile($filecss, $csscontent) { global $pathofwebsite; dol_syslog("Save css file into ".$filecss); dol_mkdir($pathofwebsite); $result = file_put_contents($filecss, $csscontent); dolChmod($filecss); return $result; } /** * Save content of a page on disk. For example into documents/website/mywebsite/javascript.js.php file. * * @param string $filejs Full path of filename to generate * @param string $jscontent Content of file * @return boolean True if OK */ function dolSaveJsFile($filejs, $jscontent) { global $pathofwebsite; dol_syslog("Save js file into ".$filejs); dol_mkdir($pathofwebsite); $result = file_put_contents($filejs, $jscontent); dolChmod($filejs); return $result; } /** * Save content of a page on disk * * @param string $filerobot Full path of filename to generate * @param string $robotcontent Content of file * @return boolean True if OK */ function dolSaveRobotFile($filerobot, $robotcontent) { global $pathofwebsite; dol_syslog("Save robot file into ".$filerobot); dol_mkdir($pathofwebsite); $result = file_put_contents($filerobot, $robotcontent); dolChmod($filerobot); return $result; } /** * Save content of a page on disk * * @param string $filehtaccess Full path of filename to generate * @param string $htaccess Content of file * @return boolean True if OK */ function dolSaveHtaccessFile($filehtaccess, $htaccess) { global $pathofwebsite; dol_syslog("Save htaccess file into ".$filehtaccess); dol_mkdir($pathofwebsite); $result = file_put_contents($filehtaccess, $htaccess); dolChmod($filehtaccess); return $result; } /** * Save content of a page on disk * * @param string $file Full path of filename to generate * @param string $content Content of file * @return boolean True if OK */ function dolSaveManifestJson($file, $content) { global $pathofwebsite; dol_syslog("Save manifest.js.php file into ".$file); dol_mkdir($pathofwebsite); $result = file_put_contents($file, $content); dolChmod($file); return $result; } /** * Save content of a page on disk * * @param string $file Full path of filename to generate * @param string $content Content of file * @return boolean True if OK */ function dolSaveReadme($file, $content) { global $pathofwebsite; dol_syslog("Save README.md file into ".$file); dol_mkdir($pathofwebsite); $result = file_put_contents($file, $content); dolChmod($file); return $result; } /** * Save content of a page on disk * * @param string $file Full path of filename to generate * @param string $content Content of file * @return boolean True if OK */ function dolSaveLicense($file, $content) { global $pathofwebsite; dol_syslog("Save LICENSE file into ".$file); dol_mkdir($pathofwebsite); $result = file_put_contents($file, $content); dolChmod($file); return $result; } /** * Show list of themes. Show all thumbs of themes/skins * * @param Website $website Object website to load the template into * @return void */ function showWebsiteTemplates(Website $website) { global $conf, $langs, $form, $user; $dirthemes = array('/doctemplates/websites'); /* if (!empty($conf->modules_parts['websitetemplates'])) { foreach ($conf->modules_parts['websitetemplates'] as $reldir) { $dirthemes = array_merge($dirthemes, (array) ($reldir.'doctemplates/websites')); } } */ $dirthemes = array_unique($dirthemes); // Now dir_themes=array('/themes') or dir_themes=array('/theme','/mymodule/theme') $colspan = 2; print ''."\n"; print ''; // Title print ''; print ''; print ''; print '
'; print $form->textwithpicto($langs->trans("Templates"), $langs->trans("ThemeDir").' : '.implode(", ", $dirthemes)); print ' '; print ''; print img_picto('', 'refresh'); print ''; print ''; $url = 'https://www.dolistore.com/43-web-site-templates'; print ''; print img_picto('', 'globe', 'class="pictofixedwidth"').$langs->trans('DownloadMoreSkins'); print ''; print '
'; print '
'; if (count($dirthemes)) { $i = 0; foreach ($dirthemes as $dir) { if (preg_match('/^\/doctemplates\//', $dir)) { $dirtheme = DOL_DATA_ROOT.$dir; // This include loop on $conf->file->dol_document_root } else { $dirtheme = dol_buildpath($dir); // This include loop on $conf->file->dol_document_root } if (is_dir($dirtheme)) { $handle = opendir($dirtheme); if (is_resource($handle)) { while (($subdir = readdir($handle)) !== false) { //var_dump($dirtheme.'/'.$subdir); if (is_file($dirtheme."/".$subdir) && substr($subdir, 0, 1) != '.' && substr($subdir, 0, 3) != 'CVS' && preg_match('/\.zip$/i', $subdir)) { $subdirwithoutzip = preg_replace('/\.zip$/i', '', $subdir); // Disable not stable themes (dir ends with _exp or _dev) if (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2 && preg_match('/_dev$/i', $subdir)) { continue; } if (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 1 && preg_match('/_exp$/i', $subdir)) { continue; } print '
'; $templatedir = $dirtheme."/".$subdir; $file = $dirtheme."/".$subdirwithoutzip.".jpg"; $url = DOL_URL_ROOT.'/viewimage.php?modulepart=doctemplateswebsite&file='.$subdirwithoutzip.".jpg"; if (!file_exists($file)) { $url = DOL_URL_ROOT.'/public/theme/common/nophoto.png'; } $originalfile = basename($file); $entity = $conf->entity; $modulepart = 'doctemplateswebsite'; $cache = ''; $title = $file; $ret = ''; $urladvanced = getAdvancedPreviewUrl($modulepart, $originalfile, 1, '&entity='.$entity); if (!empty($urladvanced)) { $ret .= ''; } else { $ret .= ''; } print $ret; print ''.$title.''; print ''; print '
'; print $subdir; print '
'; print ''.dol_print_size(dol_filesize($dirtheme."/".$subdir), 1, 1).' - '.dol_print_date(dol_filemtime($templatedir), 'dayhour', 'tzuserrel').''; if ($user->hasRight('website', 'delete')) { print ' ref).'&templateuserfile='.urlencode($subdir).'">'.img_picto('', 'delete').''; } print '
ref).'&templateuserfile='.urlencode($subdir).'" class="button">'.$langs->trans("Load").''; print '
'; $i++; } } print '
'; print '
'; print '
'; print '
'; print '
'; } } } } else { print ''.$langs->trans("None").''; } print '
'; print '
'; } /** * Check a new string containing only php code (including " * @param string $phpfullcodestring PHP new string. For example "" * @return int Error or not * @see dolKeepOnlyPhpCode(), dol_eval() to see sanitizing rules that should be very close. */ function checkPHPCode(&$phpfullcodestringold, &$phpfullcodestring) { global $langs, $user; $error = 0; if (empty($phpfullcodestringold) && empty($phpfullcodestring)) { return 0; } // First check permission if ($phpfullcodestringold != $phpfullcodestring) { if (!$error && !$user->hasRight('website', 'writephp')) { $error++; setEventMessages($langs->trans("NotAllowedToAddDynamicContent"), null, 'errors'); } } // Then check forbidden commands if (!$error) { $forbiddenphpstrings = array('$$', '}['); $forbiddenphpstrings = array_merge($forbiddenphpstrings, array('ReflectionFunction')); $forbiddenphpfunctions = array(); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id")); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes")); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("call_user_func")); //$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once")); if (!getDolGlobalString('WEBSITE_PHP_ALLOW_EXEC')) { // If option is not on, we disallow functions to execute commands $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen")); $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "executeCLI", "verifCond")); // native dolibarr functions $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities } if (!getDolGlobalString('WEBSITE_PHP_ALLOW_WRITE')) { // If option is not on, we disallow functions to write files $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "mkdir", "rmdir", "symlink", "touch", "unlink", "umask")); } $forbiddenphpmethods = array('invoke', 'invokeArgs'); // Method of ReflectionFunction to execute a function foreach ($forbiddenphpstrings as $forbiddenphpstring) { if (preg_match('/'.preg_quote($forbiddenphpstring, '/').'/ms', $phpfullcodestring)) { $error++; setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", $forbiddenphpstring), null, 'errors'); break; } } foreach ($forbiddenphpfunctions as $forbiddenphpcommand) { if (preg_match('/'.$forbiddenphpcommand.'\s*\(/ms', $phpfullcodestring)) { $error++; setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", $forbiddenphpcommand), null, 'errors'); break; } } foreach ($forbiddenphpmethods as $forbiddenphpmethod) { if (preg_match('/->'.$forbiddenphpmethod.'/ms', $phpfullcodestring)) { $error++; setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", $forbiddenphpmethod), null, 'errors'); break; } } } // This char can be used to execute RCE for example using with echo `ls` if (!$error) { $forbiddenphpchars = array(); if (!getDolGlobalString('WEBSITE_PHP_ALLOW_DANGEROUS_CHARS')) { // If option is not on, we disallow functions to execute commands $forbiddenphpchars = array("`"); } foreach ($forbiddenphpchars as $forbiddenphpchar) { if (preg_match('/'.$forbiddenphpchar.'/ms', $phpfullcodestring)) { $error++; setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", $forbiddenphpchar), null, 'errors'); break; } } } // Deny dynamic functions '${a}(' or '$a[b](' => So we refuse '}(' and '](' if (!$error) { if (preg_match('/[}\]]\(/ims', $phpfullcodestring)) { $error++; setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", ']('), null, 'errors'); } } // Deny dynamic functions '$xxx(' if (!$error) { if (preg_match('/\$[a-z0-9_\-\/\*]+\(/ims', $phpfullcodestring)) { $error++; setEventMessages($langs->trans("DynamicPHPCodeContainsAForbiddenInstruction", '$...('), null, 'errors'); } } // No need to block $conf->global->aaa() because PHP try to run method aaa an not function into $conf->global->aaa. // Then check if installmodules does not block dynamic PHP code change. if ($phpfullcodestringold != $phpfullcodestring) { if (!$error) { $dolibarrdataroot = preg_replace('/([\\/]+)$/i', '', DOL_DATA_ROOT); $allowimportsite = true; include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; if (dol_is_file($dolibarrdataroot.'/installmodules.lock')) { $allowimportsite = false; } if (!$allowimportsite) { $error++; // Blocked by installmodules.lock if (getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US')) { // Show clean corporate message $message = $langs->trans('InstallModuleFromWebHasBeenDisabledContactUs'); } else { // Show technical generic message $message = $langs->trans("InstallModuleFromWebHasBeenDisabledByFile", $dolibarrdataroot.'/installmodules.lock'); } setEventMessages($message, null, 'errors'); } } } return $error; }