reindex(); } // Re-index database of tags // If you create/edit/remove a page is necessary regenerate the database of tags function reindexTags() { global $tags; return $tags->reindex(); } // Generate the page 404 Not found function buildErrorPage() { global $site; global $L; try { $pageNotFoundKey = $site->pageNotFound(); $pageNotFound = new Page($pageNotFoundKey); } catch (Exception $e) { $pageNotFound = new Page(false); $pageNotFound->setField('title', $L->get('page-not-found')); $pageNotFound->setField('content', $L->get('page-not-found-content')); $pageNotFound->setField('username', 'admin'); } return $pageNotFound; } // This function is only used from the rule 69.pages.php, DO NOT use this function! // This function generate a particular page from the current slug of the url // If the slug has not a page associated returns FALSE and set not-found as true function buildThePage() { global $url; try { $pageKey = $url->slug(); $page = new Page($pageKey); } catch (Exception $e) { $url->setNotFound(); return false; } if ($page->draft() || $page->scheduled() || $page->autosave()) { $preview = $url->parameter('preview'); if (!is_string($preview) || !hash_equals(hash_hmac('sha256', $page->uuid(), DB_SITE), $preview)) { $url->setNotFound(); return false; } } return $page; } // This function is only used from the rule 69.pages.php, DO NOT use this function! function buildPagesForHome() { return buildPagesFor('home'); } // This function is only used from the rule 69.pages.php, DO NOT use this function! function buildPagesByCategory() { global $url; $categoryKey = $url->slug(); return buildPagesFor('category', $categoryKey, false); } // This function is only used from the rule 69.pages.php, DO NOT use this function! function buildPagesByTag() { global $url; $tagKey = $url->slug(); return buildPagesFor('tag', false, $tagKey); } // This function is only used from the rule 69.pages.php, DO NOT use this function! // Generate the global variables $content / $content, defined on 69.pages.php // This function is use for buildPagesForHome(), buildPagesByCategory(), buildPagesByTag() function buildPagesFor($for, $categoryKey = false, $tagKey = false) { global $pages; global $categories; global $tags; global $site; global $url; // Get the page number from URL $pageNumber = $url->pageNumber(); if ($for == 'home') { $onlyPublished = true; $numberOfItems = $site->itemsPerPage(); $list = $pages->getList($pageNumber, $numberOfItems, $onlyPublished); // Include sticky pages only in the first page if ($pageNumber == 1) { $sticky = $pages->getStickyDB(); $list = array_merge($sticky, $list); } } elseif ($for == 'category') { $numberOfItems = $site->itemsPerPage(); $list = $categories->getList($categoryKey, $pageNumber, $numberOfItems); } elseif ($for == 'tag') { $numberOfItems = $site->itemsPerPage(); $list = $tags->getList($tagKey, $pageNumber, $numberOfItems); } // There are no items, invalid tag, invalid category, out of range, etc... if ($list === false) { $url->setNotFound(); return false; } $content = array(); $invalidPageFound = false; foreach ($list as $pageKey) { try { $page = new Page($pageKey); if (($page->type() == 'published') || ($page->type() == 'sticky') || ($page->type() == 'static') ) { array_push($content, $page); } } catch (Exception $e) { $invalidPageFound = true; // continue } } if (($for === 'category' || $for === 'tag') && (!empty($list)) && (empty($content) || $invalidPageFound)) { if ($for === 'category') { reindexCategories(); $list = $categories->getList($categoryKey, $pageNumber, $numberOfItems); } else { reindexTags(); $list = $tags->getList($tagKey, $pageNumber, $numberOfItems); } if (is_array($list)) { $content = array(); foreach ($list as $pageKey) { try { $page = new Page($pageKey); if (($page->type() == 'published') || ($page->type() == 'sticky') || ($page->type() == 'static') ) { array_push($content, $page); } } catch (Exception $e) { // continue } } } } return $content; } // Returns an array with all the static pages as Page-Object // The static pages are order by position all the time function buildStaticPages() { global $pages; $list = array(); $pagesKey = $pages->getStaticDB(); foreach ($pagesKey as $pageKey) { try { $page = new Page($pageKey); array_push($list, $page); } catch (Exception $e) { // continue } } return $list; } // Returns the Page-Object if exists, FALSE otherwise function buildPage($pageKey) { try { $page = new Page($pageKey); return $page; } catch (Exception $e) { return false; } } // Returns an array with all the parent pages as Page-Object // The pages are order by the settings on the system function buildParentPages() { global $pages; $list = array(); $pagesKey = $pages->getPublishedDB(); foreach ($pagesKey as $pageKey) { try { $page = new Page($pageKey); if ($page->isParent()) { array_push($list, $page); } } catch (Exception $e) { // continue } } return $list; } // Returns the Plugin-Object if is enabled and installed, FALSE otherwise function getPlugin($pluginClassName) { global $plugins; if (pluginActivated($pluginClassName)) { return $plugins['all'][$pluginClassName]; } return false; } // Returns TRUE if the plugin is activaed / installed, FALSE otherwise function pluginActivated($pluginClassName) { global $plugins; if (isset($plugins['all'][$pluginClassName])) { return $plugins['all'][$pluginClassName]->installed(); } return false; } function activatePlugin($pluginClassName) { global $plugins; global $syslog; global $L; // Check if the plugin exists if (isset($plugins['all'][$pluginClassName])) { $plugin = $plugins['all'][$pluginClassName]; if ($plugin->install()) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'plugin-activated', 'notes' => $plugin->name() )); // Create an alert Alert::set($L->g('plugin-activated')); return true; } } return false; } function deactivatePlugin($pluginClassName) { global $plugins; global $syslog; global $L; // Check if the plugin exists if (isset($plugins['all'][$pluginClassName])) { $plugin = $plugins['all'][$pluginClassName]; if ($plugin->uninstall()) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'plugin-deactivated', 'notes' => $plugin->name() )); // Create an alert Alert::set($L->g('plugin-deactivated')); return true; } } return false; } function deactivateAllPlugin() { global $plugins; global $syslog; global $L; // Check if the plugin exists foreach ($plugins['all'] as $plugin) { if ($plugin->uninstall()) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'plugin-deactivated', 'notes' => $plugin->name() )); } } return false; } function changePluginsPosition($pluginClassList) { global $plugins; global $syslog; global $L; foreach ($pluginClassList as $position => $pluginClassName) { if (isset($plugins['all'][$pluginClassName])) { $plugin = $plugins['all'][$pluginClassName]; $plugin->setPosition(++$position); } } // Add to syslog $syslog->add(array( 'dictionaryKey' => 'plugins-sorted', 'notes' => '' )); Alert::set($L->g('The changes have been saved')); return true; } /* Create a new page The array $args support all the keys from variable $dbFields of the class pages.class.php If you don't pass all the keys, the default values are used, the default values are from $dbFields in the class pages.class.php */ function createPage($args) { global $pages; global $syslog; global $L; // Check if the autosave page exists for this new page and delete it if (isset($args['uuid'])) { $autosaveKey = $pages->getByUUID('autosave-' . $args['uuid']); if (!empty($autosaveKey)) { Log::set('Function createPage()' . LOG_SEP . 'Autosave deleted for ' . $args['title'], LOG_TYPE_INFO); deletePage($autosaveKey); } } // The user is always the one logged $args['username'] = Session::get('username'); if (empty($args['username'])) { Log::set('Function createPage()' . LOG_SEP . 'Empty username.', LOG_TYPE_ERROR); return false; } $key = $pages->add($args); if ($key) { // Call the plugins after page created Theme::plugins('afterPageCreate', array($key)); reindexCategories(); reindexTags(); // Add to syslog $syslog->add(array( 'dictionaryKey' => 'new-content-created', 'notes' => (empty($args['title']) ? $key : $args['title']) )); return $key; } Log::set('Function createNewPage()' . LOG_SEP . 'Error occurred when trying to create the page', LOG_TYPE_ERROR); Log::set('Function createNewPage()' . LOG_SEP . 'Cleaning database...', LOG_TYPE_ERROR); deletePage($key); Log::set('Function createNewPage()' . LOG_SEP . 'Cleaning finished...', LOG_TYPE_ERROR); return false; } function editPage($args) { global $pages; global $syslog; // Check if the autosave/preview page exists for this new page and delete it if (isset($args['uuid'])) { $autosaveKey = $pages->getByUUID('autosave-' . $args['uuid']); if ($autosaveKey) { Log::set('Function editPage()' . LOG_SEP . 'Autosave/Preview deleted for ' . $autosaveKey, LOG_TYPE_INFO); deletePage($autosaveKey); } } // Check if the key is not empty if (empty($args['key'])) { Log::set('Function editPage()' . LOG_SEP . 'Empty key.', LOG_TYPE_ERROR); return false; } // Check if the page key exist if (!$pages->exists($args['key'])) { Log::set('Function editPage()' . LOG_SEP . 'Page key does not exist, ' . $args['key'], LOG_TYPE_ERROR); return false; } $key = $pages->edit($args); if ($key) { // Call the plugins after page modified Theme::plugins('afterPageModify', array($key)); reindexCategories(); reindexTags(); // Add to syslog $syslog->add(array( 'dictionaryKey' => 'content-edited', 'notes' => empty($args['title']) ? $key : $args['title'] )); return $key; } Log::set('Function editPage()' . LOG_SEP . 'Something happen when try to edit the page.', LOG_TYPE_ERROR); return false; } function deletePage($key) { global $pages; global $syslog; if ($pages->delete($key)) { // Call the plugins after page deleted Theme::plugins('afterPageDelete', array($key)); reindexCategories(); reindexTags(); // Add to syslog $syslog->add(array( 'dictionaryKey' => 'content-deleted', 'notes' => $key )); return true; } return false; } function editUser($args) { global $users; global $syslog; if ($users->set($args)) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'user-edited', 'notes' => $args['username'] )); return true; } return false; } function disableUser($args) { global $users; global $login; global $syslog; // Arguments $username = $args['username']; // Only administrators can disable users if ($login->role() !== 'admin') { return false; } // Check if the username exists if (!$users->exists($username)) { return false; } // Disable the user if ($users->disableUser($username)) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'user-disabled', 'notes' => $username )); return true; } return false; } function deleteUser($args) { global $users, $pages; global $login; global $syslog; // Arguments $username = $args['username']; $deleteContent = isset($args['deleteContent']) ? $args['deleteContent'] : false; // Only administrators can delete users if ($login->role() !== 'admin') { return false; } // The user admin cannot be deleted if ($username == 'admin') { return false; } // Check if the username exists if (!$users->exists($username)) { return false; } if ($deleteContent) { $pages->deletePagesByUser(array('username' => $username)); } else { $pages->transferPages(array('oldUsername' => $username)); } if ($users->delete($username)) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'user-deleted', 'notes' => $username )); return true; } return false; } function createUser($args) { global $users; global $L; global $syslog; $args['new_username'] = Text::removeSpecialCharacters($args['new_username']); // Check empty username if (Text::isEmpty($args['new_username'])) { Alert::set($L->g('username-field-is-empty'), ALERT_STATUS_FAIL); return false; } // Check already exist username if ($users->exists($args['new_username'])) { Alert::set($L->g('username-already-exists'), ALERT_STATUS_FAIL); return false; } // Password length if (Text::length($args['new_password']) < PASSWORD_LENGTH) { Alert::set($L->g('Password must be at least ' . PASSWORD_LENGTH . ' characters long'), ALERT_STATUS_FAIL); return false; } // Check new password and confirm password are equal if ($args['new_password'] !== $args['confirm_password']) { Alert::set($L->g('The password and confirmation password do not match'), ALERT_STATUS_FAIL); return false; } // Filter form fields $tmp = array(); $tmp['username'] = $args['new_username']; $tmp['password'] = $args['new_password']; $tmp['role'] = $args['role']; $tmp['email'] = $args['email']; // Add the user to the database if ($users->add($tmp)) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'new-user-created', 'notes' => $tmp['username'] )); return true; } return false; } function editSettings($args) { global $site; global $syslog; global $L; global $pages; if (isset($args['language'])) { if ($args['language'] != $site->language()) { $tmp = new dbJSON(PATH_LANGUAGES . $args['language'] . '.json', false); if (isset($tmp->db['language-data']['locale'])) { $args['locale'] = $tmp->db['language-data']['locale']; } else { $args['locale'] = $args['language']; } } } if (empty($args['homepage'])) { $args['homepage'] = ''; $args['uriBlog'] = ''; } if (empty($args['pageNotFound'])) { $args['pageNotFound'] = ''; } if (isset($args['uriPage'])) { $args['uriPage'] = Text::addSlashes($args['uriPage']); } if (isset($args['uriTag'])) { $args['uriTag'] = Text::addSlashes($args['uriTag']); } if (isset($args['uriCategory'])) { $args['uriCategory'] = Text::addSlashes($args['uriCategory']); } if (!empty($args['uriBlog'])) { $args['uriBlog'] = Text::addSlashes($args['uriBlog']); } else { if (!empty($args['homepage']) && empty($args['uriBlog'])) { $args['uriBlog'] = '/blog/'; } else { $args['uriBlog'] = ''; } } if (isset($args['extremeFriendly'])) { $args['extremeFriendly'] = (($args['extremeFriendly'] == 'true') ? true : false); } if (isset($args['customFields'])) { // Custom fields need to be JSON format valid, also the empty JSON need to be "{}" json_decode($args['customFields']); if (json_last_error() != JSON_ERROR_NONE) { return false; } $pages->setCustomFields($args['customFields']); } if ($site->set($args)) { // Check current order-by if changed it reorder the content if ($site->orderBy() != ORDER_BY) { if ($site->orderBy() == 'date') { $pages->sortByDate(); } else { $pages->sortByPosition(); } $pages->save(); } // Add syslog $syslog->add(array( 'dictionaryKey' => 'settings-changes', 'notes' => '' )); // Create alert Alert::set($L->g('The changes have been saved')); return true; } return false; } function changeUserPassword($args) { global $users; global $login; global $L; global $syslog; // Arguments $username = $args['username']; // Authorization: only admins or the user themselves can change a password if ($login->role() !== 'admin' && $login->username() !== $username) { Alert::set($L->g('You do not have permissions to access this page'), ALERT_STATUS_FAIL); return false; } $newPassword = $args['newPassword']; $confirmPassword = $args['confirmPassword']; // Password length if (Text::length($newPassword) < PASSWORD_LENGTH) { Alert::set($L->g('Password must be at least ' . PASSWORD_LENGTH . ' characters long'), ALERT_STATUS_FAIL); return false; } if ($newPassword != $confirmPassword) { Alert::set($L->g('The password and confirmation password do not match'), ALERT_STATUS_FAIL); return false; } if ($users->setPassword(array('username' => $username, 'password' => $newPassword))) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'user-password-changed', 'notes' => $username )); Alert::set($L->g('The changes have been saved'), ALERT_STATUS_OK); return true; } return false; } // Returns true if the user is allowed to proceed function checkRole($allowRoles, $redirect = true) { global $login; global $L; global $syslog; $userRole = $login->role(); if (in_array($userRole, $allowRoles, true)) { return true; } if ($redirect) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'access-denied', 'notes' => $login->username() )); Alert::set($L->g('You do not have sufficient permissions')); Redirect::page('dashboard'); } return false; } // Add a new category to the system // Returns TRUE is successfully added, FALSE otherwise function createCategory($args) { global $categories; global $L; global $syslog; if (Text::isEmpty($args['name'])) { Alert::set($L->g('Category name is empty'), ALERT_STATUS_FAIL); return false; } if ($categories->add(array('name' => $args['name'], 'description' => $args['description']))) { // Add to syslog $syslog->add(array( 'dictionaryKey' => 'new-category-created', 'notes' => $args['name'] )); Alert::set($L->g('Category added'), ALERT_STATUS_OK); return true; } Alert::set($L->g('The category already exists'), ALERT_STATUS_FAIL); return false; } function editCategory($args) { global $L; global $pages; global $categories; global $syslog; if (Text::isEmpty($args['name']) || Text::isEmpty($args['newKey'])) { Alert::set($L->g('Empty fields')); return false; } $newCategoryKey = $categories->edit($args); if ($newCategoryKey == false) { Alert::set($L->g('The category already exists')); return false; } // Change the category key in the pages database $pages->changeCategory($args['oldKey'], $newCategoryKey); // Add to syslog $syslog->add(array( 'dictionaryKey' => 'category-edited', 'notes' => $newCategoryKey )); Alert::set($L->g('The changes have been saved')); return true; } function deleteCategory($args) { global $L; global $categories; global $syslog; // Remove the category by key $categories->remove($args['oldKey']); // Remove the category from the pages ? or keep it if the user want to recovery the category ? // Add to syslog $syslog->add(array( 'dictionaryKey' => 'category-deleted', 'notes' => $args['oldKey'] )); Alert::set($L->g('The changes have been saved')); return true; } // Returns an array with all the categories // By default, the database of categories is alphanumeric sorted function getCategories() { global $categories; $list = array(); foreach ($categories->keys() as $key) { $category = new Category($key); array_push($list, $category); } return $list; } // Returns the object category if the category exists, FALSE otherwise function getCategory($key) { try { $category = new Category($key); return $category; } catch (Exception $e) { return false; } } // Returns an array with all the tags // By default, the database of tags is alphanumeric sorted function getTags() { global $tags; $list = array(); foreach ($tags->db as $key => $fields) { $tag = new Tag($key); array_push($list, $tag); } return $list; } // Returns the object tag if the tag exists, FALSE otherwise function getTag($key) { try { $tag = new Tag($key); return $tag; } catch (Exception $e) { return false; } } // Activate a theme function activateTheme($themeDirectory) { global $site; global $syslog; global $L, $language; if (Sanitize::pathFile(PATH_THEMES . $themeDirectory)) { // Disable current theme $currentTheme = $site->theme(); deactivatePlugin($currentTheme); // Install new theme if (Filesystem::fileExists(PATH_THEMES . $themeDirectory . DS . 'install.php')) { include_once(PATH_THEMES . $themeDirectory . DS . 'install.php'); } // Install theme's plugin activatePlugin($themeDirectory); $site->set(array('theme' => $themeDirectory)); $syslog->add(array( 'dictionaryKey' => 'new-theme-configured', 'notes' => $themeDirectory )); Alert::set($L->g('The changes have been saved')); return true; } return false; } function ajaxResponse($status = 0, $message = "", $data = array()) { $default = array('status' => $status, 'message' => $message); $output = array_merge($default, $data); exit(json_encode($output)); } /* | This function checks the image extension, | generate a new filename to not overwrite the exists, | generate the thumbnail, | and move the image to a proper place | | @file string Path and filename of the image | @imageDir string Path where the image is going to be stored | @thumbnailDir string Path where the thumbnail is going to be stored, if you don't set the variable is not going to create the thumbnail | | @return string/boolean Path and filename of the new image or FALSE if there were some error */ function transformImage($file, $imageDir, $thumbnailDir = false) { global $site; // Block dotfiles if (strpos(basename($file), '.') === 0) { if (file_exists($file)) { @unlink($file); } return false; } // Check image extension $fileExtension = Filesystem::extension($file); $fileExtension = Text::lowercase($fileExtension); if (!in_array($fileExtension, $GLOBALS['ALLOWED_IMG_EXTENSION'])) { return false; } // Generate a filename to not overwrite current image if exists $filename = Filesystem::filename($file); // Additional sanitization for filenames to prevent issues with special characters $filenameWithoutExt = pathinfo($filename, PATHINFO_FILENAME); $filenameWithoutExt = Text::removeSpecialCharacters($filenameWithoutExt, '-'); $filenameWithoutExt = Text::removeQuotes($filenameWithoutExt); $filenameWithoutExt = Text::removeSpaces($filenameWithoutExt, '-'); $filename = $filenameWithoutExt . '.' . $fileExtension; $nextFilename = Filesystem::nextFilename($filename, $imageDir); // Move the image to a proper place and rename $image = $imageDir . $nextFilename; Filesystem::mv($file, $image); chmod($image, 0644); // Generate Thumbnail if (!empty($thumbnailDir) && $site->thumbnailEnable()) { if (($fileExtension == 'svg')) { Filesystem::symlink($image, $thumbnailDir . $nextFilename); } else { $Image = new Image(); // setImage() returns false when GD cannot decode the source (missing // format support or corrupted file). Skip saveImage() in that case and // log so the operator can diagnose — the original image remains stored // so the Media Manager can still display it via the fallback path. if ($Image->setImage($image, $site->thumbnailWidth(), $site->thumbnailHeight(), 'crop') === false) { Log::set('Thumbnail generation skipped (GD cannot decode): ' . $image, LOG_TYPE_ERROR); } else { $Image->saveImage($thumbnailDir . $nextFilename, $site->thumbnailQuality()); } } } return $image; } /* | Builds the file list for the Media Manager. Scans originals (not thumbnails) | and pairs each original with its matching thumbnail. Used by list-images.php | and the Media Manager preload in media.php. | | The thumbnail filename usually matches the original, but can differ: | - Legacy uploads: before commit 82f30f8b thumbnails were forced to .jpg, so | pre-existing pairs may have different extensions (e.g., original test.png | with thumbnail test.jpg). | - Thumbnail generation disabled or failed: no thumbnail file exists at all. | In that case 'thumbnail' is returned as an empty string so the client can | fall back to the original image URL instead of building a broken | PAGE_THUMBNAILS_URL + filename that would 404. | | @imagePath string Filesystem path to the originals directory (trailing DS) | @thumbnailPath string Filesystem path to the thumbnails directory (trailing DS) | @chunk int Chunk size for pagination | | @return array[] Array of chunks; each chunk is an array of items with keys | 'filename' (original) and 'thumbnail' (resolved preview). */ function mediaManagerListImages($imagePath, $thumbnailPath, $chunk) { global $site; // Filesystem::listFiles globs '*.*' which does not match the 'thumbnails' // subdirectory (no dot in the name), so scanning the originals directory // naturally skips it. $chunks = Filesystem::listFiles($imagePath, '*', '*', MEDIA_MANAGER_SORT_BY_DATE, $chunk); if (empty($chunks)) { return array(); } // The site logo is stored in PATH_UPLOADS (not in a per-page directory) // alongside uploaded images when IMAGE_RESTRICT is off. Exclude it from the // Media Manager listing so it cannot be inserted/deleted as a media file. $logoFilename = ($site && method_exists($site, 'logo')) ? $site->logo(false) : ''; foreach ($chunks as $i => $chunkFiles) { $items = array(); foreach ($chunkFiles as $file) { if (is_dir($file)) { continue; } $filename = basename($file); if ($logoFilename && $filename === $logoFilename) { continue; } $extension = Text::lowercase(pathinfo($filename, PATHINFO_EXTENSION)); if (!in_array($extension, $GLOBALS['ALLOWED_IMG_EXTENSION'])) { continue; } // Resolve thumbnail. Fast path: same filename. Legacy fallback: before // commit 82f30f8b thumbnails were forced to .jpg, so try that single // extension when the fast path misses. Only accept the .jpg candidate // when it does not exist as an independent original (i.e. it is truly a // legacy thumbnail, not another file's own thumbnail). If nothing matches, // return an empty string so the client falls back to the original URL. $thumbnail = ''; if (is_file($thumbnailPath . $filename)) { $thumbnail = $filename; } else { $base = pathinfo($filename, PATHINFO_FILENAME); $candidate = $base . '.jpg'; if ($candidate !== $filename && is_file($thumbnailPath . $candidate) && !is_file($imagePath . $candidate) ) { $thumbnail = $candidate; } } $items[] = array( 'filename' => $filename, 'thumbnail' => $thumbnail, ); } $chunks[$i] = $items; } return $chunks; } function downloadRestrictedFile($file) { if (is_file($file)) { header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($file) . '"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($file)); readfile($file); exit(0); } }