Passed
Push — master ( b6c62e...3fdbae )
by Michael
07:59
created

game.php (1 issue)

Labels
Severity
1
<?php
2
3
use XoopsModules\Chess;
4
5
//  ------------------------------------------------------------------------ //
6
//                XOOPS - PHP Content Management System                      //
7
//                    Copyright (c) 2000 XOOPS.org                           //
8
//                       <https://xoops.org>                             //
9
// ------------------------------------------------------------------------- //
10
//  This program is free software; you can redistribute it and/or modify     //
11
//  it under the terms of the GNU General Public License as published by     //
12
//  the Free Software Foundation; either version 2 of the License, or        //
13
//  (at your option) any later version.                                      //
14
//                                                                           //
15
//  You may not change or alter any portion of this comment or credits       //
16
//  of supporting developers from this source code or any supporting         //
17
//  source code which is considered copyrighted (c) material of the          //
18
//  original comment or credit authors.                                      //
19
//                                                                           //
20
//  This program is distributed in the hope that it will be useful,          //
21
//  but WITHOUT ANY WARRANTY; without even the implied warranty of           //
22
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            //
23
//  GNU General Public License for more details.                             //
24
//                                                                           //
25
//  You should have received a copy of the GNU General Public License        //
26
//  along with this program; if not, write to the Free Software              //
27
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA //
28
//  ------------------------------------------------------------------------ //
29
30
/**
31
 * Handle an individual chess game.
32
 *
33
 * The array $gamedata used throughout this file contains the columns of the
34
 * database row for the current game.
35
 * @see        ChessGame
36
 *
37
 * @package    chess
38
 * @subpackage game
39
 */
40
41
/**#@+
42
 */
43
require_once dirname(__DIR__, 2) . '/mainfile.php';
44
require_once XOOPS_ROOT_PATH . '/class/xoopsformloader.php';
45
46
$GLOBALS['xoopsOption']['template_main']                  = 'chess_game_main.tpl';
47
$xoopsConfig['module_cache'][$xoopsModule->getVar('mid')] = 0; // disable caching
48
require_once XOOPS_ROOT_PATH . '/header.php';
49
require_once XOOPS_ROOT_PATH . '/modules/chess/include/constants.inc.php';
50
require_once XOOPS_ROOT_PATH . '/modules/chess/include/functions.php';
51
52
chess_game();
53
54
require_once XOOPS_ROOT_PATH . '/include/comment_view.php';
55
require_once XOOPS_ROOT_PATH . '/footer.php';
56
/**#@-*/
57
58
/**
59
 * Handle an individual chess game.
60
 *
61
 * processing:
62
 *  - fetch game data from database
63
 *  - handle user request
64
 *  - update game data in database
65
 *  - display board
66
 */
