|
|
@@ -50,7 +50,8 @@ class Index extends Frontend
|
|
|
if ($token !== '') {
|
|
|
$user = $this->mprocLoadUserByToken($token);
|
|
|
if ($user) {
|
|
|
- $this->mprocTouchLoginState($user, $token);
|
|
|
+ $token = $this->mprocTouchLoginState($user, $token);
|
|
|
+ $user['token'] = $token;
|
|
|
|
|
|
return $user;
|
|
|
}
|
|
|
@@ -60,8 +61,8 @@ class Index extends Frontend
|
|
|
if (!$user) {
|
|
|
return null;
|
|
|
}
|
|
|
- $token = bin2hex(random_bytes(16));
|
|
|
- $this->mprocTouchLoginState($user, $token);
|
|
|
+ $token = $this->mprocPackSignedAuthToken($user);
|
|
|
+ $token = $this->mprocTouchLoginState($user, $token);
|
|
|
$user['token'] = $token;
|
|
|
|
|
|
return $user;
|
|
|
@@ -79,16 +80,49 @@ class Index extends Frontend
|
|
|
if ($token === null || $token === '') {
|
|
|
$token = $this->request->request('mproc_token', '');
|
|
|
}
|
|
|
- $token = preg_replace('/[^a-f0-9]/i', '', (string)$token);
|
|
|
+ $token = trim((string)$token);
|
|
|
+ if ($token === '') {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ if ($this->mprocIsSignedAuthToken($token)) {
|
|
|
+ return $token;
|
|
|
+ }
|
|
|
+ $token = preg_replace('/[^a-f0-9]/i', '', $token);
|
|
|
|
|
|
return strlen($token) >= 16 ? $token : '';
|
|
|
}
|
|
|
|
|
|
+ protected function mprocIsSignedAuthToken(string $token): bool
|
|
|
+ {
|
|
|
+ return strpos($token, '.') !== false
|
|
|
+ && preg_match('/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/', $token) === 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function mprocAuthCacheKey(string $token): string
|
|
|
+ {
|
|
|
+ return 'mproc_u_' . md5($token);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* @return array<string, mixed>|null
|
|
|
*/
|
|
|
protected function mprocLoadUserByToken(string $token): ?array
|
|
|
{
|
|
|
+ if ($this->mprocIsSignedAuthToken($token)) {
|
|
|
+ $user = $this->mprocUserFromSignedToken($token);
|
|
|
+ if (!$user) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (time() - (int)($user['login_time'] ?? 0) > $this->mprocTtlSeconds) {
|
|
|
+ $this->mprocClearLogin($token);
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ $user['token'] = $token;
|
|
|
+
|
|
|
+ return $user;
|
|
|
+ }
|
|
|
+
|
|
|
$user = Cache::get('mproc_u_' . $token);
|
|
|
if (!is_array($user)) {
|
|
|
$user = $this->mprocUserFromRememberCookie();
|
|
|
@@ -110,40 +144,59 @@ class Index extends Frontend
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 滑动续期:刷新 Cache / Session / Cookie(保留 7 天)
|
|
|
+ * 滑动续期:刷新签名令牌 / Cache / Session / Cookie(保留 7 天)
|
|
|
*
|
|
|
* @param array<string, mixed> $user
|
|
|
*/
|
|
|
- protected function mprocTouchLoginState(array $user, string $token): void
|
|
|
+ protected function mprocTouchLoginState(array $user, string $token): string
|
|
|
{
|
|
|
$user['login_time'] = time();
|
|
|
- Cache::set('mproc_u_' . $token, $user, $this->mprocTtlSeconds + 86400);
|
|
|
+ if ($this->mprocIsSignedAuthToken($token)) {
|
|
|
+ $token = $this->mprocPackSignedAuthToken($user);
|
|
|
+ }
|
|
|
+ Cache::set($this->mprocAuthCacheKey($token), $user, $this->mprocTtlSeconds + 86400);
|
|
|
+ if (!$this->mprocIsSignedAuthToken($token)) {
|
|
|
+ Cache::set('mproc_u_' . $token, $user, $this->mprocTtlSeconds + 86400);
|
|
|
+ }
|
|
|
Session::set('mproc_token', $token);
|
|
|
$this->mprocSetTokenCookie($token);
|
|
|
- $this->mprocSetRememberCookie($user);
|
|
|
+ $this->mprocSetRememberCookie($user, $token);
|
|
|
+
|
|
|
+ return $token;
|
|
|
}
|
|
|
|
|
|
- protected function mprocSetTokenCookie(string $token): void
|
|
|
+ /**
|
|
|
+ * @return array<string, mixed>
|
|
|
+ */
|
|
|
+ protected function mprocCookieOptions(): array
|
|
|
{
|
|
|
- Cookie::set('mproc_token', $token, [
|
|
|
+ $opts = [
|
|
|
'expire' => $this->mprocTtlSeconds,
|
|
|
'path' => '/',
|
|
|
'httponly' => true,
|
|
|
- 'samesite' => 'Lax',
|
|
|
- ]);
|
|
|
+ ];
|
|
|
+ if ($this->request->isSsl()) {
|
|
|
+ $opts['secure'] = true;
|
|
|
+ }
|
|
|
+ if (PHP_VERSION_ID >= 70300) {
|
|
|
+ $opts['samesite'] = 'Lax';
|
|
|
+ }
|
|
|
+
|
|
|
+ return $opts;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function mprocSetTokenCookie(string $token): void
|
|
|
+ {
|
|
|
+ Cookie::set('mproc_token', $token, $this->mprocCookieOptions());
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @param array<string, mixed> $user
|
|
|
*/
|
|
|
- protected function mprocSetRememberCookie(array $user): void
|
|
|
+ protected function mprocSetRememberCookie(array $user, ?string $token = null): void
|
|
|
{
|
|
|
- Cookie::set('mproc_remember', $this->mprocPackRememberCookie($user), [
|
|
|
- 'expire' => $this->mprocTtlSeconds,
|
|
|
- 'path' => '/',
|
|
|
- 'httponly' => true,
|
|
|
- 'samesite' => 'Lax',
|
|
|
- ]);
|
|
|
+ $val = $token !== null && $token !== '' ? $token : $this->mprocPackSignedAuthToken($user);
|
|
|
+ Cookie::set('mproc_remember', $val, $this->mprocCookieOptions());
|
|
|
}
|
|
|
|
|
|
protected function mprocAuthSignSecret(): string
|
|
|
@@ -159,7 +212,7 @@ class Index extends Frontend
|
|
|
/**
|
|
|
* @param array<string, mixed> $user
|
|
|
*/
|
|
|
- protected function mprocPackRememberCookie(array $user): string
|
|
|
+ protected function mprocPackSignedAuthToken(array $user): string
|
|
|
{
|
|
|
$payload = [
|
|
|
'uid' => (int)($user['customer_user_id'] ?? $user['customer_id'] ?? 0),
|
|
|
@@ -174,16 +227,18 @@ class Index extends Frontend
|
|
|
return $b64 . '.' . $sig;
|
|
|
}
|
|
|
|
|
|
+ /** @deprecated 使用 mprocPackSignedAuthToken */
|
|
|
+ protected function mprocPackRememberCookie(array $user): string
|
|
|
+ {
|
|
|
+ return $this->mprocPackSignedAuthToken($user);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* @return array<string, mixed>|null
|
|
|
*/
|
|
|
- protected function mprocUserFromRememberCookie(): ?array
|
|
|
+ protected function mprocUserFromSignedToken(string $raw): ?array
|
|
|
{
|
|
|
- $raw = Cookie::get('mproc_remember');
|
|
|
- if ($raw === null || $raw === '') {
|
|
|
- return null;
|
|
|
- }
|
|
|
- $parts = explode('.', (string)$raw, 2);
|
|
|
+ $parts = explode('.', trim($raw), 2);
|
|
|
if (count($parts) !== 2) {
|
|
|
return null;
|
|
|
}
|
|
|
@@ -212,6 +267,22 @@ class Index extends Frontend
|
|
|
return $this->mprocRebuildUserFromRememberPayload($payload, $lt);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @return array<string, mixed>|null
|
|
|
+ */
|
|
|
+ protected function mprocUserFromRememberCookie(): ?array
|
|
|
+ {
|
|
|
+ $raw = Cookie::get('mproc_remember');
|
|
|
+ if ($raw === null || $raw === '') {
|
|
|
+ $raw = Cookie::get('mproc_token');
|
|
|
+ }
|
|
|
+ if ($raw === null || $raw === '') {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this->mprocUserFromSignedToken((string)$raw);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* @param array<string, mixed> $payload
|
|
|
* @return array<string, mixed>|null
|
|
|
@@ -275,7 +346,10 @@ class Index extends Frontend
|
|
|
protected function mprocClearLogin($token)
|
|
|
{
|
|
|
if ($token !== null && $token !== '') {
|
|
|
- Cache::rm('mproc_u_' . $token);
|
|
|
+ Cache::rm($this->mprocAuthCacheKey((string)$token));
|
|
|
+ if (!$this->mprocIsSignedAuthToken((string)$token)) {
|
|
|
+ Cache::rm('mproc_u_' . $token);
|
|
|
+ }
|
|
|
}
|
|
|
Session::delete('mproc_token');
|
|
|
Cookie::delete('mproc_token');
|
|
|
@@ -575,11 +649,11 @@ class Index extends Frontend
|
|
|
{
|
|
|
$old = Session::get('mproc_token');
|
|
|
if ($old) {
|
|
|
- Cache::rm('mproc_u_' . preg_replace('/[^a-f0-9]/i', '', (string)$old));
|
|
|
+ $this->mprocClearLogin((string)$old);
|
|
|
}
|
|
|
- $token = bin2hex(random_bytes(16));
|
|
|
$userData['login_time'] = time();
|
|
|
- $this->mprocTouchLoginState($userData, $token);
|
|
|
+ $token = $this->mprocPackSignedAuthToken($userData);
|
|
|
+ $token = $this->mprocTouchLoginState($userData, $token);
|
|
|
|
|
|
$postR = $this->mprocSanitizeRedirectUrl($this->request->post('redirect', ''));
|
|
|
$sessR = $this->mprocSanitizeRedirectUrl((string)Session::get('mproc_intended_url', ''));
|
|
|
@@ -972,7 +1046,11 @@ class Index extends Frontend
|
|
|
$token = Session::get('mproc_token');
|
|
|
if ($token) {
|
|
|
$user['login_time'] = (int)($user['login_time'] ?? time());
|
|
|
- Cache::set('mproc_u_' . preg_replace('/[^a-f0-9]/i', '', (string)$token), $user, $this->mprocTtlSeconds + 86400);
|
|
|
+ $tok = trim((string)$token);
|
|
|
+ Cache::set($this->mprocAuthCacheKey($tok), $user, $this->mprocTtlSeconds + 86400);
|
|
|
+ if (!$this->mprocIsSignedAuthToken($tok)) {
|
|
|
+ Cache::set('mproc_u_' . preg_replace('/[^a-f0-9]/i', '', $tok), $user, $this->mprocTtlSeconds + 86400);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return $user;
|
|
|
@@ -1204,6 +1282,81 @@ class Index extends Frontend
|
|
|
];
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 短信/邮件直达链接:确保 focus 对应明细出现在当前列表(便于高亮定位)
|
|
|
+ *
|
|
|
+ * @param array<string, mixed> $bundle
|
|
|
+ * @param array<string, mixed> $user
|
|
|
+ * @return array<string, mixed>
|
|
|
+ */
|
|
|
+ protected function mprocEnsureFocusRowInList(array $bundle, int $focusEid, array $user, $statusNameCol): array
|
|
|
+ {
|
|
|
+ if ($focusEid <= 0) {
|
|
|
+ return $bundle;
|
|
|
+ }
|
|
|
+ $rows = isset($bundle['rows']) && is_array($bundle['rows']) ? $bundle['rows'] : [];
|
|
|
+ foreach ($rows as $r) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ((int)($r['eid'] ?? $r['id'] ?? $r['ID'] ?? 0) === $focusEid) {
|
|
|
+ return $bundle;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $idCol = $this->mprocResolveProcuremenColumn(['id']);
|
|
|
+ if ($idCol === null) {
|
|
|
+ return $bundle;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $qrow = Db::table('purchase_order_detail')->where($idCol, $focusEid);
|
|
|
+ $qw = $this->mprocListWhereForLoginUser($user);
|
|
|
+ if ($qw !== []) {
|
|
|
+ $qrow->where($qw);
|
|
|
+ }
|
|
|
+ $dr = $qrow->find();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $dr = null;
|
|
|
+ }
|
|
|
+ if (!is_array($dr) || $dr === []) {
|
|
|
+ return $bundle;
|
|
|
+ }
|
|
|
+ $row = $dr;
|
|
|
+ $row['eid'] = (int)($row['id'] ?? $row['ID'] ?? $focusEid);
|
|
|
+ $sid = (int)($row['scydgy_id'] ?? $row['SCYDGY_ID'] ?? 0);
|
|
|
+ $poRow = null;
|
|
|
+ if ($this->mprocIsValidScydgyRowId($sid)) {
|
|
|
+ try {
|
|
|
+ $poRow = Db::table('purchase_order')->where('scydgy_id', $sid)->find();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $poRow = null;
|
|
|
+ }
|
|
|
+ if (is_array($poRow)) {
|
|
|
+ $this->mprocMergePurchaseOrderIntoDetail($row, $poRow);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $effectiveSn = $this->mprocResolveEffectiveStatusName($row, is_array($poRow) ? $poRow : null);
|
|
|
+ $row['status_name'] = $effectiveSn;
|
|
|
+ $row['mproc_can_edit'] = $this->mprocCanEditRow($user, $row) ? 1 : 0;
|
|
|
+ $am = $row['amount'] ?? null;
|
|
|
+ $row['amount_display'] = ($am === null || $am === '' || (is_string($am) && trim($am) === '')) ? '' : (is_scalar($am) ? (string)$am : '');
|
|
|
+ $dv = isset($row['delivery']) ? trim((string)$row['delivery']) : '';
|
|
|
+ if ($dv !== '' && preg_match('/^(\d{4}-\d{2}-\d{2})/', $dv, $m)) {
|
|
|
+ $row['delivery_display'] = $m[1];
|
|
|
+ } elseif ($dv !== '') {
|
|
|
+ $row['delivery_display'] = $dv;
|
|
|
+ } else {
|
|
|
+ $row['delivery_display'] = '';
|
|
|
+ }
|
|
|
+ $row['amount_missing'] = ($am === null || $am === '' || (is_string($am) && trim($am) === '')) ? 1 : 0;
|
|
|
+ $row['delivery_missing'] = ($dv === '' || preg_match('/^0000-00-00/i', $dv)) ? 1 : 0;
|
|
|
+ $row['mproc_fill_hint'] = '';
|
|
|
+ $row['mproc_this_quantity_display'] = $this->mprocResolveDisplayThisQuantity($row);
|
|
|
+ array_unshift($rows, $row);
|
|
|
+ $bundle['rows'] = $rows;
|
|
|
+
|
|
|
+ return $bundle;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 列表展示用「本次数量」:主表本次数量为空时回退显示 NGZL(工作量)
|
|
|
*
|
|
|
@@ -1424,7 +1577,16 @@ class Index extends Frontend
|
|
|
if (is_array($dr) && $dr !== []) {
|
|
|
$mprocFocusEid = $focusEid;
|
|
|
$q = '';
|
|
|
- $sn = trim((string)($dr['status_name'] ?? $dr['STATUS_NAME'] ?? ''));
|
|
|
+ $sid = (int)($dr['scydgy_id'] ?? $dr['SCYDGY_ID'] ?? 0);
|
|
|
+ $po = null;
|
|
|
+ if ($this->mprocIsValidScydgyRowId($sid)) {
|
|
|
+ try {
|
|
|
+ $po = Db::table('purchase_order')->where('scydgy_id', $sid)->find();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $po = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $sn = $this->mprocResolveEffectiveStatusName($dr, is_array($po) ? $po : null);
|
|
|
$mapTab = ['未提交' => 'draft', '已提交' => 'submitted', '已完成' => 'done'];
|
|
|
if ($sn !== '' && isset($mapTab[$sn])) {
|
|
|
$tab = $mapTab[$sn];
|
|
|
@@ -1453,6 +1615,9 @@ class Index extends Frontend
|
|
|
}
|
|
|
|
|
|
$bundle = $this->mprocFetchProcuremenList($user, $tab, $q, $statusNameCol);
|
|
|
+ if ($mprocFocusEid > 0) {
|
|
|
+ $bundle = $this->mprocEnsureFocusRowInList($bundle, $mprocFocusEid, $user, $statusNameCol);
|
|
|
+ }
|
|
|
$this->view->assign('rows', $bundle['rows']);
|
|
|
|
|
|
return $this->view->fetch();
|
|
|
@@ -1624,21 +1789,26 @@ class Index extends Frontend
|
|
|
$this->error('请使用 POST');
|
|
|
}
|
|
|
$token = $this->mprocReadTokenFromRequest();
|
|
|
- if ($token === '') {
|
|
|
- $this->error('登录已过期,请重新登录', url('index/index/login'));
|
|
|
+ $user = null;
|
|
|
+ if ($token !== '') {
|
|
|
+ $user = $this->mprocLoadUserByToken($token);
|
|
|
}
|
|
|
- $user = $this->mprocLoadUserByToken($token);
|
|
|
if (!$user) {
|
|
|
$user = $this->mprocUserFromRememberCookie();
|
|
|
if ($user) {
|
|
|
- $token = bin2hex(random_bytes(16));
|
|
|
+ $token = $this->mprocPackSignedAuthToken($user);
|
|
|
}
|
|
|
}
|
|
|
if (!$user) {
|
|
|
$this->error('登录已过期,请重新登录', url('index/index/login'));
|
|
|
}
|
|
|
- $this->mprocTouchLoginState($user, $token);
|
|
|
- $this->success('ok', '', ['mproc_token' => $token]);
|
|
|
+ $token = $this->mprocTouchLoginState($user, $token !== '' ? $token : $this->mprocPackSignedAuthToken($user));
|
|
|
+ $redirect = $this->mprocSanitizeRedirectUrl($this->request->post('redirect', ''));
|
|
|
+ $jump = $this->mprocBuildAfterLoginIndexUrl($redirect);
|
|
|
+ $this->success('ok', $jump, [
|
|
|
+ 'mproc_token' => $token,
|
|
|
+ 'keep_days' => max(1, (int)round($this->mprocTtlSeconds / 86400)),
|
|
|
+ ]);
|
|
|
}
|
|
|
|
|
|
/**
|