From 69b4ecc0f8957935434751b2d58454ef260b86eb Mon Sep 17 00:00:00 2001 From: emanuele Date: Sat, 12 May 2018 20:57:13 +0200 Subject: [PATCH 01/13] Initial work on "accept agreement": form for accepting, backup and log --- install/install_1-1.php | 17 +++++ install/upgrade_1-1.php | 30 +++++++++ sources/Load.php | 8 +++ sources/Subs.php | 65 +++++++++++++++---- .../admin/ManageRegistration.controller.php | 16 ++++- sources/controllers/Register.controller.php | 49 ++++++++++++++ sources/subs/Agreement.class.php | 55 +++++++++++++++- sources/subs/Members.subs.php | 24 +++++++ themes/default/Register.template.php | 6 +- themes/default/Theme.php | 5 ++ themes/default/index.template.php | 28 ++++++++ .../languages/english/Admin.english.php | 2 + .../languages/english/Login.english.php | 1 + .../languages/english/Profile.english.php | 3 + .../languages/english/index.english.php | 1 + 15 files changed, 294 insertions(+), 16 deletions(-) diff --git a/install/install_1-1.php b/install/install_1-1.php index 1cbc2f3825..d5b949df56 100644 --- a/install/install_1-1.php +++ b/install/install_1-1.php @@ -1153,6 +1153,23 @@ public function table_log_activity() ); } + public function table_log_agreement_accept() + { + return $this->table->db_create_table('{db_prefix}log_agreement_accept', + array( + array('name' => 'agreement_date', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'id_member', 'type' => 'mediumint', 'size' => 10, 'unsigned' => true, 'default' => 0), + array('name' => 'accepted_date', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'accepted_ip', 'type' => 'varchar', 'size' => 255, 'default' => ''), + ), + array( + array('name' => 'modify_date', 'columns' => array('modify_date', 'id_member'), 'type' => 'primary'), + ), + array(), + 'ignore' + ); + } + public function table_log_badbehavior() { return $this->table->db_create_table('{db_prefix}log_badbehavior', diff --git a/install/upgrade_1-1.php b/install/upgrade_1-1.php index 82c4be98de..36014eeb9a 100644 --- a/install/upgrade_1-1.php +++ b/install/upgrade_1-1.php @@ -865,4 +865,34 @@ public function update_settings() ) ); } + + public function agreement_logging_title() + { + return 'Introducing the logging of accepted agreement...'; + } + + public function agreement_logging() + { + return array( + array( + 'debug_title' => 'Creating the table...', + 'function' => function() + { + $this->table->db_create_table('{db_prefix}log_agreement_accept', + array( + array('name' => 'agreement_date', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'id_member', 'type' => 'mediumint', 'size' => 10, 'unsigned' => true, 'default' => 0), + array('name' => 'accepted_date', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'accepted_ip', 'type' => 'varchar', 'size' => 255, 'default' => ''), + ), + array( + array('name' => 'modify_date', 'columns' => array('modify_date', 'id_member'), 'type' => 'primary'), + ), + array(), + 'ignore' + ); + } + ) + ); + } } diff --git a/sources/Load.php b/sources/Load.php index eb3c66b37c..b9bc72bd7c 100644 --- a/sources/Load.php +++ b/sources/Load.php @@ -430,6 +430,14 @@ function loadUserSettings() else $user_info['query_wanna_see_board'] = '(' . $user_info['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $user_info['ignoreboards']) . '))'; + if ($user_info['is_guest'] === false) + { + $http_request = HttpReq::instance(); + if (!empty($modSettings['agreementRevision']) && !empty($modSettings['requireAgreement']) && in_array($http_request->query->action, array('reminder', 'register')) === false) + { + checkAcceptedAgreement($id_member, $modSettings['agreementRevision']); + } + } call_integration_hook('integrate_user_info'); } diff --git a/sources/Subs.php b/sources/Subs.php index 3bb30db4ef..92194674d0 100644 --- a/sources/Subs.php +++ b/sources/Subs.php @@ -926,6 +926,21 @@ function obExit($header = null, $do_footer = null, $from_index = false, $from_fa // Need user agent $req = request(); + setOldUrl(); + + // For session check verification.... don't switch browsers... + $_SESSION['USER_AGENT'] = $req->user_agent(); + + // Hand off the output to the portal, etc. we're integrated with. + call_integration_hook('integrate_exit', array($do_footer)); + + // Don't exit if we're coming from index.php; that will pass through normally. + if (!$from_index) + exit; +} + +function setOldUrl($index = 'old_url') +{ // Remember this URL in case someone doesn't like sending HTTP_REFERER. $invalid_old_url = array( 'action=dlattach', @@ -945,17 +960,9 @@ function obExit($header = null, $do_footer = null, $from_index = false, $from_fa } } if ($make_old === true) - $_SESSION['old_url'] = $_SERVER['REQUEST_URL']; - - // For session check verification.... don't switch browsers... - $_SESSION['USER_AGENT'] = $req->user_agent(); - - // Hand off the output to the portal, etc. we're integrated with. - call_integration_hook('integrate_exit', array($do_footer)); - - // Don't exit if we're coming from index.php; that will pass through normally. - if (!$from_index) - exit; + { + $_SESSION[$index] = $_SERVER['REQUEST_URL']; + } } /** @@ -2109,4 +2116,40 @@ function obStart($use_compression = false) ob_start(); header('Content-Encoding: none'); } +} + +function checkAcceptedAgreement($id_member, $agreement_date) +{ + global $context, $txt; + + $db = database(); + + $accepted = $db->fetchQuery(' + SELECT 1 + FROM {db_prefix}log_agreement_accept + WHERE agreement_date = {date:agreement_date} + AND id_member = {int:id_member}', + array( + 'id_member' => $id_member, + 'agreement_date' => $agreement_date, + ) + ); + + if (empty($accepted)) + { + setOldUrl('agreement_url_redirect'); + redirectexit('action=register;sa=agreement', true); + } + if (!empty($_SESSION['agreement_accepted'])) + { + // This loadLanguage is needed here because the check is done way too early in the process. + // As a stop-gap it's fine, but in future versions it should be fixed somehow. + loadLanguage('index'); + $_SESSION['agreement_accepted'] = null; + $context['accepted_agreement'] = array( + 'errors' => array( + 'accepted_agreement' => $txt['agreement_accepted'] + ) + ); + } } \ No newline at end of file diff --git a/sources/admin/ManageRegistration.controller.php b/sources/admin/ManageRegistration.controller.php index 62e1dbb257..81c8603a5e 100644 --- a/sources/admin/ManageRegistration.controller.php +++ b/sources/admin/ManageRegistration.controller.php @@ -271,6 +271,7 @@ public function action_agreement() } } + $context['warning'] = ''; $agreement = new \Agreement($context['current_agreement']); if (isset($this->_req->post->save) && isset($this->_req->post->agreement)) @@ -279,14 +280,25 @@ public function action_agreement() validateToken('admin-rega'); // Off it goes to the agreement file. - $agreement->save($this->_req->post->agreement); + $success = $agreement->save($this->_req->post->agreement, !empty($this->_req->post->checkboxAcceptAgreement)); + if (!empty($this->_req->post->checkboxAcceptAgreement)) + { + if ($success === false) + { + $context['warning'] .= $txt['agreement_backup_not_writable'] . '
'; + } + else + { + updateSettings(array('agreementRevision' => $success)); + } + } updateSettings(array('requireAgreement' => !empty($this->_req->post->requireAgreement), 'checkboxAgreement' => !empty($this->_req->post->checkboxAgreement))); } $context['agreement'] = Util::htmlspecialchars($agreement->getPlainText(false)); - $context['warning'] = $agreement->isWritable() ? '' : $txt['agreement_not_writable']; + $context['warning'] .= $agreement->isWritable() ? '' : $txt['agreement_not_writable']; $context['require_agreement'] = !empty($modSettings['requireAgreement']); $context['checkbox_agreement'] = !empty($modSettings['checkboxAgreement']); diff --git a/sources/controllers/Register.controller.php b/sources/controllers/Register.controller.php index e8d9306cf7..a035acdfb8 100644 --- a/sources/controllers/Register.controller.php +++ b/sources/controllers/Register.controller.php @@ -108,6 +108,13 @@ public function action_register() { global $txt, $context, $modSettings, $user_info, $scripturl; + // If we are here only to accept the agreement, then let's do that. + if (isset($this->_req->post->accept_agreement)) + { + $this->action_agreement(); + return; + } + // If this user is an admin - redirect them to the admin registration page. if (allowedTo('moderate_forum') && !$user_info['is_guest']) redirectexit('action=admin;area=regcenter;sa=register'); @@ -1240,4 +1247,46 @@ public function action_registerCheckUsername() $context['valid_username'] = !$errors->hasErrors(); } + + public function action_agreement() + { + global $context, $user_info, $modSettings; + + if (isset($this->_req->post->accept_agreement)) + { + require_once(SUBSDIR . '/Members.subs.php'); + registerAgreementAccepted($user_info['id'], $user_info['ip'], empty($modSettings['agreementRevision']) ? strftime('%Y-%m-%d', forum_time(false)) : $modSettings['agreementRevision']); + $_SESSION['agreement_accepted'] = true; + if (isset($_SESSION['agreement_url_redirect'])) + { + redirectexit($_SESSION['agreement_url_redirect']); + } + } + elseif (isset($this->_req->post->no_accept)) + { + // This should lead to the account deletion page + } + else + { + $context['sub_template'] = 'registration_agreement'; + } + + loadLanguage('Login'); + loadLanguage('Profile'); + loadTemplate('Register'); + // If you have to agree to the agreement, it needs to be fetched from the file. + $agreement = new \Agreement($user_info['language']); + $context['agreement'] = $agreement->getParsedText(); + + $context['show_coppa'] = !empty($modSettings['coppaAge']); + $context['show_contact_button'] = !empty($modSettings['enable_contactform']) && $modSettings['enable_contactform'] === 'registration'; + // Under age restrictions? + if ($context['show_coppa']) + { + $context['skip_coppa'] = false; + $context['coppa_agree_above'] = sprintf($txt[($context['require_agreement'] ? 'agreement_' : '') . 'agree_coppa_above'], $modSettings['coppaAge']); + $context['coppa_agree_below'] = sprintf($txt[($context['require_agreement'] ? 'agreement_' : '') . 'agree_coppa_below'], $modSettings['coppaAge']); + } + createToken('register'); + } } diff --git a/sources/subs/Agreement.class.php b/sources/subs/Agreement.class.php index cc86162ab9..6630def4ca 100644 --- a/sources/subs/Agreement.class.php +++ b/sources/subs/Agreement.class.php @@ -25,14 +25,27 @@ class Agreement */ protected $_language = ''; + /** + * The directory where backups are stored + * + * @var string + */ + protected $_backup_dir = ''; + /** * Everything starts here. * * @param string $language the wanted language of the agreement. + * @param string $backup_dir where to store the backup of the agreements. */ - public function __construct($language) + public function __construct($language, $backup_dir = null) { $this->_language = strtr($language, array('.' => '')); + if ($backup_dir === null || file_exists($backup_dir) === false) + { + $backup_dir = BOARDDIR . '/packages/backups/agreements'; + } + $this->_backup_dir = $backup_dir; } /** @@ -41,13 +54,26 @@ public function __construct($language) * If the language passed to the class is empty, then it uses agreement.txt. * * @param string $text the language of the agreement we want. + * @param bool $update_backup if store a copy of the text of the agreements. */ - public function save($text) + public function save($text, $update_backup = false) { + $backup_id = ''; + if ($update_backup === true) + { + $backup_id = strftime('%Y-%m-%d', forum_time(false)); + if ($this->_createBackup($backup_id) === false) + { + $backup_id = false; + } + } + // Off it goes to the agreement file. $fp = fopen(BOARDDIR . '/agreement' . $this->normalizeLanguage() . '.txt', 'w'); fwrite($fp, str_replace("\r", '', $text)); fclose($fp); + + return $backup_id; } /** @@ -114,4 +140,29 @@ protected function normalizeLanguage() { return $this->_language === '' ? '' : '.' . $this->_language; } + + /** + * Creates a full backup of all the agreements. + * + * @param string $backup_id the name of the directory of the backup + * @return bool true if successful, false if failes to create the directory + */ + protected function _createBackup($backup_id) + { + $destination = $this->_backup_dir . '/' . $backup_id . '/'; + if (file_exists($this->_backup_dir) === false) + { + mkdir($this->_backup_dir); + } + if (mkdir($destination) === false) + { + return false; + } + $glob = new GlobIterator(BOARDDIR . '/agreement*.txt', FilesystemIterator::SKIP_DOTS); + foreach ($glob as $file) + { + copy($file->getPathname(), $destination . $file->getBasename()); + } + return true; + } } \ No newline at end of file diff --git a/sources/subs/Members.subs.php b/sources/subs/Members.subs.php index 8f0b73413d..b271b90e13 100644 --- a/sources/subs/Members.subs.php +++ b/sources/subs/Members.subs.php @@ -2607,3 +2607,27 @@ function loadMembersIPs($ip_string, $ip_var) return $ips; } + +function registerAgreementAccepted($id_member, $ip, $agreement_version) +{ + $db = database(); + + $db->insert('', + '{db_prefix}log_agreement_accept', + array( + 'agreement_date' => 'date', + 'id_member' => 'int', + 'accepted_date' => 'date', + 'accepted_ip' => 'string-255', + ), + array( + array( + 'agreement_date' => $agreement_version, + 'id_member' => $id_member, + 'accepted_date' => strftime('%Y-%m-%d', forum_time(false)), + 'accepted_ip' => $ip, + ) + ), + array('agreement_date', 'id_member') + ); +} \ No newline at end of file diff --git a/themes/default/Register.template.php b/themes/default/Register.template.php index 9403d4c7c1..346fa162be 100644 --- a/themes/default/Register.template.php +++ b/themes/default/Register.template.php @@ -60,6 +60,8 @@ function template_registration_agreement() else echo ' '; + echo ' + '; if ($context['show_contact_button']) echo ' @@ -764,12 +766,14 @@ function template_edit_agreement() // Show the actual agreement in an oversized text box. echo '