67
function chess_game()
68
{
69
    // user input
70
71
    $game_id = (int)@$_GET['game_id'];
72
73
    $submit_move = isset($_POST['submit_move']);
74
75
    $submit_refresh = isset($_POST['submit_refresh']);
76
77
    $move = chess_sanitize(trim(@$_POST['chessmove']), 'A-Za-z0-9=-');
78
79
    $movetype = chess_sanitize(@$_POST['movetype']);
80
81
    $confirm = isset($_POST['confirm']) ? 1 : 0;
82
83
    $move_explain = chess_sanitize(trim(@$_POST['move_explain']), 'A-Za-z0-9 .,;:=()[]*?!-');
84
85
    $arbiter_explain = chess_sanitize(trim(@$_POST['arbiter_explain']), 'A-Za-z0-9 .,;:=()[]*?!-');
86
87
    $orientation = chess_sanitize(@$_POST['orientation']);
88
89
    $show_arbiter_ctrl = isset($_POST['show_arbiter_ctrl']);
90
91
    $submit_arbitrate = isset($_POST['submit_arbitrate']);
92
93
    $arbiter_action = chess_sanitize(@$_POST['arbiter_action']);
94
95
    // If form-submit, check security token.
96
97
    if (($submit_move || $submit_refresh || $submit_arbitrate || $show_arbiter_ctrl) && is_object($GLOBALS['xoopsSecurity']) && !$GLOBALS['xoopsSecurity']->check()) {
98
        redirect_header(
99
            XOOPS_URL . '/modules/chess/',
100
            _CHESS_REDIRECT_DELAY_FAILURE,
101
            _MD_CHESS_TOKEN_ERROR . '<br>' . implode('<br>', $GLOBALS['xoopsSecurity']->getErrors())
102
        );
103
    }
104
105
    // Fetch the game data from the database.
106
107
    $gamedata = chess_get_game($game_id);
108
109
    if (false === $gamedata) {
110
        redirect_header(XOOPS_URL . '/modules/chess/', _CHESS_REDIRECT_DELAY_FAILURE, _MD_CHESS_GAME_NOT_FOUND);
111
    }
112
113
    $gamedata_updated = false;
114
115
    $move_performed        = false; // status result from move-handler
116
    $move_result_text      = '';    // text result from move-handler
117
    $draw_claim_error_text = '';    // text describing invalid draw-claim
118
    $notify                = '';    // text description of action for notification
119
    $delete_game           = false;
120
121
    global $xoopsUser;
122
123
    $uid = $xoopsUser ? $xoopsUser->getVar('uid') : 0;
124
125
    $selfplay = $gamedata['white_uid'] == $gamedata['black_uid'];
126
127
    if ($selfplay) {
128
        if ($uid == $gamedata['white_uid']) {
129
            $user_color = $gamedata['fen_active_color']; // current user is either player, so consider him active player
130
        } else {
131
            $user_color = '';                            // current user is not a player
132
        }
133
        // not self-play
134
    } else {
135
        if ($uid == $gamedata['white_uid']) {
136
            $user_color = 'w'; // current user is white
137
        } elseif ($uid == $gamedata['black_uid']) {
138
            $user_color = 'b'; // current user is black
139
        } else {
140
            $user_color = '';  // current user is not a player
141
        }
142
    }
143
144
    // Determine whether current user is a player in the current game, and whether it's his move.
145
146
    $user_is_player = 'w' == $user_color || 'b' == $user_color;
147
148
    $user_is_player_to_move = $user_color == $gamedata['fen_active_color'];
149
150
    $user_color_name = 'w' == $user_color ? _MD_CHESS_WHITE : _MD_CHESS_BLACK;
151
152
    if ($submit_move && !$gamedata['suspended']) {
153
        // If the player has changed his confirm-move preference, it needs to be updated in the database.
154
155
        // For a self-play game, the white and black confirm-move preferences are kept the same, to avoid confusion.
156
157
        if ($user_is_player) {
158
            if ('w' == $user_color && $gamedata['white_confirm'] != $confirm) {
159
                $gamedata['white_confirm'] = $confirm;
160
161
                if ($selfplay) {
162
                    $gamedata['black_confirm'] = $confirm;
163
                }
164
165
                $gamedata_updated = true;
166
            } elseif ('b' == $user_color && $gamedata['black_confirm'] != $confirm) {
167
                $gamedata['black_confirm'] = $confirm;
168
169
                if ($selfplay) {
170
                    $gamedata['white_confirm'] = $confirm;
171
                }
172
173
                $gamedata_updated = true;
174
            }
175
        }
176
177
        switch ($movetype) {
178
            default:
179
            case _CHESS_MOVETYPE_NORMAL:
180
                if ($user_is_player_to_move && $gamedata['offer_draw'] != $user_color) {
181
                    [$move_performed, $move_result_text] = chess_move($gamedata, $move);
182
183
                    if ($move_performed) {
184
                        if ($selfplay) { // If self-play, the current user's color switches.
185
                            $user_color = $gamedata['fen_active_color'];
186
                        }
187
188
                        $gamedata['offer_draw'] = '';
189
190
                        $gamedata_updated = true;
191
192
                        $notify = "$user_color_name: $move";
193
                    }
194
                }
195
                break;
196
            case _CHESS_MOVETYPE_CLAIM_DRAW_3:
197
            case _CHESS_MOVETYPE_CLAIM_DRAW_50:
198
                if ($user_is_player_to_move && $gamedata['offer_draw'] != $user_color) {
199
                    if (!empty($move)) {
200
                        [$move_performed, $move_result_text] = chess_move($gamedata, $move);
201
202
                        #var_dump('chess_game', $move_performed, $move_result_text);#*#DEBUG#
203
204
                        if ($move_performed) {
205
                            if ($selfplay) { // If self-play, the current user's color switches.
206
                                $user_color = $gamedata['fen_active_color'];
207
                            }
208
209
                            $gamedata['offer_draw'] = '';
210
211
                            $gamedata_updated = true;
212
213
                            $notify = "$user_color_name: $move";
214
                        } else {
215
                            break; // move invalid, so don't bother checking draw-claim
216
                        }
217
                    }
218
219
                    [$draw_claim_valid, $draw_claim_text] = _CHESS_MOVETYPE_CLAIM_DRAW_3 == $movetype ? chess_is_draw_threefold_repetition($gamedata) : chess_is_draw_50_move_rule($gamedata);
220
221
                    #var_dump('chess_game', $draw_claim_valid, $draw_claim_text);#*#DEBUG#
222
223
                    if ($draw_claim_valid) {
224
                        $gamedata['offer_draw'] = '';
225
226
                        $gamedata['pgn_result'] = '1/2-1/2';
227
228
                        $comment = '{' . $draw_claim_text . '}';
229
230
                        $gamedata['pgn_movetext'] = str_replace('*', "{$gamedata['pgn_result']} $comment", $gamedata['pgn_movetext']);
231
232
                        $gamedata_updated = true;
233
234
                        $notify = !empty($move) ? "$user_color_name: $move: $draw_claim_text" : "$user_color_name: $draw_claim_text";
235
                    } else {
236
                        $draw_claim_error_text = $draw_claim_text;
237
                    }
238
                }
239
                break;
240
            case _CHESS_MOVETYPE_RESIGN:
241
                if ($user_is_player) {
242
                    $gamedata['offer_draw'] = '';
243
244
                    $gamedata['pgn_result'] = 'w' == $user_color ? '0-1' : '1-0';
245
246
                    $comment = '{' . $user_color_name . ' ' . _MD_CHESS_RESIGNED . '}';
247
248
                    $gamedata['pgn_movetext'] = str_replace('*', "{$gamedata['pgn_result']} $comment", $gamedata['pgn_movetext']);
249
250
                    $gamedata_updated = true;
251
252
                    $notify = "$user_color_name " . _MD_CHESS_RESIGNED;
253
                }
254
                break;
255
            case _CHESS_MOVETYPE_OFFER_DRAW:
256
                if ($user_is_player && empty($gamedata['offer_draw']) && !$selfplay) {
257
                    $gamedata['offer_draw'] = $user_color;
258
259
                    $gamedata_updated = true;
260
261
                    $notify = "$user_color_name " . _MD_CHESS_OFFERED_DRAW;
262
                }
263
                break;
264
            case _CHESS_MOVETYPE_ACCEPT_DRAW:
265
                if ($user_is_player && !empty($gamedata['offer_draw']) && $gamedata['offer_draw'] != $user_color && !$selfplay) {
266
                    $gamedata['offer_draw'] = '';
267
268
                    $gamedata['pgn_result'] = '1/2-1/2';
269
270
                    $comment = '{' . _MD_CHESS_DRAW_BY_AGREEMENT . '}';
271
272
                    $gamedata['pgn_movetext'] = str_replace('*', "{$gamedata['pgn_result']} $comment", $gamedata['pgn_movetext']);
273
274
                    $gamedata_updated = true;
275
276
                    $notify = "$user_color_name " . _MD_CHESS_ACCEPTED_DRAW;
277
                }
278
279
            // no break
280
            case _CHESS_MOVETYPE_REJECT_DRAW:
281
                if ($user_is_player && !empty($gamedata['offer_draw']) && $gamedata['offer_draw'] != $user_color && !$selfplay) {
282
                    $gamedata['offer_draw'] = '';
283
284
                    $gamedata_updated = true;
285
286
                    $notify = "$user_color_name " . _MD_CHESS_REJECTED_DRAW;
287
                }
288
                break;
289
            case _CHESS_MOVETYPE_RESTART:
290
                if ($user_is_player && $selfplay) {
291
                    // instantiate a ChessGame to get a "fresh" gamestate
292
293
                    $chessgame = new Chess\ChessGame($gamedata['pgn_fen']);
0 ignored issues
show
The type XoopsModules\Chess\ChessGame was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
294
295
                    $new_gamestate = $chessgame->gamestate();
296
297
                    // update the game data
298
299
                    $gamedata['fen_piece_placement'] = $new_gamestate['fen_piece_placement'];
300
301
                    $gamedata['fen_active_color'] = $new_gamestate['fen_active_color'];
302
303
                    $gamedata['fen_castling_availability'] = $new_gamestate['fen_castling_availability'];
304
305
                    $gamedata['fen_en_passant_target_square'] = $new_gamestate['fen_en_passant_target_square'];
306
307
                    $gamedata['fen_halfmove_clock'] = $new_gamestate['fen_halfmove_clock'];
308
309
                    $gamedata['fen_fullmove_number'] = $new_gamestate['fen_fullmove_number'];
310
311
                    $gamedata['pgn_fen'] = $new_gamestate['pgn_fen'];
312
313
                    $gamedata['pgn_result'] = $new_gamestate['pgn_result'];
314
315
                    $gamedata['pgn_movetext'] = $new_gamestate['pgn_movetext'];
316
317
                    // update user color
318
319
                    $user_color = $gamedata['fen_active_color'];
320
321
                    $gamedata_updated = true;
322
                }
323
                break;
324
            case _CHESS_MOVETYPE_DELETE:
325
                if ($user_is_player && ($selfplay || chess_can_delete())) {
326
                    $delete_game = true; // must defer actual deletion until after notifications are sent
327
                    $notify      = eval('return \'' . _MD_CHESS_DELETED_GAME . '\';'); // eval references $username
328
                }
329
                break;
330
            case _CHESS_MOVETYPE_WANT_ARBITRATION:
331
                if ($user_is_player) {
332
                    // Ensure that $move_explain does not contain separator $sep.
333
334
                    $sep = '|';
335
336
                    $move_explain = str_replace($sep, '_', $move_explain);
337
338
                    $gamedata['suspended'] = implode($sep, [date('Y-m-d H:i:s'), $uid, _CHESS_MOVETYPE_WANT_ARBITRATION, $move_explain]);
339
340
                    $gamedata_updated = true;
341
342
                    $notify = "$user_color_name " . _MD_CHESS_RQSTED_ARBT . ' ' . _MD_CHESS_BEEN_SUSPENDED;
343
                }
344
                break;
345
        }
346
    }
347
348
    // If board orientation setting uninitialized or invalid, set it to the appropriate default.
349
350
    if (!in_array($orientation, [_CHESS_ORIENTATION_ACTIVE, _CHESS_ORIENTATION_WHITE, _CHESS_ORIENTATION_BLACK])) {
351
        if ($user_is_player && 'b' == $user_color) {
352
            $orientation = _CHESS_ORIENTATION_BLACK;
353
        } else {
354
            $orientation = _CHESS_ORIENTATION_WHITE;
355
        }
356
    }
357
358
    // Determine if user is a valid arbiter.
359
360
    global $xoopsModule;
361
362
    $is_arbiter = is_object($xoopsUser) && $xoopsUser->isAdmin($xoopsModule->getVar('mid'));
363
364
    // If arbiter action was submitted, and user is a valid arbiter, process the action.
365
366
    if ($submit_arbitrate && $is_arbiter) {
367
        $username = $xoopsUser ? $xoopsUser->getVar('uname') : '*unknown*';
368
369
        switch ($arbiter_action) {
370
            case _CHESS_ARBITER_RESUME:
371
                if ($gamedata['suspended'] && '*' == $gamedata['pgn_result']) {
372
                    $gamedata['suspended'] = '';
373
374
                    $gamedata_updated = true;
375
376
                    $notify = eval('return \'' . _MD_CHESS_RESUMED_PLAY . '\';'); // eval references $username
377
                }
378
                break;
379
            case _CHESS_ARBITER_DRAW:
380
                if ($gamedata['suspended'] && '*' == $gamedata['pgn_result']) {
381
                    $gamedata['offer_draw'] = '';
382
383
                    $gamedata['pgn_result'] = '1/2-1/2';
384
385
                    $arbiter_explain = str_replace('{', '(', $arbiter_explain);
386
387
                    $arbiter_explain = str_replace('}', ')', $arbiter_explain);
388
389
                    $comment = '{' . sprintf(_MD_CHESS_DRAW_DECLARED, $arbiter_explain) . '}';
390
391
                    $gamedata['pgn_movetext'] = str_replace('*', "{$gamedata['pgn_result']} $comment", $gamedata['pgn_movetext']);
392
393
                    $gamedata['suspended'] = '';
394
395
                    $gamedata_updated = true;
396
397
                    $notify = eval('return \'' . _MD_CHESS_DECLARED_DRAW . '\';'); // eval references $username
398
                }
399
                break;
400
            case _CHESS_ARBITER_DELETE:
401
                if ($gamedata['suspended']) {
402
                    $delete_game = true; // must defer actual deletion until after notifications are sent
403
                    $notify      = eval('return \'' . _MD_CHESS_DELETED_GAME . '\';'); // eval references $username
404
                }
405
                break;
406
            case _CHESS_ARBITER_SUSPEND:
407
                if (!$gamedata['suspended'] && '*' == $gamedata['pgn_result']) {
408
                    // Ensure that $arbiter_explain does not contain separator $sep.
409
410
                    $sep = '|';
411
412
                    $arbiter_explain = str_replace($sep, '_', $arbiter_explain);
413
414
                    $gamedata['suspended'] = implode('|', [date('Y-m-d H:i:s'), $uid, _CHESS_MOVETYPE_ARBITER_SUSPEND, $arbiter_explain]);
415
416
                    $gamedata_updated = true;
417
418
                    $notify = eval('return \'' . _MD_CHESS_SUSPENDED_PLAY . '\';'); // eval references $username
419
                }
420
                break;
421
            default:
422
                break;
423
        }
424
    }
425
426
    // Store the updated game data in the database.
427
428
    if ($gamedata_updated) {
429
        chess_put_game($game_id, $gamedata);
430
    }
431
432
    // Display the (possibly updated) board.
433
434
    if (!$delete_game) {
435
        chess_show_board($gamedata, $orientation, $user_color, $move_performed, $move_result_text, $draw_claim_error_text, $show_arbiter_ctrl && $is_arbiter);
436
    }
437
438
    // If a move (or other action) was made, notify any subscribers.
439
440
    if (!empty($notify)) {
441
        $notificationHandler = xoops_getHandler('notification');
442
443
        $notificationHandler->triggerEvent('game', $game_id, 'notify_game_move', ['CHESS_ACTION' => $notify]);
444
445
        // admin notifications
446
447
        if (_CHESS_MOVETYPE_WANT_ARBITRATION == $movetype) {
448
            $event = 'notify_request_arbitration';
449
450
            $username = $xoopsUser ? $xoopsUser->getVar('uname') : '(' . _MD_CHESS_UNKNOWN . ')';
451
452
            $extra_tags = ['CHESS_REQUESTOR' => $username, 'CHESS_GAME_ID' => $game_id, 'CHESS_EXPLAIN' => $move_explain];
453
454
            $notificationHandler->triggerEvent('global', 0, $event, $extra_tags);
455
        }
456
    }
457
458
    // Handle delete-game action.
459
460
    if ($delete_game) {
461
        chess_delete_game($game_id);
462
463
        redirect_header(XOOPS_URL . '/modules/chess/', _CHESS_REDIRECT_DELAY_SUCCESS, _MD_CHESS_GAME_DELETED);
464
    }
465
}
466
467
/**
468
 * Fetch game data from database.
469
 *
470
 * @param int $game_id Game ID
471
 * @return array  Game data
472
 */
