function translator_template_helper($matches)
{
    $attr = shortcode_parse_atts($matches[3]);
    // anything which is not a slug is not an exercise and passes through unchanged.
    if (!array_key_exists('slug', $attr)) {
        return $matches[0];
    }
    if (!array_key_exists('title', $attr)) {
        pyboxlog('[in translator_template_helper] Warning: ' . $attr['slug'] . ' has no title!');
        $attr['title'] = "";
    }
    $r = '[pyRecall slug="' . $attr['slug'] . '"';
    $to_translate = array('title', 'epilogue', 'right', 'wrong', 'defaultcode');
    foreach ($to_translate as $key) {
        if (array_key_exists($key, $attr)) {
            $r .= ' ' . $key . '="';
            $value = $attr[$key];
            $value = str_replace('"', '""', $value);
            $value = str_replace("\n", '\\n', $value);
            $r .= $value . '"';
        }
    }
    $r .= ']' . $matches[5] . '[/pyRecall]';
    return $r;
}
function pybox_database_install()
{
    pyboxlog("running pybox_database_install");
    global $wpdb;
    // we use dbDelta since it is more compatible with upgrades
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    // we create indexes with direct queries since "Duplicate key name" works in our favour
    $table_name = $wpdb->prefix . "pb_completed";
    $sql = "CREATE TABLE " . $table_name . " (\nuserid integer, \nproblem text,\ntime timestamp\n) CHARACTER SET utf8 COLLATE utf8_general_ci;";
    dbDelta($sql);
    $wpdb->query("create index pb_index_problem on " . $wpdb->prefix . "pb_completed (problem (16));");
    $wpdb->query("create index pb_index on " . $wpdb->prefix . "pb_completed (userid, problem (16));");
    $table_name = $wpdb->prefix . "pb_lessons";
    $sql = "CREATE TABLE " . $table_name . " (\nmajor integer,\nminor text,\nordering integer,\ntitle text,\nid integer,\nnumber text,\nlang text,\nPRIMARY KEY  (id)\n) CHARACTER SET utf8 COLLATE utf8_general_ci;";
    dbDelta($sql);
    $wpdb->query("create index pb_index on " . $wpdb->prefix . "pb_lessons (lang (2), ordering);");
    $table_name = $wpdb->prefix . "pb_problems";
    $sql = "CREATE TABLE " . $table_name . " (\npostid integer,\nlesson integer,\nboxid integer,\nslug text,\ntype text,\nfacultative boolean,\nshortcodeArgs text,\ngraderArgs text,\nhash text,\nurl text,\npublicname text,\ncontent text,\nlang text\n) CHARACTER SET utf8 COLLATE utf8_general_ci;";
    dbDelta($sql);
    $wpdb->query("create index pb_index on " . $wpdb->prefix . "pb_problems (hash (32));");
    $wpdb->query("create index pb_index_named on " . $wpdb->prefix . "pb_problems (lang (2), slug (16));");
    $table_name = $wpdb->prefix . "pb_profiling";
    $sql = "CREATE TABLE " . $table_name . " (\nID integer NOT NULL AUTO_INCREMENT,\nactivity text,\nstart datetime,\npreciseStart text,\npreciseEnd text,\nuserid integer,\nduration decimal(20, 10),\ncrossref integer,\nparent integer,\nmeta text,\nprimary key  (ID)\n) CHARACTER SET utf8 COLLATE utf8_general_ci;";
    dbDelta($sql);
    $wpdb->query("create index pb_index on " . $wpdb->prefix . "pb_profiling (activity (32), start);");
    $table_name = $wpdb->prefix . "pb_mail";
    $sql = "CREATE TABLE " . $table_name . " (\nID integer NOT NULL AUTO_INCREMENT,\nustudent integer,\nufrom integer,\nuto integer,\nproblem text,\nbody text,\ntime timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,\nunanswered boolean,\nprimary key  (ID)\n) CHARACTER SET utf8 COLLATE utf8_general_ci;";
    dbDelta($sql);
    $wpdb->query("create index pb_index on " . $wpdb->prefix . "pb_mail (uto, unanswered);");
    $wpdb->query("create index pb_index_subject on " . $wpdb->prefix . "pb_mail (ustudent, problem (16), ID);");
    $wpdb->query("create index pb_index_problem on " . $wpdb->prefix . "pb_mail (problem (16));");
    $wpdb->query("create index pb_index_from on " . $wpdb->prefix . "pb_mail (ufrom);");
    $table_name = $wpdb->prefix . "pb_submissions";
    $sql = "CREATE TABLE " . $table_name . " (\nID INT NOT NULL AUTO_INCREMENT,\nbeginstamp datetime,\nendstamp timestamp, \nuserid integer,\nproblem text,\nhash text,\nusercode text,\nuserinput text,\nresult mediumtext,\nipaddress text,\npostmisc text,\nreferer text,\nPRIMARY KEY  (ID)\n) CHARACTER SET utf8 COLLATE utf8_general_ci;";
    dbDelta($sql);
    $wpdb->query("create index pb_index on " . $wpdb->prefix . "pb_submissions (userid, problem (16), beginstamp);");
    $wpdb->query("create index pb_index_problem on " . $wpdb->prefix . "pb_submissions (problem (16));");
    $wpdb->query("create index pb_hash_result on " . $wpdb->prefix . "pb_submissions (hash (40), result (2));");
}
define('MAX_VIS_CACHED_LEN', 30000);
/************* preliminary stuff *************/
header("Content-type: text/plain; charset=utf8");
require_once "include-to-load-wp.php";
foreach ($_REQUEST as $k => $v) {
    $_REQUEST[$k] = stripslashes($v);
}
if (!array_key_exists('user_script', $_REQUEST)) {
    echo "Error, missing inputs";
    return;
}
if (!array_key_exists('raw_input_json', $_REQUEST)) {
    $_REQUEST['raw_input_json'] = '';
}
if (strlen(print_r($_REQUEST, TRUE)) > POSTLIMIT) {
    pyboxlog("action-optv3.php got too many bytes of data:" . strlen(print_r($_REQUEST, TRUE)));
    return sprintf(__t('Submitted data (program and/or test input) ' . 'too large. Reduce size or <a href = "%s">' . 'run at home</a>.'), cscurl('install'));
}
global $wpdb;
/************* check for a cached version *************/
$cached_result = NULL;
$basehash = md5($_REQUEST['user_script'] . "\t\t\t" . $_REQUEST['raw_input_json']) . '-viz';
$versionedhash = $basehash . VIZ_VERSION;
$dontCache = strstr($_REQUEST['user_script'], 'random') !== FALSE;
if (!$dontCache) {
    // don't cache if randomized
    $the_count = $wpdb->get_var($wpdb->prepare("SELECT count(1) FROM {$wpdb->prefix}pb_submissions\nWHERE hash LIKE %s LIMIT 4", $basehash . '%'));
    $cached_result = $wpdb->get_var($wpdb->prepare("SELECT result FROM {$wpdb->prefix}pb_submissions\nWHERE hash = %s AND result is NOT NULL LIMIT 1", $versionedhash));
    // if cached_result is NULL, we still have to compute it anyway
}
/************* do logging *************/
<?php

