From c6534fe05ff4a0d2e74937d53dc0f7cf5daac94e Mon Sep 17 00:00:00 2001 From: Russ Michell Date: Thu, 2 Jun 2016 22:05:20 +1200 Subject: [PATCH] Initial commit. --- .editorconfig | 18 ++ .gitignore | 4 + CONTRIBUTING | 3 + LICENCE | 27 +++ README | 43 ++++ _config.php | 7 + code/models/fieldtypes/SimpleJSONText.php | 261 ++++++++++++++++++++++ composer.json | 19 ++ 8 files changed, 382 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 CONTRIBUTING create mode 100644 LICENCE create mode 100644 README create mode 100644 _config.php create mode 100644 code/models/fieldtypes/SimpleJSONText.php create mode 100644 composer.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7d65503 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# For more information about the properties used in +# this file, please see the EditorConfig documentation: +# http://editorconfig.org/ + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 +indent_style = space diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..273da33 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/.project +/.settings +/.idea diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000..f536c0d --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,3 @@ +# Contributing + +Contributions in the form of suggestions, PR's and beer are *always* welcome. diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..530bf28 --- /dev/null +++ b/LICENCE @@ -0,0 +1,27 @@ +Copyright (c) 2016, Russell Michell - theruss.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the author Russell Michell nor the names of any of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README b/README new file mode 100644 index 0000000..c440e09 --- /dev/null +++ b/README @@ -0,0 +1,43 @@ +# SilverStripe JSONText + +Pretty much does what it says on the tin: Provides a simple TEXT field into which JSON can be stored. +This allows basic updates and "queries" to performed on the JSON data itself. + +# Usage + + class MyDataObject extends DataObject + { + private static $db = [ + 'MyJSON' => 'SimpleJSONText' + ]; + + public function getMyJSON($key) + { + return $this->dbObject('MyJSON')->getValueForKey($key); + } + + public function getFirstJSONVal() + { + return $this->dbObject('MyJSON')->first(); + } + + public function getLastJSONVal() + { + return $this->dbObject('MyJSON')->last(); + } + + public function getNthJSONVal($n) + { + return $this->dbObject('MyJSON')->nth($n); + } + } + +# TODO + +* Lose the fugly way that data is queried via `$this->dbObject()` +* Add basic set of tests for `first()`, `last()` etc +* Add a new field class that allows for deeper ORM interactions on JSON data + +# Author + +Russell Michell diff --git a/_config.php b/_config.php new file mode 100644 index 0000000..4705da5 --- /dev/null +++ b/_config.php @@ -0,0 +1,7 @@ + + * @package jsontext + */ + +define('MODULE_NAME', 'JSON Text'); diff --git a/code/models/fieldtypes/SimpleJSONText.php b/code/models/fieldtypes/SimpleJSONText.php new file mode 100644 index 0000000..cb6b52f --- /dev/null +++ b/code/models/fieldtypes/SimpleJSONText.php @@ -0,0 +1,261 @@ + + * static $db = array( + * "MyJSONStructure" => "SimpleJSONText", + * ); + * + * + * @package silverstripe-jsontext + * @subpackage fields + * @author Russell Michell + */ +class JSONText extends StringField +{ + /** + * Returns an input field. + * + * @param string $name + * @param null|string $title + * @param string $value + */ + public function __construct($name, $title = null, $value = '') + { + parent::__construct($name, $title, $value); + } + + /** + * Taken from {@link TextField}. + * @see DBField::requireField() + * @return void + */ + public function requireField() + { + $parts = [ + 'datatype' => 'mediumtext', + 'character set' => 'utf8', + 'collate' => 'utf8_general_ci', + 'arrayValue' => $this->arrayValue + ]; + + $values = [ + 'type' => 'text', + 'parts' => $parts + ]; + + DB::require_field($this->tableName, $this->name, $values, $this->default); + } + + /** + * @param string $title + * @return HiddenField + */ + public function scaffoldSearchField($title = null) + { + return HiddenField::create($this->getName()); + } + + /** + * @param string $title + * @return HiddenField + */ + public function scaffoldFormField($title = null) + { + return HiddenField::create($this->getName()); + } + + /** + * Returns the value of this field as an associative array. + * + * @return array + * @throws SimpleJSONException + */ + public function getValueAsArray() + { + if (!$value = $this->getValue()) { + return []; + } + + if (!$this->isJson($value)) { + $msg = 'DB data is munged.'; + throw new SimpleJSONException($msg); + } + + if (!$decoded = json_decode($value, true)) { + return []; + } + + if (!is_array($decoded)) { + $decoded = (array) $decoded; + } + + return $decoded; + } + + /** + * @param mixed string|int + * @return mixed + */ + public function getValueForKey($key) + { + $currentData = $this->getValueAsArray(); + if (isset($currentData[$key])) { + return $currentData[$key]; + } + + return null; + } + + /** + * Utility method to determine whether the data is really JSON or not. + * + * @param string $value + * @return boolean + */ + public function isJson($value) + { + return !is_null(json_decode($value, true)); + } + + /** + * @param array $value + * @return mixed null|string + */ + public function toJson($value) + { + if (!is_array($value)) { + $value = (array) $value; + } + + $opts = ( + JSON_UNESCAPED_SLASHES + ); + + return json_encode($value, $opts); + } + + /** + * Return an array of the JSON key + value represented as first JSON node. + * + * @return mixed null|array + */ + public function first() + { + $data = $this->getValueAsArray(); + + if (!$data) { + return null; + } + + return array_slice($data, 0, 1, true); + } + + /** + * Return an array of the JSON key + value represented as last JSON node. + * + * @return mixed null|array + */ + public function last() + { + $data = $this->getValueAsArray(); + + if (!$data) { + return null; + } + + return array_slice($data, -1, 1, true); + } + + /** + * Return an array of the JSON key + value represented as the $n'th JSON node. + * + * @param int $n + * @return mixed null|array + * @throws SimpleJSONException + */ + public function nth($n) + { + $data = $this->getValueAsArray(); + + if (!$data) { + return null; + } + + if (!is_numeric($n)) { + $msg = 'Argument passed to ' . __FUNCTION__ . ' must be numeric.'; + throw new SimpleJSONException($msg); + } + + if (!isset(array_values($data)[$n])) { + return null; + } + + return array_slice($data, $n, 1, true); + } + + /** + * Return an array of the JSON key(s) + value(s) represented when $value is found in a JSON node's value + * + * @param string $value + * @return mixed null|array + * @throws SimpleJSONException + */ + public function find($value) + { + $data = $this->getValueAsArray(); + + if (!$data) { + return null; + } + + if (!is_scalar($value)) { + $msg = 'Argument passed to ' . __FUNCTION__ . ' must be a scalar.'; + throw new SimpleJSONException($msg); + } + + return null; + + // array_search($value, $data); + //array_keys + } + + /** + * Converts special JSON characters in incoming data. Use the $invert param to convert strings coming back out. + * + * @param string $value + * @param boolean $invert + * @return string + */ + public function jsonSafe($value, $invert = false) + { + $map = [ + '{' => '%7B', + '}' => '%7D', + '"' => '"' + ]; + + if ($invert) { + $map = array_flip($map); + } + + return str_replace(array_keys($map), array_values($map), $value); + } + +} + +/** + * @package silverstripe-advancedcontent + * @author Russell Michell 2016 + */ +class SimpleJSONException extends Exception +{ +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b0c830f --- /dev/null +++ b/composer.json @@ -0,0 +1,19 @@ +{ + "name": "phptek/jsontext", + "type": "silverstripe-module", + "description": "JSON storage and querying", + "homepage": "http://theruss.com/", + "license": "BSD", + "keywords": ["silverstripe", "JSON", "Field""], + "authors": [ + { + "name": "Russell Michell", + "email": "russ@theruss.com" + } + ], + "require": { + "php": ">=5.4<7", + "silverstripe/framework": ">=3.1", + "silverstripe/cms": ">=3.1" + } +}