Ghi chú phát triển NukeViet 4.5 - nukeviet/nukeviet Wiki

Phát triển 4.5.00

Thay thư viện endroid/qrcode bằng tecnickcom/tc-lib-barcode

UPDATE `nv4_vi_blocks_groups` SET `config`='' WHERE `file_name`='global.QR_code.php';

Xóa 2 function: nv_block_qr_code_config và nv_block_qr_code_config_submit.

Tìm đến:

$block_config['selfurl'] = NV_MAIN_DOMAIN . nv_url_rewrite($current_page_url, true);

Thay bằng:

str_starts_with($current_page_url, NV_MY_DOMAIN) && $current_page_url = substr($current_page_url, strlen(NV_MY_DOMAIN));
$block_config['selfurl'] = NV_MY_DOMAIN . nv_url_rewrite($current_page_url, true);

Tìm và xóa:

 data-level="{QRCODE.level}" data-ppp="{QRCODE.pixel_per_point}" data-of="{QRCODE.outer_frame}"

Tìm và xóa:

<config>a:3:{s:5:"level";s:1:"M";s:15:"pixel_per_point";i:4;s:11:"outer_frame";i:1;}</config>
 + "&l=" + $(a).data("level") + "&ppp=" + $(a).data("ppp") + "&of=" + $(a).data("of")

Viết lại mã JS của module Statistics khi nâng cấp Chart.js lên v3.x

Sửa file themes/tên_theme/modules/statistics/main.tpl

Cập nhật quản lý tập trung Captcha

DELETE FROM `nv4_config` WHERE  `lang`='vi' AND `config_name`='captcha_type_comm';
DELETE FROM `nv4_config` WHERE  `lang`='vi' AND `module`='news' AND `config_name`='ucaptcha_type';
DELETE FROM `nv4_config` WHERE  `lang`='vi' AND `module`='news' AND `config_name`='scaptcha_type';

UPDATE `nv4_config` SET `config_name`='captcha_type' WHERE  `lang`='sys' AND `module`='site' AND `config_name`='ucaptcha_type';
UPDATE `nv4_config` SET `config_name`='captcha_area' WHERE  `lang`='sys' AND `module`='site' AND `config_name`='ucaptcha_area';

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'comment', 'captcha_type', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'news', 'captcha_type', 'captcha');

Ghi chú nâng cấp các module ngoài hệ thống:

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'tên_module', 'captcha_type', 'captcha');
global ..., $module_captcha;

Và chỉnh lại như ở đây

Thêm thuộc tính SameSite vào cookie

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'global', 'cookie_SameSite', 'Lax');

Fix error "Email Hyperlink Injection"

ALTER TABLE `nv4_users_field` CHANGE COLUMN `match_type` `match_type` ENUM('none','alphanumeric','unicodename','email','url','regex','callback') NOT NULL DEFAULT 'none' AFTER `sql_choices`;
UPDATE `nv4_users_field` SET `match_type`='unicodename' WHERE  `field` IN ('first_name','last_name');

Lỗi Cross-site Scripting (XSS) tiềm ẩn

Trong tất cả các file ở thư mục modules/ten_module/func/ cần thêm vào biến $page_url (Nơi nào định nghĩa biến $canonicalUrl - nơi đó thêm lên trên $page_url), ví dụ:

$page_url = NV_BASE_SITEURL . 'index.php?' . NV_LANG_VARIABLE . '=' . NV_LANG_DATA . '&amp;' . NV_NAME_VARIABLE . '=' . $module_name . '&amp;' . NV_OP_VARIABLE . '=' . $op;
$canonicalUrl = getCanonicalUrl($page_url, true, true);

Tìm ở các nơi:

nv_rss_generate($channel, $items)

Thay bằng:

$atomlink = NV_BASE_SITEURL . "index.php?" . NV_LANG_VARIABLE . "=" . NV_LANG_DATA . "&amp;" . NV_NAME_VARIABLE . "=" . $module_name . "&amp;" . NV_OP_VARIABLE . "=" . $module_info['alias']['rss'];
nv_rss_generate($channel, $items, $atomlink);

chỗ nào dùng $client_info['selfurl'] để hiển thị trong html-output - chỗ đó cần thay bằng:

global $page_url;
// $current_page_url thay cho $client_info['selfurl']
$current_page_url = NV_MAIN_DOMAIN . nv_url_rewrite($page_url, true);

