diff options
Diffstat (limited to 'Zotlabs')
-rw-r--r-- | Zotlabs/Module/Admin.php | 9 | ||||
-rw-r--r-- | Zotlabs/Module/Admin/Accounts.php | 128 | ||||
-rw-r--r-- | Zotlabs/Module/Admin/Site.php | 404 | ||||
-rw-r--r-- | Zotlabs/Module/Invite.php | 599 | ||||
-rw-r--r-- | Zotlabs/Module/Regate.php | 352 | ||||
-rw-r--r-- | Zotlabs/Module/Register.php | 544 | ||||
-rw-r--r-- | Zotlabs/Module/Settings/Account.php | 34 | ||||
-rw-r--r-- | Zotlabs/Update/_1244.php | 15 |
8 files changed, 1807 insertions, 278 deletions
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..7073f026a 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,45 @@ 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 = ' . "'" . '<img src="' . z_root() . '/images/zapax16.gif">' . "';\n"; + + // better useability at the moment to tell all (ACCOUNT_PENDING >= 0) instead of (> 0 for those need approval) + $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 +246,7 @@ class Accounts { intval(\App::$pager['itemspage']), intval(\App::$pager['start']) ); - + // function _setup_users($e){ // $accounts = Array( // t('Normal Account'), @@ -163,12 +266,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 +294,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..879923132 100644 --- a/Zotlabs/Module/Admin/Site.php +++ b/Zotlabs/Module/Admin/Site.php @@ -5,14 +5,28 @@ namespace Zotlabs\Module\Admin; class Site { + // system cfgs + const ivo = 'invitation_only'; + const iva = 'invitation_also'; + /** * @brief POST handler for Admin Site Page. * */ 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 +38,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); - $invite_only = ((x($_POST,'invite_only')) ? True : False); + $reg_autochannel = ((x($_POST,'auto_channel_create')) ? True : False); + $invitation_only = ((x($_POST,'invitation_only')) ? True : False); + $invitation_also = ((x($_POST,'invitation_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 +91,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 +108,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 +151,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 +185,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','invitation_only', $invite_only); + set_config('system','auto_channel_create', $reg_autochannel); + set_config('system',self::ivo, $invitation_only); + set_config('system',self::iva, $invitation_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 +321,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 +350,68 @@ 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 + ) + ); + $invitation_only = get_config('system',self::ivo); + $invitation_also = get_config('system',self::iva); + + $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 +428,91 @@ 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 - <a href='#' id='cnftheme'>change theme settings</a>"), $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` ' + . ' <a id="zar083a" href="javascript:;">' . t('Parse and test your input') . '</a>'. 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'), + + '$invitation_only' => array(self::ivo, + ($invitation_only === false ? '✗' : '✓') . ' ' . t("Invitation only"), + $invitation_only, + t("Only allow new member registrations with an invitation code. Above register policy must be set to Yes."), + "", "", 'ZAR0880C'), + + '$invitation_also' => array(self::iva, + ($invitation_also === false ? '✗' : '✓') . ' ' . t("Invitation also"), + $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 +554,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 = '<b>' . t('Invite App') . ' (' . t('Not Installed') . '):</b><br>'; - $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='<span class="fa zai_fa zai_lccsym"></span>'; // alt + $tplsym='<span class="fa zai_fa"></span>'; + + // 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 + .= '<span class="fa zai_fa zai_lccsym' . $lcc2 . $lcc5 . $lccg . '"></span>' + . '<a href="javascript:;" class="zai_lcc' . $lcc2 . $lcc5 . $lccg . $hi . '">' . $lcc . '</a>'; + // 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.'<a href="javascript:;" id="zai-' . $t1 + . '" class="invites'.$hi.'">' . $t1 . '</a>'; + } + + // 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..cf6b76bfa --- /dev/null +++ b/Zotlabs/Module/Regate.php @@ -0,0 +1,352 @@ +<?php + +namespace Zotlabs\Module; + +require_once('include/security.php'); + +/** + * + * @version 2.0.0 + * @author hilmar runge + * @since 2020-03-03 + * Check verification pin + * input field email address + * input field pin (told during register) + * check duty + * check startup and expire + * compare email address + * check pin + * limited tries to enter the correct pin/pass 2 handle via f2b + * on success create account and update register + * + */ + + define ( 'REGISTER_AGREED', 0x0020 ); + define ( 'REGISTER_DENIED', 0x0040 ); + +class Regate extends \Zotlabs\Web\Controller { + + const MYP = 'ZAR'; //ZAR1x + const VERSION = '2.0.0'; + + + function post() { + + check_form_security_token_redirectOnErr('/', 'regate'); + + if ( argc() > 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)); + zar_log($msg . ' ' . $cra['account']['account_email'] + . ' ' . $cra['account']['account_language']); + $nextpage = 'new_channel'; + + $auto_create = (get_config('system','auto_channel_create') ? true : false); + + if($auto_create) { + // prepare channel creation + 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) { + // create channel + $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; + $msg = 'ZAR1239I ' . t('Channel successfull created') . ' ' . $did2; + } + else { + $msg = 'ZAR1239E ' . t('Channel still not created') . ' ' . $did2; + } + zar_log($msg . ' ' . $reonar['chan.did1'] . ' (' . $reonar['chan.name'] . ')'); + } + unset($_SESSION['login_return_url']); + } + else { + q("ROLLBACK"); + $msg = 'ZAR1238E ' . 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..b473cd91f 100644 --- a/Zotlabs/Module/Register.php +++ b/Zotlabs/Module/Register.php @@ -1,4 +1,5 @@ <?php + namespace Zotlabs\Module; use Zotlabs\Web\Controller; @@ -7,8 +8,13 @@ require_once('include/security.php'); class Register extends Controller { + const MYP = 'ZAR'; // ZAR0x + const VERSION = '2.0.0'; + function init() { - + + // ZAR0 + $result = null; $cmd = ((argc() > 1) ? argv(1) : ''); @@ -16,7 +22,7 @@ class Register extends Controller { // when they first need to register someplace. Once they've // created a channel, we'll try to revive the connection request // and process it. - + if($_REQUEST['connect']) $_SESSION['connect'] = $_REQUEST['connect']; @@ -43,25 +49,116 @@ 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 (most posts are not accepted during off duty). + * + */ + + + $act = q("SELECT COUNT(*) AS act FROM account")[0]['act']; + $duty = zar_register_dutystate(); + $is247 = false; + $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(); + + + // case when an invited prepares the own account by supply own pw, accept tos, prepage channel (if auto) + if ($email && $invite_code) { + + if ( preg_match('/^.{2,64}\@[a-z0-9.-]{4,32}\.[a-z]{2,12}$/', $email ) ) { + if ( preg_match('/^[a-z0-9]{12,12}$/', $invite_code ) ) { + $is247 = true; + } + } + + } + // 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]{5,10}$/', $email ) ) { + // dId2 A artifical & anonymous + goaway(z_root() . '/regate/' . bin2hex($email) . 'a' ); + } + + } + + + if ($act > 0 && !$is247 && !$duty['isduty']) { + // normally (except very 1st timr after install), that should never arrive here (ie js hack or sth like) + // log suitable for f2b also + $logmsg = 'ZAR0230S Unexpected registration request off duty'; + zar_log($logmsg); + goaway(z_root() . '/~'); + } + + if ($sameip && !$is247) { + $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 + // msg? + if ( !$is247 && self::check_reg_limits()['is'] ) return; + + // accept tos if(! x($_POST,'tos')) { - notice( t('Please indicate acceptance of the Terms of Service. Registration failed.') . EOL); + // msg! + 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'])) { + // msg! + 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'] ) ) { + // msg! + 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 +168,7 @@ class Register extends Controller { break; case REGISTER_APPROVE: - $flags = ACCOUNT_BLOCKED | ACCOUNT_PENDING; + $flags = ACCOUNT_PENDING; break; default: @@ -84,115 +181,242 @@ 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 ?? + + // transit ? + + // update reg vital 0 off + $icdone = q("UPDATE register SET reg_vital = 0 WHERE reg_id = %d ", + intval($reg['reg_id']) + ); + + $msg = 'ZAR0237I ' . t('Invitation code succesfully applied'); + zar_log($msg) . ', ' . $email; + // msg! + info($msg . EOL); + + $well = true; + + + } else { + // msg! + 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']) + ); + + zar_log( 'ZAR0239A ' . t('New register request') . ' d' . $didnew . ', ' + . $regdelay . ' - ' . $regexpire); + // 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); - } function get() { - + $registration_is = ''; $other_sites = ''; 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 +425,35 @@ 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('<a href="pubsites">Register at another affiliated hub.</a>'); + $registration_is = t('Registration on this hub is by approval only.') . '<sup>ZAR0131I</sup>'; + $other_sites = '<a href="pubsites">' . t('Register at another affiliated hub in case when prefered') . '</a>'; } + 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('<a href="pubsites">Register at another affiliated hub.</a>'); - } - - $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.') . '<sup>ZAR0132I</sup>'; + $other_sites = '<a href="pubsites">' . t('Register at another affiliated hub') . '</a>'; + } elseif (intval(get_config('system','invitation_also'))) { + $invitations = true; } + $opal = self::check_reg_limits(); + if ( $opal['is']) + $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 +476,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)') . ' <sup>ZAR0136I</sup>', + $emailval, + t('If the registation was already submitted with your data once ago, enter your identity (like email) here and submit') . '<sup>ZAR0133I</sup>' + ); + $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.') . ' <a href="help/member/member_guide#Channel_Permission_Roles" target="_blank">' . t('Read more about channel permission roles') . '</a>',$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.') + . ' <a href="help/member/member_guide#Channel_Permission_Roles" target="_blank">' + . t('Read more about channel permission roles') + . '</a>',$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'); @@ -273,13 +514,23 @@ class Register extends Controller { $o = replace_macros(get_markup_template('register.tpl'), array( + '$tao' => "typeof(window.tao) == 'undefined' ? window.tao = {} : '';\n" + . "tao.zar = { vsn: '2.0.0', form: {}, msg: {} };\n" + . "tao.zar.patano = /^d[0-9]{5,10}$/;\n" + . "tao.zar.patema = /^[a-z0-9.-]{2,64}@[a-z0-9.-]{4,32}\.[a-z]{2,12}$/;\n" + . "tao.zar.msg.ZAR0239E = '" . t('email mistake') . "';\n", + '$form_security_token' => get_form_security_token("register"), '$title' => t('Registration'), '$reg_is' => $registration_is, '$registertext' => bbcode(get_config('system','register_text')), '$other_sites' => $other_sites, + '$msg' => $opal['rn'] . ',' . $opal['an'], '$invitations' => $invitations, '$invite_code' => $invite_code, + '$haveivc' => t('I have an invite code') . '.<sup>ZAR0134I</sup>', + '$now' => $duty['nowfmt'], + '$atform' => $duty['atform'], '$auto_create' => $auto_create, '$name' => $name, '$role' => $role, @@ -288,15 +539,48 @@ 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.') . '<sup>ZAR0135I</sup>' : ''), )); return $o; - } - - + + function check_reg_limits() { + // check against register, account + $rear = array( 'is' => false, 'rn' => 0, 'an' => 0, 'msg' => '' ); + + $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') + ); + + $rear['is'] = ( $r && $r[0]['nr'] >= $max_dailies ) ? true : false; + $rear['rn'] = $r[0]['nr']; + + if ( !$rear['is']) { + $r = q("SELECT COUNT(account_id) AS nr FROM account WHERE account_created > %s - INTERVAL %s", + db_utcnow(), db_quoteinterval('1 day') + ); + + $rear['is'] = ( $r && ($r[0]['nr'] + $rear['rn']) >= $max_dailies ) ? true : false; + $rear['ra'] = $r[0]['nr']; + } + + if ( $rear['is']) { + $rear['msg'] = 'ZAR0333W ' . t('This site has exceeded the number of allowed daily account registrations'); + zar_log($msg); + $rear['is'] = true; + } + } + return $rear; + } } 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/_1244.php b/Zotlabs/Update/_1244.php new file mode 100644 index 000000000..0aebe4013 --- /dev/null +++ b/Zotlabs/Update/_1244.php @@ -0,0 +1,15 @@ +<?php + +namespace Zotlabs\Update; + +require_once('include/account.php'); + +class _1244 { + + function run() { + + return verify_register_scheme(); + + } + +} |