summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/JSON/JSON.php806
-rw-r--r--lib/JSON/inc_json.php22
-rw-r--r--lib/conf/apps.php18
-rw-r--r--lib/deck/edit.php2
-rw-r--r--lib/list/addbatch.php43
-rw-r--r--lib/list/edbatch.php47
-rw-r--r--lib/list/edit.php43
-rw-r--r--lib/list/inc_process.php28
-rw-r--r--lib/list/index.php25
-rw-r--r--lib/list/new.php33
-rw-r--r--lib/list/rmbatch.php20
-rw-r--r--lib/list/view.php31
-rw-r--r--lib/study/batch.php31
-rw-r--r--lib/study/batchreview.php23
-rw-r--r--lib/study/brresults.php35
-rw-r--r--lib/study/list.php37
-rw-r--r--lib/study/listadd.php13
-rw-r--r--lib/study/listrm.php16
18 files changed, 1272 insertions, 1 deletions
diff --git a/lib/JSON/JSON.php b/lib/JSON/JSON.php
new file mode 100644
index 0000000..0cddbdd
--- /dev/null
+++ b/lib/JSON/JSON.php
@@ -0,0 +1,806 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Converts to and from JSON format.
+ *
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
+ * format. It is easy for humans to read and write. It is easy for machines
+ * to parse and generate. It is based on a subset of the JavaScript
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+ * This feature can also be found in Python. JSON is a text format that is
+ * completely language independent but uses conventions that are familiar
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
+ * ideal data-interchange language.
+ *
+ * This package provides a simple encoder and decoder for JSON notation. It
+ * is intended for use with client-side Javascript applications that make
+ * use of HTTPRequest to perform server communication functions - data can
+ * be encoded into JSON notation for use in a client-side javascript, or
+ * decoded from incoming Javascript requests. JSON format is native to
+ * Javascript, and can be directly eval()'ed with no further parsing
+ * overhead
+ *
+ * All strings should be in ASCII or UTF-8 format!
+ *
+ * LICENSE: Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met: Redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * @category
+ * @package Services_JSON
+ * @author Michal Migurski <mike-json@teczno.com>
+ * @author Matt Knapp <mdknapp[at]gmail[dot]com>
+ * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+ * @copyright 2005 Michal Migurski
+ * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
+ * @license http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+ */
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_SLICE', 1);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_STR', 2);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_ARR', 3);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_OBJ', 4);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+ * Converts to and from JSON format.
+ *
+ * Brief example of use:
+ *
+ * <code>
+ * // create a new instance of Services_JSON
+ * $json = new Services_JSON();
+ *
+ * // convert a complexe value to JSON notation, and send it to the browser
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+ * $output = $json->encode($value);
+ *
+ * print($output);
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+ *
+ * // accept incoming POST data, assumed to be in JSON notation
+ * $input = file_get_contents('php://input', 1000000);
+ * $value = $json->decode($input);
+ * </code>
+ */
+class Services_JSON
+{
+ /**
+ * constructs a new JSON instance
+ *
+ * @param int $use object behavior flags; combine with boolean-OR
+ *
+ * possible values:
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
+ * "{...}" syntax creates associative arrays
+ * instead of objects in decode().
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
+ * Values which can't be encoded (e.g. resources)
+ * appear as NULL instead of throwing errors.
+ * By default, a deeply-nested resource will
+ * bubble up with an error, so all return values
+ * from encode() should be checked with isError()
+ */
+ function Services_JSON($use = 0)
+ {
+ $this->use = $use;
+ }
+
+ /**
+ * convert a string from one UTF-16 char to one UTF-8 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf16 UTF-16 character
+ * @return string UTF-8 character
+ * @access private
+ */
+ function utf162utf8($utf16)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+ }
+
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+ switch(true) {
+ case ((0x7F & $bytes) == $bytes):
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x7F & $bytes);
+
+ case (0x07FF & $bytes) == $bytes:
+ // return a 2-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
+ . chr(0x80 | ($bytes & 0x3F));
+
+ case (0xFFFF & $bytes) == $bytes:
+ // return a 3-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
+ . chr(0x80 | ($bytes & 0x3F));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * convert a string from one UTF-8 char to one UTF-16 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf8 UTF-8 character
+ * @return string UTF-16 character
+ * @access private
+ */
+ function utf82utf16($utf8)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+ }
+
+ switch(strlen($utf8)) {
+ case 1:
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return $utf8;
+
+ case 2:
+ // return a UTF-16 character from a 2-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x07 & (ord($utf8{0}) >> 2))
+ . chr((0xC0 & (ord($utf8{0}) << 6))
+ | (0x3F & ord($utf8{1})));
+
+ case 3:
+ // return a UTF-16 character from a 3-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr((0xF0 & (ord($utf8{0}) << 4))
+ | (0x0F & (ord($utf8{1}) >> 2)))
+ . chr((0xC0 & (ord($utf8{1}) << 6))
+ | (0x7F & ord($utf8{2})));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access public
+ */
+ function encode($var)
+ {
+ switch (gettype($var)) {
+ case 'boolean':
+ return $var ? 'true' : 'false';
+
+ case 'NULL':
+ return 'null';
+
+ case 'integer':
+ return (int) $var;
+
+ case 'double':
+ case 'float':
+ return (float) $var;
+
+ case 'string':
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+ $ascii = '';
+ $strlen_var = strlen($var);
+
+ /*
+ * Iterate over every character in the string,
+ * escaping with a slash or encoding to UTF-8 where necessary
+ */
+ for ($c = 0; $c < $strlen_var; ++$c) {
+
+ $ord_var_c = ord($var{$c});
+
+ switch (true) {
+ case $ord_var_c == 0x08:
+ $ascii .= '\b';
+ break;
+ case $ord_var_c == 0x09:
+ $ascii .= '\t';
+ break;
+ case $ord_var_c == 0x0A:
+ $ascii .= '\n';
+ break;
+ case $ord_var_c == 0x0C:
+ $ascii .= '\f';
+ break;
+ case $ord_var_c == 0x0D:
+ $ascii .= '\r';
+ break;
+
+ case $ord_var_c == 0x22:
+ case $ord_var_c == 0x2F:
+ case $ord_var_c == 0x5C:
+ // double quote, slash, slosh
+ $ascii .= '\\'.$var{$c};
+ break;
+
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+ // characters U-00000000 - U-0000007F (same as ASCII)
+ $ascii .= $var{$c};
+ break;
+
+ case (($ord_var_c & 0xE0) == 0xC0):
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+ $c += 1;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF0) == 0xE0):
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}));
+ $c += 2;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF8) == 0xF0):
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}));
+ $c += 3;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFC) == 0xF8):
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}));
+ $c += 4;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFE) == 0xFC):
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}),
+ ord($var{$c + 5}));
+ $c += 5;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+ }
+ }
+
+ return '"'.$ascii.'"';
+
+ case 'array':
+ /*
+ * As per JSON spec if any array key is not an integer
+ * we must treat the the whole array as an object. We
+ * also try to catch a sparsely populated associative
+ * array with numeric keys here because some JS engines
+ * will create an array with empty indexes up to
+ * max_index which can cause memory issues and because
+ * the keys, which may be relevant, will be remapped
+ * otherwise.
+ *
+ * As per the ECMA and JSON specification an object may
+ * have any string as a property. Unfortunately due to
+ * a hole in the ECMA specification if the key is a
+ * ECMA reserved word or starts with a digit the
+ * parameter is only accessible using ECMAScript's
+ * bracket notation.
+ */
+
+ // treat as a JSON object
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($var),
+ array_values($var));
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . join(',', $properties) . '}';
+ }
+
+ // treat it like a regular array
+ $elements = array_map(array($this, 'encode'), $var);
+
+ foreach($elements as $element) {
+ if(Services_JSON::isError($element)) {
+ return $element;
+ }
+ }
+
+ return '[' . join(',', $elements) . ']';
+
+ case 'object':
+ $vars = get_object_vars($var);
+
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($vars),
+ array_values($vars));
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . join(',', $properties) . '}';
+
+ default:
+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+ ? 'null'
+ : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
+ }
+ }
+
+ /**
+ * array-walking function for use in generating JSON-formatted name-value pairs
+ *
+ * @param string $name name of key to use
+ * @param mixed $value reference to an array element to be encoded
+ *
+ * @return string JSON-formatted name-value pair, like '"name":value'
+ * @access private
+ */
+ function name_value($name, $value)
+ {
+ $encoded_value = $this->encode($value);
+
+ if(Services_JSON::isError($encoded_value)) {
+ return $encoded_value;
+ }
+
+ return $this->encode(strval($name)) . ':' . $encoded_value;
+ }
+
+ /**
+ * reduce a string by removing leading and trailing comments and whitespace
+ *
+ * @param $str string string value to strip of comments and whitespace
+ *
+ * @return string string value stripped of comments and whitespace
+ * @access private
+ */
+ function reduce_string($str)
+ {
+ $str = preg_replace(array(
+
+ // eliminate single line comments in '// ...' form
+ '#^\s*//(.+)$#m',
+
+ // eliminate multi-line comments in '/* ... */' form, at start of string
+ '#^\s*/\*(.+)\*/#Us',
+
+ // eliminate multi-line comments in '/* ... */' form, at end of string
+ '#/\*(.+)\*/\s*$#Us'
+
+ ), '', $str);
+
+ // eliminate extraneous space
+ return trim($str);
+ }
+
+ /**
+ * decodes a JSON string into appropriate variable
+ *
+ * @param string $str JSON-formatted string
+ *
+ * @return mixed number, boolean, string, array, or object
+ * corresponding to given JSON input string.
+ * See argument 1 to Services_JSON() above for object-output behavior.
+ * Note that decode() always returns strings
+ * in ASCII or UTF-8 format!
+ * @access public
+ */
+ function decode($str)
+ {
+ $str = $this->reduce_string($str);
+
+ switch (strtolower($str)) {
+ case 'true':
+ return true;
+
+ case 'false':
+ return false;
+
+ case 'null':
+ return null;
+
+ default:
+ $m = array();
+
+ if (is_numeric($str)) {
+ // Lookie-loo, it's a number
+
+ // This would work on its own, but I'm trying to be
+ // good about returning integers where appropriate:
+ // return (float)$str;
+
+ // Return float or int, as appropriate
+ return ((float)$str == (integer)$str)
+ ? (integer)$str
+ : (float)$str;
+
+ } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+ // STRINGS RETURNED IN UTF-8 FORMAT
+ $delim = substr($str, 0, 1);
+ $chrs = substr($str, 1, -1);
+ $utf8 = '';
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+ $ord_chrs_c = ord($chrs{$c});
+
+ switch (true) {
+ case $substr_chrs_c_2 == '\b':
+ $utf8 .= chr(0x08);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\t':
+ $utf8 .= chr(0x09);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\n':
+ $utf8 .= chr(0x0A);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\f':
+ $utf8 .= chr(0x0C);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\r':
+ $utf8 .= chr(0x0D);
+ ++$c;
+ break;
+
+ case $substr_chrs_c_2 == '\\"':
+ case $substr_chrs_c_2 == '\\\'':
+ case $substr_chrs_c_2 == '\\\\':
+ case $substr_chrs_c_2 == '\\/':
+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+ $utf8 .= $chrs{++$c};
+ }
+ break;
+
+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+ // single, escaped unicode character
+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+ . chr(hexdec(substr($chrs, ($c + 4), 2)));
+ $utf8 .= $this->utf162utf8($utf16);
+ $c += 5;
+ break;
+
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+ $utf8 .= $chrs{$c};
+ break;
+
+ case ($ord_chrs_c & 0xE0) == 0xC0:
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 2);
+ ++$c;
+ break;
+
+ case ($ord_chrs_c & 0xF0) == 0xE0:
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 3);
+ $c += 2;
+ break;
+
+ case ($ord_chrs_c & 0xF8) == 0xF0:
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 4);
+ $c += 3;
+ break;
+
+ case ($ord_chrs_c & 0xFC) == 0xF8:
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 5);
+ $c += 4;
+ break;
+
+ case ($ord_chrs_c & 0xFE) == 0xFC:
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 6);
+ $c += 5;
+ break;
+
+ }
+
+ }
+
+ return $utf8;
+
+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+ // array, or object notation
+
+ if ($str{0} == '[') {
+ $stk = array(SERVICES_JSON_IN_ARR);
+ $arr = array();
+ } else {
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = array();
+ } else {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = new stdClass();
+ }
+ }
+
+ array_push($stk, array('what' => SERVICES_JSON_SLICE,
+ 'where' => 0,
+ 'delim' => false));
+
+ $chrs = substr($str, 1, -1);
+ $chrs = $this->reduce_string($chrs);
+
+ if ($chrs == '') {
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } else {
+ return $obj;
+
+ }
+ }
+
+ //print("\nparsing {$chrs}\n");
+
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+ $top = end($stk);
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+ if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+ // found a comma that is not inside a string, array, etc.,
+ // OR we've reached the end of the character list
+ $slice = substr($chrs, $top['where'], ($c - $top['where']));
+ array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+ //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ // we are in an array, so just push an element onto the stack
+ array_push($arr, $this->decode($slice));
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ // we are in an object, so figure
+ // out the property name and set an
+ // element in an associative array,
+ // for now
+ $parts = array();
+
+ if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // "name":value pair
+ $key = $this->decode($parts[1]);
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // name:value pair, where name is unquoted
+ $key = $parts[1];
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ }
+
+ }
+
+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+ // found a quote, and we are not inside a string
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+ //print("Found start of string at {$c}\n");
+
+ } elseif (($chrs{$c} == $top['delim']) &&
+ ($top['what'] == SERVICES_JSON_IN_STR) &&
+ ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
+ // found a quote, we're in a string, and it's not escaped
+ // we know that it's not escaped becase there is _not_ an
+ // odd number of backslashes at the end of the string so far
+ array_pop($stk);
+ //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '[') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-bracket, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+ //print("Found start of array at {$c}\n");
+
+ } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+ // found a right-bracket, and we're in an array
+ array_pop($stk);
+ //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '{') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-brace, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+ //print("Found start of object at {$c}\n");
+
+ } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+ // found a right-brace, and we're in an object
+ array_pop($stk);
+ //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($substr_chrs_c_2 == '/*') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a comment start, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+ $c++;
+ //print("Found start of comment at {$c}\n");
+
+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+ // found a comment end, and we're in one now
+ array_pop($stk);
+ $c++;
+
+ for ($i = $top['where']; $i <= $c; ++$i)
+ $chrs = substr_replace($chrs, ' ', $i, 1);
+
+ //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ }
+
+ }
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ return $obj;
+
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @todo Ultimately, this should just call PEAR::isError()
+ */
+ function isError($data, $code = null)
+ {
+ if (class_exists('pear')) {
+ return PEAR::isError($data, $code);
+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+ is_subclass_of($data, 'services_json_error'))) {
+ return true;
+ }
+
+ return false;
+ }
+}
+
+if (class_exists('PEAR_Error')) {
+
+ class Services_JSON_Error extends PEAR_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+ }
+ }
+
+} else {
+
+ /**
+ * @todo Ultimately, this class shall be descended from PEAR_Error
+ */
+ class Services_JSON_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+
+ }
+ }
+
+}
+
+?>
diff --git a/lib/JSON/inc_json.php b/lib/JSON/inc_json.php
new file mode 100644
index 0000000..b36c2a4
--- /dev/null
+++ b/lib/JSON/inc_json.php
@@ -0,0 +1,22 @@
+<?php
+
+if (!function_exists('json_decode')) {
+ function json_decode($content, $assoc=false) {
+ require_once 'lib/JSON/JSON.php';
+ if ($assoc) {
+ $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
+ }
+ else {
+ $json = new Services_JSON;
+ }
+ return $json->decode($content);
+ }
+}
+
+if (!function_exists('json_encode')) {
+ function json_encode($content) {
+ require_once 'lib/JSON/JSON.php';
+ $json = new Services_JSON;
+ return $json->encode($content);
+ }
+}
diff --git a/lib/conf/apps.php b/lib/conf/apps.php
index ef491ee..d8cb1c2 100644
--- a/lib/conf/apps.php
+++ b/lib/conf/apps.php
@@ -40,6 +40,17 @@ $apps = array(
"edent" => 1, // edit card
"mvent" => 1, // move card
),
+ "list" => array(
+ "index" => 0,
+ "view" => 0,
+ "new" => 1,
+ "edit" => 1, // edit global info = name, desc, ...
+
+ "addbatch" => 1,
+ "edbatch" => 1,
+ "rmbatch" => 1,
+ ),
+
"study" => array (
"index" => 1,
"deckadd" => 1,
@@ -47,6 +58,13 @@ $apps = array(
"deckrm" => 1,
"setrate" => 1,
"setcard" => 1,
+
+ "listadd" => 1, // create list_study
+ "list" => 1, // !!!!!! show global progress for a list_study
+ "listrm" => 1, // delete list_study
+ "batch" => 1, // (JS) shows progress for a batch_study
+ "batchreview" => 1, // (JS) batch review app
+ "brresults" => 1,
),
);
diff --git a/lib/deck/edit.php b/lib/deck/edit.php
index 3d893a2..d4102bb 100644
--- a/lib/deck/edit.php
+++ b/lib/deck/edit.php
@@ -32,7 +32,7 @@ if (isset($_POST['name']) && isset($_POST['comment'])) {
}
}
-$title = "Edit : " . $deck['name'];
+$title = "Edit deck : " . $deck['name'];
$fields = array(
array("label" => "Name : ", "name" => "name", "value" => $deck_name),
array("label" => "Comment : ", "name" => "comment", "type" => "textarea", "value" => $deck_comment),
diff --git a/lib/list/addbatch.php b/lib/list/addbatch.php
new file mode 100644
index 0000000..5cb5fb6
--- /dev/null
+++ b/lib/list/addbatch.php
@@ -0,0 +1,43 @@
+<?php
+
+require("lib/list/inc_process.php");
+
+assert_redir(count($args) == 3, 'list');
+$listid = intval($args[2]);
+
+$list = mysql_fetch_assoc(sql(
+ "SELECT lists.id AS id, lists.name AS name, lists.owner AS owner_id ".
+ "FROM lists WHERE lists.id = $listid"));
+assert_error($list && $list['owner_id'] == $user['id'],
+ "This list does not exist, or you are not allowed to edit it.");
+
+$batch_name = "";
+$batch_model = "";
+$batch_contents = "";
+if (isset($_POST['name']) && isset($_POST['model']) && isset($_POST['contents'])) {
+ $batch_name = esca($_POST['name']);
+ $batch_model = esca($_POST['model']);
+ $batch_contents = esca($_POST['contents']);
+ $batch_json = mk_batch_json($batch_model, $batch_contents);
+ if ($batch_name == "") {
+ $error = "You must give a name to this batch.";
+ } else if (mysql_fetch_assoc(sql("SELECT id FROM batches WHERE list = $listid AND name = '" . escs($batch_name) . "'"))) {
+ $error = "You already have a batch using that name.";
+ } else {
+ sql(
+ "INSERT INTO batches(list, name, model, contents, json_data) ".
+ "VALUES($listid, '" . escs($batch_name) . "', '" . escs($batch_model) . "', '" . escs($batch_contents) . "', '" . escs($batch_json) . "')");
+ header("Location: view-list-$listid");
+ die();
+ }
+}
+
+$title = "Add batch to " . $list['name'];
+$fields = array(
+ array("label" => "Name : ", "name" => "name", "value" => $batch_name),
+ array("label" => "Columns : ", "name" => "model", "value" => $batch_model),
+ array("label" => "Contents : ", "name" => "contents", "type" => "textarea", "value" => $batch_contents)
+);
+$validate = "Add batch";
+
+require("tpl/list/ef.php");
diff --git a/lib/list/edbatch.php b/lib/list/edbatch.php
new file mode 100644
index 0000000..380ecf2
--- /dev/null
+++ b/lib/list/edbatch.php
@@ -0,0 +1,47 @@
+<?php
+
+require("lib/list/inc_process.php");
+
+assert_redir(count($args) == 3, 'list');
+$batchid = intval($args[2]);
+
+$batch = mysql_fetch_assoc(sql(
+ "SELECT lists.id AS listid, lists.owner AS listowner, lists.name AS listname, batches.name AS name, ".
+ "batches.model AS model, batches.contents AS contents ".
+ "FROM batches LEFT JOIN lists ON lists.id = batches.list ".
+ "WHERE batches.id = $batchid"));
+assert_error($batch && $batch['listowner'] == $user['id'],
+ "this batch does not exist, or you are not allowed to edit it.");
+$list = array("id" => $batch['listid'], 'name' => $batch['listname']);
+
+$batch_name = $batch['name'];
+$batch_model = $batch['model'];
+$batch_contents = $batch['contents'];
+if (isset($_POST['name']) && isset($_POST['model']) && isset($_POST['contents'])) {
+ $batch_name = esca($_POST['name']);
+ $batch_model = esca($_POST['model']);
+ $batch_contents = esca($_POST['contents']);
+ $batch_json = mk_batch_json($batch_model, $batch_contents);
+ if ($batch_name == "") {
+ $error = "You must give a name to this batch.";
+ } else if (mysql_fetch_assoc(sql("SELECT id FROM batches WHERE list = " . $list['id'] . " AND name = '" . escs($batch_name) . "' AND id != $batchid"))) {
+ $error = "You already have a batch using that name.";
+ } else {
+ sql(
+ "UPDATE batches SET name = '" . escs($batch_name) . "', model = '" . escs($batch_model) . "', ".
+ "contents = '" . escs($batch_contents) . "', json_data = '" . escs($batch_json) . "' WHERE id = $batchid"
+ );
+ header("Location: view-list-" . $list['id']);
+ die();
+ }
+}
+
+$title = "Edit batch in " . $list['name'];
+$fields = array(
+ array("label" => "Name : ", "name" => "name", "value" => $batch_name),
+ array("label" => "Columns : ", "name" => "model", "value" => $batch_model),
+ array("label" => "Contents : ", "name" => "contents", "type" => "textarea", "value" => $batch_contents)
+);
+$validate = "Edit batch";
+
+require("tpl/list/ef.php");
diff --git a/lib/list/edit.php b/lib/list/edit.php
new file mode 100644
index 0000000..64394be
--- /dev/null
+++ b/lib/list/edit.php
@@ -0,0 +1,43 @@
+<?php
+
+require("lib/markdown.php");
+
+assert_redir(count($args) == 3, 'list');
+$listid = intval($args[2]);
+
+$list = mysql_fetch_assoc(sql(
+ "SELECT lists.id AS id, lists.name AS name, lists.comment_md AS comment, account.id AS owner_id ".
+ "FROM lists LEFT JOIN account ON account.id = lists.owner ".
+ "WHERE lists.id = $listid"));
+assert_error($list && $list['owner_id'] == $user['id'],
+ "This list does not exist, or you are not allowed to edit it.");
+
+$list_name = $list['name'];
+$list_comment = $list['comment'];
+if (isset($_POST['name']) && isset($_POST['comment'])) {
+ $list_name = esca($_POST['name']);
+ $list_comment = esca($_POST['comment']);
+ $list_comment_html = Markdown($list_comment);
+ if ($list_name == "") {
+ $error = "You must enter a name for your list.";
+ } else if (mysql_fetch_assoc(sql("SELECT id FROM lists WHERE owner = " . $user['id'] . " AND name = '" . escs($list_name) . "' AND id != $listid"))) {
+ $error = "You already have a list with that title.";
+ } else if ($list_comment == "") {
+ $error = "Please enter a comment on your list.";
+ } else {
+ sql("UPDATE lists SET name = '" . escs($list_name) . "', comment_md = '" . escs($list_comment) .
+ "', comment_html = '" . escs($list_comment_html) . "' WHERE id = $listid");
+ header("Location: view-list-" . $listid);
+ die();
+ }
+}
+
+$title = "Edit list : " . $list['name'];
+$fields = array(
+ array("label" => "Name : ", "name" => "name", "value" => $list_name),
+ array("label" => "Comment : ", "name" => "comment", "type" => "textarea", "value" => $list_comment),
+);
+$validate = "Edit list";
+
+require("tpl/list/ef.php");
+
diff --git a/lib/list/inc_process.php b/lib/list/inc_process.php
new file mode 100644
index 0000000..d4656b5
--- /dev/null
+++ b/lib/list/inc_process.php
@@ -0,0 +1,28 @@
+<?php
+
+require("lib/JSON/inc_json.php");
+
+function mk_batch_json($models, $contents) {
+ $data = array("columns" => array(), "items" => array());
+
+ $columns = explode('|', $models);
+ foreach ($columns as $c) {
+ if ($c[0] == '!') {
+ $data['columns'][] = array("question" => false, "name" => substr($c, 1));
+ } else {
+ $data['columns'][] = array("question" => true, "name" => $c);
+ }
+ }
+
+ $items = explode("\n", $contents);
+ foreach($items as $i) {
+ $ii = explode('|', str_replace("\r", '', $i));
+ if (count($ii) == count($columns)) {
+ $data['items'][] = $ii;
+ }
+ }
+
+ return json_encode($data);
+}
+
+
diff --git a/lib/list/index.php b/lib/list/index.php
new file mode 100644
index 0000000..cbcad67
--- /dev/null
+++ b/lib/list/index.php
@@ -0,0 +1,25 @@
+<?php
+
+$filters = array (
+ "order" => array (
+ "nbUsers" => "popularity",
+ "name" => "name",
+ "owner" => "author",
+ ),
+ "way" => $ord_ways,
+);
+$fdefaults = array (
+ "order" => "nbUsers",
+ "way" => "DESC",
+);
+
+$lists = array();
+$n = sql(
+ "SELECT lists.id AS id, lists.name AS name, account.login AS owner, COUNT(list_study.id) AS nbUsers ".
+ "FROM lists LEFT JOIN account ON lists.owner = account.id LEFT JOIN list_study ON list_study.list = lists.id ".
+ "GROUP BY lists.id ORDER BY " . get_filter("order") . " " . get_filter("way")
+ );
+while ($nn = mysql_fetch_assoc($n)) $lists[] = $nn;
+
+require("tpl/list/index.php");
+
diff --git a/lib/list/new.php b/lib/list/new.php
new file mode 100644
index 0000000..9a9c801
--- /dev/null
+++ b/lib/list/new.php
@@ -0,0 +1,33 @@
+<?php
+
+require("lib/markdown.php");
+
+$list_name = "";
+$list_comment = "";
+if (isset($_POST["name"]) && isset($_POST['comment'])) {
+ $list_name = esca($_POST['name']);
+ $list_comment = esca($_POST['comment']);
+ $list_comment_html = Markdown($list_comment);
+ if ($list_name == "") {
+ $error = "You must enter a name for your list.";
+ } else if (mysql_fetch_assoc(sql("SELECT id FROM lists WHERE owner = " . $user['id'] . " AND name = '" . escs($list_name) . "'"))) {
+ $error = "You already have a list with that title.";
+ } else if ($list_comment == "") {
+ $error = "Please enter a comment on your list.";
+ } else {
+ sql("INSERT INTO lists(owner, name, comment_md, comment_html) ".
+ "VALUES(" . $user['id'] . ", '" . escs($list_name) . "', '" . escs($list_comment) . "', '" . escs($list_comment_html) . "')");
+ header("Location: view-list-" . mysql_insert_id());
+ die();
+ }
+}
+
+$title = "Create list";
+$fields = array(
+ array("label" => "Name : ", "name" => "name", "value" => $list_name),
+ array("label" => "Comment : ", "name" => "comment", "type" => "textarea", "value" => $list_comment),
+ );
+$validate = "Create list";
+
+require("tpl/list/new.php");
+
diff --git a/lib/list/rmbatch.php b/lib/list/rmbatch.php
new file mode 100644
index 0000000..90ea370
--- /dev/null
+++ b/lib/list/rmbatch.php
@@ -0,0 +1,20 @@
+<?php
+
+assert_redir(count($args) >= 3, 'list');
+$batchid = intval($args[2]);
+
+$batch = mysql_fetch_assoc(sql(
+ "SELECT lists.id AS listid, lists.owner AS listowner, lists.name AS listname, batches.name AS name, ".
+ "batches.model AS model, batches.contents AS contents ".
+ "FROM batches LEFT JOIN lists ON lists.id = batches.list ".
+ "WHERE batches.id = $batchid"));
+assert_error($batch && $batch['listowner'] == $user['id'],
+ "this batch does not exist, or you are not allowed to edit it.");
+
+token_validate("Do you really want to delete this batch ?", "view-list-" . $batch['listid']);
+
+sql("DELETE FROM batches WHERE id = $batchid");
+sql("DELETE FROM batch_study WHERE batch = $batchid");
+sql("DELETE FROM batch_review WHERE batch = $batchid");
+header("Location: view-list-" . $batch['listid']);
+die();
diff --git a/lib/list/view.php b/lib/list/view.php
new file mode 100644
index 0000000..7a05bf2
--- /dev/null
+++ b/lib/list/view.php
@@ -0,0 +1,31 @@
+<?php
+
+assert_redir(count($args) == 3, 'list');
+$listid = intval($args[2]);
+
+$list = mysql_fetch_assoc(sql(
+ "SELECT lists.id AS id, lists.name AS name, lists.comment_html AS comment, account.login AS owner, ".
+ "account.id AS owner_id ".
+ "FROM lists LEFT JOIN account ON account.id = lists.owner ".
+ "WHERE lists.id = $listid"));
+assert_error($list, "This list does not exist.");
+
+$can_edit = false;
+if ($list["owner_id"] == $user['id']) $can_edit = true;
+
+$batches = array();
+$n = sql(
+ "SELECT id, name FROM batches WHERE list = $listid ".
+ "ORDER BY name ASC"
+ );
+while ($nn = mysql_fetch_assoc($n)) $batches[] = $nn;
+
+$can_start_study = false;
+if ($user['id'] != 0) {
+ if (!mysql_fetch_assoc(sql("SELECT id FROM list_study WHERE list = $listid AND user = " . $user['id'])));
+ $can_start_study = true;
+} else {
+ $message = "You should create an account in order to study this list.";
+}
+
+require("tpl/list/view.php");
diff --git a/lib/study/batch.php b/lib/study/batch.php
new file mode 100644
index 0000000..d8df526
--- /dev/null
+++ b/lib/study/batch.php
@@ -0,0 +1,31 @@
+<?php
+
+assert_redir(count($args) == 3, 'study');
+$batchid = intval($args[2]);
+
+$info = mysql_fetch_assoc(sql(
+ "SELECT list_study.id AS studyid, batches.name AS bname, ".
+ "lists.name AS lname, account.login AS uname, ".
+ "batch_study.id AS bsid, batches.id AS batchid, batches.json_data AS json_data ".
+ "FROM batches LEFT JOIN list_study ON list_study.list = batches.list AND list_study.user = " . $user['id'] . " " .
+ "LEFT JOIN batch_study ON batch_study.batch = $batchid AND batch_study.user = " . $user['id'] . " " .
+ "LEFT JOIN lists ON batches.list = lists.id ".
+ "LEFT JOIN account ON account.id = lists.owner ".
+ "WHERE batches.id = $batchid"));
+assert_error($info, "This batch does not exist.");
+assert_error($info['studyid'] != 0, "You are not studying this list.");
+
+if ($info["bsid"] == 0) {
+ sql("INSERT INTO batch_study(user, batch, last_review) VALUES(" . $user['id'] . ", $batchid, 0)");
+ $info['bsid'] = mysql_insert_id();
+}
+
+$reviews = array();
+$d = sql("SELECT results, score, date, batch FROM batch_review WHERE batch = $batchid AND user = " . $user['id'] ." ORDER BY date ASC");
+while ($r = mysql_fetch_assoc($d)) {
+ $reviews[] = '{"batch": ' . $r['batch'] . ', "date" : "' . $r['date'] . '", '.
+ '"score": ' . $r['score'] . ', "results": ' . $r['results'] . '}';
+}
+$reviews = '[' . implode(', ', $reviews) . ']';
+
+require("tpl/study/batch.php");
diff --git a/lib/study/batchreview.php b/lib/study/batchreview.php
new file mode 100644
index 0000000..8639193
--- /dev/null
+++ b/lib/study/batchreview.php
@@ -0,0 +1,23 @@
+<?php
+
+assert_redir(count($args) == 3, 'study');
+$batchid = intval($args[2]);
+
+$info = mysql_fetch_assoc(sql(
+ "SELECT list_study.id AS studyid, batches.name AS bname, ".
+ "lists.name AS lname, account.login AS uname, ".
+ "batch_study.id AS bsid, batches.id AS batchid, batches.json_data AS json_data ".
+ "FROM batches LEFT JOIN list_study ON list_study.list = batches.list AND list_study.user = " . $user['id'] . " " .
+ "LEFT JOIN batch_study ON batch_study.batch = $batchid AND batch_study.user = " . $user['id'] . " " .
+ "LEFT JOIN lists ON batches.list = lists.id ".
+ "LEFT JOIN account ON account.id = lists.owner ".
+ "WHERE batches.id = $batchid"));
+assert_error($info, "This batch does not exist.");
+assert_error($info['studyid'] != 0, "You are not studying this list.");
+
+if ($info["bsid"] == 0) {
+ sql("INSERT INTO batch_study(user, batch, last_review) VALUES(" . $user['id'] . ", $batchid, 0)");
+ $info['bsid'] = mysql_insert_id();
+}
+
+require("tpl/study/batch_review.php");
diff --git a/lib/study/brresults.php b/lib/study/brresults.php
new file mode 100644
index 0000000..b3e1068
--- /dev/null
+++ b/lib/study/brresults.php
@@ -0,0 +1,35 @@
+<?php
+
+assert_redir(count($args) == 3, 'study');
+$batchid = intval($args[2]);
+
+$info = mysql_fetch_assoc(sql(
+ "SELECT list_study.id AS studyid, ".
+ "batch_study.id AS bsid, batches.id AS batchid, batches.json_data AS json_data ".
+ "FROM batches LEFT JOIN list_study ON list_study.list = batches.list AND list_study.user = " . $user['id'] . " " .
+ "LEFT JOIN batch_study ON batch_study.batch = $batchid AND batch_study.user = " . $user['id'] . " " .
+ "WHERE batches.id = $batchid"));
+if (!($info)) {
+ echo "This batch does not exist";
+ die();
+}
+if (!($info['studyid'] != 0)) {
+ echo "You are not studying this list.";
+ die();
+}
+
+if ($info["bsid"] == 0) {
+ sql("INSERT INTO batch_study(user, batch, last_review) VALUES(" . $user['id'] . ", $batchid, 0)");
+ $info['bsid'] = mysql_insert_id();
+}
+
+if (isset($_POST['results']) && isset($_POST['score'])) {
+ sql("INSERT INTO batch_review(user, batch, results, score, date) ".
+ "VALUES(" . $user['id'] . ", $batchid, '" . escs(esca($_POST['results'])) . "', " . intval($_POST['score']) . ", NOW())");
+ sql("UPDATE batch_study SET last_review = " . mysql_insert_id() . " WHERE id = " . $info['bsid']);
+ echo 'Saved';
+} else {
+ echo 'Error';
+}
+
+die();
diff --git a/lib/study/list.php b/lib/study/list.php
new file mode 100644
index 0000000..2f0031f
--- /dev/null
+++ b/lib/study/list.php
@@ -0,0 +1,37 @@
+<?php
+
+assert_redir(count($args) == 3, 'study');
+$studyid = intval($args[2]);
+
+$study = mysql_fetch_assoc(sql(
+ "SELECT lists.id AS listid, lists.name AS listname, account.login AS listowner, ".
+ " list_study.user AS learn_user ".
+ "FROM list_study LEFT JOIN lists ON list_study.list = lists.id LEFT JOIN account ON account.id = lists.owner ".
+ "WHERE list_study.id = $studyid"));
+assert_error($study && $study['learn_user'] == $user['id'], "You are not at the right place here.");
+
+$filters = array(
+ "order" => array(
+ "name" => "name",
+ "lr_score" => "last score"
+ ),
+ "way" => $ord_ways,
+);
+$fdefaults = array(
+ "order" => "name",
+ "way" => "ASC",
+);
+
+$batches = array();
+$n = sql(
+ "SELECT batches.id AS id, batches.name AS name, ".
+ "batch_study.id AS bs_id, batch_review.date AS lr_date, batch_review.score AS lr_score ".
+ "FROM batches ".
+ "LEFT JOIN batch_study ON batch_study.batch = batches.id AND batch_study.user = " . $user['id'] . " " .
+ "LEFT JOIN batch_review ON batch_review.id = batch_study.last_review ".
+ "WHERE batches.list = " . $study['listid'] . " " .
+ "ORDER BY " . get_filter("order") . " " . get_filter("way")
+ );
+while ($b = mysql_fetch_assoc($n)) $batches[] = $b;
+
+require("tpl/study/list.php");
diff --git a/lib/study/listadd.php b/lib/study/listadd.php
new file mode 100644
index 0000000..536ab1d
--- /dev/null
+++ b/lib/study/listadd.php
@@ -0,0 +1,13 @@
+<?php
+
+assert_redir(count($args) == 3, 'list');
+$listid = intval($args[2]);
+$list = mysql_fetch_assoc(sql("SELECT id FROM lists WHERE id = $listid"));
+assert_error($list, "This list does not exist.");
+
+assert_error(!mysql_fetch_assoc(sql("SELECT id FROM list_study WHERE list = $listid AND user = " . $user['id'])),
+ "You are already studying this list.");
+
+sql("INSERT INTO list_study(user, list) VALUES(" . $user['id'] . ", $listid)");
+header("Location: list-study-".mysql_insert_id());
+die();
diff --git a/lib/study/listrm.php b/lib/study/listrm.php
new file mode 100644
index 0000000..a761e97
--- /dev/null
+++ b/lib/study/listrm.php
@@ -0,0 +1,16 @@
+<?php
+
+assert_redir(count($args) >= 3, 'study');
+$studyid = intval($args[2]);
+
+$study = mysql_fetch_assoc(sql(
+ "SELECT ".
+ " list_study.user AS learn_user ".
+ "FROM list_study ".
+ "WHERE list_study.id = $studyid"));
+assert_error($study && $study['learn_user'] == $user['id'], "You are not at the right place here.");
+
+token_validate("Do you really want to remove this list from your studies ? Your progress will not be lost.", "list-study-$studyid");
+sql("DELETE FROM list_study WHERE id = $studyid");
+header("Location: study");
+die();