Thêm nút bật/tắt cấp module cho chức năng đánh giá bài viết (module News)

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'news', 'allowed_rating', '1');

Tạo và quản lý header Referrer-Policy

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'nv_rp_act', '1');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'nv_rp', 'no-referrer-when-downgrade, strict-origin-when-cross-origin');

Thêm rel="noopener noreferrer nofollow" vào các liên kết bên ngoài

Mở file theme/ten_theme/js/main.js, tìm đến khu vực $(function() {...} và thêm như sau:

$(function() {
    // Add rel="noopener noreferrer nofollow" to all external links
    $('a[href^="http"]').not('a[href*="' + location.hostname + '"]').not('[rel*=dofollow]').attr({target: "_blank", rel: "noopener noreferrer nofollow"});
    ...
}

Lưu ý: Nếu chỗ nào không muốn thêm rel="noopener noreferrer nofollow", chỗ đó cần thêm rel="dofollow".

Thêm cấu hình hình ảnh mặc định cho thẻ Open Graph

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'ogp_image', '');

Thêm Content-Security-Policy

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'nv_csp_act', '1');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'nv_csp', 'script-src &#039;self&#039; *.google.com *.google-analytics.com *.googletagmanager.com *.gstatic.com *.facebook.com *.facebook.net *.twitter.com *.zalo.me *.zaloapp.com &#039;unsafe-inline&#039; &#039;unsafe-eval&#039;;style-src &#039;self&#039; *.google.com &#039;unsafe-inline&#039;;frame-src &#039;self&#039; *.google.com *.youtube.com *.facebook.com *.facebook.net *.twitter.com *.zalo.me;base-uri &#039;self&#039;;');

Chế độ hình ảnh cho mobile

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'define', 'nv_mobile_mode_img', '480');

Đính kèm thư chữ ký số DKIM và chứng chỉ S/MIME

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'dkim_included', 'sendmail,mail');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'smime_included', 'sendmail,mail');

Thông báo bảo trì site

UPDATE `nv4_config` SET `module`='global' WHERE `lang`='sys' AND `module`='site' AND `config_name` IN ('closed_site','site_reopening_time');

Thêm popup thông báo về việc sử dụng cookie khi người dùng truy cập website lần đầu

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'global', 'cookie_notice_popup', '0');

Thêm vào file theme/ten_theme/layout/footer_only.tpl:

<!-- BEGIN: cookie_notice --><div class="cookie-notice"><div><button onclick="cookie_notice_hide();">&times;</button>{COOKIE_NOTICE}</div></div><!-- END: cookie_notice -->

Thêm vào file themes/ten_theme/js/main.js

// Hide Cookie Notice Popup
function cookie_notice_hide() {
    nv_setCookie(nv_cookie_prefix + '_cn', '1', 365);
    $(".cookie-notice").hide()
}

Thêm vào file themes/ten_theme/css/style.css

/*cookie-notice popup*/
.cookie-notice {
    position:fixed;
    bottom: 20px;
    left: 20px;
    width: 350px;
    z-index:99999999999999;
    background-color: #eee;
    border: solid 1px #dedede;
    border-radius: 4px;
    box-shadow:0 0 4px rgba(0,0,0,0.15);
}

.cookie-notice a {
    color: #1a3f5e;
    text-decoration: underline;
}

.cookie-notice div {
    position: relative;
    width: 100%;
    padding: 20px;
    color: #333;
}

.cookie-notice button {
    float: right;
    margin-top: -20px;
    margin-right: -20px;
    margin-left: 10px;
    margin-bottom: 10px;
    width: 40px;
    height: 40px;
    border: 0;
    font-size: 24px;
}

Di chuyển việc quản lý captcha đến từng function Commit

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'ucaptcha_area', 'r,m,p');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'ucaptcha_type', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'banners', 'captcha_type', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'contact', 'captcha_type', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'news', 'ucaptcha_type', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'news', 'scaptcha_type', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'voting', 'captcha_type', 'captcha');
UPDATE `nv4_config` SET `config_name`='captcha_area_comm' WHERE  `lang`='vi' AND `module` IN ('about','news','page','siteterms') AND `config_name`='captcha';
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'about', 'captcha_type_comm', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'news', 'captcha_type_comm', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'page', 'captcha_type_comm', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'siteterms', 'captcha_type_comm', 'captcha');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'global', 'recaptcha_ver', '2');
DELETE FROM `nv4_config` WHERE  `lang`='sys' AND `module`='global' AND `config_name` IN ('captcha_type','gfx_chk');

