Passed
Push — master ( e12440...7742f2 )
by Michael
02:31
created
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(dirname(__DIR__)) . '/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']);
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
            create_function(
0 ignored issues
show
Deprecated Code introduced by
The function create_function() has been deprecated: 7.2 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

963
            /** @scrutinizer ignore-deprecated */ create_function(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
964
                '$matches',
965
                'return str_repeat(\'x\', $matches[1]);'
966
            ),
967
            $rank
968
        );
969
970
        $rank_tiles = preg_split('//', $expanded_row, -1, PREG_SPLIT_NO_EMPTY);
971
972
        $tiles[] = $flip ? array_reverse($rank_tiles) : $rank_tiles;
973
    }
974
975
    #var_dump('tiles', $tiles);#*#DEBUG#
976
977
    // Convert pgn_movetext into Nx3 array $movelist.
978
979
    $movelist = chess_make_movelist($gamedata['pgn_movetext']);
980
981
    static $file_labels = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
982
983
    if ($flip) {
984
        $file_labels = array_reverse($file_labels);
985
    }
986
987
    $xoopsTpl->assign('chess_gamedata', $gamedata);
988
989
    // comment at end of movetext
990
991
    if (preg_match('/\{(.*?)\}\s*$/', $gamedata['pgn_movetext'], $matches)) {
992
        $xoopsTpl->assign('chess_result_comment', $matches[1]);
993
    } else {
994
        $xoopsTpl->assign('chess_result_comment', '');
995
    }
996
997
    $xoopsTpl->assign('chess_create_date', '0000-00-00 00:00:00' != $gamedata['create_date'] ? strtotime($gamedata['create_date']) : 0);
998
999
    $xoopsTpl->assign('chess_start_date', '0000-00-00 00:00:00' != $gamedata['start_date'] ? strtotime($gamedata['start_date']) : 0);
1000
1001
    $xoopsTpl->assign('chess_last_date', '0000-00-00 00:00:00' != $gamedata['last_date'] ? strtotime($gamedata['last_date']) : 0);
1002
1003
    $xoopsTpl->assign('chess_white_user', $white_username);
1004
1005
    $xoopsTpl->assign('chess_black_user', $black_username);
1006
1007
    $xoopsTpl->assign('chess_tiles', $tiles);
1008
1009
    $xoopsTpl->assign('chess_file_labels', $file_labels);
1010
1011
    $xoopsTpl->assign(
1012
        'chess_pgn_string',
1013
        chess_to_pgn_string(
1014
            [
1015
                'event'    => '?',
1016
                'site'     => $_SERVER['SERVER_NAME'],
1017
                'datetime' => $gamedata['start_date'],
1018
                'round'    => '?',
1019
                'white'    => $white_username,
1020
                'black'    => $black_username,
1021
                'result'   => $gamedata['pgn_result'],
1022
                'setup'    => !empty($gamedata['pgn_fen']) ? 1 : 0,
1023
                'fen'      => $gamedata['pgn_fen'],
1024
                'movetext' => $gamedata['pgn_movetext'],
1025
            ]
1026
        )
1027
    );
1028
1029
    $xoopsTpl->assign('chess_movelist', $movelist);
1030
1031
    $xoopsTpl->assign('chess_date_format', _MEDIUMDATESTRING);
1032
1033
    #if (empty($move_result_text)) {$move_result_text = 'test';}#*#DEBUG#
1034
1035
    $xoopsTpl->assign('chess_move_performed', $move_performed);
1036
1037
    $xoopsTpl->assign('chess_move_result', $move_result_text);
1038
1039
    $xoopsTpl->assign('chess_draw_claim_error_text', $draw_claim_error_text);
1040
1041
    $xoopsTpl->assign('chess_user_color', $user_color);
1042
1043
    $xoopsTpl->assign('chess_confirm', $gamedata['w' == $user_color ? 'white_confirm' : 'black_confirm']);
1044
1045
    $xoopsTpl->assign('chess_orientation', $orientation);
1046
1047
    $xoopsTpl->assign('chess_rank_start', $flip ? 1 : 8);
1048
1049
    $xoopsTpl->assign('chess_rank_direction', $flip ? 'up' : 'down');
1050
1051
    $xoopsTpl->assign('chess_file_start', $flip ? 8 : 1);
1052
1053
    $xoopsTpl->assign('chess_file_direction', $flip ? 'down' : 'up');
