├── CHANGELOG.md ├── README.md ├── api.php ├── api.yaml ├── blueprints.yaml └── resources ├── config.php ├── pages.php ├── resource.php ├── users.php └── version.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.0 2 | ## 10/30/2015 3 | 4 | 1. [](#new) 5 | * Initial Release 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grav API Plugin 2 | 3 | The **API Plugin** for [Grav](http://github.com/getgrav/grav) adds a REST API to Grav. 4 | 5 | | IMPORTANT!!! This plugin is currently in development as is to be considered an **alpha release**. As such, use this in a production environment **at your own risk!**. More features will be added in the future. 6 | 7 | # Installation 8 | 9 | Or clone from GitHub and put in the `user/plugins/api` folder. 10 | 11 | # Principles 12 | 13 | - Two URLs per resource: one for the resource collection, one for the individual resource 14 | - Use plural forms 15 | - Keep URLs as short as possible 16 | - Use nouns as resource names 17 | 18 | HTTP Method Names 19 | - GET: read a resource or collection 20 | - POST: create a resource 21 | - PUT: update a resource 22 | - DELETE: remove a resource 23 | 24 | # HTTP Status Codes 25 | 26 | Use meaningful HTTP Status Codes (TODO) 27 | 28 | - 200: Success. 29 | - 201: Created. Returned on successful creation of a new resource. Include a 'Location' header with a link to the newly-created resource. 30 | - 400: Bad request. Data issues such as invalid JSON, etc. 31 | - 403: Forbidden. Example: trying to create a page already existing 32 | - 404: Not found. Resource not found on GET. 33 | - 405: Not Allowed. 34 | - 501: Not Implemented. Returned by default by non implemented API methods 35 | 36 | # Things still missing 37 | 38 | - Support filtering, sorting and pagination on collections 39 | - Allow clients to reduce the number of fields that come back in the response 40 | - Access control to each API method, for groups or single users 41 | - Use http://fractal.thephpleague.com 42 | - Allow plugins to add API methods 43 | - Allow to limit access to a specific IP (range) 44 | - Use OAuth2 to secure the API http://oauth2.thephpleague.com/ 45 | - JSONP 46 | - Return the children of a page in the page request, if desired 47 | - Allow to only retrieve some fields on API calls that are heavy on the system (e.g. `GET /api/pages/:page`) 48 | 49 | # Authentication 50 | 51 | In this current form the Grav API uses Basic Authentication, with the username and password currently set in Grav. 52 | It's just a first implementation, willing to change that at least with digest authentication. 53 | 54 | Users with `admin.super` or `admin.api` permissions can access the whole API. 55 | 56 | ## Usage or Basic Authentication on the client-side 57 | 58 | On the client side, add the authorization header of the request as follows: 59 | 60 | ``` 61 | Authorization: Basic 62 | ``` 63 | 64 | the base64 value is username:password 65 | 66 | # Standard Grav API Endpoints 67 | 68 | ## Implemented 69 | 70 | ### Version 71 | 72 | Methods that return the API plugin version. Depending on the returned value, some methods might be available or not depending on the plugin evolution. To be used by clients when interacting with the Grav API. 73 | 74 | ### GET /api/version 75 | 76 | Get the API plugin major version. 77 | 78 | e.g. "0" or "1" 79 | 80 | ### GET /api/version/major 81 | 82 | Same as `GET /api/version` 83 | 84 | e.g. "0" or "1" 85 | 86 | ### GET /api/version/minor 87 | 88 | Get the API plugin major and minor version. 89 | 90 | e.g. "0.1" or "1.0" 91 | 92 | ### GET /api/version/full 93 | 94 | Get the API plugin full version. 95 | 96 | e.g. "0.1.3" 97 | 98 | ### Pages 99 | 100 | #### GET /api/pages 101 | 102 | Lists the site pages 103 | 104 | #### GET /api/pages/:page 105 | 106 | Get a specific page 107 | 108 | e.g. /api/pages/blog/my-first-post 109 | e.g. /api/pages/contact 110 | 111 | ### Users 112 | 113 | #### GET /api/users 114 | 115 | Lists the site users 116 | 117 | #### GET /api/users/:user 118 | 119 | Get a specific user 120 | 121 | e.g. /api/users/admin 122 | e.g. /api/users/andy 123 | 124 | ## Not yet implemented 125 | 126 | - GET /api/plugins 127 | - GET /api/plugins/shoppingcart 128 | - GET /api/themes 129 | - GET /api/themes/theme 130 | - GET /api/data 131 | - GET /api/logs 132 | - GET /api/config 133 | - GET /api/config/system 134 | - GET /api/config/plugin/:pluginName 135 | - GET /api/backups 136 | - GET /api/gpm 137 | - POST /api/gpm/update 138 | - GET /api/grav 139 | - POST /api/grav/self-upgrade 140 | - GET /api/version 141 | - GET /api/debug 142 | - ..... 143 | -------------------------------------------------------------------------------- /api.php: -------------------------------------------------------------------------------- 1 | ['onPagesInitialized', 0], 19 | ]; 20 | } 21 | 22 | public function onPagesInitialized() 23 | { 24 | $uri = $this->grav['uri']; 25 | 26 | if (strpos($uri->path(), $this->config->get('plugins.api.route') . '/' . $this->route) === false) { 27 | return; 28 | } 29 | 30 | if (!$this->isAuthorized()) { 31 | header('HTTP/1.1 401 Unauthorized'); 32 | exit(); 33 | } 34 | 35 | $paths = $this->grav['uri']->paths(); 36 | $paths = array_splice($paths, 1); 37 | $resource = $paths[0]; 38 | 39 | if ($resource) { 40 | $file = __DIR__ . '/resources/' . $resource . '.php'; 41 | if (file_exists($file)) { 42 | require_once $file; 43 | $resourceClassName = '\Grav\Plugin\Api\\' . ucfirst($resource); 44 | $resource = new $resourceClassName($this->grav); 45 | $output = $resource->execute(); 46 | $resource->setHeaders(); 47 | 48 | echo $output; 49 | } else { 50 | header('HTTP/1.1 404 Not Found'); 51 | } 52 | } 53 | 54 | exit(); 55 | } 56 | 57 | private function isAuthorized() 58 | { 59 | if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) { 60 | return false; 61 | } 62 | $username = $_SERVER['PHP_AUTH_USER']; 63 | $password = $_SERVER['PHP_AUTH_PW']; 64 | $user = User::load($username); 65 | $isAuthenticated = $user->authenticate($password); 66 | 67 | if ($isAuthenticated) { 68 | if ($this->authorize($user, ['admin.api', 'admin.super'])) { 69 | return true; 70 | } 71 | } 72 | 73 | return false; 74 | } 75 | 76 | /** 77 | * Checks user authorisation to the action. 78 | * 79 | * @param string $action 80 | * 81 | * @return bool 82 | */ 83 | public function authorize($user, $action) 84 | { 85 | $action = (array)$action; 86 | 87 | foreach ($action as $a) { 88 | if ($user->authorize($a)) { 89 | return true; 90 | } 91 | } 92 | 93 | return false; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /api.yaml: -------------------------------------------------------------------------------- 1 | enabled: true -------------------------------------------------------------------------------- /blueprints.yaml: -------------------------------------------------------------------------------- 1 | name: API 2 | version: 0.1.0 3 | description: Adds a JSON API to Grav 4 | icon: code 5 | author: 6 | name: Team Grav 7 | email: devs@getgrav.org 8 | url: http://getgrav.org 9 | homepage: https://github.com/getgrav/grav-plugin-api 10 | keywords: api, plugin 11 | bugs: https://github.com/getgrav/grav-plugin-api/issues 12 | readme: https://github.com/getgrav/grav-plugin-api/blob/develop/README.md 13 | license: MIT 14 | 15 | form: 16 | validation: loose 17 | fields: 18 | enabled: 19 | type: toggle 20 | label: Plugin status 21 | highlight: 1 22 | default: 0 23 | options: 24 | 1: Enabled 25 | 0: Disabled 26 | validate: 27 | type: bool 28 | -------------------------------------------------------------------------------- /resources/config.php: -------------------------------------------------------------------------------- 1 | finder = new ConfigFinder; 28 | $locator = $this->grav['locator']; 29 | $this->configLookup = $locator->findResources('config://'); 30 | $this->blueprintLookup = $locator->findResources('blueprints://config'); 31 | $this->pluginLookup = $locator->findResources('plugins://'); 32 | 33 | $config = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup); 34 | return $config; 35 | } 36 | 37 | /** 38 | * Get a single config file 39 | * 40 | * Implements: 41 | * 42 | * - GET /api/config/:config 43 | * 44 | * Examples: 45 | * 46 | * - GET /api/config/system 47 | * - GET /api/config/system.languages 48 | * - GET /api/config/system/languages 49 | * - GET /api/config/plugins 50 | * - GET /api/config/plugins.admin 51 | * - GET /api/config/plugins/admin 52 | * 53 | * @todo: 54 | * 55 | * @return array the single config file content 56 | */ 57 | public function getItem() 58 | { 59 | $config = $this->grav['config']; 60 | return (array)$config->get(str_replace('/', '.', $this->getIdentifier())); 61 | } 62 | } -------------------------------------------------------------------------------- /resources/pages.php: -------------------------------------------------------------------------------- 1 | grav['pages']->all(); 27 | 28 | $return = []; 29 | 30 | foreach($pagesCollection as $page) { 31 | $return[$page->route()] = []; 32 | $return[$page->route()]['title'] = $page->title(); 33 | $return[$page->route()]['url'] = $page->url(); 34 | $return[$page->route()]['visible'] = $page->visible(); 35 | $return[$page->route()]['isDir'] = $page->isDir(); 36 | $return[$page->route()]['published'] = $page->published(); 37 | } 38 | 39 | return $return; 40 | } 41 | 42 | /** 43 | * Get a single page 44 | * 45 | * Implements: 46 | * 47 | * - GET /api/pages/:page 48 | * 49 | * @return array the single page 50 | */ 51 | public function getItem() 52 | { 53 | $pages = $this->grav['pages']; 54 | $page = $pages->dispatch('/' . $this->getIdentifier(), false); 55 | return $this->buildPageStructure($page); 56 | } 57 | 58 | /** 59 | * Create a new page 60 | * 61 | * Implements: 62 | * 63 | * - POST /api/pages/:page 64 | * 65 | * @todo: 66 | * 67 | * @return array the single page 68 | */ 69 | public function postItem() 70 | { 71 | $data = $this->getPost(); 72 | $pages = $this->grav['pages']; 73 | 74 | $page = $pages->dispatch('/' . $this->getIdentifier(), false); 75 | if ($page !== null) { 76 | // Page already exists 77 | $this->setErrorCode(403); 78 | $message = $this->buildReturnMessage('Page already exists. Cannot create a page with the same route'); 79 | return $message; 80 | } 81 | 82 | $page = $this->page($this->getIdentifier()); 83 | $page = $this->preparePage($page, $data); 84 | 85 | $page->save(); 86 | 87 | $page = $pages->dispatch('/' . $this->getIdentifier(), false); 88 | 89 | return $this->buildPageStructure($page); 90 | } 91 | 92 | /** 93 | * Updates an existing page 94 | * 95 | * Implements: 96 | * 97 | * - PUT /api/pages/:page 98 | * 99 | * @todo: 100 | * 101 | * @return array the single page 102 | */ 103 | public function putItem() 104 | { 105 | $data = $this->getPost(); 106 | $pages = $this->grav['pages']; 107 | 108 | $page = $pages->dispatch('/' . $this->getIdentifier(), false); 109 | if ($page == null) { 110 | // Page does not exist 111 | $this->setErrorCode(404); 112 | $message = $this->buildReturnMessage('Page does not exist.'); 113 | return $message; 114 | } 115 | 116 | $page = $this->page($this->getIdentifier()); 117 | $page = $this->preparePage($page, $data); 118 | $page->save(); 119 | 120 | $page = $pages->dispatch('/' . $this->getIdentifier(), false); 121 | 122 | return $this->buildPageStructure($page); 123 | } 124 | 125 | /** 126 | * Deletes an existing page 127 | * 128 | * If the 'lang' param is present in the request header, it only deletes a single 129 | * language if there are other languages set for that page. 130 | * 131 | * Implements: 132 | * 133 | * - DELETE /api/pages/:page 134 | * 135 | * @todo: 136 | * 137 | * @return bool 138 | */ 139 | public function deleteItem() 140 | { 141 | $data = $this->getPost(); 142 | 143 | $pages = $this->grav['pages']; 144 | $page = $pages->dispatch('/' . $this->getIdentifier(), false); 145 | if ($page == null) { 146 | // Page does not exist 147 | $this->setErrorCode(404); 148 | $message = $this->buildReturnMessage('Page does not exist.'); 149 | return $message; 150 | } 151 | 152 | try { 153 | if (isset($data->lang) && count($page->translatedLanguages()) > 1) { 154 | $language = trim(basename($page->extension(), 'md'), '.') ?: null; 155 | $filename = str_replace($language, $data->lang, $page->name()); 156 | $path = $page->path() . DS . $filename; 157 | $page->filePath($path); 158 | 159 | $page->file()->delete(); 160 | } else { 161 | Folder::delete($page->path()); 162 | } 163 | } catch (\Exception $e) { 164 | throw new \RuntimeException('Deleting page failed on error: ' . $e->getMessage()); 165 | } 166 | 167 | return ''; 168 | } 169 | 170 | /** 171 | * Build a page structure 172 | * 173 | * @todo: add commented fields 174 | * 175 | * @return array the single page 176 | */ 177 | private function buildPageStructure($page) { 178 | return [ 179 | 'active' => $page->active(), 180 | 'activeChild' => $page->activeChild(), 181 | // 'adjacentSibling' => $page->adjacentSibling(), 182 | 'blueprintName' => $page->blueprintName(), 183 | //'blueprints' => $page->blueprints(), 184 | 'children' => $page->children(), 185 | 'childType' => $page->childType(), 186 | 'content' => $page->content(), 187 | 'date' => $page->date(), 188 | 'eTag' => $page->eTag(), 189 | 'expires' => $page->expires(), 190 | 'exists' => $page->exists(), 191 | 'extension' => $page->extension(), 192 | // 'extra' => $page->extra(), 193 | 'file' => $page->file(), 194 | 'filePath' => $page->filePath(), 195 | 'filePathClean' => $page->filePathClean(), 196 | 'folder' => $page->folder(), 197 | 'frontmatter' => $page->frontmatter(), 198 | 'getRawContent' => $page->getRawContent(), 199 | 'header' => $page->header(), 200 | 'home' => $page->home(), 201 | 'id' => $page->id(), 202 | 'isDir' => $page->isDir(), 203 | // 'isFirst' => $page->isFirst(), 204 | // 'isLast' => $page->isLast(), 205 | 'isPage' => $page->isPage(), 206 | 'language' => $page->language(), 207 | 'lastModified' => $page->lastModified(), 208 | 'link' => $page->link(), 209 | 'maxCount' => $page->maxCount(), 210 | 'menu' => $page->menu(), 211 | 'metadata' => $page->metadata(), 212 | 'modified' => $page->modified(), 213 | 'modularTwig' => $page->modularTwig(), 214 | 'modular' => $page->modular(), 215 | 'name' => $page->name(), 216 | // 'nextSibling' => $page->nextSibling(), 217 | 'order' => $page->order(), 218 | 'orderDir' => $page->orderDir(), 219 | 'orderBy' => $page->orderBy(), 220 | 'orderManual' => $page->orderManual(), 221 | 'parent' => $page->parent(), 222 | 'path' => $page->path(), 223 | 'permalink' => $page->permalink(), 224 | // 'prevSibling' => $page->prevSibling(), 225 | 'publishDate' => $page->publishDate(), 226 | 'published' => $page->published(), 227 | 'raw' => $page->raw(), 228 | 'rawMarkdown' => $page->rawMarkdown(), 229 | 'rawRoute' => $page->rawRoute(), 230 | 'root' => $page->root(), 231 | 'routable' => $page->routable(), 232 | 'route' => $page->route(), 233 | 'routeCanonical' => $page->routeCanonical(), 234 | 'slug' => $page->slug(), 235 | 'summary' => $page->summary(), 236 | 'taxonomy' => $page->taxonomy(), 237 | 'template' => $page->template(), 238 | 'title' => $page->title(), 239 | 'translatedLanguages' => $page->translatedLanguages(), 240 | 'unpublishDate' => $page->unpublishDate(), 241 | 'untranslatedLanguages' => $page->untranslatedLanguages(), 242 | 'url' => $page->url(), 243 | 'visible' => $page->visible(), 244 | ]; 245 | } 246 | 247 | /** 248 | * Returns edited page. 249 | * 250 | * @param bool $route 251 | * 252 | * @return Page 253 | */ 254 | private function page($route = false) 255 | { 256 | $path = $route; 257 | if (!isset($this->pages[$path])) { 258 | $this->pages[$path] = $this->getPage($path); 259 | } 260 | return $this->pages[$path]; 261 | } 262 | 263 | /** 264 | * Returns the page creating it if it does not exist. 265 | * 266 | * @todo: Copied from Admin Plugin. Refactor to use in both 267 | * 268 | * @param $path 269 | * 270 | * @return Page 271 | */ 272 | private function getPage($path) 273 | { 274 | /** @var Pages $pages */ 275 | $pages = $this->grav['pages']; 276 | 277 | if ($path && $path[0] != '/') { 278 | $path = "/{$path}"; 279 | } 280 | 281 | $page = $path ? $pages->dispatch($path, true) : $pages->root(); 282 | 283 | if (!$page) { 284 | $slug = basename($path); 285 | 286 | if ($slug == '') { 287 | return null; 288 | } 289 | 290 | $ppath = str_replace('\\', '/' , dirname($path)); 291 | 292 | // Find or create parent(s). 293 | $parent = $this->getPage($ppath != '/' ? $ppath : ''); 294 | 295 | // Create page. 296 | $page = new Page; 297 | $page->parent($parent); 298 | $page->filePath($parent->path() . '/' . $slug . '/' . $page->name()); 299 | 300 | // Add routing information. 301 | $pages->addPage($page, $path); 302 | 303 | // Set if Modular 304 | $page->modularTwig($slug[0] == '_'); 305 | 306 | // Determine page type. 307 | if (isset($this->session->{$page->route()})) { 308 | // Found the type and header from the session. 309 | $data = $this->session->{$page->route()}; 310 | 311 | $header = ['title' => $data['title']]; 312 | 313 | if (isset($data['visible'])) { 314 | if ($data['visible'] == '' || $data['visible']) { 315 | // if auto (ie '') 316 | $children = $page->parent()->children(); 317 | foreach ($children as $child) { 318 | if ($child->order()) { 319 | // set page order 320 | $page->order(1000); 321 | break; 322 | } 323 | } 324 | } 325 | 326 | } 327 | 328 | if ($data['name'] == 'modular') { 329 | $header['body_classes'] = 'modular'; 330 | } 331 | 332 | $name = $page->modular() ? str_replace('modular/', '', $data['name']) : $data['name']; 333 | $page->name($name . '.md'); 334 | $page->header($header); 335 | $page->frontmatter(Yaml::dump((array)$page->header(), 10, 2, false)); 336 | } else { 337 | // Find out the type by looking at the parent. 338 | $type = $parent->childType() ? $parent->childType() : $parent->blueprints()->get('child_type', 339 | 'default'); 340 | $page->name($type . CONTENT_EXT); 341 | $page->header(); 342 | } 343 | $page->modularTwig($slug[0] == '_'); 344 | } 345 | 346 | return $page; 347 | } 348 | 349 | /** 350 | * Prepare a page to be stored: update its folder, name, template, header and content 351 | * 352 | * @param \Grav\Common\Page\Page $page 353 | * @param object $post 354 | */ 355 | private function preparePage(\Grav\Common\Page\Page $page, $post = null) 356 | { 357 | $post = (array)$post; 358 | 359 | if (isset($post['order'])) { 360 | $order = max(0, (int) isset($post['order']) ? $post['order'] : $page->value('order')); 361 | $ordering = $order ? sprintf('%02d.', $order) : ''; 362 | $slug = empty($post['folder']) ? $page->value('folder') : (string) $post['folder']; 363 | $page->folder($ordering . $slug); 364 | } 365 | 366 | if (isset($post['name']) && !empty($post['name'])) { 367 | $type = (string) strtolower($post['name']); 368 | $name = preg_replace('|.*/|', '', $type); 369 | $page->name($name); 370 | $page->template($type); 371 | } 372 | 373 | if (isset($post['header'])) { 374 | $header = $post['header']; 375 | $page->header((object) $header); 376 | $page->frontmatter(Yaml::dump((array) $page->header())); 377 | } 378 | 379 | $language = trim(basename($page->extension(), 'md'), '.') ?: null; 380 | $filename = str_replace($language, $post['lang'], $page->name()); 381 | $path = $page->path() . DS . $filename; 382 | $page->filePath($path); 383 | 384 | if (isset($post['content'])) { 385 | $page->rawMarkdown((string) $post['content']); 386 | $page->content((string) $post['content']); 387 | $page->file()->markdown($page->rawMarkdown()); 388 | } 389 | 390 | 391 | return $page; 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /resources/resource.php: -------------------------------------------------------------------------------- 1 | grav = $grav; 14 | } 15 | 16 | /** 17 | * Execute the request 18 | * 19 | * @return string the output to render 20 | */ 21 | public function execute() 22 | { 23 | $httpMethod = $this->getMethod(); 24 | 25 | if ($httpMethod == 'get') { 26 | if ($this->getIdentifier()) { 27 | $return = $this->getItem(); 28 | } else { 29 | $return = $this->getList(); 30 | } 31 | } else { 32 | $method = $httpMethod . 'Item'; 33 | $return = $this->$method(); 34 | } 35 | 36 | if ($return !== '') { 37 | return json_encode($return); 38 | } else { 39 | return ''; 40 | } 41 | 42 | } 43 | 44 | /** 45 | * Get the request method 46 | * 47 | * @return string the method name, lowercase 48 | */ 49 | protected function getMethod() 50 | { 51 | return strtolower($_SERVER['REQUEST_METHOD']); 52 | } 53 | 54 | /** 55 | * Get the resource name 56 | * 57 | * @return string the resource name 58 | */ 59 | protected function getResource() 60 | { 61 | $paths = $this->grav['uri']->paths(); 62 | $paths = array_splice($paths, 1); 63 | $resource = $paths[0]; 64 | return $resource; 65 | } 66 | 67 | /** 68 | * Get the identifier name 69 | * 70 | * @return string the resource identifier name 71 | */ 72 | protected function getIdentifier() 73 | { 74 | $paths = $this->grav['uri']->paths(); 75 | $paths = array_splice($paths, 2); 76 | 77 | $identifier = join('/', $paths); 78 | return $identifier; 79 | } 80 | 81 | /** 82 | * Set the response headers based on the content retrieved 83 | * 84 | * Use the following response headers: 85 | * - ETag: An arbitrary string for the version of a representation. Make sure to include the media type in the hash value, because that makes a different representation. (ex: ETag: "686897696a7c876b7e") 86 | * - Date: Date and time the response was returned (in RFC1123 format). (ex: Date: Sun, 06 Nov 1994 08:49:37 GMT) 87 | * - Cache-Control: The maximum number of seconds (max age) a response can be cached. However, if caching is not supported for the response, then no-cache is the value. (ex: Cache-Control: 360 or Cache-Control: no-cache) 88 | * - Expires: If max age is given, contains the timestamp (in RFC1123 format) for when the response expires, which is the value of Date (e.g. now) plus max age. If caching is not supported for the response, this header is not present. (ex: Expires: Sun, 06 Nov 1994 08:49:37 GMT) 89 | * - Last-Modified: The timestamp that the resource itself was modified last (in RFC1123 format). (ex: Last-Modified: Sun, 06 Nov 1994 08:49:37 GMT) 90 | * 91 | * @todo implement 92 | */ 93 | public function setHeaders() 94 | { 95 | header('Content-type: application/json'); 96 | 97 | // Calculate Expires Headers if set to > 0 98 | $expires = $this->grav['config']->get('system.pages.expires'); 99 | if ($expires > 0) { 100 | $expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT'; 101 | header('Cache-Control: max-age=' . $expires); 102 | header('Expires: '. $expires_date); 103 | } 104 | 105 | // TODO: Set Last-Modified 106 | // TODO: Set the ETag 107 | // TODO: Set the HTTP response code 108 | // TODO: Set the Date 109 | } 110 | 111 | /** 112 | * Set the correct error code 113 | * 114 | * @todo implement all error codes 115 | */ 116 | protected function setErrorCode($code) { 117 | switch((int)$code) { 118 | case 401: 119 | header('HTTP/1.1 401 Unauthorized'); 120 | break; 121 | case 403: 122 | header('HTTP/1.1 403 Forbidden'); 123 | break; 124 | case 404: 125 | header('HTTP/1.1 404 Not Found'); 126 | break; 127 | case 405: 128 | header('HTTP/1.1 405 Not Allowed'); 129 | break; 130 | case 501: 131 | header('HTTP/1.1 501 Not Implemented'); 132 | break; 133 | default: 134 | header('HTTP/1.1 ' . $code); 135 | 136 | } 137 | } 138 | 139 | /** 140 | * Get list action 141 | * 142 | * @return 143 | */ 144 | public function getList() 145 | { 146 | $this->setErrorCode(501); //Not Implemented 147 | return; 148 | } 149 | 150 | /** 151 | * Get item action 152 | * 153 | * @return 154 | */ 155 | public function getItem() 156 | { 157 | $this->setErrorCode(501); //Not Implemented 158 | return; 159 | } 160 | 161 | /** 162 | * Post action 163 | * 164 | * @return 165 | */ 166 | public function postItem() 167 | { 168 | $this->setErrorCode(501); //Not Implemented 169 | return; 170 | } 171 | 172 | /** 173 | * Put action 174 | * 175 | * @return 176 | */ 177 | public function putItem() 178 | { 179 | $this->setErrorCode(501); //Not Implemented 180 | return; 181 | } 182 | 183 | /** 184 | * Delete action 185 | * 186 | * @return 187 | */ 188 | public function deleteItem() 189 | { 190 | $this->setErrorCode(501); //Not Implemented 191 | return; 192 | } 193 | 194 | /** 195 | * Prepare and return POST data. 196 | * 197 | * @param array $post 198 | * @return array 199 | */ 200 | protected function getPost() 201 | { 202 | return json_decode(file_get_contents('php://input')); 203 | } 204 | 205 | 206 | /** 207 | * Builds the return message. 208 | * 209 | * Example usage from a resource action: 210 | * 211 | * ``` 212 | * $this->setErrorCode(403); 213 | * $message = $this->buildReturnMessage('Page already exists. Cannot create a page with the same route'); 214 | * return $message; 215 | * ``` 216 | * 217 | * @param array $post 218 | * @return array 219 | */ 220 | protected function buildReturnMessage($message) { 221 | return [ 222 | 'message' => $message 223 | ]; 224 | } 225 | 226 | } 227 | -------------------------------------------------------------------------------- /resources/users.php: -------------------------------------------------------------------------------- 1 | grav['locator']->findResource("account://") . '/*.yaml'); 31 | 32 | if ($files) foreach ($files as $file) { 33 | $users[] = array_merge(array('username' => basename($file, '.yaml')), Yaml::parse($file)); 34 | } 35 | 36 | return $users; 37 | } 38 | 39 | /** 40 | * Get a single user 41 | * 42 | * Implements: 43 | * 44 | * - GET /api/users/:user 45 | * 46 | * @todo: 47 | * 48 | * @return array the single user 49 | */ 50 | public function getItem() 51 | { 52 | $file = $this->grav['locator']->findResource("account://") . '/' . $this->getIdentifier() . '.yaml'; 53 | if (!file_exists($file)) { 54 | $this->setErrorCode(404); 55 | return; 56 | } 57 | return array_merge(array('username' => basename($file, '.yaml')), Yaml::parse($file)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /resources/version.php: -------------------------------------------------------------------------------- 1 | getVersion(); 23 | return strtok($version, '.'); 24 | } 25 | 26 | /** 27 | * Implements: 28 | * 29 | * - GET /api/version/major 30 | * - GET /api/version/minor 31 | * - GET /api/version/full 32 | */ 33 | public function getItem() 34 | { 35 | switch($this->getIdentifier()) { 36 | case 'major': 37 | // Get API Major Version 38 | $version = $this->getVersion(); 39 | return strtok($version, '.'); 40 | case 'minor': 41 | // Get API Minor Version 42 | $version = $this->getVersion(); 43 | return strtok($version, '.') . '.' . strtok('.'); 44 | case 'full': 45 | //Get API Full Version 46 | return $this->getVersion(); 47 | } 48 | 49 | return; 50 | } 51 | 52 | /** 53 | * Fetch the plugin version from the blueprints.yaml file 54 | */ 55 | private function getVersion() 56 | { 57 | $locator = $this->grav['locator']; 58 | $path = $locator->findResource('user://plugins/api', true); 59 | $fullFileName = $path . DS . 'blueprints.yaml'; 60 | 61 | $file = File::instance($fullFileName); 62 | $data = Yaml::parse($file->content()); 63 | 64 | return $data['version']; 65 | } 66 | } 67 | --------------------------------------------------------------------------------