- +


+
+

diff --git a/themes/default/Theme.php b/themes/default/Theme.php index f85fa16923..71be249cfd 100644 --- a/themes/default/Theme.php +++ b/themes/default/Theme.php @@ -461,6 +461,11 @@ public function template_admin_warning_above() template_show_error('new_version_updates'); } + if (!empty($context['accepted_agreement'])) + { + template_show_error('accepted_agreement'); + } + // Any special notices to remind the admin about? if (!empty($context['warning_controls'])) { diff --git a/themes/default/index.template.php b/themes/default/index.template.php index 6a12be8991..d736c5bb42 100644 --- a/themes/default/index.template.php +++ b/themes/default/index.template.php @@ -401,6 +401,19 @@ function template_uc_news_fader() } } +/** + * All your data are belong to us (cit.) + */ +function template_uc_agreement_accepted() +{ + global $txt; + + echo ' +
+

', $txt['agreement_accepted'], '

+
'; +} + /** * Section down the page, before closing body */ @@ -795,6 +808,21 @@ function template_show_error($error_id)
'; } +function template_uc_generic_infobox() +{ + global $context; + + if (empty($context['generic_infobox'])) + { + return; + } + + foreach ($context['generic_infobox'] as $key) + { + template_show_error($key); + } +} + /** * Another used and abused piece of template that can be found everywhere * diff --git a/themes/default/languages/english/Admin.english.php b/themes/default/languages/english/Admin.english.php index ca9834bdc8..4ca2ccd2ed 100644 --- a/themes/default/languages/english/Admin.english.php +++ b/themes/default/languages/english/Admin.english.php @@ -47,6 +47,7 @@ $txt['admin_credits'] = 'Credits'; $txt['admin_agreement'] = 'Show and require agreement letter when registering'; $txt['admin_checkbox_agreement'] = 'Show a checkbox for the agreement in registration form instead of a full page'; +$txt['admin_checkbox_accept_agreement'] = 'Force members to accept the agreement at the next login'; $txt['admin_agreement_default'] = 'Default'; $txt['admin_agreement_select_language'] = 'Language to edit'; $txt['admin_agreement_select_language_change'] = 'Change'; @@ -124,6 +125,7 @@ $txt['live'] = 'Latest Software Updates'; $txt['remove_all'] = 'Clear Log'; $txt['agreement_not_writable'] = 'Warning - agreement.txt is not writable, any changes you make will NOT be saved.'; +$txt['agreement_backup_not_writable'] = 'Warning - the backup directory in forum_root/packages/backup cannot be created.'; $txt['version_check_desc'] = 'This shows you the versions of your installation\'s files versus those of the latest version. If any of these files are out of date, you should download and upgrade to the latest version at our ElkArte Site.'; $txt['version_check_more'] = '(more detailed)'; diff --git a/themes/default/languages/english/Login.english.php b/themes/default/languages/english/Login.english.php index 850d2a7454..a0c7b2d687 100644 --- a/themes/default/languages/english/Login.english.php +++ b/themes/default/languages/english/Login.english.php @@ -4,6 +4,7 @@ // Registration agreement page. $txt['registration_agreement'] = 'Registration Agreement'; $txt['agreement_agree'] = 'I accept the terms of the agreement.'; +$txt['agreement_no_agree'] = 'I do not accept the terms of the agreement.'; $txt['agreement_agree_coppa_above'] = 'I accept the terms of the agreement and I am at least %1$d years old.'; $txt['agreement_agree_coppa_below'] = 'I accept the terms of the agreement and I am younger than %1$d years old.'; $txt['agree_coppa_above'] = 'I am at least %1$d years old.'; diff --git a/themes/default/languages/english/Profile.english.php b/themes/default/languages/english/Profile.english.php index 87068f9c1e..ba9e48d34e 100644 --- a/themes/default/languages/english/Profile.english.php +++ b/themes/default/languages/english/Profile.english.php @@ -86,6 +86,9 @@ $txt['reminder_openid_is'] = 'The OpenID identity associated with your account is:
    %1$s

Please make a note of this for future reference.'; $txt['reminder_continue'] = 'Continue'; +$txt['accept_agreement_title'] = 'Accept agreement'; +$txt['agreement_accepted_title'] = 'Continue'; + $txt['current_theme'] = 'Current Theme'; $txt['change'] = 'Change Theme'; $txt['theme_forum_default'] = 'Forum or Board Default'; diff --git a/themes/default/languages/english/index.english.php b/themes/default/languages/english/index.english.php index 7ce43e7d18..799f178832 100644 --- a/themes/default/languages/english/index.english.php +++ b/themes/default/languages/english/index.english.php @@ -550,6 +550,7 @@ $txt['not_removed_extra'] = '%1$s is a backup of %2$s that was not generated by ElkArte. It can be accessed directly and used to gain unauthorised access to your forum. You should delete it immediately.'; $txt['generic_warning'] = 'Warning'; $txt['agreement_missing'] = 'You are requiring new users to accept a registration agreement, however the file (agreement.txt) doesn\'t exist.'; +$txt['agreement_accepted'] = 'You have successfully accepted the agreement.'; $txt['new_version_updates'] = 'You have just updated!'; $txt['new_version_updates_text'] = 'Click here to see what\'s new in this version of ElkArte!!'; From b754d5ad82102727e4eb66d120f407456ff36b35 Mon Sep 17 00:00:00 2001 From: emanuele Date: Sat, 12 May 2018 23:01:12 +0200 Subject: [PATCH 02/13] In case of not wanting to accept the agreement... delete is the only solution --- sources/Load.php | 5 ++++- sources/controllers/Register.controller.php | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/sources/Load.php b/sources/Load.php index b9bc72bd7c..117ca33885 100644 --- a/sources/Load.php +++ b/sources/Load.php @@ -435,7 +435,10 @@ function loadUserSettings() $http_request = HttpReq::instance(); if (!empty($modSettings['agreementRevision']) && !empty($modSettings['requireAgreement']) && in_array($http_request->query->action, array('reminder', 'register')) === false) { - checkAcceptedAgreement($id_member, $modSettings['agreementRevision']); + if ($http_request->query->action !== 'profile' || $http_request->area !== 'deleteaccount') + { + checkAcceptedAgreement($id_member, $modSettings['agreementRevision']); + } } } call_integration_hook('integrate_user_info'); diff --git a/sources/controllers/Register.controller.php b/sources/controllers/Register.controller.php index a035acdfb8..1b5443b434 100644 --- a/sources/controllers/Register.controller.php +++ b/sources/controllers/Register.controller.php @@ -109,7 +109,7 @@ public function action_register() global $txt, $context, $modSettings, $user_info, $scripturl; // If we are here only to accept the agreement, then let's do that. - if (isset($this->_req->post->accept_agreement)) + if (isset($this->_req->post->accept_agreement) || isset($this->_req->post->no_accept)) { $this->action_agreement(); return; @@ -1264,7 +1264,7 @@ public function action_agreement() } elseif (isset($this->_req->post->no_accept)) { - // This should lead to the account deletion page + redirectexit('action=profile;area=deleteaccount'); } else { From 0e4160dcc4b2027193ce4b543723bbb546b1faa5 Mon Sep 17 00:00:00 2001 From: emanuele Date: Sun, 13 May 2018 12:25:34 +0200 Subject: [PATCH 03/13] Move agreement accepted check to Agreement class --- sources/Load.php | 21 ++++++++++++++++++- sources/Subs.php | 36 -------------------------------- sources/subs/Agreement.class.php | 30 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/sources/Load.php b/sources/Load.php index 117ca33885..6e6e205c7d 100644 --- a/sources/Load.php +++ b/sources/Load.php @@ -437,7 +437,26 @@ function loadUserSettings() { if ($http_request->query->action !== 'profile' || $http_request->area !== 'deleteaccount') { - checkAcceptedAgreement($id_member, $modSettings['agreementRevision']); + $agreement = new Agreement($user_info['language']); + if (false === $agreement->checkAccepted($id_member, $modSettings['agreementRevision'])) + { + + setOldUrl('agreement_url_redirect'); + redirectexit('action=register;sa=agreement', true); + } + + if (!empty($_SESSION['agreement_accepted'])) + { + // This loadLanguage is needed here because the check is done way too early in the process. + // As a stop-gap it's fine, but in future versions it should be fixed somehow. + loadLanguage('index'); + $_SESSION['agreement_accepted'] = null; + $context['accepted_agreement'] = array( + 'errors' => array( + 'accepted_agreement' => $txt['agreement_accepted'] + ) + ); + } } } } diff --git a/sources/Subs.php b/sources/Subs.php index 92194674d0..d7d5d4f71b 100644 --- a/sources/Subs.php +++ b/sources/Subs.php @@ -2116,40 +2116,4 @@ function obStart($use_compression = false) ob_start(); header('Content-Encoding: none'); } -} - -function checkAcceptedAgreement($id_member, $agreement_date) -{ - global $context, $txt; - - $db = database(); - - $accepted = $db->fetchQuery(' - SELECT 1 - FROM {db_prefix}log_agreement_accept - WHERE agreement_date = {date:agreement_date} - AND id_member = {int:id_member}', - array( - 'id_member' => $id_member, - 'agreement_date' => $agreement_date, - ) - ); - - if (empty($accepted)) - { - setOldUrl('agreement_url_redirect'); - redirectexit('action=register;sa=agreement', true); - } - if (!empty($_SESSION['agreement_accepted'])) - { - // This loadLanguage is needed here because the check is done way too early in the process. - // As a stop-gap it's fine, but in future versions it should be fixed somehow. - loadLanguage('index'); - $_SESSION['agreement_accepted'] = null; - $context['accepted_agreement'] = array( - 'errors' => array( - 'accepted_agreement' => $txt['agreement_accepted'] - ) - ); - } } \ No newline at end of file diff --git a/sources/subs/Agreement.class.php b/sources/subs/Agreement.class.php index 6630def4ca..7c6ef58f8b 100644 --- a/sources/subs/Agreement.class.php +++ b/sources/subs/Agreement.class.php @@ -32,6 +32,13 @@ class Agreement */ protected $_backup_dir = ''; + /** + * The database object + * + * @var Object + */ + protected $_db = null; + /** * Everything starts here. * @@ -46,6 +53,7 @@ public function __construct($language, $backup_dir = null) $backup_dir = BOARDDIR . '/packages/backups/agreements'; } $this->_backup_dir = $backup_dir; + $this->_db = database(); } /** @@ -131,6 +139,28 @@ public function isWritable() return file_exists($filename) && is_writable($filename); } + /** + * Test if the user accepted the current agreement or not. + * + * @param int $id_member The id of the member + * @param string $agreement_date The date of the agreement + */ + public function checkAccepted($id_member, $agreement_date) + { + $accepted = $this->_db->fetchQuery(' + SELECT 1 + FROM {db_prefix}log_agreement_accept + WHERE agreement_date = {date:agreement_date} + AND id_member = {int:id_member}', + array( + 'id_member' => $id_member, + 'agreement_date' => $agreement_date, + ) + ); + + return empty($accepted); + } + /** * Takes care of the edge-case of the default agreement that doesn't have * the language in the name, and the fact that the admin panels loads it From 516352f40aaec090d9724660dc514f4c7808e71c Mon Sep 17 00:00:00 2001 From: emanuele Date: Sun, 13 May 2018 12:32:20 +0200 Subject: [PATCH 04/13] Revise the way the template_registration_agreement works adding an optional sub action hidden input field --- sources/controllers/Register.controller.php | 9 ++------- sources/subs/Agreement.class.php | 2 +- themes/default/Register.template.php | 6 ++++++ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/sources/controllers/Register.controller.php b/sources/controllers/Register.controller.php index 1b5443b434..4d8b8ceebd 100644 --- a/sources/controllers/Register.controller.php +++ b/sources/controllers/Register.controller.php @@ -84,6 +84,7 @@ public function action_index() 'verificationcode' => array($this, 'action_verificationcode'), 'coppa' => array($this, 'action_coppa'), 'agrelang' => array($this, 'action_agrelang'), + 'agreement' => array($this, 'action_agreement'), ); // Setup the action handler @@ -108,13 +109,6 @@ public function action_register() { global $txt, $context, $modSettings, $user_info, $scripturl; - // If we are here only to accept the agreement, then let's do that. - if (isset($this->_req->post->accept_agreement) || isset($this->_req->post->no_accept)) - { - $this->action_agreement(); - return; - } - // If this user is an admin - redirect them to the admin registration page. if (allowedTo('moderate_forum') && !$user_info['is_guest']) redirectexit('action=admin;area=regcenter;sa=register'); @@ -1269,6 +1263,7 @@ public function action_agreement() else { $context['sub_template'] = 'registration_agreement'; + $context['register_subaction'] = 'agreement'; } loadLanguage('Login'); diff --git a/sources/subs/Agreement.class.php b/sources/subs/Agreement.class.php index 7c6ef58f8b..b3b82d53ed 100644 --- a/sources/subs/Agreement.class.php +++ b/sources/subs/Agreement.class.php @@ -158,7 +158,7 @@ public function checkAccepted($id_member, $agreement_date) ) ); - return empty($accepted); + return !empty($accepted); } /** diff --git a/themes/default/Register.template.php b/themes/default/Register.template.php index 346fa162be..12a0fff6f5 100644 --- a/themes/default/Register.template.php +++ b/themes/default/Register.template.php @@ -68,6 +68,12 @@ function template_registration_agreement()

