diff --git a/.gitignore b/.gitignore index 8fd2cf81..15949525 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ data images thumbs !lib/images +*.phar +*.sqlite # Created by http://www.gitignore.io diff --git a/.htaccess b/.htaccess index ec345a41..fd951590 100644 --- a/.htaccess +++ b/.htaccess @@ -3,7 +3,12 @@ - Deny from all + + Require all denied + + + Deny from all + diff --git a/.scrutinizer.yml b/.scrutinizer.yml index b8c0837b..fe2bf80f 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,6 +1,9 @@ imports: - - javascript - - php +- javascript +- php filter: - excluded_paths: [lib/*,ext/tagger/script.js,ext/chatbox/*] + excluded_paths: [lib/*,ext/tagger/script.js,ext/chatbox/*] + +tools: + external_code_coverage: true diff --git a/.travis.yml b/.travis.yml index 785e53ff..549af438 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,47 +2,42 @@ language: php sudo: false php: -# Here is where we can list the versions of PHP you want to test against -# using major version aliases - - 5.4 - - 5.5 - - 5.6 - - nightly +- 5.4 +- 5.5 +- 5.6 +- nightly -# optionally specify a list of environments, for example to test different RDBMS env: + matrix: - DB=mysql - DB=pgsql - DB=sqlite install: - # Enable logging of all queries (for debugging) and create the database schema for shimmie. - - mkdir -p data/config - - if [[ "$DB" == "pgsql" ]]; then psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres; fi - - if [[ "$DB" == "pgsql" ]]; then psql -c "CREATE DATABASE shimmie;" -U postgres; fi - - if [[ "$DB" == "pgsql" ]]; then echo ' data/config/auto_install.conf.php ; fi - - if [[ "$DB" == "mysql" ]]; then mysql -e "SET GLOBAL general_log = 'ON';" -uroot; fi - - if [[ "$DB" == "mysql" ]]; then mysql -e "CREATE DATABASE shimmie;" -uroot; fi - - if [[ "$DB" == "mysql" ]]; then echo ' data/config/auto_install.conf.php ; fi - - if [[ "$DB" == "sqlite" ]]; then echo ' data/config/auto_install.conf.php ; fi +- mkdir -p data/config +- if [[ "$DB" == "pgsql" ]]; then psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres; fi +- if [[ "$DB" == "pgsql" ]]; then psql -c "CREATE DATABASE shimmie;" -U postgres; fi +- if [[ "$DB" == "pgsql" ]]; then echo ' data/config/auto_install.conf.php ; fi +- if [[ "$DB" == "mysql" ]]; then mysql -e "SET GLOBAL general_log = 'ON';" -uroot; fi +- if [[ "$DB" == "mysql" ]]; then mysql -e "CREATE DATABASE shimmie;" -uroot; fi +- if [[ "$DB" == "mysql" ]]; then echo ' data/config/auto_install.conf.php ; fi +- if [[ "$DB" == "sqlite" ]]; then echo ' data/config/auto_install.conf.php ; fi +- wget https://scrutinizer-ci.com/ocular.phar script: - - php install.php - - phpunit --configuration tests/phpunit.xml +- php install.php +- phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover -# If a failure occured then dump out a bunch of logs for debugging purposes. after_failure: - - head -n 100 data/config/* - - ls /var/run/mysql* - - ls /var/log/*mysql* - - cat /var/log/mysql.err - - cat /var/log/mysql.log - - cat /var/log/mysql/error.log - - cat /var/log/mysql/slow.log - - ls /var/log/postgresql - - cat /var/log/postgresql/postgresql* +- head -n 100 data/config/* +- ls /var/run/mysql* +- ls /var/log/*mysql* +- cat /var/log/mysql.err +- cat /var/log/mysql.log +- cat /var/log/mysql/error.log +- cat /var/log/mysql/slow.log +- ls /var/log/postgresql +- cat /var/log/postgresql/postgresql* -# configure notifications (email, IRC, campfire etc) -#notifications: -# irc: "irc.freenode.org#shimmie" -# +after_script: +- php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover diff --git a/core/basethemelet.class.php b/core/basethemelet.class.php index 19b17e6c..9bfb1a18 100644 --- a/core/basethemelet.class.php +++ b/core/basethemelet.class.php @@ -96,7 +96,7 @@ class BaseThemelet { * * @param string $base_url * @param string $query - * @param int|string $page + * @param string $page * @param string $name * @return string */ @@ -108,8 +108,8 @@ class BaseThemelet { /** * @param string $base_url * @param string $query - * @param int|string $page - * @param int|string $current_page + * @param string $page + * @param int $current_page * @param string $name * @return string */ diff --git a/core/block.class.php b/core/block.class.php index afdb23db..1fbe0e3f 100644 --- a/core/block.class.php +++ b/core/block.class.php @@ -20,7 +20,7 @@ class Block { */ public $body; - /** + /** * Where the block should be placed. The default theme supports * "main" and "left", other themes can add their own areas. * @@ -58,7 +58,7 @@ class Block { $this->body = $body; $this->section = $section; $this->position = $position; - $this->id = str_replace(' ', '_', is_null($id) ? (is_null($header) ? md5($body) : $header) . $section : $id); + $this->id = preg_replace('/[^\w]/', '',str_replace(' ', '_', is_null($id) ? (is_null($header) ? md5($body) : $header) . $section : $id)); } /** @@ -94,4 +94,3 @@ class NavBlock extends Block { parent::__construct("Navigation", "Index", "left", 0); } } - diff --git a/core/database.class.php b/core/database.class.php index 915ef0c7..13cf612c 100644 --- a/core/database.class.php +++ b/core/database.class.php @@ -639,7 +639,7 @@ class Database { * * @param string $query * @param array $args - * @return mixed|null + * @return array|null */ public function get_row($query, $args=array()) { $_start = microtime(true); @@ -702,7 +702,7 @@ class Database { * Get the ID of the last inserted row. * * @param string|null $seq - * @return string + * @return int */ public function get_last_insert_id($seq) { if($this->engine->name == "pgsql") { diff --git a/core/extension.class.php b/core/extension.class.php index eff3f6ca..06c2c5b5 100644 --- a/core/extension.class.php +++ b/core/extension.class.php @@ -282,7 +282,7 @@ abstract class DataHandlerExtension extends Extension { abstract protected function supported_ext($ext); /** - * @param $tmpname + * @param string $tmpname * @return bool */ abstract protected function check_contents($tmpname); diff --git a/core/imageboard.pack.php b/core/imageboard.pack.php index 985c9b25..1d8365f7 100644 --- a/core/imageboard.pack.php +++ b/core/imageboard.pack.php @@ -63,7 +63,7 @@ class Image { public $tag_array; public $owner_id, $owner_ip; - public $posted, $posted_timestamp; + public $posted; public $source; public $locked; @@ -82,7 +82,6 @@ class Image { $name = str_replace("images.", "", $name); $this->$name = $value; // hax, this is likely the cause of much scrutinizer-ci complaints. } - $this->posted_timestamp = strtotime($this->posted); // pray $this->locked = bool_escape($this->locked); assert(is_numeric($this->id)); @@ -192,6 +191,13 @@ class Image { return ($yays > 1 || $nays > 0); } + /** + * @param string[] $tags + * @param int $offset + * @param int $limit + * @return null|PDOStatement + * @throws SCoreException + */ public function get_accelerated_result($tags, $offset, $limit) { global $database; @@ -244,7 +250,7 @@ class Image { * Count the number of image results for a given search * * @param string[] $tags - * @return mixed + * @return int */ public static function count_images($tags=array()) { assert('is_array($tags)'); @@ -284,7 +290,6 @@ class Image { return ceil(Image::count_images($tags) / $config->get_int('index_images')); } - /* * Accessors & mutators */ @@ -401,9 +406,9 @@ class Image { /** * Check configured template for a link, then try nice URL, then plain URL * - * @param $template - * @param $nice - * @param $plain + * @param string $template + * @param string $nice + * @param string $plain * @return string */ private function get_link($template, $nice, $plain) { @@ -532,6 +537,10 @@ class Image { return $this->locked; } + /** + * @param bool $tf + * @throws SCoreException + */ public function set_locked($tf) { global $database; $ln = $tf ? "Y" : "N"; @@ -551,9 +560,22 @@ class Image { */ public function delete_tags_from_image() { global $database; - $database->execute( - "UPDATE tags SET count = count - 1 WHERE id IN ". - "(SELECT tag_id FROM image_tags WHERE image_id = :id)", array("id"=>$this->id)); + if($database->get_driver_name() == "mysql") { + //mysql < 5.6 has terrible subquery optimization, using EXISTS / JOIN fixes this + $database->execute(" + UPDATE tags t + INNER JOIN image_tags it ON t.id = it.tag_id + SET count = count - 1 + WHERE it.image_id = :id", + array("id"=>$this->id) + ); + } else { + $database->execute(" + UPDATE tags + SET count = count - 1 + WHERE id IN (SELECT tag_id FROM image_tags WHERE image_id = :id)", array("id"=>$this->id) + ); + } $database->execute("DELETE FROM image_tags WHERE image_id=:id", array("id"=>$this->id)); } @@ -567,9 +589,6 @@ class Image { assert('is_array($tags) && count($tags) > 0', var_export($tags, true)); global $database; - $tags = array_map(array('Tag', 'sanitise'), $tags); - $tags = Tag::resolve_aliases($tags); - if(count($tags) <= 0) { throw new SCoreException('Tried to set zero tags'); } @@ -579,12 +598,6 @@ class Image { $this->delete_tags_from_image(); // insert each new tags foreach($tags as $tag) { - $ttpe = new TagTermParseEvent($tag, $this->id); - send_event($ttpe); - if($ttpe->is_metatag()) { - continue; - } - if(mb_strlen($tag, 'UTF-8') > 255){ flash_message("The tag below is longer than 255 characters, please use a shorter tag.\n$tag\n"); continue; @@ -623,6 +636,19 @@ class Image { } } + /** + * Send list of metatags to be parsed. + * + * @param [] $metatags + * @param int $image_id + */ + public function parse_metatags($metatags, $image_id) { + foreach($metatags as $tag) { + $ttpe = new TagTermParseEvent($tag, $image_id, TRUE); + send_event($ttpe); + } + } + /** * Delete this image from the database and disk */ @@ -736,9 +762,58 @@ class Image { return Image::build_accurate_search_querylet($terms); } + /** + * @param string[] $terms + * @return ImgQuerylet[] + */ + private static function parse_meta_terms($terms) { + $img_querylets = array(); + $stpe = new SearchTermParseEvent(null, $terms); + send_event($stpe); + if ($stpe->is_querylet_set()) { + foreach ($stpe->get_querylets() as $querylet) { + $img_querylets[] = new ImgQuerylet($querylet, true); + } + } + return $img_querylets; + } + + /** + * @param ImgQuerylet[] $img_querylets + * @return Querylet + */ + private static function build_img_search($img_querylets) { + // merge all the image metadata searches into one generic querylet + $n = 0; + $sql = ""; + $terms = array(); + foreach ($img_querylets as $iq) { + if ($n++ > 0) $sql .= " AND"; + if (!$iq->positive) $sql .= " NOT"; + $sql .= " (" . $iq->qlet->sql . ")"; + $terms = array_merge($terms, $iq->qlet->variables); + } + return new Querylet($sql, $terms); + } + + /** + * @param Querylet $img_search + * @return Querylet + */ + private static function build_simple_query($img_search) { + $query = new Querylet("SELECT images.* FROM images "); + + if (!empty($img_search->sql)) { + $query->append_sql(" WHERE "); + $query->append($img_search); + return $query; + } + return $query; + } + /** * WARNING: this description is no longer accurate, though it does get across - * the general idea - the actual method has a few extra optimisiations + * the general idea - the actual method has a few extra optimisations * * "foo bar -baz user=foo" becomes * @@ -752,7 +827,7 @@ class Image { * A) Incredibly simple: * Each search term maps to a list of image IDs * B) Runs really fast on a good database: - * These lists are calucalted once, and the set intersection taken + * These lists are calculated once, and the set intersection taken * C) Runs really slow on bad databases: * All the subqueries are executed every time for every row in the * images table. Yes, MySQL does suck this much. @@ -764,21 +839,12 @@ class Image { global $database; $tag_querylets = array(); - $img_querylets = array(); + $img_querylets = self::parse_meta_terms($terms); $positive_tag_count = 0; - $stpe = new SearchTermParseEvent(null, $terms); - send_event($stpe); - if($stpe->is_querylet_set()) { - foreach($stpe->get_querylets() as $querylet) { - $img_querylets[] = new ImgQuerylet($querylet, true); - } - } - - $terms = Tag::resolve_aliases($terms); - // parse the words that are searched for into // various types of querylet + $terms = Tag::resolve_aliases($terms); foreach($terms as $term) { $positive = true; if(is_string($term) && !empty($term) && ($term[0] == '-')) { @@ -804,41 +870,25 @@ class Image { } } } - - - // merge all the image metadata searches into one generic querylet - $n = 0; - $sql = ""; - $terms = array(); - foreach($img_querylets as $iq) { - if($n++ > 0) $sql .= " AND"; - if(!$iq->positive) $sql .= " NOT"; - $sql .= " (" . $iq->qlet->sql . ")"; - $terms = array_merge($terms, $iq->qlet->variables); - } - $img_search = new Querylet($sql, $terms); + $img_search = self::build_img_search($img_querylets); // How many tag querylets are there? $count_tag_querylets = count($tag_querylets); // no tags, do a simple search (+image metadata if we have any) if($count_tag_querylets === 0) { - $query = new Querylet("SELECT images.* FROM images "); - - if(!empty($img_search->sql)) { - $query->append_sql(" WHERE "); - $query->append($img_search); - } + $query = self::build_simple_query($img_search); } // one positive tag (a common case), do an optimised search else if($count_tag_querylets === 1 && $tag_querylets[0]->positive) { $query = new Querylet($database->scoreql_to_sql(" - SELECT images.* FROM images + SELECT images.* + FROM images JOIN image_tags ON images.id=image_tags.image_id JOIN tags ON image_tags.tag_id=tags.id WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag) - "), array("tag"=>$tag_querylets[0]->tag)); + "), array("tag"=>$tag_querylets[0]->tag)); if(!empty($img_search->sql)) { $query->append_sql(" AND "); @@ -854,10 +904,12 @@ class Image { foreach($tag_querylets as $tq) { $tag_ids = $database->get_col( - $database->scoreql_to_sql( - "SELECT id FROM tags WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag)" - ), - array("tag"=>$tq->tag)); + $database->scoreql_to_sql(" + SELECT id + FROM tags + WHERE SCORE_STRNORM(tag) = SCORE_STRNORM(:tag) + "), array("tag"=>$tq->tag) + ); if($tq->positive) { $positive_tag_id_array = array_merge($positive_tag_id_array, $tag_ids); $tags_ok = count($tag_ids) > 0; @@ -872,7 +924,7 @@ class Image { $have_pos = count($positive_tag_id_array) > 0; $have_neg = count($negative_tag_id_array) > 0; - $sql = "SELECT images.* FROM images WHERE images.id IN ("; + $sql = ""; if($have_pos) { $positive_tag_id_list = join(', ', $positive_tag_id_array); $sql .= " @@ -894,8 +946,11 @@ class Image { WHERE tag_id IN ($negative_tag_id_list) "; } - $sql .= ")"; - $query = new Querylet($sql); + $query = new Querylet(" + SELECT images.* + FROM images + WHERE images.id IN ($sql) + "); if(strlen($img_search->sql) > 0) { $query->append_sql(" AND "); @@ -921,23 +976,16 @@ class Image { * build_accurate_search_querylet() for a full explanation * * @param array $terms + * @return Querylet */ private static function build_ugly_search_querylet($terms) { global $database; $tag_querylets = array(); - $img_querylets = array(); + $img_querylets = self::parse_meta_terms($terms); $positive_tag_count = 0; $negative_tag_count = 0; - $stpe = new SearchTermParseEvent(null, $terms); - send_event($stpe); - if($stpe->is_querylet_set()) { - foreach($stpe->get_querylets() as $querylet) { - $img_querylets[] = new ImgQuerylet($querylet, true); - } - } - $terms = Tag::resolve_aliases($terms); reset($terms); // rewind to first element in array. @@ -979,44 +1027,24 @@ class Image { else $negative_tag_count++; } $tag_search = new Querylet($sql, $terms); - - // merge all the image metadata searches into one generic querylet - $n = 0; - $sql = ""; - $terms = array(); - foreach($img_querylets as $iq) { - if($n++ > 0) $sql .= " AND"; - if(!$iq->positive) $sql .= " NOT"; - $sql .= " (" . $iq->qlet->sql . ")"; - $terms = array_merge($terms, $iq->qlet->variables); - } - $img_search = new Querylet($sql, $terms); - + $img_search = self::build_img_search($img_querylets); // no tags, do a simple search (+image metadata if we have any) if($positive_tag_count + $negative_tag_count == 0) { - $query = new Querylet("SELECT images.*,UNIX_TIMESTAMP(posted) AS posted_timestamp FROM images "); - - if(!empty($img_search->sql)) { - $query->append_sql(" WHERE "); - $query->append($img_search); - } + $query = self::build_simple_query($img_search); } // one positive tag (a common case), do an optimised search else if($positive_tag_count === 1 && $negative_tag_count === 0) { - $query = new Querylet( - // MySQL is braindead, and does a full table scan on images, running the subquery once for each row -_- - // "{$this->get_images} WHERE images.id IN (SELECT image_id FROM tags WHERE tag LIKE ?) ", - " - SELECT images.*, UNIX_TIMESTAMP(posted) AS posted_timestamp - FROM tags, image_tags, images - WHERE - tag LIKE :tag0 - AND tags.id = image_tags.tag_id - AND image_tags.image_id = images.id - ", - $tag_search->variables); + // MySQL is braindead, and does a full table scan on images, running the subquery once for each row -_- + // "{$this->get_images} WHERE images.id IN (SELECT image_id FROM tags WHERE tag LIKE ?) ", + $query = new Querylet(" + SELECT images.* + FROM images + JOIN image_tags ON images.id=image_tags.image_id + JOIN tags ON image_tags.tag_id=tags.id + WHERE tag LIKE :tag0 + ", $tag_search->variables); if(!empty($img_search->sql)) { $query->append_sql(" AND "); @@ -1031,7 +1059,10 @@ class Image { $x = 0; foreach($tag_search->variables as $tag) { - $tag_ids = $database->get_col("SELECT id FROM tags WHERE tag LIKE :tag", array("tag"=>$tag)); + $tag_ids = $database->get_col( + "SELECT id FROM tags WHERE tag LIKE :tag", + array("tag"=>$tag) + ); $tag_id_array = array_merge($tag_id_array, $tag_ids); $tags_ok = count($tag_ids) > 0 || !$tag_querylets[$x]->positive; @@ -1057,7 +1088,7 @@ class Image { ) ); $query = new Querylet(' - SELECT *, UNIX_TIMESTAMP(posted) AS posted_timestamp + SELECT * FROM ('.$subquery->sql.') AS images ', $subquery->variables); if(!empty($img_search->sql)) { @@ -1102,7 +1133,7 @@ class Tag { * Remove any excess fluff from a user-input tag * * @param string $tag - * @return mixed + * @return string */ public static function sanitise($tag) { assert('is_string($tag)'); @@ -1118,7 +1149,7 @@ class Tag { * * @param string|string[] $tags * @param bool $tagme - * @return array + * @return string[] */ public static function explode($tags, $tagme=true) { assert('is_string($tags) || is_array($tags)'); @@ -1142,6 +1173,8 @@ class Tag { $tag_array = array("tagme"); } + $tag_array = array_iunique($tag_array); //remove duplicate tags + sort($tag_array); return $tag_array; @@ -1213,7 +1246,10 @@ class Tag { global $database; $db_wild_tag = str_replace("%", "\%", $tag); $db_wild_tag = str_replace("*", "%", $db_wild_tag); - $newtags = $database->get_col($database->scoreql_to_sql("SELECT tag FROM tags WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(?)"), array($db_wild_tag)); + $newtags = $database->get_col( + $database->scoreql_to_sql("SELECT tag FROM tags WHERE SCORE_STRNORM(tag) LIKE SCORE_STRNORM(?)"), + array($db_wild_tag) + ); if(count($newtags) > 0) { $resolved = $newtags; } else { @@ -1237,7 +1273,7 @@ class Tag { $i = 0; $tag_count = count($tags); while($i<$tag_count) { - $aliases = explode(' ', Tag::resolve_alias($tags[$i])); + $aliases = Tag::explode(Tag::resolve_alias($tags[$i]), FALSE); foreach($aliases as $alias){ if(!in_array($alias, $new)){ if($tags[$i] == $alias){ @@ -1284,7 +1320,7 @@ function move_upload_to_archive(DataUploadEvent $event) { * @param $base string * @return array */ -function add_dir(/*string*/ $base) { +function add_dir($base) { $results = array(); foreach(list_files($base) as $full_path) { @@ -1307,12 +1343,12 @@ function add_dir(/*string*/ $base) { } /** - * @param $tmpname - * @param $filename - * @param $tags + * @param string $tmpname + * @param string $filename + * @param string $tags * @throws UploadException */ -function add_image(/*string*/ $tmpname, /*string*/ $filename, /*string*/ $tags) { +function add_image($tmpname, $filename, $tags) { assert(file_exists($tmpname)); $pathinfo = pathinfo($filename); @@ -1337,7 +1373,7 @@ function add_image(/*string*/ $tmpname, /*string*/ $filename, /*string*/ $tags) * * @param int $orig_width * @param int $orig_height - * @return array + * @return int[] */ function get_thumbnail_size(/*int*/ $orig_width, /*int*/ $orig_height) { global $config; @@ -1363,4 +1399,3 @@ function get_thumbnail_size(/*int*/ $orig_width, /*int*/ $orig_height) { } } - diff --git a/core/page.class.php b/core/page.class.php index 3d35fee5..cca68f63 100644 --- a/core/page.class.php +++ b/core/page.class.php @@ -205,6 +205,10 @@ class Page { $this->cookies[] = array($full_name, $value, $time, $path); } + /** + * @param string $name + * @return string|null + */ public function get_cookie(/*string*/ $name) { $full_name = COOKIE_PREFIX."_".$name; if(isset($_COOKIE[$full_name])) { diff --git a/core/sys_config.inc.php b/core/sys_config.inc.php index 46e43028..506c8993 100644 --- a/core/sys_config.inc.php +++ b/core/sys_config.inc.php @@ -36,7 +36,7 @@ _d("COMPILE_ELS", false); // boolean pre-build the list of event listeners _d("NICE_URLS", false); // boolean force niceurl mode _d("SEARCH_ACCEL", false); // boolean use search accelerator _d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse -_d("VERSION", '2.5.4+'); // string shimmie version +_d("VERSION", '2.5.5+'); // string shimmie version _d("TIMEZONE", null); // string timezone _d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable _d("EXTRA_EXTS", ""); // optional extra extensions diff --git a/core/util.inc.php b/core/util.inc.php index c9936341..11aa9ae0 100644 --- a/core/util.inc.php +++ b/core/util.inc.php @@ -124,6 +124,25 @@ function no_escape($input) { return $input; } +/** + * @param int $val + * @param int|null $min + * @param int|null $max + * @return int + */ +function clamp($val, $min, $max) { + if(!is_numeric($val) || (!is_null($min) && $val < $min)) { + $val = $min; + } + if(!is_null($max) && $val > $max) { + $val = $max; + } + if(!is_null($min) && !is_null($max)) { + assert('$val >= $min && $val <= $max', "$min <= $val <= $max"); + } + return $val; +} + /** * @param string $name * @param array $attrs @@ -268,17 +287,22 @@ function validate_input($inputs) { foreach($inputs as $key => $validations) { $flags = explode(',', $validations); + + if(in_array('bool', $flags) && !isset($_POST[$key])) { + $_POST[$key] = 'off'; + } + if(in_array('optional', $flags)) { - if(!isset($_POST[$key])) { + if(!isset($_POST[$key]) || trim($_POST[$key]) == "") { + $outputs[$key] = null; continue; } } - - if(!isset($_POST[$key])) { + if(!isset($_POST[$key]) || trim($_POST[$key]) == "") { throw new InvalidInput("Input '$key' not set"); } - $value = $_POST[$key]; + $value = trim($_POST[$key]); if(in_array('user_id', $flags)) { $id = int_escape($value); @@ -308,11 +332,36 @@ function validate_input($inputs) { $outputs[$key] = $value; } else if(in_array('email', $flags)) { - $outputs[$key] = $value; + $outputs[$key] = trim($value); } else if(in_array('password', $flags)) { $outputs[$key] = $value; } + else if(in_array('int', $flags)) { + $value = trim($value); + if(empty($value) || !is_numeric($value)) { + throw new InvalidInput("Invalid int: ".html_escape($value)); + } + $outputs[$key] = (int)$value; + } + else if(in_array('bool', $flags)) { + $outputs[$key] = bool_escape($value); + } + else if(in_array('string', $flags)) { + if(in_array('trim', $flags)) { + $value = trim($value); + } + if(in_array('lower', $flags)) { + $value = strtolower($value); + } + if(in_array('not-empty', $flags)) { + throw new InvalidInput("$key must not be blank"); + } + if(in_array('nullify', $flags)) { + if(empty($value)) $value = null; + } + $outputs[$key] = $value; + } else { throw new InvalidInput("Unknown validation '$validations'"); } @@ -910,21 +959,19 @@ function transload($url, $mfile) { } if($config->get_string("transload_engine") === "fopen") { - $fp = @fopen($url, "r"); - if(!$fp) { + $fp_in = @fopen($url, "r"); + $fp_out = fopen($mfile, "w"); + if(!$fp_in || !$fp_out) { return false; } - $data = ""; $length = 0; - while(!feof($fp) && $length <= $config->get_int('upload_size')) { - $data .= fread($fp, 8192); - $length = strlen($data); + while(!feof($fp_in) && $length <= $config->get_int('upload_size')) { + $data = fread($fp_in, 8192); + $length += strlen($data); + fwrite($fp_out, $data); } - fclose($fp); - - $fp = fopen($mfile, "w"); - fwrite($fp, $data); - fclose($fp); + fclose($fp_in); + fclose($fp_out); $headers = http_parse_headers(implode("\n", $http_response_header)); diff --git a/ext/admin/test.php b/ext/admin/test.php index 271ef104..3f893899 100644 --- a/ext/admin/test.php +++ b/ext/admin/test.php @@ -1,6 +1,6 @@ get_page('admin'); $this->assert_response(403); $this->assert_title("Permission Denied"); @@ -16,7 +16,7 @@ class AdminPageTest extends ShimmiePHPUnitTestCase { $this->assert_title("Admin Tools"); } - function testLowercase() { + public function testLowercase() { $ts = time(); // we need a tag that hasn't been used before $this->log_in_as_admin(); @@ -37,7 +37,7 @@ class AdminPageTest extends ShimmiePHPUnitTestCase { } # FIXME: make sure the admin tools actually work - function testRecount() { + public function testRecount() { $this->log_in_as_admin(); $this->get_page('admin'); $this->assert_title("Admin Tools"); @@ -46,7 +46,7 @@ class AdminPageTest extends ShimmiePHPUnitTestCase { send_event(new AdminActionEvent('recount_tag_use')); } - function testDump() { + public function testDump() { $this->log_in_as_admin(); $this->get_page('admin'); $this->assert_title("Admin Tools"); @@ -57,7 +57,7 @@ class AdminPageTest extends ShimmiePHPUnitTestCase { //$this->assert_response(200); } - function testDBQ() { + public function testDBQ() { $this->log_in_as_user(); $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "test"); $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "test2"); diff --git a/ext/alias_editor/test.php b/ext/alias_editor/test.php index f714a904..0b8e4512 100644 --- a/ext/alias_editor/test.php +++ b/ext/alias_editor/test.php @@ -1,12 +1,12 @@ get_page('alias/list'); $this->assert_response(200); $this->assert_title("Alias List"); } - function testAliasListReadOnly() { + public function testAliasListReadOnly() { // Check that normal users can't add aliases. $this->log_in_as_user(); $this->get_page('alias/list'); @@ -14,7 +14,7 @@ class AliasEditorTest extends ShimmiePHPUnitTestCase { $this->assert_no_text("Add"); } - function testAliasEditor() { + public function testAliasEditor() { /* ********************************************************************** * FIXME: TODO: @@ -26,6 +26,8 @@ class AliasEditorTest extends ShimmiePHPUnitTestCase { * dig into this and determine exactly what is happening. * ********************************************************************* + */ + $this->markTestIncomplete(); $this->log_in_as_admin(); @@ -97,8 +99,6 @@ class AliasEditorTest extends ShimmiePHPUnitTestCase { $this->get_page('alias/list'); $this->assert_title("Alias List"); $this->assert_no_text("Add"); - - */ } } diff --git a/ext/artists/main.php b/ext/artists/main.php index c5e70b30..a2380120 100644 --- a/ext/artists/main.php +++ b/ext/artists/main.php @@ -21,8 +21,7 @@ class AuthorSetEvent extends Event { * @param User $user * @param string $author */ - public function __construct(Image $image, User $user, /*string*/ $author) - { + public function __construct(Image $image, User $user, /*string*/ $author) { $this->image = $image; $this->user = $user; $this->author = $author; @@ -37,20 +36,12 @@ class Artists extends Extension { } } - public function onAuthorSet(AuthorSetEvent $event) { - $this->update_author($event); - } - - public function onInitExt(InitExtEvent $event) { - $this->try_install(); - } - public function onImageInfoBoxBuilding(ImageInfoBoxBuildingEvent $event) { - $this->add_author_field_to_image($event); - } - - public function onPageRequest(PageRequestEvent $event) { - $this->handle_commands($event); + global $user; + $artistName = $this->get_artistName_by_imageID($event->image->id); + if(!$user->is_anonymous()) { + $event->add_part($this->theme->get_author_editor_html($artistName), 42); + } } public function onSearchTermParse(SearchTermParseEvent $event) { @@ -61,7 +52,7 @@ class Artists extends Extension { } } - public function try_install() { + public function onInitExt(InitExtEvent $event) { global $config, $database; if ($config->get_int("ext_artists_version") < 1) { @@ -105,7 +96,7 @@ class Artists extends Extension { FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, FOREIGN KEY (artist_id) REFERENCES artists (id) ON UPDATE CASCADE ON DELETE CASCADE "); - $database->execute("ALTER TABLE images ADD COLUMN author VARCHAR(255) NULL", array()); + $database->execute("ALTER TABLE images ADD COLUMN author VARCHAR(255) NULL"); $config->set_int("artistsPerPage", 20); $config->set_int("ext_artists_version", 1); @@ -114,8 +105,7 @@ class Artists extends Extension { } } - public function update_author($event) - { + public function onAuthorSet(AuthorSetEvent $event) { global $database; $author = strtolower($event->author); @@ -137,28 +127,25 @@ class Artists extends Extension { if (is_null($artistID) && $this->url_exists_by_url($author)) $artistID = $this->get_artistID_by_url($author); - if (!is_null($artistID)) + if (!is_null($artistID)) { $artistName = $this->get_artistName_by_artistID($artistID); - else - { + } + else { $this->save_new_artist($author, ""); $artistName = $author; } - $database->execute("UPDATE images SET author = ? WHERE id = ?" - , array( - $artistName - , $event->image->id - )); + $database->execute( + "UPDATE images SET author = ? WHERE id = ?", + array($artistName, $event->image->id) + ); } - public function handle_commands($event) - { + + public function onPageRequest(PageRequestEvent $event) { global $page, $user; - if($event->page_matches("artist")) - { - switch($event->get_arg(0)) - { + if($event->page_matches("artist")) { + switch($event->get_arg(0)) { //*************ARTIST SECTION************** case "list": { @@ -168,9 +155,10 @@ class Artists extends Extension { } case "new": { - if(!$user->is_anonymous()){ + if(!$user->is_anonymous()) { $this->theme->new_artist_composer(); - }else{ + } + else { $this->theme->display_error(401, "Error", "You must be registered and logged in to create a new artist."); } break; @@ -183,21 +171,17 @@ class Artists extends Extension { } case "create": { - if(!$user->is_anonymous()) - { + if(!$user->is_anonymous()) { $newArtistID = $this->add_artist(); - if ($newArtistID == -1) - { + if ($newArtistID == -1) { $this->theme->display_error(400, "Error", "Error when entering artist data."); } - else - { + else { $page->set_mode("redirect"); $page->set_redirect(make_link("artist/view/".$newArtistID)); } } - else - { + else { $this->theme->display_error(401, "Error", "You must be registered and logged in to create a new artist."); } break; @@ -217,8 +201,7 @@ class Artists extends Extension { $images = Image::find_images(0, 4, Tag::explode($artist['name'])); $this->theme->show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin); - if ($userIsLogged) - { + if ($userIsLogged) { //$this->theme->show_new_alias_composer($artistID); //$this->theme->show_new_member_composer($artistID); //$this->theme->show_new_url_composer($artistID); @@ -237,12 +220,13 @@ class Artists extends Extension { $members = $this->get_members($artistID); $urls = $this->get_urls($artistID); - if(!$user->is_anonymous()){ + if(!$user->is_anonymous()) { $this->theme->show_artist_editor($artist, $aliases, $members, $urls); $userIsAdmin = $user->is_admin(); $this->theme->sidebar_options("editor", $artistID, $userIsAdmin); - }else{ + } + else { $this->theme->display_error(401, "Error", "You must be registered and logged in to edit an artist."); } break; @@ -272,7 +256,7 @@ class Artists extends Extension { case "nuke": { $artistID = $event->get_arg(1); - $this->delete_artist($artistID); // this will delete the artist, it's alias, it's urls and it's members + $this->delete_artist($artistID); // this will delete the artist, its alias, its urls and its members $page->set_mode("redirect"); $page->set_redirect(make_link("artist/list")); break; @@ -423,223 +407,228 @@ class Artists extends Extension { } } - public function add_author_field_to_image($event) - { - global $user; - $artistName = $this->get_artistName_by_imageID($event->image->id); - if(!$user->is_anonymous()) { - $event->add_part($this->theme->get_author_editor_html($artistName), 42); - } - } - - private function get_artistName_by_imageID($imageID) - { - if(!is_numeric($imageID)) return null; + /** + * @param int $imageID + * @return string + */ + private function get_artistName_by_imageID($imageID) { + assert(is_numeric($imageID)); global $database; - $result = $database->get_row("SELECT author FROM images WHERE id = ?", array($imageID)); return stripslashes($result['author']); } - private function url_exists_by_url($url) - { + /** + * @param string $url + * @return bool + */ + private function url_exists_by_url($url) { global $database; - $result = $database->get_one("SELECT COUNT(1) FROM artist_urls WHERE url = ?", array($url)); return ($result != 0); } - private function member_exists_by_name($member) - { + /** + * @param string $member + * @return bool + */ + private function member_exists_by_name($member) { global $database; - $result = $database->get_one("SELECT COUNT(1) FROM artist_members WHERE name = ?", array($member)); return ($result != 0); } - private function alias_exists_by_name($alias) - { + /** + * @param string $alias + * @return bool + */ + private function alias_exists_by_name($alias) { global $database; $result = $database->get_one("SELECT COUNT(1) FROM artist_alias WHERE alias = ?", array($alias)); return ($result != 0); } - private function alias_exists($artistID, $alias){ - if (!is_numeric($artistID)) return; + /** + * @param int $artistID + * @param string $alias + * @return bool + */ + private function alias_exists($artistID, $alias) { + assert(is_numeric($artistID)); global $database; - - $result = $database->get_one("SELECT COUNT(1) FROM artist_alias WHERE artist_id = ? AND alias = ?", array( - $artistID - , $alias - )); + $result = $database->get_one( + "SELECT COUNT(1) FROM artist_alias WHERE artist_id = ? AND alias = ?", + array($artistID, $alias) + ); return ($result != 0); } - private function get_artistID_by_url($url) - { + /** + * @param string $url + * @return int + */ + private function get_artistID_by_url($url) { global $database; - $result = $database->get_row("SELECT artist_id FROM artist_urls WHERE url = ?", array($url)); - return $result['artist_id']; + return $database->get_one("SELECT artist_id FROM artist_urls WHERE url = ?", array($url)); } - private function get_artistID_by_memberName($member) - { + /** + * @param string $member + * @return int + */ + private function get_artistID_by_memberName($member) { global $database; - $result = $database->get_row("SELECT artist_id FROM artist_members WHERE name = ?", array($member)); - return $result['artist_id']; - } - private function get_artistName_by_artistID($artistID) - { - if (!is_numeric($artistID)) return; - - global $database; - $result = $database->get_row("SELECT name FROM artists WHERE id = ?", array($artistID)); - return stripslashes($result['name']); + return $database->get_one("SELECT artist_id FROM artist_members WHERE name = ?", array($member)); } - private function get_artistID_by_aliasID($aliasID) - { - if (!is_numeric($aliasID)) return; + /** + * @param int $artistID + * @return string + */ + private function get_artistName_by_artistID($artistID) { + assert(is_numeric($artistID)); global $database; - $result = $database->get_row("SELECT artist_id FROM artist_alias WHERE id = ?", array($aliasID)); - return $result['artist_id']; + return $database->get_one("SELECT name FROM artists WHERE id = ?", array($artistID)); } - private function get_artistID_by_memberID($memberID) - { - if (!is_numeric($memberID)) return; + /** + * @param int $aliasID + * @return int + */ + private function get_artistID_by_aliasID($aliasID) { + assert(is_numeric($aliasID)); global $database; - $result = $database->get_row("SELECT artist_id FROM artist_members WHERE id = ?", array($memberID)); - return $result['artist_id']; + return $database->get_one("SELECT artist_id FROM artist_alias WHERE id = ?", array($aliasID)); } - private function get_artistID_by_urlID($urlID) - { - if (!is_numeric($urlID)) return; + /** + * @param int $memberID + * @return int + */ + private function get_artistID_by_memberID($memberID) { + assert(is_numeric($memberID)); global $database; - $result = $database->get_row("SELECT artist_id FROM artist_urls WHERE id = ?", array($urlID)); - return $result['artist_id']; + return $database->get_one("SELECT artist_id FROM artist_members WHERE id = ?", array($memberID)); } - private function delete_alias($aliasID) - { - if (!is_numeric($aliasID)) return; + /** + * @param int $urlID + * @return int + */ + private function get_artistID_by_urlID($urlID) { + assert(is_numeric($urlID)); + + global $database; + return $database->get_one("SELECT artist_id FROM artist_urls WHERE id = ?", array($urlID)); + } + + /** + * @param int $aliasID + */ + private function delete_alias($aliasID) { + assert(is_numeric($aliasID)); global $database; $database->execute("DELETE FROM artist_alias WHERE id = ?", array($aliasID)); } - private function delete_url($urlID) - { - if (!is_numeric($urlID)) return; + /** + * @param int $urlID + */ + private function delete_url($urlID) { + assert(is_numeric($urlID)); global $database; $database->execute("DELETE FROM artist_urls WHERE id = ?", array($urlID)); } - private function delete_member($memberID) - { - if (!is_numeric($memberID)) return; + /** + * @param int $memberID + */ + private function delete_member($memberID) { + assert(is_numeric($memberID)); global $database; $database->execute("DELETE FROM artist_members WHERE id = ?", array($memberID)); } - - private function get_alias_by_id($aliasID) - { - if (!is_numeric($aliasID)) return; + /** + * @param int $aliasID + * @return array + */ + private function get_alias_by_id($aliasID) { + assert(is_numeric($aliasID)); global $database; $result = $database->get_row("SELECT * FROM artist_alias WHERE id = ?", array($aliasID)); - $result["alias"] = stripslashes($result["alias"]); - return $result; } - private function get_url_by_id($urlID) - { - if (!is_numeric($urlID)) return; + /** + * @param int $urlID + * @return array + */ + private function get_url_by_id($urlID) { + assert(is_numeric($urlID)); global $database; $result = $database->get_row("SELECT * FROM artist_urls WHERE id = ?", array($urlID)); - $result["url"] = stripslashes($result["url"]); - return $result; } - private function get_member_by_id($memberID) - { - if (!is_numeric($memberID)) return; + /** + * @param int $memberID + * @return array + */ + private function get_member_by_id($memberID) { + assert(is_numeric($memberID)); global $database; $result = $database->get_row("SELECT * FROM artist_members WHERE id = ?", array($memberID)); - $result["name"] = stripslashes($result["name"]); - return $result; } - private function update_artist() - { + private function update_artist() { global $user; - $artistID = $_POST['id']; - $name = strtolower($_POST['name']); - $notes = $_POST['notes']; + $inputs = validate_input(array( + 'id' => 'int', + 'name' => 'string,lower', + 'notes' => 'string,trim,nullify', + 'aliases' => 'string,trim,nullify', + 'aliasesIDs' => 'string,trim,nullify', + 'members' => 'string,trim,nullify', + )); + $artistID = $inputs['id']; + $name = $inputs['name']; + $notes = $inputs['notes']; $userID = $user->id; - $aliasesAsString = trim($_POST["aliases"]); - if (strlen($aliasesAsString) == 0) $aliasesAsString = NULL; - $aliasesIDsAsString = trim($_POST["aliasesIDs"]); - if (strlen($aliasesIDsAsString) == 0) $aliasesIDsAsString = NULL; + $aliasesAsString = $inputs["aliases"]; + $aliasesIDsAsString = $inputs["aliasesIDs"]; - $membersAsString = trim($_POST["members"]); - if (strlen($membersAsString) == 0) $membersAsString = NULL; - $membersIDsAsString = trim($_POST["membersIDs"]); - if (strlen($membersIDsAsString) == 0) $membersIDsAsString = NULL; + $membersAsString = $inputs["members"]; + $membersIDsAsString = $inputs["membersIDs"]; - $urlsAsString = trim($_POST["urls"]); - if (strlen($urlsAsString) == 0) $urlsAsString = NULL; - $urlsIDsAsString = trim($_POST["urlsIDs"]); - if (strlen($urlsIDsAsString) == 0) $urlsIDsAsString = NULL; + $urlsAsString = $inputs["urls"]; + $urlsIDsAsString = $inputs["urlsIDs"]; - if (is_null($artistID) || !is_numeric($artistID)) + if(strpos($name, " ")) return; - if (is_null($userID) || !is_numeric($userID)) - return; - - if (is_null($name) || strlen($name) == 0 || strpos($name, " ")) - return; - - //if (is_null($aliasesAsString) || is_null($aliasesIDsAsString)) - // return; - - //if (is_null($membersAsString) || is_null($membersIDsAsString)) - // return; - - //if (is_null($urlsAsString) || is_null($urlsIDsAsString)) - // return; - - if (strlen($notes) == 0) - $notes = NULL; - global $database; - $database->execute("UPDATE artists SET name = ?, notes = ?, updated = now(), user_id = ? WHERE id = ? " - , array( - $name - , $notes - , $userID - , $artistID - )); + $database->execute( + "UPDATE artists SET name = ?, notes = ?, updated = now(), user_id = ? WHERE id = ? ", + array($name, $notes, $userID, $artistID) + ); // ALIAS MATCHING SECTION $i = 0; @@ -649,7 +638,6 @@ class Artists extends Extension { { // if an alias was updated if ($i < count($aliasesIDsAsArray)) - // save it $this->save_existing_alias($aliasesIDsAsArray[$i], $aliasesAsArray[$i], $userID); else // if we already updated all, save new ones @@ -669,7 +657,6 @@ class Artists extends Extension { { // if a member was updated if ($i < count($membersIDsAsArray)) - //save it $this->save_existing_member($membersIDsAsArray[$i], $membersAsArray[$i], $userID); else // if we already updated all, save new ones @@ -690,13 +677,10 @@ class Artists extends Extension { while ($i < count($urlsAsArray)) { // if an URL was updated - if ($i < count($urlsIDsAsArray)) - { - // save it + if ($i < count($urlsIDsAsArray)) { $this->save_existing_url($urlsIDsAsArray[$i], $urlsAsArray[$i], $userID); } - else - { + else { $this->save_new_url($artistID, $urlsAsArray[$i], $userID); } @@ -708,142 +692,128 @@ class Artists extends Extension { $this->delete_url($urlsIDsAsArray[$i++]); } - private function update_alias() - { - $aliasID = $_POST['aliasID']; - $alias = strtolower($_POST['alias']); - - if (is_null($aliasID) || !is_numeric($aliasID)) - return; - - if (is_null($alias) || strlen($alias) === 0) - return; - + private function update_alias() { global $user; - $this->save_existing_alias($aliasID, $alias, $user->id); + $inputs = validate_input(array( + "aliasID" => "int", + "alias" => "string,lower", + )); + $this->save_existing_alias($inputs['aliasID'], $inputs['alias'], $user->id); } - private function save_existing_alias($aliasID, $alias, $userID) - { - if (!is_numeric($userID)) return; - if (!is_numeric($aliasID)) return; + /** + * @param int $aliasID + * @param string $alias + * @param int $userID + */ + private function save_existing_alias($aliasID, $alias, $userID) { + assert(is_numeric($userID)); + assert(is_numeric($aliasID)); global $database; - $database->execute("UPDATE artist_alias SET alias = ?, updated = now(), user_id = ? WHERE id = ? " - , array( - $alias - , $userID - , $aliasID - )); + $database->execute( + "UPDATE artist_alias SET alias = ?, updated = now(), user_id = ? WHERE id = ? ", + array($alias, $userID, $aliasID) + ); } - private function update_url() - { - $urlID = $_POST['urlID']; - $url = $_POST['url']; - - if (is_null($urlID) || !is_numeric($urlID)) - return; - - if (is_null($url) || strlen($url) == 0) - return; - + private function update_url() { global $user; - $this->save_existing_url($urlID, $url, $user->id); + $inputs = validate_input(array( + "urlID" => "int", + "url" => "string", + )); + $this->save_existing_url($inputs['urlID'], $inputs['url'], $user->id); } - private function save_existing_url($urlID, $url, $userID) - { - if (!is_numeric($userID)) return; - if (!is_numeric($urlID)) return; + /** + * @param int $urlID + * @param string $url + * @param int $userID + */ + private function save_existing_url($urlID, $url, $userID) { + assert(is_numeric($userID)); + assert(is_numeric($urlID)); global $database; - $database->execute("UPDATE artist_urls SET url = ?, updated = now(), user_id = ? WHERE id = ?" - , array( - $url - , $userID - , $urlID - )); + $database->execute( + "UPDATE artist_urls SET url = ?, updated = now(), user_id = ? WHERE id = ?", + array($url, $userID, $urlID) + ); } - private function update_member() - { - $memberID = $_POST['memberID']; - $memberName = strtolower($_POST['name']); - - if (is_null($memberID) || !is_numeric($memberID)) - return; - - if (is_null($memberName) || strlen($memberName) === 0) - return; - - global $user; - $this->save_existing_member($memberID, $memberName, $user->id); + private function update_member() { + global $user; + $inputs = validate_input(array( + "memberID" => "int", + "name" => "string,lower", + )); + $this->save_existing_member($inputs['memberID'], $inputs['name'], $user->id); } - private function save_existing_member($memberID, $memberName, $userID) - { - if (!is_numeric($memberID)) return; - if (!is_numeric($userID)) return; + /** + * @param int $memberID + * @param string $memberName + * @param int $userID + */ + private function save_existing_member($memberID, $memberName, $userID) { + assert(is_numeric($memberID)); + assert(is_numeric($userID)); global $database; - - $database->execute("UPDATE artist_members SET name = ?, updated = now(), user_id = ? WHERE id = ?" - , array( - $memberName - , $userID - , $memberID - )); + $database->execute( + "UPDATE artist_members SET name = ?, updated = now(), user_id = ? WHERE id = ?", + array($memberName, $userID, $memberID) + ); } - /* - * HERE WE ADD AN ARTIST - */ private function add_artist(){ global $user; + $inputs = validate_input(array( + "name" => "string,lower", + "notes" => "string,optional", + "aliases" => "string,lower,optional", + "members" => "string,lower,optional", + "urls" => "string,optional" + )); - $name = html_escape(strtolower($_POST["name"])); - if (is_null($name) || (strlen($name) === 0) || strpos($name, " ")) + $name = $inputs["name"]; + if(strpos($name, " ")) return -1; - $notes = html_escape(ucfirst($_POST["notes"])); - if (strlen($notes) == 0) - $notes = NULL; + $notes = $inputs["notes"]; - $aliases = strtolower($_POST["aliases"]); - $members = strtolower($_POST["members"]); - $urls = $_POST["urls"]; + $aliases = $inputs["aliases"]; + $members = $inputs["members"]; + $urls = $inputs["urls"]; $userID = $user->id; //$artistID = ""; //// WE CHECK IF THE ARTIST ALREADY EXISTS ON DATABASE; IF NOT WE CREATE - if(!$this->artist_exists($name)) - { + if(!$this->artist_exists($name)) { $artistID = $this->save_new_artist($name, $notes); log_info("artists", "Artist {$artistID} created by {$user->name}"); } - else + else { $artistID = $this->get_artist_id($name); + } - if (strlen($aliases) > 0) - { + if (!is_null($aliases)) { $aliasArray = explode(" ", $aliases); foreach($aliasArray as $alias) if (!$this->alias_exists($artistID, $alias)) $this->save_new_alias($artistID, $alias, $userID); } - if (strlen($members) > 0) - { + if (!is_null($members)) { $membersArray = explode(" ", $members); foreach ($membersArray as $member) if (!$this->member_exists($artistID, $member)) $this->save_new_member($artistID, $member, $userID); } - if (strlen($urls)) - { + if (!is_null($urls)) { //delete double "separators" $urls = str_replace("\r\n", "\n", $urls); $urls = str_replace("\n\r", "\n", $urls); @@ -856,50 +826,45 @@ class Artists extends Extension { return $artistID; } - private function save_new_artist($name, $notes) - { + /** + * @param string $name + * @param string $notes + * @return int + */ + private function save_new_artist($name, $notes) { global $database, $user; - $database->execute(" - INSERT INTO artists - (user_id, name, notes, created, updated) - VALUES - (?, ?, ?, now(), now())", - array( - $user->id - , $name - , $notes - )); - - $result = $database->get_row("SELECT LAST_INSERT_ID() AS artistID", array()); - - return $result["artistID"]; + INSERT INTO artists (user_id, name, notes, created, updated) + VALUES (?, ?, ?, now(), now()) + ", array($user->id, $name, $notes)); + return $database->get_last_insert_id(); } - /* - * HERE WE CHECK IF ARTIST EXIST - */ - private function artist_exists($name){ + /** + * @param string $name + * @return bool + */ + private function artist_exists($name) { global $database; - - $result = $database->get_one("SELECT COUNT(1) FROM artists WHERE name = ?" - , array( - $name - )); + $result = $database->get_one( + "SELECT COUNT(1) FROM artists WHERE name = ?", + array($name) + ); return ($result != 0); } - /* - * HERE WE GET THE INFO OF THE ARTIST - */ + /** + * @param int $artistID + * @return array + */ private function get_artist($artistID){ - if (!is_numeric($artistID)) return; + assert(is_numeric($artistID)); global $database; - $result = $database->get_row("SELECT * FROM artists WHERE id = ?", - array( - $artistID - )); + $result = $database->get_row( + "SELECT * FROM artists WHERE id = ?", + array($artistID) + ); $result["name"] = stripslashes($result["name"]); $result["notes"] = stripslashes($result["notes"]); @@ -907,103 +872,95 @@ class Artists extends Extension { return $result; } - private function get_members($artistID) - { - if (!is_numeric($artistID)) return; + /** + * @param int $artistID + * @return array + */ + private function get_members($artistID) { + assert(is_numeric($artistID)); global $database; - $result = $database->get_all("SELECT * FROM artist_members WHERE artist_id = ?" - , array( - $artistID - )); + $result = $database->get_all( + "SELECT * FROM artist_members WHERE artist_id = ?", + array($artistID) + ); $num = count($result); - - for ($i = 0 ; $i < $num ; $i++) - { + for ($i = 0 ; $i < $num ; $i++) { $result[$i]["name"] = stripslashes($result[$i]["name"]); } return $result; } - private function get_urls($artistID) - { - if (!is_numeric($artistID)) return; + + /** + * @param int $artistID + * @return array + */ + private function get_urls($artistID) { + assert(is_numeric($artistID)); global $database; - $result = $database->get_all("SELECT id, url FROM artist_urls WHERE artist_id = ?" - , array( - $artistID - )); + $result = $database->get_all( + "SELECT id, url FROM artist_urls WHERE artist_id = ?", + array($artistID) + ); $num = count($result); - - for ($i = 0 ; $i < $num ; $i++) - { + for ($i = 0 ; $i < $num ; $i++) { $result[$i]["url"] = stripslashes($result[$i]["url"]); } - return $result; } /** - * HERE WE GET THE ID OF THE ARTIST. - * * @param string $name - * @return string|int + * @return int */ - private function get_artist_id($name){ + private function get_artist_id($name) { global $database; - $artistID = $database->get_row("SELECT id FROM artists WHERE name = ?" - , array( - $name - )); - return $artistID['id']; + return (int)$database->get_one( + "SELECT id FROM artists WHERE name = ?", + array($name) + ); } - private function get_artistID_by_aliasName($alias) - { - global $database; + /** + * @param string $alias + * @return int + */ + private function get_artistID_by_aliasName($alias) { + global $database; - $artistID = $database->get_row("SELECT artist_id FROM artist_alias WHERE alias = ?" - , array( - $alias - )); - return $artistID["artist_id"]; - } - - - /* - * HERE WE DELETE THE ARTIST - */ - private function delete_artist($artistID) - { - if (!is_numeric($artistID)) return; + return (int)$database->get_one( + "SELECT artist_id FROM artist_alias WHERE alias = ?", + array($alias) + ); + } - global $database; - $database->execute("DELETE FROM artists WHERE id = ? " - , array( - $artistID - )); + + /** + * @param int $artistID + */ + private function delete_artist($artistID) { + assert(is_numeric($artistID)); + + global $database; + $database->execute( + "DELETE FROM artists WHERE id = ? ", + array($artistID) + ); } - - /* * HERE WE GET THE LIST OF ALL ARTIST WITH PAGINATION */ - private function get_listing(Page $page, $event) + private function get_listing(Page $page, PageRequestEvent $event) { - $pageNumber = $event->get_arg(1); - if(is_null($pageNumber) || !is_numeric($pageNumber)) - $pageNumber = 0; - else if ($pageNumber <= 0) - $pageNumber = 0; - else - $pageNumber--; - global $config, $database; + + $pageNumber = clamp($event->get_arg(1), 1, null) - 1; $artistsPerPage = $config->get_int("artistsPerPage"); $listing = $database->get_all( @@ -1067,14 +1024,14 @@ class Artists extends Extension { $listing[$i]["artist_name"] = stripslashes($listing[$i]["artist_name"]); } - $count = $database->get_one( - "SELECT COUNT(1) + $count = $database->get_one(" + SELECT COUNT(1) FROM artists AS a LEFT OUTER JOIN artist_members AS am ON a.id = am.artist_id LEFT OUTER JOIN artist_alias AS aa ON a.id = aa.artist_id - "); + "); $totalPages = ceil ($count / $artistsPerPage); @@ -1084,154 +1041,154 @@ class Artists extends Extension { /* * HERE WE ADD AN ALIAS */ - private function add_urls() - { - global $user; - $artistID = $_POST["artistID"]; - $urls = $_POST["urls"]; - $userID = $user->id; + private function add_urls() { + global $user; + $inputs = validate_input(array( + "artistID" => "int", + "urls" => "string", + )); + $artistID = $inputs["artistID"]; + $urls = explode("\n", $inputs["urls"]); - if (is_null($artistID) || !is_numeric($artistID)) - return; + foreach ($urls as $url) + if (!$this->url_exists($artistID, $url)) + $this->save_new_url($artistID, $url, $user->id); + } - if (is_null($urls) || strlen($urls) == 0) - return; - - $urlArray = explode("\n", $urls); + /** + * @param int $artistID + * @param string $url + * @param int $userID + */ + private function save_new_url($artistID, $url, $userID) { + global $database; - foreach ($urlArray as $url) - if (!$this->url_exists($artistID, $url)) - $this->save_new_url($artistID, $url, $userID); + assert(is_numeric($artistID)); + assert(is_numeric($userID)); + + $database->execute( + "INSERT INTO artist_urls (artist_id, created, updated, url, user_id) VALUES (?, now(), now(), ?, ?)", + array($artistID, $url, $userID) + ); + } + + private function add_alias() { + global $user; + $inputs = validate_input(array( + "artistID" => "int", + "aliases" => "string,lower", + )); + $artistID = $inputs["artistID"]; + $aliases = explode(" ", $inputs["aliases"]); + + foreach ($aliases as $alias) + if (!$this->alias_exists($artistID, $alias)) + $this->save_new_alias($artistID, $alias, $user->id); + } + + /** + * @param int $artistID + * @param string $alias + * @param int $userID + */ + private function save_new_alias($artistID, $alias, $userID) { + global $database; + + assert(is_numeric($artistID)); + assert(is_numeric($userID)); + + $database->execute( + "INSERT INTO artist_alias (artist_id, created, updated, alias, user_id) VALUES (?, now(), now(), ?, ?)", + array($artistID, $alias, $userID) + ); + } + + private function add_members() { + global $user; + $inputs = validate_input(array( + "artistID" => "int", + "members" => "string,lower", + )); + $artistID = $inputs["artistID"]; + $members = explode(" ", $inputs["members"]); + + foreach ($members as $member) + if (!$this->member_exists($artistID, $member)) + $this->save_new_member($artistID, $member, $user->id); + } + + /** + * @param int $artistID + * @param string $member + * @param int $userID + */ + private function save_new_member($artistID, $member, $userID) { + global $database; + + assert(is_numeric($artistID)); + assert(is_numeric($userID)); + + $database->execute( + "INSERT INTO artist_members (artist_id, name, created, updated, user_id) VALUES (?, ?, now(), now(), ?)", + array($artistID, $member, $userID) + ); + } + + /** + * @param int $artistID + * @param string $member + * @return bool + */ + private function member_exists($artistID, $member) { + global $database; + + assert(is_numeric($artistID)); + + $result = $database->get_one( + "SELECT COUNT(1) FROM artist_members WHERE artist_id = ? AND name = ?", + array($artistID, $member) + ); + return ($result != 0); + } + + /** + * @param int $artistID + * @param string $url + * @return bool + */ + private function url_exists($artistID, $url) { + global $database; + + assert(is_numeric($artistID)); + + $result = $database->get_one( + "SELECT COUNT(1) FROM artist_urls WHERE artist_id = ? AND url = ?", + array($artistID, $url) + ); + return ($result != 0); + } + + /** + * HERE WE GET THE INFO OF THE ALIAS + * + * @param int $artistID + * @return array + */ + private function get_alias($artistID) { + global $database; + + assert(is_numeric($artistID)); + + $result = $database->get_all(" + SELECT id AS alias_id, alias AS alias_name + FROM artist_alias + WHERE artist_id = ? + ORDER BY alias ASC + ", array($artistID)); + + for ($i = 0 ; $i < count($result) ; $i++) { + $result[$i]["alias_name"] = stripslashes($result[$i]["alias_name"]); } - - private function save_new_url($artistID, $url, $userID) - { - if (!is_numeric($artistID)) return; - if (!is_numeric($userID)) return; - - global $database; - $database->execute("INSERT INTO artist_urls (artist_id, created, updated, url, user_id) VALUES (?, now(), now(), ?, ?)" - , array( - $artistID - , $url - , $userID - )); - } - - private function add_alias() - { - global $user; - $artistID = $_POST["artistID"]; - $aliases = strtolower($_POST["aliases"]); - $userID = $user->id; - - if (is_null($artistID) || !is_numeric($artistID)) - return; - - if (is_null($aliases) || trim($aliases) == "") - return; - - $aliasArray = explode(" ", $aliases); - foreach ($aliasArray as $alias) - if (!$this->alias_exists($artistID, $alias)) - $this->save_new_alias($artistID, $alias, $userID); - } - - private function save_new_alias($artistID, $alias, $userID) - { - if (!is_numeric($artistID)) return; - if (!is_numeric($userID)) return; - - global $database; - $database->execute("INSERT INTO artist_alias (artist_id, created, updated, alias, user_id) VALUES (?, now(), now(), ?, ?)" - , array( - $artistID - , $alias - , $userID - )); - } - - private function add_members() - { - global $user; - $artistID = $_POST["artistID"]; - $members = strtolower($_POST["members"]); - $userID = $user->id; - - if (is_null($artistID) || !is_numeric($artistID)) - return; - - if (is_null($members) || trim($members) == "") - return; - - $memberArray = explode(" ", $members); - foreach ($memberArray as $member) - if (!$this->member_exists($artistID, $member)) - $this->save_new_member($artistID, $member, $userID); - } - - private function save_new_member($artistID, $member, $userID) - { - if (!is_numeric($artistID)) return; - if (!is_numeric($userID)) return; - - global $database; - $database->execute("INSERT INTO artist_members (artist_id, name, created, updated, user_id) VALUES (?, ?, now(), now(), ?)" - , array( - $artistID - , $member - , $userID - )); - } - - private function member_exists($artistID, $member) - { - if (!is_numeric($artistID)) return; - - global $database; - - $result = $database->get_one("SELECT COUNT(1) FROM artist_members WHERE artist_id = ? AND name = ?" - , array( - $artistID - , $member - )); - return ($result != 0); - } - - private function url_exists($artistID, $url) - { - if (!is_numeric($artistID)) return; - - global $database; - - $result = $database->get_one("SELECT COUNT(1) FROM artist_urls WHERE artist_id = ? AND url = ?" - , array( - $artistID - , $url - )); - return ($result != 0); - } - - /* - * HERE WE GET THE INFO OF THE ALIAS - */ - private function get_alias($artistID) - { - if (!is_numeric($artistID)) return; - - global $database; - - $result = $database->get_all("SELECT id AS alias_id, alias AS alias_name ". - "FROM artist_alias ". - "WHERE artist_id = ? ". - "ORDER BY alias ASC" - , array($artistID)); - - for ($i = 0 ; $i < count($result) ; $i++) - { - $result[$i]["alias_name"] = stripslashes($result[$i]["alias_name"]); - } - return $result; + return $result; } } - diff --git a/ext/artists/test.php b/ext/artists/test.php index 6f17ee9c..9cbfdf5e 100644 --- a/ext/artists/test.php +++ b/ext/artists/test.php @@ -1,6 +1,6 @@ get_page("post/list/author=bob/1"); #$this->assert_response(200); diff --git a/ext/artists/theme.php b/ext/artists/theme.php index 347df0f1..cc30e6bd 100644 --- a/ext/artists/theme.php +++ b/ext/artists/theme.php @@ -23,7 +23,7 @@ class ArtistsTheme extends Themelet { * @param null|int $artistID * @param bool $is_admin */ - public function sidebar_options(/*string*/ $mode, $artistID=NULL, $is_admin=FALSE){ + public function sidebar_options(/*string*/ $mode, $artistID=NULL, $is_admin=FALSE) { global $page, $user; $html = ""; @@ -77,49 +77,44 @@ class ArtistsTheme extends Themelet { if($html) $page->add_block(new Block("Manage Artists", $html, "left", 10)); } - public function show_artist_editor($artist, $aliases, $members, $urls) - { - global $user; + public function show_artist_editor($artist, $aliases, $members, $urls) { + global $user; - $artistName = $artist['name']; - $artistNotes = $artist['notes']; - $artistID = $artist['id']; + $artistName = $artist['name']; + $artistNotes = $artist['notes']; + $artistID = $artist['id']; - // aliases - $aliasesString = ""; - $aliasesIDsString = ""; - foreach ($aliases as $alias) - { - $aliasesString .= $alias["alias_name"]." "; - $aliasesIDsString .= $alias["alias_id"]." "; - } - $aliasesString = rtrim($aliasesString); - $aliasesIDsString = rtrim($aliasesIDsString); + // aliases + $aliasesString = ""; + $aliasesIDsString = ""; + foreach ($aliases as $alias) { + $aliasesString .= $alias["alias_name"]." "; + $aliasesIDsString .= $alias["alias_id"]." "; + } + $aliasesString = rtrim($aliasesString); + $aliasesIDsString = rtrim($aliasesIDsString); - // members - $membersString = ""; - $membersIDsString = ""; - foreach ($members as $member) - { - $membersString .= $member["name"]." "; - $membersIDsString .= $member["id"]." "; - } - $membersString = rtrim($membersString); - $membersIDsString = rtrim($membersIDsString); + // members + $membersString = ""; + $membersIDsString = ""; + foreach ($members as $member) { + $membersString .= $member["name"]." "; + $membersIDsString .= $member["id"]." "; + } + $membersString = rtrim($membersString); + $membersIDsString = rtrim($membersIDsString); - // urls - $urlsString = ""; - $urlsIDsString = ""; - foreach ($urls as $url) - { - $urlsString .= $url["url"]."\n"; - $urlsIDsString .= $url["id"]." "; - } - $urlsString = substr($urlsString, 0, strlen($urlsString) -1); - $urlsIDsString = rtrim($urlsIDsString); + // urls + $urlsString = ""; + $urlsIDsString = ""; + foreach ($urls as $url) { + $urlsString .= $url["url"]."\n"; + $urlsIDsString .= $url["id"]." "; + } + $urlsString = substr($urlsString, 0, strlen($urlsString) -1); + $urlsIDsString = rtrim($urlsIDsString); - $html = -' + $html = '
'.$user->get_auth_html().' @@ -135,113 +130,108 @@ class ArtistsTheme extends Themelet {
- -'; + '; - global $page; - $page->add_block(new Block("Edit artist", $html, "main", 10)); - } - - public function new_artist_composer() - { - global $page, $user; - - $html = "
- ".$user->get_auth_html()." - - - - - - - -
Name:
Aliases:
Members:
URLs:
Notes:
- "; - - $page->set_title("Artists"); - $page->set_heading("Artists"); - $page->add_block(new Block("Artists", $html, "main", 10)); + global $page; + $page->add_block(new Block("Edit artist", $html, "main", 10)); } - public function list_artists($artists, $pageNumber, $totalPages) - { - global $user, $page; + public function new_artist_composer() { + global $page, $user; - $html = "". - "". - "". - "". - "". - ""; + $html = " + ".$user->get_auth_html()." +
NameTypeLast updaterPosts
+ + + + + + +
Name:
Aliases:
Members:
URLs:
Notes:
+ "; + $page->set_title("Artists"); + $page->set_heading("Artists"); + $page->add_block(new Block("Artists", $html, "main", 10)); + } + + public function list_artists($artists, $pageNumber, $totalPages) { + global $user, $page; - if(!$user->is_anonymous()) $html .= "Action"; // space for edit link + $html = "". + "". + "". + "". + "". + ""; + + if(!$user->is_anonymous()) $html .= ""; // space for edit link - $html .= ""; + $html .= ""; - $deletionLinkActionArray = - array('artist' => 'artist/nuke/' - , 'alias' => 'artist/alias/delete/' - , 'member' => 'artist/member/delete/' - ); + $deletionLinkActionArray = array( + 'artist' => 'artist/nuke/', + 'alias' => 'artist/alias/delete/', + 'member' => 'artist/member/delete/', + ); - $editionLinkActionArray = - array('artist' => 'artist/edit/' - , 'alias' => 'artist/alias/edit/' - , 'member' => 'artist/member/edit/' - ); + $editionLinkActionArray = array( + 'artist' => 'artist/edit/', + 'alias' => 'artist/alias/edit/', + 'member' => 'artist/member/edit/', + ); - $typeTextArray = - array('artist' => 'Artist' - , 'alias' => 'Alias' - , 'member' => 'Member' - ); + $typeTextArray = array( + 'artist' => 'Artist', + 'alias' => 'Alias', + 'member' => 'Member', + ); - foreach ($artists as $artist) { - if ($artist['type'] != 'artist') - $artist['name'] = str_replace("_", " ", $artist['name']); + foreach ($artists as $artist) { + if ($artist['type'] != 'artist') + $artist['name'] = str_replace("_", " ", $artist['name']); - $elementLink = "".str_replace("_", " ", $artist['name']).""; - //$artist_link = "".str_replace("_", " ", $artist['artist_name']).""; - $user_link = "".$artist['user_name'].""; - $edit_link = "Edit"; - $del_link = "Delete"; + $elementLink = "".str_replace("_", " ", $artist['name']).""; + //$artist_link = "".str_replace("_", " ", $artist['artist_name']).""; + $user_link = "".$artist['user_name'].""; + $edit_link = "Edit"; + $del_link = "Delete"; - $html .= "". - "". + "". - "". - "". - ""; + $html .= "". + "". + "". + ""; - if(!$user->is_anonymous()) $html .= ""; - if($user->is_admin()) $html .= ""; + if(!$user->is_anonymous()) $html .= ""; + if($user->is_admin()) $html .= ""; - $html .= ""; - } + $html .= ""; + } - $html .= "
NameTypeLast updaterPostsAction
".$elementLink; + $html .= "
".$elementLink; - //if ($artist['type'] == 'member') - // $html .= " (member of ".$artist_link.")"; + //if ($artist['type'] == 'member') + // $html .= " (member of ".$artist_link.")"; - //if ($artist['type'] == 'alias') - // $html .= " (alias for ".$artist_link.")"; + //if ($artist['type'] == 'alias') + // $html .= " (alias for ".$artist_link.")"; - $html .= "".$typeTextArray[$artist['type']]."".$user_link."".$artist['posts']."".$typeTextArray[$artist['type']]."".$user_link."".$artist['posts']."".$edit_link."".$del_link."".$edit_link."".$del_link."
"; + $html .= ""; - $page->set_title("Artists"); - $page->set_heading("Artists"); - $page->add_block(new Block("Artists", $html, "main", 10)); + $page->set_title("Artists"); + $page->set_heading("Artists"); + $page->add_block(new Block("Artists", $html, "main", 10)); - $this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages); + $this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages); } - public function show_new_alias_composer($artistID) - { - global $user; + public function show_new_alias_composer($artistID) { + global $user; - $html = - ' + $html = ' + '.$user->get_auth_html().'
Alias: @@ -249,277 +239,290 @@ class ArtistsTheme extends Themelet {
- '; + '; - global $page; - $page->add_block(new Block("Artist Aliases", $html, "main", 20)); - } - public function show_new_member_composer($artistID) - { - global $user; + global $page; + $page->add_block(new Block("Artist Aliases", $html, "main", 20)); + } - $html = - '
+ public function show_new_member_composer($artistID) { + global $user; + + $html = ' + '.$user->get_auth_html().' - - - -
Members: -
-
- '; + + + +
Members: +
+ + '; - global $page; - $page->add_block(new Block("Artist members", $html, "main", 30)); - } + global $page; + $page->add_block(new Block("Artist members", $html, "main", 30)); + } - public function show_new_url_composer($artistID) - { - global $user; + public function show_new_url_composer($artistID) { + global $user; - $html = - '
+ $html = ' + '.$user->get_auth_html().' - - - -
URL: -
-
- '; + + + +
URL: +
+ + '; - global $page; - $page->add_block(new Block("Artist URLs", $html, "main", 40)); - } + global $page; + $page->add_block(new Block("Artist URLs", $html, "main", 40)); + } - public function show_alias_editor($alias) - { - global $user; + public function show_alias_editor($alias) { + global $user; - $html = - ' -
- '.$user->get_auth_html().' - - - - -
- '; + $html = ' +
+ '.$user->get_auth_html().' + + + + +
+ '; - global $page; - $page->add_block(new Block("Edit Alias", $html, "main", 10)); - } + global $page; + $page->add_block(new Block("Edit Alias", $html, "main", 10)); + } - public function show_url_editor($url) - { - global $user; + public function show_url_editor($url) { + global $user; - $html = - ' -
- '.$user->get_auth_html().' - - - - -
- '; + $html = ' +
+ '.$user->get_auth_html().' + + + + +
+ '; - global $page; - $page->add_block(new Block("Edit URL", $html, "main", 10)); - } + global $page; + $page->add_block(new Block("Edit URL", $html, "main", 10)); + } - public function show_member_editor($member) - { - global $user; + public function show_member_editor($member) { + global $user; - $html = - ' -
- '.$user->get_auth_html().' - - - - -
- '; + $html = ' +
+ '.$user->get_auth_html().' + + + + +
+ '; - global $page; - $page->add_block(new Block("Edit Member", $html, "main", 10)); - } + global $page; + $page->add_block(new Block("Edit Member", $html, "main", 10)); + } - public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin) - { - global $page; + public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin) { + global $page; - $artist_link = "".str_replace("_", " ", $artist['name']).""; + $artist_link = "".str_replace("_", " ", $artist['name']).""; - $html = " + $html = "
"; - if ($userIsLogged) - $html .= ""; + if ($userIsLogged) $html .= ""; + if ($userIsAdmin) $html .= ""; - if ($userIsAdmin) - $html .= ""; - - $html .= " + $html .= " "; - if ($userIsLogged) $html .= ""; - if ($userIsAdmin) $html .= ""; - $html .= ""; + if ($userIsLogged) $html .= ""; + if ($userIsAdmin) $html .= ""; + $html .= ""; - if (count($aliases) > 0) - { - $aliasViewLink = str_replace("_", " ", $aliases[0]['alias_name']); // no link anymore - $aliasEditLink = "Edit"; - $aliasDeleteLink = "Delete"; - - $html .= " - - "; - - if ($userIsLogged) - $html .= ""; + $html .= $this->render_aliases($aliases, $userIsLogged, $userIsAdmin); + $html .= $this->render_members($members, $userIsLogged, $userIsAdmin); + $html .= $this->render_urls($urls, $userIsLogged, $userIsAdmin); - if ($userIsAdmin) - $html .= ""; - - $html .= ""; - - if (count($aliases) > 1) - { - for ($i = 1; $i < count($aliases); $i++) - { - $aliasViewLink = str_replace("_", " ", $aliases[$i]['alias_name']); // no link anymore - $aliasEditLink = "Edit"; - $aliasDeleteLink = "Delete"; - - $html .= " - - "; - if ($userIsLogged) - $html .= ""; - if ($userIsAdmin) - $html .= ""; - - $html .= ""; - } - } - } - - if (count($members) > 0) - { - $memberViewLink = str_replace("_", " ", $members[0]['name']); // no link anymore - $memberEditLink = "Edit"; - $memberDeleteLink = "Delete"; - - $html .= " - - "; - if ($userIsLogged) - $html .= ""; - if ($userIsAdmin) - $html .= ""; - - $html .= ""; - - if (count($members) > 1) - { - for ($i = 1; $i < count($members); $i++) - { - $memberViewLink = str_replace("_", " ", $members[$i]['name']); // no link anymore - $memberEditLink = "Edit"; - $memberDeleteLink = "Delete"; - - $html .= " - - "; - if ($userIsLogged) - $html .= ""; - if ($userIsAdmin) - $html .= ""; - - $html .= ""; - } - } - } - - if (count($urls) > 0) - { - $urlViewLink = "".str_replace("_", " ", $urls[0]['url']).""; - $urlEditLink = "Edit"; - $urlDeleteLink = "Delete"; - - $html .= " - - "; - - if ($userIsLogged) - $html .= ""; - - if ($userIsAdmin) - $html .= ""; - - $html .= ""; - - if (count($urls) > 1) - { - for ($i = 1; $i < count($urls); $i++) - { - $urlViewLink = "".str_replace("_", " ", $urls[$i]['url']).""; - $urlEditLink = "Edit"; - $urlDeleteLink = "Delete"; - - $html .= " - - "; - if ($userIsLogged) - $html .= ""; - - if ($userIsAdmin) - $html .= ""; - - $html .= ""; - } - } - } - - $html .= - " + $html .= ""; - if ($userIsLogged) $html .= ""; - if ($userIsAdmin) $html .= ""; - //TODO how will notes be edited? On edit artist? (should there be an editartist?) or on a editnotes? - //same question for deletion - $html .= " -
Name: ".$artist_link."
Aliases:".$aliasViewLink."".$aliasEditLink."".$aliasDeleteLink."
 ".$aliasViewLink."".$aliasEditLink."".$aliasDeleteLink."
Members:".$memberViewLink."".$memberEditLink."".$memberDeleteLink."
 ".$memberViewLink."".$memberEditLink."".$memberDeleteLink."
URLs:".$urlViewLink."".$urlEditLink."".$urlDeleteLink."
 ".$urlViewLink."".$urlEditLink."".$urlDeleteLink."
Notes: ".$artist["notes"]."
"; + if ($userIsLogged) $html .= ""; + if ($userIsAdmin) $html .= ""; + //TODO how will notes be edited? On edit artist? (should there be an editartist?) or on a editnotes? + //same question for deletion + $html .= " + "; - $page->set_title("Artist"); - $page->set_heading("Artist"); - $page->add_block(new Block("Artist", $html, "main", 10)); + $page->set_title("Artist"); + $page->set_heading("Artist"); + $page->add_block(new Block("Artist", $html, "main", 10)); - //we show the images for the artist - $artist_images = ""; - foreach($images as $image) { - - $thumb_html = $this->build_thumb_html($image); + //we show the images for the artist + $artist_images = ""; + foreach($images as $image) { + $thumb_html = $this->build_thumb_html($image); - $artist_images .= ''. - ''.$thumb_html.''. - ''; - } + $artist_images .= ''. + ''.$thumb_html.''. + ''; + } - $page->add_block(new Block("Artist Images", $artist_images, "main", 20)); + $page->add_block(new Block("Artist Images", $artist_images, "main", 20)); + } + + /** + * @param $aliases + * @param $userIsLogged + * @param $userIsAdmin + * @return string + */ + private function render_aliases($aliases, $userIsLogged, $userIsAdmin) { + $html = ""; + if(count($aliases) > 0) { + $aliasViewLink = str_replace("_", " ", $aliases[0]['alias_name']); // no link anymore + $aliasEditLink = "Edit"; + $aliasDeleteLink = "Delete"; + + $html .= " + Aliases: + " . $aliasViewLink . ""; + + if ($userIsLogged) + $html .= "" . $aliasEditLink . ""; + + if ($userIsAdmin) + $html .= "" . $aliasDeleteLink . ""; + + $html .= ""; + + if (count($aliases) > 1) { + for ($i = 1; $i < count($aliases); $i++) { + $aliasViewLink = str_replace("_", " ", $aliases[$i]['alias_name']); // no link anymore + $aliasEditLink = "Edit"; + $aliasDeleteLink = "Delete"; + + $html .= " +   + " . $aliasViewLink . ""; + if ($userIsLogged) + $html .= "" . $aliasEditLink . ""; + if ($userIsAdmin) + $html .= "" . $aliasDeleteLink . ""; + + $html .= ""; + } + } + } + return $html; + } + + /** + * @param $members + * @param $userIsLogged + * @param $userIsAdmin + * @return string + */ + private function render_members($members, $userIsLogged, $userIsAdmin) { + $html = ""; + if(count($members) > 0) { + $memberViewLink = str_replace("_", " ", $members[0]['name']); // no link anymore + $memberEditLink = "Edit"; + $memberDeleteLink = "Delete"; + + $html .= " + Members: + " . $memberViewLink . ""; + if ($userIsLogged) + $html .= "" . $memberEditLink . ""; + if ($userIsAdmin) + $html .= "" . $memberDeleteLink . ""; + + $html .= ""; + + if (count($members) > 1) { + for ($i = 1; $i < count($members); $i++) { + $memberViewLink = str_replace("_", " ", $members[$i]['name']); // no link anymore + $memberEditLink = "Edit"; + $memberDeleteLink = "Delete"; + + $html .= " +   + " . $memberViewLink . ""; + if ($userIsLogged) + $html .= "" . $memberEditLink . ""; + if ($userIsAdmin) + $html .= "" . $memberDeleteLink . ""; + + $html .= ""; + } + } + } + return $html; + } + + /** + * @param $urls + * @param $userIsLogged + * @param $userIsAdmin + * @return string + */ + private function render_urls($urls, $userIsLogged, $userIsAdmin) { + $html = ""; + if(count($urls) > 0) { + $urlViewLink = "" . str_replace("_", " ", $urls[0]['url']) . ""; + $urlEditLink = "Edit"; + $urlDeleteLink = "Delete"; + + $html .= " + URLs: + " . $urlViewLink . ""; + + if ($userIsLogged) + $html .= "" . $urlEditLink . ""; + + if ($userIsAdmin) + $html .= "" . $urlDeleteLink . ""; + + $html .= ""; + + if (count($urls) > 1) { + for ($i = 1; $i < count($urls); $i++) { + $urlViewLink = "" . str_replace("_", " ", $urls[$i]['url']) . ""; + $urlEditLink = "Edit"; + $urlDeleteLink = "Delete"; + + $html .= " +   + " . $urlViewLink . ""; + if ($userIsLogged) + $html .= "" . $urlEditLink . ""; + + if ($userIsAdmin) + $html .= "" . $urlDeleteLink . ""; + + $html .= ""; + } + return $html; + } + } + return $html; } } diff --git a/ext/ban_words/test.php b/ext/ban_words/test.php index 80fb4bae..886aee18 100644 --- a/ext/ban_words/test.php +++ b/ext/ban_words/test.php @@ -1,6 +1,6 @@ set_string("banned_words", "viagra\nporn\n\n/http:.*\.cn\//"); diff --git a/ext/bbcode/main.php b/ext/bbcode/main.php index 6c4d404e..ee20fa7c 100644 --- a/ext/bbcode/main.php +++ b/ext/bbcode/main.php @@ -93,7 +93,7 @@ class BBCode extends FormatterExtension { /** * @param string $text - * @return mixed + * @return string */ private function filter_spoiler(/*string*/ $text) { return str_replace( diff --git a/ext/blocks/test.php b/ext/blocks/test.php index 397418b5..e5681c4e 100644 --- a/ext/blocks/test.php +++ b/ext/blocks/test.php @@ -1,6 +1,6 @@ log_in_as_admin(); $this->get_page("blocks/list"); $this->assert_response(200); diff --git a/ext/blotter/test.php b/ext/blotter/test.php index aba22105..eafec499 100644 --- a/ext/blotter/test.php +++ b/ext/blotter/test.php @@ -1,13 +1,13 @@ log_in_as_admin(); //$this->assert_text("Blotter Editor"); //$this->click("Blotter Editor"); //$this->log_out(); } - function testDenial() { + public function testDenial() { $this->get_page("blotter/editor"); $this->assert_response(403); $this->get_page("blotter/add"); @@ -16,7 +16,7 @@ class BlotterTest extends ShimmiePHPUnitTestCase { $this->assert_response(403); } - function testAddViewRemove() { + public function testAddViewRemove() { $this->log_in_as_admin(); $this->get_page("blotter/editor"); diff --git a/ext/blotter/theme.php b/ext/blotter/theme.php index 27987687..ba274cf8 100644 --- a/ext/blotter/theme.php +++ b/ext/blotter/theme.php @@ -62,7 +62,7 @@ class BlotterTheme extends Themelet { if($entries[$i]['important'] == 'Y') { $important = 'Y'; } else { $important = 'N'; } // Add the new table row(s) - $table_rows .= + $table_rows .= " $entry_date $entry_text @@ -114,7 +114,7 @@ class BlotterTheme extends Themelet { $i_open = ""; $i_close=""; } - $html .= "{$i_open}{$clean_date} - {$entry_text}{$i_close}

"; + $html .= "{$i_open}{$clean_date} - {$entry_text}{$i_close}

"; } $html .= ""; return $html; @@ -139,9 +139,9 @@ class BlotterTheme extends Themelet { $entry_text = $entries[$i]['entry_text']; if($entries[$i]['important'] == 'Y') { $i_open = ""; - $i_close=""; + $i_close=""; } - $entries_list .= "
  • {$i_open}{$clean_date} - {$entry_text}{$i_close}
  • "; + $entries_list .= "
  • {$i_open}{$clean_date} - {$entry_text}{$i_close}
  • "; } $pos_break = ""; @@ -149,7 +149,7 @@ class BlotterTheme extends Themelet { if($position === "left") { $pos_break = "
    "; - $pos_align = ""; + $pos_align = ""; } if(count($entries) === 0) { @@ -176,4 +176,3 @@ class BlotterTheme extends Themelet { return $html; } } - diff --git a/ext/bookmarks/test.php b/ext/bookmarks/test.php index baac6697..02f99fec 100644 --- a/ext/bookmarks/test.php +++ b/ext/bookmarks/test.php @@ -1,6 +1,6 @@ get_page("bookmark/add"); $this->get_page("bookmark/remove"); } diff --git a/ext/browser_search/test.php b/ext/browser_search/test.php index 129d2308..3d77f423 100644 --- a/ext/browser_search/test.php +++ b/ext/browser_search/test.php @@ -1,6 +1,6 @@ get_page("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml"); $this->get_page("browser_search/test"); } diff --git a/ext/bulk_add/test.php b/ext/bulk_add/test.php index e9fabb75..5ecb7de1 100644 --- a/ext/bulk_add/test.php +++ b/ext/bulk_add/test.php @@ -1,6 +1,6 @@ log_in_as_admin(); $this->get_page('admin'); @@ -11,7 +11,8 @@ class BulkAddTest extends ShimmiePHPUnitTestCase { $this->assertContains("Error, asdf is not a readable directory", $bae->results, implode("\n", $bae->results)); - return; // FIXME: have BAE return a list of successes as well as errors? + // FIXME: have BAE return a list of successes as well as errors? + $this->markTestIncomplete(); $this->get_page('admin'); $this->assert_title("Admin Tools"); diff --git a/ext/bulk_add_csv/main.php b/ext/bulk_add_csv/main.php index 7fd6d2a7..34645e91 100644 --- a/ext/bulk_add_csv/main.php +++ b/ext/bulk_add_csv/main.php @@ -54,6 +54,14 @@ class BulkAddCSV extends Extension { /** * Generate the necessary DataUploadEvent for a given image and tags. + * + * @param string $tmpname + * @param string $filename + * @param string $tags + * @param string $source + * @param string $rating + * @param string $thumbfile + * @throws UploadException */ private function add_image($tmpname, $filename, $tags, $source, $rating, $thumbfile) { assert(file_exists($tmpname)); diff --git a/ext/comment/main.php b/ext/comment/main.php index 1ad9bf99..a36f2166 100644 --- a/ext/comment/main.php +++ b/ext/comment/main.php @@ -71,12 +71,16 @@ class Comment { } /** - * @param \User $user + * @param User $user * @return mixed */ public static function count_comments_by_user($user) { global $database; - return $database->get_one("SELECT COUNT(*) AS count FROM comments WHERE owner_id=:owner_id", array("owner_id"=>$user->id)); + return $database->get_one(" + SELECT COUNT(*) AS count + FROM comments + WHERE owner_id=:owner_id + ", array("owner_id"=>$user->id)); } /** @@ -89,6 +93,9 @@ class Comment { } class CommentList extends Extension { + /** @var CommentListTheme $theme */ + var $theme; + public function onInitExt(InitExtEvent $event) { global $config, $database; $config->set_default_int('comment_window', 5); @@ -147,78 +154,92 @@ class CommentList extends Extension { } public function onPageRequest(PageRequestEvent $event) { - global $page, $user, $database; if($event->page_matches("comment")) { - if($event->get_arg(0) === "add") { - if(isset($_POST['image_id']) && isset($_POST['comment'])) { - try { - $i_iid = int_escape($_POST['image_id']); - $cpe = new CommentPostingEvent($_POST['image_id'], $user, $_POST['comment']); - send_event($cpe); - $page->set_mode("redirect"); - $page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid")); - } - catch(CommentPostingException $ex) { - $this->theme->display_error(403, "Comment Blocked", $ex->getMessage()); - } - } - } - else if($event->get_arg(0) === "delete") { - if($user->can("delete_comment")) { - // FIXME: post, not args - if($event->count_args() === 3) { - send_event(new CommentDeletionEvent($event->get_arg(1))); - flash_message("Deleted comment"); - $page->set_mode("redirect"); - if(!empty($_SERVER['HTTP_REFERER'])) { - $page->set_redirect($_SERVER['HTTP_REFERER']); - } - else { - $page->set_redirect(make_link("post/view/".$event->get_arg(2))); - } - } - } - else { - $this->theme->display_permission_denied(); - } - } - else if($event->get_arg(0) === "bulk_delete") { - if($user->can("delete_comment") && !empty($_POST["ip"])) { - $ip = $_POST['ip']; - - $cids = $database->get_col("SELECT id FROM comments WHERE owner_ip=:ip", array("ip"=>$ip)); - $num = count($cids); - log_warning("comment", "Deleting $num comments from $ip"); - foreach($cids as $cid) { - send_event(new CommentDeletionEvent($cid)); - } - flash_message("Deleted $num comments"); - - $page->set_mode("redirect"); - $page->set_redirect(make_link("admin")); - } - else { - $this->theme->display_permission_denied(); - } - } - else if($event->get_arg(0) === "list") { - $page_num = int_escape($event->get_arg(1)); - $this->build_page($page_num); - } - else if($event->get_arg(0) === "beta-search") { - $search = $event->get_arg(1); - $page_num = int_escape($event->get_arg(2)); - $duser = User::by_name($search); - $i_comment_count = Comment::count_comments_by_user($duser); - $com_per_page = 50; - $total_pages = ceil($i_comment_count/$com_per_page); - $page_num = $this->sanity_check_pagenumber($page_num, $total_pages); - $comments = $this->get_user_comments($duser->id, $com_per_page, ($page_num-1) * $com_per_page); - $this->theme->display_all_user_comments($comments, $page_num, $total_pages, $duser); + switch($event->get_arg(0)) { + case "add": $this->onPageRequest_add(); break; + case "delete": $this->onPageRequest_delete($event); break; + case "bulk_delete": $this->onPageRequest_bulk_delete(); break; + case "list": $this->onPageRequest_list($event); break; + case "beta-search": $this->onPageRequest_beta_search($event); break; } } } + private function onPageRequest_add() { + global $user, $page; + if (isset($_POST['image_id']) && isset($_POST['comment'])) { + try { + $i_iid = int_escape($_POST['image_id']); + $cpe = new CommentPostingEvent($_POST['image_id'], $user, $_POST['comment']); + send_event($cpe); + $page->set_mode("redirect"); + $page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid")); + } catch (CommentPostingException $ex) { + $this->theme->display_error(403, "Comment Blocked", $ex->getMessage()); + } + } + } + + private function onPageRequest_delete(PageRequestEvent $event) { + global $user, $page; + if ($user->can("delete_comment")) { + // FIXME: post, not args + if ($event->count_args() === 3) { + send_event(new CommentDeletionEvent($event->get_arg(1))); + flash_message("Deleted comment"); + $page->set_mode("redirect"); + if (!empty($_SERVER['HTTP_REFERER'])) { + $page->set_redirect($_SERVER['HTTP_REFERER']); + } else { + $page->set_redirect(make_link("post/view/" . $event->get_arg(2))); + } + } + } else { + $this->theme->display_permission_denied(); + } + } + + private function onPageRequest_bulk_delete() { + global $user, $database, $page; + if ($user->can("delete_comment") && !empty($_POST["ip"])) { + $ip = $_POST['ip']; + + $comment_ids = $database->get_col(" + SELECT id + FROM comments + WHERE owner_ip=:ip + ", array("ip" => $ip)); + $num = count($comment_ids); + log_warning("comment", "Deleting $num comments from $ip"); + foreach($comment_ids as $cid) { + send_event(new CommentDeletionEvent($cid)); + } + flash_message("Deleted $num comments"); + + $page->set_mode("redirect"); + $page->set_redirect(make_link("admin")); + } else { + $this->theme->display_permission_denied(); + } + } + + private function onPageRequest_list(PageRequestEvent $event) { + $page_num = int_escape($event->get_arg(1)); + $this->build_page($page_num); + } + + private function onPageRequest_beta_search(PageRequestEvent $event) { + $search = $event->get_arg(1); + $page_num = int_escape($event->get_arg(2)); + $duser = User::by_name($search); + $i_comment_count = Comment::count_comments_by_user($duser); + $com_per_page = 50; + $total_pages = ceil($i_comment_count / $com_per_page); + $page_num = clamp($page_num, 1, $total_pages); + $comments = $this->get_user_comments($duser->id, $com_per_page, ($page_num - 1) * $com_per_page); + $this->theme->display_all_user_comments($comments, $page_num, $total_pages, $duser); + } + public function onAdminBuilding(AdminBuildingEvent $event) { $this->theme->display_admin_block(); } @@ -264,7 +285,10 @@ class CommentList extends Extension { public function onCommentDeletion(CommentDeletionEvent $event) { global $database; - $database->Execute("DELETE FROM comments WHERE id=:comment_id", array("comment_id"=>$event->comment_id)); + $database->Execute(" + DELETE FROM comments + WHERE id=:comment_id + ", array("comment_id"=>$event->comment_id)); log_info("comment", "Deleting Comment #{$event->comment_id}"); } @@ -328,38 +352,32 @@ class CommentList extends Extension { ") / 10); $database->cache->set("comment_pages", $total_pages, 600); } - - if(is_null($current_page) || $current_page <= 0) { - $current_page = 1; - } - $current_page = $this->sanity_check_pagenumber($current_page, $total_pages); + $total_pages = max($total_pages, 1); + + $current_page = clamp($current_page, 1, $total_pages); $threads_per_page = 10; $start = $threads_per_page * ($current_page - 1); - $get_threads = " + $result = $database->Execute(" SELECT image_id,MAX(posted) AS latest FROM comments $where GROUP BY image_id ORDER BY latest DESC LIMIT :limit OFFSET :offset - "; - $result = $database->Execute($get_threads, array("limit"=>$threads_per_page, "offset"=>$start)); + ", array("limit"=>$threads_per_page, "offset"=>$start)); - if(ext_is_live("Ratings")) { - $user_ratings = Ratings::get_user_privs($user); - } else { - $user_ratings = ""; - } + $user_ratings = ext_is_live("Ratings") ? Ratings::get_user_privs($user) : ""; $images = array(); while($row = $result->fetch()) { $image = Image::by_id($row["image_id"]); - if(ext_is_live("Ratings") && !is_null($image)) { - if(strpos($user_ratings, $image->rating) === FALSE) { - $image = null; // this is "clever", I may live to regret it - } + if( + ext_is_live("Ratings") && !is_null($image) && + strpos($user_ratings, $image->rating) === FALSE + ) { + $image = null; // this is "clever", I may live to regret it } if(!is_null($image)) { $comments = $this->get_comments($image->id); @@ -373,22 +391,13 @@ class CommentList extends Extension { // get comments {{{ /** - * @param int $count - * @return array + * @param string $query + * @param array $args + * @return Comment[] */ - private function get_recent_comments($count) { + private function get_generic_comments($query, $args) { global $database; - $rows = $database->get_all(" - SELECT - users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class, - comments.comment as comment, comments.id as comment_id, - comments.image_id as image_id, comments.owner_ip as poster_ip, - comments.posted as posted - FROM comments - LEFT JOIN users ON comments.owner_id=users.id - ORDER BY comments.id DESC - LIMIT :limit - ", array("limit"=>$count)); + $rows = $database->get_all($query, $args); $comments = array(); foreach($rows as $row) { $comments[] = new Comment($row); @@ -396,60 +405,68 @@ class CommentList extends Extension { return $comments; } + /** + * @param int $count + * @return Comment[] + */ + private function get_recent_comments($count) { + return $this->get_generic_comments(" + SELECT + users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class, + comments.comment as comment, comments.id as comment_id, + comments.image_id as image_id, comments.owner_ip as poster_ip, + comments.posted as posted + FROM comments + LEFT JOIN users ON comments.owner_id=users.id + ORDER BY comments.id DESC + LIMIT :limit + ", array("limit"=>$count)); + } + /** * @param int $user_id * @param int $count * @param int $offset - * @return array + * @return Comment[] */ private function get_user_comments(/*int*/ $user_id, /*int*/ $count, /*int*/ $offset=0) { - global $database; - $rows = $database->get_all(" - SELECT + return $this->get_generic_comments(" + SELECT users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class, comments.comment as comment, comments.id as comment_id, comments.image_id as image_id, comments.owner_ip as poster_ip, comments.posted as posted - FROM comments - LEFT JOIN users ON comments.owner_id=users.id - WHERE users.id = :user_id - ORDER BY comments.id DESC - LIMIT :limit OFFSET :offset - ", array("user_id"=>$user_id, "offset"=>$offset, "limit"=>$count)); - $comments = array(); - foreach($rows as $row) { - $comments[] = new Comment($row); - } - return $comments; + FROM comments + LEFT JOIN users ON comments.owner_id=users.id + WHERE users.id = :user_id + ORDER BY comments.id DESC + LIMIT :limit OFFSET :offset + ", array("user_id"=>$user_id, "offset"=>$offset, "limit"=>$count)); } /** * @param int $image_id - * @return array + * @return Comment[] */ private function get_comments(/*int*/ $image_id) { - global $database; - $i_image_id = int_escape($image_id); - $rows = $database->get_all(" - SELECT + return $this->get_generic_comments(" + SELECT users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class, comments.comment as comment, comments.id as comment_id, comments.image_id as image_id, comments.owner_ip as poster_ip, comments.posted as posted - FROM comments - LEFT JOIN users ON comments.owner_id=users.id - WHERE comments.image_id=:image_id - ORDER BY comments.id ASC - ", array("image_id"=>$i_image_id)); - $comments = array(); - foreach($rows as $row) { - $comments[] = new Comment($row); - } - return $comments; + FROM comments + LEFT JOIN users ON comments.owner_id=users.id + WHERE comments.image_id=:image_id + ORDER BY comments.id ASC + ", array("image_id"=>$image_id)); } // }}} // add / remove / edit comments {{{ + /** + * @return bool + */ private function is_comment_limit_hit() { global $config, $database; @@ -463,9 +480,11 @@ class CommentList extends Extension { else $window_sql = "interval '$window minute'"; // window doesn't work as an SQL param because it's inside quotes >_< - $result = $database->get_all("SELECT * FROM comments WHERE owner_ip = :remote_ip ". - "AND posted > now() - $window_sql", - Array("remote_ip"=>$_SERVER['REMOTE_ADDR'])); + $result = $database->get_all(" + SELECT * + FROM comments + WHERE owner_ip = :remote_ip AND posted > now() - $window_sql + ", array("remote_ip"=>$_SERVER['REMOTE_ADDR'])); return (count($result) >= $max); } @@ -483,6 +502,8 @@ class CommentList extends Extension { * many times. * * FIXME: assumes comments are posted via HTTP... + * + * @return string */ public static function get_hash() { return md5($_SERVER['REMOTE_ADDR'] . date("%Y%m%d")); @@ -537,28 +558,14 @@ class CommentList extends Extension { */ private function is_dupe(/*int*/ $image_id, /*string*/ $comment) { global $database; - return ($database->get_row("SELECT * FROM comments WHERE image_id=:image_id AND comment=:comment", array("image_id"=>$image_id, "comment"=>$comment))); + return $database->get_row(" + SELECT * + FROM comments + WHERE image_id=:image_id AND comment=:comment + ", array("image_id"=>$image_id, "comment"=>$comment)); } // do some checks - /** - * @param int $pagenum - * @param int $maxpage - * @return int - */ - private function sanity_check_pagenumber(/*int*/ $pagenum, /*int*/ $maxpage){ - if (!is_numeric($pagenum)){ - $pagenum=1; - } - if ($pagenum>$maxpage){ - $pagenum=$maxpage; - } - if ($pagenum<=0){ - $pagenum=1; - } - return $pagenum; - } - /** * @param int $image_id * @param User $user @@ -588,6 +595,12 @@ class CommentList extends Extension { log_info("comment", "Comment #$cid added to Image #$image_id: $snippet", false, array("image_id"=>$image_id, "comment_id"=>$cid)); } + /** + * @param int $image_id + * @param User $user + * @param string $comment + * @throws CommentPostingException + */ private function comment_checks(/*int*/ $image_id, User $user, /*string*/ $comment) { global $config, $page; diff --git a/ext/comment/test.php b/ext/comment/test.php index 442a79b8..93230a71 100644 --- a/ext/comment/test.php +++ b/ext/comment/test.php @@ -1,19 +1,19 @@ set_int("comment_limit", 100); $this->log_out(); } - function tearDown() { + public function tearDown() { global $config; $config->set_int("comment_limit", 10); parent::tearDown(); } - function testCommentsPage() { + public function testCommentsPage() { global $user; $this->log_in_as_user(); @@ -85,8 +85,9 @@ class CommentListTest extends ShimmiePHPUnitTestCase { $this->assert_no_text('ASDFASDF'); } - /* - function testSingleDel() { + public function testSingleDel() { + $this->markTestIncomplete(); + $this->log_in_as_admin(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); @@ -106,5 +107,4 @@ class CommentListTest extends ShimmiePHPUnitTestCase { $this->delete_image($image_id); $this->log_out(); } - */ } diff --git a/ext/danbooru_api/main.php b/ext/danbooru_api/main.php index 18ae0f26..3bbad680 100644 --- a/ext/danbooru_api/main.php +++ b/ext/danbooru_api/main.php @@ -55,360 +55,46 @@ class DanbooruApi extends Extension { } // Danbooru API - private function api_danbooru(PageRequestEvent $event) - { + private function api_danbooru(PageRequestEvent $event) { global $page; - global $config; - global $database; - global $user; $page->set_mode("data"); - $page->set_type("application/xml"); - //debug - //$page->set_type("text/plain"); - $results = array(); - - $danboorup_kludge=1; // danboorup for firefox makes broken links out of location: /path - - /* - add_post() - Adds a post to the database. - Parameters - * login: login - * password: password - * file: file as a multipart form - * source: source url - * title: title **IGNORED** - * tags: list of tags as a string, delimited by whitespace - * md5: MD5 hash of upload in hexadecimal format - * rating: rating of the post. can be explicit, questionable, or safe. **IGNORED** - Notes - * The only necessary parameter is tags and either file or source. - * If you want to sign your post, you need a way to authenticate your account, either by supplying login and password, or by supplying a cookie. - * If an account is not supplied or if it doesn‘t authenticate, he post will be added anonymously. - * If the md5 parameter is supplied and does not match the hash of what‘s on the server, the post is rejected. - Response - The response depends on the method used: - Post - * X-Danbooru-Location set to the URL for newly uploaded post. - Get - * Redirected to the newly uploaded post. - */ - if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) - { + if(($event->get_arg(1) == 'add_post') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'create.xml'))) { // No XML data is returned from this function $page->set_type("text/plain"); - // Check first if a login was supplied, if it wasn't check if the user is logged in via cookie - // If all that fails, it's an anonymous upload - $this->authenticate_user(); - // Now we check if a file was uploaded or a url was provided to transload - // Much of this code is borrowed from /ext/upload - - if($user->can("create_image")) - { - if(isset($_FILES['file'])) - { // A file was POST'd in - $file = $_FILES['file']['tmp_name']; - $filename = $_FILES['file']['name']; - // If both a file is posted and a source provided, I'm assuming source is the source of the file - if(isset($_REQUEST['source']) && !empty($_REQUEST['source'])) - { - $source = $_REQUEST['source']; - } else - { - $source = null; - } - } elseif(isset($_FILES['post'])) - { - $file = $_FILES['post']['tmp_name']['file']; - $filename = $_FILES['post']['name']['file']; - if(isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source'])) - { - $source = $_REQUEST['post']['source']; - } else - { - $source = null; - } - } elseif(isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) - { // A url was provided - $url = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source']; - $source = $url; - $tmp_filename = tempnam("/tmp", "shimmie_transload"); - - // Are we using fopen wrappers or curl? - if($config->get_string("transload_engine") == "fopen") - { - $fp = fopen($url, "r"); - if(!$fp) { - $page->set_code(409); - $page->add_http_header("X-Danbooru-Errors: fopen read error"); - } - - $data = ""; - $length = 0; - while(!feof($fp) && $length <= $config->get_int('upload_size')) - { - $data .= fread($fp, 8192); - $length = strlen($data); - } - fclose($fp); - - $fp = fopen($tmp_filename, "w"); - fwrite($fp, $data); - fclose($fp); - } - - if($config->get_string("transload_engine") == "curl") - { - $ch = curl_init($url); - $fp = fopen($tmp_filename, "w"); - - curl_setopt($ch, CURLOPT_FILE, $fp); - curl_setopt($ch, CURLOPT_HEADER, 0); - - curl_exec($ch); - curl_close($ch); - fclose($fp); - } - $file = $tmp_filename; - $filename = basename($url); - } else - { // Nothing was specified at all - $page->set_code(409); - $page->add_http_header("X-Danbooru-Errors: no input files"); - return; - } - - // Get tags out of url - $posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']); - $hash = md5_file($file); - // Was an md5 supplied? Does it match the file hash? - if(isset($_REQUEST['md5'])) - { - if(strtolower($_REQUEST['md5']) != $hash) - { - $page->set_code(409); - $page->add_http_header("X-Danbooru-Errors: md5 mismatch"); - return; - } - } - // Upload size checking is now performed in the upload extension - // It is also currently broken due to some confusion over file variable ($tmp_filename?) - - // Does it exist already? - $existing = Image::by_hash($hash); - if(!is_null($existing)) { - $page->set_code(409); - $page->add_http_header("X-Danbooru-Errors: duplicate"); - $existinglink = make_link("post/view/" . $existing->id); - if($danboorup_kludge) $existinglink=make_http($existinglink); - $page->add_http_header("X-Danbooru-Location: $existinglink"); - return; // wut! - } - - // Fire off an event which should process the new file and add it to the db - $fileinfo = pathinfo($filename); - $metadata = array(); - $metadata['filename'] = $fileinfo['basename']; - $metadata['extension'] = $fileinfo['extension']; - $metadata['tags'] = $posttags; - $metadata['source'] = $source; - //log_debug("danbooru_api","========== NEW($filename) ========="); - //log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")..."); - - try { - $nevent = new DataUploadEvent($file, $metadata); - //log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")"); - send_event($nevent); - // If it went ok, grab the id for the newly uploaded image and pass it in the header - $newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error? - $newid = make_link("post/view/" . $newimg->id); - if($danboorup_kludge) $newid=make_http($newid); - - // Did we POST or GET this call? - if($_SERVER['REQUEST_METHOD'] == 'POST') - { - $page->add_http_header("X-Danbooru-Location: $newid"); - } - else - $page->add_http_header("Location: $newid"); - } - catch(UploadException $ex) { - // Did something screw up? - $page->set_code(409); - $page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage()); - return; - } - } else - { - $page->set_code(409); - $page->add_http_header("X-Danbooru-Errors: authentication error"); - return; - } + $this->api_add_post(); } - /* - find_posts() - Find all posts that match the search criteria. Posts will be ordered by id descending. - Parameters - * md5: md5 hash to search for (comma delimited) - * id: id to search for (comma delimited) - * tags: what tags to search for - * limit: limit - * page: page number - * after_id: limit results to posts added after this id - */ - if(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) - { - $this->authenticate_user(); - $start = 0; - - if(isset($_GET['md5'])) - { - $md5list = explode(",",$_GET['md5']); - foreach($md5list as $md5) - { - $results[] = Image::by_hash($md5); - } - $count = count($results); - } elseif(isset($_GET['id'])) - { - $idlist = explode(",",$_GET['id']); - foreach($idlist as $id) - { - $results[] = Image::by_id($id); - } - $count = count($results); - } else - { - $limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100; - - // Calculate start offset. - if (isset($_GET['page'])) // Danbooru API uses 'page' >= 1 - $start = (int_escape($_GET['page'])-1) * $limit; - else if (isset($_GET['pid'])) // Gelbooru API uses 'pid' >= 0 - $start = int_escape($_GET['pid']) * $limit; - else - $start = 0; - - $tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : array(); - $count = Image::count_images($tags); - $results = Image::find_images(max($start, 0), min($limit, 100), $tags); - } - - // Now we have the array $results filled with Image objects - // Let's display them - $xml = "\n"; - foreach($results as $img) - { - // Sanity check to see if $img is really an image object - // If it isn't (e.g. someone requested an invalid md5 or id), break out of the this - if(!is_object($img)) - continue; - $taglist = $img->get_tag_list(); - $owner = $img->get_owner(); - $previewsize = get_thumbnail_size($img->width, $img->height); - $xml .= xml_tag("post", array( - "id" => $img->id, - "md5" => $img->hash, - "file_name" => $img->filename, - "file_url" => $img->get_image_link(), - "height" => $img->height, - "width" => $img->width, - "preview_url" => $img->get_thumb_link(), - "preview_height" => $previewsize[1], - "preview_width" => $previewsize[0], - "rating" => "u", - "date" => $img->posted, - "is_warehoused" => false, - "tags" => $taglist, - "source" => $img->source, - "score" => 0, - "author" => $owner->name - )); - } - $xml .= ""; - $page->set_data($xml); + elseif(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml'))) { + $page->set_type("application/xml"); + $page->set_data($this->api_find_posts()); } - /* - find_tags() Find all tags that match the search criteria. - Parameters - * id: A comma delimited list of tag id numbers. - * name: A comma delimited list of tag names. - * tags: any typical tag query. See Tag#parse_query for details. - * after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh - */ - if($event->get_arg(1) == 'find_tags') { - if(isset($_GET['id'])) { - $idlist = explode(",",$_GET['id']); - foreach($idlist as $id) { - $sqlresult = $database->get_all( - "SELECT id,tag,count FROM tags WHERE id = ?", - array($id)); - foreach($sqlresult as $row) { - $results[] = array($row['count'], $row['tag'], $row['id']); - } - } - } - elseif(isset($_GET['name'])) { - $namelist = explode(",",$_GET['name']); - foreach($namelist as $name) { - $sqlresult = $database->get_all( - "SELECT id,tag,count FROM tags WHERE tag = ?", - array($name)); - foreach($sqlresult as $row) { - $results[] = array($row['count'], $row['tag'], $row['id']); - } - } - } - /* Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags - elseif(isset($_GET['tags'])) { - $start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0; - $tags = Tag::explode($_GET['tags']); - - } - */ - else { - $start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0; - $sqlresult = $database->get_all( - "SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC", - array($start)); - foreach($sqlresult as $row) { - $results[] = array($row['count'], $row['tag'], $row['id']); - } - } - - // Tag results collected, build XML output - $xml = "\n"; - foreach($results as $tag) { - $xml .= "xmlspecialchars($tag[1]) . "\" id=\"$tag[2]\"/>\n"; - } - $xml .= ""; - $page->set_data($xml); + elseif($event->get_arg(1) == 'find_tags') { + $page->set_type("application/xml"); + $page->set_data($this->api_find_tags()); } // Hackery for danbooruup 0.3.2 providing the wrong view url. This simply redirects to the proper // Shimmie view page // Example: danbooruup says the url is http://shimmie/api/danbooru/post/show/123 // This redirects that to http://shimmie/post/view/123 - if(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) { + elseif(($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'show')) { $fixedlocation = make_link("post/view/" . $event->get_arg(3)); $page->set_mode("redirect"); $page->set_redirect($fixedlocation); } } - // Turns out I use this a couple times so let's make it a utility function - // Authenticates a user based on the contents of the login and password parameters - // or makes them anonymous. Does not set any cookies or anything permanent. - private function authenticate_user() - { - global $config; - global $user; + /** + * Turns out I use this a couple times so let's make it a utility function + * Authenticates a user based on the contents of the login and password parameters + * or makes them anonymous. Does not set any cookies or anything permanent. + */ + private function authenticate_user() { + global $config, $user; - if(isset($_REQUEST['login']) && isset($_REQUEST['password'])) - { + if(isset($_REQUEST['login']) && isset($_REQUEST['password'])) { // Get this user from the db, if it fails the user becomes anonymous // Code borrowed from /ext/user $name = $_REQUEST['login']; @@ -416,18 +102,294 @@ class DanbooruApi extends Extension { $duser = User::by_name_and_pass($name, $pass); if(!is_null($duser)) { $user = $duser; - } else - { + } + else { $user = User::by_id($config->get_int("anon_id", 0)); } } } - // From htmlspecialchars man page on php.net comments - // If tags contain quotes they need to be htmlified - private function xmlspecialchars($text) - { - return str_replace(''', ''', htmlspecialchars($text, ENT_QUOTES)); + /** + * find_tags() + * Find all tags that match the search criteria. + * + * Parameters + * - id: A comma delimited list of tag id numbers. + * - name: A comma delimited list of tag names. + * - tags: any typical tag query. See Tag#parse_query for details. + * - after_id: limit results to tags with an id number after after_id. Useful if you only want to refresh + * + * @return string + */ + private function api_find_tags() { + global $database; + $results = array(); + if(isset($_GET['id'])) { + $idlist = explode(",", $_GET['id']); + foreach ($idlist as $id) { + $sqlresult = $database->get_all( + "SELECT id,tag,count FROM tags WHERE id = ?", + array($id)); + foreach ($sqlresult as $row) { + $results[] = array($row['count'], $row['tag'], $row['id']); + } + } + } + elseif(isset($_GET['name'])) { + $namelist = explode(",", $_GET['name']); + foreach ($namelist as $name) { + $sqlresult = $database->get_all( + "SELECT id,tag,count FROM tags WHERE tag = ?", + array($name)); + foreach ($sqlresult as $row) { + $results[] = array($row['count'], $row['tag'], $row['id']); + } + } + } + // Currently disabled to maintain identical functionality to danbooru 1.0's own "broken" find_tags + elseif(false && isset($_GET['tags'])) { + $start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0; + $tags = Tag::explode($_GET['tags']); + } + else { + $start = isset($_GET['after_id']) ? int_escape($_GET['offset']) : 0; + $sqlresult = $database->get_all( + "SELECT id,tag,count FROM tags WHERE count > 0 AND id >= ? ORDER BY id DESC", + array($start)); + foreach ($sqlresult as $row) { + $results[] = array($row['count'], $row['tag'], $row['id']); + } + } + + // Tag results collected, build XML output + $xml = "\n"; + foreach ($results as $tag) { + $xml .= xml_tag("tag", array( + "type" => "0", + "counts" => $tag[0], + "name" => $tag[1], + "id" => $tag[2], + )); + } + $xml .= ""; + return $xml; + } + + /** + * find_posts() + * Find all posts that match the search criteria. Posts will be ordered by id descending. + * + * Parameters: + * - md5: md5 hash to search for (comma delimited) + * - id: id to search for (comma delimited) + * - tags: what tags to search for + * - limit: limit + * - page: page number + * - after_id: limit results to posts added after this id + * + * @return string + * @throws SCoreException + */ + private function api_find_posts() { + $results = array(); + + $this->authenticate_user(); + $start = 0; + + if(isset($_GET['md5'])) { + $md5list = explode(",", $_GET['md5']); + foreach ($md5list as $md5) { + $results[] = Image::by_hash($md5); + } + $count = count($results); + } + elseif(isset($_GET['id'])) { + $idlist = explode(",", $_GET['id']); + foreach ($idlist as $id) { + $results[] = Image::by_id($id); + } + $count = count($results); + } + else { + $limit = isset($_GET['limit']) ? int_escape($_GET['limit']) : 100; + + // Calculate start offset. + if (isset($_GET['page'])) // Danbooru API uses 'page' >= 1 + $start = (int_escape($_GET['page']) - 1) * $limit; + else if (isset($_GET['pid'])) // Gelbooru API uses 'pid' >= 0 + $start = int_escape($_GET['pid']) * $limit; + else + $start = 0; + + $tags = isset($_GET['tags']) ? Tag::explode($_GET['tags']) : array(); + $count = Image::count_images($tags); + $results = Image::find_images(max($start, 0), min($limit, 100), $tags); + } + + // Now we have the array $results filled with Image objects + // Let's display them + $xml = "\n"; + foreach ($results as $img) { + // Sanity check to see if $img is really an image object + // If it isn't (e.g. someone requested an invalid md5 or id), break out of the this + if (!is_object($img)) + continue; + $taglist = $img->get_tag_list(); + $owner = $img->get_owner(); + $previewsize = get_thumbnail_size($img->width, $img->height); + $xml .= xml_tag("post", array( + "id" => $img->id, + "md5" => $img->hash, + "file_name" => $img->filename, + "file_url" => $img->get_image_link(), + "height" => $img->height, + "width" => $img->width, + "preview_url" => $img->get_thumb_link(), + "preview_height" => $previewsize[1], + "preview_width" => $previewsize[0], + "rating" => "u", + "date" => $img->posted, + "is_warehoused" => false, + "tags" => $taglist, + "source" => $img->source, + "score" => 0, + "author" => $owner->name + )); + } + $xml .= ""; + return $xml; + } + + /** + * add_post() + * Adds a post to the database. + * + * Parameters: + * - login: login + * - password: password + * - file: file as a multipart form + * - source: source url + * - title: title **IGNORED** + * - tags: list of tags as a string, delimited by whitespace + * - md5: MD5 hash of upload in hexadecimal format + * - rating: rating of the post. can be explicit, questionable, or safe. **IGNORED** + * + * Notes: + * - The only necessary parameter is tags and either file or source. + * - If you want to sign your post, you need a way to authenticate your account, either by supplying login and password, or by supplying a cookie. + * - If an account is not supplied or if it doesn‘t authenticate, he post will be added anonymously. + * - If the md5 parameter is supplied and does not match the hash of what‘s on the server, the post is rejected. + * + * Response + * The response depends on the method used: + * Post: + * - X-Danbooru-Location set to the URL for newly uploaded post. + * Get: + * - Redirected to the newly uploaded post. + */ + private function api_add_post() { + global $user, $config, $page; + $danboorup_kludge = 1; // danboorup for firefox makes broken links out of location: /path + + // Check first if a login was supplied, if it wasn't check if the user is logged in via cookie + // If all that fails, it's an anonymous upload + $this->authenticate_user(); + // Now we check if a file was uploaded or a url was provided to transload + // Much of this code is borrowed from /ext/upload + + if (!$user->can("create_image")) { + $page->set_code(409); + $page->add_http_header("X-Danbooru-Errors: authentication error"); + return; + } + + if (isset($_FILES['file'])) { // A file was POST'd in + $file = $_FILES['file']['tmp_name']; + $filename = $_FILES['file']['name']; + // If both a file is posted and a source provided, I'm assuming source is the source of the file + if (isset($_REQUEST['source']) && !empty($_REQUEST['source'])) { + $source = $_REQUEST['source']; + } else { + $source = null; + } + } elseif (isset($_FILES['post'])) { + $file = $_FILES['post']['tmp_name']['file']; + $filename = $_FILES['post']['name']['file']; + if (isset($_REQUEST['post']['source']) && !empty($_REQUEST['post']['source'])) { + $source = $_REQUEST['post']['source']; + } else { + $source = null; + } + } elseif (isset($_REQUEST['source']) || isset($_REQUEST['post']['source'])) { // A url was provided + $source = isset($_REQUEST['source']) ? $_REQUEST['source'] : $_REQUEST['post']['source']; + $file = tempnam("/tmp", "shimmie_transload"); + $ok = transload($source, $file); + if (!$ok) { + $page->set_code(409); + $page->add_http_header("X-Danbooru-Errors: fopen read error"); + return; + } + $filename = basename($source); + } else { // Nothing was specified at all + $page->set_code(409); + $page->add_http_header("X-Danbooru-Errors: no input files"); + return; + } + + // Get tags out of url + $posttags = Tag::explode(isset($_REQUEST['tags']) ? $_REQUEST['tags'] : $_REQUEST['post']['tags']); + + // Was an md5 supplied? Does it match the file hash? + $hash = md5_file($file); + if (isset($_REQUEST['md5']) && strtolower($_REQUEST['md5']) != $hash) { + $page->set_code(409); + $page->add_http_header("X-Danbooru-Errors: md5 mismatch"); + return; + } + // Upload size checking is now performed in the upload extension + // It is also currently broken due to some confusion over file variable ($tmp_filename?) + + // Does it exist already? + $existing = Image::by_hash($hash); + if (!is_null($existing)) { + $page->set_code(409); + $page->add_http_header("X-Danbooru-Errors: duplicate"); + $existinglink = make_link("post/view/" . $existing->id); + if ($danboorup_kludge) $existinglink = make_http($existinglink); + $page->add_http_header("X-Danbooru-Location: $existinglink"); + return; + } + + // Fire off an event which should process the new file and add it to the db + $fileinfo = pathinfo($filename); + $metadata = array(); + $metadata['filename'] = $fileinfo['basename']; + $metadata['extension'] = $fileinfo['extension']; + $metadata['tags'] = $posttags; + $metadata['source'] = $source; + //log_debug("danbooru_api","========== NEW($filename) ========="); + //log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")..."); + + try { + $nevent = new DataUploadEvent($file, $metadata); + //log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")"); + send_event($nevent); + // If it went ok, grab the id for the newly uploaded image and pass it in the header + $newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error? + $newid = make_link("post/view/" . $newimg->id); + if ($danboorup_kludge) $newid = make_http($newid); + + // Did we POST or GET this call? + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $page->add_http_header("X-Danbooru-Location: $newid"); + } else { + $page->add_http_header("Location: $newid"); + } + } catch (UploadException $ex) { + // Did something screw up? + $page->set_code(409); + $page->add_http_header("X-Danbooru-Errors: exception - " . $ex->getMessage()); + } } } diff --git a/ext/danbooru_api/test.php b/ext/danbooru_api/test.php index bdb2d63b..6ea0fef7 100644 --- a/ext/danbooru_api/test.php +++ b/ext/danbooru_api/test.php @@ -1,6 +1,6 @@ log_in_as_admin(); $image_id = $this->post_image("tests/bedroom_workshop.jpg", "data"); diff --git a/ext/downtime/test.php b/ext/downtime/test.php index 561e0018..4331e27f 100644 --- a/ext/downtime/test.php +++ b/ext/downtime/test.php @@ -1,11 +1,11 @@ set_bool("downtime", false); } - function testDowntime() { + public function testDowntime() { global $config; $config->set_string("downtime_message", "brb, unit testing"); diff --git a/ext/emoticons/test.php b/ext/emoticons/test.php index ab1364e2..bc4a8af9 100644 --- a/ext/emoticons/test.php +++ b/ext/emoticons/test.php @@ -1,6 +1,6 @@ log_in_as_user(); diff --git a/ext/et/test.php b/ext/et/test.php index 4e53a8bb..1d741eda 100644 --- a/ext/et/test.php +++ b/ext/et/test.php @@ -1,6 +1,6 @@ log_in_as_admin(); $this->get_page("system_info"); $this->assert_title("System Info"); diff --git a/ext/ext_manager/main.php b/ext/ext_manager/main.php index 64e7acc6..ee465965 100644 --- a/ext/ext_manager/main.php +++ b/ext/ext_manager/main.php @@ -23,7 +23,7 @@ class ExtensionInfo { var $description, $documentation, $version, $visibility; var $enabled; - function __construct($main) { + public function __construct($main) { $matches = array(); $lines = file($main); $number_of_lines = count($lines); @@ -37,26 +37,26 @@ class ExtensionInfo { if(preg_match("/Name: (.*)/", $line, $matches)) { $this->name = $matches[1]; } - if(preg_match("/Visibility: (.*)/", $line, $matches)) { + else if(preg_match("/Visibility: (.*)/", $line, $matches)) { $this->visibility = $matches[1]; } - if(preg_match("/Link: (.*)/", $line, $matches)) { + else if(preg_match("/Link: (.*)/", $line, $matches)) { $this->link = $matches[1]; if($this->link[0] == "/") { $this->link = make_link(substr($this->link, 1)); } } - if(preg_match("/Version: (.*)/", $line, $matches)) { + else if(preg_match("/Version: (.*)/", $line, $matches)) { $this->version = $matches[1]; } - if(preg_match("/Author: (.*) [<\(](.*@.*)[>\)]/", $line, $matches)) { + else if(preg_match("/Author: (.*) [<\(](.*@.*)[>\)]/", $line, $matches)) { $this->author = $matches[1]; $this->email = $matches[2]; } else if(preg_match("/Author: (.*)/", $line, $matches)) { $this->author = $matches[1]; } - if(preg_match("/(.*)Description: ?(.*)/", $line, $matches)) { + else if(preg_match("/(.*)Description: ?(.*)/", $line, $matches)) { $this->description = $matches[2]; $start = $matches[1]." "; $start_len = strlen($start); @@ -65,7 +65,7 @@ class ExtensionInfo { $i++; } } - if(preg_match("/(.*)Documentation: ?(.*)/", $line, $matches)) { + else if(preg_match("/(.*)Documentation: ?(.*)/", $line, $matches)) { $this->documentation = $matches[2]; $start = $matches[1]." "; $start_len = strlen($start); @@ -75,7 +75,7 @@ class ExtensionInfo { } $this->documentation = str_replace('$site', make_http(get_base_href()), $this->documentation); } - if(preg_match("/\*\//", $line, $matches)) { + else if(preg_match("/\*\//", $line, $matches)) { break; } } @@ -156,7 +156,7 @@ class ExtManager extends Extension { /** * @param bool $all - * @return array + * @return ExtensionInfo[] */ private function get_extensions(/*bool*/ $all) { $extensions = array(); diff --git a/ext/ext_manager/test.php b/ext/ext_manager/test.php index 330df3cc..850abc27 100644 --- a/ext/ext_manager/test.php +++ b/ext/ext_manager/test.php @@ -1,6 +1,6 @@ get_page('ext_manager'); $this->assert_title("Extensions"); diff --git a/ext/favorites/main.php b/ext/favorites/main.php index 0cfca51b..8e6af251 100644 --- a/ext/favorites/main.php +++ b/ext/favorites/main.php @@ -204,7 +204,7 @@ class Favorites extends Extension { /** * @param Image $image - * @return array + * @return string[] */ private function list_persons_who_have_favorited(Image $image) { global $database; diff --git a/ext/favorites/test.php b/ext/favorites/test.php index bd172711..cb6c09c7 100644 --- a/ext/favorites/test.php +++ b/ext/favorites/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test"); diff --git a/ext/featured/test.php b/ext/featured/test.php index 4d93a5ba..74aa5678 100644 --- a/ext/featured/test.php +++ b/ext/featured/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); diff --git a/ext/handle_404/test.php b/ext/handle_404/test.php index ff84edac..2d7c9f73 100644 --- a/ext/handle_404/test.php +++ b/ext/handle_404/test.php @@ -1,6 +1,6 @@ get_page('not/a/page'); // most descriptive error first $this->assert_text("No handler could be found for the page 'not/a/page'"); diff --git a/ext/handle_archive/main.php b/ext/handle_archive/main.php index ba2fd59c..1fe56ca6 100644 --- a/ext/handle_archive/main.php +++ b/ext/handle_archive/main.php @@ -44,7 +44,7 @@ class ArchiveFileHandler extends Extension { } /** - * @param $ext + * @param string $ext * @return bool */ private function supported_ext($ext) { diff --git a/ext/handle_flash/main.php b/ext/handle_flash/main.php index 58d93a3f..9b8eda6c 100644 --- a/ext/handle_flash/main.php +++ b/ext/handle_flash/main.php @@ -50,7 +50,7 @@ class FlashFileHandler extends DataHandlerExtension { } /** - * @param $file + * @param string $file * @return bool */ protected function check_contents(/*string*/ $file) { diff --git a/ext/handle_ico/main.php b/ext/handle_ico/main.php index 185bd7f3..c16fb64e 100644 --- a/ext/handle_ico/main.php +++ b/ext/handle_ico/main.php @@ -50,7 +50,7 @@ class IcoFileHandler extends Extension { } /** - * @param $ext + * @param string $ext * @return bool */ private function supported_ext($ext) { @@ -59,8 +59,8 @@ class IcoFileHandler extends Extension { } /** - * @param $filename - * @param $metadata + * @param string $filename + * @param mixed[] $metadata * @return Image */ private function create_image_from_data($filename, $metadata) { diff --git a/ext/handle_ico/test.php b/ext/handle_ico/test.php index 06674539..b019eed0 100644 --- a/ext/handle_ico/test.php +++ b/ext/handle_ico/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("lib/static/favicon.ico", "shimmie favicon"); $this->get_page("post/view/$image_id"); // test for no crash diff --git a/ext/handle_mp3/main.php b/ext/handle_mp3/main.php index 691fc644..069107b5 100644 --- a/ext/handle_mp3/main.php +++ b/ext/handle_mp3/main.php @@ -26,7 +26,7 @@ class MP3FileHandler extends DataHandlerExtension { /** * @param string $filename - * @param array $metadata + * @param mixed[] $metadata * @return Image|null */ protected function create_image_from_data($filename, $metadata) { diff --git a/ext/handle_pixel/main.php b/ext/handle_pixel/main.php index bacc8804..e216568a 100644 --- a/ext/handle_pixel/main.php +++ b/ext/handle_pixel/main.php @@ -67,7 +67,7 @@ class PixelFileHandler extends DataHandlerExtension { } /** - * @param $hash + * @param string $hash * @return bool */ protected function create_thumb_force(/*string*/ $hash) { @@ -91,9 +91,6 @@ class PixelFileHandler extends DataHandlerExtension { return $ok; } - /** - * @param ImageAdminBlockBuildingEvent $event - */ public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) { $event->add_part("
    diff --git a/ext/handle_pixel/test.php b/ext/handle_pixel/test.php index db2f524f..0eed282a 100644 --- a/ext/handle_pixel/test.php +++ b/ext/handle_pixel/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); //$this->assert_response(302); diff --git a/ext/handle_svg/main.php b/ext/handle_svg/main.php index 96640595..e4f2f9fe 100644 --- a/ext/handle_svg/main.php +++ b/ext/handle_svg/main.php @@ -51,7 +51,7 @@ class SVGFileHandler extends Extension { } /** - * @param $ext + * @param string $ext * @return bool */ private function supported_ext($ext) { @@ -60,8 +60,8 @@ class SVGFileHandler extends Extension { } /** - * @param $filename - * @param $metadata + * @param string $filename + * @param mixed[] $metadata * @return Image */ private function create_image_from_data($filename, $metadata) { @@ -82,7 +82,7 @@ class SVGFileHandler extends Extension { } /** - * @param $file + * @param string $file * @return bool */ private function check_contents($file) { diff --git a/ext/handle_svg/test.php b/ext/handle_svg/test.php index 41501b1c..aaa2c350 100644 --- a/ext/handle_svg/test.php +++ b/ext/handle_svg/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/test.svg", "something"); $this->get_page("post/view/$image_id"); // test for no crash diff --git a/ext/handle_video/main.php b/ext/handle_video/main.php index 4a253d5b..b6930177 100644 --- a/ext/handle_video/main.php +++ b/ext/handle_video/main.php @@ -118,7 +118,7 @@ class VideoFileHandler extends DataHandlerExtension { /** * @param string $filename - * @param array $metadata + * @param mixed[] $metadata * @return Image|null */ protected function create_image_from_data($filename, $metadata) { @@ -162,7 +162,7 @@ class VideoFileHandler extends DataHandlerExtension { } /** - * @param $file + * @param string $file * @return bool */ protected function check_contents($file) { diff --git a/ext/home/test.php b/ext/home/test.php index d6793e9d..3bf3c36d 100644 --- a/ext/home/test.php +++ b/ext/home/test.php @@ -1,6 +1,6 @@ get_page('home'); // FIXME: this page doesn't use blocks; need assert_data_contains diff --git a/ext/image/main.php b/ext/image/main.php index 3eddd7b2..71c829a2 100644 --- a/ext/image/main.php +++ b/ext/image/main.php @@ -32,6 +32,9 @@ class ImageAdditionEvent extends Event { class ImageAdditionException extends SCoreException { var $error; + /** + * @param string $error + */ public function __construct($error) { $this->error = $error; } diff --git a/ext/image_hash_ban/main.php b/ext/image_hash_ban/main.php index 742e8a85..07a36174 100644 --- a/ext/image_hash_ban/main.php +++ b/ext/image_hash_ban/main.php @@ -13,6 +13,9 @@ class RemoveImageHashBanEvent extends Event { var $hash; + /** + * @param string $hash + */ public function __construct($hash) { $this->hash = $hash; } @@ -23,6 +26,10 @@ class AddImageHashBanEvent extends Event { var $hash; var $reason; + /** + * @param string $hash + * @param string $reason + */ public function __construct($hash, $reason) { $this->hash = $hash; $this->reason = $reason; @@ -126,6 +133,11 @@ class ImageBan extends Extension { // DB funness + /** + * @param int $page + * @param int $size + * @return array + */ public function get_image_hash_bans($page, $size=100) { global $database; diff --git a/ext/image_hash_ban/test.php b/ext/image_hash_ban/test.php index 56c5dfc0..4e22fe71 100644 --- a/ext/image_hash_ban/test.php +++ b/ext/image_hash_ban/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); $this->log_out(); diff --git a/ext/index/main.php b/ext/index/main.php index 75a7fab0..78404fe6 100644 --- a/ext/index/main.php +++ b/ext/index/main.php @@ -253,9 +253,9 @@ class Index extends Extension { $search_terms = $event->get_search_terms(); $page_number = $event->get_page_number(); $page_size = $event->get_page_size(); - + $count_search_terms = count($search_terms); - + try { #log_debug("index", "Search for ".implode(" ", $search_terms), false, array("terms"=>$search_terms)); $total_pages = Image::count_pages($search_terms); @@ -277,7 +277,7 @@ class Index extends Extension { } $count_images = count($images); - + if($count_search_terms === 0 && $count_images === 0 && $page_number === 1) { $this->theme->display_intro($page); send_event(new PostListBuildingEvent($search_terms)); @@ -322,8 +322,17 @@ class Index extends Extension { // check for tags first as tag based searches are more common. if(preg_match("/^tags([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) { $cmp = ltrim($matches[1], ":") ?: "="; - $tags = $matches[2]; - $event->add_querylet(new Querylet('images.id IN (SELECT DISTINCT image_id FROM image_tags GROUP BY image_id HAVING count(image_id) '.$cmp.' '.$tags.')')); + $count = $matches[2]; + $event->add_querylet( + new Querylet("EXISTS ( + SELECT 1 + FROM image_tags it + LEFT JOIN tags t ON it.tag_id = t.id + WHERE images.id = it.image_id + GROUP BY image_id + HAVING COUNT(*) $cmp $count + )") + ); } else if(preg_match("/^ratio([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+):(\d+)$/i", $event->term, $matches)) { $cmp = preg_replace('/^:/', '=', $matches[1]); @@ -394,4 +403,3 @@ class Index extends Extension { $this->stpen++; } } - diff --git a/ext/index/test.php b/ext/index/test.php index c6a196f9..a53cdc28 100644 --- a/ext/index/test.php +++ b/ext/index/test.php @@ -1,6 +1,6 @@ get_page('post/list'); $this->assert_title("Welcome to Shimmie ".VERSION); $this->assert_no_text("Prev | Index | Next"); @@ -29,7 +29,7 @@ class IndexTest extends ShimmiePHPUnitTestCase { # FIXME: test search box } - function testSearches() { + public function testSearches() { $this->log_in_as_user(); $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "computer bedroom workshop"); diff --git a/ext/index/theme.php b/ext/index/theme.php index 1cacd4e2..dbe21094 100644 --- a/ext/index/theme.php +++ b/ext/index/theme.php @@ -3,6 +3,11 @@ class IndexTheme extends Themelet { var $page_number, $total_pages, $search_terms; + /** + * @param int $page_number + * @param int $total_pages + * @param string[] $search_terms + */ public function set_page($page_number, $total_pages, $search_terms) { $this->page_number = $page_number; $this->total_pages = $total_pages; @@ -27,6 +32,10 @@ and of course start organising your images :-) $page->add_block(new Block("Installation Succeeded!", $text, "main", 0)); } + /** + * @param Page $page + * @param Image[] $images + */ public function display_page(Page $page, $images) { $this->display_page_header($page, $images); @@ -41,12 +50,21 @@ and of course start organising your images :-) } } - public function display_admin_block(/*array(string)*/ $parts) { + /** + * @param string[] $parts + */ + public function display_admin_block($parts) { global $page; $page->add_block(new Block("List Controls", join("
    ", $parts), "left", 50)); } + /** + * @param int $page_number + * @param int $total_pages + * @param string[] $search_terms + * @return string + */ protected function build_navigation($page_number, $total_pages, $search_terms) { $prev = $page_number - 1; $next = $page_number + 1; @@ -72,6 +90,11 @@ and of course start organising your images :-) return $h_prev.' | '.$h_index.' | '.$h_next.'
    '.$h_search; } + /** + * @param Image[] $images + * @param string $query + * @return string + */ protected function build_table($images, $query) { $h_query = html_escape($query); $table = "
    "; @@ -82,6 +105,10 @@ and of course start organising your images :-) return $table; } + /** + * @param Page $page + * @param Image[] $images + */ protected function display_page_header(Page $page, $images) { global $config; @@ -102,6 +129,10 @@ and of course start organising your images :-) $page->set_heading($page_title); } + /** + * @param Page $page + * @param Image[] $images + */ protected function display_page_images(Page $page, $images) { if (count($this->search_terms) > 0) { $query = url_escape(implode(' ', $this->search_terms)); diff --git a/ext/ipban/test.php b/ext/ipban/test.php index 5e745a2a..ec0dfe0b 100644 --- a/ext/ipban/test.php +++ b/ext/ipban/test.php @@ -1,6 +1,6 @@ get_page('ip_ban/list'); $this->assert_response(403); $this->assert_title("Permission Denied"); @@ -9,7 +9,9 @@ class IPBanTest extends ShimmiePHPUnitTestCase { $this->get_page('ip_ban/list'); $this->assert_no_text("42.42.42.42"); - /* + + $this->markTestIncomplete(); + $this->set_field('ip', '42.42.42.42'); $this->set_field('reason', 'unit testing'); $this->set_field('end', '1 week'); @@ -18,7 +20,6 @@ class IPBanTest extends ShimmiePHPUnitTestCase { $this->assert_text("42.42.42.42"); $this->click("Remove"); // FIXME: remove which ban? :S $this->assert_no_text("42.42.42.42"); - */ $this->get_page('ip_ban/list?all=on'); // just test it doesn't crash for now diff --git a/ext/link_image/test.php b/ext/link_image/test.php index 09138953..ca8e6f9b 100644 --- a/ext/link_image/test.php +++ b/ext/link_image/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pie"); @@ -9,7 +9,8 @@ class LinkImageTest extends ShimmiePHPUnitTestCase { # in there, see if it takes us to the right page $this->get_page("post/view/$image_id"); - /* + $this->markTestIncomplete(); + // FIXME $matches = array(); preg_match("#value='(http://.*(/|%2F)post(/|%2F)view(/|%2F)[0-9]+)'#", $raw, $matches); @@ -18,7 +19,6 @@ class LinkImageTest extends ShimmiePHPUnitTestCase { $this->get($matches[1]); $this->assert_title("Image $image_id: pie"); } - */ } } diff --git a/ext/log_db/test.php b/ext/log_db/test.php index 3ab326fe..042a4640 100644 --- a/ext/log_db/test.php +++ b/ext/log_db/test.php @@ -1,6 +1,6 @@ log_in_as_admin(); $this->get_page("log/view"); $this->get_page("log/view?module=core-image"); diff --git a/ext/mass_tagger/main.php b/ext/mass_tagger/main.php index d65b8a46..a1806d21 100644 --- a/ext/mass_tagger/main.php +++ b/ext/mass_tagger/main.php @@ -16,62 +16,54 @@ class MassTagger extends Extension { public function onPostListBuilding(PostListBuildingEvent $event) { global $config, $page, $user; - if( !$user->is_admin() ) return; - - $this->theme->display_mass_tagger( $page, $event, $config ); + if($user->is_admin()) { + $this->theme->display_mass_tagger( $page, $event, $config ); + } } public function onPageRequest(PageRequestEvent $event) { - global $config, $page, $user; - if( !$event->page_matches("mass_tagger") ) return; - if( !$user->is_admin() ) return; - - if($event->get_arg(0) == "tag") $this->_apply_mass_tags( $config, $page, $user, $event ); - } - - private function _apply_mass_tags( $config, Page $page, $user, $event ) { - if( !isset($_POST['ids']) or !isset($_POST['tag']) ) return; - - $tag = $_POST['tag']; - - $tag_array = explode(" ",$tag); - $pos_tag_array = array(); - $neg_tag_array = array(); - foreach($tag_array as $new_tag) { - if (strpos($new_tag, '-') === 0) - $neg_tag_array[] = substr($new_tag,1); - else - $pos_tag_array[] = $new_tag; - } - - $ids = explode( ':', $_POST['ids'] ); - $ids = array_filter ( $ids , 'is_numeric' ); - - $images = array_map( "Image::by_id", $ids ); - - if(isset($_POST['setadd']) && - $_POST['setadd'] == 'set') - { - foreach($images as $image) { - $image->set_tags(Tag::explode($tag)); - } - } - else - { - foreach($images as $image) { - if (!empty($neg_tag_array)) { - $img_tags = array_merge($pos_tag_array, explode(" ",$image->get_tag_list())); - $img_tags = array_diff($img_tags, $neg_tag_array); - $image->set_tags(Tag::explode($img_tags)); - } + global $page, $user; + if($event->page_matches("mass_tagger/tag") && $user->is_admin()) { + if( !isset($_POST['ids']) or !isset($_POST['tag']) ) return; + + $tag = $_POST['tag']; + + $tag_array = explode(" ",$tag); + $pos_tag_array = array(); + $neg_tag_array = array(); + foreach($tag_array as $new_tag) { + if (strpos($new_tag, '-') === 0) + $neg_tag_array[] = substr($new_tag,1); else - $image->set_tags(Tag::explode($tag . " " . $image->get_tag_list())); + $pos_tag_array[] = $new_tag; } + + $ids = explode( ':', $_POST['ids'] ); + $ids = array_filter ( $ids , 'is_numeric' ); + + $images = array_map( "Image::by_id", $ids ); + + if(isset($_POST['setadd']) && $_POST['setadd'] == 'set') { + foreach($images as $image) { + $image->set_tags(Tag::explode($tag)); + } + } + else { + foreach($images as $image) { + if (!empty($neg_tag_array)) { + $img_tags = array_merge($pos_tag_array, explode(" ",$image->get_tag_list())); + $img_tags = array_diff($img_tags, $neg_tag_array); + $image->set_tags(Tag::explode($img_tags)); + } + else + $image->set_tags(Tag::explode($tag . " " . $image->get_tag_list())); + } + } + + $page->set_mode("redirect"); + if(!isset($_SERVER['HTTP_REFERER'])) $_SERVER['HTTP_REFERER'] = make_link(); + $page->set_redirect($_SERVER['HTTP_REFERER']); } - - $page->set_mode("redirect"); - if(!isset($_SERVER['HTTP_REFERER'])) $_SERVER['HTTP_REFERER'] = make_link(); - $page->set_redirect($_SERVER['HTTP_REFERER']); } } diff --git a/ext/not_a_tag/main.php b/ext/not_a_tag/main.php index 777d6dbe..29f4f502 100644 --- a/ext/not_a_tag/main.php +++ b/ext/not_a_tag/main.php @@ -28,7 +28,10 @@ class NotATag extends Extension { $this->scan($event->tags); } - private function scan(/*array*/ $tags_mixed) { + /** + * @param string[] $tags_mixed + */ + private function scan($tags_mixed) { global $database; $tags = array(); @@ -90,6 +93,11 @@ class NotATag extends Extension { } } + /** + * @param int $page + * @param int $size + * @return array + */ public function get_untags($page, $size=100) { global $database; diff --git a/ext/notes/main.php b/ext/notes/main.php index 6a66996e..15327cdf 100644 --- a/ext/notes/main.php +++ b/ext/notes/main.php @@ -276,8 +276,7 @@ class Notes extends Extension { (?, ?, ?, ?, now(), ?, ?, ?, ?, ?)", array(1, $imageID, $user_id, $_SERVER['REMOTE_ADDR'], $noteX1, $noteY1, $noteHeight, $noteWidth, $noteText)); - $result = $database->get_row("SELECT LAST_INSERT_ID() AS noteID", array()); - $noteID = $result["noteID"]; + $noteID = $database->get_last_insert_id('notes_id_seq'); log_info("notes", "Note added {$noteID} by {$user->name}"); @@ -304,9 +303,9 @@ class Notes extends Extension { (?, ?, now())", array($image_id, $user_id)); - $result = $database->get_row("SELECT LAST_INSERT_ID() AS requestID", array()); + $resultID = $database->get_last_insert_id('note_request_id_seq'); - log_info("notes", "Note requested {$result["requestID"]} by {$user->name}"); + log_info("notes", "Note requested {$requestID} by {$user->name}"); } diff --git a/ext/numeric_score/main.php b/ext/numeric_score/main.php index 5954afd5..9286e7e2 100644 --- a/ext/numeric_score/main.php +++ b/ext/numeric_score/main.php @@ -13,7 +13,12 @@ class NumericScoreSetEvent extends Event { var $image_id, $user, $score; - public function __construct(/*int*/ $image_id, User $user, /*int*/ $score) { + /** + * @param int $image_id + * @param User $user + * @param int $score + */ + public function __construct($image_id, User $user, $score) { $this->image_id = $image_id; $this->user = $user; $this->score = $score; @@ -102,15 +107,15 @@ class NumericScore extends Extension { if(!empty($_GET['day'])){ $D = (int) $_GET['day']; - if($D >= 1 && $D <= 31) $day = $D; + $day = clamp($D, 1, 31); } if(!empty($_GET['month'])){ $M = (int) $_GET['month']; - if($M >= 1 && $M <= 12) $month = $M; + $month = clamp($M, 1 ,12); } if(!empty($_GET['year'])){ $Y = (int) $_GET['year']; - if($Y >= 1970 && $Y < 2100) $year = $Y; + $year = clamp($Y, 1970, 2100); } $totaldate = $year."/".$month."/".$day; @@ -168,7 +173,10 @@ class NumericScore extends Extension { $this->delete_votes_by($event->id); } - public function delete_votes_by(/*int*/ $user_id) { + /** + * @param int $user_id + */ + public function delete_votes_by($user_id) { global $database; $image_ids = $database->get_col("SELECT image_id FROM numeric_score_votes WHERE user_id=?", array($user_id)); @@ -239,9 +247,9 @@ class NumericScore extends Extension { "images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=-1)", array("ns_user_id"=>$iid))); } - else if(preg_match("/^order[=|:](numeric_)?(score)[_]?(desc|asc)?$/i", $event->term, $matches)){ + else if(preg_match("/^order[=|:](?:numeric_)?(score)(?:_(desc|asc))?$/i", $event->term, $matches)){ $default_order_for_column = "DESC"; - $sort = isset($matches[3]) ? strtoupper($matches[3]) : $default_order_for_column; + $sort = isset($matches[2]) ? strtoupper($matches[2]) : $default_order_for_column; Image::$order_sql = "images.numeric_score $sort"; $event->add_querylet(new Querylet("1=1")); //small hack to avoid metatag being treated as normal tag } @@ -250,7 +258,7 @@ class NumericScore extends Extension { public function onTagTermParse(TagTermParseEvent $event) { $matches = array(); - if(preg_match("/^vote[=|:](up|down|remove)$/", $event->term, $matches)) { + if(preg_match("/^vote[=|:](up|down|remove)$/", $event->term, $matches) && $event->parse) { global $user; $score = ($matches[1] == "up" ? 1 : ($matches[1] == "down" ? -1 : 0)); if(!$user->is_anonymous()) { @@ -290,7 +298,7 @@ class NumericScore extends Extension { * @param int $user_id * @param int $score */ - private function add_vote(/*int*/ $image_id, /*int*/ $user_id, /*int*/ $score) { + private function add_vote($image_id, $user_id, $score) { global $database; $database->execute( "DELETE FROM numeric_score_votes WHERE image_id=:imageid AND user_id=:userid", diff --git a/ext/numeric_score/test.php b/ext/numeric_score/test.php index f2a9807b..f492acdb 100644 --- a/ext/numeric_score/test.php +++ b/ext/numeric_score/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); $this->get_page("post/view/$image_id"); diff --git a/ext/oekaki/test.php b/ext/oekaki/test.php index 8c59d263..1061595c 100644 --- a/ext/oekaki/test.php +++ b/ext/oekaki/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $this->get_page("oekaki/create"); } diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php index 674b3da8..69a98433 100644 --- a/ext/ouroboros_api/main.php +++ b/ext/ouroboros_api/main.php @@ -208,7 +208,7 @@ class _SafeOuroborosImage // meta $this->change = intval($img->id); //DaFug is this even supposed to do? ChangeID? // Should be JSON specific, just strip this when converting to XML - $this->created_at = array('n' => 123456789, 's' => $img->posted_timestamp, 'json_class' => 'Time'); + $this->created_at = array('n' => 123456789, 's' => strtotime($img->posted), 'json_class' => 'Time'); $this->id = intval($img->id); $this->parent_id = null; if (defined('ENABLED_EXTS')) { diff --git a/ext/pm/test.php b/ext/pm/test.php index 20aab33b..df065139 100644 --- a/ext/pm/test.php +++ b/ext/pm/test.php @@ -1,6 +1,6 @@ log_in_as_admin(); $this->get_page("user/test"); @@ -31,7 +31,7 @@ class PrivMsgTest extends ShimmiePHPUnitTestCase { $this->log_out(); } - function testAdminAccess() { + public function testAdminAccess() { $this->log_in_as_admin(); $this->get_page("user/test"); diff --git a/ext/pools/main.php b/ext/pools/main.php index aeb01996..4788de9e 100644 --- a/ext/pools/main.php +++ b/ext/pools/main.php @@ -252,19 +252,21 @@ class Pools extends Extension { * When displaying an image, optionally list all the pools that the * image is currently a member of on a side panel, as well as a link * to the Next image in the pool. + * + * @var DisplayingImageEvent $event */ public function onDisplayingImage(DisplayingImageEvent $event) { global $config; if($config->get_bool("poolsInfoOnViewImage")) { $imageID = $event->image->id; - $poolsIDs = $this->get_pool_id($imageID); + $poolsIDs = $this->get_pool_ids($imageID); $show_nav = $config->get_bool("poolsShowNavLinks", false); $navInfo = array(); foreach($poolsIDs as $poolID) { - $pool = $this->get_single_pool($poolID['pool_id']); + $pool = $this->get_single_pool($poolID); $navInfo[$pool['id']] = array(); $navInfo[$pool['id']]['info'] = $pool; @@ -374,16 +376,10 @@ class Pools extends Extension { private function list_pools(Page $page, /*int*/ $pageNumber) { global $config, $database; - if(is_null($pageNumber) || !is_numeric($pageNumber)) - $pageNumber = 0; - else if ($pageNumber <= 0) - $pageNumber = 0; - else - $pageNumber--; + $pageNumber = clamp($pageNumber, 1, null) - 1; $poolsPerPage = $config->get_int("poolsListsPerPage"); - $order_by = ""; $order = $page->get_cookie("ui-order-pool"); if($order == "created" || is_null($order)){ @@ -397,15 +393,14 @@ class Pools extends Extension { } $pools = $database->get_all(" - SELECT p.id, p.user_id, p.public, p.title, p.description, - p.posts, u.name as user_name - FROM pools AS p - INNER JOIN users AS u - ON p.user_id = u.id - $order_by - LIMIT :l OFFSET :o - ", array("l"=>$poolsPerPage, "o"=>$pageNumber * $poolsPerPage) - ); + SELECT p.id, p.user_id, p.public, p.title, p.description, + p.posts, u.name as user_name + FROM pools AS p + INNER JOIN users AS u + ON p.user_id = u.id + $order_by + LIMIT :l OFFSET :o + ", array("l"=>$poolsPerPage, "o"=>$pageNumber * $poolsPerPage)); $totalPages = ceil($database->get_one("SELECT COUNT(*) FROM pools") / $poolsPerPage); @@ -416,7 +411,7 @@ class Pools extends Extension { /** * HERE WE CREATE A NEW POOL * - * @return mixed + * @return int * @throws PoolCreationException */ private function add_pool() { @@ -438,12 +433,9 @@ class Pools extends Extension { VALUES (:uid, :public, :title, :desc, now())", array("uid"=>$user->id, "public"=>$public, "title"=>$_POST["title"], "desc"=>$_POST["description"])); - $result = array(); - $result['poolID'] = $database->get_last_insert_id('pools_id_seq'); - - log_info("pools", "Pool {$result["poolID"]} created by {$user->name}"); - - return $result["poolID"]; + $poolID = $database->get_last_insert_id('pools_id_seq'); + log_info("pools", "Pool {$poolID} created by {$user->name}"); + return $poolID; } /** @@ -482,11 +474,11 @@ class Pools extends Extension { /** * Get all of the pool IDs that an image is in, given an image ID. * @param int $imageID Integer ID for the image - * @return array + * @return int[] */ - private function get_pool_id(/*int*/ $imageID) { + private function get_pool_ids(/*int*/ $imageID) { global $database; - return $database->get_all("SELECT pool_id FROM pool_images WHERE image_id=:iid", array("iid"=>$imageID)); + return $database->get_col("SELECT pool_id FROM pool_images WHERE image_id=:iid", array("iid"=>$imageID)); } /** diff --git a/ext/pools/test.php b/ext/pools/test.php index 81e9d63c..0a3cc265 100644 --- a/ext/pools/test.php +++ b/ext/pools/test.php @@ -1,6 +1,6 @@ get_page('pool/list'); $this->assert_title("Pools"); diff --git a/ext/random_image/test.php b/ext/random_image/test.php index 0af4e906..ca4031c3 100644 --- a/ext/random_image/test.php +++ b/ext/random_image/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "test"); $this->log_out(); @@ -15,7 +15,7 @@ class RandomTest extends ShimmiePHPUnitTestCase { # FIXME: assert($raw == file(blah.jpg)) } - function testPostListBlock() { + public function testPostListBlock() { $this->log_in_as_admin(); $this->get_page("setup"); diff --git a/ext/rating/test.php b/ext/rating/test.php index 6a0ac913..87d5e1f3 100644 --- a/ext/rating/test.php +++ b/ext/rating/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); diff --git a/ext/regen_thumb/test.php b/ext/regen_thumb/test.php index 8e8f47b8..7deb66df 100644 --- a/ext/regen_thumb/test.php +++ b/ext/regen_thumb/test.php @@ -1,6 +1,6 @@ log_in_as_admin(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); $this->get_page("post/view/$image_id"); diff --git a/ext/relatationships/main.php b/ext/relatationships/main.php index 6cd1a7b0..8ecc6991 100644 --- a/ext/relatationships/main.php +++ b/ext/relatationships/main.php @@ -58,7 +58,7 @@ class Relationships extends Extension { public function onTagTermParse(TagTermParseEvent $event) { $matches = array(); - if(preg_match("/^parent[=|:]([0-9]+|none)$/", $event->term, $matches)) { + if(preg_match("/^parent[=|:]([0-9]+|none)$/", $event->term, $matches) && $event->parse) { $parentID = $matches[1]; if($parentID == "none" || $parentID == "0"){ @@ -67,7 +67,7 @@ class Relationships extends Extension { $this->set_parent($event->id, $parentID); } } - else if(preg_match("/^child[=|:]([0-9]+)$/", $event->term, $matches)) { + else if(preg_match("/^child[=|:]([0-9]+)$/", $event->term, $matches) && $event->parse) { $childID = $matches[1]; $this->set_child($event->id, $childID); diff --git a/ext/report_image/main.php b/ext/report_image/main.php index ee6a84ae..c64b1e63 100644 --- a/ext/report_image/main.php +++ b/ext/report_image/main.php @@ -141,7 +141,10 @@ class ReportImage extends Extension { $this->delete_reports_by($event->id); } - public function delete_reports_by(/*int*/ $user_id) { + /** + * @param int $user_id + */ + public function delete_reports_by($user_id) { global $database; $database->execute("DELETE FROM image_reports WHERE reporter_id=?", array($user_id)); $database->cache->delete("image-report-count"); @@ -165,7 +168,7 @@ class ReportImage extends Extension { /** * @param Image $image - * @return array + * @return string[] */ public function get_reporters(Image $image) { global $database; @@ -206,7 +209,7 @@ class ReportImage extends Extension { } /** - * @return mixed + * @return int */ public function count_reported_images() { global $database; @@ -220,13 +223,3 @@ class ReportImage extends Extension { return $count; } } -// ===== Changelog ===== -// * Version 0.3a / 0.3a_rc - 11/06/07 - I can no longer use the same theme.php file for both SVN and RCx. Sorry. -// * Same deal with theme.php as it is with main.php -// * Version 0.3 / 0.3_rc - 11/06/07 - Added the option to display thumbnails, moved the reported image list to it's -// own page, and checked to make sure the user is an admin before letting them delete / view reported images. -// * Version 0.2c_rc2 - 10/27/07 - Now (really!) supports Shimmie2 RC2! -// * Version 0.2b - 10/27/07 - Now supports Shimmie2 RC2! -// * Version 0.2a - 10/24/07 - Fixed some SQL issues. I will make sure to test before commiting :) -// * Version 0.2 - 10/24/07 - First public release. - diff --git a/ext/report_image/test.php b/ext/report_image/test.php index 8edbf66f..3af10913 100644 --- a/ext/report_image/test.php +++ b/ext/report_image/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); $this->get_page("post/view/$image_id"); diff --git a/ext/res_limit/test.php b/ext/res_limit/test.php index 0ed418c9..50260a8b 100644 --- a/ext/res_limit/test.php +++ b/ext/res_limit/test.php @@ -1,6 +1,6 @@ set_int("upload_min_height", 0); $config->set_int("upload_min_width", 0); @@ -16,7 +16,7 @@ class ResLimitTest extends ShimmiePHPUnitTestCase { $this->assert_no_text("ratio"); } - function testResLimitSmall() { + public function testResLimitSmall() { global $config; $config->set_int("upload_min_height", 900); $config->set_int("upload_min_width", 900); @@ -33,7 +33,7 @@ class ResLimitTest extends ShimmiePHPUnitTestCase { } } - function testResLimitLarge() { + public function testResLimitLarge() { global $config; $config->set_int("upload_min_height", 0); $config->set_int("upload_min_width", 0); @@ -50,7 +50,7 @@ class ResLimitTest extends ShimmiePHPUnitTestCase { } - function testResLimitRatio() { + public function testResLimitRatio() { global $config; $config->set_int("upload_min_height", -1); $config->set_int("upload_min_width", -1); diff --git a/ext/resize/main.php b/ext/resize/main.php index acfa14b0..02d7bf96 100644 --- a/ext/resize/main.php +++ b/ext/resize/main.php @@ -75,12 +75,12 @@ class ResizeImage extends Extension { $isanigif = 0; if($image_obj->ext == "gif"){ $image_filename = warehouse_path("images", $image_obj->hash); - if(!($fh = @fopen($image_filename, 'rb'))){ //check if gif is animated (via http://www.php.net/manual/en/function.imagecreatefromgif.php#104473) - return false; - } - while(!feof($fh) && $isanigif < 2) { - $chunk = fread($fh, 1024 * 100); - $isanigif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches); + if(($fh = @fopen($image_filename, 'rb'))) { + //check if gif is animated (via http://www.php.net/manual/en/function.imagecreatefromgif.php#104473) + while(!feof($fh) && $isanigif < 2) { + $chunk = fread($fh, 1024 * 100); + $isanigif += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches); + } } } if($isanigif == 0){ @@ -180,51 +180,15 @@ class ResizeImage extends Extension { if (($image_obj->width != $info[0] ) || ($image_obj->height != $info[1])) { throw new ImageResizeException("The current image size does not match what is set in the database! - Aborting Resize."); } - - /* - Check Memory usage limits - - Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024); - New check: memory_use = width * height * (bits per channel) * channels * 2.5 - - It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4) - We need to consider the size that we are GOING TO instead. - - The factor of 2.5 is simply a rough guideline. - http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize - */ - - if (isset($info['bits']) && isset($info['channels'])) - { - $memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024; - } else { - // - // If we don't have bits and channel info from the image then assume default values - // of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color - // - $memory_use = ($info[0] * $info[1] * 1 * 4 * 2.5) / 1024; - } - + + $memory_use = $this->calc_memory_use($info); $memory_limit = get_memory_limit(); - if ($memory_use > $memory_limit) { throw new ImageResizeException("The image is too large to resize given the memory limits. ($memory_use > $memory_limit)"); } - - /* Calculate the new size of the image */ - if ( $height > 0 && $width > 0 ) { - $new_height = $height; - $new_width = $width; - } else { - // Scale the new image - if ($width == 0) $factor = $height/$image_obj->height; - elseif ($height == 0) $factor = $width/$image_obj->width; - else $factor = min( $width / $image_obj->width, $height / $image_obj->height ); - $new_width = round( $image_obj->width * $factor ); - $new_height = round( $image_obj->height * $factor ); - } - + list($new_height, $new_width) = $this->calc_new_size($image_obj, $width, $height); + /* Attempt to load the image */ switch ( $info[2] ) { case IMAGETYPE_GIF: $image = imagecreatefromgif($image_filename); break; @@ -303,19 +267,65 @@ class ResizeImage extends Extension { send_event(new ThumbnailGenerationEvent($new_hash, $filetype)); /* Update the database */ - $database->Execute( - "UPDATE images SET - filename = :filename, filesize = :filesize, hash = :hash, width = :width, height = :height - WHERE - id = :id - ", - array( - "filename"=>$new_filename, "filesize"=>$new_size, "hash"=>$new_hash, - "width"=>$new_width, "height"=>$new_height, "id"=>$image_obj->id - ) - ); + $database->Execute(" + UPDATE images SET filename = :filename, filesize = :filesize, hash = :hash, width = :width, height = :height + WHERE id = :id + ", array( + "filename"=>$new_filename, "filesize"=>$new_size, "hash"=>$new_hash, + "width"=>$new_width, "height"=>$new_height, "id"=>$image_obj->id + )); log_info("resize", "Resized Image #{$image_obj->id} - New hash: {$new_hash}"); } + + /** + * Check Memory usage limits + * + * Old check: $memory_use = (filesize($image_filename)*2) + ($width*$height*4) + (4*1024*1024); + * New check: memory_use = width * height * (bits per channel) * channels * 2.5 + * + * It didn't make sense to compute the memory usage based on the NEW size for the image. ($width*$height*4) + * We need to consider the size that we are GOING TO instead. + * + * The factor of 2.5 is simply a rough guideline. + * http://stackoverflow.com/questions/527532/reasonable-php-memory-limit-for-image-resize + * + * @param $info + * @return int + */ + private function calc_memory_use($info) { + if (isset($info['bits']) && isset($info['channels'])) { + return $memory_use = ($info[0] * $info[1] * ($info['bits'] / 8) * $info['channels'] * 2.5) / 1024; + } + else { + // If we don't have bits and channel info from the image then assume default values + // of 8 bits per color and 4 channels (R,G,B,A) -- ie: regular 24-bit color + return $memory_use = ($info[0] * $info[1] * 1 * 4 * 2.5) / 1024; + } + } + + /** + * @param Image $image_obj + * @param $width + * @param $height + * @return int[] + */ + private function calc_new_size(Image $image_obj, $width, $height) { + /* Calculate the new size of the image */ + if ($height > 0 && $width > 0) { + $new_height = $height; + $new_width = $width; + return array($new_height, $new_width); + } else { + // Scale the new image + if ($width == 0) $factor = $height / $image_obj->height; + elseif ($height == 0) $factor = $width / $image_obj->width; + else $factor = min($width / $image_obj->width, $height / $image_obj->height); + + $new_width = round($image_obj->width * $factor); + $new_height = round($image_obj->height * $factor); + return array($new_height, $new_width); + } + } } diff --git a/ext/rss_comments/main.php b/ext/rss_comments/main.php index 7b3c17db..91b6a004 100644 --- a/ext/rss_comments/main.php +++ b/ext/rss_comments/main.php @@ -25,16 +25,16 @@ class RSS_Comments extends Extension { $page->set_type("application/rss+xml"); $comments = $database->get_all(" - SELECT + SELECT users.id as user_id, users.name as user_name, comments.comment as comment, comments.id as comment_id, comments.image_id as image_id, comments.owner_ip as poster_ip, - UNIX_TIMESTAMP(posted) AS posted_timestamp - FROM comments - LEFT JOIN users ON comments.owner_id=users.id - ORDER BY comments.id DESC - LIMIT 10 - "); + comments.posted as posted + FROM comments + LEFT JOIN users ON comments.owner_id=users.id + ORDER BY comments.id DESC + LIMIT 10 + "); $data = ""; foreach($comments as $comment) { @@ -42,7 +42,7 @@ class RSS_Comments extends Extension { $comment_id = $comment['comment_id']; $link = make_http(make_link("post/view/$image_id")); $owner = html_escape($comment['user_name']); - $posted = date(DATE_RSS, $comment['posted_timestamp']); + $posted = date(DATE_RSS, strtotime($comment['posted'])); $comment = html_escape($comment['comment']); $content = html_escape("$owner: $comment"); diff --git a/ext/rss_images/main.php b/ext/rss_images/main.php index 4fb500af..5edffddf 100644 --- a/ext/rss_images/main.php +++ b/ext/rss_images/main.php @@ -98,7 +98,7 @@ class RSS_Images extends Extension { $owner = $image->get_owner(); $thumb_url = $image->get_thumb_link(); $image_url = $image->get_image_link(); - $posted = date(DATE_RSS, $image->posted_timestamp); + $posted = date(DATE_RSS, strtotime($image->posted)); $content = html_escape( "

    " . $this->theme->build_thumb_html($image) . "

    " . "

    Uploaded by " . html_escape($owner->name) . "

    " diff --git a/ext/setup/test.php b/ext/setup/test.php index 5af8fca9..2989472d 100644 --- a/ext/setup/test.php +++ b/ext/setup/test.php @@ -1,6 +1,6 @@ assert_no_content("\n"); } - function testAuthAnon() { + public function testAuthAnon() { $this->get_page('setup'); $this->assert_response(403); $this->assert_title("Permission Denied"); } - function testAuthUser() { + public function testAuthUser() { $this->log_in_as_user(); $this->get_page('setup'); $this->assert_response(403); $this->assert_title("Permission Denied"); } - function testAuthAdmin() { + public function testAuthAdmin() { $this->log_in_as_admin(); $this->get_page('setup'); $this->assert_title("Shimmie Setup"); $this->assert_text("General"); } - function testAdvanced() { + public function testAdvanced() { $this->log_in_as_admin(); $this->get_page('setup/advanced'); $this->assert_title("Shimmie Setup"); diff --git a/ext/shimmie_api/main.php b/ext/shimmie_api/main.php index 17a77c7d..7fb13d47 100644 --- a/ext/shimmie_api/main.php +++ b/ext/shimmie_api/main.php @@ -19,8 +19,6 @@ class _SafeImage { -#{"id":"2","height":"768","width":"1024","hash":"71cdfaabbcdad3f777e0b60418532e94","filesize":"439561","filename":"HeilAmu.png","ext":"png","owner_ip":"0.0.0.0","posted":"0000-00-00 00:00:00","source":null,"locked":"N","owner_id":"0","rating":"u","numeric_score":"0","text_score":"0","notes":"0","favorites":"0","posted_timestamp":-62169955200,"tag_array":["cat","kunimitsu"]} - public $id; public $height; public $width; @@ -39,7 +37,7 @@ class _SafeImage { $this->hash = $img->hash; $this->filesize = $img->filesize; $this->ext = $img->ext; - $this->posted = $img->posted_timestamp; + $this->posted = strtotime($img->posted); $this->source = $img->source; $this->owner_id = $img->owner_id; $this->tags = $img->get_tag_array(); @@ -48,52 +46,30 @@ class _SafeImage { class ShimmieApi extends Extension { public function onPageRequest(PageRequestEvent $event) { - global $database, $page, $user; + global $page, $user; if($event->page_matches("api/shimmie")) { $page->set_mode("data"); $page->set_type("text/plain"); - if(!$event->page_matches("api/shimmie/get_tags") && !$event->page_matches("api/shimmie/get_image") && !$event->page_matches("api/shimmie/find_images") && !$event->page_matches("api/shimmie/get_user")){ - $page->set_mode("redirect"); - $page->set_redirect(make_link("ext_doc/shimmie_api")); - } if($event->page_matches("api/shimmie/get_tags")){ - $arg = $event->get_arg(0); - - if(!empty($arg)){ - $all = $database->get_all( - "SELECT tag FROM tags WHERE tag LIKE ?", - array($arg."%")); - } - elseif(isset($_GET['tag'])){ - $all = $database->get_all( - "SELECT tag FROM tags WHERE tag LIKE ?", - array($_GET['tag']."%")); - } - else { - $all = $database->get_all("SELECT tag FROM tags"); - } - $res = array(); - foreach($all as $row) {$res[] = $row["tag"];} + $tag = $event->get_arg(0); + if(empty($tag) && isset($_GET['tag'])) $tag = $_GET['tag']; + $res = $this->api_get_tags($tag); $page->set_data(json_encode($res)); } - if($event->page_matches("api/shimmie/get_image")) { + elseif($event->page_matches("api/shimmie/get_image")) { $arg = $event->get_arg(0); - if(!empty($arg)){ - $image = Image::by_id(int_escape($event->get_arg(0))); - } - elseif(isset($_GET['id'])){ - $image = Image::by_id(int_escape($_GET['id'])); - } + if(empty($arg) && isset($_GET['id'])) $arg = $_GET['id']; + $image = Image::by_id(int_escape($arg)); // FIXME: handle null image $image->get_tag_array(); // tag data isn't loaded into the object until necessary $safe_image = new _SafeImage($image); $page->set_data(json_encode($safe_image)); } - if($event->page_matches("api/shimmie/find_images")) { + elseif($event->page_matches("api/shimmie/find_images")) { $search_terms = $event->get_search_terms(); $page_number = $event->get_page_number(); $page_size = $event->get_page_size(); @@ -106,7 +82,7 @@ class ShimmieApi extends Extension { $page->set_data(json_encode($safe_images)); } - if($event->page_matches("api/shimmie/get_user")) { + elseif($event->page_matches("api/shimmie/get_user")) { $query = $user->id; $type = "id"; if($event->count_args() == 1) { @@ -121,41 +97,77 @@ class ShimmieApi extends Extension { $type = "name"; } - $all = $database->get_row( - "SELECT id,name,joindate,class FROM users WHERE $type=?", - array($query)); - - if(!empty($all)){ - //FIXME?: For some weird reason, get_all seems to return twice. Unsetting second value to make things look nice.. - // - it returns data as eg array(0=>1234, 'id'=>1234, 1=>'bob', 'name'=>bob, ...); - for($i=0; $i<4; $i++) unset($all[$i]); - $all['uploadcount'] = Image::count_images(array("user_id=".$all['id'])); - $all['commentcount'] = $database->get_one( - "SELECT COUNT(*) AS count FROM comments WHERE owner_id=:owner_id", - array("owner_id"=>$all['id'])); - - if(isset($_GET['recent'])){ - $recent = $database->get_all( - "SELECT * FROM images WHERE owner_id=? ORDER BY id DESC LIMIT 0, 5", - array($all['id'])); - - $i = 0; - foreach($recent as $all['recentposts'][$i]){ - unset($all['recentposts'][$i]['owner_id']); //We already know the owners id.. - unset($all['recentposts'][$i]['owner_ip']); - - for($x=0; $x<14; $x++) unset($all['recentposts'][$i][$x]); - if(empty($all['recentposts'][$i]['author'])) unset($all['recentposts'][$i]['author']); - if($all['recentposts'][$i]['notes'] > 0) $all['recentposts'][$i]['has_notes'] = "Y"; - else $all['recentposts'][$i]['has_notes'] = "N"; - unset($all['recentposts'][$i]['notes']); - $i += 1; - } - } - } + $all = $this->api_get_user($type, $query); $page->set_data(json_encode($all)); } + + else { + $page->set_mode("redirect"); + $page->set_redirect(make_link("ext_doc/shimmie_api")); + } + } } + + /** + * @param string $arg + * @return string[] + */ + private function api_get_tags($arg) { + global $database; + if (!empty($arg)) { + $all = $database->get_all("SELECT tag FROM tags WHERE tag LIKE ?", array($arg . "%")); + } else { + $all = $database->get_all("SELECT tag FROM tags"); + } + $res = array(); + foreach ($all as $row) { + $res[] = $row["tag"]; + } + return $res; + } + + /** + * @param $type + * @param $query + * @return array + */ + private function api_get_user($type, $query) { + global $database; + $all = $database->get_row( + "SELECT id, name, joindate, class FROM users WHERE $type=?", + array($query) + ); + + if (!empty($all)) { + //FIXME?: For some weird reason, get_all seems to return twice. Unsetting second value to make things look nice.. + // - it returns data as eg array(0=>1234, 'id'=>1234, 1=>'bob', 'name'=>bob, ...); + for ($i = 0; $i < 4; $i++) unset($all[$i]); + $all['uploadcount'] = Image::count_images(array("user_id=" . $all['id'])); + $all['commentcount'] = $database->get_one( + "SELECT COUNT(*) AS count FROM comments WHERE owner_id=:owner_id", + array("owner_id" => $all['id'])); + + if (isset($_GET['recent'])) { + $recent = $database->get_all( + "SELECT * FROM images WHERE owner_id=? ORDER BY id DESC LIMIT 0, 5", + array($all['id'])); + + $i = 0; + foreach ($recent as $all['recentposts'][$i]) { + unset($all['recentposts'][$i]['owner_id']); //We already know the owners id.. + unset($all['recentposts'][$i]['owner_ip']); + + for ($x = 0; $x < 14; $x++) unset($all['recentposts'][$i][$x]); + if (empty($all['recentposts'][$i]['author'])) unset($all['recentposts'][$i]['author']); + if ($all['recentposts'][$i]['notes'] > 0) $all['recentposts'][$i]['has_notes'] = "Y"; + else $all['recentposts'][$i]['has_notes'] = "N"; + unset($all['recentposts'][$i]['notes']); + $i += 1; + } + } + } + return $all; + } } diff --git a/ext/shimmie_api/test.php b/ext/shimmie_api/test.php index 4d4e210b..99327dba 100644 --- a/ext/shimmie_api/test.php +++ b/ext/shimmie_api/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); diff --git a/ext/site_description/test.php b/ext/site_description/test.php index 4309a3ac..0ebd3d68 100644 --- a/ext/site_description/test.php +++ b/ext/site_description/test.php @@ -1,6 +1,6 @@ set_string("site_description", "A Shimmie testbed"); $this->get_page("post/list"); @@ -10,7 +10,7 @@ class SiteDescriptionTest extends ShimmiePHPUnitTestCase { ); } - function testSiteKeywords() { + public function testSiteKeywords() { global $config, $page; $config->set_string("site_keywords", "foo,bar,baz"); $this->get_page("post/list"); diff --git a/ext/sitemap/main.php b/ext/sitemap/main.php index 904c36fc..9c34890a 100644 --- a/ext/sitemap/main.php +++ b/ext/sitemap/main.php @@ -56,7 +56,7 @@ class XMLSitemap extends Extension $latestimages_urllist[$arrayid] = "post/view/$image->id"; } - $this->add_sitemap_queue($latestimages_urllist, "monthly", "0.8", date("Y-m-d", $image->posted_timestamp)); + $this->add_sitemap_queue($latestimages_urllist, "monthly", "0.8", date("Y-m-d", strtotime($image->posted))); /* --- Display page --- */ // when sitemap is ok, display it from the file @@ -88,7 +88,7 @@ class XMLSitemap extends Extension // create url from image id's $latestimages_urllist[$arrayid] = "post/view/$image->id"; } - $this->add_sitemap_queue($latestimages_urllist, "monthly", "0.8", date("Y-m-d", $image->posted_timestamp)); + $this->add_sitemap_queue($latestimages_urllist, "monthly", "0.8", date("Y-m-d", strtotime($image->posted))); /* --- Add other tags --- */ $other_tags = $database->get_all("SELECT tag, count FROM tags ORDER BY `count` DESC LIMIT 21,10000000"); @@ -106,7 +106,7 @@ class XMLSitemap extends Extension // create url from image id's $otherimages[$arrayid] = "post/view/$image->id"; } - $this->add_sitemap_queue($otherimages, "monthly", "0.6", date("Y-m-d", $image->posted_timestamp)); + $this->add_sitemap_queue($otherimages, "monthly", "0.6", date("Y-m-d", strtotime($image->posted))); /* --- Display page --- */ diff --git a/ext/sitemap/test.php b/ext/sitemap/test.php index f98ec4ae..a2756249 100644 --- a/ext/sitemap/test.php +++ b/ext/sitemap/test.php @@ -1,6 +1,6 @@ get_page('sitemap.xml'); diff --git a/ext/tag_edit/main.php b/ext/tag_edit/main.php index 1dfa025e..12be5ab7 100644 --- a/ext/tag_edit/main.php +++ b/ext/tag_edit/main.php @@ -95,11 +95,38 @@ class SourceSetEvent extends Event { class TagSetEvent extends Event { /** @var \Image */ public $image; - var $tags; + public $tags; + public $metatags; public function __construct(Image $image, $tags) { - $this->image = $image; - $this->tags = Tag::explode($tags); + $this->image = $image; + + $this->tags = array(); + $this->metatags = array(); + + //tags need to be sanitised, alias checked & have metatags removed before being passed to onTagSet + $tag_array = Tag::explode($tags); + $tag_array = array_map(array('Tag', 'sanitise'), $tag_array); + $tag_array = Tag::resolve_aliases($tag_array); + + foreach($tag_array as $tag) { + if((strpos($tag, ':') === FALSE) && (strpos($tag, '=') === FALSE)) { + //Tag doesn't contain : or =, meaning it can't possibly be a metatag. + //This should help speed wise, as it avoids running every single tag through a bunch of preg_match instead. + array_push($this->tags, $tag); + continue; + } + + $ttpe = new TagTermParseEvent($tag, $this->image->id, FALSE); //Only check for metatags, don't parse. Parsing is done after set_tags. + send_event($ttpe); + + //seperate tags from metatags + if(!$ttpe->is_metatag()) { + array_push($this->tags, $tag); + }else{ + array_push($this->metatags, $tag); + } + } } } @@ -131,14 +158,17 @@ class LockSetEvent extends Event { * Signal that a tag term needs parsing */ class TagTermParseEvent extends Event { - var $term = null; - var $id = null; + public $term = NULL; //tag + public $id = NULL; //image_id /** @var bool */ - public $metatag = false; + public $metatag = FALSE; + /** @var bool */ + public $parse = TRUE; //marks the tag to be parsed, and not just checked if valid metatag - public function __construct($term, $id) { - $this->term = $term; - $this->id = $id; + public function __construct($term, $id, $parse) { + $this->term = $term; + $this->id = $id; + $this->parse = $parse; } /** @@ -215,6 +245,7 @@ class TagEdit extends Extension { if($user->can("edit_image_tag") && (!$event->image->is_locked() || $user->can("edit_image_lock"))) { $event->image->set_tags($event->tags); } + $event->image->parse_metatags($event->metatags, $event->image->id); } public function onSourceSet(SourceSetEvent $event) { @@ -257,7 +288,7 @@ class TagEdit extends Extension { public function onTagTermParse(TagTermParseEvent $event) { $matches = array(); - if(preg_match("/^source[=|:](.*)$/i", $event->term, $matches)) { + if(preg_match("/^source[=|:](.*)$/i", $event->term, $matches) && $event->parse) { $source = ($matches[1] !== "none" ? $matches[1] : null); send_event(new SourceSetEvent(Image::by_id($event->id), $source)); } diff --git a/ext/tag_edit/test.php b/ext/tag_edit/test.php index fcc2ff09..498d29f4 100644 --- a/ext/tag_edit/test.php +++ b/ext/tag_edit/test.php @@ -1,6 +1,6 @@ log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); $this->get_page("post/view/$image_id"); @@ -21,7 +21,7 @@ class TagEditTest extends ShimmiePHPUnitTestCase { $this->log_out(); } - function testSourceEdit() { + public function testSourceEdit() { $this->log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); $this->get_page("post/view/$image_id"); @@ -48,10 +48,12 @@ class TagEditTest extends ShimmiePHPUnitTestCase { $this->log_out(); } -/* - * FIXME: Mass Tagger seems to be broken, and this test case always fails. - * - function testMassEdit() { + /* + * FIXME: Mass Tagger seems to be broken, and this test case always fails. + */ + public function testMassEdit() { + $this->markTestIncomplete(); + $this->log_in_as_admin(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); @@ -71,6 +73,5 @@ class TagEditTest extends ShimmiePHPUnitTestCase { $this->log_out(); } -*/ } diff --git a/ext/tag_editcloud/main.php b/ext/tag_editcloud/main.php index 9efb6b99..aca32af7 100644 --- a/ext/tag_editcloud/main.php +++ b/ext/tag_editcloud/main.php @@ -22,7 +22,7 @@ class TagEditCloud extends Extension { } } } - + public function onInitExt(InitExtEvent $event) { global $config; $config->set_default_bool("tageditcloud_disable", false); @@ -74,16 +74,14 @@ class TagEditCloud extends Extension { $ignore_tags = Tag::explode($config->get_string("tageditcloud_ignoretags")); - if(class_exists("TagCategories")){ + if(ext_is_live("TagCategories")) { $categories = $database->get_all("SELECT category, color FROM image_tag_categories"); $cat_color = array(); - foreach($categories as $row){ + foreach($categories as $row) { $cat_color[$row['category']] = $row['color']; } } - $tag_data = null; - switch($sort_method) { case 'a': case 'p': @@ -99,31 +97,29 @@ class TagEditCloud extends Extension { break; case 'r': $relevant_tags = array_diff($image->get_tag_array(),$ignore_tags); - if(count($relevant_tags) > 0) { - $relevant_tags = implode(",",array_map(array($database,"escape"),$relevant_tags)); - $tag_data = $database->get_all(" - SELECT t2.tag AS tag, COUNT(image_id) AS count, FLOOR(LN(LN(COUNT(image_id) - :tag_min1 + 1)+1)*150)/200 AS scaled - FROM image_tags it1 - JOIN image_tags it2 USING(image_id) - JOIN tags t1 ON it1.tag_id = t1.id - JOIN tags t2 ON it2.tag_id = t2.id - WHERE t1.count >= :tag_min2 AND t1.tag IN($relevant_tags) - GROUP BY t2.tag - ORDER BY count DESC - LIMIT :limit", - array("tag_min1" => $tags_min, "tag_min2" => $tags_min, "limit" => $max_count)); + if(count($relevant_tags) == 0) { + return null; } + $relevant_tags = implode(",",array_map(array($database,"escape"),$relevant_tags)); + $tag_data = $database->get_all(" + SELECT t2.tag AS tag, COUNT(image_id) AS count, FLOOR(LN(LN(COUNT(image_id) - :tag_min1 + 1)+1)*150)/200 AS scaled + FROM image_tags it1 + JOIN image_tags it2 USING(image_id) + JOIN tags t1 ON it1.tag_id = t1.id + JOIN tags t2 ON it2.tag_id = t2.id + WHERE t1.count >= :tag_min2 AND t1.tag IN($relevant_tags) + GROUP BY t2.tag + ORDER BY count DESC + LIMIT :limit", + array("tag_min1" => $tags_min, "tag_min2" => $tags_min, "limit" => $max_count)); break; } - if(is_null($tag_data)) { - return null; - } - + $counter = 1; foreach($tag_data as $row) { $full_tag = $row['tag']; - if(class_exists("TagCategories")){ + if(ext_is_live("TagCategories")){ $tc = explode(':',$row['tag']); if(isset($tc[1]) && isset($cat_color[$tc[0]])){ $h_tag = html_escape($tc[1]); diff --git a/ext/tag_history/main.php b/ext/tag_history/main.php index fe4b807a..eefe8cd0 100644 --- a/ext/tag_history/main.php +++ b/ext/tag_history/main.php @@ -329,7 +329,8 @@ class Tag_History extends Extension { $image = Image::by_id($stored_image_id); if ( ! $image instanceof Image) { - throw new ImageDoesNotExist("Error: cannot find any image with the ID = ". $stored_image_id); + continue; + //throw new ImageDoesNotExist("Error: cannot find any image with the ID = ". $stored_image_id); } log_debug("tag_history", 'Reverting tags of Image #'.$stored_image_id.' to ['.$stored_tags.']'); diff --git a/ext/tag_history/test.php b/ext/tag_history/test.php index a8730917..4914be06 100644 --- a/ext/tag_history/test.php +++ b/ext/tag_history/test.php @@ -1,12 +1,13 @@ log_in_as_admin(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx"); $this->get_page("post/view/$image_id"); $this->assert_title("Image $image_id: pbx"); - /* + $this->markTestIncomplete(); + // FIXME $this->set_field("tag_edit__tags", "new"); $this->click("Set"); @@ -15,7 +16,6 @@ class TagHistoryTest extends ShimmiePHPUnitTestCase { $this->assert_text("new (Set by demo"); $this->click("Revert To"); $this->assert_title("Image $image_id: pbx"); - */ $this->get_page("tag_history/all/1"); $this->assert_title("Global Tag History"); diff --git a/ext/tag_list/test.php b/ext/tag_list/test.php index 4910f304..3d9b464f 100644 --- a/ext/tag_list/test.php +++ b/ext/tag_list/test.php @@ -2,7 +2,7 @@ class TagListTest extends ShimmiePHPUnitTestCase { var $pages = array("map", "alphabetic", "popularity", "categories"); - function testTagList() { + public function testTagList() { $this->get_page('tags/map'); $this->assert_title('Tag List'); @@ -18,7 +18,7 @@ class TagListTest extends ShimmiePHPUnitTestCase { # FIXME: test that these show the right stuff } - function testMinCount() { + public function testMinCount() { foreach($this->pages as $page) { $this->get_page("tags/$page?mincount=999999"); $this->assert_title("Tag List"); diff --git a/ext/tips/test.php b/ext/tips/test.php index bc5015a9..e95fb5d8 100644 --- a/ext/tips/test.php +++ b/ext/tips/test.php @@ -1,6 +1,6 @@ log_in_as_admin(); @@ -15,7 +15,7 @@ class TipsTest extends ShimmiePHPUnitTestCase { $this->log_out(); } - function testImageless() { + public function testImageless() { $this->log_in_as_admin(); $this->get_page("tips/list"); @@ -37,7 +37,7 @@ class TipsTest extends ShimmiePHPUnitTestCase { $this->log_out(); } - function testImaged() { + public function testImaged() { $this->log_in_as_admin(); $this->get_page("tips/list"); @@ -59,7 +59,7 @@ class TipsTest extends ShimmiePHPUnitTestCase { $this->log_out(); } - function testDisabled() { + public function testDisabled() { $this->log_in_as_admin(); $this->get_page("tips/list"); diff --git a/ext/upload/main.php b/ext/upload/main.php index 27862703..9c7b3fa0 100644 --- a/ext/upload/main.php +++ b/ext/upload/main.php @@ -234,8 +234,8 @@ class Upload extends Extension { } /** - * @param string|int $id - * @return array + * @param int $id + * @return string[] */ private function tags_for_upload_slot($id) { if(isset($_POST["tags$id"])) { diff --git a/ext/upload/test.php b/ext/upload/test.php index 70f32368..b1044202 100644 --- a/ext/upload/test.php +++ b/ext/upload/test.php @@ -1,18 +1,18 @@ log_in_as_user(); $this->get_page("upload"); $this->assert_title("Upload"); } - function testUpload() { + public function testUpload() { $this->log_in_as_user(); $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); } - function testRejectDupe() { + public function testRejectDupe() { $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); try { @@ -23,7 +23,7 @@ class UploadTest extends ShimmiePHPUnitTestCase { } } - function testRejectUnknownFiletype() { + public function testRejectUnknownFiletype() { try { $this->post_image("index.php", "test"); } @@ -32,16 +32,16 @@ class UploadTest extends ShimmiePHPUnitTestCase { } } - function testRejectHuge() { - /* + public function testRejectHuge() { + $this->markTestIncomplete(); + // FIXME: huge.dat is rejected for other reasons; manual testing shows that this works file_put_contents("huge.dat", file_get_contents("tests/pbx_screenshot.jpg") . str_repeat("U", 1024*1024*3)); - $image_id_4 = $this->post_image("index.php", "test"); + $this->post_image("index.php", "test"); $this->assert_response(200); $this->assert_title("Upload Status"); $this->assert_text("File too large"); unlink("huge.dat"); - */ } } diff --git a/ext/user/main.php b/ext/user/main.php index 53981d60..32d31eaa 100644 --- a/ext/user/main.php +++ b/ext/user/main.php @@ -80,6 +80,9 @@ class UserCreationException extends SCoreException {} class NullUserException extends SCoreException {} class UserPage extends Extension { + /** @var UserPageTheme $theme */ + var $theme; + public function onInitExt(InitExtEvent $event) { global $config; $config->set_default_bool("login_signup_enabled", true); @@ -94,64 +97,22 @@ class UserPage extends Extension { public function onPageRequest(PageRequestEvent $event) { global $config, $page, $user; - // user info is shown on all pages - if($user->is_anonymous()) { - $this->theme->display_login_block($page); - } - else { - $ubbe = new UserBlockBuildingEvent(); - send_event($ubbe); - ksort($ubbe->parts); - $this->theme->display_user_block($page, $user, $ubbe->parts); - } + $this->show_user_info(); if($event->page_matches("user_admin")) { if($event->get_arg(0) == "login") { if(isset($_POST['user']) && isset($_POST['pass'])) { - $this->login($page); + $this->page_login($_POST['user'], $_POST['pass']); } else { $this->theme->display_login_page($page); } } else if($event->get_arg(0) == "recover") { - $user = User::by_name($_POST['username']); - if(is_null($user)) { - $this->theme->display_error(404, "Error", "There's no user with that name"); - } - else if(is_null($user->email)) { - $this->theme->display_error(400, "Error", "That user has no registered email address"); - } - else { - // send email - } + $this->page_recover($_POST['username']); } else if($event->get_arg(0) == "create") { - if(!$config->get_bool("login_signup_enabled")) { - $this->theme->display_signups_disabled($page); - } - else if(!isset($_POST['name'])) { - $this->theme->display_signup_page($page); - } - else if($_POST['pass1'] != $_POST['pass2']) { - $this->theme->display_error(400, "Password Mismatch", "Passwords don't match"); - } - else { - try { - if(!captcha_check()) { - throw new UserCreationException("Error in captcha"); - } - - $uce = new UserCreationEvent($_POST['name'], $_POST['pass1'], $_POST['email']); - send_event($uce); - $this->set_login_cookie($uce->username, $uce->password); - $page->set_mode("redirect"); - $page->set_redirect(make_link("user")); - } - catch(UserCreationException $ex) { - $this->theme->display_error(400, "User Creation Error", $ex->getMessage()); - } - } + $this->page_create(); } else if($event->get_arg(0) == "list") { // select users.id,name,joindate,admin, @@ -165,24 +126,7 @@ class UserPage extends Extension { $this->theme->display_user_list($page, User::by_list(0), $user); } else if($event->get_arg(0) == "logout") { - $page->add_cookie("session", "", time()+60*60*24*$config->get_int('login_memory'), "/"); - if(CACHE_HTTP || SPEED_HAX) { - # to keep as few versions of content as possible, - # make cookies all-or-nothing - $page->add_cookie("user", "", time()+60*60*24*$config->get_int('login_memory'), "/"); - } - log_info("user", "Logged out"); - $page->set_mode("redirect"); - - // Try forwarding to same page on logout unless user comes from registration page - if ($config->get_int("user_loginshowprofile",0) == 0 && - isset($_SERVER['HTTP_REFERER']) && - strstr($_SERVER['HTTP_REFERER'], "post/")) - { - $page->set_redirect ($_SERVER['HTTP_REFERER']); - } else { - $page->set_redirect(make_link()); - } + $this->page_logout(); } if(!$user->check_auth_token()) { @@ -369,8 +313,8 @@ class UserPage extends Extension { global $user; $matches = array(); - if(preg_match("/^(poster|user)[=|:](.*)$/i", $event->term, $matches)) { - $duser = User::by_name($matches[2]); + if(preg_match("/^(?:poster|user)[=|:](.*)$/i", $event->term, $matches)) { + $duser = User::by_name($matches[1]); if(!is_null($duser)) { $user_id = $duser->id; } @@ -379,25 +323,33 @@ class UserPage extends Extension { } $event->add_querylet(new Querylet("images.owner_id = $user_id")); } - else if(preg_match("/^(poster|user)_id[=|:]([0-9]+)$/i", $event->term, $matches)) { - $user_id = int_escape($matches[2]); + else if(preg_match("/^(?:poster|user)_id[=|:]([0-9]+)$/i", $event->term, $matches)) { + $user_id = int_escape($matches[1]); $event->add_querylet(new Querylet("images.owner_id = $user_id")); } - else if($user->can("view_ip") && preg_match("/^(poster|user)_ip[=|:]([0-9\.]+)$/i", $event->term, $matches)) { - $user_ip = $matches[2]; // FIXME: ip_escape? + else if($user->can("view_ip") && preg_match("/^(?:poster|user)_ip[=|:]([0-9\.]+)$/i", $event->term, $matches)) { + $user_ip = $matches[1]; // FIXME: ip_escape? $event->add_querylet(new Querylet("images.owner_ip = '$user_ip'")); } } + + private function show_user_info() { + global $user, $page; + // user info is shown on all pages + if ($user->is_anonymous()) { + $this->theme->display_login_block($page); + } else { + $ubbe = new UserBlockBuildingEvent(); + send_event($ubbe); + ksort($ubbe->parts); + $this->theme->display_user_block($page, $user, $ubbe->parts); + } + } // }}} // Things done *with* the user {{{ - /** - * @param Page $page - */ - private function login(Page $page) { - global $config, $user; + private function page_login($name, $pass) { + global $config, $user, $page; - $name = $_POST['user']; - $pass = $_POST['pass']; if(empty($name) || empty($pass)) { $this->theme->display_error(400, "Error", "Username or password left blank"); @@ -427,12 +379,72 @@ class UserPage extends Extension { } } + private function page_logout() { + global $page, $config; + $page->add_cookie("session", "", time() + 60 * 60 * 24 * $config->get_int('login_memory'), "/"); + if (CACHE_HTTP || SPEED_HAX) { + # to keep as few versions of content as possible, + # make cookies all-or-nothing + $page->add_cookie("user", "", time() + 60 * 60 * 24 * $config->get_int('login_memory'), "/"); + } + log_info("user", "Logged out"); + $page->set_mode("redirect"); + + // Try forwarding to same page on logout unless user comes from registration page + if ($config->get_int("user_loginshowprofile", 0) == 0 && + isset($_SERVER['HTTP_REFERER']) && + strstr($_SERVER['HTTP_REFERER'], "post/") + ) { + $page->set_redirect($_SERVER['HTTP_REFERER']); + } else { + $page->set_redirect(make_link()); + } + } + + /** + * @param string $username + */ + private function page_recover($username) { + $user = User::by_name($username); + if (is_null($user)) { + $this->theme->display_error(404, "Error", "There's no user with that name"); + } else if (is_null($user->email)) { + $this->theme->display_error(400, "Error", "That user has no registered email address"); + } else { + // send email + } + } + + private function page_create() { + global $config, $page; + if (!$config->get_bool("login_signup_enabled")) { + $this->theme->display_signups_disabled($page); + } else if (!isset($_POST['name'])) { + $this->theme->display_signup_page($page); + } else if ($_POST['pass1'] != $_POST['pass2']) { + $this->theme->display_error(400, "Password Mismatch", "Passwords don't match"); + } else { + try { + if (!captcha_check()) { + throw new UserCreationException("Error in captcha"); + } + + $uce = new UserCreationEvent($_POST['name'], $_POST['pass1'], $_POST['email']); + send_event($uce); + $this->set_login_cookie($uce->username, $uce->password); + $page->set_mode("redirect"); + $page->set_redirect(make_link("user")); + } catch (UserCreationException $ex) { + $this->theme->display_error(400, "User Creation Error", $ex->getMessage()); + } + } + } + /** * @param UserCreationEvent $event * @throws UserCreationException */ - private function check_user_creation(UserCreationEvent $event) - { + private function check_user_creation(UserCreationEvent $event) { $name = $event->username; //$pass = $event->password; //$email = $event->email; @@ -450,8 +462,7 @@ class UserPage extends Extension { } } - private function create_user(UserCreationEvent $event) - { + private function create_user(UserCreationEvent $event) { global $database, $user; $email = (!empty($event->email)) ? $event->email : null; diff --git a/ext/user/test.php b/ext/user/test.php index 8c777c67..6e72ebc3 100644 --- a/ext/user/test.php +++ b/ext/user/test.php @@ -1,6 +1,6 @@ get_page('user'); $this->assert_title("Not Logged In"); $this->assert_no_text("Options"); diff --git a/ext/view/test.php b/ext/view/test.php index ae63fa50..d4ae305c 100644 --- a/ext/view/test.php +++ b/ext/view/test.php @@ -1,6 +1,11 @@ log_in_as_user(); $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "test"); $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "test2"); @@ -9,8 +14,16 @@ class ViewTest extends ShimmiePHPUnitTestCase { $this->get_page("post/view/$image_id_1"); $this->assert_title("Image $image_id_1: test"); + } + + public function testPrevNext() { + $this->markTestIncomplete(); + + $this->log_in_as_user(); + $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "test"); + $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "test2"); + $image_id_3 = $this->post_image("tests/favicon.png", "test"); - /* $this->click("Prev"); $this->assert_title("Image $image_id_2: test2"); @@ -19,22 +32,35 @@ class ViewTest extends ShimmiePHPUnitTestCase { $this->click("Next"); $this->assert_title("Image not found"); - */ + } + + public function testView404() { + $this->log_in_as_user(); + $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "test"); + $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "test2"); + $image_id_3 = $this->post_image("tests/favicon.png", "test"); + $idp1 = $image_id_3 + 1; $this->get_page("post/view/$idp1"); $this->assert_title('Image not found'); $this->get_page('post/view/-1'); $this->assert_title('Image not found'); + } - /* - * FIXME: this assumes Nice URLs. - * + public function testNextSearchResult() { + $this->markTestIncomplete(); + + $this->log_in_as_user(); + $image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "test"); + $image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "test2"); + $image_id_3 = $this->post_image("tests/favicon.png", "test"); + + // FIXME: this assumes Nice URLs. # note: skips image #2 $this->get_page("post/view/$image_id_1?search=test"); // FIXME: assumes niceurls $this->click("Prev"); $this->assert_title("Image $image_id_3: test"); - */ } } diff --git a/ext/wiki/test.php b/ext/wiki/test.php index 76a331ce..8d6e9bb2 100644 --- a/ext/wiki/test.php +++ b/ext/wiki/test.php @@ -1,13 +1,14 @@ get_page("wiki"); $this->assert_title("Index"); $this->assert_text("This is a default page"); } -/* - function testAccess() { + public function testAccess() { + $this->markTestIncomplete(); + global $config; foreach(array("anon", "user", "admin") as $user) { foreach(array(false, true) as $allowed) { @@ -39,7 +40,9 @@ class WikiTest extends ShimmiePHPUnitTestCase { } } - function testLock() { + public function testLock() { + $this->markTestIncomplete(); + global $config; $config->set_bool("wiki_edit_anon", true); $config->set_bool("wiki_edit_user", false); @@ -73,7 +76,9 @@ class WikiTest extends ShimmiePHPUnitTestCase { $this->log_out(); } - function testDefault() { + public function testDefault() { + $this->markTestIncomplete(); + $this->log_in_as_admin(); $this->get_page("wiki/wiki:default"); $this->assert_title("wiki:default"); @@ -90,7 +95,9 @@ class WikiTest extends ShimmiePHPUnitTestCase { $this->log_out(); } - function testRevisions() { + public function testRevisions() { + $this->markTestIncomplete(); + $this->log_in_as_admin(); $this->get_page("wiki/test"); $this->assert_title("test"); @@ -111,6 +118,5 @@ class WikiTest extends ShimmiePHPUnitTestCase { $this->click("Delete All"); $this->log_out(); } -*/ } diff --git a/ext/word_filter/test.php b/ext/word_filter/test.php index e08d4747..4ac1748d 100644 --- a/ext/word_filter/test.php +++ b/ext/word_filter/test.php @@ -1,12 +1,12 @@ set_string("word_filter", "whore,nice lady\na duck,a kitten\n white ,\tspace\ninvalid"); } - function _doThings($in, $out) { + public function _doThings($in, $out) { global $user; $this->log_in_as_user(); $image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot"); @@ -15,49 +15,49 @@ class WordFilterTest extends ShimmiePHPUnitTestCase { $this->assert_text($out); } - function testRegular() { + public function testRegular() { $this->_doThings( "posted by a whore", "posted by a nice lady" ); } - function testReplaceAll() { + public function testReplaceAll() { $this->_doThings( "a whore is a whore is a whore", "a nice lady is a nice lady is a nice lady" ); } - function testMixedCase() { + public function testMixedCase() { $this->_doThings( "monkey WhorE", "monkey nice lady" ); } - function testOnlyWholeWords() { + public function testOnlyWholeWords() { $this->_doThings( "my name is whoretta", "my name is whoretta" ); } - function testMultipleWords() { + public function testMultipleWords() { $this->_doThings( "I would like a duck", "I would like a kitten" ); } - function testWhitespace() { + public function testWhitespace() { $this->_doThings( "A colour is white", "A colour is space" ); } - function testIgnoreInvalid() { + public function testIgnoreInvalid() { $this->_doThings( "The word was invalid", "The word was invalid" diff --git a/tests/test-deep.sh b/tests/test-deep.sh new file mode 100755 index 00000000..8e63ef98 --- /dev/null +++ b/tests/test-deep.sh @@ -0,0 +1,9 @@ +#!/bin/sh +php \ + -d extension.dir=/usr/lib/php/extensions/no-debug-non-zts-20121212/ \ + -d extension=xdebug.so \ + -d xdebug.profiler_output_dir=./data/prof/ \ + -d xdebug.profiler_enable=1 \ + ./phpunit.phar \ + --config tests/phpunit.xml \ + --coverage-clover data/coverage.clover diff --git a/themes/danbooru/themelet.class.php b/themes/danbooru/themelet.class.php index c8026b5d..b2badbb1 100644 --- a/themes/danbooru/themelet.class.php +++ b/themes/danbooru/themelet.class.php @@ -30,7 +30,7 @@ class Themelet extends BaseThemelet { * @param string $base_url * @param string $query * @param int|string $page - * @param int|string $current_page + * @param int $current_page * @param string $name * @return string */ diff --git a/themes/danbooru2/index.theme.php b/themes/danbooru2/index.theme.php index 090cd248..3f462887 100644 --- a/themes/danbooru2/index.theme.php +++ b/themes/danbooru2/index.theme.php @@ -1,6 +1,10 @@ "; diff --git a/themes/danbooru2/layout.class.php b/themes/danbooru2/layout.class.php index ac8bfa13..4f459893 100644 --- a/themes/danbooru2/layout.class.php +++ b/themes/danbooru2/layout.class.php @@ -264,12 +264,18 @@ $header_html EOD; } - + + /** + * @param string $link + * @param string $desc + * @param string[] $pages_matched + * @return string + */ private function navlinks($link, $desc, $pages_matched) { /** * Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.) */ - $html = null; + $html = ""; $url = _get_query(); $re1='.*?'; @@ -286,7 +292,7 @@ EOD; $html = "
  • $desc
  • "; } } - if(is_null($html)) {$html = "
  • $desc
  • ";} + if(empty($html)) {$html = "
  • $desc
  • ";} return $html; } } diff --git a/themes/danbooru2/themelet.class.php b/themes/danbooru2/themelet.class.php index c8026b5d..b2badbb1 100644 --- a/themes/danbooru2/themelet.class.php +++ b/themes/danbooru2/themelet.class.php @@ -30,7 +30,7 @@ class Themelet extends BaseThemelet { * @param string $base_url * @param string $query * @param int|string $page - * @param int|string $current_page + * @param int $current_page * @param string $name * @return string */ diff --git a/themes/lite/view.theme.php b/themes/lite/view.theme.php index b2848072..b1083d0b 100644 --- a/themes/lite/view.theme.php +++ b/themes/lite/view.theme.php @@ -16,7 +16,11 @@ class CustomViewImageTheme extends ViewImageTheme { $page->add_block(new Block(null, $this->build_info($image, $editor_parts), "main", 11)); $page->add_block(new Block(null, $this->build_pin($image), "main", 11)); } - + + /** + * @param Image $image + * @return string + */ private function build_stats(Image $image) { $h_owner = html_escape($image->get_owner()->name); $h_ownerlink = "$h_owner"; diff --git a/themes/material/layout.class.php b/themes/material/layout.class.php new file mode 100644 index 00000000..5d88d965 --- /dev/null +++ b/themes/material/layout.class.php @@ -0,0 +1,281 @@ +get_string('theme', 'material'); + $site_name = $config->get_string('title'); + $data_href = get_base_href(); + $main_page = $config->get_string('main_page'); + $contact_link = $config->get_string('contact_link'); + $site_link = make_link(); + + $header_html = ""; + ksort($page->html_headers); + foreach($page->html_headers as $line) { + $header_html .= "\t\t$line\n"; + } + + $left_block_html = ""; + $main_block_html = ""; + $head_block_html = ""; + $sub_block_html = ""; + $drawer_block_html = ""; //use exampled in user.theme.php & view.theme.php + $toolbar_block_html = ""; // not used at this point + $subtoolbar_block_html = ""; // use exampled in user.theme.php + $navigation = ""; + + $h_search = " +
    + + +
    + + + +
    + +
    + "; + + foreach($page->blocks as $block) { + switch($block->section) { + case "toolbar": + $toolbar_block_html .= $this->get_html($block, "toolbar"); + break; + case "subtoolbar": + $subtoolbar_block_html .= $this->get_html($block, "subtoolbar"); + break; + case "left": + if($block->header == "Navigation"){ + $subtoolbar_block_html = $this->rework_navigation($block); + break; + } + // $left_block_html .= $block->get_html(true); + $left_block_html .= $this->get_html($block, "full", true, "left-blocks nav-card mdl-cell--4-col-tablet"); + break; + case "head": + $head_block_html .= $this->get_html($block, "third", true, "nav-card head-blocks"); + break; + case "drawer": + $drawer_block_html .= $this->get_html($block, "full", true, "nav-card drawer-blocks"); + break; + case "main": + // $main_block_html .= $block->get_html(false); + $main_block_html .= $this->get_html($block, "main", true, ""); + break; + case "subheading": + // $sub_block_html .= $block->body; // $this->block_to_html($block, true); + $sub_block_html .= $this->get_html($block, "third", true, "nav-card"); + break; + default: + print "

    error: {$block->header} using an unknown section ({$block->section})"; + break; + } + } + + $debug = get_debug_info(); + + $contact = empty($contact_link) ? "" : "
    Contact"; + /*$subheading = empty($page->subheading) ? "" : "

    {$page->subheading}
    "; + + $wrapper = ""; + if(strlen($page->heading) > 100) { + $wrapper = ' style="height: 3em; overflow: auto;"'; + } + */ + + $flash = $page->get_cookie("flash_message"); + $flash_html = ""; + if($flash) { + $flash_html = "".nl2br(html_escape($flash))." [X]"; + } + + print << + + + + + + + + + {$page->title} + + + + $header_html + + + + + + + +
    +
    + +
    + + + + +
    + $h_search + {$toolbar_block_html} + +
    + +
    +
    + + {$subtoolbar_block_html} +
    +
    +
    + Drawer +
    + $drawer_block_html +
    + +
    +
    +
    +
    + $head_block_html + $sub_block_html +
    +
    +
    +
    + +
    + + $left_block_html +
    +
    +
    + + +
    + $flash_html + $main_block_html +
    +
    +
    +
    + $debug + $contact +
    +
    +
    + +
      +
    • Layout Top
    • +
    • Layout Right
    • +
    • Layout Bottom
    • +
    • Layout Left
    • +
    + + +EOD; + } + + public function rework_navigation(Block $block){ + $h = $block->header; + $b = $block->body; + $i = $block->id; + + $dom = new DomDocument(); + $dom->loadHTML($b); + $output = array(); + $html = "
    \n\n
    \n"; + return $html; + } + + /** + * Get the HTML for this block. from core + * + * @param bool $hidable + * @return string + */ + public function get_html(Block $block, $section="main", $hidable=false, $extra_class="") { + $h = $block->header; + $b = $block->body; + $i = $block->id;//blotter extention id has `!` + + if($section == "toolbar"){ + $html = "
    \n\n
    \n"; + return $html; + } + if($section == "subtoolbar"){ + $html = "
    \n\n
    \n"; + return $html; + } + if($section == "full"){ + $html = "
    "; + $h_toggler = $hidable ? " shm-toggler" : ""; + if(!empty($h)) $html .="

    $h

    "; + if(!empty($b)) $html .="
    $b
    "; + $html .= "
    \n"; + return $html; + } + if($section == "third"){ + $html = "
    "; + $h_toggler = $hidable ? " shm-toggler" : ""; + if(!empty($h)) $html .="

    $h

    "; + if(!empty($b)) $html .="
    $b
    "; + $html .= "
    \n"; + return $html; + } + $html = "
    "; + $h_toggler = $hidable ? " shm-toggler" : ""; + if(!empty($h)) $html .= "

    $h

    "; + if(!empty($b)) $html .= "
    $b
    "; + $html .= "
    \n"; + return $html; + } + +} + + +//@todo fix ext/blotter id tag +//@todo fix table row error for ext/ip_ban +//@todo fix table row error for ext/image_hash_ban +//@todo fix table row error for ext/untag +//@todo fix ext private-messages gives Uncaught TypeError: Cannot read property 'href' of null when no messages are there.. diff --git a/themes/material/material.min.css b/themes/material/material.min.css new file mode 100644 index 00000000..e72ca355 --- /dev/null +++ b/themes/material/material.min.css @@ -0,0 +1,8 @@ +/** + * material-design-lite - Material Design Components in CSS, JS and HTML + * @version v1.0.5 + * @license Apache-2.0 + * @copyright 2015 Google, Inc. + * @link https://github.com/google/material-design-lite + */ +@charset "UTF-8";html{color:rgba(0,0,0,.87)}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.browserupgrade{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.hidden{display:none!important}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}@media print{*,*:before,*:after,*:first-letter,*:first-line{background:transparent!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}a,.mdl-accordion,.mdl-button,.mdl-card,.mdl-checkbox,.mdl-dropdown-menu,.mdl-icon-toggle,.mdl-item,.mdl-radio,.mdl-slider,.mdl-switch,.mdl-tabs__tab{-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:rgba(255,255,255,0)}html{width:100%;height:100%;-ms-touch-action:manipulation;touch-action:manipulation}body{width:100%;min-height:100%}main{display:block}*[hidden]{display:none!important}html,body{font-family:"Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:20px}h1,h2,h3,h4,h5,h6,p{padding:0}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400;line-height:1.35;letter-spacing:-.02em;opacity:.54;font-size:.6em}h1{font-size:56px;line-height:1.35;letter-spacing:-.02em;margin:24px 0}h1,h2{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h2{font-size:45px;line-height:48px}h2,h3{margin:24px 0}h3{font-size:34px;line-height:40px}h3,h4{font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:400}h4{font-size:24px;line-height:32px;-moz-osx-font-smoothing:grayscale;margin:24px 0 16px}h5{font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}h5,h6{font-family:"Roboto","Helvetica","Arial",sans-serif;margin:24px 0 16px}h6{font-size:16px;letter-spacing:.04em}h6,p{font-weight:400;line-height:24px}p{font-size:14px;letter-spacing:0;margin:0 0 16px}a{color:rgb(255,110,64);font-weight:500}blockquote{font-family:"Roboto","Helvetica","Arial",sans-serif;position:relative;font-size:24px;font-weight:300;font-style:italic;line-height:1.35;letter-spacing:.08em}blockquote:before{position:absolute;left:-.5em;content:'“'}blockquote:after{content:'”';margin-left:-.05em}mark{background-color:#f4ff81}dt{font-weight:700}address{font-size:12px;line-height:1;font-style:normal}address,ul,ol{font-weight:400;letter-spacing:0}ul,ol{font-size:14px;line-height:24px}.mdl-typography--display-4,.mdl-typography--display-4-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:112px;font-weight:300;line-height:1;letter-spacing:-.04em}.mdl-typography--display-4-color-contrast{opacity:.54}.mdl-typography--display-3,.mdl-typography--display-3-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:56px;font-weight:400;line-height:1.35;letter-spacing:-.02em}.mdl-typography--display-3-color-contrast{opacity:.54}.mdl-typography--display-2,.mdl-typography--display-2-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:45px;font-weight:400;line-height:48px}.mdl-typography--display-2-color-contrast{opacity:.54}.mdl-typography--display-1,.mdl-typography--display-1-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:34px;font-weight:400;line-height:40px}.mdl-typography--display-1-color-contrast{opacity:.54}.mdl-typography--headline,.mdl-typography--headline-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:24px;font-weight:400;line-height:32px;-moz-osx-font-smoothing:grayscale}.mdl-typography--headline-color-contrast{opacity:.87}.mdl-typography--title,.mdl-typography--title-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;font-weight:500;line-height:1;letter-spacing:.02em}.mdl-typography--title-color-contrast{opacity:.87}.mdl-typography--subhead,.mdl-typography--subhead-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:16px;font-weight:400;line-height:24px;letter-spacing:.04em}.mdl-typography--subhead-color-contrast{opacity:.87}.mdl-typography--body-2,.mdl-typography--body-2-color-contrast{font-size:14px;font-weight:700;line-height:24px;letter-spacing:0}.mdl-typography--body-2-color-contrast{opacity:.87}.mdl-typography--body-1,.mdl-typography--body-1-color-contrast{font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-color-contrast{opacity:.87}.mdl-typography--body-2-force-preferred-font,.mdl-typography--body-2-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:24px;letter-spacing:0}.mdl-typography--body-2-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--body-1-force-preferred-font,.mdl-typography--body-1-force-preferred-font-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:400;line-height:24px;letter-spacing:0}.mdl-typography--body-1-force-preferred-font-color-contrast{opacity:.87}.mdl-typography--caption,.mdl-typography--caption-force-preferred-font{font-size:12px;font-weight:400;line-height:1;letter-spacing:0}.mdl-typography--caption-force-preferred-font{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--caption-color-contrast,.mdl-typography--caption-force-preferred-font-color-contrast{font-size:12px;font-weight:400;line-height:1;letter-spacing:0;opacity:.54}.mdl-typography--caption-force-preferred-font-color-contrast,.mdl-typography--menu{font-family:"Roboto","Helvetica","Arial",sans-serif}.mdl-typography--menu{font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--menu-color-contrast{opacity:.87}.mdl-typography--menu-color-contrast,.mdl-typography--button,.mdl-typography--button-color-contrast{font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;line-height:1;letter-spacing:0}.mdl-typography--button,.mdl-typography--button-color-contrast{text-transform:uppercase}.mdl-typography--button-color-contrast{opacity:.87}.mdl-typography--text-left{text-align:left}.mdl-typography--text-right{text-align:right}.mdl-typography--text-center{text-align:center}.mdl-typography--text-justify{text-align:justify}.mdl-typography--text-nowrap{white-space:nowrap}.mdl-typography--text-lowercase{text-transform:lowercase}.mdl-typography--text-uppercase{text-transform:uppercase}.mdl-typography--text-capitalize{text-transform:capitalize}.mdl-typography--font-thin{font-weight:200!important}.mdl-typography--font-light{font-weight:300!important}.mdl-typography--font-regular{font-weight:400!important}.mdl-typography--font-medium{font-weight:500!important}.mdl-typography--font-bold{font-weight:700!important}.mdl-typography--font-black{font-weight:900!important}.mdl-color-text--red{color:#f44336 !important}.mdl-color--red{background-color:#f44336 !important}.mdl-color-text--red-50{color:#ffebee !important}.mdl-color--red-50{background-color:#ffebee !important}.mdl-color-text--red-100{color:#ffcdd2 !important}.mdl-color--red-100{background-color:#ffcdd2 !important}.mdl-color-text--red-200{color:#ef9a9a !important}.mdl-color--red-200{background-color:#ef9a9a !important}.mdl-color-text--red-300{color:#e57373 !important}.mdl-color--red-300{background-color:#e57373 !important}.mdl-color-text--red-400{color:#ef5350 !important}.mdl-color--red-400{background-color:#ef5350 !important}.mdl-color-text--red-500{color:#f44336 !important}.mdl-color--red-500{background-color:#f44336 !important}.mdl-color-text--red-600{color:#e53935 !important}.mdl-color--red-600{background-color:#e53935 !important}.mdl-color-text--red-700{color:#d32f2f !important}.mdl-color--red-700{background-color:#d32f2f !important}.mdl-color-text--red-800{color:#c62828 !important}.mdl-color--red-800{background-color:#c62828 !important}.mdl-color-text--red-900{color:#b71c1c !important}.mdl-color--red-900{background-color:#b71c1c !important}.mdl-color-text--red-A100{color:#ff8a80 !important}.mdl-color--red-A100{background-color:#ff8a80 !important}.mdl-color-text--red-A200{color:#ff5252 !important}.mdl-color--red-A200{background-color:#ff5252 !important}.mdl-color-text--red-A400{color:#ff1744 !important}.mdl-color--red-A400{background-color:#ff1744 !important}.mdl-color-text--red-A700{color:#d50000 !important}.mdl-color--red-A700{background-color:#d50000 !important}.mdl-color-text--pink{color:#e91e63 !important}.mdl-color--pink{background-color:#e91e63 !important}.mdl-color-text--pink-50{color:#fce4ec !important}.mdl-color--pink-50{background-color:#fce4ec !important}.mdl-color-text--pink-100{color:#f8bbd0 !important}.mdl-color--pink-100{background-color:#f8bbd0 !important}.mdl-color-text--pink-200{color:#f48fb1 !important}.mdl-color--pink-200{background-color:#f48fb1 !important}.mdl-color-text--pink-300{color:#f06292 !important}.mdl-color--pink-300{background-color:#f06292 !important}.mdl-color-text--pink-400{color:#ec407a !important}.mdl-color--pink-400{background-color:#ec407a !important}.mdl-color-text--pink-500{color:#e91e63 !important}.mdl-color--pink-500{background-color:#e91e63 !important}.mdl-color-text--pink-600{color:#d81b60 !important}.mdl-color--pink-600{background-color:#d81b60 !important}.mdl-color-text--pink-700{color:#c2185b !important}.mdl-color--pink-700{background-color:#c2185b !important}.mdl-color-text--pink-800{color:#ad1457 !important}.mdl-color--pink-800{background-color:#ad1457 !important}.mdl-color-text--pink-900{color:#880e4f !important}.mdl-color--pink-900{background-color:#880e4f !important}.mdl-color-text--pink-A100{color:#ff80ab !important}.mdl-color--pink-A100{background-color:#ff80ab !important}.mdl-color-text--pink-A200{color:#ff4081 !important}.mdl-color--pink-A200{background-color:#ff4081 !important}.mdl-color-text--pink-A400{color:#f50057 !important}.mdl-color--pink-A400{background-color:#f50057 !important}.mdl-color-text--pink-A700{color:#c51162 !important}.mdl-color--pink-A700{background-color:#c51162 !important}.mdl-color-text--purple{color:#9c27b0 !important}.mdl-color--purple{background-color:#9c27b0 !important}.mdl-color-text--purple-50{color:#f3e5f5 !important}.mdl-color--purple-50{background-color:#f3e5f5 !important}.mdl-color-text--purple-100{color:#e1bee7 !important}.mdl-color--purple-100{background-color:#e1bee7 !important}.mdl-color-text--purple-200{color:#ce93d8 !important}.mdl-color--purple-200{background-color:#ce93d8 !important}.mdl-color-text--purple-300{color:#ba68c8 !important}.mdl-color--purple-300{background-color:#ba68c8 !important}.mdl-color-text--purple-400{color:#ab47bc !important}.mdl-color--purple-400{background-color:#ab47bc !important}.mdl-color-text--purple-500{color:#9c27b0 !important}.mdl-color--purple-500{background-color:#9c27b0 !important}.mdl-color-text--purple-600{color:#8e24aa !important}.mdl-color--purple-600{background-color:#8e24aa !important}.mdl-color-text--purple-700{color:#7b1fa2 !important}.mdl-color--purple-700{background-color:#7b1fa2 !important}.mdl-color-text--purple-800{color:#6a1b9a !important}.mdl-color--purple-800{background-color:#6a1b9a !important}.mdl-color-text--purple-900{color:#4a148c !important}.mdl-color--purple-900{background-color:#4a148c !important}.mdl-color-text--purple-A100{color:#ea80fc !important}.mdl-color--purple-A100{background-color:#ea80fc !important}.mdl-color-text--purple-A200{color:#e040fb !important}.mdl-color--purple-A200{background-color:#e040fb !important}.mdl-color-text--purple-A400{color:#d500f9 !important}.mdl-color--purple-A400{background-color:#d500f9 !important}.mdl-color-text--purple-A700{color:#a0f !important}.mdl-color--purple-A700{background-color:#a0f !important}.mdl-color-text--deep-purple{color:#673ab7 !important}.mdl-color--deep-purple{background-color:#673ab7 !important}.mdl-color-text--deep-purple-50{color:#ede7f6 !important}.mdl-color--deep-purple-50{background-color:#ede7f6 !important}.mdl-color-text--deep-purple-100{color:#d1c4e9 !important}.mdl-color--deep-purple-100{background-color:#d1c4e9 !important}.mdl-color-text--deep-purple-200{color:#b39ddb !important}.mdl-color--deep-purple-200{background-color:#b39ddb !important}.mdl-color-text--deep-purple-300{color:#9575cd !important}.mdl-color--deep-purple-300{background-color:#9575cd !important}.mdl-color-text--deep-purple-400{color:#7e57c2 !important}.mdl-color--deep-purple-400{background-color:#7e57c2 !important}.mdl-color-text--deep-purple-500{color:#673ab7 !important}.mdl-color--deep-purple-500{background-color:#673ab7 !important}.mdl-color-text--deep-purple-600{color:#5e35b1 !important}.mdl-color--deep-purple-600{background-color:#5e35b1 !important}.mdl-color-text--deep-purple-700{color:#512da8 !important}.mdl-color--deep-purple-700{background-color:#512da8 !important}.mdl-color-text--deep-purple-800{color:#4527a0 !important}.mdl-color--deep-purple-800{background-color:#4527a0 !important}.mdl-color-text--deep-purple-900{color:#311b92 !important}.mdl-color--deep-purple-900{background-color:#311b92 !important}.mdl-color-text--deep-purple-A100{color:#b388ff !important}.mdl-color--deep-purple-A100{background-color:#b388ff !important}.mdl-color-text--deep-purple-A200{color:#7c4dff !important}.mdl-color--deep-purple-A200{background-color:#7c4dff !important}.mdl-color-text--deep-purple-A400{color:#651fff !important}.mdl-color--deep-purple-A400{background-color:#651fff !important}.mdl-color-text--deep-purple-A700{color:#6200ea !important}.mdl-color--deep-purple-A700{background-color:#6200ea !important}.mdl-color-text--indigo{color:#3f51b5 !important}.mdl-color--indigo{background-color:#3f51b5 !important}.mdl-color-text--indigo-50{color:#e8eaf6 !important}.mdl-color--indigo-50{background-color:#e8eaf6 !important}.mdl-color-text--indigo-100{color:#c5cae9 !important}.mdl-color--indigo-100{background-color:#c5cae9 !important}.mdl-color-text--indigo-200{color:#9fa8da !important}.mdl-color--indigo-200{background-color:#9fa8da !important}.mdl-color-text--indigo-300{color:#7986cb !important}.mdl-color--indigo-300{background-color:#7986cb !important}.mdl-color-text--indigo-400{color:#5c6bc0 !important}.mdl-color--indigo-400{background-color:#5c6bc0 !important}.mdl-color-text--indigo-500{color:#3f51b5 !important}.mdl-color--indigo-500{background-color:#3f51b5 !important}.mdl-color-text--indigo-600{color:#3949ab !important}.mdl-color--indigo-600{background-color:#3949ab !important}.mdl-color-text--indigo-700{color:#303f9f !important}.mdl-color--indigo-700{background-color:#303f9f !important}.mdl-color-text--indigo-800{color:#283593 !important}.mdl-color--indigo-800{background-color:#283593 !important}.mdl-color-text--indigo-900{color:#1a237e !important}.mdl-color--indigo-900{background-color:#1a237e !important}.mdl-color-text--indigo-A100{color:#8c9eff !important}.mdl-color--indigo-A100{background-color:#8c9eff !important}.mdl-color-text--indigo-A200{color:#536dfe !important}.mdl-color--indigo-A200{background-color:#536dfe !important}.mdl-color-text--indigo-A400{color:#3d5afe !important}.mdl-color--indigo-A400{background-color:#3d5afe !important}.mdl-color-text--indigo-A700{color:#304ffe !important}.mdl-color--indigo-A700{background-color:#304ffe !important}.mdl-color-text--blue{color:#2196f3 !important}.mdl-color--blue{background-color:#2196f3 !important}.mdl-color-text--blue-50{color:#e3f2fd !important}.mdl-color--blue-50{background-color:#e3f2fd !important}.mdl-color-text--blue-100{color:#bbdefb !important}.mdl-color--blue-100{background-color:#bbdefb !important}.mdl-color-text--blue-200{color:#90caf9 !important}.mdl-color--blue-200{background-color:#90caf9 !important}.mdl-color-text--blue-300{color:#64b5f6 !important}.mdl-color--blue-300{background-color:#64b5f6 !important}.mdl-color-text--blue-400{color:#42a5f5 !important}.mdl-color--blue-400{background-color:#42a5f5 !important}.mdl-color-text--blue-500{color:#2196f3 !important}.mdl-color--blue-500{background-color:#2196f3 !important}.mdl-color-text--blue-600{color:#1e88e5 !important}.mdl-color--blue-600{background-color:#1e88e5 !important}.mdl-color-text--blue-700{color:#1976d2 !important}.mdl-color--blue-700{background-color:#1976d2 !important}.mdl-color-text--blue-800{color:#1565c0 !important}.mdl-color--blue-800{background-color:#1565c0 !important}.mdl-color-text--blue-900{color:#0d47a1 !important}.mdl-color--blue-900{background-color:#0d47a1 !important}.mdl-color-text--blue-A100{color:#82b1ff !important}.mdl-color--blue-A100{background-color:#82b1ff !important}.mdl-color-text--blue-A200{color:#448aff !important}.mdl-color--blue-A200{background-color:#448aff !important}.mdl-color-text--blue-A400{color:#2979ff !important}.mdl-color--blue-A400{background-color:#2979ff !important}.mdl-color-text--blue-A700{color:#2962ff !important}.mdl-color--blue-A700{background-color:#2962ff !important}.mdl-color-text--light-blue{color:#03a9f4 !important}.mdl-color--light-blue{background-color:#03a9f4 !important}.mdl-color-text--light-blue-50{color:#e1f5fe !important}.mdl-color--light-blue-50{background-color:#e1f5fe !important}.mdl-color-text--light-blue-100{color:#b3e5fc !important}.mdl-color--light-blue-100{background-color:#b3e5fc !important}.mdl-color-text--light-blue-200{color:#81d4fa !important}.mdl-color--light-blue-200{background-color:#81d4fa !important}.mdl-color-text--light-blue-300{color:#4fc3f7 !important}.mdl-color--light-blue-300{background-color:#4fc3f7 !important}.mdl-color-text--light-blue-400{color:#29b6f6 !important}.mdl-color--light-blue-400{background-color:#29b6f6 !important}.mdl-color-text--light-blue-500{color:#03a9f4 !important}.mdl-color--light-blue-500{background-color:#03a9f4 !important}.mdl-color-text--light-blue-600{color:#039be5 !important}.mdl-color--light-blue-600{background-color:#039be5 !important}.mdl-color-text--light-blue-700{color:#0288d1 !important}.mdl-color--light-blue-700{background-color:#0288d1 !important}.mdl-color-text--light-blue-800{color:#0277bd !important}.mdl-color--light-blue-800{background-color:#0277bd !important}.mdl-color-text--light-blue-900{color:#01579b !important}.mdl-color--light-blue-900{background-color:#01579b !important}.mdl-color-text--light-blue-A100{color:#80d8ff !important}.mdl-color--light-blue-A100{background-color:#80d8ff !important}.mdl-color-text--light-blue-A200{color:#40c4ff !important}.mdl-color--light-blue-A200{background-color:#40c4ff !important}.mdl-color-text--light-blue-A400{color:#00b0ff !important}.mdl-color--light-blue-A400{background-color:#00b0ff !important}.mdl-color-text--light-blue-A700{color:#0091ea !important}.mdl-color--light-blue-A700{background-color:#0091ea !important}.mdl-color-text--cyan{color:#00bcd4 !important}.mdl-color--cyan{background-color:#00bcd4 !important}.mdl-color-text--cyan-50{color:#e0f7fa !important}.mdl-color--cyan-50{background-color:#e0f7fa !important}.mdl-color-text--cyan-100{color:#b2ebf2 !important}.mdl-color--cyan-100{background-color:#b2ebf2 !important}.mdl-color-text--cyan-200{color:#80deea !important}.mdl-color--cyan-200{background-color:#80deea !important}.mdl-color-text--cyan-300{color:#4dd0e1 !important}.mdl-color--cyan-300{background-color:#4dd0e1 !important}.mdl-color-text--cyan-400{color:#26c6da !important}.mdl-color--cyan-400{background-color:#26c6da !important}.mdl-color-text--cyan-500{color:#00bcd4 !important}.mdl-color--cyan-500{background-color:#00bcd4 !important}.mdl-color-text--cyan-600{color:#00acc1 !important}.mdl-color--cyan-600{background-color:#00acc1 !important}.mdl-color-text--cyan-700{color:#0097a7 !important}.mdl-color--cyan-700{background-color:#0097a7 !important}.mdl-color-text--cyan-800{color:#00838f !important}.mdl-color--cyan-800{background-color:#00838f !important}.mdl-color-text--cyan-900{color:#006064 !important}.mdl-color--cyan-900{background-color:#006064 !important}.mdl-color-text--cyan-A100{color:#84ffff !important}.mdl-color--cyan-A100{background-color:#84ffff !important}.mdl-color-text--cyan-A200{color:#18ffff !important}.mdl-color--cyan-A200{background-color:#18ffff !important}.mdl-color-text--cyan-A400{color:#00e5ff !important}.mdl-color--cyan-A400{background-color:#00e5ff !important}.mdl-color-text--cyan-A700{color:#00b8d4 !important}.mdl-color--cyan-A700{background-color:#00b8d4 !important}.mdl-color-text--teal{color:#009688 !important}.mdl-color--teal{background-color:#009688 !important}.mdl-color-text--teal-50{color:#e0f2f1 !important}.mdl-color--teal-50{background-color:#e0f2f1 !important}.mdl-color-text--teal-100{color:#b2dfdb !important}.mdl-color--teal-100{background-color:#b2dfdb !important}.mdl-color-text--teal-200{color:#80cbc4 !important}.mdl-color--teal-200{background-color:#80cbc4 !important}.mdl-color-text--teal-300{color:#4db6ac !important}.mdl-color--teal-300{background-color:#4db6ac !important}.mdl-color-text--teal-400{color:#26a69a !important}.mdl-color--teal-400{background-color:#26a69a !important}.mdl-color-text--teal-500{color:#009688 !important}.mdl-color--teal-500{background-color:#009688 !important}.mdl-color-text--teal-600{color:#00897b !important}.mdl-color--teal-600{background-color:#00897b !important}.mdl-color-text--teal-700{color:#00796b !important}.mdl-color--teal-700{background-color:#00796b !important}.mdl-color-text--teal-800{color:#00695c !important}.mdl-color--teal-800{background-color:#00695c !important}.mdl-color-text--teal-900{color:#004d40 !important}.mdl-color--teal-900{background-color:#004d40 !important}.mdl-color-text--teal-A100{color:#a7ffeb !important}.mdl-color--teal-A100{background-color:#a7ffeb !important}.mdl-color-text--teal-A200{color:#64ffda !important}.mdl-color--teal-A200{background-color:#64ffda !important}.mdl-color-text--teal-A400{color:#1de9b6 !important}.mdl-color--teal-A400{background-color:#1de9b6 !important}.mdl-color-text--teal-A700{color:#00bfa5 !important}.mdl-color--teal-A700{background-color:#00bfa5 !important}.mdl-color-text--green{color:#4caf50 !important}.mdl-color--green{background-color:#4caf50 !important}.mdl-color-text--green-50{color:#e8f5e9 !important}.mdl-color--green-50{background-color:#e8f5e9 !important}.mdl-color-text--green-100{color:#c8e6c9 !important}.mdl-color--green-100{background-color:#c8e6c9 !important}.mdl-color-text--green-200{color:#a5d6a7 !important}.mdl-color--green-200{background-color:#a5d6a7 !important}.mdl-color-text--green-300{color:#81c784 !important}.mdl-color--green-300{background-color:#81c784 !important}.mdl-color-text--green-400{color:#66bb6a !important}.mdl-color--green-400{background-color:#66bb6a !important}.mdl-color-text--green-500{color:#4caf50 !important}.mdl-color--green-500{background-color:#4caf50 !important}.mdl-color-text--green-600{color:#43a047 !important}.mdl-color--green-600{background-color:#43a047 !important}.mdl-color-text--green-700{color:#388e3c !important}.mdl-color--green-700{background-color:#388e3c !important}.mdl-color-text--green-800{color:#2e7d32 !important}.mdl-color--green-800{background-color:#2e7d32 !important}.mdl-color-text--green-900{color:#1b5e20 !important}.mdl-color--green-900{background-color:#1b5e20 !important}.mdl-color-text--green-A100{color:#b9f6ca !important}.mdl-color--green-A100{background-color:#b9f6ca !important}.mdl-color-text--green-A200{color:#69f0ae !important}.mdl-color--green-A200{background-color:#69f0ae !important}.mdl-color-text--green-A400{color:#00e676 !important}.mdl-color--green-A400{background-color:#00e676 !important}.mdl-color-text--green-A700{color:#00c853 !important}.mdl-color--green-A700{background-color:#00c853 !important}.mdl-color-text--light-green{color:#8bc34a !important}.mdl-color--light-green{background-color:#8bc34a !important}.mdl-color-text--light-green-50{color:#f1f8e9 !important}.mdl-color--light-green-50{background-color:#f1f8e9 !important}.mdl-color-text--light-green-100{color:#dcedc8 !important}.mdl-color--light-green-100{background-color:#dcedc8 !important}.mdl-color-text--light-green-200{color:#c5e1a5 !important}.mdl-color--light-green-200{background-color:#c5e1a5 !important}.mdl-color-text--light-green-300{color:#aed581 !important}.mdl-color--light-green-300{background-color:#aed581 !important}.mdl-color-text--light-green-400{color:#9ccc65 !important}.mdl-color--light-green-400{background-color:#9ccc65 !important}.mdl-color-text--light-green-500{color:#8bc34a !important}.mdl-color--light-green-500{background-color:#8bc34a !important}.mdl-color-text--light-green-600{color:#7cb342 !important}.mdl-color--light-green-600{background-color:#7cb342 !important}.mdl-color-text--light-green-700{color:#689f38 !important}.mdl-color--light-green-700{background-color:#689f38 !important}.mdl-color-text--light-green-800{color:#558b2f !important}.mdl-color--light-green-800{background-color:#558b2f !important}.mdl-color-text--light-green-900{color:#33691e !important}.mdl-color--light-green-900{background-color:#33691e !important}.mdl-color-text--light-green-A100{color:#ccff90 !important}.mdl-color--light-green-A100{background-color:#ccff90 !important}.mdl-color-text--light-green-A200{color:#b2ff59 !important}.mdl-color--light-green-A200{background-color:#b2ff59 !important}.mdl-color-text--light-green-A400{color:#76ff03 !important}.mdl-color--light-green-A400{background-color:#76ff03 !important}.mdl-color-text--light-green-A700{color:#64dd17 !important}.mdl-color--light-green-A700{background-color:#64dd17 !important}.mdl-color-text--lime{color:#cddc39 !important}.mdl-color--lime{background-color:#cddc39 !important}.mdl-color-text--lime-50{color:#f9fbe7 !important}.mdl-color--lime-50{background-color:#f9fbe7 !important}.mdl-color-text--lime-100{color:#f0f4c3 !important}.mdl-color--lime-100{background-color:#f0f4c3 !important}.mdl-color-text--lime-200{color:#e6ee9c !important}.mdl-color--lime-200{background-color:#e6ee9c !important}.mdl-color-text--lime-300{color:#dce775 !important}.mdl-color--lime-300{background-color:#dce775 !important}.mdl-color-text--lime-400{color:#d4e157 !important}.mdl-color--lime-400{background-color:#d4e157 !important}.mdl-color-text--lime-500{color:#cddc39 !important}.mdl-color--lime-500{background-color:#cddc39 !important}.mdl-color-text--lime-600{color:#c0ca33 !important}.mdl-color--lime-600{background-color:#c0ca33 !important}.mdl-color-text--lime-700{color:#afb42b !important}.mdl-color--lime-700{background-color:#afb42b !important}.mdl-color-text--lime-800{color:#9e9d24 !important}.mdl-color--lime-800{background-color:#9e9d24 !important}.mdl-color-text--lime-900{color:#827717 !important}.mdl-color--lime-900{background-color:#827717 !important}.mdl-color-text--lime-A100{color:#f4ff81 !important}.mdl-color--lime-A100{background-color:#f4ff81 !important}.mdl-color-text--lime-A200{color:#eeff41 !important}.mdl-color--lime-A200{background-color:#eeff41 !important}.mdl-color-text--lime-A400{color:#c6ff00 !important}.mdl-color--lime-A400{background-color:#c6ff00 !important}.mdl-color-text--lime-A700{color:#aeea00 !important}.mdl-color--lime-A700{background-color:#aeea00 !important}.mdl-color-text--yellow{color:#ffeb3b !important}.mdl-color--yellow{background-color:#ffeb3b !important}.mdl-color-text--yellow-50{color:#fffde7 !important}.mdl-color--yellow-50{background-color:#fffde7 !important}.mdl-color-text--yellow-100{color:#fff9c4 !important}.mdl-color--yellow-100{background-color:#fff9c4 !important}.mdl-color-text--yellow-200{color:#fff59d !important}.mdl-color--yellow-200{background-color:#fff59d !important}.mdl-color-text--yellow-300{color:#fff176 !important}.mdl-color--yellow-300{background-color:#fff176 !important}.mdl-color-text--yellow-400{color:#ffee58 !important}.mdl-color--yellow-400{background-color:#ffee58 !important}.mdl-color-text--yellow-500{color:#ffeb3b !important}.mdl-color--yellow-500{background-color:#ffeb3b !important}.mdl-color-text--yellow-600{color:#fdd835 !important}.mdl-color--yellow-600{background-color:#fdd835 !important}.mdl-color-text--yellow-700{color:#fbc02d !important}.mdl-color--yellow-700{background-color:#fbc02d !important}.mdl-color-text--yellow-800{color:#f9a825 !important}.mdl-color--yellow-800{background-color:#f9a825 !important}.mdl-color-text--yellow-900{color:#f57f17 !important}.mdl-color--yellow-900{background-color:#f57f17 !important}.mdl-color-text--yellow-A100{color:#ffff8d !important}.mdl-color--yellow-A100{background-color:#ffff8d !important}.mdl-color-text--yellow-A200{color:#ff0 !important}.mdl-color--yellow-A200{background-color:#ff0 !important}.mdl-color-text--yellow-A400{color:#ffea00 !important}.mdl-color--yellow-A400{background-color:#ffea00 !important}.mdl-color-text--yellow-A700{color:#ffd600 !important}.mdl-color--yellow-A700{background-color:#ffd600 !important}.mdl-color-text--amber{color:#ffc107 !important}.mdl-color--amber{background-color:#ffc107 !important}.mdl-color-text--amber-50{color:#fff8e1 !important}.mdl-color--amber-50{background-color:#fff8e1 !important}.mdl-color-text--amber-100{color:#ffecb3 !important}.mdl-color--amber-100{background-color:#ffecb3 !important}.mdl-color-text--amber-200{color:#ffe082 !important}.mdl-color--amber-200{background-color:#ffe082 !important}.mdl-color-text--amber-300{color:#ffd54f !important}.mdl-color--amber-300{background-color:#ffd54f !important}.mdl-color-text--amber-400{color:#ffca28 !important}.mdl-color--amber-400{background-color:#ffca28 !important}.mdl-color-text--amber-500{color:#ffc107 !important}.mdl-color--amber-500{background-color:#ffc107 !important}.mdl-color-text--amber-600{color:#ffb300 !important}.mdl-color--amber-600{background-color:#ffb300 !important}.mdl-color-text--amber-700{color:#ffa000 !important}.mdl-color--amber-700{background-color:#ffa000 !important}.mdl-color-text--amber-800{color:#ff8f00 !important}.mdl-color--amber-800{background-color:#ff8f00 !important}.mdl-color-text--amber-900{color:#ff6f00 !important}.mdl-color--amber-900{background-color:#ff6f00 !important}.mdl-color-text--amber-A100{color:#ffe57f !important}.mdl-color--amber-A100{background-color:#ffe57f !important}.mdl-color-text--amber-A200{color:#ffd740 !important}.mdl-color--amber-A200{background-color:#ffd740 !important}.mdl-color-text--amber-A400{color:#ffc400 !important}.mdl-color--amber-A400{background-color:#ffc400 !important}.mdl-color-text--amber-A700{color:#ffab00 !important}.mdl-color--amber-A700{background-color:#ffab00 !important}.mdl-color-text--orange{color:#ff9800 !important}.mdl-color--orange{background-color:#ff9800 !important}.mdl-color-text--orange-50{color:#fff3e0 !important}.mdl-color--orange-50{background-color:#fff3e0 !important}.mdl-color-text--orange-100{color:#ffe0b2 !important}.mdl-color--orange-100{background-color:#ffe0b2 !important}.mdl-color-text--orange-200{color:#ffcc80 !important}.mdl-color--orange-200{background-color:#ffcc80 !important}.mdl-color-text--orange-300{color:#ffb74d !important}.mdl-color--orange-300{background-color:#ffb74d !important}.mdl-color-text--orange-400{color:#ffa726 !important}.mdl-color--orange-400{background-color:#ffa726 !important}.mdl-color-text--orange-500{color:#ff9800 !important}.mdl-color--orange-500{background-color:#ff9800 !important}.mdl-color-text--orange-600{color:#fb8c00 !important}.mdl-color--orange-600{background-color:#fb8c00 !important}.mdl-color-text--orange-700{color:#f57c00 !important}.mdl-color--orange-700{background-color:#f57c00 !important}.mdl-color-text--orange-800{color:#ef6c00 !important}.mdl-color--orange-800{background-color:#ef6c00 !important}.mdl-color-text--orange-900{color:#e65100 !important}.mdl-color--orange-900{background-color:#e65100 !important}.mdl-color-text--orange-A100{color:#ffd180 !important}.mdl-color--orange-A100{background-color:#ffd180 !important}.mdl-color-text--orange-A200{color:#ffab40 !important}.mdl-color--orange-A200{background-color:#ffab40 !important}.mdl-color-text--orange-A400{color:#ff9100 !important}.mdl-color--orange-A400{background-color:#ff9100 !important}.mdl-color-text--orange-A700{color:#ff6d00 !important}.mdl-color--orange-A700{background-color:#ff6d00 !important}.mdl-color-text--deep-orange{color:#ff5722 !important}.mdl-color--deep-orange{background-color:#ff5722 !important}.mdl-color-text--deep-orange-50{color:#fbe9e7 !important}.mdl-color--deep-orange-50{background-color:#fbe9e7 !important}.mdl-color-text--deep-orange-100{color:#ffccbc !important}.mdl-color--deep-orange-100{background-color:#ffccbc !important}.mdl-color-text--deep-orange-200{color:#ffab91 !important}.mdl-color--deep-orange-200{background-color:#ffab91 !important}.mdl-color-text--deep-orange-300{color:#ff8a65 !important}.mdl-color--deep-orange-300{background-color:#ff8a65 !important}.mdl-color-text--deep-orange-400{color:#ff7043 !important}.mdl-color--deep-orange-400{background-color:#ff7043 !important}.mdl-color-text--deep-orange-500{color:#ff5722 !important}.mdl-color--deep-orange-500{background-color:#ff5722 !important}.mdl-color-text--deep-orange-600{color:#f4511e !important}.mdl-color--deep-orange-600{background-color:#f4511e !important}.mdl-color-text--deep-orange-700{color:#e64a19 !important}.mdl-color--deep-orange-700{background-color:#e64a19 !important}.mdl-color-text--deep-orange-800{color:#d84315 !important}.mdl-color--deep-orange-800{background-color:#d84315 !important}.mdl-color-text--deep-orange-900{color:#bf360c !important}.mdl-color--deep-orange-900{background-color:#bf360c !important}.mdl-color-text--deep-orange-A100{color:#ff9e80 !important}.mdl-color--deep-orange-A100{background-color:#ff9e80 !important}.mdl-color-text--deep-orange-A200{color:#ff6e40 !important}.mdl-color--deep-orange-A200{background-color:#ff6e40 !important}.mdl-color-text--deep-orange-A400{color:#ff3d00 !important}.mdl-color--deep-orange-A400{background-color:#ff3d00 !important}.mdl-color-text--deep-orange-A700{color:#dd2c00 !important}.mdl-color--deep-orange-A700{background-color:#dd2c00 !important}.mdl-color-text--brown{color:#795548 !important}.mdl-color--brown{background-color:#795548 !important}.mdl-color-text--brown-50{color:#efebe9 !important}.mdl-color--brown-50{background-color:#efebe9 !important}.mdl-color-text--brown-100{color:#d7ccc8 !important}.mdl-color--brown-100{background-color:#d7ccc8 !important}.mdl-color-text--brown-200{color:#bcaaa4 !important}.mdl-color--brown-200{background-color:#bcaaa4 !important}.mdl-color-text--brown-300{color:#a1887f !important}.mdl-color--brown-300{background-color:#a1887f !important}.mdl-color-text--brown-400{color:#8d6e63 !important}.mdl-color--brown-400{background-color:#8d6e63 !important}.mdl-color-text--brown-500{color:#795548 !important}.mdl-color--brown-500{background-color:#795548 !important}.mdl-color-text--brown-600{color:#6d4c41 !important}.mdl-color--brown-600{background-color:#6d4c41 !important}.mdl-color-text--brown-700{color:#5d4037 !important}.mdl-color--brown-700{background-color:#5d4037 !important}.mdl-color-text--brown-800{color:#4e342e !important}.mdl-color--brown-800{background-color:#4e342e !important}.mdl-color-text--brown-900{color:#3e2723 !important}.mdl-color--brown-900{background-color:#3e2723 !important}.mdl-color-text--grey{color:#9e9e9e !important}.mdl-color--grey{background-color:#9e9e9e !important}.mdl-color-text--grey-50{color:#fafafa !important}.mdl-color--grey-50{background-color:#fafafa !important}.mdl-color-text--grey-100{color:#f5f5f5 !important}.mdl-color--grey-100{background-color:#f5f5f5 !important}.mdl-color-text--grey-200{color:#eee !important}.mdl-color--grey-200{background-color:#eee !important}.mdl-color-text--grey-300{color:#e0e0e0 !important}.mdl-color--grey-300{background-color:#e0e0e0 !important}.mdl-color-text--grey-400{color:#bdbdbd !important}.mdl-color--grey-400{background-color:#bdbdbd !important}.mdl-color-text--grey-500{color:#9e9e9e !important}.mdl-color--grey-500{background-color:#9e9e9e !important}.mdl-color-text--grey-600{color:#757575 !important}.mdl-color--grey-600{background-color:#757575 !important}.mdl-color-text--grey-700{color:#616161 !important}.mdl-color--grey-700{background-color:#616161 !important}.mdl-color-text--grey-800{color:#424242 !important}.mdl-color--grey-800{background-color:#424242 !important}.mdl-color-text--grey-900{color:#212121 !important}.mdl-color--grey-900{background-color:#212121 !important}.mdl-color-text--blue-grey{color:#607d8b !important}.mdl-color--blue-grey{background-color:#607d8b !important}.mdl-color-text--blue-grey-50{color:#eceff1 !important}.mdl-color--blue-grey-50{background-color:#eceff1 !important}.mdl-color-text--blue-grey-100{color:#cfd8dc !important}.mdl-color--blue-grey-100{background-color:#cfd8dc !important}.mdl-color-text--blue-grey-200{color:#b0bec5 !important}.mdl-color--blue-grey-200{background-color:#b0bec5 !important}.mdl-color-text--blue-grey-300{color:#90a4ae !important}.mdl-color--blue-grey-300{background-color:#90a4ae !important}.mdl-color-text--blue-grey-400{color:#78909c !important}.mdl-color--blue-grey-400{background-color:#78909c !important}.mdl-color-text--blue-grey-500{color:#607d8b !important}.mdl-color--blue-grey-500{background-color:#607d8b !important}.mdl-color-text--blue-grey-600{color:#546e7a !important}.mdl-color--blue-grey-600{background-color:#546e7a !important}.mdl-color-text--blue-grey-700{color:#455a64 !important}.mdl-color--blue-grey-700{background-color:#455a64 !important}.mdl-color-text--blue-grey-800{color:#37474f !important}.mdl-color--blue-grey-800{background-color:#37474f !important}.mdl-color-text--blue-grey-900{color:#263238 !important}.mdl-color--blue-grey-900{background-color:#263238 !important}.mdl-color--black{background-color:#000 !important}.mdl-color-text--black{color:#000 !important}.mdl-color--white{background-color:#fff !important}.mdl-color-text--white{color:#fff !important}.mdl-color--primary{background-color:rgb(158,158,158)!important}.mdl-color--primary-contrast{background-color:rgb(66,66,66)!important}.mdl-color--primary-dark{background-color:rgb(97,97,97)!important}.mdl-color--accent{background-color:rgb(255,110,64)!important}.mdl-color--accent-contrast{background-color:rgb(66,66,66)!important}.mdl-color-text--primary{color:rgb(158,158,158)!important}.mdl-color-text--primary-contrast{color:rgb(66,66,66)!important}.mdl-color-text--primary-dark{color:rgb(97,97,97)!important}.mdl-color-text--accent{color:rgb(255,110,64)!important}.mdl-color-text--accent-contrast{color:rgb(66,66,66)!important}.mdl-ripple{background:#000;border-radius:50%;height:50px;left:0;opacity:0;pointer-events:none;position:absolute;top:0;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:50px;overflow:hidden}.mdl-ripple.is-animating{-webkit-transition:-webkit-transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1);transition:transform .3s cubic-bezier(0,0,.2,1),width .3s cubic-bezier(0,0,.2,1),height .3s cubic-bezier(0,0,.2,1),opacity .6s cubic-bezier(0,0,.2,1)}.mdl-ripple.is-visible{opacity:.3}.mdl-animation--default,.mdl-animation--fast-out-slow-in{-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-animation--linear-out-slow-in{-webkit-transition-timing-function:cubic-bezier(0,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1)}.mdl-animation--fast-out-linear-in{-webkit-transition-timing-function:cubic-bezier(.4,0,1,1);transition-timing-function:cubic-bezier(.4,0,1,1)}.mdl-badge{position:relative;white-space:nowrap;margin-right:24px}.mdl-badge:not([data-badge]){margin-right:auto}.mdl-badge[data-badge]:after{content:attr(data-badge);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:center;-ms-flex-line-pack:center;align-content:center;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:absolute;top:-11px;right:-24px;font-family:"Roboto","Helvetica","Arial",sans-serif;font-weight:600;font-size:12px;width:22px;height:22px;border-radius:50%;background:rgb(255,110,64);color:#fff}.mdl-button .mdl-badge[data-badge]:after{top:-10px;right:-5px}.mdl-badge.mdl-badge--no-background[data-badge]:after{color:rgb(255,110,64);background:rgba(255,255,255,.2);box-shadow:0 0 1px gray}.mdl-button{background:0 0;border:none;border-radius:2px;color:#000;position:relative;height:36px;min-width:64px;padding:0 8px;display:inline-block;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;letter-spacing:0;overflow:hidden;will-change:box-shadow,transform;-webkit-transition:box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);transition:box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);outline:none;cursor:pointer;text-decoration:none;text-align:center;line-height:36px;vertical-align:middle}.mdl-button::-moz-focus-inner{border:0}.mdl-button:hover{background-color:rgba(158,158,158,.2)}.mdl-button:focus:not(:active){background-color:rgba(0,0,0,.12)}.mdl-button:active{background-color:rgba(158,158,158,.4)}.mdl-button.mdl-button--colored{color:rgb(158,158,158)}.mdl-button.mdl-button--colored:focus:not(:active){background-color:rgba(0,0,0,.12)}input.mdl-button[type="submit"]{-webkit-appearance:none}.mdl-button--raised{background:rgba(158,158,158,.2);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-button--raised:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--raised:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--raised.mdl-button--colored{background:rgb(158,158,158);color:rgb(66,66,66)}.mdl-button--raised.mdl-button--colored:hover{background-color:rgb(158,158,158)}.mdl-button--raised.mdl-button--colored:active{background-color:rgb(158,158,158)}.mdl-button--raised.mdl-button--colored:focus:not(:active){background-color:rgb(158,158,158)}.mdl-button--raised.mdl-button--colored .mdl-ripple{background:rgb(66,66,66)}.mdl-button--fab{border-radius:50%;font-size:24px;height:56px;margin:auto;min-width:56px;width:56px;padding:0;overflow:hidden;background:rgba(158,158,158,.2);box-shadow:0 1px 1.5px 0 rgba(0,0,0,.12),0 1px 1px 0 rgba(0,0,0,.24);position:relative;line-height:normal}.mdl-button--fab .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);-ms-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--fab.mdl-button--mini-fab{height:40px;min-width:40px;width:40px}.mdl-button--fab .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button--fab:active{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2);background-color:rgba(158,158,158,.4)}.mdl-button--fab:focus:not(:active){box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36);background-color:rgba(158,158,158,.4)}.mdl-button--fab.mdl-button--colored{background:rgb(255,110,64);color:rgb(66,66,66)}.mdl-button--fab.mdl-button--colored:hover{background-color:rgb(255,110,64)}.mdl-button--fab.mdl-button--colored:focus:not(:active){background-color:rgb(255,110,64)}.mdl-button--fab.mdl-button--colored:active{background-color:rgb(255,110,64)}.mdl-button--fab.mdl-button--colored .mdl-ripple{background:rgb(66,66,66)}.mdl-button--icon{border-radius:50%;font-size:24px;height:32px;margin-left:0;margin-right:0;min-width:32px;width:32px;padding:0;overflow:hidden;color:inherit;line-height:normal}.mdl-button--icon .material-icons{position:absolute;top:50%;left:50%;-webkit-transform:translate(-12px,-12px);-ms-transform:translate(-12px,-12px);transform:translate(-12px,-12px);line-height:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon{height:24px;min-width:24px;width:24px}.mdl-button--icon.mdl-button--mini-icon .material-icons{top:0;left:0}.mdl-button--icon .mdl-button__ripple-container{border-radius:50%;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-button__ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-button[disabled] .mdl-button__ripple-container .mdl-ripple,.mdl-button.mdl-button--disabled .mdl-button__ripple-container .mdl-ripple{background-color:transparent}.mdl-button--primary.mdl-button--primary{color:rgb(158,158,158)}.mdl-button--primary.mdl-button--primary .mdl-ripple{background:rgb(66,66,66)}.mdl-button--primary.mdl-button--primary.mdl-button--raised,.mdl-button--primary.mdl-button--primary.mdl-button--fab{color:rgb(66,66,66);background-color:rgb(158,158,158)}.mdl-button--accent.mdl-button--accent{color:rgb(255,110,64)}.mdl-button--accent.mdl-button--accent .mdl-ripple{background:rgb(66,66,66)}.mdl-button--accent.mdl-button--accent.mdl-button--raised,.mdl-button--accent.mdl-button--accent.mdl-button--fab{color:rgb(66,66,66);background-color:rgb(255,110,64)}.mdl-button[disabled][disabled],.mdl-button.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26);cursor:auto;background-color:transparent}.mdl-button--fab[disabled][disabled],.mdl-button--fab.mdl-button--disabled.mdl-button--disabled,.mdl-button--raised[disabled][disabled],.mdl-button--raised.mdl-button--disabled.mdl-button--disabled{background-color:rgba(0,0,0,.12);color:rgba(0,0,0,.26);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-button--colored[disabled][disabled],.mdl-button--colored.mdl-button--disabled.mdl-button--disabled{color:rgba(0,0,0,.26)}.mdl-button .material-icons{vertical-align:middle}.mdl-card{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;font-size:16px;font-weight:400;min-height:200px;overflow:hidden;width:330px;z-index:1;position:relative;background:#fff;border-radius:2px;box-sizing:border-box}.mdl-card__media{background-color:rgb(255,110,64);background-repeat:repeat;background-position:50% 50%;background-size:cover;background-origin:padding-box;background-attachment:scroll;box-sizing:border-box}.mdl-card__title{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;color:#000;display:block;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:stretch;-webkit-justify-content:stretch;-ms-flex-pack:stretch;justify-content:stretch;line-height:normal;padding:16px;-webkit-perspective-origin:165px 56px;perspective-origin:165px 56px;-webkit-transform-origin:165px 56px;-ms-transform-origin:165px 56px;transform-origin:165px 56px;box-sizing:border-box}.mdl-card__title.mdl-card--border{border-bottom:1px solid rgba(0,0,0,.1)}.mdl-card__title-text{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;color:inherit;display:block;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:24px;font-weight:300;line-height:normal;overflow:hidden;-webkit-transform-origin:149px 48px;-ms-transform-origin:149px 48px;transform-origin:149px 48px;margin:0}.mdl-card__subtitle-text{font-size:14px;color:grey;margin:0}.mdl-card__supporting-text{color:rgba(0,0,0,.54);font-size:13px;line-height:18px;overflow:hidden;padding:16px;width:90%}.mdl-card__actions{font-size:16px;line-height:normal;width:100%;background-color:transparent;padding:8px;box-sizing:border-box}.mdl-card__actions.mdl-card--border{border-top:1px solid rgba(0,0,0,.1)}.mdl-card--expand{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-card__menu{position:absolute;right:16px;top:16px}.mdl-checkbox{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0}.mdl-checkbox.is-upgraded{padding-left:24px}.mdl-checkbox__input{line-height:24px}.mdl-checkbox.is-upgraded .mdl-checkbox__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-checkbox__box-outline{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;overflow:hidden;border:2px solid rgba(0,0,0,.54);border-radius:2px;z-index:2}.mdl-checkbox.is-checked .mdl-checkbox__box-outline{border:2px solid rgb(158,158,158)}.mdl-checkbox.is-disabled .mdl-checkbox__box-outline{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__focus-helper{position:absolute;top:3px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;border-radius:50%;background-color:transparent}.mdl-checkbox.is-focused .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-checkbox.is-focused.is-checked .mdl-checkbox__focus-helper{box-shadow:0 0 0 8px rgba(158,158,158,.26);background-color:rgba(158,158,158,.26)}.mdl-checkbox__tick-outline{position:absolute;top:0;left:0;height:100%;width:100%;-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iY2xpcCI+CiAgICAgIDxwYXRoCiAgICAgICAgIGQ9Ik0gMCwwIDAsMSAxLDEgMSwwIDAsMCB6IE0gMC44NTM0Mzc1LDAuMTY3MTg3NSAwLjk1OTY4NzUsMC4yNzMxMjUgMC40MjkzNzUsMC44MDM0Mzc1IDAuMzIzMTI1LDAuOTA5Njg3NSAwLjIxNzE4NzUsMC44MDM0Mzc1IDAuMDQwMzEyNSwwLjYyNjg3NSAwLjE0NjU2MjUsMC41MjA2MjUgMC4zMjMxMjUsMC42OTc1IDAuODUzNDM3NSwwLjE2NzE4NzUgeiIKICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8bWFzayBpZD0ibWFzayIgbWFza1VuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgbWFza0NvbnRlbnRVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giPgogICAgICA8cGF0aAogICAgICAgICBkPSJNIDAsMCAwLDEgMSwxIDEsMCAwLDAgeiBNIDAuODUzNDM3NSwwLjE2NzE4NzUgMC45NTk2ODc1LDAuMjczMTI1IDAuNDI5Mzc1LDAuODAzNDM3NSAwLjMyMzEyNSwwLjkwOTY4NzUgMC4yMTcxODc1LDAuODAzNDM3NSAwLjA0MDMxMjUsMC42MjY4NzUgMC4xNDY1NjI1LDAuNTIwNjI1IDAuMzIzMTI1LDAuNjk3NSAwLjg1MzQzNzUsMC4xNjcxODc1IHoiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmUiIC8+CiAgICA8L21hc2s+CiAgPC9kZWZzPgogIDxyZWN0CiAgICAgd2lkdGg9IjEiCiAgICAgaGVpZ2h0PSIxIgogICAgIHg9IjAiCiAgICAgeT0iMCIKICAgICBjbGlwLXBhdGg9InVybCgjY2xpcCkiCiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgLz4KPC9zdmc+Cg==");background:0 0;-webkit-transition-duration:.28s;transition-duration:.28s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1);-webkit-transition-property:background;transition-property:background}.mdl-checkbox.is-checked .mdl-checkbox__tick-outline{background:rgb(158,158,158)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}.mdl-checkbox.is-checked.is-disabled .mdl-checkbox__tick-outline{background:rgba(0,0,0,.26)url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMSAxIgogICBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij4KICA8cGF0aAogICAgIGQ9Ik0gMC4wNDAzODA1OSwwLjYyNjc3NjcgMC4xNDY0NDY2MSwwLjUyMDcxMDY4IDAuNDI5Mjg5MzIsMC44MDM1NTMzOSAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IE0gMC4yMTcxNTcyOSwwLjgwMzU1MzM5IDAuODUzNTUzMzksMC4xNjcxNTcyOSAwLjk1OTYxOTQxLDAuMjczMjIzMyAwLjMyMzIyMzMsMC45MDk2MTk0MSB6IgogICAgIGlkPSJyZWN0Mzc4MCIKICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lIiAvPgo8L3N2Zz4K")}.mdl-checkbox__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0}.mdl-checkbox.is-disabled .mdl-checkbox__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-checkbox__ripple-container{position:absolute;z-index:2;top:-6px;left:-10px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-checkbox__ripple-container .mdl-ripple{background:rgb(158,158,158)}.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container{cursor:auto}.mdl-checkbox.is-disabled .mdl-checkbox__ripple-container .mdl-ripple{background:0 0}.mdl-data-table{position:relative;border:1px solid rgba(0,0,0,.12);border-collapse:collapse;white-space:nowrap;font-size:13px;background-color:#fff}.mdl-data-table thead{padding-bottom:3px}.mdl-data-table thead .mdl-data-table__select{margin-top:0}.mdl-data-table tbody tr{position:relative;height:48px;-webkit-transition-duration:.28s;transition-duration:.28s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1);-webkit-transition-property:background-color;transition-property:background-color}.mdl-data-table tbody tr.is-selected{background-color:#e0e0e0}.mdl-data-table tbody tr:hover{background-color:#eee}.mdl-data-table td{text-align:right}.mdl-data-table th{padding:0 18px 0 18px;text-align:right}.mdl-data-table td:first-of-type,.mdl-data-table th:first-of-type{padding-left:24px}.mdl-data-table td:last-of-type,.mdl-data-table th:last-of-type{padding-right:24px}.mdl-data-table td{position:relative;vertical-align:top;height:48px;border-top:1px solid rgba(0,0,0,.12);border-bottom:1px solid rgba(0,0,0,.12);padding:12px 18px 0;box-sizing:border-box}.mdl-data-table td .mdl-data-table__select{vertical-align:top;position:absolute;left:24px}.mdl-data-table th{position:relative;vertical-align:bottom;text-overflow:ellipsis;font-weight:700;line-height:24px;letter-spacing:0;height:48px;font-size:12px;color:rgba(0,0,0,.54);padding-bottom:8px;box-sizing:border-box}.mdl-data-table th .mdl-data-table__select{position:absolute;bottom:8px;left:24px}.mdl-data-table__select{width:16px}.mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric{text-align:left}.mdl-mega-footer{padding:16px 40px;color:#9e9e9e;background-color:#424242}.mdl-mega-footer--top-section:after,.mdl-mega-footer--middle-section:after,.mdl-mega-footer--bottom-section:after,.mdl-mega-footer__top-section:after,.mdl-mega-footer__middle-section:after,.mdl-mega-footer__bottom-section:after{content:'';display:block;clear:both}.mdl-mega-footer--left-section,.mdl-mega-footer__left-section,.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{margin-bottom:16px}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:block;margin-bottom:16px;color:inherit;text-decoration:none}@media screen and (min-width:760px){.mdl-mega-footer--left-section,.mdl-mega-footer__left-section{float:left}.mdl-mega-footer--right-section,.mdl-mega-footer__right-section{float:right}.mdl-mega-footer--right-section a,.mdl-mega-footer__right-section a{display:inline-block;margin-left:16px;line-height:36px;vertical-align:middle}}.mdl-mega-footer--social-btn,.mdl-mega-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{display:block;position:relative}@media screen and (min-width:760px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer__drop-down-section{width:33%}.mdl-mega-footer--drop-down-section:nth-child(1),.mdl-mega-footer--drop-down-section:nth-child(2),.mdl-mega-footer__drop-down-section:nth-child(1),.mdl-mega-footer__drop-down-section:nth-child(2){float:left}.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(3){float:right}.mdl-mega-footer--drop-down-section:nth-child(3):after,.mdl-mega-footer__drop-down-section:nth-child(3):after{clear:right}.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section:nth-child(4){clear:right;float:right}.mdl-mega-footer--middle-section:after,.mdl-mega-footer__middle-section:after{content:'';display:block;clear:both}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:0}}@media screen and (min-width:1024px){.mdl-mega-footer--drop-down-section,.mdl-mega-footer--drop-down-section:nth-child(3),.mdl-mega-footer--drop-down-section:nth-child(4),.mdl-mega-footer__drop-down-section,.mdl-mega-footer__drop-down-section:nth-child(3),.mdl-mega-footer__drop-down-section:nth-child(4){width:24%;float:left}}.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{position:absolute;width:100%;height:55.8px;padding:32px;margin:-16px 0 0;cursor:pointer;z-index:1;opacity:0}.mdl-mega-footer--heading-checkbox~.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox~.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox~.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox~.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CE'}.mdl-mega-footer--heading-checkbox:checked~ul,.mdl-mega-footer__heading-checkbox:checked~ul{display:none}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__heading:after{font-family:'Material Icons';content:'\E5CF'}.mdl-mega-footer--heading,.mdl-mega-footer__heading{position:relative;width:100%;padding-right:39.8px;margin-bottom:16px;box-sizing:border-box;font-size:14px;line-height:23.8px;font-weight:500;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:#e0e0e0}.mdl-mega-footer--heading:after,.mdl-mega-footer__heading:after{content:'';position:absolute;top:0;right:0;display:block;width:23.8px;height:23.8px;background-size:cover}.mdl-mega-footer--link-list,.mdl-mega-footer__link-list{list-style:none;padding:0;margin:0 0 32px}.mdl-mega-footer--link-list:after,.mdl-mega-footer__link-list:after{clear:both;display:block;content:''}.mdl-mega-footer--link-list li,.mdl-mega-footer__link-list li{font-size:14px;font-weight:400;letter-spacing:0;line-height:20px}.mdl-mega-footer--link-list a,.mdl-mega-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}@media screen and (min-width:760px){.mdl-mega-footer--heading-checkbox,.mdl-mega-footer__heading-checkbox{display:none}.mdl-mega-footer--heading-checkbox~.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox~.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox~.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox~.mdl-mega-footer__heading:after{background-image:none}.mdl-mega-footer--heading-checkbox:checked~ul,.mdl-mega-footer__heading-checkbox:checked~ul{display:block}.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer--heading:after,.mdl-mega-footer--heading-checkbox:checked~.mdl-mega-footer__heading:after,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer--heading:after,.mdl-mega-footer__heading-checkbox:checked~.mdl-mega-footer__heading:after{content:''}}.mdl-mega-footer--bottom-section,.mdl-mega-footer__bottom-section{padding-top:16px;margin-bottom:16px}.mdl-logo{margin-bottom:16px;color:#fff}.mdl-mega-footer--bottom-section .mdl-mega-footer--link-list li,.mdl-mega-footer__bottom-section .mdl-mega-footer__link-list li{float:left;margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-logo{float:left;margin-bottom:0;margin-right:16px}}.mdl-mini-footer{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:32px 16px;color:#9e9e9e;background-color:#424242}.mdl-mini-footer:after{content:'';display:block}.mdl-mini-footer .mdl-logo{line-height:36px}.mdl-mini-footer--link-list,.mdl-mini-footer__link-list{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;list-style:none;margin:0;padding:0}.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{margin-bottom:0;margin-right:16px}@media screen and (min-width:760px){.mdl-mini-footer--link-list li,.mdl-mini-footer__link-list li{line-height:36px}}.mdl-mini-footer--link-list a,.mdl-mini-footer__link-list a{color:inherit;text-decoration:none;white-space:nowrap}.mdl-mini-footer--left-section,.mdl-mini-footer__left-section{display:inline-block;-webkit-box-ordinal-group:1;-webkit-order:0;-ms-flex-order:0;order:0}.mdl-mini-footer--right-section,.mdl-mini-footer__right-section{display:inline-block;-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.mdl-mini-footer--social-btn,.mdl-mini-footer__social-btn{width:36px;height:36px;padding:0;margin:0;background-color:#9e9e9e;border:none}.mdl-icon-toggle{position:relative;z-index:1;vertical-align:middle;display:inline-block;height:32px;margin:0;padding:0}.mdl-icon-toggle__input{line-height:32px}.mdl-icon-toggle.is-upgraded .mdl-icon-toggle__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-icon-toggle__label{display:inline-block;position:relative;cursor:pointer;height:32px;width:32px;min-width:32px;color:#616161;border-radius:50%;padding:0;margin-left:0;margin-right:0;text-align:center;background-color:transparent;will-change:background-color;-webkit-transition:background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);transition:background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1)}.mdl-icon-toggle__label.material-icons{line-height:32px;font-size:24px}.mdl-icon-toggle.is-checked .mdl-icon-toggle__label{color:rgb(158,158,158)}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__label{color:rgba(0,0,0,.26);cursor:auto;-webkit-transition:none;transition:none}.mdl-icon-toggle.is-focused .mdl-icon-toggle__label{background-color:rgba(0,0,0,.12)}.mdl-icon-toggle.is-focused.is-checked .mdl-icon-toggle__label{background-color:rgba(158,158,158,.26)}.mdl-icon-toggle__ripple-container{position:absolute;z-index:2;top:-2px;left:-2px;box-sizing:border-box;width:36px;height:36px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-icon-toggle__ripple-container .mdl-ripple{background:#616161}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container{cursor:auto}.mdl-icon-toggle.is-disabled .mdl-icon-toggle__ripple-container .mdl-ripple{background:0 0}.mdl-menu__container{display:block;margin:0;padding:0;border:none;position:absolute;overflow:visible;height:0;width:0;visibility:hidden;z-index:-1}.mdl-menu__container.is-visible,.mdl-menu__container.is-animating{z-index:999;visibility:visible}.mdl-menu__outline{display:block;background:#fff;margin:0;padding:0;border:none;border-radius:2px;position:absolute;top:0;left:0;overflow:hidden;opacity:0;-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);will-change:transform;-webkit-transition:-webkit-transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1);transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1);z-index:-1}.mdl-menu__container.is-visible .mdl-menu__outline{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1);z-index:999}.mdl-menu__outline.mdl-menu--bottom-right{-webkit-transform-origin:100% 0;-ms-transform-origin:100% 0;transform-origin:100% 0}.mdl-menu__outline.mdl-menu--top-left{-webkit-transform-origin:0 100%;-ms-transform-origin:0 100%;transform-origin:0 100%}.mdl-menu__outline.mdl-menu--top-right{-webkit-transform-origin:100% 100%;-ms-transform-origin:100% 100%;transform-origin:100% 100%}.mdl-menu{position:absolute;list-style:none;top:0;left:0;height:auto;width:auto;min-width:124px;padding:8px 0;margin:0;opacity:0;clip:rect(0 0 0 0);z-index:-1}.mdl-menu__container.is-visible .mdl-menu{opacity:1;z-index:999}.mdl-menu.is-animating{-webkit-transition:opacity .2s cubic-bezier(.4,0,.2,1),clip .3s cubic-bezier(.4,0,.2,1);transition:opacity .2s cubic-bezier(.4,0,.2,1),clip .3s cubic-bezier(.4,0,.2,1)}.mdl-menu.mdl-menu--bottom-right{left:auto;right:0}.mdl-menu.mdl-menu--top-left{top:auto;bottom:0}.mdl-menu.mdl-menu--top-right{top:auto;left:auto;bottom:0;right:0}.mdl-menu.mdl-menu--unaligned{top:auto;left:auto}.mdl-menu__item{display:block;border:none;color:rgba(0,0,0,.87);background-color:transparent;text-align:left;margin:0;padding:0 16px;outline-color:#bdbdbd;position:relative;overflow:hidden;font-size:14px;font-weight:400;letter-spacing:0;text-decoration:none;cursor:pointer;height:48px;line-height:48px;white-space:nowrap;opacity:0;-webkit-transition:opacity .2s cubic-bezier(.4,0,.2,1);transition:opacity .2s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-menu__container.is-visible .mdl-menu__item{opacity:1}.mdl-menu__item::-moz-focus-inner{border:0}.mdl-menu__item[disabled]{color:#bdbdbd;background-color:transparent;cursor:auto}.mdl-menu__item[disabled]:hover{background-color:transparent}.mdl-menu__item[disabled]:focus{background-color:transparent}.mdl-menu__item[disabled] .mdl-ripple{background:0 0}.mdl-menu__item:hover{background-color:#eee}.mdl-menu__item:focus{outline:none;background-color:#eee}.mdl-menu__item:active{background-color:#e0e0e0}.mdl-menu__item--ripple-container{display:block;height:100%;left:0;position:absolute;top:0;width:100%;z-index:0;overflow:hidden}.mdl-progress{display:block;position:relative;height:4px;width:500px}.mdl-progress>.bar{display:block;position:absolute;top:0;bottom:0;width:0%;-webkit-transition:width .2s cubic-bezier(.4,0,.2,1);transition:width .2s cubic-bezier(.4,0,.2,1)}.mdl-progress>.progressbar{background-color:rgb(158,158,158);z-index:1;left:0}.mdl-progress>.bufferbar{background-image:-webkit-linear-gradient(left,rgba(66,66,66,.7),rgba(66,66,66,.7)),-webkit-linear-gradient(left,rgb(158,158,158),rgb(158,158,158));background-image:linear-gradient(to right,rgba(66,66,66,.7),rgba(66,66,66,.7)),linear-gradient(to right,rgb(158,158,158),rgb(158,158,158));z-index:0;left:0}.mdl-progress>.auxbar{right:0}@supports (-webkit-appearance:none){.mdl-progress:not(.mdl-progress__indeterminate):not(.mdl-progress__indeterminate)>.auxbar{background-image:-webkit-linear-gradient(left,rgba(66,66,66,.7),rgba(66,66,66,.7)),-webkit-linear-gradient(left,rgb(158,158,158),rgb(158,158,158));background-image:linear-gradient(to right,rgba(66,66,66,.7),rgba(66,66,66,.7)),linear-gradient(to right,rgb(158,158,158),rgb(158,158,158));-webkit-mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=");mask:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjEyIiBoZWlnaHQ9IjQiIHZpZXdQb3J0PSIwIDAgMTIgNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxlbGxpcHNlIGN4PSIyIiBjeT0iMiIgcng9IjIiIHJ5PSIyIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN4IiBmcm9tPSIyIiB0bz0iLTEwIiBkdXI9IjAuNnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPgogIDwvZWxsaXBzZT4KICA8ZWxsaXBzZSBjeD0iMTQiIGN5PSIyIiByeD0iMiIgcnk9IjIiIGNsYXNzPSJsb2FkZXIiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3giIGZyb209IjE0IiB0bz0iMiIgZHVyPSIwLjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4KICA8L2VsbGlwc2U+Cjwvc3ZnPgo=")}}.mdl-progress:not(.mdl-progress__indeterminate)>.auxbar{background-image:-webkit-linear-gradient(left,rgba(66,66,66,.9),rgba(66,66,66,.9)),-webkit-linear-gradient(left,rgb(158,158,158),rgb(158,158,158));background-image:linear-gradient(to right,rgba(66,66,66,.9),rgba(66,66,66,.9)),linear-gradient(to right,rgb(158,158,158),rgb(158,158,158))}.mdl-progress.mdl-progress__indeterminate>.bar1{-webkit-animation-name:indeterminate1;animation-name:indeterminate1}.mdl-progress.mdl-progress__indeterminate>.bar1,.mdl-progress.mdl-progress__indeterminate>.bar3{background-color:rgb(158,158,158);-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear}.mdl-progress.mdl-progress__indeterminate>.bar3{background-image:none;-webkit-animation-name:indeterminate2;animation-name:indeterminate2}@-webkit-keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@keyframes indeterminate1{0%{left:0%;width:0%}50%{left:25%;width:75%}75%{left:100%;width:0%}}@-webkit-keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}@keyframes indeterminate2{0%,50%{left:0%;width:0%}75%{left:0%;width:25%}100%{left:100%;width:0%}}.mdl-navigation{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;box-sizing:border-box}.mdl-navigation__link{color:#424242;text-decoration:none;font-weight:500;font-size:13px;margin:0}.mdl-layout{width:100%;height:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;overflow-y:auto;overflow-x:hidden;position:relative;-webkit-overflow-scrolling:touch}.mdl-layout.is-small-screen .mdl-layout--large-screen-only{display:none}.mdl-layout:not(.is-small-screen) .mdl-layout--small-screen-only{display:none}.mdl-layout__container{position:absolute;width:100%;height:100%}.mdl-layout-title{display:block;position:relative;font-family:"Roboto","Helvetica","Arial",sans-serif;font-size:20px;line-height:1;letter-spacing:.02em;font-weight:400;box-sizing:border-box}.mdl-layout-spacer{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.mdl-layout__drawer{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:240px;height:100%;max-height:100%;position:absolute;top:0;left:0;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);box-sizing:border-box;border-right:1px solid #e0e0e0;background:#fafafa;-webkit-transform:translateX(-250px);-ms-transform:translateX(-250px);transform:translateX(-250px);-webkit-transform-style:preserve-3d;transform-style:preserve-3d;will-change:transform;-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1);-webkit-transition-property:-webkit-transform;transition-property:transform;color:#424242;overflow:visible;overflow-y:auto;z-index:5}.mdl-layout__drawer.is-visible{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}.mdl-layout__drawer>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__drawer>.mdl-layout-title{line-height:64px;padding-left:40px}@media screen and (max-width:1024px){.mdl-layout__drawer>.mdl-layout-title{line-height:56px;padding-left:16px}}.mdl-layout__drawer .mdl-navigation{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;padding-top:16px}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{display:block;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;padding:16px 40px;margin:0;color:#757575}@media screen and (max-width:1024px){.mdl-layout__drawer .mdl-navigation .mdl-navigation__link{padding:16px}}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link:hover{background-color:#e0e0e0}.mdl-layout__drawer .mdl-navigation .mdl-navigation__link--current{background-color:#000;color:rgb(158,158,158)}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__drawer{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}}.mdl-layout__drawer-button{display:block;position:absolute;height:48px;width:48px;border:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden;text-align:center;cursor:pointer;font-size:26px;line-height:50px;font-family:Helvetica,Arial,sans-serif;margin:10px 12px;top:0;left:0;color:rgb(66,66,66);z-index:4}.mdl-layout__header .mdl-layout__drawer-button{position:absolute;color:rgb(66,66,66);background-color:inherit}@media screen and (max-width:1024px){.mdl-layout__header .mdl-layout__drawer-button{margin:4px}}@media screen and (max-width:1024px){.mdl-layout__drawer-button{margin:4px;color:rgba(0,0,0,.5)}}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__drawer-button{display:none}}.mdl-layout__header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start;box-sizing:border-box;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;width:100%;margin:0;padding:0;border:none;min-height:64px;max-height:1000px;z-index:3;background-color:rgb(158,158,158);color:rgb(66,66,66);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1);-webkit-transition-property:max-height,box-shadow;transition-property:max-height,box-shadow}@media screen and (max-width:1024px){.mdl-layout__header{min-height:56px}}.mdl-layout--fixed-drawer.is-upgraded:not(.is-small-screen)>.mdl-layout__header{margin-left:240px;width:calc(100% - 240px)}@media screen and (min-width:1025px){.mdl-layout--fixed-drawer>.mdl-layout__header .mdl-layout__header-row{padding-left:40px}}.mdl-layout__header>.mdl-layout-icon{position:absolute;left:40px;top:16px;height:32px;width:32px;overflow:hidden;z-index:3;display:block}@media screen and (max-width:1024px){.mdl-layout__header>.mdl-layout-icon{left:16px;top:12px}}.mdl-layout.has-drawer .mdl-layout__header>.mdl-layout-icon{display:none}.mdl-layout__header.is-compact{max-height:64px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact{max-height:56px}}.mdl-layout__header.is-compact.has-tabs{height:112px}@media screen and (max-width:1024px){.mdl-layout__header.is-compact.has-tabs{min-height:104px}}@media screen and (max-width:1024px){.mdl-layout__header{display:none}.mdl-layout--fixed-header>.mdl-layout__header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}}.mdl-layout__header--transparent.mdl-layout__header--transparent{background-color:transparent;box-shadow:none}.mdl-layout__header--seamed,.mdl-layout__header--scroll{box-shadow:none}.mdl-layout__header--waterfall{box-shadow:none;overflow:hidden}.mdl-layout__header--waterfall.is-casting-shadow{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-layout__header-row{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;box-sizing:border-box;-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:64px;margin:0;padding:0 40px 0 80px}@media screen and (max-width:1024px){.mdl-layout__header-row{height:56px;padding:0 16px 0 72px}}.mdl-layout__header-row>*{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0}.mdl-layout__header--scroll .mdl-layout__header-row{width:100%}.mdl-layout__header-row .mdl-navigation{margin:0;padding:0;height:64px;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation{height:56px}}.mdl-layout__header-row .mdl-navigation__link{display:block;color:rgb(66,66,66);line-height:64px;padding:0 24px}@media screen and (max-width:1024px){.mdl-layout__header-row .mdl-navigation__link{line-height:56px;padding:0 16px}}.mdl-layout__obfuscator{background-color:transparent;position:absolute;top:0;left:0;height:100%;width:100%;z-index:4;visibility:hidden;-webkit-transition-property:background-color;transition-property:background-color;-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-layout__drawer.is-visible~.mdl-layout__obfuscator{background-color:rgba(0,0,0,.5);visibility:visible}.mdl-layout__content{-ms-flex:0 1 auto;display:inline-block;overflow-y:auto;overflow-x:hidden;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;z-index:1;-webkit-overflow-scrolling:touch}.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:240px}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow:visible}@media screen and (max-width:1024px){.mdl-layout--fixed-drawer>.mdl-layout__content{margin-left:0}.mdl-layout__container.has-scrolling-header .mdl-layout__content{overflow-y:auto;overflow-x:hidden}}.mdl-layout__tab-bar{height:96px;margin:0;width:calc(100% - 112px);padding:0 0 0 56px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:rgb(158,158,158);overflow-y:hidden;overflow-x:scroll}.mdl-layout__tab-bar::-webkit-scrollbar{display:none}@media screen and (max-width:1024px){.mdl-layout__tab-bar{width:calc(100% - 60px);padding:0 0 0 60px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar{padding:0;overflow:hidden;width:100%}.mdl-layout__tab-bar-container{position:relative;height:48px;width:100%;border:none;margin:0;z-index:2;-webkit-box-flex:0;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;overflow:hidden}.mdl-layout__container>.mdl-layout__tab-bar-container{position:absolute;top:0;left:0}.mdl-layout__tab-bar-button{display:inline-block;position:absolute;top:0;height:48px;width:56px;z-index:4;text-align:center;background-color:rgb(158,158,158);color:transparent;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media screen and (max-width:1024px){.mdl-layout__tab-bar-button{display:none;width:60px}}.mdl-layout--fixed-tabs .mdl-layout__tab-bar-button{display:none}.mdl-layout__tab-bar-button .material-icons{line-height:48px}.mdl-layout__tab-bar-button.is-active{color:rgb(66,66,66)}.mdl-layout__tab-bar-left-button{left:0}.mdl-layout__tab-bar-right-button{right:0}.mdl-layout__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;-webkit-box-flex:0;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(66,66,66,.6);overflow:hidden}@media screen and (max-width:1024px){.mdl-layout__tab{padding:0 12px}}.mdl-layout--fixed-tabs .mdl-layout__tab{float:none;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;padding:0}.mdl-layout.is-upgraded .mdl-layout__tab.is-active{color:rgb(66,66,66)}.mdl-layout.is-upgraded .mdl-layout__tab.is-active::after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(255,110,64);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;-webkit-transition:all 1s cubic-bezier(.4,0,1,1);transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-layout__tab .mdl-layout__tab-ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-layout__tab .mdl-layout__tab-ripple-container .mdl-ripple{background-color:rgb(66,66,66)}.mdl-layout__tab-panel{display:block}.mdl-layout.is-upgraded .mdl-layout__tab-panel{display:none}.mdl-layout.is-upgraded .mdl-layout__tab-panel.is-active{display:block}.mdl-radio{position:relative;font-size:16px;line-height:24px;display:inline-block;box-sizing:border-box;margin:0;padding-left:0}.mdl-radio.is-upgraded{padding-left:24px}.mdl-radio__button{line-height:24px}.mdl-radio.is-upgraded .mdl-radio__button{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-radio__outer-circle{position:absolute;top:4px;left:0;display:inline-block;box-sizing:border-box;width:16px;height:16px;margin:0;cursor:pointer;border:2px solid rgba(0,0,0,.54);border-radius:50%;z-index:2}.mdl-radio.is-checked .mdl-radio__outer-circle{border:2px solid rgb(158,158,158)}.mdl-radio.is-disabled .mdl-radio__outer-circle{border:2px solid rgba(0,0,0,.26);cursor:auto}.mdl-radio__inner-circle{position:absolute;z-index:1;margin:0;top:8px;left:4px;box-sizing:border-box;width:8px;height:8px;cursor:pointer;-webkit-transition-duration:.28s;transition-duration:.28s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1);-webkit-transition-property:-webkit-transform;transition-property:transform;-webkit-transform:scale3d(0,0,0);transform:scale3d(0,0,0);border-radius:50%;background:rgb(158,158,158)}.mdl-radio.is-checked .mdl-radio__inner-circle{-webkit-transform:scale3d(1,1,1);transform:scale3d(1,1,1)}.mdl-radio.is-disabled .mdl-radio__inner-circle{background:rgba(0,0,0,.26);cursor:auto}.mdl-radio.is-focused .mdl-radio__inner-circle{box-shadow:0 0 0 10px rgba(0,0,0,.1)}.mdl-radio__label{cursor:pointer}.mdl-radio.is-disabled .mdl-radio__label{color:rgba(0,0,0,.26);cursor:auto}.mdl-radio__ripple-container{position:absolute;z-index:2;top:-9px;left:-13px;box-sizing:border-box;width:42px;height:42px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.mdl-radio__ripple-container .mdl-ripple{background:rgb(158,158,158)}.mdl-radio.is-disabled .mdl-radio__ripple-container{cursor:auto}.mdl-radio.is-disabled .mdl-radio__ripple-container .mdl-ripple{background:0 0}_:-ms-input-placeholder,:root .mdl-slider.mdl-slider.is-upgraded{-ms-appearance:none;height:32px;margin:0}.mdl-slider{width:calc(100% - 40px);margin:0 20px}.mdl-slider.is-upgraded{-webkit-appearance:none;-moz-appearance:none;appearance:none;height:2px;background:0 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:0;padding:0;color:rgb(158,158,158);-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;z-index:1;cursor:pointer}.mdl-slider.is-upgraded::-moz-focus-outer{border:0}.mdl-slider.is-upgraded::-ms-tooltip{display:none}.mdl-slider.is-upgraded::-webkit-slider-runnable-track{background:0 0}.mdl-slider.is-upgraded::-moz-range-track{background:0 0;border:none}.mdl-slider.is-upgraded::-ms-track{background:0 0;color:transparent;height:2px;width:100%;border:none}.mdl-slider.is-upgraded::-ms-fill-lower{padding:0;background:linear-gradient(to right,transparent,transparent 16px,rgb(158,158,158)16px,rgb(158,158,158)0)}.mdl-slider.is-upgraded::-ms-fill-upper{padding:0;background:linear-gradient(to left,transparent,transparent 16px,rgba(0,0,0,.26)16px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background:rgb(158,158,158);border:none;-webkit-transition:-webkit-transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1);transition:transform .18s cubic-bezier(.4,0,.2,1),border .18s cubic-bezier(.4,0,.2,1),box-shadow .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded::-moz-range-thumb{-moz-appearance:none;width:12px;height:12px;box-sizing:border-box;border-radius:50%;background-image:none;background:rgb(158,158,158);border:none}.mdl-slider.is-upgraded:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(158,158,158,.26)}.mdl-slider.is-upgraded:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(158,158,158,.26)}.mdl-slider.is-upgraded:active::-webkit-slider-thumb{background-image:none;background:rgb(158,158,158);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded:active::-moz-range-thumb{background-image:none;background:rgb(158,158,158);transform:scale(1.5)}.mdl-slider.is-upgraded::-ms-thumb{width:32px;height:32px;border:none;border-radius:50%;background:rgb(158,158,158);-ms-transform:scale(.375);transform:scale(.375);transition:transform .18s cubic-bezier(.4,0,.2,1),background .28s cubic-bezier(.4,0,.2,1)}.mdl-slider.is-upgraded:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgb(158,158,158)0%,rgb(158,158,158)37.5%,rgba(158,158,158,.26)37.5%,rgba(158,158,158,.26)100%);-ms-transform:scale(1);transform:scale(1)}.mdl-slider.is-upgraded:active::-ms-thumb{background:rgb(158,158,158);-ms-transform:scale(.5625);transform:scale(.5625)}.mdl-slider.is-upgraded.is-lowest-value::-webkit-slider-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-moz-range-thumb{border:2px solid rgba(0,0,0,.26);background:0 0}.mdl-slider.is-upgraded.is-lowest-value~.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-webkit-slider-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(0,0,0,.12);background:rgba(0,0,0,.12)}.mdl-slider.is-upgraded.is-lowest-value:active::-webkit-slider-thumb{border:1.6px solid rgba(0,0,0,.26);-webkit-transform:scale(1.5);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value:active~.mdl-slider__background-flex>.mdl-slider__background-upper{left:9px}.mdl-slider.is-upgraded.is-lowest-value:active::-moz-range-thumb{border:1.5px solid rgba(0,0,0,.26);transform:scale(1.5)}.mdl-slider.is-upgraded.is-lowest-value::-ms-thumb{background:radial-gradient(circle closest-side,transparent 0%,transparent 66.67%,rgba(0,0,0,.26)66.67%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value:focus:not(:active)::-ms-thumb{background:radial-gradient(circle closest-side,rgba(0,0,0,.12)0%,rgba(0,0,0,.12)25%,rgba(0,0,0,.26)25%,rgba(0,0,0,.26)37.5%,rgba(0,0,0,.12)37.5%,rgba(0,0,0,.12)100%);-ms-transform:scale(1);transform:scale(1)}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-thumb{-ms-transform:scale(.5625);transform:scale(.5625);background:radial-gradient(circle closest-side,transparent 0%,transparent 77.78%,rgba(0,0,0,.26)77.78%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-lower{background:0 0}.mdl-slider.is-upgraded.is-lowest-value::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:active::-ms-fill-upper{margin-left:9px}.mdl-slider.is-upgraded:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded:disabled::-webkit-slider-thumb{-webkit-transform:scale(.667);transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded:disabled::-moz-range-thumb{transform:scale(.667);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded:disabled~.mdl-slider__background-flex>.mdl-slider__background-lower{background-color:rgba(0,0,0,.26);left:-6px}.mdl-slider.is-upgraded:disabled~.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-webkit-slider-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-webkit-slider-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;-webkit-transform:scale(.667);transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-moz-range-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-moz-range-thumb{border:3px solid rgba(0,0,0,.26);background:0 0;transform:scale(.667)}.mdl-slider.is-upgraded.is-lowest-value:disabled:active~.mdl-slider__background-flex>.mdl-slider__background-upper{left:6px}.mdl-slider.is-upgraded:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded:disabled:active::-ms-thumb,.mdl-slider.is-upgraded:disabled::-ms-thumb{-ms-transform:scale(.25);transform:scale(.25);background:rgba(0,0,0,.26)}.mdl-slider.is-upgraded.is-lowest-value:disabled:focus::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-thumb,.mdl-slider.is-upgraded.is-lowest-value:disabled::-ms-thumb{-ms-transform:scale(.25);transform:scale(.25);background:radial-gradient(circle closest-side,transparent 0%,transparent 50%,rgba(0,0,0,.26)50%,rgba(0,0,0,.26)100%)}.mdl-slider.is-upgraded:disabled::-ms-fill-lower{margin-right:6px;background:linear-gradient(to right,transparent,transparent 25px,rgba(0,0,0,.26)25px,rgba(0,0,0,.26)0)}.mdl-slider.is-upgraded:disabled::-ms-fill-upper{margin-left:6px}.mdl-slider.is-upgraded.is-lowest-value:disabled:active::-ms-fill-upper{margin-left:6px}.mdl-slider__ie-container{height:18px;overflow:visible;border:none;margin:none;padding:none}.mdl-slider__container{height:18px;position:relative;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.mdl-slider__container,.mdl-slider__background-flex{background:0 0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.mdl-slider__background-flex{position:absolute;height:2px;width:calc(100% - 52px);top:50%;left:0;margin:0 26px;overflow:hidden;border:0;padding:0;-webkit-transform:translate(0,-1px);-ms-transform:translate(0,-1px);transform:translate(0,-1px)}.mdl-slider__background-lower{background:rgb(158,158,158)}.mdl-slider__background-lower,.mdl-slider__background-upper{-webkit-box-flex:0;-webkit-flex:0;-ms-flex:0;flex:0;position:relative;border:0;padding:0}.mdl-slider__background-upper{background:rgba(0,0,0,.26);-webkit-transition:left .18s cubic-bezier(.4,0,.2,1);transition:left .18s cubic-bezier(.4,0,.2,1)}.mdl-spinner{display:inline-block;position:relative;width:28px;height:28px}.mdl-spinner:not(.is-upgraded).is-active:after{content:"Loading..."}.mdl-spinner.is-upgraded.is-active{-webkit-animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite;animation:mdl-spinner__container-rotate 1568.23529412ms linear infinite}@-webkit-keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes mdl-spinner__container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.mdl-spinner__layer{position:absolute;width:100%;height:100%;opacity:0}.mdl-spinner__layer-1{border-color:#42a5f5}.mdl-spinner--single-color .mdl-spinner__layer-1{border-color:rgb(158,158,158)}.mdl-spinner.is-active .mdl-spinner__layer-1{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-1-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-2{border-color:#f44336}.mdl-spinner--single-color .mdl-spinner__layer-2{border-color:rgb(158,158,158)}.mdl-spinner.is-active .mdl-spinner__layer-2{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-2-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-3{border-color:#fdd835}.mdl-spinner--single-color .mdl-spinner__layer-3{border-color:rgb(158,158,158)}.mdl-spinner.is-active .mdl-spinner__layer-3{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-3-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__layer-4{border-color:#4caf50}.mdl-spinner--single-color .mdl-spinner__layer-4{border-color:rgb(158,158,158)}.mdl-spinner.is-active .mdl-spinner__layer-4{-webkit-animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__fill-unfill-rotate 5332ms cubic-bezier(.4,0,.2,1)infinite both,mdl-spinner__layer-4-fade-in-out 5332ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@keyframes mdl-spinner__fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@keyframes mdl-spinner__layer-1-fade-in-out{from,25%{opacity:.99}26%,89%{opacity:0}90%,100%{opacity:.99}}@-webkit-keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@keyframes mdl-spinner__layer-2-fade-in-out{from,15%{opacity:0}25%,50%{opacity:.99}51%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@keyframes mdl-spinner__layer-3-fade-in-out{from,40%{opacity:0}50%,75%{opacity:.99}76%{opacity:0}}@-webkit-keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}@keyframes mdl-spinner__layer-4-fade-in-out{from,65%{opacity:0}75%,90%{opacity:.99}100%{opacity:0}}.mdl-spinner__gap-patch{position:absolute;box-sizing:border-box;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__gap-patch .mdl-spinner__circle{width:1000%;left:-450%}.mdl-spinner__circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.mdl-spinner__circle-clipper .mdl-spinner__circle{width:200%}.mdl-spinner__circle{box-sizing:border-box;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent!important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0;left:0}.mdl-spinner__left .mdl-spinner__circle{border-right-color:transparent!important;-webkit-transform:rotate(129deg);-ms-transform:rotate(129deg);transform:rotate(129deg)}.mdl-spinner.is-active .mdl-spinner__left .mdl-spinner__circle{-webkit-animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__left-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}.mdl-spinner__right .mdl-spinner__circle{left:-100%;border-left-color:transparent!important;-webkit-transform:rotate(-129deg);-ms-transform:rotate(-129deg);transform:rotate(-129deg)}.mdl-spinner.is-active .mdl-spinner__right .mdl-spinner__circle{-webkit-animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both;animation:mdl-spinner__right-spin 1333ms cubic-bezier(.4,0,.2,1)infinite both}@-webkit-keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@keyframes mdl-spinner__left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}@keyframes mdl-spinner__right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}.mdl-switch{position:relative;z-index:1;vertical-align:middle;display:inline-block;box-sizing:border-box;width:100%;height:24px;margin:0;padding:0;overflow:visible;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mdl-switch.is-upgraded{padding-left:28px}.mdl-switch__input{line-height:24px}.mdl-switch.is-upgraded .mdl-switch__input{position:absolute;width:0;height:0;margin:0;padding:0;opacity:0;-ms-appearance:none;-moz-appearance:none;-webkit-appearance:none;appearance:none;border:none}.mdl-switch__track{background:rgba(0,0,0,.26);position:absolute;left:0;top:5px;height:14px;width:36px;border-radius:14px;cursor:pointer}.mdl-switch.is-checked .mdl-switch__track{background:rgba(158,158,158,.5)}.mdl-switch.is-disabled .mdl-switch__track{background:rgba(0,0,0,.12);cursor:auto}.mdl-switch__thumb{background:#fafafa;position:absolute;left:0;top:2px;height:20px;width:20px;border-radius:50%;cursor:pointer;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);-webkit-transition-duration:.28s;transition-duration:.28s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1);-webkit-transition-property:left;transition-property:left}.mdl-switch.is-checked .mdl-switch__thumb{background:rgb(158,158,158);left:16px;box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-switch.is-disabled .mdl-switch__thumb{background:#bdbdbd;cursor:auto}.mdl-switch__focus-helper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-4px,-4px);-ms-transform:translate(-4px,-4px);transform:translate(-4px,-4px);display:inline-block;box-sizing:border-box;width:8px;height:8px;border-radius:50%;background-color:transparent}.mdl-switch.is-focused .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(0,0,0,.1);background-color:rgba(0,0,0,.1)}.mdl-switch.is-focused.is-checked .mdl-switch__focus-helper{box-shadow:0 0 0 20px rgba(158,158,158,.26);background-color:rgba(158,158,158,.26)}.mdl-switch__label{position:relative;cursor:pointer;font-size:16px;line-height:24px;margin:0;left:24px}.mdl-switch.is-disabled .mdl-switch__label{color:#bdbdbd;cursor:auto}.mdl-switch__ripple-container{position:absolute;z-index:2;top:-12px;left:-14px;box-sizing:border-box;width:48px;height:48px;border-radius:50%;cursor:pointer;overflow:hidden;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000);-webkit-transition-duration:.4s;transition-duration:.4s;-webkit-transition-timing-function:step-end;transition-timing-function:step-end;-webkit-transition-property:left;transition-property:left}.mdl-switch__ripple-container .mdl-ripple{background:rgb(158,158,158)}.mdl-switch.is-disabled .mdl-switch__ripple-container{cursor:auto}.mdl-switch.is-disabled .mdl-switch__ripple-container .mdl-ripple{background:0 0}.mdl-switch.is-checked .mdl-switch__ripple-container{cursor:auto;left:2px}.mdl-tabs{display:block;width:100%}.mdl-tabs__tab-bar{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:space-between;-ms-flex-line-pack:justify;align-content:space-between;-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;height:48px;padding:0;margin:0;border-bottom:1px solid #e0e0e0}.mdl-tabs__tab{margin:0;border:none;padding:0 24px;float:left;position:relative;display:block;color:red;text-decoration:none;height:48px;line-height:48px;text-align:center;font-weight:500;font-size:14px;text-transform:uppercase;color:rgba(0,0,0,.54);overflow:hidden}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active{color:rgba(0,0,0,.87)}.mdl-tabs.is-upgraded .mdl-tabs__tab.is-active:after{height:2px;width:100%;display:block;content:" ";bottom:0;left:0;position:absolute;background:rgb(158,158,158);-webkit-animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;animation:border-expand .2s cubic-bezier(.4,0,.4,1).01s alternate forwards;-webkit-transition:all 1s cubic-bezier(.4,0,1,1);transition:all 1s cubic-bezier(.4,0,1,1)}.mdl-tabs__tab .mdl-tabs__ripple-container{display:block;position:absolute;height:100%;width:100%;left:0;top:0;z-index:1;overflow:hidden}.mdl-tabs__tab .mdl-tabs__ripple-container .mdl-ripple{background:rgb(158,158,158)}.mdl-tabs__panel{display:block}.mdl-tabs.is-upgraded .mdl-tabs__panel{display:none}.mdl-tabs.is-upgraded .mdl-tabs__panel.is-active{display:block}@-webkit-keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}@keyframes border-expand{0%{opacity:0;width:0}100%{opacity:1;width:100%}}.mdl-textfield{position:relative;font-size:16px;display:inline-block;box-sizing:border-box;width:300px;max-width:100%;margin:0;padding:20px 0}.mdl-textfield .mdl-button{position:absolute;bottom:20px}.mdl-textfield--align-right{text-align:right}.mdl-textfield--full-width{width:100%}.mdl-textfield--expandable{min-width:32px;width:auto;min-height:32px}.mdl-textfield__input{border:none;border-bottom:1px solid rgba(0,0,0,.12);display:block;font-size:16px;margin:0;padding:4px 0;width:100%;background:0 0;text-align:left;color:inherit}.mdl-textfield.is-focused .mdl-textfield__input{outline:none}.mdl-textfield.is-invalid .mdl-textfield__input{border-color:#de3226;box-shadow:none}.mdl-textfield.is-disabled .mdl-textfield__input{background-color:transparent;border-bottom:1px dotted rgba(0,0,0,.12);color:rgba(0,0,0,.26)}.mdl-textfield textarea.mdl-textfield__input{display:block}.mdl-textfield__label{bottom:0;color:rgba(0,0,0,.26);font-size:16px;left:0;right:0;pointer-events:none;position:absolute;display:block;top:24px;width:100%;overflow:hidden;white-space:nowrap;text-align:left}.mdl-textfield.is-dirty .mdl-textfield__label{visibility:hidden}.mdl-textfield--floating-label .mdl-textfield__label{-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1)}.mdl-textfield.is-disabled.is-disabled .mdl-textfield__label{color:rgba(0,0,0,.26)}.mdl-textfield--floating-label.is-focused .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__label{color:rgb(158,158,158);font-size:12px;top:4px;visibility:visible}.mdl-textfield--floating-label.is-focused .mdl-textfield__expandable-holder .mdl-textfield__label,.mdl-textfield--floating-label.is-dirty .mdl-textfield__expandable-holder .mdl-textfield__label{top:-16px}.mdl-textfield--floating-label.is-invalid .mdl-textfield__label{color:#de3226;font-size:12px}.mdl-textfield__label:after{background-color:rgb(158,158,158);bottom:20px;content:'';height:2px;left:45%;position:absolute;-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1);visibility:hidden;width:10px}.mdl-textfield.is-focused .mdl-textfield__label:after{left:0;visibility:visible;width:100%}.mdl-textfield.is-invalid .mdl-textfield__label:after{background-color:#de3226}.mdl-textfield__error{color:#de3226;position:absolute;font-size:12px;margin-top:3px;visibility:hidden;display:block}.mdl-textfield.is-invalid .mdl-textfield__error{visibility:visible}.mdl-textfield__expandable-holder{display:inline-block;position:relative;margin-left:32px;-webkit-transition-duration:.2s;transition-duration:.2s;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-block;max-width:.1px}.mdl-textfield.is-focused .mdl-textfield__expandable-holder,.mdl-textfield.is-dirty .mdl-textfield__expandable-holder{max-width:600px}.mdl-textfield__expandable-holder .mdl-textfield__label:after{bottom:0}.mdl-tooltip{-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);-webkit-transform-origin:top center;-ms-transform-origin:top center;transform-origin:top center;will-change:transform;z-index:999;background:rgba(97,97,97,.9);border-radius:2px;color:#fff;display:inline-block;font-size:10px;font-weight:500;line-height:14px;max-width:170px;position:fixed;top:-500px;left:-500px;padding:8px;text-align:center}.mdl-tooltip.is-active{-webkit-animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards;animation:pulse 200ms cubic-bezier(0,0,.2,1)forwards}.mdl-tooltip--large{line-height:14px;font-size:14px;padding:16px}@-webkit-keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}@keyframes pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:0}50%{-webkit-transform:scale(.99);transform:scale(.99)}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1;visibility:visible}}.mdl-shadow--2dp{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.mdl-shadow--3dp{box-shadow:0 3px 4px 0 rgba(0,0,0,.14),0 3px 3px -2px rgba(0,0,0,.2),0 1px 8px 0 rgba(0,0,0,.12)}.mdl-shadow--4dp{box-shadow:0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12),0 2px 4px -1px rgba(0,0,0,.2)}.mdl-shadow--6dp{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.2)}.mdl-shadow--8dp{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.2)}.mdl-shadow--16dp{box-shadow:0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12),0 8px 10px -5px rgba(0,0,0,.2)}.mdl-grid{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;margin:0 auto;-webkit-box-align:stretch;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch}.mdl-grid.mdl-grid--no-spacing{padding:0}.mdl-cell{box-sizing:border-box}.mdl-cell--top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.mdl-cell--middle{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.mdl-cell--bottom{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.mdl-cell--stretch{-webkit-align-self:stretch;-ms-flex-item-align:stretch;align-self:stretch}.mdl-grid.mdl-grid--no-spacing>.mdl-cell{margin:0}@media (max-width:479px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:100%}.mdl-cell--hide-phone{display:none!important}.mdl-cell--1-col,.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-phone.mdl-cell--1-col-phone{width:25%}.mdl-cell--2-col,.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-phone.mdl-cell--2-col-phone{width:50%}.mdl-cell--3-col,.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-phone.mdl-cell--3-col-phone{width:75%}.mdl-cell--4-col,.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-phone.mdl-cell--4-col-phone{width:100%}.mdl-cell--5-col,.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-phone.mdl-cell--5-col-phone{width:100%}.mdl-cell--6-col,.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-phone.mdl-cell--6-col-phone{width:100%}.mdl-cell--7-col,.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-phone.mdl-cell--7-col-phone{width:100%}.mdl-cell--8-col,.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-phone.mdl-cell--8-col-phone{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-phone.mdl-cell--9-col-phone{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-phone.mdl-cell--10-col-phone{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-phone.mdl-cell--11-col-phone{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-phone.mdl-cell--12-col-phone{width:100%}}@media (min-width:480px) and (max-width:839px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:50%}.mdl-cell--hide-tablet{display:none!important}.mdl-cell--1-col,.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:calc(12.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-tablet.mdl-cell--1-col-tablet{width:12.5%}.mdl-cell--2-col,.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-tablet.mdl-cell--2-col-tablet{width:25%}.mdl-cell--3-col,.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:calc(37.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-tablet.mdl-cell--3-col-tablet{width:37.5%}.mdl-cell--4-col,.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-tablet.mdl-cell--4-col-tablet{width:50%}.mdl-cell--5-col,.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:calc(62.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-tablet.mdl-cell--5-col-tablet{width:62.5%}.mdl-cell--6-col,.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-tablet.mdl-cell--6-col-tablet{width:75%}.mdl-cell--7-col,.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:calc(87.5% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-tablet.mdl-cell--7-col-tablet{width:87.5%}.mdl-cell--8-col,.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-tablet.mdl-cell--8-col-tablet{width:100%}.mdl-cell--9-col,.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-tablet.mdl-cell--9-col-tablet{width:100%}.mdl-cell--10-col,.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-tablet.mdl-cell--10-col-tablet{width:100%}.mdl-cell--11-col,.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-tablet.mdl-cell--11-col-tablet{width:100%}.mdl-cell--12-col,.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-tablet.mdl-cell--12-col-tablet{width:100%}}@media (min-width:840px){.mdl-grid{padding:8px}.mdl-cell{margin:8px;width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell{width:33.3333333333%}.mdl-cell--hide-desktop{display:none!important}.mdl-cell--1-col,.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:calc(8.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--1-col,.mdl-grid--no-spacing>.mdl-cell--1-col-desktop.mdl-cell--1-col-desktop{width:8.3333333333%}.mdl-cell--2-col,.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:calc(16.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--2-col,.mdl-grid--no-spacing>.mdl-cell--2-col-desktop.mdl-cell--2-col-desktop{width:16.6666666667%}.mdl-cell--3-col,.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:calc(25% - 16px)}.mdl-grid--no-spacing>.mdl-cell--3-col,.mdl-grid--no-spacing>.mdl-cell--3-col-desktop.mdl-cell--3-col-desktop{width:25%}.mdl-cell--4-col,.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:calc(33.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--4-col,.mdl-grid--no-spacing>.mdl-cell--4-col-desktop.mdl-cell--4-col-desktop{width:33.3333333333%}.mdl-cell--5-col,.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:calc(41.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--5-col,.mdl-grid--no-spacing>.mdl-cell--5-col-desktop.mdl-cell--5-col-desktop{width:41.6666666667%}.mdl-cell--6-col,.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:calc(50% - 16px)}.mdl-grid--no-spacing>.mdl-cell--6-col,.mdl-grid--no-spacing>.mdl-cell--6-col-desktop.mdl-cell--6-col-desktop{width:50%}.mdl-cell--7-col,.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:calc(58.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--7-col,.mdl-grid--no-spacing>.mdl-cell--7-col-desktop.mdl-cell--7-col-desktop{width:58.3333333333%}.mdl-cell--8-col,.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:calc(66.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--8-col,.mdl-grid--no-spacing>.mdl-cell--8-col-desktop.mdl-cell--8-col-desktop{width:66.6666666667%}.mdl-cell--9-col,.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:calc(75% - 16px)}.mdl-grid--no-spacing>.mdl-cell--9-col,.mdl-grid--no-spacing>.mdl-cell--9-col-desktop.mdl-cell--9-col-desktop{width:75%}.mdl-cell--10-col,.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:calc(83.3333333333% - 16px)}.mdl-grid--no-spacing>.mdl-cell--10-col,.mdl-grid--no-spacing>.mdl-cell--10-col-desktop.mdl-cell--10-col-desktop{width:83.3333333333%}.mdl-cell--11-col,.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:calc(91.6666666667% - 16px)}.mdl-grid--no-spacing>.mdl-cell--11-col,.mdl-grid--no-spacing>.mdl-cell--11-col-desktop.mdl-cell--11-col-desktop{width:91.6666666667%}.mdl-cell--12-col,.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:calc(100% - 16px)}.mdl-grid--no-spacing>.mdl-cell--12-col,.mdl-grid--no-spacing>.mdl-cell--12-col-desktop.mdl-cell--12-col-desktop{width:100%}}body{margin:0}.styleguide-demo h1{margin:48px 24px 0}.styleguide-demo h1:after{content:'';display:block;width:100%;border-bottom:1px solid rgba(0,0,0,.5);margin-top:24px}.styleguide-demo{opacity:0;-webkit-transition:opacity .6s ease;transition:opacity .6s ease}.styleguide-masthead{height:256px;background:#212121;padding:115px 16px 0}.styleguide-container{position:relative;max-width:960px;width:100%}.styleguide-title{color:#fff;bottom:auto;position:relative;font-size:56px;font-weight:300;line-height:1;letter-spacing:-.02em}.styleguide-title:after{border-bottom:0}.styleguide-title span{font-weight:300}.mdl-styleguide .mdl-layout__drawer .mdl-navigation__link{padding:10px 24px}.demosLoaded .styleguide-demo{opacity:1}iframe{display:block;width:100%;border:none}iframe.heightSet{overflow:hidden}.demo-wrapper{margin:24px}.demo-wrapper iframe{border:1px solid rgba(0,0,0,.5)} \ No newline at end of file diff --git a/themes/material/material.min.js b/themes/material/material.min.js new file mode 100644 index 00000000..fbab3af4 --- /dev/null +++ b/themes/material/material.min.js @@ -0,0 +1,10 @@ +/** + * material-design-lite - Material Design Components in CSS, JS and HTML + * @version v1.0.5 + * @license Apache-2.0 + * @copyright 2015 Google, Inc. + * @link https://github.com/google/material-design-lite + */ +!function(){"use strict";function e(e,t){if(e){if(t.element_.classList.contains(t.CssClasses_.MDL_JS_RIPPLE_EFFECT)){var s=document.createElement("span");s.classList.add(t.CssClasses_.MDL_RIPPLE_CONTAINER),s.classList.add(t.CssClasses_.MDL_JS_RIPPLE_EFFECT);var i=document.createElement("span");i.classList.add(t.CssClasses_.MDL_RIPPLE),s.appendChild(i),e.appendChild(s)}e.addEventListener("click",function(s){s.preventDefault();var i=e.href.split("#")[1],n=t.element_.querySelector("#"+i);t.resetTabState_(),t.resetPanelState_(),e.classList.add(t.CssClasses_.ACTIVE_CLASS),n.classList.add(t.CssClasses_.ACTIVE_CLASS)})}}function t(e,t,s,i){if(e){if(i.tabBar_.classList.contains(i.CssClasses_.JS_RIPPLE_EFFECT)){var n=document.createElement("span");n.classList.add(i.CssClasses_.RIPPLE_CONTAINER),n.classList.add(i.CssClasses_.JS_RIPPLE_EFFECT);var a=document.createElement("span");a.classList.add(i.CssClasses_.RIPPLE),n.appendChild(a),e.appendChild(n)}e.addEventListener("click",function(n){n.preventDefault();var a=e.href.split("#")[1],l=i.content_.querySelector("#"+a);i.resetTabState_(t),i.resetPanelState_(s),e.classList.add(i.CssClasses_.IS_ACTIVE),l.classList.add(i.CssClasses_.IS_ACTIVE)})}}var s={upgradeDom:function(e,t){},upgradeElement:function(e,t){},upgradeElements:function(e){},upgradeAllRegistered:function(){},registerUpgradedCallback:function(e,t){},register:function(e){},downgradeElements:function(e){}};s=function(){function e(e,t){for(var s=0;sd;d++){if(r=l[d],!r)throw new Error("Unable to find a registered component for the given class.");a.push(r.className),i.setAttribute("data-upgraded",a.join(","));var h=new r.classConstructor(i);h[C]=r,p.push(h);for(var u=0,m=r.callbacks.length;m>u;u++)r.callbacks[u](i);r.widget&&(i[r.className]=h);var E=document.createEvent("Events");E.initEvent("mdl-componentupgraded",!0,!0),i.dispatchEvent(E)}}function a(e){Array.isArray(e)||(e="function"==typeof e.item?Array.prototype.slice.call(e):[e]);for(var t,s=0,i=e.length;i>s;s++)t=e[s],t instanceof HTMLElement&&(n(t),t.children.length>0&&a(t.children))}function l(t){var s="undefined"==typeof t.widget&&"undefined"==typeof t.widget,i=!0;s||(i=t.widget||t.widget);var n={classConstructor:t.constructor||t.constructor,className:t.classAsString||t.classAsString,cssClass:t.cssClass||t.cssClass,widget:i,callbacks:[]};if(c.forEach(function(e){if(e.cssClass===n.cssClass)throw new Error("The provided cssClass has already been registered: "+e.cssClass);if(e.className===n.className)throw new Error("The provided className has already been registered")}),t.constructor.prototype.hasOwnProperty(C))throw new Error("MDL component classes must not have "+C+" defined as a property.");var a=e(t.classAsString,n);a||c.push(n)}function o(t,s){var i=e(t);i&&i.callbacks.push(s)}function r(){for(var e=0;e0&&this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)&&(e.keyCode===this.Keycodes_.UP_ARROW?(e.preventDefault(),t[t.length-1].focus()):e.keyCode===this.Keycodes_.DOWN_ARROW&&(e.preventDefault(),t[0].focus()))}},_.prototype.handleItemKeyboardEvent_=function(e){if(this.element_&&this.container_){var t=this.element_.querySelectorAll("."+this.CssClasses_.ITEM+":not([disabled])");if(t&&t.length>0&&this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)){var s=Array.prototype.slice.call(t).indexOf(e.target);if(e.keyCode===this.Keycodes_.UP_ARROW)e.preventDefault(),s>0?t[s-1].focus():t[t.length-1].focus();else if(e.keyCode===this.Keycodes_.DOWN_ARROW)e.preventDefault(),t.length>s+1?t[s+1].focus():t[0].focus();else if(e.keyCode===this.Keycodes_.SPACE||e.keyCode===this.Keycodes_.ENTER){e.preventDefault();var i=new MouseEvent("mousedown");e.target.dispatchEvent(i),i=new MouseEvent("mouseup"),e.target.dispatchEvent(i),e.target.click()}else e.keyCode===this.Keycodes_.ESCAPE&&(e.preventDefault(),this.hide())}}},_.prototype.handleItemClick_=function(e){null!==e.target.getAttribute("disabled")?e.stopPropagation():(this.closing_=!0,window.setTimeout(function(e){this.hide(),this.closing_=!1}.bind(this),this.Constant_.CLOSE_TIMEOUT))},_.prototype.applyClip_=function(e,t){this.element_.classList.contains(this.CssClasses_.UNALIGNED)?this.element_.style.clip="":this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)?this.element_.style.clip="rect(0 "+t+"px 0 "+t+"px)":this.element_.classList.contains(this.CssClasses_.TOP_LEFT)?this.element_.style.clip="rect("+e+"px 0 "+e+"px 0)":this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)?this.element_.style.clip="rect("+e+"px "+t+"px "+e+"px "+t+"px)":this.element_.style.clip=""},_.prototype.addAnimationEndListener_=function(){var e=function(){this.element_.removeEventListener("transitionend",e),this.element_.removeEventListener("webkitTransitionEnd",e),this.element_.classList.remove(this.CssClasses_.IS_ANIMATING)}.bind(this);this.element_.addEventListener("transitionend",e),this.element_.addEventListener("webkitTransitionEnd",e)},_.prototype.show=function(e){if(this.element_&&this.container_&&this.outline_){var t=this.element_.getBoundingClientRect().height,s=this.element_.getBoundingClientRect().width;this.container_.style.width=s+"px",this.container_.style.height=t+"px",this.outline_.style.width=s+"px",this.outline_.style.height=t+"px";for(var i=this.Constant_.TRANSITION_DURATION_SECONDS*this.Constant_.TRANSITION_DURATION_FRACTION,n=this.element_.querySelectorAll("."+this.CssClasses_.ITEM),a=0;a=this.maxRows&&e.preventDefault()},E.prototype.onFocus_=function(e){this.element_.classList.add(this.CssClasses_.IS_FOCUSED)},E.prototype.onBlur_=function(e){this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},E.prototype.updateClasses_=function(){this.checkDisabled(),this.checkValidity(),this.checkDirty()},E.prototype.checkDisabled=function(){this.input_.disabled?this.element_.classList.add(this.CssClasses_.IS_DISABLED):this.element_.classList.remove(this.CssClasses_.IS_DISABLED)},E.prototype.checkDisabled=E.prototype.checkDisabled,E.prototype.checkValidity=function(){this.input_.validity.valid?this.element_.classList.remove(this.CssClasses_.IS_INVALID):this.element_.classList.add(this.CssClasses_.IS_INVALID)},E.prototype.checkValidity=E.prototype.checkValidity,E.prototype.checkDirty=function(){this.input_.value&&this.input_.value.length>0?this.element_.classList.add(this.CssClasses_.IS_DIRTY):this.element_.classList.remove(this.CssClasses_.IS_DIRTY)},E.prototype.checkDirty=E.prototype.checkDirty,E.prototype.disable=function(){this.input_.disabled=!0,this.updateClasses_()},E.prototype.disable=E.prototype.disable,E.prototype.enable=function(){this.input_.disabled=!1,this.updateClasses_()},E.prototype.enable=E.prototype.enable,E.prototype.change=function(e){e?this.input_.value=e:this.input_.value="",this.updateClasses_()},E.prototype.change=E.prototype.change,E.prototype.init=function(){this.element_&&(this.label_=this.element_.querySelector("."+this.CssClasses_.LABEL),this.input_=this.element_.querySelector("."+this.CssClasses_.INPUT),this.input_&&(this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)&&(this.maxRows=parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE),10),isNaN(this.maxRows)&&(this.maxRows=this.Constant_.NO_MAX_ROWS)),this.boundUpdateClassesHandler=this.updateClasses_.bind(this),this.boundFocusHandler=this.onFocus_.bind(this),this.boundBlurHandler=this.onBlur_.bind(this),this.input_.addEventListener("input",this.boundUpdateClassesHandler),this.input_.addEventListener("focus",this.boundFocusHandler),this.input_.addEventListener("blur",this.boundBlurHandler),this.maxRows!==this.Constant_.NO_MAX_ROWS&&(this.boundKeyDownHandler=this.onKeyDown_.bind(this),this.input_.addEventListener("keydown",this.boundKeyDownHandler)),this.updateClasses_(),this.element_.classList.add(this.CssClasses_.IS_UPGRADED)))},E.prototype.mdlDowngrade_=function(){this.input_.removeEventListener("input",this.boundUpdateClassesHandler),this.input_.removeEventListener("focus",this.boundFocusHandler),this.input_.removeEventListener("blur",this.boundBlurHandler),this.boundKeyDownHandler&&this.input_.removeEventListener("keydown",this.boundKeyDownHandler)},s.register({constructor:E,classAsString:"MaterialTextfield",cssClass:"mdl-js-textfield",widget:!0});var L=function(e){this.element_=e,this.init()};window.MaterialTooltip=L,L.prototype.Constant_={},L.prototype.CssClasses_={IS_ACTIVE:"is-active"},L.prototype.handleMouseEnter_=function(e){e.stopPropagation();var t=e.target.getBoundingClientRect(),s=t.left+t.width/2,i=-1*(this.element_.offsetWidth/2);0>s+i?(this.element_.style.left=0,this.element_.style.marginLeft=0):(this.element_.style.left=s+"px",this.element_.style.marginLeft=i+"px"),this.element_.style.top=t.top+t.height+10+"px",this.element_.classList.add(this.CssClasses_.IS_ACTIVE),window.addEventListener("scroll",this.boundMouseLeaveHandler,!1),window.addEventListener("touchmove",this.boundMouseLeaveHandler,!1)},L.prototype.handleMouseLeave_=function(e){e.stopPropagation(),this.element_.classList.remove(this.CssClasses_.IS_ACTIVE),window.removeEventListener("scroll",this.boundMouseLeaveHandler),window.removeEventListener("touchmove",this.boundMouseLeaveHandler,!1)},L.prototype.init=function(){if(this.element_){var e=this.element_.getAttribute("for");e&&(this.forElement_=document.getElementById(e)),this.forElement_&&(this.forElement_.getAttribute("tabindex")||this.forElement_.setAttribute("tabindex","0"),this.boundMouseEnterHandler=this.handleMouseEnter_.bind(this),this.boundMouseLeaveHandler=this.handleMouseLeave_.bind(this),this.forElement_.addEventListener("mouseenter",this.boundMouseEnterHandler,!1),this.forElement_.addEventListener("click",this.boundMouseEnterHandler,!1),this.forElement_.addEventListener("blur",this.boundMouseLeaveHandler),this.forElement_.addEventListener("touchstart",this.boundMouseEnterHandler,!1),this.forElement_.addEventListener("mouseleave",this.boundMouseLeaveHandler))}},L.prototype.mdlDowngrade_=function(){this.forElement_&&(this.forElement_.removeEventListener("mouseenter",this.boundMouseEnterHandler,!1),this.forElement_.removeEventListener("click",this.boundMouseEnterHandler,!1),this.forElement_.removeEventListener("touchstart",this.boundMouseEnterHandler,!1),this.forElement_.removeEventListener("mouseleave",this.boundMouseLeaveHandler))},s.register({constructor:L,classAsString:"MaterialTooltip",cssClass:"mdl-tooltip"});var I=function(e){this.element_=e,this.init()};window.MaterialLayout=I,I.prototype.Constant_={MAX_WIDTH:"(max-width: 1024px)",TAB_SCROLL_PIXELS:100,MENU_ICON:"menu",CHEVRON_LEFT:"chevron_left",CHEVRON_RIGHT:"chevron_right"},I.prototype.Mode_={STANDARD:0,SEAMED:1,WATERFALL:2,SCROLL:3},I.prototype.CssClasses_={CONTAINER:"mdl-layout__container",HEADER:"mdl-layout__header",DRAWER:"mdl-layout__drawer",CONTENT:"mdl-layout__content",DRAWER_BTN:"mdl-layout__drawer-button",ICON:"material-icons",JS_RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_CONTAINER:"mdl-layout__tab-ripple-container",RIPPLE:"mdl-ripple",RIPPLE_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",HEADER_SEAMED:"mdl-layout__header--seamed",HEADER_WATERFALL:"mdl-layout__header--waterfall",HEADER_SCROLL:"mdl-layout__header--scroll",FIXED_HEADER:"mdl-layout--fixed-header",OBFUSCATOR:"mdl-layout__obfuscator",TAB_BAR:"mdl-layout__tab-bar",TAB_CONTAINER:"mdl-layout__tab-bar-container",TAB:"mdl-layout__tab",TAB_BAR_BUTTON:"mdl-layout__tab-bar-button",TAB_BAR_LEFT_BUTTON:"mdl-layout__tab-bar-left-button",TAB_BAR_RIGHT_BUTTON:"mdl-layout__tab-bar-right-button",PANEL:"mdl-layout__tab-panel",HAS_DRAWER:"has-drawer",HAS_TABS:"has-tabs",HAS_SCROLLING_HEADER:"has-scrolling-header",CASTING_SHADOW:"is-casting-shadow",IS_COMPACT:"is-compact",IS_SMALL_SCREEN:"is-small-screen",IS_DRAWER_OPEN:"is-visible",IS_ACTIVE:"is-active",IS_UPGRADED:"is-upgraded",IS_ANIMATING:"is-animating",ON_LARGE_SCREEN:"mdl-layout--large-screen-only",ON_SMALL_SCREEN:"mdl-layout--small-screen-only"},I.prototype.contentScrollHandler_=function(){this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)||(this.content_.scrollTop>0&&!this.header_.classList.contains(this.CssClasses_.IS_COMPACT)?(this.header_.classList.add(this.CssClasses_.CASTING_SHADOW),this.header_.classList.add(this.CssClasses_.IS_COMPACT),this.header_.classList.add(this.CssClasses_.IS_ANIMATING)):this.content_.scrollTop<=0&&this.header_.classList.contains(this.CssClasses_.IS_COMPACT)&&(this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW),this.header_.classList.remove(this.CssClasses_.IS_COMPACT),this.header_.classList.add(this.CssClasses_.IS_ANIMATING)))},I.prototype.screenSizeHandler_=function(){this.screenSizeMediaQuery_.matches?this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN):(this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN),this.drawer_&&this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN))},I.prototype.drawerToggleHandler_=function(){this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN)},I.prototype.headerTransitionEndHandler_=function(){this.header_.classList.remove(this.CssClasses_.IS_ANIMATING)},I.prototype.headerClickHandler_=function(){this.header_.classList.contains(this.CssClasses_.IS_COMPACT)&&(this.header_.classList.remove(this.CssClasses_.IS_COMPACT),this.header_.classList.add(this.CssClasses_.IS_ANIMATING))},I.prototype.resetTabState_=function(e){for(var t=0;t0?h.classList.add(this.CssClasses_.IS_ACTIVE):h.classList.remove(this.CssClasses_.IS_ACTIVE),this.tabBar_.scrollLeft0)return;this.setFrameCount(1);var i,n,a=e.currentTarget.getBoundingClientRect();if(0===e.clientX&&0===e.clientY)i=Math.round(a.width/2),n=Math.round(a.height/2);else{var l=e.clientX?e.clientX:e.touches[0].clientX,o=e.clientY?e.clientY:e.touches[0].clientY;i=Math.round(l-a.left),n=Math.round(o-a.top)}this.setRippleXY(i,n),this.setRippleStyles(!0),window.requestAnimationFrame(this.animFrameHandler.bind(this))}},b.prototype.upHandler_=function(e){e&&2!==e.detail&&this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE),window.setTimeout(function(){this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE)}.bind(this),0)},b.prototype.init=function(){if(this.element_){var e=this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER);this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)||(this.rippleElement_=this.element_.querySelector("."+this.CssClasses_.RIPPLE),this.frameCount_=0,this.rippleSize_=0,this.x_=0,this.y_=0,this.ignoringMouseDown_=!1,this.boundDownHandler=this.downHandler_.bind(this),this.element_.addEventListener("mousedown",this.boundDownHandler),this.element_.addEventListener("touchstart",this.boundDownHandler),this.boundUpHandler=this.upHandler_.bind(this),this.element_.addEventListener("mouseup",this.boundUpHandler),this.element_.addEventListener("mouseleave",this.boundUpHandler),this.element_.addEventListener("touchend",this.boundUpHandler),this.element_.addEventListener("blur",this.boundUpHandler),this.getFrameCount=function(){return this.frameCount_},this.setFrameCount=function(e){this.frameCount_=e},this.getRippleElement=function(){return this.rippleElement_},this.setRippleXY=function(e,t){this.x_=e,this.y_=t},this.setRippleStyles=function(t){if(null!==this.rippleElement_){var s,i,n,a="translate("+this.x_+"px, "+this.y_+"px)";t?(i=this.Constant_.INITIAL_SCALE,n=this.Constant_.INITIAL_SIZE):(i=this.Constant_.FINAL_SCALE,n=this.rippleSize_+"px",e&&(a="translate("+this.boundWidth/2+"px, "+this.boundHeight/2+"px)")),s="translate(-50%, -50%) "+a+i,this.rippleElement_.style.webkitTransform=s,this.rippleElement_.style.msTransform=s,this.rippleElement_.style.transform=s,t?this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING):this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING)}},this.animFrameHandler=function(){this.frameCount_-->0?window.requestAnimationFrame(this.animFrameHandler.bind(this)):this.setRippleStyles(!1)})}},b.prototype.mdlDowngrade_=function(){this.element_.removeEventListener("mousedown",this.boundDownHandler),this.element_.removeEventListener("touchstart",this.boundDownHandler),this.element_.removeEventListener("mouseup",this.boundUpHandler),this.element_.removeEventListener("mouseleave",this.boundUpHandler),this.element_.removeEventListener("touchend",this.boundUpHandler),this.element_.removeEventListener("blur",this.boundUpHandler)},s.register({constructor:b,classAsString:"MaterialRipple",cssClass:"mdl-js-ripple-effect",widget:!1})}(); +//# sourceMappingURL=material.min.js.map diff --git a/themes/material/script.js b/themes/material/script.js new file mode 100644 index 00000000..e69de29b diff --git a/themes/material/script0.js b/themes/material/script0.js new file mode 100644 index 00000000..1fd32cd8 --- /dev/null +++ b/themes/material/script0.js @@ -0,0 +1,95 @@ +$(function(){ + $( "#layout-top" ).click(function() { + leftAddFullSize("top"); + }); + + $( "#layout-right" ).click(function() { + leftAddSideSize("right"); + }); + + $( "#layout-bottom" ).click(function() { + leftAddFullSize("bottom"); + }); + + $( "#layout-left" ).click(function() { + leftAddSideSize("left"); + }); + + function leftAddSideSize(layout_type){ + $("#left-block").removeClass (function (index, css) { + return (css.match (/(^|\s)mdl-cell--\S+/g) || []).join(' '); + }).addClass("mdl-cell--4-col mdl-cell--8-col-tablet mdl-cell--4-col-phone"); + + $(".left-blocks").removeClass (function (index, css) { + return (css.match (/(^|\s)mdl-cell--\S+/g) || []).join(' '); + }).addClass("mdl-cell--12-col mdl-cell--4-col-tablet"); + + $("#main-block").removeClass (function (index, css) { + return (css.match (/(^|\s)mdl-cell--\S+/g) || []).join(' '); + }).addClass("mdl-cell--8-col mdl-cell--8-col-tablet"); + + if(layout_type == "right"){ + $("#left-block").appendTo("#main-grid") + } else { + $("#left-block").prependTo("#main-grid") + } + $.cookie("ui-layout-type", layout_type, {path: '/', expires: 365}); + zoom("width"); + } + + function leftAddFullSize(layout_type){ + $("#left-block").removeClass (function (index, css) { + return (css.match (/(^|\s)mdl-cell--\S+/g) || []).join(' '); + }).addClass("mdl-cell--12-col"); + + $(".left-blocks").removeClass (function (index, css) { + return (css.match (/(^|\s)mdl-cell--\S+/g) || []).join(' '); + }).addClass("mdl-cell--4-col"); + + $("#main-block").removeClass (function (index, css) { + return (css.match (/(^|\s)mdl-cell--\S+/g) || []).join(' '); + }).addClass("mdl-cell--12-col"); + + if(layout_type == "bottom"){ + $("#left-block").appendTo("#main-grid") + } else { + $("#left-block").prependTo("#main-grid") + } + $.cookie("ui-layout-type", layout_type, {path: '/', expires: 365}); + zoom("width"); + } + current_layout = $.cookie("layout-type"); + if (current_layout != null) { + if(current_layout =="top" || current_layout == "bottom"){ + leftAddFullSize(current_layout); + } else { + leftAddSideSize(current_layout); + } + } + $('#main-block, #left-block').show(); +}); + + +function zoom(zoom_type) { + var img = $('.shm-main-image'); + if(zoom_type == "full") { + img.css('max-width', img.data('width') + 'px'); + img.css('max-height', img.data('height') + 'px'); + } + if(zoom_type == "width") { + img.css('max-width', ($( "#main-block" ).width()) + 'px'); + img.css('max-height', img.data('height') + 'px'); + } + if(zoom_type == "height") { + img.css('max-width', img.data('width') + 'px'); + img.css('max-height', (window.innerHeight * 0.95) + 'px'); + } + if(zoom_type == "both") { + img.css('max-width', '95%'); + img.css('max-height', (window.innerHeight * 0.95) + 'px'); + } + + $(".shm-zoomer").val(zoom_type); + + $.cookie("ui-image-zoom", zoom_type, {path: '/', expires: 365}); +} diff --git a/themes/material/style.css b/themes/material/style.css new file mode 100644 index 00000000..d3aa59d7 --- /dev/null +++ b/themes/material/style.css @@ -0,0 +1,3 @@ +.nav-card{ + min-height: 3em; +} diff --git a/themes/material/themelet.class.php b/themes/material/themelet.class.php new file mode 100644 index 00000000..77c927c3 --- /dev/null +++ b/themes/material/themelet.class.php @@ -0,0 +1,3 @@ +add_block(new Block("Upload", $this->build_upload_block(), "left", 20)); + // } + // + // public function display_full(Page $page) { + // $page->add_block(new Block("Upload", "Disk nearly full, uploads disabled", "left", 20)); + // } +} diff --git a/themes/material/user.theme.php b/themes/material/user.theme.php new file mode 100644 index 00000000..ab401801 --- /dev/null +++ b/themes/material/user.theme.php @@ -0,0 +1,29 @@ +name); + $html = " | "; + foreach($parts as $part) { + $html .= "{$part["name"]} | "; + } + $page->add_block(new Block("Logged in as $h_name", $html, "drawer", 90)); + } + + public function display_login_block(Page $page) { + global $config; + $html = " +
    + + + + +
    Name
    Password
    +
    + "; + if($config->get_bool("login_signup_enabled")) { + $html .= "Create Account"; + } + $page->add_block(new Block("Login", $html, "head", 90)); + } +} diff --git a/themes/material/view.theme.php b/themes/material/view.theme.php new file mode 100644 index 00000000..8b243e6a --- /dev/null +++ b/themes/material/view.theme.php @@ -0,0 +1,82 @@ +get_tag_list())); + + $page->set_title("Image {$image->id}: ".html_escape($image->get_tag_list())); + $page->add_html_header(""); + $page->add_html_header(""); + $page->add_html_header(""); + $page->add_html_header("get_thumb_link())."\">"); + $page->add_html_header("id}"))."\">"); + $page->set_heading(html_escape($image->get_tag_list())); + $page->add_block(new Block(null, $this->build_pin($image), "subtoolbar", 0)); + $page->add_block(new Block(null, $this->build_info($image, $editor_parts), "left", 20)); + } + + public function display_admin_block(Page $page, $parts) { + if(count($parts) > 0) { + $page->add_block(new Block("Image Controls", join("
    ", $parts), "drawer", 50)); + } + } + + protected function build_pin(Image $image) { + + global $database; + + if(isset($_GET['search'])) { + $search_terms = explode(' ', $_GET['search']); + $query = "search=".url_escape($_GET['search']); + } + else { + $search_terms = array(); + $query = null; + } + + $h_prev = 'id}", $query).'">Prev'; + $h_index = "Current"; + $h_next = 'id}", $query).'">Next'; + + return $h_prev.$h_index.$h_next; + } + + + protected function build_info(Image $image, $editor_parts) { + global $user; + + if(count($editor_parts) == 0) return ($image->is_locked() ? "
    [Image Locked]" : ""); + + $html = make_form(make_link("post/set"))." + + + "; + foreach($editor_parts as $part) { + $html .= $part; + } + if( + (!$image->is_locked() || $user->can("edit_image_lock")) && + $user->can("edit_image_tag") + ) { + $html .= " + + "; + } + $html .= " +
    + + +
    + + "; + return $html; + } + +}