diff --git a/dist/README.md b/dist/README.md index 86d6312..0f147d7 100644 --- a/dist/README.md +++ b/dist/README.md @@ -1,7 +1,5 @@ - # EasyCoder distribution file set + # EasyCoder server files - Here are the files that can be used to create standalone EasyCoder applications. Not all will be needed for any given application but it's probably simplest to copy them all. The main `easycoder.js` contains everything that will be needed for a wide range of applications. Extra functionality is supplied by the files in the `plugins` folder. These will be requested where they are needed, using the `require` keyword. - - The files in the `plugin` folder are simply copies of the source set at `js`. The two files `easycoder.js` and `easycoder-min.js` are built from their sources; the latter is a minimized version of the former. + These are mostly server-side files. The most significant are 2 small REST servers written in PHP and Python, which perform the same functions. The former is for setting up a `localhost` server and the latter is for use on LAMP servers. - To use EasyCoder, put the entire `easycoder` folder onto your webserver (giving it any suitable name) and in your HTML page header call for `easycoder.js` or `easycoder-min.js` (the former is more useful for debugging). This will call in other files as it needs them. + The files `easycoder.php`, `ec-rest.txt` and `readme.txt` are for WordPress installations. diff --git a/dist/easycoder.php b/dist/easycoder.php new file mode 100644 index 0000000..4ba3cd8 --- /dev/null +++ b/dist/easycoder.php @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/dist/ec-rest.txt b/dist/ec-rest.txt new file mode 100644 index 0000000..d133204 --- /dev/null +++ b/dist/ec-rest.txt @@ -0,0 +1,26 @@ +# The properties file for EasyCoder +# It is required mostly by the REST server - see the documentation +# Modify as required and install it above the root of your WordPress installation (if possible) +# Rename it to {your website URL}.txt +# Do not include the braces shown below + +# The URL of the MySQL server +sqlhost={your mysql server} + +# The name of the database user +sqluser={your mysql user name} + +# The database password +sqlpassword={your mysql password} + +# The database to use +sqldatabase={the name of your database} + +# An encrypted password, if needed, e.g. for restricted admin access to your site. +# If you use 'rest get Result from `_verify/` cat {password}' +# the REST server will encrypt the value of {password} and compare it with +# the value held here. It will return 'yes' if there is a match; 'no' if not +# and place it in the Result variable. +# You can create a hash value using this URL: +# {Your website}/wp-content/plugins/easycoder/rest.php/_hash/{password} +password={encrypted password} \ No newline at end of file diff --git a/dist/readme.txt b/dist/readme.txt new file mode 100644 index 0000000..595e940 --- /dev/null +++ b/dist/readme.txt @@ -0,0 +1,193 @@ +=== EasyCoder Plugin === + +Donate link: https://easycoder.software +Contributors: gtanyware +Tags: code, compiler, css, customise, customize, debug, debugger, DSL, interactivity, graphics, javascript, program, programming, rest, script, scripting, tracer, webservice +Requires at least: 4.4 +Requires PHP: 5.2 +Tested up to: 5.2 +Stable tag: trunk +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + +Control the appearance and behavior of your posts and pages by embedding simple English-like scripts, without the need to learn JavaScript + +== Description == + +*EasyCoder* is a programming language written in JavaScript/ECMAScript6 and packaged as a browser plugin. It is known to work with popular browsers such as Chrome and Firefox on Windows or Linux, as well as on current iOS and Android devices. + +*EasyCoder* is for non-programmers, who generally find JavaScript hard to learn. Not everyone wants to be a professional programmer; most just want to achieve results with the minimum of effort. The work done by JavaScript in a web page mostly uses a small fraction of its capabilities, so we've taken the more common requirements and packaged them up in an English-like script syntax. Some of the things you may want to do are + +* Change the appearance of a screen element by modifying its CSS attributes. You may want something a little - or a lot - different to what your theme provides +* Show and hide parts of your content, avoiding the need to reload the page by putting everything in at the start and selecting which parts of it to show +* React to button clicks and other events by altering the appearance or revealing content as above +* Retrieve content from web services using REST and JSON +* Draw and animate simple graphics +* Create a Google Map, put markers on it and intearct with the map and the markers +* Use CodeMirror to create a unique color-coded text editor +* Include the CKEditor rich text editor in your pages +* Include the Showdown markdown converter in your pages + +*EasyCoder* provides a simple syntax to do all these things, at about the same level of complexity and readability as SQL or Excel macros. It's a full programming language, though, capable of making complex logical decisions. + +*EasyCoder* scripts are embedded in your page or post, inside a special "preformatted" tag. When the page loads, *EasyCoder* looks for this element then compiles and runs the script it contains. When it interacts with HTML elements it attaches their IDs to its own variables, so your HTML and its controlling script are in the same file. + +The *EasyCoder* core module is currently about 60k bytes in its minimised form and it downloads its own plugin modules from a growing library. Its performance is good because it precompiles scripts - a process that takes jus a few tens of milliseconds - and the compiled code for each command is only a thin wrapper around the corresponding JavaScript functionality. + +When *EasyCoder* detects an error, either in compilation or at runtime, it opens a popup window with a friendly error message that tries to tell you what went wrong and where in the script it happened. + +As a further help when developing scripts, *EasyCoder* has a single-step tracer where you can decide which variables to display for each step of your script. To use this feature you add another special "preformatted" section to your page and add the 'trace' command in your script where you want tracing to start. See the [Documentation](https://easycoder.software/documentation). + +*EasyCoder* has a fully pluggable architecture. This means any JavaScript development team can make their own plugins, that 'wrap' *EasyCoder* functionality round their products so WordPress site developers can use them without the need to learn JavaScript. Plugins can be served from any website without any need to notify EasyCoder Software. + +== Documentation == + +Extensive documentation is available at our [documentation](https://easycoder.software/documentation)page. + +== Examples == + +A range of example scripts can be seen at our [examples](https://easycoder.software/examples) page. + +== Learning resources == + +For tutorials and a programmers' reference see our [EasyCoder Software Codex](https://codex.easycoder.software). + +== Changelog == + += 2.6.0 21-feb 2020 = +* Added a VFX plugin; many other updates & bug fixes + += 2.5.6 14-dec-2019 = +* Fix bug in REST handling errors + += 2.5.5 12-dec-2019 = +* Fix problems with exit(); replace program with name in objects + += 2.5.4 10-dec-2019 = +* Fixed bug in parent script handling + += 2.5.3 09-dec-2019 = +* Revised run/import handling & minor additions + += 2.5.2 05-oct-2019 = +* Fix bugs in drag & drop and others + += 2.5.1 30-aug-2019 = +* CORS, bug fixes + += 2.5.0 26-aug-2019 = +* Drag & drop + += 2.4.6 09-aug-2019 = +* Minor updates to gmap and others + += 2.4.5 24-jul-2019 = +* Update version number + += 2.4.4 21-jul-2019 = +* Add 'or' clause to 'attach' + += 2.4.3 5-jul-2019 = +* Refactor for faster tokenizing + += 2.4.2 22-jun-2019 = +* Fix another bug in REST server + += 2.4.1 18-jun-2019 = +* Fix bug in REST server + += 2.4.0 18-jun-2019 = +* Sorting & filtering; added the script editor + += 2.3.1 7-jun-2019 = +* Bug fixes & updates to support learn-to-code + += 2.3.0 16-may-2019 = +* All JS modues version-numbered; split json into json/rest + += 2.2.6 12-may-2019 = +* Updated 'require'; replaced missing sample file + += 2.2.5 9-may-2019 = +* Updated 'require'; replaced missing sample file + += 2.2.4 1-may-2019 = +* Handler for on leave + += 2.2.3 29-apr-2019 = +* Detect portrait and landscape + += 2.2.2 1-apr-2019 = +* Added date formatting + += 2.2.1 17-mar-2019 = +* New plugin: anagrams. Various changes/optimisations/bugfixes + += 2.2.0 25-feb-2019 = +* New plugins: gmap and showdown. + += 2.1.9 21-jan-2019 = +* Code checked by eslint. + += 2.1.8 16-dec-2018 = +* All UI components can be created programmatically. + += 2.1.7 09-dec-2018 = +* Moved local files out of plugins folder. + += 2.1.6 07-dec-2018 = +* Fixed bugs in plugin loader. + += 2.1.5 06-dec-2018 = +* Improvements to REST; new UI package. + += 2.1.4 18-nov-2018 = +* Bug fixes and minor improvements. + += 2.1.3 29-oct-2018 = +* Revised REST functionality. + += 2.1.2 26-oct-2018 = +* Better error handling and reporting. + += 2.1.1 25-oct-2018 = +* Added some more REST functions and a REST server. + += 2.1.0 21-oct-2018 = +* Change from curly braces to backticks. + += 2.0.1 18-oct-2018 = +* Bug fixes. + += 2.0.0 17-oct-2018 = +* New pluggable architecture. + += 1.5.3 11-oct-2018 = +* Added 'replace'; fixed bugs. + += 1.5.2 = +* Added some minor features, fixed bugs. + += 1.5.1 = +* Fix some REST bugs. + += 1.5.0 = +* Added sin, cos & tan trig functions; left and right substring operators; json shuffle. + += 1.4.0 = +* Added alias variables. + += 1.3.0 = +* Added width/height, modulo and array data assignment. Various other bug fixes. + += 1.2.0 = +* Added new features to groups and other graphics items. + += 1.1.1 = +* Bug fix + += 1.1.0 = +* Added graphics features based on SVG. + += 1.0.0 = +* Initial release version. diff --git a/dist/rest-local-sample.php b/dist/rest-local-sample.php new file mode 100644 index 0000000..70efebf --- /dev/null +++ b/dist/rest-local-sample.php @@ -0,0 +1,31 @@ + diff --git a/dist/rest.php b/dist/rest.php new file mode 100644 index 0000000..138274e --- /dev/null +++ b/dist/rest.php @@ -0,0 +1,653 @@ + 1) { + $props[$ss[0]] = $ss[1]; + } + } + fclose($file); + } + $resources = $props['resources'] ? $props['resources'] : 'easycoder'; + $scripts = $props['scripts'] ? $props['scripts'] : 'easycoder/scripts'; + $upload = $props['upload'] ? $props['upload'] : 'easycoder/upload'; + + // First, the commands that don't require a database connection. + switch ($method) { + case 'GET': + switch ($table) { + case '_list': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_list/[{path}] + // List the contents of a directory + if (count($request) && $request[0]) { + $path = '/' . str_replace('~', '/', $request[0]); + } + // Start at the easycoder folder + $path = "../../../$resources$path"; + $files = scandir($path); + print '['; + // First list all the directories + $flag = false; + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + print "{\"name\":\"$file\",\"type\":\"dir\"}"; + } + } + } + // Now do the ordinary files + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (!is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + $type = 'file'; + $p = strrpos($file, '.'); + if ($p > 0) { + $ext = substr($file, $p + 1); + $type = $ext; + switch (strtolower($ext)) { + case 'jpg': + case 'jpeg': + case 'png': + case 'gif': + $type = 'img'; + break; + } + } + print "{\"name\":\"$file\",\"type\":\"$type\"}"; + } + } + } + print ']'; + exit; + case '_hash': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_hash/{value-to-hash} + print password_hash($request[0], PASSWORD_DEFAULT); + exit; + case '_verify': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_verify/{value-to-verify} + print password_verify($request[0], $props['password']) ? 'yes' : 'no'; + exit; + case '_verify2': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_verify2/{value-to-verify}/{hash} + print password_verify($request[0], str_replace('~', '/', $request[1])) ? 'yes' : 'no'; + exit; + case '_validate': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_validate/{value-to-validate}/{encrypted-value} + print password_verify($request[0], str_replace('~', '/', $request[1])) ? 'yes' : 'no'; + exit; + case '_load': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_load/{path} + header("Content-Type: text/plain"); + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + print file_get_contents($path); + exit; + case '_loadall': + // Load all the files in the named folder + // Endpoint: {site root}/easycoder/rest.php/_loadall/{path} + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + $files = scandir($path); + print '['; + $flag = false; + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (!is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + print file_get_contents("$path/$file"); + } + } + } + print ']'; + exit; + case '_scripted': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_scripted + print file_get_contents('scripted'); + exit; + case '_eclist': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_eclist/[{path}] + // List the contents of a directory + if (count($request) && $request[0]) { + $path = '/' . str_replace('~', '/', $request[0]); + } + $files = scandir(getcwd().$path); + print '['; + // First list all the directories + $flag = false; + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + print "{\"name\":\"$file\",\"type\":\"dir\"}"; + } + } + } + // Now do the ordinary files + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (!is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + $type = 'file'; + $p = strrpos($file, '.'); + if ($p > 0) { + $ext = substr($file, $p + 1); + $type = $ext; + switch (strtolower($ext)) { + case 'jpg': + case 'jpeg': + case 'png': + case 'gif': + $type = 'img'; + break; + } + } + print "{\"name\":\"$file\",\"type\":\"$type\"}"; + } + } + } + print ']'; + exit; + case '_ecload': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_load/{path} + header("Content-Type: text/plain"); + $path = str_replace('~', '/', $request[0]); + print file_get_contents($path); + exit; + break; + } + case 'POST': + switch ($table) { + case '_mkdir': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_mkdir/{path} + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + mkdir($path, 0777, true); + exit; + case '_upload': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_upload + // Upload a file (an image) to the current directory + $path = $_POST['path']; + $pathsegs = explode("/", $path); + $path = "../../../$upload/" . str_replace('~', '/', $pathsegs[1]); + mkdir($path, 0777, true); + $fileName = str_replace(' ', '-', $_FILES['source']['name']); + $tempName = $_FILES['source']['tmp_name']; + $fileType = $_FILES['source']['type']; + $fileSize = $_FILES['source']['size']; + $fileError = $_FILES['source']['error']; + if (!move_uploaded_file($tempName, "$path/$fileName")) { + unlink($tempName); + http_response_code(400); + logger("Failed to upload $fileName to $path/$fileName.\ntempName: $tempName\nfileType: $fileType\nfileSize:$fileSize\nfileError: $fileError"); + } else { + logger("File $fileName uploaded successfully to $path/$fileName"); + $size = getimagesize("$path/$fileName"); + logger("$path/$fileName: width:".$size[0].", height:".$size[1]); + if ($size[0] > 1024) { + logger("mogrify -resize 1024x1024 $path/$fileName"); + system("mogrify -resize 1024x1024 $path/$fileName"); + } + } + exit; + case '_thumb': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_thumb + header("Content-Type: application/json"); + $value = stripslashes(file_get_contents("php://input")); + $json = json_decode($value); + $source = "../../../$resources/" . str_replace('~', '/', $json->source); + $dest = "../../../$resources/" . str_replace('~', '/', $json->dest); + mkdir(substr($dest, 0, strrpos($dest, '/')), 0777, true); + system("convert $source -resize 100x100^ -gravity Center -crop 100x100+0+0 +repage $dest"); + exit; + case '_delete': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_delete/{path} + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + if (is_dir($path)) { + rmdir($path); + } else { + unlink($path); + } + exit; + case '_email': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_email + header("Content-Type: application/text"); + $value = stripslashes(file_get_contents("php://input")); + $json = json_decode($value); + $from = $json->from; + $to = $json->to; + $subject = $json->subject; + $message = $json->message; + $headers = "MIME-Version: 1.0\r\n"; + $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n"; + $headers .= "From: $from\r\n"; + mail($to, $subject, $message, "$headers\r\n"); + print "$headers\r\n$message"; + exit; + case '_save': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_save/{path} + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + $p = strrpos($path, '/'); + $dir = substr($path, 0, $p); + mkdir($dir, 0777, true); + header("Content-Type: application/text"); + $content = stripslashes(file_get_contents("php://input")); + $p = strrpos($path, '.'); + $root = substr($path, 0, $p); + $ext = substr($path, $p); + $backup = "$root-bak$ext"; + unlink($backup); + copy($path, $backup); + file_put_contents($path, $content); + logger("Saved $path"); + exit; + case '_ecsave': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_ecsave/{path} + $path = str_replace('~', '/', $request[0]); + $p = strrpos($path, '/'); + if ($p > 0) { + $dir = substr($path, 0, $p); + mkdir($dir, 0777, true); + } + header("Content-Type: application/text"); + $content = stripslashes(file_get_contents("php://input")); +// print $content; +// $content = str_replace('\\', '\\\\', $content); + unlink($path); + file_put_contents($path, $content); + exit; + case '_ecdelete': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_ecdelete/{path} + $path = str_replace('~', '/', $request[0]); + if (is_dir($path)) { + rmdir($path); + } else { + unlink($path); + } + exit; + } + break; + case 'OPTIONS': + include '../../../easycoder/rest-local.php'; + options_local($conn, $request); + } + + // Most further commands require use of the database. + $conn = mysqli_connect($props['sqlhost'], $props['sqluser'], + $props['sqlpassword'], $props['sqldatabase']); + if (!$conn) + { + http_response_code(404); + die("Failed to connect to MySQL: " . mysqli_connect_error()); + } + mysqli_set_charset($conn,'utf8'); + + if (!count($request)) { + http_response_code(400); + print "{\"message\":\"Incomplete REST query: ".substr($_SERVER['PATH_INFO'], 1).".\"}"; + exit; + } + + // You can have a custom extension that deals with special requests. + // These all have '_' as the table name. + switch ($method) { + + case 'GET': + if ($table == '_') { + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_/{request-and-arguments} + include '../../../easycoder/rest-local.php'; + get_local($conn, $request); + return; + } else { + // Use the handler below + get($conn, $table, $request); + } + break; + + case 'POST': + if ($table == '_') { + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_/{request-and-arguments} + include '../../../easycoder/rest-local.php'; + post_local($conn, $request); + return; + } else { + // Use the handler below + post($conn, $table, $request); + } + break; + + default: + http_response_code(400); + break; + } + mysqli_close(); + exit; + + ///////////////////////////////////////////////////////////////////////// + // All the other commands deal with tables having a specific format, with the following fields: + // + // id int(11) + // name varchar(40) + // value text + // ts int(11) + // + // GET + function get($conn, $table, $request) { + $action = $request[0]; + switch ($action) { + case 'count': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/count + // Return the number of items in the table + $result = $conn->query("SELECT id from $table"); + //print "{\"count\":".mysqli_num_rows($result)."}"; + print mysqli_num_rows($result); + mysqli_free_result($result); + break; + + case 'list': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/list/{offset}/{count} + // List items by ID, with optional offset & count, defaulting to 0 & 10 + switch (count($request)) { + case 2: + $offset = 0; + $count = $request[1]; + break; + case 3: + $offset = $request[1]; + $count = $request[2]; + break; + default: + $offset = 0; + $count = 10; + break; + } + $result = $conn->query("SELECT id FROM $table LIMIT $offset, $count"); + $response = '['; + while ($row = mysqli_fetch_object($result)) { + if ($response != '[') { + $response .= ','; + } + $response .= $row->id; + } + mysqli_free_result($result); + $response .= ']'; + print $response; + break; + + case 'names': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/names/{offset}/{count} + // List items by name in ascending alphabetical order, + // with optional offset & count, defaulting to 0 & 10 + switch (count($request)) { + case 2: + $offset = 0; + $count = $request[1]; + break; + case 3: + $offset = $request[1]; + $count = $request[2]; + break; + default: + $offset = 0; + $count = 10; + break; + } + $result = $conn->query("SELECT name FROM $table ORDER BY name LIMIT $offset, $count"); + $response = '['; + while ($row = mysqli_fetch_object($result)) { + if ($response != '[') { + $response .= ','; + } + $response .= "\"$row->name\""; + } + mysqli_free_result($result); + $response .= ']'; + print $response; + break; + + case 'id': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/id/{id} + // Get a record given its id + if (count($request) < 2) { + http_response_code(400); + print "Incomplete REST query."; + exit; + } + $id = $request[1]; + $result = $conn->query("SELECT value FROM $table WHERE id='$id'"); + if ($row = mysqli_fetch_object($result)) { + print $row->value; + } else { + http_response_code(404); + print "Cannot get item id '$id' as it does not exist."; + } + mysqli_free_result($result); + break; + + case 'name': + case 'query': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/name/{name} + // Get a record given its name + if (count($request) < 2) { + http_response_code(400); + print "Incomplete REST query."; + exit; + } + $name = $request[1]; + $result = $conn->query("SELECT value FROM $table WHERE name='$name'"); + if ($row = mysqli_fetch_object($result)) { + print $row->value; + } else if ($action == 'name') { + http_response_code(404); + print "Cannot get item named '$name' as it does not exist."; + } + break; + + default: + http_response_code(404); + print "I don't understand this request."; + break; + } + } + + ///////////////////////////////////////////////////////////////////////// + // POST + function post($conn, $table, $request) { + $ts = time(); + $action = $request[0]; + switch ($action) { + case 'set': + // Set the value of a record + if (count($request) > 2) { + switch ($request[1]) { + case 'id': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/set/{table}/id/{id} + // Set by id. The record must already exist + header("Content-Type: application/text"); + $value = stripslashes(file_get_contents("php://input")); + $id = $request[2]; + // See if there's an item with this id + $result = $conn->query("SELECT id FROM $table WHERE id=$id"); + if (mysqli_fetch_object($result)) { + // It exists, so update it + $value = urldecode($value); + logger("UPDATE $table SET value='$value',ts=$ts WHERE id=$id"); + query($conn, "UPDATE $table SET value='$value',ts=$ts WHERE id=$id"); + } else { + // Not found + http_response_code(404); + logger("{\"code\":\"404\",\"message\":\"Cannot set record $id of $table.\"}"); + print "{\"message\":\"Cannot set record $id of $table.\"}"; + } + mysqli_free_result($result); + break; + + case 'name': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/set/{table}/name/{name} + // Set by name. If the record does not exist, add it + header("Content-Type: application/text"); + $value = stripslashes(file_get_contents("php://input")); + $name = $request[2]; + // See if there's an item with this name + $result = $conn->query("SELECT id FROM $table WHERE name='$name'"); + if (mysqli_fetch_object($result)) { + // It exists, so update it + query($conn, "UPDATE $table SET value='$value',ts=$ts WHERE name='$name'"); + } else { + // Add a new item + query($conn, "INSERT INTO $table (name,value,ts) VALUES ('$name','$value','$ts')"); + http_response_code(201); + } + mysqli_free_result($result); + break; + + default: + http_response_code(400); + print "{\"message\":\"Value '".$request[1]."' should be 'id' or 'name'.\"}"; + break; + } + } else { + http_response_code(400); + print "{\"message\":\"Incomplete REST query.\"}"; + } + break; + + case 'delete': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/delete/{id} + // Or: .../rest.php/table/delete/{name} + // Delete a record, by id or by name + if (count($request) > 1) { + $item = $request[1]; + if (is_int($item)) { + // Delete the requested id + query($conn, "DELETE FROM $table WHERE id=$id"); + } else { + // Delete the named item + query($conn, "DELETE FROM $table WHERE name='$item'"); + } + } + break; + + case 'rename': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/rename + // Rename a record + $value = $_POST['value']; + $id = $_POST['id']; + if (!$id && count($request) > 1) { + $id = $request[1]; + } + if ($id) { + query($conn, "UPDATE $table SET name='$name',value='$value' WHERE id=$id"); + } else { + $name = $_POST['name']; + $newname = $_POST['newname']; + // See if there's a data item with the new name + $result = $conn->query("SELECT id FROM $table WHERE name='$newname'"); + if ($row = mysqli_fetch_object($result)) { + // Conflict + http_response_code(409); + print "{\"message\":\"Cannot rename item '$name' to '$newname' as it already exists.\"}"; + } else { + // See if there's a data item with this name + $result = $conn->query("SELECT id FROM $table WHERE name='$name'"); + if ($row = mysqli_fetch_object($result)) { + // There's a data item to rename + $id = $row->id; + query($conn, "UPDATE $table SET name='$newname',value='$value' WHERE id=$id"); + } else { + // Not found + http_response_code(404); + print "{\"message\":\"Cannot rename item '$name' as it does not exist.\"}"; + } + } + mysqli_free_result($result); + } + break; + + default: + http_response_code(404); + print "{\"message\":\"Unrecognised action '$action' requested.\"}"; + break; + } + } + + ///////////////////////////////////////////////////////////////////////// + // Do an SQL query + function query($conn, $sql) + { + $result = mysqli_query($conn, $sql); + if (!$result) { + http_response_code(404); + logger('Error: '.mysqli_error($conn)); + die('Error: '.mysqli_error($conn)); + } + return $result; + } + + //////////////////////////////////////////////////////////////////////////// + // Log a message. + function logger($message) + { + $timestamp = time(); + $date = date("Y/m/d H:i", $timestamp); + if (!file_exists("log")) mkdir("log"); + $file = "log/".date("Y", $timestamp); + if (!file_exists($file)) mkdir($file); + $file.= "/".date("Ymd", $timestamp).".txt"; + $fp = fopen($file, "a+") or die("Can't open $file"); + fwrite($fp, "$date: $message\n"); + fclose($fp); + } +?> diff --git a/dist/rest.py b/dist/rest.py new file mode 100644 index 0000000..6d238b1 --- /dev/null +++ b/dist/rest.py @@ -0,0 +1,85 @@ +import bottle +import subprocess +import os +from bottle import Bottle, run, get, post, request, response, static_file + +def HOST(): + return 'http://localhost:8080' + +# start up the browser (version for Linux/Chromium) +cmd = f'chromium-browser {HOST()}/flist/Codex' +#subprocess.call(cmd, shell=True) + +app = Bottle() + +# Endpoint: GET localhost:8080/_list +# Lists all files in the server root directory +@app.get('/_list') +def listRoot(): + return listFiles('') + +# Endpoint: GET localhost:8080/_list/name +# Lists all files in a specified directory +@app.get('/_list/') +def listFiles(name): + ff = '' + for file in os.listdir(f'resources{"/" if name else ""}{name}'): + path = os.path.join(f'{name if name else ""}', file) + if os.path.isdir(f'resources/{path}'): + type = 'dir' + else: + extension = os.path.splitext(file)[1] + if extension == '.jpg' or extension == '.png' or extension == '.gif': + type = 'img' + else: + type = 'file' + ff += f'{"," if ff else ""}{{"name":"{file}","type":"{type}"}}' + print(ff) + return f'[{ff}]' + +# Endpoint: GET localhost:8080/_load/name +# Load a file +@app.get('/_load/') +def loadFile(name): + f = open(f'resources/{name.replace(chr(0x7e), "/")}', 'r') + response = f.read() + f.close() + return response + +# Endpoint: POST localhost:8080/_save/name +# Save a file +@app.post('/_save/') +def saveFile(name): + f = open(f'resources/{name.replace(chr(0x7e), "/")}', 'w+') + content = request.body.read().decode("utf-8") + f.write(content) + f.close() + return response + +# Endpoint: POST localhost:8080/_delete/connametent +# Delete a file +@app.post('/_delete/') +def deleteFile(name): + os.remove(f'resources/{name.replace(chr(0x7e), "/")}') + return '' + +############################################################################### +# Generic endpoints + +# Endpoint: GET localhost:8080/ +# Gets a file +@app.get('/') +def getFile(filename): + print(f'Get (generic) {filename}') + response = bottle.static_file(filename, root='.') + response.set_header("Cache-Control", "public, max-age=0") + return response + +# Endpoint: GET localhost:8080> +# Gets the default home page +@app.get('/') +def index(): + return getFile('index.html') + +if __name__ == '__main__': + app.run() diff --git a/easycoder/README.md b/easycoder/README.md index 86d6312..0f147d7 100644 --- a/easycoder/README.md +++ b/easycoder/README.md @@ -1,7 +1,5 @@ - # EasyCoder distribution file set + # EasyCoder server files - Here are the files that can be used to create standalone EasyCoder applications. Not all will be needed for any given application but it's probably simplest to copy them all. The main `easycoder.js` contains everything that will be needed for a wide range of applications. Extra functionality is supplied by the files in the `plugins` folder. These will be requested where they are needed, using the `require` keyword. - - The files in the `plugin` folder are simply copies of the source set at `js`. The two files `easycoder.js` and `easycoder-min.js` are built from their sources; the latter is a minimized version of the former. + These are mostly server-side files. The most significant are 2 small REST servers written in PHP and Python, which perform the same functions. The former is for setting up a `localhost` server and the latter is for use on LAMP servers. - To use EasyCoder, put the entire `easycoder` folder onto your webserver (giving it any suitable name) and in your HTML page header call for `easycoder.js` or `easycoder-min.js` (the former is more useful for debugging). This will call in other files as it needs them. + The files `easycoder.php`, `ec-rest.txt` and `readme.txt` are for WordPress installations. diff --git a/easycoder/easycoder.php b/easycoder/easycoder.php new file mode 100644 index 0000000..4ba3cd8 --- /dev/null +++ b/easycoder/easycoder.php @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/easycoder/ec-rest.txt b/easycoder/ec-rest.txt new file mode 100644 index 0000000..d133204 --- /dev/null +++ b/easycoder/ec-rest.txt @@ -0,0 +1,26 @@ +# The properties file for EasyCoder +# It is required mostly by the REST server - see the documentation +# Modify as required and install it above the root of your WordPress installation (if possible) +# Rename it to {your website URL}.txt +# Do not include the braces shown below + +# The URL of the MySQL server +sqlhost={your mysql server} + +# The name of the database user +sqluser={your mysql user name} + +# The database password +sqlpassword={your mysql password} + +# The database to use +sqldatabase={the name of your database} + +# An encrypted password, if needed, e.g. for restricted admin access to your site. +# If you use 'rest get Result from `_verify/` cat {password}' +# the REST server will encrypt the value of {password} and compare it with +# the value held here. It will return 'yes' if there is a match; 'no' if not +# and place it in the Result variable. +# You can create a hash value using this URL: +# {Your website}/wp-content/plugins/easycoder/rest.php/_hash/{password} +password={encrypted password} \ No newline at end of file diff --git a/easycoder/readme.txt b/easycoder/readme.txt new file mode 100644 index 0000000..595e940 --- /dev/null +++ b/easycoder/readme.txt @@ -0,0 +1,193 @@ +=== EasyCoder Plugin === + +Donate link: https://easycoder.software +Contributors: gtanyware +Tags: code, compiler, css, customise, customize, debug, debugger, DSL, interactivity, graphics, javascript, program, programming, rest, script, scripting, tracer, webservice +Requires at least: 4.4 +Requires PHP: 5.2 +Tested up to: 5.2 +Stable tag: trunk +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + +Control the appearance and behavior of your posts and pages by embedding simple English-like scripts, without the need to learn JavaScript + +== Description == + +*EasyCoder* is a programming language written in JavaScript/ECMAScript6 and packaged as a browser plugin. It is known to work with popular browsers such as Chrome and Firefox on Windows or Linux, as well as on current iOS and Android devices. + +*EasyCoder* is for non-programmers, who generally find JavaScript hard to learn. Not everyone wants to be a professional programmer; most just want to achieve results with the minimum of effort. The work done by JavaScript in a web page mostly uses a small fraction of its capabilities, so we've taken the more common requirements and packaged them up in an English-like script syntax. Some of the things you may want to do are + +* Change the appearance of a screen element by modifying its CSS attributes. You may want something a little - or a lot - different to what your theme provides +* Show and hide parts of your content, avoiding the need to reload the page by putting everything in at the start and selecting which parts of it to show +* React to button clicks and other events by altering the appearance or revealing content as above +* Retrieve content from web services using REST and JSON +* Draw and animate simple graphics +* Create a Google Map, put markers on it and intearct with the map and the markers +* Use CodeMirror to create a unique color-coded text editor +* Include the CKEditor rich text editor in your pages +* Include the Showdown markdown converter in your pages + +*EasyCoder* provides a simple syntax to do all these things, at about the same level of complexity and readability as SQL or Excel macros. It's a full programming language, though, capable of making complex logical decisions. + +*EasyCoder* scripts are embedded in your page or post, inside a special "preformatted" tag. When the page loads, *EasyCoder* looks for this element then compiles and runs the script it contains. When it interacts with HTML elements it attaches their IDs to its own variables, so your HTML and its controlling script are in the same file. + +The *EasyCoder* core module is currently about 60k bytes in its minimised form and it downloads its own plugin modules from a growing library. Its performance is good because it precompiles scripts - a process that takes jus a few tens of milliseconds - and the compiled code for each command is only a thin wrapper around the corresponding JavaScript functionality. + +When *EasyCoder* detects an error, either in compilation or at runtime, it opens a popup window with a friendly error message that tries to tell you what went wrong and where in the script it happened. + +As a further help when developing scripts, *EasyCoder* has a single-step tracer where you can decide which variables to display for each step of your script. To use this feature you add another special "preformatted" section to your page and add the 'trace' command in your script where you want tracing to start. See the [Documentation](https://easycoder.software/documentation). + +*EasyCoder* has a fully pluggable architecture. This means any JavaScript development team can make their own plugins, that 'wrap' *EasyCoder* functionality round their products so WordPress site developers can use them without the need to learn JavaScript. Plugins can be served from any website without any need to notify EasyCoder Software. + +== Documentation == + +Extensive documentation is available at our [documentation](https://easycoder.software/documentation)page. + +== Examples == + +A range of example scripts can be seen at our [examples](https://easycoder.software/examples) page. + +== Learning resources == + +For tutorials and a programmers' reference see our [EasyCoder Software Codex](https://codex.easycoder.software). + +== Changelog == + += 2.6.0 21-feb 2020 = +* Added a VFX plugin; many other updates & bug fixes + += 2.5.6 14-dec-2019 = +* Fix bug in REST handling errors + += 2.5.5 12-dec-2019 = +* Fix problems with exit(); replace program with name in objects + += 2.5.4 10-dec-2019 = +* Fixed bug in parent script handling + += 2.5.3 09-dec-2019 = +* Revised run/import handling & minor additions + += 2.5.2 05-oct-2019 = +* Fix bugs in drag & drop and others + += 2.5.1 30-aug-2019 = +* CORS, bug fixes + += 2.5.0 26-aug-2019 = +* Drag & drop + += 2.4.6 09-aug-2019 = +* Minor updates to gmap and others + += 2.4.5 24-jul-2019 = +* Update version number + += 2.4.4 21-jul-2019 = +* Add 'or' clause to 'attach' + += 2.4.3 5-jul-2019 = +* Refactor for faster tokenizing + += 2.4.2 22-jun-2019 = +* Fix another bug in REST server + += 2.4.1 18-jun-2019 = +* Fix bug in REST server + += 2.4.0 18-jun-2019 = +* Sorting & filtering; added the script editor + += 2.3.1 7-jun-2019 = +* Bug fixes & updates to support learn-to-code + += 2.3.0 16-may-2019 = +* All JS modues version-numbered; split json into json/rest + += 2.2.6 12-may-2019 = +* Updated 'require'; replaced missing sample file + += 2.2.5 9-may-2019 = +* Updated 'require'; replaced missing sample file + += 2.2.4 1-may-2019 = +* Handler for on leave + += 2.2.3 29-apr-2019 = +* Detect portrait and landscape + += 2.2.2 1-apr-2019 = +* Added date formatting + += 2.2.1 17-mar-2019 = +* New plugin: anagrams. Various changes/optimisations/bugfixes + += 2.2.0 25-feb-2019 = +* New plugins: gmap and showdown. + += 2.1.9 21-jan-2019 = +* Code checked by eslint. + += 2.1.8 16-dec-2018 = +* All UI components can be created programmatically. + += 2.1.7 09-dec-2018 = +* Moved local files out of plugins folder. + += 2.1.6 07-dec-2018 = +* Fixed bugs in plugin loader. + += 2.1.5 06-dec-2018 = +* Improvements to REST; new UI package. + += 2.1.4 18-nov-2018 = +* Bug fixes and minor improvements. + += 2.1.3 29-oct-2018 = +* Revised REST functionality. + += 2.1.2 26-oct-2018 = +* Better error handling and reporting. + += 2.1.1 25-oct-2018 = +* Added some more REST functions and a REST server. + += 2.1.0 21-oct-2018 = +* Change from curly braces to backticks. + += 2.0.1 18-oct-2018 = +* Bug fixes. + += 2.0.0 17-oct-2018 = +* New pluggable architecture. + += 1.5.3 11-oct-2018 = +* Added 'replace'; fixed bugs. + += 1.5.2 = +* Added some minor features, fixed bugs. + += 1.5.1 = +* Fix some REST bugs. + += 1.5.0 = +* Added sin, cos & tan trig functions; left and right substring operators; json shuffle. + += 1.4.0 = +* Added alias variables. + += 1.3.0 = +* Added width/height, modulo and array data assignment. Various other bug fixes. + += 1.2.0 = +* Added new features to groups and other graphics items. + += 1.1.1 = +* Bug fix + += 1.1.0 = +* Added graphics features based on SVG. + += 1.0.0 = +* Initial release version. diff --git a/easycoder/rest-local-sample.php b/easycoder/rest-local-sample.php new file mode 100644 index 0000000..70efebf --- /dev/null +++ b/easycoder/rest-local-sample.php @@ -0,0 +1,31 @@ + diff --git a/easycoder/rest.php b/easycoder/rest.php new file mode 100644 index 0000000..138274e --- /dev/null +++ b/easycoder/rest.php @@ -0,0 +1,653 @@ + 1) { + $props[$ss[0]] = $ss[1]; + } + } + fclose($file); + } + $resources = $props['resources'] ? $props['resources'] : 'easycoder'; + $scripts = $props['scripts'] ? $props['scripts'] : 'easycoder/scripts'; + $upload = $props['upload'] ? $props['upload'] : 'easycoder/upload'; + + // First, the commands that don't require a database connection. + switch ($method) { + case 'GET': + switch ($table) { + case '_list': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_list/[{path}] + // List the contents of a directory + if (count($request) && $request[0]) { + $path = '/' . str_replace('~', '/', $request[0]); + } + // Start at the easycoder folder + $path = "../../../$resources$path"; + $files = scandir($path); + print '['; + // First list all the directories + $flag = false; + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + print "{\"name\":\"$file\",\"type\":\"dir\"}"; + } + } + } + // Now do the ordinary files + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (!is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + $type = 'file'; + $p = strrpos($file, '.'); + if ($p > 0) { + $ext = substr($file, $p + 1); + $type = $ext; + switch (strtolower($ext)) { + case 'jpg': + case 'jpeg': + case 'png': + case 'gif': + $type = 'img'; + break; + } + } + print "{\"name\":\"$file\",\"type\":\"$type\"}"; + } + } + } + print ']'; + exit; + case '_hash': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_hash/{value-to-hash} + print password_hash($request[0], PASSWORD_DEFAULT); + exit; + case '_verify': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_verify/{value-to-verify} + print password_verify($request[0], $props['password']) ? 'yes' : 'no'; + exit; + case '_verify2': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_verify2/{value-to-verify}/{hash} + print password_verify($request[0], str_replace('~', '/', $request[1])) ? 'yes' : 'no'; + exit; + case '_validate': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_validate/{value-to-validate}/{encrypted-value} + print password_verify($request[0], str_replace('~', '/', $request[1])) ? 'yes' : 'no'; + exit; + case '_load': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_load/{path} + header("Content-Type: text/plain"); + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + print file_get_contents($path); + exit; + case '_loadall': + // Load all the files in the named folder + // Endpoint: {site root}/easycoder/rest.php/_loadall/{path} + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + $files = scandir($path); + print '['; + $flag = false; + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (!is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + print file_get_contents("$path/$file"); + } + } + } + print ']'; + exit; + case '_scripted': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_scripted + print file_get_contents('scripted'); + exit; + case '_eclist': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_eclist/[{path}] + // List the contents of a directory + if (count($request) && $request[0]) { + $path = '/' . str_replace('~', '/', $request[0]); + } + $files = scandir(getcwd().$path); + print '['; + // First list all the directories + $flag = false; + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + print "{\"name\":\"$file\",\"type\":\"dir\"}"; + } + } + } + // Now do the ordinary files + foreach ($files as $file) { + if (strpos($file, '.') !== 0) { + if (!is_dir("$path/$file")) { + if ($flag) { + print ','; + } else { + $flag = true; + } + $type = 'file'; + $p = strrpos($file, '.'); + if ($p > 0) { + $ext = substr($file, $p + 1); + $type = $ext; + switch (strtolower($ext)) { + case 'jpg': + case 'jpeg': + case 'png': + case 'gif': + $type = 'img'; + break; + } + } + print "{\"name\":\"$file\",\"type\":\"$type\"}"; + } + } + } + print ']'; + exit; + case '_ecload': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_load/{path} + header("Content-Type: text/plain"); + $path = str_replace('~', '/', $request[0]); + print file_get_contents($path); + exit; + break; + } + case 'POST': + switch ($table) { + case '_mkdir': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_mkdir/{path} + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + mkdir($path, 0777, true); + exit; + case '_upload': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_upload + // Upload a file (an image) to the current directory + $path = $_POST['path']; + $pathsegs = explode("/", $path); + $path = "../../../$upload/" . str_replace('~', '/', $pathsegs[1]); + mkdir($path, 0777, true); + $fileName = str_replace(' ', '-', $_FILES['source']['name']); + $tempName = $_FILES['source']['tmp_name']; + $fileType = $_FILES['source']['type']; + $fileSize = $_FILES['source']['size']; + $fileError = $_FILES['source']['error']; + if (!move_uploaded_file($tempName, "$path/$fileName")) { + unlink($tempName); + http_response_code(400); + logger("Failed to upload $fileName to $path/$fileName.\ntempName: $tempName\nfileType: $fileType\nfileSize:$fileSize\nfileError: $fileError"); + } else { + logger("File $fileName uploaded successfully to $path/$fileName"); + $size = getimagesize("$path/$fileName"); + logger("$path/$fileName: width:".$size[0].", height:".$size[1]); + if ($size[0] > 1024) { + logger("mogrify -resize 1024x1024 $path/$fileName"); + system("mogrify -resize 1024x1024 $path/$fileName"); + } + } + exit; + case '_thumb': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_thumb + header("Content-Type: application/json"); + $value = stripslashes(file_get_contents("php://input")); + $json = json_decode($value); + $source = "../../../$resources/" . str_replace('~', '/', $json->source); + $dest = "../../../$resources/" . str_replace('~', '/', $json->dest); + mkdir(substr($dest, 0, strrpos($dest, '/')), 0777, true); + system("convert $source -resize 100x100^ -gravity Center -crop 100x100+0+0 +repage $dest"); + exit; + case '_delete': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_delete/{path} + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + if (is_dir($path)) { + rmdir($path); + } else { + unlink($path); + } + exit; + case '_email': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_email + header("Content-Type: application/text"); + $value = stripslashes(file_get_contents("php://input")); + $json = json_decode($value); + $from = $json->from; + $to = $json->to; + $subject = $json->subject; + $message = $json->message; + $headers = "MIME-Version: 1.0\r\n"; + $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n"; + $headers .= "From: $from\r\n"; + mail($to, $subject, $message, "$headers\r\n"); + print "$headers\r\n$message"; + exit; + case '_save': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_save/{path} + $path = "../../../$resources/" . str_replace('~', '/', $request[0]); + $p = strrpos($path, '/'); + $dir = substr($path, 0, $p); + mkdir($dir, 0777, true); + header("Content-Type: application/text"); + $content = stripslashes(file_get_contents("php://input")); + $p = strrpos($path, '.'); + $root = substr($path, 0, $p); + $ext = substr($path, $p); + $backup = "$root-bak$ext"; + unlink($backup); + copy($path, $backup); + file_put_contents($path, $content); + logger("Saved $path"); + exit; + case '_ecsave': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_ecsave/{path} + $path = str_replace('~', '/', $request[0]); + $p = strrpos($path, '/'); + if ($p > 0) { + $dir = substr($path, 0, $p); + mkdir($dir, 0777, true); + } + header("Content-Type: application/text"); + $content = stripslashes(file_get_contents("php://input")); +// print $content; +// $content = str_replace('\\', '\\\\', $content); + unlink($path); + file_put_contents($path, $content); + exit; + case '_ecdelete': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_ecdelete/{path} + $path = str_replace('~', '/', $request[0]); + if (is_dir($path)) { + rmdir($path); + } else { + unlink($path); + } + exit; + } + break; + case 'OPTIONS': + include '../../../easycoder/rest-local.php'; + options_local($conn, $request); + } + + // Most further commands require use of the database. + $conn = mysqli_connect($props['sqlhost'], $props['sqluser'], + $props['sqlpassword'], $props['sqldatabase']); + if (!$conn) + { + http_response_code(404); + die("Failed to connect to MySQL: " . mysqli_connect_error()); + } + mysqli_set_charset($conn,'utf8'); + + if (!count($request)) { + http_response_code(400); + print "{\"message\":\"Incomplete REST query: ".substr($_SERVER['PATH_INFO'], 1).".\"}"; + exit; + } + + // You can have a custom extension that deals with special requests. + // These all have '_' as the table name. + switch ($method) { + + case 'GET': + if ($table == '_') { + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_/{request-and-arguments} + include '../../../easycoder/rest-local.php'; + get_local($conn, $request); + return; + } else { + // Use the handler below + get($conn, $table, $request); + } + break; + + case 'POST': + if ($table == '_') { + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/_/{request-and-arguments} + include '../../../easycoder/rest-local.php'; + post_local($conn, $request); + return; + } else { + // Use the handler below + post($conn, $table, $request); + } + break; + + default: + http_response_code(400); + break; + } + mysqli_close(); + exit; + + ///////////////////////////////////////////////////////////////////////// + // All the other commands deal with tables having a specific format, with the following fields: + // + // id int(11) + // name varchar(40) + // value text + // ts int(11) + // + // GET + function get($conn, $table, $request) { + $action = $request[0]; + switch ($action) { + case 'count': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/table/count + // Return the number of items in the table + $result = $conn->query("SELECT id from $table"); + //print "{\"count\":".mysqli_num_rows($result)."}"; + print mysqli_num_rows($result); + mysqli_free_result($result); + break; + + case 'list': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/list/{offset}/{count} + // List items by ID, with optional offset & count, defaulting to 0 & 10 + switch (count($request)) { + case 2: + $offset = 0; + $count = $request[1]; + break; + case 3: + $offset = $request[1]; + $count = $request[2]; + break; + default: + $offset = 0; + $count = 10; + break; + } + $result = $conn->query("SELECT id FROM $table LIMIT $offset, $count"); + $response = '['; + while ($row = mysqli_fetch_object($result)) { + if ($response != '[') { + $response .= ','; + } + $response .= $row->id; + } + mysqli_free_result($result); + $response .= ']'; + print $response; + break; + + case 'names': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/names/{offset}/{count} + // List items by name in ascending alphabetical order, + // with optional offset & count, defaulting to 0 & 10 + switch (count($request)) { + case 2: + $offset = 0; + $count = $request[1]; + break; + case 3: + $offset = $request[1]; + $count = $request[2]; + break; + default: + $offset = 0; + $count = 10; + break; + } + $result = $conn->query("SELECT name FROM $table ORDER BY name LIMIT $offset, $count"); + $response = '['; + while ($row = mysqli_fetch_object($result)) { + if ($response != '[') { + $response .= ','; + } + $response .= "\"$row->name\""; + } + mysqli_free_result($result); + $response .= ']'; + print $response; + break; + + case 'id': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/id/{id} + // Get a record given its id + if (count($request) < 2) { + http_response_code(400); + print "Incomplete REST query."; + exit; + } + $id = $request[1]; + $result = $conn->query("SELECT value FROM $table WHERE id='$id'"); + if ($row = mysqli_fetch_object($result)) { + print $row->value; + } else { + http_response_code(404); + print "Cannot get item id '$id' as it does not exist."; + } + mysqli_free_result($result); + break; + + case 'name': + case 'query': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/name/{name} + // Get a record given its name + if (count($request) < 2) { + http_response_code(400); + print "Incomplete REST query."; + exit; + } + $name = $request[1]; + $result = $conn->query("SELECT value FROM $table WHERE name='$name'"); + if ($row = mysqli_fetch_object($result)) { + print $row->value; + } else if ($action == 'name') { + http_response_code(404); + print "Cannot get item named '$name' as it does not exist."; + } + break; + + default: + http_response_code(404); + print "I don't understand this request."; + break; + } + } + + ///////////////////////////////////////////////////////////////////////// + // POST + function post($conn, $table, $request) { + $ts = time(); + $action = $request[0]; + switch ($action) { + case 'set': + // Set the value of a record + if (count($request) > 2) { + switch ($request[1]) { + case 'id': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/set/{table}/id/{id} + // Set by id. The record must already exist + header("Content-Type: application/text"); + $value = stripslashes(file_get_contents("php://input")); + $id = $request[2]; + // See if there's an item with this id + $result = $conn->query("SELECT id FROM $table WHERE id=$id"); + if (mysqli_fetch_object($result)) { + // It exists, so update it + $value = urldecode($value); + logger("UPDATE $table SET value='$value',ts=$ts WHERE id=$id"); + query($conn, "UPDATE $table SET value='$value',ts=$ts WHERE id=$id"); + } else { + // Not found + http_response_code(404); + logger("{\"code\":\"404\",\"message\":\"Cannot set record $id of $table.\"}"); + print "{\"message\":\"Cannot set record $id of $table.\"}"; + } + mysqli_free_result($result); + break; + + case 'name': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/set/{table}/name/{name} + // Set by name. If the record does not exist, add it + header("Content-Type: application/text"); + $value = stripslashes(file_get_contents("php://input")); + $name = $request[2]; + // See if there's an item with this name + $result = $conn->query("SELECT id FROM $table WHERE name='$name'"); + if (mysqli_fetch_object($result)) { + // It exists, so update it + query($conn, "UPDATE $table SET value='$value',ts=$ts WHERE name='$name'"); + } else { + // Add a new item + query($conn, "INSERT INTO $table (name,value,ts) VALUES ('$name','$value','$ts')"); + http_response_code(201); + } + mysqli_free_result($result); + break; + + default: + http_response_code(400); + print "{\"message\":\"Value '".$request[1]."' should be 'id' or 'name'.\"}"; + break; + } + } else { + http_response_code(400); + print "{\"message\":\"Incomplete REST query.\"}"; + } + break; + + case 'delete': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/delete/{id} + // Or: .../rest.php/table/delete/{name} + // Delete a record, by id or by name + if (count($request) > 1) { + $item = $request[1]; + if (is_int($item)) { + // Delete the requested id + query($conn, "DELETE FROM $table WHERE id=$id"); + } else { + // Delete the named item + query($conn, "DELETE FROM $table WHERE name='$item'"); + } + } + break; + + case 'rename': + // Endpoint: {site root}/wp-content/plugins/easycoder/rest.php/{table}/rename + // Rename a record + $value = $_POST['value']; + $id = $_POST['id']; + if (!$id && count($request) > 1) { + $id = $request[1]; + } + if ($id) { + query($conn, "UPDATE $table SET name='$name',value='$value' WHERE id=$id"); + } else { + $name = $_POST['name']; + $newname = $_POST['newname']; + // See if there's a data item with the new name + $result = $conn->query("SELECT id FROM $table WHERE name='$newname'"); + if ($row = mysqli_fetch_object($result)) { + // Conflict + http_response_code(409); + print "{\"message\":\"Cannot rename item '$name' to '$newname' as it already exists.\"}"; + } else { + // See if there's a data item with this name + $result = $conn->query("SELECT id FROM $table WHERE name='$name'"); + if ($row = mysqli_fetch_object($result)) { + // There's a data item to rename + $id = $row->id; + query($conn, "UPDATE $table SET name='$newname',value='$value' WHERE id=$id"); + } else { + // Not found + http_response_code(404); + print "{\"message\":\"Cannot rename item '$name' as it does not exist.\"}"; + } + } + mysqli_free_result($result); + } + break; + + default: + http_response_code(404); + print "{\"message\":\"Unrecognised action '$action' requested.\"}"; + break; + } + } + + ///////////////////////////////////////////////////////////////////////// + // Do an SQL query + function query($conn, $sql) + { + $result = mysqli_query($conn, $sql); + if (!$result) { + http_response_code(404); + logger('Error: '.mysqli_error($conn)); + die('Error: '.mysqli_error($conn)); + } + return $result; + } + + //////////////////////////////////////////////////////////////////////////// + // Log a message. + function logger($message) + { + $timestamp = time(); + $date = date("Y/m/d H:i", $timestamp); + if (!file_exists("log")) mkdir("log"); + $file = "log/".date("Y", $timestamp); + if (!file_exists($file)) mkdir($file); + $file.= "/".date("Ymd", $timestamp).".txt"; + $fp = fopen($file, "a+") or die("Can't open $file"); + fwrite($fp, "$date: $message\n"); + fclose($fp); + } +?> diff --git a/easycoder/rest.py b/easycoder/rest.py new file mode 100644 index 0000000..6d238b1 --- /dev/null +++ b/easycoder/rest.py @@ -0,0 +1,85 @@ +import bottle +import subprocess +import os +from bottle import Bottle, run, get, post, request, response, static_file + +def HOST(): + return 'http://localhost:8080' + +# start up the browser (version for Linux/Chromium) +cmd = f'chromium-browser {HOST()}/flist/Codex' +#subprocess.call(cmd, shell=True) + +app = Bottle() + +# Endpoint: GET localhost:8080/_list +# Lists all files in the server root directory +@app.get('/_list') +def listRoot(): + return listFiles('') + +# Endpoint: GET localhost:8080/_list/name +# Lists all files in a specified directory +@app.get('/_list/') +def listFiles(name): + ff = '' + for file in os.listdir(f'resources{"/" if name else ""}{name}'): + path = os.path.join(f'{name if name else ""}', file) + if os.path.isdir(f'resources/{path}'): + type = 'dir' + else: + extension = os.path.splitext(file)[1] + if extension == '.jpg' or extension == '.png' or extension == '.gif': + type = 'img' + else: + type = 'file' + ff += f'{"," if ff else ""}{{"name":"{file}","type":"{type}"}}' + print(ff) + return f'[{ff}]' + +# Endpoint: GET localhost:8080/_load/name +# Load a file +@app.get('/_load/') +def loadFile(name): + f = open(f'resources/{name.replace(chr(0x7e), "/")}', 'r') + response = f.read() + f.close() + return response + +# Endpoint: POST localhost:8080/_save/name +# Save a file +@app.post('/_save/') +def saveFile(name): + f = open(f'resources/{name.replace(chr(0x7e), "/")}', 'w+') + content = request.body.read().decode("utf-8") + f.write(content) + f.close() + return response + +# Endpoint: POST localhost:8080/_delete/connametent +# Delete a file +@app.post('/_delete/') +def deleteFile(name): + os.remove(f'resources/{name.replace(chr(0x7e), "/")}') + return '' + +############################################################################### +# Generic endpoints + +# Endpoint: GET localhost:8080/ +# Gets a file +@app.get('/') +def getFile(filename): + print(f'Get (generic) {filename}') + response = bottle.static_file(filename, root='.') + response.set_header("Cache-Control", "public, max-age=0") + return response + +# Endpoint: GET localhost:8080> +# Gets the default home page +@app.get('/') +def index(): + return getFile('index.html') + +if __name__ == '__main__': + app.run() diff --git a/server/README.md b/server/README.md index 40a2f09..0f147d7 100644 --- a/server/README.md +++ b/server/README.md @@ -1,3 +1,5 @@ # EasyCoder server files These are mostly server-side files. The most significant are 2 small REST servers written in PHP and Python, which perform the same functions. The former is for setting up a `localhost` server and the latter is for use on LAMP servers. + + The files `easycoder.php`, `ec-rest.txt` and `readme.txt` are for WordPress installations.