Merge branch 'release-1.4.0' into stable

This commit is contained in:
Marcel Kapfer (mmk2410) 2016-05-07 17:43:03 +02:00
commit e2d8082c1f
451 changed files with 45228 additions and 3093 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
nbproject/
.idea/
completer.hist
feed/
node_modules/

View File

@ -1,24 +1,54 @@
[S] = stable release
[B] = beta release
[D] = development release
# Changelog
[S] release are always compared to the previous [S] release.
- [S] = stable release
- [B] = beta release
- [D] = development release
Version 1.3.0 (2016-02-16) [S]
=============
- [S] release are always compared to the previous [S] release.
## Version 1.4.0 (2016-05-07) [S]
- Fix: Feeds contain no text
- Switch to YAML as config language
- Scripts for
- Switching from config.php to config.yaml
- Initializing RCC
- Initializing Rangitaki
- Rangitaki API
- Switch from JavaScript to CoffeeScript
- Switch from CSS to SASS
- Building and minimizing with Gulp
- Open links in articles in a new tab
- Better code style: PSR-2
## Version 1.4.0-beta (2016-04-27) [B]
- Fix: Feeds contain no text
- Switch to YAML as config language
- Scripts for
- Switching from config.php to config.yaml
- Initializing RCC
- Initializing Rangitaki
- Rangitaki API
- Switch from JavaScript to CoffeeScript
- Switch from CSS to SASS
- Building and minimizing with Gulp
- Open links in articles in a new tab
- Better code style: PSR-2
## Version 1.3.0 (2016-02-16) [S]
- Respecting do-not-track
- Atom feed
- Title fix
- Switch to composer
Version 1.2.1 (2016-01-11) [S]
=============
## Version 1.2.1 (2016-01-11) [S]
- Support for PHP 7
Version 1.2.0 (2015-12-24) [S]
=============
## Version 1.2.0 (2015-12-24) [S]
- Pagination: Split your blog posts over several page
- JavaScript Extension Support
@ -27,20 +57,17 @@ Version 1.2.0 (2015-12-24) [S]
- RCC: Upload Media
- RCC: Edit Posts
Version 1.1.90 (2015-12-21) [B]
==============
## Version 1.1.90 (2015-12-21) [B]
- BUGFIX: ArticleGenerator error when no tags set
- Pagination: Localized strings
Version 1.1.2 (2015-12-20) [D]
=============
## Version 1.1.2 (2015-12-20) [D]
- Pagination
- Code style imporvements
Version 1.1.1 (2015-12-07) [D]
=============
## Version 1.1.1 (2015-12-07) [D]
- BUGFIX: RCC: new post: post title was the blog title
- BUGFIX: RCC: new post filename just the date without the time.
@ -50,8 +77,7 @@ Version 1.1.1 (2015-12-07) [D]
- RCC: Delete posts
- RCC: Edit posts
Version 1.1.0 (2015-11-22) [D]
=============
## Version 1.1.0 (2015-11-22) [D]
- RCC: Write blog posts
- RCC: Media Upload
@ -61,8 +87,7 @@ Version 1.1.0 (2015-11-22) [D]
- Metatags / Title based on subblog and / or article
- Update script
Version 1.0.0 (2015-08-22) [S]
=============
## Version 1.0.0 (2015-08-22) [S]
- Post writing in Markdown with a few keywords for the title, tags, date and the author (all optional)
- Multiple blogs
@ -82,8 +107,7 @@ Version 1.0.0 (2015-08-22) [S]
- Rangitaki Control Center (aka RCC; optional, Read the RCC Documentation)
- Online post upload
Version 0.9.0 (2015-07-25) [B]
=============
## Version 0.9.0 (2015-07-25) [B]
- BUGFIX: 'Blogs of {BLOG NAME}' always shown (even if there are no other blogs)
- pictures in articles not centred
@ -91,53 +115,46 @@ Version 0.9.0 (2015-07-25) [B]
- Localization strings are now grouped in one array
- Better code (in some parts)
Version 0.8.0 (2015-07-14) [B]
=============
## Version 0.8.0 (2015-07-14) [B]
- Bugfixes and other improvements
Version 0.7.0 (2015-07-05) [D]
=============
## Version 0.7.0 (2015-07-05) [D]
Version 0.6.0 (2015-07-03) [D]
=============
## Version 0.6.0 (2015-07-03) [D]
- Localization support. More information will follow soon
- Theme support. More information will follow (hopefully) soon
- Various improvements (Check the commits for more)
Version 0.5.0 (2015-06-16) [D]
=============
## Version 0.5.0 (2015-06-16) [D]
- Improvements to the Rangitaki Control Center (rcc)
- Material Design (Blog and rcc)
Version 0.4 (2015-06-14) [D]
===========
## Version 0.4 (2015-06-14) [D]
- Multiple Blogs
- Online post upload (optional)
- Tags
- Author
Version 0.3 (2015-06-11) [D]
===========
## Version 0.3 (2015-06-11) [D]
- Portation of all main features of Version 0.2.2
- Code highlighting
The first release with the name Rangitaki.
The following releases are of pBlog.
*The first release with the name Rangitaki.
The following releases are of pBlog.*
Version 0.2.2 (2015-05-13) [S]
=============
## Version 0.2.2 (2015-05-13) [S]
- Links are now underlined, when you hover over them
- Simplified it to add the disqus comments
- Added and configuration option for setting a favicon
- Added the option to use Google Analytics
Version 0.2.1 / pBlog 2.1 (2015-03-29) [S]
=============
## Version 0.2.1 / pBlog 2.1 (2015-03-29) [S]
- Fix problems when creating article links

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 Marcel Kapfer
Copyright (c) 2015 - 2016 Marcel Kapfer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

67
bin/config.php Normal file
View File

@ -0,0 +1,67 @@
<?php
// Marcel Kapfer (mmk2410)
// Script for moving from config.php to config.yaml
// License: MIT
require 'res/php/Config.php';
require 'config.php';
use mmk2410\rbe\config\Config as Config;
if ($bloghome == "yes") {
$bloghome = "on";
}
if ($blogintro == "yes") {
$blogintro = "on";
}
if ($sharefab == "yes") {
$sharefab = "on";
}
if ($rcc == "yes") {
$rcc = "on";
}
if ($nav_drawer == "yes") {
$nav_drawer = "on";
}
$yaml = array(
"blog" => array(
"title" => $blogtitle,
"author" => $blogauthor,
"description" => $blogdescription,
"home" => $bloghome,
"homeurl" => $bloghomeurl,
"homename" => $bloghomename,
"mainname" => $blogmainname,
"intro" => $blogintro,
"disqus" => $blogdisqus,
"analytics" => $bloganalytics,
"footer" => $blogfooter,
"url" => $blogurl
),
"design" => array(
"fab" => $sharefab,
"drawer" => $nav_drawer,
"theme" => $theme,
"pagination" => $pagination,
"favicon" => $favicon,
),
"rcc" => array(
"rcc" => "off",
"api" => "off",
),
"language" => $language,
);
$config = new Config('config.yaml', 'vendor/autoload.php');
if ($config->writeConfig($yaml)) {
echo "YAML config saved.\nYou can delete the config.php file\n";
} else {
echo "Failed to save YAML config.";
}

107
bin/init.php Normal file
View File

@ -0,0 +1,107 @@
<?php
// Marcel Kapfer (mmk2410)
// PHP script for initializing Rangitaki
// MIT License
error_reporting(0);
require 'res/php/Config.php';
use mmk2410\rbe\config\Config as Config;
$config = new Config('config.yaml', 'vendor/autoload.php');
if (!file_exists('config.yaml')) {
$yaml = array();
} else {
$yaml = $config->getConfig();
}
// blog part
$yaml["blog"]["title"] = get("Title of your blog", $yaml["blog"]["title"], "Example Blog");
$yaml["blog"]["author"] = get("Your name:", $yaml["blog"]["author"], "John");
$yaml["blog"]["description"] = get("A description of your blog:", $yaml["blog"]["description"], "A short description of your blog");
$yaml["blog"]["home"] = getBool("Do you have a top site? (on/off)", $yaml["blog"]["home"], "on");
if ($yaml["blog"]["home"] == "on") {
$yaml["blog"]["homeurl"] = get("Path / Url to home page", $yaml["blog"]["homeurl"], "../");
$yaml["blog"]["homename"] = get("Name of your home page", $yaml["blog"]["homename"], "Home");
}
$yaml["blog"]["mainname"] = get("Name of the main blog (if empty, the blog title will be used)", "");
$yaml["blog"]["intro"] = getBool("Do you want a blog intro text? (on/off)", $yaml["blog"]["intro"], "on");
$yaml["blog"]["disqus"] = get("Your Disqus shortname (Leave empty to disable)", $yaml["blog"]["disqus"], "");
$yaml["blog"]["analytics"] = get("Google Analytics ID (Leave empty to disable)", $yaml["blog"]["analytics"], "");
$yaml["blog"]["footer"] = get("The footer of your blog", $yaml["blog"]["footer"], "Example Blog 2016 CC-BY-SA 4.0");
// design part
$yaml["design"]["fab"] = getBool("Would you like to use the share buttons (on/off)", $yaml["design"]["blog"], "on");
$yaml["design"]["drawer"] = getBool("Would you like the use the navigation drawer? (on/off)", $yaml["design"]["drawer"], "on");
$themes = getDir('./themes');
$yaml["design"]["theme"] = get("Which theme would you like to use? (" . $themes . ")", $yaml["design"]["theme"], "material-light");
$yaml["design"]["pagination"] =
get("Which posts should be displayed on one page (0 to disable)", $yaml["design"]["pagination"], "0");
$yaml["design"]["favicon"] = get("URL to your favicon", $yaml["design"]["favicon"], "https://example.com/fav.ico");
// rcc
$yaml["rcc"]["rcc"] = "off";
$yaml["rcc"]["api"] = "off";
// languages
$langs = getDir('./lang');
$yaml["language"] = get("Choose a language (" . $langs . ")", $yaml["language"], "en");
$config->writeConfig($yaml);
function get($question, $value, $default)
{
if (isset($value) && $value != "") {
$input = readline($question . " (" . $value . "): ");
if ($input == "") {
return $value;
} else {
return $input;
}
} else {
$input = readline($question . " (" . $default . ")" . ": ");
if ($input == "") {
return $default;
} else {
return $input;
}
}
}
function getBool($question, $value, $default)
{
if (isset($value) && $value != "") {
$input = "someval";
while (!in_array($input, array("on", "off", ""))) {
$input = readline($question . " (" . $value . "): ");
}
if ($input == "") {
return $value;
} else {
return $input;
}
} else {
$input = "";
while (!in_array($input, array("on", "off"))) {
$input = readline($question . " (" . $default . ")" . ": ");
}
if ($input == "") {
return $default;
} else {
return $input;
}
}
}
function getDir($path)
{
$dir = scandir($path, SCANDIR_SORT_DESCENDING);
unset($dir[sizeof($dir) - 1]);
unset($dir[sizeof($dir) - 1]);
return implode(", ", $dir);
}

70
bin/init_rcc.php Normal file
View File

@ -0,0 +1,70 @@
<?php
// Marcel Kapfer (mmk2410)
// PHP script for initializing the RCC
// License: MIT
require 'res/php/Config.php';
use mmk2410\rbe\config\Config as Config;
echo 'RCC Initializion Script' . "\n";
$username = readline("Username: ");
if ($username == "") {
echo "No username given. Aborting...\n";
exit();
}
echo 'Password: ';
$password = readline("Password: ");
if ($password == "") {
echo "No password given. Aborting...\n";
exit();
}
$username = '$username = "' . $username . '";';
$password = '$password = "' . $password . '";';
$file = '<?php' . "\n" . $username . "\n" . $password . "\n";
if (file_put_contents('./rcc/password.php', $file)) {
chmod('./rcc/password.php', 0640);
echo "\nPassword successfully saved.\n";
}
$config = new Config('config.yaml', 'vendor/autoload.php');
$yaml = $config->getConfig();
$rccOn = "";
while (!(in_array($rccOn, array("y", "Y", "n", "N")))) {
$rccOn = readline("Enable RCC: (y/n) ");
}
if (in_array($rccOn, array("y", "Y"))) {
$yaml["rcc"]["rcc"] = "on";
} else {
$yaml["rcc"]["rcc"] = "off";
}
$apiOn = "";
while (!(in_array($apiOn, array("Y", "y", "n", "N")))) {
$apiOn = readline("Enable RCC API: (y/n) ");
}
if (in_array($apiOn, array("y", "Y"))) {
$yaml["rcc"]["api"] = "on";
} else {
$yaml["rcc"]["api"] = "off";
}
$config = new Config('config.yaml', 'vendor/autoload.php');
if ($config->writeConfig($yaml)) {
echo "Changes saved.\n";
} else {
echo "Failed to save changes.\n";
}

View File

@ -4,12 +4,15 @@
"type": "project",
"require": {
"erusev/parsedown": "^1.6",
"fguillot/picofeed": "^0.1.18"
"fguillot/picofeed": "^0.1.18",
"slim/slim": "^3.0",
"symfony/yaml": "^3.0",
"codeguy/upload": "^1.3"
},
"license": "MIT License",
"authors": [
{
"name": "mmk2410",
"name": "Marcel Kapfer (mmk2410)",
"email": "marcelmichaelkapfer@yahoo.co.nz"
}
],

391
composer.lock generated
View File