473
function chess_get_game($game_id)
474
{
475
    global $xoopsDB;
476
477
    $games_table = $xoopsDB->prefix('chess_games');
478
479
    $result = $xoopsDB->query("SELECT * FROM $games_table WHERE game_id = '$game_id'");
480
481
    $gamedata = $xoopsDB->fetchArray($result);
482
483
    #var_dump('chess_get_game, gamedata', $gamedata);#*#DEBUG#
484
485
    $xoopsDB->freeRecordSet($result);
486
487
    return $gamedata;
488
}
489
490
/**
491
 * Store game data in database.
492
 *
493
 * @param int   $game_id  Game ID
494
 * @param array $gamedata Game data
495
 */
496
function chess_put_game($game_id, $gamedata)
497
{
498
    global $xoopsDB;
499
500
    $myts = MyTextSanitizer::getInstance();
501
502
    $suspended_q = $myts->addSlashes($gamedata['suspended']);
503
504
    $movetext_q = $myts->addSlashes($gamedata['pgn_movetext']);
505
506
    $table = $xoopsDB->prefix('chess_games');
507
508
    #echo "updating database table $table<br>\n";#*#DEBUG#
509
510
    $xoopsDB->query(
511
        trim(
512
            "
513
        UPDATE $table
514
        SET
515
            start_date                   = '{$gamedata['start_date']}',
516
            last_date                    = '{$gamedata['last_date']}',
517
            fen_piece_placement          = '{$gamedata['fen_piece_placement']}',
518
            fen_active_color             = '{$gamedata['fen_active_color']}',
519
            fen_castling_availability    = '{$gamedata['fen_castling_availability']}',
520
            fen_en_passant_target_square = '{$gamedata['fen_en_passant_target_square']}',
521
            fen_halfmove_clock           = '{$gamedata['fen_halfmove_clock']}',
522
            fen_fullmove_number          = '{$gamedata['fen_fullmove_number']}',
523
            pgn_result                   = '{$gamedata['pgn_result']}',
524
            pgn_movetext                 = '$movetext_q',
525
            offer_draw                   = '{$gamedata['offer_draw']}',
526
            suspended                    = '$suspended_q',
527
            white_confirm                = '{$gamedata['white_confirm']}',
528
            black_confirm                = '{$gamedata['black_confirm']}'
529
        WHERE  game_id = '$game_id'
530
    "
531
        )
532
    );
533
534
    if ($xoopsDB->errno()) {
535
        trigger_error($xoopsDB->errno() . ':' . $xoopsDB->error(), E_USER_ERROR);
536
    }
537
538
    if ('*' != $gamedata['pgn_result'] && '1' == $gamedata['is_rated']) {
539
        require_once XOOPS_ROOT_PATH . '/modules/chess/include/ratings.php';
540
541
        chess_ratings_adj($game_id);
542
    }
543
}
544
545
/**
546
 * Delete game from database.
547
 *
548
 * @param int $game_id Game ID
549
 */