Tìm trong các file TPL:

id="{RECAPTCHA_ELEMENT}"

Thay bằng:

id="{RECAPTCHA_ELEMENT}" data-toggle="recaptcha" data-pnum="4" data-btnselector="[type=submit]"

Trong đó giá trị data-pnum bằng pnum của nv_recaptcha_elements.push ngay dưới, data-btnselector bằng giá trị btnselector của nv_recaptcha_elements.push ngay dưới. Xóa toàn bộ đoạn js chứa nv_recaptcha_elements.push.

Trình bày lại module Users + chức năng đăng ký và quản lý nhóm Commit

UPDATE IGNORE `nv4_vi_modthemes` SET `layout`='left-main' WHERE `func_id` IN (SELECT `func_id` FROM `nv4_vi_modfuncs` WHERE `in_module` = 'users') AND `theme`='default';
UPDATE `nv4_vi_modfuncs` SET `in_submenu`='1' WHERE `alias`='groups' AND `in_module`='users';

CREATE TABLE `nv4_users_groups_detail` (
	`group_id` SMALLINT(5) UNSIGNED NOT NULL DEFAULT '0',
	`lang` CHAR(2) NOT NULL DEFAULT '',
	`title` VARCHAR(240) NOT NULL,
	`description` VARCHAR(240) NOT NULL DEFAULT '',
	`content` TEXT,
	UNIQUE INDEX `group_id_lang` (`lang`, `group_id`)
);

DROP PROCEDURE IF EXISTS ROWPERROW;
DELIMITER ;;
CREATE PROCEDURE ROWPERROW()
BEGIN
DECLARE i INT DEFAULT 0;
SELECT @count := COUNT(*) FROM `nv4_users_groups`;
SET i=0;
WHILE i < @count DO 
  SELECT @group_id := `group_id`, @title := `title`, @description := `description`, @content := `content` FROM `nv4_users_groups` LIMIT i,1;
  INSERT INTO `nv4_users_groups_detail`(`lang`, `group_id`, `title`, `description`, `content`) VALUES ('vi', @group_id, @title, @description, @content);
  SET i = i + 1;
END WHILE;
End;
;;
DELIMITER ;
CALL ROWPERROW();

UPDATE `nv4_users_groups` SET `title` = REPLACE(`title`, ' ', '-') WHERE `title` REGEXP ' ';
ALTER TABLE `nv4_users_groups`
	CHANGE COLUMN `title` `alias` VARCHAR(240) NOT NULL AFTER `group_id`,
	DROP COLUMN `description`,
	DROP COLUMN `content`,
	DROP INDEX `ktitle`,
	ADD UNIQUE INDEX `kalias` (`alias`, `idsite`);

Thay đổi cách quản lý các nút công cụ mạng xã hội

INSERT INTO `nv4_config` (`module`, `config_name`, `config_value`) VALUES ('site', 'zaloOfficialAccountID', '');
INSERT INTO `nv4_vi_page_config` (`config_name`, `config_value`) VALUES ('socialbutton', 'facebook,twitter');
INSERT INTO `nv4_vi_about_config` (`config_name`, `config_value`) VALUES ('socialbutton', 'facebook,twitter');
INSERT INTO `nv4_vi_siteterms_config` (`config_name`, `config_value`) VALUES ('socialbutton', 'facebook,twitter');

themes/ten_theme/js/main.js

tìm đến:

    0 < $(".twitter-share-button").length && function() {
            var a = document.createElement("script");
            a.type = "text/javascript";
            a.src = "//platform.twitter.com/widgets.js";
            var b = document.getElementsByTagName("script")[0];
            b.parentNode.insertBefore(a, b);
    }();

thêm xuống dưới:

    0 < $(".zalo-share-button, .zalo-follow-only-button, .zalo-follow-button, .zalo-chat-widget").length && function() {
            var a = document.createElement("script");
            a.type = "text/javascript";
            a.src = "//sp.zalo.me/plugins/sdk.js";
            var b = document.getElementsByTagName("script")[0];
            b.parentNode.insertBefore(a, b);
    }();

themes/ten_theme/modules/news/detail.tpl

Tìm đến:

        <div class="socialicon clearfix">
            <div class="fb-like" data-href="{SELFURL}" data-layout="button_count" data-action="like" data-show-faces="false" data-share="true">&nbsp;</div>
            <a href="http://twitter.com/share" class="twitter-share-button">Tweet</a>
        </div>

