├── 02-features ├── closures │ ├── arraymap.php │ ├── attach-state-bindto.php │ ├── attach-state-use.php │ └── simple.php ├── generators │ ├── csv-generator.php │ ├── data.csv │ ├── range-generator-BAD.php │ ├── range-generator-GOOD.php │ └── simple-generator.php ├── interfaces │ ├── CommandOutputDocument.php │ ├── DocumentStore.php │ ├── Documentable.php │ ├── HtmlDocument.php │ ├── StreamDocument.php │ ├── index.php │ └── stream.txt ├── namespaces │ ├── .gitignore │ ├── 01-http-foundation.php │ ├── 02-http-foundation.php │ ├── 03-http-foundation.php │ ├── 04-namespace-qualification.php │ ├── 05-namespace-qualification.php │ └── composer.json └── traits │ ├── .gitignore │ ├── Geocodable.php │ ├── RetailStore.php │ ├── composer.json │ └── index.php ├── 03-standards ├── psr3 │ ├── .gitignore │ ├── composer.json │ ├── index.php │ └── logs │ │ ├── development.log │ │ └── production.log └── psr4 │ ├── index.php │ └── src │ └── Bar.php ├── 04-components ├── url-scanner-app │ ├── .gitignore │ ├── composer.json │ ├── scan.php │ └── urls.csv └── url-scanner-component │ ├── .gitignore │ ├── composer.json │ ├── example.php │ └── src │ └── Url │ └── Scanner.php ├── 05-good-practices ├── databases │ ├── pdo-constructor-settings.php │ ├── pdo-constructor.php │ ├── prepared-statement-email.php │ ├── prepared-statement-id.php │ ├── prepared-statement-results.php │ ├── schema.sql │ ├── settings.php │ ├── transactions-with.php │ └── transactions-without.php ├── dates-and-times │ ├── dateinterval-class-inverted.php │ ├── dateinterval-class.php │ ├── dateperiod-class-options.php │ ├── dateperiod-class.php │ ├── datetime-class.php │ ├── datetimezone-class.php │ └── set-default-timezone.php ├── exceptions │ ├── error-handler.php │ ├── exception-catch-multiple.php │ ├── exception-catch.php │ ├── exception-handler.php │ ├── monolog │ │ ├── .gitignore │ │ ├── composer.json │ │ ├── development.php │ │ ├── logs │ │ │ ├── development.log │ │ │ └── production.log │ │ └── production.php │ └── whoops │ │ ├── .gitignore │ │ ├── composer.json │ │ └── index.php ├── passwords │ ├── login.php │ └── register.php ├── sanitize-validate-escape │ ├── escape-output.php │ ├── sanitize-email.php │ ├── sanitize-htmlentities.php │ ├── sanitize-international.php │ ├── sanitize-sql-bad.php │ └── validate-email.php └── streams │ ├── DirtyWordsFilter.php │ ├── context.php │ ├── data.txt │ ├── file-wrapper-explicit.php │ ├── file-wrapper-implicit.php │ ├── filter-upper-instantiation.php │ ├── filter-upper.php │ ├── flickr-api.php │ └── log-iterator.php ├── 09-deployment ├── Capfile ├── composer.json ├── config │ ├── deploy.rb │ └── deploy │ │ └── production.rb └── public │ └── index.php ├── 10-testing ├── .gitignore ├── .travis.yml ├── composer.json ├── coverage │ ├── Whovian.php.html │ ├── css │ │ ├── bootstrap.min.css │ │ ├── nv.d3.css │ │ └── style.css │ ├── dashboard.html │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── index.html │ └── js │ │ ├── bootstrap.min.js │ │ ├── d3.min.js │ │ ├── holder.js │ │ ├── html5shiv.min.js │ │ ├── jquery.min.js │ │ ├── nv.d3.min.js │ │ └── respond.min.js ├── phpunit.xml ├── src │ └── Whovian.php └── tests │ ├── WhovianTest.php │ └── bootstrap.php ├── LICENSE └── README.md /02-features/closures/arraymap.php: -------------------------------------------------------------------------------- 1 | [2,3,4] 7 | -------------------------------------------------------------------------------- /02-features/closures/attach-state-bindto.php: -------------------------------------------------------------------------------- 1 | routes[$routePath] = $routeCallback->bindTo($this, __CLASS__); 12 | } 13 | 14 | public function dispatch($currentPath) 15 | { 16 | foreach ($this->routes as $routePath => $callback) { 17 | if ($routePath === $currentPath) { 18 | $callback(); 19 | } 20 | } 21 | 22 | header('HTTP/1.1 ' . $this->responseStatus); 23 | header('Content-type: ' . $this->responseContentType); 24 | header('Content-length: ' . mb_strlen($this->responseBody)); 25 | echo $this->responseBody; 26 | } 27 | } 28 | 29 | $app = new App(); 30 | $app->addRoute('/users/josh', function () { 31 | $this->responseContentType = 'application/json;charset=utf8'; 32 | $this->responseBody = '{"name": "Josh"}'; 33 | }); 34 | $app->dispatch('/users/josh'); 35 | -------------------------------------------------------------------------------- /02-features/closures/attach-state-use.php: -------------------------------------------------------------------------------- 1 | "Clay, get me sweet tea!" 14 | -------------------------------------------------------------------------------- /02-features/closures/simple.php: -------------------------------------------------------------------------------- 1 | "Hello Josh" 8 | -------------------------------------------------------------------------------- /02-features/generators/csv-generator.php: -------------------------------------------------------------------------------- 1 | command = $command; 9 | } 10 | 11 | public function getId() 12 | { 13 | return $this->command; 14 | } 15 | 16 | public function getContent() 17 | { 18 | return shell_exec($this->command); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /02-features/interfaces/DocumentStore.php: -------------------------------------------------------------------------------- 1 | getId(); 9 | $value = $document->getContent(); 10 | $this->data[$key] = $value; 11 | } 12 | 13 | public function getDocuments() 14 | { 15 | return $this->data; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /02-features/interfaces/Documentable.php: -------------------------------------------------------------------------------- 1 | url = $url; 9 | } 10 | 11 | public function getId() 12 | { 13 | return $this->url; 14 | } 15 | 16 | public function getContent() 17 | { 18 | $ch = curl_init(); 19 | curl_setopt($ch, CURLOPT_URL, $this->url); 20 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 21 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); 22 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 23 | curl_setopt($ch, CURLOPT_MAXREDIRS, 3); 24 | $html = curl_exec($ch); 25 | curl_close($ch); 26 | 27 | return $html; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /02-features/interfaces/StreamDocument.php: -------------------------------------------------------------------------------- 1 | resource = $resource; 10 | $this->buffer = $buffer; 11 | } 12 | 13 | public function getId() 14 | { 15 | return 'resource-' . (int)$this->resource; 16 | } 17 | 18 | public function getContent() 19 | { 20 | $streamContent = ''; 21 | rewind($this->resource); 22 | while (feof($this->resource) === false) { 23 | $streamContent .= fread($this->resource, $this->buffer); 24 | } 25 | 26 | return $streamContent; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /02-features/interfaces/index.php: -------------------------------------------------------------------------------- 1 | addDocument($htmlDoc); 13 | 14 | // Add stream document 15 | $streamDoc = new StreamDocument(fopen('stream.txt', 'rb')); 16 | $documentStore->addDocument($streamDoc); 17 | 18 | // Add terminal command document 19 | $cmdDoc = new CommandOutputDocument('cat /etc/hosts'); 20 | $documentStore->addDocument($cmdDoc); 21 | 22 | print_r($documentStore->getDocuments()); 23 | -------------------------------------------------------------------------------- /02-features/interfaces/stream.txt: -------------------------------------------------------------------------------- 1 | This is stream content. 2 | -------------------------------------------------------------------------------- /02-features/namespaces/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | composer.lock 3 | -------------------------------------------------------------------------------- /02-features/namespaces/01-http-foundation.php: -------------------------------------------------------------------------------- 1 | send(); 5 | -------------------------------------------------------------------------------- /02-features/namespaces/02-http-foundation.php: -------------------------------------------------------------------------------- 1 | send(); 8 | -------------------------------------------------------------------------------- /02-features/namespaces/03-http-foundation.php: -------------------------------------------------------------------------------- 1 | send(); 8 | -------------------------------------------------------------------------------- /02-features/namespaces/04-namespace-qualification.php: -------------------------------------------------------------------------------- 1 | geocoder = $geocoder; 16 | } 17 | 18 | public function setAddress($address) 19 | { 20 | $this->address = $address; 21 | } 22 | 23 | public function getLatitude() 24 | { 25 | if (!isset($this->geocoderResult)) { 26 | $this->geocodeAddress(); 27 | } 28 | 29 | return $this->geocoderResult->first()->getLatitude(); 30 | } 31 | 32 | public function getLongitude() 33 | { 34 | if (!isset($this->geocoderResult)) { 35 | $this->geocodeAddress(); 36 | } 37 | 38 | return $this->geocoderResult->first()->getLongitude(); 39 | } 40 | 41 | protected function geocodeAddress() 42 | { 43 | $this->geocoderResult = $this->geocoder->geocode($this->address); 44 | 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /02-features/traits/RetailStore.php: -------------------------------------------------------------------------------- 1 | setAddress('420 9th Avenue, New York, NY 10001 USA'); 11 | $store->setGeocoder($geocoder); 12 | 13 | $latitude = $store->getLatitude(); 14 | $longitude = $store->getLongitude(); 15 | 16 | echo $latitude, ':', $longitude; 17 | -------------------------------------------------------------------------------- /03-standards/psr3/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /03-standards/psr3/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "monolog/monolog": "^1.17" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /03-standards/psr3/index.php: -------------------------------------------------------------------------------- 1 | pushHandler(new StreamHandler('logs/development.log', Logger::DEBUG)); 12 | $log->pushHandler(new StreamHandler('logs/production.log', Logger::WARNING)); 13 | 14 | // Use logger 15 | $log->debug('This is a debug message'); 16 | $log->warning('This is a warning message'); 17 | -------------------------------------------------------------------------------- /03-standards/psr3/logs/development.log: -------------------------------------------------------------------------------- 1 | [2016-01-23 11:16:47] myApp.DEBUG: This is a debug message [] [] 2 | [2016-01-23 11:16:47] myApp.WARNING: This is a warning message [] [] 3 | -------------------------------------------------------------------------------- /03-standards/psr3/logs/production.log: -------------------------------------------------------------------------------- 1 | [2016-01-23 11:16:47] myApp.WARNING: This is a warning message [] [] 2 | -------------------------------------------------------------------------------- /03-standards/psr4/index.php: -------------------------------------------------------------------------------- 1 | sayHello(); 45 | -------------------------------------------------------------------------------- /03-standards/psr4/src/Bar.php: -------------------------------------------------------------------------------- 1 | options($csvRow[0]); 14 | 15 | // 5. Inspect HTTP response status code 16 | if ($httpResponse->getStatusCode() >= 400) { 17 | throw new \Exception(); 18 | } 19 | } catch (\Exception $e) { 20 | // 6. Send bad URLs to standard out 21 | echo $csvRow[0] . PHP_EOL; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /04-components/url-scanner-app/urls.csv: -------------------------------------------------------------------------------- 1 | https://apple.com 2 | http://sdsdg.org 3 | http://php.net 4 | -------------------------------------------------------------------------------- /04-components/url-scanner-component/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /04-components/url-scanner-component/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modernphp/scanner", 3 | "description": "Scan URLs from a CSV file and report inaccessible URLs", 4 | "keywords": ["url", "scanner", "csv"], 5 | "homepage": "http://example.com", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Josh Lockhart", 10 | "homepage": "https://github.com/codeguy", 11 | "role": "Developer" 12 | } 13 | ], 14 | "support": { 15 | "email": "help@example.com" 16 | }, 17 | "require": { 18 | "php" : ">=5.6.0", 19 | "guzzlehttp/guzzle": "^6.1" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^5.0" 23 | }, 24 | "suggest": { 25 | "league/csv": "^8.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Oreilly\\ModernPHP\\": "src/" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /04-components/url-scanner-component/example.php: -------------------------------------------------------------------------------- 1 | getInvalidUrls()); 11 | -------------------------------------------------------------------------------- /04-components/url-scanner-component/src/Url/Scanner.php: -------------------------------------------------------------------------------- 1 | urls = $urls; 23 | $this->httpClient = new \GuzzleHttp\Client(); 24 | } 25 | 26 | /** 27 | * Get invalid URLs 28 | * @return array 29 | */ 30 | public function getInvalidUrls() 31 | { 32 | $invalidUrls = []; 33 | foreach ($this->urls as $url) { 34 | try { 35 | $statusCode = $this->getStatusCodeForUrl($url); 36 | } catch (\Exception $e) { 37 | $statusCode = 500; 38 | } 39 | 40 | if ($statusCode >= 400) { 41 | array_push($invalidUrls, [ 42 | 'url' => $url, 43 | 'status' => $statusCode 44 | ]); 45 | } 46 | } 47 | 48 | return $invalidUrls; 49 | } 50 | 51 | /** 52 | * Get HTTP status code for URL 53 | * @param string $url The remote URL 54 | * @return int The HTTP status code 55 | */ 56 | protected function getStatusCodeForUrl($url) 57 | { 58 | $httpResponse = $this->httpClient->options($url); 59 | 60 | return $httpResponse->getStatusCode(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /05-good-practices/databases/pdo-constructor-settings.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 28 | $statement->bindValue(':email', $email); 29 | -------------------------------------------------------------------------------- /05-good-practices/databases/prepared-statement-id.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 28 | $statement->bindValue(':id', $userId, PDO::PARAM_INT); 29 | -------------------------------------------------------------------------------- /05-good-practices/databases/prepared-statement-results.php: -------------------------------------------------------------------------------- 1 | prepare($sql); 26 | $email = filter_input(INPUT_GET, 'email'); 27 | $statement->bindValue(':email', $email, PDO::PARAM_INT); 28 | 29 | // Iterate results one at a time 30 | echo 'One result as a time as associative array', PHP_EOL; 31 | $statement->execute(); 32 | while (($result = $statement->fetch(PDO::FETCH_ASSOC)) !== false) { 33 | echo $result['email'], PHP_EOL; 34 | } 35 | 36 | // Iterate ALL results at once 37 | echo 'All results at once as associative array', PHP_EOL; 38 | $statement->execute(); 39 | $allResults = $statement->fetchAll(PDO::FETCH_ASSOC); 40 | foreach ($allResults as $result) { 41 | echo $result['email'], PHP_EOL; 42 | } 43 | 44 | // Fetch one column value at a time 45 | echo 'Fetch one column, one row at a time as associative array', PHP_EOL; 46 | $statement->execute(); 47 | while (($email = $statement->fetchColumn(1)) !== false) { 48 | echo $email, PHP_EOL; 49 | } 50 | 51 | // Iterate results as objects 52 | echo 'One result as a time as object', PHP_EOL; 53 | $statement->execute(); 54 | while (($result = $statement->fetchObject()) !== false) { 55 | echo $result->email, PHP_EOL; 56 | } 57 | -------------------------------------------------------------------------------- /05-good-practices/databases/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS books; 2 | CREATE TABLE books ( 3 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 4 | title TEXT, 5 | author TEXT, 6 | isbn TEXT, 7 | PRIMARY KEY (id) 8 | ); 9 | 10 | INSERT INTO books VALUES (NULL, 'Sahara', 'Clive Cussler', '9781439135686'); 11 | INSERT INTO books VALUES (NULL, 'Atlantis Found', 'Clive Cussler', '9780425177174'); 12 | INSERT INTO books VALUES (NULL, 'Inca Gold', 'Clive Cussler', '9781416525721'); 13 | 14 | DROP TABLE IF EXISTS users; 15 | CREATE TABLE users ( 16 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 17 | email VARCHAR(255) NOT NULL, 18 | password VARCHAR(255) NOT NULL, 19 | 20 | PRIMARY KEY (id) 21 | ); 22 | 23 | # Real password is "password" 24 | INSERT INTO users VALUES (NULL, 'john@example.com', '$2y$10$q/39UmDLlVZvk9z.bHhgVuqKyVIYq4OibWs697.52qCdJvufJayg2'); 25 | 26 | DROP TABLE IF EXISTS accounts; 27 | CREATE TABLE accounts ( 28 | id INT UNSIGNED NOT NULL AUTO_INCREMENT, 29 | name VARCHAR(255), 30 | amount DECIMAL(10,2), 31 | PRIMARY KEY (id) 32 | ); 33 | 34 | INSERT INTO accounts VALUES (NULL, 'Checking', 1500.50); 35 | INSERT INTO accounts VALUES (NULL, 'Savings', 75500.20); 36 | -------------------------------------------------------------------------------- /05-good-practices/databases/settings.php: -------------------------------------------------------------------------------- 1 | '127.0.0.1', 4 | 'port' => '3306', 5 | 'name' => 'books', 6 | 'username' => 'root', 7 | 'password' => 'jlwaxhaw', 8 | 'charset' => 'utf8' 9 | ]; 10 | -------------------------------------------------------------------------------- /05-good-practices/databases/transactions-with.php: -------------------------------------------------------------------------------- 1 | prepare(' 25 | UPDATE accounts 26 | SET amount = amount - :amount 27 | WHERE name = :name 28 | '); 29 | $stmtAdd = $pdo->prepare(' 30 | UPDATE accounts 31 | SET amount = amount + :amount 32 | WHERE name = :name 33 | '); 34 | 35 | // Start transaction 36 | $pdo->beginTransaction(); 37 | 38 | // Withdraw funds from account 1 39 | $fromAccount = 'Checking'; 40 | $withdrawal = 50; 41 | $stmtSubtract->bindParam(':name', $fromAccount); 42 | $stmtSubtract->bindParam(':amount', $withDrawal, PDO::PARAM_INT); 43 | $stmtSubtract->execute(); 44 | 45 | // Deposit funds into account 2 46 | $toAccount = 'Savings'; 47 | $deposit = 50; 48 | $stmtAdd->bindParam(':name', $toAccount); 49 | $stmtAdd->bindParam(':amount', $deposit, PDO::PARAM_INT); 50 | $stmtAdd->execute(); 51 | 52 | // Commit transaction 53 | $pdo->commit(); 54 | -------------------------------------------------------------------------------- /05-good-practices/databases/transactions-without.php: -------------------------------------------------------------------------------- 1 | prepare(' 25 | UPDATE accounts 26 | SET amount = amount - :amount 27 | WHERE name = :name 28 | '); 29 | $stmtAdd = $pdo->prepare(' 30 | UPDATE accounts 31 | SET amount = amount + :amount 32 | WHERE name = :name 33 | '); 34 | 35 | // Withdraw funds from account 1 36 | $fromAccount = 'Checking'; 37 | $withdrawal = 50; 38 | $stmtSubtract->bindParam(':name', $fromAccount); 39 | $stmtSubtract->bindParam(':amount', $withDrawal, PDO::PARAM_INT); 40 | $stmtSubtract->execute(); 41 | 42 | // Deposit funds into account 2 43 | $toAccount = 'Savings'; 44 | $deposit = 50; 45 | $stmtAdd->bindParam(':name', $toAccount); 46 | $stmtAdd->bindParam(':amount', $deposit, PDO::PARAM_INT); 47 | $stmtAdd->execute(); 48 | -------------------------------------------------------------------------------- /05-good-practices/dates-and-times/dateinterval-class-inverted.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d'), PHP_EOL; 9 | } 10 | -------------------------------------------------------------------------------- /05-good-practices/dates-and-times/dateinterval-class.php: -------------------------------------------------------------------------------- 1 | add($interval); 12 | echo $datetime->format('Y-m-d H:i:s'); 13 | -------------------------------------------------------------------------------- /05-good-practices/dates-and-times/dateperiod-class-options.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d H:i:s'), PHP_EOL; 15 | } 16 | -------------------------------------------------------------------------------- /05-good-practices/dates-and-times/dateperiod-class.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d H:i:s'), PHP_EOL; 10 | } 11 | -------------------------------------------------------------------------------- /05-good-practices/dates-and-times/datetime-class.php: -------------------------------------------------------------------------------- 1 | setTimezone(new DateTimeZone('Asia/Hong_Kong')); 7 | -------------------------------------------------------------------------------- /05-good-practices/dates-and-times/set-default-timezone.php: -------------------------------------------------------------------------------- 1 | getCode(); 7 | $message = $e->getMessage(); 8 | 9 | // Display a nice message to the user 10 | echo 'Something went wrong. Check back soon, please.'; 11 | exit; 12 | } 13 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/exception-handler.php: -------------------------------------------------------------------------------- 1 | getMessage(); 6 | }); 7 | 8 | // Your code goes here... 9 | throw new \Exception("Someting went wrong!"); 10 | 11 | // Restore previous exception handler 12 | restore_exception_handler(); 13 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/monolog/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/monolog/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "monolog/monolog": "~1.11", 4 | "swiftmailer/swiftmailer": "~5.3" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/monolog/development.php: -------------------------------------------------------------------------------- 1 | pushHandler(new StreamHandler('logs/development.log', Logger::WARNING)); 12 | 13 | // Use logger 14 | $log->warning('This is a warning!'); 15 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/monolog/logs/development.log: -------------------------------------------------------------------------------- 1 | [2014-12-13 19:39:00] my-app-name.WARNING: This is a warning! [] [] 2 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/monolog/logs/production.log: -------------------------------------------------------------------------------- 1 | [2014-12-13 19:41:26] my-app-name.CRITICAL: The server is on fire! [] [] 2 | [2014-12-13 14:41:46] my-app-name.CRITICAL: The server is on fire! [] [] 3 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/monolog/production.php: -------------------------------------------------------------------------------- 1 | pushHandler(new StreamHandler('logs/production.log', Logger::WARNING)); 15 | 16 | // Add SwiftMailer handler for critical errors 17 | $transport = \Swift_SmtpTransport::newInstance('smtp.example.com', 587) 18 | ->setUsername('USERNAME') 19 | ->setPassword('PASSWORD'); 20 | $mailer = \Swift_Mailer::newInstance($transport); 21 | $message = \Swift_Message::newInstance() 22 | ->setSubject('Website error!') 23 | ->setFrom(array('daemon@example.com' => 'John Doe')) 24 | ->setTo(array('admin@example.com')); 25 | $log->pushHandler(new SwiftMailerHandler($mailer, $message, Logger::CRITICAL)); 26 | 27 | // Use logger 28 | $log->critical('The server is on fire!'); 29 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/whoops/.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/whoops/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "filp/whoops": "~1.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /05-good-practices/exceptions/whoops/index.php: -------------------------------------------------------------------------------- 1 | pushHandler(new \Whoops\Handler\PrettyPageHandler); 8 | $whoops->register(); 9 | 10 | throw new \Exception('This is an exception!'); 11 | -------------------------------------------------------------------------------- /05-good-practices/passwords/login.php: -------------------------------------------------------------------------------- 1 | password_hash) === false) { 15 | throw new Exception('Invalid password'); 16 | } 17 | 18 | // Re-hash password if necessary (see note below) 19 | $currentHashAlgorithm = PASSWORD_DEFAULT; 20 | $currentHashOptions = array('cost' => 15); 21 | $passwordNeedsRehash = password_needs_rehash( 22 | $user->password_hash, 23 | $currentHashAlgorithm, 24 | $currentHashOptions 25 | ); 26 | if ($passwordNeedsRehash === true) { 27 | // Save new password hash (THIS IS PSUEDO-CODE) 28 | $user->password_hash = password_hash( 29 | $password, 30 | $currentHashAlgorithm, 31 | $currentHashOptions 32 | ); 33 | $user->save(); 34 | } 35 | 36 | // Save login status to session 37 | $_SESSION['user_logged_in'] = 'yes'; 38 | $_SESSION['user_email'] = $email; 39 | 40 | // Redirect to profile page 41 | header('HTTP/1.1 302 Redirect'); 42 | header('Location: /user-profile.php'); 43 | } catch (Exception $e) { 44 | header('HTTP/1.1 401 Unauthorized'); 45 | echo $e->getMessage(); 46 | } 47 | -------------------------------------------------------------------------------- /05-good-practices/passwords/register.php: -------------------------------------------------------------------------------- 1 | 12] 20 | ); 21 | if ($passwordHash === false) { 22 | throw new Exception('Password hash failed'); 23 | } 24 | 25 | // Create user account (THIS IS PSUEDO-CODE) 26 | // $user = new User(); 27 | // $user->email = $email; 28 | // $user->password_hash = $passwordHash; 29 | // $user->save(); 30 | 31 | // Redirect to login page 32 | header('HTTP/1.1 302 Redirect'); 33 | header('Location: /login.php'); 34 | } catch (Exception $e) { 35 | // Report error 36 | header('HTTP/1.1 400 Bad request'); 37 | echo $e->getMessage(); 38 | } 39 | -------------------------------------------------------------------------------- /05-good-practices/sanitize-validate-escape/escape-output.php: -------------------------------------------------------------------------------- 1 | '; 3 | echo htmlentities($output, ENT_QUOTES, 'UTF-8'); 4 | -------------------------------------------------------------------------------- /05-good-practices/sanitize-validate-escape/sanitize-email.php: -------------------------------------------------------------------------------- 1 |

'; 3 | echo htmlentities($input, ENT_QUOTES, 'UTF-8'); 4 | -------------------------------------------------------------------------------- /05-good-practices/sanitize-validate-escape/sanitize-international.php: -------------------------------------------------------------------------------- 1 | data = str_replace($bad, $good, $bucket->data); 26 | 27 | // Increment total data consumed 28 | $consumed += $bucket->datalen; 29 | 30 | // Send bucket to downstream brigade 31 | stream_bucket_append($out, $bucket); 32 | } 33 | 34 | return PSFS_PASS_ON; 35 | } 36 | } 37 | stream_filter_register('dirty_words_filter', 'DirtyWordsFilter'); 38 | 39 | // Use custom filter 40 | $handle = fopen('data.txt', 'rb'); 41 | stream_filter_append($handle, 'dirty_words_filter'); 42 | while (feof($handle) !== true) { 43 | echo fgets($handle); 44 | } 45 | fclose($handle); 46 | -------------------------------------------------------------------------------- /05-good-practices/streams/context.php: -------------------------------------------------------------------------------- 1 | array( 5 | 'method' => 'POST', 6 | 'header' => "Content-Type: application/json;charset=utf-8;\r\n" . 7 | "Content-Length: " . mb_strlen($requestBody), 8 | 'content' => $requestBody 9 | ) 10 | )); 11 | $response = file_get_contents('https://my-api.com/users', false, $context); 12 | -------------------------------------------------------------------------------- /05-good-practices/streams/data.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, dirt conseCtetur adipisicing elit. Totam blanditiis fugiat quia error, quI maiores grime laudantium eveniet unde quidem animi alias ducimus facilis grease nobis ratione ipsa, natus placeat distinctio deserunt? 2 | -------------------------------------------------------------------------------- /05-good-practices/streams/file-wrapper-explicit.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d') . '.log.bz2'; 7 | if (file_exists($file)) { 8 | $handle = fopen($file, 'rb'); 9 | stream_filter_append($handle, 'bzip2.decompress'); 10 | while (feof($handle) !== true) { 11 | $line = fgets($handle); 12 | if (strpos($line, 'www.example.com') !== false) { 13 | fwrite(STDOUT, $line); 14 | } 15 | } 16 | fclose($handle); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /09-deployment/Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and set up stages 2 | require 'capistrano/setup' 3 | 4 | # Include default deployment tasks 5 | require 'capistrano/deploy' 6 | 7 | # Include tasks from other gems included in your Gemfile 8 | # 9 | # For documentation on these, see for example: 10 | # 11 | # https://github.com/capistrano/rvm 12 | # https://github.com/capistrano/rbenv 13 | # https://github.com/capistrano/chruby 14 | # https://github.com/capistrano/bundler 15 | # https://github.com/capistrano/rails 16 | # https://github.com/capistrano/passenger 17 | # 18 | # require 'capistrano/rvm' 19 | # require 'capistrano/rbenv' 20 | # require 'capistrano/chruby' 21 | # require 'capistrano/bundler' 22 | # require 'capistrano/rails/assets' 23 | # require 'capistrano/rails/migrations' 24 | # require 'capistrano/passenger' 25 | 26 | # Load custom tasks from `lib/capistrano/tasks' if you have any defined 27 | Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } 28 | -------------------------------------------------------------------------------- /09-deployment/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "symfony/http-foundation": "~2.6" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /09-deployment/config/deploy.rb: -------------------------------------------------------------------------------- 1 | # config valid only for current version of Capistrano 2 | lock '3.3.5' 3 | 4 | set :application, 'APP' 5 | set :repo_url, 'git@github.com:USERNAME/REPO.git' 6 | 7 | # Default branch is :master 8 | # ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call 9 | 10 | # Default deploy_to directory is /var/www/my_app_name 11 | set :deploy_to, '/home/deploy/apps/my_app' 12 | 13 | # Default value for :scm is :git 14 | # set :scm, :git 15 | 16 | # Default value for :format is :pretty 17 | # set :format, :pretty 18 | 19 | # Default value for :log_level is :debug 20 | # set :log_level, :debug 21 | 22 | # Default value for :pty is false 23 | # set :pty, true 24 | 25 | # Default value for :linked_files is [] 26 | # set :linked_files, fetch(:linked_files, []).push('config/database.yml') 27 | 28 | # Default value for linked_dirs is [] 29 | # set :linked_dirs, fetch(:linked_dirs, []).push('bin', 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system') 30 | 31 | # Default value for default_env is {} 32 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } 33 | 34 | # Default value for keep_releases is 5 35 | set :keep_releases, 5 36 | 37 | namespace :deploy do 38 | desc "Build" 39 | after :updated, :build do 40 | on roles(:web) do 41 | within release_path do 42 | execute :composer, "install --no-dev --quiet" 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /09-deployment/config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | role :web, %w{deploy@123.456.78.90} 2 | -------------------------------------------------------------------------------- /09-deployment/public/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Code Coverage for /Users/josh/Repos/modern-php/repo/10-testing/src/Whovian.php 6 | 7 | 8 | 9 | 13 | 14 | 15 |
16 |
17 |
18 |
19 | 24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | 81 | 82 | 88 | 89 | 90 | 91 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 109 | 110 | 111 | 112 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 130 | 131 | 132 | 133 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 151 | 152 | 153 | 154 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
 
Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
46 |
47 | 100.00% covered (success) 48 |
49 |
50 |
100.00%
1 / 1
54 |
55 | 100.00% covered (success) 56 |
57 |
58 |
100.00%
3 / 3
CRAP
63 |
64 | 100.00% covered (success) 65 |
66 |
67 |
100.00%
13 / 13
Whovian
75 |
76 | 100.00% covered (success) 77 |
78 |
79 |
100.00%
1 / 1
83 |
84 | 100.00% covered (success) 85 |
86 |
87 |
100.00%
3 / 3
4
92 |
93 | 100.00% covered (success) 94 |
95 |
96 |
100.00%
13 / 13
 __construct($favoriteDoctor)
104 |
105 | 100.00% covered (success) 106 |
107 |
108 |
100.00%
1 / 1
1
113 |
114 | 100.00% covered (success) 115 |
116 |
117 |
100.00%
2 / 2
 say()
125 |
126 | 100.00% covered (success) 127 |
128 |
129 |
100.00%
1 / 1
1
134 |
135 | 100.00% covered (success) 136 |
137 |
138 |
100.00%
1 / 1
 respondTo($input)
146 |
147 | 100.00% covered (success) 148 |
149 |
150 |
100.00%
1 / 1
2
155 |
156 | 100.00% covered (success) 157 |
158 |
159 |
100.00%
10 / 10
167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 |
<?php
class Whovian
{
    /**
     * @var string
     */
    protected $favoriteDoctor;
    /**
     * Constructor
     * @param  string $favoriteDoctor
     */
    public function __construct($favoriteDoctor)
    {
        $this->favoriteDoctor = (string)$favoriteDoctor;
    }
    /**
     * Say
     * @return string
     */
    public function say()
    {
        return 'The best doctor is ' . $this->favoriteDoctor;
    }
    /**
     * Respond to
     * @param  string $input
     * @return string
     * @throws \Exception
     */
    public function respondTo($input)
    {
        $input = strtolower($input);
        $myDoctor = strtolower($this->favoriteDoctor);
        if (strpos($input, $myDoctor) === false) {
            throw new Exception(
                sprintf(
                    'No way! %s is the best doctor ever!',
                    $this->favoriteDoctor
                )
            );
        }
        return 'I agree!';
    }
}
221 | 234 |
235 | 236 | 237 | 238 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /10-testing/coverage/css/nv.d3.css: -------------------------------------------------------------------------------- 1 | 2 | /******************** 3 | * HTML CSS 4 | */ 5 | 6 | 7 | .chartWrap { 8 | margin: 0; 9 | padding: 0; 10 | overflow: hidden; 11 | } 12 | 13 | /******************** 14 | Box shadow and border radius styling 15 | */ 16 | .nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip { 17 | -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); 18 | -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); 19 | box-shadow: 0 5px 10px rgba(0,0,0,.2); 20 | 21 | -webkit-border-radius: 6px; 22 | -moz-border-radius: 6px; 23 | border-radius: 6px; 24 | } 25 | 26 | /******************** 27 | * TOOLTIP CSS 28 | */ 29 | 30 | .nvtooltip { 31 | position: absolute; 32 | background-color: rgba(255,255,255,1.0); 33 | padding: 1px; 34 | border: 1px solid rgba(0,0,0,.2); 35 | z-index: 10000; 36 | 37 | font-family: Arial; 38 | font-size: 13px; 39 | text-align: left; 40 | pointer-events: none; 41 | 42 | white-space: nowrap; 43 | 44 | -webkit-touch-callout: none; 45 | -webkit-user-select: none; 46 | -khtml-user-select: none; 47 | -moz-user-select: none; 48 | -ms-user-select: none; 49 | user-select: none; 50 | } 51 | 52 | /*Give tooltips that old fade in transition by 53 | putting a "with-transitions" class on the container div. 54 | */ 55 | .nvtooltip.with-transitions, .with-transitions .nvtooltip { 56 | transition: opacity 250ms linear; 57 | -moz-transition: opacity 250ms linear; 58 | -webkit-transition: opacity 250ms linear; 59 | 60 | transition-delay: 250ms; 61 | -moz-transition-delay: 250ms; 62 | -webkit-transition-delay: 250ms; 63 | } 64 | 65 | .nvtooltip.x-nvtooltip, 66 | .nvtooltip.y-nvtooltip { 67 | padding: 8px; 68 | } 69 | 70 | .nvtooltip h3 { 71 | margin: 0; 72 | padding: 4px 14px; 73 | line-height: 18px; 74 | font-weight: normal; 75 | background-color: rgba(247,247,247,0.75); 76 | text-align: center; 77 | 78 | border-bottom: 1px solid #ebebeb; 79 | 80 | -webkit-border-radius: 5px 5px 0 0; 81 | -moz-border-radius: 5px 5px 0 0; 82 | border-radius: 5px 5px 0 0; 83 | } 84 | 85 | .nvtooltip p { 86 | margin: 0; 87 | padding: 5px 14px; 88 | text-align: center; 89 | } 90 | 91 | .nvtooltip span { 92 | display: inline-block; 93 | margin: 2px 0; 94 | } 95 | 96 | .nvtooltip table { 97 | margin: 6px; 98 | border-spacing:0; 99 | } 100 | 101 | 102 | .nvtooltip table td { 103 | padding: 2px 9px 2px 0; 104 | vertical-align: middle; 105 | } 106 | 107 | .nvtooltip table td.key { 108 | font-weight:normal; 109 | } 110 | .nvtooltip table td.value { 111 | text-align: right; 112 | font-weight: bold; 113 | } 114 | 115 | .nvtooltip table tr.highlight td { 116 | padding: 1px 9px 1px 0; 117 | border-bottom-style: solid; 118 | border-bottom-width: 1px; 119 | border-top-style: solid; 120 | border-top-width: 1px; 121 | } 122 | 123 | .nvtooltip table td.legend-color-guide div { 124 | width: 8px; 125 | height: 8px; 126 | vertical-align: middle; 127 | } 128 | 129 | .nvtooltip .footer { 130 | padding: 3px; 131 | text-align: center; 132 | } 133 | 134 | 135 | .nvtooltip-pending-removal { 136 | position: absolute; 137 | pointer-events: none; 138 | } 139 | 140 | 141 | /******************** 142 | * SVG CSS 143 | */ 144 | 145 | 146 | svg { 147 | -webkit-touch-callout: none; 148 | -webkit-user-select: none; 149 | -khtml-user-select: none; 150 | -moz-user-select: none; 151 | -ms-user-select: none; 152 | user-select: none; 153 | /* Trying to get SVG to act like a greedy block in all browsers */ 154 | display: block; 155 | width:100%; 156 | height:100%; 157 | } 158 | 159 | 160 | svg text { 161 | font: normal 12px Arial; 162 | } 163 | 164 | svg .title { 165 | font: bold 14px Arial; 166 | } 167 | 168 | .nvd3 .nv-background { 169 | fill: white; 170 | fill-opacity: 0; 171 | /* 172 | pointer-events: none; 173 | */ 174 | } 175 | 176 | .nvd3.nv-noData { 177 | font-size: 18px; 178 | font-weight: bold; 179 | } 180 | 181 | 182 | /********** 183 | * Brush 184 | */ 185 | 186 | .nv-brush .extent { 187 | fill-opacity: .125; 188 | shape-rendering: crispEdges; 189 | } 190 | 191 | 192 | 193 | /********** 194 | * Legend 195 | */ 196 | 197 | .nvd3 .nv-legend .nv-series { 198 | cursor: pointer; 199 | } 200 | 201 | .nvd3 .nv-legend .disabled circle { 202 | fill-opacity: 0; 203 | } 204 | 205 | 206 | 207 | /********** 208 | * Axes 209 | */ 210 | .nvd3 .nv-axis { 211 | pointer-events:none; 212 | } 213 | 214 | .nvd3 .nv-axis path { 215 | fill: none; 216 | stroke: #000; 217 | stroke-opacity: .75; 218 | shape-rendering: crispEdges; 219 | } 220 | 221 | .nvd3 .nv-axis path.domain { 222 | stroke-opacity: .75; 223 | } 224 | 225 | .nvd3 .nv-axis.nv-x path.domain { 226 | stroke-opacity: 0; 227 | } 228 | 229 | .nvd3 .nv-axis line { 230 | fill: none; 231 | stroke: #e5e5e5; 232 | shape-rendering: crispEdges; 233 | } 234 | 235 | .nvd3 .nv-axis .zero line, 236 | /*this selector may not be necessary*/ .nvd3 .nv-axis line.zero { 237 | stroke-opacity: .75; 238 | } 239 | 240 | .nvd3 .nv-axis .nv-axisMaxMin text { 241 | font-weight: bold; 242 | } 243 | 244 | .nvd3 .x .nv-axis .nv-axisMaxMin text, 245 | .nvd3 .x2 .nv-axis .nv-axisMaxMin text, 246 | .nvd3 .x3 .nv-axis .nv-axisMaxMin text { 247 | text-anchor: middle 248 | } 249 | 250 | 251 | 252 | /********** 253 | * Brush 254 | */ 255 | 256 | .nv-brush .resize path { 257 | fill: #eee; 258 | stroke: #666; 259 | } 260 | 261 | 262 | 263 | /********** 264 | * Bars 265 | */ 266 | 267 | .nvd3 .nv-bars .negative rect { 268 | zfill: brown; 269 | } 270 | 271 | .nvd3 .nv-bars rect { 272 | zfill: steelblue; 273 | fill-opacity: .75; 274 | 275 | transition: fill-opacity 250ms linear; 276 | -moz-transition: fill-opacity 250ms linear; 277 | -webkit-transition: fill-opacity 250ms linear; 278 | } 279 | 280 | .nvd3 .nv-bars rect.hover { 281 | fill-opacity: 1; 282 | } 283 | 284 | .nvd3 .nv-bars .hover rect { 285 | fill: lightblue; 286 | } 287 | 288 | .nvd3 .nv-bars text { 289 | fill: rgba(0,0,0,0); 290 | } 291 | 292 | .nvd3 .nv-bars .hover text { 293 | fill: rgba(0,0,0,1); 294 | } 295 | 296 | 297 | /********** 298 | * Bars 299 | */ 300 | 301 | .nvd3 .nv-multibar .nv-groups rect, 302 | .nvd3 .nv-multibarHorizontal .nv-groups rect, 303 | .nvd3 .nv-discretebar .nv-groups rect { 304 | stroke-opacity: 0; 305 | 306 | transition: fill-opacity 250ms linear; 307 | -moz-transition: fill-opacity 250ms linear; 308 | -webkit-transition: fill-opacity 250ms linear; 309 | } 310 | 311 | .nvd3 .nv-multibar .nv-groups rect:hover, 312 | .nvd3 .nv-multibarHorizontal .nv-groups rect:hover, 313 | .nvd3 .nv-discretebar .nv-groups rect:hover { 314 | fill-opacity: 1; 315 | } 316 | 317 | .nvd3 .nv-discretebar .nv-groups text, 318 | .nvd3 .nv-multibarHorizontal .nv-groups text { 319 | font-weight: bold; 320 | fill: rgba(0,0,0,1); 321 | stroke: rgba(0,0,0,0); 322 | } 323 | 324 | /*********** 325 | * Pie Chart 326 | */ 327 | 328 | .nvd3.nv-pie path { 329 | stroke-opacity: 0; 330 | transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; 331 | -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; 332 | -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; 333 | 334 | } 335 | 336 | .nvd3.nv-pie .nv-slice text { 337 | stroke: #000; 338 | stroke-width: 0; 339 | } 340 | 341 | .nvd3.nv-pie path { 342 | stroke: #fff; 343 | stroke-width: 1px; 344 | stroke-opacity: 1; 345 | } 346 | 347 | .nvd3.nv-pie .hover path { 348 | fill-opacity: .7; 349 | } 350 | .nvd3.nv-pie .nv-label { 351 | pointer-events: none; 352 | } 353 | .nvd3.nv-pie .nv-label rect { 354 | fill-opacity: 0; 355 | stroke-opacity: 0; 356 | } 357 | 358 | /********** 359 | * Lines 360 | */ 361 | 362 | .nvd3 .nv-groups path.nv-line { 363 | fill: none; 364 | stroke-width: 1.5px; 365 | /* 366 | stroke-linecap: round; 367 | shape-rendering: geometricPrecision; 368 | 369 | transition: stroke-width 250ms linear; 370 | -moz-transition: stroke-width 250ms linear; 371 | -webkit-transition: stroke-width 250ms linear; 372 | 373 | transition-delay: 250ms 374 | -moz-transition-delay: 250ms; 375 | -webkit-transition-delay: 250ms; 376 | */ 377 | } 378 | 379 | .nvd3 .nv-groups path.nv-line.nv-thin-line { 380 | stroke-width: 1px; 381 | } 382 | 383 | 384 | .nvd3 .nv-groups path.nv-area { 385 | stroke: none; 386 | /* 387 | stroke-linecap: round; 388 | shape-rendering: geometricPrecision; 389 | 390 | stroke-width: 2.5px; 391 | transition: stroke-width 250ms linear; 392 | -moz-transition: stroke-width 250ms linear; 393 | -webkit-transition: stroke-width 250ms linear; 394 | 395 | transition-delay: 250ms 396 | -moz-transition-delay: 250ms; 397 | -webkit-transition-delay: 250ms; 398 | */ 399 | } 400 | 401 | .nvd3 .nv-line.hover path { 402 | stroke-width: 6px; 403 | } 404 | 405 | /* 406 | .nvd3.scatter .groups .point { 407 | fill-opacity: 0.1; 408 | stroke-opacity: 0.1; 409 | } 410 | */ 411 | 412 | .nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point { 413 | fill-opacity: 0; 414 | stroke-opacity: 0; 415 | } 416 | 417 | .nvd3.nv-scatter.nv-single-point .nv-groups .nv-point { 418 | fill-opacity: .5 !important; 419 | stroke-opacity: .5 !important; 420 | } 421 | 422 | 423 | .with-transitions .nvd3 .nv-groups .nv-point { 424 | transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 425 | -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 426 | -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 427 | 428 | } 429 | 430 | .nvd3.nv-scatter .nv-groups .nv-point.hover, 431 | .nvd3 .nv-groups .nv-point.hover { 432 | stroke-width: 7px; 433 | fill-opacity: .95 !important; 434 | stroke-opacity: .95 !important; 435 | } 436 | 437 | 438 | .nvd3 .nv-point-paths path { 439 | stroke: #aaa; 440 | stroke-opacity: 0; 441 | fill: #eee; 442 | fill-opacity: 0; 443 | } 444 | 445 | 446 | 447 | .nvd3 .nv-indexLine { 448 | cursor: ew-resize; 449 | } 450 | 451 | 452 | /********** 453 | * Distribution 454 | */ 455 | 456 | .nvd3 .nv-distribution { 457 | pointer-events: none; 458 | } 459 | 460 | 461 | 462 | /********** 463 | * Scatter 464 | */ 465 | 466 | /* **Attempting to remove this for useVoronoi(false), need to see if it's required anywhere 467 | .nvd3 .nv-groups .nv-point { 468 | pointer-events: none; 469 | } 470 | */ 471 | 472 | .nvd3 .nv-groups .nv-point.hover { 473 | stroke-width: 20px; 474 | stroke-opacity: .5; 475 | } 476 | 477 | .nvd3 .nv-scatter .nv-point.hover { 478 | fill-opacity: 1; 479 | } 480 | 481 | /* 482 | .nv-group.hover .nv-point { 483 | fill-opacity: 1; 484 | } 485 | */ 486 | 487 | 488 | /********** 489 | * Stacked Area 490 | */ 491 | 492 | .nvd3.nv-stackedarea path.nv-area { 493 | fill-opacity: .7; 494 | /* 495 | stroke-opacity: .65; 496 | fill-opacity: 1; 497 | */ 498 | stroke-opacity: 0; 499 | 500 | transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; 501 | -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; 502 | -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; 503 | 504 | /* 505 | transition-delay: 500ms; 506 | -moz-transition-delay: 500ms; 507 | -webkit-transition-delay: 500ms; 508 | */ 509 | 510 | } 511 | 512 | .nvd3.nv-stackedarea path.nv-area.hover { 513 | fill-opacity: .9; 514 | /* 515 | stroke-opacity: .85; 516 | */ 517 | } 518 | /* 519 | .d3stackedarea .groups path { 520 | stroke-opacity: 0; 521 | } 522 | */ 523 | 524 | 525 | 526 | .nvd3.nv-stackedarea .nv-groups .nv-point { 527 | stroke-opacity: 0; 528 | fill-opacity: 0; 529 | } 530 | 531 | /* 532 | .nvd3.nv-stackedarea .nv-groups .nv-point.hover { 533 | stroke-width: 20px; 534 | stroke-opacity: .75; 535 | fill-opacity: 1; 536 | }*/ 537 | 538 | 539 | 540 | /********** 541 | * Line Plus Bar 542 | */ 543 | 544 | .nvd3.nv-linePlusBar .nv-bar rect { 545 | fill-opacity: .75; 546 | } 547 | 548 | .nvd3.nv-linePlusBar .nv-bar rect:hover { 549 | fill-opacity: 1; 550 | } 551 | 552 | 553 | /********** 554 | * Bullet 555 | */ 556 | 557 | .nvd3.nv-bullet { font: 10px sans-serif; } 558 | .nvd3.nv-bullet .nv-measure { fill-opacity: .8; } 559 | .nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; } 560 | .nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; } 561 | .nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; } 562 | .nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; } 563 | .nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; } 564 | .nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; } 565 | .nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; } 566 | .nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; } 567 | .nvd3.nv-bullet .nv-subtitle { fill: #999; } 568 | 569 | 570 | .nvd3.nv-bullet .nv-range { 571 | fill: #bababa; 572 | fill-opacity: .4; 573 | } 574 | .nvd3.nv-bullet .nv-range:hover { 575 | fill-opacity: .7; 576 | } 577 | 578 | 579 | 580 | /********** 581 | * Sparkline 582 | */ 583 | 584 | .nvd3.nv-sparkline path { 585 | fill: none; 586 | } 587 | 588 | .nvd3.nv-sparklineplus g.nv-hoverValue { 589 | pointer-events: none; 590 | } 591 | 592 | .nvd3.nv-sparklineplus .nv-hoverValue line { 593 | stroke: #333; 594 | stroke-width: 1.5px; 595 | } 596 | 597 | .nvd3.nv-sparklineplus, 598 | .nvd3.nv-sparklineplus g { 599 | pointer-events: all; 600 | } 601 | 602 | .nvd3 .nv-hoverArea { 603 | fill-opacity: 0; 604 | stroke-opacity: 0; 605 | } 606 | 607 | .nvd3.nv-sparklineplus .nv-xValue, 608 | .nvd3.nv-sparklineplus .nv-yValue { 609 | /* 610 | stroke: #666; 611 | */ 612 | stroke-width: 0; 613 | font-size: .9em; 614 | font-weight: normal; 615 | } 616 | 617 | .nvd3.nv-sparklineplus .nv-yValue { 618 | stroke: #f66; 619 | } 620 | 621 | .nvd3.nv-sparklineplus .nv-maxValue { 622 | stroke: #2ca02c; 623 | fill: #2ca02c; 624 | } 625 | 626 | .nvd3.nv-sparklineplus .nv-minValue { 627 | stroke: #d62728; 628 | fill: #d62728; 629 | } 630 | 631 | .nvd3.nv-sparklineplus .nv-currentValue { 632 | /* 633 | stroke: #444; 634 | fill: #000; 635 | */ 636 | font-weight: bold; 637 | font-size: 1.1em; 638 | } 639 | 640 | /********** 641 | * historical stock 642 | */ 643 | 644 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick { 645 | stroke-width: 2px; 646 | } 647 | 648 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover { 649 | stroke-width: 4px; 650 | } 651 | 652 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive { 653 | stroke: #2ca02c; 654 | } 655 | 656 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative { 657 | stroke: #d62728; 658 | } 659 | 660 | .nvd3.nv-historicalStockChart .nv-axis .nv-axislabel { 661 | font-weight: bold; 662 | } 663 | 664 | .nvd3.nv-historicalStockChart .nv-dragTarget { 665 | fill-opacity: 0; 666 | stroke: none; 667 | cursor: move; 668 | } 669 | 670 | .nvd3 .nv-brush .extent { 671 | /* 672 | cursor: ew-resize !important; 673 | */ 674 | fill-opacity: 0 !important; 675 | } 676 | 677 | .nvd3 .nv-brushBackground rect { 678 | stroke: #000; 679 | stroke-width: .4; 680 | fill: #fff; 681 | fill-opacity: .7; 682 | } 683 | 684 | 685 | 686 | /********** 687 | * Indented Tree 688 | */ 689 | 690 | 691 | /** 692 | * TODO: the following 3 selectors are based on classes used in the example. I should either make them standard and leave them here, or move to a CSS file not included in the library 693 | */ 694 | .nvd3.nv-indentedtree .name { 695 | margin-left: 5px; 696 | } 697 | 698 | .nvd3.nv-indentedtree .clickable { 699 | color: #08C; 700 | cursor: pointer; 701 | } 702 | 703 | .nvd3.nv-indentedtree span.clickable:hover { 704 | color: #005580; 705 | text-decoration: underline; 706 | } 707 | 708 | 709 | .nvd3.nv-indentedtree .nv-childrenCount { 710 | display: inline-block; 711 | margin-left: 5px; 712 | } 713 | 714 | .nvd3.nv-indentedtree .nv-treeicon { 715 | cursor: pointer; 716 | /* 717 | cursor: n-resize; 718 | */ 719 | } 720 | 721 | .nvd3.nv-indentedtree .nv-treeicon.nv-folded { 722 | cursor: pointer; 723 | /* 724 | cursor: s-resize; 725 | */ 726 | } 727 | 728 | /********** 729 | * Parallel Coordinates 730 | */ 731 | 732 | .nvd3 .background path { 733 | fill: none; 734 | stroke: #ccc; 735 | stroke-opacity: .4; 736 | shape-rendering: crispEdges; 737 | } 738 | 739 | .nvd3 .foreground path { 740 | fill: none; 741 | stroke: steelblue; 742 | stroke-opacity: .7; 743 | } 744 | 745 | .nvd3 .brush .extent { 746 | fill-opacity: .3; 747 | stroke: #fff; 748 | shape-rendering: crispEdges; 749 | } 750 | 751 | .nvd3 .axis line, .axis path { 752 | fill: none; 753 | stroke: #000; 754 | shape-rendering: crispEdges; 755 | } 756 | 757 | .nvd3 .axis text { 758 | text-shadow: 0 1px 0 #fff; 759 | } 760 | 761 | /**** 762 | Interactive Layer 763 | */ 764 | .nvd3 .nv-interactiveGuideLine { 765 | pointer-events:none; 766 | } 767 | .nvd3 line.nv-guideline { 768 | stroke: #ccc; 769 | } -------------------------------------------------------------------------------- /10-testing/coverage/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 10px; 3 | } 4 | 5 | .popover { 6 | max-width: none; 7 | } 8 | 9 | .glyphicon { 10 | margin-right:.25em; 11 | } 12 | 13 | .table-bordered>thead>tr>td { 14 | border-bottom-width: 1px; 15 | } 16 | 17 | .table tbody>tr>td, .table thead>tr>td { 18 | padding-top: 3px; 19 | padding-bottom: 3px; 20 | } 21 | 22 | .table-condensed tbody>tr>td { 23 | padding-top: 0; 24 | padding-bottom: 0; 25 | } 26 | 27 | .table .progress { 28 | margin-bottom: inherit; 29 | } 30 | 31 | .table-borderless th, .table-borderless td { 32 | border: 0 !important; 33 | } 34 | 35 | .table tbody td.success, li.success, span.success { 36 | background-color: #dff0d8; 37 | } 38 | 39 | .table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { 40 | background-color: #f2dede; 41 | } 42 | 43 | .table tbody td.warning, li.warning, span.warning { 44 | background-color: #fcf8e3; 45 | } 46 | 47 | .table tbody td.info { 48 | background-color: #d9edf7; 49 | } 50 | 51 | td.big { 52 | width: 117px; 53 | } 54 | 55 | td.small { 56 | } 57 | 58 | td.codeLine { 59 | font-family: monospace; 60 | white-space: pre; 61 | } 62 | 63 | td span.comment { 64 | color: #888a85; 65 | } 66 | 67 | td span.default { 68 | color: #2e3436; 69 | } 70 | 71 | td span.html { 72 | color: #888a85; 73 | } 74 | 75 | td span.keyword { 76 | color: #2e3436; 77 | font-weight: bold; 78 | } 79 | 80 | pre span.string { 81 | color: #2e3436; 82 | } 83 | 84 | span.success, span.warning, span.danger { 85 | margin-right: 2px; 86 | padding-left: 10px; 87 | padding-right: 10px; 88 | text-align: center; 89 | } 90 | 91 | #classCoverageDistribution, #classComplexity { 92 | height: 200px; 93 | width: 475px; 94 | } 95 | 96 | #toplink { 97 | position: fixed; 98 | left: 5px; 99 | bottom: 5px; 100 | outline: 0; 101 | } 102 | 103 | svg text { 104 | font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; 105 | font-size: 11px; 106 | color: #666; 107 | fill: #666; 108 | } 109 | 110 | .scrollbox { 111 | height:245px; 112 | overflow-x:hidden; 113 | overflow-y:scroll; 114 | } -------------------------------------------------------------------------------- /10-testing/coverage/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dashboard for /Users/josh/Repos/modern-php/repo/10-testing/src 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 |
17 |
18 |
19 |
20 | 25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |

Classes

33 |
34 |
35 |
36 |
37 |

Coverage Distribution

38 |
39 | 40 |
41 |
42 |
43 |

Complexity

44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 |

Insufficient Coverage

52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
ClassCoverage
64 |
65 |
66 |
67 |

Project Risks

68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
ClassCRAP
80 |
81 |
82 |
83 |
84 |
85 |

Methods

86 |
87 |
88 |
89 |
90 |

Coverage Distribution

91 |
92 | 93 |
94 |
95 |
96 |

Complexity

97 |
98 | 99 |
100 |
101 |
102 |
103 |
104 |

Insufficient Coverage

105 |
106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
MethodCoverage
117 |
118 |
119 |
120 |

Project Risks

121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 |
MethodCRAP
133 |
134 |
135 |
136 | 142 |
143 | 144 | 145 | 146 | 147 | 148 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /10-testing/coverage/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeguy/modern-php/2f6036af0165986fc36597d1ea4893cc4dc5865e/10-testing/coverage/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /10-testing/coverage/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeguy/modern-php/2f6036af0165986fc36597d1ea4893cc4dc5865e/10-testing/coverage/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /10-testing/coverage/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeguy/modern-php/2f6036af0165986fc36597d1ea4893cc4dc5865e/10-testing/coverage/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /10-testing/coverage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Code Coverage for /Users/josh/Repos/modern-php/repo/10-testing/src 6 | 7 | 8 | 9 | 13 | 14 | 15 |
16 |
17 |
18 |
19 | 24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 81 | 87 | 88 | 89 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
46 |
47 | 100.00% covered (success) 48 |
49 |
50 |
100.00%
13 / 13
54 |
55 | 100.00% covered (success) 56 |
57 |
58 |
100.00%
3 / 3
62 |
63 | 100.00% covered (success) 64 |
65 |
66 |
100.00%
1 / 1
Whovian.php
74 |
75 | 100.00% covered (success) 76 |
77 |
78 |
100.00%
13 / 13
82 |
83 | 100.00% covered (success) 84 |
85 |
86 |
100.00%
3 / 3
90 |
91 | 100.00% covered (success) 92 |
93 |
94 |
100.00%
1 / 1
102 | 114 |
115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /10-testing/coverage/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.0",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus","focus"==b.type)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.0",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c="prev"==a?-1:1,d=this.getItemIndex(b),e=(d+c)%this.$items.length;return this.$items.eq(e)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i="next"==b?"first":"last",j=this;if(!f.length){if(!this.options.wrap)return;f=this.$element.find(".item")[i]()}if(f.hasClass("active"))return this.sliding=!1;var k=f[0],l=a.Event("slide.bs.carousel",{relatedTarget:k,direction:h});if(this.$element.trigger(l),!l.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var m=a(this.$indicators.children()[this.getItemIndex(f)]);m&&m.addClass("active")}var n=a.Event("slid.bs.carousel",{relatedTarget:k,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),j.sliding=!1,setTimeout(function(){j.$element.trigger(n)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(n)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.0",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.find("> .panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('