'; + if (!empty($context['register_subaction'])) + { + echo ' + '; + } + echo ' From 321d87e65952a495624a924261e47ce8f307ae54 Mon Sep 17 00:00:00 2001 From: emanuele Date: Sun, 20 May 2018 21:45:25 +0200 Subject: [PATCH 05/13] Agreement class a bit more flexibly (for later accepting privacy policy as well) --- install/install_1-1.php | 21 +++++- install/upgrade_1-1.php | 4 +- sources/Load.php | 29 ++++----- sources/controllers/Register.controller.php | 5 +- sources/subs/Agreement.class.php | 71 +++++++++++++++++---- sources/subs/Members.subs.php | 8 +-- 6 files changed, 100 insertions(+), 38 deletions(-) diff --git a/install/install_1-1.php b/install/install_1-1.php index d5b949df56..4ede073943 100644 --- a/install/install_1-1.php +++ b/install/install_1-1.php @@ -1157,13 +1157,13 @@ public function table_log_agreement_accept() { return $this->table->db_create_table('{db_prefix}log_agreement_accept', array( - array('name' => 'agreement_date', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'version', 'type' => 'date', 'default' => '0001-01-01'), array('name' => 'id_member', 'type' => 'mediumint', 'size' => 10, 'unsigned' => true, 'default' => 0), array('name' => 'accepted_date', 'type' => 'date', 'default' => '0001-01-01'), array('name' => 'accepted_ip', 'type' => 'varchar', 'size' => 255, 'default' => ''), ), array( - array('name' => 'modify_date', 'columns' => array('modify_date', 'id_member'), 'type' => 'primary'), + array('name' => 'version', 'columns' => array('version', 'id_member'), 'type' => 'primary'), ), array(), 'ignore' @@ -2421,6 +2421,23 @@ public function table_postby_emails_filters() ); } + public function table_log_privacy_policy_accept() + { + return $this->table->db_create_table('{db_prefix}log_privacy_policy_accept', + array( + array('name' => 'version', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'id_member', 'type' => 'mediumint', 'size' => 10, 'unsigned' => true, 'default' => 0), + array('name' => 'accepted_date', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'accepted_ip', 'type' => 'varchar', 'size' => 255, 'default' => ''), + ), + array( + array('name' => 'version', 'columns' => array('version', 'id_member'), 'type' => 'primary'), + ), + array(), + 'ignore' + ); + } + public function table_scheduled_tasks() { return $this->table->db_create_table('{db_prefix}scheduled_tasks', diff --git a/install/upgrade_1-1.php b/install/upgrade_1-1.php index 36014eeb9a..1d8da34fa2 100644 --- a/install/upgrade_1-1.php +++ b/install/upgrade_1-1.php @@ -880,13 +880,13 @@ public function agreement_logging() { $this->table->db_create_table('{db_prefix}log_agreement_accept', array( - array('name' => 'agreement_date', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'version', 'type' => 'date', 'default' => '0001-01-01'), array('name' => 'id_member', 'type' => 'mediumint', 'size' => 10, 'unsigned' => true, 'default' => 0), array('name' => 'accepted_date', 'type' => 'date', 'default' => '0001-01-01'), array('name' => 'accepted_ip', 'type' => 'varchar', 'size' => 255, 'default' => ''), ), array( - array('name' => 'modify_date', 'columns' => array('modify_date', 'id_member'), 'type' => 'primary'), + array('name' => 'version', 'columns' => array('version', 'id_member'), 'type' => 'primary'), ), array(), 'ignore' diff --git a/sources/Load.php b/sources/Load.php index 6e6e205c7d..028343c081 100644 --- a/sources/Load.php +++ b/sources/Load.php @@ -433,9 +433,9 @@ function loadUserSettings() if ($user_info['is_guest'] === false) { $http_request = HttpReq::instance(); - if (!empty($modSettings['agreementRevision']) && !empty($modSettings['requireAgreement']) && in_array($http_request->query->action, array('reminder', 'register')) === false) + if (!empty($modSettings['agreementRevision']) && !empty($modSettings['requireAgreement']) && in_array($http_request->action, array('reminder', 'register')) === false) { - if ($http_request->query->action !== 'profile' || $http_request->area !== 'deleteaccount') + if ($http_request->action !== 'profile' || $http_request->area !== 'deleteaccount') { $agreement = new Agreement($user_info['language']); if (false === $agreement->checkAccepted($id_member, $modSettings['agreementRevision'])) @@ -444,19 +444,6 @@ function loadUserSettings() setOldUrl('agreement_url_redirect'); redirectexit('action=register;sa=agreement', true); } - - if (!empty($_SESSION['agreement_accepted'])) - { - // This loadLanguage is needed here because the check is done way too early in the process. - // As a stop-gap it's fine, but in future versions it should be fixed somehow. - loadLanguage('index'); - $_SESSION['agreement_accepted'] = null; - $context['accepted_agreement'] = array( - 'errors' => array( - 'accepted_agreement' => $txt['agreement_accepted'] - ) - ); - } } } } @@ -1650,6 +1637,18 @@ function loadTheme($id_theme = 0, $initialize = true) 'atom' => $scripturl . '?action=.xml;type=atom;limit=' . (!empty($modSettings['xmlnews_limit']) ? $modSettings['xmlnews_limit'] : 5) ); + if (!empty($_SESSION['agreement_accepted'])) + { + // This loadLanguage is needed here because the check is done way too early in the process. + // As a stop-gap it's fine, but in future versions it should be fixed somehow. + $_SESSION['agreement_accepted'] = null; + $context['accepted_agreement'] = array( + 'errors' => array( + 'accepted_agreement' => $txt['agreement_accepted'] + ) + ); + } + theme()->loadThemeJavascript(); Hooks::instance()->newPath(array('$themedir' => $settings['theme_dir'])); diff --git a/sources/controllers/Register.controller.php b/sources/controllers/Register.controller.php index 4d8b8ceebd..d8ff9a892d 100644 --- a/sources/controllers/Register.controller.php +++ b/sources/controllers/Register.controller.php @@ -1248,8 +1248,9 @@ public function action_agreement() if (isset($this->_req->post->accept_agreement)) { - require_once(SUBSDIR . '/Members.subs.php'); - registerAgreementAccepted($user_info['id'], $user_info['ip'], empty($modSettings['agreementRevision']) ? strftime('%Y-%m-%d', forum_time(false)) : $modSettings['agreementRevision']); + $agreement = new Agreement($user_info['language']); + $agreement->accept($user_info['id'], $user_info['ip'], empty($modSettings['agreementRevision']) ? strftime('%Y-%m-%d', forum_time(false)) : $modSettings['agreementRevision']); + $_SESSION['agreement_accepted'] = true; if (isset($_SESSION['agreement_url_redirect'])) { diff --git a/sources/subs/Agreement.class.php b/sources/subs/Agreement.class.php index b3b82d53ed..672e7677fd 100644 --- a/sources/subs/Agreement.class.php +++ b/sources/subs/Agreement.class.php @@ -32,6 +32,27 @@ class Agreement */ protected $_backup_dir = ''; + /** + * The name of the file where the agreement is stored + * + * @var string + */ + protected $_file_name = 'agreement'; + + /** + * The name of the directory where the backup will be saved + * + * @var string + */ + protected $_backupdir_name = 'agreements'; + + /** + * The name of the log table + * + * @var string + */ + protected $_log_table_name = '{db_prefix}log_agreement_accept'; + /** * The database object * @@ -50,7 +71,7 @@ public function __construct($language, $backup_dir = null) $this->_language = strtr($language, array('.' => '')); if ($backup_dir === null || file_exists($backup_dir) === false) { - $backup_dir = BOARDDIR . '/packages/backups/agreements'; + $backup_dir = BOARDDIR . '/packages/backups/' . $this->_backupdir_name; } $this->_backup_dir = $backup_dir; $this->_db = database(); @@ -77,7 +98,7 @@ public function save($text, $update_backup = false) } // Off it goes to the agreement file. - $fp = fopen(BOARDDIR . '/agreement' . $this->normalizeLanguage() . '.txt', 'w'); + $fp = fopen(BOARDDIR . '/' . $this->_file_name . $this->normalizeLanguage() . '.txt', 'w'); fwrite($fp, str_replace("\r", '', $text)); fclose($fp); @@ -96,13 +117,13 @@ public function save($text, $update_backup = false) public function getPlainText($fallback = true) { // Have we got a localized one? - if (file_exists(BOARDDIR . '/agreement' . $this->normalizeLanguage() . '.txt')) + if (file_exists(BOARDDIR . '/' . $this->_file_name . $this->normalizeLanguage() . '.txt')) { - $agreement = file_get_contents(BOARDDIR . '/agreement' . $this->normalizeLanguage() . '.txt'); + $agreement = file_get_contents(BOARDDIR . '/' . $this->_file_name . $this->normalizeLanguage() . '.txt'); } - elseif ($fallback === true && file_exists(BOARDDIR . '/agreement.txt')) + elseif ($fallback === true && file_exists(BOARDDIR . '/' . $this->_file_name . '.txt')) { - $agreement = file_get_contents(BOARDDIR . '/agreement.txt'); + $agreement = file_get_contents(BOARDDIR . '/' . $this->_file_name . '.txt'); } else { @@ -134,7 +155,7 @@ public function getParsedText($fallback = true) */ public function isWritable() { - $filename = BOARDDIR . '/agreement' . $this->normalizeLanguage() . '.txt'; + $filename = BOARDDIR . '/' . $this->_file_name . $this->normalizeLanguage() . '.txt'; return file_exists($filename) && is_writable($filename); } @@ -143,24 +164,48 @@ public function isWritable() * Test if the user accepted the current agreement or not. * * @param int $id_member The id of the member - * @param string $agreement_date The date of the agreement + * @param string $version The date of the agreement */ - public function checkAccepted($id_member, $agreement_date) + public function checkAccepted($id_member, $version) { $accepted = $this->_db->fetchQuery(' SELECT 1 - FROM {db_prefix}log_agreement_accept - WHERE agreement_date = {date:agreement_date} + FROM ' . $this->_log_table_name . ' + WHERE version = {date:version} AND id_member = {int:id_member}', array( 'id_member' => $id_member, - 'agreement_date' => $agreement_date, + 'version' => $version, ) ); return !empty($accepted); } + public function accept($id_member, $ip, $version) + { + $db = database(); + + $db->insert('', + $this->_log_table_name, + array( + 'version' => 'date', + 'id_member' => 'int', + 'accepted_date' => 'date', + 'accepted_ip' => 'string-255', + ), + array( + array( + 'version' => $version, + 'id_member' => $id_member, + 'accepted_date' => strftime('%Y-%m-%d', forum_time(false)), + 'accepted_ip' => $ip, + ) + ), + array('version', 'id_member') + ); + } + /** * Takes care of the edge-case of the default agreement that doesn't have * the language in the name, and the fact that the admin panels loads it @@ -188,7 +233,7 @@ protected function _createBackup($backup_id) { return false; } - $glob = new GlobIterator(BOARDDIR . '/agreement*.txt', FilesystemIterator::SKIP_DOTS); + $glob = new GlobIterator(BOARDDIR . '/' . $this->_file_name . '*.txt', FilesystemIterator::SKIP_DOTS); foreach ($glob as $file) { copy($file->getPathname(), $destination . $file->getBasename()); diff --git a/sources/subs/Members.subs.php b/sources/subs/Members.subs.php index b271b90e13..6ac9bd38d0 100644 --- a/sources/subs/Members.subs.php +++ b/sources/subs/Members.subs.php @@ -2615,19 +2615,19 @@ function registerAgreementAccepted($id_member, $ip, $agreement_version) $db->insert('', '{db_prefix}log_agreement_accept', array( - 'agreement_date' => 'date', + 'version' => 'date', 'id_member' => 'int', 'accepted_date' => 'date', 'accepted_ip' => 'string-255', ), array( array( - 'agreement_date' => $agreement_version, + 'version' => $agreement_version, 'id_member' => $id_member, 'accepted_date' => strftime('%Y-%m-%d', forum_time(false)), 'accepted_ip' => $ip, ) ), - array('agreement_date', 'id_member') + array('version', 'id_member') ); -} \ No newline at end of file +} From 5445dd55ac07c7b5fec16c41cb1a7a07d6d9af1f Mon Sep 17 00:00:00 2001 From: emanuele Date: Thu, 24 May 2018 21:36:24 +0200 Subject: [PATCH 06/13] Add the admin interface to edit the privacy policy --- install/install_1-1.php | 4 +- install/upgrade_1-1.php | 19 +++- sources/admin/Admin.controller.php | 1 + .../admin/ManageRegistration.controller.php | 93 +++++++++++++++++++ sources/controllers/Register.controller.php | 44 +++++++++ sources/subs/Agreement.class.php | 23 ++++- sources/subs/PrivacyPolicy.class.php | 35 +++++++ themes/default/Register.template.php | 19 ++-- .../languages/english/Admin.english.php | 9 +- .../languages/english/Login.english.php | 3 +- themes/default/scripts/admin.js | 7 ++ 11 files changed, 240 insertions(+), 17 deletions(-) create mode 100644 sources/subs/PrivacyPolicy.class.php diff --git a/install/install_1-1.php b/install/install_1-1.php index 4ede073943..e3cca67adf 100644 --- a/install/install_1-1.php +++ b/install/install_1-1.php @@ -1157,7 +1157,7 @@ public function table_log_agreement_accept() { return $this->table->db_create_table('{db_prefix}log_agreement_accept', array( - array('name' => 'version', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'version', 'type' => 'varchar', 'size' => 20, 'default' => ''), array('name' => 'id_member', 'type' => 'mediumint', 'size' => 10, 'unsigned' => true, 'default' => 0), array('name' => 'accepted_date', 'type' => 'date', 'default' => '0001-01-01'), array('name' => 'accepted_ip', 'type' => 'varchar', 'size' => 255, 'default' => ''), @@ -2425,7 +2425,7 @@ public function table_log_privacy_policy_accept() { return $this->table->db_create_table('{db_prefix}log_privacy_policy_accept', array( - array('name' => 'version', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'version', 'type' => 'varchar', 'size' => 20, 'default' => ''), array('name' => 'id_member', 'type' => 'mediumint', 'size' => 10, 'unsigned' => true, 'default' => 0), array('name' => 'accepted_date', 'type' => 'date', 'default' => '0001-01-01'), array('name' => 'accepted_ip', 'type' => 'varchar', 'size' => 255, 'default' => ''), diff --git a/install/upgrade_1-1.php b/install/upgrade_1-1.php index 1d8da34fa2..3d39787c36 100644 --- a/install/upgrade_1-1.php +++ b/install/upgrade_1-1.php @@ -868,19 +868,32 @@ public function update_settings() public function agreement_logging_title() { - return 'Introducing the logging of accepted agreement...'; + return 'Introducing the logging of accepted agreement and privacy policy...'; } public function agreement_logging() { return array( array( - 'debug_title' => 'Creating the table...', + 'debug_title' => 'Creating the tables...', 'function' => function() { $this->table->db_create_table('{db_prefix}log_agreement_accept', array( - array('name' => 'version', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'version', 'type' => 'varchar', 'size' => 20, 'default' => ''), + array('name' => 'id_member', 'type' => 'mediumint', 'size' => 10, 'unsigned' => true, 'default' => 0), + array('name' => 'accepted_date', 'type' => 'date', 'default' => '0001-01-01'), + array('name' => 'accepted_ip', 'type' => 'varchar', 'size' => 255, 'default' => ''), + ), + array( + array('name' => 'version', 'columns' => array('version', 'id_member'), 'type' => 'primary'), + ), + array(), + 'ignore' + ); + $this->table->db_create_table('{db_prefix}log_privacy_policy_accept', + array( + array('name' => 'version', 'type' => 'varchar', 'size' => 20, 'default' => ''), array('name' => 'id_member', 'type' => 'mediumint', 'size' => 10, 'unsigned' => true, 'default' => 0), array('name' => 'accepted_date', 'type' => 'date', 'default' => '0001-01-01'), array('name' => 'accepted_ip', 'type' => 'varchar', 'size' => 255, 'default' => ''), diff --git a/sources/admin/Admin.controller.php b/sources/admin/Admin.controller.php index 5cf25d1058..183346bc96 100644 --- a/sources/admin/Admin.controller.php +++ b/sources/admin/Admin.controller.php @@ -457,6 +457,7 @@ private function loadMenu() 'subsections' => array( 'register' => array($txt['admin_browse_register_new'], 'moderate_forum'), 'agreement' => array($txt['registration_agreement'], 'admin_forum'), + 'privacypol' => array($txt['privacy_policy'], 'admin_forum'), 'reservednames' => array($txt['admin_reserved_set'], 'admin_forum'), 'settings' => array($txt['settings'], 'admin_forum'), ), diff --git a/sources/admin/ManageRegistration.controller.php b/sources/admin/ManageRegistration.controller.php index 81c8603a5e..f76bb32b46 100644 --- a/sources/admin/ManageRegistration.controller.php +++ b/sources/admin/ManageRegistration.controller.php @@ -62,6 +62,11 @@ public function action_index() 'function' => 'action_agreement', 'permission' => 'admin_forum', ), + 'privacypol' => array( + 'controller' => $this, + 'function' => 'action_privacypol', + 'permission' => 'admin_forum', + ), 'reservednames' => array( 'controller' => $this, 'function' => 'action_reservednames', @@ -89,6 +94,9 @@ public function action_index() 'agreement' => array( 'description' => $txt['registration_agreement_desc'], ), + 'privacypol' => array( + 'description' => $txt['privacy_policy_desc'], + ), 'reservednames' => array( 'description' => $txt['admin_reserved_desc'], ), @@ -303,10 +311,95 @@ public function action_agreement() $context['checkbox_agreement'] = !empty($modSettings['checkboxAgreement']); $context['sub_template'] = 'edit_agreement'; + $context['subaction'] = 'agreement'; + $context['agreement_show_options'] = true; $context['page_title'] = $txt['registration_agreement']; createToken('admin-rega'); } + /** + * Allows the administrator to edit the privacy policy, and choose whether + * it should be shown or not. + * + * - It writes and saves the privacy policy to the privacypol.txt file. + * - Accessed by ?action=admin;area=regcenter;sa=privacypol. + * - Requires the admin_forum permission. + * + * @uses Admin template and the edit_agreement sub template. + */ + public function action_privacypol() + { + // I hereby agree not to be a lazy bum. + global $txt, $context, $modSettings; + + // By default we look at privacypol.txt. + $context['current_agreement'] = ''; + + // Is there more than one to edit? + $context['editable_agreements'] = array( + '' => $txt['admin_agreement_default'], + ); + + // Get our languages. + $languages = getLanguages(); + + // Try to figure out if we have more agreements. + foreach ($languages as $lang) + { + if (file_exists(BOARDDIR . '/privacypol.' . $lang['filename'] . '.txt')) + { + $context['editable_agreements'][$lang['filename']] = $lang['name']; + + // Are we editing this? + if (isset($this->_req->post->agree_lang) && $this->_req->post->agree_lang === $lang['filename']) + { + $context['current_agreement'] = $lang['filename']; + break; + } + } + } + + $context['warning'] = ''; + $privacypol = new \PrivacyPolicy($context['current_agreement']); + + if (isset($this->_req->post->save) && isset($this->_req->post->agreement)) + { + checkSession(); + validateToken('admin-rega'); + + // Off it goes to the agreement file. + $success = $privacypol->save($this->_req->post->agreement, !empty($this->_req->post->checkboxAcceptAgreement)); + if (!empty($this->_req->post->checkboxAcceptAgreement)) + { + if ($success === false) + { + $context['warning'] .= $txt['privacypol_backup_not_writable'] . '
'; + } + else + { + updateSettings(array('agreementRevision' => $success)); + } + } + + updateSettings(array('requirePrivacypolicy' => !empty($this->_req->post->requireAgreement), 'checkboxPrivacypolicy' => !empty($this->_req->post->checkboxAgreement))); + } + + $context['agreement'] = Util::htmlspecialchars($privacypol->getPlainText(false)); + + $context['warning'] .= $privacypol->isWritable() ? '' : $txt['privacypol_not_writable']; + $context['require_agreement'] = !empty($modSettings['requirePrivacypolicy']); + $context['checkbox_agreement'] = !empty($modSettings['checkboxPrivacypolicy']); + + $context['sub_template'] = 'edit_agreement'; + $context['subaction'] = 'privacypol'; + $context['page_title'] = $txt['privacy_policy']; + // These overrides are here to be able to reuse the template in a simple way without having to change much. + $txt['admin_agreement'] = $txt['admin_privacypol']; + $txt['admin_checkbox_accept_agreement'] = $txt['admin_checkbox_accept_privacypol']; + + createToken('admin-rega'); + } + /** * Set the names under which users are not allowed to register. * diff --git a/sources/controllers/Register.controller.php b/sources/controllers/Register.controller.php index d8ff9a892d..0fc0c28f75 100644 --- a/sources/controllers/Register.controller.php +++ b/sources/controllers/Register.controller.php @@ -84,6 +84,7 @@ public function action_index() 'verificationcode' => array($this, 'action_verificationcode'), 'coppa' => array($this, 'action_coppa'), 'agrelang' => array($this, 'action_agrelang'), + 'privacypol' => array($this, 'action_privacypol'), 'agreement' => array($this, 'action_agreement'), ); @@ -1285,4 +1286,47 @@ public function action_agreement() } createToken('register'); } + + public function action_privacypol() + { + global $context, $user_info, $modSettings; + + $policy = new \PrivacyPolicy($user_info['language']); + + if (isset($this->_req->post->accept_agreement)) + { + $policy->accept($user_info['id'], $user_info['ip'], empty($modSettings['privacyPolicyRevision']) ? strftime('%Y-%m-%d', forum_time(false)) : $modSettings['privacyPolicyRevision']); + + $_SESSION['privacypolicy_accepted'] = true; + if (isset($_SESSION['privacypolicy_url_redirect'])) + { + redirectexit($_SESSION['privacypolicy_url_redirect']); + } + } + elseif (isset($this->_req->post->no_accept)) + { + redirectexit('action=profile;area=deleteaccount'); + } + else + { + $context['sub_template'] = 'registration_agreement'; + $context['register_subaction'] = 'privacypol'; + } + + loadLanguage('Login'); + loadLanguage('Profile'); + loadTemplate('Register'); + + $txt['agreement_agree'] = $txt['policy_agree']; + $txt['agreement_no_agree'] = $txt['policy_no_agree']; + + // If you have to agree to the privacy policy, it needs to be fetched from the file. + $context['agreement'] = $policy->getParsedText(); + + $context['show_coppa'] = false; + $context['skip_coppa'] = true; + $context['show_contact_button'] = !empty($modSettings['enable_contactform']) && $modSettings['enable_contactform'] === 'registration'; + + createToken('register'); + } } diff --git a/sources/subs/Agreement.class.php b/sources/subs/Agreement.class.php index 672e7677fd..c2781dfdc4 100644 --- a/sources/subs/Agreement.class.php +++ b/sources/subs/Agreement.class.php @@ -90,7 +90,7 @@ public function save($text, $update_backup = false) $backup_id = ''; if ($update_backup === true) { - $backup_id = strftime('%Y-%m-%d', forum_time(false)); + $backup_id = $this->_backupId(); if ($this->_createBackup($backup_id) === false) { $backup_id = false; @@ -171,7 +171,7 @@ public function checkAccepted($id_member, $version) $accepted = $this->_db->fetchQuery(' SELECT 1 FROM ' . $this->_log_table_name . ' - WHERE version = {date:version} + WHERE version = {string:version} AND id_member = {int:id_member}', array( 'id_member' => $id_member, @@ -189,7 +189,7 @@ public function accept($id_member, $ip, $version) $db->insert('', $this->_log_table_name, array( - 'version' => 'date', + 'version' => 'string-20', 'id_member' => 'int', 'accepted_date' => 'date', 'accepted_ip' => 'string-255', @@ -216,6 +216,21 @@ protected function normalizeLanguage() return $this->_language === '' ? '' : '.' . $this->_language; } + protected function _backupId() + { + $backup_id = strftime('%Y-%m-%d', forum_time(false)); + $counter = ''; + $merger = ''; + + while (file_exists($this->_backup_dir . '/' . $backup_id . $merger . $counter . '/') === true) + { + $counter++; + $merger = '_'; + } + + return $backup_id . $merger . $counter; + } + /** * Creates a full backup of all the agreements. * @@ -240,4 +255,4 @@ protected function _createBackup($backup_id) } return true; } -} \ No newline at end of file +} diff --git a/sources/subs/PrivacyPolicy.class.php b/sources/subs/PrivacyPolicy.class.php new file mode 100644 index 0000000000..8109bf12af --- /dev/null +++ b/sources/subs/PrivacyPolicy.class.php @@ -0,0 +1,35 @@ +_log_table_name = '{db_prefix}log_privacy_policy_accept'; + $this->_backupdir_name = 'privacypolicies'; + $this->_file_name = 'privacypolicy'; + + parent::__construct($language, $backup_dir); + } +} \ No newline at end of file diff --git a/themes/default/Register.template.php b/themes/default/Register.template.php index 12a0fff6f5..693c0c2fe4 100644 --- a/themes/default/Register.template.php +++ b/themes/default/Register.template.php @@ -733,8 +733,8 @@ function template_edit_agreement() // Just a big box to edit the text file ;). echo ' -
-

', $txt['registration_agreement'], '

'; + +

', $context['page_title'], '

'; // Warning for if the file isn't writable. if (!empty($context['warning'])) @@ -775,15 +775,22 @@ function template_edit_agreement()

- + '; + + if (!empty($context['agreement_show_options'])) + { + echo '
- + '; + } + + echo '
- +

- +
diff --git a/themes/default/languages/english/Admin.english.php b/themes/default/languages/english/Admin.english.php index 4ca2ccd2ed..30670399ae 100644 --- a/themes/default/languages/english/Admin.english.php +++ b/themes/default/languages/english/Admin.english.php @@ -47,11 +47,14 @@ $txt['admin_credits'] = 'Credits'; $txt['admin_agreement'] = 'Show and require agreement letter when registering'; $txt['admin_checkbox_agreement'] = 'Show a checkbox for the agreement in registration form instead of a full page'; -$txt['admin_checkbox_accept_agreement'] = 'Force members to accept the agreement at the next login'; +$txt['admin_checkbox_accept_agreement'] = 'Force all members to accept the agreement at the next visit to the forum'; $txt['admin_agreement_default'] = 'Default'; $txt['admin_agreement_select_language'] = 'Language to edit'; $txt['admin_agreement_select_language_change'] = 'Change'; +$txt['admin_privacypol'] = 'Require to accept the privacy policy when registering'; +$txt['admin_checkbox_accept_privacypol'] = 'Force all members to accept the privacy policy at the next visit to the forum'; + $txt['admin_delete_members'] = 'Delete Selected Members'; $txt['admin_change_primary_membergroup'] = 'Change primary member group'; $txt['admin_change_secondary_membergroup'] = 'Change/add additional member group'; @@ -88,6 +91,8 @@ $txt['database_name'] = 'Database Name'; $txt['registration_agreement'] = 'Registration Agreement'; $txt['registration_agreement_desc'] = 'This agreement is shown when a user registers an account on this forum and has to be accepted before users can continue registration.'; +$txt['privacy_policy'] = 'Privacy Policy'; +$txt['privacy_policy_desc'] = 'This privacy policy is shown when a user registers an account on this forum and can be made mandatory before users can continue registration.'; $txt['database_prefix'] = 'Database Tables Prefix'; $txt['errors_list'] = 'Listing of forum errors'; $txt['errors_found'] = 'The following errors are fouling up your forum'; @@ -126,6 +131,8 @@ $txt['remove_all'] = 'Clear Log'; $txt['agreement_not_writable'] = 'Warning - agreement.txt is not writable, any changes you make will NOT be saved.'; $txt['agreement_backup_not_writable'] = 'Warning - the backup directory in forum_root/packages/backup cannot be created.'; +$txt['privacypol_not_writable'] = 'Warning - privacypol.txt is not writable, any changes you make will NOT be saved.'; +$txt['privacypol_backup_not_writable'] = 'Warning - the backup directory in forum_root/packages/backup cannot be created.'; $txt['version_check_desc'] = 'This shows you the versions of your installation\'s files versus those of the latest version. If any of these files are out of date, you should download and upgrade to the latest version at our ElkArte Site.'; $txt['version_check_more'] = '(more detailed)'; diff --git a/themes/default/languages/english/Login.english.php b/themes/default/languages/english/Login.english.php index a0c7b2d687..0f4314d71d 100644 --- a/themes/default/languages/english/Login.english.php +++ b/themes/default/languages/english/Login.english.php @@ -29,6 +29,7 @@ $txt['login_below'] = 'Please login below.'; $txt['login_below_or_register'] = 'Please login below or register an account with %2$s'; $txt['checkbox_agreement'] = 'I accept the registration agreement'; +$txt['confirm_request_accept_agreement'] = 'Are you sure you want to force all the users to accept the agreement?'; $txt['login_hash_error'] = 'Password security has recently been upgraded.
Please enter your password again.'; @@ -148,4 +149,4 @@ $txt['contact_your_message'] = 'Your message'; $txt['errors_contact_form'] = 'The following errors occurred while processing your contact request'; $txt['contact_subject'] = 'A guest has sent you a message'; -$txt['contact_thankyou'] = 'Thank you for your message, someone will contact you as soon as possible.'; \ No newline at end of file +$txt['contact_thankyou'] = 'Thank you for your message, someone will contact you as soon as possible.'; diff --git a/themes/default/scripts/admin.js b/themes/default/scripts/admin.js index a105a99a2e..bbaf17a49f 100644 --- a/themes/default/scripts/admin.js +++ b/themes/default/scripts/admin.js @@ -2004,3 +2004,10 @@ $(function() { }); }); }); + +function confirmAgreement(text) { + if ($('#checkboxAcceptAgreement').is(':checked')) { + return confirm(text); + } + return true; +} From 765040204158803605ed873cf26c51f7b09b237f Mon Sep 17 00:00:00 2001 From: emanuele Date: Thu, 24 May 2018 23:58:15 +0200 Subject: [PATCH 07/13] And finally binds the registration to the acceptance of both the agreement and the privacy policy (if present) --- .../admin/ManageRegistration.controller.php | 3 +- sources/controllers/Jslocale.controller.php | 13 ++++- sources/controllers/Register.controller.php | 44 ++++++++++++++- themes/default/Register.template.php | 54 +++++++++++++++---- themes/default/css/index.css | 4 +- .../languages/english/Errors.english.php | 3 +- .../languages/english/Login.english.php | 4 ++ themes/default/scripts/register.js | 5 +- 8 files changed, 109 insertions(+), 21 deletions(-) diff --git a/sources/admin/ManageRegistration.controller.php b/sources/admin/ManageRegistration.controller.php index f76bb32b46..3ce6c98902 100644 --- a/sources/admin/ManageRegistration.controller.php +++ b/sources/admin/ManageRegistration.controller.php @@ -381,14 +381,13 @@ public function action_privacypol() } } - updateSettings(array('requirePrivacypolicy' => !empty($this->_req->post->requireAgreement), 'checkboxPrivacypolicy' => !empty($this->_req->post->checkboxAgreement))); + updateSettings(array('requirePrivacypolicy' => !empty($this->_req->post->requireAgreement))); } $context['agreement'] = Util::htmlspecialchars($privacypol->getPlainText(false)); $context['warning'] .= $privacypol->isWritable() ? '' : $txt['privacypol_not_writable']; $context['require_agreement'] = !empty($modSettings['requirePrivacypolicy']); - $context['checkbox_agreement'] = !empty($modSettings['checkboxPrivacypolicy']); $context['sub_template'] = 'edit_agreement'; $context['subaction'] = 'privacypol'; diff --git a/sources/controllers/Jslocale.controller.php b/sources/controllers/Jslocale.controller.php index c2c5e35d5f..a7467c7fbb 100644 --- a/sources/controllers/Jslocale.controller.php +++ b/sources/controllers/Jslocale.controller.php @@ -87,9 +87,18 @@ public function action_agreement_api() { // If you have to agree to the agreement, it needs to be fetched from the file. $agreement = new \Agreement($lang); + if (!empty($modSettings['requirePrivacypolicy'])) + { + $privacypol = new \PrivacyPolicy($lang); + } + $context['json_data'] = array('agreement' => '', 'privacypol' => ''); try { - $context['json_data'] = $agreement->getParsedText(); + $context['json_data']['agreement'] = $agreement->getParsedText(); + if (!empty($modSettings['requirePrivacypolicy'])) + { + $context['json_data']['privacypol'] = $privacypol->getParsedText(); + } } catch (\Elk_Exception $e) { @@ -133,4 +142,4 @@ private function _sendFile() // And terminate obExit(false); } -} \ No newline at end of file +} diff --git a/sources/controllers/Register.controller.php b/sources/controllers/Register.controller.php index 0fc0c28f75..0fa1b99384 100644 --- a/sources/controllers/Register.controller.php +++ b/sources/controllers/Register.controller.php @@ -133,7 +133,9 @@ public function action_register() // Do we need them to agree to the registration agreement, first? $context['require_agreement'] = !empty($modSettings['requireAgreement']); $context['checkbox_agreement'] = !empty($modSettings['checkboxAgreement']); + $context['require_privacypol'] = !empty($modSettings['requirePrivacypolicy']); $context['registration_passed_agreement'] = !empty($_SESSION['registration_agreed']); + $context['registration_passed_privacypol'] = !empty($_SESSION['registration_privacypolicy']); $context['show_coppa'] = !empty($modSettings['coppaAge']); $context['show_contact_button'] = !empty($modSettings['enable_contactform']) && $modSettings['enable_contactform'] === 'registration'; if (!empty($modSettings['show_DisplayNameOnRegistration'])) @@ -161,6 +163,7 @@ public function action_register() if ($current_step == 1 && (isset($this->_req->post->accept_agreement) || isset($this->_req->post->accept_agreement_coppa))) { $context['registration_passed_agreement'] = $_SESSION['registration_agreed'] = true; + $context['registration_passed_privacypol'] = $_SESSION['registration_privacypolicy'] = true; $current_step = 2; // Skip the coppa procedure if the user says he's old enough. @@ -224,6 +227,23 @@ public function action_register() throw new Elk_Exception('registration_disabled', false); } + if (!empty($context['require_privacypol'])) + { + $privacypol = new \PrivacyPolicy($user_info['language']); + $context['privacy_policy'] = $privacypol->getParsedText(); + + if (empty($context['privacy_policy'])) + { + // No file found or a blank file, log the error so the admin knows there is a problem! + loadLanguage('Errors'); + Errors::instance()->log_error($txt['registration_privacy_policy_missing'], 'critical'); + throw new Elk_Exception('registration_disabled', false); + } + } + + // If we have language support enabled then they need to be loaded + $this->_load_language_support(); + // Any custom or standard profile fields we want filled in during registration? $this->_load_profile_fields(); @@ -289,6 +309,13 @@ public function action_register2() if (!empty($modSettings['requireAgreement']) && empty($_SESSION['registration_agreed'])) redirectexit(); + if (!empty($modSettings['requireAgreement']) && !empty($modSettings['requirePrivacypolicy']) && !empty($this->_req->post->checkbox_privacypol)) + $_SESSION['registration_privacypolicy'] = true; + + // Well, if you don't agree, you can't register. + if (!empty($modSettings['requireAgreement']) && !empty($modSettings['requirePrivacypolicy']) && empty($_SESSION['registration_privacypolicy'])) + redirectexit(); + // Make sure they came from *somewhere*, have a session. if (!isset($_SESSION['old_url'])) redirectexit('action=register'); @@ -492,6 +519,16 @@ public function do_register($verifiedOpenID = false) $regOptions['ip2'] = $req->ban_ip(); $memberID = registerMember($regOptions, 'register'); + $lang = !empty($modSettings['userLanguage']) ? $modSettings['userLanguage'] : 'english'; + $agreement = new Agreement($lang); + $agreement->accept($memberID, $user_info['ip'], empty($modSettings['agreementRevision']) ? strftime('%Y-%m-%d', forum_time(false)) : $modSettings['agreementRevision']); + + if (!empty($modSettings['requirePrivacypolicy'])) + { + $policy = new \PrivacyPolicy($lang); + $policy->accept($memberID, $user_info['ip'], empty($modSettings['privacyPolicyRevision']) ? strftime('%Y-%m-%d', forum_time(false)) : $modSettings['privacyPolicyRevision']); + } + // If there are "important" errors and you are not an admin: log the first error // Otherwise grab all of them and don't log anything if ($reg_errors->hasErrors(1) && !$user_info['is_admin']) @@ -1245,7 +1282,7 @@ public function action_registerCheckUsername() public function action_agreement() { - global $context, $user_info, $modSettings; + global $context, $user_info, $modSettings, $txt; if (isset($this->_req->post->accept_agreement)) { @@ -1274,6 +1311,7 @@ public function action_agreement() // If you have to agree to the agreement, it needs to be fetched from the file. $agreement = new \Agreement($user_info['language']); $context['agreement'] = $agreement->getParsedText(); + $context['page_title'] = $txt['registration_agreement']; $context['show_coppa'] = !empty($modSettings['coppaAge']); $context['show_contact_button'] = !empty($modSettings['enable_contactform']) && $modSettings['enable_contactform'] === 'registration'; @@ -1289,7 +1327,7 @@ public function action_agreement() public function action_privacypol() { - global $context, $user_info, $modSettings; + global $context, $user_info, $modSettings, $txt; $policy = new \PrivacyPolicy($user_info['language']); @@ -1319,6 +1357,8 @@ public function action_privacypol() $txt['agreement_agree'] = $txt['policy_agree']; $txt['agreement_no_agree'] = $txt['policy_no_agree']; + $txt['registration_agreement'] = $txt['registration_privacy_policy']; + $context['page_title'] = $txt['registration_agreement']; // If you have to agree to the privacy policy, it needs to be fetched from the file. $context['agreement'] = $policy->getParsedText(); diff --git a/themes/default/Register.template.php b/themes/default/Register.template.php index 693c0c2fe4..1eecac82e6 100644 --- a/themes/default/Register.template.php +++ b/themes/default/Register.template.php @@ -21,34 +21,54 @@ function template_registration_agreement() global $context, $scripturl, $txt; echo ' - -

', $txt['registration_agreement']; + '; if (!empty($context['languages'])) { if (count($context['languages']) === 1) + { foreach ($context['languages'] as $lang_key => $lang_val) + { echo ' '; + } + } else { echo ' '; } } - echo ' -

+ if (!empty($context['agreement'])) + { + echo ' +

', $txt['registration_agreement'], '

', $context['agreement'], '

-
+ '; + } + + if (!empty($context['privacy_policy'])) + { + echo ' +

', $txt['registration_privacy_policy'], ' +

+
+

', $context['privacy_policy'], '

+
'; + } + + echo '
'; // Age restriction in effect? @@ -410,11 +430,23 @@ function verifyAgree()
', $context['agreement'], ' -
'; +
+ +
+ ', $context['privacy_policy'], ' +
+ '; if (!empty($context['languages'])) { echo ' +
'; } echo ' - '; } @@ -437,17 +465,23 @@ function verifyAgree() // Age restriction in effect? if ((!$context['require_agreement'] || $context['checkbox_agreement']) && $context['show_coppa']) + { echo '

'; + } else + { echo ' '; + } if ($context['show_contact_button']) + { echo ' '; + } echo ' diff --git a/themes/default/css/index.css b/themes/default/css/index.css index ab73639069..58447d0849 100644 --- a/themes/default/css/index.css +++ b/themes/default/css/index.css @@ -5233,7 +5233,7 @@ a.help .icon { border: 1px solid; } -#agreement_box { +#agreement_box, #privacypol_box { overflow-y: scroll; margin-bottom: 2em; padding: 8px; @@ -6787,4 +6787,4 @@ a > i { width: 2em; height: 2em; } -} \ No newline at end of file +} diff --git a/themes/default/languages/english/Errors.english.php b/themes/default/languages/english/Errors.english.php index 4ac52ec059..118810e611 100644 --- a/themes/default/languages/english/Errors.english.php +++ b/themes/default/languages/english/Errors.english.php @@ -130,6 +130,7 @@ $txt['theme_dir_wrong'] = 'The default theme\'s directory is wrong, please correct it by clicking this text.'; $txt['registration_disabled'] = 'Sorry, registration is currently disabled.'; $txt['registration_agreement_missing'] = 'The registration agreement file, agreement.txt, is either missing or empty. Registrations will be disabled until this is fixed'; +$txt['registration_privacy_policy_missing'] = 'The privacy policy file, privacypolicy.txt, is either missing or empty. Registrations will be disabled until this is fixed'; $txt['registration_no_secret_question'] = 'Sorry, there is no secret question set for this member.'; $txt['poll_range_error'] = 'Sorry, the poll must run for more than 0 days.'; $txt['delFirstPost'] = 'You are not allowed to delete the first post in a topic.

If you want to delete this topic, click on the Remove link, or ask a moderator/administrator to do it for you.

'; @@ -428,4 +429,4 @@ // Drag / Drop sort errors $txt['no_sortable_items'] = 'No sortable items were found'; -$txt['error_invalid_notification_id'] = 'An addons is trying to register a notification method with an existing ID. ID lower than 5 are protected and cannot be used by addons, if the ID is higher, than two addons may be sharing the same ID.'; \ No newline at end of file +$txt['error_invalid_notification_id'] = 'An addons is trying to register a notification method with an existing ID. ID lower than 5 are protected and cannot be used by addons, if the ID is higher, than two addons may be sharing the same ID.'; diff --git a/themes/default/languages/english/Login.english.php b/themes/default/languages/english/Login.english.php index 0f4314d71d..5f01960fb6 100644 --- a/themes/default/languages/english/Login.english.php +++ b/themes/default/languages/english/Login.english.php @@ -3,8 +3,11 @@ // Registration agreement page. $txt['registration_agreement'] = 'Registration Agreement'; +$txt['registration_privacy_policy'] = 'Privacy Policy'; $txt['agreement_agree'] = 'I accept the terms of the agreement.'; $txt['agreement_no_agree'] = 'I do not accept the terms of the agreement.'; +$txt['policy_agree'] = 'I accept the terms of the privacy policy.'; +$txt['policy_no_agree'] = 'I do not accept the terms of the privacy policy.'; $txt['agreement_agree_coppa_above'] = 'I accept the terms of the agreement and I am at least %1$d years old.'; $txt['agreement_agree_coppa_below'] = 'I accept the terms of the agreement and I am younger than %1$d years old.'; $txt['agree_coppa_above'] = 'I am at least %1$d years old.'; @@ -29,6 +32,7 @@ $txt['login_below'] = 'Please login below.'; $txt['login_below_or_register'] = 'Please login below or register an account with %2$s'; $txt['checkbox_agreement'] = 'I accept the registration agreement'; +$txt['checkbox_privacypol'] = 'I accept the privacy policy'; $txt['confirm_request_accept_agreement'] = 'Are you sure you want to force all the users to accept the agreement?'; $txt['login_hash_error'] = 'Password security has recently been upgraded.
Please enter your password again.'; diff --git a/themes/default/scripts/register.js b/themes/default/scripts/register.js index 814487d4aa..7283f9dc9f 100644 --- a/themes/default/scripts/register.js +++ b/themes/default/scripts/register.js @@ -428,7 +428,8 @@ function onCheckChange() .done(function(request) { if (request != '') { - $('#agreement_box').html(request); + $('#agreement_box').html(request.agreement); + $('#privacypol_box').html(request.privacypol); } }) .fail(function(request) { @@ -440,4 +441,4 @@ function onCheckChange() }) }); }); -})(jQuery); \ No newline at end of file +})(jQuery); From adb91e0b0b6f0a2e0ab662fe535dd4ae40cf8ad4 Mon Sep 17 00:00:00 2001 From: emanuele Date: Sun, 27 May 2018 22:29:44 +0200 Subject: [PATCH 08/13] Make sure a backup of the agreement and the privacy polixy is created at upgrade and install time, so that we have a baseline --- install/Install_Controller.php | 34 +++++++++++++++++++ install/upgrade_1-1.php | 20 +++++++++++ privacypolicy.txt | 0 .../admin/ManageRegistration.controller.php | 2 +- sources/subs/Agreement.class.php | 26 ++++++++++---- 5 files changed, 74 insertions(+), 8 deletions(-) create mode 100755 privacypolicy.txt diff --git a/install/Install_Controller.php b/install/Install_Controller.php index baf2868034..be97a43b9c 100644 --- a/install/Install_Controller.php +++ b/install/Install_Controller.php @@ -240,6 +240,7 @@ private function action_checkFilesWritable() 'smileys', 'themes', 'agreement.txt', + 'privacypolicy.txt', 'db_last_error.txt', 'Settings.php', 'Settings_bak.php' @@ -816,6 +817,39 @@ private function action_databasePopulation() array('variable') ); + // Better safe, than sorry, just in case the autoloader doesn't cope well with the upgrade + require_once(TMP_BOARDDIR . '/sources/subs/Agreement.class.php'); + require_once(TMP_BOARDDIR . '/sources/subs/PrivacyPolicy.class.php'); + + $agreement = new \Agreement('english'); + $success = $agreement->storeBackup(); + $db->insert('replace', + $db_prefix . 'settings', + array( + 'variable' => 'string-255', 'value' => 'string-65534', + ), + array( + 'agreementRevision', $success, + ), + array('variable') + ); + + if (file_exists(TMP_BOARDDIR . '/privacypolicy.txt')) + { + $privacypol = new \PrivacyPolicy('english'); + $success = $privacypol->storeBackup(); + $db->insert('replace', + $db_prefix . 'settings', + array( + 'variable' => 'string-255', 'value' => 'string-65534', + ), + array( + 'privacypolicyRevision', $success, + ), + array('variable') + ); + } + // Maybe we can auto-detect better cookie settings? preg_match('~^http[s]?://([^\.]+?)([^/]*?)(/.*)?$~', $boardurl, $matches); if (!empty($matches)) diff --git a/install/upgrade_1-1.php b/install/upgrade_1-1.php index 3d39787c36..652bd19f5b 100644 --- a/install/upgrade_1-1.php +++ b/install/upgrade_1-1.php @@ -905,6 +905,26 @@ public function agreement_logging() 'ignore' ); } + ), + array( + 'debug_title' => 'Preparing first status...', + 'function' => function() + { + // Better safe, than sorry, just in case the autoloader doesn't cope well with the upgrade + require_once(SUBSDIR . '/Agreement.class.php'); + require_once(SUBSDIR . '/PrivacyPolicy.class.php'); + + $agreement = new \Agreement('english'); + $success = $agreement->storeBackup(); + updateSettings(array('agreementRevision' => $success)); + + if (file_exists(BOARDDIR . '/privacypolicy.txt')) + { + $privacypol = new \PrivacyPolicy('english'); + $success = $privacypol->storeBackup(); + updateSettings(array('privacypolicyRevision' => $success)); + } + } ) ); } diff --git a/privacypolicy.txt b/privacypolicy.txt new file mode 100755 index 0000000000..e69de29bb2 diff --git a/sources/admin/ManageRegistration.controller.php b/sources/admin/ManageRegistration.controller.php index 3ce6c98902..451c365a8b 100644 --- a/sources/admin/ManageRegistration.controller.php +++ b/sources/admin/ManageRegistration.controller.php @@ -377,7 +377,7 @@ public function action_privacypol() } else { - updateSettings(array('agreementRevision' => $success)); + updateSettings(array('privacypolicyRevision' => $success)); } } diff --git a/sources/subs/Agreement.class.php b/sources/subs/Agreement.class.php index c2781dfdc4..ba9c00fcac 100644 --- a/sources/subs/Agreement.class.php +++ b/sources/subs/Agreement.class.php @@ -90,11 +90,7 @@ public function save($text, $update_backup = false) $backup_id = ''; if ($update_backup === true) { - $backup_id = $this->_backupId(); - if ($this->_createBackup($backup_id) === false) - { - $backup_id = false; - } + $backup_id = $this->storeBackup(); } // Off it goes to the agreement file. @@ -105,6 +101,22 @@ public function save($text, $update_backup = false) return $backup_id; } + /** + * Creates a backup of the current version of the agreement/s. + * + * @return string|bool the backup_id if successful, false if creating the backup fails + */ + public function storeBackup() + { + $backup_id = $this->_backupId(); + if ($this->_createBackup($backup_id) === false) + { + $backup_id = false; + } + + return $backup_id; + } + /** * Retrieves the plain text version of the agreement directly from * the file that contains it. @@ -242,9 +254,9 @@ protected function _createBackup($backup_id) $destination = $this->_backup_dir . '/' . $backup_id . '/'; if (file_exists($this->_backup_dir) === false) { - mkdir($this->_backup_dir); + @mkdir($this->_backup_dir); } - if (mkdir($destination) === false) + if (@mkdir($destination) === false) { return false; } From c15836ef89d74629643bbbc3ec94f0abed2a8f57 Mon Sep 17 00:00:00 2001 From: emanuele Date: Mon, 28 May 2018 22:04:46 +0200 Subject: [PATCH 09/13] And a master setting for forcing the accepting of agreement and privacy policy --- sources/Load.php | 31 ++++++++++++++----- .../admin/ManageRegistration.controller.php | 2 ++ .../languages/english/Admin.english.php | 4 +-- .../languages/english/Login.english.php | 2 ++ 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/sources/Load.php b/sources/Load.php index 028343c081..6583952580 100644 --- a/sources/Load.php +++ b/sources/Load.php @@ -433,16 +433,33 @@ function loadUserSettings() if ($user_info['is_guest'] === false) { $http_request = HttpReq::instance(); - if (!empty($modSettings['agreementRevision']) && !empty($modSettings['requireAgreement']) && in_array($http_request->action, array('reminder', 'register')) === false) + if (!empty($modSettings['force_accept_agreement'])) { - if ($http_request->action !== 'profile' || $http_request->area !== 'deleteaccount') + if (!empty($modSettings['agreementRevision']) && !empty($modSettings['requireAgreement']) && in_array($http_request->action, array('reminder', 'register')) === false) { - $agreement = new Agreement($user_info['language']); - if (false === $agreement->checkAccepted($id_member, $modSettings['agreementRevision'])) + if ($http_request->action !== 'profile' || $http_request->area !== 'deleteaccount') { - - setOldUrl('agreement_url_redirect'); - redirectexit('action=register;sa=agreement', true); + $agreement = new \Agreement($user_info['language']); + if (false === $agreement->checkAccepted($id_member, $modSettings['agreementRevision'])) + { + setOldUrl('agreement_url_redirect'); + redirectexit('action=register;sa=agreement', true); + } + } + } + } + if (!empty($modSettings['force_accept_privacy_policy'])) + { + if (!empty($modSettings['privacypolicyRevision']) && !empty($modSettings['requirePrivacypolicy']) && in_array($http_request->action, array('reminder', 'register')) === false) + { + if ($http_request->action !== 'profile' || $http_request->area !== 'deleteaccount') + { + $privacypol = new \PrivacyPolicy($user_info['language']); + if (false === $privacypol->checkAccepted($id_member, $modSettings['agreementRevision'])) + { + setOldUrl('agreement_url_redirect'); + redirectexit('action=register;sa=agreement', true); + } } } } diff --git a/sources/admin/ManageRegistration.controller.php b/sources/admin/ManageRegistration.controller.php index 451c365a8b..9a02acb27a 100644 --- a/sources/admin/ManageRegistration.controller.php +++ b/sources/admin/ManageRegistration.controller.php @@ -520,6 +520,8 @@ private function _settings() array('select', 'registration_method', array($txt['setting_registration_standard'], $txt['setting_registration_activate'], $txt['setting_registration_approval'], $txt['setting_registration_disabled'])), array('check', 'enableOpenID'), array('check', 'notify_new_registration'), + array('check', 'force_accept_agreement'), + array('check', 'force_accept_privacy_policy'), array('check', 'send_welcomeEmail'), array('check', 'show_DisplayNameOnRegistration'), '', diff --git a/themes/default/languages/english/Admin.english.php b/themes/default/languages/english/Admin.english.php index 30670399ae..894782ac9d 100644 --- a/themes/default/languages/english/Admin.english.php +++ b/themes/default/languages/english/Admin.english.php @@ -47,13 +47,13 @@ $txt['admin_credits'] = 'Credits'; $txt['admin_agreement'] = 'Show and require agreement letter when registering'; $txt['admin_checkbox_agreement'] = 'Show a checkbox for the agreement in registration form instead of a full page'; -$txt['admin_checkbox_accept_agreement'] = 'Force all members to accept the agreement at the next visit to the forum'; +$txt['admin_checkbox_accept_agreement'] = 'Force all members to accept this new version of the agreement at the next visit to the forum'; $txt['admin_agreement_default'] = 'Default'; $txt['admin_agreement_select_language'] = 'Language to edit'; $txt['admin_agreement_select_language_change'] = 'Change'; $txt['admin_privacypol'] = 'Require to accept the privacy policy when registering'; -$txt['admin_checkbox_accept_privacypol'] = 'Force all members to accept the privacy policy at the next visit to the forum'; +$txt['admin_checkbox_accept_privacypol'] = 'Force all members to accept this new version of the privacy policy at the next visit to the forum'; $txt['admin_delete_members'] = 'Delete Selected Members'; $txt['admin_change_primary_membergroup'] = 'Change primary member group'; diff --git a/themes/default/languages/english/Login.english.php b/themes/default/languages/english/Login.english.php index 5f01960fb6..84c84cd803 100644 --- a/themes/default/languages/english/Login.english.php +++ b/themes/default/languages/english/Login.english.php @@ -81,6 +81,8 @@ $txt['setting_registration_activate'] = 'Email Activation'; $txt['setting_registration_approval'] = 'Admin Approval'; $txt['setting_notify_new_registration'] = 'Notify administrators when a new member joins'; +$txt['setting_force_accept_agreement'] = 'Force members to accept the registration agreement when changed'; +$txt['force_accept_privacy_policy'] = 'Force members to accept the privacy policy when changed'; $txt['setting_send_welcomeEmail'] = 'Send welcome email to new members'; $txt['setting_show_DisplayNameOnRegistration'] = 'Allow users to enter their screen name'; From 924bde008d0b5622f2e22d124ae601006daec43c Mon Sep 17 00:00:00 2001 From: emanuele Date: Mon, 28 May 2018 23:08:38 +0200 Subject: [PATCH 10/13] Actually force them to accept the policy and some cleanup --- sources/Load.php | 16 ++++++++++++---- sources/admin/ManageRegistration.controller.php | 9 +++++---- sources/controllers/Register.controller.php | 8 ++++++++ sources/subs/Agreement.class.php | 2 +- themes/default/index.template.php | 13 ------------- .../default/languages/english/Admin.english.php | 6 +++--- .../default/languages/english/Errors.english.php | 2 +- .../default/languages/english/Login.english.php | 3 ++- .../default/languages/english/index.english.php | 3 ++- 9 files changed, 34 insertions(+), 28 deletions(-) diff --git a/sources/Load.php b/sources/Load.php index 6583952580..57d234abbb 100644 --- a/sources/Load.php +++ b/sources/Load.php @@ -455,10 +455,10 @@ function loadUserSettings() if ($http_request->action !== 'profile' || $http_request->area !== 'deleteaccount') { $privacypol = new \PrivacyPolicy($user_info['language']); - if (false === $privacypol->checkAccepted($id_member, $modSettings['agreementRevision'])) + if (false === $privacypol->checkAccepted($id_member, $modSettings['privacypolicyRevision'])) { setOldUrl('agreement_url_redirect'); - redirectexit('action=register;sa=agreement', true); + redirectexit('action=register;sa=privacypol', true); } } } @@ -1656,8 +1656,6 @@ function loadTheme($id_theme = 0, $initialize = true) if (!empty($_SESSION['agreement_accepted'])) { - // This loadLanguage is needed here because the check is done way too early in the process. - // As a stop-gap it's fine, but in future versions it should be fixed somehow. $_SESSION['agreement_accepted'] = null; $context['accepted_agreement'] = array( 'errors' => array( @@ -1666,6 +1664,16 @@ function loadTheme($id_theme = 0, $initialize = true) ); } + if (!empty($_SESSION['privacypolicy_accepted'])) + { + $_SESSION['privacypolicy_accepted'] = null; + $context['accepted_agreement'] = array( + 'errors' => array( + 'accepted_privacy_policy' => $txt['privacypolicy_accepted'] + ) + ); + } + theme()->loadThemeJavascript(); Hooks::instance()->newPath(array('$themedir' => $settings['theme_dir'])); diff --git a/sources/admin/ManageRegistration.controller.php b/sources/admin/ManageRegistration.controller.php index 9a02acb27a..c2e597198b 100644 --- a/sources/admin/ManageRegistration.controller.php +++ b/sources/admin/ManageRegistration.controller.php @@ -321,8 +321,8 @@ public function action_agreement() * Allows the administrator to edit the privacy policy, and choose whether * it should be shown or not. * - * - It writes and saves the privacy policy to the privacypol.txt file. - * - Accessed by ?action=admin;area=regcenter;sa=privacypol. + * - It writes and saves the privacy policy to the privacypolicy.txt file. + * - Accessed by ?action=admin;area=regcenter;sa=privacypol * - Requires the admin_forum permission. * * @uses Admin template and the edit_agreement sub template. @@ -332,7 +332,7 @@ public function action_privacypol() // I hereby agree not to be a lazy bum. global $txt, $context, $modSettings; - // By default we look at privacypol.txt. + // By default we look at privacypolicy.txt. $context['current_agreement'] = ''; // Is there more than one to edit? @@ -346,7 +346,7 @@ public function action_privacypol() // Try to figure out if we have more agreements. foreach ($languages as $lang) { - if (file_exists(BOARDDIR . '/privacypol.' . $lang['filename'] . '.txt')) + if (file_exists(BOARDDIR . '/privacypolicy.' . $lang['filename'] . '.txt')) { $context['editable_agreements'][$lang['filename']] = $lang['name']; @@ -395,6 +395,7 @@ public function action_privacypol() // These overrides are here to be able to reuse the template in a simple way without having to change much. $txt['admin_agreement'] = $txt['admin_privacypol']; $txt['admin_checkbox_accept_agreement'] = $txt['admin_checkbox_accept_privacypol']; + $txt['confirm_request_accept_agreement'] = $txt['confirm_request_accept_privacy_policy']; createToken('admin-rega'); } diff --git a/sources/controllers/Register.controller.php b/sources/controllers/Register.controller.php index 0fa1b99384..5567f31c53 100644 --- a/sources/controllers/Register.controller.php +++ b/sources/controllers/Register.controller.php @@ -1294,6 +1294,10 @@ public function action_agreement() { redirectexit($_SESSION['agreement_url_redirect']); } + else + { + redirectexit(); + } } elseif (isset($this->_req->post->no_accept)) { @@ -1340,6 +1344,10 @@ public function action_privacypol() { redirectexit($_SESSION['privacypolicy_url_redirect']); } + else + { + redirectexit(); + } } elseif (isset($this->_req->post->no_accept)) { diff --git a/sources/subs/Agreement.class.php b/sources/subs/Agreement.class.php index ba9c00fcac..005ee64edf 100644 --- a/sources/subs/Agreement.class.php +++ b/sources/subs/Agreement.class.php @@ -198,7 +198,7 @@ public function accept($id_member, $ip, $version) { $db = database(); - $db->insert('', + $db->insert('ignore', $this->_log_table_name, array( 'version' => 'string-20', diff --git a/themes/default/index.template.php b/themes/default/index.template.php index d736c5bb42..69f9eaa5ae 100644 --- a/themes/default/index.template.php +++ b/themes/default/index.template.php @@ -401,19 +401,6 @@ function template_uc_news_fader() } } -/** - * All your data are belong to us (cit.) - */ -function template_uc_agreement_accepted() -{ - global $txt; - - echo ' -
-

', $txt['agreement_accepted'], '

-
'; -} - /** * Section down the page, before closing body */ diff --git a/themes/default/languages/english/Admin.english.php b/themes/default/languages/english/Admin.english.php index 894782ac9d..fac1a55f90 100644 --- a/themes/default/languages/english/Admin.english.php +++ b/themes/default/languages/english/Admin.english.php @@ -52,7 +52,7 @@ $txt['admin_agreement_select_language'] = 'Language to edit'; $txt['admin_agreement_select_language_change'] = 'Change'; -$txt['admin_privacypol'] = 'Require to accept the privacy policy when registering'; +$txt['admin_privacypol'] = 'Show and require accepting the privacy policy when registering'; $txt['admin_checkbox_accept_privacypol'] = 'Force all members to accept this new version of the privacy policy at the next visit to the forum'; $txt['admin_delete_members'] = 'Delete Selected Members'; @@ -129,9 +129,9 @@ $txt['attachment_delete_admin'] = '[attachment deleted by admin]'; $txt['live'] = 'Latest Software Updates'; $txt['remove_all'] = 'Clear Log'; -$txt['agreement_not_writable'] = 'Warning - agreement.txt is not writable, any changes you make will NOT be saved.'; +$txt['agreement_not_writable'] = 'Warning - agreement.txt is not writable any changes you make will NOT be saved.'; $txt['agreement_backup_not_writable'] = 'Warning - the backup directory in forum_root/packages/backup cannot be created.'; -$txt['privacypol_not_writable'] = 'Warning - privacypol.txt is not writable, any changes you make will NOT be saved.'; +$txt['privacypol_not_writable'] = 'Warning - privacypolicy.txt is not writable any changes you make will NOT be saved.'; $txt['privacypol_backup_not_writable'] = 'Warning - the backup directory in forum_root/packages/backup cannot be created.'; $txt['version_check_desc'] = 'This shows you the versions of your installation\'s files versus those of the latest version. If any of these files are out of date, you should download and upgrade to the latest version at our ElkArte Site.'; diff --git a/themes/default/languages/english/Errors.english.php b/themes/default/languages/english/Errors.english.php index 118810e611..ad02cc5c2c 100644 --- a/themes/default/languages/english/Errors.english.php +++ b/themes/default/languages/english/Errors.english.php @@ -429,4 +429,4 @@ // Drag / Drop sort errors $txt['no_sortable_items'] = 'No sortable items were found'; -$txt['error_invalid_notification_id'] = 'An addons is trying to register a notification method with an existing ID. ID lower than 5 are protected and cannot be used by addons, if the ID is higher, than two addons may be sharing the same ID.'; +$txt['error_invalid_notification_id'] = 'An addon is trying to register a notification method with an existing ID. IDs lower than 5 are protected and cannot be used by addons. If the ID is higher, then two addons may be sharing the same ID.'; diff --git a/themes/default/languages/english/Login.english.php b/themes/default/languages/english/Login.english.php index 84c84cd803..0615f75401 100644 --- a/themes/default/languages/english/Login.english.php +++ b/themes/default/languages/english/Login.english.php @@ -33,7 +33,8 @@ $txt['login_below_or_register'] = 'Please login below or register an account with %2$s'; $txt['checkbox_agreement'] = 'I accept the registration agreement'; $txt['checkbox_privacypol'] = 'I accept the privacy policy'; -$txt['confirm_request_accept_agreement'] = 'Are you sure you want to force all the users to accept the agreement?'; +$txt['confirm_request_accept_agreement'] = 'Are you sure you want to force all members to accept the agreement?'; +$txt['confirm_request_accept_privacy_policy'] = 'Are you sure you want to force all members to accept the prvacy policy?'; $txt['login_hash_error'] = 'Password security has recently been upgraded.
Please enter your password again.'; diff --git a/themes/default/languages/english/index.english.php b/themes/default/languages/english/index.english.php index 799f178832..e85c804a2d 100644 --- a/themes/default/languages/english/index.english.php +++ b/themes/default/languages/english/index.english.php @@ -550,7 +550,8 @@ $txt['not_removed_extra'] = '%1$s is a backup of %2$s that was not generated by ElkArte. It can be accessed directly and used to gain unauthorised access to your forum. You should delete it immediately.'; $txt['generic_warning'] = 'Warning'; $txt['agreement_missing'] = 'You are requiring new users to accept a registration agreement, however the file (agreement.txt) doesn\'t exist.'; -$txt['agreement_accepted'] = 'You have successfully accepted the agreement.'; +$txt['agreement_accepted'] = 'You have just accepted the agreement.'; +$txt['privacypolicy_accepted'] = 'You have just accepted the forum privacy policy.'; $txt['new_version_updates'] = 'You have just updated!'; $txt['new_version_updates_text'] = 'Click here to see what\'s new in this version of ElkArte!!'; From 9cf8edc7b8f307184600a79e7e67f31acbccd7d0 Mon Sep 17 00:00:00 2001 From: emanuele Date: Mon, 28 May 2018 23:20:30 +0200 Subject: [PATCH 11/13] English fix as suggested by Frenzie --- themes/default/languages/english/Admin.english.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/themes/default/languages/english/Admin.english.php b/themes/default/languages/english/Admin.english.php index fac1a55f90..005d9ce5df 100644 --- a/themes/default/languages/english/Admin.english.php +++ b/themes/default/languages/english/Admin.english.php @@ -129,9 +129,9 @@ $txt['attachment_delete_admin'] = '[attachment deleted by admin]'; $txt['live'] = 'Latest Software Updates'; $txt['remove_all'] = 'Clear Log'; -$txt['agreement_not_writable'] = 'Warning - agreement.txt is not writable any changes you make will NOT be saved.'; +$txt['agreement_not_writable'] = 'Warning - agreement.txt is not writable. Any changes you make will NOT be saved.'; $txt['agreement_backup_not_writable'] = 'Warning - the backup directory in forum_root/packages/backup cannot be created.'; -$txt['privacypol_not_writable'] = 'Warning - privacypolicy.txt is not writable any changes you make will NOT be saved.'; +$txt['privacypol_not_writable'] = 'Warning - privacypolicy.txt is not writable. Any changes you make will NOT be saved.'; $txt['privacypol_backup_not_writable'] = 'Warning - the backup directory in forum_root/packages/backup cannot be created.'; $txt['version_check_desc'] = 'This shows you the versions of your installation\'s files versus those of the latest version. If any of these files are out of date, you should download and upgrade to the latest version at our ElkArte Site.'; From c7b759aaeb443c731ce744cb7f64e7b60e7862af Mon Sep 17 00:00:00 2001 From: emanuele Date: Tue, 29 May 2018 23:34:39 +0200 Subject: [PATCH 12/13] Typo --- themes/default/languages/english/Login.english.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/default/languages/english/Login.english.php b/themes/default/languages/english/Login.english.php index 0615f75401..3c307a29e8 100644 --- a/themes/default/languages/english/Login.english.php +++ b/themes/default/languages/english/Login.english.php @@ -34,7 +34,7 @@ $txt['checkbox_agreement'] = 'I accept the registration agreement'; $txt['checkbox_privacypol'] = 'I accept the privacy policy'; $txt['confirm_request_accept_agreement'] = 'Are you sure you want to force all members to accept the agreement?'; -$txt['confirm_request_accept_privacy_policy'] = 'Are you sure you want to force all members to accept the prvacy policy?'; +$txt['confirm_request_accept_privacy_policy'] = 'Are you sure you want to force all members to accept the privacy policy?'; $txt['login_hash_error'] = 'Password security has recently been upgraded.
Please enter your password again.'; From 71b6bb44c692d325ce5a400c94c0b20adb648271 Mon Sep 17 00:00:00 2001 From: emanuele Date: Tue, 29 May 2018 23:36:11 +0200 Subject: [PATCH 13/13] English suggested by Frenzie --- themes/default/languages/english/Login.english.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/default/languages/english/Login.english.php b/themes/default/languages/english/Login.english.php index 3c307a29e8..d6519a34e6 100644 --- a/themes/default/languages/english/Login.english.php +++ b/themes/default/languages/english/Login.english.php @@ -156,4 +156,4 @@ $txt['contact_your_message'] = 'Your message'; $txt['errors_contact_form'] = 'The following errors occurred while processing your contact request'; $txt['contact_subject'] = 'A guest has sent you a message'; -$txt['contact_thankyou'] = 'Thank you for your message, someone will contact you as soon as possible.'; +$txt['contact_thankyou'] = 'Thank you for your message. Someone will contact you as soon as possible.';