550
function chess_delete_game($game_id)
551
{
552
    global $xoopsModule, $xoopsDB;
553
554
    // delete notifications associated with this game
555
556
    xoops_notification_deletebyitem($xoopsModule->getVar('mid'), 'game', $game_id);
557
558
    $table = $xoopsDB->prefix('chess_games');
559
560
    $xoopsDB->query("DELETE FROM $table WHERE game_id='$game_id'");
561
562
    if ($xoopsDB->errno()) {
563
        trigger_error($xoopsDB->errno() . ':' . $xoopsDB->error(), E_USER_ERROR);
564
    }
565
}
566
567
/**
568
 * Handle a move.
569
 *
570
 * @param array  $gamedata Game data (input/output)
571
 * @param string $move     The move
572
 * @return array A two-element array:
573
 *                         - $move_performed: true if the move was performed and the game state has been updated, false otherwise
574
 *                         - $move_result_text: text message
575
 */
576
function chess_move(&$gamedata, $move)
577
{
578
    $gamestate = [
579
        'fen_piece_placement'          => $gamedata['fen_piece_placement'],
580
        'fen_active_color'             => $gamedata['fen_active_color'],
581
        'fen_castling_availability'    => $gamedata['fen_castling_availability'],
582
        'fen_en_passant_target_square' => $gamedata['fen_en_passant_target_square'],
583
        'fen_halfmove_clock'           => $gamedata['fen_halfmove_clock'],
584
        'fen_fullmove_number'          => $gamedata['fen_fullmove_number'],
585
        'pgn_fen'                      => $gamedata['pgn_fen'],
586
        'pgn_result'                   => $gamedata['pgn_result'],
587
        'pgn_movetext'                 => $gamedata['pgn_movetext'],
588
    ];
589
590
    $chessgame = new Chess\ChessGame($gamestate);
591
592
    #echo "Performing move: '$move'<br>\n";#*#DEBUG#
593
594
    [$move_performed, $move_result_text] = $chessgame->move($move);
595
596
    #echo "Move result: '$move_result_text'<br>\n";#*#DEBUG#
597
598
    if ($move_performed) {
599
        // The move was valid - update the game data.
600
601
        $new_gamestate = $chessgame->gamestate();
602
603
        #var_dump('new_gamestate', $new_gamestate);#*#DEBUG#
604
605
        #*#DEBUG# - start
606
607
        #if ($new_gamestate['fen_castling_availability'] != $gamedata['fen_castling_availability']) {
608
609
        #    echo "*** castling_availability changed from '{$gamedata['fen_castling_availability']}' to '{$new_gamestate['fen_castling_availability']}' ***<br>\n";
610
611
        #}
612
613
        #*#DEBUG# - end
614
615
        $gamedata['fen_piece_placement'] = $new_gamestate['fen_piece_placement'];
616
617
        $gamedata['fen_active_color'] = $new_gamestate['fen_active_color'];
618
619
        $gamedata['fen_castling_availability'] = $new_gamestate['fen_castling_availability'];
620
621
        $gamedata['fen_en_passant_target_square'] = $new_gamestate['fen_en_passant_target_square'];
622
623
        $gamedata['fen_halfmove_clock'] = $new_gamestate['fen_halfmove_clock'];
624
625
        $gamedata['fen_fullmove_number'] = $new_gamestate['fen_fullmove_number'];
626
627
        $gamedata['pgn_fen'] = $new_gamestate['pgn_fen'];
628
629
        $gamedata['pgn_result'] = $new_gamestate['pgn_result'];
630
631
        $gamedata['pgn_movetext'] = $new_gamestate['pgn_movetext'];
632
633
        $gamedata['last_date'] = date('Y-m-d H:i:s');
634
635
        // if start_date undefined (first move), initialize it
636
637
        if ('0000-00-00 00:00:00' == $gamedata['start_date']) {
638
            $gamedata['start_date'] = $gamedata['last_date'];
639
        }
640
    }
641
642
    return [$move_performed, $move_result_text];
643
}
644
645
/**
646
 * Verify a draw-claim under the 50-move rule.
647
 *
648
 * @param array $gamedata Game data
649
 * @return array  A two-element array:
650
 *                        - $draw_claim_valid: True if draw-claim is valid, otherwise false
651
 *                        - $draw_claim_text: Describes draw-claim result
652
 */