Thay bằng:

        <div style="display:flex;align-items:flex-start;">
            <!-- BEGIN: facebook --><div class="margin-right"><div class="fb-like" style="float:left!important;margin-right:0!important;margin-bottom:0!important;top:0!important" data-href="{SELFURL}" data-layout="button_count" data-action="like" data-show-faces="false" data-share="true"></div></div><!-- END: facebook -->
            <!-- BEGIN: twitter --><div class="margin-right"><a href="http://twitter.com/share" class="twitter-share-button">Tweet</a></div><!-- END: twitter -->
            <!-- BEGIN: zalo --><div><div class="zalo-share-button" data-href="" data-oaid="{ZALO_OAID}" data-layout="1" data-color="blue" data-customize=false></div></div><!-- END: zalo -->
        </div>

themes/default/modules/page/main.tpl

Tìm đến:

        <div class="well well-sm">
            <ul class="nv-social-share">
                <li class="facebook">
                    <div class="fb-like" data-href="{SELFURL}" data-layout="button_count" data-action="like" data-show-faces="false" data-share="true">&nbsp;</div>
                </li>
                <li>
                    <a href="http://twitter.com/share" class="twitter-share-button">Tweet</a>
                </li>
            </ul>
        </div>

Thay bằng:

        <div class="margin-bottom">
            <div style="display:flex;align-items:flex-start;">
                <!-- BEGIN: facebook --><div class="margin-right"><div class="fb-like" style="float:left!important;margin-right:0!important" data-href="{SELFURL}" data-layout="button_count" data-action="like" data-show-faces="false" data-share="true"></div></div><!-- END: facebook -->
                <!-- BEGIN: twitter --><div class="margin-right"><a href="http://twitter.com/share" class="twitter-share-button">Tweet</a></div><!-- END: twitter -->
                <!-- BEGIN: zalo --><div><div class="zalo-share-button" data-href="" data-oaid="{ZALO_OAID}" data-layout="1" data-color="blue" data-customize=false></div></div><!-- END: zalo -->
            </div>
        </div>

Thêm quản lý tác giả bài viết

