From 67db1c6e9bc474c34cb10029794b64be8c85a393 Mon Sep 17 00:00:00 2001 From: Hilmar R Date: Sat, 23 Jan 2021 15:24:24 +0100 Subject: melt diff prod fork 4.6.2 air onto 5.2.1 to 5.2.2 DB 1241 --- CHANGELOG | 3 + Zotlabs/Module/Admin.php | 9 +- Zotlabs/Module/Admin/Accounts.php | 127 +++++++- Zotlabs/Module/Admin/Site.php | 391 ++++++++++++++++++++++- Zotlabs/Module/Invite.php | 599 ++++++++++++++++++++++++++++++------ Zotlabs/Module/Regate.php | 347 +++++++++++++++++++++ Zotlabs/Module/Register.php | 498 ++++++++++++++++++++++-------- Zotlabs/Module/Settings/Account.php | 34 +- Zotlabs/Update/_1241.php | 15 + boot.php | 4 +- include/account.php | 559 +++++++++++++++++++++++++++++---- include/datetime.php | 32 ++ include/security.php | 103 +++++++ include/text.php | 12 + install/schema_mysql.sql | 39 ++- install/schema_postgres.sql | 42 ++- view/css/mod_admin.css | 4 + view/js/mod_register.js | 5 +- view/tpl/admin_accounts.tpl | 107 +++++-- view/tpl/admin_site.tpl | 93 +++--- view/tpl/invite.tpl | 130 +++++++- view/tpl/plain.tpl | 3 + view/tpl/regate.tpl | 23 ++ view/tpl/register.tpl | 35 ++- view/tpl/register_duty.tpl | 28 ++ 25 files changed, 2810 insertions(+), 432 deletions(-) create mode 100644 Zotlabs/Module/Regate.php create mode 100644 Zotlabs/Update/_1241.php create mode 100644 view/tpl/plain.tpl create mode 100644 view/tpl/regate.tpl create mode 100644 view/tpl/register_duty.tpl diff --git a/CHANGELOG b/CHANGELOG index 2c384ca97..5f3528a74 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +Hubzilla 5.2.2 /2021-01-23) + - Update and enhance Account, Invite, Register + Hubzilla 5.2.1 (2021-01-16) - Fix attach_upgrade() to catch all broken entries in attach - Fix collect_recipients() public policy filter for zot6 diff --git a/Zotlabs/Module/Admin.php b/Zotlabs/Module/Admin.php index 88b84b9d2..44c10b339 100644 --- a/Zotlabs/Module/Admin.php +++ b/Zotlabs/Module/Admin.php @@ -101,11 +101,14 @@ class Admin extends \Zotlabs\Web\Controller { // pending registrations - $pdg = q("SELECT account.*, register.hash from account left join register on account_id = register.uid where (account_flags & %d ) > 0 ", - intval(ACCOUNT_PENDING) + // $pdg = q("SELECT account.*, register.reg_hash from account left join register on account_id = register.reg_uid // where (account_flags & %d ) > 0 ", + // intval(ACCOUNT_PENDING) + // ); + $pdg = q("SELECT COUNT(*) AS pdg FROM register WHERE reg_vital = 1 AND reg_expires > '%s' ", + dbesc(date('Y-m-d H:i:s')) ); - $pending = (($pdg) ? count($pdg) : 0); + $pending = ($pdg ? $pdg[0]['pdg'] : 0); // available channels, primary and clones $channels = array(); diff --git a/Zotlabs/Module/Admin/Accounts.php b/Zotlabs/Module/Admin/Accounts.php index 0c7e089be..74e562a9c 100644 --- a/Zotlabs/Module/Admin/Accounts.php +++ b/Zotlabs/Module/Admin/Accounts.php @@ -15,6 +15,9 @@ class Accounts { * */ + const MYP = 'ZAR'; // ZAR2x + const VERSION = '2.0.0'; + function post() { $pending = ( x($_POST, 'pending') ? $_POST['pending'] : array() ); @@ -23,6 +26,68 @@ class Accounts { check_form_security_token_redirectOnErr('/admin/accounts', 'admin_accounts'); + $isajax = is_ajax(); + $rc = 0; + + If (!is_site_admin()) { + if ($isajax) { + killme(); + exit; + } + goaway(z_root() . '/'); + } + + if ($isajax) { + //$debug = print_r($_SESSION[self::MYP],true); + $zarop = (x($_POST['zardo']) && preg_match('/^[ad]{1,1}$/', $_POST['zardo']) ) + ? $_POST['zardo'] : ''; + // zarat arrives with leading underscore _n + $zarat = (x($_POST['zarat']) && preg_match('/^_{1,1}[0-9]{1,6}$/', $_POST['zarat']) ) + ? substr($_POST['zarat'],1) : ''; + $zarse = (x($_POST['zarse']) && preg_match('/^[0-9a-f]{8,8}$/', $_POST['zarse']) ) + ? hex2bin($_POST['zarse']) : ''; + + if ($zarop && $zarat >= 0 && $zarse && $zarse == $_SESSION[self::MYP]['h'][$zarat]) { + + // + if ($zarop == 'd') { + $rd = q("UPDATE register SET reg_vital = 0 WHERE reg_id = %d AND SUBSTR(reg_hash,1,4) = '%s' ", + intval($_SESSION[self::MYP]['i'][$zarat]), + dbesc($_SESSION[self::MYP]['h'][$zarat]) + ); + $rc = '× ' . count($rd); + } + elseif ($zarop == 'a') { + // approval, REGISTER_DENIED by user 0x0040, REGISTER_AGREED by user 0x0020 @Regate + $rd = q("UPDATE register SET reg_flags = (reg_flags & ~ 16), " + . " reg_vital = (CASE (reg_flags & ~ 48) WHEN 0 THEN 0 ELSE 1 END) " + . " WHERE reg_vital = 1 AND reg_id = %d AND SUBSTR(reg_hash,1,4) = '%s' ", + intval($_SESSION[self::MYP]['i'][$zarat]), + dbesc($_SESSION[self::MYP]['h'][$zarat]) + ); + $rc = 0; + $rs = q("SELECT * from register WHERE reg_id = %d ", + intval($_SESSION[self::MYP]['i'][$zarat]) + ); + if ($rs && ($rs[0]['reg_flags'] & ~ 48) == 0) { + + // create account + $rc='ok'.$rs[0]['reg_id']; + $ac = create_account_from_register($rs[0]); + if ( $ac['success'] ) $rc .= '✔'; + + } else { + $rc='oh×'; + } + } + + // + echo json_encode(array('re' => $zarop, 'at' => '_' . $zarat, 'rc' => $rc)); + } + killme(); + exit; + } + // change to switch structure? // account block/unblock button was submitted if (x($_POST, 'page_accounts_block')) { @@ -82,6 +147,8 @@ class Accounts { } check_form_security_token_redirectOnErr('/admin/accounts', 'admin_accounts', 't'); + + $debug = ''; switch (argv(2)){ case 'delete': @@ -112,9 +179,44 @@ class Accounts { } /* get pending */ - $pending = q("SELECT account.*, register.hash from account left join register on account_id = register.uid where (account_flags & %d )>0 ", + // [hilmar -> + /* + $pending = q("SELECT account.*, reg_hash FROM account LEFT JOIN register ON account_id = reg_uid WHERE reg_vital = 1 AND (account_flags & %d) > 0", + intval(ACCOUNT_PENDING) + ); + */ + $tao = 'tao.zar.zarax = ' . "'" . '' . "';\n"; + + $pending = q("SELECT @i:=@i+1 AS reg_n, @i MOD 2 AS reg_z, " + ." reg_did2, reg_created, reg_startup, reg_expires, reg_email, reg_atip, reg_hash, reg_id, " + ." CASE (reg_flags & %d) WHEN 0 THEN '✔ verified' WHEN 1 THEN '× not yet' END AS reg_vfd " + ." FROM register, (SELECT @i:=0) AS i " + ." WHERE reg_vital = 1 AND (reg_flags & %d) > 0 ", + intval(ACCOUNT_UNVERIFIED), intval(ACCOUNT_PENDING) ); + + unset($_SESSION[self::MYP]); + if ($pending) { + // collect and group all ip + $atips = q("SELECT reg_atip AS atip, COUNT(reg_atip) AS atips FROM register " + ." WHERE reg_vital = 1 GROUP BY reg_atip "); + $atips ? $atipn = array_column($atips, 'atips', 'atip') : $atipn = array('' => 0); + + $tao .= 'tao.zar.zarar = {'; + foreach ($pending as $n => $v) { + if (array_key_exists($v['reg_atip'], $atipn)) { + + $pending[$n]['reg_atip'] = $v['reg_atip'] . ' ◄' . $atipn[ $v['reg_atip'] ] . '×'; + } + // better secure + $tao .= $n . ": '" . substr(bin2hex($v['reg_hash']),0,8) . "',"; + $_SESSION[self::MYP]['h'][] = substr($v['reg_hash'],0,4); + $_SESSION[self::MYP]['i'][] = $v['reg_id']; + } + $tao = rtrim($tao,',') . '};' . "\n"; + } + // <- hilmar] /* get accounts */ @@ -143,7 +245,7 @@ class Accounts { intval(\App::$pager['itemspage']), intval(\App::$pager['start']) ); - + // function _setup_users($e){ // $accounts = Array( // t('Normal Account'), @@ -163,12 +265,16 @@ class Accounts { $t = get_markup_template('admin_accounts.tpl'); $o = replace_macros($t, array( // strings // + '$debug' => $debug, '$title' => t('Administration'), '$page' => t('Accounts'), '$submit' => t('Submit'), '$select_all' => t('select all'), + '$sel_tall' => t('SelectToggle'), + '$sel_deny' => t('× DenySelected'), + '$sel_aprv' => t('✔ ApproveSelected'), '$h_pending' => t('Registrations waiting for confirm'), - '$th_pending' => array( t('Request date'), t('Email') ), + '$th_pending' => array( t('Request date'), t('Startup,Expires'), 'dId2', t('specified,atip') ), '$no_pending' => t('No registrations.'), '$approve' => t('Approve'), '$deny' => t('Deny'), @@ -187,21 +293,22 @@ class Accounts { [ t('Expires'), 'account_expires' ], [ t('Service Class'), 'account_service_class'] ), - '$confirm_delete_multi' => t('Selected accounts will be deleted!\n\nEverything these accounts had posted on this site will be permanently deleted!\n\nAre you sure?'), - '$confirm_delete' => t('The account {0} will be deleted!\n\nEverything this account has posted on this site will be permanently deleted!\n\nAre you sure?'), + '$confirm_delete_multi' => p2j(t('Selected accounts will be deleted!\n\nEverything these accounts had posted on this site will be permanently deleted!\n\nAre you sure?')), + '$confirm_delete' => p2j(t('The account {0} will be deleted!\n\nEverything this account has posted on this site will be permanently deleted!\n\nAre you sure?')), '$form_security_token' => get_form_security_token("admin_accounts"), // values // - '$baseurl' => z_root(), - - '$pending' => $pending, - '$users' => $users, + '$now' => date('Y-m-d H:i:s'), + '$baseurl' => z_root(), + '$tao' => $tao, + '$pending' => $pending, + '$users' => $users, )); $o .= paginate($a); return $o; } - } + diff --git a/Zotlabs/Module/Admin/Site.php b/Zotlabs/Module/Admin/Site.php index 011bf3ce4..cb919746b 100644 --- a/Zotlabs/Module/Admin/Site.php +++ b/Zotlabs/Module/Admin/Site.php @@ -10,9 +10,19 @@ class Site { * */ function post(){ + // [hilmar-> + $this->isajax = is_ajax(); + $this->eol = $this->isajax ? "\n" : EOL; + // ] if (!x($_POST, 'page_site')) { - return; + // [ + if (!$this->isajax) + // ] + return; } + // [ + $this->msgbg = ''; + // <-hilmar] check_form_security_token_redirectOnErr('/admin/site', 'admin_site'); @@ -24,14 +34,16 @@ class Site { $siteinfo = ((x($_POST,'siteinfo')) ? trim($_POST['siteinfo']) : ''); $language = ((x($_POST,'language')) ? notags(trim($_POST['language'])) : ''); $theme = ((x($_POST,'theme')) ? notags(trim($_POST['theme'])) : ''); -// $theme_mobile = ((x($_POST,'theme_mobile')) ? notags(trim($_POST['theme_mobile'])) : ''); -// $site_channel = ((x($_POST,'site_channel')) ? notags(trim($_POST['site_channel'])) : ''); + // $theme_mobile = ((x($_POST,'theme_mobile')) ? notags(trim($_POST['theme_mobile'])) : ''); + // $site_channel = ((x($_POST,'site_channel')) ? notags(trim($_POST['site_channel'])) : ''); $maximagesize = ((x($_POST,'maximagesize')) ? intval(trim($_POST['maximagesize'])) : 0); $register_policy = ((x($_POST,'register_policy')) ? intval(trim($_POST['register_policy'])) : 0); $minimum_age = ((x($_POST,'minimum_age')) ? intval(trim($_POST['minimum_age'])) : 13); $access_policy = ((x($_POST,'access_policy')) ? intval(trim($_POST['access_policy'])) : 0); + $reg_autochannel = ((x($_POST,'auto_channel_create')) ? True : False); $invite_only = ((x($_POST,'invite_only')) ? True : False); + $invite_also = ((x($_POST,'invite_also')) ? True : False); $abandon_days = ((x($_POST,'abandon_days')) ? intval(trim($_POST['abandon_days'])) : 0); $register_text = ((x($_POST,'register_text')) ? notags(trim($_POST['register_text'])) : ''); @@ -75,6 +87,16 @@ class Site { $maxloadavg = ((x($_POST,'maxloadavg')) ? intval(trim($_POST['maxloadavg'])) : 50); $feed_contacts = ((x($_POST,'feed_contacts')) ? intval($_POST['feed_contacts']) : 0); $verify_email = ((x($_POST,'verify_email')) ? 1 : 0); + $register_perday = ((x($_POST,'register_perday')) ? intval(trim($_POST['register_perday'])) : 50); + $register_sameip = ((x($_POST,'register_sameip')) ? intval(trim($_POST['register_sameip'])) : 3); + + $regdelayn = ((x($_POST,'zardelayn')) ? intval(trim($_POST['zardelayn'])) : 0); + $regdelayu = ((x($_POST,'zardelay')) ? notags(trim($_POST['zardelay'])) : ''); + $reg_delay = (preg_match('/^[a-z]{1,1}$/', $regdelayu) ? $regdelayn . $regdelayu : ''); + $regexpiren = ((x($_POST,'zarexpiren')) ? intval(trim($_POST['zarexpiren'])) : 0); + $regexpireu = ((x($_POST,'zarexpire')) ? notags(trim($_POST['zarexpire'])) : ''); + $reg_expire = (preg_match('/^[a-z]{1,1}$/', $regexpireu) ? $regexpiren . $regexpireu : ''); + $imagick_path = ((x($_POST,'imagick_path')) ? trim($_POST['imagick_path']) : ''); $force_queue = ((intval($_POST['force_queue']) > 0) ? intval($_POST['force_queue']) : 3000); $pub_incl = escape_tags(trim($_POST['pub_incl'])); @@ -82,6 +104,35 @@ class Site { $permissions_role = escape_tags(trim($_POST['permissions_role'])); + // [hilmar-> + $this->register_duty = ((x($_POST,'register_duty')) ? notags(trim($_POST['register_duty'])) : ''); + if (! preg_match('/^[0-9 .,:\-]{0,191}$/', $this->register_duty)) { + $this->msgbg .= 'ZAR0131E,' . t('Invalid input') . $this->eol; + $this->error++; + } else { + + $this->duty(); + + if ($this->isajax) { + echo json_encode(array('msgbg' => $this->msgbg, 'me' => 'zar')); + // that mission is complete + killme(); + exit; + + } else { + + //logger( print_r( $this->msgbg, true) ); + //logger( print_r( $this->joo, true) ); + if ($this->error === 0) { + set_config('system', 'register_duty', $this->register_duty); + set_config('system', 'register_duty_jso', $this->joo); + } else { + notice('ZAR0130E,'.t('Errors') . ': ' . $this->error) . EOL . $this->msgfg; + } + } + } + // <-hilmar] + set_config('system', 'feed_contacts', $feed_contacts); set_config('system', 'delivery_interval', $delivery_interval); set_config('system', 'delivery_batch_count', $delivery_batch_count); @@ -96,6 +147,10 @@ class Site { set_config('system', 'login_on_homepage', $login_on_homepage); set_config('system', 'enable_context_help', $enable_context_help); set_config('system', 'verify_email', $verify_email); + set_config('system', 'max_daily_registrations', $register_perday); + set_config('system', 'register_sameip', $register_sameip); + set_config('system', 'register_delay', $reg_delay); + set_config('system', 'register_expire', $reg_expire); set_config('system', 'default_expire_days', $default_expire_days); set_config('system', 'active_expire_days', $active_expire_days); set_config('system', 'reply_address', $reply_address); @@ -126,17 +181,19 @@ class Site { set_config('system','siteinfo',$siteinfo); set_config('system', 'language', $language); set_config('system', 'theme', $theme); -// if ( $theme_mobile === '---' ) { -// del_config('system', 'mobile_theme'); -// } else { -// set_config('system', 'mobile_theme', $theme_mobile); -// } - // set_config('system','site_channel', $site_channel); + // if ( $theme_mobile === '---' ) { + // del_config('system', 'mobile_theme'); + // } else { + // set_config('system', 'mobile_theme', $theme_mobile); + // } + // set_config('system','site_channel', $site_channel); set_config('system','maximagesize', $maximagesize); set_config('system','register_policy', $register_policy); set_config('system','minimum_age', $minimum_age); + set_config('system','auto_channel_create', $reg_autochannel); set_config('system','invitation_only', $invite_only); + set_config('system','invitation_also', $invite_also); set_config('system','access_policy', $access_policy); set_config('system','account_abandon_days', $abandon_days); set_config('system','register_text', $register_text); @@ -260,6 +317,9 @@ class Site { REGISTER_APPROVE => t("Yes - with approval"), REGISTER_OPEN => t("Yes") ); + $this->register_duty ? get_config('system', 'register_duty') : ''; + $register_perday = get_config('system','max_daily_registrations'); + $register_perday ? '' : $register_perday = 50; /* Acess policy */ $access_choices = Array( @@ -286,9 +346,66 @@ class Site { $homelogin = get_config('system','login_on_homepage'); $enable_context_help = get_config('system','enable_context_help'); + // for reuse reg_delay and reg_expire + $reg_rabots = array( + 'i' => t('Minute(s)'), + 'h' => t('Hour(s)') , + 'd' => t('Day(s)') , + 'w' => t('Week(s)') , + 'm' => t('Month(s)') , + 'y' => t('Year(s)') + ); + $regdelay = get_config('system','register_delay'); + if ($regdelay) + list($regdelay_n, $regdelay_u) = array(substr($regdelay,0,-1),substr($regdelay,-1)); + $reg_delay = replace_macros(get_markup_template('field_duration.qmc.tpl'), + array( + 'label' => t('Account registration delay'), + 'qmc' => 'zar', + 'qmcid' => 'ZAR0860C', + 'help' => t('How long a registration request has to wait before validation can perform.' + . ' No delay if zero or no value.'), + 'field' => array( + 'name' => 'delay', + 'title' => t('duration up from now'), + 'value' => ($regdelay_n ? $regdelay_n : 90), + 'min' => '0', + 'max' => '99', + 'size' => '2', + 'default' => ($regdelay_u ? $regdelay_u : 'i') + ), + 'rabot' => $reg_rabots + ) + ); + $regexpire = get_config('system','register_expire'); + if ($regexpire) + list($regexpire_n, $regexpire_u) = array(substr($regexpire,0,-1),substr($regexpire,-1)); + $reg_expire = replace_macros(get_markup_template('field_duration.qmc.tpl'), + array( + 'label' => t('Account registration expiration'), + 'qmc' => 'zar', + 'qmcid' => 'ZAR0862C', + 'help' => t('How long a registration to confirm remains valid.' + . ' Not expire if zero or no value.'), + 'field' => array( + 'name' => 'expire', + 'title' => t('duration up from now'), + 'value' => ($regexpire_n ? $regexpire_n : 2), + 'min' => '0', + 'max' => '99', + 'size' => '2', + 'default' => ($regexpire_u ? $regexpire_u : 'i') + ), + 'rabot' => $reg_rabots + ) + ); + + $tao = ''; $t = get_markup_template("admin_site.tpl"); return replace_macros($t, array( '$title' => t('Administration'), + // interfacing js vars + '$tao' => $tao, '$page' => t('Site'), '$submit' => t('Submit'), '$registration' => t('Registration'), @@ -305,21 +422,88 @@ class Site { '$siteinfo' => array('siteinfo', t('Site Information'), get_config('system','siteinfo'), t("Publicly visible description of this site. Displayed on siteinfo page. BBCode can be used here")), '$language' => array('language', t("System language"), get_config('system','language'), "", $lang_choices), '$theme' => array('theme', t("System theme"), get_config('system','theme'), t("Default system theme - may be over-ridden by user profiles - change theme settings"), $theme_choices), -// '$theme_mobile' => array('theme_mobile', t("Mobile system theme"), get_config('system','mobile_theme'), t("Theme for mobile devices"), $theme_choices_mobile), -// '$site_channel' => array('site_channel', t("Channel to use for this website's static pages"), get_config('system','site_channel'), t("Site Channel")), + // '$theme_mobile' => array('theme_mobile', t("Mobile system theme"), get_config('system','mobile_theme'), t("Theme for mobile devices"), $theme_choices_mobile), + // '$site_channel' => array('site_channel', t("Channel to use for this website's static pages"), get_config('system','site_channel'), t("Site Channel")), '$feed_contacts' => array('feed_contacts', t('Allow Feeds as Connections'),get_config('system','feed_contacts'),t('(Heavy system resource usage)')), '$maximagesize' => array('maximagesize', t("Maximum image size"), intval(get_config('system','maximagesize')), t("Maximum size in bytes of uploaded images. Default is 0, which means no limits.")), - '$register_policy' => array('register_policy', t("Does this site allow new member registration?"), get_config('system','register_policy'), "", $register_choices), - '$invite_only' => array('invite_only', t("Invitation only"), get_config('system','invitation_only'), t("Only allow new member registrations with an invitation code. Above register policy must be set to Yes.")), '$minimum_age' => array('minimum_age', t("Minimum age"), (x(get_config('system','minimum_age'))?get_config('system','minimum_age'):13), t("Minimum age (in years) for who may register on this site.")), '$access_policy' => array('access_policy', t("Which best describes the types of account offered by this hub?"), get_config('system','access_policy'), t("This is displayed on the public server site list."), $access_choices), - '$register_text' => array('register_text', t("Register text"), htmlspecialchars(get_config('system','register_text'), ENT_QUOTES, 'UTF-8'), t("Will be displayed prominently on the registration page.")), + + // Register + // [hilmar-> + '$register_text' => array('register_text', + t("Register text"), + htmlspecialchars(get_config('system','register_text'), ENT_QUOTES, 'UTF-8'), + t("Will be displayed prominently on the registration page.") . ' ' + . t('If you operate with register duties (see ZAR0830C), best practise is to tell cleartext about here'), + 'ZAR0810C'), + '$register_policy' => array('register_policy', + t("Does this site allow new member registration?"), + get_config('system','register_policy'), + "", + $register_choices, + 'ZAR0820C'), + '$register_duty' => array('register_duty', + t('Registration office on duty'), + $this->register_duty = get_config('system', 'register_duty'), + t('The weekdays and hours the register office is open for registrations') . '. ' + . t('Split weekdays and hours per `:`') . '. ' + . t('Separate weekday(s):hour(s) pairs with blank(s)') . '. ' + . t('Several values or ranges are to split by comma') . '. ' + . t('From-To ranges are joined with `-`') . '. ' + . t('ie') . ' `1-5:0900-1200,1300-1700 6:900-1230` ' . t('or') .' `1-2,4-5:800-1800` ' + . ' ' . t('Parse and test your input') . ''. EOL + . t('If left empty, defaults to 24h closed everyday the week.') . ' ' + . t('To keep open 24h everyday the week, short is `-:-`.') . ' ' + . t('Note, ranges are specified as open-close pairs and in case of') + . ' 0900-1200 ' + . t('results to: opens 9h and closes 12h. If meant open 9h to 12h exactly, say `0900-1201`'), + 'ZAR0830C'), + '$register_perday' => array('register_perday', + t('Account registrations max per day'), + (x(get_config('system', 'max_daily_registrations'))) + ? get_config('system', 'max_daily_registrations') : 50, + t('How many registration requests the site accepts during one day. Unlimited if zero or no value. Default 50'), + 'ZAR0840C'), + '$register_sameip' => array('register_sameip', + t('Account registrations from same ip'), + (x(get_config('system', 'register_sameip'))) + ? get_config('system', 'register_sameip') : 3, + t('How many pending registration requests the site accepts from a same ip address.'), + 'ZAR0850C'), + '$reg_delay'=>$reg_delay, + '$reg_expire'=>$reg_expire, + '$reg_autochannel' => array('auto_channel_create', + t("Auto channel create"), + get_config('system','auto_channel_create'), + t("Auto create a channel when register a new account. When On, the register form will show additional fields for the channel-name and the nickname."), + "", "", 'ZAR0870C'), + '$invite_only' => array('invite_only', + t("Invitation only"), + get_config('system','invitation_only'), + t("Only allow new member registrations with an invitation code. Above register policy must be set to Yes."), + "", "", 'ZAR0880C'), + '$invite_also' => array('invite_also', + t("Invitation also"), + get_config('system','invitation_also'), + t("Also allow new member registrations with an invitation code. Above register policy must be set to Yes."), + "", "", 'ZAR0881C'), + '$verify_email' => array('verify_email', + t("Verify Email Addresses"), + get_config('system','verify_email'), + t("Check to verify email addresses used in account registration (recommended)."), + "", "", 'ZAR0890C'), + '$abandon_days' => array('abandon_days', + t('Accounts abandoned after x days'), + get_config('system','account_abandon_days'), + t('Will not waste system resources polling external sites for abandonded accounts. Enter 0 for no time limit.'), + 'appears not to be implemented (2010.01)'), + // <-hilmar] + '$role' => $role, '$frontpage' => array('frontpage', t("Site homepage to show visitors (default: login box)"), get_config('system','frontpage'), t("example: 'pubstream' to show public stream, 'page/sys/home' to show a system webpage called 'home' or 'include:home.html' to include a file.")), '$mirror_frontpage' => array('mirror_frontpage', t("Preserve site homepage URL"), get_config('system','mirror_frontpage'), t('Present the site homepage in a frame at the original location instead of redirecting')), - '$abandon_days' => array('abandon_days', t('Accounts abandoned after x days'), get_config('system','account_abandon_days'), t('Will not waste system resources polling external sites for abandonded accounts. Enter 0 for no time limit.')), '$allowed_sites' => array('allowed_sites', t("Allowed friend domains"), get_config('system','allowed_sites'), t("Comma separated list of domains which are allowed to establish friendships with this site. Wildcards are accepted. Empty to allow any domains")), - '$verify_email' => array('verify_email', t("Verify Email Addresses"), get_config('system','verify_email'), t("Check to verify email addresses used in account registration (recommended).")), '$force_publish' => array('publish_all', t("Force publish"), get_config('system','publish_all'), t("Check to force all profiles on this site to be listed in the site directory.")), '$disable_discover_tab' => array('disable_discover_tab', t('Import Public Streams'), $discover_tab, t('Import and allow access to public content pulled from other sites. Warning: this content is unmoderated.')), '$site_firehose' => array('site_firehose', t('Site only Public Streams'), get_config('system','site_firehose'), t('Allow access to public content originating only from this site if Imported Public Streams are disabled.')), @@ -361,4 +545,179 @@ class Site { )); } + /** + * @brief Admin page site common post submit and ajax interaction + * @author hilmar runge + * @since 2020-02-04 + * Configure register office duty weekdays and hours + * Syntax: weekdays:hours [weekdays:hours] + * [.d[,d-d.]]]:hhmm-hhmm[,hhmm-hhmm...] + * ranges are between blanks, days are 1-7, where 1 = Monday + * hours are [h]hmm 3-4digit 24 clock values + * ie 0900-1200,1300-1800 for hours + * ie 1-2,4,5 for weekdays + * ie 1-2:900-1800 monday and tuesday open from 9 to 18h + * + * @var $register_duty is the input field from the admin -> site page + * @return the results are in the class vars $error, $msgbg and $jsoo + * $jsoo is + */ + + // 3-4 digit 24h clock regex + const regxTime34 = '/^(?:2[0-3]|[01][0-9]|[0-9])[0-5][0-9]$/'; + var $wdconst = array('','mo','tu','we','th','fr','sa','so'); + + // in + var $register_duty; + // intermediate + var $isajax; + // return + var $jsoo; + var $msgbg; + var $error = 0; + var $msgfg = ''; + + private function duty() { + + $aro=array_fill(1, 7, 0); + + if ($this->isajax) { + $op = (preg_match('/[a-z]{2,4}/', $_REQUEST['zarop'])) ? $_REQUEST['zarop'] : ''; + if ($op == 'zar083') { + $this->msgbg = 'ZAR0130I Testmode:' . $this->eol . $this->msgbg; + } else { + killme(); + exit; + } + } + + if (! $this->register_duty) + $this->register_duty = '1-7'; + $ranges = preg_split('/\s+/', $this->register_duty); + $this->msgbg .= '..ranges: ' . print_r(count($ranges),true) . $this->eol; + + foreach ($ranges as $rn => $range) { + list($ws,$hs,) = explode(':', $range); + + $ws ? $arw = explode( ',', $ws) : $arw=array(); + $this->msgbg .= ($rn+1).'.weekday ranges: ' . count($arw) . $this->eol; + // $this->msgbg .= print_r($arw,true); + $hs ? $arh = explode( ',', $hs) : $arh=array(); + $this->msgbg .= ($rn+1).'.hour ranges: ' . count($arh) . $this->eol; + + $this->msgbg .= ($rn+1).'.wdays: ' . ( $ws ? print_r($ws,true) : 'none') . ' : ' + . ' hours: ' . print_r($hs,true) . $this->eol; + + // several hs may belog to one wd + // aro[0] is tmp store + foreach ($arh as $hs) { + list($ho,$hc,) = explode( '-', $hs ); + + // no value forces open very early, and be sure having valid hhmm values + !$ho ? $ho = "0000" : ''; + !$hc ? $hc = "0000" : ''; // pseudo + if (preg_match(self::regxTime34, $ho) + && preg_match(self::regxTime34, $hc)) { + + // fix pseudo, allow no reverse range + $hc == "0000" || $ho > $hc ? $hc = "2400" : ''; + + $aro[0][$ho] = 0; + $aro[0][$hc] = 1; + + $this->msgbg .= ($ho ? ' .open:' . $ho : '') . ($hc ? ' close:' . $hc : '') .$this->eol; + } else { + $this->msgbg .= ' .' . t('Invalid 24h time value (hhmm/hmm)') . $this->eol; + $this->msgfg .= ' .ZAR0132E,' . t('Invalid 24h time value (hhmm/hmm)') . $this->eol; + $this->error++; + } + } + + // the weekday(s) values or ranges + foreach ($arw as $ds) { + $wd=explode('-', $ds); + array_key_exists("1", $wd) && $wd[1]=="" ? $wd[1] = "7" : ''; // a case 3- + array_key_exists("1", $wd) && $wd[0]=="" ? $wd[0] = "1" : ''; // a case -3 + !array_key_exists("1", $wd) ? $wd[1] = $wd[0] : ''; // a case 3 + if ($wd[0] > $wd[1]) continue; // reverse order will be ignored // a case 5-3 + if (preg_match('/^[1-7]{1}$/', $wd[0])) { + if (preg_match('/^[1-7]{1}$/', $wd[1])) { + // $this->msgbg .= print_r($wd,true); + for ($i=$wd[0]; $i<=$wd[1]; $i++) { + // take the tmp store for the selected day(s) + $aro[$i]=$aro[0]; + } + } + } + } + //$this->msgbg .= 'aro0: ' . print_r($aro,true) . $this->eol; // 4devels + // clear the tmp store + $aro[0]=array(); + } + // discart the tmp store + unset($aro[0]); + // not configured days close at the beginning 0000h + for ($i=1;$i<=7;$i++) { is_array($aro[$i]) ? '' : $aro[$i] = array("0000" => 1); } + // $this->msgbg .= 'aro: ' . print_r($aro,true) . $this->eol; // 4devels + + if ($this->isajax) { + // tell what we have + // $this->msgbg .= 'aro0: ' . print_r($aro,true) . $this->eol; // 4devels + $this->msgbg .= 'Duty time table:' . $this->eol; + foreach ($aro as $dow => $hrs) { + $this->msgbg .= ' ' . $this->wdconst[$dow] . ' '; + // $this->msgbg .= '**' . print_r($hrs,true); + foreach ($hrs as $h => $o) { + $this->msgbg .= ((!$o) ? $h . ':open' : $h . ':close') . ', '; + } + $this->msgbg = rtrim($this->msgbg, ', ') . $this->eol; + } + + $this->msgbg .= 'Generating 6 random times to check duty hours: ' . $this->eol; + // we only need some random dates from anyway in past or future + // because only the weekday and the clock is to test + for ($i=0; $i<6; $i++) { + $adow = rand(1, 7); // 1 to 7 (days) + $cdow = $this->wdconst[$adow]; + // below is the essential algo to verify a date (of format Hi) meets an open or closed condition + $t = date('Hi', ( rand(time(), 60*60*24+time()) ) ); + $how='closed'; + foreach ($aro[$adow] as $o => $v) { + // $this->msgbg .= 'debug: ' . $o . ' gt ' . $t . ' / ' . $v . $this->eol; // 4devels + if ($o > $t) { + $how = ($v ? 'open' : 'closed'); + break; + } + } + // now we know + $this->msgbg .= ' ' . $cdow . '.' . $t . '=' . $how . ', '; + } + $this->msgbg = rtrim($this->msgbg, ', ') . $this->eol; + } + + /* + //$jov1 = array( 'view1' => $aro, 'view2' => ''); + $jov2=array(); + foreach ($aro as $d => $ts) { + foreach ($ts as $t => $ft) { + $jov2['view2'][$ft][] = $d.$t; + //$ft=="1" && $t=="0000" ? $jov2['view2']["0"][] = $d."2400" : ''; + } + } + $this->msgbg .= print_r($jov2, true) . $this->eol; // 4devels + */ + + $this->joo = json_encode($aro); + // $this->msgbg .= $this->joo . $this->eol; // 4devels + // $this->msgbg .= print_r($aro, true) . $this->eol; // 4devels + $okko = (json_decode($this->joo, true) ? true : false); + if (!$okko) { + $this->msgbg .= 'ZAR0139D,json 4 duty KO crash' . $this->eol; + $this->msgfg .= 'ZAR0139D,json 4 duty KO crash' . $this->eol; + $this->error++; + } + return ; + } + + } diff --git a/Zotlabs/Module/Invite.php b/Zotlabs/Module/Invite.php index 6359da54c..24792c8c1 100644 --- a/Zotlabs/Module/Invite.php +++ b/Zotlabs/Module/Invite.php @@ -6,7 +6,7 @@ use Zotlabs\Lib\Apps; use Zotlabs\Web\Controller; /** - * module: invite.php + * module: invitexv2.php * * send email invitations to join social network * @@ -15,91 +15,291 @@ use Zotlabs\Web\Controller; class Invite extends Controller { + /** + * While coding this, I want to introduce a system of qualified messages and notifications. + * Each message consists of a 3 letter prefix, a 4 digit number and a one letter suffix (PREnnnnS). + * The spirit about is not from me, but many decades used by IBM inc. in devel with best success. + * + * The system prefix, used uppercase as system message id, lowercase as css and js prefix (classes, ids etc). + * Usually not used as self::MYP, but placed in the code dominant enough for easy to find. + * + * Concrete here: + * The prefix indicates Z for the Zlabs(core), A for Account stuff, I for Invite. + * The numbers scope will be 00xx within/for templates, 01xx for get, 02xx for post functions. + * Message qualification ends with a uppercase suffix, where + * I=Info(only), + * W=Warning(more then info and less then error), + * E=Error, + * F=Fatal(for unexpected errors). + * Btw, in case of using fail2ban, a scan of messages going to log is very much more with ease, + * esspecially in multi language driven systems where messages vary. + * + * @author Hilmar Runge + * @version 2.0.0 + * @since 2020-01-20 + * + */ + + const MYP = 'ZAI'; + const VERSION = '2.0.0'; + function post() { + + // zai02 - if(! local_channel()) { - notice( t('Permission denied.') . EOL); + if (! local_channel()) { + notice( 'ZAI0201E,' .t('Permission denied.') . EOL); return; } - if(! Apps::system_app_installed(local_channel(), 'Invite')) { + if (! Apps::system_app_installed(local_channel(), 'Invite')) { + notice( 'ZAI0202E,' . t('Invite App') . ' (' . t('Not Installed') . ')' . EOL); return; } check_form_security_token_redirectOnErr('/', 'send_invite'); - - $max_invites = intval(get_config('system','max_invites')); - if(! $max_invites) - $max_invites = 50; - - $current_invites = intval(get_pconfig(local_channel(),'system','sent_invites')); - if($current_invites > $max_invites) { - notice( t('Total invitation limit exceeded.') . EOL); + + $ok = $ko = 0; + $feedbk = ''; + $isajax = is_ajax(); + $eol = $isajax ? "\n" : EOL; + $policy = intval(get_config('system','register_policy')); + if ($policy == REGISTER_CLOSED) { + notice( 'ZAI0212E,' . t('Register is closed') . ')' . EOL); return; - }; - - - $recips = ((x($_POST,'recipients')) ? explode("\n",$_POST['recipients']) : array()); - $message = ((x($_POST,'message')) ? notags(trim($_POST['message'])) : ''); - - $total = 0; - - if(get_config('system','invitation_only')) { - $invonly = true; - $x = get_pconfig(local_channel(),'system','invites_remaining'); - if((! $x) && (! is_site_admin())) - return; } + if ($policy == REGISTER_OPEN) + $flags = 0; + elseif ($policy == REGISTER_APPROVE) + $flags = ACCOUNT_PENDING; + $flags = ($flags | intval(get_config('system','verify_email'))); + + // how many max recipients in one mail submit + $maxto = get_config('system','invitation_max_recipients', 'na'); + If (is_site_admin()) { + // set, if admin is operator, default to 12 + if ($maxto === 'na') set_config('system','invitation_max_recipients', 12); + } + $maxto = ($maxto === 'na') ? 12 : $maxto; - foreach($recips as $recip) { - - $recip = trim($recip); - if(! $recip) - continue; - - if(! validate_email($recip)) { - notice( sprintf( t('%s : Not a valid email address.'), $recip) . EOL); - continue; + // language code current for the invitation + $lcc = x($_POST['zailcc']) && preg_match('/[a-z\-]{2,5}/', $_POST['zailcc']) + ? $_POST['zailcc'] + : ''; + + // expiration duration amount quantity, in case of doubts defaults 2 + $durn = x($_POST['zaiexpiren']) && preg_match('/[0-9]{1,2}/', $_POST['zaiexpiren']) + ? trim(intval($_POST['zaiexpiren'])) + : '2'; + !$durn ? $durn = 2 : ''; + + // expiration duration unit 1st letter (day, weeks, months, years), defaults days + $durq = x($_POST['zaiexpire']) && preg_match('/[ihd]{1,1}/', $_POST['zaiexpire']) + ? $_POST['zaiexpire'] + : 'd'; + + $dur = self::calcdue($durn.$durq); + $due = t('Note, the invitation code is valid up to') . ' ' . $dur['due']; + + if ($isajax) { + $feedbk .= 'ZAI0207I ' . $due . $eol; + } + + // take the received email addresses and discart duplicates + $recips = array_filter( array_unique( preg_replace('/^\s*$/', '', + ((x($_POST,'zaito')) ? explode( "\n",$_POST['zaito']) : array() ) ))); + + $havto = count($recips); + + if ( $havto > $maxto) { + $feedbk .= 'ZAI0210E ' . sprintf( t('Too many recipients for one invitation (max %d)'), $maxto) . $eol; + $ko++; + + } elseif ( $havto == 0 ) { + $feedbk .= 'ZAI0211E ' . t('No recipients for this invitation') . $eol; + $ko++; + + } else { + + // each email address + foreach($recips as $n => $recip) { + + // if empty ignore + $recip = $recips[$n] = trim($recip); + if(! $recip) continue; + + // see if we have an email address who@domain.tld + if (!preg_match('/^.{2,64}\@[a-z0-9.-]{4,32}\.[a-z]{2,12}$/', $recip)) { + $feedbk .= 'ZAI0203E ' . ($n+1) . ': ' . sprintf( t('(%s) : Not a valid email address'), $recip) . $eol; + $ko++; + continue; + } + if(! validate_email($recip)) { + $feedbk .= 'ZAI0204E ' . ($n+1) . ': ' . sprintf( t('(%s) : Not a real email address'), $recip) . $eol; + $ko++; + continue; + } + + // do we accept the email (not black listed) + if(! allowed_email($recip)) { + $feedbk .= 'ZAI0205E ' . ($n+1) . ': ' . sprintf( t('(%s) : Not allowed email address'), $recip) . $eol; + $ko++; + continue; + } + + // is the email address just in use for account or registered before + $r = q("SELECT account_email AS em FROM account WHERE account_email = '%s'" + . " UNION " + ."SELECT reg_email AS em FROM register WHERE reg_vital = 1 AND reg_email = '%s' LIMIT 1;", + dbesc($recip), + dbesc($recip) + ); + if($r && $r[0]['em'] == $recip) { + $feedbk .= 'ZAI0206E ' . ($n+1) . ': ' . sprintf( t('(%s) : email address already in use'), $recip) . $eol; + $ko++; + continue; + } + + if ($isajax) { + // seems we have an email address acceptable + $feedbk .= 'ZAI0209I ' . ($n+1) . ': ' . sprintf( t('(%s) : Accepted email address'), $recip) . $eol; + } } + } + + if ($isajax) { + // we are not silent on the ajax road + echo json_encode(array('feedbk' => $feedbk, 'due' => $due)); + + // that mission is complete + killme(); + exit; + } + + // Total ?todo notice( t('Invitation limit exceeded. Please contact your site administrator.') . EOL); + + // any errors up to now in fg? + + + // down from here, only on the main road (no more ajax) + + // tell if sth is to tell + $feedbk ? notice($feedbk) . $eol : ''; + + if ($ko > 0) return; + + // the personal mailtext + $mailtext = ((x($_POST,'zaitxt')) ? notags(trim($_POST['zaitxt'])) : ''); + + // to log in db + $reonar = json_decode( ((x($_POST,'zaireon')) ? notags(trim($_POST['zaireon'])) : ''), TRUE, 8) ; + + // me, the invitor + $account = App::get_account(); + $reonar['from'] = $account['account_email']; + $reonar['date'] = datetime_convert(); + $reonar['fromip'] = $_SERVER['REMOTE_ADDR']; + + // who is the invitor on + $inby = local_channel(); + + $ok = $ko = 0; + + // send the mail(s) + foreach($recips as $n => $recip) { - else - $nmessage = $message; - - $account = App::get_account(); + $reonar['due'] = $due; + $reonar['to'] = $recip; + $reonar['txtpersonal'] = $mailtext; - $res = z_mail( + // generate an invide code to store and pm + $invite_code = autoname(8) . rand(1000,9999); + + // again the final localized templates $reonar['subject'] $reonar['lang'] $reonar['tpl'] + + // save current operators lc and take the desired to mail + push_lang($reonar['lang']); + // resolve + $tx = replace_macros(get_intltext_template('invite.'.$reonar['tpl'].'.tpl'), + array( + '$projectname' => t('$Projectname'), + '$invite_code' => $invite_code, + '$invite_where' => z_root() . '/register', + '$invite_whereami' => str_replace('@', '@+', $reonar['whereami']), + '$invite_whoami' => z_root() . '/channel/' . $reonar['whoami'], + '$invite_anywhere' => z_root() . '/pubsites' + ) + ); + // restore lc to operator + pop_lang(); + + $reonar['txttemplate'] = $tx; + + // pm + $zem = z_mail( [ 'toEmail' => $recip, 'fromName' => ' ', - 'fromEmail' => $account['account_email'], - 'messageSubject' => t('Please join us on $Projectname'), - 'textVersion' => $nmessage, + 'fromEmail' => $reonar['from'], + 'messageSubject' => $reonar['subject'], + 'textVersion' => ($mailtext ? $mailtext . "\n\n" : '') . $tx . "\n" . $due, ] ); - if($res) { - $total ++; - $current_invites ++; - set_pconfig(local_channel(),'system','sent_invites',$current_invites); - if($current_invites > $max_invites) { - notice( t('Invitation limit exceeded. Please contact your site administrator.') . EOL); - return; - } - } - else { - notice( sprintf( t('%s : Message delivery failed.'), $recip) . EOL); + if(!$zem) { + + $ko++; + $msg = 'ZAI0208E,' . sprintf( t('%s : Message delivery failed.'), $recip); + + } else { + + $ok++; + $msg = 'ZAI0208I ' . sprintf( t('To %s : Message delivery success.'), $recip); + + // if verify_email is the rule, email becomes a dId2 - NO + // $did2 = ($flags & ACCOUNT_UNVERIFIED) == ACCOUNT_UNVERIFIED ? $recip : ''; + + // always enforce verify email with invitations, thus email becomes a dId2 + $did2 = $recip; + $flags |= ACCOUNT_UNVERIFIED; + + // defaults vital, reg_pass + $r = q("INSERT INTO register (" + . "reg_flags,reg_didx,reg_did2,reg_hash,reg_created,reg_startup,reg_expires,reg_email,reg_byc,reg_uid,reg_atip,reg_lang,reg_stuff)" + . " VALUES ( %d, 'i', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s') ", + intval($flags), + dbesc($did2), + dbesc($invite_code), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($dur['due']), + dbesc($recip), + intval($inby), + intval($account['account_id']), + dbesc($reonar['fromip']), + dbesc($reonar['lang']), + dbesc(json_encode( array('reon' => $reonar) )) + ); } - + $msg .= ' (a' . $account['account_id'] . ', c' . $inby . ', from:' . $reonar['from'] . ')'; + zar_log( $msg); } - notice( sprintf( tt("%d message sent.", "%d messages sent.", $total) , $total) . EOL); + + $ok + $ko > 0 + ? notice( 'ZAI0212I ' . sprintf( t('%1$d mail(s) sent, %2$d mail error(s)'), $ok, $ko) . EOL) + : ''; + //logger( print_r( $reonar, true) ); + return; } function get() { + + // zai1 if(! local_channel()) { - notice( t('Permission denied.') . EOL); + notice( 'ZAI0101E,' . t('Permission denied.') . EOL); return; } @@ -107,68 +307,263 @@ class Invite extends Controller { //Do not display any associated widgets at this point App::$pdl = ''; - $o = '' . t('Invite App') . ' (' . t('Not Installed') . '):
'; - $o .= t('Send email invitations to join this network'); + $o = 'ZAI0102E,' . t('Invite App') . ' (' . t('Not Installed') . ')' . EOL; + return $o; + } + + if (! (get_config('system','invitation_also') || get_config('system','invitation_only')) ) { + $o = 'ZAI0103E,' . t('Invites not proposed by configuration') . '. '; + $o .= t('Contact the site admin'); return $o; } + // invitation_by_user may still not configured, the default 'na' will tell this + // if configured, 0 disables invitations by users, other numbers are how many invites a user may propagate + $invuser = get_config('system','invitation_by_user', 'na'); + + // if the mortal user drives the invitation + If (! is_site_admin()) { + + // when not configured, 4 is the default + $invuser = ($invuser === 'na') ? 4 : $invuser; + + // a config value 0 disables invitation by users + if (!$invuser) { + $o = 'ZAI0104E, ' . t('Invites by users not enabled') . '. '; + return $o; + } + + if ($ihave >= $invuser) { + notice( 'ZAI0105W,' . t('You have no more invitations available') . EOL); + return ''; + } + + } else { + // general deity admin invite limit infinite (theoretical) + if ($invuser === 'na') set_config('system','invitation_by_user', 4); + // for display only + $invuser = '∞'; + } + + // xchan record of the page observer + // while quoting matters the user, the sending is associated with a channel (of the user) + // also the admin may and should decide, which channel will told to the public + $ob = App::get_observer(); + if(! $ob) + return 'ZAI0109F,' . t('Not on xchan') . EOL; + $whereami = $ob['xchan_addr']; + $channel = App::get_channel(); + $whoami = $channel['channel_address']; + + // to pass also to post() + $tao = 'tao.zai.whereami = ' . "'" . $whereami . "';\n" + . 'tao.zai.whoami = ' . "'" . $whoami . "';\n"; + + // expirations, duration interval + $dur = self::calcdue(); + $tao .= 'tao.zai.expire = { durn: ' . $dur['durn'] + . ', durq: ' . "'" . $dur['durq'] . "'" + . ', due: ' . "'" . $dur['due'] . "' };\n"; + + // to easy redisplay the empty form nav_set_selected('Invite'); + // inform about the count of invitations we have at all + $r = q("SELECT count(reg_id) as ct FROM register WHERE reg_vital = 1"); // where not admin TODO + $wehave = ($r ? $r[0]['ct'] : 0); + + // invites max for all users except admins + $invmaxau = intval(get_config('system','invitations_max_users')); + if(! $invmaxau) { + $invmaxau = 50; + if (is_site_admin()) { + set_config('system','invitations_max_users',intval($invmaxau)); + } + } + + if ($wehave > $invmaxau) { + if (! is_site_admin()) { + $feedbk .= 'ZAI0200E,' . t('All users invitation limit exceeded.') . $eol; + } + } + + // let see how many invites currently used by the user + $r = q("SELECT count(reg_id) AS n FROM register WHERE reg_vital = 1 AND reg_byc = %d", + intval(local_channel())); + $ihave = $r ? $r[0]['n'] : 0; + $tpl = get_markup_template('invite.tpl'); - $invonly = false; + + $inv_rabots = array( + 'i' => t('Minute(s)'), + 'h' => t('Hour(s)') , + 'd' => t('Day(s)') + ); + $inv_expire = replace_macros(get_markup_template('field_duration.qmc.tpl'), + array( + 'label' => t('Invitation expires after'), + 'qmc' => 'zai', + 'qmcid' => 'ZAI0014I', + 'field' => array( + 'name' => 'expire', + 'title' => t('duration up from now'), + 'value' => ($invexpire_n ? $invexpire_n : 2), + 'min' => '1', + 'max' => '99', + 'size' => '2', + 'default' => ($invexpire_u ? $invexpire_u : 'd') + ), + 'rabot' => $inv_rabots + ) + ); + + // let generate an invite code that here and never will be applied (only to fill displayed template) + // real invite codes become generated for each recipient when we store the new invitation(s) + // $invite_code = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 8) . rand(1000,9999); + // let take one descriptive for template (as said is never used) + $invite_code = 'INVITATE2020'; - if(get_config('system','invitation_only')) { - $invonly = true; - $x = get_pconfig(local_channel(),'system','invites_remaining'); - if((! $x) && (! is_site_admin())) { - notice( t('You have no more invitations available') . EOL); - return ''; + // what languages we use now + $lccmy = ((isset(App::$config['system']['language'])) ? App::$config['system']['language'] : 'en'); + // and all the localized templates belonging to invite + $tpls = glob('view/*/invite.*.tpl'); + + $tpla=$tplx=$tplxs=array(); + foreach ($tpls as $tpli) { + list( $nop, $l, $t ) = explode( '/', $tpli); + if ( preg_match('/\.subject/', $t) =='1' ) { + // indicate a subject tpl exists + $t=str_replace(array('invite.', '.subject', '.tpl'), '', $t); + $tplxs[$l][$t]=true; + continue; } + // collect unique template names cross all languages and + // tpla[language][]=template those available in each language + $tplx[] = $tpla[$l][] = str_replace( array('invite.', '.tpl'), '', $t); } - - if($invonly && ($x || is_site_admin())) { - $invite_code = autoname(8) . rand(1000,9999); - $nmessage = str_replace('$invite_code',$invite_code,$message); - - $r = q("INSERT INTO register (hash,created,uid,password,lang) VALUES ('%s', '%s',0,'','') ", - dbesc($invite_code), - dbesc(datetime_convert()) + asort( $langs = array_keys($tpla) ); + asort( $tplx = array_unique($tplx) ); + + // prepare current language and the default standard template (causual) for js + // With and in js, I use a var 'tao' as a shortcut for top array object + // and also qualify the object with the prefix zai = tao.zai as my var used outsite functions + // can be unique within the overall included spaghette whirls + // one can say Im too lazy to write prototypes and just I can agree. + // tao simply applies the fact of using the same var as object and/or array in ja. + $tao.='tao.zai.lccmy = ' . "'" . $lccmy . "';\n" . 'tao.zai.itpl = ' . "'" . 'casual' . "';\n"; + + $lcclane=$tx=$tplin=''; + //$lccsym=''; // alt  + $tplsym=''; + + // I will uncomment for js console debug + // $tao.='tao.zai.debug = ' . "'" . json_encode($tplxs) . "';\n"; + + // running thru the localized templates (subjects and textmsgs) and bring them to tao + // lcc LanguageCountryCode, + // lcc2 is a 2 character and lcc5 a 5 character LanguageCountryCode + foreach($tpla as $l => $tn) { + + // restyle lc to iso getttext format to avoid errors in js, hilite the current + $lcc = str_replace('-', '_', $l); + $hi = ($l == $lccmy) ? ' zai_hi' : ''; + $lcc2 = strlen($l) == 2 ? ' zai_lcc2' : ''; + $lcc5 = strlen($l) == 5 ? ' zai_lcc5' : ''; + $lccg = ' zai_lccg' . substr( $l, 0, 2 ); + $lcclane + .= '' + . '' . $lcc . ''; + // textmsg + $tao .= 'tao.zai.t.' . $lcc . ' = {};' . "\n"; + // subject + $tao .= 'tao.zai.s.' . $lcc . ' = {};' . "\n"; + + // resolve localized templates and take intented lc for + foreach($tn as $t1) { + + // save current lc and take the desired + push_lang($l); + + // resolve + $tx = replace_macros(get_intltext_template('invite.'.$t1.'.tpl'), + array( + '$projectname' => t('$Projectname'), + '$invite_code' => $invite_code, + '$invite_where' => z_root() . '/register', + '$invite_whereami' => $whereami, + '$invite_whoami' => z_root() . '/channel/' . $whoami, + '$invite_anywhere' => z_root() . '/pubsites' + ) ); - - if(! is_site_admin()) { - $x --; - if($x >= 0) - set_pconfig(local_channel(),'system','invites_remaining',$x); - else - return; - } + + // a default subject if no associated exists + $ts=t('Invitation'); + if ( $tplxs[$l][$t1] ) + $ts = replace_macros(get_intltext_template('invite.'.$t1.'.subject.tpl'), + array( + '$projectname' => t('$Projectname'), + '$invite_loc' => get_config('system','sitename') + ) + ); + + // restore lc to current foreground + pop_lang(); + + // bring to tao as js like it + $tao .= 'tao.zai.t.' . $lcc . '.' . $t1 . " = '" . rawurlencode($tx) . "';\n"; + $tao .= 'tao.zai.s.' . $lcc . '.' . $t1 . " = '" . rawurlencode($ts) . "';\n"; } - - $ob = App::get_observer(); - if(! $ob) - return $o; - - $channel = App::get_channel(); - + } + + // hilite the current defauls just from the beginning + foreach ($tplx as $t1) { + $hi = ($t1 == 'casual') ? ' zai_hi' : ''; + $tplin .= $tplsym.'' . $t1 . ''; + } + + // fill the form for foreground $o = replace_macros($tpl, array( '$form_security_token' => get_form_security_token("send_invite"), + '$zai' => strtolower(self::MYP), + '$tao' => $tao, '$invite' => t('Send invitations'), - '$addr_text' => t('Enter email addresses, one per line:'), - '$msg_text' => t('Your message:'), - '$default_message' => t('Please join my community on $Projectname.') . "\r\n" . "\r\n" - . $linktxt - . (($invonly) ? "\r\n" . "\r\n" . t('You will need to supply this invitation code:') . " " . $invite_code . "\r\n" . "\r\n" : '') - . t('1. Register at any $Projectname location (they are all inter-connected)') - . "\r\n" . "\r\n" . z_root() . '/register' - . "\r\n" . "\r\n" . t('2. Enter my $Projectname network address into the site searchbar.') - . "\r\n" . "\r\n" . $ob['xchan_addr'] . ' (' . t('or visit') . " " . z_root() . '/channel/' . $channel['channel_address'] . ')' - . "\r\n" . "\r\n" - . t('3. Click [Connect]') - . "\r\n" . "\r\n" , + '$ihave' => 'ZAI0106I, ' . t('Invitations I am using') . ': ' . $ihave . ' / ' . $invuser, + '$wehave' => 'ZAI0107I, ' . t('Invitations we are using') . ': ' . $wehave . ' / ' . $invmaxau, + '$n10' => 'ZAI0010I', '$m10' => t('§ Note, the email(s) sent will be recorded in the system logs'), + '$n11' => 'ZAI0011I', '$m11' => t('Enter email addresses, one per line:'), + '$n12' => 'ZAI0012I', '$m12' => t('Your message:'), + '$n13' => 'ZAI0013I', '$m13' => t('Invite template'), + '$inv_expire' => $inv_expire, + '$subject_label' => t('Subject:'), + '$subject' => t('Invitation'), + '$lcclane' => $lcclane, + '$tplin' => $tplin, + '$standard_message' => '', + '$personal_message' => '', + '$personal_pointer' => t('Here you may enter personal notes to the recipient(s)'), + '$due' => t('Note, the invitation code is valid up to') . ' ' . $dur['due'], '$submit' => t('Submit') )); - + return $o; } - + + function calcdue($duri=false) { + // expirations, duration interval + if ($duri===false) + $duri = get_config('system','register_expire', '2d'); + if ( preg_match( '/^[0-9]{1,2}[ihdwmy]{1}$/', $duri ) ) { + $durq = substr($duri, -1); + $durn = substr($duri, 0, -1); + $due = date('Y-m-d H:i:s', strtotime('+' . $durn . ' ' + . str_replace( array(':i',':h',':d',':w',':m',':y'), + array('minutes', 'hours', 'days', 'weeks', 'months', 'years'), + (':'.$durq)) + )); + return array( 'durn' => $durn, 'durq' => $durq, 'due' => $due); + } + return false; + } } + diff --git a/Zotlabs/Module/Regate.php b/Zotlabs/Module/Regate.php new file mode 100644 index 000000000..8ec559332 --- /dev/null +++ b/Zotlabs/Module/Regate.php @@ -0,0 +1,347 @@ + 1 ) { + $did2 = hex2bin( substr( argv(1), 0, -1) ); + $didx = substr( argv(1), -1 ); + } + + $msg = ''; + $nextpage = ''; + + if ($did2) { + + $nowhhmm = date('Hi'); + $day = date('N'); + $now = date('Y-m-d H:i:s'); + $ip = $_SERVER['REMOTE_ADDR']; + + $isduty = zar_register_dutystate(); + if ($isduty['isduty'] !== false && $isduty['isduty'] != 1) { + // normally, that should never happen here + // log suitable for fail2ban also + $logmsg = 'ZAR1230S Unexpected registration verification request for ' + . get_config('system','sitename') . ' arrived from § ' . $ip . ' §'; + zar_log($logmsg); + goaway(z_root() . '/'); + } + + // do we have a valid dId2 ? + if ( ($didx == 'a' && substr( $did2 , -2) == substr( base_convert( md5( substr( $did2, 1, -2) ),16 ,10), -2)) + || ($didx == 'e') ) { + // check startup and expiration via [=[register + $r = q("SELECT * FROM register WHERE reg_vital = 1 AND reg_did2 = '%s' ", dbesc($did2) ); + if ( $r && count($r) == 1 ) { + $r = $r[0]; + // check timeframe + if ( $r['reg_startup'] <= $now && $r['reg_expires'] >= $now ) { + + if ( isset($_POST['resend']) && $didx == 'e' ) { + $re = q("SELECT * FROM register WHERE reg_vital = 1 AND reg_didx = 'e' AND reg_did2 = '%s' ", dbesc($r['reg_did2']) ); + if ( $re && count($re) == 1 ) { + $re = $re[0]; + $reonar = json_decode($re['reg_stuff'],true); + $reonar['subject'] = 'Re,Fwd,' . $reonar['subject']; + if ($reonar) { + $zm = zar_reg_mail($reonar); + $msg = ($zm) ? 'ZAR1238I ' . t('Email resent') + : 'ZAR1238E ' . t('Resent failed'); + zar_log($msg . ' ' . $r['reg_did2']); + info($msg); + goaway(z_root() . '/' . $nextpage); + } + } + } + + // check hash + if ( $didx == 'a' ) + $acpin = (preg_match('/^[0-9]{6,6}$/', $_POST['acpin']) ? $_POST['acpin'] : false); + elseif ( $didx == 'e' ) + $acpin = (preg_match('/^[0-9a-f]{24,24}$/', $_POST['acpin']) ? $_POST['acpin'] : false); + else $acpin = false; + + if ( $acpin && ($r['reg_hash'] == $acpin )) { + + $flags = $r['reg_flags']; + if ( ($flags & ACCOUNT_UNVERIFIED ) == ACCOUNT_UNVERIFIED) { + + // verification success + $msg = 'ZAR1237I' . ' ' . t('Verify successfull'); + $reonar = json_decode( $r['reg_stuff'], true); + $reonar['valid'] = $now . ',' . $ip . ' ' . $did2 . ' ' . $msg; + // clear flag + $flags &= $flags ^ ACCOUNT_UNVERIFIED; + // sth todo? + $vital = $flags == 0 ? 0 : 1; + // set flag + $flags |= REGISTER_AGREED; + zar_log($msg . ' ' . $did2 . ':flags' . $flags . ',rid' . $r['reg_id']); + + q("START TRANSACTION"); + + $qu = q("UPDATE register SET reg_stuff = '%s', reg_vital = %d, reg_flags = %d " + ." WHERE reg_id = %d ", + dbesc(json_encode($reonar)), + intval($vital), + intval($flags), + intval($r['reg_id']) + ); + + if ( ($flags & ACCOUNT_PENDING ) == ACCOUNT_PENDING ) { + $msg .= "\n".t('Last step will be by an instance admin to agree your account request'); + q("COMMIT"); + } + elseif ( ($flags ^ REGISTER_AGREED) == 0) { + + $cra = create_account_from_register([ 'reg_id' => $r['reg_id'] ]); + + if ($cra['success']) { + + q("COMMIT"); + $msg = 'ZAR1238I' . t('Account successfull created'); + zar_log($msg . ':' . print_r($cra, true)); + $nextpage = '~'; + $auto_create = (get_config('system','auto_channel_create') ? true : false); + + if($auto_create) { + if($reonar['chan.name']) + set_aconfig($cra['account']['account_id'], + 'register','channel_name',$reonar['chan.name']); + if($reonar['chan.did1']) + set_aconfig($cra['account']['account_id'], + 'register','channel_address',$reonar['chan.did1']); + } + + authenticate_success($cra['account'],null,true,false,true); + + if($auto_create) { + $new_channel = auto_channel_create($cra['account']['account_id']); + if($new_channel['success']) { + $channel_id = $new_channel['channel']['channel_id']; + change_channel($channel_id); + $nextpage = 'profiles/' . $channel_id; + } + else { + zar_log($new_channel['message'] + . ', ' . $reonar['chan.did1'] + . ', ' . $reonar['chan.name'] + ); + } + } + unset($_SESSION['login_return_url']); + } + else { + q("ROLLBACK"); + $msg = 'ZAR1239E ' . t('Account creation error'); + zar_log($msg . ':' . print_r($cra, true)); + } + } + else { + // new flags implemented and not recognized or sth like + zar_log('ZAR1237D unexpected,' . $flags); + } + } + else { + // nothing to confirm + $msg = 'ZAR1236E' . ' ' . t('Verify failed'); + } + } + else { + $msg = 'ZAR1235E' . ' ' . t('Token verification failed'); + } + } + else { + $msg = 'ZAR1234W' . ' ' . t('Request not inside time frame'); + //info($r[0]['reg_startup'] . EOL . $r[0]['reg_expire'] ); + } + } + else { + $msg = 'ZAR1232E' . ' ' . t('Identity unknown'); + zar_log($msg . ':' . $did2 . $didx); + } + } + else { + $msg = 'ZAR1231E' . t('dId2 mistaken'); + } + + } + + if ($msg > '') info($msg); + goaway( z_root() . '/' . $nextpage ); + } + + + function get() { + + if ( argc() > 1 ) { + $did2 = hex2bin( substr( argv(1), 0, -1) ); + $didx = substr( argv(1), -1 ); + $deny = argc() > 2 ? argv(2) : ''; + $deny = preg_match('/^[0-9a-f]{8,8}$/', $deny) ? hex2bin($deny) : false; + } + + if ($_SESSION['zar']['msg']) { + $o = replace_macros(get_markup_template('plain.tpl'), [ + '$title' => t('Your Registration'), + '$now' => '', + '$infos' => $_SESSION['zar']['msg'] . EOL, + ]); + unset($_SESSION['zar']['msg']); + return $o; + } + + $now = date('Y-m-d H:i:s'); + $ip = $_SERVER['REMOTE_ADDR']; + + $isduty = zar_register_dutystate(); + $nowfmt = $isduty['nowfmt']; + $atform = $isduty['atform']; + + $title = t('Register Verification'); + + // do we have a valid dId2 ? + if ( ($didx == 'a' && substr( $did2 , -2) == substr( base_convert( md5( substr( $did2, 1, -2) ),16 ,10), -2)) + || ($didx == 'e') ) { + + $r = q("SELECT * FROM register WHERE reg_vital = 1 AND reg_didx = '%s' AND reg_did2 = '%s'", + dbesc($didx), + dbesc($did2) + ); + + if ( $r && count($r) == 1 && $r[0]['reg_flags'] &= (ACCOUNT_UNVERIFIED | ACCOUNT_PENDING)) { + $r = $r[0]; + + // provide a button in case + $resend = ($r['reg_didx'] == 'e') ? t('Resend') : false; + + // is still only instance admins intervention required? + if ( $r['reg_flags'] == ACCOUNT_PENDING ) { + $o = replace_macros(get_markup_template('plain.tpl'), [ + '$title' => t('Register Verification Status'), + '$now' => $nowfmt, + '$infos' => t('Soon all is well.') . EOL + . t('Only one instance admin has still to agree your account request.') . EOL + . t('Please be patient') . EOL . EOL . 'ZAR1138I', + ]); + } + else { + + if ($deny) { + + if (substr($r['reg_hash'],0,4) == $deny) { + + zar_log('ZAR1134S email verfication denied ' . $did2); + + $msg = 'ZAR1133A' . ' ' . t('Sorry for any inconvience. Thank you for your response.'); + $o = replace_macros(get_markup_template('plain.tpl'), [ + '$title' => t('Registration request denied'), + '$now' => $nowf, + '$infos' => $msg . EOL, + ]); + + $reonar = json_decode( $r['reg_stuff'], true); + $reonar['deny'] = $now . ',' . $ip . ' ' . $did2 . ' ' . $msg; + $flags = ( $r['reg_flags'] &= ( $r['reg_flags'] ^ ACCOUNT_UNVERIFIED) ) + | ( $r['reg_flags'] |= REGISTER_DENIED); + $rd = q("UPDATE register SET reg_stuff='%s', reg_vital=0, reg_flags=%d WHERE reg_id = %d ", + dbesc(json_encode($reonar)), + intval($flags), + intval($r['reg_id']) + ); + } + else { + zar_log('ZAR1135E not awaited url parameter received'); + goaway(z_root); + } + } + else { + + if ( $r['reg_startup'] <= $now && $r['reg_expires'] >= $now) { + $o = replace_macros(get_markup_template('regate.tpl'), [ + '$form_security_token' => get_form_security_token("regate"), + '$title' => $title, + '$desc' => t('You were given a validation token. Please enter that token here to continue the register verification step and allow some delay for proccessing.'), + '$did2' => bin2hex($did2) . $didx, + '$now' => $nowfmt, + '$atform' => $atform, + '$resend' => $resend, + '$submit' => t('Submit'), + '$acpin' => [ 'acpin', t('Validation token'),'','' ], + ]); + } + else { + // expired ? + if ( $now > $r['reg_expires'] ) { + $rd = q("UPDATE register SET reg_vital = 0 WHERE reg_id = %d ", + intval($r['reg_id']) + ); + } + + $o = replace_macros(get_markup_template('plain.tpl'), [ + '$title' => $title, + '$now' => $nowf, + '$infos' => 'ZAR1132W' . ' ' . t('Request not inside time frame') . EOL, + ]); + } + } + } + } + else { + $msg = 'ZAR1132E' . ' ' . t('Identity unknown'); + zar_log($msg . ':' . $did2 . ',' . $didx); + $o = replace_macros(get_markup_template('plain.tpl'), [ + '$title' => $title, + '$now' => $nowf, + '$infos' => $msg . EOL, + ]); + } + + } + else { + $msg = 'ZAR1131E ' . t('dId2 mistaken'); + // $log = ' from § ' . $ip . ' §' . ' (' . dbesc($did2) . ')'; + zar_log($msg); + $o = replace_macros(get_markup_template('plain.tpl'), [ + '$title' => $title, + '$now' => $nowf, + '$infos' => ($msg) . EOL, + ]); + } + + return $o; + } +} + diff --git a/Zotlabs/Module/Register.php b/Zotlabs/Module/Register.php index 278cf15ca..d0a41b3c2 100644 --- a/Zotlabs/Module/Register.php +++ b/Zotlabs/Module/Register.php @@ -1,4 +1,5 @@ 1) ? argv(1) : ''); @@ -43,25 +49,98 @@ class Register extends Controller { check_form_security_token_redirectOnErr('/register', 'register'); - $max_dailies = intval(get_config('system','max_daily_registrations')); - if($max_dailies) { - $r = q("select count(account_id) as total from account where account_created > %s - INTERVAL %s", - db_utcnow(), db_quoteinterval('1 day') + /** + * [hilmar:] + * It may happen, the posted form arrives in a strange fashion. With the control of the duty hours + * for registration, the input form was disabled at html. While receiving posted data, checks are + * required if all is on the right road (no posts accepted during off duty). + * + */ + + $act = q("SELECT COUNT(*) AS act FROM account")[0]['act']; + $duty = zar_register_dutystate(); + $ip = $_SERVER['REMOTE_ADDR']; + $sameip = intval(get_config('system','register_sameip')); + + $arr = $_POST; + $invite_code = ( (x($arr,'invite_code')) ? notags(trim($arr['invite_code'])) : ''); + $email = ( (x($arr,'email')) ? notags(punify(trim($arr['email']))) : ''); + $password = ( (x($arr,'password')) ? trim($arr['password']) : ''); + $reonar = array(); + + // assume someone tries to validate (dId2 C/D/E), because only field email entered + if ( $email && ( ! $invite_code ) && ( ! $password ) && ( ! $_POST['password2'] ) ) { + + // dId2 logic + + if ( preg_match('/^\@{1,1}.{2,64}\@[a-z0-9.-]{4,32}\.[a-z]{2,12}$/', $email ) ) { + // dId2 C channel - ffu + } + + if ( preg_match('/^.{2,64}\@[a-z0-9.-]{4,32}\.[a-z]{2,12}$/', $email ) ) { + // dId2 E email + goaway(z_root() . '/regate/' . bin2hex($email) . 'e' ); + } + + if ( preg_match('/^d{1,1}[0-9]{1,10}$/', $email ) ) { + // dId2 A artifical & anonymous + goaway(z_root() . '/regate/' . bin2hex($email) . 'a' ); + } + + } + + if ($act > 0 && !$duty['isduty']) { + // normally, that should never arrive here (ie js hack or sth like) + // log suitable for f2b also + $logmsg = 'ZAR0230S Unexpected registration request'; + zar_log($logmsg); + goaway(z_root() . '/'); + } + + if ($sameip) { + $f = q("SELECT COUNT(reg_atip) AS atip FROM register WHERE reg_vital = 1 AND reg_atip = '%s' ", + dbesc($ip) ); - if($r && $r[0]['total'] >= $max_dailies) { - notice( t('Maximum daily site registrations exceeded. Please try again tomorrow.') . EOL); - return; + if ($f && $f[0]['atip'] > $sameip) { + $logmsg = 'ZAR0239S Exceeding same ip register request of ' . $sameip; + zar_log($logmsg); + goaway(z_root() . '/'); } } - + + // s2 max daily + if ( self::check_max_daily_exceeded() ) return; + + // accept tos if(! x($_POST,'tos')) { - notice( t('Please indicate acceptance of the Terms of Service. Registration failed.') . EOL); + notice( 'ZAR0230E ' + . t('Please indicate acceptance of the Terms of Service. Registration failed.') . EOL); return; } - $policy = get_config('system','register_policy'); + // pw1 == pw2 + if((! $_POST['password']) || ($_POST['password'] !== $_POST['password2'])) { + notice( 'ZAR0230E ' + . t('Passwords do not match.') . EOL); + return; + } + + + $email_verify = intval(get_config('system','verify_email')); + + if ($email) { + if ( ! preg_match('/^.{2,64}\@[a-z0-9.-]{4,32}\.[a-z]{2,12}$/', $_POST['email'] ) ) { + notice('ZAR0239E ' + . t('Email address mistake') . EOL); + return; + } + } - $email_verify = get_config('system','verify_email'); + $policy = intval(get_config('system','register_policy')); + $invonly = intval(get_config('system','invitation_only')); + $invalso = intval(get_config('system','invitation_also')); + $auto_create = (get_config('system','auto_channel_create') ? true : false); + $auto_create = true; switch($policy) { @@ -71,7 +150,7 @@ class Register extends Controller { break; case REGISTER_APPROVE: - $flags = ACCOUNT_BLOCKED | ACCOUNT_PENDING; + $flags = ACCOUNT_PENDING; break; default: @@ -84,103 +163,224 @@ class Register extends Controller { break; } - if($email_verify && $policy == REGISTER_OPEN) - $flags = $flags | ACCOUNT_UNVERIFIED; + if($email_verify && ($policy == REGISTER_OPEN || $policy == REGISTER_APPROVE) ) + $flags = ($flags | ACCOUNT_UNVERIFIED); - - if((! $_POST['password']) || ($_POST['password'] !== $_POST['password2'])) { - notice( t('Passwords do not match.') . EOL); - return; - } - - $arr = $_POST; + // $arr has $_POST; $arr['account_flags'] = $flags; - - $result = create_account($arr); - - if(! $result['success']) { - notice($result['message']); - return; - } - require_once('include/security.php'); - - - if($_REQUEST['name']) - set_aconfig($result['account']['account_id'],'register','channel_name',$_REQUEST['name']); - if($_REQUEST['nickname']) - set_aconfig($result['account']['account_id'],'register','channel_address',$_REQUEST['nickname']); - if($_REQUEST['permissions_role']) - set_aconfig($result['account']['account_id'],'register','permissions_role',$_REQUEST['permissions_role']); - - - $using_invites = intval(get_config('system','invitation_only')); - $num_invites = intval(get_config('system','number_invites')); - $invite_code = ((x($_POST,'invite_code')) ? notags(trim($_POST['invite_code'])) : ''); - - if($using_invites && $invite_code) { - q("delete from register where hash = '%s'", dbesc($invite_code)); - // @FIXME - this also needs to be considered when using 'invites_remaining' in mod/invite.php - set_aconfig($result['account']['account_id'],'system','invites_remaining',$num_invites); - } - - if($policy == REGISTER_OPEN ) { - if($email_verify) { - $res = verify_email_address($result); - } - else { - $res = send_register_success_email($result['email'],$result['password']); - } - if($res) { - if($invite_code) { - info( t('Registration successful. Continue to create your first channel...') . EOL ) ; - } - else { - info( t('Registration successful. Please check your email for validation instructions.') . EOL ) ; + $now = datetime_convert(); + $well = false; + + // s3 + if ($invite_code) { + + if ($invonly || $invalso) { + + $reg = q("SELECT * from register WHERE reg_vital = 1 AND reg_didx = 'i' AND reg_hash = '%s'", + dbesc($invite_code)); + + if ( $reg && count($reg) == 1 ) { + $reg = $reg[0]; + if ($reg['reg_email'] == ($email)) { + + if ($reg['reg_startup'] <= $now && $reg['reg_expires'] >= $now) { + + // is invitor admin + $isa = get_account_by_id($reg['reg_uid']); + $isa = ( $isa && ($isa['account_roles'] && ACCOUNT_ROLE_ADMIN) ); + + // approve contra invite by admin + if ($isa && $policy == REGISTER_APPROVE) + $flags &= $flags ^ ACCOUNT_PENDING; + + // if $flags == 0 ?? + + // trans ? + + // update reg vital 0 off + $icdone = q("UPDATE register SET reg_vital = 0 WHERE reg_id = %d ", + intval($reg['reg_id']) + ); + + info('ZAR0237I ' . t('Invitation code succesfully applied') . EOL); + + $well = true; + + + } else { + notice('ZAR0236E ' . t('Invitation not in time or too late') . EOL); + goaway(z_root()); + } + + } else { + // no match email adr + $msg = 'ZAR0235S ' . t('Invitation email failed'); + zar_log($msg); + notice($msg . EOL); + goaway(z_root()); + } + + } else { + // no match invitecode + $msg = 'ZAR0234S ' . t('Invitation code failed') ; + zar_log($msg); + notice( $msg . EOL); + goaway(z_root()); } + + } else { + notice('ZAR0232E ' . t('Invitations are not available') . EOL); + goaway(z_root()); } - } - elseif($policy == REGISTER_APPROVE) { - $res = send_reg_approval_email($result); - if($res) { - info( t('Your registration is pending approval by the site owner.') . EOL ) ; - } - else { - notice( t('Your registration can not be processed.') . EOL); + + + } else { + + $icdone = false; + // no ivc entered + if ( ! $invonly) { + // possibly the email is just in use ? + $reg = q("SELECT * from register WHERE reg_vital = 1 AND reg_email = '%s'", + dbesc('e' . $email)); + + if ( ! $reg) + $act = q("SELECT * from account WHERE account_email = '%s'", dbesc($email)); + + // in case an invitation was made but the invitecode was not entered, better ignore. + // goaway(z_root() . '/regate/' . bin2hex($reg['email'])); + + if ( ! $reg && ! $act) { + // email useable + + $well = true; + + + } else { + $msg = 'ZAR0237E ' . t('Email address already in use') . EOL; + notice($msg); + // problem, the msg tells to anonymous about existant email addrs + // use another msg instead ? TODO ? + // on the other hand can play the fail2ban game + zar_log($msg . ' (' . $email . ')'); + goaway(z_root()); + } + + } else { + $msg = 'ZAR0233E ' . t('Registration on this hub is by invitation only') . EOL; + notice($msg); + zar_log($msg); + goaway(z_root()); } - goaway(z_root()); - } - - if($email_verify) { - goaway(z_root() . '/email_validation/' . bin2hex($result['email'])); + } - // fall through and authenticate if no approvals or verifications were required. + if ($well) { - authenticate_success($result['account'],null,true,false,true); - - $new_channel = false; - $next_page = 'new_channel'; - - if(get_config('system','auto_channel_create')) { - $new_channel = auto_channel_create($result['account']['account_id']); - if($new_channel['success']) { - $channel_id = $new_channel['channel']['channel_id']; - change_channel($channel_id); - $next_page = '~'; + if($policy == REGISTER_OPEN || $policy == REGISTER_APPROVE ) { + + $cfgdelay = get_config( 'system', 'register_delay' ); + $regdelay = calculate_adue( $cfgdelay ); + $regdelay = $regdelay ? $regdelay['due'] : $now; + + $cfgexpire = get_config('system','register_expire' ); + $regexpire = calculate_adue( $cfgexpire ); + $regexpire = $regexpire ? $regexpire['due'] : '2099-12-31 23:59:59'; + + // handle an email request that will be verified or an ivitation associated with an email address + if ( $email > '' && ($email_verify || $icdone) ) { + // enforce in case of icdone + $flags |= ACCOUNT_UNVERIFIED; + $empin = $pass2 = random_string(24); + $did2 = $email; + $didx = 'e'; + + push_lang(($reg['lang']) ? $reg['lang'] : 'en'); + $reonar['from'] = get_config('system', 'from_email'); + $reonar['to'] = $email; + $reonar['subject'] = sprintf( t('Registration confirmation for %s'), get_config('system','sitename')); + $reonar['txtpersonal']= t('Valid from') . ' ' . $regdelay . ' ' . t('and expire') . ' ' . $regexpire; + $reonar['txttemplate']= replace_macros(get_intltext_template('register_verify_member.tpl'), + [ + '$sitename' => get_config('system','sitename'), + '$siteurl' => z_root(), + '$email' => $email, + '$due' => $reonar['txtpersonal'], + '$mail' => bin2hex($email) . 'e', + '$ko' => bin2hex(substr($empin,0,4)), + '$hash' => $empin + ] + ); + pop_lang(); + zar_reg_mail($reonar); + + } else { + // that is an anonymous request without email or with email not to verify + $acpin = $pass2 = rand(100000,999999); + $did2 = rand(10,99); + $didx = 'a'; + // enforce delayed verify + $flags = ($flags | ACCOUNT_UNVERIFIED); + if ($email) { + $reonar['email.untrust'] = $email; + $reonar['email.comment'] = 'received, but no need for'; + } + } + + if ( $auto_create ) { + $reonar['chan.name'] = notags(trim($arr['name'])); + $reonar['chan.did1'] = notags(trim($arr['nickname'])); + } + + $reg = q("INSERT INTO register (" + . "reg_flags,reg_didx,reg_did2,reg_hash,reg_created,reg_startup,reg_expires," + . "reg_email,reg_pass,reg_lang,reg_atip,reg_stuff)" + . " VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", + intval($flags), + dbesc($didx), + dbesc($did2), + dbesc($pass2), + dbesc(datetime_convert('','',$now)), + dbesc(datetime_convert('','',$regdelay)), + dbesc(datetime_convert('','',$regexpire)), + dbesc($email), + dbesc(bin2hex($password)), + dbesc(substr(get_best_language(),0,2)), + dbesc($ip), + dbesc(json_encode( $reonar )) + ); + + if ($didx == 'a') { + + $lid = q("SELECT reg_id FROM register WHERE reg_vital = 1 AND reg_did2 = '%s' AND reg_pass = '%s' ", + dbesc($did2), dbesc(bin2hex($password)) ); + + if ($lid && count($lid) == 1 ) { + + $didnew = ( $lid[0]['reg_id'] . $did2 ) + . ( substr( base_convert( md5( $lid[0]['reg_id'] . $did2 ), 16, 10 ),-2 ) ); + + $reg = q("UPDATE register SET reg_did2 = CONCAT('d','%s') WHERE reg_id = %d ", + dbesc($didnew), intval($lid[0]['reg_id']) + ); + + // notice( 'ZAR0239I,' . t( 'Your didital id is' ) . EOL . 'd' . $didnew . EOL + $_SESSION['zar']['msg'] = ( 'ZAR0239I,' . t( 'Your didital id is' ) . EOL . 'd' . $didnew . EOL + . t('and your pin for is') . ' ' . $pass2 . EOL + . t('Keep these infos and your entered password safe') . EOL + . t('Valid from') . ' ' . $regdelay . ' ' . t('and expire') . ' ' . $regexpire . EOL ); + + // acpin verify + // goaway(z_root() . '/regate/' . bin2hex('d' . $didnew) . 'a' ); + goaway(z_root() . '/regate'); + } + else { + $msg = 'ZAR0239D,' . t('Error creating dId A'); + notice( $msg ); + zar_log( $msg . ' ' . $did2); + } + } } - else - $new_channel = false; - } - - $x = get_config('system','workflow_register_next'); - if($x) { - $next_page = $x; - $_SESSION['workflow'] = true; } - - unset($_SESSION['login_return_url']); - goaway(z_root() . '/' . $next_page); - } @@ -192,7 +392,7 @@ class Register extends Controller { if(intval(get_config('system','register_policy')) === REGISTER_CLOSED) { if(intval(get_config('system','directory_mode')) === DIRECTORY_MODE_STANDALONE) { - notice( t('Registration on this hub is disabled.') . EOL); + notice( 'ZAR0130E ' . t('Registration on this hub is disabled.') . EOL); return; } @@ -201,37 +401,34 @@ class Register extends Controller { } if(intval(get_config('system','register_policy')) == REGISTER_APPROVE) { - $registration_is = t('Registration on this hub is by approval only.'); - $other_sites = t('Register at another affiliated hub.'); + $registration_is = t('Registration on this hub is by approval only.') . 'ZAR0131I'; + $other_sites = '' . t('Register at another affiliated hub in case when prefered') . ''; } + if ( !get_config('system', 'register_duty_jso') ) { + // duty yet not configured + $duty = array( 'isduty' => false, 'atfrm' => '', 'nowfmt' => ''); + } else { + $duty = zar_register_dutystate(); + } $invitations = false; - if(intval(get_config('system','invitation_only'))) { $invitations = true; - $registration_is = t('Registration on this hub is by invitation only.'); - $other_sites = t('Register at another affiliated hub.'); - } - - $max_dailies = intval(get_config('system','max_daily_registrations')); - if($max_dailies) { - $r = q("select count(account_id) as total from account where account_created > %s - INTERVAL %s", - db_utcnow(), db_quoteinterval('1 day') - ); - if($r && $r[0]['total'] >= $max_dailies) { - logger('max daily registrations exceeded.'); - notice( t('This site has exceeded the number of allowed daily account registrations. Please try again tomorrow.') . EOL); - return; - } + $registration_is = t('Registration on this hub is by invitation only.') . 'ZAR0132I'; + $other_sites = '' . t('Register at another affiliated hub') . ''; + } elseif (intval(get_config('system','invitation_also'))) { + $invitations = true; } + if ( self::check_max_daily_exceeded() ) + $duty['atform'] = 'disabled'; + $privacy_role = ((x($_REQUEST,'permissions_role')) ? $_REQUEST['permissions_role'] : ""); $perm_roles = \Zotlabs\Access\PermissionRoles::roles(); // Configurable terms of service link - $tosurl = get_config('system','tos_url'); if(! $tosurl) $tosurl = z_root() . '/help/TermsOfService'; @@ -254,16 +451,35 @@ class Register extends Controller { $enable_tos = 1 - intval(get_config('system','no_termsofservice')); - $email = array('email', t('Your email address'), ((x($_REQUEST,'email')) ? strip_tags(trim($_REQUEST['email'])) : "")); + $emailval = ((x($_REQUEST,'email')) ? strip_tags(trim($_REQUEST['email'])) : ""); + $email = array('email', + t('Your email address (or leave blank to register without email)') . ' ZAR0136I', + $emailval, + t('If the registation was already submitted with your data once ago, enter your identity (like email) here and submit') . 'ZAR0133I' + ); + $password = array('password', t('Choose a password'), ''); $password2 = array('password2', t('Please re-enter your password'), ''); + $invite_code = array('invite_code', t('Please enter your invitation code'), ((x($_REQUEST,'invite_code')) ? strip_tags(trim($_REQUEST['invite_code'])) : "")); - $name = array('name', t('Your Name'), ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''), t('Real names are preferred.')); + + // + $name = array('name', t('Your Name'), + ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''), t('Real names are preferred.')); $nickhub = '@' . str_replace(array('http://','https://','/'), '', get_config('system','baseurl')); - $nickname = array('nickname', t('Choose a short nickname'), ((x($_REQUEST,'nickname')) ? $_REQUEST['nickname'] : ''), sprintf( t('Your nickname will be used to create an easy to remember channel address e.g. nickname%s'), $nickhub)); - $role = array('permissions_role' , t('Channel role and privacy'), ($privacy_role) ? $privacy_role : 'social', t('Select a channel permission role for your usage needs and privacy requirements.') . ' ' . t('Read more about channel permission roles') . '',$perm_roles); - $tos = array('tos', $label_tos, '', '', array(t('no'),t('yes'))); + $nickname = array('nickname', t('Choose a short nickname'), + ((x($_REQUEST,'nickname')) ? $_REQUEST['nickname'] : ''), + sprintf( t('Your nickname will be used to create an easy to remember channel address e.g. nickname%s'), + $nickhub)); + $role = array('permissions_role' , t('Channel role and privacy'), + ($privacy_role) ? $privacy_role : 'social', + t('Select a channel permission role for your usage needs and privacy requirements.') + . ' ' + . t('Read more about channel permission roles') + . '',$perm_roles); + // + $tos = array('tos', $label_tos, '', '', array(t('no'),t('yes'))); $auto_create = (get_config('system','auto_channel_create') ? true : false); $default_role = get_config('system','default_permissions_role'); @@ -280,6 +496,9 @@ class Register extends Controller { '$other_sites' => $other_sites, '$invitations' => $invitations, '$invite_code' => $invite_code, + '$haveivc' => t('I have an invite code') . '.ZAR0134I', + '$now' => $duty['nowfmt'], + '$atform' => $duty['atform'], '$auto_create' => $auto_create, '$name' => $name, '$role' => $role, @@ -288,15 +507,40 @@ class Register extends Controller { '$enable_tos' => $enable_tos, '$tos' => $tos, '$email' => $email, + '$validate' => $validate, + '$validate_link'=> $validate_link, + '$validate_here'=> $validate_here, '$pass1' => $password, '$pass2' => $password2, '$submit' => t('Register'), - '$verify_note' => (($email_verify) ? t('This site requires email verification. After completing this form, please check your email for further instructions.') : ''), + '$verify_note' => (($email_verify) ? t('This site requires verification. After completing this form, please check the notice or your email for further instructions.') . 'ZAR0135I' : ''), )); return $o; - } - - + + function check_max_daily_exceeded() { + // check against register, account + $max_dailies = intval(get_config('system','max_daily_registrations')); + if ( $max_dailies ) { + $r = q("SELECT COUNT(reg_id) AS nr FROM register WHERE reg_vital = 1 AND reg_created > %s - INTERVAL %s", + db_utcnow(), db_quoteinterval('1 day') + ); + $re = ( $r && $r[0]['nr'] >= $max_dailies ) ? true : false; + if ( !$re ) { + $r = q("SELECT COUNT(account_id) AS nr FROM account WHERE account_created > %s - INTERVAL %s", + db_utcnow(), db_quoteinterval('1 day') + ); + $re = ( $r && $r[0]['nr'] >= $max_dailies ) ? true : false; + } + if ( $re ) { + zar_log('ZAR0333W max daily registrations exceeded.'); + notice( 'ZAR0333W ' + . t('This site has exceeded the number of allowed daily account registrations. Please try again tomorrow.') + . EOL); + return true; + } + } + return false; + } } diff --git a/Zotlabs/Module/Settings/Account.php b/Zotlabs/Module/Settings/Account.php index b40f516ca..97cc9389a 100644 --- a/Zotlabs/Module/Settings/Account.php +++ b/Zotlabs/Module/Settings/Account.php @@ -15,20 +15,23 @@ class Account { $account = \App::get_account(); if($email != $account['account_email']) { - if(! validate_email($email)) - $errs[] = t('Not valid email.'); - $adm = trim(get_config('system','admin_email')); - if(($adm) && (strcasecmp($email,$adm) == 0)) { - $errs[] = t('Protected email address. Cannot change to that email.'); - $email = \App::$account['account_email']; - } - if(! $errs) { - $r = q("update account set account_email = '%s' where account_id = %d", - dbesc($email), - intval($account['account_id']) - ); - if(! $r) - $errs[] = t('System failure storing new email. Please try again.'); + // a DId2 not an email addr does not allow to change to email addr + if (strpos($email, '@') > 0) { + if(! validate_email($email)) + $errs[] = t('Not valid email.'); + $adm = trim(get_config('system','admin_email')); + if(($adm) && (strcasecmp($email,$adm) == 0)) { + $errs[] = t('Protected email address. Cannot change to that email.'); + $email = \App::$account['account_email']; + } + if(! $errs) { + $r = q("update account set account_email = '%s' where account_id = %d", + dbesc($email), + intval($account['account_id']) + ); + if(! $r) + $errs[] = t('System failure storing new email. Please try again.'); + } } } @@ -92,6 +95,7 @@ class Account { call_hooks('account_settings', $account_settings); $email = \App::$account['account_email']; + $attremail = (!strpos($email, '@')) ? 'disabled="disabled"' : ''; $tpl = get_markup_template("settings_account.tpl"); $o .= replace_macros($tpl, array( @@ -101,7 +105,7 @@ class Account { '$password1'=> array('npassword', t('Enter New Password'), '', ''), '$password2'=> array('confirm', t('Confirm New Password'), '', t('Leave password fields blank unless changing')), '$submit' => t('Submit'), - '$email' => array('email', t('Email Address:'), $email, ''), + '$email' => array('email', t('DId2 or Email Address:'), $email, '', '', $attremail), '$removeme' => t('Remove Account'), '$removeaccount' => t('Remove this account including all its channels'), '$account_settings' => $account_settings diff --git a/Zotlabs/Update/_1241.php b/Zotlabs/Update/_1241.php new file mode 100644 index 000000000..5f806b7d1 --- /dev/null +++ b/Zotlabs/Update/_1241.php @@ -0,0 +1,15 @@ + false, 'message' => ''); - $using_invites = get_config('system','invitation_only'); + // [hilmar -> + $using_invites = (get_config('system','invitation_only') + || get_config('system','invitation_also')); if($using_invites) { + if(! $invite_code) { - $result['message'] .= t('An invitation is required.') . EOL; - } - $r = q("select * from register where hash = '%s' limit 1", dbesc($invite_code)); - if(! $r) { - $result['message'] .= t('Invitation could not be verified.') . EOL; + + $result['message'] + .= 'ZAR0510E,' . t('An invitation is required.') . EOL; + + } else { + + // check if invite code exists + $r = q("SELECT * FROM register WHERE reg_hash = '%s' AND reg_vital = 1 LIMIT 1", + dbesc($invite_code)); + if(! $r) { + $result['message'] + .= 'ZAR0511E,' . t('Invitation could not be verified.') . EOL; + } } } + // <- hilmar] + if(strlen($result['message'])) $result['error'] = true; @@ -105,8 +118,8 @@ function account_total() { return false; } - -function account_store_lowlevel($arr) { +// legacy +function account_store_lowlevel_IS_OBSOLETE($arr) { $store = [ 'account_parent' => ((array_key_exists('account_parent',$arr)) ? $arr['account_parent'] : '0'), @@ -128,12 +141,21 @@ function account_store_lowlevel($arr) { 'account_password_changed' => ((array_key_exists('account_password_changed',$arr)) ? $arr['account_password_changed'] : '0001-01-01 00:00:00') ]; + // never ever is this a create table but a pdo insert into account + // strange function placement in text.php (obscure by design :-) return create_table_from_array('account',$store); - + // the TODO may be to adjust others using create_table_from_array(): + // channel.php + // connections.php + // event.php + // hubloc.php + // import.php } -function create_account($arr) { + +// legacy +function create_account_IS_OBSOLETE($arr) { // Required: { email, password } @@ -257,10 +279,160 @@ function create_account($arr) { return $result; } +/** + * create_account_from_register + * @author hilmar runge + * @since 2020-02-20 + * + * Account creation only happens via table register. + * This function creates the account when all conditions are solved. + * + */ +function create_account_from_register($arr) { + + $result = array('success' => false, 'message' => 'rid:' . $arr['reg_id']); + $now = date('Y-m-d H:i:s'); + + // reg_flags 0x0020 = REGISTER_AGREED = register request verified by user @ regate + $register = q("SELECT * FROM register WHERE reg_id = %d AND (reg_flags & 31) = 0 " + . " AND reg_startup < '%s' AND reg_expires > '%s' ", + intval($arr['reg_id']), + dbesc($now), + dbesc($now) + ); + + if ( ! $register ) return $result; + + // account + $expires = NULL_DATE; + + $default_service_class = get_config('system','default_service_class'); + if($default_service_class === false) + $default_service_class = ''; + + $roles = 0; + // prevent form hackery + if($roles & ACCOUNT_ROLE_ADMIN) { + $admin_result = check_account_admin($arr); + if(! $admin_result) { + $roles = 0; + } + } + + // any accounts available ? + $isa = q("SELECT COUNT(*) AS isa FROM account"); + if ($isa && $isa[0]['isa'] == 0) { + $roles = ACCOUNT_ROLE_ADMIN; + } + + $salt = random_string(32); + $password_encoded = hash('whirlpool', $salt . (hex2bin($register[0]['reg_pass']))); + + $ri = q( + "INSERT INTO account (" + . " account_parent, account_salt, account_password, account_email, " + . " account_language, account_created, account_flags, account_roles, account_level, " + . " account_expires, account_service_class) VALUES( " + . " %d, '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s' ) ", + intval($parent), + dbesc($salt), + dbesc($password_encoded), + dbesc($register[0]['reg_did2']), + dbesc($register[0]['reg_lang']), + dbesc($now), + intval($register[0]['reg_flags'] & 31), // off REGISTER_AGREE at ACCOUNT + intval($roles), + intval(5), + dbesc($expires), + dbesc($default_service_class) + ); + + if(! $ri) { + logger('create_account: DB INSERT failed.'); + $result['message'] = 'ZAR ' . t('Failed to store account information.'); + return($result); + } + + $r = q("SELECT * FROM account WHERE account_email = '%s' AND account_password = '%s' LIMIT 1", + dbesc($register[0]['reg_did2']), + dbesc($password_encoded) + ); + if($r && count($r)) { + $result['account'] = $r[0]; + } + else { + logger('create_account: could not retrieve newly created account'); + } + + // Set the parent record to the current record_id if no parent was provided + + if(! $parent) { + $r = q("UPDATE account SET account_parent = %d WHERE account_id = %d", + intval($result['account']['account_id']), + intval($result['account']['account_id']) + ); + if(! $r) { + logger('create_account: failed to set parent'); + } + $result['account']['parent'] = $result['account']['account_id']; + } + + $result['success'] = true; + //call_hooks('register_account',$result); + + return $result; +} + +/** + * @brief as far to see, email validation for register account verification + * @param array (account) + * @param array ('resend' => true, 'email' = > email) + * + */ function verify_email_address($arr) { + // $hash = random_string(24); + + // [hilmar -> + $reg = q("SELECT * FROM register WHERE reg_vital = 1 AND reg_email = 's%' ", + dbesc($arr['email']) + ); + if ( ! $reg) + return false; + + push_lang(($reg[0]['email']) ? $reg[0]['email'] : 'en'); + + $email_msg = replace_macros(get_intltext_template('register_verify_member.tpl'), + [ + '$sitename' => get_config('system','sitename'), + '$siteurl' => z_root(), + '$email' => $arr['email'], + '$uid' => 1, + '$hash' => $hash, + '$details' => '' + ] + ); + + $res = z_mail( + [ + 'toEmail' => $arr['email'], + 'messageSubject' => sprintf( t('Registration confirmation for %s'), get_config('system','sitename')), + 'textVersion' => $email_msg, + ] + ); + + pop_lang(); + + if(! $res) + logger('send_reg_approval_email: failed to account_id: ' . $arr['account']['account_id']); + + return $res; +} + +function verify_email_addressNOP($arr) { + if(array_key_exists('resend',$arr)) { $a = q("select * from account where account_email = '%s' limit 1", dbesc($arr['email']) @@ -269,11 +441,14 @@ function verify_email_address($arr) { return false; } $account = $a[0]; - $v = q("select * from register where uid = %d and password = 'verify' limit 1", + // [hilmar -> + $v = q("SELECT * FROM register WHERE reg_uid = %d AND reg_vital = 1 " + . " AND reg_pass = 'verify' LIMIT 1", intval($account['account_id']) ); + // <- hilmar] if($v) { - $hash = $v[0]['hash']; + $hash = $v[0]['reg_hash']; } else { return false; @@ -282,13 +457,16 @@ function verify_email_address($arr) { else { $hash = random_string(24); - q("INSERT INTO register ( hash, created, uid, password, lang ) VALUES ( '%s', '%s', %d, '%s', '%s' ) ", + // [hilmar -> + q("INSERT INTO register ( reg_hash, reg_created, reg_uid, reg_pass, reg_lang, reg_stuff ) " + ." VALUES ( '%s', '%s', %d, '%s', '%s', '' ) ", dbesc($hash), dbesc(datetime_convert()), intval($arr['account']['account_id']), dbesc('verify'), dbesc($arr['account']['account_language']) ); + // <- hilmar] $account = $arr['account']; } @@ -345,11 +523,17 @@ function send_reg_approval_email($arr) { $hash = random_string(); - $r = q("INSERT INTO register ( hash, created, uid, password, lang ) VALUES ( '%s', '%s', %d, '%s', '%s' ) ", + // [hilmar -> + // code before fetches the $admins as recipients for the approval request mail + // $arr has a user (self registered) account + // ... $arr['email'] ??? + // ... reg expiration ? + $r = q("INSERT INTO register ( reg_hash, reg_email, reg_created, reg_uid, reg_pass, reg_lang, reg_stuff )" + . " VALUES ( '%s', '%s', '%s', %d, '', '%s', '' ) ", dbesc($hash), + dbesc($arr['account']['account_email']), dbesc(datetime_convert()), intval($arr['account']['account_id']), - dbesc(''), dbesc($arr['account']['account_language']) ); @@ -423,7 +607,7 @@ function account_allow($hash) { $ret = array('success' => false); - $register = q("SELECT * FROM register WHERE hash = '%s' LIMIT 1", + $register = q("SELECT * FROM register WHERE reg_hash = '%s' LIMIT 1", dbesc($hash) ); @@ -431,57 +615,89 @@ function account_allow($hash) { return $ret; $account = q("SELECT * FROM account WHERE account_id = %d LIMIT 1", - intval($register[0]['uid']) + intval($register[0]['reg_uid']) ); + // a register entry without account assigned to if(! $account) return $ret; - q("DELETE FROM register WHERE hash = '%s'", - dbesc($register[0]['hash']) + // [hilmar -> + + q("START TRANSACTION"); + //q("DELETE FROM register WHERE reg_hash = '%s'", + // dbesc($register[0]['reg_hash']) + //); + $r1 = q("UPDATE register SET reg_vital = 0 WHERE reg_hash = '%s'", + dbesc($register[0]['reg_hash']) ); - q("update account set account_flags = (account_flags & ~%d) where (account_flags & %d)>0 and account_id = %d", + /* instead of ... + + // unblock + q("UPDATE account SET account_flags = (account_flags & ~%d) " + . " WHERE (account_flags & %d)>0 AND account_id = %d", intval(ACCOUNT_BLOCKED), intval(ACCOUNT_BLOCKED), - intval($register[0]['uid']) + intval($register[0]['reg_uid']) ); - q("update account set account_flags = (account_flags & ~%d) where (account_flags & %d)>0 and account_id = %d", + // unpend + q("UPDATE account SET account_flags = (account_flags & ~%d) " + . " WHERE (account_flags & %d)>0 AND account_id = %d", intval(ACCOUNT_PENDING), intval(ACCOUNT_PENDING), - intval($register[0]['uid']) + intval($register[0]['reg_uid']) ); - push_lang($register[0]['lang']); + */ + // together unblock and unpend + $r2 = q("UPDATE account SET account_flags = %d WHERE account_id = %d", + intval($account['account_flags'] + &= $account['account_flags'] ^ (ACCOUNT_BLOCKED | ACCOUNT_PENDING)), + intval($register[0]['reg_uid']) + ); + + if($r1 && $r2) { + q("COMMIT"); - $email_tpl = get_intltext_template("register_open_eml.tpl"); - $email_msg = replace_macros($email_tpl, array( - '$sitename' => get_config('system','sitename'), - '$siteurl' => z_root(), - '$username' => $account[0]['account_email'], - '$email' => $account[0]['account_email'], - '$password' => '', - '$uid' => $account[0]['account_id'] - )); + // <- hilmar] - $res = z_mail( - [ - 'toEmail' => $account[0]['account_email'], - 'messageSubject' => sprintf( t('Registration details for %s'), get_config('system','sitename')), - 'textVersion' => $email_msg, - ] - ); + push_lang($register[0]['reg_lang']); - pop_lang(); + $email_tpl = get_intltext_template("register_open_eml.tpl"); + $email_msg = replace_macros($email_tpl, array( + '$sitename' => get_config('system','sitename'), + '$siteurl' => z_root(), + '$username' => $account[0]['account_email'], + '$email' => $account[0]['account_email'], + '$password' => '', + '$uid' => $account[0]['account_id'] + )); - if(get_config('system','auto_channel_create')) - auto_channel_create($register[0]['uid']); + $res = z_mail( + [ + 'toEmail' => $account[0]['account_email'], + 'messageSubject' => sprintf( t('Registration details for %s'), get_config('system','sitename')), + 'textVersion' => $email_msg, + ] + ); - if ($res) { - info( t('Account approved.') . EOL ); - return true; + pop_lang(); + + if(get_config('system','auto_channel_create')) + auto_channel_create($register[0]['uid']); + + if ($res) { + info( t('Account approved.') . EOL ); + return true; + } + + // [hilmar -> + } else { + q("ROLLBACK"); } + // <- hilmar] } @@ -498,42 +714,65 @@ function account_allow($hash) { function account_deny($hash) { - $register = q("SELECT * FROM register WHERE hash = '%s' LIMIT 1", + // [hilmar-> + $register = q("SELECT * FROM register WHERE reg_hash = '%s' AND reg_vital = 1 LIMIT 1", dbesc($hash) ); + // <-hilmar] if(! count($register)) return false; $account = q("SELECT account_id, account_email FROM account WHERE account_id = %d LIMIT 1", - intval($register[0]['uid']) + intval($register[0]['reg_uid']) ); if(! $account) return false; - q("DELETE FROM account WHERE account_id = %d", - intval($register[0]['uid']) - ); + // [hilmar -> + q("START TRANSACTION"); - q("DELETE FROM register WHERE id = %d", - dbesc($register[0]['id']) + $r1 = q("DELETE FROM account WHERE account_id = %d", + intval($register[0]['reg_uid']) + ); + // q("DELETE FROM register WHERE reg_id = %d", + // dbesc($register[0]['reg_id']) + //); + $r2 = q("UPDATE register SET reg_vital = 0 WHERE reg_id = %d AND reg_vital = 1", + dbesc($register[0]['reg_id']) ); - notice( sprintf(t('Registration revoked for %s'), $account[0]['account_email']) . EOL); - return true; + if($r1 && $r2) { + q("COMMIT"); + notice( 'ZAR0512I,' . sprintf( t('Registration revoked for %s'), + $account[0]['account_email']) . EOL); + return true; -} + } else { -// called from regver to activate an account from the email verification link + q("ROLLBACK"); + notice( 'ZAR0513F,' . sprintf( t('Could not revoke registration for %s'), + $account[0]['account_email']) . EOL); + return false; + } + // <- hilmar] +} +/** + * called from Regver to allow/revoke an account + * Use case is under REGISTER_OPEN with APPROVAL + * Ref Regver, Email_validation, Email_resend + * ZAR052+ + */ function account_approve($hash) { $ret = false; // Note: when the password in the register table is 'verify', the uid actually contains the account_id + // hmm - $register = q("SELECT * FROM register WHERE hash = '%s' and password = 'verify' LIMIT 1", + $register = q("SELECT * FROM register WHERE reg_hash = '%s' and reg_pass = 'verify' LIMIT 1", dbesc($hash) ); @@ -541,45 +780,58 @@ function account_approve($hash) { return $ret; $account = q("SELECT * FROM account WHERE account_id = %d LIMIT 1", - intval($register[0]['uid']) + intval($register[0]['reg_uid']) ); if(! $account) return $ret; - q("DELETE FROM register WHERE hash = '%s' and password = 'verify'", - dbesc($register[0]['hash']) + // tr ? + + q("DELETE FROM register WHERE reg_hash = '%s' and reg_pass = 'verify'", + dbesc($register[0]['reg_hash']) ); q("update account set account_flags = (account_flags & ~%d) where (account_flags & %d)>0 and account_id = %d", intval(ACCOUNT_BLOCKED), intval(ACCOUNT_BLOCKED), - intval($register[0]['uid']) + intval($register[0]['reg_uid']) ); q("update account set account_flags = (account_flags & ~%d) where (account_flags & %d)>0 and account_id = %d", intval(ACCOUNT_PENDING), intval(ACCOUNT_PENDING), - intval($register[0]['uid']) + intval($register[0]['reg_uid']) ); q("update account set account_flags = (account_flags & ~%d) where (account_flags & %d)>0 and account_id = %d", intval(ACCOUNT_UNVERIFIED), intval(ACCOUNT_UNVERIFIED), - intval($register[0]['uid']) + intval($register[0]['reg_uid']) ); + /* + // together unblock unpend and verified + q("UPDATE account SET account_flags = %d WHERE account_id = %d", + intval($account['account_flags'] + &= $account['account_flags'] + ^ (ACCOUNT_BLOCKED | ACCOUNT_PENDING | ACCOUNT_UNVERIFIED)), + intval($register[0]['reg_uid']) + ); + */ + + // get a fresh copy after we've modified it. $account = q("SELECT * FROM account WHERE account_id = %d LIMIT 1", - intval($register[0]['uid']) + intval($register[0]['reg_uid']) ); if(! $account) return $ret; if(get_config('system','auto_channel_create')) - auto_channel_create($register[0]['uid']); + auto_channel_create($register[0]['reg_uid']); else { $_SESSION['login_return_url'] = 'new_channel'; authenticate_success($account[0],null,true,true,false,true); @@ -589,6 +841,118 @@ function account_approve($hash) { } +function verify_register_scheme() { + + $dbc = db_columns('register'); + if ($dbc) { + + if ($dbc[0]=='id') { + // v1 format + q("START TRANSACTION"); + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $r1 = q("ALTER TABLE register RENAME TO register100;"); + + $r2 = q("CREATE TABLE register (" + . "reg_id serial NOT NULL," + . "reg_vital int DEFAULT 1 NOT NULL," + . "reg_flags bigint DEFAULT 0 NOT NULL," + . "reg_didx char(1) DEFAULT '' NOT NULL," + . "reg_did2 text DEFAULT '' NOT NULL," + . "reg_hash text DEFAULT '' NOT NULL," + . "reg_email text DEFAULT '' NOT NULL," + . "reg_created timestamp NOT NULL," + . "reg_startup timestamp NOT NULL," + . "reg_expires timestamp NOT NULL," + . "reg_byc bigint DEFAULT 0 NOT NULL," + . "reg_uid bigint DEFAULT 0 NOT NULL," + . "reg_atip text DEFAULT '' NOT NULL," + . "reg_pass text DEFAULT '' NOT NULL," + . "reg_lang varchar(16) DEFAULT '' NOT NULL," + . "reg_stuff text NOT NULL," + . "PRIMARY KEY (reg_id) );" + ); + $r0 = q("CREATE INDEX ix_reg_vital ON register (reg_vital);"); + $r0 = q("CREATE INDEX ix_reg_flags ON register (reg_flags);"); + $r0 = q("CREATE INDEX ix_reg_didx ON register (reg_didx);"); + $r0 = q("CREATE INDEX ix_reg_did2 ON register (reg_did2);"); + $r0 = q("CREATE INDEX ix_reg_hash ON register (reg_hash);"); + $r0 = q("CREATE INDEX ix_reg_email ON register (reg_email);"); + $r0 = q("CREATE INDEX ix_reg_created ON register (reg_created);"); + $r0 = q("CREATE INDEX ix_reg_startup ON register (reg_startup);"); + $r0 = q("CREATE INDEX ix_reg_expires ON register (reg_expires);"); + $r0 = q("CREATE INDEX ix_reg_byc ON register (reg_byc);"); + $r0 = q("CREATE INDEX ix_reg_uid ON register (reg_uid);"); + $r0 = q("CREATE INDEX ix_reg_atip ON register (reg_atip);"); + + $r3 = q("INSERT INTO register (reg_id, reg_hash, reg_created, reg_uid, reg_pass, reg_lang, reg_stuff) " + . "SELECT id, hash, created, uid, password, lang, '' FROM register100;"); + + $r4 = q("DROP TABLE register100"); + + } + else { + $r1 = q("RENAME TABLE register TO register100;"); + + $r2 = q("CREATE TABLE IF NOT EXISTS register (" + . "reg_id int(10) UNSIGNED NOT NULL AUTO_INCREMENT," + . "reg_vital int(10) UNSIGNED NOT NULL DEFAULT 1," + . "reg_flags int(10) UNSIGNED NOT NULL DEFAULT 0," + . "reg_didx char(1) NOT NULL DEFAULT ''," + . "reg_did2 char(191) NOT NULL DEFAULT ''," + . "reg_hash char(191) NOT NULL DEFAULT ''," + . "reg_email char(191) NOT NULL DEFAULT ''," + . "reg_created datetime NOT NULL DEFAULT '0001-01-01 00:00:00'," + . "reg_startup datetime NOT NULL DEFAULT '0001-01-01 00:00:00'," + . "reg_expires datetime NOT NULL DEFAULT '0001-01-01 00:00:00'," + . "reg_byc int(10) UNSIGNED NOT NULL DEFAULT 0 ," + . "reg_uid int(10) UNSIGNED NOT NULL DEFAULT 0 ," + . "reg_atip char(191) NOT NULL DEFAULT ''," + . "reg_pass char(191) NOT NULL DEFAULT ''," + . "reg_lang char(16) NOT NULL DEFAULT ''," + . "reg_stuff text NOT NULL," + . "PRIMARY KEY (reg_id)," + . "KEY ix_reg_hash (reg_hash)," + . "KEY ix_reg_vital (reg_vital)," + . "KEY ix_reg_flags (reg_flags)," + . "KEY ix_reg_didx (reg_didx)," + . "KEY ix_reg_did2 (reg_did2)," + . "KEY ix_reg_email (reg_email)," + . "KEY ix_reg_created (reg_created)," + . "KEY ix_reg_startup (reg_startup)," + . "KEY ix_reg_expires (reg_expires)," + . "KEY ix_reg_byc (reg_byc)," + . "KEY ix_reg_uid (reg_uid)," + . "KEY ix_reg_atip (reg_atip)" + . ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;" + ); + + $r3 = q("INSERT INTO register (reg_id, reg_hash, reg_created, reg_uid, reg_pass, reg_lang, reg_stuff) " + . "SELECT id, hash, created, uid, password, lang, '' FROM register100;"); + + $r4 = q("DROP TABLE register100"); + } + + // $r = ($r1 && $r2 && $r3 && $r4); + // the only important + $r = $r2; + + if($r) { + q("COMMIT"); + return UPDATE_SUCCESS; + } + + q("ROLLBACK"); + return UPDATE_FAILED; + } + elseif ( count($dbc) != 16 ) { + // ffu + // fields in v2.0.0 = 16 + } + } +} + + /** * @brief Checks for accounts that have past their expiration date. * @@ -824,3 +1188,66 @@ function get_account_techlevel($account_id = 0) { return (5); } + +function zar_log($msg='') { + file_put_contents('./zar.log', + date('Y-m-d_H:i:s') . ' ' . $msg . ', ip: § ' . $_SERVER['REMOTE_ADDR'] . ' §' . "\n", FILE_APPEND); + return; +} + +function zar_reg_mail($reonar=false) { + if ($reonar) { + $zem = z_mail( + [ + 'toEmail' => $reonar['to'], + 'fromName' => ' ', + 'fromEmail' => $reonar['from'], + 'messageSubject' => $reonar['subject'], + 'textVersion' => $reonar['txttemplate'], + ] + ); + return $zem; + } +} + +/** + * ckeck current day and time against register duties + * + * @author Hilmar Runge + * @since 2020-02-25 + * @param the current date and time is taken as default + * @return ['isduty'] true/false + * ['nowfmt'] the textmsg about the current state + * ['atform'] the disabled html attribute for form input fields + * + */ +function zar_register_dutystate( $now=NULL, $day=NULL ) { + + is_null($now) ? $now = date('Hi') : ''; + is_null($day) ? $day = date('N') : ''; + + $isduty = zarIsDuty($day, $now, 'isOpen'); + + if ( $isduty === false ) { + return array( 'isduty' => $isduty, 'nowfmt' => '', 'atform' => '' ); + } + + $dutyis = $isduty ? t('open') : t('closed'); + $atform = $isduty ? '' : 'disabled'; + + $nowfmt = t('Registration is currently') + . ' ('.substr($now,0,2) . ':' . substr($now,-2) . ') ' + . ' ' . $dutyis; + + if (!$isduty) { + $pernext = zarIsDuty($day, $now, 'nextOpen'); + + if (is_array($pernext)) + $nowfmt .= '. ' . t('Next opens') . ' ' + . ucfirst( array('','mo','tu','we','th','fr','sa','so')[$pernext[0]]) . ' ' + . substr($pernext[1],0,2) . ':' . substr($pernext[1],-2); + } + return array( 'isduty' => $isduty, 'nowfmt' => $nowfmt, 'atform' => $atform); + +} + diff --git a/include/datetime.php b/include/datetime.php index ef0927ea4..0b8722b4f 100644 --- a/include/datetime.php +++ b/include/datetime.php @@ -534,3 +534,35 @@ function update_birthdays() { } } } + +/** + * + * Calculate a due by interval + * based on the current datetime the interval is added/subtracted + * @author Hilmar Runge + * @since 2020-02-20 + * @param $duri the interval in the format n[n]i + * where n is a 1-2 digit numeric amount and i is a unit + * example $duri='1w' represents one week + * unit may be one of i(minutes), h(hours), d(days), w(weeks), m(months, y(years)) + * @return array['due'] computed datetime in format 'Y-m-d H:i:s' + * ['durn'] the amount + * ['duru'] the unit + * or false + */ + function calculate_adue($duri=false, $sign='+') { + if ( preg_match( '/^[0-9]{1,2}[ihdwmy]{1}$/', $duri ) && ($sign == '+' || $sign == '-') ) { + $duru = substr( $duri, -1); + $durn = substr( $duri, 0, -1); + $due = date( 'Y-m-d H:i:s', strtotime( + '+' . $durn . ' ' + . str_replace( array(':i',':h',':d',':w',':m',':y'), + array('minutes', 'hours', 'days', 'weeks', 'months', 'years'), + ( ':'. $duru ) + ) + ) + ); + return array( 'durn' => $durn, 'duru' => $duru, 'due' => $due); + } + return false; + } diff --git a/include/security.php b/include/security.php index c9df00f1e..97bf002d8 100644 --- a/include/security.php +++ b/include/security.php @@ -835,3 +835,106 @@ function stream_perms_xchans($perms = NULL ) { return $str; } + + +/** + * Duty day / time checks for account register + * @author hilmar runge + * @since 2020.02.10 + * @param $op what to test: isOpen, nextOpen + * @param $wd weekday according ISO-8601 (1 monday, 7 sunday) + * @param $hhmm a 24h clock value hours and minutes + * if no params are given, the values are taken from the current time + * return is bool(false) if register_duty is not available + */ +function zarIsDuty($wd=NULL, $hhmm=NULL, $op='isOpen') { + + $isduty = get_config('system', 'register_duty_jso'); + + if (!$isduty) + return (bool)false; + + is_null($wd) ? $wd = date('N') : ''; + is_null($hhmm) ? $hhmm = date('Hi') : ''; + + if (!intval($wd . $hhmm)) return (bool)false; + + // be sure to have a valid weekday as index + $wd = (7 + $wd) % 7; + $wd === 0 ? $wd = 7 : ''; + + $duty = json_decode($isduty, true); + if (!$duty) + return (bool)false; + + switch ($op) { + case 'isOpen': + /** + * Check if registration is open + * @return int(0) for not close (open) or int(1) for closed. + * return is bool(false) if register_duty is not available + */ + if (!$duty[$wd]) return (bool)false; + $dutyis = 0; + foreach ($duty[$wd] as $o => $tf) { + if ($o > $hhmm) { + $dutyis = $tf; + break; + } + } + return $dutyis; + break; + + case 'nextOpen': + /** + * Look for next period opens + * @return "=>N =>Hi" date value of the next period register is open for requests + * where N is a weekday (1=monday ... 7=sunday) according ISO-8601 + * where Hi is a 24h clock value hhmm by hours and minutes. + * If no next period open is available, return results to false. + */ + $myd = $wd; + $myh = $hhmm; + $is1 = false; + + // $myd = "5"; // testcase only + // $myh = "1110"; // testcase only + + // a 1st match may be applied below my time and is to see as a cycle to the next week + // but looking is also for a open time after my time is available this week + foreach ($duty as $dd => $dhs) { + + if ($is1 && $dd < $myd) + continue; + + foreach ($dhs as $dh => $tf) { + + if ($tf) continue; // close + + // a weeks 1st open + if (!$is1) $is1 = array($dd, $dh); + + // but is a match after now? + //if ($dd == $myd && $myh >= $dh && $myh <= $dh) continue; + + // if the day is not (more) today start find early morning + if ($dd > $myd) $myh = "0000"; + + // a next period after now in the remainder of the week + if ($dd >= $myd && $dh >= $myh && !$tf) + return array($dd, $dh); + else + continue; + } + } + return $is1; // false or array + break; + + default: + // + break; + } + +} + + diff --git a/include/text.php b/include/text.php index d6b196f1e..96e5c7882 100644 --- a/include/text.php +++ b/include/text.php @@ -2313,6 +2313,18 @@ function undo_post_tagging($s) { return $s; } +/** + * @brief php to js string transfer + * Hilmar, 20200227 + * String values built in php for using as content for js variables become sanitized. Often required + * in cases, where some content will be translated by t(any text...) and is furthermore transfered via + * templates to the outputted html stream. Redecoding in js not required nor useful. + * Apply like: p2j(t('any text\nI will place on a "next line"')); + */ +function p2j($string) { + return preg_replace('/\r?\n/', '\\n', addslashes($string)); +} + function quote_tag($s) { if(strpos($s,' ') !== false) return '"' . $s . '"'; diff --git a/install/schema_mysql.sql b/install/schema_mysql.sql index 977d26232..17ad1767e 100644 --- a/install/schema_mysql.sql +++ b/install/schema_mysql.sql @@ -1097,16 +1097,35 @@ CREATE TABLE IF NOT EXISTS `profile_check` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `register` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `hash` char(191) NOT NULL DEFAULT '', - `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00', - `uid` int(10) unsigned NOT NULL DEFAULT 0 , - `password` char(191) NOT NULL DEFAULT '', - `lang` char(16) NOT NULL DEFAULT '', - PRIMARY KEY (`id`), - KEY `hash` (`hash`), - KEY `created` (`created`), - KEY `uid` (`uid`) + `reg_id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `reg_vital` int(10) unsigned NOT NULL DEFAULT 1, + `reg_flags` int(10) unsigned NOT NULL DEFAULT 0, + `reg_didx` char(1) NOT NULL DEFAULT '', + `reg_did2` char(191) NOT NULL DEFAULT '', + `reg_hash` char(191) NOT NULL DEFAULT '', + `reg_email` char(191) NOT NULL DEFAULT '', + `reg_created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00', + `reg_startup` datetime NOT NULL DEFAULT '0001-01-01 00:00:00', + `reg_expires` datetime NOT NULL DEFAULT '0001-01-01 00:00:00', + `reg_byc` int(10) unsigned NOT NULL DEFAULT 0 , + `reg_uid` int(10) unsigned NOT NULL DEFAULT 0 , + `reg_atip` char(191) NOT NULL DEFAULT '', + `reg_pass` char(191) NOT NULL DEFAULT '', + `reg_lang` char(16) NOT NULL DEFAULT '', + `reg_stuff` text NOT NULL, + PRIMARY KEY (`reg_id`), + KEY `ix_reg_vital` (`reg_vital`), + KEY `ix_reg_flags` (`reg_flags`), + KEY `ix_reg_didx` (`reg_didx`), + KEY `ix_reg_did2` (`reg_did2`), + KEY `ix_reg_hash` (`reg_hash`), + KEY `ix_reg_email` (`reg_email`), + KEY `ix_reg_created` (`reg_created`), + KEY `ix_reg_startup` (`reg_startup`), + KEY `ix_reg_expires` (`reg_expires`), + KEY `ix_reg_byc` (`reg_byc`), + KEY `ix_reg_uid` (`reg_uid`), + KEY `ix_reg_atip` (`reg_atip`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `session` ( diff --git a/install/schema_postgres.sql b/install/schema_postgres.sql index c04ba9c67..cdca79cfc 100644 --- a/install/schema_postgres.sql +++ b/install/schema_postgres.sql @@ -1092,17 +1092,37 @@ create index "pc_sec" on profile_check ("sec"); create index "pc_expire" on profile_check ("expire"); CREATE TABLE "register" ( - "id" serial NOT NULL, - "hash" text NOT NULL, - "created" timestamp NOT NULL, - "uid" bigint NOT NULL, - "password" text NOT NULL, - "lang" varchar(16) NOT NULL, - PRIMARY KEY ("id") -); -create index "reg_hash" on register ("hash"); -create index "reg_created" on register ("created"); -create index "reg_uid" on register ("uid"); + "reg_id" serial NOT NULL, + "reg_vital" int DEFAULT 1 NOT NULL, + "reg_flags_" bigint DEFAULT 0 NOT NULL, + "reg_didx" char(1) DEFAULT '' NOT NULL, + "reg_did2" text DEFAULT '' NOT NULL, + "reg_hash" text DEFAULT '' NOT NULL, + "reg_email" text DEFAULT '' NOT NULL, + "reg_created" timestamp NOT NULL, + "reg_startup" timestamp NOT NULL, + "reg_expires" timestamp NOT NULL, + "reg_byc" bigint DEFAULT 0 NOT NULL, + "reg_uid" bigint DEFAULT 0 NOT NULL, + "reg_atip" text DEFAULT '' NOT NULL, + "reg_pass" text DEFAULT '' NOT NULL, + "reg_lang" varchar(16) DEFAULT '' NOT NULL, + "reg_stuff" text NOT NULL, + PRIMARY KEY ("reg_id") +); +create index "ix_reg_vital" on register ("reg_vital"); +create index "ix_reg_flags" on register ("reg_flags"); +create index "ix_reg_didx" on register ("reg_didx"); +create index "ix_reg_did2" on register ("reg_did2"); +create index "ix_reg_hash" on register ("reg_hash"); +create index "ix_reg_email" on register ("reg_email"); +create index "ix_reg_created" on register ("reg_created"); +create index "ix_reg_startup" on register ("reg_startup"); +create index "ix_reg_expires" on register ("reg_expires"); +create index "ix_reg_byc" on register ("reg_byc"); +create index "ix_reg_uid" on register ("reg_uid"); +create index "ix_reg_atip" on register ("reg_atip"); + CREATE TABLE "session" ( "id" serial, "sid" text NOT NULL, diff --git a/view/css/mod_admin.css b/view/css/mod_admin.css index 409744402..8f1b98b61 100644 --- a/view/css/mod_admin.css +++ b/view/css/mod_admin.css @@ -70,3 +70,7 @@ margin-top: 0px !important; margin-left: 0px !important; } + +tr.zebra9 { background-color: #eafaf1; } +tr.zebra0 { background-color: #fbeee6; } +tr.zebra1 { background-color: #fef9e7; } diff --git a/view/js/mod_register.js b/view/js/mod_register.js index 6607579a2..ba3c4cab3 100644 --- a/view/js/mod_register.js +++ b/view/js/mod_register.js @@ -1,5 +1,8 @@ $(document).ready(function() { - $("#id_email").blur(function() { + typeof(window.tao) == 'undefined' ? window.tao = {} : ''; + tao.zar = { vsn: '2.0.0' }; + + $("#id_emailNOP").blur(function() { var zreg_email = $("#id_email").val(); $.get("register/email_check.json?f=&email=" + encodeURIComponent(zreg_email), function(data) { $("#help_email").html(data.message); diff --git a/view/tpl/admin_accounts.tpl b/view/tpl/admin_accounts.tpl index 2dd56c8dc..8d6506184 100755 --- a/view/tpl/admin_accounts.tpl +++ b/view/tpl/admin_accounts.tpl @@ -1,15 +1,3 @@ -

{{$title}} - {{$page}}

@@ -17,6 +5,7 @@

{{$h_pending}}

+ {{if $debug}}
{{$debug}}
{{/if}} {{if $pending}} @@ -27,20 +16,34 @@ - {{foreach $pending as $u}} - - - - + {{foreach $pending as $n => $u}} + + + + + + {{/foreach}}
{{$u.account_created}}
{{$u.reg_created}}
{{$u.reg_n}} + {{if $u.reg_expires < $now}} ★EXPIRED★{{/if}}
{{$u.reg_startup}}
{{$u.reg_expires}}
- - + + +
- + {{* before, alternate: + * + {{$select_all}} + * + *}} +
+ {{$sel_aprv}} ◄► + {{$sel_deny}} ◄► + {{$sel_tall}} +

+
@@ -83,7 +86,11 @@ {{/foreach}} + + + {{* + *}}
@@ -93,3 +100,63 @@ {{/if}}
+{{* + COMMENTS for this template: + hilmar, 2020.01 + script placed at the end +*}} + diff --git a/view/tpl/admin_site.tpl b/view/tpl/admin_site.tpl index 8d32ba9a4..4896a2b26 100755 --- a/view/tpl/admin_site.tpl +++ b/view/tpl/admin_site.tpl @@ -1,39 +1,3 @@ -

{{$title}} - {{$page}}

@@ -69,10 +33,19 @@ {{include file="field_input.tpl" field=$register_text}} {{include file="field_select_grouped.tpl" field=$role}} {{include file="field_select.tpl" field=$register_policy}} + {{include file="register_duty.tpl" field=$register_duty}} + {{include file="field_input.tpl" field=$register_perday}} + {{include file="field_input.tpl" field=$register_sameip}} + {{$reg_delay}} + {{$reg_expire}} + {{include file="field_checkbox.tpl" field=$reg_autochannel}} {{include file="field_checkbox.tpl" field=$invite_only}} + {{include file="field_checkbox.tpl" field=$invite_also}} {{include file="field_input.tpl" field=$minimum_age}} {{include file="field_select.tpl" field=$access_policy}} {{include file="field_input.tpl" field=$location}} + {{include file="field_select.tpl" field=$access_policy}} + {{include file="field_input.tpl" field=$minimum_age}} {{include file="field_input.tpl" field=$sellpage}} {{include file="field_input.tpl" field=$first_page}} @@ -115,3 +88,51 @@
+{{* + COMMENTS for this template: + hilmar, 2020.01 + script placed at the end +*}} + diff --git a/view/tpl/invite.tpl b/view/tpl/invite.tpl index 440e1e02b..2712cfee4 100755 --- a/view/tpl/invite.tpl +++ b/view/tpl/invite.tpl @@ -1,6 +1,7 @@
-

{{$invite}}

+

{{$invite}}

+

{{$lcclane}}

@@ -8,21 +9,136 @@ +
{{$ihave}}
{{$wehave}}
+ + +
- - + + + + + {{$m10}}({{$n10}}) + check
+ +
+ {{$inv_expire}}
-
- - +
+ +
+
{{$subject_label}} + {{$subject}} +
+ +
+ + +
+
{{$m13}}
({{$n13}}) {{$tplin}}
+
+      {{$standard_message}}
+      
+
+      {{$due}}
+      
+
- +
+ + diff --git a/view/tpl/plain.tpl b/view/tpl/plain.tpl new file mode 100644 index 000000000..9af76718e --- /dev/null +++ b/view/tpl/plain.tpl @@ -0,0 +1,3 @@ +

{{$title}}

+{{if $now}}
{{$now}}
{{/if}} +
{{$infos}}
diff --git a/view/tpl/regate.tpl b/view/tpl/regate.tpl new file mode 100644 index 000000000..38135d424 --- /dev/null +++ b/view/tpl/regate.tpl @@ -0,0 +1,23 @@ +

{{$title}}

+ +

{{$now}}

+ +

{{$desc}}

+ +
+ +{{include file="field_input.tpl" field=[$acpin.0,$acpin.1,"","","",$atform]}} + +
+ +
+ +{{if $resend > ''}} +
+ +
+{{/if}} + +
+
+ diff --git a/view/tpl/register.tpl b/view/tpl/register.tpl index 1054c7567..451a16c2a 100755 --- a/view/tpl/register.tpl +++ b/view/tpl/register.tpl @@ -9,6 +9,7 @@
{{$reg_is}}
{{$other_sites}}
+

{{$now}}

{{/if}} @@ -19,14 +20,19 @@ {{/if}} {{if $invitations}} - {{include file="field_input.tpl" field=$invite_code}} +
+ {{$haveivc}} +
+ {{/if}} - {{include file="field_input.tpl" field=$email}} + {{include file="field_input.tpl" field=[$email.0,$email.1,"",$email.3,"",""]}} - {{include file="field_password.tpl" field=$pass1}} + {{include file="field_password.tpl" field=[$pass1.0,$pass1.1,"","","",$atform]}} - {{include file="field_password.tpl" field=$pass2}} + {{include file="field_password.tpl" field=[$pass2.0,$pass2.1,"","","",$atform]}} {{if $auto_create}} {{if $default_role}} @@ -46,12 +52,12 @@ {{/if}} {{if $enable_tos}} - {{include file="field_checkbox.tpl" field=$tos}} + {{include file="field_checkbox.tpl" field=[$tos.0,$tos.1,"","","",$atform]}} {{else}} {{/if}} - +

@@ -59,3 +65,20 @@
+{{* + COMMENTS for this template: + hilmar, 2020.02 +*}} + diff --git a/view/tpl/register_duty.tpl b/view/tpl/register_duty.tpl new file mode 100644 index 000000000..d5a66e556 --- /dev/null +++ b/view/tpl/register_duty.tpl @@ -0,0 +1,28 @@ +{{include file="field_input.tpl" field=$register_duty}} +

+
\ No newline at end of file
-- 
cgit v1.2.3