653
function chess_is_draw_50_move_rule($gamedata)
654
{
655
    #var_dump('gamedata', $gamedata);#*#DEBUG#
656
657
    if ($gamedata['fen_halfmove_clock'] >= 100) {
658
        $draw_claim_valid = true;
659
660
        $draw_claim_text = _MD_CHESS_DRAW_50;
661
    } else {
662
        $draw_claim_valid = false;
663
664
        $draw_claim_text = _MD_CHESS_NO_DRAW_50;
665
    }
666
667
    return [$draw_claim_valid, $draw_claim_text];
668
}
669
670
/**
671
 * Verify a draw-claim under the threefold-repetition rule.
672
 *
673
 * Board positions are compared using the first four fields of the FEN data:
674
 * fen_piece_placement, fen_active_color, fen_castling_availability and fen_en_passant_target_square.
675
 *
676
 * @param array $gamedata Game data
677
 * @return array  A two-element array:
678
 *                        - $draw_claim_valid: True if draw-claim is valid, otherwise false
679
 *                        - $draw_claim_text: Describes draw-claim result
680
 */
681
function chess_is_draw_threefold_repetition($gamedata)
682
{
683
    #var_dump('gamedata', $gamedata);#*#DEBUG#
684
685
    // Define this constant as true to output a log file containing a move analysis.
686
687
    define('CHESS_LOG_3FOLD', false);
688
689
    if (CHESS_LOG_3FOLD) {
690
        $log = [];
691
    }
692
693
    $chessgame = new Chess\ChessGame($gamedata);
694
695
    // board position against which to check for repetitions
696
697
    $last_board_state = "{$gamedata['fen_piece_placement']} {$gamedata['fen_active_color']} {$gamedata['fen_castling_availability']} {$gamedata['fen_en_passant_target_square']}";
698
699
    $last_move_number = $gamedata['fen_fullmove_number'];
700
701
    #echo "last_board_state='$last_board_state'<br>\n";#*#DEBUG#
702
703
    if (CHESS_LOG_3FOLD) {
704
        $log[] = sprintf('%08x %03d%1s %s', crc32($last_board_state), $gamedata['fen_fullmove_number'], $gamedata['fen_active_color'], $last_board_state);
705
    }
706
707
    $chessgame = new Chess\ChessGame($gamedata['pgn_fen']);
708
709
    empty($chessgame->error) or trigger_error('chessgame invalid', E_USER_ERROR);
710
711
    $tmp_gamedata = $chessgame->gamestate();
712
713
    is_array($tmp_gamedata) or trigger_error('gamestate invalid', E_USER_ERROR);
714
715
    #*#DEBUG# - start
716
717
    /***
718
     * if (function_exists('posix_times')) {
719
     * $posix_times = posix_times();
720
     * var_dump('posix_times', $posix_times);
721
     * }
722
     * if (function_exists('getrusage')) {
723
     * $rusage = getrusage();
724
     * var_dump('rusage', $rusage);
725
     * }
726
     ***/
727
728
    #*#DEBUG# - end
729
730
    // $repeats is the list of moves for which the board positions are identical.
731
732
    // For example, '6w' would represent the board position immediately before white's sixth move.
733
734
    // The current position is included, since that's the position against which the other positions will be compared.
735
736
    $repeats[] = $gamedata['fen_fullmove_number'] . $gamedata['fen_active_color'];
737
738
    // Compare initial board position with last board position, unless the move number is the same, meaning that there haven't been any moves.
739
740
    #echo "FEN: '{$tmp_gamedata['fen_piece_placement']} {$tmp_gamedata['fen_active_color']} {$tmp_gamedata['fen_castling_availability']} {$tmp_gamedata['fen_en_passant_target_square']} {$tmp_gamedata['fen_halfmove_clock']} {$tmp_gamedata['fen_fullmove_number']}'<br>\n";#*#DEBUG#
741
742
    $board_state = "{$tmp_gamedata['fen_piece_placement']} {$tmp_gamedata['fen_active_color']} {$tmp_gamedata['fen_castling_availability']} {$tmp_gamedata['fen_en_passant_target_square']}";
743
744
    if ($tmp_gamedata['fen_fullmove_number'] != $last_move_number && $board_state == $last_board_state) {
745
        $repeats[] = $tmp_gamedata['fen_fullmove_number'] . $tmp_gamedata['fen_active_color'];
746
747
        if (CHESS_LOG_3FOLD) {
748
            $log[] = sprintf('%08x %03d%1s %s', crc32($board_state), $tmp_gamedata['fen_fullmove_number'], $tmp_gamedata['fen_active_color'], $board_state);
749
        }
750
751
        #*#DEBUG# - start
752
        /***
753
         * if (count($repeats) >= 3) {
754
         * echo "*** Three repetitions! {$repeats[1]},{$repeats[2]},{$repeats[0]} ***<br>\n";
755
         * } elseif (count($repeats) >= 2) {
756
         * echo "*** Two repetitions!  {$repeats[1]},{$repeats[0]} ***<br>\n";
757
         * }
758
         ***/
759
760
        #*#DEBUG# - end
761
    }
762
763
    // Convert pgn_movetext into Nx3 array $movelist.
764
765
    $movelist = chess_make_movelist($gamedata['pgn_movetext']);
766
767
    #var_dump('movelist', $movelist);#*#DEBUG#
768
769
    // Compare board positions after each move with last board position.
770
771
    foreach ($movelist as $fullmove) {
772
        #echo "'{$fullmove[0]}' '{$fullmove[1]}' '{$fullmove[2]}'<br>\n";#*#DEBUG#
773
774
        if (CHESS_LOG_3FOLD) {
775
            #$log[] = "'{$fullmove[0]}' '{$fullmove[1]}' '{$fullmove[2]}'";#*#LOG_3FOLD# #*#DEBUG#
776
        }
777
778
        for ($i = 1; $i <= 2; ++$i) {
779
            if (!empty($fullmove[$i])) {
780
                $move = $fullmove[$i];
781
            } else {
782
                continue; // $fullmove[$i] can be empty if last move was white's, or if game was setup with black to move first.
783
            }
784
785
            // Remove check/checkmate annotation, if present.
786
787
            $move = str_replace('+', '', $move);
788
789
            $move = str_replace('#', '', $move);
790
791
            #echo "Performing move: '$move'<br>\n";#*#DEBUG#
792
793
            [$tmp_move_performed, $tmp_move_result_text] = $chessgame->move($move);
794
795
            #echo "Move result: '$tmp_move_result_text'<br>\n";#*#DEBUG#
796
797
            $tmp_move_performed or trigger_error("Failed to perform move $move: $tmp_move_result_text", E_USER_ERROR);
798
799
            $tmp_gamedata = $chessgame->gamestate();
800
801
            #echo "FEN: '{$tmp_gamedata['fen_piece_placement']} {$tmp_gamedata['fen_active_color']} {$tmp_gamedata['fen_castling_availability']} {$tmp_gamedata['fen_en_passant_target_square']} {$tmp_gamedata['fen_halfmove_clock']} {$tmp_gamedata['fen_fullmove_number']}'<br>\n";#*#DEBUG#
802
803
            $board_state = "{$tmp_gamedata['fen_piece_placement']} {$tmp_gamedata['fen_active_color']} {$tmp_gamedata['fen_castling_availability']} {$tmp_gamedata['fen_en_passant_target_square']}";
804
805
            if (CHESS_LOG_3FOLD) {
806
                $log[] = sprintf('%08x %03d%1s %s', crc32($board_state), $tmp_gamedata['fen_fullmove_number'], $tmp_gamedata['fen_active_color'], $board_state);
807
            }
808
809
            if ($tmp_gamedata['fen_fullmove_number'] != $last_move_number && $board_state == $last_board_state) {
810
                $repeats[] = $tmp_gamedata['fen_fullmove_number'] . $tmp_gamedata['fen_active_color'];
811
812
                #*#DEBUG# - start
813
814
                /***
815
                 * if (count($repeats) >= 3) {
816
                 * echo "*** Three repetitions! {$repeats[1]},{$repeats[2]},{$repeats[0]} ***<br>\n";
817
                 * } elseif (count($repeats) >= 2) {
818
                 * echo "*** Two repetitions!  {$repeats[1]},{$repeats[0]} ***<br>\n";
819
                 * }
820
                 ***/
821
822
                #*#DEBUG# - end
823
824
                if (count($repeats) >= 3) {
825
                    break 2;
826
                }
827
            }
828
        }
829
    }
830
831
    #*#DEBUG# - start
832
833
    /***
834
     * if (function_exists('posix_times')) {
835
     * $posix_times = posix_times();
836
     * var_dump('posix_times', $posix_times);
837
     * }
838
     * if (function_exists('getrusage')) {
839
     * $rusage = getrusage();
840
     * var_dump('rusage', $rusage);
841
     * }
842
     ***/
843
844
    #*#DEBUG# - end
845
846
    if (count($repeats) >= 3) {
847
        $draw_claim_valid = true;
848
849
        $draw_claim_text = sprintf(_MD_CHESS_DRAW_3, "{$repeats[1]},{$repeats[2]},{$repeats[0]}");
850
    } else {
851
        $draw_claim_valid = false;
852
853
        $draw_claim_text = _MD_CHESS_NO_DRAW_3;
854
    }
855
856
    if (CHESS_LOG_3FOLD) {
857
        $logfile = XOOPS_ROOT_PATH . '/cache/' . date('Ymd_His') . '_3fold.log';
858
859
        sort($log);
860
861
        error_log(implode("\n", $log), 3, $logfile);
862
    }
863
864
    return [$draw_claim_valid, $draw_claim_text];
865
}
866
867
/**
868
 * Convert pgn_movetext into Nx3 array.
869
 *
870
 * @param array $movetext pgn_movetext
871
 * @return array Nx3 array
872
 */