@ -4,9 +4,132 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "4f3494edee00a6dd92be308e4f860622",
"content-hash": "d93c93221ea797a445a0313f18feccda",
"hash": "bf0772b9501ce6231c06bfbcdb671d1d",
"content-hash": "49b3f5550e60b62ffeb5306a75a87d97",
"packages": [
{
"name": "bshaffer/oauth2-server-php",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/bshaffer/oauth2-server-php.git",
"reference": "058c98f73209f9c49495e1799d32c035196fe8b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bshaffer/oauth2-server-php/zipball/058c98f73209f9c49495e1799d32c035196fe8b8",
"reference": "058c98f73209f9c49495e1799d32c035196fe8b8",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"suggest": {
"aws/aws-sdk-php": "~2.8 is required to use the DynamoDB storage engine",
"firebase/php-jwt": "~2.2 is required to use JWT features",
"predis/predis": "Required to use the Redis storage engine",
"thobbs/phpcassa": "Required to use the Cassandra storage engine"
},
"type": "library",
"autoload": {
"psr-0": {
"OAuth2": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brent Shaffer",
"email": "bshafs@gmail.com",
"homepage": "http://brentertainment.com"
}
],
"description": "OAuth2 Server for PHP",
"homepage": "http://github.com/bshaffer/oauth2-server-php",
"keywords": [
"auth",
"oauth",
"oauth2"
],
"time": "2015-09-18 18:05:10"
},
{
"name": "codeguy/upload",
"version": "1.3.2",
"source": {
"type": "git",
"url": "https://github.com/codeguy/Upload.git",
"reference": "6a9e5e1fb58d65346d0e557db2d46fb25efd3e37"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/codeguy/Upload/zipball/6a9e5e1fb58d65346d0e557db2d46fb25efd3e37",
"reference": "6a9e5e1fb58d65346d0e557db2d46fb25efd3e37",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"type": "library",
"autoload": {
"psr-0": {
"Upload": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Josh Lockhart",
"email": "info@joshlockhart.com",
"homepage": "http://www.joshlockhart.com/"
}
],
"description": "Handle file uploads with extensible validation and storage strategies",
"homepage": "http://github.com/codeguy/Upload",
"keywords": [
"file",
"upload",
"validation"
],
"time": "2013-07-07 17:01:41"
},
{
"name": "container-interop/container-interop",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/container-interop/container-interop.git",
"reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e",
"reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e",
"shasum": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Interop\\Container\\": "src/Interop/Container/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
"time": "2014-12-30 15:22:37"
},
{
"name": "erusev/parsedown",
"version": "1.6.0",
@ -48,16 +171,16 @@
},
{
"name": "fguillot/picofeed",
"version": "v0.1.18",
"version": "v0.1.23",
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoFeed.git",
"reference": "8f776343b0dada397c2a950a7c3f7be57442fa35"
"reference": "a7c3d420c239fe9ffc39b0d06b6e57db39ce3797"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fguillot/picoFeed/zipball/8f776343b0dada397c2a950a7c3f7be57442fa35",
"reference": "8f776343b0dada397c2a950a7c3f7be57442fa35",
"url": "https://api.github.com/repos/fguillot/picoFeed/zipball/a7c3d420c239fe9ffc39b0d06b6e57db39ce3797",
"reference": "a7c3d420c239fe9ffc39b0d06b6e57db39ce3797",
"shasum": ""
},
"require": {
@ -92,7 +215,261 @@
],
"description": "Modern library to handle RSS/Atom feeds",
"homepage": "https://github.com/fguillot/picoFeed",
"time": "2016-02-09 02:49:54"
"time": "2016-04-17 22:31:55"
},
{
"name": "nikic/fast-route",
"version": "v0.6.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/FastRoute.git",
"reference": "31fa86924556b80735f98b294a7ffdfb26789f22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/FastRoute/zipball/31fa86924556b80735f98b294a7ffdfb26789f22",
"reference": "31fa86924556b80735f98b294a7ffdfb26789f22",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"type": "library",
"autoload": {
"psr-4": {
"FastRoute\\": "src/"
},
"files": [
"src/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov",
"email": "nikic@php.net"
}
],
"description": "Fast request router for PHP",
"keywords": [
"router",
"routing"
],
"time": "2015-06-18 19:15:47"
},
{
"name": "pimple/pimple",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/silexphp/Pimple.git",
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a",
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"psr-0": {
"Pimple": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Pimple, a simple Dependency Injection Container",
"homepage": "http://pimple.sensiolabs.org",
"keywords": [
"container",
"dependency injection"
],
"time": "2015-09-11 15:10:35"
},
{
"name": "psr/http-message",
"version": "1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
"reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2015-05-04 20:22:00"
},
{
"name": "slim/slim",
"version": "3.3.0",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim.git",
"reference": "939f2e85d57508de9cff241d10091cd972f221c3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/939f2e85d57508de9cff241d10091cd972f221c3",
"reference": "939f2e85d57508de9cff241d10091cd972f221c3",
"shasum": ""
},
"require": {
"container-interop/container-interop": "^1.1",
"nikic/fast-route": "^0.6",
"php": ">=5.5.0",
"pimple/pimple": "^3.0",
"psr/http-message": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0",
"squizlabs/php_codesniffer": "^2.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Slim\\": "Slim"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rob Allen",
"email": "rob@akrabat.com",
"homepage": "http://akrabat.com"
},
{
"name": "Josh Lockhart",
"email": "hello@joshlockhart.com",
"homepage": "https://joshlockhart.com"
},
{
"name": "Gabriel Manricks",
"email": "gmanricks@me.com",
"homepage": "http://gabrielmanricks.com"
},
{
"name": "Andrew Smith",
"email": "a.smith@silentworks.co.uk",
"homepage": "http://silentworks.co.uk"
}
],
"description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
"homepage": "http://slimframework.com",
"keywords": [
"api",
"framework",
"micro",
"router"
],
"time": "2016-03-10 21:37:40"
},
{
"name": "symfony/yaml",
"version": "v3.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "0047c8366744a16de7516622c5b7355336afae96"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96",
"reference": "0047c8366744a16de7516622c5b7355336afae96",
"shasum": ""
},
"require": {
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2016-03-04 07:55:57"
},
{
"name": "zendframework/zendxml",

View File

@ -1,87 +0,0 @@
<?php
/**
* PHP Version 7
*
* Rangitaki Project
* This is the configuration file. You can configure here all necessary
* (and possible) options without editing the index.php file.
* Every line has an description about what you can change here.
* Don't delete any strings. You can set your value after the '=' sign
* and between the apostrophes.
*
* Make sure that every line ends with an semicolon (';').
*
* @category Config
* @package Rbe
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link http://marcel-kapfer.de/rangitaki
*/
// Blog Title / Set here an individual title of yourblog by replacing
// Rangitaki Blog with it.
$blogtitle = 'Example Blog';
// Blog Author - Set here your name
$blogauthor = 'John';
// Blog description
$blogdescription = 'A short description of your blog';
// Home - set yes if you want to link to your homepage and no if not
$bloghome = 'yes';
// Home URL - Set here the url to your main page. Either as a path (e.g. '../')
// or as an url (e.g. 'http://github.com')
$bloghomeurl = '../';
// Home name - Set here an individual name for your main page
$bloghomename = 'Home';
// Main Blog name -> Set a specific name for your main blog
// This value is empty by default
$blogmainname = '';
// Intro - set yes if you have a blog intro and no if you don't have one
$blogintro = 'yes';
// Disqus - Provide here your Disqus shortname. Leave empty if you don't
// want to use it.
$blogdisqus = 'rangitaki';
// Share FAB - this enables or disables the share button
$sharefab = 'yes';
// Google Analytics - Provide here your Google Analytics Tracking-ID. Leave
// empty if you don't want to use it.
$bloganalytics = '';
// Footer - set here the text for your footer (e.g. a copyright info). You can
// replace the whole text after the '=' with your own one.
$blogfooter = 'Rangitaki ' . date("Y") .
' <a href="https://github.com/mmk2410/Rangitaki" target="blank">
github.com/mmk2410/Rangitaki</a>';
// This enables the optional rangitaki control center. Please read the
// documentation before you enable it.
$rcc = 'yes';
// Here you can disable and enable the navigation menu. Usefull if you have
// no subblogs and no home directory
$nav_drawer = 'yes';
// Set here the name of your theme. Read the documentation for more themes
$theme = 'material-light';
// Set here your language. The file must exist in the lang directory
$language = "en";
// pagination: how many articles should be on one page
// set to 0 to disable it
$pagination = 0;
// Favicon - Set here the path to your favicon
$favicon = "http://example.com/res/img/favicon.png";
// Blog url - set here the blog url
$blogurl = "https://example.com/blog/";

23
config.yaml Normal file
View File

@ -0,0 +1,23 @@
blog:
title: 'Example Blog'
author: John
description: 'A short description of your blog'
home: 'on'
homeurl: ../
homename: Home
mainname: ''
intro: 'on'
disqus: rangitaki
analytics: ''
footer: "Rangitaki 2016 <a href=\"https://github.com/mmk2410/Rangitaki\" target=\"blank\">\n github.com/mmk2410/Rangitaki</a>"
url: 'https://example.com/blog/'
design:
fab: 'on'
drawer: 'on'
theme: material-light
pagination: 0
favicon: 'http://example.com/res/img/favicon.png'
rcc:
rcc: 'on'
api: 'on'
language: en

View File

@ -1,11 +1 @@
/**
* Created by mmk2410 on 12/5/15.
*
* Example JavaScript file to demonstrate the rangitaki extension support
*/
function main() {
console.log("Welcome Developer! \nYou're seeing the output of a javascript extension for the rangitaki blogging engine.")
}
$(document).ready(main());
(function(){var e;e=function(){return console.log("Welcome Developer! \nYou're seeing the output of a\nJavaScript extension for the Rangitaki blogging engine.")},$(document).ready(e())}).call(this);

67
gulpfile.coffee Normal file
View File

@ -0,0 +1,67 @@
###
2015 - 2016 (c) by Marcel Kapfer (mmk2410)
Licensed under MIT License
Rangitaki Gulp File
###
gulp = require 'gulp'
sass = require 'gulp-sass'
sourcemaps = require 'gulp-sourcemaps'
minifyCss = require 'gulp-csso'
coffee = require 'gulp-coffee'
coffeelint = require 'gulp-coffeelint'
uglify = require 'gulp-uglify'
merge = require 'merge-stream'
del = require 'del'
size = require 'gulp-size'
gulp.task 'coffee', ->
main = gulp.src './src/coffee/*.coffee'
.pipe coffeelint()
.pipe coffeelint.reporter()
.pipe coffee()
.pipe uglify()
.pipe gulp.dest './res/js/'
extensions = gulp.src './src/coffee-extensions/*.coffee'
.pipe coffeelint()
.pipe coffeelint.reporter()
.pipe coffee()
.pipe uglify()
.pipe gulp.dest './extensions/'
merge(main, extensions)
.pipe size {title: 'Coffee'}
gulp.task 'sass', ->
main = gulp.src './src/sass/*.sass'
.pipe sourcemaps.init()
.pipe sass {
outputStyle: 'compressed'
}
.pipe sourcemaps.write './'
.pipe gulp.dest './res/css/'
theme = gulp.src './src/sass-themes/*.sass'
.pipe sourcemaps.init()
.pipe sass {
outputStyle: 'compressed'
}
.pipe sourcemaps.write './'
.pipe gulp.dest './themes/'
merge(theme, main)
.pipe size {title: 'SASS'}
gulp.task 'clean', del.bind null, ['res/css/no-nav.css', 'res/css/rangitaki.css', 'themes/', 'res/js/app.js']
gulp.task 'init', ['coffee', 'sass']
gulp.task 'default', ->
gulp.watch './src/**/*.sass', ['sass']
gulp.watch './src/**/*.coffee', ['coffee']

106
index.php
View File

@ -1,15 +1,11 @@
<!DOCTYPE HTML>
<!--
Rangitaki Blogging Engine
GitHub: https://github.com/mmk2410/Rangitaki
Code: https://gitlab.com/mmk2410/rangitaki
Issus and Project Management: https://phab.mmk2410.org
Web: https://marcel-kapfer.de/rangitaki
Twitter: @Rangitaki
Google+: +Rangitaki
-->
<!--
COPYRIGHT (c) 2015 mmk2410
MIT License
2015 - 2016 Marcel Kapfer (mmk2410)
License: MIT
-->
<html>
<?php
@ -28,8 +24,14 @@ MIT License
date_default_timezone_set('UTC');
require __DIR__ . '/vendor/autoload.php'; // loading composer libs
require 'config.php'; // Config file (this must be the first line)
require './lang/' . $language . ".php"; // Language file
require './res/php/Config.php';
use mmk2410\rbe\config\Config as Config;
$configParser = new Config('config.yaml', 'vendor/autoload.php');
$config = $configParser->getConfig();
require './lang/' . $config["language"] . ".php"; // Language file
require_once 'res/php/ArticleGenerator.php'; // The article generator
require_once './res/php/BlogListGenerator.php'; // and the blog list generator
@ -42,16 +44,16 @@ $url = "http://" . filter_input(INPUT_SERVER, "HTTP_HOST") .
$pagenumber = filter_input(INPUT_GET, "page"); // get the pagenumber
// Pagination algorithm
if ($pagination == 0) {
$pagination = false;
if ($config["design"]["pagination"] == 0) {
$config["design"]["pagination"] = false;
} else {
// pag_max: the newest post to show on a page
$pag_max = $pagination * ( $pagenumber + 1 );
$pag_max = $config["design"]["pagination"] * ( $pagenumber + 1 );
// pag_min: the oldest post to show on a page
$pag_min = $pag_max - $pagination;
$pag_min = $pag_max - $config["design"]["pagination"];
if ($pagenumber > 0) {
// Disable the blog intro if not on first page
$blogintro = "no";
$config["blog"]["intro"] = "off";
}
}
@ -72,10 +74,10 @@ if (isset($getarticle)) {
= ArticleGenerator::getTitle($articlesdir, $getarticle . '.md');
}
// Make sure that the entry has a title, because main.md hasn't one
if (empty($blogmainname)) {
$blogmaintitle = $blogtitle;
if (empty($config["blog"]["mainname"])) {
$blogmaintitle = $config["blog"]["title"];
} else {
$blogmaintitle = $blogmainname;
$blogmaintitle = $config["blog"]["mainname"];
}
if (isset($getblog)) {
$subblogtitle = BlogListGenerator::getName('./blogs/' . $getblog . '.md');
@ -90,18 +92,18 @@ if (isset($getarticle)) {
}
// url of the feed
$feedurl = $blogurl . "/feed/" . $blog . ".atom";
$feedurl = $config["blog"]["url"] . "/feed/" . $blog . ".atom";
?>
<head>
<meta charset="utf-8">
<title><?php echo $blogtitle . " » " .$hd_subblog_title; ?></title>
<title><?php echo $config["blog"]["title"] . " » " .$hd_subblog_title; ?></title>
<!--Metatags-->
<meta name="author"
content="<?php echo $blogauthor; // Set the blog author ?>"/>
content="<?php echo $config["blog"]["author"]; // Set the blog author ?>"/>
<meta name="description"
content="<?php echo $blogdescription; // the blog description ?>"/>
content="<?php echo $config["blog"]["description"]; // the blog description ?>"/>
<!-- Meta tag for responsive ui-->
<meta name='viewport'
content='width=device-width, initial-scale=1.0,
@ -110,15 +112,15 @@ $feedurl = $blogurl . "/feed/" . $blog . ".atom";
<meta property="og:title" content="<?php echo $hd_subblog_title; ?>"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="<?php echo $url; ?>"/>
<meta property="og:image" content="<?php echo $favicon; ?>"/>
<meta property="og:description" content="<?php echo $blogdescription; ?>"/>
<meta property="og:image" content="<?php echo $config['design']['favicon']; ?>"/>
<meta property="og:description" content="<?php echo $config['blog']['description']; ?>"/>
<meta property="og:locale:alternate" content="<?php echo $lang; ?>"/>
<!-- Twitter meta tags -->
<meta name="twitter:card" content="summary"/>
<meta name="twitter:site" content="<?php echo $twitter; ?>"/>
<meta name="twitter:title" content="<?php echo $hd_subblog_title; ?>"/>
<meta name="twitter:description" content="<?php echo $blogdescription; ?>"/>
<meta name="twitter:image" content="<?php echo $favicon; ?>"/>
<meta name="twitter:description" content="<?php echo $config['blog']['description']; ?>"/>
<meta name="twitter:image" content="<?php echo $config['design']['favicon']; ?>"/>
<meta name="twitter:url" content="<?php echo $url; ?>"/>
<!-- atom feed -->
<?php
@ -136,10 +138,10 @@ $feedurl = $blogurl . "/feed/" . $blog . ".atom";
<!-- stylesheet for code highlighting-->
<link rel="stylesheet" href="./res/css/github-gist.css">
<link rel="stylesheet" type="text/css"
href="themes/<?php echo $theme; // getting the theme stylesheet?>.css"/>
href="themes/<?php echo $config['design']['theme']; // getting the theme stylesheet?>.css"/>
<?php
// Checking if the drawer is enabled
if ($nav_drawer == 'no') {
if ($config["design"]["drawer"] != 'on') {
// Loading additional stylesheet for disabling the drawer?>
<link rel="stylesheet" type="text/css" href="res/css/no-nav.css"/>
<?php
@ -150,8 +152,8 @@ $feedurl = $blogurl . "/feed/" . $blog . ".atom";
type='text/css'> <!--Font-->
<!--Favicons-->
<link rel="shortcut icon" type="image/x-icon"
href="<?php echo $favicon; ?>"/>
<link rel="apple-touch-icon-precomposed" href="<?php echo $favicon; ?>">
href="<?php echo $config['design']['favicon']; ?>"/>
<link rel="apple-touch-icon-precomposed" href="<?php echo $config['design']['favicon']; ?>">
<!-- JavaScript Pt. 1: HightlightJS (get and load): Code highlighting-->
<script src="./res/js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
@ -160,7 +162,7 @@ $feedurl = $blogurl . "/feed/" . $blog . ".atom";
<body>
<?php
// Checking if the navigation drawer is enabled. If not -> skip it
if ($nav_drawer == "yes") {
if ($config["design"]["drawer"] == "on") {
?>
<!--
Darken the background when fading the drawer in. See also the JS file
@ -181,8 +183,8 @@ if ($nav_drawer == "yes") {
echo "<section>";
// 1. Set localized string 2. Set blogtitle
echo "<div class='nav-item-static'>" .
$BLOGLANG['Blogs on'] .
" $blogtitle:</div>";
$BLOGLANG['Blogs on'] . $config["blog"]["title"] .
":</div>";
// iterating through the blogs/ directory
foreach ($blogs as $navblog) {
// check if filename is larger than three chars and if the
@ -192,7 +194,7 @@ if ($nav_drawer == "yes") {
if ($navblog != "main.md") { // excluding main blog
// creating navigation item
BlogListGenerator::listBlog(
"./blogs/", $navblog, $blogtitle
"./blogs/", $navblog, $config["blog"]["title"]
);
}
} else {
@ -215,11 +217,11 @@ if ($nav_drawer == "yes") {
<a class="nav-item" onclick="goBack()">Go back</a>
<?php
}
if ($bloghome == "yes") { // If a blog home is existend
if ($config["blog"]["home"] == "on") { // If a blog home is existend
?>
<div class="divider"></div>
<a class="nav-item" href="<?php echo $bloghomeurl; ?>">
<?php echo $bloghomename; ?>
<a class="nav-item" href="<?php echo $config['blog']['homeurl']; ?>">
<?php echo $config['blog']['homename']; ?>
</a>
<?php
}
@ -246,13 +248,13 @@ if ($nav_drawer == "yes") {
<img src="./res/img/menu.svg" class="nav-img"/>
<!-- Blog title with subblog title and links to each one-->
<!-- link to main blog-->
<nobr><span class="title"><a href="./"><?php echo $blogtitle; ?>
<nobr><span class="title"><a href="./"><?php echo $config["blog"]["title"]; ?>
<?php
if (empty($getblog)) { // if not on a subblog
if (!empty($blogmainname)) {
if (!empty($config['blog']['mainname'])) {
// If you see a (square) here : This is not a bug,
// but a missing sign in your font
echo "" . $blogmainname;
echo "" . $config['blog']['mainname'];
}
} else { // On subblog: set also a link to the subblog
?>
@ -276,7 +278,7 @@ if ($nav_drawer == "yes") {
// Blog Intro text
if (file_exists("blogs/$blog.md")
&& $getarticle == ""
&& $blogintro == "yes"
&& $config["blog"]["intro"] == "on"
&& $gettag == ""
) {
// only shown if not in article or tag view
@ -292,11 +294,11 @@ if ($nav_drawer == "yes") {
?>
<section class="card" id="intro">
<div class="articletext">
<?php // generate the html text from the markdown file
$intro = Parsedown::instance()
->setBreaksEnabled(true)// with linebreaks
->text($file);
echo $intro; // PRINTS THE SH****
<?php // generate the html text from the markdown file
$intro = Parsedown::instance()
->setBreaksEnabled(true)// with linebreaks
->text($file);
echo $intro; // PRINTS THE SH****
?>
</div>
</section>
@ -332,7 +334,7 @@ if ($nav_drawer == "yes") {
// check if the file is a article file
if (strlen($article) >= 3 && substr($article, -3) == ".md") {
// generate the article
if ($pagination) {
if ($config["design"]["pagination"]) {
if ($posts_amount < $pag_max && $posts_amount >= $pag_min) {
ArticleGenerator::newArticle(
$articlesdir, $article, $getblog
@ -346,7 +348,7 @@ if ($nav_drawer == "yes") {
}
$posts_amount++;
}
if ($pagination) {
if ($config["design"]["pagination"]) {
include './res/php/Pagination.php';
}
} elseif (isset($getarticle)) { // ARTICLE VIEW
@ -360,16 +362,16 @@ if ($nav_drawer == "yes") {
}
?>
<div class="footer">
<?php echo $blogfooter; //print the blog footer?>
<?php echo $config["blog"]["footer"]; //print the blog footer?>
</div>
<?php
// show the fab if it's enabled
if ($sharefab == "yes") {
if ($config["design"]["fab"] == "on") {
?>
<div class="fabmenu">
<div class="subfab"><!--Email subfab-->
<a href='mailto:?subject=<?php
echo $blogtitle;
echo $config["blog"]["title"];
?>&body=<?php
echo $BLOGLANG['Check out this blog'];
?>: <?php
@ -398,7 +400,7 @@ if ($nav_drawer == "yes") {
<a href='https://www.facebook.com/sharer/sharer.php?u=<?php
echo $url;
?>&t=<?php
echo "echo $blogtitle"
echo "echo " . $config["blog"]["title"];
?>' target="blank">
<img src="./res/img/facebook.svg" class="subfab-img"/>
</a>

35
package.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "rangitaki",
"version": "1.4.0",
"description": "A simple PHP blogging engine without any database dependencies",
"main": "index.php",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com/mmk2410/rangitaki.git"
},
"keywords": [
"blogging",
"php"
],
"author": "Marcel Kapfer (mmk2410)",
"license": "MIT",
"bugs": {
"url": "https://gitlab.com/mmk2410/rangitaki/issues"
},
"homepage": "https://gitlab.com/mmk2410/rangitaki#README",
"devDependencies": {
"del": "^2.2.0",
"gulp": "^3.9.1",
"gulp-coffee": "^2.3.2",
"gulp-coffeelint": "^0.6.0",
"gulp-csso": "^2.0.0",
"gulp-sass": "^2.3.1",
"gulp-size": "^2.1.0",
"gulp-sourcemaps": "^2.0.0-alpha",
"gulp-uglify": "^1.5.3",
"merge-stream": "^1.0.0"
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* PHP Version 7
*
* Authentication Helper Class
*
* @category Authentication
* @package Rbe
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link http://marcel-kapfer.de/rangitaki
*/
namespace mmk2410\rbe\digestAuth;
/**
* PHP Version 7
*
* Authentication Helper Class
*
* @category Authentication
* @package Rbe
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link http://marcel-kapfer.de/rangitaki
*/
class DigestAuth
{
/**
* parser for http digest
*
* @param $txt data to parse
*
* @return parsed data or FALSE
*/
public function httpDigestParse($txt)
{
// protect against missing data
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($needed_parts));
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}
return $needed_parts ? false : $data;
}
}

39
rcc/api/auth/auth.php Normal file
View File

@ -0,0 +1,39 @@
<?php
// Marcel Kapfer (mmk2410)
// License: MIT License
// api digest auth
require 'DigestAuth.php';
require '../../password.php';
use \mmk2410\rbe\digestAuth\DigestAuth as DigestAuth;
$realm = 'Restricted area';
$users = array($username => $password);
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
die('Access to RCC API not granted');
}
// analyze the PHP_AUTH_DIGEST variable
if (!($data = DigestAuth::httpDigestParse($_SERVER['PHP_AUTH_DIGEST'])) ||
!isset($users[$data['username']])) {
die('Wrong Credentials!');
}
// generate the valid response
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] != $valid_response) {
die('Wrong Credentials!');
}

59
rcc/api/list/index.php Normal file
View File

@ -0,0 +1,59 @@
<?php
// Marcel Kapfer (mmk2410)
// License: MIT License
// api for fetching various lists
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '../../../vendor/autoload.php';
require '../../../res/php/Config.php';
include '../auth/auth.php';
use \mmk2410\rbe\config\Config as Config;
$config = new Config("../../../config.yaml", '../../../vendor/autoload.php');
$settings = $config->getConfig();
if ($settings["rcc"]["api"] == "on" && $settings["rcc"]["rcc"] == "on") {
$app = new \Slim\App();
/**
* api for get the list of blogs and if $_GET["blog"] is set the list of
* blogs posts in that blog
*
* @param string $_GET["blog"] optional name of the blog
*
* @return JSON json string containing the blogs / blog posts
*/
$app->get('/', function (Request $request, Response $response) {
$blog = $_GET["blog"];
if (!isset($blog)) {
$files = scandir('../../../blogs/', SCANDIR_SORT_DESCENDING);
unset($files[sizeof($files) - 1]);
unset($files[sizeof($files) - 1]);
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($files, 201);
return $response;
}
$path = "../../../articles/" . $blog . "/";
$files = scandir($path, SCANDIR_SORT_DESCENDING);
unset($files[sizeof($files) - 1]);
unset($files[sizeof($files) - 1]);
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($files, 201);
return $response;
});
$app->run();
}

48
rcc/api/media/index.php Normal file
View File

@ -0,0 +1,48 @@
<?php
// Marcel Kapfer (mmk2410)
// License: MIT License
// api for uploading files
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '../../../vendor/autoload.php';
require '../../../res/php/Config.php';
require '../../../res/php/ArticleGenerator.php';
include '../auth/auth.php';
use \mmk2410\rbe\config\Config as Config;
$config = new Config("../../../config.yaml", '../../../vendor/autoload.php');
$settings = $config->getConfig();
if ($settings["rcc"]["api"] == "on" && $settings["rcc"]["rcc"] == "on") {
$app = new \Slim\App();
/**
* api for uploading files
*
* @return JSON json string with status
*/
$app->post('/', function (Request $request, Response $response) {
$storage = new \Upload\Storage\FileSystem('../../../media/');
$file = new \Upload\File('file', $storage);
try {
$file->upload();
$data = array("code" => 201);
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 201);
} catch (\Exception $e) {
$errors = $file->getErrors();
$data = array("code" => 500, "error" => $Errors);
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 500);
}
return $response;
});
$app->run();
}

141
rcc/api/post/index.php Normal file
View File

@ -0,0 +1,141 @@
<?php
// Marcel Kapfer (mmk2410)
// License: MIT License
// api for accessing blog posts
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require '../../../vendor/autoload.php';
require '../../../res/php/Config.php';
require '../../../res/php/ArticleGenerator.php';
include '../auth/auth.php';
use \mmk2410\rbe\config\Config as Config;
$config = new Config("../../../config.yaml", '../../../vendor/autoload.php');
$settings = $config->getConfig();
if ($settings["rcc"]["api"] == "on" && $settings["rcc"]["rcc"] == "on") {
$app = new \Slim\App();
/**
* api for fetching a blog post
*
* @param string $_GET["blog"] name of the blog
* @param string $_GET["post"] filename of the blog post
*
* @return JSON json string containing the blog post
*/
$app->get('/', function (Request $request, Response $response) {
$blog = $_GET["blog"];
$post = $_GET["post"];
if (!isset($blog) || !isset($post)) {
$data = array('code' => 400, 'status' => 'Bad Request', 'error' => 'Not enough arguments');
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 400);
return $response;
}
$path = "../../../articles/" . $blog . "/";
$data =
ArticleGenerator::getArray($path, $post);
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 201);
return $response;
});
/**
* api for changing/creating a blog post
*
* @param string $_POST["data"] all data
*/
$app->post('/', function (Request $request, Response $response) {
$blog = $_POST["blog"];
$post = $_POST["post"];
$title = $_POST["title"];
$author = $_POST["author"];
$date = $_POST["date"];
$tags = $_POST["tags"];
$text = $_POST["text"];
if (!isset($blog) || !isset($post) || (!isset($title) && !isset($text))) {
$data = array('code' => 400, 'status' => 'Bad Request', 'error' => 'Not enough arguments');
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 400);
return $response;
}
$text = str_replace('\n', '<br>', $text);
$md = <<<EOD
%TITLE: $title
%DATE: $date
%AUTHOR: $author
%TAGS: $tags
$text
EOD;
$path = "../../../articles/$blog/$post";
if (file_put_contents($path, $md)) {
$data = array('code' => 201, 'status' => 'Post created successfully');
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 201);
} else {
$data = array('code' => 500, 'status' => 'Internal server error');
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 500);
}
return $response;
});
/**
* api for deleting a blog post
*
* @param string $_GET["blog"] name of the blog
* @param string $_GET["post"] filename of the blog post
*
* @return JSON json string containing the blog post
*/
$app->delete('/', function (Request $request, Response $response) {
$blog = $_GET["blog"];
$post = $_GET["post"];
if (!isset($blog) || !isset($post)) {
$data = array('code' => 400, 'status' => 'Bad Request', 'error' => 'Not enough arguments');
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 400);
return $response;
}
$path = "../../../articles/$blog/$post";
if (!file_exists($path)) {
$data = array('code' => 400, 'status' => 'Bad Request', 'error' => 'No such file');
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 400);
return $response;
}
if (!unlink($path)) {
$data = array('code' => 500, 'status' => 'Bad Request', 'error' => 'Internal server error');
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 500);
return $response;
}
$data = array('code' => 201, 'status' => 'File successfully deleted');
$response = $response->withHeader('Content-type', 'application/json');
$response = $response->withJson($data, 201);
return $response;
});
$app->run();
}

View File

@ -1,112 +1,172 @@
<?php
/**
* PHP Version 7
*
* @category Blogging
* @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link https://mmk2410.org/rangitaki
*
* Edit page of RCC (Rangitaki Control Center)
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
date_default_timezone_set('UTC');
?>
<!DOCTYPE html>
<!--
The MIT License
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<html>
<head>
<meta charset="UTF-8">
<title>Rangitaki Control Center</title>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"/>
<meta name="robots" content="nofollow, noindex, noarchive, nosnippet">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0,
user-scalable=0" name="viewport"/>
<meta name="theme-color" content="#383838">
<meta name="description" content="Rangitaki Control Center (RCC)">
<link rel="stylesheet" href="../res/rcc.css"/>
<link href='//fonts.googleapis.com/css?family=Roboto:400,500,700,300,
400italic,100,100italic,900' rel='stylesheet'
type='text/css'> <!--Font-->
</head>
<body>
<div class="header">
<a href="../" class="title">Rangitaki Control Center</a>
</div>
<div class="main">
<?php
session_start();
if ($_SESSION['login']) {
include_once("../../res/php/ArticleGenerator.php");
$directory = "./../../articles/" . $_GET['blog'] . "/";
$article = $_GET['post'] . ".md";
?>
<div class="header">
<a href="../" class="title">Rangitaki Control Center</a>
</div>
<div class="main">
<?php
session_start();
if ($_SESSION['login']) {
include_once "../../res/php/ArticleGenerator.php";
$directory = "./../../articles/" . $_GET['blog'] . "/";
$article = $_GET['post'] . ".md";
?>
<!-- Edit Post -->
<section class="card">
<div class="headline">Edit Post</div>
<p>Title:<br><br><input type="text" class="itextfield"
value="<?php echo ArticleGenerator::getTitle($directory, $article) ?>"
name="title"
id="title"/>
<p>Title:
<br><br>
<input type="text" class="itextfield"
value="<?php
echo ArticleGenerator::getTitle($directory, $article);
?>" name="title" id="title"/>
</p>
<p>Date:<br><br><input type="text" class="itextfield"
value="<?php echo ArticleGenerator::getDate($directory, $article) ?>" name="date"
id="date"/>
<p>Date:
<br><br>
<input type="text" class="itextfield"
value="<?php
echo ArticleGenerator::getDate($directory, $article);
?>" name="date" id="date"/>
</p>
<p>Author:<br><br><input type="text"
value="<?php echo ArticleGenerator::getAuthor($directory, $article) ?>"
class="itextfield" name="author"
id="author"/></p>
<p>Author:
<br><br>
<input type="text" class="itextfield"
value="<?php
echo ArticleGenerator::getAuthor($directory, $article);
?>" name="author" id="author"/>
</p>
<p>Tags:<br><br><input type="text"
value="<?php
$tags = "";
foreach (ArticleGenerator::getTags($directory, $article) as $tag) {
$tags = $tags . ', ' . $tag;
}
$tags = substr($tags, 2);
echo $tags;
?>"
class="itextfield" name="tags"
id="tags"/></p>
<p>Tags:
<br><br>
<input type="text" class="itextfield"
value="<?php
$tags = "";
foreach (
ArticleGenerator::getTags($directory, $article)
as $tag) {
$tags = $tags . ', ' . $tag;
}
$tags = substr($tags, 2);
echo $tags;
?>" name="tags" id="tags"/>
</p>
<p>Text:</p>
<textarea class="itextarea" name="text" id="text">
<?php echo ArticleGenerator::getText($directory, $article) ?>
<?php
echo ArticleGenerator::getText($directory, $article);
?>
</textarea>
<br><br>
<a class="button" id="save_changes">SAVE CHANGES</a>
</section>
<!-- Go Back -->
<section class="card">
<div class="headline">Back</div>
<p>
Go back to the RCC home. All changes will be lost.
</p>
<a class="button" href="../">BACK</a>
</section>
<?php
} else {
?>
<?php
} else {
?>
<!-- Access denied -->
<section class="card">
<div class="headline">Access denied</div>
<p>
The access to this area is not granted. Make sure you're logged in.
The access to this area is not granted. Make sure you're logged
in.
</p>
<a class="button" href="../">BACK</a>
</section>
<?php
}
?>
</div>
<script>
var getVariables = <?php echo json_encode($_GET); ?>;
</script>
<script src="../../res/js/jquery-2.1.4.min.js"></script>
<script src="../res/rcc.js"></script>
<script src="../res/edit.js"></script>
<?php
}
?>
</div>
<script>
var getVariables = <?php echo json_encode($_GET); ?>;
</script>
<script src="../../res/js/jquery-2.1.4.min.js"></script>
<script src="../res/rcc.js"></script>
<script src="../res/edit.js"></script>
</body>
</html>

View File

@ -3,11 +3,13 @@
* PHP Version 7
*
* @category Atom_Feed
* @package Rbe
* @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link https://github.com/mmk2410/rangitaki
*
* Feed Generator
*
* The MIT License
*
* Copyright 2015 mmk2410.
@ -49,7 +51,7 @@ if ($_SESSION['login']) {
$writer->title = $blogtitle;
$writer->site_url = $blogurl;
$writer->feed_url = $blogurl . "/feed/feed.atom";
$writer->feed_url = $blogurl . "/feed/" . $_GET['blog'] . ".atom";
$writer->author = array(
'name' => $blogauthor,
'url' => $blogurl,
@ -65,6 +67,10 @@ if ($_SESSION['login']) {
if ($amount == 10) {
break;
} else {
$file = ArticleGenerator::getText($art_dir, $article);
$text = Parsedown::instance()
->setBreaksEnabled(true)// with linebreaks
->text($file);
$writer->items[] = array(
'title' => ArticleGenerator::getTitle($art_dir, $article),
'updated' => strtotime(
@ -73,11 +79,9 @@ if ($_SESSION['login']) {
'url' => $blogurl . "./?article=" .
substr($article, 0, strlen($article) - 3),
'summary'=> ArticleGenerator::getSummary(
$art_dir, $articles
$art_dir, $article
),
'content' => "<p>" . ArticleGenerator::getText(
$art_dir, $articles
) . "</p>"
'content' => $text
);
$amount += 1;
}
@ -88,11 +92,14 @@ if ($_SESSION['login']) {
$feed = $writer->execute();
$file = fopen($feed_path, "w");
if (fwrite($file, $feed) === false) {
echo "-1";
exit;
}
fclose($file);
echo "0";
}
?>

View File

@ -1,207 +1,308 @@
<?php
/**
* PHP Version 7
*
* @category Blogging
* @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link https://mmk2410.org/rangitaki
*
* Main page of RCC (Rangitaki Control Center)
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!--
Rangitaki Blogging Engine - RCC (Rangitaki Control Center)
Copyright (c) 2016 by Marcel Kapfer (mmk2410)
MIT License
-->
<title>Rangitaki Control Center</title>
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport'/>
<meta name="robots" content="nofollow, noindex, noarchive, nosnippet">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0,
user-scalable=0" name="viewport"/>
<meta name="theme-color" content="#383838">
<meta name="description" content="Rangitaki Control Center (RCC)">
<link rel="stylesheet" href="./res/rcc.css"/>
<link href='//fonts.googleapis.com/css?family=Roboto:400,500,700,300,
400italic,100,100italic,900' rel='stylesheet'
type='text/css'> <!--Font-->
</head>
<body>
<div class="header">
<a href="./" class="title">Rangitaki Control Center</a>
<a href="../" class="back">Back to the blog</a>
</div>
<div class="main">
<div class="header">
<a href="./" class="title">Rangitaki Control Center</a>
<a href="../" class="back">Back to the blog</a>
</div>
<div class="main">
<?php
require '../res/php/Config.php';
use mmk2410\rbe\config\Config as Config;
$configParser = new Config('../config.yaml', '../vendor/autoload.php');
$config = $configParser->getConfig();
if ($config["rcc"]["rcc"] == "on") {
include 'password.php';
session_start();
if (isset($_POST['passwd'])) {
$passwd = $_POST['passwd'];
$_SESSION['passwd'] = $_POST['passwd'];
} elseif (isset($_SESSION['passwd'])) {
$passwd = $_SESSION['passwd'];
}
if ($passwd == "") {
?>
<!-- Login Card -->
<section class="card">
<div class="headline">Log In</div>
<form action="./" method="post">
<p>Password:
<br><br>
<input type="password" class="itextfield" name="passwd"/>
</p>
<input type="Submit" class="button" value="Log in"/>
</form>
</section>
<?php
require '../config.php';
if ($rcc == "yes") {
include 'password.php';
session_start();
if (isset($_POST['passwd'])) {
$passwd = $_POST['passwd'];
$_SESSION['passwd'] = $_POST['passwd'];
} else if (isset($_SESSION['passwd'])) {
$passwd = $_SESSION['passwd'];
}
if ($passwd == "") {
?>
<section class="card">
<div class="headline">Log In</div>
<form action="./" method="post">
<p>Password:<br><br><input type="password" class="itextfield" name="passwd"/></p>
<input type="Submit" class="button" value="Log in"/>
</form>
</section>
<?php
} else {
if ($passwd == $password) {
$_SESSION['login'] = true;
include_once("./../res/php/BlogListGenerator.php");
?>
<section class="card">
<div class="headline">Post Upload</div>
<form enctype="multipart/form-data" action="uploaded/" method="POST">
<select name="blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
<input type="hidden" name="MAX_FILE_SIZE" value="100000"/>
<input id="" name="userfile" type="file" value="Choose a file"/>
<br>
<br>
<input id="button" type="submit" value="Upload" class="button"/>
</form>
</section>
<section class="card">
<div class="headline">New Post</div>
<form action="newpost/" method="POST">
<p>Blog:</p>
<select name="blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
<p>Title:<br><br><input type="text" class="itextfield" name="title"/></p>
<p>Date:<br><br><input type="text" class="itextfield" name="date"/></p>
<p>Author:<br><br><input type="text" class="itextfield" name="author"/></p>
<p>Tags:<br><br><input type="text" class="itextfield" name="tags"/></p>
<p>Text:</p>
<textarea class="itextarea" name="text"></textarea>
<br><br>
<input id="button" type="submit" value="Post" class="button"/>
</form>
</section>
<section class="card">
<div class="headline">Edit post</div>
<p>
First select the blog of the post you wan't to edit.
</p>
<p id="edit_select_blog">
<select name="blog" id="edit_selected_blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
</p>
<a class="button" id="edit_get_posts">GET POSTS</a>
</section>
<section class="card">
<div class="headline">Delete Post</div>
<p>
First select the subblog of the post you want to delete.
</p>
<p id="delete_select_blog">
<select name="blog" id="delete_selected_blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
</p>
<a class="button" id="delete_get_posts">GET POSTS</a>
</section>
<section class="card">
<div class="headline">Media Upload</div>
<form enctype="multipart/form-data" action="media/" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000000000"/>
<input id="" name="userfile" type="file" value="Choose a file"/>
<br>
<br>
<input id="button" type="submit" value="Upload" class="button"/>
</form>
</section>
<section class="card">
<div class="headline">Atom Feed Generator</div>
<p>
<select name="blog" id="generate_atom_blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
</p>
<a class="button" id="generate_atom">GENERATE</a>
</section>
<?php
} else {
?>
<section class="card">
<div class="headline">Wrong Password</div>
<p>
Please go back and try again.
</p>
<a href="./" class="button">GO BACK</a>
</section>
<?php
}
}
} else {
if ($passwd == $password) {
$_SESSION['login'] = true;
include_once "./../res/php/BlogListGenerator.php";
?>
<!-- Post Upload -->
<section class="card">
<div class="headline">Post Upload</div>
<form enctype="multipart/form-data" action="uploaded/"
method="POST">
<select name="blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
<input type="hidden" name="MAX_FILE_SIZE" value="100000"/>
<input id="" name="userfile" type="file" value="Choose a file"/>
<br><br>
<input id="button" type="submit" value="Upload" class="button"/>
</form>
</section>
<!-- New Post -->
<section class="card">
<div class="headline">New Post</div>
<form action="newpost/" method="POST">
<p>Blog:</p>
<select name="blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
<p>Title:
<br><br>
<input type="text" class="itextfield" name="title"/>
</p>
<p>Date:
<br><br>
<input type="text" class="itextfield" name="date"/>
</p>
<p>Author:
<br><br>
<input type="text" class="itextfield" name="author"/>
</p>
<p>Tags:
<br><br>
<input type="text" class="itextfield" name="tags"/>
</p>
<p>Text:</p>
<textarea class="itextarea" name="text"></textarea>
<br><br>
<input id="button" type="submit" value="Post"
class="button"/>
</form>
</section>
<!-- Edit post -->
<section class="card">
<div class="headline">Edit post</div>
<p>
First select the blog of the post you wan't to edit.
</p>
<p id="edit_select_blog">
<select name="blog" id="edit_selected_blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
</p>
<a class="button" id="edit_get_posts">GET POSTS</a>
</section>
<!-- Delete Post -->
<section class="card">
<div class="headline">Delete Post</div>
<p>
First select the subblog of the post you want to delete.
</p>
<p id="delete_select_blog">
<select name="blog" id="delete_selected_blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
</p>
<a class="button" id="delete_get_posts">GET POSTS</a>
</section>
<!-- Media Upload -->
<section class="card">
<div class="headline">Media Upload</div>
<form enctype="multipart/form-data" action="media/" method="POST">
<input type="hidden" name="MAX_FILE_SIZE"
value="100000000000"/>
<input id="" name="userfile" type="file" value="Choose a file"/>
<br><br>
<input id="button" type="submit" value="Upload" class="button"/>
</form>
</section>
<!-- Atom Feed Generator -->
<section class="card">
<div class="headline">Atom Feed Generator</div>
<p>
<select name="blog" id="generate_atom_blog">
<?php
$blogs = scandir("../blogs/");
foreach ($blogs as $blog) {
if (strlen($blog) >= 3 && substr($blog, -3) == ".md") {
$blog = substr($blog, 0, -3);
echo "<option value='$blog'>$blog</option>";
}
}
?>
</select>
</p>
<a class="button" id="generate_atom">GENERATE</a>
</section>
<?php
} else {
?>
<!-- Wrong Password -->
<section class="card">
<div class="headline">Wrong Password</div>
<p>
The entered password was wrong, please try again.
</p>
<form action="./" method="post">
<p>Password:
<br><br>
<input type="password" class="itextfield" name="passwd"/>
</p>
<input type="Submit" class="button" value="Log in"/>
</form>
</section>
<?php
}
}
} else {
?>
<!-- Not enabled -->
<section class="card">
<div class="headline">Rangitaki Control Center</div>
<p>
The Rangitaki Control Center is disabled. You can enable it in
your config file. But please read first the documentation.
The Rangitaki Control Center is disabled. You can enable
it in your config file. But please read first the
documentation.
</p>
</section>
<?php
}
?>
<section class="card" id="back-card">
<div class="headline">Back</div>
<p>
Go back to your blog.
</p>
<a href="../" class="button">GO BACK</a>
</section>
</div>
<script src="./res/rcc.js"></script>
<script src="../res/js/jquery-2.1.4.min.js"></script>
<script src="./res/delete.js"></script>
<script src="./res/edit.js"></script>
<script src="./res/atom.js"></script>
<?php
}
?>
<!-- Back -->
<section class="card" id="back-card">
<div class="headline">Back</div>
<p>Go back to your blog.</p>
<a href="../" class="button">GO BACK</a>
</section>
</div>
<script src="./res/rcc.js"></script>
<script src="../res/js/jquery-2.1.4.min.js"></script>
<script src="./res/delete.js"></script>
<script src="./res/edit.js"></script>
<script src="./res/atom.js"></script>
</body>
</html>

View File

@ -1,60 +1,100 @@
<?php
/**
* PHP Version 7
*
* @category Blogging
* @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link https://mmk2410.org/rangitaki
*
* Media page of RCC (Rangitaki Control Center)
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
?>
<!DOCTYPE html>
<!--
The MIT License
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<html>
<head>
<meta charset="UTF-8">
<title>Rangitaki Control Center</title>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
<link rel="stylesheet" href="../res/rcc.css" />
</head>
<body>
<div class="header">
<a href="../" class="title">Rangitaki Control Center</a>
</div>
<div class="main">
<section class="card">
<div class="headline">File Upload</div>
<?php
session_start();
if($_SESSION['login']) {
if ($_FILES['userfile']['name'] == "") {
echo "<p>You have to choose a file!</p>";
} else {
$uploaddir = "../../media/";
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
<head>
<meta charset="UTF-8">
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo "<p>The post was successfully uploaded and is now published.</p>";
} else {
echo "<p>During the uploading process an error occured! <br> Error Code:" . ($_FILES['userfile']['error'] . "</p>");
}
}
?><a href="../" class="button">GO BACK</a><?php
}
?>
</section>
</div>
</body>
<title>Rangitaki Control Center</title>
<meta name="robots" content="nofollow, noindex, noarchive, nosnippet">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0,
user-scalable=0" name="viewport"/>
<meta name="theme-color" content="#383838">
<meta name="description" content="Rangitaki Control Center (RCC)">
<link rel="stylesheet" href="../res/rcc.css"/>
<link href='//fonts.googleapis.com/css?family=Roboto:400,500,700,300,
400italic,100,100italic,900' rel='stylesheet'
type='text/css'> <!--Font-->
</head>
<body>
<div class="header">
<a href="../" class="title">Rangitaki Control Center</a>
</div>
<div class="main">
<section class="card">
<div class="headline">File Upload</div>
<?php
session_start();
if ($_SESSION['login']) {
if ($_FILES['userfile']['name'] == "") {
echo "<p>You have to choose a file!</p>";
} else {
$uploaddir = "../../media/";
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo
"<p>
The post was successfully uploaded and is now published.
</p>";
} else {
echo
"<p>During the uploading process an error occured! <br>
Error Code:"
. ($_FILES['userfile']['error'] . "</p>");
}
}
?>
<a href="../" class="button">GO BACK</a>
<?php
}
?>
</section>
</div>
</body>
</html>

View File

@ -1,54 +1,82 @@
<?php
/**
* PHP Version 7
*
* @category Blogging
* @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link https://mmk2410.org/rangitaki
*
* Newpost page of RCC (Rangitaki Control Center)
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
date_default_timezone_set('UTC');
?>
<!DOCTYPE html>
<!--
The MIT License
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<html>
<head>
<meta charset="UTF-8">
<title>Rangitaki Control Center</title>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"/>
<meta name="robots" content="nofollow, noindex, noarchive, nosnippet">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0,
user-scalable=0" name="viewport"/>
<meta name="theme-color" content="#383838">
<meta name="description" content="Rangitaki Control Center (RCC)">
<link rel="stylesheet" href="../res/rcc.css"/>
<link href='//fonts.googleapis.com/css?family=Roboto:400,500,700,300,
400italic,100,100italic,900' rel='stylesheet'
type='text/css'> <!--Font-->
</head>
<body>
<div class="header">
<a href="../" class="title">Rangitaki Control Center</a>
</div>
<div class="main">
<section class="card">
<div class="headline">New Post</div>
<?php
session_start();
if ($_SESSION['login']) {
$title = $_POST["title"];
$date = $_POST["date"];
$author = $_POST["author"];
$tags = $_POST["tags"];
$text = $_POST["text"];
$blog = $_POST["blog"];
$md = <<<EOD
<div class="header">
<a href="../" class="title">Rangitaki Control Center</a>
</div>
<div class="main">
<section class="card">
<div class="headline">New Post</div>
<?php
session_start();
if ($_SESSION['login']) {
$title = $_POST["title"];
$date = $_POST["date"];
$author = $_POST["author"];
$tags = $_POST["tags"];
$text = $_POST["text"];
$blog = $_POST["blog"];
$md = <<<EOD
%TITLE: $title
%DATE: $date
%AUTHOR: $author
@ -56,18 +84,23 @@ THE SOFTWARE.
$text
EOD;
$filename = date("Y-m-d-H-i-s") . ".md";
$handle = fopen("../../articles/$blog/$filename", "c");
fwrite($handle, $md);
if (fclose($handle)) {
echo "Post successfully published.";
} else {
echo "Some error happend, while publishing.";
}
?><a href="../" class="button">GO BACK</a><?php
}
?>
</section>
</div>
$filename = date("Y-m-d-H-i-s") . ".md";
$handle = fopen("../../articles/$blog/$filename", "c");
fwrite($handle, $md);
if (fclose($handle)) {
echo "Post successfully published.";
} else {
echo "Some error happend, while publishing.";
}
?>
<a href="../" class="button">GO BACK</a>
<?php
}
?>
</section>
</div>
</body>
</html>

View File

@ -1,3 +1,3 @@
<?php
// Set here your RCC password
$password = "setyourpassword";
$username = "example";
$password = "example";

View File

@ -1,13 +1,10 @@
/**
* Created by mmk2410 on 2016-02-16.
*
* JavaScript for the ajax request to generate a atom feed
*
* Copyright (c) 2016 by mmk2410
* License: MIT License
*/
function main() {
// listener and function for calling the ajax request to create the
// requested atom feed

View File

@ -1,30 +1,45 @@
/**
* Created by mmk2410 on 12/6/15.
* JavaScript for the ajax request to delete blog post
*
* JavaScript for the functionality to delete blogs
* Copyright (c) 2016 by mmk2410
* License: MIT License
*/
function main() {
// listener and function for recieving the posts of the selected blogs
$("#delete_get_posts").click(function () {
var selectedBlog = $("#delete_selected_blog").val();
$.get("res/get_posts.php", {
blog: selectedBlog
}, function (data) {
$("#delete_select_post").remove();
$("#delete_select_post_info").remove();
$("#delete_post_button").remove();
$("#delete_get_posts").after("<p id='delete_select_post'></p>");
$("#delete_get_posts").after("<p id='delete_select_post_info'>Now select the post you want to delete. " +
"Remember that once a post is deleted it can't be restored.</p>");
$("#delete_select_post").append("<select id='delete_selected_post'></select>");
$("#delete_get_posts").after(
"<p id='delete_select_post_info'>" +
"Now select the post you want to delete. " +
"Remember that once a post is deleted it can't be restored.</p>"
);
$("#delete_select_post").append(
"<select id='delete_selected_post'></select>"
);
$.each($.parseJSON(data), function (index, value) {
var post = value.substring(0, value.length - 3);
$("#delete_selected_post").append("<option value='" + post + "'>" + post + "</option>");
$("#delete_selected_post").append(
"<option value='" + post + "'>" + post + "</option>"
);
});
$("#delete_select_post").after("<a class='button' id='delete_post_button' " +
"onclick='deletePostButton()'>DELETE POST</a>")
$("#delete_select_post").after(
"<a class='button' id='delete_post_button' " +
"onclick='deletePostButton()'>DELETE POST</a>"
);
});
});
@ -37,10 +52,12 @@ function deletePostButton() {
var selectedBlog = $("#delete_selected_blog").val();
var selectedPost = $("#delete_selected_post").val();
$.get("res/delete_post.php", {
blog: selectedBlog,
post: selectedPost
}, function (data) {
$("#delete_select_post").remove();
$("#delete_select_post_info").remove();
$("#delete_post_button").remove();
@ -51,7 +68,10 @@ function deletePostButton() {
} else if (data == "941") {
alert("ERROR 941: No blog as get argument given");
} else if (data == "961") {
alert("ERROR 961: Error while deleting the file. Check if the web server has the permission to do so.");
alert(
"ERROR 961: Error while deleting the file. Check if the" +
"web server has the permission to do so."
);
} else if (data == "0") {
alert("Post successfully deleted.");
}
@ -59,4 +79,4 @@ function deletePostButton() {
}
$(document).ready(main());
$(document).ready(main());

View File

@ -2,8 +2,35 @@
/**
* PHP Version 7
*
* User: mmk2410
* Date: 12/6/15
* @category Blogging
* @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link https://mmk2410.org/rangitaki
*
* delete post script
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Error Codes:
* 901 No post given as get argument
@ -11,17 +38,24 @@
* 941 No blog given as get argument
* 961 Error while deleting the post
*/
$post = $_GET["post"];
$blog = $_GET["blog"];
if (!isset($post)) {
echo "901";
} else if (!isset($blog)) {
echo "941";
} else if (!file_exists("./../../articles/$blog/$post.md")) {
echo "921";
} else {
if (unlink("./../../articles/$blog/$post.md")) {
echo "0";
session_start();
if ($_SESSION['login']) {
if (!isset($post)) {
echo "901";
} else if (!isset($blog)) {
echo "941";
} else if (!file_exists("./../../articles/$blog/$post.md")) {
echo "921";
} else {
if (unlink("./../../articles/$blog/$post.md")) {
echo "0";
}
echo "961";
}
echo "961";
}
?>

View File

@ -1,41 +1,54 @@
/**
* Created by mmk2410 on 12/6/15.
* JavaScript for the ajax request to edit a article
*
* JavaScript for the functionality to delete blogs
* Copyright (c) 2016 by mmk2410
* License: MIT License
*/
function main() {
// listener and function for recieving the posts of the selected blogs
$("#edit_get_posts").click(function () {
var selectedBlog = $("#edit_selected_blog").val();
$.get("res/get_posts.php", {
blog: selectedBlog
}, function (data) {
$("#edit_select_post").remove();
$("#edit_select_post_info").remove();
$("#edit_post_button").remove();
$("#edit_get_posts").after("<p id='edit_select_post'></p>");
$("#edit_get_posts").after("<p id='edit_select_post_info'>Now select the post you want to edit.</p>");
$("#edit_select_post").append("<select id='edit_selected_post'></select>");
$("#edit_get_posts").after(
"<p id='edit_select_post_info'>" +
"Now select the post you want to edit.</p>"
);
$("#edit_select_post").append(
"<select id='edit_selected_post'></select>"
);
$.each($.parseJSON(data), function (index, value) {
var post = value.substring(0, value.length - 3);
$("#edit_selected_post").append("<option value='" + post + "'>" + post + "</option>");
$("#edit_selected_post").append(
"<option value='" + post + "'>" + post + "</option>"
);
});
$("#edit_select_post").after("<a class='button' id='edit_post_button' " +
"onclick='editPostButton()'>EDIT POST</a>")
$("#edit_select_post").after(
"<a class='button' id='edit_post_button' " +
"onclick='editPostButton()'>EDIT POST</a>"
);
});
});
$("#save_changes").click(function () {
var postTitle = $("#title").val();
var postDate = $("#date").val();
var postAuthor = $("#author").val();
var postTags = $("#tags").val();
var postText = $("#text").val();
var file = "../../articles/" + getVariables['blog'] + "/" + getVariables['post'] + ".md";
console.log(file);
var file = "../../articles/" + getVariables.blog +
"/" + getVariables.post + ".md";
$.post("../res/save.php", {
title: postTitle,
@ -69,4 +82,4 @@ function editPostButton() {
}
$(document).ready(main());
$(document).ready(main());

View File

@ -2,26 +2,57 @@
/**
* PHP Version 7
*
* User: mmk2410
* Date: 12/6/15
* @category Blogging
* @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link https://mmk2410.org/rangitaki
*
* get post script
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Error Codes:
* 901 No blog given as get argument
* 921 No blog with the given name available
*/
$blog = $_GET["blog"];
if (!isset($blog)) {
echo "901";
} else if (!file_exists("./../../blogs/$blog.md")) {
echo "921";
} else {
$posts = array();
$i = 0;
foreach (scandir("./../../articles/$blog/") as $article) {
if (substr($article, -3) == ".md") {
$posts[$i] = $article;
$i++;
session_start();
if ($_SESSION['login']) {
$blog = $_GET["blog"];
if (!isset($blog)) {
echo "901";
} else if (!file_exists("./../../blogs/$blog.md")) {
echo "921";
} else {
$posts = array();
$i = 0;
foreach (scandir("./../../articles/$blog/") as $article) {
if (substr($article, -3) == ".md") {
$posts[$i] = $article;
$i++;
}
}
print json_encode($posts);
}
print json_encode($posts);
}

View File

@ -1,30 +1,29 @@
/*
The MIT License
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
Created on : Jun 14, 2015, 6:13:38 PM
Author : mmk2410
*/
* RCC cascade style sheet
*
* Copyright (C) 2015-2016 Marcel Kapfer (mmk2410)
* MIT License
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/* BODY */
@ -38,19 +37,19 @@ body{
.main{
height: 100%;
width: 100%;
margin-left: 0px;
margin-left: 0;
}
/* HEADER */
.header{
background-color: #ff4415;
position: fixed;
top: 0px;
right: 0px;
left: 0px;
top: 0;
right: 0;
left: 0;
width: 100%;
height: 64px;
box-shadow: 2px 0px 2px 2px rgba(62, 62, 62, 0.45);
box-shadow: 2px 0 2px 2px rgba(62, 62, 62, 0.45);
}
.title{
@ -82,7 +81,7 @@ body{
background: #fff;
border-radius: 2px;
padding: 24px;
box-shadow: 0px 1px 1.5px 1.5px rgba(62, 62, 62, 0.3);
box-shadow: 0 1px 1.5px 1.5px rgba(62, 62, 62, 0.3);
margin-bottom: 40px;
max-width: 1160px;
}
@ -145,9 +144,9 @@ body{
border-width: 1px;
border-style: solid;
border-color: #ff4415;
box-shadow: 0.4px 1px 1.5px 1px #aaa;
-moz-box-shadow: 0.4px 1px 1.5px 1px #aaa;
-webkit-box-shadow: 0.4px 1px 1.5px 1px #aaa;
-moz-box-shadow: 0.4px 1px 1.5px 1px #aaa;
box-shadow: 0.4px 1px 1.5px 1px #aaa;
border-radius: 2px;
margin-top: 4px;
margin-bottom: 5px;
@ -158,6 +157,10 @@ body{
transition-delay: 50ms;
transition-duration: 125ms;
transition-timing-function: ease;
-o-transition-property: box-shadow;
-o-transition-delay: 50ms;
-o-transition-duration: 125ms;
-o-transition-timing-function: ease;
-moz-transition-property: box-shadow;
-moz-transition-delay: 50ms;
-moz-transition-duration: 125ms;
@ -170,12 +173,13 @@ body{
}
.button:hover, .button:hover {
box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa;
-webkit-box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa;
-moz-box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa;
-webkit-box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa; }
box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa;
}
input.button{
padding: 0px 8px;
padding: 0 8px;
}
/* INPUT FIELD */
@ -192,6 +196,10 @@ input.button{
transition-delay: 50ms;
transition-duration: 125ms;
transition-timing-function: ease;
-o-transition-property: border-bottom-color;
-o-transition-delay: 50ms;
-o-transition-duration: 125ms;
-o-transition-timing-function: ease;
-moz-transition-property: border-bottom-color;
-moz-transition-delay: 50ms;
-moz-transition-duration: 125ms;
@ -224,6 +232,10 @@ input.button{
transition-delay: 50ms;
transition-duration: 125ms;
transition-timing-function: ease;
-o-transition-property: border-bottom-color;
-o-transition-delay: 50ms;
-o-transition-duration: 125ms;
-o-transition-timing-function: ease;
-moz-transition-property: border-bottom-color;
-moz-transition-delay: 50ms;
-moz-transition-duration: 125ms;
@ -273,4 +285,4 @@ form{
#back-card {
display: none;
}
}
}

View File

@ -1,20 +1,32 @@
/**
* JavaScript for RCC
*
* Copyright (c) 2016 by mmk2410
* License: MIT License
*/
window.onload = function () {
var t = document.getElementsByTagName('textarea')[0];
var offset = !window.opera ? (t.offsetHeight - t.clientHeight) : (t.offsetHeight + parseInt(window.getComputedStyle(t, null).getPropertyValue('border-top-width')));
var offset = !window.opera ? (t.offsetHeight - t.clientHeight) : (
t.offsetHeight + parseInt(
window.getComputedStyle(t, null).getPropertyValue('border-top-width')
)
);
/**
* The following three code clocks are for proper resizing of the input textarea
* The following three code clocks are for proper resizing of the input
* textarea
*/
var resize = function (t) {
t.style.height = 'auto';
t.style.height = (t.scrollHeight + offset ) + 'px';
}
};
t.addEventListener && t.addEventListener('input', function (event) {
resize(t);
});
t['attachEvent'] && t.attachEvent('onkeyup', function () {
t.attachEvent && t.attachEvent('onkeyup', function () {
resize(t);
});
}
};

View File

@ -1,11 +1,46 @@
<?php
$title = $_POST["title"];
$date = $_POST["date"];
$author = $_POST["author"];
$tags = $_POST["tags"];
$text = $_POST["text"];
$filename = $_POST["file"];
$md = <<<EOD
/**
* PHP Version 7
*
* @category Blogging
* @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link https://mmk2410.org/rangitaki
*
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
session_start();
if ($_SESSION['login']) {
$title = $_POST["title"];
$date = $_POST["date"];
$author = $_POST["author"];
$tags = $_POST["tags"];
$text = $_POST["text"];
$filename = $_POST["file"];
$md = <<<EOD
%TITLE: $title
%DATE: $date
%AUTHOR: $author
@ -13,10 +48,11 @@ $md = <<<EOD
$text
EOD;
if (file_put_contents($filename, $md)) {
echo 0;
} else if (file_exists(($filename))) {
echo 1;
} else {
echo -1;
if (file_put_contents($filename, $md)) {
echo 0;
} else if (file_exists(($filename))) {
echo 1;
} else {
echo -1;
}
}

View File

@ -1,61 +1,102 @@
<?php
/**
* PHP Version 7
*
* @category Blogging
* @package Rcc
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link https://mmk2410.org/rangitaki
*
* Main page of RCC (Rangitaki Control Center)
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
?>
<!DOCTYPE html>
<!--
The MIT License
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<html>
<head>
<meta charset="UTF-8">
<title>Rangitaki Control Center</title>
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport' />
<link rel="stylesheet" href="../res/rcc.css" />
</head>
<body>
<div class="header">
<a href="../" class="title">Rangitaki Control Center</a>
</div>
<div class="main">
<section class="card">
<div class="headline">File Upload</div>
<?php
session_start();
if($_SESSION['login']) {
if ($_FILES['userfile']['name'] == "") {
echo "<p>You have to choose a file!</p>";
} else {
$blog = filter_input(INPUT_POST, "blog");
$uploaddir = "../..//articles/$blog/";
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
<head>
<meta charset="UTF-8">
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo "<p>The post was successfully uploaded and is now published.</p>";
} else {
echo "<p>During the uploading process an error occured! <br> Error Code:" . ($_FILES['userfile']['error'] . "</p>");
}
}
?><a href="../" class="button">GO BACK</a><?php
}
?>
</section>
</div>
</body>
<title>Rangitaki Control Center</title>
<meta name="robots" content="nofollow, noindex, noarchive, nosnippet">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0,
user-scalable=0" name="viewport"/>
<meta name="theme-color" content="#383838">
<meta name="description" content="Rangitaki Control Center (RCC)">
<link rel="stylesheet" href="./res/rcc.css"/>
<link href='//fonts.googleapis.com/css?family=Roboto:400,500,700,300,
400italic,100,100italic,900' rel='stylesheet'
type='text/css'> <!--Font-->
</head>
<body>
<div class="header">
<a href="../" class="title">Rangitaki Control Center</a>
</div>
<div class="main">
<section class="card">
<div class="headline">File Upload</div>
<?php
session_start();
if ($_SESSION['login']) {
if ($_FILES['userfile']['name'] == "") {
echo "<p>You have to choose a file!</p>";
} else {
$blog = filter_input(INPUT_POST, "blog");
$uploaddir = "../..//articles/$blog/";
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo
"<p>The post was successfully uploaded and is now
published.</p>";
} else {
echo
"<p>During the uploading process an error occured! <br>
Error Code:"
. ($_FILES['userfile']['error'] . "</p>");
}
}
?>
<a href="../" class="button">GO BACK</a>
<?php
}
?>
</section>
</div>
</body>
</html>

View File

@ -1,57 +1,3 @@
/*
Rangitaki Project
.nav{display:none}.nav-img{display:none}@media screen and (min-width: 1440px){.header{left:0}.main{margin-left:0;width:100%}}@media screen and (max-width: 720px){.title{left:25px}}
The MIT License
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
Created on : Jun 18, 2015, 7:07:45 PM
Author : mmk2410
*/
/*
A stylesheet for overriding the default styles if the navigation drawer is disabled
*/
.nav{ /* hide the navigation drawer */
display: none;
}
.nav-img{ /* hide the hamburger icon */
display: none;
}
@media screen and (min-width: 1440px) { /* large devices */
.header { /* full width */
left: 0;
}
.main{ /* full width */
margin-left: 0;
width: 100%;
}
}
@media screen and (max-width: 720px){ /* small devices */
.title{ /* move the title more to the left */
left: 25px;
}
}
/*# sourceMappingURL=no-nav.css.map */

1
res/css/no-nav.css.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"no-nav.css","sources":["no-nav.sass"],"sourcesContent":["/*\n * Rangitaki Project\n *\n * The MIT License\n *\n * Copyright 2015 mmk2410.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * A stylesheet for overriding the default styles if the navigation drawer is disabled\n */\n\n.nav {\n display: none; }\n\n.nav-img {\n display: none; }\n\n@media screen and (min-width: 1440px) {\n .header {\n left: 0; }\n\n .main {\n margin-left: 0;\n width: 100%; } }\n\n@media screen and (max-width: 720px) {\n\n .title {\n left: 25px; } }\n"],"mappings":"AA4BA,AAAA,IAAI,AAAC,CACD,OAAO,CAAE,IAAK,CAAG,AAErB,AAAA,QAAQ,AAAC,CACL,OAAO,CAAE,IAAK,CAAG,AAErB,MAAM,CAAN,MAAM,MAAM,SAAS,EAAE,MAAM,EACzB,AAAA,OAAO,AAAC,CACJ,IAAI,CAAE,CAAE,CAAG,AAEf,AAAA,KAAK,AAAC,CACF,WAAW,CAAE,CAAE,CACf,KAAK,CAAE,IAAK,CAAG,CAEvB,MAAM,CAAN,MAAM,MAAM,SAAS,EAAE,KAAK,EAExB,AAAA,MAAM,AAAC,CACH,IAAI,CAAE,IAAK,CAAG","names":[]}

View File

@ -1,305 +1,3 @@
/*
Rangitaki Project
body{margin-top:94px}.main{height:100%;margin-left:0}.overlay{opacity:0;position:fixed;top:0;left:0;background-color:black;width:100%;z-index:30;height:100%;display:none}.header{top:0;right:0;left:0;width:100%;height:64px;position:absolute}.title{color:#fff;font-size:23px;text-decoration:none;line-height:64px;vertical-align:middle;left:75px}.title>a{text-decoration:none;color:#fff}.fadeout{position:absolute;height:64px;top:0;right:0;width:40px}.nav-img{height:26px;padding:19px;cursor:pointer}.nav{width:300px;position:fixed;height:100%;top:0;left:-301px;z-index:40}.nav-item,.nav-item-static{text-decoration:none;text-indent:0;display:inline-block;height:48px;vertical-align:middle;width:284px;line-height:48px;padding-left:16px;transition:background-color 125ms ease-in-out 0ms}.nav-close{cursor:pointer}.nav-close-img{height:35px;padding:12px}.nav-item{cursor:pointer}.divider{width:100%}.card{margin-right:auto;margin-left:auto;width:75%;padding:24px;margin-bottom:40px;max-width:1160px}.card a{-moz-hyphens:auto;-epub-hyphens:auto;-ms-hyphens:auto;-webkit-hyphens:auto;hyphens:auto;word-wrap:break-word}.headline{display:block;padding-bottom:8px}.card img{max-width:100%;max-height:400px;display:block;margin-left:auto;margin-right:auto}.author{display:block}.fabmenu{position:fixed;bottom:20px;right:20px}.fab{height:60px;width:60px;border-radius:30px;cursor:pointer}.fab-img{width:28px;padding:15px}.subfab{height:45px;width:45px;border-radius:30px;margin-right:auto;margin-left:auto;margin-bottom:25px;display:none}.subfab-img{width:22px;padding:12px}.pag_buttons{margin-right:auto;margin-left:auto;width:calc(75% + 48px);margin-bottom:80px;max-width:1160px;text-align:right}.button{text-decoration:none;color:#fff;line-height:36px;min-width:64px;text-align:center;height:36px;padding:8px;margin-top:4px;margin-bottom:5px;cursor:pointer}.pag_next{margin-left:8px}.footer a{transition:border-bottom-color 150ms ease-in-out 100ms}.footer a:hover{border-bottom-color:#383838}@media screen and (min-width: 1440px){.nav{left:0;padding-top:64px}.nav-close-img{display:none}.nav-close{display:none}.nav-img{display:none}.header{left:300px}.main{margin-left:300px;width:calc(100% - 300px)}}@media screen and (max-width: 720px){.card{width:82%}.pag_buttons{width:calc(82% + 48px)}}
The MIT License
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
Created on : Jun 14, 2015, 6:13:38 PM
Author : mmk2410
*/
/* BODY */
body{
margin-top: 94px; /* Set enough space for the header */
}
.main{ /* main content */
height: 100%;
margin-left: 0;
}
.overlay { /* overlay: used for a darker background when the navigation drawer is open */
opacity: 0;
position: fixed;
top: 0;
left: 0;
background-color: black;
width: 100%;
z-index: 30;
height: 100%;
display: none; /* because the thing is hidden at the beginning and shown with js */
}
/* HEADER */
.header{
top: 0;
right: 0;
left: 0;
width: 100%;
height: 64px;
position: absolute;
}
.title{ /* title in the header */
color: #fff;
font-size: 23px;
text-decoration: none;
line-height: 64px;
vertical-align: middle;
left: 75px;
}
.title > a{ /* and a a styling */
text-decoration: none;
color: #fff;
}
.fadeout{ /* a fadeout if the title is to long */
position: absolute;
height: 64px;
top: 0;
right: 0;
width: 40px;
}
.nav-img{ /* the hamburger icon */
height: 26px;
padding: 19px;
cursor: pointer;
}
/* NAV DRAWER */
.nav{ /* the main object */
width: 300px;
position: fixed;
height: 100%;
top: 0;
left: -301px;
z-index: 40;
}
.nav-item, .nav-item-static{ /* a nav-item */
text-decoration: none;
text-indent: 0;
display: inline-block;
height: 48px;
vertical-align: middle;
width: 284px;
line-height: 48px;
padding-left: 16px;
transition: background-color 125ms ease-in-out 0ms;
}
.nav-close {
cursor: pointer;
}
.nav-close-img{ /* the hamburger icon */
height: 35px;
padding: 12px;
}
.nav-item{ /* additional setting for clickable nav items */
cursor: pointer;
}
.divider{ /* a simple divider with all options. better than <hr> */
width: 100%;
}
/* MAIN */
.card{ /* just a card */
margin-right: auto;
margin-left: auto;
width: 75%;
padding: 24px;
margin-bottom: 40px;
max-width: 1160px;
}
.card a{ /* and another a styling */
-moz-hyphens: auto;
-epub-hyphens: auto;
-ms-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto;
word-wrap: break-word; /* until here: break long links */
}
.card a:hover{ /* hovered card links; just here to help you create a own theme*/
}
.headline{ /* title in a card */
display: block;
padding-bottom: 8px;
}
.card img{ /* image in a card*/
max-width: 100%; /* regulate width */
max-height: 400px; /* regulate height */
display: block; /* centered */
margin-left: auto; /* centerd */
margin-right: auto; /* centred */
}
.date{ /* possibility to style the date */
}
.articletext{ /* posibitlity to style the text */
}
.author{ /* styling for the author */
display: block;
}
.tag{ /* possibility to style the text */
}
/* FAB */
.fabmenu{ /* surrounds the complete fab menu */
position: fixed;
bottom: 20px;
right: 20px;
}
.fab{ /* the main fab (always visible if enabled) */
height: 60px;
width: 60px;
border-radius: 30px;
cursor: pointer;
}
.fab-img{ /* fab image for the main fab */
width: 28px;
padding: 15px;
}
.subfab{ /* a smaller fab in the fab menu */
height: 45px;
width: 45px;
border-radius: 30px;
margin-right: auto;
margin-left: auto;
margin-bottom: 25px;
display: none;
}
.subfab-img{ /* and the image for it */
width: 22px;
padding: 12px;
}
/* BUTTON */
.pag_buttons {
margin-right: auto;
margin-left: auto;
width: calc(75% + 48px);
margin-bottom: 80px;
max-width: 1160px;
text-align: right;
}
.button {
text-decoration: none;
color: #fff;
line-height: 36px;
min-width: 64px;
text-align: center;
height: 36px;
padding: 8px;
margin-top: 4px;
margin-bottom: 5px;
cursor: pointer;
}
.pag_next{
margin-left: 8px;
}
/* FOOTER */
.footer{ /* possibility for styling the footer */
}
.footer a{ /* styling of the footer a */
transition: border-bottom-color 150ms ease-in-out 100ms;
}
.footer a:hover{ /* and the hovered one*/
border-bottom-color: #383838;
}
@media screen and (min-width: 1440px) { /* make it responsive: large devices */
.nav{ /* always show navigation bar */
left: 0;
padding-top: 64px;
}
.nav-close-img {
display: none;
}
.nav-close {
display: none;
}
.nav-img{ /* always hide navigation bar icon*/
display: none;
}
.header{ /* move header to the right */
left: 300px;
}
.main{ /* move main content to the right and limit its width */
margin-left: 300px;
width: calc(100% - 300px);
}
}
@media screen and (max-width: 720px){ /* make it responsive: small devices */
.card{ /* wider cards */
width: 82%;
}
.pag_buttons {
width: calc(82% + 48px);
}
}
/*# sourceMappingURL=rangitaki.css.map */

File diff suppressed because one or more lines are too long

View File

@ -1,112 +1 @@
/*
* Rangitaki Project
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
var main = function () { // main function; called below
var fabActive = false; // fab hidden at begin
$('.fabmenu').click( // action on fab click
function () {
if (!(fabActive)) { // if fab is hidden
fabFadeIn(); // fade fab in
fabActive = true; // fab = active
} else { // if fab is shown
fabFadeOut(); // fade fab out
fabActive = false; // fab = hidden
}
}
);
var navOpen = false; // nav hidden at begin
$('.nav-img, .overlay, .nav-close').click( // action on hamburger click
function () {
if (!(navOpen)) { // if nav is hidden
openNav(); // open the nav drawer
navOpen = true; // nav = open
} else { // if nav is closed
closeNav(); // close the nav drawer
navOpen = false; // nav = closed
}
}
);
/**
* Keyhandling for the navigation drawer.
* opens the drawer on 'm' (key code: 77)
* closes the drawer on 'Esc' (key code: 27)
*/
$(document).keyup(function (e) {
if (navOpen && (e.which === 27)) {
closeNav();
navOpen = false;
} else if (!(navOpen) && (e.which === 77)) {
openNav();
navOpen = true;
}
});
};
$(document).ready(main); // run if document is loaded
function goBack() { // go back function
history.go(-1);
}
function fabFadeIn() { // fade fab in
$('.subfab').fadeIn(125); // fade subfabs in
$('.fab-img').fadeOut( // fade fab share image out
60, function callback() {
$('.fab-img').attr("src", "./res/img/close.svg"); // change to fab close image
}
);
$('.fab-img').fadeIn(60); // fade fab close image in
}
function fabFadeOut() { // fade fab out
$('.subfab').fadeOut(125); // fade subfabs out
$('.fab-img').fadeOut( // fade fab close image out
60, function callback() {
$('.fab-img').attr("src", "./res/img/share.svg"); // change to fab share image
}
);
$('.fab-img').fadeIn(60); // fade fab share image in
}
function openNav() { // fade navigation drawer in
$('.nav').animate({"left": "0px"}, 125); // slide in
$('.overlay').show(); // set overlay to show ...
$('.overlay').animate({"opacity": "0.4"}, 125); // ... and fade to a darker transparent color
}
function closeNav() { // fade navigation drawer out
$('.nav').animate({"left": "-301px"}, 125); // slide out
$('.overlay').animate(
{"opacity": "0.0"}, 125, function () { // fade the overlay to complete transparency
$('.overlay').hide(); // hide it then
}
);
}
(function(){var n,t,a,e,r,i;r=function(){var e,r;return e=!1,$(".fabmenu").click(function(){return e?(a(),e=!1):(t(),e=!0)}),r=!1,$(".nav-img, .overlay, .nav-close").click(function(){return r?(n(),r=!1):(i(),r=!0)}),$(document).keyup(function(t){return r&&27===t.which?(n(),r=!1):r||77!==t.which?void 0:(i(),r=!0)}),$(".articletext a").attr("target","_blank")},$(document).ready(r),e=function(){return history.go(-1)},t=function(){return $(".subfab").fadeIn(125),$(".fab-img").fadeOut(60,function(){return $(".fab-img").attr("src","./res/img/close.svg"),$(".fab-img").fadeIn(60)})},a=function(){return $(".subfab").fadeOut(125),$(".fab-img").fadeOut(60,function(){return $(".fab-img").attr("src","./res/img/share.svg"),$(".fab-img").fadeIn(60)})},i=function(){return $(".nav").animate({left:"0px"},125),$(".overlay").show(),$(".overlay").animate({opacity:"0.4"},125)},n=function(){return $(".nav").animate({left:"-301px"},125),$(".overlay").animate({opacity:"0.0"},125,function(){return $(".overlay").hide})}}).call(this);

View File

@ -265,4 +265,46 @@ class ArticleGenerator
return $article;
}
/**
* A function to get a array of the article
*
* @param $directory The directory where the article is stored
* @param $articlefile The name of the article file
* @return string
*/
public function getArray($directory, $articlefile)
{
$article = file_get_contents($directory . $articlefile);
if (substr($article, 0, 6) == "%TITLE") { // get and remove the title
$title = substr($article, 8, strpos($article, "\n") - 8);
$article = substr($article, strpos($article, "\n") + 1);
}
if (substr($article, 0, 5) == "%DATE") { // get and remove the title
$date = substr($article, 7, strpos($article, "\n") - 7);
$article = substr($article, strpos($article, "\n") + 1);
}
if (substr($article, 0, 7) == "%AUTHOR") { // get and remove the title
$author = substr($article, 9, strpos($article, "\n") - 9);
$article = substr($article, strpos($article, "\n") + 1);
}
if (substr($article, 0, 5) == "%TAGS") { // get and remove the tags
$tags = substr($article, 7, strpos($article, "\n") - 7); // get the tags
$tags = explode(", ", $tags); // split them into an array
$article = substr($article, strpos($article, "\n") + 1);
}
$data = array(
"title" => $title,
"date" => $date,
"author" => $author,
"tags" => $tags,
"text" => $article
);
return $data;
}
}

74
res/php/Config.php Normal file
View File

@ -0,0 +1,74 @@
<?php
/**
* PHP Version 7
*
* Configuration parser for yaml configuration files
*
* @category Configuration
* @package Rbe
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link http://marcel-kapfer.de/rangitaki
*/
namespace mmk2410\rbe\config;
/**
* PHP Version 7
*
* Configuration parser for yaml configuration files
*
* @category Configuration
* @package Rbe
* @author Marcel Kapfer (mmk2410) <marcelmichaelkapfer@yahoo.co.nz>
* @license MIT License
* @link http://marcel-kapfer.de/rangitaki
*/
class Config
{
/**
* Path to yaml file
* @var string
*/
private $file;
/**
* Constructor for the Config class
*
* @param $config path to the yaml file
* @param $composer path to the composer autoload
*/
public function __construct($config, $composer)
{
$this->file = $config;
require $composer;
}
/**
* Return yaml config as PHP array
*
* @return config array
*/
public function getConfig()
{
$yaml = new \Symfony\Component\Yaml\Parser();
return $yaml->parse(file_get_contents($this->file));
}
/*
* Write array into confi*
* Write array into config file
*
* @param array config new config
*
* @return FALSE if failed to write
*/
public function writeConfig($config)
{
$dumper = new \Symfony\Component\Yaml\Dumper();
$yaml = $dumper->dump($config, 2);
return file_put_contents($this->file, $yaml);
}
}

View File

@ -27,7 +27,7 @@ THE SOFTWARE.
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES * * */
var disqus_shortname = '<?php echo $blogdisqus; ?>';
var disqus_shortname = '<?php echo $config['blog']['disqus']; ?>';
/* * * DON'T EDIT BELOW THIS LINE * * */
(function () {

View File

@ -26,7 +26,7 @@
*/
if ($bloganalytics) { // check if google analytics is enabled
if ($config["blog"]["analytics"]) { // check if google analytics is enabled
if (!($_SERVER['HTTP_DNT'] == 1)) {
?>
<script>

View File

@ -11,26 +11,32 @@
* @link http://marcel-kapfer.de/rangitaki
*/
require_once "BlogListGenerator.php";
require_once "config.php";
require_once "lang/" . $language . ".php";
if ($pagination) {
require_once './res/php/Config.php';
$configParser = new mmk2410\rbe\config\Config('./config.yaml', './vendor/autoload.php');
$conf = $configParser->getConfig();
require_once "lang/" . $config["language"] . ".php";
if ($blog["design"]["pagination"]) {
?>
<div class="pag_buttons">
<?php
<?php
if ($pag_min > 0) {
if (isset($getblog)) {
?>
?>
<a href="<?php
echo "?blog=" . $getblog . "&page=" . ($pagenumber - 1);
?>" class="pag_prev button"><?php echo $BLOGLANG["Previous Page"]; ?></a>
<?php
<?php
} else {
?>
?>
<a href="<?php
echo "?page=" . ($pagenumber - 1);
?>" class="pag_prev button"><?php echo $BLOGLANG['Previous Page']; ?></a>
<?php
<?php
}
}
if (isset($getblog)) {
@ -40,20 +46,20 @@ if ($pagination) {
}
if ($pag_max < BlogListGenerator::getArticleAmount($pag_blog)) {
if (isset($getblog)) {
?>
?>
<a href="<?php
echo "?blog=" . $getblog . "&page=" . ($pagenumber + 1);
?>" class="pag_next button"><?php echo $BLOGLANG["Next Page"]; ?></a>
<?php
<?php
} else {
?>
?>
<a href="<?php
echo "?page=" . ($pagenumber + 1);
?>" class="pag_next button"><?php echo $BLOGLANG["Next Page"];?></a>
<?php
<?php
}
}
?>
?>
</div>
<?php
}

View File

@ -0,0 +1,38 @@
###
Rangitaki Project
The MIT License
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
###
###
This is a example JavaScript extension for Rangitaki
###
main = () ->
console.log """
Welcome Developer! \nYou're seeing the output of a
JavaScript extension for the Rangitaki blogging engine.
"""
$(document).ready main()

91
src/coffee/app.coffee Normal file
View File

@ -0,0 +1,91 @@
###
Rangitaki Project
The MIT License
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
###
main = () ->
# FAB
fabActive = false
$('.fabmenu').click ->
if !fabActive
fabFadeIn()
fabActive = true
else
fabFadeOut()
fabActive = false
# Navigation Drawer
navOpen = false
$('.nav-img, .overlay, .nav-close').click ->
if !navOpen
openNav()
navOpen = true
else
closeNav()
navOpen = false
###
Keyhandling for the navigation drawer.
opens the drawer on 'm' (key code: 77)
closes the drawer on 'Esc' (key code: 27)
###
$(document).keyup (e) ->
if navOpen and e.which is 27
closeNav()
navOpen = false
else if !navOpen and e.which == 77
openNav()
navOpen = true
# Make every link in articles target="_blank"
$('.articletext a').attr 'target', '_blank'
$(document).ready main
goBack = () ->
history.go -1
fabFadeIn = () ->
$('.subfab').fadeIn 125
$('.fab-img').fadeOut 60, ->
$('.fab-img').attr "src", "./res/img/close.svg"
$('.fab-img').fadeIn 60
fabFadeOut = () ->
$('.subfab').fadeOut 125
$('.fab-img').fadeOut 60, ->
$('.fab-img').attr "src", "./res/img/share.svg"
$('.fab-img').fadeIn 60
openNav = () ->
$('.nav').animate {"left": "0px"}, 125
$('.overlay').show()
$('.overlay').animate {"opacity": "0.4"}, 125
closeNav = () ->
$('.nav').animate {"left": "-301px"}, 125
$('.overlay').animate {"opacity": "0.0"}, 125, ->
$('.overlay').hide

View File

@ -0,0 +1,179 @@
/*
* Rangitaki Project
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
body
font-family: "Roboto", sans-serif
background: #f6f6f6
color: #383838
background-image: url(//example.com/res/img/intro.svg)
background-size: cover
background-attachment: fixed
background-position: top center
background-repeat: no-repeat
.header
background-color: rgba(0, 0, 0, 0.45)
position: fixed
.title
color: #fff
position: absolute
.title > a
color: #fff
.fadeout
background: -moz-linear-gradient(left, rgba(30,87,153,0) 0%, rgba(0, 0, 0, 0.45) 100%)
background: -webkit-linear-gradient(left, rgba(30,87,153,0) 0%,rgba(0, 0, 0, 0.45) 100%)
background: -o-linear-gradient(left, rgba(30,87,153,0) 0%,rgba(0, 0, 0, 0.45) 100%)
background: -ms-linear-gradient(left, rgba(30,87,153,0) 0%,rgba(0, 0, 0, 0.45) 100%)
background: linear-gradient(to right, rgba(30,87,153,0) 0%,rgba(0, 0, 0, 0.45) 100%)
.nav
background-color: #fff
border-right: 1px solid #e0e0e0
.nav-item, .nav-item-static
color: #383838
.nav-item
font-weight: 600
.nav-item:active
background-color: #e2e2e2
.divider
border-bottom: 1px solid #e0e0e0
.card
background: #fff
border-radius: 2px
box-shadow: 0 1px 1.5px 1.5px rgba(62, 62, 62, 0.3)
.card a
color: #ff4415
text-decoration: none
border-bottom: 1px solid transparent
border-bottom-color: transparent
transition: border-bottom-color 150ms ease-in-out 100ms
.card a:hover
border-bottom-color: #ff4415
.headline
font-size: 24px
color: #383838!important
text-decoration: none
border-bottom: none!important
.headline:hover
color: #ff4415!important
.date
font-size: 13px
.articletext
font-size: 14px
line-height: 24px
.author
font-size: 13px
.tag
font-size: 13px
.fab
background-color: #ff4415
box-shadow: 0 1px 1.5px 1.5px rgba(62, 62, 62, 0.3)
.subfab
background-color: #fff
box-shadow: 0 1px 1.5px 1.5px rgba(62, 62, 62, 0.3)
.button
border-width: 1px
border-style: solid
text-transform: uppercase
-webkit-box-shadow: 0 1px 1.5px 1.5px rgba(42, 42, 42, 0.65)
-moz-box-shadow: 0 1px 1.5px 1.5px rgba(42, 42, 42, 0.65)
box-shadow: 0 1px 1.5px 1.5px rgba(42, 42, 42, 0.65)
border-radius: 2px
letter-spacing: 0.4px
font-weight: 700
font-size: 14px
transition-property: box-shadow
transition-delay: 50ms
transition-duration: 125ms
transition-timing-function: ease
-o-transition-property: box-shadow
-o-transition-delay: 50ms
-o-transition-duration: 125ms
-o-transition-timing-function: ease
-moz-transition-property: box-shadow
-moz-transition-delay: 50ms
-moz-transition-duration: 125ms
-moz-transition-timing-function: ease
-webkit-transition-property: box-shadow
-webkit-transition-delay: 50ms
-webkit-transition-duration: 125ms
-webkit-transition-timing-function: ease
.button:hover, .button:hover
-webkit-box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85)
-moz-box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85)
box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85)
.pag_next
background-color: #ff4415
border-color: #ff4415
.pag_prev
background-color: #fff
border-color: #fff
color: #383838
.footer
font-size: 12px
text-align: center
color: #fff
text-shadow: 1px 1px rgba(55, 55, 55, 0.3)
.footer a
color: #fff
text-shadow: 1px 1px rgba(175, 175, 175, 0.3)
text-decoration: none
border-bottom: 1px solid transparent
border-bottom-color: transparent
.footer a:hover
border-bottom-color: #fff
@media screen and (min-width: 1440px)
.nav
background-color: rgba(255, 255, 255, 0.8)

View File

@ -0,0 +1,161 @@
/*
* Rangitaki Project
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
body
font-family: "Roboto", sans-serif
background: #303030
color: #fff
.header
background-color: #212121
position: fixed
box-shadow: 2px 0 2px 2px rgba(32, 32, 32, 0.85)
.title
color: #fff
position: absolute
> a
color: #fff
.fadeout
background: -moz-linear-gradient(left, rgba(30, 87, 153, 0) 0%, #212121 100%)
/* FF3.6+
background: -webkit-linear-gradient(left, rgba(30, 87, 153, 0) 0%, #212121 100%)
/* Chrome10+,Safari5.1+
background: -o-linear-gradient(left, rgba(30, 87, 153, 0) 0%, #212121 100%)
/* Opera 11.10+
background: -ms-linear-gradient(left, rgba(30, 87, 153, 0) 0%, #212121 100%)
/* IE10+
background: linear-gradient(to right, rgba(30, 87, 153, 0) 0%, #212121 100%)
/* W3C
.nav
background-color: #424242
border-right: 1px solid #1f1f1f
.nav-item, .nav-item-static
color: #fff
.nav-item
font-weight: 600
&:active
background-color: #383838
.divider
border-bottom: 1px solid #1f1f1f
.card
background: #424242
border-radius: 2px
box-shadow: 0 1px 1.5px 1.5px rgba(42, 42, 42, 0.65)
a
color: #ff4415
text-decoration: none
border-bottom: 1px solid transparent
border-bottom-color: transparent
transition: border-bottom-color 150ms ease-in-out 100ms
&:hover
border-bottom-color: #ff4415
.headline
font-size: 24px
color: #fff !important
text-decoration: none
border-bottom: none !important
&:hover
color: #ff4415 !important
.date
font-size: 13px
.articletext
font-size: 14px
line-height: 24px
.author, .tag
font-size: 13px
.fab
background-color: #ff4415
box-shadow: 0 1px 1.5px 1.5px rgba(42, 42, 42, 0.65)
.subfab
background-color: #424242
box-shadow: 0 1px 1.5px 1.5px rgba(42, 42, 42, 0.65)
.button
text-transform: uppercase
border-width: 1px
border-style: solid
-webkit-box-shadow: 0 1px 1.5px 1.5px rgba(42, 42, 42, 0.65)
-moz-box-shadow: 0 1px 1.5px 1.5px rgba(42, 42, 42, 0.65)
box-shadow: 0 1px 1.5px 1.5px rgba(42, 42, 42, 0.65)
border-radius: 2px
letter-spacing: 0.4px
font-weight: 700
font-size: 14px
transition-property: box-shadow
transition-delay: 50ms
transition-duration: 125ms
transition-timing-function: ease
-o-transition-property: box-shadow
-o-transition-delay: 50ms
-o-transition-duration: 125ms
-o-transition-timing-function: ease
-moz-transition-property: box-shadow
-moz-transition-delay: 50ms
-moz-transition-duration: 125ms
-moz-transition-timing-function: ease
-webkit-transition-property: box-shadow
-webkit-transition-delay: 50ms
-webkit-transition-duration: 125ms
-webkit-transition-timing-function: ease
&:hover
-webkit-box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85)
-moz-box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85)
box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85)
.pag_next
background-color: #ff4415
border-color: #ff4415
.pag_prev
background-color: #424242
border-color: #424242
color: #CCCCCC
.footer
font-size: 12px
text-align: center
color: #fff
a
color: #fff
text-decoration: none
border-bottom: 1px solid transparent
border-bottom-color: transparent
&:hover
border-bottom-color: #fff

View File

@ -0,0 +1,156 @@
/*
* Rangitaki Project
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
body
font-family: "Roboto", sans-serif
background: #f6f6f6
color: #383838
.header
background-color: #ff4415
position: fixed
box-shadow: 2px 0 2px 2px rgba(62, 62, 62, 0.45)
.title
color: #fff
position: absolute
> a
color: #fff
.fadeout
background: -moz-linear-gradient(left, rgba(30, 87, 153, 0) 0%, #ff4415 100%)
background: -webkit-linear-gradient(left, rgba(30, 87, 153, 0) 0%, #ff4415 100%)
background: -o-linear-gradient(left, rgba(30, 87, 153, 0) 0%, #ff4415 100%)
background: -ms-linear-gradient(left, rgba(30, 87, 153, 0) 0%, #ff4415 100%)
background: linear-gradient(to right, rgba(30, 87, 153, 0) 0%, #ff4415 100%)
/* W3C
.nav
background-color: #fff
border-right: 1px solid #e0e0e0
.nav-item, .nav-item-static
color: #383838
.nav-item
font-weight: 600
&:hover, &:active
background-color: #e2e2e2
.divider
border-bottom: 1px solid #e0e0e0
.card
background: #fff
border-radius: 2px
box-shadow: 0 1px 1.5px 1.5px rgba(62, 62, 62, 0.3)
a
color: #ff4415
text-decoration: none
border-bottom: 1px solid transparent
border-bottom-color: transparent
transition: border-bottom-color 150ms ease-in-out 100ms
&:hover
border-bottom-color: #ff4415
.headline
font-size: 24px
color: #383838 !important
text-decoration: none
border-bottom: none !important
&:hover
color: #ff4415 !important
.date
font-size: 13px
.articletext
font-size: 14px
line-height: 24px
.author, .tag
font-size: 13px
.fab
background-color: #ff4415
box-shadow: 0 1px 1.5px 1.5px rgba(62, 62, 62, 0.3)
.subfab
background-color: #fff
box-shadow: 0 1px 1.5px 1.5px rgba(62, 62, 62, 0.3)
.button
text-transform: uppercase
border-width: 1px
border-style: solid
-webkit-box-shadow: 0.4px 1px 1.5px 1px #aaa
-moz-box-shadow: 0.4px 1px 1.5px 1px #aaa
box-shadow: 0.4px 1px 1.5px 1px #aaa
border-radius: 2px
letter-spacing: 0.4px
font-weight: 700
font-size: 14px
transition-property: box-shadow
transition-delay: 50ms
transition-duration: 125ms
transition-timing-function: ease
-o-transition-property: box-shadow
-o-transition-delay: 50ms
-o-transition-duration: 125ms
-o-transition-timing-function: ease
-moz-transition-property: box-shadow
-moz-transition-delay: 50ms
-moz-transition-duration: 125ms
-moz-transition-timing-function: ease
-webkit-transition-property: box-shadow
-webkit-transition-delay: 50ms
-webkit-transition-duration: 125ms
-webkit-transition-timing-function: ease
&:hover
-webkit-box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa
-moz-box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa
box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa
.pag_next
background-color: #ff4415
border-color: #ff4415
.pag_prev
background-color: #fff
border-color: #fff
color: #383838
.footer
font-size: 12px
text-align: center
a
color: #383838
text-decoration: none
border-bottom: 1px solid transparent
border-bottom-color: transparent
&:hover
border-bottom-color: #383838

46
src/sass/no-nav.sass Normal file
View File

@ -0,0 +1,46 @@
/*
* Rangitaki Project
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* A stylesheet for overriding the default styles if the navigation drawer is disabled
*/
.nav
display: none
.nav-img
display: none
@media screen and (min-width: 1440px)
.header
left: 0
.main
margin-left: 0
width: 100%
@media screen and (max-width: 720px)
.title
left: 25px

249
src/sass/rangitaki.sass Normal file
View File

@ -0,0 +1,249 @@
/*
* Rangitaki Project
*
* The MIT License
*
* Copyright 2015 mmk2410.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* A stylesheet for overriding the default styles if the navigation drawer is disabled
*/
/* BODY */
body
margin-top: 94px
.main
height: 100%
margin-left: 0
.overlay
opacity: 0
position: fixed
top: 0
left: 0
background-color: black
width: 100%
z-index: 30
height: 100%
display: none
/* HEADER */
.header
top: 0
right: 0
left: 0
width: 100%
height: 64px
position: absolute
.title
color: #fff
font-size: 23px
text-decoration: none
line-height: 64px
vertical-align: middle
left: 75px
.title > a
text-decoration: none
color: #fff
.fadeout
position: absolute
height: 64px
top: 0
right: 0
width: 40px
.nav-img
height: 26px
padding: 19px
cursor: pointer
/* NAV DRAWER */
.nav
width: 300px
position: fixed
height: 100%
top: 0
left: -301px
z-index: 40
.nav-item, .nav-item-static
text-decoration: none
text-indent: 0
display: inline-block
height: 48px
vertical-align: middle
width: 284px
line-height: 48px
padding-left: 16px
transition: background-color 125ms ease-in-out 0ms
.nav-close
cursor: pointer
.nav-close-img
height: 35px
padding: 12px
.nav-item
cursor: pointer
.divider
width: 100%
/* MAIN */
.card
margin-right: auto
margin-left: auto
width: 75%
padding: 24px
margin-bottom: 40px
max-width: 1160px
.card a
-moz-hyphens: auto
-epub-hyphens: auto
-ms-hyphens: auto
-webkit-hyphens: auto
hyphens: auto
word-wrap: break-word
.card a:hover
.headline
display: block
padding-bottom: 8px
.card img
max-width: 100%
max-height: 400px
display: block
margin-left: auto
margin-right: auto
.date
.articletext
.author
display: block
.tag
/* FAB */
.fabmenu
position: fixed
bottom: 20px
right: 20px
.fab
height: 60px
width: 60px
border-radius: 30px
cursor: pointer
.fab-img
width: 28px
padding: 15px
.subfab
height: 45px
width: 45px
border-radius: 30px
margin-right: auto
margin-left: auto
margin-bottom: 25px
display: none
.subfab-img
width: 22px
padding: 12px
/* BUTTON */
.pag_buttons
margin-right: auto
margin-left: auto
width: calc(75% + 48px)
margin-bottom: 80px
max-width: 1160px
text-align: right
.button
text-decoration: none
color: #fff
line-height: 36px
min-width: 64px
text-align: center
height: 36px
padding: 8px
margin-top: 4px
margin-bottom: 5px
cursor: pointer
.pag_next
margin-left: 8px
/* FOOTER */
.footer
.footer a
transition: border-bottom-color 150ms ease-in-out 100ms
.footer a:hover
border-bottom-color: #383838
@media screen and (min-width: 1440px)
.nav
left: 0
padding-top: 64px
.nav-close-img
display: none
.nav-close
display: none
.nav-img
display: none
.header
left: 300px
.main
margin-left: 300px
width: calc(100% - 300px)
@media screen and (max-width: 720px)
.card
width: 82%
.pag_buttons
width: calc(82% + 48px)

View File

@ -1,207 +1,3 @@
/*
The MIT License
body{font-family:"Roboto", sans-serif;background:#f6f6f6;color:#383838;background-image:url(//example.com/res/img/intro.svg);background-size:cover;background-attachment:fixed;background-position:top center;background-repeat:no-repeat}.header{background-color:rgba(0,0,0,0.45);position:fixed}.title{color:#fff;position:absolute}.title>a{color:#fff}.fadeout{background:-moz-linear-gradient(left, rgba(30,87,153,0) 0%, rgba(0,0,0,0.45) 100%);background:-webkit-linear-gradient(left, rgba(30,87,153,0) 0%, rgba(0,0,0,0.45) 100%);background:-o-linear-gradient(left, rgba(30,87,153,0) 0%, rgba(0,0,0,0.45) 100%);background:-ms-linear-gradient(left, rgba(30,87,153,0) 0%, rgba(0,0,0,0.45) 100%);background:linear-gradient(to right, rgba(30,87,153,0) 0%, rgba(0,0,0,0.45) 100%)}.nav{background-color:#fff;border-right:1px solid #e0e0e0}.nav-item,.nav-item-static{color:#383838}.nav-item{font-weight:600}.nav-item:active{background-color:#e2e2e2}.divider{border-bottom:1px solid #e0e0e0}.card{background:#fff;border-radius:2px;box-shadow:0 1px 1.5px 1.5px rgba(62,62,62,0.3)}.card a{color:#ff4415;text-decoration:none;border-bottom:1px solid transparent;border-bottom-color:transparent;transition:border-bottom-color 150ms ease-in-out 100ms}.card a:hover{border-bottom-color:#ff4415}.headline{font-size:24px;color:#383838 !important;text-decoration:none;border-bottom:none !important}.headline:hover{color:#ff4415 !important}.date{font-size:13px}.articletext{font-size:14px;line-height:24px}.author{font-size:13px}.tag{font-size:13px}.fab{background-color:#ff4415;box-shadow:0 1px 1.5px 1.5px rgba(62,62,62,0.3)}.subfab{background-color:#fff;box-shadow:0 1px 1.5px 1.5px rgba(62,62,62,0.3)}.button{border-width:1px;border-style:solid;text-transform:uppercase;-webkit-box-shadow:0 1px 1.5px 1.5px rgba(42,42,42,0.65);-moz-box-shadow:0 1px 1.5px 1.5px rgba(42,42,42,0.65);box-shadow:0 1px 1.5px 1.5px rgba(42,42,42,0.65);border-radius:2px;letter-spacing:0.4px;font-weight:700;font-size:14px;transition-property:box-shadow;transition-delay:50ms;transition-duration:125ms;transition-timing-function:ease;-o-transition-property:box-shadow;-o-transition-delay:50ms;-o-transition-duration:125ms;-o-transition-timing-function:ease;-moz-transition-property:box-shadow;-moz-transition-delay:50ms;-moz-transition-duration:125ms;-moz-transition-timing-function:ease;-webkit-transition-property:box-shadow;-webkit-transition-delay:50ms;-webkit-transition-duration:125ms;-webkit-transition-timing-function:ease}.button:hover,.button:hover{-webkit-box-shadow:0.5px 1.8px 2.1px 1.4px rgba(32,32,32,0.85);-moz-box-shadow:0.5px 1.8px 2.1px 1.4px rgba(32,32,32,0.85);box-shadow:0.5px 1.8px 2.1px 1.4px rgba(32,32,32,0.85)}.pag_next{background-color:#ff4415;border-color:#ff4415}.pag_prev{background-color:#fff;border-color:#fff;color:#383838}.footer{font-size:12px;text-align:center;color:#fff;text-shadow:1px 1px rgba(55,55,55,0.3)}.footer a{color:#fff;text-shadow:1px 1px rgba(175,175,175,0.3);text-decoration:none;border-bottom:1px solid transparent;border-bottom-color:transparent}.footer a:hover{border-bottom-color:#fff}@media screen and (min-width: 1440px){.nav{background-color:rgba(255,255,255,0.8)}}
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
Created on : Jun 18, 2015, 6:39:37 PM
Author : mmk2410
*/
body{
font-family: "Roboto", sans-serif;
background: #f6f6f6;
color: #383838;
background-image: url(//marcel-kapfer.de/res/img/intro.svg);
background-size: cover;
background-attachment: fixed;
background-position: top center;
background-repeat: no-repeat;
}
.header{
background-color: rgba(0, 0, 0, 0.45);
position: fixed;
}
.title{
color: #fff;
position: absolute;
}
.title > a{
color: #fff;
}
.fadeout{
background: -moz-linear-gradient(left, rgba(30,87,153,0) 0%, rgba(0, 0, 0, 0.45) 100%); /* FF3.6+ */
background: -webkit-linear-gradient(left, rgba(30,87,153,0) 0%,rgba(0, 0, 0, 0.45) 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(left, rgba(30,87,153,0) 0%,rgba(0, 0, 0, 0.45) 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(left, rgba(30,87,153,0) 0%,rgba(0, 0, 0, 0.45) 100%); /* IE10+ */
background: linear-gradient(to right, rgba(30,87,153,0) 0%,rgba(0, 0, 0, 0.45) 100%); /* W3C */
}
.nav{
background-color: #fff;
border-right: 1px solid #e0e0e0;
}
.nav-item, .nav-item-static{
color: #383838;
}
.nav-item{
font-weight: 600;
}
.nav-item:active{
background-color: #e2e2e2;
}
.divider{
border-bottom: 1px solid #e0e0e0;
}
.card{
background: #fff;
border-radius: 2px;
box-shadow: 0px 1px 1.5px 1.5px rgba(62, 62, 62, 0.3);
}
.card a{
color: #ff4415;
text-decoration: none;
border-bottom: 1px solid transparent;
border-bottom-color: transparent;
transition: border-bottom-color 150ms ease-in-out 100ms;
}
.card a:hover{
border-bottom-color: #ff4415;
}
.headline{
font-size: 24px;
color: #383838!important;
text-decoration: none;
border-bottom: none!important;
}
.headline:hover {
color: #ff4415!important;
}
.date{
font-size: 13px;
}
.articletext{
font-size: 14px;
line-height: 24px;
}
.author{
font-size: 13px;
}
.tag{
font-size: 13px;
}
.fab{
background-color: #ff4415;
box-shadow: 0px 1px 1.5px 1.5px rgba(62, 62, 62, 0.3);
}
.subfab{
background-color: #fff;
box-shadow: 0px 1px 1.5px 1.5px rgba(62, 62, 62, 0.3);
}
.button {
border-width: 1px;
border-style: solid;
text-transform: uppercase;
box-shadow: 0px 1px 1.5px 1.5px rgba(42, 42, 42, 0.65);
-moz-box-shadow: 0px 1px 1.5px 1.5px rgba(42, 42, 42, 0.65);
-webkit-box-shadow: 0px 1px 1.5px 1.5px rgba(42, 42, 42, 0.65);
border-radius: 2px;
letter-spacing: 0.4px;
font-weight: 700;
font-size: 14px;
transition-property: box-shadow;
transition-delay: 50ms;
transition-duration: 125ms;
transition-timing-function: ease;
-moz-transition-property: box-shadow;
-moz-transition-delay: 50ms;
-moz-transition-duration: 125ms;
-moz-transition-timing-function: ease;
-webkit-transition-property: box-shadow;
-webkit-transition-delay: 50ms;
-webkit-transition-duration: 125ms;
-webkit-transition-timing-function: ease;
}
.button:hover, .button:hover {
box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85);
-moz-box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85);
-webkit-box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85);
}
.pag_next{
background-color: #ff4415;
border-color: #ff4415;
}
.pag_prev{
background-color: #fff;
border-color: #fff;
color: #383838;
}
.footer{
font-size: 12px;
text-align: center;
color: #fff;
text-shadow: 1px 1px rgba(55, 55, 55, 0.3);
}
.footer a{
color: #fff;
text-shadow: 1px 1px rgba(175, 175, 175, 0.3);
text-decoration: none;
border-bottom: 1px solid transparent;
border-bottom-color: transparent;
}
.footer a:hover{
border-bottom-color: #fff;
}
@media screen and (min-width: 1440px) {
.nav{
background-color: rgba(255, 255, 255, 0.8);
}
}
/*# sourceMappingURL=background-img.css.map */

File diff suppressed because one or more lines are too long

View File

@ -1,193 +1,3 @@
/*
The MIT License
body{font-family:"Roboto", sans-serif;background:#303030;color:#fff}.header{background-color:#212121;position:fixed;box-shadow:2px 0 2px 2px rgba(32,32,32,0.85)}.title{color:#fff;position:absolute}.title>a{color:#fff}.fadeout{background:-moz-linear-gradient(left, rgba(30,87,153,0) 0%, #212121 100%);background:-webkit-linear-gradient(left, rgba(30,87,153,0) 0%, #212121 100%);background:-o-linear-gradient(left, rgba(30,87,153,0) 0%, #212121 100%);background:-ms-linear-gradient(left, rgba(30,87,153,0) 0%, #212121 100%);background:linear-gradient(to right, rgba(30,87,153,0) 0%, #212121 100%)}.nav{background-color:#424242;border-right:1px solid #1f1f1f}.nav-item,.nav-item-static{color:#fff}.nav-item{font-weight:600}.nav-item:active{background-color:#383838}.divider{border-bottom:1px solid #1f1f1f}.card{background:#424242;border-radius:2px;box-shadow:0 1px 1.5px 1.5px rgba(42,42,42,0.65)}.card a{color:#ff4415;text-decoration:none;border-bottom:1px solid transparent;border-bottom-color:transparent;transition:border-bottom-color 150ms ease-in-out 100ms}.card a:hover{border-bottom-color:#ff4415}.headline{font-size:24px;color:#fff !important;text-decoration:none;border-bottom:none !important}.headline:hover{color:#ff4415 !important}.date{font-size:13px}.articletext{font-size:14px;line-height:24px}.author,.tag{font-size:13px}.fab{background-color:#ff4415;box-shadow:0 1px 1.5px 1.5px rgba(42,42,42,0.65)}.subfab{background-color:#424242;box-shadow:0 1px 1.5px 1.5px rgba(42,42,42,0.65)}.button{text-transform:uppercase;border-width:1px;border-style:solid;-webkit-box-shadow:0 1px 1.5px 1.5px rgba(42,42,42,0.65);-moz-box-shadow:0 1px 1.5px 1.5px rgba(42,42,42,0.65);box-shadow:0 1px 1.5px 1.5px rgba(42,42,42,0.65);border-radius:2px;letter-spacing:0.4px;font-weight:700;font-size:14px;transition-property:box-shadow;transition-delay:50ms;transition-duration:125ms;transition-timing-function:ease;-o-transition-property:box-shadow;-o-transition-delay:50ms;-o-transition-duration:125ms;-o-transition-timing-function:ease;-moz-transition-property:box-shadow;-moz-transition-delay:50ms;-moz-transition-duration:125ms;-moz-transition-timing-function:ease;-webkit-transition-property:box-shadow;-webkit-transition-delay:50ms;-webkit-transition-duration:125ms;-webkit-transition-timing-function:ease}.button:hover{-webkit-box-shadow:0.5px 1.8px 2.1px 1.4px rgba(32,32,32,0.85);-moz-box-shadow:0.5px 1.8px 2.1px 1.4px rgba(32,32,32,0.85);box-shadow:0.5px 1.8px 2.1px 1.4px rgba(32,32,32,0.85)}.pag_next{background-color:#ff4415;border-color:#ff4415}.pag_prev{background-color:#424242;border-color:#424242;color:#CCCCCC}.footer{font-size:12px;text-align:center;color:#fff}.footer a{color:#fff;text-decoration:none;border-bottom:1px solid transparent;border-bottom-color:transparent}.footer a:hover{border-bottom-color:#fff}
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
Created on : Jun 18, 2015, 6:39:37 PM
Author : mmk2410
*/
body{
font-family: "Roboto", sans-serif;
background: #303030;
color: #fff;
}
.header{
background-color: #212121;
position: fixed;
box-shadow: 2px 0px 2px 2px rgba(32, 32, 32, 0.85);
}
.title{
color: #fff;
position: absolute;
}
.title > a{
color: #fff;
}
.fadeout{
background: -moz-linear-gradient(left, rgba(30,87,153,0) 0%, #212121 100%); /* FF3.6+ */
background: -webkit-linear-gradient(left, rgba(30,87,153,0) 0%,#212121 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(left, rgba(30,87,153,0) 0%,#212121 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(left, rgba(30,87,153,0) 0%,#212121 100%); /* IE10+ */
background: linear-gradient(to right, rgba(30,87,153,0) 0%,#212121 100%); /* W3C */
}
.nav{
background-color: #424242;
border-right: 1px solid #1f1f1f;
}
.nav-item, .nav-item-static{
color: #fff;
}
.nav-item{
font-weight: 600;
}
.nav-item:active{
background-color: #383838;
}
.divider{
border-bottom: 1px solid #1f1f1f;
}
.card{
background: #424242;
border-radius: 2px;
box-shadow: 0px 1px 1.5px 1.5px rgba(42, 42, 42, 0.65);
}
.card a{
color: #ff4415;
text-decoration: none;
border-bottom: 1px solid transparent;
border-bottom-color: transparent;
transition: border-bottom-color 150ms ease-in-out 100ms;
}
.card a:hover{
border-bottom-color: #ff4415;
}
.headline{
font-size: 24px;
color: #fff!important;
text-decoration: none;
border-bottom: none!important;
}
.headline:hover {
color: #ff4415!important;
}
.date{
font-size: 13px;
}
.articletext{
font-size: 14px;
line-height: 24px;
}
.author{
font-size: 13px;
}
.tag{
font-size: 13px;
}
.fab{
background-color: #ff4415;
box-shadow: 0px 1px 1.5px 1.5px rgba(42, 42, 42, 0.65);
}
.subfab{
background-color: #424242;
box-shadow: 0px 1px 1.5px 1.5px rgba(42, 42, 42, 0.65);
}
.button{
text-transform: uppercase;
border-width: 1px;
border-style: solid;
box-shadow: 0px 1px 1.5px 1.5px rgba(42, 42, 42, 0.65);
-moz-box-shadow: 0px 1px 1.5px 1.5px rgba(42, 42, 42, 0.65);
-webkit-box-shadow: 0px 1px 1.5px 1.5px rgba(42, 42, 42, 0.65);
border-radius: 2px;
letter-spacing: 0.4px;
font-weight: 700;
font-size: 14px;
transition-property: box-shadow;
transition-delay: 50ms;
transition-duration: 125ms;
transition-timing-function: ease;
-moz-transition-property: box-shadow;
-moz-transition-delay: 50ms;
-moz-transition-duration: 125ms;
-moz-transition-timing-function: ease;
-webkit-transition-property: box-shadow;
-webkit-transition-delay: 50ms;
-webkit-transition-duration: 125ms;
-webkit-transition-timing-function: ease;
}
.button:hover, .button:hover {
box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85);
-moz-box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85);
-webkit-box-shadow: 0.5px 1.8px 2.1px 1.4px rgba(32, 32, 32, 0.85);
}
.pag_next{
background-color: #ff4415;
border-color: #ff4415;
}
.pag_prev{
background-color: #424242;
border-color: #424242;
color: #CCCCCC;
}
.footer{
font-size: 12px;
text-align: center;
color: #fff;
}
.footer a{
color: #fff;
text-decoration: none;
border-bottom: 1px solid transparent;
border-bottom-color: transparent;
}
.footer a:hover{
border-bottom-color: #fff;
}
/*# sourceMappingURL=material-dark.css.map */

File diff suppressed because one or more lines are too long

View File

@ -1,196 +1,3 @@
/*
The MIT License
body{font-family:"Roboto", sans-serif;background:#f6f6f6;color:#383838}.header{background-color:#ff4415;position:fixed;box-shadow:2px 0 2px 2px rgba(62,62,62,0.45)}.title{color:#fff;position:absolute}.title>a{color:#fff}.fadeout{background:-moz-linear-gradient(left, rgba(30,87,153,0) 0%, #ff4415 100%);background:-webkit-linear-gradient(left, rgba(30,87,153,0) 0%, #ff4415 100%);background:-o-linear-gradient(left, rgba(30,87,153,0) 0%, #ff4415 100%);background:-ms-linear-gradient(left, rgba(30,87,153,0) 0%, #ff4415 100%);background:linear-gradient(to right, rgba(30,87,153,0) 0%, #ff4415 100%)}.nav{background-color:#fff;border-right:1px solid #e0e0e0}.nav-item,.nav-item-static{color:#383838}.nav-item{font-weight:600}.nav-item:hover,.nav-item:active{background-color:#e2e2e2}.divider{border-bottom:1px solid #e0e0e0}.card{background:#fff;border-radius:2px;box-shadow:0 1px 1.5px 1.5px rgba(62,62,62,0.3)}.card a{color:#ff4415;text-decoration:none;border-bottom:1px solid transparent;border-bottom-color:transparent;transition:border-bottom-color 150ms ease-in-out 100ms}.card a:hover{border-bottom-color:#ff4415}.headline{font-size:24px;color:#383838 !important;text-decoration:none;border-bottom:none !important}.headline:hover{color:#ff4415 !important}.date{font-size:13px}.articletext{font-size:14px;line-height:24px}.author,.tag{font-size:13px}.fab{background-color:#ff4415;box-shadow:0 1px 1.5px 1.5px rgba(62,62,62,0.3)}.subfab{background-color:#fff;box-shadow:0 1px 1.5px 1.5px rgba(62,62,62,0.3)}.button{text-transform:uppercase;border-width:1px;border-style:solid;-webkit-box-shadow:0.4px 1px 1.5px 1px #aaa;-moz-box-shadow:0.4px 1px 1.5px 1px #aaa;box-shadow:0.4px 1px 1.5px 1px #aaa;border-radius:2px;letter-spacing:0.4px;font-weight:700;font-size:14px;transition-property:box-shadow;transition-delay:50ms;transition-duration:125ms;transition-timing-function:ease;-o-transition-property:box-shadow;-o-transition-delay:50ms;-o-transition-duration:125ms;-o-transition-timing-function:ease;-moz-transition-property:box-shadow;-moz-transition-delay:50ms;-moz-transition-duration:125ms;-moz-transition-timing-function:ease;-webkit-transition-property:box-shadow;-webkit-transition-delay:50ms;-webkit-transition-duration:125ms;-webkit-transition-timing-function:ease}.button:hover{-webkit-box-shadow:0.5px 1.8px 2.1px 1.4px #aaa;-moz-box-shadow:0.5px 1.8px 2.1px 1.4px #aaa;box-shadow:0.5px 1.8px 2.1px 1.4px #aaa}.pag_next{background-color:#ff4415;border-color:#ff4415}.pag_prev{background-color:#fff;border-color:#fff;color:#383838}.footer{font-size:12px;text-align:center}.footer a{color:#383838;text-decoration:none;border-bottom:1px solid transparent;border-bottom-color:transparent}.footer a:hover{border-bottom-color:#383838}
Copyright 2015 mmk2410.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
Created on : Jun 18, 2015, 6:39:37 PM
Author : mmk2410
*/
body{
font-family: "Roboto", sans-serif;
background: #f6f6f6;
color: #383838;
}
.header{
background-color: #ff4415;
position: fixed;
box-shadow: 2px 0px 2px 2px rgba(62, 62, 62, 0.45);
}
.title{
color: #fff;
position: absolute;
}
.title > a{
color: #fff;
}
.fadeout{
background: -moz-linear-gradient(left, rgba(30,87,153,0) 0%, #ff4415 100%); /* FF3.6+ */
background: -webkit-linear-gradient(left, rgba(30,87,153,0) 0%,#ff4415 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(left, rgba(30,87,153,0) 0%,#ff4415 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(left, rgba(30,87,153,0) 0%,#ff4415 100%); /* IE10+ */
background: linear-gradient(to right, rgba(30,87,153,0) 0%,#ff4415 100%); /* W3C */
}
.nav{
background-color: #fff;
border-right: 1px solid #e0e0e0;
}
.nav-item, .nav-item-static{
color: #383838;
}
.nav-item{
font-weight: 600;
}
.nav-item:hover{
background-color: #e2e2e2;
}
.nav-item:active{
background-color: #e2e2e2;
}
.divider{
border-bottom: 1px solid #e0e0e0;
}
.card{
background: #fff;
border-radius: 2px;
box-shadow: 0px 1px 1.5px 1.5px rgba(62, 62, 62, 0.3);
}
.card a{
color: #ff4415;
text-decoration: none;
border-bottom: 1px solid transparent;
border-bottom-color: transparent;
transition: border-bottom-color 150ms ease-in-out 100ms;
}
.card a:hover{
border-bottom-color: #ff4415;
}
.headline{
font-size: 24px;
color: #383838!important;
text-decoration: none;
border-bottom: none!important;
}
.headline:hover {
color: #ff4415!important;
}
.date{
font-size: 13px;
}
.articletext{
font-size: 14px;
line-height: 24px;
}
.author{
font-size: 13px;
}
.tag{
font-size: 13px;
}
.fab{
background-color: #ff4415;
box-shadow: 0px 1px 1.5px 1.5px rgba(62, 62, 62, 0.3);
}
.subfab{
background-color: #fff;
box-shadow: 0px 1px 1.5px 1.5px rgba(62, 62, 62, 0.3);
}
.button {
text-transform: uppercase;
border-width: 1px;
border-style: solid;
box-shadow: 0.4px 1px 1.5px 1px #aaa;
-moz-box-shadow: 0.4px 1px 1.5px 1px #aaa;
-webkit-box-shadow: 0.4px 1px 1.5px 1px #aaa;
border-radius: 2px;
letter-spacing: 0.4px;
font-weight: 700;
font-size: 14px;
transition-property: box-shadow;
transition-delay: 50ms;
transition-duration: 125ms;
transition-timing-function: ease;
-moz-transition-property: box-shadow;
-moz-transition-delay: 50ms;
-moz-transition-duration: 125ms;
-moz-transition-timing-function: ease;
-webkit-transition-property: box-shadow;
-webkit-transition-delay: 50ms;
-webkit-transition-duration: 125ms;
-webkit-transition-timing-function: ease;
}
.button:hover, .button:hover {
box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa;
-moz-box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa;
-webkit-box-shadow: 0.5px 1.8px 2.1px 1.4px #aaa;
}
.pag_next{
background-color: #ff4415;
border-color: #ff4415;
}
.pag_prev{
background-color: #fff;
border-color: #fff;
color: #383838;
}
.footer{
font-size: 12px;
text-align: center;
}
.footer a{
color: #383838;
text-decoration: none;
border-bottom: 1px solid transparent;
border-bottom-color: transparent;
}
.footer a:hover{
border-bottom-color: #383838;
}
/*# sourceMappingURL=material-light.css.map */

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ conf="./config.php"
echo "Downloading version $version from GitHub..."
mkdir $new
cd $new || exit
wget -c https://github.com/mmk2410/Rangitaki/archive/"$version".zip
wget -c https://github.com/mmk2410/Rangitaki/archive/v"$version".zip
echo "Extracting"
unzip v"$version".zip

View File

@ -0,0 +1,80 @@
#!/bin/bash
# Update script for Rangitaki from version 1.3.0 to 1.4.0
version="1.4.0"
new="./rbe-new"
echo "Downloading version $version from GitLab..."
git clone https://gitlab.com/mmk2410/rangitaki.git "$new"
if [[ $1 == "--debug" ]]; then
cd $new
git checkout master
cd ../
fi
echo "Updating ressources..."
rm -rf ./res/
mv $new/res/ ./
echo "Updating extensions..."
rm ./extensions/example.js
mv $new/extensions/* ./extensions/
echo "Importing binaries..."
mv $new/bin/ ./
echo "Importing source files..."
mv $new/src/ ./
echo "Updating extensions..."
rm ./themes/material-light.css
rm ./themes/material-dark.css
rm ./themes/background-img.css
mv $new/themes/* ./themes/
echo "Updating RCC..."
rm -rf ./rcc
mv $new/rcc ./
rm ./rcc/password.php
echo "Updating core..."
rm ./index.php
mv $new/index.php ./
echo "Preparing composer..."
rm -rf ./vendor/
rm composer.lock
rm composer.json
mv $new/vendor ./
mv $new/composer.lock ./
mv $new/composer.json ./
echo "Preparing npm..."
mv $new/package.json ./
echo "Updating Changelog..."
if [ -f ./CHANGELOG.txt ]; then
rm CHANGELOG.txt
fi
mv $new/CHANGELOG.md ./
echo "Preparing gulp..."
mv $new/gulpfile.coffee ./
echo "Cleaning up..."
if [[ $1 != "--debug" ]]; then
rm -rf $new
fi
echo "Update config file..."
php bin/config.php
if [ -d "./update-scripts" ]; then
echo "Remove obsolete update scripts folder."
rm -rf "./update-scripts"
fi
echo "Your Rangitaki installation is updated to version $version"

2
vendor/autoload.php vendored
View File

@ -4,4 +4,4 @@
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInite149f47a700f596a9845d47c917afeaf::getLoader();
return ComposerAutoloaderInitd1db2574a85c0ba6f142743249ba228f::getLoader();

View File

@ -0,0 +1,5 @@
# Test Files #
test/config/test.sqlite
vendor
composer.lock
.idea

View File

@ -0,0 +1,25 @@
language: php
sudo: false
php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
env:
global:
- SKIP_MONGO_TESTS=1
- secure: Bc5ZqvZ1YYpoPZNNuU2eCB8DS6vBYrAdfBtTenBs5NSxzb+Vjven4kWakbzaMvZjb/Ib7Uph7DGuOtJXpmxnvBXPLd707LZ89oFWN/yqQlZKCcm8iErvJCB5XL+/ONHj2iPdR242HJweMcat6bMCwbVWoNDidjtWMH0U2mYFy3M=
- secure: R3bXlymyFiY2k2jf7+fv/J8i34wtXTkmD4mCr5Ps/U+vn9axm2VtvR2Nj+r7LbRjn61gzFE/xIVjYft/wOyBOYwysrfriydrnRVS0owh6y+7EyOyQWbRX11vVQMf8o31QCQE5BY58V5AJZW3MjoOL0FVlTgySJiJvdw6Pv18v+E=
services:
- mongodb
- redis-server
- cassandra
before_script:
- psql -c 'create database oauth2_server_php;' -U postgres
- composer require predis/predis:dev-master
- composer require thobbs/phpcassa:dev-master
- composer require 'aws/aws-sdk-php:~2.8'
- composer require 'firebase/php-jwt:~2.2'
after_script:
- php test/cleanup.php

View File

@ -0,0 +1,165 @@
CHANGELOG for 1.x
=================
This changelog references the relevant changes (bug and security fixes) done
in 1.x minor versions.
To see the files changed for a given bug, go to https://github.com/bshaffer/oauth2-server-php/issues/### where ### is the bug number
To get the diff between two versions, go to https://github.com/bshaffer/oauth2-server-php/compare/v1.0...v1.1
To get the diff for a specific change, go to https://github.com/bshaffer/oauth2-server-php/commit/XXX where XXX is the change hash
* 1.8.0 (2015-09-18)
PR: https://github.com/bshaffer/oauth2-server-php/pull/643
* bug #594 - adds jti
* bug #598 - fixes lifetime configurations for JWTs
* bug #634 - fixes travis builds, upgrade to containers
* bug #586 - support for revoking tokens
* bug #636 - Adds FirebaseJWT bridge
* bug #639 - Mongo HHVM compatibility
* 1.7.0 (2015-04-23)
PR: https://github.com/bshaffer/oauth2-server-php/pull/572
* bug #500 - PDO fetch mode changed from FETCH_BOTH to FETCH_ASSOC
* bug #508 - Case insensitive for Bearer token header name ba716d4
* bug #512 - validateRedirectUri is now public
* bug #530 - Add PublicKeyInterface, UserClaimsInterface to Cassandra Storage
* bug #505 - DynamoDB storage fixes
* bug #556 - adds "code id_token" return type to openid connect
* bug #563 - Include "issuer" config key for JwtAccessToken
* bug #564 - Fixes JWT vulnerability
* bug #571 - Added unset_refresh_token_after_use option
* 1.6 (2015-01-16)
PR: https://github.com/bshaffer/oauth2-server-php/pull/496
* bug 437 - renames CryptoToken to JwtAccessToken / use_crypto_tokens to use_jwt_access_tokens
* bug 447 - Adds a Couchbase storage implementation
* bug 460 - Rename JWT claims to match spec
* bug 470 - order does not matter for multi-valued response types
* bug 471 - Make validateAuthorizeRequest available for POST in addition to GET
* bug 475 - Adds JTI table definitiion
* bug 481 - better randomness for generating access tokens
* bug 480 - Use hash_equals() for signature verification (prevents remote timing attacks)
* bugs 489, 491, 498 - misc other fixes
* 1.5 (2014-08-27)
PR: https://github.com/bshaffer/oauth2-server-php/pull/446
* bug #399 - Add DynamoDB Support
* bug #404 - renamed error name for malformed/expired tokens
* bug #412 - Openid connect: fixes for claims with more than one scope / Add support for the prompt parameter ('consent' and 'none')
* bug #411 - fixes xml output
* bug #413 - fixes invalid format error
* bug #401 - fixes code standards / whitespace
* bug #354 - bundles PDO SQL with the library
* [BC] bug #397 - refresh tokens should not be encrypted
* bug #423 - makes "scope" optional for refresh token storage
* 1.4 (2014-06-12)
PR: https://github.com/bshaffer/oauth2-server-php/pull/392
* bug #189 Storage\PDO - allows DSN string in constructor
* bug #233 Bearer Tokens - allows token in request body for PUT requests
* bug #346 Fixes open_basedir warning
* bug #351 Adds OpenID Connect support
* bug #355 Adds php 5.6 and HHVM to travis.ci testing
* [BC] bug #358 Adds `getQuerystringIdentifier()` to the GrantType interface
* bug #363 Encryption\JWT - Allows for subclassing JWT Headers
* bug #349 Bearer Tokens - adds requestHasToken method for when access tokens are optional
* bug #301 Encryption\JWT - fixes urlSafeB64Encode(): ensures newlines are replaced as expected
* bug #323 ResourceController - client_id is no longer required to be returned when calling getAccessToken
* bug #367 Storage\PDO - adds Postgres support
* bug #368 Access Tokens - use mcrypt_create_iv or openssl_random_pseudo_bytes to create token string
* bug #376 Request - allows case insensitive headers
* bug #384 Storage\PDO - can pass in PDO options in constructor of PDO storage
* misc fixes #361, #292, #373, #374, #379, #396
* 1.3 (2014-02-27)
PR: https://github.com/bshaffer/oauth2-server-php/pull/325
* bug #311 adds cassandra storage
* bug #298 fixes response code for user credentials grant type
* bug #318 adds 'use_crypto_tokens' config to Server class for better DX
* [BC] bug #320 pass client_id to getDefaultScope
* bug #324 better feedback when running tests
* bug #335 adds support for non-expiring refresh tokens
* bug #333 fixes Pdo storage for getClientKey
* bug #336 fixes Redis storage for expireAuthorizationCode
* 1.3 (2014-02-27)
PR: https://github.com/bshaffer/oauth2-server-php/pull/325
* bug #311 adds cassandra storage
* bug #298 fixes response code for user credentials grant type
* bug #318 adds 'use_crypto_tokens' config to Server class for better DX
* bug #320 pass client_id to getDefaultScope
* bug #324 better feedback when running tests
* bug #335 adds support for non-expiring refresh tokens
* bug #333 fixes Pdo storage for getClientKey
* bug #336 fixes Redis storage for expireAuthorizationCode
* 1.2 (2014-01-03)
PR: https://github.com/bshaffer/oauth2-server-php/pull/288
* bug #285 changed response header from 200 to 401 when empty token received
* bug #286 adds documentation and links to spec for not including error messages when no token is supplied
* bug #280 ensures PHP warnings do not get thrown as a result of an invalid argument to $jwt->decode()
* bug #279 predis wrong number of arguments
* bug #277 Securing JS WebApp client secret w/ password grant type
* 1.1 (2013-12-17)
PR: https://github.com/bshaffer/oauth2-server-php/pull/276
* bug #278 adds refresh token configuration to Server class
* bug #274 Supplying a null client_id and client_secret grants API access
* bug #244 [MongoStorage] More detailed implementation info
* bug #268 Implement jti for JWT Bearer tokens to prevent replay attacks.
* bug #266 Removing unused argument to getAccessTokenData
* bug #247 Make Bearer token type consistent
* bug #253 Fixing CryptoToken refresh token lifetime
* bug #246 refactors public key logic to be more intuitive
* bug #245 adds support for JSON crypto tokens
* bug #230 Remove unused columns in oauth_clients
* bug #215 makes Redis Scope Storage obey the same paradigm as PDO
* bug #228 removes scope group
* bug #227 squelches open basedir restriction error
* bug #223 Updated docblocks for RefreshTokenInterface.php
* bug #224 Adds protected properties
* bug #217 Implement ScopeInterface for PDO, Redis
* 1.0 (2013-08-12)
* bug #203 Add redirect\_status_code config param for AuthorizeController
* bug #205 ensures unnecessary ? is not set when ** bug
* bug #204 Fixed call to LogicException
* bug #202 Add explode to checkRestrictedGrant in PDO Storage
* bug #197 adds support for 'false' default scope ** bug
* bug #192 reference errors and adds tests
* bug #194 makes some appropriate properties ** bug
* bug #191 passes config to HttpBasic
* bug #190 validates client credentials before ** bug
* bug #171 Fix wrong redirect following authorization step
* bug #187 client_id is now passed to getDefaultScope().
* bug #176 Require refresh_token in getRefreshToken response
* bug #174 make user\_id not required for refresh_token grant
* bug #173 Duplication in JwtBearer Grant
* bug #168 user\_id not required for authorization_code grant
* bug #133 hardens default security for user object
* bug #163 allows redirect\_uri on authorization_code to be NULL in docs example
* bug #162 adds getToken on ResourceController for convenience
* bug #161 fixes fatal error
* bug #163 Invalid redirect_uri handling
* bug #156 user\_id in OAuth2\_Storage_AuthorizationCodeInterface::getAuthorizationCode() response
* bug #157 Fix for extending access and refresh tokens
* bug #154 ResponseInterface: getParameter method is used in the library but not defined in the interface
* bug #148 Add more detail to examples in Readme.md

View File

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2014 Brent Shaffer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,8 @@
oauth2-server-php
=================
[![Build Status](https://travis-ci.org/bshaffer/oauth2-server-php.svg?branch=develop)](https://travis-ci.org/bshaffer/oauth2-server-php)
[![Total Downloads](https://poser.pugx.org/bshaffer/oauth2-server-php/downloads.png)](https://packagist.org/packages/bshaffer/oauth2-server-php)
View the [complete documentation](http://bshaffer.github.io/oauth2-server-php-docs/)

View File

@ -0,0 +1,27 @@
{
"name": "bshaffer/oauth2-server-php",
"description":"OAuth2 Server for PHP",
"keywords":["oauth","oauth2","auth"],
"type":"library",
"license":"MIT",
"authors":[
{
"name":"Brent Shaffer",
"email": "bshafs@gmail.com",
"homepage":"http://brentertainment.com"
}
],
"homepage": "http://github.com/bshaffer/oauth2-server-php",
"require":{
"php":">=5.3.9"
},
"autoload": {
"psr-0": { "OAuth2": "src/" }
},
"suggest": {
"predis/predis": "Required to use the Redis storage engine",
"thobbs/phpcassa": "Required to use the Cassandra storage engine",
"aws/aws-sdk-php": "~2.8 is required to use the DynamoDB storage engine",
"firebase/php-jwt": "~2.2 is required to use JWT features"
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="test/bootstrap.php"
>
<testsuites>
<testsuite name="Oauth2 Test Suite">
<directory>./test/OAuth2/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/OAuth2/</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,48 @@
<?php
namespace OAuth2;
/**
* Autoloads OAuth2 classes
*
* @author Brent Shaffer <bshafs at gmail dot com>
* @license MIT License
*/
class Autoloader
{
private $dir;
public function __construct($dir = null)
{
if (is_null($dir)) {
$dir = dirname(__FILE__).'/..';
}
$this->dir = $dir;
}
/**
* Registers OAuth2\Autoloader as an SPL autoloader.
*/
public static function register($dir = null)
{
ini_set('unserialize_callback_func', 'spl_autoload_call');
spl_autoload_register(array(new self($dir), 'autoload'));
}
/**
* Handles autoloading of classes.
*
* @param string $class A class name.
*
* @return boolean Returns true if the class has been loaded
*/
public function autoload($class)
{
if (0 !== strpos($class, 'OAuth2')) {
return;
}
if (file_exists($file = $this->dir.'/'.str_replace('\\', '/', $class).'.php')) {
require $file;
}
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace OAuth2\ClientAssertionType;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* Interface for all OAuth2 Client Assertion Types
*/
interface ClientAssertionTypeInterface
{
public function validateRequest(RequestInterface $request, ResponseInterface $response);
public function getClientId();
}

View File

@ -0,0 +1,123 @@
<?php
namespace OAuth2\ClientAssertionType;
use OAuth2\Storage\ClientCredentialsInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* Validate a client via Http Basic authentication
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class HttpBasic implements ClientAssertionTypeInterface
{
private $clientData;
protected $storage;
protected $config;
/**
* @param OAuth2\Storage\ClientCredentialsInterface $clientStorage REQUIRED Storage class for retrieving client credentials information
* @param array $config OPTIONAL Configuration options for the server
* <code>
* $config = array(
* 'allow_credentials_in_request_body' => true, // whether to look for credentials in the POST body in addition to the Authorize HTTP Header
* 'allow_public_clients' => true // if true, "public clients" (clients without a secret) may be authenticated
* );
* </code>
*/
public function __construct(ClientCredentialsInterface $storage, array $config = array())
{
$this->storage = $storage;
$this->config = array_merge(array(
'allow_credentials_in_request_body' => true,
'allow_public_clients' => true,
), $config);
}
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$clientData = $this->getClientCredentials($request, $response)) {
return false;
}
if (!isset($clientData['client_id'])) {
throw new \LogicException('the clientData array must have "client_id" set');
}
if (!isset($clientData['client_secret']) || $clientData['client_secret'] == '') {
if (!$this->config['allow_public_clients']) {
$response->setError(400, 'invalid_client', 'client credentials are required');
return false;
}
if (!$this->storage->isPublicClient($clientData['client_id'])) {
$response->setError(400, 'invalid_client', 'This client is invalid or must authenticate using a client secret');
return false;
}
} elseif ($this->storage->checkClientCredentials($clientData['client_id'], $clientData['client_secret']) === false) {
$response->setError(400, 'invalid_client', 'The client credentials are invalid');
return false;
}
$this->clientData = $clientData;
return true;
}
public function getClientId()
{
return $this->clientData['client_id'];
}
/**
* Internal function used to get the client credentials from HTTP basic
* auth or POST data.
*
* According to the spec (draft 20), the client_id can be provided in
* the Basic Authorization header (recommended) or via GET/POST.
*
* @return
* A list containing the client identifier and password, for example
* @code
* return array(
* "client_id" => CLIENT_ID, // REQUIRED the client id
* "client_secret" => CLIENT_SECRET, // OPTIONAL the client secret (may be omitted for public clients)
* );
* @endcode
*
* @see http://tools.ietf.org/html/rfc6749#section-2.3.1
*
* @ingroup oauth2_section_2
*/
public function getClientCredentials(RequestInterface $request, ResponseInterface $response = null)
{
if (!is_null($request->headers('PHP_AUTH_USER')) && !is_null($request->headers('PHP_AUTH_PW'))) {
return array('client_id' => $request->headers('PHP_AUTH_USER'), 'client_secret' => $request->headers('PHP_AUTH_PW'));
}
if ($this->config['allow_credentials_in_request_body']) {
// Using POST for HttpBasic authorization is not recommended, but is supported by specification
if (!is_null($request->request('client_id'))) {
/**
* client_secret can be null if the client's password is an empty string
* @see http://tools.ietf.org/html/rfc6749#section-2.3.1
*/
return array('client_id' => $request->request('client_id'), 'client_secret' => $request->request('client_secret'));
}
}
if ($response) {
$message = $this->config['allow_credentials_in_request_body'] ? ' or body' : '';
$response->setError(400, 'invalid_client', 'Client credentials were not found in the headers'.$message);
}
return null;
}
}

View File

@ -0,0 +1,383 @@
<?php
namespace OAuth2\Controller;
use OAuth2\Storage\ClientInterface;
use OAuth2\ScopeInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
use OAuth2\Scope;
/**
* @see OAuth2\Controller\AuthorizeControllerInterface
*/
class AuthorizeController implements AuthorizeControllerInterface
{
private $scope;
private $state;
private $client_id;
private $redirect_uri;
private $response_type;
protected $clientStorage;
protected $responseTypes;
protected $config;
protected $scopeUtil;
/**
* @param OAuth2\Storage\ClientInterface $clientStorage REQUIRED Instance of OAuth2\Storage\ClientInterface to retrieve client information
* @param array $responseTypes OPTIONAL Array of OAuth2\ResponseType\ResponseTypeInterface objects. Valid array
* keys are "code" and "token"
* @param array $config OPTIONAL Configuration options for the server
* <code>
* $config = array(
* 'allow_implicit' => false, // if the controller should allow the "implicit" grant type
* 'enforce_state' => true // if the controller should require the "state" parameter
* 'require_exact_redirect_uri' => true, // if the controller should require an exact match on the "redirect_uri" parameter
* 'redirect_status_code' => 302, // HTTP status code to use for redirect responses
* );
* </code>
* @param OAuth2\ScopeInterface $scopeUtil OPTIONAL Instance of OAuth2\ScopeInterface to validate the requested scope
*/
public function __construct(ClientInterface $clientStorage, array $responseTypes = array(), array $config = array(), ScopeInterface $scopeUtil = null)
{
$this->clientStorage = $clientStorage;
$this->responseTypes = $responseTypes;
$this->config = array_merge(array(
'allow_implicit' => false,
'enforce_state' => true,
'require_exact_redirect_uri' => true,
'redirect_status_code' => 302,
), $config);
if (is_null($scopeUtil)) {
$scopeUtil = new Scope();
}
$this->scopeUtil = $scopeUtil;
}
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null)
{
if (!is_bool($is_authorized)) {
throw new \InvalidArgumentException('Argument "is_authorized" must be a boolean. This method must know if the user has granted access to the client.');
}
// We repeat this, because we need to re-validate. The request could be POSTed
// by a 3rd-party (because we are not internally enforcing NONCEs, etc)
if (!$this->validateAuthorizeRequest($request, $response)) {
return;
}
// If no redirect_uri is passed in the request, use client's registered one
if (empty($this->redirect_uri)) {
$clientData = $this->clientStorage->getClientDetails($this->client_id);
$registered_redirect_uri = $clientData['redirect_uri'];
}
// the user declined access to the client's application
if ($is_authorized === false) {
$redirect_uri = $this->redirect_uri ?: $registered_redirect_uri;
$this->setNotAuthorizedResponse($request, $response, $redirect_uri, $user_id);
return;
}
// build the parameters to set in the redirect URI
if (!$params = $this->buildAuthorizeParameters($request, $response, $user_id)) {
return;
}
$authResult = $this->responseTypes[$this->response_type]->getAuthorizeResponse($params, $user_id);
list($redirect_uri, $uri_params) = $authResult;
if (empty($redirect_uri) && !empty($registered_redirect_uri)) {
$redirect_uri = $registered_redirect_uri;
}
$uri = $this->buildUri($redirect_uri, $uri_params);
// return redirect response
$response->setRedirect($this->config['redirect_status_code'], $uri);
}
protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null)
{
$error = 'access_denied';
$error_message = 'The user denied access to your application';
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->state, $error, $error_message);
}
/*
* We have made this protected so this class can be extended to add/modify
* these parameters
*/
protected function buildAuthorizeParameters($request, $response, $user_id)
{
// @TODO: we should be explicit with this in the future
$params = array(
'scope' => $this->scope,
'state' => $this->state,
'client_id' => $this->client_id,
'redirect_uri' => $this->redirect_uri,
'response_type' => $this->response_type,
);
return $params;
}
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response)
{
// Make sure a valid client id was supplied (we can not redirect because we were unable to verify the URI)
if (!$client_id = $request->query('client_id', $request->request('client_id'))) {
// We don't have a good URI to use
$response->setError(400, 'invalid_client', "No client id supplied");
return false;
}
// Get client details
if (!$clientData = $this->clientStorage->getClientDetails($client_id)) {
$response->setError(400, 'invalid_client', 'The client id supplied is invalid');
return false;
}
$registered_redirect_uri = isset($clientData['redirect_uri']) ? $clientData['redirect_uri'] : '';
// Make sure a valid redirect_uri was supplied. If specified, it must match the clientData URI.
// @see http://tools.ietf.org/html/rfc6749#section-3.1.2
// @see http://tools.ietf.org/html/rfc6749#section-4.1.2.1
// @see http://tools.ietf.org/html/rfc6749#section-4.2.2.1
if ($supplied_redirect_uri = $request->query('redirect_uri', $request->request('redirect_uri'))) {
// validate there is no fragment supplied
$parts = parse_url($supplied_redirect_uri);
if (isset($parts['fragment']) && $parts['fragment']) {
$response->setError(400, 'invalid_uri', 'The redirect URI must not contain a fragment');
return false;
}
// validate against the registered redirect uri(s) if available
if ($registered_redirect_uri && !$this->validateRedirectUri($supplied_redirect_uri, $registered_redirect_uri)) {
$response->setError(400, 'redirect_uri_mismatch', 'The redirect URI provided is missing or does not match', '#section-3.1.2');
return false;
}
$redirect_uri = $supplied_redirect_uri;
} else {
// use the registered redirect_uri if none has been supplied, if possible
if (!$registered_redirect_uri) {
$response->setError(400, 'invalid_uri', 'No redirect URI was supplied or stored');
return false;
}
if (count(explode(' ', $registered_redirect_uri)) > 1) {
$response->setError(400, 'invalid_uri', 'A redirect URI must be supplied when multiple redirect URIs are registered', '#section-3.1.2.3');
return false;
}
$redirect_uri = $registered_redirect_uri;
}
// Select the redirect URI
$response_type = $request->query('response_type', $request->request('response_type'));
// for multiple-valued response types - make them alphabetical
if (false !== strpos($response_type, ' ')) {
$types = explode(' ', $response_type);
sort($types);
$response_type = ltrim(implode(' ', $types));
}
$state = $request->query('state', $request->request('state'));
// type and client_id are required
if (!$response_type || !in_array($response_type, $this->getValidResponseTypes())) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_request', 'Invalid or missing response type', null);
return false;
}
if ($response_type == self::RESPONSE_TYPE_AUTHORIZATION_CODE) {
if (!isset($this->responseTypes['code'])) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'authorization code grant type not supported', null);
return false;
}
if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'authorization_code')) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null);
return false;
}
if ($this->responseTypes['code']->enforceRedirect() && !$redirect_uri) {
$response->setError(400, 'redirect_uri_mismatch', 'The redirect URI is mandatory and was not supplied');
return false;
}
} else {
if (!$this->config['allow_implicit']) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unsupported_response_type', 'implicit grant type not supported', null);
return false;
}
if (!$this->clientStorage->checkRestrictedGrantType($client_id, 'implicit')) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'unauthorized_client', 'The grant type is unauthorized for this client_id', null);
return false;
}
}
// validate requested scope if it exists
$requestedScope = $this->scopeUtil->getScopeFromRequest($request);
if ($requestedScope) {
// restrict scope by client specific scope if applicable,
// otherwise verify the scope exists
$clientScope = $this->clientStorage->getClientScope($client_id);
if ((is_null($clientScope) && !$this->scopeUtil->scopeExists($requestedScope))
|| ($clientScope && !$this->scopeUtil->checkScope($requestedScope, $clientScope))) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_scope', 'An unsupported scope was requested', null);
return false;
}
} else {
// use a globally-defined default scope
$defaultScope = $this->scopeUtil->getDefaultScope($client_id);
if (false === $defaultScope) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $state, 'invalid_client', 'This application requires you specify a scope parameter', null);
return false;
}
$requestedScope = $defaultScope;
}
// Validate state parameter exists (if configured to enforce this)
if ($this->config['enforce_state'] && !$state) {
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, null, 'invalid_request', 'The state parameter is required');
return false;
}
// save the input data and return true
$this->scope = $requestedScope;
$this->state = $state;
$this->client_id = $client_id;
// Only save the SUPPLIED redirect URI (@see http://tools.ietf.org/html/rfc6749#section-4.1.3)
$this->redirect_uri = $supplied_redirect_uri;
$this->response_type = $response_type;
return true;
}
/**
* Build the absolute URI based on supplied URI and parameters.
*
* @param $uri An absolute URI.
* @param $params Parameters to be append as GET.
*
* @return
* An absolute URI with supplied parameters.
*
* @ingroup oauth2_section_4
*/
private function buildUri($uri, $params)
{
$parse_url = parse_url($uri);
// Add our params to the parsed uri
foreach ($params as $k => $v) {
if (isset($parse_url[$k])) {
$parse_url[$k] .= "&" . http_build_query($v, '', '&');
} else {
$parse_url[$k] = http_build_query($v, '', '&');
}
}
// Put humpty dumpty back together
return
((isset($parse_url["scheme"])) ? $parse_url["scheme"] . "://" : "")
. ((isset($parse_url["user"])) ? $parse_url["user"]
. ((isset($parse_url["pass"])) ? ":" . $parse_url["pass"] : "") . "@" : "")
. ((isset($parse_url["host"])) ? $parse_url["host"] : "")
. ((isset($parse_url["port"])) ? ":" . $parse_url["port"] : "")
. ((isset($parse_url["path"])) ? $parse_url["path"] : "")
. ((isset($parse_url["query"]) && !empty($parse_url['query'])) ? "?" . $parse_url["query"] : "")
. ((isset($parse_url["fragment"])) ? "#" . $parse_url["fragment"] : "")
;
}
protected function getValidResponseTypes()
{
return array(
self::RESPONSE_TYPE_ACCESS_TOKEN,
self::RESPONSE_TYPE_AUTHORIZATION_CODE,
);
}
/**
* Internal method for validating redirect URI supplied
*
* @param string $inputUri The submitted URI to be validated
* @param string $registeredUriString The allowed URI(s) to validate against. Can be a space-delimited string of URIs to
* allow for multiple URIs
*
* @see http://tools.ietf.org/html/rfc6749#section-3.1.2
*/
protected function validateRedirectUri($inputUri, $registeredUriString)
{
if (!$inputUri || !$registeredUriString) {
return false; // if either one is missing, assume INVALID
}
$registered_uris = preg_split('/\s+/', $registeredUriString);
foreach ($registered_uris as $registered_uri) {
if ($this->config['require_exact_redirect_uri']) {
// the input uri is validated against the registered uri using exact match
if (strcmp($inputUri, $registered_uri) === 0) {
return true;
}
} else {
// the input uri is validated against the registered uri using case-insensitive match of the initial string
// i.e. additional query parameters may be applied
if (strcasecmp(substr($inputUri, 0, strlen($registered_uri)), $registered_uri) === 0) {
return true;
}
}
}
return false;
}
/**
* Convenience methods to access the parameters derived from the validated request
*/
public function getScope()
{
return $this->scope;
}
public function getState()
{
return $this->state;
}
public function getClientId()
{
return $this->client_id;
}
public function getRedirectUri()
{
return $this->redirect_uri;
}
public function getResponseType()
{
return $this->response_type;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace OAuth2\Controller;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* This controller is called when a user should be authorized
* by an authorization server. As OAuth2 does not handle
* authorization directly, this controller ensures the request is valid, but
* requires the application to determine the value of $is_authorized
*
* ex:
* > $user_id = $this->somehowDetermineUserId();
* > $is_authorized = $this->somehowDetermineUserAuthorization();
* > $response = new OAuth2\Response();
* > $authorizeController->handleAuthorizeRequest(
* > OAuth2\Request::createFromGlobals(),
* > $response,
* > $is_authorized,
* > $user_id);
* > $response->send();
*
*/
interface AuthorizeControllerInterface
{
/**
* List of possible authentication response types.
* The "authorization_code" mechanism exclusively supports 'code'
* and the "implicit" mechanism exclusively supports 'token'.
*
* @var string
* @see http://tools.ietf.org/html/rfc6749#section-4.1.1
* @see http://tools.ietf.org/html/rfc6749#section-4.2.1
*/
const RESPONSE_TYPE_AUTHORIZATION_CODE = 'code';
const RESPONSE_TYPE_ACCESS_TOKEN = 'token';
public function handleAuthorizeRequest(RequestInterface $request, ResponseInterface $response, $is_authorized, $user_id = null);
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response);
}

View File

@ -0,0 +1,111 @@
<?php
namespace OAuth2\Controller;
use OAuth2\TokenType\TokenTypeInterface;
use OAuth2\Storage\AccessTokenInterface;
use OAuth2\ScopeInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
use OAuth2\Scope;
/**
* @see OAuth2\Controller\ResourceControllerInterface
*/
class ResourceController implements ResourceControllerInterface
{
private $token;
protected $tokenType;
protected $tokenStorage;
protected $config;
protected $scopeUtil;
public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, $config = array(), ScopeInterface $scopeUtil = null)
{
$this->tokenType = $tokenType;
$this->tokenStorage = $tokenStorage;
$this->config = array_merge(array(
'www_realm' => 'Service',
), $config);
if (is_null($scopeUtil)) {
$scopeUtil = new Scope();
}
$this->scopeUtil = $scopeUtil;
}
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null)
{
$token = $this->getAccessTokenData($request, $response);
// Check if we have token data
if (is_null($token)) {
return false;
}
/**
* Check scope, if provided
* If token doesn't have a scope, it's null/empty, or it's insufficient, then throw 403
* @see http://tools.ietf.org/html/rfc6750#section-3.1
*/
if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->scopeUtil->checkScope($scope, $token["scope"]))) {
$response->setError(403, 'insufficient_scope', 'The request requires higher privileges than provided by the access token');
$response->addHttpHeaders(array(
'WWW-Authenticate' => sprintf('%s realm="%s", scope="%s", error="%s", error_description="%s"',
$this->tokenType->getTokenType(),
$this->config['www_realm'],
$scope,
$response->getParameter('error'),
$response->getParameter('error_description')
)
));
return false;
}
// allow retrieval of the token
$this->token = $token;
return (bool) $token;
}
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response)
{
// Get the token parameter
if ($token_param = $this->tokenType->getAccessTokenParameter($request, $response)) {
// Get the stored token data (from the implementing subclass)
// Check we have a well formed token
// Check token expiration (expires is a mandatory paramter)
if (!$token = $this->tokenStorage->getAccessToken($token_param)) {
$response->setError(401, 'invalid_token', 'The access token provided is invalid');
} elseif (!isset($token["expires"]) || !isset($token["client_id"])) {
$response->setError(401, 'malformed_token', 'Malformed token (missing "expires")');
} elseif (time() > $token["expires"]) {
$response->setError(401, 'expired_token', 'The access token provided has expired');
} else {
return $token;
}
}
$authHeader = sprintf('%s realm="%s"', $this->tokenType->getTokenType(), $this->config['www_realm']);
if ($error = $response->getParameter('error')) {
$authHeader = sprintf('%s, error="%s"', $authHeader, $error);
if ($error_description = $response->getParameter('error_description')) {
$authHeader = sprintf('%s, error_description="%s"', $authHeader, $error_description);
}
}
$response->addHttpHeaders(array('WWW-Authenticate' => $authHeader));
return null;
}
// convenience method to allow retrieval of the token
public function getToken()
{
return $this->token;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace OAuth2\Controller;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* This controller is called when a "resource" is requested.
* call verifyResourceRequest in order to determine if the request
* contains a valid token.
*
* ex:
* > if (!$resourceController->verifyResourceRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response())) {
* > $response->send(); // authorization failed
* > die();
* > }
* > return json_encode($resource); // valid token! Send the stuff!
*
*/
interface ResourceControllerInterface
{
public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null);
public function getAccessTokenData(RequestInterface $request, ResponseInterface $response);
}

View File

@ -0,0 +1,274 @@
<?php
namespace OAuth2\Controller;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
use OAuth2\GrantType\GrantTypeInterface;
use OAuth2\ScopeInterface;
use OAuth2\Scope;
use OAuth2\Storage\ClientInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* @see OAuth2\Controller\TokenControllerInterface
*/
class TokenController implements TokenControllerInterface
{
protected $accessToken;
protected $grantTypes;
protected $clientAssertionType;
protected $scopeUtil;
protected $clientStorage;
public function __construct(AccessTokenInterface $accessToken, ClientInterface $clientStorage, array $grantTypes = array(), ClientAssertionTypeInterface $clientAssertionType = null, ScopeInterface $scopeUtil = null)
{
if (is_null($clientAssertionType)) {
foreach ($grantTypes as $grantType) {
if (!$grantType instanceof ClientAssertionTypeInterface) {
throw new \InvalidArgumentException('You must supply an instance of OAuth2\ClientAssertionType\ClientAssertionTypeInterface or only use grant types which implement OAuth2\ClientAssertionType\ClientAssertionTypeInterface');
}
}
}
$this->clientAssertionType = $clientAssertionType;
$this->accessToken = $accessToken;
$this->clientStorage = $clientStorage;
foreach ($grantTypes as $grantType) {
$this->addGrantType($grantType);
}
if (is_null($scopeUtil)) {
$scopeUtil = new Scope();
}
$this->scopeUtil = $scopeUtil;
}
public function handleTokenRequest(RequestInterface $request, ResponseInterface $response)
{
if ($token = $this->grantAccessToken($request, $response)) {
// @see http://tools.ietf.org/html/rfc6749#section-5.1
// server MUST disable caching in headers when tokens are involved
$response->setStatusCode(200);
$response->addParameters($token);
$response->addHttpHeaders(array('Cache-Control' => 'no-store', 'Pragma' => 'no-cache'));
}
}
/**
* Grant or deny a requested access token.
* This would be called from the "/token" endpoint as defined in the spec.
* You can call your endpoint whatever you want.
*
* @param $request - RequestInterface
* Request object to grant access token
*
* @throws InvalidArgumentException
* @throws LogicException
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @see http://tools.ietf.org/html/rfc6749#section-10.6
* @see http://tools.ietf.org/html/rfc6749#section-4.1.3
*
* @ingroup oauth2_section_4
*/
public function grantAccessToken(RequestInterface $request, ResponseInterface $response)
{
if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
$response->setError(405, 'invalid_request', 'The request method must be POST when requesting an access token', '#section-3.2');
$response->addHttpHeaders(array('Allow' => 'POST'));
return null;
}
/**
* Determine grant type from request
* and validate the request for that grant type
*/
if (!$grantTypeIdentifier = $request->request('grant_type')) {
$response->setError(400, 'invalid_request', 'The grant type was not specified in the request');
return null;
}
if (!isset($this->grantTypes[$grantTypeIdentifier])) {
/* TODO: If this is an OAuth2 supported grant type that we have chosen not to implement, throw a 501 Not Implemented instead */
$response->setError(400, 'unsupported_grant_type', sprintf('Grant type "%s" not supported', $grantTypeIdentifier));
return null;
}
$grantType = $this->grantTypes[$grantTypeIdentifier];
/**
* Retrieve the client information from the request
* ClientAssertionTypes allow for grant types which also assert the client data
* in which case ClientAssertion is handled in the validateRequest method
*
* @see OAuth2\GrantType\JWTBearer
* @see OAuth2\GrantType\ClientCredentials
*/
if (!$grantType instanceof ClientAssertionTypeInterface) {
if (!$this->clientAssertionType->validateRequest($request, $response)) {
return null;
}
$clientId = $this->clientAssertionType->getClientId();
}
/**
* Retrieve the grant type information from the request
* The GrantTypeInterface object handles all validation
* If the object is an instance of ClientAssertionTypeInterface,
* That logic is handled here as well
*/
if (!$grantType->validateRequest($request, $response)) {
return null;
}
if ($grantType instanceof ClientAssertionTypeInterface) {
$clientId = $grantType->getClientId();
} else {
// validate the Client ID (if applicable)
if (!is_null($storedClientId = $grantType->getClientId()) && $storedClientId != $clientId) {
$response->setError(400, 'invalid_grant', sprintf('%s doesn\'t exist or is invalid for the client', $grantTypeIdentifier));
return null;
}
}
/**
* Validate the client can use the requested grant type
*/
if (!$this->clientStorage->checkRestrictedGrantType($clientId, $grantTypeIdentifier)) {
$response->setError(400, 'unauthorized_client', 'The grant type is unauthorized for this client_id');
return false;
}
/**
* Validate the scope of the token
*
* requestedScope - the scope specified in the token request
* availableScope - the scope associated with the grant type
* ex: in the case of the "Authorization Code" grant type,
* the scope is specified in the authorize request
*
* @see http://tools.ietf.org/html/rfc6749#section-3.3
*/
$requestedScope = $this->scopeUtil->getScopeFromRequest($request);
$availableScope = $grantType->getScope();
if ($requestedScope) {
// validate the requested scope
if ($availableScope) {
if (!$this->scopeUtil->checkScope($requestedScope, $availableScope)) {
$response->setError(400, 'invalid_scope', 'The scope requested is invalid for this request');
return null;
}
} else {
// validate the client has access to this scope
if ($clientScope = $this->clientStorage->getClientScope($clientId)) {
if (!$this->scopeUtil->checkScope($requestedScope, $clientScope)) {
$response->setError(400, 'invalid_scope', 'The scope requested is invalid for this client');
return false;
}
} elseif (!$this->scopeUtil->scopeExists($requestedScope)) {
$response->setError(400, 'invalid_scope', 'An unsupported scope was requested');
return null;
}
}
} elseif ($availableScope) {
// use the scope associated with this grant type
$requestedScope = $availableScope;
} else {
// use a globally-defined default scope
$defaultScope = $this->scopeUtil->getDefaultScope($clientId);
// "false" means default scopes are not allowed
if (false === $defaultScope) {
$response->setError(400, 'invalid_scope', 'This application requires you specify a scope parameter');
return null;
}
$requestedScope = $defaultScope;
}
return $grantType->createAccessToken($this->accessToken, $clientId, $grantType->getUserId(), $requestedScope);
}
/**
* addGrantType
*
* @param grantType - OAuth2\GrantTypeInterface
* the grant type to add for the specified identifier
* @param identifier - string
* a string passed in as "grant_type" in the response that will call this grantType
*/
public function addGrantType(GrantTypeInterface $grantType, $identifier = null)
{
if (is_null($identifier) || is_numeric($identifier)) {
$identifier = $grantType->getQuerystringIdentifier();
}
$this->grantTypes[$identifier] = $grantType;
}
public function handleRevokeRequest(RequestInterface $request, ResponseInterface $response)
{
if ($this->revokeToken($request, $response)) {
$response->setStatusCode(200);
$response->addParameters(array('revoked' => true));
}
}
/**
* Revoke a refresh or access token. Returns true on success and when tokens are invalid
*
* Note: invalid tokens do not cause an error response since the client
* cannot handle such an error in a reasonable way. Moreover, the
* purpose of the revocation request, invalidating the particular token,
* is already achieved.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return bool|null
*/
public function revokeToken(RequestInterface $request, ResponseInterface $response)
{
if (strtolower($request->server('REQUEST_METHOD')) != 'post') {
$response->setError(405, 'invalid_request', 'The request method must be POST when revoking an access token', '#section-3.2');
$response->addHttpHeaders(array('Allow' => 'POST'));
return null;
}
$token_type_hint = $request->request('token_type_hint');
if (!in_array($token_type_hint, array(null, 'access_token', 'refresh_token'), true)) {
$response->setError(400, 'invalid_request', 'Token type hint must be either \'access_token\' or \'refresh_token\'');
return null;
}
$token = $request->request('token');
if ($token === null) {
$response->setError(400, 'invalid_request', 'Missing token parameter to revoke');
return null;
}
// @todo remove this check for v2.0
if (!method_exists($this->accessToken, 'revokeToken')) {
$class = get_class($this->accessToken);
throw new \RuntimeException("AccessToken {$class} does not implement required revokeToken method");
}
$this->accessToken->revokeToken($token, $token_type_hint);
return true;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace OAuth2\Controller;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* This controller is called when a token is being requested.
* it is called to handle all grant types the application supports.
* It also validates the client's credentials
*
* ex:
* > $tokenController->handleTokenRequest(OAuth2\Request::createFromGlobals(), $response = new OAuth2\Response());
* > $response->send();
*
*/
interface TokenControllerInterface
{
/**
* handleTokenRequest
*
* @param $request
* OAuth2\RequestInterface - The current http request
* @param $response
* OAuth2\ResponseInterface - An instance of OAuth2\ResponseInterface to contain the response data
*
*/
public function handleTokenRequest(RequestInterface $request, ResponseInterface $response);
public function grantAccessToken(RequestInterface $request, ResponseInterface $response);
}

View File

@ -0,0 +1,11 @@
<?php
namespace OAuth2\Encryption;
interface EncryptionInterface
{
public function encode($payload, $key, $algorithm = null);
public function decode($payload, $key, $algorithm = null);
public function urlSafeB64Encode($data);
public function urlSafeB64Decode($b64);
}

View File

@ -0,0 +1,47 @@
<?php
namespace OAuth2\Encryption;
/**
* Bridge file to use the firebase/php-jwt package for JWT encoding and decoding.
* @author Francis Chuang <francis.chuang@gmail.com>
*/
class FirebaseJwt implements EncryptionInterface
{
public function __construct()
{
if (!class_exists('\JWT')) {
throw new \ErrorException('firebase/php-jwt must be installed to use this feature. You can do this by running "composer require firebase/php-jwt"');
}
}
public function encode($payload, $key, $alg = 'HS256', $keyId = null)
{
return \JWT::encode($payload, $key, $alg, $keyId);
}
public function decode($jwt, $key = null, $allowedAlgorithms = null)
{
try {
//Maintain BC: Do not verify if no algorithms are passed in.
if (!$allowedAlgorithms) {
$key = null;
}
return (array)\JWT::decode($jwt, $key, $allowedAlgorithms);
} catch (\Exception $e) {
return false;
}
}
public function urlSafeB64Encode($data)
{
return \JWT::urlsafeB64Encode($data);
}
public function urlSafeB64Decode($b64)
{
return \JWT::urlsafeB64Decode($b64);
}
}

View File

@ -0,0 +1,173 @@
<?php
namespace OAuth2\Encryption;
/**
* @link https://github.com/F21/jwt
* @author F21
*/
class Jwt implements EncryptionInterface
{
public function encode($payload, $key, $algo = 'HS256')
{
$header = $this->generateJwtHeader($payload, $algo);
$segments = array(
$this->urlSafeB64Encode(json_encode($header)),
$this->urlSafeB64Encode(json_encode($payload))
);
$signing_input = implode('.', $segments);
$signature = $this->sign($signing_input, $key, $algo);
$segments[] = $this->urlsafeB64Encode($signature);
return implode('.', $segments);
}
public function decode($jwt, $key = null, $allowedAlgorithms = true)
{
if (!strpos($jwt, '.')) {
return false;
}
$tks = explode('.', $jwt);
if (count($tks) != 3) {
return false;
}
list($headb64, $payloadb64, $cryptob64) = $tks;
if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) {
return false;
}
if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) {
return false;
}
$sig = $this->urlSafeB64Decode($cryptob64);
if ((bool) $allowedAlgorithms) {
if (!isset($header['alg'])) {
return false;
}
// check if bool arg supplied here to maintain BC
if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) {
return false;
}
if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) {
return false;
}
}
return $payload;
}
private function verifySignature($signature, $input, $key, $algo = 'HS256')
{
// use constants when possible, for HipHop support
switch ($algo) {
case'HS256':
case'HS384':
case'HS512':
return $this->hash_equals(
$this->sign($input, $key, $algo),
$signature
);
case 'RS256':
return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256') === 1;
case 'RS384':
return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1;
case 'RS512':
return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1;
default:
throw new \InvalidArgumentException("Unsupported or invalid signing algorithm.");
}
}
private function sign($input, $key, $algo = 'HS256')
{
switch ($algo) {
case 'HS256':
return hash_hmac('sha256', $input, $key, true);
case 'HS384':
return hash_hmac('sha384', $input, $key, true);
case 'HS512':
return hash_hmac('sha512', $input, $key, true);
case 'RS256':
return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256');
case 'RS384':
return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384');
case 'RS512':
return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512');
default:
throw new \Exception("Unsupported or invalid signing algorithm.");
}
}
private function generateRSASignature($input, $key, $algo)
{
if (!openssl_sign($input, $signature, $key, $algo)) {
throw new \Exception("Unable to sign data.");
}
return $signature;
}
public function urlSafeB64Encode($data)
{
$b64 = base64_encode($data);
$b64 = str_replace(array('+', '/', "\r", "\n", '='),
array('-', '_'),
$b64);
return $b64;
}
public function urlSafeB64Decode($b64)
{
$b64 = str_replace(array('-', '_'),
array('+', '/'),
$b64);
return base64_decode($b64);
}
/**
* Override to create a custom header
*/
protected function generateJwtHeader($payload, $algorithm)
{
return array(
'typ' => 'JWT',
'alg' => $algorithm,
);
}
protected function hash_equals($a, $b)
{
if (function_exists('hash_equals')) {
return hash_equals($a, $b);
}
$diff = strlen($a) ^ strlen($b);
for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
$diff |= ord($a[$i]) ^ ord($b[$i]);
}
return $diff === 0;
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\Storage\AuthorizationCodeInterface;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class AuthorizationCode implements GrantTypeInterface
{
protected $storage;
protected $authCode;
/**
* @param OAuth2\Storage\AuthorizationCodeInterface $storage REQUIRED Storage class for retrieving authorization code information
*/
public function __construct(AuthorizationCodeInterface $storage)
{
$this->storage = $storage;
}
public function getQuerystringIdentifier()
{
return 'authorization_code';
}
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$request->request('code')) {
$response->setError(400, 'invalid_request', 'Missing parameter: "code" is required');
return false;
}
$code = $request->request('code');
if (!$authCode = $this->storage->getAuthorizationCode($code)) {
$response->setError(400, 'invalid_grant', 'Authorization code doesn\'t exist or is invalid for the client');
return false;
}
/*
* 4.1.3 - ensure that the "redirect_uri" parameter is present if the "redirect_uri" parameter was included in the initial authorization request
* @uri - http://tools.ietf.org/html/rfc6749#section-4.1.3
*/
if (isset($authCode['redirect_uri']) && $authCode['redirect_uri']) {
if (!$request->request('redirect_uri') || urldecode($request->request('redirect_uri')) != $authCode['redirect_uri']) {
$response->setError(400, 'redirect_uri_mismatch', "The redirect URI is missing or do not match", "#section-4.1.3");
return false;
}
}
if (!isset($authCode['expires'])) {
throw new \Exception('Storage must return authcode with a value for "expires"');
}
if ($authCode["expires"] < time()) {
$response->setError(400, 'invalid_grant', "The authorization code has expired");
return false;
}
if (!isset($authCode['code'])) {
$authCode['code'] = $code; // used to expire the code after the access token is granted
}
$this->authCode = $authCode;
return true;
}
public function getClientId()
{
return $this->authCode['client_id'];
}
public function getScope()
{
return isset($this->authCode['scope']) ? $this->authCode['scope'] : null;
}
public function getUserId()
{
return isset($this->authCode['user_id']) ? $this->authCode['user_id'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
$token = $accessToken->createAccessToken($client_id, $user_id, $scope);
$this->storage->expireAuthorizationCode($this->authCode['code']);
return $token;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\ClientAssertionType\HttpBasic;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\Storage\ClientCredentialsInterface;
/**
* @author Brent Shaffer <bshafs at gmail dot com>
*
* @see OAuth2\ClientAssertionType_HttpBasic
*/
class ClientCredentials extends HttpBasic implements GrantTypeInterface
{
private $clientData;
public function __construct(ClientCredentialsInterface $storage, array $config = array())
{
/**
* The client credentials grant type MUST only be used by confidential clients
*
* @see http://tools.ietf.org/html/rfc6749#section-4.4
*/
$config['allow_public_clients'] = false;
parent::__construct($storage, $config);
}
public function getQuerystringIdentifier()
{
return 'client_credentials';
}
public function getScope()
{
$this->loadClientData();
return isset($this->clientData['scope']) ? $this->clientData['scope'] : null;
}
public function getUserId()
{
$this->loadClientData();
return isset($this->clientData['user_id']) ? $this->clientData['user_id'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
/**
* Client Credentials Grant does NOT include a refresh token
*
* @see http://tools.ietf.org/html/rfc6749#section-4.4.3
*/
$includeRefreshToken = false;
return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
}
private function loadClientData()
{
if (!$this->clientData) {
$this->clientData = $this->storage->getClientDetails($this->getClientId());
}
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* Interface for all OAuth2 Grant Types
*/
interface GrantTypeInterface
{
public function getQuerystringIdentifier();
public function validateRequest(RequestInterface $request, ResponseInterface $response);
public function getClientId();
public function getUserId();
public function getScope();
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope);
}

View File

@ -0,0 +1,226 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\ClientAssertionType\ClientAssertionTypeInterface;
use OAuth2\Storage\JwtBearerInterface;
use OAuth2\Encryption\Jwt;
use OAuth2\Encryption\EncryptionInterface;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* The JWT bearer authorization grant implements JWT (JSON Web Tokens) as a grant type per the IETF draft.
*
* @see http://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-04#section-4
*
* @author F21
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class JwtBearer implements GrantTypeInterface, ClientAssertionTypeInterface
{
private $jwt;
protected $storage;
protected $audience;
protected $jwtUtil;
protected $allowedAlgorithms;
/**
* Creates an instance of the JWT bearer grant type.
*
* @param OAuth2\Storage\JWTBearerInterface|JwtBearerInterface $storage A valid storage interface that implements storage hooks for the JWT bearer grant type.
* @param string $audience The audience to validate the token against. This is usually the full URI of the OAuth token requests endpoint.
* @param EncryptionInterface|OAuth2\Encryption\JWT $jwtUtil OPTONAL The class used to decode, encode and verify JWTs.
* @param array $config
*/
public function __construct(JwtBearerInterface $storage, $audience, EncryptionInterface $jwtUtil = null, array $config = array())
{
$this->storage = $storage;
$this->audience = $audience;
if (is_null($jwtUtil)) {
$jwtUtil = new Jwt();
}
$this->config = array_merge(array(
'allowed_algorithms' => array('RS256', 'RS384', 'RS512')
), $config);
$this->jwtUtil = $jwtUtil;
$this->allowedAlgorithms = $this->config['allowed_algorithms'];
}
/**
* Returns the grant_type get parameter to identify the grant type request as JWT bearer authorization grant.
*
* @return
* The string identifier for grant_type.
*
* @see OAuth2\GrantType\GrantTypeInterface::getQuerystringIdentifier()
*/
public function getQuerystringIdentifier()
{
return 'urn:ietf:params:oauth:grant-type:jwt-bearer';
}
/**
* Validates the data from the decoded JWT.
*
* @return
* TRUE if the JWT request is valid and can be decoded. Otherwise, FALSE is returned.
*
* @see OAuth2\GrantType\GrantTypeInterface::getTokenData()
*/
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$request->request("assertion")) {
$response->setError(400, 'invalid_request', 'Missing parameters: "assertion" required');
return null;
}
// Store the undecoded JWT for later use
$undecodedJWT = $request->request('assertion');
// Decode the JWT
$jwt = $this->jwtUtil->decode($request->request('assertion'), null, false);
if (!$jwt) {
$response->setError(400, 'invalid_request', "JWT is malformed");
return null;
}
// ensure these properties contain a value
// @todo: throw malformed error for missing properties
$jwt = array_merge(array(
'scope' => null,
'iss' => null,
'sub' => null,
'aud' => null,
'exp' => null,
'nbf' => null,
'iat' => null,
'jti' => null,
'typ' => null,
), $jwt);
if (!isset($jwt['iss'])) {
$response->setError(400, 'invalid_grant', "Invalid issuer (iss) provided");
return null;
}
if (!isset($jwt['sub'])) {
$response->setError(400, 'invalid_grant', "Invalid subject (sub) provided");
return null;
}
if (!isset($jwt['exp'])) {
$response->setError(400, 'invalid_grant', "Expiration (exp) time must be present");
return null;
}
// Check expiration
if (ctype_digit($jwt['exp'])) {
if ($jwt['exp'] <= time()) {
$response->setError(400, 'invalid_grant', "JWT has expired");
return null;
}
} else {
$response->setError(400, 'invalid_grant', "Expiration (exp) time must be a unix time stamp");
return null;
}
// Check the not before time
if ($notBefore = $jwt['nbf']) {
if (ctype_digit($notBefore)) {
if ($notBefore > time()) {
$response->setError(400, 'invalid_grant', "JWT cannot be used before the Not Before (nbf) time");
return null;
}
} else {
$response->setError(400, 'invalid_grant', "Not Before (nbf) time must be a unix time stamp");
return null;
}
}
// Check the audience if required to match
if (!isset($jwt['aud']) || ($jwt['aud'] != $this->audience)) {
$response->setError(400, 'invalid_grant', "Invalid audience (aud)");
return null;
}
// Check the jti (nonce)
// @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-13#section-4.1.7
if (isset($jwt['jti'])) {
$jti = $this->storage->getJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
//Reject if jti is used and jwt is still valid (exp parameter has not expired).
if ($jti && $jti['expires'] > time()) {
$response->setError(400, 'invalid_grant', "JSON Token Identifier (jti) has already been used");
return null;
} else {
$this->storage->setJti($jwt['iss'], $jwt['sub'], $jwt['aud'], $jwt['exp'], $jwt['jti']);
}
}
// Get the iss's public key
// @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06#section-4.1.1
if (!$key = $this->storage->getClientKey($jwt['iss'], $jwt['sub'])) {
$response->setError(400, 'invalid_grant', "Invalid issuer (iss) or subject (sub) provided");
return null;
}
// Verify the JWT
if (!$this->jwtUtil->decode($undecodedJWT, $key, $this->allowedAlgorithms)) {
$response->setError(400, 'invalid_grant', "JWT failed signature verification");
return null;
}
$this->jwt = $jwt;
return true;
}
public function getClientId()
{
return $this->jwt['iss'];
}
public function getUserId()
{
return $this->jwt['sub'];
}
public function getScope()
{
return null;
}
/**
* Creates an access token that is NOT associated with a refresh token.
* If a subject (sub) the name of the user/account we are accessing data on behalf of.
*
* @see OAuth2\GrantType\GrantTypeInterface::createAccessToken()
*/
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
$includeRefreshToken = false;
return $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\Storage\RefreshTokenInterface;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class RefreshToken implements GrantTypeInterface
{
private $refreshToken;
protected $storage;
protected $config;
/**
* @param OAuth2\Storage\RefreshTokenInterface $storage REQUIRED Storage class for retrieving refresh token information
* @param array $config OPTIONAL Configuration options for the server
* <code>
* $config = array(
* 'always_issue_new_refresh_token' => true, // whether to issue a new refresh token upon successful token request
* 'unset_refresh_token_after_use' => true // whether to unset the refresh token after after using
* );
* </code>
*/
public function __construct(RefreshTokenInterface $storage, $config = array())
{
$this->config = array_merge(array(
'always_issue_new_refresh_token' => false,
'unset_refresh_token_after_use' => true
), $config);
// to preserve B.C. with v1.6
// @see https://github.com/bshaffer/oauth2-server-php/pull/580
// @todo - remove in v2.0
if (isset($config['always_issue_new_refresh_token']) && !isset($config['unset_refresh_token_after_use'])) {
$this->config['unset_refresh_token_after_use'] = $config['always_issue_new_refresh_token'];
}
$this->storage = $storage;
}
public function getQuerystringIdentifier()
{
return 'refresh_token';
}
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$request->request("refresh_token")) {
$response->setError(400, 'invalid_request', 'Missing parameter: "refresh_token" is required');
return null;
}
if (!$refreshToken = $this->storage->getRefreshToken($request->request("refresh_token"))) {
$response->setError(400, 'invalid_grant', 'Invalid refresh token');
return null;
}
if ($refreshToken['expires'] > 0 && $refreshToken["expires"] < time()) {
$response->setError(400, 'invalid_grant', 'Refresh token has expired');
return null;
}
// store the refresh token locally so we can delete it when a new refresh token is generated
$this->refreshToken = $refreshToken;
return true;
}
public function getClientId()
{
return $this->refreshToken['client_id'];
}
public function getUserId()
{
return isset($this->refreshToken['user_id']) ? $this->refreshToken['user_id'] : null;
}
public function getScope()
{
return isset($this->refreshToken['scope']) ? $this->refreshToken['scope'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
/*
* It is optional to force a new refresh token when a refresh token is used.
* However, if a new refresh token is issued, the old one MUST be expired
* @see http://tools.ietf.org/html/rfc6749#section-6
*/
$issueNewRefreshToken = $this->config['always_issue_new_refresh_token'];
$unsetRefreshToken = $this->config['unset_refresh_token_after_use'];
$token = $accessToken->createAccessToken($client_id, $user_id, $scope, $issueNewRefreshToken);
if ($unsetRefreshToken) {
$this->storage->unsetRefreshToken($this->refreshToken['refresh_token']);
}
return $token;
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace OAuth2\GrantType;
use OAuth2\Storage\UserCredentialsInterface;
use OAuth2\ResponseType\AccessTokenInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class UserCredentials implements GrantTypeInterface
{
private $userInfo;
protected $storage;
/**
* @param OAuth2\Storage\UserCredentialsInterface $storage REQUIRED Storage class for retrieving user credentials information
*/
public function __construct(UserCredentialsInterface $storage)
{
$this->storage = $storage;
}
public function getQuerystringIdentifier()
{
return 'password';
}
public function validateRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$request->request("password") || !$request->request("username")) {
$response->setError(400, 'invalid_request', 'Missing parameters: "username" and "password" required');
return null;
}
if (!$this->storage->checkUserCredentials($request->request("username"), $request->request("password"))) {
$response->setError(401, 'invalid_grant', 'Invalid username and password combination');
return null;
}
$userInfo = $this->storage->getUserDetails($request->request("username"));
if (empty($userInfo)) {
$response->setError(400, 'invalid_grant', 'Unable to retrieve user information');
return null;
}
if (!isset($userInfo['user_id'])) {
throw new \LogicException("you must set the user_id on the array returned by getUserDetails");
}
$this->userInfo = $userInfo;
return true;
}
public function getClientId()
{
return null;
}
public function getUserId()
{
return $this->userInfo['user_id'];
}
public function getScope()
{
return isset($this->userInfo['scope']) ? $this->userInfo['scope'] : null;
}
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
return $accessToken->createAccessToken($client_id, $user_id, $scope);
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace OAuth2\OpenID\Controller;
use OAuth2\Controller\AuthorizeController as BaseAuthorizeController;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* @see OAuth2\Controller\AuthorizeControllerInterface
*/
class AuthorizeController extends BaseAuthorizeController implements AuthorizeControllerInterface
{
private $nonce;
protected function setNotAuthorizedResponse(RequestInterface $request, ResponseInterface $response, $redirect_uri, $user_id = null)
{
$prompt = $request->query('prompt', 'consent');
if ($prompt == 'none') {
if (is_null($user_id)) {
$error = 'login_required';
$error_message = 'The user must log in';
} else {
$error = 'interaction_required';
$error_message = 'The user must grant access to your application';
}
} else {
$error = 'consent_required';
$error_message = 'The user denied access to your application';
}
$response->setRedirect($this->config['redirect_status_code'], $redirect_uri, $this->getState(), $error, $error_message);
}
protected function buildAuthorizeParameters($request, $response, $user_id)
{
if (!$params = parent::buildAuthorizeParameters($request, $response, $user_id)) {
return;
}
// Generate an id token if needed.
if ($this->needsIdToken($this->getScope()) && $this->getResponseType() == self::RESPONSE_TYPE_AUTHORIZATION_CODE) {
$params['id_token'] = $this->responseTypes['id_token']->createIdToken($this->getClientId(), $user_id, $this->nonce);
}
// add the nonce to return with the redirect URI
$params['nonce'] = $this->nonce;
return $params;
}
public function validateAuthorizeRequest(RequestInterface $request, ResponseInterface $response)
{
if (!parent::validateAuthorizeRequest($request, $response)) {
return false;
}
$nonce = $request->query('nonce');
// Validate required nonce for "id_token" and "id_token token"
if (!$nonce && in_array($this->getResponseType(), array(self::RESPONSE_TYPE_ID_TOKEN, self::RESPONSE_TYPE_ID_TOKEN_TOKEN))) {
$response->setError(400, 'invalid_nonce', 'This application requires you specify a nonce parameter');
return false;
}
$this->nonce = $nonce;
return true;
}
protected function getValidResponseTypes()
{
return array(
self::RESPONSE_TYPE_ACCESS_TOKEN,
self::RESPONSE_TYPE_AUTHORIZATION_CODE,
self::RESPONSE_TYPE_ID_TOKEN,
self::RESPONSE_TYPE_ID_TOKEN_TOKEN,
self::RESPONSE_TYPE_CODE_ID_TOKEN,
);
}
/**
* Returns whether the current request needs to generate an id token.
*
* ID Tokens are a part of the OpenID Connect specification, so this
* method checks whether OpenID Connect is enabled in the server settings
* and whether the openid scope was requested.
*
* @param $request_scope
* A space-separated string of scopes.
*
* @return
* TRUE if an id token is needed, FALSE otherwise.
*/
public function needsIdToken($request_scope)
{
// see if the "openid" scope exists in the requested scope
return $this->scopeUtil->checkScope('openid', $request_scope);
}
public function getNonce()
{
return $this->nonce;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace OAuth2\OpenID\Controller;
interface AuthorizeControllerInterface
{
const RESPONSE_TYPE_ID_TOKEN = 'id_token';
const RESPONSE_TYPE_ID_TOKEN_TOKEN = 'id_token token';
const RESPONSE_TYPE_CODE_ID_TOKEN = 'code id_token';
}

View File

@ -0,0 +1,58 @@
<?php
namespace OAuth2\OpenID\Controller;
use OAuth2\Scope;
use OAuth2\TokenType\TokenTypeInterface;
use OAuth2\Storage\AccessTokenInterface;
use OAuth2\OpenID\Storage\UserClaimsInterface;
use OAuth2\Controller\ResourceController;
use OAuth2\ScopeInterface;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* @see OAuth2\Controller\UserInfoControllerInterface
*/
class UserInfoController extends ResourceController implements UserInfoControllerInterface
{
private $token;
protected $tokenType;
protected $tokenStorage;
protected $userClaimsStorage;
protected $config;
protected $scopeUtil;
public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, UserClaimsInterface $userClaimsStorage, $config = array(), ScopeInterface $scopeUtil = null)
{
$this->tokenType = $tokenType;
$this->tokenStorage = $tokenStorage;
$this->userClaimsStorage = $userClaimsStorage;
$this->config = array_merge(array(
'www_realm' => 'Service',
), $config);
if (is_null($scopeUtil)) {
$scopeUtil = new Scope();
}
$this->scopeUtil = $scopeUtil;
}
public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response)
{
if (!$this->verifyResourceRequest($request, $response, 'openid')) {
return;
}
$token = $this->getToken();
$claims = $this->userClaimsStorage->getUserClaims($token['user_id'], $token['scope']);
// The sub Claim MUST always be returned in the UserInfo Response.
// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
$claims += array(
'sub' => $token['user_id'],
);
$response->addParameters($claims);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace OAuth2\OpenID\Controller;
use OAuth2\RequestInterface;
use OAuth2\ResponseInterface;
/**
* This controller is called when the user claims for OpenID Connect's
* UserInfo endpoint should be returned.
*
* ex:
* > $response = new OAuth2\Response();
* > $userInfoController->handleUserInfoRequest(
* > OAuth2\Request::createFromGlobals(),
* > $response;
* > $response->send();
*
*/
interface UserInfoControllerInterface
{
public function handleUserInfoRequest(RequestInterface $request, ResponseInterface $response);
}

View File

@ -0,0 +1,33 @@
<?php
namespace OAuth2\OpenID\GrantType;
use OAuth2\GrantType\AuthorizationCode as BaseAuthorizationCode;
use OAuth2\ResponseType\AccessTokenInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class AuthorizationCode extends BaseAuthorizationCode
{
public function createAccessToken(AccessTokenInterface $accessToken, $client_id, $user_id, $scope)
{
$includeRefreshToken = true;
if (isset($this->authCode['id_token'])) {
// OpenID Connect requests include the refresh token only if the
// offline_access scope has been requested and granted.
$scopes = explode(' ', trim($scope));
$includeRefreshToken = in_array('offline_access', $scopes);
}
$token = $accessToken->createAccessToken($client_id, $user_id, $scope, $includeRefreshToken);
if (isset($this->authCode['id_token'])) {
$token['id_token'] = $this->authCode['id_token'];
}
$this->storage->expireAuthorizationCode($this->authCode['code']);
return $token;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\AuthorizationCode as BaseAuthorizationCode;
use OAuth2\OpenID\Storage\AuthorizationCodeInterface as AuthorizationCodeStorageInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
class AuthorizationCode extends BaseAuthorizationCode implements AuthorizationCodeInterface
{
public function __construct(AuthorizationCodeStorageInterface $storage, array $config = array())
{
parent::__construct($storage, $config);
}
public function getAuthorizeResponse($params, $user_id = null)
{
// build the URL to redirect to
$result = array('query' => array());
$params += array('scope' => null, 'state' => null, 'id_token' => null);
$result['query']['code'] = $this->createAuthorizationCode($params['client_id'], $user_id, $params['redirect_uri'], $params['scope'], $params['id_token']);
if (isset($params['state'])) {
$result['query']['state'] = $params['state'];
}
return array($params['redirect_uri'], $result);
}
/**
* Handle the creation of the authorization code.
*
* @param $client_id
* Client identifier related to the authorization code
* @param $user_id
* User ID associated with the authorization code
* @param $redirect_uri
* An absolute URI to which the authorization server will redirect the
* user-agent to when the end-user authorization step is completed.
* @param $scope
* (optional) Scopes to be stored in space-separated string.
* @param $id_token
* (optional) The OpenID Connect id_token.
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @ingroup oauth2_section_4
*/
public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null)
{
$code = $this->generateAuthorizationCode();
$this->storage->setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, time() + $this->config['auth_code_lifetime'], $scope, $id_token);
return $code;
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\AuthorizationCodeInterface as BaseAuthorizationCodeInterface;
/**
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
{
/**
* Handle the creation of the authorization code.
*
* @param $client_id Client identifier related to the authorization code
* @param $user_id User ID associated with the authorization code
* @param $redirect_uri An absolute URI to which the authorization server will redirect the
* user-agent to when the end-user authorization step is completed.
* @param $scope OPTIONAL Scopes to be stored in space-separated string.
* @param $id_token OPTIONAL The OpenID Connect id_token.
*
* @see http://tools.ietf.org/html/rfc6749#section-4
* @ingroup oauth2_section_4
*/
public function createAuthorizationCode($client_id, $user_id, $redirect_uri, $scope = null, $id_token = null);
}

View File

@ -0,0 +1,24 @@
<?php
namespace OAuth2\OpenID\ResponseType;
class CodeIdToken implements CodeIdTokenInterface
{
protected $authCode;
protected $idToken;
public function __construct(AuthorizationCodeInterface $authCode, IdTokenInterface $idToken)
{
$this->authCode = $authCode;
$this->idToken = $idToken;
}
public function getAuthorizeResponse($params, $user_id = null)
{
$result = $this->authCode->getAuthorizeResponse($params, $user_id);
$id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce']);
$result[1]['query']['id_token'] = $id_token;
return $result;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\ResponseTypeInterface;
interface CodeIdTokenInterface extends ResponseTypeInterface
{
}

View File

@ -0,0 +1,124 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\Encryption\EncryptionInterface;
use OAuth2\Encryption\Jwt;
use OAuth2\Storage\PublicKeyInterface;
use OAuth2\OpenID\Storage\UserClaimsInterface;
class IdToken implements IdTokenInterface
{
protected $userClaimsStorage;
protected $publicKeyStorage;
protected $config;
protected $encryptionUtil;
public function __construct(UserClaimsInterface $userClaimsStorage, PublicKeyInterface $publicKeyStorage, array $config = array(), EncryptionInterface $encryptionUtil = null)
{
$this->userClaimsStorage = $userClaimsStorage;
$this->publicKeyStorage = $publicKeyStorage;
if (is_null($encryptionUtil)) {
$encryptionUtil = new Jwt();
}
$this->encryptionUtil = $encryptionUtil;
if (!isset($config['issuer'])) {
throw new \LogicException('config parameter "issuer" must be set');
}
$this->config = array_merge(array(
'id_lifetime' => 3600,
), $config);
}
public function getAuthorizeResponse($params, $userInfo = null)
{
// build the URL to redirect to
$result = array('query' => array());
$params += array('scope' => null, 'state' => null, 'nonce' => null);
// create the id token.
list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo);
$userClaims = $this->userClaimsStorage->getUserClaims($user_id, $params['scope']);
$id_token = $this->createIdToken($params['client_id'], $userInfo, $params['nonce'], $userClaims, null);
$result["fragment"] = array('id_token' => $id_token);
if (isset($params['state'])) {
$result["fragment"]["state"] = $params['state'];
}
return array($params['redirect_uri'], $result);
}
public function createIdToken($client_id, $userInfo, $nonce = null, $userClaims = null, $access_token = null)
{
// pull auth_time from user info if supplied
list($user_id, $auth_time) = $this->getUserIdAndAuthTime($userInfo);
$token = array(
'iss' => $this->config['issuer'],
'sub' => $user_id,
'aud' => $client_id,
'iat' => time(),
'exp' => time() + $this->config['id_lifetime'],
'auth_time' => $auth_time,
);
if ($nonce) {
$token['nonce'] = $nonce;
}
if ($userClaims) {
$token += $userClaims;
}
if ($access_token) {
$token['at_hash'] = $this->createAtHash($access_token, $client_id);
}
return $this->encodeToken($token, $client_id);
}
protected function createAtHash($access_token, $client_id = null)
{
// maps HS256 and RS256 to sha256, etc.
$algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
$hash_algorithm = 'sha' . substr($algorithm, 2);
$hash = hash($hash_algorithm, $access_token);
$at_hash = substr($hash, 0, strlen($hash) / 2);
return $this->encryptionUtil->urlSafeB64Encode($at_hash);
}
protected function encodeToken(array $token, $client_id = null)
{
$private_key = $this->publicKeyStorage->getPrivateKey($client_id);
$algorithm = $this->publicKeyStorage->getEncryptionAlgorithm($client_id);
return $this->encryptionUtil->encode($token, $private_key, $algorithm);
}
private function getUserIdAndAuthTime($userInfo)
{
$auth_time = null;
// support an array for user_id / auth_time
if (is_array($userInfo)) {
if (!isset($userInfo['user_id'])) {
throw new \LogicException('if $user_id argument is an array, user_id index must be set');
}
$auth_time = isset($userInfo['auth_time']) ? $userInfo['auth_time'] : null;
$user_id = $userInfo['user_id'];
} else {
$user_id = $userInfo;
}
if (is_null($auth_time)) {
$auth_time = time();
}
// userInfo is a scalar, and so this is the $user_id. Auth Time is null
return array($user_id, $auth_time);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\ResponseTypeInterface;
interface IdTokenInterface extends ResponseTypeInterface
{
/**
* Create the id token.
*
* If Authorization Code Flow is used, the id_token is generated when the
* authorization code is issued, and later returned from the token endpoint
* together with the access_token.
* If the Implicit Flow is used, the token and id_token are generated and
* returned together.
*
* @param string $client_id The client id.
* @param string $user_id The user id.
* @param string $nonce OPTIONAL The nonce.
* @param string $userClaims OPTIONAL Claims about the user.
* @param string $access_token OPTIONAL The access token, if known.
*
* @return string The ID Token represented as a JSON Web Token (JWT).
*
* @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken
*/
public function createIdToken($client_id, $userInfo, $nonce = null, $userClaims = null, $access_token = null);
}

View File

@ -0,0 +1,27 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\AccessTokenInterface;
class IdTokenToken implements IdTokenTokenInterface
{
protected $accessToken;
protected $idToken;
public function __construct(AccessTokenInterface $accessToken, IdTokenInterface $idToken)
{
$this->accessToken = $accessToken;
$this->idToken = $idToken;
}
public function getAuthorizeResponse($params, $user_id = null)
{
$result = $this->accessToken->getAuthorizeResponse($params, $user_id);
$access_token = $result[1]['fragment']['access_token'];
$id_token = $this->idToken->createIdToken($params['client_id'], $user_id, $params['nonce'], null, $access_token);
$result[1]['fragment']['id_token'] = $id_token;
return $result;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace OAuth2\OpenID\ResponseType;
use OAuth2\ResponseType\ResponseTypeInterface;
interface IdTokenTokenInterface extends ResponseTypeInterface
{
}

View File

@ -0,0 +1,37 @@
<?php
namespace OAuth2\OpenID\Storage;
use OAuth2\Storage\AuthorizationCodeInterface as BaseAuthorizationCodeInterface;
/**
* Implement this interface to specify where the OAuth2 Server
* should get/save authorization codes for the "Authorization Code"
* grant type
*
* @author Brent Shaffer <bshafs at gmail dot com>
*/
interface AuthorizationCodeInterface extends BaseAuthorizationCodeInterface
{
/**
* Take the provided authorization code values and store them somewhere.
*
* This function should be the storage counterpart to getAuthCode().
*
* If storage fails for some reason, we're not currently checking for
* any sort of success/failure, so you should bail out of the script
* and provide a descriptive fail message.
*
* Required for OAuth2::GRANT_TYPE_AUTH_CODE.
*
* @param $code authorization code to be stored.
* @param $client_id client identifier to be stored.
* @param $user_id user identifier to be stored.
* @param string $redirect_uri redirect URI(s) to be stored in a space-separated string.
* @param int $expires expiration to be stored as a Unix timestamp.
* @param string $scope OPTIONAL scopes to be stored in space-separated string.
* @param string $id_token OPTIONAL the OpenID Connect id_token.
*
* @ingroup oauth2_section_4
*/
public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null);
}

View File

@ -0,0 +1,38 @@
<?php
namespace OAuth2\OpenID\Storage;
/**
* Implement this interface to specify where the OAuth2 Server
* should retrieve user claims for the OpenID Connect id_token.
*/
interface UserClaimsInterface
{
// valid scope values to pass into the user claims API call
const VALID_CLAIMS = 'profile email address phone';
// fields returned for the claims above
const PROFILE_CLAIM_VALUES = 'name family_name given_name middle_name nickname preferred_username profile picture website gender birthdate zoneinfo locale updated_at';
const EMAIL_CLAIM_VALUES = 'email email_verified';
const ADDRESS_CLAIM_VALUES = 'formatted street_address locality region postal_code country';
const PHONE_CLAIM_VALUES = 'phone_number phone_number_verified';
/**
* Return claims about the provided user id.
*
* Groups of claims are returned based on the requested scopes. No group
* is required, and no claim is required.
*
* @param $user_id
* The id of the user for which claims should be returned.
* @param $scope
* The requested scope.
* Scopes with matching claims: profile, email, address, phone.
*
* @return
* An array in the claim => value format.
*
* @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
*/
public function getUserClaims($user_id, $scope);
}

Some files were not shown because too many files have changed in this diff Show More