require_once "include-to-load-wp.php";
$message = $_REQUEST["message"];
$millis = $_REQUEST["millis"];
$user_stdin = $_REQUEST["user_stdin"];
$user_script = $_REQUEST["user_script"];
$error = getSoft($_REQUEST, "error", "");
$meta = array('message' => $message, 'user_stdin' => $user_stdin, 'user_script' => $user_script);
if ($error != "") {
    $meta['error'] = $error;
    if ($message == 'failed') {
        pyboxlog("Notified of a visualizer error" . "\n" . $error . "\n" . $_SERVER['SERVER_PROTOCOL'] . " " . $_SERVER['HTTP_USER_AGENT'] . " " . $_SERVER["REMOTE_ADDR"] . " " . getUserID() . "\n" . $user_stdin . "\n" . $user_script);
    }
}
retroProfilingEntry($millis * 0.001, array('activity' => 'visualize', 'meta' => $meta));
function resendEmails()
{
    global $wpdb;
    $return;
    foreach ($wpdb->get_results("SELECT * FROM " . $wpdb->prefix . "pb_mail WHERE ID >= 3818 and ID <= 3835 and uto != 0", ARRAY_A) as $r) {
        $problem = $r['problem'];
        $pname = $wpdb->get_var("SELECT publicname FROM " . $wpdb->prefix . "pb_problems WHERE slug like '{$problem}'");
        $purl = $wpdb->get_var("SELECT url FROM " . $wpdb->prefix . "pb_problems WHERE slug like '{$problem}'");
        $subject = "CS Circles - Message about {$pname}";
        $to = get_user_by('id', $r['uto'])->user_email;
        if ($r['ufrom'] == 0) {
            $mFrom = '"' . __t("CS Circles Assistant") . '" <' . CSCIRCLES_BOUNCE_EMAIL . '>';
        } else {
            $user = get_user_by('id', $r['ufrom']);
            $mFrom = '"' . $user->user_login . '" <' . $user->user_email . '>';
        }
        $student = $r['ustudent'];
        $slug = $problem;
        $mailref = $r['ID'];
        $contents = "[Please accept our apologies for the delay in this message, which was caused by a mail daemon problem.]\n\n";
        $contents .= $r['body'] . "\n===\n";
        $contents .= __t("To send a reply message, please visit") . "\n";
        $contents .= cscurl('mail') . "?who={$student}&what={$slug}&which={$mailref}#m";
        $contents .= __t("Problem URL:") . " " . $purl . "\n";
        $contents .= "[" . __t("Sent by CS Circles") . " " . cscurl("homepage") . "]";
        //    $contents .= "\n\n" . $to;
        pyboxlog("Trying to resend message {$mailref}:{$mFrom}|{$to}|{$subject}|{$contents}", TRUE);
        pb_mail($mFrom, $to, $subject, $contents);
        pyboxlog("Resent message {$mailref}", TRUE);
    }
}
function pyBoxHandler($options, $content)
{
    //echo "PB[[[".$content."]]]";
    // given a shortcode, print out the html for the user,
    // and save the relevant grader options in a hash file.
    $id = generateId();
    if ($options == FALSE) {
        $options = array();
    }
    // wordpress does a weird thing where valueless
    for ($i = 0; array_key_exists($i, $options); $i++) {
        // attributes map like [0]=>'attname'.
        $options[$options[$i]] = "Y";
        // these lines change it to
        unset($options[$i]);
        // 'attname'=>'Y'
    }
    $shortcodeOptions = json_encode($options);
    // this will be put into DB.
    /// do some cleaning-up and preprocessing of options, and create the problem info for grader
    if (array_key_exists('translate', $options)) {
        $GLOBALS['pb_translation'] = $options['translate'];
    }
    if (array_key_exists('pyexample', $options)) {
        setSoft($options, 'grader', '*nograder*');
        setSoft($options, 'readonly', 'Y');
        setSoft($options, 'hideemptyinput', 'Y');
        unset($options['pyexample']);
    }
    if (array_key_exists('code', $options)) {
        // sugar: code is an alias for defaultcode
        $options["defaultcode"] = $options["code"];
        unset($options['code']);
    }
    $richreadonly = array_key_exists('richreadonly', $options);
    // sugar
    if ($richreadonly) {
        $options['readonly'] = "Y";
        unset($options['richreadonly']);
    }
    if (array_key_exists('nograder', $options)) {
        // syntactic sugar for nograder option
        if (array_key_exists('grader', $options)) {
            pyboxlog('Warning: grader overwritten with *nograder*');
        }
        $options["grader"] = "*nograder*";
        unset($options['nograder']);
    }
    foreach ($options as $optname => $optvalue) {
        // syntactic sugar for inplace grader
        if (preg_match('|tests|', $optname) > 0 || preg_match('|precode|', $optname) > 0) {
            $options["inplace"] = "Y";
        }
    }
    global $post, $lesson_reg_info;
    // $lessonNumber is numeric (major) part of lesson number
    // if lesson_reg_info is set we don't really care about displaying the pybox,
    // but we'll do things for consistency anyway. NB: when displaying a problem
    // in a place other than its original page (e.g., mail) this needs ot be fixed
    $post_title = isset($lesson_reg_info) ? get_the_title($lesson_reg_info['id']) : $post->post_name;
    if (preg_match('|^(\\d+).*|', $post_title, $matches) == 0) {
        $lessonNumber = -1;
    } else {
        $lessonNumber = $matches[1];
    }
    $inplace = booleanize(getSoft($options, 'inplace', 'N'));
    $scramble = booleanize(getSoft($options, 'scramble', 'N'));
    // important booleans used to determine
    $readonly = booleanize(getSoft($options, 'readonly', 'N'));
    // other options... get them first
    $showEditorToggle = booleanize(getSoft($options, 'showeditortoggle', 'N'));
    unset($options['scramble']);
    unset($options['readonly']);
    // don't extract() these!
    unset($options['inplace']);
    if ($inplace) {
        setSoft($options, 'hideemptyoutput', 'Y');
    }
    $defaultValues = array('defaultcode' => FALSE, 'autocommentline' => $lessonNumber > 3 && !($scramble || $readonly), 'console' => 'N', 'rows' => 10, 'allowinput' => $lessonNumber > 5 && !$scramble && !$readonly, 'disablericheditor' => ($lessonNumber > -1 && $lessonNumber < 7 || $scramble || $readonly) && !$richreadonly, 'usertni' => $inplace);
    foreach ($defaultValues as $key => $value) {
        if (!array_key_exists($key, $options)) {
            $options[$key] = $defaultValues[$key];
        }
    }
    extract($options);
    $allowinput = booleanize($allowinput);
    $disablericheditor = booleanize($disablericheditor);
    $console = booleanize($console);
    $autocommentline = booleanize($autocommentline);
    $usertni = booleanize($usertni);
    $facultative = isset($grader) && $grader == '*nograder*' || $console || $readonly;
    if ($scramble || $readonly) {
        $options['nolog'] = 'Y';
    }
    // for grader. note that if they are absent, their default values are 'N'
    if ($facultative) {
        $options['facultative'] = 'Y';
    } else {
        unset($options['facultative']);
    }
    if ($allowinput) {
        $options['allowinput'] = 'Y';
    } else {
        unset($options['allowinput']);
    }
    if ($scramble) {
        $options['scramble'] = 'Y';
    }
    // already unset
    if ($readonly) {
        $options['readonly'] = 'Y';
    }
    if ($inplace) {
        $options['inplace'] = 'Y';
    }
    if ($usertni) {
        $options['usertni'] = 'Y';
    } else {
        unset($options['usertni']);
    }
    $cosmeticOptions = array('defaultcode', 'autocommentline', 'console', 'rows', 'disablericheditor', 'scramble', 'readonly', 'showeditortoggle', 'title', 'placeholder');
    $copyForGrader = array();
    foreach ($options as $optname => $optvalue) {
        if (!in_array($optname, $cosmeticOptions)) {
            $copyForGrader[$optname] = $optvalue;
        }
    }
    if (array_key_exists('maxeditdistance', $options)) {
        $copyForGrader['originalcode'] = $defaultcode;
    }
    $optionsJson = json_encode($copyForGrader);
    $hash = md5($shortcodeOptions . $optionsJson);
    $slug = getSoft($options, 'slug', 'NULL');
    registerPybox($id, $slug, $scramble ? "scramble" : "code", $facultative, getSoft($options, 'title', NULL), $content, $shortcodeOptions, $hash, $optionsJson);
    if (isMakingDatabases()) {
        $res = do_short_and_sweetcode($content);
        // faster db generation with accurate count
        $GLOBALS['pb_translation'] = NULL;
        return $res;
    }
    /// we've delivered options to the grader. get on with producing html
    if ($defaultcode === FALSE && $scramble && $solver !== FALSE) {
        $lines = explode("\n", trim(softSafeDereference($solver)));
        shuffle($lines);
        $defaultcode = implode("\n", $lines);
    }
    if ($defaultcode !== FALSE) {
        try {
            $defaultcode = softSafeDereference($defaultcode);
        } catch (PyboxException $e) {
            $GLOBALS['pb_translation'] = NULL;
            return pberror("PyBox error: defaultcode file " . $defaultcode . " not found.");
        }
        $defaultcode = ensureNewlineTerminated($defaultcode);
    }
    /// actually start outputting here. part 1: headers and description
    $r = '';
    $readyScripts = '';
    $r .= '<form class="pbform" action="#" id="pbform' . $id . '" method="POST">' . "\n";
    if ($scramble) {
        $c = "scramble";
    } else {
        if (debugEnabled()) {
            $c = "debug";
        } else {
            $c = "";
        }
    }
    if ($facultative) {
        $c .= " facultative";
    }
    $r .= "<div class='pybox modeNeutral {$c}' id='pybox{$id}'>\n";
    if (!$facultative && !array_key_exists("slug", $options)) {
        pyboxlog("Hash " . $hash . " not read-only, but needs a slug", TRUE);
        $r .= slugwarn();
    }
    if ($facultative) {
        if ($console) {
            unset($options["title"]);
            $r .= heading("Console", $options);
        } else {
            $r .= heading(__t('Example'), $options);
        }
    } else {
        $r .= checkbox($slug);
        $r .= heading($scramble ? __t('Scramble Exercise') : __t('Coding Exercise'), $options);
        //if ($scramble)
        //  $r .= "<b>Note (Dec 13)</b>: scramble exercises are temporarily broken &mdash; sorry!<br>";
    }
    $r .= do_short_and_sweetcode($content);
    //instructions, problem description. process any shortcodes inside.
    // part 1.5: help box
    if (!$facultative && !$scramble) {
        $r .= '<div class="helpOuter" style="display: none;"><div class="helpInner">';
        if (!is_user_logged_in()) {
            $r .= '<div style="text-align: center">' . __t('You need to create an account and log in to ask a question.') . '</div>';
        } else {
            global $wpdb;
            $guru_login = get_the_author_meta('pbguru', get_current_user_id());
            if ($guru_login != '') {
                $guruid = $wpdb->get_var($wpdb->prepare('SELECT ID from ' . $wpdb->prefix . 'users WHERE user_login = %s', $guru_login));
            }
            $r .= '<div style="text-align: center">';
            if ($guru_login != '' and $guruid !== NULL) {
                $r .= __t('Send a question by e-mail to: ');
                $r .= "<select class='recipient'>\n<option value='1'>" . __t("My guru") . " ({$guru_login})</option>\n<option value='-1'>" . __t("CS Circles Assistant") . "</option>\n</select></div>";
            } else {
                $r .= __t('Send a question by e-mail to: ');
                $r .= "<select class='recipient'>\n<option value='-1'>" . __t("CS Circles Assistant") . "</option>\n<option value='0'>" . __t("(No guru specified in your profile)") . "</option>\n</select>";
                $r .= '<br/></div>';
            }
            $r .= __t("Enter text for the message below. <i>Be sure to explain where you're stuck and what you've tried so far. Your partial solution code will be automatically included with the message.</i>");
            $r .= "<textarea style='font-family: serif'></textarea>";
            $r .= "<table class='helpControls'><tr class='wp-core-ui'><td style='width: 50%'><a class='button' onclick='sendMessage({$id},\"{$slug}\")'>" . __t("Send this message") . "</a></td><td style='width: 50%'>\n           <a class='button' onclick='helpClick({$id})'>" . __t("Cancel") . "</a></td></tr></table>";
        }
        $r .= '</div></div>';
    }
    /// part 2: code input
    if ($readonly) {
        $thecode = trim($defaultcode);
        $rows = count(explode("\n", $thecode));
    } elseif ($console == "Y" && array_key_exists("consolecode", $_GET)) {
        $thecode = htmlspecialchars(html_entity_decode(stripslashes($_GET["consolecode"])));
        $rows = count(explode("\n", $thecode)) + 1;
    } else {
        $thecode = $defaultcode;
        if ($autocommentline) {
            $thecode .= __t('# delete this comment and enter your code here') . "\n";
        }
        if (array_key_exists('slug', $options) && $scramble === FALSE) {
            $savedCode = loadMostRecent($options['slug']);
            if ($savedCode !== NULL) {
                $thecode = $savedCode;
            }
        }
    }
    if ($scramble) {
        $r .= '<ul class="pyscramble" name="pyscramble" id="pyscramble' . $id . '">' . "\n";
        foreach (explode("\n", rtrim($thecode)) as $s) {
            if (strpos($s, 'delete this comment') === FALSE) {
                // fix an old bug -- got stuck in database
                $r .= ' <li class="pyscramble">' . (trim($s) == '' ? '&nbsp;' : htmlspecialchars(html_entity_decode($s))) . "</li>\n";
            }
        }
        $r .= "</ul>\n";
        $r .= "<input type='hidden' id='usercode{$id}' name='usercode{$id}'/>\n";
    } else {
        //    $r .= "<div class='acecontain ace_hide' id='acecontain$id' ><div class='aceinner' id='ace$id'></div></div>";
        $px = $rows * 26 + 6;
        //+6 for border, padding in weird box model
        $h = $px;
        if (!$readonly) {
            $h = max(50, $h);
        }
        $h = " style='height: {$h}px;'";
        $ro = $readonly ? "readonly='readonly'" : "";
        $c = $readonly ? "RO" : "RW";
        $p = $readonly ? " style = 'height : {$px}px;' " : "";
        $s = $readonly ? "" : "resizy";
        //cols=... required for valid html but width actually set by css
        //height is set explicitly since it's the only way for IE to render the textarea at the correct height
        $pl = array_key_exists("placeholder", $options) ? "placeholder='" . $options['placeholder'] . "'" : "";
        $r .= "<div class='pyboxTextwrap pyboxCodewrap {$c} {$s}' {$h}><textarea wrap='off' name='usercode{$id}' id='usercode{$id}' {$pl} cols=10 rows={$rows} {$ro} {$p} class='pyboxCode {$c}'>\n";
        $r .= $thecode;
        $r .= '</textarea></div>' . "\n";
    }
    // part 2.5 history container
    $r .= "<div id='pbhistory{$id}' class='flexcontain' style='display:none;'></div>\n";
    /// part 3: stdin
    if ($allowinput) {
        if ($usertni === TRUE) {
            $description = __t('Enter testing statements like <tt>print(myfunction("test argument"))</tt> below.');
        } else {
            $description = __t("You may enter input for the program in the box below.");
        }
        $r .= '<div name="pyinput" id="pyinput' . $id . '">';
        $r .= $description;
        $r .= '<div class="pyboxTextwrap resizy" style="height: 102px;" ><textarea wrap="off" name="userinput" class="pyboxInput" cols=10 rows=4></textarea></div>';
        $r .= '</div>' . "\n";
        //cols=10 required for valid html but width actually set by css
    }
    /// part 4: controls
    $tni = $usertni ? 'Y' : 'N';
    $actions = array();
    if ($allowinput) {
        $actions['switch'] = array('id' => "switch{$id}", 'value' => 'Input Switch', 'onclick' => "pbInputSwitch({$id},'{$tni}')");
    }
    if (!$disablericheditor) {
        //    $userLikesRich = (!is_user_logged_in()) || ("true"!==get_the_author_meta( 'pbplain', get_current_user_id()));
        $userLikesRich = TRUE;
        if ($showEditorToggle || $richreadonly) {
            $actions['CMtoggle'] = array('value' => __t('Rich editor'), 'id' => "toggleCM{$id}", 'onclick' => "pbToggleCodeMirror({$id})");
        }
        if ($userLikesRich) {
            $readyScripts .= "jQuery(function(){pbToggleCodeMirror({$id});});";
        }
    }
    if (!$scramble && !$console && ($lessonNumber >= 4 || $lessonNumber < 0)) {
        $actions['consolecopy'] = array('value' => __t('Open in console'), 'onclick' => "pbConsoleCopy({$id})");
    }
    if (!$scramble && ($lessonNumber >= 4 || $lessonNumber < 0)) {
        $actions['visualize'] = array('value' => __t('Visualize'), 'onclick' => "pbVisualize({$id},'{$tni}')");
    }
    if (!$readonly && !$scramble) {
        //$actions['save'] = array('value'=>'Save without running', 'onclick'=>"pbSave($id)");
        if (array_key_exists("slug", $options)) {
            $historyAction = "historyClick({$id},'{$slug}')";
            $actions['history'] = array('value' => __t('History'), 'onclick' => $historyAction);
        }
        if (!($readonly === "Y") && $defaultcode != '' && $defaultcode !== FALSE) {
            // prepare the string for javaScript enclosure
            // we put in single-quotes, rather than json's default double quotes, for compatibility with
            // our $button usage
            $dc = substr(json_encode(htmlspecialchars_decode($defaultcode, ENT_QUOTES), JSON_HEX_APOS), 1, -1);
            $r .= "<input type='hidden' id='defaultCode{$id}' value='{$dc}'></input>\n";
            $actions['default'] = array('value' => __t('Reset code to default'), 'onclick' => "pbSetText({$id},descape(\$('#defaultCode{$id}').val()))");
        }
    }
    if (!$facultative && !$scramble && !get_option('cscircles_hide_help')) {
        $actions['help'] = array('value' => __t('Help'), 'onclick' => "helpClick({$id});");
    }
    if ($richreadonly) {
        $actions = array('CMtoggle' => $actions['CMtoggle']);
    }
    // get rid of all other options
    $r .= "<div class='pyboxbuttons'><table><tr>\n";
    if (!$richreadonly) {
        $r .= "<td><input type='submit' name='submit' id='submit{$id}' value=' '/></td>\n";
    }
    $mb = 3;
    //maximum number of buttons, not counting 'submit'
    $i = 0;
    foreach ($actions as $name => $atts) {
        $i++;
        if ($i <= $mb) {
            $r .= button($name, $atts);
            continue;
        }
        if ($i == 1 + $mb) {
            $r .= "</tr></table><select id='pbSelect{$id}' class='selectmore'><option name='more'>" . __t("More actions...") . "</option>\n";
        }
        $r .= option($name, $atts);
    }
    if (count($actions) > $mb) {
        $r .= "</select></div>\n";
    } else {
        $r .= "</tr></table></div>\n";
    }
    if (isset($cpulimit) && $cpulimit != 1) {
        $timeout = (WALLFACTOR * $cpulimit + WALLBUFFER + 2) * 1000;
        // + 2 seconds for network latency each way
        $r .= "<input type='hidden' name='timeout' value='{$timeout}'/>\n";
    }
    $r .= '<input type="hidden" name="lang" value="' . currLang4() . '"/>';
    $r .= '<input type="hidden" id="inputInUse' . $id . '" name="inputInUse" value="Y"/>' . "\n";
    $r .= '<input type="hidden" name="pyId" value="' . $id . '"/>' . "\n";
    $r .= '<input type="hidden" name="hash" value="' . $hash . '"/>' . "\n";
    // although inputInUse starts as Y, the next script sets it to N and fixes the button labels
    $readyScripts .= $allowinput ? 'pbInputSwitch(' . $id . ',"' . ($usertni ? 'Y' : 'N') . '");' : 'document.getElementById("submit' . $id . '").value = "' . __t('Run program') . '";' . 'document.getElementById("inputInUse' . $id . '").value = "N";';
    /// part 5 : results area, and footers
    $c = count($actions) > $mb ? ' avoidline' : '';
    $r .= "<div id='pbresults{$id}' class='pbresults{$c}'></div>\n";
    $r .= problemSourceWidget(array('hash' => $hash), count($actions) > $mb);
    $r .= '</div>' . "\n";
    $r .= '</form>' . "\n";
    if ($readyScripts != '') {
        $r .= "<script type='text/javascript'>{$readyScripts}</script>\n";
    }
    $GLOBALS['pb_translation'] = NULL;
    return $r;
}
function run_submission($post)
{
    /**************************************************
        part 0 : initialization and checking that a valid problem is selected 
      ************************************************/
    global $logRow, $beginstamp, $userid, $userinput, $meta, $wpdb, $inputInUse, $facultative, $usertni, $mainProfilingID, $slug, $log_it, $appendix;
    $beginstamp = time();
    $logRow = FALSE;
    $meta = array();
    if ($log_it) {
        $mainProfilingID = beginProfilingEntry(array("activity" => "submit-code"));
    }
    /*if ($_SERVER['REQUEST_METHOD'] != 'POST')
      return merror('', 'HTTP mangling: method "' . $_SERVER['REQUEST_METHOD'] .
      '" was requested instead of "POST"', 'suppress');*/
    if (count($post) == 0) {
        return merror('', 'HTTP mangling: request contained no data', 'suppress');
    }
    if (strlen(print_r($post, TRUE)) > POSTLIMIT) {
        pyboxlog("submit.php got too many bytes of data:" . strlen(print_r($post, TRUE)));
        return mfail(sprintf(__t('Submitted data (program and/or test input) ' . 'too large. Reduce size or <a href = "%s">' . 'run at home</a>.'), cscurl('install')));
    }
    $id = getSoft($post, "pyId", "EMPTY");
    $usercode = tabs_to_spaces(3, getSoft($post, "usercode" . $id, -1));
    if (!is_string($usercode)) {
        return merror("", "No usercode" . $id . "!" . print_r($post, TRUE));
    }
    $usercode = preg_replace('|\\xc2\\xa0|', ' ', $usercode);
    // nbsp
    $userinput = getSoft($post, "userinput", "");
    $userinput = preg_replace('|\\xc2\\xa0|', ' ', $userinput);
    //nbsp
    $hash = $post["hash"];
    //$graderArgsString = safeDereference("@file:" . $hash, 'hashes');
    //if (!is_string($graderArgsString))
    //  return merror("", "PyBox error: problem hash " . $hash . " not found.");
    /**************************************************
        part 1 : set global variables, build skeleton log row; quit upon justsave.
      ************************************************/
    //$problemArgs = multilineToAssociative($graderArgsString);
    //  foreach ($problemArgs as $key=>$value)
    //  $problemArgs[$key] = stripcslashes($value);
    $problemArgs = $wpdb->get_var($wpdb->prepare("\nSELECT graderArgs from " . $wpdb->prefix . "pb_problems WHERE hash = %s", $hash));
    if ($problemArgs === NULL) {
        return merror("", sprintf(__t("Pybox error: problem hash %s not found. " . "Try reloading the page."), $hash));
    }
    $problemArgs = json_decode($problemArgs, TRUE);
    //  if ($problemArgs != $problemArgsNew)
    //  pyboxlog("different: " . var_export($problemArgs, TRUE)
    // . var_export($problemArgsNew, TRUE));
    //else
    //  pyboxlog("same", TRUE);
    $re = '/^(' . implode("|", array_keys(optionsAndDefaults())) . ')([0-9]*)$/';
    $problemOptions = optionsAndDefaults();
    $subproblemOptions = array();
    foreach ($problemArgs as $key => $value) {
        $match = preg_match($re, $key, $matches);
        if ($match == 0) {
            return merror("", "PyBox error: unknown option " . $key);
        }
        if ($matches[2] == "") {
            $problemOptions[$matches[1]] = $value;
        } else {
            if (!array_key_exists($matches[2], $subproblemOptions)) {
                $subproblemOptions[$matches[2]] = array();
            }
            $subproblemOptions[$matches[2]][$matches[1]] = $value;
        }
    }
    foreach ($subproblemOptions as $index => $spo) {
        foreach ($problemOptions as $option => $value) {
            if (!array_key_exists($option, $spo)) {
                $subproblemOptions[$index][$option] = $value;
            }
        }
    }
    $inputInUse = isSoft($post, "inputInUse", "Y");
    /*  var_dump( $post, TRUE);
      echo "eq: " .(($post["inputInUse"] === "Y") ? "T":"F");
      echo "inputinuse: " . ($inputInUse ? "T":"F");*/
    if ($inputInUse && !isSoft($problemOptions, "allowinput", "Y")) {
        return merror("", "Pybox error: input not actually allowed");
    }
    $facultative = isSoft($problemOptions, "facultative", "Y") || $inputInUse;
    $usertni = isSoft($problemOptions, "usertni", "Y");
    $userid = is_user_logged_in() ? wp_get_current_user()->ID : -1;
    $meta['userid'] = $userid;
    $meta['problem'] = getSoft($problemArgs, 'slug', $hash);
    $slug = getSoft($problemArgs, 'slug', NULL);
    //most of submit logging preparation. quitting earlier => not logged in DB
    if ($log_it and !isSoft($problemOptions, "nolog", "Y")) {
        $postmisc = $post;
        unset($postmisc['usercode' . $id]);
        unset($postmisc['userinput']);
        unset($postmisc['hash']);
        $logRow = array('beginstamp' => date('Y-m-d H:i:s', $beginstamp), 'usercode' => $usercode, 'hash' => $hash, 'postmisc' => print_r($postmisc, TRUE), 'problem' => $slug, 'ipaddress' => $_SERVER['REMOTE_ADDR'], 'referer' => $_SERVER['HTTP_REFERER']);
        if ($inputInUse) {
            $logRow['userinput'] = $userinput;
        }
        $logRow['userid'] = $userid;
        //if ($logRow['problem']===NULL)
        //pyboxlog('nameless problem that is not read-only!', TRUE);
    }
    // old feature:
    //  $justsave = array_key_exists('justsave', $post);
    //if ($justsave)
    //  return msave();
    /**************************************************
        part 2 : grading
      ************************************************/
    if ($problemOptions['taboo'] != FALSE) {
        $taboo = explode(",", $problemOptions['taboo']);
        foreach ($taboo as $t) {
            $p = strpos($t, "|");
            if ($p === FALSE) {
                $regex = $t;
                $display = $t;
            } else {
                $display = substr($t, 0, $p);
                $regex = substr($t, $p + 1);
            }
            $match = preg_match("#.*" . trim($regex) . ".*#", $usercode);
            if ($match != 0) {
                return mfail(sprintf(__t("You cannot use %s in this exercise."), "<code>" . trim($display) . "</code>"));
            }
        }
    }
    if ($problemOptions["maxeditdistance"] != FALSE) {
        $k = $problemOptions["maxeditdistance"];
        $S = preg_replace('/\\s+/', '', $usercode);
        $T = preg_replace('/\\s+/', '', $problemOptions["originalcode"]);
        $s = strlen($S);
        $t = strlen($T);
        $msg = sprintf(__t("You are only allowed to change at most %s " . "characters compared to the original version " . "of the code."), $k);
        if (abs($s - $t) > 2 * $k + 5) {
            return mfail($msg) . " " . sprintf(__t("You changed %s or more."), 2 * $k + 5);
        } else {
            $DP = array_fill(0, $s + 1, NULL);
            for ($i = 0; $i <= $s; $i++) {
                $DP[$i] = array_fill(0, $t + 1, NULL);
            }
            for ($i = 0; $i <= $s; $i++) {
                for ($j = 0; $j <= $t; $j++) {
                    if ($i == 0 || $j == 0) {
                        $DP[$i][$j] = $i + $j;
                    } else {
                        $DP[$i][$j] = $DP[$i - 1][$j - 1];
                        if ($S[$i - 1] != $T[$j - 1]) {
                            $DP[$i][$j]++;
                        }
                        $DP[$i][$j] = min($DP[$i][$j], 1 + min($DP[$i][$j - 1], $DP[$i - 1][$j]));
                    }
                }
            }
            if ($DP[$s][$t] > 0 + $k) {
                return mfail($msg . " " . sprintf(__t("You changed %s."), $DP[$s][$t]));
            }
        }
    }
    /*************** done preprocessing source code,
       time to execute some things ************************/
    if ($inputInUse && $slug != 'console') {
        global $usertni;
        $appendix = '<div class="testing-warning">';
        if ($usertni) {
            $appendix .= __t('Note: ran with user tests.');
        } else {
            $appendix .= __t('Note: ran with user inputs.');
        }
        if (!isSoft($problemOptions, "facultative", "Y")) {
            $appendix .= ' ' . __t(' Click "Go back to grading" to switch back.');
        } else {
            $appendix .= ' ' . __t(' Click "Hide input box" to switch back.');
        }
    } else {
        $appendix = '';
    }
    if (count($subproblemOptions) == 0) {
        //    if ($problemOptions['grader'] == '*nograder*')
        $subproblemOptions["1"] = $problemOptions;
        //else
        //return merror("", "No test cases found!");
    }
    // $spo: subproblemOptions for the current subproblem
    $tcTotal = 0;
    foreach ($subproblemOptions as $N => $spo) {
        $tcTotal += $spo["repeats"];
    }
    $m = '';
    //the result string, built a bit at a time.
    ksort($subproblemOptions);
    //test case 1, then 2, ...
    $tcCurrent = 0;
    $allCorrect = TRUE;
    ///*********************************************** main grading loop ***/
    foreach ($subproblemOptions as $N => $spo) {
        // spo: subproblemOptions for current subproblem
        for ($i = 0; $i < $spo["repeats"]; $i++) {
            $tcCurrent++;
            if (!$inputInUse && $tcTotal > 1) {
                $m .= "<b>" . sprintf(__t('Results for test case %1$s out of %2$s'), $tcCurrent, $tcTotal) . "</b><br/>";
            }
            try {
                $GLOBALS['pb_translation'] = getSoft($spo, 'translate', NULL);
                $tcOutcome = doGrading($usercode, $spo);
                $GLOBALS['pb_translation'] = NULL;
                $m .= $tcOutcome["message"];
                if ($tcOutcome["result"] == "error") {
                    return merror($m, $tcOutcome["errmsg"]);
                }
                if ($tcOutcome["result"] == "fail" && $spo["haltonwrong"] == "Y") {
                    return mfail($m);
                }
                if ($tcOutcome["result"] == "fail") {
                    $allCorrect = FALSE;
                }
            } catch (PyboxException $e) {
                return merror($m, $e->getMessage());
            }
            if ($inputInUse) {
                break;
            }
        }
        if ($inputInUse) {
            break;
        }
    }
    return $allCorrect ? mpass($m) : mfail($m);
}
function cscircles_makedb_page()
{
    echo "<div class='wrap'>\n<h2>Rebuild CS Circles Databases</h2>\n<div>This page will rebuild the lesson database and the problem database. \n(If you're writing your own lessons, it assumes a structure like 99X-lessontitle\non the lesson slugs; contact us for help if needed.)</div>";
    if (!array_key_exists('submitted', $_REQUEST)) {
        echo "<form method='get' action='admin.php'>\n   <input type='hidden' name='page' value='cscircles-makedb'>\n   <input type='hidden' name='submitted' value='true'>\n   <button class='button-primary' id='submit'>Rebuild Databases</button></form>";
    } else {
        $out = get_pages();
        $lessons = array();
        foreach ($out as $page) {
            if ($page->post_status != 'publish') {
                continue;
            }
            $s = $page->post_title;
            $m = preg_match('/^([0-9]+)([A-Za-z]?)\\: (.*)$/', $s, $matches);
            if (class_exists('PLL_Base')) {
                global $polylang;
                $lang = $polylang->get_post_language($page->ID);
                if ($lang != NULL) {
                    $lang = $lang->slug;
                } else {
                    $lang = substr(get_bloginfo("language"), 0, 2);
                }
            } else {
                // use default language
                $lang = currLang2();
            }
            if ($m >= 1) {
                $lessons[] = array('number' => $matches[1] . $matches[2], 'title' => $matches[3], 'major' => $matches[1], 'minor' => $matches[2], 'id' => $page->ID, 'lang' => $lang);
            } elseif (class_exists('PLL_Base') && get_page_by_path('console')->ID == pll_get_post($page->ID, 'en') || get_page_by_path('console')->ID == $page->ID) {
                $lessons[] = array('id' => $page->ID, 'number' => NULL, 'lang' => $lang);
            }
            // go through the console page too, mainly to set up the right url in history grids,
            // it does not get added to pb_lessons but its contents do get added to pb_problems
        }
        function cmp($l1, $l2)
        {
            $c = strcmp($l1['lang'], $l2['lang']);
            if ($c != 0) {
                if ($l1['lang'] == 'en') {
                    return -1;
                }
                // put english first
                if ($l2['lang'] == 'en') {
                    return 1;
                }
                return $c;
            }
            if ($l1['number'] == NULL ^ $l2['number'] == NULL) {
                return $l1['number'] == NULL ? 1 : -1;
            }
            $c = $l1['major'] - $l2['major'];
            if ($c != 0) {
                return $c;
            }
            return strcmp($l1['minor'], $l2['minor']);
        }
        usort($lessons, 'cmp');
        pyboxlog('[makedb] Rebuilding problem and lesson db', TRUE);
        global $wpdb, $SKIP_DB_REBUILD;
        $SKIP_DB_REBUILD = false;
        // set to true to be diagnostic
        $lesson_table_name = $wpdb->prefix . "pb_lessons";
        $problem_table_name = $wpdb->prefix . "pb_problems";
        if (!$GLOBALS['SKIP_DB_REBUILD']) {
            echo '<b>Truncating tables: ';
            $wpdb->query("TRUNCATE TABLE {$problem_table_name};");
            $wpdb->query("TRUNCATE TABLE {$lesson_table_name};");
            echo 'done</b>';
        }
        $currlang = 'xx';
        $i = -1;
        $gets = array();
        foreach ($lessons as $l) {
            echo '<pre>';
            if ($currlang != $l['lang']) {
                $currlang = $l['lang'];
                $i = 0;
            }
            if ($l['number'] == NULL) {
                $index = -1;
            } else {
                $index = $i;
                $i++;
            }
            $l['ordering'] = $index;
            if ($l['number'] != NULL) {
                echo 'About to insert lesson: ' . rowSummary($l);
                //. json_encode($l);
                if (!$GLOBALS['SKIP_DB_REBUILD']) {
                    echo $wpdb->insert($lesson_table_name, $l) != 1 ? '<br>insert bad' : ' insert ok';
                }
            }
            global $lesson_reg_info, $pyRenderCount, $post;
            $lesson_reg_info = array('index' => $index, 'lang' => $currlang, 'fullnumber' => $l['number'], 'url' => str_replace('/dev', '', get_page_link($l['id'])), 'id' => $l['id']);
            // pyboxlog($currlang . ' ' . $lesson_reg_info['url'], FALSE);  // was used Apr. 7 to help transl.
            // render! the following line is not just cosmetic as it registers the problems.
            echo '<br>Snippet: ' . htmlspecialchars(substr(do_sweetcode(get_page($l['id'])->post_content), 0, 50));
            $pyRenderCount = 0;
            // to get problem links working
            echo '</pre>';
        }
        $lesson_reg_info = 1;
    }
    echo '</div>';
}