873
function chess_make_movelist($movetext)
874
{
875
    $movelist = [];
876
877
    $move_tokens = explode(' ', preg_replace('/\{.*\}/', '', $movetext));
878
879
    $index = -1;
880
881
    while ($move_tokens) {
882
        $move_token = array_shift($move_tokens);
883
884
        if (in_array($move_token, ['1-0', '0-1', '1/2-1/2', '*'])) {
885
            break;
886
        } elseif (preg_match('/^\d+(\.|\.\.\.)$/', $move_token, $matches)) {
887
            ++$index;
888
889
            $movelist[$index][] = $move_token;
890
891
            if ('...' == $matches[1]) { // setup-game with initial black move - add padding for white's move
892
                $movelist[$index][] = '';
893
            }
894
        } else {
895
            $movelist[$index][] = $move_token;
896
        }
897
    }
898
899
    return $movelist;
900
}
901
902
/**
903
 * Display chess board.
904
 *
905
 * @param array  $gamedata              Game data
906
 * @param string $orientation           _CHESS_ORIENTATION_ACTIVE, _CHESS_ORIENTATION_WHITE or _CHESS_ORIENTATION_BLACK
907
 * @param string $user_color            'w', 'b' or '' (empty string indicates that current user is not a player in this game)
908
 * @param bool   $move_performed        True if move was performed
909
 * @param string $move_result_text      Text describing move
910
 * @param string $draw_claim_error_text Non-empty if draw claim invalid
911
 * @param bool   $show_arbiter_ctrl     True if arbiter controls are to be displayed
912
 */
