├── README.md ├── api ├── application │ ├── hal.php │ ├── hal │ │ ├── joomla.php │ │ └── link.php │ ├── includemap.php │ ├── resourcemap.php │ ├── router.php │ └── web.php ├── controller │ ├── base.php │ ├── controller.php │ ├── item.php │ └── list.php ├── database │ └── query.php ├── document │ └── hal │ │ └── json.php ├── import.php ├── index.php ├── services │ ├── categories │ │ ├── get.php │ │ ├── list │ │ │ ├── embedded.json │ │ │ └── get.php │ │ └── resource.json │ ├── menuitems │ │ ├── get.php │ │ ├── list │ │ │ ├── embedded.json │ │ │ └── get.php │ │ └── resource.json │ └── root │ │ └── get.php └── transform │ ├── base.php │ ├── boolean.php │ ├── datetime.php │ ├── float.php │ ├── int.php │ ├── position.php │ ├── state.php │ ├── string.php │ ├── target.php │ ├── transform.php │ └── ynglobal.php ├── components ├── com_content │ ├── services.json │ └── services │ │ ├── articles │ │ ├── application.php │ │ ├── get.php │ │ ├── list │ │ │ ├── embedded.json │ │ │ └── get.php │ │ ├── resource.json │ │ └── transform │ │ │ └── position.php │ │ └── index.html └── com_weblinks │ ├── services.json │ └── services │ ├── index.html │ └── weblinks │ ├── get.php │ ├── list │ ├── create.php │ ├── embedded.json │ └── get.php │ └── resource.json └── etc ├── README.md ├── config.json └── services.json /README.md: -------------------------------------------------------------------------------- 1 | j3-rest-api 2 | =========== 3 | This is an experimental REST API for Joomla 3.x and these are some rough notes to 4 | get you started. DO NOT PUT THIS ON A PRODUCTION SITE. This is proof-of-concept 5 | code that is far from being complete or stable. There is no access level security 6 | (yet) so any content in your website will be publicly exposed by this code. 7 | 8 | ## Introduction 9 | This proof-of-concept demonstrates how a web services API might be created for 10 | Joomla 3.x. It runs as a standalone application which merges services that have 11 | been added to otherwise completely unmodified components. No core code is changed. 12 | 13 | The web services code makes use of the RESTful router included in the 14 | Joomla Platform code in Joomla 3.x. 15 | 16 | Any and all feedback is welcome, particularly at this stage regarding the 17 | architecture. Please send your comments to chris.davenport@joomla.org. 18 | Pull requests also welcome of course. Thanks! 19 | 20 | ## Prerequisites 21 | You must have an already installed and working installation of Joomla 3.x. 22 | 23 | ## Installation 24 | Grab the code from GitHub: https://github.com/chrisdavenport/j3-rest-api and put 25 | it in your Joomla web root. 26 | 27 | Add the following lines to your .htaccess file: 28 | 29 | ``` 30 | # If the requested path and file is not /index.php and the request 31 | # has not already been internally rewritten to the index.php script 32 | RewriteCond %{REQUEST_URI} !^/index\.php 33 | # and the request is for something within the api folder 34 | RewriteCond %{REQUEST_URI} /api/ [NC] 35 | # and the requested path and file doesn't directly match a physical file 36 | RewriteCond %{REQUEST_FILENAME} !-f 37 | # and the requested path and file doesn't directly match a physical folder 38 | RewriteCond %{REQUEST_FILENAME} !-d 39 | # internally rewrite the request to the API index.php script 40 | RewriteRule .* api/index.php [L] 41 | ``` 42 | 43 | Point a web browser at [path-to-Joomla]/api 44 | 45 | You should get some JSON back. This is the entry point service document described 46 | below. 47 | 48 | ## Using the HAL Browser 49 | You can browse the API interactively using Mike Kelly's HAL Browser. As the /api/hal-browser 50 | directory in this repository is corrupted in some weird way I haven't been able to fix, you 51 | will need to download and install it yourself from https://github.com/mikekelly/hal-browser. 52 | 53 | Once installed simply point a web browser at the following URL (adjusted for your environment): 54 | 55 | ``` 56 | http://www.example.com/path-to-Joomla/api/hal-browser/browser.html#http://www.example.com/path-to-Joomla/api 57 | ``` 58 | Important: You will need to add the following line to your /etc/config.json file 59 | in order for the HAL Browser to work: 60 | 61 | ``` 62 | "absoluteHrefs": true 63 | ``` 64 | 65 | ## Quick tour of the code 66 | The “core” code lives in the new /api directory, which is the entry point for the 67 | API application. 68 | 69 | Basic configuration files go in /etc although the standard Joomla configuration.php 70 | file is also loaded in order to get the database credentials. 71 | 72 | A new /services directory will have been added to each component for which support 73 | has been provided. The currently supported components are: articles/content, 74 | categories and weblinks. They demonstrate how an extension can have web services 75 | code added to it. The current code does not make use of any component code, 76 | and installing it does not overwrite any current code, but sharing model code at 77 | least is something that should be seriously considered. 78 | 79 | In the distributed code the Content-Type headers returned should be correct as per 80 | the specification (eg. application/vnd.joomla.service.v1+hal+json”). The HAL Browser 81 | will work with these just fine, however if you use a web browser to access the 82 | API directly they will not normally be recognised. If you want to test the API 83 | in a web browser without using the HAL Browser, then comment out the setMimeEncoding 84 | line in the execute method in the /api/controller/base.php file. The API will 85 | then return the default Content-Type: application/json header. 86 | 87 | ## Entry point service document 88 | Pointing a web browser at the api entry point: http://www.example.com/api will return the “service” document which lists the services 89 | available via the API. The format of this document is described in "application/vnd.joomla.service.v1 media type specification" referenced below. 90 | The code responsible for handling this document can be found in 91 | 92 | ``` 93 | /api/services/root/get.php 94 | ``` 95 | 96 | ## Adding web services support to a component 97 | Look in /components/com_content and /components/com_weblinks for examples of how this could be done. The core web services code looks for a services.json file in each of the installed component directories. If it finds one it automatically merges it into the services router map. 98 | 99 | Here’s the router map for com_content: 100 | 101 | ```javascript 102 | { 103 | "joomla:articles":"component/content/ArticlesList", 104 | "joomla:articles/:id":"component/content/Articles" 105 | "joomla:categories/:catid/articles":"component/content/ArticlesList" 106 | } 107 | ``` 108 | 109 | Each line consists of a “public” route and the associated “internal” route. So on the first line we have “joomla:articles” mapping to "component/content/ArticlesList", which means that a GET request to this URL: 110 | 111 | ``` 112 | http://www.example.com/api/joomla:articles 113 | ``` 114 | 115 | will cause the controller class ComponentContentArticlesListGet to be loaded from the file 116 | 117 | ``` 118 | /components/com_content/services/articles/list/get.php 119 | ``` 120 | 121 | The JLoader prefix “ComponentContentArticles” is automatically set up to point to the correct directory, so provided you have the correct class in the correct file the PHP loader will find it without further effort. 122 | 123 | The document returned in this case will contain a (paginated) list of articles and is described in "application/vnd.joomla.list.v1 media type specification" referenced below. 124 | 125 | The second line of the services.json file maps URLs such as 126 | 127 | ``` 128 | http://www.example.com/api/joomla:articles/1234 129 | ``` 130 | 131 | to the controller class ComponentContentArticlesGet in the file 132 | 133 | ``` 134 | /components/com_content/services/articles/get.php 135 | ``` 136 | 137 | The document returned in this case will contain a representation of the single article requested (by its id) and is described in "application/vnd.joomla.item.v1 media type specification" referenced below. 138 | 139 | The fields returned for the articles resource are described in https://docs.google.com/a/joomla.org/document/d/1d5qQ16r1Bo1BlXXuyS_eFB4BQcfuSg05pn9hsMpAgqk/edit#heading=h.ygla5naoxuzt 140 | 141 | The third line of the services.json file maps URLs such as 142 | 143 | ``` 144 | http://www.example.com/api/joomla:categories/383/articles 145 | ``` 146 | 147 | to the same controller as the first line. In this instance it will return a list of all articles which are in category 383. 148 | 149 | ## Resource - database field mapping 150 | In order to establish the mapping between the database fields and the fields exposed in the API, a JSON object describes the relationship so that in many (most?) cases, adding web services support to an existing component is mostly a matter of creating these map files. 151 | 152 | The map is located in a resource.json file. So for com_content, for example, you should look in the file 153 | 154 | ``` 155 | /components/com_content/services/articles/resource.json 156 | ``` 157 | 158 | The JSON object contained in this file is a simple list of key-value pairs. All the fields represented by the key-value pairs will be included in a full representation of the resource. Both keys and values are strings which have a specific format that describes the detailed mapping between the field in the API representation (the key) and the field in the model (database) representation (the value). 159 | 160 | ### Key format 161 | The ABNF (RFC5234) syntax for the key is as follows: 162 | 163 | ``` 164 | [ objectName ] “/” fieldName [ “.” propertyName ] 165 | ``` 166 | 167 | where 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 |
objectNameis the optional name of an object within the resource. The default object name is “main” and refers to the resource itself.
fieldNameis the name of the field that will be assigned the value.
propertyNameis used where fieldName is an object and propertyName refers to a property of that object.
183 | 184 | ### Value format 185 | The ABNF (RFC5234) syntax for the value string is as follows: 186 | 187 | ``` 188 | transformName “:” definition 189 | ``` 190 | 191 | where 192 | 193 | 194 | 195 | 196 | 201 | 202 | 203 | 204 | 209 | 210 |
transformNameis the name of a transform that will modify the value before passing it 197 | to the API object. This is often just a matter of type casting 198 | (eg. “string” or “int” transforms) but more sophisticated transforms 199 | are available (eg. “state”) and you can add your own or override the 200 | standard ones. See below for more information.
definitionis a string which is first parsed for model field names that will be substituted by their values before the resulting 205 | string is passed to the transform method. The definition string may contain the names of model fields enclosed in curly brackets. 206 | These will be automatically replaced by the model values. The field name may contain a “.” in which case the part before 207 | the dot refers to a JSON-encoded model field and the part after the dot refers to a field within that JSON-encoded data. 208 | The unpacking of JSON-encoded fields is handled automatically.
211 | 212 | Some examples: 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 |
string:{title}Retrieves the title field from the model and casts it to a string.
string:postReturns the literal string “post” (without the quotes).
string:/joomla:articles/{id}Retrieves the id field from the model and substitutes it into the definition string. For example, if id has the value 987 then this will return the string “/joomla:articles/987”.
228 | 229 | The standard transforms are defined in the /api/transform directory and are 230 | described here: 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 |
intCasts the definition string to an integer.
stringCasts the definition string to a string (duh!).
booleanCasts the definition string to a boolean.
datetimeReturns an ISO 8601 date/time field [NOT IMPLEMENTED YET]
floatReturns “left”, “right”, “none” or “global”.
stateReturns “unpublished”, “published”, “trashed” or “archived”.
targetReturns “parent”, “new”, “popup”, “modal” or “global”.
ynglobalReturns “yes”, “no” or “global”.
266 | 267 | ## Adding a custom transform 268 | Components may add custom transforms override the standard ones if required. 269 | Create a /transform directory at the same level in the component directory structure 270 | as the resource.json file and add the additional or override transform classes 271 | in it. There is an example of this in 272 | 273 | ``` 274 | /components/com_content/services/transform/position.php 275 | ``` 276 | 277 | ## Defining which fields to embed in a list representation 278 | Because the list representation would not normally include a full representation of each of the embeddeded objects, 279 | there is a simple JSON file that defines which fields are included. The file for com_content is 280 | 281 | ``` 282 | /components/com_content/services/articles/list/embedded.json 283 | ``` 284 | 285 | and it contains something like this: 286 | 287 | ```javascript 288 | { 289 | "embedded": 290 | [ 291 | "/access", 292 | "/featured", 293 | "/introText", 294 | "/language", 295 | "_links/self.href", 296 | "_links/joomla:assets.href", 297 | "_links/joomla:categories.href", 298 | "_links/joomla:checkout.href", 299 | "_links/joomla:checkout.method", 300 | "_links/joomla:introImage.href", 301 | "_links/joomla:introImage.float", 302 | "_links/joomla:introImage.title", 303 | "_links/joomla:introImage.caption", 304 | "metadata/authorName", 305 | "/ordering", 306 | "publish/alias", 307 | "publish/created", 308 | "publish/publishDown", 309 | "publish/publishUp", 310 | "/state", 311 | "/title" 312 | ] 313 | } 314 | ``` 315 | 316 | The array is a simple list of field names to be included in the embedded representations. 317 | Each entry must match a field name in the articles.json file. The fields definitions from the resource.json file 318 | used so that data in both single and list representations should match exactly. 319 | 320 | ## References and further reading 321 | 322 | * Joomla Web Services Working Group http://docs.joomla.org/Web_Services_Working_Group 323 | * Joomla CMS Web Services API Specification https://docs.google.com/document/d/1FVKGlV6BN6pu-YH2WR2pQHE3Ez7M6r7LD417GSw9ZSo/edit?usp=sharing 324 | * application/vnd.joomla.base.v1 media type specification https://docs.google.com/document/d/11SqH-daKQV9SrFBMEpopjBk3vM1USIHnFWZB9rjJB94/edit?usp=sharing 325 | * application/vnd.joomla.service.v1 media type specification https://docs.google.com/document/d/1wg3AcgStA26UwDcbHVV1bub4sa_BhsKfzAmX21eG-FM/edit?usp=sharing 326 | * application/vnd.joomla.item.v1 media type specification https://docs.google.com/document/d/16xwxSDDPW0U1CG9l7JcwOyGvyjm7wv5zOSd9JwgF2iQ/edit?usp=sharing 327 | * application/vnd.joomla.list.v1 media type specification https://docs.google.com/document/d/1PLym28MG5v1tWyvIyW-9483JNKh5AP21Fmsmg62plnA/edit?usp=sharing 328 | * Joomla CMS Web Service API Implementation https://docs.google.com/document/d/1d5qQ16r1Bo1BlXXuyS_eFB4BQcfuSg05pn9hsMpAgqk/edit?usp=sharing 329 | * Joomla CMS CLI Services API Specification https://docs.google.com/document/d/1wI3cSm3y4aa8n8rojJKpiF6RUpSl63WFuLgJj2WqW8o/edit?usp=sharing 330 | -------------------------------------------------------------------------------- /api/application/hal.php: -------------------------------------------------------------------------------- 1 | properties['_links'])) 32 | { 33 | $this->properties['_links'] = new stdClass; 34 | } 35 | 36 | // Get the link relation name. 37 | $rel = $link->getRel(); 38 | 39 | // Add the link to the array of links for the given link relation name. 40 | if (isset($this->properties['_links']->$rel)) 41 | { 42 | $this->properties['_links']->$rel = array_merge($this->properties['_links']->$rel, array($link)); 43 | } 44 | else 45 | { 46 | $this->properties['_links']->$rel = array($link); 47 | } 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * Embed objects. 54 | * 55 | * @param string $name Name (rel) of embedded objects. 56 | * @param array $data Array of objects to be embedded. 57 | * 58 | * @return object This method may be chained. 59 | */ 60 | public function embed($name, $data) 61 | { 62 | // If there is no _embedded property, create it. 63 | if (!isset($this->properties['_embedded'])) 64 | { 65 | $this->properties['_embedded'] = new stdClass; 66 | } 67 | 68 | $this->properties['_embedded']->$name = $data; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * Method to get the value of a property. 75 | * 76 | * @param string $name Name of the property. 77 | * @param mixed $default Value returned if property does not exist. 78 | * 79 | * @return mixed Value of the property. 80 | */ 81 | public function get($name, $default = null) 82 | { 83 | return isset($this->properties[$name]) ? $this->properties[$name] : $default; 84 | } 85 | 86 | /** 87 | * Method to return an object suitable for serialisation. 88 | * 89 | * @return stdClass A HAL object suitable for serialisation. 90 | */ 91 | public function getHal() 92 | { 93 | $hal = new stdClass; 94 | 95 | // Links with the same link relation name are stored in an array, but if 96 | // the array contains only one item, then we drop the array. 97 | if (isset($this->properties['_links']) && !empty($this->properties['_links'])) 98 | { 99 | // Get the links object. 100 | $links = $this->properties['_links']; 101 | 102 | // Convert single entry arrays to direct links. 103 | foreach ($links as $rel => $link) 104 | { 105 | if (is_array($link)) 106 | { 107 | if (count($link) == 1) 108 | { 109 | $links->$rel = $link[0]; 110 | } 111 | else 112 | { 113 | $links->$rel = $this->links[$rel]; 114 | } 115 | } 116 | } 117 | 118 | // Replace the links object. 119 | $this->properties['_links'] = $links; 120 | } 121 | 122 | // Copy all the properties into the HAL object. 123 | if (!empty($this->properties)) 124 | { 125 | foreach ($this->properties as $key => $value) 126 | { 127 | $hal->$key = $value; 128 | } 129 | } 130 | 131 | return $hal; 132 | } 133 | 134 | /** 135 | * Method to load an object or an array into this HAL object. 136 | * 137 | * @param object $object Object whose properties are to be loaded. 138 | * 139 | * @return object This method may be chained. 140 | */ 141 | public function load($object) 142 | { 143 | foreach ($object as $name => $value) 144 | { 145 | // For _links and _embedded, we merge rather than replace. 146 | if ($name == '_links' || $name == '_embedded') 147 | { 148 | $this->properties[$name] = (object) array_merge((array) $this->properties[$name], (array) $value); 149 | } 150 | else 151 | { 152 | $this->properties[$name] = $value; 153 | } 154 | } 155 | 156 | return $this; 157 | } 158 | 159 | /** 160 | * Method to set the value of a property. 161 | * 162 | * @param string $name Name of the property. 163 | * @param mixed $value Value of the property. 164 | * 165 | * @return object This method may be chained. 166 | */ 167 | public function set($name, $value) 168 | { 169 | $this->properties[$name] = $value; 170 | 171 | return $this; 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /api/application/hal/joomla.php: -------------------------------------------------------------------------------- 1 | meta = new stdClass; 55 | $this->meta->apiVersion = '1.0'; 56 | $this->set('_meta', $this->meta); 57 | 58 | // Add standard Joomla namespace as curie. 59 | $joomlaCurie = new ApiApplicationHalLink('curies', 'http://docs.joomla.org/Link_relations/{rel}'); 60 | $joomlaCurie->name = 'joomla'; 61 | $joomlaCurie->templated = true; 62 | $this->addLink($joomlaCurie); 63 | 64 | // Add basic hypermedia links. 65 | $this->addLink(new ApiApplicationHalLink('base', rtrim(JUri::base(), '/'))); 66 | if (isset($options['self'])) 67 | { 68 | $this->addLink(new ApiApplicationHalLink('self', $options['self'])); 69 | } 70 | 71 | // Set the content type. 72 | if (isset($options['contentType'])) 73 | { 74 | $this->setMetadata('contentType', $options['contentType']); 75 | } 76 | 77 | // Set link to (human-readable) schema documentation. 78 | if (isset($options['describedBy'])) 79 | { 80 | $this->setMetadata('describedBy', $options['describedBy']); 81 | } 82 | 83 | // Load the resource field map (if there is one). 84 | $resourceMapFile = isset($options['resourceMap']) ? $options['resourceMap'] : ''; 85 | if ($resourceMapFile != '' && file_exists($resourceMapFile)) 86 | { 87 | $basePath = dirname($options['resourceMap']); 88 | $this->resourceMap = new ApiApplicationResourcemap(array('basePath' => $basePath)); 89 | $this->resourceMap->fromJson(file_get_contents($resourceMapFile)); 90 | } 91 | 92 | // Load the embedded field map (if there is one). 93 | $embeddedMapFile = isset($options['embeddedMap']) ? $options['embeddedMap'] : ''; 94 | if ($embeddedMapFile != '' && file_exists($embeddedMapFile)) 95 | { 96 | // Load the embedded fields list. 97 | $this->includeMap = new ApiApplicationIncludemap(); 98 | $this->includeMap->fromJson(file_get_contents($embeddedMapFile)); 99 | } 100 | } 101 | 102 | /** 103 | * Import data into HAL object. 104 | * 105 | * @param string $name Name (rel) of data to be imported. 106 | * @param array $data Array of data items. 107 | * 108 | * @return object This object may be chained. 109 | */ 110 | public function embed($name, $data) 111 | { 112 | // If there is no map then use the standard embed method. 113 | if (!($this->includeMap instanceof ApiApplicationIncludemap)) 114 | { 115 | return parent::embed($name, $data); 116 | } 117 | 118 | // Get list of fields to be included. 119 | $include = $this->includeMap->toArray(); 120 | 121 | // Transform the source data array. 122 | $resources = array(); 123 | foreach ($data as $key => $datum) 124 | { 125 | $resources[$key] = $this->resourceMap->toExternal($datum, $include); 126 | } 127 | 128 | // Embed data into HAL object. 129 | parent::embed($name, $resources); 130 | 131 | // Add pagination URI template (per RFC6570). 132 | $pagesLink = new ApiApplicationHalLink('pages', '/' . $name . '{?fields,offset,page,perPage,sort}'); 133 | $pagesLink->templated = true; 134 | $this->addLink($pagesLink); 135 | 136 | return $this; 137 | } 138 | 139 | /** 140 | * Method to return an object suitable for serialisation. 141 | * 142 | * @return stdClass A Joomla HAL object suitable for serialisation. 143 | */ 144 | public function getHal() 145 | { 146 | $this->set('_meta', $this->meta); 147 | 148 | $hal = parent::getHal(); 149 | 150 | return $hal; 151 | } 152 | 153 | /** 154 | * Method to return a metadata field. 155 | * 156 | * @param string $field Field name. 157 | * @param string $default Optional default value. 158 | * 159 | * @return mixed Value of field. 160 | */ 161 | public function getMetadata($field, $default = '') 162 | { 163 | if (!isset($this->meta->$field)) 164 | { 165 | return $default; 166 | } 167 | 168 | return $this->meta->$field; 169 | } 170 | 171 | /** 172 | * Method to return the resource map object. 173 | * 174 | * @return ApiApplicationResourcemap Resource map object. 175 | */ 176 | public function getResourceMap() 177 | { 178 | return $this->resourceMap; 179 | } 180 | 181 | /** 182 | * Method to load an object into this HAL object. 183 | * 184 | * @param object $object Object whose properties are to be loaded. 185 | * 186 | * @return object This method may be chained. 187 | */ 188 | public function load($object) 189 | { 190 | // If there is no map then use the standard load method. 191 | if (empty($this->resourceMap)) 192 | { 193 | return parent::load($object); 194 | } 195 | 196 | parent::load($this->resourceMap->toExternal($object)); 197 | 198 | return $this; 199 | } 200 | 201 | /** 202 | * Method to add or modify a metadata field. 203 | * 204 | * @param string $field Field name. 205 | * @param mixed $value Value to be assigned to the field. 206 | * 207 | * @return object This method may be chained. 208 | */ 209 | public function setMetadata($field, $value) 210 | { 211 | $this->meta->$field = $value; 212 | 213 | return $this; 214 | } 215 | 216 | /** 217 | * Set pagination variables. 218 | * 219 | * @param array $page Array of pagination variables. 220 | * 221 | * @return object This object may be chained. 222 | */ 223 | public function setPagination($page = array()) 224 | { 225 | if (isset($page['page'])) 226 | { 227 | $this->meta->page = $page['page']; 228 | } 229 | 230 | if (isset($page['perPage'])) 231 | { 232 | $this->meta->perPage = $page['perPage']; 233 | } 234 | 235 | if (isset($page['offset'])) 236 | { 237 | $this->meta->offset = $page['offset']; 238 | } 239 | 240 | if (isset($page['totalItems'])) 241 | { 242 | $this->meta->totalItems = $page['totalItems']; 243 | } 244 | 245 | if (isset($page['totalPages'])) 246 | { 247 | $this->meta->totalPages = $page['totalPages']; 248 | } 249 | 250 | return $this; 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /api/application/hal/link.php: -------------------------------------------------------------------------------- 1 | rel = $rel; 33 | $this->href = $href; 34 | } 35 | 36 | /** 37 | * Returns rel value. 38 | */ 39 | public function getRel() 40 | { 41 | return $this->rel; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/application/includemap.php: -------------------------------------------------------------------------------- 1 | map = $map['embedded']; 37 | } 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * Method to return a simple array of included fields. 44 | * 45 | * @return array Array of included field names. 46 | */ 47 | public function toArray() 48 | { 49 | return $this->map; 50 | } 51 | 52 | /** 53 | * Method to determine if a particular map is to be included. 54 | * 55 | * @param string $fieldName Field name. 56 | * 57 | * @return boolean 58 | */ 59 | public function isIncluded($fieldName) 60 | { 61 | return isset($this->map[$fieldName]); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /api/application/resourcemap.php: -------------------------------------------------------------------------------- 1 | basePath = isset($options['basePath']) ? $options['basePath'] : ''; 39 | } 40 | 41 | /** 42 | * Load resource map from JSON. 43 | * 44 | * @param string JSON-encoded resource map. 45 | * 46 | * @return object This method may be chained. 47 | */ 48 | public function fromJson($json) 49 | { 50 | $this->map = json_decode($json, true); 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * Method to return the untransformed value associated with a field name. 57 | * 58 | * @param string $fieldName Field name. 59 | * @param mixed $default Optional default value. 60 | * 61 | * @return mixed Value associated with field name. 62 | */ 63 | public function getField($fieldName, $default = '') 64 | { 65 | return isset($this->map[$fieldName]) ? $this->map[$fieldName] : $default; 66 | } 67 | 68 | /** 69 | * Method to extract a value from the source data. 70 | * 71 | * @param string $sourceDefinition Source data field name. 72 | * @param array $sourceData Source data. 73 | * 74 | * @return mixed Requested data field. 75 | */ 76 | private function getSourceValue($sourceDefinition, $sourceData) 77 | { 78 | // Source definition fields must be in the form type:definition. 79 | // Locate first occurrence of a colon. 80 | $pos = strpos($sourceDefinition, ':'); 81 | 82 | // Separate type and definition. 83 | $sourceFieldType = substr($sourceDefinition, 0, $pos); 84 | $definition = substr($sourceDefinition, $pos+1); 85 | 86 | // Look for source field names. These are surrounded by curly brackets. 87 | preg_match_all('/\{(.*)\}/U', $definition, $matches); 88 | 89 | // If the definition contains field names, substitute their values. 90 | if (!empty($matches[0])) 91 | { 92 | foreach ($matches[1] as $key => $fieldName) 93 | { 94 | $matches[1][$key] = $this->getValue($fieldName, $sourceData); 95 | } 96 | 97 | // Replace {fieldName} with value. 98 | $definition = str_replace($matches[0], $matches[1], $definition); 99 | } 100 | 101 | // Transform the value depending on its type. 102 | $return = $this->transformField($sourceFieldType, $definition, $sourceData); 103 | 104 | return $return; 105 | } 106 | 107 | /** 108 | * Get the name of the transform class for a given field type. 109 | * 110 | * First looks for the transform class in the /transform directory 111 | * in the same directory as the resource.json file. Then looks 112 | * for it in the /api/transform directory. 113 | * 114 | * @param string $fieldType Field type. 115 | * 116 | * @return string Transform class name. 117 | */ 118 | private function getTransformClass($fieldType) 119 | { 120 | // Cache for the class names. 121 | static $classNames = array(); 122 | 123 | // Cache for component class prefix. 124 | static $componentPrefix = ''; 125 | 126 | // If we already know the class name, just return it. 127 | if (isset($classNames[$fieldType])) 128 | { 129 | return $classNames[$fieldType]; 130 | } 131 | 132 | // Compute the component class prefix if needed. 133 | // This will be used for component-level overrides. 134 | if ($componentPrefix == '') 135 | { 136 | // Get the path to the resource.json file. 137 | $path = str_replace(JPATH_BASE, '', realpath($this->basePath)); 138 | 139 | // Explode it and make some adjustments. 140 | $parts = explode('/', $path); 141 | 142 | foreach ($parts as $k => $part) 143 | { 144 | $parts[$k] = ucfirst(str_replace('com_', '', $part)); 145 | if ($part == 'components') 146 | { 147 | $parts[$k] = 'Component'; 148 | } 149 | if ($part == 'services') 150 | { 151 | unset($parts[$k]); 152 | } 153 | } 154 | 155 | $componentPrefix = implode('', $parts); 156 | } 157 | 158 | // Construct the name of the class to do the transform (default is ApiTransformString). 159 | $className = $componentPrefix . 'Transform' . ucfirst($fieldType); 160 | if (!class_exists($className)) 161 | { 162 | $className = 'ApiTransform' . ucfirst($fieldType); 163 | } 164 | 165 | // Cache it for later. 166 | $classNames[$fieldType] = $className; 167 | 168 | return $className; 169 | } 170 | 171 | /** 172 | * Method to get a value from the source data. 173 | * 174 | * @param string $fieldName Name of the field. 175 | * @param string $data Data. 176 | * 177 | * @return mixed Field value (or null if not found). 178 | */ 179 | private function getValue($fieldName, $data) 180 | { 181 | // Static array of unpacked json fields. 182 | static $unpacked = array(); 183 | 184 | $return = null; 185 | 186 | // Look for an optional field separator in name. 187 | // The dot separator indicates that the prefix is a json-encoded 188 | // field, each element of which can be addressed by the suffix. 189 | if (strpos($fieldName, '.') !== false) 190 | { 191 | // Extract the field names. 192 | list($context, $fieldName) = explode('.', $fieldName); 193 | 194 | // Make sure we have unpacked the json field. 195 | if (!isset($unpacked[$context])) 196 | { 197 | $unpacked[$context] = json_decode($data->$context); 198 | } 199 | 200 | if (isset($unpacked[$context]->$fieldName)) 201 | { 202 | $return = $unpacked[$context]->$fieldName; 203 | } 204 | } 205 | else 206 | { 207 | // If the field does not exist, return null. 208 | if (isset($data->$fieldName)) 209 | { 210 | $return = $data->$fieldName; 211 | } 212 | } 213 | 214 | return $return; 215 | } 216 | 217 | /** 218 | * Method to determine if a particular map is available. 219 | * 220 | * @param string $fieldName Field name. 221 | * 222 | * @return boolean 223 | */ 224 | public function isAvailable($fieldName) 225 | { 226 | return isset($this->map[$fieldName]); 227 | } 228 | 229 | /** 230 | * Transform a data object to its external representation. 231 | * 232 | * @param object $data Data object. 233 | * @param array $include List of fields to include (if empty then include all fields). 234 | * 235 | * @return object External representation of the data object. 236 | */ 237 | public function toExternal($data, array $include = array()) 238 | { 239 | // If there is no map then return the data unmodified. 240 | if (empty($this->map)) 241 | { 242 | return $data; 243 | } 244 | 245 | // Initialise the object store. 246 | $targetData = array(); 247 | 248 | // Run through each mapped field. 249 | foreach ($this->map as $halField => $sourceDefinition) 250 | { 251 | // Check that the field is to be included. 252 | if (!empty($include) && !in_array($halField, $include)) 253 | { 254 | continue; 255 | } 256 | 257 | // Left-hand side (HAL field) must be in the form objectName/name. 258 | // Note that objectName is optional; default is "main". 259 | list($halFieldPath, $halFieldName) = explode('/', $halField); 260 | 261 | // If we have a non-empty objectName then make sure we have an object with that name. 262 | $targetContext = $halFieldPath == '' ? 'main' : $halFieldPath; 263 | if (!isset($targetData[$targetContext])) 264 | { 265 | $targetData[$targetContext] = new stdClass; 266 | } 267 | 268 | // Look for an optional field separator in name. 269 | // The dot separator indicates that the prefix is an object 270 | // and the suffix is a property of that object. 271 | if (strpos($halFieldName, '.') !== false) 272 | { 273 | // Extract the field names. 274 | list($halFieldObject, $halFieldProperty) = explode('.', $halFieldName); 275 | 276 | // If the object doesn't already exist, create it. 277 | if (!isset($targetData[$targetContext]->$halFieldObject)) 278 | { 279 | $targetData[$targetContext]->$halFieldObject = new stdClass; 280 | } 281 | 282 | // Extract source data into object property. 283 | $targetData[$targetContext]->$halFieldObject->$halFieldProperty = $this->getSourceValue($sourceDefinition, $data); 284 | } 285 | else 286 | { 287 | // Extract source data into simple field. 288 | $targetData[$targetContext]->$halFieldName = $this->getSourceValue($sourceDefinition, $data); 289 | } 290 | } 291 | 292 | // Remove any redundant _links. 293 | if (isset($targetData['_links'])) 294 | { 295 | foreach ($targetData['_links'] as $k => $link) 296 | { 297 | if (!isset($link->href) || $link->href == '') 298 | { 299 | unset($targetData['_links']->$k); 300 | } 301 | } 302 | } 303 | 304 | // Move subsidiary objects under main object so it has the right structure when serialised. 305 | foreach ($targetData as $objName => $object) 306 | { 307 | if ($objName != 'main') 308 | { 309 | $targetData['main']->$objName = $targetData[$objName]; 310 | unset( $targetData[$objName]); 311 | } 312 | } 313 | 314 | return $targetData['main']; 315 | } 316 | 317 | /** 318 | * Transform a data object to its internal representation. 319 | * 320 | * @param object $data Data object. 321 | * 322 | * @return mixed Internal representation of the data object. 323 | */ 324 | public function toInternal($data) 325 | { 326 | // If there is no map then return the data unmodified. 327 | if (empty($this->map)) 328 | { 329 | return $data; 330 | } 331 | 332 | // @TODO 333 | } 334 | 335 | /** 336 | * Transform a source field data value. 337 | * 338 | * Calls the static toExternal method of a transform class. 339 | * 340 | * @param string $fieldType Field type. 341 | * @param string $definition Field definition. 342 | * @param string $data Data to be transformed. 343 | * 344 | * @return mixed Transformed data. 345 | */ 346 | private function transformField($fieldType, $definition, $data) 347 | { 348 | // Get the transform class name. 349 | $className = $this->getTransformClass($fieldType); 350 | 351 | // Execute the transform. 352 | if ($className instanceof ApiTransform) 353 | { 354 | return $className::toExternal($definition, $data); 355 | } 356 | else 357 | { 358 | return $definition; 359 | } 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /api/application/router.php: -------------------------------------------------------------------------------- 1 | controllerPrefix = 'Component' . ucfirst($parts[1]); 38 | $controller = $parts[2]; 39 | } 40 | 41 | return $controller; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/application/web.php: -------------------------------------------------------------------------------- 1 | _startTime = microtime(true); 67 | 68 | parent::__construct($input, $config, $client); 69 | 70 | // Load the Joomla CMS configuration object. 71 | $this->loadConfiguration($this->fetchConfigurationData()); 72 | 73 | // By default, assume response may be cached. 74 | $this->allowCache(true); 75 | } 76 | 77 | /** 78 | * Permits retrieval of the database connection for this application. 79 | * 80 | * @return JDatabaseDriver The database driver. 81 | * 82 | * @since 3.2 83 | */ 84 | public function getDatabase() 85 | { 86 | return $this->db; 87 | } 88 | 89 | /** 90 | * Allows the application to load a custom or default database driver. 91 | * 92 | * @param JDatabaseDriver $driver An optional database driver object. If omitted, the application driver is created. 93 | * 94 | * @return object This method may be chained. 95 | * 96 | * @since 3.2 97 | */ 98 | public function loadDatabase(JDatabaseDriver $driver = null) 99 | { 100 | if ($driver === null) 101 | { 102 | $this->db = JDatabaseDriver::getInstance( 103 | array( 104 | 'driver' => $this->get('dbtype'), 105 | 'host' => $this->get('host'), 106 | 'user' => $this->get('user'), 107 | 'password' => $this->get('password'), 108 | 'database' => $this->get('db'), 109 | 'prefix' => $this->get('dbprefix'), 110 | // 'schema' => $this->get('db_schema'), 111 | // 'port' => $this->get('db_port') 112 | ) 113 | ); 114 | 115 | // Select the database. 116 | $this->db->select($this->get('db')); 117 | } 118 | // Use the given database driver object. 119 | else 120 | { 121 | $this->db = $driver; 122 | } 123 | 124 | // Set the database to our static cache. 125 | JFactory::$database = $this->db; 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Method to load services route maps. 132 | * 133 | * @param array $maps A list of route maps to add to the router as $pattern => $controller. 134 | * 135 | * @return object This method may be chained. 136 | * 137 | * @since 3.2 138 | */ 139 | protected function loadMaps($maps = array()) 140 | { 141 | // Make sure we have an array. 142 | $maps = (array) $maps; 143 | 144 | // If route indicates a traditional Joomla component then register special prefix. 145 | foreach ($maps as $key => $route) 146 | { 147 | $parts = explode('/', $route); 148 | if ($parts[0] == 'component') 149 | { 150 | $path = JPATH_SITE . '/components/com_' . $parts[1] . '/services'; 151 | JLoader::registerPrefix('Component' . ucfirst($parts[1]), $path); 152 | } 153 | } 154 | 155 | $this->maps = array_merge($this->maps, $maps); 156 | 157 | return $this; 158 | } 159 | 160 | /** 161 | * Allows the application to load a custom or default router. 162 | * 163 | * @param JApplicationWebRouter $router An optional router object. If omitted, the standard router is created. 164 | * 165 | * @return object This method may be chained. 166 | * 167 | * @since 3.2 168 | */ 169 | public function loadRouter(JApplicationWebRouter $router = null) 170 | { 171 | $this->router = ($router === null) ? new ApiApplicationRouter($this, $this->input) : $router; 172 | 173 | return $this; 174 | } 175 | 176 | /** 177 | * Allows the application to load a custom or default dispatcher. 178 | * 179 | * The logic and options for creating this object are adequately generic for default cases 180 | * but for many applications it will make sense to override this method and create event 181 | * dispatchers, if required, based on more specific needs. 182 | * 183 | * @param JEventDispatcher $dispatcher An optional dispatcher object. If omitted, the factory dispatcher is created. 184 | * 185 | * @return JApplicationBase This method is chainable. 186 | * 187 | * @since 12.1 188 | */ 189 | public function loadDispatcher(JEventDispatcher $dispatcher = null) 190 | { 191 | $this->dispatcher = ($dispatcher === null) ? JEventDispatcher::getInstance() : $dispatcher; 192 | 193 | // Trigger the authentication plugins. 194 | JPluginHelper::importPlugin('authentication'); 195 | 196 | return $this; 197 | } 198 | 199 | /** 200 | * Execute the application. 201 | * 202 | * @return void 203 | * 204 | * @since 3.2 205 | */ 206 | protected function doExecute() 207 | { 208 | $documentOptions = array( 209 | 'absoluteHrefs' => $this->get('absoluteHrefs', false), 210 | ); 211 | 212 | try 213 | { 214 | // Set the controller prefix, add maps, and execute the appropriate controller. 215 | $this->input = new JInputJson; 216 | $this->document = new ApiDocumentHalJson($documentOptions); 217 | $this->router->setControllerPrefix('ApiServices') 218 | ->setDefaultController('Root') 219 | ->addMaps($this->maps) 220 | ->execute($this->get('uri.route')); 221 | } 222 | catch (Exception $e) 223 | { 224 | $this->setHeader('status', '400', true); 225 | $message = $e->getMessage(); 226 | $body = array('message' => $message, 'code' => $e->getCode(), 'type' => get_class($e)); 227 | 228 | $this->setBody(json_encode($body)); 229 | } 230 | } 231 | 232 | /** 233 | * Method to get the application configuration data to be loaded. 234 | * 235 | * @param string $file The path and filename of the configuration file. If not provided, configuration.php 236 | * in JPATH_BASE will be used. 237 | * @param string $class The class name to instantiate. 238 | * 239 | * @return object An object to be loaded into the application configuration. 240 | * 241 | * @since 3.2 242 | */ 243 | public function fetchApiConfigurationData($file = '', $class = 'JConfig') 244 | { 245 | // Instantiate variables. 246 | $config = array(); 247 | 248 | // Ensure that required path constants are defined. 249 | if (!defined('JPATH_CONFIGURATION')) 250 | { 251 | $path = getenv('JAPI_CONFIG'); 252 | if ($path) 253 | { 254 | define('JPATH_CONFIGURATION', realpath($path)); 255 | } 256 | else 257 | { 258 | define('JPATH_CONFIGURATION', realpath(dirname(JPATH_BASE) . '/config')); 259 | } 260 | } 261 | 262 | // Set the configuration file path for the application. 263 | if (file_exists(JPATH_CONFIGURATION . '/config.json')) 264 | { 265 | $file = JPATH_CONFIGURATION . '/config.json'; 266 | } 267 | else 268 | { 269 | $file = JPATH_CONFIGURATION . '/config.dist.json'; 270 | } 271 | 272 | if (!is_readable($file)) 273 | { 274 | throw new RuntimeException('Configuration file does not exist or is unreadable.'); 275 | } 276 | 277 | // Load the configuration file into an object. 278 | $config = json_decode(file_get_contents($file)); 279 | 280 | return $config; 281 | } 282 | 283 | /** 284 | * Method to load services route maps from all subdirectories 285 | * within a given directory (non-recursive). 286 | * 287 | * @param string $basePath Path to base directory. 288 | * 289 | * @return object This method may be chained. 290 | * 291 | * @since 3.2 292 | */ 293 | protected function fetchMaps($basePath = JPATH_SITE) 294 | { 295 | // Get a directory iterator for the base path. 296 | $iterator = new DirectoryIterator($basePath); 297 | 298 | // Iterate over the files, looking for just the directories. 299 | foreach ($iterator as $file) 300 | { 301 | $fileName = $file->getFilename(); 302 | 303 | // Only want directories. 304 | if ($file->isDir()) 305 | { 306 | // Look for services file. 307 | $servicesFilename = $basePath . '/' . $fileName . '/services.json'; 308 | if (file_exists($servicesFilename)) 309 | { 310 | $this->loadMaps(json_decode(file_get_contents($servicesFilename), true)); 311 | } 312 | } 313 | } 314 | 315 | return $this; 316 | } 317 | 318 | /** 319 | * Method to load services route maps from standard locations. 320 | * 321 | * @return object This method may be chained. 322 | * 323 | * @since 3.2 324 | */ 325 | public function fetchStandardMaps() 326 | { 327 | // Look for maps in front-end components. 328 | $this->fetchMaps(JPATH_SITE . '/components'); 329 | 330 | // Look for maps in back-end components. 331 | $this->fetchMaps(JPATH_ADMINISTRATOR . '/components'); 332 | 333 | // Merge the main services file. 334 | $this->loadMaps(json_decode(file_get_contents(JPATH_CONFIGURATION . '/services.json'), true)); 335 | 336 | return $this; 337 | } 338 | 339 | /** 340 | * Method to get services route maps. 341 | * 342 | * @return array A list of route maps to add to the router as $pattern => $controller. 343 | * 344 | * @since 3.2 345 | */ 346 | public function getMaps() 347 | { 348 | return $this->maps; 349 | } 350 | 351 | /** 352 | * Method to send the application response to the client. All headers will be sent prior to the main 353 | * application output data. 354 | * 355 | * @return void 356 | * 357 | * @since 3.2 358 | */ 359 | protected function respond() 360 | { 361 | $runtime = microtime(true) - $this->_startTime; 362 | 363 | // Set the Server and X-Powered-By Header. 364 | $this->setHeader('Server', '', true); 365 | $this->setHeader('X-Powered-By', 'JoomlaWebAPI/1.0', true); 366 | $this->setHeader('X-Runtime', $runtime, true); 367 | $this->setHeader('Access-Control-Allow-Origin', '*', true); 368 | 369 | // Copy document encoding and charset into application. 370 | $this->mimeType = $this->getDocument()->getMimeEncoding(); 371 | $this->charSet = $this->getDocument()->getCharset(); 372 | 373 | parent::respond(); 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /api/controller/base.php: -------------------------------------------------------------------------------- 1 | getService(); 56 | 57 | // Push results into the document. 58 | $this->app->getDocument() 59 | ->setMimeEncoding($this->contentType) // Comment this line out to debug 60 | ->setBuffer($service->getHal()) 61 | ; 62 | } 63 | 64 | /** 65 | * Get API query object. 66 | * 67 | * Returns the API query helper object, creating a new 68 | * one if it doesn't already exist. 69 | * May be overridden in child classes. 70 | * 71 | * @return ApiDatabaseQuery object; 72 | */ 73 | public function getApiQuery() 74 | { 75 | if (is_null($this->apiQuery)) 76 | { 77 | // Get a database query helper object. 78 | $this->apiQuery = new ApiDatabaseQuery($this->db); 79 | } 80 | 81 | return $this->apiQuery; 82 | } 83 | 84 | /** 85 | * Get resource data. 86 | * 87 | * May be overridden in child classes. 88 | * 89 | * @return object Resource data. 90 | */ 91 | public function getData() 92 | { 93 | } 94 | 95 | /** 96 | * Get database query. 97 | * 98 | * Returns a new base query for the table name given. 99 | * May be overridden in child classes. 100 | * 101 | * @param string $table Primary table name. 102 | * 103 | * @return JDatabaseDriver object. 104 | */ 105 | public function getQuery($table) 106 | { 107 | // Create a database query object. 108 | $query = $this->db->getQuery(true) 109 | ->select('*') 110 | ->from($this->db->qn($table) . ' as p') 111 | ; 112 | 113 | return $query; 114 | } 115 | 116 | /** 117 | * Get service object. 118 | * 119 | * May be overridden in child classes. 120 | * 121 | * @return ApiDatabaseQuery object; 122 | */ 123 | public function getService() 124 | { 125 | if (is_null($this->service)) 126 | { 127 | $this->service = new ApiApplicationHalJoomla($this->serviceOptions); 128 | } 129 | 130 | return $this->service; 131 | } 132 | 133 | /** 134 | * Set the database driver to use. 135 | * 136 | * @param JDatabaseDriver $db Database driver. 137 | * 138 | * @return object This method may be chained. 139 | */ 140 | public function setDatabase(JDatabaseDriver $db = null) 141 | { 142 | $this->db = isset($db) ? $db : $this->app->getDatabase(); 143 | 144 | return $this; 145 | } 146 | 147 | /** 148 | * Set controller options. 149 | * 150 | * @param array $options Array of controller options. 151 | * 152 | * @return object This method may be chained. 153 | */ 154 | public function setOptions($options = array()) 155 | { 156 | // Setup dependencies. 157 | $this->serviceOptions = (array) $options; 158 | 159 | // Set primary table name. 160 | if (isset($options['tableName'])) 161 | { 162 | $this->tableName = $options['tableName']; 163 | } 164 | 165 | // Set the content type. 166 | if (isset($options['contentType'])) 167 | { 168 | $this->contentType = $options['contentType']; 169 | } 170 | 171 | // Set the primary relation. 172 | if (isset($options['primaryRel'])) 173 | { 174 | $this->primaryRel = $options['primaryRel']; 175 | } 176 | 177 | return $this; 178 | 179 | } 180 | 181 | } -------------------------------------------------------------------------------- /api/controller/controller.php: -------------------------------------------------------------------------------- 1 | id = (int) $this->input->get('id'); 23 | 24 | // Get resource item data. 25 | $data = $this->getData(); 26 | 27 | // Get service object. 28 | $service = $this->getService(); 29 | 30 | // Load the data into the HAL object. 31 | $service->load($data); 32 | 33 | parent::execute(); 34 | } 35 | 36 | /** 37 | * Get data for a single resource item. 38 | * 39 | * @return object Single resource item object. 40 | */ 41 | public function getData() 42 | { 43 | // Get the database query object. 44 | $query = $this->getQuery($this->tableName); 45 | 46 | // Get a database query helper object. 47 | $apiQuery = $this->getApiQuery(); 48 | 49 | // Get single record from database. 50 | $data = $apiQuery->getItem($query, (int) $this->id); 51 | 52 | return $data; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /api/controller/list.php: -------------------------------------------------------------------------------- 1 | catid = (int) $this->input->get('catid'); 23 | 24 | // Get page of data. 25 | $data = $this->getData(); 26 | 27 | // Get service object. 28 | $service = $this->getService(); 29 | 30 | // Set pagination. 31 | $service->setPagination($this->getApiQuery()->getPagination()); 32 | 33 | // Import the data into the HAL object. 34 | $service->embed($this->primaryRel, $data); 35 | 36 | parent::execute(); 37 | } 38 | 39 | /** 40 | * Get a page of data. 41 | * 42 | * @return array Array of data records. 43 | */ 44 | public function getData() 45 | { 46 | // Get the database query object. 47 | $query = $this->getQuery($this->tableName); 48 | 49 | // Get a database query helper object. 50 | $apiQuery = $this->getApiQuery(); 51 | 52 | // Get pagination variables. 53 | $pagination = $this->getPagination(); 54 | 55 | // Get the page of data. 56 | $data = $apiQuery->setPagination($pagination)->getList($query); 57 | 58 | return $data; 59 | } 60 | 61 | /** 62 | * Get pagination variables. 63 | * 64 | * May be overridden in child classes. 65 | * 66 | * @return array Array of pagination variables. 67 | */ 68 | public function getPagination() 69 | { 70 | // Set pagination variables from input. 71 | $pagination = array( 72 | 'offset' => (int) $this->input->get('offset', 0), 73 | 'page' => (int) $this->input->get('page', 1), 74 | 'perPage' => (int) $this->input->get('perPage', 15), 75 | ); 76 | 77 | return $pagination; 78 | } 79 | 80 | /** 81 | * Get database query. 82 | * 83 | * May be overridden in child classes. 84 | * 85 | * @param string $table Primary table name. 86 | * 87 | * @return JDatabaseDriver object. 88 | */ 89 | public function getQuery($table) 90 | { 91 | // Get the base query. 92 | $query = parent::getQuery($table); 93 | 94 | if ($this->catid) 95 | { 96 | $query->where($this->db->qn('catid') . ' = ' . (int) $this->catid); 97 | } 98 | 99 | return $query; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /api/database/query.php: -------------------------------------------------------------------------------- 1 | db = $db; 35 | 36 | // Initialise pagination array. 37 | $this->pagination = array( 38 | 'offset' => 0, 39 | 'page' => 1, 40 | 'perPage' => 10, 41 | ); 42 | } 43 | 44 | /** 45 | * Get single data record. 46 | * 47 | * Given a base query this method will return the single 48 | * data record with the given value of a unique key. 49 | * 50 | * @param JDatabaseQuery $query A database query object. 51 | * @param integer $id Unique key value. 52 | * @param string $pk Key name. 53 | * 54 | * @return object Single resource item object. 55 | */ 56 | public function getItem(JDatabaseQuery $query, $id, $pk = 'id') 57 | { 58 | // Apply key to query. 59 | $itemQuery = clone($query); 60 | $itemQuery->where($this->db->qn($pk) . ' = ' . (int) $id); 61 | 62 | // Retrieve the data. 63 | $data = $this->db 64 | ->setQuery($itemQuery) 65 | ->loadObject(); 66 | 67 | return $data; 68 | } 69 | 70 | /** 71 | * Get page of data. 72 | * 73 | * Given a base query this method will apply current pagination 74 | * variables to return a page of data records. 75 | * 76 | * @param JDatabaseQuery $query A database query object. 77 | * 78 | * @return Array of data objects returned by the query. 79 | */ 80 | public function getList(JDatabaseQuery $query) 81 | { 82 | // Apply sanity check to perPage. 83 | $this->pagination['perPage'] = min(max($this->pagination['perPage'], 1), 100); 84 | 85 | // Determine total items and total pages. 86 | $countQuery = clone($query); 87 | $countQuery->clear('select')->select('count(*)'); 88 | $this->pagination['totalItems'] = (int) $this->db->setQuery($countQuery)->loadResult(); 89 | $this->pagination['totalPages'] = (int) floor(($this->pagination['totalItems']-1)/$this->pagination['perPage']) + 1; 90 | 91 | // Apply sanity check to page number. 92 | $this->pagination['page'] = min(max($this->pagination['page'], 1), $this->pagination['totalPages']); 93 | 94 | // Calculate base for paginated query. 95 | $base = ($this->pagination['page'] - 1) * $this->pagination['perPage'] + $this->pagination['offset']; 96 | 97 | // Retrieve the data. 98 | $data = $this->db 99 | ->setQuery($query, $base, $this->pagination['perPage']) 100 | ->loadObjectList(); 101 | 102 | return $data; 103 | } 104 | 105 | /** 106 | * Return array of pagination variables. 107 | * 108 | * @return array Array of pagination variables. 109 | */ 110 | public function getPagination() 111 | { 112 | return $this->pagination; 113 | } 114 | 115 | /** 116 | * Set pagination variables. 117 | * 118 | * @param array $pagination Array of pagination variables. 119 | * 120 | * @return object This object may be chained. 121 | */ 122 | public function setPagination($pagination = array()) 123 | { 124 | $this->pagination = array_merge($this->pagination, $pagination); 125 | 126 | return $this; 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /api/document/hal/json.php: -------------------------------------------------------------------------------- 1 | _mime = 'application/json'; 45 | 46 | // Set document type. 47 | $this->_type = 'hal+json'; 48 | 49 | // Set absolute/relative hrefs. 50 | $this->absoluteHrefs = isset($options['absoluteHrefs']) ? $options['absoluteHrefs'] : false; 51 | } 52 | 53 | /** 54 | * Render the document. 55 | * 56 | * @param boolean $cache If true, cache the output 57 | * @param array $params Associative array of attributes 58 | * 59 | * @return The rendered data 60 | * 61 | * @since 3.1 62 | */ 63 | public function render($cache = false, $params = array()) 64 | { 65 | JResponse::allowCache($cache); 66 | JResponse::setHeader('Content-disposition', 'attachment; filename="' . $this->getName() . '.json"', true); 67 | 68 | // Unfortunately, the exact syntax of the Content-Type header 69 | // is not defined, so we have to try to be a bit clever here. 70 | $contentType = $this->_mime; 71 | if (stripos($contentType, 'json') === false) 72 | { 73 | $contentType .= '+' . $this->_type; 74 | } 75 | $this->_mime = $contentType; 76 | 77 | parent::render(); 78 | 79 | // Get the HAL object from the buffer. 80 | $hal = $this->getBuffer(); 81 | 82 | // If required, change relative links to absolute. 83 | if ($this->absoluteHrefs && is_object($hal) && isset($hal->_links)) 84 | { 85 | // Adjust hrefs in the _links object. 86 | $this->relToAbs($hal->_links); 87 | 88 | // Adjust hrefs in the _embedded object (if there is one). 89 | if (isset($hal->_embedded)) 90 | { 91 | foreach ($hal->_embedded as $rel => $resources) 92 | { 93 | foreach ($resources as $id => $resource) 94 | { 95 | if (isset($resource->_links)) 96 | { 97 | $this->relToAbs($resource->_links); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | // Return it as a JSON string. 105 | return json_encode($hal); 106 | } 107 | 108 | /** 109 | * Returns the document name 110 | * 111 | * @return string 112 | * 113 | * @since 3.1 114 | */ 115 | public function getName() 116 | { 117 | return $this->_name; 118 | } 119 | 120 | /** 121 | * Method to convert relative to absolute links. 122 | * 123 | * @param object $links Links object (eg. _links). 124 | */ 125 | protected function relToAbs($links) 126 | { 127 | // Adjust hrefs in the _links object. 128 | foreach ($links as $rel => $link) 129 | { 130 | if (substr($link->href, 0, 1) == '/') 131 | { 132 | $links->$rel->href = rtrim(JUri::base(), '/') . $link->href; 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Sets the document name 139 | * 140 | * @param string $name Document name 141 | * 142 | * @return JDocumentJSON instance of $this to allow chaining 143 | * 144 | * @since 3.1 145 | */ 146 | public function setName($name = 'joomla') 147 | { 148 | $this->_name = $name; 149 | 150 | return $this; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /api/import.php: -------------------------------------------------------------------------------- 1 | loadSession() 95 | ->loadDatabase() 96 | ->loadIdentity() 97 | ->loadDispatcher() 98 | ->fetchStandardMaps() 99 | ->loadRouter() 100 | ->execute(); 101 | } 102 | catch (Exception $e) 103 | { 104 | // Set the server response code. 105 | header('Status: 500', true, 500); 106 | 107 | // An exception has been caught, echo the message and exit. 108 | echo json_encode(array('message' => $e->getMessage(), 'code' => $e->getCode(), 'type' => get_class($e))); 109 | exit(); 110 | } 111 | -------------------------------------------------------------------------------- /api/services/categories/get.php: -------------------------------------------------------------------------------- 1 | setDatabase(); 23 | 24 | // Set the controller options. 25 | $serviceOptions = array( 26 | 'contentType' => 'application/vnd.joomla.item.v1; schema=categories.v1', 27 | 'describedBy' => 'http://docs.joomla.org/Schemas/categories/v1', 28 | 'primaryRel' => 'joomla:categories', 29 | 'resourceMap' => __DIR__ . '/resource.json', 30 | 'self' => '/joomla:categories/' . (int) $this->input->get('id'), 31 | 'tableName' => '#__categories', 32 | ); 33 | 34 | $this->setOptions($serviceOptions); 35 | } 36 | 37 | /** 38 | * Execute the request. 39 | */ 40 | public function execute() 41 | { 42 | // Get resource item id from input. 43 | $this->id = (int) $this->input->get('id'); 44 | 45 | // Get resource item data. 46 | $data = $this->getData(); 47 | 48 | // Get service object. 49 | $service = $this->getService(); 50 | 51 | // We need to add a link from the current category to the content items that 52 | // exist within the category. However, we only know the name of the extension 53 | // and not the resource name used by the API. We try to work out the correct 54 | // entry to make by doing a reverse-lookup on the router maps. 55 | if (isset($data->extension)) 56 | { 57 | // Get the component name (without the com_ prefix). 58 | $extension = str_replace('com_', '', $data->extension); 59 | 60 | // Get the router maps. 61 | $maps = $this->app->getMaps(); 62 | 63 | // Construct the regex pattern of the route we want to find. 64 | $pattern = '#component/' . $extension . '/(.*)List#'; 65 | 66 | // Look for an appropriate route. 67 | foreach ($maps as $rel => $route) 68 | { 69 | if (substr($rel, 0, 17) == 'joomla:categories') 70 | { 71 | // Look for a match for our route. 72 | $matches = array(); 73 | preg_match($pattern, $route, $matches); 74 | 75 | if (!empty($matches)) 76 | { 77 | // Add a link to the resources associated with the category. 78 | $linkRel = 'joomla:' . strtolower($matches[1]); 79 | $linkHref = '/' . str_replace(':catid', $this->id, $rel); 80 | $service->addLink(new ApiApplicationHalLink($linkRel, $linkHref)); 81 | } 82 | } 83 | } 84 | } 85 | 86 | // Load the data into the HAL object. 87 | $service->load($data); 88 | 89 | parent::execute(); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /api/services/categories/list/embedded.json: -------------------------------------------------------------------------------- 1 | { 2 | "embedded": 3 | [ 4 | "/access", 5 | "/description", 6 | "/extension", 7 | "/language", 8 | "_links/self.href", 9 | "_links/joomla:checkout.href", 10 | "_links/joomla:checkout.method", 11 | "metadata/authorName", 12 | "/ordering", 13 | "publish/alias", 14 | "publish/created", 15 | "/state", 16 | "/title" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /api/services/categories/list/get.php: -------------------------------------------------------------------------------- 1 | setDatabase(); 23 | 24 | // Set the controller options. 25 | $serviceOptions = array( 26 | 'contentType' => 'application/vnd.joomla.list.v1', 27 | 'describedBy' => 'http://docs.joomla.org/Schemas/categories/v1', 28 | 'embeddedMap' => __DIR__ . '/embedded.json', 29 | 'primaryRel' => 'joomla:categories', 30 | 'resourceMap' => realpath(__DIR__ . '/../resource.json'), 31 | 'self' => '/joomla:categories', 32 | 'tableName' => '#__categories', 33 | ); 34 | 35 | $this->setOptions($serviceOptions); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /api/services/categories/resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "/access":"int:{access}", 3 | "/description":"string:{description}", 4 | "/extension":"string:{extension}", 5 | "/language":"string:{language}", 6 | "/level":"int:{level}", 7 | "_links/self.href":"string:/joomla:categories/{id}", 8 | "_links/joomla:assets.href":"string:/joomla:assets/{asset_id}", 9 | "_links/joomla:checkin.href":"string:/joomla:categories/{id}?checkin", 10 | "_links/joomla:checkin.method":"string:post", 11 | "_links/joomla:checkout.href":"string:/joomla:categories/{id}?checkout", 12 | "_links/joomla:checkout.method":"string:post", 13 | "_links/joomla:createdBy.href":"string:/joomla:users/{created_user_id}", 14 | "_links/joomla:image.href":"string:{params.image}", 15 | "_links/joomla:modifiedBy.href":"string:/joomla:users/{modified_user_id}", 16 | "metadata/authorName":"string:{metadata.author}", 17 | "metadata/description":"string:{metadesc}", 18 | "metadata/keywords":"string:{metakey}", 19 | "metadata/robots":"string:{metadata.robots}", 20 | "metadata/rights":"string:{metadata.rights}", 21 | "/note":"string:{note}", 22 | "/path":"string:{path}", 23 | "publish/alias":"string:{alias}", 24 | "publish/created":"datetime:{created_time}", 25 | "publish/hits":"int:{hits}", 26 | "publish/revision":"int:{version}", 27 | "render/target":"target:{params.target}", 28 | "/state":"state:{published}", 29 | "/title":"string:{title}" 30 | } 31 | -------------------------------------------------------------------------------- /api/services/menuitems/get.php: -------------------------------------------------------------------------------- 1 | setDatabase(); 23 | 24 | // Set the controller options. 25 | $serviceOptions = array( 26 | 'contentType' => 'application/vnd.joomla.item.v1; schema=menuitems.v1', 27 | 'describedBy' => 'http://docs.joomla.org/Schemas/menuitems/v1', 28 | 'primaryRel' => 'joomla:menuitems', 29 | 'resourceMap' => __DIR__ . '/resource.json', 30 | 'self' => '/joomla:menuitems/' . (int) $this->input->get('id'), 31 | 'tableName' => '#__menu', 32 | ); 33 | 34 | $this->setOptions($serviceOptions); 35 | } 36 | 37 | /** 38 | * Get a single record from database. 39 | * 40 | * @return array Array of data records. 41 | */ 42 | public function getData() 43 | { 44 | // Get the database query object. 45 | $query = $this->getQuery($this->tableName); 46 | 47 | // Get a database query helper object. 48 | $apiQuery = $this->getApiQuery(); 49 | 50 | // Get single record from database. 51 | $data = $apiQuery->getItem($query, (int) $this->input->get('id'), 'm.id'); 52 | 53 | return $data; 54 | } 55 | 56 | /** 57 | * Get database query. 58 | * 59 | * @param string $table Primary table name. 60 | * 61 | * @return JDatabaseDriver object. 62 | */ 63 | public function getQuery($table) 64 | { 65 | // Create a database query object. 66 | $query = $this->db->getQuery(true) 67 | ->select('m.*, mt.id AS menu_id') 68 | ->from('#__menu AS m') 69 | ->leftjoin('#__menu_types AS mt ON m.menutype = mt.menutype') 70 | ; 71 | 72 | return $query; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /api/services/menuitems/list/embedded.json: -------------------------------------------------------------------------------- 1 | { 2 | "embedded": 3 | [ 4 | "/access", 5 | "/language", 6 | "/level", 7 | "_links/self.href", 8 | "_links/joomla:checkout.href", 9 | "_links/joomla:checkout.method", 10 | "_links/joomla:image.href", 11 | "_links/joomla:menus.href", 12 | "publish/alias", 13 | "/state", 14 | "/title", 15 | "/type" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /api/services/menuitems/list/get.php: -------------------------------------------------------------------------------- 1 | setDatabase(); 23 | 24 | // Set the controller options. 25 | $serviceOptions = array( 26 | 'contentType' => 'application/vnd.joomla.list.v1', 27 | 'describedBy' => 'http://docs.joomla.org/Schemas/menuitems/v1', 28 | 'embeddedMap' => __DIR__ . '/embedded.json', 29 | 'primaryRel' => 'joomla:menuitems', 30 | 'resourceMap' => realpath(__DIR__ . '/../resource.json'), 31 | 'self' => '/joomla:menuitems', 32 | 'tableName' => '#__menu', 33 | ); 34 | 35 | $this->setOptions($serviceOptions); 36 | } 37 | 38 | /** 39 | * Get database query. 40 | * 41 | * @param string $table Primary table name. 42 | * 43 | * @return JDatabaseDriver object. 44 | */ 45 | public function getQuery($table) 46 | { 47 | // Create a database query object. 48 | $query = $this->db->getQuery(true) 49 | ->select('m.*, mt.id AS menu_id') 50 | ->from('#__menu AS m') 51 | ->leftjoin('#__menu_types AS mt ON m.menutype = mt.menutype') 52 | ; 53 | 54 | return $query; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /api/services/menuitems/resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "/access":"int:{access}", 3 | "/language":"string:{language}", 4 | "/level":"int:{level}", 5 | "_links/self.href":"string:/joomla:menuitems/{id}", 6 | "_links/joomla:checkin.href":"string:/joomla:menuitems/{id}?checkin", 7 | "_links/joomla:checkin.method":"string:post", 8 | "_links/joomla:checkout.href":"string:/joomla:menuitems/{id}?checkout", 9 | "_links/joomla:checkout.method":"string:post", 10 | "_links/joomla:image.href":"string:{img}", 11 | "_links/joomla:menus.href":"string:/joomla:menus/{menu_id}", 12 | "/note":"string:{note}", 13 | "publish/alias":"string:{alias}", 14 | "render/linkTitle":"string:{params.menu-anchor_title}", 15 | "render/linkCss":"string:{params.menu-anchor_css}", 16 | "/state":"state:{published}", 17 | "/title":"string:{title}", 18 | "/type":"string:{type}" 19 | } 20 | -------------------------------------------------------------------------------- /api/services/root/get.php: -------------------------------------------------------------------------------- 1 | 'application/vnd.joomla.service.v1', 24 | 'describedBy' => 'http://docs.joomla.org/Schemas/service/v1', 25 | 'self' => '/', 26 | ); 27 | 28 | $this->setOptions($serviceOptions); 29 | } 30 | 31 | /** 32 | * Execute the request. 33 | */ 34 | public function execute() 35 | { 36 | // Get service object. 37 | $service = $this->getService(); 38 | 39 | // Look for the top-level resources and add them as links. 40 | foreach ($this->app->getMaps() as $route => $map) 41 | { 42 | if (strpos($route, '/') === false) 43 | { 44 | $service->addLink(new ApiApplicationHalLink($route, '/' . $route)); 45 | } 46 | } 47 | 48 | parent::execute(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /api/transform/base.php: -------------------------------------------------------------------------------- 1 | setDatabase(); 23 | 24 | // Set the controller options. 25 | $serviceOptions = array( 26 | 'contentType' => 'application/vnd.joomla.item.v1; schema=articles.v1', 27 | 'describedBy' => 'http://docs.joomla.org/Schemas/articles/v1', 28 | 'primaryRel' => 'joomla:articles', 29 | 'resourceMap' => __DIR__ . '/resource.json', 30 | 'self' => '/joomla:articles/' . (int) $this->input->get('id'), 31 | 'tableName' => '#__content', 32 | ); 33 | 34 | $this->setOptions($serviceOptions); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /components/com_content/services/articles/list/embedded.json: -------------------------------------------------------------------------------- 1 | { 2 | "embedded": 3 | [ 4 | "/access", 5 | "/featured", 6 | "/introText", 7 | "/language", 8 | "_links/self.href", 9 | "_links/joomla:assets.href", 10 | "_links/joomla:categories.href", 11 | "_links/joomla:checkout.href", 12 | "_links/joomla:checkout.method", 13 | "_links/joomla:introImage.href", 14 | "_links/joomla:introImage.float", 15 | "_links/joomla:introImage.title", 16 | "_links/joomla:introImage.caption", 17 | "metadata/authorName", 18 | "/ordering", 19 | "publish/alias", 20 | "publish/created", 21 | "publish/publishDown", 22 | "publish/publishUp", 23 | "/state", 24 | "/title" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /components/com_content/services/articles/list/get.php: -------------------------------------------------------------------------------- 1 | setDatabase(); 23 | 24 | // Set the controller options. 25 | $serviceOptions = array( 26 | 'contentType' => 'application/vnd.joomla.list.v1', 27 | 'describedBy' => 'http://docs.joomla.org/Schemas/articles/v1', 28 | 'embeddedMap' => __DIR__ . '/embedded.json', 29 | 'primaryRel' => 'joomla:articles', 30 | 'resourceMap' => realpath(__DIR__ . '/../resource.json'), 31 | 'self' => '/joomla:articles', 32 | 'tableName' => '#__content', 33 | ); 34 | 35 | $this->setOptions($serviceOptions); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /components/com_content/services/articles/resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "/access":"int:{access}", 3 | "/featured":"boolean:{featured}", 4 | "/fullText":"string:{fulltext}", 5 | "/introText":"string:{introtext}", 6 | "/language":"string:{language}", 7 | "_links/self.href":"string:/joomla:articles/{id}", 8 | "_links/joomla:assets.href":"string:/joomla:assets/{asset_id}", 9 | "_links/joomla:categories.href":"string:/joomla:categories/{catid}", 10 | "_links/joomla:checkin.href":"string:/joomla:articles/{id}?checkin", 11 | "_links/joomla:checkin.method":"string:post", 12 | "_links/joomla:checkout.href":"string:/joomla:articles/{id}?checkout", 13 | "_links/joomla:checkout.method":"string:post", 14 | "_links/joomla:createdBy.href":"string:/joomla:users/{created_by}", 15 | "_links/joomla:fullImage.href":"string:{images.image_fulltext}", 16 | "_links/joomla:fullImage.float":"float:{images.float_fulltext}", 17 | "_links/joomla:fullImage.title":"string:{images.image_fulltext_alt}", 18 | "_links/joomla:fullImage.caption":"string:{images.image_fulltext_caption}", 19 | "_links/joomla:introImage.href":"string:{images.image_intro}", 20 | "_links/joomla:introImage.float":"float:{images.float_intro}", 21 | "_links/joomla:introImage.title":"string:{images.image_intro_alt}", 22 | "_links/joomla:introImage.caption":"string:{images.image_intro_caption}", 23 | "_links/joomla:modifiedBy.href":"string:/joomla:users/{modified_by}", 24 | "_links/joomla:urlA.href":"string:{urls.urla}", 25 | "_links/joomla:urlA.title":"string:{urls.urlatext}", 26 | "_links/joomla:urlA.target":"target:{urls.targeta}", 27 | "_links/joomla:urlB.href":"string:{urls.urlb}", 28 | "_links/joomla:urlB.title":"string:{urls.urlbtext}", 29 | "_links/joomla:urlB.target":"target:{urls.targetb}", 30 | "_links/joomla:urlC.href":"string:{urls.urlc}", 31 | "_links/joomla:urlC.title":"string:{urls.urlctext}", 32 | "_links/joomla:urlC.target":"target:{urls.targetc}", 33 | "metadata/authorName":"string:{metadata.author}", 34 | "metadata/description":"string:{metadesc}", 35 | "metadata/keywords":"string:{metakey}", 36 | "metadata/robots":"string:{metadata.robots}", 37 | "metadata/rights":"string:{metadata.rights}", 38 | "metadata/xref":"string:{xreference}", 39 | "/ordering":"int:{ordering}", 40 | "publish/alias":"string:{alias}", 41 | "publish/created":"datetime:{created}", 42 | "publish/hits":"int:{hits}", 43 | "publish/publishDown":"datetime:{publish_down}", 44 | "publish/publishUp":"datetime:{publish_up}", 45 | "publish/revision":"int:{version}", 46 | "render/adminImagesLinksShow":"ynglobal:{attribs.show_urls_images_backend}", 47 | "render/adminOptionsShow":"ynglobal:{attribs.show_article_options}", 48 | "render/authorLink":"ynglobal:{attribs.link_author}", 49 | "render/authorShow":"ynglobal:{attribs.show_author}", 50 | "render/categoryLink":"ynglobal:{attribs.link_category}", 51 | "render/categoryParentLink":"ynglobal:{attribs.link_parent_category}", 52 | "render/categoryParentShow":"ynglobal:{attribs.show_parent_category}", 53 | "render/categoryShow":"ynglobal:{attribs.show_category}", 54 | "render/createdShow":"ynglobal:{attribs.show_create_date}", 55 | "render/hitsShow":"ynglobal:{attribs.show_hits}", 56 | "render/iconEmailShow":"ynglobal:{attribs.show_email_icon}", 57 | "render/iconPrintShow":"ynglobal:{attribs.show_print_icon}", 58 | "render/iconsShow":"ynglobal:{attribs.show_icons}", 59 | "render/introShow":"ynglobal:{attribs.show_intro}", 60 | "render/linksPosition":"position:{attribs.info_block_position}", 61 | "render/modifiedShow":"ynglobal:{attribs.show_modify_date}", 62 | "render/navigationShow":"ynglobal:{attribs.show_item_navigation}", 63 | "render/noauthLinksShow":"ynglobal:{attribs.show_noauth}", 64 | "render/publishShow":"ynglobal:{attribs.show_publish_date}", 65 | "render/readMore":"string:{attribs.alternative_readmore}", 66 | "render/siteImagesLinksShow":"ynglobal:{attribs.show_item_navigation}", 67 | "render/siteOptionsShow":"ynglobal:{attribs.show_article_options}", 68 | "render/titleLink":"ynglobal:{attribs.link_titles}", 69 | "render/titleShow":"ynglobal:{attribs.show_title}", 70 | "render/votingShow":"ynglobal:{attribs.show_vote}", 71 | "/state":"state:{state}", 72 | "/title":"string:{title}" 73 | } 74 | -------------------------------------------------------------------------------- /components/com_content/services/articles/transform/position.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/com_weblinks/services.json: -------------------------------------------------------------------------------- 1 | { 2 | "joomla:weblinks":"component/weblinks/WeblinksList", 3 | "joomla:weblinks/:id":"component/weblinks/Weblinks", 4 | "joomla:categories/:catid/weblinks":"component/weblinks/WeblinksList" 5 | } 6 | -------------------------------------------------------------------------------- /components/com_weblinks/services/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/com_weblinks/services/weblinks/get.php: -------------------------------------------------------------------------------- 1 | setDatabase(); 23 | 24 | // Set the controller options. 25 | $serviceOptions = array( 26 | 'contentType' => 'application/vnd.joomla.item.v1; schema=weblinks.v1', 27 | 'describedBy' => 'http://docs.joomla.org/Schemas/weblinks/v1', 28 | 'primaryRel' => 'joomla:weblinks', 29 | 'resourceMap' => __DIR__ . '/resource.json', 30 | 'self' => '/joomla:weblinks/' . (int) $this->input->get('id'), 31 | 'tableName' => '#__weblinks', 32 | ); 33 | 34 | $this->setOptions($serviceOptions); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /components/com_weblinks/services/weblinks/list/create.php: -------------------------------------------------------------------------------- 1 | setDatabase(); 23 | 24 | // Set the controller options. 25 | $serviceOptions = array( 26 | // 'contentType' => 'application/vnd.joomla.list.v1', 27 | // 'describedBy' => 'http://docs.joomla.org/Schemas/weblinks/v1', 28 | // 'embeddedMap' => __DIR__ . '/embedded.json', 29 | // 'primaryRel' => 'joomla:weblinks', 30 | 'resourceMap' => __DIR__ . '/../resource.json', 31 | // 'self' => '/joomla:weblinks', 32 | // 'tableName' => '#__weblinks', 33 | ); 34 | 35 | $this->setOptions($serviceOptions); 36 | } 37 | 38 | /** 39 | * Execute the request. 40 | */ 41 | public function execute() 42 | { 43 | print_r($this->app->input); 44 | 45 | $array = array( 46 | '_meta' => array( 47 | 'apiVersion' => 'string', 48 | 'contentType' => 'string', 49 | 'describedBy' => 'string', 50 | ), 51 | 'description' => 'string', 52 | ); 53 | $json = $this->app->input->getArray($array); 54 | print_r($json); 55 | 56 | // Get service object. 57 | $service = $this->getService(); 58 | 59 | // Get the resource map. 60 | $resourceMap = $service->getResourceMap(); 61 | 62 | print_r($resourceMap); 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /components/com_weblinks/services/weblinks/list/embedded.json: -------------------------------------------------------------------------------- 1 | { 2 | "embedded": 3 | [ 4 | "/access", 5 | "/featured", 6 | "/language", 7 | "_links/self.href", 8 | "_links/joomla:categories.href", 9 | "_links/joomla:checkout.href", 10 | "_links/joomla:checkout.method", 11 | "metadata/authorName", 12 | "/ordering", 13 | "publish/alias", 14 | "publish/created", 15 | "publish/publishDown", 16 | "publish/publishUp", 17 | "/state", 18 | "/title" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /components/com_weblinks/services/weblinks/list/get.php: -------------------------------------------------------------------------------- 1 | setDatabase(); 23 | 24 | // Set the controller options. 25 | $serviceOptions = array( 26 | 'contentType' => 'application/vnd.joomla.list.v1', 27 | 'describedBy' => 'http://docs.joomla.org/Schemas/weblinks/v1', 28 | 'embeddedMap' => __DIR__ . '/embedded.json', 29 | 'primaryRel' => 'joomla:weblinks', 30 | 'resourceMap' => realpath(__DIR__ . '/../resource.json'), 31 | 'self' => '/joomla:weblinks', 32 | 'tableName' => '#__weblinks', 33 | ); 34 | 35 | $this->setOptions($serviceOptions); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /components/com_weblinks/services/weblinks/resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "/access":"int:{access}", 3 | "/description":"string:{description}", 4 | "/featured":"boolean:{featured}", 5 | "/language":"string:{language}", 6 | "_links/self.href":"string:/joomla:weblinks/{id}", 7 | "_links/joomla:categories.href":"string:/joomla:categories/{catid}", 8 | "_links/joomla:checkin.href":"string:/joomla:weblinks/{id}?checkin", 9 | "_links/joomla:checkin.method":"string:post", 10 | "_links/joomla:checkout.href":"string:/joomla:weblinks/{id}?checkout", 11 | "_links/joomla:checkout.method":"string:post", 12 | "_links/joomla:createdBy.href":"string:/joomla:users/{created_by}", 13 | "_links/joomla:image1.href":"string:{images.image_first}", 14 | "_links/joomla:image1.float":"float:{images.float_first}", 15 | "_links/joomla:image1.title":"string:{images.image_first_alt}", 16 | "_links/joomla:image1.caption":"string:{images.image_first_caption}", 17 | "_links/joomla:image2.href":"string:{images.image_second}", 18 | "_links/joomla:image2.float":"float:{images.float_second}", 19 | "_links/joomla:image2.title":"string:{images.image_second_alt}", 20 | "_links/joomla:image2.caption":"string:{images.image_second_caption}", 21 | "_links/joomla:modifiedBy.href":"string:/joomla:users/{modified_by}", 22 | "metadata/authorName":"string:{created_by_alias}", 23 | "metadata/description":"string:{metadesc}", 24 | "metadata/keywords":"string:{metakey}", 25 | "metadata/robots":"string:{metadata.robots}", 26 | "metadata/rights":"string:{metadata.rights}", 27 | "metadata/xref":"string:{xreference}", 28 | "/ordering":"int:{ordering}", 29 | "publish/alias":"string:{alias}", 30 | "publish/created":"datetime:{created}", 31 | "publish/hits":"int:{hits}", 32 | "publish/publishDown":"datetime:{publish_down}", 33 | "publish/publishUp":"datetime:{publish_up}", 34 | "publish/revision":"int:{version}", 35 | "render/countClicks":"ynglobal:{params.count_clicks}", 36 | "render/height":"int:{params.height}", 37 | "render/target":"target:{params.target}", 38 | "render/width":"int:{params.width}", 39 | "/state":"state:{state}", 40 | "/title":"string:{title}", 41 | "/url":"string:{url}" 42 | } 43 | -------------------------------------------------------------------------------- /etc/README.md: -------------------------------------------------------------------------------- 1 | /etc 2 | ==== 3 | Here you will find a couple of JSON files used for configuration of the API. 4 | 5 | Note that the API code also grabs configuration data from the regular Joomla configuration.php file. 6 | 7 | ## config.json 8 | Contains general configuration data. 9 | 10 | ### absoluteHrefs 11 | Set to true if link relation hrefs should be rendered as absolute instead of relative URLs. Default is false. 12 | If you want to use the HAL Browser then you will need to set this to true. 13 | 14 | ## services.json 15 | Contains routes for services that are not provided by Joomla components. Not currently used and contains dummy data. 16 | 17 | -------------------------------------------------------------------------------- /etc/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug": false, 3 | "absoluteHrefs": true 4 | } 5 | -------------------------------------------------------------------------------- /etc/services.json: -------------------------------------------------------------------------------- 1 | { 2 | "joomla:categories":"CategoriesList", 3 | "joomla:categories/:id":"Categories", 4 | "joomla:menuitems":"MenuitemsList", 5 | "joomla:menuitems/:id":"Menuitems" 6 | } 7 | --------------------------------------------------------------------------------