Add Icon & Cover Image
Set status (on/off) for External Link to open new tab.
| File | Folder Link |
|---|---|
| edit.php | \\SYNAS\Allan\DOCUMENTATION\Component\Enhancement For Page Module\rckuc\wolf\app\views\page |
| PageController.php | \\SYNAS\Allan\DOCUMENTATION\Component\Enhancement For Page Module\rckuc\wolf\app\controllers |
| style.css | \\SYNAS\Allan\DOCUMENTATION\Component\Enhancement For Page Module\rckuc\wolf\admin\themes\black_and_white |
| backend.js | \\SYNAS\Allan\DOCUMENTATION\Component\Enhancement For Page Module\rckuc\wolf\admin\javascripts |
| menu.css & default.css | \\SYNAS\Allan\DOCUMENTATION\Component\Enhancement For Page Module\rckuc\public\themes\rckuc\css |
| rckuc.js | \\SYNAS\Allan\DOCUMENTATION\Component\Enhancement For Page Module\rckuc\public\themes\rckuc\js |
Update: views -> page -> edit.php
<!-- Update this part -->
<ul class="tabNavigation">
<li class="tab"><a href="#pagetitle"><?php echo __('Page Title'); ?></a></li>
<li class="tab"><a href="#metadata"><?php echo __('Metadata'); ?></a></li>
<li class="tab"><a href="#page-setting"><?php echo __('Page Setting'); ?></a></li>
<li class="tab"><a href="#menu-setting"><?php echo __('Menu Setting'); ?></a></li>
<?php Observer::notify('view_page_edit_tab_links', $page); ?>
</ul>
<!-- Change id 'settings' to 'page-setting' -->
<div id="page-setting" class="page">...</div>
<!-- Add New div 'menu-setting' -->
<div id="menu-setting" class="page">
<div id="div-menu-setting" class="title display_flex" title="<?php echo __('Menu Setting'); ?>">
<div id="icon">
<h3><?php echo __('Icon'); ?></h3>
<div id="meta-pages" class="pages">
<div class="drop-area" data-class="icon">
<input type="hidden" name="crop_icon" id="hidden_icon">
<input type="file" class="imgFile" name="icon" accept=".png, .jpg, .jpeg, .svg, .ico, .webp" data-max-size="500" data-min-width="80" data-min-height="80" hidden/>
<img class="ori_img" src="<?php echo !empty($page) && $page->icon ? (URL_PUBLIC . 'public/page/icon/' . $page->id . '/' . rawurlencode($page->icon)) : 'none'; ?>"></img>
<div class="previewContainer">
<button class="previewImg" type="button" data-url="<?php echo !empty($page) && $page->icon ? (URL_PUBLIC . 'public/page/icon/' . $page->id . '/' . rawurlencode($page->icon)) : 'none'; ?>"
style="<?php echo !empty($page) && $page->icon ? 'background-image: url(' . (URL_PUBLIC . 'public/page/icon/' . $page->id . '/' . rawurlencode($page->icon)) . ');' : 'none'; ?>">
</button>
<span class="plusIcon">+</span>
<div class="crop_icon">
<img src="<?php echo URL_PUBLIC ?>wolf/admin/images/crop.png" alt="crop" />
</div>
</div>
<p>Drag & Drop or <span class="clickUpload">Browse</span></p>
</div>
<span class="note">(Maximum Size: 500kb)</span>
<span class="note">(Minimun: 80px x 80px)</span>
<span class="note">(Allowed file type: .png, .jpg, .jpeg, .svg, .ico, .webp)</span>
</div>
<br>
<?php
$icon_status = (!empty($postdata['icon_status']) ? $postdata['icon_status'] : (!empty($page->icon_status) ? $page->icon_status : '0'));
$icon_display = $postdata['icon_display'] ?? ($page->icon_display ?? '');
?>
<h3><?php echo __('Status'); ?></h3>
<div id="meta-pages" class="switch_container">
<label class="switch">
<input type="checkbox" class="status_switch" name="page[icon_status]" <?php echo $icon_status == 1 ? 'checked' : ''; ?> value="1">
<span class="switch_slider round"></span>
</label>
<label class="switch_label"><?php echo $icon_status == 1 ? 'Show' : 'Hidden'; ?><label>
</div>
<br>
<h3><?php echo __('Display'); ?></h3>
<div id="meta-pages" class="pages">
<select class="icon_display" name="page[icon_display]">
<option></option>
<option value="text" <?= $icon_display === 'text' ? 'selected' : '' ?>>Show Text Only</option>
<option value="icon" <?= $icon_display === 'icon' ? 'selected' : '' ?>>Show Icon Only</option>
<option value="both" <?= $icon_display === 'both' ? 'selected' : '' ?>>Show Text & Icon</option>
</select>
</div>
</div>
<?php if (isset($page) && $page->id != 1) : ?>
<div id="cover">
<h3><?php echo __('Cover Image'); ?></h3>
<div id="meta-pages" class="pages">
<div class="drop-area" data-class="cover_image">
<input type="hidden" name="crop_cover_image" id="hidden_cover_image">
<input type="file" class="imgFile" name="cover_image" accept=".png, .jpg, .jpeg, .webp" data-max-size="500" data-min-width="600" data-min-height="450" data-ratio="4:3" data-crop="false" hidden/>
<img class="ori_img" src="<?php echo !empty($page) && $page->cover_image ? (URL_PUBLIC . 'public/page/cover_image/' . $page->id . '/' . rawurlencode($page->cover_image)) : 'none'; ?>"></img>
<div class="previewContainer">
<button class="previewImg" type="button" data-url="<?php echo !empty($page) && $page->cover_image ? (URL_PUBLIC . 'public/page/cover_image/' . $page->id . '/' . rawurlencode($page->cover_image)) : 'none'; ?>"
style="<?php echo !empty($page) && $page->cover_image ? 'background-image: url(' . (URL_PUBLIC . 'public/page/cover_image/' . $page->id . '/' . rawurlencode($page->cover_image)) . ');' : 'none'; ?>">
</button>
<span class="plusIcon">+</span>
<div class="crop_icon">
<img src="<?php echo URL_PUBLIC ?>wolf/admin/images/crop.png" alt="crop" />
</div>
</div>
<p>Drag & Drop or <span class="clickUpload">Browse</span></p>
</div>
<span class="note">(Maximum Size: 500kb)</span>
<span class="note">(Minimun: 600px x 450px)</span>
<span class="note">(Ratio: 4:3)</span>
<span class="note">(Allowed file type: .png, .jpg, .jpeg, .webp)</span>
</div>
<br>
<?php
$cover_status = (!empty($postdata['cover_status']) ? $postdata['cover_status'] : (!empty($page->cover_status) ? $page->cover_status : '0'));
?>
<h3><?php echo __('Status'); ?></h3>
<div id="meta-pages" class="switch_container">
<label class="switch">
<input type="checkbox" class="status_switch" name="page[cover_status]" <?php echo $cover_status == 1 ? 'checked' : ''; ?> value="1">
<span class="switch_slider round"></span>
</label>
<label class="switch_label"><?php echo $cover_status == 1 ? 'Show' : 'Hidden'; ?><label>
</div>
</div>
<?php endif ?>
</div>
</div>
<!-- Add Switch for external link redirection status -->
<?php
$redirection = !empty($page->redirection) ? $page->redirection : '0';
?>
<div id="meta-pages" class="switch_container">
<label class="switch">
<input type="checkbox" name="page[redirection]" <?php echo $redirection == 1 ? 'checked' : ''; ?> value="1">
<span class="switch_slider round"></span>
</label>
<label class="switch_label">Open New Tab<label>
</div>
<!-- Add Cropper -->
<?php $ratios = Option::findByCategory("ratio");
usort($ratios, function($a, $b) {
// Put "Free" on top
if ($a->name === 'Free' && $b->name !== 'Free') return -1;
if ($b->name === 'Free' && $a->name !== 'Free') return 1;
// Otherwise sort alphabetically
return strcasecmp($a->name, $b->name);
});
?>
<div id="cropModal">
<div class="modal-content">
<h2 class="modal-title">Crop Image</h2>
<div class="aspect-ratio-selector">
<label for="aspectRatio">Aspect Ratio:</label>
<select id="aspectRatio">
<?php foreach ($ratios as $ratio) : ?>
<option value="<?php echo $ratio->code; ?>"><?php echo $ratio->name; ?></option>
<?php endforeach ?>
</select>
</div>
<div class="crop-area">
<img id="cropImage" />
</div>
<div class="modal-actions">
<button id="cropConfirm" type="button">Crop & Save</button>
<button id="closeCropModal" type="button">Close</button>
</div>
</div>
</div>
<!-- Update this part -->
<ul class="tabNavigation">
<li class="tab"><a href="#pagetitle"><?php echo __('Page Title'); ?></a></li>
<li class="tab"><a href="#metadata"><?php echo __('Metadata'); ?></a></li>
<li class="tab"><a href="#page-setting"><?php echo __('Page Setting'); ?></a></li>
<li class="tab"><a href="#menu-setting"><?php echo __('Menu Setting'); ?></a></li>
<?php Observer::notify('view_page_edit_tab_links', $page); ?>
</ul>
<!-- Change id 'settings' to 'page-setting' -->
<div id="page-setting" class="page">...</div>
<!-- Add New div 'menu-setting' -->
<div id="menu-setting" class="page">
<div id="div-menu-setting" class="title display_flex" title="<?php echo __('Menu Setting'); ?>">
<div id="icon">
<h3><?php echo __('Icon'); ?></h3>
<div id="meta-pages" class="pages">
<div class="drop-area" data-class="icon">
<input type="hidden" name="crop_icon" id="hidden_icon">
<input type="file" class="imgFile" name="icon" accept=".png, .jpg, .jpeg, .svg, .ico, .webp" data-max-size="500" data-min-width="80" data-min-height="80" hidden/>
<img class="ori_img" src="<?php echo !empty($page) && $page->icon ? (URL_PUBLIC . 'public/page/icon/' . $page->id . '/' . rawurlencode($page->icon)) : 'none'; ?>"></img>
<div class="previewContainer">
<button class="previewImg" type="button" data-url="<?php echo !empty($page) && $page->icon ? (URL_PUBLIC . 'public/page/icon/' . $page->id . '/' . rawurlencode($page->icon)) : 'none'; ?>"
style="<?php echo !empty($page) && $page->icon ? 'background-image: url(' . (URL_PUBLIC . 'public/page/icon/' . $page->id . '/' . rawurlencode($page->icon)) . ');' : 'none'; ?>">
</button>
<span class="plusIcon">+</span>
<div class="crop_icon">
<img src="<?php echo URL_PUBLIC ?>wolf/admin/images/crop.png" alt="crop" />
</div>
</div>
<p>Drag & Drop or <span class="clickUpload">Browse</span></p>
</div>
<span class="note">(Maximum Size: 500kb)</span>
<span class="note">(Minimun: 80px x 80px)</span>
<span class="note">(Allowed file type: .png, .jpg, .jpeg, .svg, .ico, .webp)</span>
</div>
<br>
<?php
$icon_status = (!empty($postdata['icon_status']) ? $postdata['icon_status'] : (!empty($page->icon_status) ? $page->icon_status : '0'));
$icon_display = $postdata['icon_display'] ?? ($page->icon_display ?? '');
?>
<h3><?php echo __('Status'); ?></h3>
<div id="meta-pages" class="switch_container">
<label class="switch">
<input type="checkbox" class="status_switch" name="page[icon_status]" <?php echo $icon_status == 1 ? 'checked' : ''; ?> value="1">
<span class="switch_slider round"></span>
</label>
<label class="switch_label"><?php echo $icon_status == 1 ? 'Show' : 'Hidden'; ?><label>
</div>
<br>
<h3><?php echo __('Display'); ?></h3>
<div id="meta-pages" class="pages">
<select class="icon_display" name="page[icon_display]">
<option></option>
<option value="text" <?= $icon_display === 'text' ? 'selected' : '' ?>>Show Text Only</option>
<option value="icon" <?= $icon_display === 'icon' ? 'selected' : '' ?>>Show Icon Only</option>
<option value="both" <?= $icon_display === 'both' ? 'selected' : '' ?>>Show Text & Icon</option>
</select>
</div>
</div>
<?php if (isset($page) && $page->id != 1) : ?>
<div id="cover">
<h3><?php echo __('Cover Image'); ?></h3>
<div id="meta-pages" class="pages">
<div class="drop-area" data-class="cover_image">
<input type="hidden" name="crop_cover_image" id="hidden_cover_image">
<input type="file" class="imgFile" name="cover_image" accept=".png, .jpg, .jpeg, .webp" data-max-size="500" data-min-width="600" data-min-height="450" data-ratio="4:3" data-crop="false" hidden/>
<img class="ori_img" src="<?php echo !empty($page) && $page->cover_image ? (URL_PUBLIC . 'public/page/cover_image/' . $page->id . '/' . rawurlencode($page->cover_image)) : 'none'; ?>"></img>
<div class="previewContainer">
<button class="previewImg" type="button" data-url="<?php echo !empty($page) && $page->cover_image ? (URL_PUBLIC . 'public/page/cover_image/' . $page->id . '/' . rawurlencode($page->cover_image)) : 'none'; ?>"
style="<?php echo !empty($page) && $page->cover_image ? 'background-image: url(' . (URL_PUBLIC . 'public/page/cover_image/' . $page->id . '/' . rawurlencode($page->cover_image)) . ');' : 'none'; ?>">
</button>
<span class="plusIcon">+</span>
<div class="crop_icon">
<img src="<?php echo URL_PUBLIC ?>wolf/admin/images/crop.png" alt="crop" />
</div>
</div>
<p>Drag & Drop or <span class="clickUpload">Browse</span></p>
</div>
<span class="note">(Maximum Size: 500kb)</span>
<span class="note">(Minimun: 600px x 450px)</span>
<span class="note">(Ratio: 4:3)</span>
<span class="note">(Allowed file type: .png, .jpg, .jpeg, .webp)</span>
</div>
<br>
<?php
$cover_status = (!empty($postdata['cover_status']) ? $postdata['cover_status'] : (!empty($page->cover_status) ? $page->cover_status : '0'));
?>
<h3><?php echo __('Status'); ?></h3>
<div id="meta-pages" class="switch_container">
<label class="switch">
<input type="checkbox" class="status_switch" name="page[cover_status]" <?php echo $cover_status == 1 ? 'checked' : ''; ?> value="1">
<span class="switch_slider round"></span>
</label>
<label class="switch_label"><?php echo $cover_status == 1 ? 'Show' : 'Hidden'; ?><label>
</div>
</div>
<?php endif ?>
</div>
</div>
<!-- Add Switch for external link redirection status -->
<?php
$redirection = !empty($page->redirection) ? $page->redirection : '0';
?>
<div id="meta-pages" class="switch_container">
<label class="switch">
<input type="checkbox" name="page[redirection]" <?php echo $redirection == 1 ? 'checked' : ''; ?> value="1">
<span class="switch_slider round"></span>
</label>
<label class="switch_label">Open New Tab<label>
</div>
<!-- Add Cropper -->
<?php $ratios = Option::findByCategory("ratio");
usort($ratios, function($a, $b) {
// Put "Free" on top
if ($a->name === 'Free' && $b->name !== 'Free') return -1;
if ($b->name === 'Free' && $a->name !== 'Free') return 1;
// Otherwise sort alphabetically
return strcasecmp($a->name, $b->name);
});
?>
<div id="cropModal">
<div class="modal-content">
<h2 class="modal-title">Crop Image</h2>
<div class="aspect-ratio-selector">
<label for="aspectRatio">Aspect Ratio:</label>
<select id="aspectRatio">
<?php foreach ($ratios as $ratio) : ?>
<option value="<?php echo $ratio->code; ?>"><?php echo $ratio->name; ?></option>
<?php endforeach ?>
</select>
</div>
<div class="crop-area">
<img id="cropImage" />
</div>
<div class="modal-actions">
<button id="cropConfirm" type="button">Crop & Save</button>
<button id="closeCropModal" type="button">Close</button>
</div>
</div>
</div>
Update: PageController.php
Add 2 new functions:
Add verification in _store()
Add image upload function in _store()
function upload_file($origin, $dest, $tmp_name, $overwrite = false, $nid = null, $dbColumnName = null) {
FileManagerController::_checkPermission();
// Config: max upload limit in bytes
$max_upload_limit = Setting::get('max_img_size_upload');
$max_upload_bytes = $max_upload_limit * 1024 * 1024;
$origin = basename($origin);
$file_ext = (strpos($origin, '.') === false ? '' : '.' . pathinfo($origin, PATHINFO_EXTENSION));
$file_base = pathinfo($origin, PATHINFO_FILENAME);
$file_base = preg_replace('/_\d+$/', '', $file_base); // remove _1, _2, etc.
$full_dest = $dest . $origin;
$file_name = $origin;
// Check file size
$uploaded_file_size = filesize($tmp_name);
if ($uploaded_file_size > $max_upload_bytes) {
Flash::set('error', __('Uploaded file exceeds the maximum allowed size of ' . $max_upload_limit . 'MB.'));
return false;
}
// Ensure destination folder exists
if (!file_exists($dest)) {
if (!mkdir($dest, 0755, true)) {
Flash::set('error', __('Failed to create destination directory.'));
return false;
}
}
$page = Page::findById($nid);
$oldFilename = $page->$dbColumnName;
// Always generate a new unique filename if file already exists
for ($i = 1; file_exists($full_dest); $i++) {
$file_name = $file_base . '_' . $i . $file_ext;
$full_dest = $dest . $file_name;
}
// Delete the old file only once, if overwrite is enabled
if ($page && $overwrite && $oldFilename && $oldFilename !== $file_name) {
@unlink($dest . $oldFilename);
}
// Move uploaded file
if ((is_uploaded_file($tmp_name) && move_uploaded_file($tmp_name, $full_dest))
|| (!is_uploaded_file($tmp_name) && rename($tmp_name, $full_dest))) {
chmod($full_dest, 0644);
// Update DB if needed
if ($page && $dbColumnName) {
$updated = $page->update('Page',[$dbColumnName => $file_name], 'id=' . (int) $nid);
if (!$updated) {
Flash::set('error', __('Image has not been updated in Database: ' . $dbColumnName));
}
}
return $file_name;
}
return false;
} // upload_file
// Upload if any cropped image exists, if not upload the original image.
function upload_img($module, $field, $overwrite=null, $id, $old_file_name=null) {
$failed = null;
$croppedField = 'crop_'.$field;
// Case 1: Cropped image sent as Base64 (hidden input)
if (!empty($_POST[$croppedField])) {
$data = $_POST[$croppedField];
if (strpos($data, ',') !== false) {
list(, $data) = explode(',', $data, 2);
}
$data = base64_decode($data);
// Create a temp file to mimic an uploaded file
$tmpFile = tempnam(sys_get_temp_dir(), 'crop_');
file_put_contents($tmpFile, $data);
$filename = '';
if (!empty($_FILES[$field]['name'])) {
$filename = $_FILES[$field]['name'];
} else if ($old_file_name) {
$filename = $old_file_name;
} else {
$filename = 'crop_'.$field.'.webp';
}
$path = FILES_DIR . '/'. $module .'/' . $field . '/' . $id . '/';
// Reuse your upload_file()
$upload = $this->upload_file($filename,$path,$tmpFile,$overwrite,$id,$field);
// Clean up the temp file
unlink($tmpFile);
if (!$upload) {
$failed = 'Cropped ' . ucwords(str_replace('_', ' ', $field));
}
// Case 2: Original file uploaded via input[type=file]
} else if (!empty($_FILES[$field]['tmp_name']) && $_FILES[$field]['error'] === UPLOAD_ERR_OK) {
$upload = $this->upload_file($_FILES[$field]['name'], FILES_DIR . '/'. $module .'/' . $field . '/' . $id . '/', $_FILES[$field]['tmp_name'], $overwrite, $id, $field);
if (!$upload) {
$failed = ucwords(str_replace('_', ' ', $field));
}
}
return $failed;
}
private function _store($action, $id=false) {
// Sanity checks
if ($action == 'edit' && !$id)
throw new Exception('Trying to edit page when $id is false.');
use_helper('Validate');
$data = $_POST['page'];
$data['is_protected'] = !empty($data['is_protected']) ? 1 : 0;
Flash::set('post_data', (object) $data);
$pagesetting = array();
if ($id == 1){
// $upload = $_POST['upload'];
$pagesetting = $_POST['pagesetting'];
//Flash::set('post_settingdata', (object) $pagesetting);
}
// Add pre-save checks here
$errors = false;
$error_fields = false;
// CSRF checks
if (isset($_POST['csrf_token'])) {
$csrf_token = $_POST['csrf_token'];
$csrf_id = '';
if ($action === 'edit') { $csrf_id = '/'.$id; }
if (!SecureToken::validateToken($csrf_token, BASE_URL.'page/'.$action.$csrf_id)) {
$errors[] = __('Invalid CSRF token found!');
}
}
else {
$errors[] = __('No CSRF token found!');
}
$data['title'] = trim($data['title']);
if (empty($data['title'])) {
$error_fields[] = __('Page Title');
}
/** homepage setting check **/
if ($id == 1){
if (empty($pagesetting['meeting_venue'])){
$error_fields[] = __('Meeting Venue');
}
if (empty($pagesetting['meeting_google_map'])){
$error_fields[] = __('Meeting Google Map Link');
}
if (empty($pagesetting['meeting_day'])){
$error_fields[] = __('Meeting Day');
}
if (empty($pagesetting['meeting_time'])){
$error_fields[] = __('Meeting Time');
}
if (empty($pagesetting['donate_short_text'])){
$error_fields[] = __('Donate Short Text');
}
if (empty($pagesetting['project_short_text'])){
$error_fields[] = __('Project Short Text');
}
$pagesetting_ori = PageSetting::init();
}
$data['slug'] = (!empty($data['slug']) ? trim($data['slug']) : '');
if (empty($data['slug']) && $id != '1') {
$error_fields[] = __('Slug');
}
else {
if ($data['slug'] == ADMIN_DIR) {
$errors[] = __('You cannot have a slug named :slug!', array(':slug' => ADMIN_DIR));
}
if (!Validate::slug($data['slug']) && (!empty($data['slug']) && $id == '1')) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => 'slug'));
}
}
// Check all numerical fields for a page
$fields = array('parent_id', 'layout_id', 'needs_login');
foreach ($fields as $field) {
if (!Validate::digit($data[$field])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => $field));
}
}
// Check all date fields for a page
$fields = array('created_on', 'published_on', 'valid_until');
foreach ($fields as $field) {
if (isset($data[$field])) {
$data[$field] = trim($data[$field]);
if (!empty($data[$field]) && !(bool) preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/D', (string) $data[$field])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => $field));
}
}
}
// Check all time fields for a page
$fields = array('created_on_time', 'published_on_time', 'valid_until_time');
foreach ($fields as $field) {
if (isset($data[$field])) {
$data[$field] = trim($data[$field]);
if (!empty($data[$field]) && !(bool) preg_match('/^[0-9]{2}:[0-9]{2}:[0-9]{2}$/D', (string) $data[$field])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => $field));
}
}
}
// Check alphanumerical fields
$fields = array('keywords', 'description');
foreach ($fields as $field) {
use_helper('Kses');
$data[$field] = kses(trim($data[$field]), array());
/*
if (!empty($data[$field]) && !Validate::alpha_comma($data[$field])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => $field));
}
*
*/
}
// Check behaviour_id field
if (!empty($data['behaviour_id']) && !Validate::slug($data['behaviour_id'])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => 'behaviour_id'));
}
// Make sure the title doesn't contain HTML
if (Setting::get('allow_html_title') == 'off') {
use_helper('Kses');
$data['title'] = kses(trim($data['title']), array());
}
// verification
if (empty($data['redirection'])){
$data['redirection'] = 0;
}
if (empty($data['icon_status'])) {
$data['icon_status'] = 0;
}
if (empty($data['cover_status'])) {
$data['cover_status'] = 0;
}
// Create the page object to be manipulated and populate data
if ($action == 'add') {
$page = new Page($data);
}
else {
$page = Record::findByIdFrom('Page', $id);
$page->setFromData($data);
}
// Upon errors, rebuild original page and return to screen with errors
if (false !== $errors || $error_fields !== false) {
$tags = $_POST['page_tag'];
// Rebuild time fields
if (isset($page->created_on) && isset($page->created_on_time)) {
$page->created_on = $page->created_on.' '.$page->created_on_time;
}
if (isset($page->published_on) && isset($page->published_on_time)) {
$page->published_on = $page->published_on.' '.$page->published_on_time;
}
if (isset($page->valid_until)) {
$page->valid_until = $page->valid_until.' '.$page->valid_until_time;
}
// Rebuild parts
$part = '';
if (!empty($_POST['part'])) {
$part = $_POST['part'];
$tmp = false;
foreach ($part as $key => $val) {
$tmp[$key] = (object) $val;
}
$part = $tmp;
}
// Set the errors to be displayed.
$err_msg = ($errors != false ? implode('<br/>', $errors) : '');
$err_msg .= ($error_fields != false ? 'Please specify these fields: '.implode(', ', $error_fields) : '');
Flash::setNow('error', $err_msg);
// display things ...
$this->setLayout('backend');
$pagesettingobj = new stdClass();
foreach ($pagesetting as $name => $value) {
$pagesettingobj->$name = $value;
}
$this->display('page/edit', array(
'action' => $action,
'csrf_token' => SecureToken::generateToken(BASE_URL.'page/'.$action),
'page' => (object) $page,
'pagesetting' => $pagesettingobj,
'tags' => $tags,
'filters' => Filter::findAll(),
'behaviors' => Behavior::findAll(),
'page_parts' => $part,
'layouts' => Record::findAllFrom('Layout'))
);
}
// Notify
if ($action == 'add') {
Observer::notify('page_add_before_save', $page);
}
else {
Observer::notify('page_edit_before_save', $page);
}
// Time to actually save the page
// @todo rebuild this so parts are already set before save?
// @todo determine lazy init impact
if ($page->save()) {
// Get data for parts of this page
$data_parts = $_POST['part'];
Flash::set('post_parts_data', (object) $data_parts);
if ($action == 'edit') {
$old_parts = PagePart::findByPageId($id);
// check if all old page part are passed in POST
// if not ... we need to delete it!
foreach ($old_parts as $old_part) {
$not_in = true;
foreach ($data_parts as $part_id => $data) {
$data['name'] = trim($data['name']);
if ($old_part->name == $data['name']) {
$not_in = false;
// this will not really create a new page part because
// the id of the part is passed in $data
$part = new PagePart($data);
$part->page_id = $id;
Observer::notify('part_edit_before_save', $part);
$part->save();
Observer::notify('part_edit_after_save', $part);
unset($data_parts[$part_id]);
break;
}
}
if ($not_in)
$old_part->delete();
}
}
// add the new parts
foreach ($data_parts as $data) {
$data['name'] = trim($data['name']);
$part = new PagePart($data);
$part->page_id = $page->id;
Observer::notify('part_add_before_save', $part);
$part->save();
Observer::notify('part_add_after_save', $part);
}
// save tags
$page->saveTags($_POST['page_tag']['tags']);
// save homepage info - customization
if ($id == 1) {
PageSetting::saveFromData($pagesetting);
}
$overwrite = true;
$failed = [];
// Upload 2 type images
// Make sure fields name match database name
$fields = ['icon', 'cover_image'];
foreach ($fields as $field) {
$failed[] = $this->upload_img('page', $field, $overwrite, $page->id, $page->$field);
}
if (!empty(array_filter($failed))) {
Flash::set('error', __('Page has been saved. Failed to update: ' . implode(', ', $failed)));
} else {
Flash::set('success', __('Page has been saved.'));
}
}
else {
Flash::set('error', __('Page has not been saved!'));
$url = 'page/';
$url .= ( $action == 'edit') ? 'edit/'.$id : 'add/';
redirect(get_url($url));
}
if ($action == 'add') {
Observer::notify('page_add_after_save', $page);
}
else {
Observer::notify('page_edit_after_save', $page);
}
// save and quit or save and continue editing ?
if (isset($_POST['commit'])) {
redirect(get_url('page'));
}
else {
redirect(get_url('page/edit/'.$page->id));
}
}
function upload_file($origin, $dest, $tmp_name, $overwrite = false, $nid = null, $dbColumnName = null) {
FileManagerController::_checkPermission();
// Config: max upload limit in bytes
$max_upload_limit = Setting::get('max_img_size_upload');
$max_upload_bytes = $max_upload_limit * 1024 * 1024;
$origin = basename($origin);
$file_ext = (strpos($origin, '.') === false ? '' : '.' . pathinfo($origin, PATHINFO_EXTENSION));
$file_base = pathinfo($origin, PATHINFO_FILENAME);
$file_base = preg_replace('/_\d+$/', '', $file_base); // remove _1, _2, etc.
$full_dest = $dest . $origin;
$file_name = $origin;
// Check file size
$uploaded_file_size = filesize($tmp_name);
if ($uploaded_file_size > $max_upload_bytes) {
Flash::set('error', __('Uploaded file exceeds the maximum allowed size of ' . $max_upload_limit . 'MB.'));
return false;
}
// Ensure destination folder exists
if (!file_exists($dest)) {
if (!mkdir($dest, 0755, true)) {
Flash::set('error', __('Failed to create destination directory.'));
return false;
}
}
$page = Page::findById($nid);
$oldFilename = $page->$dbColumnName;
// Always generate a new unique filename if file already exists
for ($i = 1; file_exists($full_dest); $i++) {
$file_name = $file_base . '_' . $i . $file_ext;
$full_dest = $dest . $file_name;
}
// Delete the old file only once, if overwrite is enabled
if ($page && $overwrite && $oldFilename && $oldFilename !== $file_name) {
@unlink($dest . $oldFilename);
}
// Move uploaded file
if ((is_uploaded_file($tmp_name) && move_uploaded_file($tmp_name, $full_dest))
|| (!is_uploaded_file($tmp_name) && rename($tmp_name, $full_dest))) {
chmod($full_dest, 0644);
// Update DB if needed
if ($page && $dbColumnName) {
$updated = $page->update('Page',[$dbColumnName => $file_name], 'id=' . (int) $nid);
if (!$updated) {
Flash::set('error', __('Image has not been updated in Database: ' . $dbColumnName));
}
}
return $file_name;
}
return false;
} // upload_file
// Upload if any cropped image exists, if not upload the original image.
function upload_img($module, $field, $overwrite=null, $id, $old_file_name=null) {
$failed = null;
$croppedField = 'crop_'.$field;
// Case 1: Cropped image sent as Base64 (hidden input)
if (!empty($_POST[$croppedField])) {
$data = $_POST[$croppedField];
if (strpos($data, ',') !== false) {
list(, $data) = explode(',', $data, 2);
}
$data = base64_decode($data);
// Create a temp file to mimic an uploaded file
$tmpFile = tempnam(sys_get_temp_dir(), 'crop_');
file_put_contents($tmpFile, $data);
$filename = '';
if (!empty($_FILES[$field]['name'])) {
$filename = $_FILES[$field]['name'];
} else if ($old_file_name) {
$filename = $old_file_name;
} else {
$filename = 'crop_'.$field.'.webp';
}
$path = FILES_DIR . '/'. $module .'/' . $field . '/' . $id . '/';
// Reuse your upload_file()
$upload = $this->upload_file($filename,$path,$tmpFile,$overwrite,$id,$field);
// Clean up the temp file
unlink($tmpFile);
if (!$upload) {
$failed = 'Cropped ' . ucwords(str_replace('_', ' ', $field));
}
// Case 2: Original file uploaded via input[type=file]
} else if (!empty($_FILES[$field]['tmp_name']) && $_FILES[$field]['error'] === UPLOAD_ERR_OK) {
$upload = $this->upload_file($_FILES[$field]['name'], FILES_DIR . '/'. $module .'/' . $field . '/' . $id . '/', $_FILES[$field]['tmp_name'], $overwrite, $id, $field);
if (!$upload) {
$failed = ucwords(str_replace('_', ' ', $field));
}
}
return $failed;
}
private function _store($action, $id=false) {
// Sanity checks
if ($action == 'edit' && !$id)
throw new Exception('Trying to edit page when $id is false.');
use_helper('Validate');
$data = $_POST['page'];
$data['is_protected'] = !empty($data['is_protected']) ? 1 : 0;
Flash::set('post_data', (object) $data);
$pagesetting = array();
if ($id == 1){
// $upload = $_POST['upload'];
$pagesetting = $_POST['pagesetting'];
//Flash::set('post_settingdata', (object) $pagesetting);
}
// Add pre-save checks here
$errors = false;
$error_fields = false;
// CSRF checks
if (isset($_POST['csrf_token'])) {
$csrf_token = $_POST['csrf_token'];
$csrf_id = '';
if ($action === 'edit') { $csrf_id = '/'.$id; }
if (!SecureToken::validateToken($csrf_token, BASE_URL.'page/'.$action.$csrf_id)) {
$errors[] = __('Invalid CSRF token found!');
}
}
else {
$errors[] = __('No CSRF token found!');
}
$data['title'] = trim($data['title']);
if (empty($data['title'])) {
$error_fields[] = __('Page Title');
}
/** homepage setting check **/
if ($id == 1){
if (empty($pagesetting['meeting_venue'])){
$error_fields[] = __('Meeting Venue');
}
if (empty($pagesetting['meeting_google_map'])){
$error_fields[] = __('Meeting Google Map Link');
}
if (empty($pagesetting['meeting_day'])){
$error_fields[] = __('Meeting Day');
}
if (empty($pagesetting['meeting_time'])){
$error_fields[] = __('Meeting Time');
}
if (empty($pagesetting['donate_short_text'])){
$error_fields[] = __('Donate Short Text');
}
if (empty($pagesetting['project_short_text'])){
$error_fields[] = __('Project Short Text');
}
$pagesetting_ori = PageSetting::init();
}
$data['slug'] = (!empty($data['slug']) ? trim($data['slug']) : '');
if (empty($data['slug']) && $id != '1') {
$error_fields[] = __('Slug');
}
else {
if ($data['slug'] == ADMIN_DIR) {
$errors[] = __('You cannot have a slug named :slug!', array(':slug' => ADMIN_DIR));
}
if (!Validate::slug($data['slug']) && (!empty($data['slug']) && $id == '1')) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => 'slug'));
}
}
// Check all numerical fields for a page
$fields = array('parent_id', 'layout_id', 'needs_login');
foreach ($fields as $field) {
if (!Validate::digit($data[$field])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => $field));
}
}
// Check all date fields for a page
$fields = array('created_on', 'published_on', 'valid_until');
foreach ($fields as $field) {
if (isset($data[$field])) {
$data[$field] = trim($data[$field]);
if (!empty($data[$field]) && !(bool) preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/D', (string) $data[$field])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => $field));
}
}
}
// Check all time fields for a page
$fields = array('created_on_time', 'published_on_time', 'valid_until_time');
foreach ($fields as $field) {
if (isset($data[$field])) {
$data[$field] = trim($data[$field]);
if (!empty($data[$field]) && !(bool) preg_match('/^[0-9]{2}:[0-9]{2}:[0-9]{2}$/D', (string) $data[$field])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => $field));
}
}
}
// Check alphanumerical fields
$fields = array('keywords', 'description');
foreach ($fields as $field) {
use_helper('Kses');
$data[$field] = kses(trim($data[$field]), array());
/*
if (!empty($data[$field]) && !Validate::alpha_comma($data[$field])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => $field));
}
*
*/
}
// Check behaviour_id field
if (!empty($data['behaviour_id']) && !Validate::slug($data['behaviour_id'])) {
$errors[] = __('Illegal value for :fieldname field!', array(':fieldname' => 'behaviour_id'));
}
// Make sure the title doesn't contain HTML
if (Setting::get('allow_html_title') == 'off') {
use_helper('Kses');
$data['title'] = kses(trim($data['title']), array());
}
// verification
if (empty($data['redirection'])){
$data['redirection'] = 0;
}
if (empty($data['icon_status'])) {
$data['icon_status'] = 0;
}
if (empty($data['cover_status'])) {
$data['cover_status'] = 0;
}
// Create the page object to be manipulated and populate data
if ($action == 'add') {
$page = new Page($data);
}
else {
$page = Record::findByIdFrom('Page', $id);
$page->setFromData($data);
}
// Upon errors, rebuild original page and return to screen with errors
if (false !== $errors || $error_fields !== false) {
$tags = $_POST['page_tag'];
// Rebuild time fields
if (isset($page->created_on) && isset($page->created_on_time)) {
$page->created_on = $page->created_on.' '.$page->created_on_time;
}
if (isset($page->published_on) && isset($page->published_on_time)) {
$page->published_on = $page->published_on.' '.$page->published_on_time;
}
if (isset($page->valid_until)) {
$page->valid_until = $page->valid_until.' '.$page->valid_until_time;
}
// Rebuild parts
$part = '';
if (!empty($_POST['part'])) {
$part = $_POST['part'];
$tmp = false;
foreach ($part as $key => $val) {
$tmp[$key] = (object) $val;
}
$part = $tmp;
}
// Set the errors to be displayed.
$err_msg = ($errors != false ? implode('<br/>', $errors) : '');
$err_msg .= ($error_fields != false ? 'Please specify these fields: '.implode(', ', $error_fields) : '');
Flash::setNow('error', $err_msg);
// display things ...
$this->setLayout('backend');
$pagesettingobj = new stdClass();
foreach ($pagesetting as $name => $value) {
$pagesettingobj->$name = $value;
}
$this->display('page/edit', array(
'action' => $action,
'csrf_token' => SecureToken::generateToken(BASE_URL.'page/'.$action),
'page' => (object) $page,
'pagesetting' => $pagesettingobj,
'tags' => $tags,
'filters' => Filter::findAll(),
'behaviors' => Behavior::findAll(),
'page_parts' => $part,
'layouts' => Record::findAllFrom('Layout'))
);
}
// Notify
if ($action == 'add') {
Observer::notify('page_add_before_save', $page);
}
else {
Observer::notify('page_edit_before_save', $page);
}
// Time to actually save the page
// @todo rebuild this so parts are already set before save?
// @todo determine lazy init impact
if ($page->save()) {
// Get data for parts of this page
$data_parts = $_POST['part'];
Flash::set('post_parts_data', (object) $data_parts);
if ($action == 'edit') {
$old_parts = PagePart::findByPageId($id);
// check if all old page part are passed in POST
// if not ... we need to delete it!
foreach ($old_parts as $old_part) {
$not_in = true;
foreach ($data_parts as $part_id => $data) {
$data['name'] = trim($data['name']);
if ($old_part->name == $data['name']) {
$not_in = false;
// this will not really create a new page part because
// the id of the part is passed in $data
$part = new PagePart($data);
$part->page_id = $id;
Observer::notify('part_edit_before_save', $part);
$part->save();
Observer::notify('part_edit_after_save', $part);
unset($data_parts[$part_id]);
break;
}
}
if ($not_in)
$old_part->delete();
}
}
// add the new parts
foreach ($data_parts as $data) {
$data['name'] = trim($data['name']);
$part = new PagePart($data);
$part->page_id = $page->id;
Observer::notify('part_add_before_save', $part);
$part->save();
Observer::notify('part_add_after_save', $part);
}
// save tags
$page->saveTags($_POST['page_tag']['tags']);
// save homepage info - customization
if ($id == 1) {
PageSetting::saveFromData($pagesetting);
}
$overwrite = true;
$failed = [];
// Upload 2 type images
// Make sure fields name match database name
$fields = ['icon', 'cover_image'];
foreach ($fields as $field) {
$failed[] = $this->upload_img('page', $field, $overwrite, $page->id, $page->$field);
}
if (!empty(array_filter($failed))) {
Flash::set('error', __('Page has been saved. Failed to update: ' . implode(', ', $failed)));
} else {
Flash::set('success', __('Page has been saved.'));
}
}
else {
Flash::set('error', __('Page has not been saved!'));
$url = 'page/';
$url .= ( $action == 'edit') ? 'edit/'.$id : 'add/';
redirect(get_url($url));
}
if ($action == 'add') {
Observer::notify('page_add_after_save', $page);
}
else {
Observer::notify('page_edit_after_save', $page);
}
// save and quit or save and continue editing ?
if (isset($_POST['commit'])) {
redirect(get_url('page'));
}
else {
redirect(get_url('page/edit/'.$page->id));
}
}
Update: style.css
/* For page image */
#page_edit_form #div-menu-setting {
align-items: flex-start;
gap: 3rem;
}
#page_edit_form #icon .previewImg {
height: 160px;
width: 160px;
}
#page_edit_form #cover .previewImg {
height: 200px;
width: 267px;
}
select.icon_display {
padding: 4.5px 10px;
}
/* For page image */
#page_edit_form #div-menu-setting {
align-items: flex-start;
gap: 3rem;
}
#page_edit_form #icon .previewImg {
height: 160px;
width: 160px;
}
#page_edit_form #cover .previewImg {
height: 200px;
width: 267px;
}
select.icon_display {
padding: 4.5px 10px;
}
Update: backend.js
// For module 'page' -> 'external_link' redirection
$(document).ready(function () {
function toggle_redirection() {
if ($("#external_url").val() !== "") {
$("#external_url").parent().next().fadeIn();
} else {
$("#external_url").parent().next().hide();
}
}
toggle_redirection();
$(document).on("keyup", "#external_url", function() {
toggle_redirection();
})
})
// For module 'page' -> 'external_link' redirection
$(document).ready(function () {
function toggle_redirection() {
if ($("#external_url").val() !== "") {
$("#external_url").parent().next().fadeIn();
} else {
$("#external_url").parent().next().hide();
}
}
toggle_redirection();
$(document).on("keyup", "#external_url", function() {
toggle_redirection();
})
})
Update Database: wolf_page
Add 6 new columns
ALTER TABLE wolf_page
ADD COLUMN `redirection` INT(1) DEFAULT '0' AFTER `location`,
ADD COLUMN `icon` TEXT AFTER `redirection`,
ADD COLUMN `cover_image` TEXT AFTER `icon`,
ADD COLUMN `icon_status` INT(1) DEFAULT NULL AFTER `cover_image`,
ADD COLUMN `cover_status` INT(1) DEFAULT NULL AFTER `icon_status`,
ADD COLUMN `icon_display` VARCHAR(100) DEFAULT NULL AFTER `cover_status`;
ALTER TABLE wolf_page
ADD COLUMN `redirection` INT(1) DEFAULT '0' AFTER `location`,
ADD COLUMN `icon` TEXT AFTER `redirection`,
ADD COLUMN `cover_image` TEXT AFTER `icon`,
ADD COLUMN `icon_status` INT(1) DEFAULT NULL AFTER `cover_image`,
ADD COLUMN `cover_status` INT(1) DEFAULT NULL AFTER `icon_status`,
ADD COLUMN `icon_display` VARCHAR(100) DEFAULT NULL AFTER `cover_status`;
Update Snippet: menubar
<?php
function titleWithIcon($page, $defaultTitle = '') {
$title = '';
$icon = '';
// Image if icon enabled, display is not "text", and file exists
if ((int)$page->icon_status === 1 && $page->icon_display !== 'text' && !empty($page->icon)) {
$icon = '<img class="menu-icon" src="'.URL_PUBLIC.'public/page/icon/'.$page->id.'/'.$page->icon.'" alt="menu">';
}
// Add text if display is not "icon" OR image is missing
if ($page->icon_display !== 'icon' || empty($icon)) {
$title = ucfirst($defaultTitle ?: $page->title);
}
return $icon . $title;
}
function getCoverImg($page) {
$cover_image = "";
if ((int)$page->cover_status === 1 && !empty($page->cover_image)) {
$cover_image = URL_PUBLIC.'public/page/cover_image/'.$page->id.'/'.rawurlencode($page->cover_image);
}
return $cover_image;
}
function snippet_sitemap($page_submenu, $root)
{
$out = '';
$count = count($page_submenu->children(array()));
if ($count > 0 && $page_submenu->level() < 2) {
if ($page_submenu->id == 1) {
$out = '<ul id="menulist">
<li>
<a href="' . URL_PUBLIC . '" ' . (url_match($page_submenu->slug) ? ' class="active"' : 'class=""') . '>'.titleWithIcon($page_submenu, 'HOME').'</a>
</li>';
} else {
if (!empty(getCoverImg($page_submenu))) {
$out = '<div class="thumb"><ul>';
} else {
$out = '<ul>';
}
}
$idx = 1;
foreach ($page_submenu->children(array()) as $menuPage) {
if ($menuPage->location == 'top') {
$active = url_start_with($menuPage->url) ? ' class="active"' : 'class=""';
$data_img = ' data-img = "' . getCoverImg($menuPage) . '"';
if ($menuPage->type == "link") {
if ($menuPage->external_url == '') {
$out .= '<li><span>' . titleWithIcon($menuPage) . '</span>' . snippet_sitemap($menuPage, 0) .'</li>';
} else {
$newtab = (int)$menuPage->redirection == 1 ? ' target="_blank"' : '';
$out .= '<li>' . $menuPage->extlink(titleWithIcon($menuPage), $active . $newtab . $data_img, $menuPage->external_url) . snippet_sitemap($menuPage, 0) .'</li>';
}
} else {
$out .= '<li>' . $menuPage->link(titleWithIcon($menuPage), $active . $data_img) . snippet_sitemap($menuPage, 0) .'</li>';
}
}
$idx += 1;
}
if (!empty(getCoverImg($page_submenu))) {
$out .= '</ul><div class="page-cover-img" data-img="'.getCoverImg($page_submenu).'"></div></div>';
} else {
$out .= '</ul>';
}
} else {
if (!empty(getCoverImg($page_submenu))) {
$out = '<div class="thumb"><div class="page-cover-img" data-img="'.getCoverImg($page_submenu).'"></div></div>';
} else {
$out = '';
}
}
return $out;
}
?>
<?php
echo snippet_sitemap($this->find('/'), 1);
?>
<?php
function titleWithIcon($page, $defaultTitle = '') {
$title = '';
$icon = '';
// Image if icon enabled, display is not "text", and file exists
if ((int)$page->icon_status === 1 && $page->icon_display !== 'text' && !empty($page->icon)) {
$icon = '<img class="menu-icon" src="'.URL_PUBLIC.'public/page/icon/'.$page->id.'/'.$page->icon.'" alt="menu">';
}
// Add text if display is not "icon" OR image is missing
if ($page->icon_display !== 'icon' || empty($icon)) {
$title = ucfirst($defaultTitle ?: $page->title);
}
return $icon . $title;
}
function getCoverImg($page) {
$cover_image = "";
if ((int)$page->cover_status === 1 && !empty($page->cover_image)) {
$cover_image = URL_PUBLIC.'public/page/cover_image/'.$page->id.'/'.rawurlencode($page->cover_image);
}
return $cover_image;
}
function snippet_sitemap($page_submenu, $root)
{
$out = '';
$count = count($page_submenu->children(array()));
if ($count > 0 && $page_submenu->level() < 2) {
if ($page_submenu->id == 1) {
$out = '<ul id="menulist">
<li>
<a href="' . URL_PUBLIC . '" ' . (url_match($page_submenu->slug) ? ' class="active"' : 'class=""') . '>'.titleWithIcon($page_submenu, 'HOME').'</a>
</li>';
} else {
if (!empty(getCoverImg($page_submenu))) {
$out = '<div class="thumb"><ul>';
} else {
$out = '<ul>';
}
}
$idx = 1;
foreach ($page_submenu->children(array()) as $menuPage) {
if ($menuPage->location == 'top') {
$active = url_start_with($menuPage->url) ? ' class="active"' : 'class=""';
$data_img = ' data-img = "' . getCoverImg($menuPage) . '"';
if ($menuPage->type == "link") {
if ($menuPage->external_url == '') {
$out .= '<li><span>' . titleWithIcon($menuPage) . '</span>' . snippet_sitemap($menuPage, 0) .'</li>';
} else {
$newtab = (int)$menuPage->redirection == 1 ? ' target="_blank"' : '';
$out .= '<li>' . $menuPage->extlink(titleWithIcon($menuPage), $active . $newtab . $data_img, $menuPage->external_url) . snippet_sitemap($menuPage, 0) .'</li>';
}
} else {
$out .= '<li>' . $menuPage->link(titleWithIcon($menuPage), $active . $data_img) . snippet_sitemap($menuPage, 0) .'</li>';
}
}
$idx += 1;
}
if (!empty(getCoverImg($page_submenu))) {
$out .= '</ul><div class="page-cover-img" data-img="'.getCoverImg($page_submenu).'"></div></div>';
} else {
$out .= '</ul>';
}
} else {
if (!empty(getCoverImg($page_submenu))) {
$out = '<div class="thumb"><div class="page-cover-img" data-img="'.getCoverImg($page_submenu).'"></div></div>';
} else {
$out = '';
}
}
return $out;
}
?>
<?php
echo snippet_sitemap($this->find('/'), 1);
?>
Update Snippet: mobile-menu
<!-- responsive menu -->
<script type="text/javascript">
$(function(){
let cloneMenu = $('#menulist').clone();
cloneMenu.find('.thumb > ul').unwrap();
cloneMenu.slicknav({
label: 'MENU',
prependTo:'#mobile-menu'
});
});
</script>
<!-- responsive menu -->
<script type="text/javascript">
$(function(){
let cloneMenu = $('#menulist').clone();
cloneMenu.find('.thumb > ul').unwrap();
cloneMenu.slicknav({
label: 'MENU',
prependTo:'#mobile-menu'
});
});
</script>
Update: menu.css
*Adjust according to your design
/* hide the sub levels and give them a positon absolute so that they take up no room */
/* Allan - command this out for multiple div */
/* #menu ul ul {
padding-top: 26px;
width: auto;
z-index: 120;
list-style: none;
position: absolute;
top: 51px;
left: 0px;
margin-bottom: -15px;
overflow: hidden;
visibility: hidden;
text-align: left;
} */
/* make the second level visible when hover on first level list OR link */
/* Allan - command this out for multiple div */
/* #menu ul :hover ul {
visibility: visible;
height: auto;
z-index: 200;
} */
/* Menu too much, have to reduce left right padding */
#menu a {
padding: 0 10px;
}
a > .menu-icon {
height: 30px;
width: 30px;
margin: 0;
}
#menu #menulist > li > a {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 77px;
gap: 0.5rem;
}
#menu #menulist > li ul > li > a {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;
}
/* For Mobile */
.slicknav_menu li > a,
.slicknav_menu li > a > a {
display: flex !important;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;
}
#menu > #menulist > li > .thumb {
background-color: #0050a2;
display: flex;
align-items: flex-start;
justify-content: center;
gap: 1rem;
/* padding-top: 26px; */
padding: 1rem;
width: auto;
z-index: 120;
list-style: none;
position: absolute;
top: 77px;
left: 0px;
margin-bottom: -15px;
overflow: hidden;
visibility: hidden;
text-align: left;
}
#menu ul :hover .thumb {
visibility: visible !important;
height: auto;
z-index: 200;
}
#menu > #menulist > li > .thumb > .page-cover-img {
height: 225px;
width: 300px;
flex-shrink: 0;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
/* hide the sub levels and give them a positon absolute so that they take up no room */
/* Allan - command this out for multiple div */
/* #menu ul ul {
padding-top: 26px;
width: auto;
z-index: 120;
list-style: none;
position: absolute;
top: 51px;
left: 0px;
margin-bottom: -15px;
overflow: hidden;
visibility: hidden;
text-align: left;
} */
/* make the second level visible when hover on first level list OR link */
/* Allan - command this out for multiple div */
/* #menu ul :hover ul {
visibility: visible;
height: auto;
z-index: 200;
} */
/* Menu too much, have to reduce left right padding */
#menu a {
padding: 0 10px;
}
a > .menu-icon {
height: 30px;
width: 30px;
margin: 0;
}
#menu #menulist > li > a {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 77px;
gap: 0.5rem;
}
#menu #menulist > li ul > li > a {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;
}
/* For Mobile */
.slicknav_menu li > a,
.slicknav_menu li > a > a {
display: flex !important;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;
}
#menu > #menulist > li > .thumb {
background-color: #0050a2;
display: flex;
align-items: flex-start;
justify-content: center;
gap: 1rem;
/* padding-top: 26px; */
padding: 1rem;
width: auto;
z-index: 120;
list-style: none;
position: absolute;
top: 77px;
left: 0px;
margin-bottom: -15px;
overflow: hidden;
visibility: hidden;
text-align: left;
}
#menu ul :hover .thumb {
visibility: visible !important;
height: auto;
z-index: 200;
}
#menu > #menulist > li > .thumb > .page-cover-img {
height: 225px;
width: 300px;
flex-shrink: 0;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
Update: default.css
*Adjust according to your design
@media only screen and (max-width: 2000px) {
#menulist > li:nth-last-child(-n + 2) .thumb {
flex-direction: row-reverse;
right: 0;
left: unset !important;
}
}
@media only screen and (max-width: 1700px) {
#menulist > li:nth-last-child(-n + 5) .thumb {
flex-direction: row-reverse;
right: 0;
left: unset !important;
}
}
@media only screen and (max-width: 1300px) {
#menu a, #menu a:visited {
padding: 0 8px;
}
}
@media only screen and (max-width: 1200px) {
#menulist > li:nth-child(5) .thumb,
#menulist > li:nth-child(6) .thumb {
left: 50% !important;
transform: translateX(-50%);
flex-direction: unset;
right: unset;
}
}
@media only screen and (max-width: 1150px) {
#menu {
height: 77px;
}
#menu ul ul {
top: 0;
}
}
@media only screen and (max-width: 1024px) {
#menu ul ul {
top: 0;
}
#menulist > li:nth-child(5) .thumb,
#menulist > li:nth-child(6) .thumb {
left: 50% !important;
transform: translateX(-50%);
flex-direction: unset;
right: unset;
}
/* Menu too long, need to show mobile menu early */
#menu { display: none; }
#mobile-menu { display: block; }
.slicknav_btn { position: absolute !important; right: 30px; top: 45px; }
}
@media only screen and (max-width: 2000px) {
#menulist > li:nth-last-child(-n + 2) .thumb {
flex-direction: row-reverse;
right: 0;
left: unset !important;
}
}
@media only screen and (max-width: 1700px) {
#menulist > li:nth-last-child(-n + 5) .thumb {
flex-direction: row-reverse;
right: 0;
left: unset !important;
}
}
@media only screen and (max-width: 1300px) {
#menu a, #menu a:visited {
padding: 0 8px;
}
}
@media only screen and (max-width: 1200px) {
#menulist > li:nth-child(5) .thumb,
#menulist > li:nth-child(6) .thumb {
left: 50% !important;
transform: translateX(-50%);
flex-direction: unset;
right: unset;
}
}
@media only screen and (max-width: 1150px) {
#menu {
height: 77px;
}
#menu ul ul {
top: 0;
}
}
@media only screen and (max-width: 1024px) {
#menu ul ul {
top: 0;
}
#menulist > li:nth-child(5) .thumb,
#menulist > li:nth-child(6) .thumb {
left: 50% !important;
transform: translateX(-50%);
flex-direction: unset;
right: unset;
}
/* Menu too long, need to show mobile menu early */
#menu { display: none; }
#mobile-menu { display: block; }
.slicknav_btn { position: absolute !important; right: 30px; top: 45px; }
}
Update: rckuc.js
$(document).ready(function() {
$('#menulist a').hover(function() {
let imgUrl = $(this).attr("data-img");
let imgContainer = $(".thumb > .page-cover-img");
if (imgUrl) {
imgContainer.css("background-image", "url(" + imgUrl + ")");
} else {
imgContainer.each(function() {
$(this).css("background-image", "url(" + $(this).attr("data-img") + ")");
})
}
});
})
$(document).ready(function() {
$('#menulist a').hover(function() {
let imgUrl = $(this).attr("data-img");
let imgContainer = $(".thumb > .page-cover-img");
if (imgUrl) {
imgContainer.css("background-image", "url(" + imgUrl + ")");
} else {
imgContainer.each(function() {
$(this).css("background-image", "url(" + $(this).attr("data-img") + ")");
})
}
});
})