913
function chess_show_board($gamedata, $orientation, $user_color, $move_performed, $move_result_text = '', $draw_claim_error_text = '', $show_arbiter_ctrl = false)
914
{
915
    global $xoopsTpl;
916
917
    $xoopsTpl->assign(
918
        'xoops_module_header',
919
        '
920
        <link rel="stylesheet" type="text/css" media="screen" href="' . XOOPS_URL . '/modules/chess/assets/css/style.css">
921
    '
922
    );
923
924
    $memberHandler = xoops_getHandler('member');
925
926
    $white_user = $memberHandler->getUser($gamedata['white_uid']);
927
928
    $white_username = is_object($white_user) ? $white_user->getVar('uname') : '?';
929
930
    $black_user = $memberHandler->getUser($gamedata['black_uid']);
931
932
    $black_username = is_object($black_user) ? $black_user->getVar('uname') : '?';
933
934
    // Determine whether board is flipped (black at bottom) or "normal" (white at bottom).
935
936
    switch ($orientation) {
937
        default:
938
        case _CHESS_ORIENTATION_ACTIVE:
939
            $flip = 'b' == $gamedata['fen_active_color'];
940
            break;
941
        case _CHESS_ORIENTATION_WHITE:
942
            $flip = false;
943
            break;
944
        case _CHESS_ORIENTATION_BLACK:
945
            $flip = true;
946
            break;
947
    }
948
949
    // Convert fen_piece_placement string into 8x8-array $tiles.
950
951
    $tiles = [];
952
953
    $ranks = explode('/', $gamedata['fen_piece_placement']);
954
955
    if ($flip) {
956
        $ranks = array_reverse($ranks);
957
    }
958
959
    foreach ($ranks as $rank) {
960
        $expanded_row = preg_replace_callback(
961
            '/(\d)/',
962
963
            static function ($matches) {
964
                return str_repeat('x', $matches[1]);
965
            },
966
            $rank
967
        );
968
969
        $rank_tiles = preg_split('//', $expanded_row, -1, PREG_SPLIT_NO_EMPTY);
970
971
        $tiles[] = $flip ? array_reverse($rank_tiles) : $rank_tiles;
972
    }
973
974
    #var_dump('tiles', $tiles);#*#DEBUG#
975
976
    // Convert pgn_movetext into Nx3 array $movelist.
977
978
    $movelist = chess_make_movelist($gamedata['pgn_movetext']);
979
980
    static $file_labels = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
981
982
    if ($flip) {
983
        $file_labels = array_reverse($file_labels);
984
    }
985
986
    $xoopsTpl->assign('chess_gamedata', $gamedata);
987
988
    // comment at end of movetext
989
990
    if (preg_match('/\{(.*?)\}\s*$/', $gamedata['pgn_movetext'], $matches)) {
991
        $xoopsTpl->assign('chess_result_comment', $matches[1]);
992
    } else {
993
        $xoopsTpl->assign('chess_result_comment', '');
994
    }
995
996
    $xoopsTpl->assign('chess_create_date', '0000-00-00 00:00:00' != $gamedata['create_date'] ? strtotime($gamedata['create_date']) : 0);
997
998
    $xoopsTpl->assign('chess_start_date', '0000-00-00 00:00:00' != $gamedata['start_date'] ? strtotime($gamedata['start_date']) : 0);
999
1000
    $xoopsTpl->assign('chess_last_date', '0000-00-00 00:00:00' != $gamedata['last_date'] ? strtotime($gamedata['last_date']) : 0);
1001
1002
    $xoopsTpl->assign('chess_white_user', $white_username);
1003
1004
    $xoopsTpl->assign('chess_black_user', $black_username);
1005
1006
    $xoopsTpl->assign('chess_tiles', $tiles);
1007
1008
    $xoopsTpl->assign('chess_file_labels', $file_labels);
1009
1010
    $xoopsTpl->assign(
1011
        'chess_pgn_string',
1012
        chess_to_pgn_string(
1013
            [
1014
                'event'    => '?',
1015
                'site'     => $_SERVER['SERVER_NAME'],
1016
                'datetime' => $gamedata['start_date'],
1017
                'round'    => '?',
1018
                'white'    => $white_username,
1019
                'black'    => $black_username,
1020
                'result'   => $gamedata['pgn_result'],
1021
                'setup'    => !empty($gamedata['pgn_fen']) ? 1 : 0,
1022
                'fen'      => $gamedata['pgn_fen'],
1023
                'movetext' => $gamedata['pgn_movetext'],
1024
            ]
1025
        )
1026
    );
1027
1028
    $xoopsTpl->assign('chess_movelist', $movelist);
1029
1030
    $xoopsTpl->assign('chess_date_format', _MEDIUMDATESTRING);
1031
1032
    #if (empty($move_result_text)) {$move_result_text = 'test';}#*#DEBUG#
1033
1034
    $xoopsTpl->assign('chess_move_performed', $move_performed);
1035
1036
    $xoopsTpl->assign('chess_move_result', $move_result_text);
1037
1038
    $xoopsTpl->assign('chess_draw_claim_error_text', $draw_claim_error_text);
1039
1040
    $xoopsTpl->assign('chess_user_color', $user_color);
1041
1042
    $xoopsTpl->assign('chess_confirm', $gamedata['w' == $user_color ? 'white_confirm' : 'black_confirm']);
1043
1044
    $xoopsTpl->assign('chess_orientation', $orientation);
1045
1046
    $xoopsTpl->assign('chess_rank_start', $flip ? 1 : 8);
1047
1048
    $xoopsTpl->assign('chess_rank_direction', $flip ? 'up' : 'down');
1049
1050
    $xoopsTpl->assign('chess_file_start', $flip ? 8 : 1);
1051
1052
    $xoopsTpl->assign('chess_file_direction', $flip ? 'down' : 'up');
1053
1054
    static $pieces = [
1055
        'K' => ['color' => 'w', 'name' => 'wking', 'alt' => _MD_CHESS_ALT_WKING],
1056
        'Q' => ['color' => 'w', 'name' => 'wqueen', 'alt' => _MD_CHESS_ALT_WQUEEN],
1057
        'R' => ['color' => 'w', 'name' => 'wrook', 'alt' => _MD_CHESS_ALT_WROOK],
1058
        'B' => ['color' => 'w', 'name' => 'wbishop', 'alt' => _MD_CHESS_ALT_WBISHOP],
1059
        'N' => ['color' => 'w', 'name' => 'wknight', 'alt' => _MD_CHESS_ALT_WKNIGHT],
1060
        'P' => ['color' => 'w', 'name' => 'wpawn', 'alt' => _MD_CHESS_ALT_WPAWN],
1061
        'k' => ['color' => 'b', 'name' => 'bking', 'alt' => _MD_CHESS_ALT_BKING],
1062
        'q' => ['color' => 'b', 'name' => 'bqueen', 'alt' => _MD_CHESS_ALT_BQUEEN],
1063
        'r' => ['color' => 'b', 'name' => 'brook', 'alt' => _MD_CHESS_ALT_BROOK],
1064
        'b' => ['color' => 'b', 'name' => 'bbishop', 'alt' => _MD_CHESS_ALT_BBISHOP],
1065
        'n' => ['color' => 'b', 'name' => 'bknight', 'alt' => _MD_CHESS_ALT_BKNIGHT],
1066
        'p' => ['color' => 'b', 'name' => 'bpawn', 'alt' => _MD_CHESS_ALT_BPAWN],
1067
        'x' => ['color' => 'x', 'name' => 'empty', 'alt' => _MD_CHESS_ALT_EMPTY],
1068
    ];
1069
1070
    $xoopsTpl->assign('chess_pieces', $pieces);
1071
1072
    $xoopsTpl->assign('chess_show_arbitration_controls', $show_arbiter_ctrl);
1073
1074
    if ($show_arbiter_ctrl && $gamedata['suspended']) {
1075
        [$susp_date, $susp_uid, $susp_type, $susp_explain] = explode('|', $gamedata['suspended']);
1076
1077
        switch ($susp_type) {
1078
            case 'arbiter_suspend':
1079
                $susp_type_display = _MD_CHESS_SUSP_TYPE_ARBITER;
1080
                break;
1081
            case 'want_arbitration':
1082
                $susp_type_display = _MD_CHESS_SUSP_TYPE_PLAYER;
1083
                break;
1084
            default:
1085
                $susp_type_display = _MD_CHESS_LABEL_ERROR;
1086
                break;
1087
        }
1088
1089
        $susp_user = $memberHandler->getUser($susp_uid);
1090
1091
        $susp_username = is_object($susp_user) ? $susp_user->getVar('uname') : _MD_CHESS_UNKNOWN;
1092
1093
        $suspend_info = [
1094
            'date'   => strtotime($susp_date),
1095
            'user'   => $susp_username,
1096
            'type'   => $susp_type_display,
1097
            'reason' => $susp_explain,
1098
        ];
1099
1100
        $xoopsTpl->assign('chess_suspend_info', $suspend_info);
1101
    }
1102
1103
    // Initialize $captured_pieces_all to indicate all pieces captured, then subtract off the pieces remaining on the board.
1104
1105
    $captured_pieces_all = [
1106
        'Q' => 1,
1107
        'R' => 2,
1108
        'B' => 2,
1109
        'N' => 2,
1110
        'P' => 8,
1111
        'q' => 1,
1112
        'r' => 2,
1113
        'b' => 2,
1114
        'n' => 2,
1115
        'p' => 8,
1116
    ];
1117
1118
    for ($i = 0, $iMax = mb_strlen($gamedata['fen_piece_placement']); $i < $iMax; ++$i) {
1119
        $piece = $gamedata['fen_piece_placement'][$i];
1120
1121
        if (!empty($captured_pieces_all[$piece])) {
1122
            --$captured_pieces_all[$piece];
1123
        }
1124
    }
1125
1126
    // Construct lists of white's and black's captured pieces.
1127
1128
    $captured_pieces = ['white' => [], 'black' => []];
1129
1130
    foreach ($captured_pieces_all as $piece => $count) {
1131
        if ($count > 0) {
1132
            if (ctype_upper($piece)) {
1133
                $captured_pieces['white'] = array_merge($captured_pieces['white'], array_pad([], $count, $piece));
1134
            } elseif (ctype_lower($piece)) {
1135
                $captured_pieces['black'] = array_merge($captured_pieces['black'], array_pad([], $count, $piece));
1136
            }
1137
        }
1138
    }
1139
1140
    #var_dump('captured_pieces_all', $captured_pieces_all);#*#DEBUG#
1141
1142
    #var_dump('captured_pieces', $captured_pieces);#*#DEBUG#
1143
1144
    $xoopsTpl->assign('chess_captured_pieces', $captured_pieces);
1145
1146
    $xoopsTpl->assign('chess_pawn_promote_choices', 'w' == $gamedata['fen_active_color'] ? ['Q', 'R', 'B', 'N'] : ['q', 'r', 'b', 'n']);
1147
1148
    $xoopsTpl->assign('chess_allow_delete', chess_can_delete());
1149
1150
    // popup window contents for selecting piece to which pawn is promoted
1151
1152
    // (Note that template is compiled here by fetch(), so any template variables it uses must already be defined.)
1153
1154
    $xoopsTpl->assign('chess_pawn_promote_popup', $user_color ? $xoopsTpl->fetch('db:chess_game_promote_popup.tpl') : '');
1155
1156
    $xoopsTpl->assign('chess_ratings_enabled', 'none' != chess_moduleConfig('rating_system'));
1157
1158
    // security token
1159
1160
    $xoopsTpl->assign('chess_xoops_request_token', is_object($GLOBALS['xoopsSecurity']) ? $GLOBALS['xoopsSecurity']->getTokenHTML() : '');
1161
}
1162
1163
?>
1164