CREATE TABLE `nv4_vi_news_author` (
	`id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
	`uid` INT(11) UNSIGNED NOT NULL,
	`alias` VARCHAR(100) NOT NULL DEFAULT '',
	`pseudonym` VARCHAR(100) NOT NULL DEFAULT '',
	`image` VARCHAR(255) NULL DEFAULT '',
	`description` TEXT NULL DEFAULT NULL,
	`add_time` INT(11) UNSIGNED NOT NULL DEFAULT '0',
	`edit_time` INT(11) UNSIGNED NOT NULL DEFAULT '0',
	`active` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1',
	`numnews` MEDIUMINT(8) NOT NULL DEFAULT '0',
	PRIMARY KEY (`id`),
	UNIQUE KEY `uid` (`uid`),
	UNIQUE KEY `alias` (`alias`)
);

CREATE TABLE `nv4_vi_news_authorlist` (
	`id` INT(11) NOT NULL,
	`aid` MEDIUMINT(8) NOT NULL,
	`alias` VARCHAR(100) NOT NULL DEFAULT '',
	`pseudonym` VARCHAR(100) NOT NULL DEFAULT '',
	UNIQUE KEY `id_aid` (`id`, `aid`),
	KEY `aid` (`aid`),
	KEY `alias` (`alias`)
);

INSERT INTO `nv4_vi_modfuncs` (`func_name`, `alias`, `func_custom_name`, `in_module`, `show_func`, `subweight`) VALUES ('author', 'author', 'Author', 'news', '1', '10');

INSERT INTO `nv4_vi_modthemes` (`func_id`, `layout`, `theme`) VALUES ((SELECT `func_id` FROM `nv4_vi_modfuncs` WHERE `alias` = 'author'), 'left-main-right', 'default');

INSERT INTO `nv4_vi_modthemes` (`func_id`, `layout`, `theme`) VALUES ((SELECT `func_id` FROM `nv4_vi_modfuncs` WHERE `alias` = 'author'), 'main', 'mobile_default');

Thêm tiêu đề phản hồi "Retry-After" khi trang web ở trạng thái đóng

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'site_reopening_time', '0');

Thêm tiêu đề cho tag của module news

ALTER TABLE `nv4_vi_news_tags` ADD `title` VARCHAR(250) NOT NULL DEFAULT '' AFTER `numnews`;

Lưu ý: Module news có thể ảo hóa

Thêm vào cấu hình module News chế độ: "Phương án thể hiện trang chủ ở giao diện mobile"

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'news', 'mobile_indexfile', 'viewcat_page_new');

Tích hợp Google Analytics 4

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'site', 'googleAnalytics4ID', '');

Máy chủ chứa các tệp tĩnh

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'global', 'nv_static_url', '');

Lưu ý về hosting chứa các tệp tĩnh:

		SetEnvIf Origin "http(s)?://(www\.)?(nukeviet.site|nukeviet.tk)$" AccessControlAllowOrigin=$0
		Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
		Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type"
		Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"

Tích hợp reCaptcha v3

Nguyên tắc sửa:

Khu vực xác thực captcha, thường ở trong các file kiểu như modules/tên_module/funcs/tên_file.php

Chỗ nào có:

$global_config['captcha_type'] == 2

Thay bằng:

($global_config['captcha_type'] == 2 or $global_config['captcha_type'] == 3)

Khu vực quản lý hiển thị captcha, thường ở trong các file kiểu như modules/tên_module/theme.php

Chỗ nào có:

if ($global_config['captcha_type'] == 2) {
    ...
    $xtpl->parse('main.recaptcha');
}

Thay bằng:

if ($global_config['captcha_type'] == 3) {
    $xtpl->parse('main.recaptcha3');
} elseif ($global_config['captcha_type'] == 2) {
    ...
    $xtpl->parse('main.recaptcha');
}

Các file tpl hiển thị captcha, ví dụ: themes/default/module/users/login_form.tpl

Tìm đến form có chứa mã hiển thị captcha (có chứa các dòng như BEGIN: captcha, BEGIN: recaptcha)

<form action="{USER_LOGIN}" method="post" onsubmit="return login_validForm(this);" autocomplete="off" novalidate>

Thay bằng:

<form action="{USER_LOGIN}" method="post" onsubmit="return login_validForm(this);" autocomplete="off" novalidate<!-- BEGIN: recaptcha3 --> data-recaptcha3="1"<!-- END: recaptcha3 -->>

Thay đổi: themes/tên_theme/js/main.js

Cập nhật giao diện #3069

themes/default/modules/news/detail.tpl

Tìm dòng 168:

                    <input class="hover-star" type="radio" value="1" title="{LANGSTAR.verypoor}" /><input class="hover-star" type="radio" value="2" title="{LANGSTAR.poor}" /><input class="hover-star" type="radio" value="3" title="{LANGSTAR.ok}" /><input class="hover-star" type="radio" value="4" title="{LANGSTAR.good}" /><input class="hover-star" type="radio" value="5" title="{LANGSTAR.verygood}" /><span id="hover-test" style="margin: 0 0 0 20px;">{LANGSTAR.note}</span>

Thay bằng:

                    <!-- BEGIN: star --><input class="hover-star required" type="radio" value="{STAR.val}" title="{STAR.title}"{STAR.checked}/><!-- END: star -->
                    <span id="hover-test" style="margin: 0 0 0 20px;">{LANG.star_note}</span>

Tìm đến (từ dòng 174):

        $(function() {
            var sr = 0;
            $(".hover-star").rating({
                focus: function(b, c) {
                    var a = $("#hover-test");
                    2 != sr && (a[0].data = a[0].data || a.html(), a.html(c.title || "value: " + b), sr = 1)
                },
                blur: function(b, c) {
                    var a = $("#hover-test");
                    2 != sr && ($("#hover-test").html(a[0].data || ""), sr = 1)
                },
                callback: function(b, c) {
                    1 == sr && (sr = 2, $(".hover-star").rating("disable"), sendrating("{NEWSID}", b, "{NEWSCHECKSS}"))
                }
            });
            $(".hover-star").rating("select", "{NUMBERRATING}");
            <!-- BEGIN: disablerating -->
            $(".hover-star").rating('disable');
            sr = 2;
            <!-- END: disablerating -->
        })

Thay bằng:

        $(function() {
            var isDisable = false;
            $('.hover-star').rating({
                focus : function(value, link) {
                    var tip = $('#hover-test');
                    if (!isDisable) {
                        tip[0].data = tip[0].data || tip.html();
                        tip.html(link.title || 'value: ' + value)
                    }
                },
                blur : function(value, link) {
                    var tip = $('#hover-test');
                    if (!isDisable) {
                        $('#hover-test').html(tip[0].data || '')
                    }
                },
                callback : function(value, link) {
                    if (!isDisable) {
                        isDisable = true;
                        $('.hover-star').rating('disable');
                        sendrating('{NEWSID}', value, '{NEWSCHECKSS}');
                    }
                }
            });
            <!-- BEGIN: disablerating -->
            $(".hover-star").rating('disable');
            isDisable = true;
            <!-- END: disablerating -->
        })

themes/mobile_default/modules/news/detail.tpl

Tìm đến dòng 160:

                            <input class="hover-star" type="radio" value="1" title="{LANGSTAR.verypoor}" /><input class="hover-star" type="radio" value="2" title="{LANGSTAR.poor}" /><input class="hover-star" type="radio" value="3" title="{LANGSTAR.ok}" /><input class="hover-star" type="radio" value="4" title="{LANGSTAR.good}" /><input class="hover-star" type="radio" value="5" title="{LANGSTAR.verygood}" /><span id="hover-test" style="margin: 0 0 0 20px;">{LANGSTAR.note}</span>

Thay bằng:

                            <!-- BEGIN: star --><input class="hover-star required" type="radio" value="{STAR.val}" title="{STAR.title}"{STAR.checked}/><!-- END: star -->
                            <span id="hover-test" style="margin: 0 0 0 20px;">{LANG.star_note}</span>

Tìm đến (từ dòng 165):

                <script type="text/javascript">
                    var sr = 0;
                    $('.hover-star').rating({
                        focus : function(value, link) {
                            var tip = $('#hover-test');
                            if (sr != 2) {
                                tip[0].data = tip[0].data || tip.html();
                                tip.html(link.title || 'value: ' + value);
                                sr = 1;
                            }
                        },
                        blur : function(value, link) {
                            var tip = $('#hover-test');
                            if (sr != 2) {
                                $('#hover-test').html(tip[0].data || '');
                                sr = 1;
                            }
                        },
                        callback : function(value, link) {
                            if (sr == 1) {
                                sr = 2;
                                $('.hover-star').rating('disable');
                                sendrating('{NEWSID}', value, '{NEWSCHECKSS}');
                            }
                        }
                    });
                    $('.hover-star').rating('select', '{NUMBERRATING}');
                </script>
                <!-- BEGIN: disablerating -->
                <script type="text/javascript">
                    $(".hover-star").rating('disable');
                    sr = 2;
                </script>
                <!-- END: disablerating -->

Thay bằng:

                <script type="text/javascript">
                $(function() {
                    var isDisable = false;
                    $('.hover-star').rating({
                        focus : function(value, link) {
                            var tip = $('#hover-test');
                            if (!isDisable) {
                                tip[0].data = tip[0].data || tip.html();
                                tip.html(link.title || 'value: ' + value)
                            }
                        },
                        blur : function(value, link) {
                            var tip = $('#hover-test');
                            if (!isDisable) {
                                $('#hover-test').html(tip[0].data || '')
                            }
                        },
                        callback : function(value, link) {
                            if (!isDisable) {
                                isDisable = true;
                                $('.hover-star').rating('disable');
                                sendrating('{NEWSID}', value, '{NEWSCHECKSS}');
                            }
                        }
                    });
                    <!-- BEGIN: disablerating -->
                    $(".hover-star").rating('disable');
                    isDisable = true;
                    <!-- END: disablerating -->
                })
                </script>

Cập nhật giao diện #3066

themes/tên_theme/theme.php Tìm:

if (! defined('NV_SYSTEM') or ! defined('NV_MAINFILE')) {
    die('Stop!!!');
}

Thêm xuống dưới:


$theme_config = [
    'pagination' => [
        // Nếu dùng bootstrap 3: 'pagination'
        // Nếu dùng bootstrap 4/5: 'pagination justify-content-center'
        'ul_class' => 'pagination',
        // Nếu dùng bootstrap 3: '',
        // Nếu dùng bootstrap 4/5: 'page-item'
        'li_class' => '',
        // Nếu dùng bootstrap 3: '',
        // Nếu dùng bootstrap 4/5: 'page-link'
        'a_class' => ''
    ]
];

Cho phép Origin = null làm việc với phương thức POST

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'global', 'allow_null_origin', '0');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'global', 'ip_allow_null_origin', '');

API

CREATE TABLE nv4_authors_api_credential (
  admin_id int(11) unsigned NOT NULL,
  credential_title varchar(255) NOT NULL DEFAULT '',
  credential_ident varchar(50) NOT NULL DEFAULT '',
  credential_secret varchar(255) NOT NULL DEFAULT '',
  credential_ips varchar(255) NOT NULL DEFAULT '',
  api_roles varchar(255) NOT NULL DEFAULT '',
  addtime int(11) NOT NULL DEFAULT '0',
  edittime int(11) NOT NULL DEFAULT '0',
  last_access int(11) NOT NULL DEFAULT '0',
  UNIQUE KEY credential_ident (credential_ident),
  UNIQUE KEY credential_secret (credential_secret),
  KEY admin_id (admin_id)
) ENGINE=MyISAM;

CREATE TABLE nv4_authors_api_role (
  role_id smallint(4) NOT NULL AUTO_INCREMENT,
  role_title varchar(250) NOT NULL DEFAULT '',
  role_description text NOT NULL,
  role_data text NOT NULL,
  addtime int(11) NOT NULL DEFAULT '0',
  edittime int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (role_id)
) ENGINE=MyISAM;

INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'global', 'remote_api_access', '0');
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('sys', 'global', 'remote_api_log', '1');

Chức năng thành viên chỉnh bình chọn một lần

CREATE TABLE nv4_vi_voting_voted (vid SMALLINT(5) UNSIGNED NOT NULL, voted TEXT, UNIQUE KEY vid (vid));
INSERT INTO `nv4_config` (`lang`, `module`, `config_name`, `config_value`) VALUES ('vi', 'voting', 'difftimeout', '3600');
ALTER TABLE `nv4_vi_voting` ADD `vote_one` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0 cho phép vote nhiều lần 1 cho phép vote 1 lần' AFTER `act`;

Chức năng đánh dấu đã xử lý module contact

ALTER TABLE `nv4_vi_contact_send` ADD `is_processed` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `is_reply`, ADD `processed_by` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `is_processed`, ADD `processed_time` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `processed_by`;

Chỉnh js của giao diện

main.js

Sửa hàm

function checkAll(a) {
    $(".checkAll", a).is(":checked") ? $(".checkSingle", a).each(function() {
        $(this).prop("checked", !0)
    }) : $(".checkSingle", a).each(function() {
        $(this).prop("checked", !1)
    });
    return !1
}

Thành

function checkAll(a) {
    $(".checkAll", a).is(":checked") ? $(".checkSingle", a).not(":disabled").each(function() {
        $(this).prop("checked", !0)
    }) : $(".checkSingle", a).not(":disabled").each(function() {
        $(this).prop("checked", !1)
    });
    return !1
}

Cập nhật giao diện module users

themes/default/modules/users/info.tpl

Tìm

                                <th class="text-center"><input name="in_groups[]" type="checkbox" value="{GROUP_LIST.group_id}" class="checkSingle" onclick="checkSingle(this.form);"{GROUP_LIST.checked} <!-- BEGIN: is_disable_checkbox -->disabled="disabled"<!-- END: is_disable_checkbox --> /></th>

Sửa thành

                                <th class="text-center"><input name="in_groups[]" type="checkbox" value="{GROUP_LIST.group_id}" class="checkSingle" onclick="checkSingle(this.form);"{GROUP_LIST.checked}<!-- BEGIN: is_disable_checkbox --> disabled="disabled"/><input type="hidden" name="in_groups[]" value="{GROUP_LIST.group_id}"<!-- END: is_disable_checkbox -->/></th>

themes/default/modules/users/groups.tpl

Tìm

                                <th class="text-center"><input name="in_groups[]" type="checkbox" value="{GROUP_LIST.group_id}" class="checkSingle" onclick="checkSingle(this.form);"{GROUP_LIST.checked} <!-- BEGIN: is_disable_checkbox -->disabled="disabled"<!-- END: is_disable_checkbox --> /></th>

Sửa thành

                                <th class="text-center"><input name="in_groups[]" type="checkbox" value="{GROUP_LIST.group_id}" class="checkSingle" onclick="checkSingle(this.form);"{GROUP_LIST.checked}<!-- BEGIN: is_disable_checkbox --> disabled="disabled"/><input type="hidden" name="in_groups[]" value="{GROUP_LIST.group_id}"<!-- END: is_disable_checkbox -->/></th>