diff --git a/di_config.php b/di_config.php new file mode 100644 index 0000000..ab7d289 --- /dev/null +++ b/di_config.php @@ -0,0 +1,18 @@ + "data/settings.toml", + "settings.file_custom" => "settings.default.toml", + + \SBRL\TomlConfig::class => function(ContainerInterface $c) { + return new TomlConfig( + $c->get("settings.file_default"), + $c->get("settings.file_custom") + ); + }, + + \SBRL\SessionManager::class => DI\factory([ \SBRL\SessionManager::class, "get_instance" ]) +]; diff --git a/index.php b/index.php new file mode 100644 index 0000000..9f0cf30 --- /dev/null +++ b/index.php @@ -0,0 +1,59 @@ +addPrefix("AirQuality", "logic"); +$autoloader->addPrefix("SBRL", "lib/SBRL"); +$autoloader->register(); + + +// 2: Dependency injection + +$di_builder = new DI\ContainerBuilder(); +$di_builder->addDefinitions("di_config.php"); + +$di_container = $di_builder->build(); + + +// 3: Settings + +$settings = $di_container->get(\SBRL\TomlConfig::class); + + +// 4: Database + +// TODO: Setup the database here + + +// 5: Action + +// Figure out the action name +$action = str_replace( + " ", "", + ucwords(str_replace( + "-", " ", + preg_replace("/[^a-z-]/i", "", $_GET["action"] ?? $settings->get("routing.default-action")) + )) +); + +$handler_name = "AirQuality\\Actions\\$action"; + +// if(!class_exists($handler_name)) +// send_error(404, "Unknown action $handler_name."); + +// Fetch it from the dependency injector and execute it, but only if it exists + +if(!class_exists($handler_name)) { + http_response_code(404); + header("content-type: text/plain"); + exit("Error: No action with the name '$action' could be found."); +} + +// FUTURE: A PSR-7 request/response system would be rather nice here. +$handler = $di_container->get($handler_name); +$handler->handle(); diff --git a/lib/SBRL/Generators.php b/lib/SBRL/Generators.php new file mode 100644 index 0000000..195dfa3 --- /dev/null +++ b/lib/SBRL/Generators.php @@ -0,0 +1,13 @@ +customSettingsBanner = "-------[ Custom Settings File - Last updated " . date("Y-m-d") . " ]-------"; + + $this->defaultSettings = Toml::ParseFile($defaultSettingsFilePath, true); + + $this->customSettings = new stdClass(); + if(file_exists($settingsFilePath)) { + $this->customSettings = Toml::ParseFile($settingsFilePath, true); + } else { + mkdir(dirname($settingsFilePath), 0750, true); + file_put_contents($settingsFilePath, "$this->customSettingsBanner\n"); + } + } + /** + * Gets the value identified by the specified property, in dotted notation (e.g. `Network.Firewalling.Ports.Http`) + * @param string $key The property, in dotted notation, to return. + * @return mixed The value odentified by the given dotted property notation. + */ + public function get($key) { + if(static::hasPropertyByPath($this->customSettings, $key)) { + return static::getPropertyByPath($this->customSettings, $key); + } + return static::getPropertyByPath($this->defaultSettings, $key); + } + /** + * Fetches the default value for the specified property, specified in dotted notation. + * @param string $key The key to fetch. + * @return mixed The default value of the property identified by the given dotted notation. + */ + public function getDefault($key) { + return static::getPropertyByPath($this->defaultSettings, $key); + } + /** + * Determines whether the given property has been customised or explicitly specified in the customised settings file. + * @param string $key The property, in dotted notation, to check. + * @return boolean Whether the specified properties file has been explicitly specified in the custom settings file. + */ + public function hasChanged($key) { + return static::hasPropertyByPath($this->customSettings, $key); + } + /** + * Sets the property identified by the specified dotted path. + * Does not re-save the properties back to disk! + * @param string $key The dotted path to update. + * @param mixed $value The value to set the property to. + * @return self This ConfigFile instance, to aid method chaining. + */ + public function set($key, $value) { + static::setPropertyByPath($this->customSettings, $key, $value); + return $this; + } + + public function setCustomSettingsBanner($value) { + $this->customSettingsBanner = $value; + return $this; + } + + /** + * Saves the customised settings back to the disk. + * @return boolean Whether the save was successful or not. + */ + public function save() { + $output_builder = new TomlBuilder(); + $toml_builder->addComment($this->customSettingsBanner); + + $this->build($toml_builder, $this->customSettings); + + return file_put_contents($settingsFilePath, $toml_builder->getTomlString()); + } + + private function build($toml_builder, $customSettingsObject, $subobject_name = null) { + if($subobject_name !== null) + $toml_builder->addTable($subobject_name); + + foreach($customSettingsObject as $key => $value) { + if(!\is_object($value)) + $toml_builder->addValue($key, $value); + else + $this->build($toml_builder, $value, "$subobject_name.$key"); + } + } + + /** + * Works out whether a given dotted path to a property exists on a given object. + * @param stdClass $obj The object to search. + * @param string $path The dotted path to search with. e.g. `Network.Firewall.OpenPorts` + * @return boolean Whether the given property is present in the given object. + */ + public static function hasPropertyByPath($obj, $path) { + $pathParts = explode(".", $path); + $subObj = $obj; + foreach($pathParts as $part) { + if(is_object($subObj) && !isset($subObj->$part)) + return false; + else if(is_array($subObj) && !isset($subObj[$part])) + return false; + else if(!is_object($subObj) && !is_array($subObj)) + return false; + + if(is_object($subObj)) + $subObj = $subObj->$part; + else // It must be an array + $subObj = $subObj[$part]; + } + return true; + } + /** + * Returns the property identified by the given dotted path. + * If you're not sure whether a property exists or not, use static::hasPropertyByPath() first to check. + * @param stdClass $obj The object to fetch from. + * @param string $path The path to extract from the given object. + * @return mixed The property identified by the given object. + */ + public static function getPropertyByPath($obj, $path) { + $pathParts = explode(".", $path); + $subObj = $obj; + foreach($pathParts as $part) { + if(is_object($subObj)) + $subObj = $subObj->$part; + else if(is_array($subObj)) + $subObj = $subObj[$part]; + else + throw new Exception("Error: Can't find part '$part' on sub-item if it isn't an object or array"); + } + return $subObj; + } + /** + * Returns the property identified by the given dotted path. + * If you're not sure whether a property exists or not, use static::hasPropertyByPath() first to check. + * @param stdClass $obj The object to set the property on. + * @param string $path The path to set on the given object. + * @param mixed $value The value to set the given property to. + */ + public static function setPropertyByPath($obj, $path, $value) { + $pathParts = explode(".", $path); + $pathPartsCount = count($pathParts); + + $subObj = $obj; + for($i = 0; $i < $pathPartsCount; $i++) { + // Set the property on the last iteration + if($i + 1 == $pathPartsCount) { + $subObj->$part = $value; + break; + } + // Create sub-objects if they dobn't exist already + if(!isset($subObj->$part)) { + $subObj->$part = new stdClass(); + } + $subObj = $subObj->$part; + } + } +} diff --git a/logic/Actions/FetchData.php b/logic/Actions/FetchData.php new file mode 100644 index 0000000..b293e3b --- /dev/null +++ b/logic/Actions/FetchData.php @@ -0,0 +1,23 @@ +settings = $in_settings; + $this->renderer = $in_renderer; + } + + public function handle() { + echo($this->renderer->render_file(ROOT_DIR."templates/main.html", [ + "title" => "Home", + "content" => file_get_contents(ROOT_DIR."templates/mockup.html"), + "footer_html" => "" + ])); + } +} diff --git a/logic/Validator.php b/logic/Validator.php new file mode 100644 index 0000000..9b0a047 --- /dev/null +++ b/logic/Validator.php @@ -0,0 +1,111 @@ +target_data = $in_target_data; + } + + public function exists($key) { + $this->add_test( + $key, + function($data) { return isset($data); }, + 400, + "Error: The field $key wasn't found in your request." + ); + } + + public function is_numberish($key) { + $this->add_test( + $key, + function($data) { return is_numeric($data); }, + 400, + "Error: The field $key in your request isn't a number." + ); + } + public function is_min_length($key, $min_length) { + $this->add_test( + $key, + function($data) use ($min_length) { return mb_strlen($data) >= $min_length; }, + 400, + "Error: The field $key is too short - it must be a minimum of $min_length characters." + ); + } + public function is_max_length($key, $max_length) { + $this->add_test( + $key, + function($data) use ($max_length) { return mb_strlen($data) <= $max_length; }, + 400, + "Error: The field $key is too long - it must be a maximum of $max_length characters." + ); + } + + public function are_equal($key_a, $key_b, $message) { + $key_b_data = $this->target_data[$key_b]; + $this->add_test( + $key_a, + function($data) use($key_b_data) { + return $data === $key_b_data; + }, + 400, + $message + ); + } + + public function matches_regex($key, $regex, $message) { + $this->add_test( + $key, + function($data) use($regex) { + return preg_match($regex, $data) === 1; + }, + 400, + $message + ); + } + + public function is_valid_email($key) { + $this->add_test( + $key, + function($data) { + $validator = new \EmailValidator\Validator(); + return $validator->isValid($data); + }, + 400, + "That email address isn't valid." + ); + } + + public function add_test($key, $test, $response_code, $message) { + $new_test = [ + "key" => $key, + "test" => $test, + "response_code" => $response_code, + "message" => $message + ]; + + $this->tests[] = $new_test; + } + + protected function send_error($code, $message) { + http_response_code($code); + header("content-type: text/plain"); + exit($message); + } + + public function run() { + foreach($this->tests as $test) { + + if(!isset($this->target_data[$test["key"]]) || !$test["test"]($this->target_data[$test["key"]])) { + $this->send_error($test["response_code"], $test["message"]); + } + + } + } + + +} diff --git a/settings.default.toml b/settings.default.toml new file mode 100644 index 0000000..695c187 --- /dev/null +++ b/settings.default.toml @@ -0,0 +1,17 @@ +# This file defines the default configuration settings. +##### DO NOT EDIT THIS FILE. ##### +# Your changes may be overwritten in a future update. +# Instead, edit the custom configuration file located at "data/settings.toml". + +[database] +# Settings that control the database, or the connection to it + +type = mysql # MariaDB. MySQL servers might work too, but no promises. +username = "user" +password = "Define_in_custom_config_file" + +[routing] +# Settings that control the router's behaviour + +# The default action to take if no action is specified +default-action = fetch-data