1054
1055
    static $pieces = [
1056
        'K' => ['color' => 'w', 'name' => 'wking', 'alt' => _MD_CHESS_ALT_WKING],
1057
        'Q' => ['color' => 'w', 'name' => 'wqueen', 'alt' => _MD_CHESS_ALT_WQUEEN],
1058
        'R' => ['color' => 'w', 'name' => 'wrook', 'alt' => _MD_CHESS_ALT_WROOK],
1059
        'B' => ['color' => 'w', 'name' => 'wbishop', 'alt' => _MD_CHESS_ALT_WBISHOP],
1060
        'N' => ['color' => 'w', 'name' => 'wknight', 'alt' => _MD_CHESS_ALT_WKNIGHT],
1061
        'P' => ['color' => 'w', 'name' => 'wpawn', 'alt' => _MD_CHESS_ALT_WPAWN],
1062
        'k' => ['color' => 'b', 'name' => 'bking', 'alt' => _MD_CHESS_ALT_BKING],
1063
        'q' => ['color' => 'b', 'name' => 'bqueen', 'alt' => _MD_CHESS_ALT_BQUEEN],
1064
        'r' => ['color' => 'b', 'name' => 'brook', 'alt' => _MD_CHESS_ALT_BROOK],
1065
        'b' => ['color' => 'b', 'name' => 'bbishop', 'alt' => _MD_CHESS_ALT_BBISHOP],
1066
        'n' => ['color' => 'b', 'name' => 'bknight', 'alt' => _MD_CHESS_ALT_BKNIGHT],
1067
        'p' => ['color' => 'b', 'name' => 'bpawn', 'alt' => _MD_CHESS_ALT_BPAWN],
1068
        'x' => ['color' => 'x', 'name' => 'empty', 'alt' => _MD_CHESS_ALT_EMPTY],
1069
    ];
1070
1071
    $xoopsTpl->assign('chess_pieces', $pieces);
1072
1073
    $xoopsTpl->assign('chess_show_arbitration_controls', $show_arbiter_ctrl);
1074
1075
    if ($show_arbiter_ctrl && $gamedata['suspended']) {
1076
        [$susp_date, $susp_uid, $susp_type, $susp_explain] = explode('|', $gamedata['suspended']);
1077
1078
        switch ($susp_type) {
1079
            case 'arbiter_suspend':
1080
                $susp_type_display = _MD_CHESS_SUSP_TYPE_ARBITER;
1081
                break;
1082
            case 'want_arbitration':
1083
                $susp_type_display = _MD_CHESS_SUSP_TYPE_PLAYER;
1084
                break;
1085
            default:
1086
                $susp_type_display = _MD_CHESS_LABEL_ERROR;
1087
                break;
1088
        }
1089
1090
        $susp_user = $memberHandler->getUser($susp_uid);
1091
1092
        $susp_username = is_object($susp_user) ? $susp_user->getVar('uname') : _MD_CHESS_UNKNOWN;
1093
1094
        $suspend_info = [
1095
            'date'   => strtotime($susp_date),
1096
            'user'   => $susp_username,
1097
            'type'   => $susp_type_display,
1098
            'reason' => $susp_explain,
1099
        ];
1100
1101
        $xoopsTpl->assign('chess_suspend_info', $suspend_info);
1102
    }
1103
1104
    // Initialize $captured_pieces_all to indicate all pieces captured, then subtract off the pieces remaining on the board.
1105
1106
    $captured_pieces_all = [
1107
        'Q' => 1,
1108
        'R' => 2,
1109
        'B' => 2,
1110
        'N' => 2,
1111
        'P' => 8,
1112
        'q' => 1,
1113
        'r' => 2,
1114
        'b' => 2,
1115
        'n' => 2,
1116
        'p' => 8,
1117
    ];
1118
1119
    for ($i = 0, $iMax = mb_strlen($gamedata['fen_piece_placement']); $i < $iMax; ++$i) {
1120
        $piece = $gamedata['fen_piece_placement'][$i];
1121
1122
        if (!empty($captured_pieces_all[$piece])) {
1123
            --$captured_pieces_all[$piece];
1124
        }
1125
    }
1126
1127
    // Construct lists of white's and black's captured pieces.
1128
1129
    $captured_pieces = ['white' => [], 'black' => []];
1130
1131
    foreach ($captured_pieces_all as $piece => $count) {
1132
        if ($count > 0) {
1133
            if (ctype_upper($piece)) {
1134
                $captured_pieces['white'] = array_merge($captured_pieces['white'], array_pad([], $count, $piece));
1135
            } elseif (ctype_lower($piece)) {
1136
                $captured_pieces['black'] = array_merge($captured_pieces['black'], array_pad([], $count, $piece));
1137
            }
1138
        }
1139
    }
1140
1141
    #var_dump('captured_pieces_all', $captured_pieces_all);#*#DEBUG#
1142
1143
    #var_dump('captured_pieces', $captured_pieces);#*#DEBUG#
1144
1145
    $xoopsTpl->assign('chess_captured_pieces', $captured_pieces);
1146
1147
    $xoopsTpl->assign('chess_pawn_promote_choices', 'w' == $gamedata['fen_active_color'] ? ['Q', 'R', 'B', 'N'] : ['q', 'r', 'b', 'n']);
1148
1149
    $xoopsTpl->assign('chess_allow_delete', chess_can_delete());
1150
1151
    // popup window contents for selecting piece to which pawn is promoted
1152
1153
    // (Note that template is compiled here by fetch(), so any template variables it uses must already be defined.)
1154
1155
    $xoopsTpl->assign('chess_pawn_promote_popup', $user_color ? $xoopsTpl->fetch('db:chess_game_promote_popup.tpl') : '');
1156
1157
    $xoopsTpl->assign('chess_ratings_enabled', 'none' != chess_moduleConfig('rating_system'));
1158
1159
    // security token
1160
1161
    $xoopsTpl->assign('chess_xoops_request_token', is_object($GLOBALS['xoopsSecurity']) ? $GLOBALS['xoopsSecurity']->getTokenHTML() : '');
1162
}
1163
1164
?>
1165