added plugins
This commit is contained in:
parent
896ecdcfe0
commit
226875b230
|
|
@ -0,0 +1,18 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator;
|
||||
|
||||
use System\Classes\PluginBase;
|
||||
|
||||
class Plugin extends PluginBase
|
||||
{
|
||||
public $require = [
|
||||
'RainLab.Builder'
|
||||
];
|
||||
|
||||
public function registerComponents()
|
||||
{
|
||||
}
|
||||
|
||||
public function registerSettings()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
# API Generator
|
||||
|
||||
> October CMS plugin to build RESTful APIs.
|
||||
|
||||
## Features
|
||||
|
||||
- Auto generate routes
|
||||
- Auto Generate Controller (CRUD)
|
||||
- Support relationship restful API
|
||||
|
||||
## Install
|
||||
```
|
||||
composer require AhmadFatoni.ApiGenerator
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Form
|
||||
- API Name : Name of your API module
|
||||
- Base Endpoint : Base endpoint of your API, ex : api/v1/modulename
|
||||
- Short Description : Describe your API
|
||||
- Model : select model that will be created API
|
||||
- Custom Condition : Build customer response using JSON modeling
|
||||
|
||||
### Custom Condition Example
|
||||
```
|
||||
{
|
||||
'fillable': 'id,title,content',
|
||||
'relation': [{
|
||||
'name': 'user',
|
||||
'fillable': 'id,first_name'
|
||||
}, {
|
||||
'name': 'categories',
|
||||
'fillable': 'id,name
|
||||
}]
|
||||
}
|
||||
```
|
||||
* please replace single quote with quote
|
||||
|
||||
## Contribute
|
||||
|
||||
Pull Requests accepted.
|
||||
|
||||
## Contact
|
||||
|
||||
You can communicate with me using [linkedin](https://www.linkedin.com/in/ahmad-fatoni)
|
||||
|
||||
## License
|
||||
The OctoberCMS platform is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "ahmadfatoni/apigenerator-plugin",
|
||||
"type": "october-plugin",
|
||||
"description": "None",
|
||||
"require": {
|
||||
"composer/installers": "~1.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Controllers;
|
||||
|
||||
use Backend\Classes\Controller;
|
||||
use AhmadFatoni\ApiGenerator\Models\ApiGenerator;
|
||||
use BackendMenu;
|
||||
use Backend;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Redirect;
|
||||
use Flash;
|
||||
|
||||
class ApiGeneratorController extends Controller
|
||||
{
|
||||
public $implement = ['Backend\Behaviors\ListController','Backend\Behaviors\FormController','Backend\Behaviors\ReorderController'];
|
||||
|
||||
public $listConfig = 'config_list.yaml';
|
||||
public $formConfig = 'config_form.yaml';
|
||||
public $reorderConfig = 'config_reorder.yaml';
|
||||
protected $path = "/api/";
|
||||
private $homePage = 'ahmadfatoni/apigenerator/apigeneratorcontroller';
|
||||
protected $files;
|
||||
public $requiredPermissions = ['ahmadfatoni.apigenerator.manage'];
|
||||
|
||||
public function __construct(Filesystem $files)
|
||||
{
|
||||
parent::__construct();
|
||||
BackendMenu::setContext('AhmadFatoni.ApiGenerator', 'api-generator');
|
||||
$this->files = $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete selected data (multiple delete)
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function index_onDelete()
|
||||
{
|
||||
if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) {
|
||||
|
||||
foreach ($checkedIds as $id) {
|
||||
if ((!$item = ApiGenerator::find($id)))
|
||||
continue;
|
||||
$name = $item->name;
|
||||
if($item->delete()){
|
||||
$this->deleteApi($name);
|
||||
}
|
||||
}
|
||||
|
||||
Flash::success('Successfully deleted those data.');
|
||||
}
|
||||
|
||||
return $this->listRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* generate API
|
||||
* @param Request $request [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function generateApi(Request $request){
|
||||
|
||||
$data['model'] = $request->model;
|
||||
$modelname = explode("\\", $request->model);
|
||||
$modelname = $modelname[count($modelname)-1];
|
||||
$data['modelname'] = $modelname;
|
||||
$data['controllername'] = str_replace(" ", "", $request->name);
|
||||
$data['endpoint'] = $request->endpoint;
|
||||
$data['custom_format'] = $request->custom_format;
|
||||
|
||||
if( strpos($data['controllername'], ".") OR strpos($data['controllername'], "/") ){
|
||||
|
||||
Flash::success('Failed to create data, invalid API name.');
|
||||
return Redirect::to( Backend::url($this->homePage));
|
||||
|
||||
}
|
||||
|
||||
if( isset($request->id) ){
|
||||
$this->deleteApi($request->oldname, 'false');
|
||||
}
|
||||
|
||||
$this->files->put(__DIR__ . $this->path . $data['controllername'].'Controller.php', $this->compile($data));
|
||||
|
||||
$this->files->put(__DIR__ . '/'.'../routes.php', $this->compileRoute($data));
|
||||
|
||||
return Redirect::to( Backend::url($this->homePage));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* delete available API
|
||||
* @param [type] $name [description]
|
||||
* @param [type] $redirect [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function deleteApi($name, $redirect = null){
|
||||
|
||||
$fileLocation = __DIR__ . $this->path.$name;
|
||||
$fileLocation = str_replace(".", "", $fileLocation);
|
||||
|
||||
if( ! file_exists($fileLocation.'Controller.php') ){
|
||||
|
||||
Flash::success('Failed to delete data, invalid file location.');
|
||||
return Redirect::to( Backend::url($this->homePage));
|
||||
|
||||
}
|
||||
|
||||
if( strpos( strtolower($name), 'apigenerator' ) === false){
|
||||
$data = [];
|
||||
|
||||
//generate new route
|
||||
$this->files->put(__DIR__ . '/'.'../routes.php', $this->compileRoute($data));
|
||||
|
||||
//remove controller
|
||||
if (file_exists( __DIR__ . $this->path.$name.'Controller.php' )) {
|
||||
|
||||
unlink(__DIR__ . $this->path.$name.'Controller.php');
|
||||
|
||||
}
|
||||
|
||||
if( $redirect != null ){
|
||||
return 'success without redirect';
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to( Backend::url($this->homePage));
|
||||
|
||||
}
|
||||
|
||||
public function updateApi($name){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* compile controller from template
|
||||
* @param [type] $data [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function compile($data){
|
||||
if( $data['custom_format'] != ''){
|
||||
|
||||
$template = $this->files->get(__DIR__ .'/../template/customcontroller.dot');
|
||||
$template = $this->replaceAttribute($template, $data);
|
||||
$template = $this->replaceCustomAttribute($template, $data);
|
||||
}else{
|
||||
$template = $this->files->get(__DIR__ .'/../template/controller.dot');
|
||||
$template = $this->replaceAttribute($template, $data);
|
||||
}
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* replace attribute
|
||||
* @param [type] $template [description]
|
||||
* @param [type] $data [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function replaceAttribute($template, $data){
|
||||
if( isset( $data['model'] ) ){
|
||||
$template = str_replace('{{model}}', $data['model'], $template);
|
||||
}
|
||||
$template = str_replace('{{modelname}}', $data['modelname'], $template);
|
||||
$template = str_replace('{{controllername}}', $data['controllername'], $template);
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* replace custom attribute
|
||||
* @param [type] $template [description]
|
||||
* @param [type] $data [description]
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function replaceCustomAttribute($template, $data){
|
||||
|
||||
$arr = str_replace('\t', '', $data['custom_format']);
|
||||
$arr = json_decode($arr);
|
||||
$select = str_replace('<br />', '', $this->compileOpenIndexFunction($data['modelname'], 'index'));
|
||||
$show = str_replace('<br />', '', $this->compileOpenIndexFunction($data['modelname'], 'show'));
|
||||
$fillableParent = '';
|
||||
|
||||
if( isset($arr->fillable) AND $arr->fillable != null ) {
|
||||
$fillableParent = $this->compileFillableParent($arr->fillable);
|
||||
}
|
||||
|
||||
if( isset($arr->relation) AND $arr->relation != null AND is_array($arr->relation) AND count($arr->relation) > 0) {
|
||||
$select .= str_replace('<br />', '', $this->compileFillableChild($arr->relation));
|
||||
$show .= str_replace('<br />', '', $this->compileFillableChild($arr->relation));
|
||||
}
|
||||
|
||||
$select .= "->select(".$fillableParent.")";
|
||||
$show .= "->select(".$fillableParent.")->where('id', '=', \$id)->first();";
|
||||
|
||||
( $fillableParent != '') ? $select .= "->get()->toArray();" : $select .= "->toArray();" ;
|
||||
|
||||
$closeFunction = str_replace('<br />', '', nl2br(
|
||||
"
|
||||
return \$this->helpers->apiArrayResponseBuilder(200, 'success', \$data);
|
||||
}"));
|
||||
$select .= $closeFunction;
|
||||
$show .= $closeFunction;
|
||||
|
||||
$template = str_replace('{{select}}', $select, $template);
|
||||
$template = str_replace('{{show}}', $show, $template);
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
public function compileOpenIndexFunction($modelname, $type){
|
||||
if( $type == 'index'){
|
||||
return nl2br("
|
||||
public function index(){
|
||||
\$data = \$this->".$modelname);
|
||||
}else{
|
||||
return nl2br("
|
||||
public function show(\$id){
|
||||
\$data = \$this->".$modelname);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function compileFillableParent($fillable){
|
||||
|
||||
$fillableParentArr = explode(",", $fillable);
|
||||
$fillableParent = '';
|
||||
|
||||
foreach ($fillableParentArr as $key) {
|
||||
|
||||
$fillableParent .= ",'".$key."'";
|
||||
|
||||
}
|
||||
|
||||
$fillableParent = substr_replace($fillableParent, '', 0 , 1);
|
||||
|
||||
return $fillableParent;
|
||||
}
|
||||
|
||||
public function compileFillableChild($fillable){
|
||||
|
||||
$select = "->with(array(";
|
||||
|
||||
foreach ($fillable as $key) {
|
||||
|
||||
$fillableChild = "";
|
||||
|
||||
if( isset($key->fillable) AND $key->fillable != null ){
|
||||
$fillableChildArr = explode(",", $key->fillable);
|
||||
|
||||
|
||||
foreach ($fillableChildArr as $key2) {
|
||||
|
||||
$fillableChild .= ",'".$key2."'";
|
||||
|
||||
}
|
||||
|
||||
$fillableChild = substr_replace($fillableChild, '', 0 , 1);
|
||||
}
|
||||
|
||||
$select .= nl2br(
|
||||
"
|
||||
'".$key->name."'=>function(\$query){
|
||||
\$query->select(".$fillableChild.");
|
||||
},");
|
||||
|
||||
}
|
||||
|
||||
$select .= " ))";
|
||||
|
||||
return $select;
|
||||
}
|
||||
|
||||
public function compileRoute($data){
|
||||
|
||||
$oldData = ApiGenerator::all();
|
||||
$routeList = "";
|
||||
|
||||
if( count($oldData) > 0 ){
|
||||
|
||||
$routeList .= $this->parseRouteOldData($oldData, $data);
|
||||
|
||||
}
|
||||
|
||||
if( count($data) > 0 ){
|
||||
$data['modelname'] = $data['endpoint'];
|
||||
if( $data['modelname'][0] == "/" ){
|
||||
$data['modelname'] = substr_replace($data['modelname'], '', 0 , 1);
|
||||
}
|
||||
$routeList .= $this->parseRoute($data);
|
||||
}
|
||||
|
||||
$route = $this->files->get(__DIR__ .'/../template/routes.dot');
|
||||
$route = str_replace('{{route}}', $routeList, $route);
|
||||
|
||||
return $route;
|
||||
|
||||
}
|
||||
|
||||
public function parseRouteOldData($oldData, $data = null){
|
||||
|
||||
$routeList = "";
|
||||
|
||||
if( count($data) == 0 ) $data['modelname']='';
|
||||
|
||||
foreach ( $oldData as $key ) {
|
||||
|
||||
$modelname = explode("\\", $key->model);
|
||||
$modelname = $modelname[count($modelname)-1];
|
||||
$old['modelname'] = $key->endpoint;
|
||||
$old['controllername'] = $key->name;
|
||||
|
||||
if( $data['modelname'] != $modelname ){
|
||||
|
||||
if( $old['modelname'][0] == "/" ){
|
||||
$old['modelname'] = substr_replace($old['modelname'], '', 0 , 1);
|
||||
}
|
||||
|
||||
$routeList .= $this->parseRoute($old);
|
||||
}
|
||||
}
|
||||
|
||||
return $routeList;
|
||||
|
||||
}
|
||||
|
||||
public function parseRoute($data){
|
||||
|
||||
$template = $this->files->get(__DIR__ .'/../template/route.dot');
|
||||
$template = $this->replaceAttribute($template, $data);
|
||||
return $template;
|
||||
}
|
||||
|
||||
|
||||
public static function getAfterFilters() {return [];}
|
||||
public static function getBeforeFilters() {return [];}
|
||||
public function callAction($method, $parameters=false) {
|
||||
return call_user_func_array(array($this, $method), $parameters);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Controllers\API;
|
||||
|
||||
use Cms\Classes\Controller;
|
||||
use BackendMenu;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use AhmadFatoni\ApiGenerator\Helpers\Helpers;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Atash\Contact\Models\Card_data;
|
||||
class cardController extends Controller
|
||||
{
|
||||
protected $Card_data;
|
||||
|
||||
protected $helpers;
|
||||
|
||||
public function __construct(Card_data $Card_data, Helpers $helpers)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->Card_data = $Card_data;
|
||||
$this->helpers = $helpers;
|
||||
}
|
||||
|
||||
public function index(){
|
||||
|
||||
// $data = $this->Card_data->all()->toArray();
|
||||
$data = $this->Card_data->with(['translations:locale,model_id,attribute_data','image'])->get();
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', $data);
|
||||
}
|
||||
|
||||
public function show($id){
|
||||
|
||||
$data = $this->Card_data::find($id);
|
||||
|
||||
if ($data){
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', [$data]);
|
||||
} else {
|
||||
$this->helpers->apiArrayResponseBuilder(404, 'not found', ['error' => 'Resource id=' . $id . ' could not be found']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function store(Request $request){
|
||||
|
||||
$arr = $request->all();
|
||||
|
||||
while ( $data = current($arr)) {
|
||||
$this->Card_data->{key($arr)} = $data;
|
||||
next($arr);
|
||||
}
|
||||
|
||||
$validation = Validator::make($request->all(), $this->Card_data->rules);
|
||||
|
||||
if( $validation->passes() ){
|
||||
$this->Card_data->save();
|
||||
return $this->helpers->apiArrayResponseBuilder(201, 'created', ['id' => $this->Card_data->id]);
|
||||
}else{
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validation->errors() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function update($id, Request $request){
|
||||
|
||||
$status = $this->Card_data->where('id',$id)->update($data);
|
||||
|
||||
if( $status ){
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been updated successfully.');
|
||||
|
||||
}else{
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'bad request', 'Error, data failed to update.');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($id){
|
||||
|
||||
$this->Card_data->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
public function destroy($id){
|
||||
|
||||
$this->Card_data->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
|
||||
public static function getAfterFilters() {return [];}
|
||||
public static function getBeforeFilters() {return [];}
|
||||
public static function getMiddleware() {return [];}
|
||||
public function callAction($method, $parameters=false) {
|
||||
return call_user_func_array(array($this, $method), $parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Controllers\API;
|
||||
|
||||
use Cms\Classes\Controller;
|
||||
use BackendMenu;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use AhmadFatoni\ApiGenerator\Helpers\Helpers;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Atash\Contact\Models\Credit_data;
|
||||
class creditController extends Controller
|
||||
{
|
||||
protected $Credit_data;
|
||||
|
||||
protected $helpers;
|
||||
|
||||
public function __construct(Credit_data $Credit_data, Helpers $helpers)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->Credit_data = $Credit_data;
|
||||
$this->helpers = $helpers;
|
||||
}
|
||||
|
||||
public function index(){
|
||||
|
||||
// $data = $this->Credit_data->all()->toArray();
|
||||
$data = $this->Credit_data->with(['translations:locale,model_id,attribute_data'])->get();
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', $data);
|
||||
}
|
||||
|
||||
public function show($id){
|
||||
|
||||
$data = $this->Credit_data::find($id);
|
||||
|
||||
if ($data){
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', [$data]);
|
||||
} else {
|
||||
$this->helpers->apiArrayResponseBuilder(404, 'not found', ['error' => 'Resource id=' . $id . ' could not be found']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function store(Request $request){
|
||||
|
||||
$arr = $request->all();
|
||||
|
||||
while ( $data = current($arr)) {
|
||||
$this->Credit_data->{key($arr)} = $data;
|
||||
next($arr);
|
||||
}
|
||||
|
||||
$validation = Validator::make($request->all(), $this->Credit_data->rules);
|
||||
|
||||
if( $validation->passes() ){
|
||||
$this->Credit_data->save();
|
||||
return $this->helpers->apiArrayResponseBuilder(201, 'created', ['id' => $this->Credit_data->id]);
|
||||
}else{
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validation->errors() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function update($id, Request $request){
|
||||
|
||||
$status = $this->Credit_data->where('id',$id)->update($data);
|
||||
|
||||
if( $status ){
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been updated successfully.');
|
||||
|
||||
}else{
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'bad request', 'Error, data failed to update.');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($id){
|
||||
|
||||
$this->Credit_data->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
public function destroy($id){
|
||||
|
||||
$this->Credit_data->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
|
||||
public static function getAfterFilters() {return [];}
|
||||
public static function getBeforeFilters() {return [];}
|
||||
public static function getMiddleware() {return [];}
|
||||
public function callAction($method, $parameters=false) {
|
||||
return call_user_func_array(array($this, $method), $parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
api controller here
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Controllers\API;
|
||||
|
||||
use Cms\Classes\Controller;
|
||||
use BackendMenu;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use AhmadFatoni\ApiGenerator\Helpers\Helpers;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Atash\Contact\Models\TypeAccountReplenishment;
|
||||
class typeAccountReplenishmentController extends Controller
|
||||
{
|
||||
protected $TypeAccountReplenishment;
|
||||
|
||||
protected $helpers;
|
||||
|
||||
public function __construct(TypeAccountReplenishment $TypeAccountReplenishment, Helpers $helpers)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->TypeAccountReplenishment = $TypeAccountReplenishment;
|
||||
$this->helpers = $helpers;
|
||||
}
|
||||
|
||||
public function index(){
|
||||
|
||||
// $data = $this->TypeAccountReplenishment->all()->toArray();
|
||||
$data = $this->TypeAccountReplenishment->with(['translations:locale,model_id,attribute_data'])->get();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', $data);
|
||||
}
|
||||
|
||||
public function show($id){
|
||||
|
||||
$data = $this->TypeAccountReplenishment::find($id);
|
||||
|
||||
if ($data){
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', [$data]);
|
||||
} else {
|
||||
$this->helpers->apiArrayResponseBuilder(404, 'not found', ['error' => 'Resource id=' . $id . ' could not be found']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function store(Request $request){
|
||||
|
||||
$arr = $request->all();
|
||||
|
||||
while ( $data = current($arr)) {
|
||||
$this->TypeAccountReplenishment->{key($arr)} = $data;
|
||||
next($arr);
|
||||
}
|
||||
|
||||
$validation = Validator::make($request->all(), $this->TypeAccountReplenishment->rules);
|
||||
|
||||
if( $validation->passes() ){
|
||||
$this->TypeAccountReplenishment->save();
|
||||
return $this->helpers->apiArrayResponseBuilder(201, 'created', ['id' => $this->TypeAccountReplenishment->id]);
|
||||
}else{
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validation->errors() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function update($id, Request $request){
|
||||
|
||||
$status = $this->TypeAccountReplenishment->where('id',$id)->update($data);
|
||||
|
||||
if( $status ){
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been updated successfully.');
|
||||
|
||||
}else{
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'bad request', 'Error, data failed to update.');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($id){
|
||||
|
||||
$this->TypeAccountReplenishment->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
public function destroy($id){
|
||||
|
||||
$this->TypeAccountReplenishment->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
|
||||
public static function getAfterFilters() {return [];}
|
||||
public static function getBeforeFilters() {return [];}
|
||||
public static function getMiddleware() {return [];}
|
||||
public function callAction($method, $parameters=false) {
|
||||
return call_user_func_array(array($this, $method), $parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Controllers\API;
|
||||
|
||||
use Cms\Classes\Controller;
|
||||
use BackendMenu;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use AhmadFatoni\ApiGenerator\Helpers\Helpers;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use RainLab\User\Models\User;
|
||||
class usersigninController extends Controller
|
||||
{
|
||||
protected $User;
|
||||
|
||||
protected $helpers;
|
||||
|
||||
public function __construct(User $User, Helpers $helpers)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->User = $User;
|
||||
$this->helpers = $helpers;
|
||||
}
|
||||
|
||||
public function index(){
|
||||
|
||||
$data = $this->User->all()->toArray();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', $data);
|
||||
}
|
||||
|
||||
public function show($id){
|
||||
|
||||
$data = $this->User::find($id);
|
||||
|
||||
if ($data){
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', [$data]);
|
||||
} else {
|
||||
$this->helpers->apiArrayResponseBuilder(404, 'not found', ['error' => 'Resource id=' . $id . ' could not be found']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function store(Request $request){
|
||||
|
||||
$arr = $request->all();
|
||||
|
||||
while ( $data = current($arr)) {
|
||||
$this->User->{key($arr)} = $data;
|
||||
next($arr);
|
||||
}
|
||||
|
||||
$validation = Validator::make($request->all(), $this->User->rules);
|
||||
|
||||
if( $validation->passes() ){
|
||||
$this->User->save();
|
||||
return $this->helpers->apiArrayResponseBuilder(201, 'created', ['id' => $this->User->id]);
|
||||
}else{
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validation->errors() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function update($id, Request $request){
|
||||
|
||||
$status = $this->User->where('id',$id)->update($data);
|
||||
|
||||
if( $status ){
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been updated successfully.');
|
||||
|
||||
}else{
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'bad request', 'Error, data failed to update.');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($id){
|
||||
|
||||
$this->User->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
public function destroy($id){
|
||||
|
||||
$this->User->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
|
||||
public static function getAfterFilters() {return [];}
|
||||
public static function getBeforeFilters() {return [];}
|
||||
public static function getMiddleware() {return [];}
|
||||
public function callAction($method, $parameters=false) {
|
||||
return call_user_func_array(array($this, $method), $parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<div data-control="toolbar">
|
||||
<a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller/create') ?>" class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.form.create')) ?></a>
|
||||
<button
|
||||
class="btn btn-default oc-icon-trash-o"
|
||||
disabled="disabled"
|
||||
onclick="$(this).data('request-data', {
|
||||
checked: $('.control-list').listWidget('getChecked')
|
||||
})"
|
||||
data-request="onDelete"
|
||||
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
|
||||
data-trigger-action="enable"
|
||||
data-trigger=".control-list input[type=checkbox]"
|
||||
data-trigger-condition="checked"
|
||||
data-request-success="$(this).prop('disabled', true)"
|
||||
data-stripe-load-indicator>
|
||||
<?= e(trans('backend::lang.list.delete_selected')) ?>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<div data-control="toolbar">
|
||||
<a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>" class="btn btn-primary oc-icon-caret-left"><?= e(trans('backend::lang.form.return_to_list')) ?></a>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
name: ApiGeneratorController
|
||||
form: $/ahmadfatoni/apigenerator/models/apigenerator/fields.yaml
|
||||
modelClass: AhmadFatoni\ApiGenerator\Models\ApiGenerator
|
||||
defaultRedirect: ahmadfatoni/apigenerator/apigeneratorcontroller
|
||||
create:
|
||||
redirect: 'ahmadfatoni/apigenerator/apigeneratorcontroller/update/:id'
|
||||
redirectClose: ahmadfatoni/apigenerator/apigeneratorcontroller
|
||||
update:
|
||||
redirect: ahmadfatoni/apigenerator/apigeneratorcontroller
|
||||
redirectClose: ahmadfatoni/apigenerator/apigeneratorcontroller
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
list: $/ahmadfatoni/apigenerator/models/apigenerator/columns.yaml
|
||||
modelClass: AhmadFatoni\ApiGenerator\Models\ApiGenerator
|
||||
title: ApiGeneratorController
|
||||
noRecordsMessage: 'backend::lang.list.no_records'
|
||||
showSetup: true
|
||||
showCheckboxes: true
|
||||
toolbar:
|
||||
buttons: list_toolbar
|
||||
search:
|
||||
prompt: 'backend::lang.list.search_prompt'
|
||||
recordUrl: 'ahmadfatoni/apigenerator/apigeneratorcontroller/update/:id'
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
title: ApiGeneratorController
|
||||
modelClass: AhmadFatoni\ApiGenerator\Models\ApiGenerator
|
||||
toolbar:
|
||||
buttons: reorder_toolbar
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>">ApiListController</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal-id">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Custom Condition</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php
|
||||
$data = '{
|
||||
"fillable": "id,title,content",
|
||||
"relation": [{
|
||||
"name": "user",
|
||||
"fillable": "id,first_name"
|
||||
}, {
|
||||
"name": "categories",
|
||||
"fillable": "id,name"
|
||||
}]
|
||||
}';
|
||||
echo $data ;
|
||||
|
||||
?>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-request-success="saveData()"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.create')) ?>
|
||||
</button>
|
||||
|
||||
<a data-toggle="modal" href='#modal-id'>
|
||||
<button type="button" class="btn btn-default">
|
||||
Example Custom Condition
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>" class="btn btn-warning">Cancel</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Form::close() ?>
|
||||
|
||||
<form method="post" id="generate" accept-charset="utf-8" action="<?= route('fatoni.generate.api') ?>" style="display:none">
|
||||
<input type='text' name='name' id="name">
|
||||
<input type='text' name='model' id="model">
|
||||
<input type='text' name='custom_format' id="custom_format">
|
||||
<input type='text' name='endpoint' id="endpoint">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary" name="send" id="send">
|
||||
send
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
|
||||
<?php endif ?>
|
||||
|
||||
<script type="text/javascript">
|
||||
function saveData(){
|
||||
document.getElementById('name').value = document.getElementById('Form-field-ApiGenerator-name').value;
|
||||
document.getElementById('model').value = document.getElementById('Form-field-ApiGenerator-model').value;
|
||||
document.getElementById('custom_format').value = document.getElementById('Form-field-ApiGenerator-custom_format').value;
|
||||
document.getElementById('endpoint').value = document.getElementById('Form-field-ApiGenerator-endpoint').value;
|
||||
|
||||
document.getElementById('send').click();
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<?= $this->listRender() ?>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>">ApiGeneratorController</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<div class="form-preview">
|
||||
<?= $this->formRenderPreview() ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
|
||||
<?php endif ?>
|
||||
|
||||
<p>
|
||||
<a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>" class="btn btn-default oc-icon-chevron-left">
|
||||
<?= e(trans('backend::lang.form.return_to_list')) ?>
|
||||
</a>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>">ApiGeneratorController</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?= $this->reorderRender() ?>
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>">ApiListController</a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal-id">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Custom Condition</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php
|
||||
$data = '{
|
||||
"fillable": "id,title,content",
|
||||
"relation": [{
|
||||
"name": "user",
|
||||
"fillable": "id,first_name"
|
||||
}, {
|
||||
"name": "categories",
|
||||
"fillable": "id,name"
|
||||
}]
|
||||
}';
|
||||
echo $data ;
|
||||
|
||||
?>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-buttons">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-request-success="saveData()"
|
||||
data-request-data="redirect:0"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.save')) ?>
|
||||
</button>
|
||||
|
||||
<a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>" class="btn btn-warning"><div id="cancel">Cancel</div></a>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="oc-icon-trash-o btn-icon danger pull-right"
|
||||
data-request="onDelete"
|
||||
data-request-success="delData()"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.deleting')) ?>"
|
||||
data-request-confirm="<?= e(trans('backend::lang.form.confirm_delete')) ?>">
|
||||
</button>
|
||||
|
||||
<a data-toggle="modal" href='#modal-id'>
|
||||
<button type="button" class="btn btn-default">
|
||||
Example Custom Condition
|
||||
</button>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<?= Form::close() ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('ahmadfatoni/apigenerator/apigeneratorcontroller') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
|
||||
<?php endif ?>
|
||||
|
||||
<form method="get" id="del" style="display:none" action="<?= route('fatoni.delete.api', ['id'=> $this->widget->form->data->attributes['name'] ]) ?>">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary" name="send" id="send">
|
||||
send
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="post" id="generate" accept-charset="utf-8" action="<?= route('fatoni.generate.api') ?>" style="display:none">
|
||||
<input type='text' name='id' id="id" value="<?= $this->widget->form->data->attributes['id'] ?>">
|
||||
<input type='text' name='name' id="name">
|
||||
<input type='text' name='endpoint' id="endpoint">
|
||||
<input type='text' name='oldname' id="oldname" value="<?= $this->widget->form->data->attributes['name'] ?>">
|
||||
<input type='text' name='oldmodel' id="oldmodel" value="<?= $this->widget->form->data->attributes['model'] ?>">
|
||||
<input type='text' name='oldendpoint' id="oldendpoint" value="<?= $this->widget->form->data->attributes['endpoint'] ?>">
|
||||
<textarea name='oldcustom_format' id="oldcustom_format"><?= $this->widget->form->data->attributes['custom_format'] ?></textarea>
|
||||
|
||||
<input type='text' name='model' id="model">
|
||||
<input type='text' name='custom_format' id="custom_format">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary" name="save" id="save">
|
||||
send
|
||||
</button>
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
function delData(){
|
||||
document.getElementById('send').click();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function saveData(){
|
||||
document.getElementById('name').value = document.getElementById('Form-field-ApiGenerator-name').value;
|
||||
document.getElementById('model').value = document.getElementById('Form-field-ApiGenerator-model').value;
|
||||
document.getElementById('custom_format').value = document.getElementById('Form-field-ApiGenerator-custom_format').value;
|
||||
document.getElementById('endpoint').value = document.getElementById('Form-field-ApiGenerator-endpoint').value;
|
||||
if (
|
||||
document.getElementById('name').value != document.getElementById('oldname').value ||
|
||||
document.getElementById('Form-field-ApiGenerator-model').value != document.getElementById('oldmodel').value ||
|
||||
document.getElementById('Form-field-ApiGenerator-custom_format').value != document.getElementById('oldcustom_format').value ||
|
||||
document.getElementById('Form-field-ApiGenerator-endpoint').value != document.getElementById('oldendpoint').value
|
||||
){
|
||||
document.getElementById('save').click();
|
||||
}else{
|
||||
document.getElementById('cancel').click();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Helpers;
|
||||
|
||||
Class Helpers {
|
||||
|
||||
public function apiArrayResponseBuilder($statusCode = null, $message = null, $data = [])
|
||||
{
|
||||
$arr = [
|
||||
'status_code' => (isset($statusCode)) ? $statusCode : 500,
|
||||
'message' => (isset($message)) ? $message : 'error'
|
||||
];
|
||||
if (count($data) > 0) {
|
||||
$arr['data'] = $data;
|
||||
}
|
||||
|
||||
return response()->json($arr, $arr['status_code']);
|
||||
//return $arr;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php return [
|
||||
'plugin' => [
|
||||
'name' => 'API-Generator',
|
||||
'description' => 'Generate API base on Builder Plugin'
|
||||
]
|
||||
];
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Models;
|
||||
|
||||
use Model, Log;
|
||||
use RainLab\Builder\Classes\ComponentHelper;
|
||||
|
||||
/**
|
||||
* Model
|
||||
*/
|
||||
class ApiGenerator extends Model
|
||||
{
|
||||
use \October\Rain\Database\Traits\Validation;
|
||||
|
||||
/*
|
||||
* Validation
|
||||
*/
|
||||
public $rules = [
|
||||
'name' => 'required|unique:ahmadfatoni_apigenerator_data,name|regex:/^[\pL\s\-]+$/u',
|
||||
'endpoint' => 'required|unique:ahmadfatoni_apigenerator_data,endpoint',
|
||||
'custom_format' => 'json'
|
||||
];
|
||||
|
||||
public $customMessages = [
|
||||
'custom_format.json' => 'Invalid Json Format Custom Condition'
|
||||
];
|
||||
|
||||
/*
|
||||
* Disable timestamps by default.
|
||||
* Remove this line if timestamps are defined in the database table.
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
public $table = 'ahmadfatoni_apigenerator_data';
|
||||
|
||||
/**
|
||||
* get model List
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function getModelOptions(){
|
||||
|
||||
return ComponentHelper::instance()->listGlobalModels();
|
||||
}
|
||||
|
||||
/**
|
||||
* [setCustomFormatAttribute description]
|
||||
* @param [type] $value [description]
|
||||
*/
|
||||
public function setCustomFormatAttribute($value){
|
||||
|
||||
$json = str_replace('\t', '', $value);
|
||||
$json = json_decode($json);
|
||||
|
||||
if( $json != null){
|
||||
|
||||
if( ! isset($json->fillable) AND ! isset($json->relation) ){
|
||||
|
||||
return $this->attributes['custom_format'] = 'invalid format';
|
||||
|
||||
}
|
||||
|
||||
if( isset($json->relation) AND $json->relation != null ){
|
||||
foreach ($json->relation as $key) {
|
||||
if( !isset($key->name) OR $key->name == null ){
|
||||
return $this->attributes['custom_format'] = 'invalid format';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->attributes['custom_format'] = $value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
columns:
|
||||
name:
|
||||
label: 'API NAME'
|
||||
type: text
|
||||
searchable: true
|
||||
sortable: true
|
||||
endpoint:
|
||||
label: 'BASE ENDPOINT'
|
||||
type: text
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
fields:
|
||||
name:
|
||||
label: 'API Name'
|
||||
oc.commentPosition: ''
|
||||
span: auto
|
||||
placeholder: 'Name of your API'
|
||||
required: 1
|
||||
type: text
|
||||
endpoint:
|
||||
label: 'Base Endpoint'
|
||||
oc.commentPosition: ''
|
||||
span: auto
|
||||
placeholder: api/v1/modulename
|
||||
required: 1
|
||||
type: text
|
||||
description:
|
||||
label: 'Short Description'
|
||||
oc.commentPosition: ''
|
||||
span: auto
|
||||
placeholder: 'Descript your API'
|
||||
type: text
|
||||
model:
|
||||
label: 'Select Model'
|
||||
oc.commentPosition: ''
|
||||
span: auto
|
||||
required: 1
|
||||
type: dropdown
|
||||
custom_format:
|
||||
label: 'Custom Condition (Fillable and Relation)'
|
||||
size: large
|
||||
oc.commentPosition: ''
|
||||
span: full
|
||||
type: textarea
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
plugin:
|
||||
name: 'ahmadfatoni.apigenerator::lang.plugin.name'
|
||||
description: 'ahmadfatoni.apigenerator::lang.plugin.description'
|
||||
author: AhmadFatoni
|
||||
icon: oc-icon-bolt
|
||||
homepage: ''
|
||||
navigation:
|
||||
api-generator:
|
||||
label: 'API Generator'
|
||||
url: ahmadfatoni/apigenerator/apigeneratorcontroller
|
||||
icon: icon-cogs
|
||||
permissions:
|
||||
- ahmadfatoni.apigenerator.manage
|
||||
permissions:
|
||||
ahmadfatoni.apigenerator.manage:
|
||||
tab: 'API Generator'
|
||||
label: 'Manage the API Generator'
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
Route::post('fatoni/generate/api', array('as' => 'fatoni.generate.api', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\ApiGeneratorController@generateApi'));
|
||||
Route::post('fatoni/update/api/{id}', array('as' => 'fatoni.update.api', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\ApiGeneratorController@updateApi'));
|
||||
Route::get('fatoni/delete/api/{id}', array('as' => 'fatoni.delete.api', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\ApiGeneratorController@deleteApi'));
|
||||
|
||||
Route::resource('api/v1/credit_data', 'AhmadFatoni\ApiGenerator\Controllers\API\creditController', ['except' => ['destroy', 'create', 'edit']]);
|
||||
Route::get('api/v1/credit_data/{id}/delete', ['as' => 'api/v1/credit_data.delete', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\API\creditController@destroy']);
|
||||
Route::resource('api/v1/card_data', 'AhmadFatoni\ApiGenerator\Controllers\API\cardController', ['except' => ['destroy', 'create', 'edit']]);
|
||||
Route::get('api/v1/card_data/{id}/delete', ['as' => 'api/v1/card_data.delete', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\API\cardController@destroy']);
|
||||
Route::resource('api/v1/type_account_replenishment', 'AhmadFatoni\ApiGenerator\Controllers\API\typeAccountReplenishmentController', ['except' => ['destroy', 'create', 'edit']]);
|
||||
Route::get('api/v1/type_account_replenishment/{id}/delete', ['as' => 'api/v1/type_account_replenishment.delete', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\API\typeAccountReplenishmentController@destroy']);
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Controllers\API;
|
||||
|
||||
use Cms\Classes\Controller;
|
||||
use BackendMenu;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use AhmadFatoni\ApiGenerator\Helpers\Helpers;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use {{model}};
|
||||
class {{controllername}}Controller extends Controller
|
||||
{
|
||||
protected ${{modelname}};
|
||||
|
||||
protected $helpers;
|
||||
|
||||
public function __construct({{modelname}} ${{modelname}}, Helpers $helpers)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->{{modelname}} = ${{modelname}};
|
||||
$this->helpers = $helpers;
|
||||
}
|
||||
|
||||
public function index(){
|
||||
|
||||
$data = $this->{{modelname}}->all()->toArray();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', $data);
|
||||
}
|
||||
|
||||
public function show($id){
|
||||
|
||||
$data = $this->{{modelname}}::find($id);
|
||||
|
||||
if ($data){
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', [$data]);
|
||||
} else {
|
||||
$this->helpers->apiArrayResponseBuilder(404, 'not found', ['error' => 'Resource id=' . $id . ' could not be found']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function store(Request $request){
|
||||
|
||||
$arr = $request->all();
|
||||
|
||||
while ( $data = current($arr)) {
|
||||
$this->{{modelname}}->{key($arr)} = $data;
|
||||
next($arr);
|
||||
}
|
||||
|
||||
$validation = Validator::make($request->all(), $this->{{modelname}}->rules);
|
||||
|
||||
if( $validation->passes() ){
|
||||
$this->{{modelname}}->save();
|
||||
return $this->helpers->apiArrayResponseBuilder(201, 'created', ['id' => $this->{{modelname}}->id]);
|
||||
}else{
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validation->errors() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function update($id, Request $request){
|
||||
|
||||
$status = $this->{{modelname}}->where('id',$id)->update($data);
|
||||
|
||||
if( $status ){
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been updated successfully.');
|
||||
|
||||
}else{
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'bad request', 'Error, data failed to update.');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($id){
|
||||
|
||||
$this->{{modelname}}->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
public function destroy($id){
|
||||
|
||||
$this->{{modelname}}->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
|
||||
public static function getAfterFilters() {return [];}
|
||||
public static function getBeforeFilters() {return [];}
|
||||
public static function getMiddleware() {return [];}
|
||||
public function callAction($method, $parameters=false) {
|
||||
return call_user_func_array(array($this, $method), $parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Controllers\API;
|
||||
|
||||
use Cms\Classes\Controller;
|
||||
use BackendMenu;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use {{model}};
|
||||
class ApiListController extends Controller
|
||||
{
|
||||
protected ${{modelname}};
|
||||
|
||||
public function __construct({{modelname}} ${{modelname}})
|
||||
{
|
||||
parent::__construct();
|
||||
$this->{{modelname}} = ${{modelname}};
|
||||
}
|
||||
|
||||
public static function getAfterFilters() {return [];}
|
||||
public static function getBeforeFilters() {return [];}
|
||||
public static function getMiddleware() {return [];}
|
||||
public function callAction($method, $parameters=false) {
|
||||
return call_user_func_array(array($this, $method), $parameters);
|
||||
}
|
||||
|
||||
// public function create(Request $request){
|
||||
|
||||
// $arr = $request->all();
|
||||
|
||||
// while ( $data = current($arr)) {
|
||||
// $this->
|
||||
// }
|
||||
// return json_encode($this->{{modelname}}->store($request));
|
||||
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Controllers\API;
|
||||
|
||||
use Cms\Classes\Controller;
|
||||
use BackendMenu;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use AhmadFatoni\ApiGenerator\Helpers\Helpers;
|
||||
use {{model}};
|
||||
class {{controllername}}Controller extends Controller
|
||||
{
|
||||
protected ${{modelname}};
|
||||
|
||||
protected $helpers;
|
||||
|
||||
public function __construct({{modelname}} ${{modelname}}, Helpers $helpers)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->{{modelname}} = ${{modelname}};
|
||||
$this->helpers = $helpers;
|
||||
}
|
||||
|
||||
{{select}}
|
||||
|
||||
{{show}}
|
||||
|
||||
public function store(Request $request){
|
||||
|
||||
$arr = $request->all();
|
||||
|
||||
while ( $data = current($arr)) {
|
||||
$this->{{modelname}}->{key($arr)} = $data;
|
||||
next($arr);
|
||||
}
|
||||
|
||||
$validation = Validator::make($request->all(), $this->{{modelname}}->rules);
|
||||
|
||||
if( $validation->passes() ){
|
||||
$this->{{modelname}}->save();
|
||||
return $this->helpers->apiArrayResponseBuilder(201, 'created', ['id' => $this->{{modelname}}->id]);
|
||||
}else{
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'fail', $validation->errors() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function update($id, Request $request){
|
||||
|
||||
$status = $this->{{modelname}}->where('id',$id)->update($data);
|
||||
|
||||
if( $status ){
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been updated successfully.');
|
||||
|
||||
}else{
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(400, 'bad request', 'Error, data failed to update.');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($id){
|
||||
|
||||
$this->{{modelname}}->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
public function destroy($id){
|
||||
|
||||
$this->{{modelname}}->where('id',$id)->delete();
|
||||
|
||||
return $this->helpers->apiArrayResponseBuilder(200, 'success', 'Data has been deleted successfully.');
|
||||
}
|
||||
|
||||
|
||||
public static function getAfterFilters() {return [];}
|
||||
public static function getBeforeFilters() {return [];}
|
||||
public static function getMiddleware() {return [];}
|
||||
public function callAction($method, $parameters=false) {
|
||||
return call_user_func_array(array($this, $method), $parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
Route::resource('{{modelname}}', 'AhmadFatoni\ApiGenerator\Controllers\API\{{controllername}}Controller', ['except' => ['destroy', 'create', 'edit']]);
|
||||
Route::get('{{modelname}}/{id}/delete', ['as' => '{{modelname}}.delete', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\API\{{controllername}}Controller@destroy']);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
Route::post('fatoni/generate/api', array('as' => 'fatoni.generate.api', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\ApiGeneratorController@generateApi'));
|
||||
Route::post('fatoni/update/api/{id}', array('as' => 'fatoni.update.api', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\ApiGeneratorController@updateApi'));
|
||||
Route::get('fatoni/delete/api/{id}', array('as' => 'fatoni.delete.api', 'uses' => 'AhmadFatoni\ApiGenerator\Controllers\ApiGeneratorController@deleteApi'));
|
||||
{{route}}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php namespace AhmadFatoni\ApiGenerator\Updates;
|
||||
|
||||
use Schema;
|
||||
use October\Rain\Database\Updates\Migration;
|
||||
|
||||
class BuilderTableCreateAhmadfatoniApigeneratorData extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('ahmadfatoni_apigenerator_data', function($table)
|
||||
{
|
||||
$table->engine = 'InnoDB';
|
||||
$table->increments('id');
|
||||
$table->string('name');
|
||||
$table->string('endpoint');
|
||||
$table->string('model');
|
||||
$table->string('description')->nullable();
|
||||
$table->text('custom_format')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('ahmadfatoni_apigenerator_data');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
1.0.1:
|
||||
- 'Initialize plugin.'
|
||||
1.0.2:
|
||||
- 'Database implementation'
|
||||
1.0.3:
|
||||
- 'add builder plugin on requirements dependency'
|
||||
- builder_table_create_ahmadfatoni_apigenerator_data.php
|
||||
1.0.4:
|
||||
- 'fixing bug on PHP 7'
|
||||
1.0.5:
|
||||
- 'fixing bug on request delete data'
|
||||
1.0.6:
|
||||
- 'fixing bug on generate endpoint'
|
||||
1.0.7:
|
||||
- 'fixing bug on October CMS v1.0.456'
|
||||
|
|
@ -0,0 +1 @@
|
|||
/vendor/
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# How to contribute
|
||||
|
||||
Contributions to this project are highly welcome.
|
||||
|
||||
1. Submit your pull requests to the `develop` branch
|
||||
1. Adhere to the [PSR-2 coding](http://www.php-fig.org/psr/psr-2/) standard
|
||||
1. If you are not sure if your ideas are fit for this project, create an issue and ask
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 OFFLINE GmbH
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php namespace OFFLINE\CORS;
|
||||
|
||||
use OFFLINE\CORS\Classes\HandleCors;
|
||||
use OFFLINE\CORS\Classes\HandlePreflight;
|
||||
use OFFLINE\CORS\Classes\ServiceProvider;
|
||||
use OFFLINE\CORS\Models\Settings;
|
||||
use System\Classes\PluginBase;
|
||||
|
||||
class Plugin extends PluginBase
|
||||
{
|
||||
public function boot()
|
||||
{
|
||||
\App::register(ServiceProvider::class);
|
||||
|
||||
$this->app['Illuminate\Contracts\Http\Kernel']
|
||||
->prependMiddleware(HandleCors::class);
|
||||
|
||||
if (request()->isMethod('OPTIONS')) {
|
||||
$this->app['Illuminate\Contracts\Http\Kernel']
|
||||
->prependMiddleware(HandlePreflight::class);
|
||||
}
|
||||
}
|
||||
|
||||
public function registerPermissions()
|
||||
{
|
||||
return [
|
||||
'offline.cors.manage' => [
|
||||
'label' => 'Can manage cors settings',
|
||||
'tab' => 'CORS',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function registerSettings()
|
||||
{
|
||||
return [
|
||||
'cors' => [
|
||||
'label' => 'CORS-Settings',
|
||||
'description' => 'Manage CORS headers',
|
||||
'category' => 'system::lang.system.categories.cms',
|
||||
'icon' => 'icon-code',
|
||||
'class' => Settings::class,
|
||||
'order' => 500,
|
||||
'keywords' => 'cors',
|
||||
'permissions' => ['offline.cors.manage'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# CORS plugin for October CMS
|
||||
|
||||
This plugin is based on [https://github.com/barryvdh/laravel-cors](https://github.com/barryvdh/laravel-cors/blob/master/config/cors.php).
|
||||
|
||||
All configuration for the plugin can be done via the backend settings.
|
||||
|
||||
The following cors headers are supported:
|
||||
|
||||
* Access-Control-Allow-Origin
|
||||
* Access-Control-Allow-Headers
|
||||
* Access-Control-Allow-Methods
|
||||
* Access-Control-Allow-Credentials
|
||||
* Access-Control-Expose-Headers
|
||||
* Access-Control-Max-Age
|
||||
|
||||
Currently these headers are sent for every request. There is no per-route configuration possible at this time.
|
||||
|
||||
## Setup
|
||||
|
||||
After installing the plugin visit the CORS settings page in your October CMS backend settings.
|
||||
|
||||
You can add `*` as an entry to `Allowed origins`, `Allowed headers` and `Allowed methods` to allow any kind of CORS request from everywhere.
|
||||
|
||||
It is advised to be more explicit about these settings. You can add values for each header via the repeater fields.
|
||||
|
||||
> It is important to set these intial settings once for the plugin to work as excpected!
|
||||
|
||||
### Filesystem configuration
|
||||
|
||||
As an alternative to the backend settings you can create a `config/config.php` file in the plugins root directory to configure it.
|
||||
|
||||
The filesystem configuration will overwrite any defined backend setting.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// plugins/offline/cors/config/config.php
|
||||
return [
|
||||
'supportsCredentials' => true,
|
||||
'maxAge' => 3600,
|
||||
'allowedOrigins' => ['*'],
|
||||
'allowedHeaders' => ['*'],
|
||||
'allowedMethods' => ['GET', 'POST'],
|
||||
'exposedHeaders' => [''],
|
||||
];
|
||||
```
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace OFFLINE\CORS\Classes;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
/**
|
||||
* Based on asm89/stack-cors
|
||||
*/
|
||||
class Cors implements HttpKernelInterface
|
||||
{
|
||||
/**
|
||||
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
|
||||
*/
|
||||
private $app;
|
||||
|
||||
/**
|
||||
* @var CorsService
|
||||
*/
|
||||
private $cors;
|
||||
|
||||
private $defaultOptions = [
|
||||
'allowedHeaders' => [],
|
||||
'allowedMethods' => [],
|
||||
'allowedOrigins' => [],
|
||||
'exposedHeaders' => false,
|
||||
'maxAge' => false,
|
||||
'supportsCredentials' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* Cors constructor.
|
||||
*
|
||||
* @param HttpKernelInterface $app
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(HttpKernelInterface $app, array $options = [])
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->cors = new CorsService(array_merge($this->defaultOptions, $options));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param int $type
|
||||
* @param bool $catch
|
||||
*
|
||||
* @return bool|Response
|
||||
*/
|
||||
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
|
||||
{
|
||||
if ( ! $this->cors->isCorsRequest($request)) {
|
||||
return $this->app->handle($request, $type, $catch);
|
||||
}
|
||||
|
||||
if ($this->cors->isPreflightRequest($request)) {
|
||||
return $this->cors->handlePreflightRequest($request);
|
||||
}
|
||||
|
||||
if ( ! $this->cors->isActualRequestAllowed($request)) {
|
||||
return new Response('Not allowed.', 403);
|
||||
}
|
||||
|
||||
$response = $this->app->handle($request, $type, $catch);
|
||||
|
||||
return $this->cors->addActualRequestHeaders($response, $request);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
namespace OFFLINE\CORS\Classes;
|
||||
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Based on asm89/stack-cors
|
||||
*/
|
||||
class CorsService
|
||||
{
|
||||
private $options;
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->options = $this->normalizeOptions($options);
|
||||
}
|
||||
|
||||
private function normalizeOptions(array $options = [])
|
||||
{
|
||||
$options += [
|
||||
'supportsCredentials' => false,
|
||||
'maxAge' => 0,
|
||||
];
|
||||
|
||||
// Make sure these values are arrays, if not specified in the backend settings.
|
||||
$arrayKeys = [
|
||||
'allowedOrigins',
|
||||
'allowedHeaders',
|
||||
'exposedHeaders',
|
||||
'allowedMethods',
|
||||
];
|
||||
|
||||
foreach ($arrayKeys as $key) {
|
||||
if (!$options[$key]) {
|
||||
$options[$key] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// normalize array('*') to true
|
||||
if (in_array('*', $options['allowedOrigins'])) {
|
||||
$options['allowedOrigins'] = true;
|
||||
}
|
||||
if (in_array('*', $options['allowedHeaders'])) {
|
||||
$options['allowedHeaders'] = true;
|
||||
} else {
|
||||
$options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']);
|
||||
}
|
||||
|
||||
if (in_array('*', $options['allowedMethods'])) {
|
||||
$options['allowedMethods'] = true;
|
||||
} else {
|
||||
$options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function isActualRequestAllowed(Request $request)
|
||||
{
|
||||
return $this->checkOrigin($request);
|
||||
}
|
||||
|
||||
public function isCorsRequest(Request $request)
|
||||
{
|
||||
return $request->headers->has('Origin') && $request->headers->get('Origin') !== $request->getSchemeAndHttpHost();
|
||||
}
|
||||
|
||||
public function isPreflightRequest(Request $request)
|
||||
{
|
||||
return $this->isCorsRequest($request)
|
||||
&& $request->getMethod() === 'OPTIONS'
|
||||
&& $request->headers->has('Access-Control-Request-Method');
|
||||
}
|
||||
|
||||
public function addActualRequestHeaders(Response $response, Request $request)
|
||||
{
|
||||
if ( ! $this->checkOrigin($request)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
|
||||
|
||||
if ( ! $response->headers->has('Vary')) {
|
||||
$response->headers->set('Vary', 'Origin');
|
||||
} else {
|
||||
$response->headers->set('Vary', $response->headers->get('Vary') . ', Origin');
|
||||
}
|
||||
|
||||
if ($this->options['supportsCredentials']) {
|
||||
$response->headers->set('Access-Control-Allow-Credentials', 'true');
|
||||
}
|
||||
|
||||
if ($this->options['exposedHeaders']) {
|
||||
$response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->options['exposedHeaders']));
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function handlePreflightRequest(Request $request)
|
||||
{
|
||||
if (true !== $check = $this->checkPreflightRequestConditions($request)) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
return $this->buildPreflightCheckResponse($request);
|
||||
}
|
||||
|
||||
private function buildPreflightCheckResponse(Request $request)
|
||||
{
|
||||
$response = new Response();
|
||||
|
||||
if ($this->options['supportsCredentials']) {
|
||||
$response->headers->set('Access-Control-Allow-Credentials', 'true');
|
||||
}
|
||||
|
||||
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
|
||||
|
||||
if ($this->options['maxAge']) {
|
||||
$response->headers->set('Access-Control-Max-Age', $this->options['maxAge']);
|
||||
}
|
||||
|
||||
$allowMethods = $this->options['allowedMethods'] === true
|
||||
? strtoupper($request->headers->get('Access-Control-Request-Method'))
|
||||
: implode(', ', $this->options['allowedMethods']);
|
||||
$response->headers->set('Access-Control-Allow-Methods', $allowMethods);
|
||||
|
||||
$allowHeaders = $this->options['allowedHeaders'] === true
|
||||
? strtoupper($request->headers->get('Access-Control-Request-Headers'))
|
||||
: implode(', ', $this->options['allowedHeaders']);
|
||||
$response->headers->set('Access-Control-Allow-Headers', $allowHeaders);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function checkPreflightRequestConditions(Request $request)
|
||||
{
|
||||
if ( ! $this->checkOrigin($request)) {
|
||||
return $this->createBadRequestResponse(403, 'Origin not allowed');
|
||||
}
|
||||
|
||||
if ( ! $this->checkMethod($request)) {
|
||||
return $this->createBadRequestResponse(405, 'Method not allowed');
|
||||
}
|
||||
|
||||
$requestHeaders = [];
|
||||
// if allowedHeaders has been set to true ('*' allow all flag) just skip this check
|
||||
if ($this->options['allowedHeaders'] !== true && $request->headers->has('Access-Control-Request-Headers')) {
|
||||
$headers = strtolower($request->headers->get('Access-Control-Request-Headers'));
|
||||
$requestHeaders = explode(',', $headers);
|
||||
|
||||
foreach ($requestHeaders as $header) {
|
||||
if ( ! in_array(trim($header), $this->options['allowedHeaders'])) {
|
||||
return $this->createBadRequestResponse(403, 'Header not allowed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function createBadRequestResponse($code, $reason = '')
|
||||
{
|
||||
return new Response($reason, $code);
|
||||
}
|
||||
|
||||
private function checkOrigin(Request $request)
|
||||
{
|
||||
if ($this->options['allowedOrigins'] === true) {
|
||||
// allow all '*' flag
|
||||
return true;
|
||||
}
|
||||
$origin = $request->headers->get('Origin');
|
||||
|
||||
foreach ($this->options['allowedOrigins'] as $allowedOrigin) {
|
||||
if (OriginMatcher::matches($allowedOrigin, $origin)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function checkMethod(Request $request)
|
||||
{
|
||||
if ($this->options['allowedMethods'] === true) {
|
||||
// allow all '*' flag
|
||||
return true;
|
||||
}
|
||||
|
||||
$requestMethod = strtoupper($request->headers->get('Access-Control-Request-Method'));
|
||||
|
||||
return in_array($requestMethod, $this->options['allowedMethods']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace OFFLINE\CORS\Classes;
|
||||
|
||||
use Closure;
|
||||
|
||||
class HandleCors
|
||||
{
|
||||
/**
|
||||
* The CORS service
|
||||
*
|
||||
* @var CorsService
|
||||
*/
|
||||
protected $cors;
|
||||
|
||||
/**
|
||||
* @param CorsService $cors
|
||||
*/
|
||||
public function __construct(CorsService $cors)
|
||||
{
|
||||
$this->cors = $cors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request. Based on Asm89\Stack\Cors by asm89
|
||||
* @see https://github.com/asm89/stack-cors/blob/master/src/Asm89/Stack/Cors.php
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if ( ! $this->cors->isCorsRequest($request)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if ( ! $this->cors->isActualRequestAllowed($request)) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
/** @var \Illuminate\Http\Response $response */
|
||||
$response = $next($request);
|
||||
|
||||
return $this->cors->addActualRequestHeaders($response, $request);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace OFFLINE\CORS\Classes;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Http\Kernel;
|
||||
use Illuminate\Routing\Router;
|
||||
|
||||
class HandlePreflight
|
||||
{
|
||||
/**
|
||||
* @param CorsService $cors
|
||||
*/
|
||||
public function __construct(CorsService $cors, Router $router, Kernel $kernel)
|
||||
{
|
||||
$this->cors = $cors;
|
||||
$this->router = $router;
|
||||
$this->kernel = $kernel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming OPTIONS request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$response = $next($request);
|
||||
if ($this->cors->isPreflightRequest($request) && $this->hasMatchingCorsRoute($request)) {
|
||||
$preflight = $this->cors->handlePreflightRequest($request);
|
||||
$response->headers->add($preflight->headers->all());
|
||||
}
|
||||
$response->setStatusCode(204);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the current OPTIONS request matches a CORS-enabled route
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function hasMatchingCorsRoute($request)
|
||||
{
|
||||
// Check if CORS is added in a global middleware
|
||||
if ($this->kernel->hasMiddleware(HandleCors::class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if CORS is added as a route middleware
|
||||
$request = clone $request;
|
||||
$request->setMethod($request->header('Access-Control-Request-Method'));
|
||||
|
||||
try {
|
||||
$route = $this->router->getRoutes()->match($request);
|
||||
// change of method name in laravel 5.3
|
||||
if (method_exists($this->router, 'gatherRouteMiddleware')) {
|
||||
$middleware = $this->router->gatherRouteMiddleware($route);
|
||||
} else {
|
||||
$middleware = $this->router->gatherRouteMiddlewares($route);
|
||||
}
|
||||
|
||||
return in_array(HandleCors::class, $middleware);
|
||||
} catch (\Exception $e) {
|
||||
app('log')->error($e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace OFFLINE\CORS\Classes;
|
||||
|
||||
class OriginMatcher
|
||||
{
|
||||
|
||||
public static function matches($pattern, $origin)
|
||||
{
|
||||
if ($pattern === $origin) {
|
||||
return true;
|
||||
}
|
||||
$scheme = parse_url($origin, PHP_URL_SCHEME);
|
||||
$host = parse_url($origin, PHP_URL_HOST);
|
||||
$port = parse_url($origin, PHP_URL_PORT);
|
||||
|
||||
$schemePattern = static::parseOriginPattern($pattern, PHP_URL_SCHEME);
|
||||
$hostPattern = static::parseOriginPattern($pattern, PHP_URL_HOST);
|
||||
$portPattern = static::parseOriginPattern($pattern, PHP_URL_PORT);
|
||||
|
||||
$schemeMatches = static::schemeMatches($schemePattern, $scheme);
|
||||
$hostMatches = static::hostMatches($hostPattern, $host);
|
||||
$portMatches = static::portMatches($portPattern, $port);
|
||||
|
||||
return $schemeMatches && $hostMatches && $portMatches;
|
||||
}
|
||||
|
||||
public static function schemeMatches($pattern, $scheme)
|
||||
{
|
||||
return is_null($pattern) || $pattern === $scheme;
|
||||
}
|
||||
|
||||
public static function hostMatches($pattern, $host)
|
||||
{
|
||||
$patternComponents = array_reverse(explode('.', $pattern));
|
||||
$hostComponents = array_reverse(explode('.', $host));
|
||||
foreach ($patternComponents as $index => $patternComponent) {
|
||||
if ($patternComponent === '*') {
|
||||
return true;
|
||||
}
|
||||
if ( ! isset($hostComponents[$index])) {
|
||||
return false;
|
||||
}
|
||||
if ($hostComponents[$index] !== $patternComponent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return count($patternComponents) === count($hostComponents);
|
||||
}
|
||||
|
||||
public static function portMatches($pattern, $port)
|
||||
{
|
||||
if ($pattern === "*") {
|
||||
return true;
|
||||
}
|
||||
if ((string)$pattern === "") {
|
||||
return (string)$port === "";
|
||||
}
|
||||
if (preg_match('/\A\d+\z/', $pattern)) {
|
||||
return (string)$pattern === (string)$port;
|
||||
}
|
||||
if (preg_match('/\A(?P<from>\d+)-(?P<to>\d+)\z/', $pattern, $captured)) {
|
||||
return $captured['from'] <= $port && $port <= $captured['to'];
|
||||
}
|
||||
throw new \InvalidArgumentException("Invalid port pattern: ${pattern}");
|
||||
}
|
||||
|
||||
public static function parseOriginPattern($originPattern, $component = -1)
|
||||
{
|
||||
$matched = preg_match(
|
||||
'!\A
|
||||
(?: (?P<scheme> ([a-z][a-z0-9+\-.]*) ):// )?
|
||||
(?P<host> (?:\*|[\w-]+)(?:\.[\w-]+)* )
|
||||
(?: :(?P<port> (?: \*|\d+(?:-\d+)? ) ) )?
|
||||
\z!x',
|
||||
$originPattern,
|
||||
$captured
|
||||
);
|
||||
if ( ! $matched) {
|
||||
throw new \InvalidArgumentException("Invalid origin pattern ${originPattern}");
|
||||
}
|
||||
$components = [
|
||||
'scheme' => $captured['scheme'] ?: null,
|
||||
'host' => $captured['host'],
|
||||
'port' => array_key_exists('port', $captured) ? $captured['port'] : null,
|
||||
];
|
||||
switch ($component) {
|
||||
case -1:
|
||||
return $components;
|
||||
case PHP_URL_SCHEME:
|
||||
return $components['scheme'];
|
||||
case PHP_URL_HOST:
|
||||
return $components['host'];
|
||||
case PHP_URL_PORT:
|
||||
return $components['port'];
|
||||
}
|
||||
throw new \InvalidArgumentException("Invalid component: ${component}");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace OFFLINE\CORS\Classes;
|
||||
|
||||
use October\Rain\Support\ServiceProvider as BaseServiceProvider;
|
||||
use OFFLINE\CORS\Models\Settings;
|
||||
|
||||
class ServiceProvider extends BaseServiceProvider
|
||||
{
|
||||
/**
|
||||
* Indicates if loading of the provider is deferred.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $defer = false;
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton(CorsService::class, function ($app) {
|
||||
return new CorsService($this->getSettings());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default Settings
|
||||
*/
|
||||
protected function getSettings()
|
||||
{
|
||||
$supportsCredentials = (bool)$this->getConfigValue('supportsCredentials', false);
|
||||
$maxAge = (int)$this->getConfigValue('maxAge', 0);
|
||||
$allowedOrigins = $this->getConfigValue('allowedOrigins', []);
|
||||
$allowedHeaders = $this->getConfigValue('allowedHeaders', []);
|
||||
$allowedMethods = $this->getConfigValue('allowedMethods', []);
|
||||
$exposedHeaders = $this->getConfigValue('exposedHeaders', []);
|
||||
|
||||
return compact(
|
||||
'supportsCredentials',
|
||||
'allowedOrigins',
|
||||
'allowedHeaders',
|
||||
'allowedMethods',
|
||||
'exposedHeaders',
|
||||
'maxAge'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an effective config value.
|
||||
*
|
||||
* If a filesystem config is available it takes precedence
|
||||
* over the backend settings values.
|
||||
*
|
||||
* @param $key
|
||||
* @param null $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfigValue($key, $default = null)
|
||||
{
|
||||
return $this->filesystemConfig($key) ?: $this->getValues(Settings::get($key, $default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filesystem config value if available.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function filesystemConfig($key)
|
||||
{
|
||||
return config('offline.cors::' . $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the repeater field values.
|
||||
*
|
||||
* @param mixed $values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getValues($values)
|
||||
{
|
||||
return \is_array($values) ? collect($values)->pluck('value')->toArray() : $values;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "offline/oc-cors-plugin",
|
||||
"description": "Setup and manage Cross-Origin Resource Sharing headers in October CMS",
|
||||
"type": "october-plugin",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Tobias Kündig",
|
||||
"email": "tobias@offline.swiss"
|
||||
}
|
||||
],
|
||||
"require": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php return [
|
||||
'plugin' => [
|
||||
'name' => 'CORS',
|
||||
'description' => 'Verwalte Cross-Origin Resource Sharing Header in October CMS',
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php return [
|
||||
'plugin' => [
|
||||
'name' => 'CORS',
|
||||
'description' => 'Setup and manage Cross-Origin Resource Sharing headers',
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace OFFLINE\CORS\Models;
|
||||
|
||||
use Model;
|
||||
|
||||
class Settings extends Model
|
||||
{
|
||||
public $implement = ['System.Behaviors.SettingsModel'];
|
||||
public $settingsCode = 'offline_cors_settings';
|
||||
public $settingsFields = 'fields.yaml';
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
fields:
|
||||
supportsCredentials:
|
||||
label: Supports credentials
|
||||
type: switch
|
||||
comment: 'Set Access-Control-Allow-Credentials header to true'
|
||||
default: false
|
||||
span: left
|
||||
maxAge:
|
||||
label: Max age
|
||||
type: number
|
||||
comment: 'Set Access-Control-Max-Age to this value'
|
||||
default: 0
|
||||
span: right
|
||||
tabs:
|
||||
fields:
|
||||
allowedOrigins:
|
||||
label: Allowed origins
|
||||
tab: Allowed origins
|
||||
type: repeater
|
||||
span: left
|
||||
form:
|
||||
fields:
|
||||
value:
|
||||
type: text
|
||||
label: Origin
|
||||
allowedHeaders:
|
||||
label: Allowed headers
|
||||
tab: Allowed headers
|
||||
type: repeater
|
||||
span: left
|
||||
form:
|
||||
fields:
|
||||
value:
|
||||
type: text
|
||||
label: Header
|
||||
allowedMethods:
|
||||
label: Allowed methods
|
||||
tab: Allowed methods
|
||||
type: repeater
|
||||
span: left
|
||||
form:
|
||||
fields:
|
||||
value:
|
||||
type: text
|
||||
label: Method
|
||||
exposedHeaders:
|
||||
label: Exposed headers
|
||||
tab: Exposed headers
|
||||
type: repeater
|
||||
span: left
|
||||
form:
|
||||
fields:
|
||||
value:
|
||||
type: text
|
||||
label: Header
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
plugin:
|
||||
name: 'offline.cors::lang.plugin.name'
|
||||
description: 'offline.cors::lang.plugin.description'
|
||||
author: 'OFFLINE GmbH'
|
||||
icon: oc-icon-code
|
||||
homepage: ''
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
1.0.1:
|
||||
- Initial release.
|
||||
1.0.2:
|
||||
- Fixed backend settings label (thanks to LukeTowers)
|
||||
1.0.3:
|
||||
- Added support for filesystem configuration file / Added plugin to Packagist (https://packagist.org/packages/offline/oc-cors-plugin)
|
||||
1.0.4:
|
||||
- Fixed minor bug when running the plugin without custom settings
|
||||
1.0.5:
|
||||
- "Return proper 204 response code for preflight requests (thanks to @adrian-marinescu-ch on GitHub)"
|
||||
1.0.6:
|
||||
- "Dummy release to sync with Packagist version"
|
||||
1.0.7:
|
||||
- "Optimized compatibility with October v2"
|
||||
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# 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.
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
<?php namespace RainLab\Blog;
|
||||
|
||||
use Backend;
|
||||
use Controller;
|
||||
use RainLab\Blog\Models\Post;
|
||||
use System\Classes\PluginBase;
|
||||
use RainLab\Blog\Classes\TagProcessor;
|
||||
use RainLab\Blog\Models\Category;
|
||||
use Event;
|
||||
|
||||
class Plugin extends PluginBase
|
||||
{
|
||||
public function pluginDetails()
|
||||
{
|
||||
return [
|
||||
'name' => 'rainlab.blog::lang.plugin.name',
|
||||
'description' => 'rainlab.blog::lang.plugin.description',
|
||||
'author' => 'Alexey Bobkov, Samuel Georges',
|
||||
'icon' => 'icon-pencil',
|
||||
'homepage' => 'https://github.com/rainlab/blog-plugin'
|
||||
];
|
||||
}
|
||||
|
||||
public function registerComponents()
|
||||
{
|
||||
return [
|
||||
'RainLab\Blog\Components\Post' => 'blogPost',
|
||||
'RainLab\Blog\Components\Posts' => 'blogPosts',
|
||||
'RainLab\Blog\Components\Categories' => 'blogCategories',
|
||||
'RainLab\Blog\Components\RssFeed' => 'blogRssFeed'
|
||||
];
|
||||
}
|
||||
|
||||
public function registerPermissions()
|
||||
{
|
||||
return [
|
||||
'rainlab.blog.manage_settings' => [
|
||||
'tab' => 'rainlab.blog::lang.blog.tab',
|
||||
'label' => 'rainlab.blog::lang.blog.manage_settings'
|
||||
],
|
||||
'rainlab.blog.access_posts' => [
|
||||
'tab' => 'rainlab.blog::lang.blog.tab',
|
||||
'label' => 'rainlab.blog::lang.blog.access_posts'
|
||||
],
|
||||
'rainlab.blog.access_categories' => [
|
||||
'tab' => 'rainlab.blog::lang.blog.tab',
|
||||
'label' => 'rainlab.blog::lang.blog.access_categories'
|
||||
],
|
||||
'rainlab.blog.access_other_posts' => [
|
||||
'tab' => 'rainlab.blog::lang.blog.tab',
|
||||
'label' => 'rainlab.blog::lang.blog.access_other_posts'
|
||||
],
|
||||
'rainlab.blog.access_import_export' => [
|
||||
'tab' => 'rainlab.blog::lang.blog.tab',
|
||||
'label' => 'rainlab.blog::lang.blog.access_import_export'
|
||||
],
|
||||
'rainlab.blog.access_publish' => [
|
||||
'tab' => 'rainlab.blog::lang.blog.tab',
|
||||
'label' => 'rainlab.blog::lang.blog.access_publish'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function registerNavigation()
|
||||
{
|
||||
return [
|
||||
'blog' => [
|
||||
'label' => 'rainlab.blog::lang.blog.menu_label',
|
||||
'url' => Backend::url('rainlab/blog/posts'),
|
||||
'icon' => 'icon-pencil',
|
||||
'iconSvg' => 'plugins/rainlab/blog/assets/images/blog-icon.svg',
|
||||
'permissions' => ['rainlab.blog.*'],
|
||||
'order' => 300,
|
||||
|
||||
'sideMenu' => [
|
||||
'new_post' => [
|
||||
'label' => 'rainlab.blog::lang.posts.new_post',
|
||||
'icon' => 'icon-plus',
|
||||
'url' => Backend::url('rainlab/blog/posts/create'),
|
||||
'permissions' => ['rainlab.blog.access_posts']
|
||||
],
|
||||
'posts' => [
|
||||
'label' => 'rainlab.blog::lang.blog.posts',
|
||||
'icon' => 'icon-copy',
|
||||
'url' => Backend::url('rainlab/blog/posts'),
|
||||
'permissions' => ['rainlab.blog.access_posts']
|
||||
],
|
||||
'categories' => [
|
||||
'label' => 'rainlab.blog::lang.blog.categories',
|
||||
'icon' => 'icon-list-ul',
|
||||
'url' => Backend::url('rainlab/blog/categories'),
|
||||
'permissions' => ['rainlab.blog.access_categories']
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function registerSettings()
|
||||
{
|
||||
return [
|
||||
'blog' => [
|
||||
'label' => 'rainlab.blog::lang.blog.menu_label',
|
||||
'description' => 'rainlab.blog::lang.blog.settings_description',
|
||||
'category' => 'rainlab.blog::lang.blog.menu_label',
|
||||
'icon' => 'icon-pencil',
|
||||
'class' => 'RainLab\Blog\Models\Settings',
|
||||
'order' => 500,
|
||||
'keywords' => 'blog post category',
|
||||
'permissions' => ['rainlab.blog.manage_settings']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register method, called when the plugin is first registered.
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
/*
|
||||
* Register the image tag processing callback
|
||||
*/
|
||||
TagProcessor::instance()->registerCallback(function($input, $preview) {
|
||||
if (!$preview) {
|
||||
return $input;
|
||||
}
|
||||
|
||||
return preg_replace('|\<img src="image" alt="([0-9]+)"([^>]*)\/>|m',
|
||||
'<span class="image-placeholder" data-index="$1">
|
||||
<span class="upload-dropzone">
|
||||
<span class="label">Click or drop an image...</span>
|
||||
<span class="indicator"></span>
|
||||
</span>
|
||||
</span>',
|
||||
$input);
|
||||
});
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
/*
|
||||
* Register menu items for the RainLab.Pages plugin
|
||||
*/
|
||||
Event::listen('pages.menuitem.listTypes', function() {
|
||||
return [
|
||||
'blog-category' => 'rainlab.blog::lang.menuitem.blog_category',
|
||||
'all-blog-categories' => 'rainlab.blog::lang.menuitem.all_blog_categories',
|
||||
'blog-post' => 'rainlab.blog::lang.menuitem.blog_post',
|
||||
'all-blog-posts' => 'rainlab.blog::lang.menuitem.all_blog_posts',
|
||||
'category-blog-posts' => 'rainlab.blog::lang.menuitem.category_blog_posts',
|
||||
];
|
||||
});
|
||||
|
||||
Event::listen('pages.menuitem.getTypeInfo', function($type) {
|
||||
if ($type == 'blog-category' || $type == 'all-blog-categories') {
|
||||
return Category::getMenuTypeInfo($type);
|
||||
}
|
||||
elseif ($type == 'blog-post' || $type == 'all-blog-posts' || $type == 'category-blog-posts') {
|
||||
return Post::getMenuTypeInfo($type);
|
||||
}
|
||||
});
|
||||
|
||||
Event::listen('pages.menuitem.resolveItem', function($type, $item, $url, $theme) {
|
||||
if ($type == 'blog-category' || $type == 'all-blog-categories') {
|
||||
return Category::resolveMenuItem($item, $url, $theme);
|
||||
}
|
||||
elseif ($type == 'blog-post' || $type == 'all-blog-posts' || $type == 'category-blog-posts') {
|
||||
return Post::resolveMenuItem($item, $url, $theme);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
# Blog Plugin
|
||||
|
||||
A simple, extensible blogging platform for October CMS.
|
||||
|
||||
[Blog & Forum Building Tutorial Video](https://player.vimeo.com/video/97088926)
|
||||
|
||||
## Editing posts
|
||||
|
||||
The plugin uses the markdown markup for the posts. You can use any Markdown syntax and some special tags for embedding images and videos (requires RainLab Blog Video plugin). To embed an image use the image placeholder:
|
||||
|
||||

|
||||
|
||||
The number in the first part is the placeholder index. If you use multiple images in a post you should use an unique index for each image:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
You can also add classes or ids to images by using the [markdown extra](http://michelf.ca/projects/php-markdown/extra/) syntax:
|
||||
|
||||
{#id .class}
|
||||
|
||||
## Excerpt Vs. Read more
|
||||
|
||||
Posts are managed by selecting *Blog > Posts* from the menu. Each post can contain an excerpt by entering some text in this field on the *Manage* tab. This content is displayed on the page using the `summary` attribute of the blog post.
|
||||
|
||||
{{ post.summary|raw }}
|
||||
|
||||
Alternatively this field can be left blank and the excerpt can be captured from the main content (*Edit* tab). Use the special tag `<!-- more -->` to specify a summary from the main content, all content above this tag will be treated as the summary. For example:
|
||||
|
||||
This is a great introduction to a great blog post. This text is included as part of the excerpt / summary.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
Let's dive in to more detail about why this post is so great. This text will not be included in the summary.
|
||||
|
||||
Finally, if no excerpt is specified and the "more" tag is not used, the blog post will capture the first 600 characters of the content and use this for the summary.
|
||||
|
||||
## Implementing front-end pages
|
||||
|
||||
The plugin provides several components for building the post list page (archive), category page, post details page and category list for the sidebar.
|
||||
|
||||
### Post list page
|
||||
|
||||
Use the `blogPosts` component to display a list of latest blog posts on a page. The component has the following properties:
|
||||
|
||||
* **pageNumber** - this value is used to determine what page the user is on, it should be a routing parameter for the default markup. The default value is **{{ :page }}** to obtain the value from the route parameter `:page`.
|
||||
* **categoryFilter** - a category slug to filter the posts by. If left blank, all posts are displayed.
|
||||
* **postsPerPage** - how many posts to display on a single page (the pagination is supported automatically). The default value is 10.
|
||||
* **noPostsMessage** - message to display in the empty post list.
|
||||
* **sortOrder** - the column name and direction used for the sort order of the posts. The default value is **published_at desc**.
|
||||
* **categoryPage** - path to the category page. The default value is **blog/category** - it matches the pages/blog/category.htm file in the theme directory. This property is used in the default component partial for creating links to the blog categories.
|
||||
* **postPage** - path to the post details page. The default value is **blog/post** - it matches the pages/blog/post.htm file in the theme directory. This property is used in the default component partial for creating links to the blog posts.
|
||||
* **exceptPost** - ignore a single post by its slug or unique ID. The ignored post will not be included in the list, useful for showing other/related posts.
|
||||
* **exceptCategories** - ignore posts from a comma-separated list of categories, given by their unique slug. The ignored posts will not be included in the list.
|
||||
|
||||
The blogPosts component injects the following variables to the page where it's used:
|
||||
|
||||
* **posts** - a list of blog posts loaded from the database.
|
||||
* **postPage** - contains the value of the `postPage` component's property.
|
||||
* **category** - the blog category object loaded from the database. If the category is not found, the variable value is **null**.
|
||||
* **categoryPage** - contains the value of the `categoryPage` component's property.
|
||||
* **noPostsMessage** - contains the value of the `noPostsMessage` component's property.
|
||||
|
||||
The component supports pagination and reads the current page index from the `:page` URL parameter. The next example shows the basic component usage on the blog home page:
|
||||
|
||||
title = "Blog"
|
||||
url = "/blog/:page?"
|
||||
|
||||
[blogPosts]
|
||||
postsPerPage = "5"
|
||||
==
|
||||
{% component 'blogPosts' %}
|
||||
|
||||
The next example shows the basic component usage with the category filter:
|
||||
|
||||
title = "Blog Category"
|
||||
url = "/blog/category/:slug/:page?"
|
||||
|
||||
[blogPosts]
|
||||
categoryFilter = "{{ :slug }}"
|
||||
==
|
||||
function onEnd()
|
||||
{
|
||||
// Optional - set the page title to the category name
|
||||
if ($this->category)
|
||||
$this->page->title = $this->category->name;
|
||||
}
|
||||
==
|
||||
{% if not category %}
|
||||
<h2>Category not found</h2>
|
||||
{% else %}
|
||||
<h2>{{ category.name }}</h2>
|
||||
|
||||
{% component 'blogPosts' %}
|
||||
{% endif %}
|
||||
|
||||
The post list and the pagination are coded in the default component partial `plugins/rainlab/blog/components/posts/default.htm`. If the default markup is not suitable for your website, feel free to copy it from the default partial and replace the `{% component %}` call in the example above with the partial contents.
|
||||
|
||||
### Post page
|
||||
|
||||
Use the `blogPost` component to display a blog post on a page. The component has the following properties:
|
||||
|
||||
* **slug** - the value used for looking up the post by its slug. The default value is **{{ :slug }}** to obtain the value from the route parameter `:slug`.
|
||||
* **categoryPage** - path to the category page. The default value is **blog/category** - it matches the pages/blog/category.htm file in the theme directory. This property is used in the default component partial for creating links to the blog categories.
|
||||
|
||||
The component injects the following variables to the page where it's used:
|
||||
|
||||
* **post** - the blog post object loaded from the database. If the post is not found, the variable value is **null**.
|
||||
|
||||
The next example shows the basic component usage on the blog page:
|
||||
|
||||
title = "Blog Post"
|
||||
url = "/blog/post/:slug"
|
||||
|
||||
[blogPost]
|
||||
==
|
||||
<?php
|
||||
function onEnd()
|
||||
{
|
||||
// Optional - set the page title to the post title
|
||||
if ($this->post)
|
||||
$this->page->title = $this->post->title;
|
||||
}
|
||||
?>
|
||||
==
|
||||
{% if post %}
|
||||
<h2>{{ post.title }}</h2>
|
||||
|
||||
{% component 'blogPost' %}
|
||||
{% else %}
|
||||
<h2>Post not found</h2>
|
||||
{% endif %}
|
||||
|
||||
The post details is coded in the default component partial `plugins/rainlab/blog/components/post/default.htm`.
|
||||
|
||||
### Category list
|
||||
|
||||
Use the `blogCategories` component to display a list of blog post categories with links. The component has the following properties:
|
||||
|
||||
* **slug** - the value used for looking up the current category by its slug. The default value is **{{ :slug }}** to obtain the value from the route parameter `:slug`.
|
||||
* **displayEmpty** - determines if empty categories should be displayed. The default value is false.
|
||||
* **categoryPage** - path to the category page. The default value is **blog/category** - it matches the pages/blog/category.htm file in the theme directory. This property is used in the default component partial for creating links to the blog categories.
|
||||
|
||||
The component injects the following variables to the page where it's used:
|
||||
|
||||
* **categoryPage** - contains the value of the `categoryPage` component's property.
|
||||
* **categories** - a list of blog categories loaded from the database.
|
||||
* **currentCategorySlug** - slug of the current category. This property is used for marking the current category in the category list.
|
||||
|
||||
The component can be used on any page. The next example shows the basic component usage on the blog home page:
|
||||
|
||||
title = "Blog"
|
||||
url = "/blog/:page?"
|
||||
|
||||
[blogCategories]
|
||||
==
|
||||
...
|
||||
<div class="sidebar">
|
||||
{% component 'blogCategories' %}
|
||||
</div>
|
||||
...
|
||||
|
||||
The category list is coded in the default component partial `plugins/rainlab/blog/components/categories/default.htm`.
|
||||
|
||||
### RSS feed
|
||||
|
||||
Use the `blogRssFeed` component to display an RSS feed containing the latest blog posts. The following properties are supported:
|
||||
|
||||
* **categoryFilter** - a category slug to filter the posts by. If left blank, all posts are displayed.
|
||||
* **postsPerPage** - how many posts to display on the feed. The default value is 10.
|
||||
* **blogPage** - path to the main blog page. The default value is **blog** - it matches the pages/blog.htm file in the theme directory. This property is used in the RSS feed for creating links to the main blog page.
|
||||
* **postPage** - path to the post details page. The default value is **blog/post** - it matches the pages/blog/post.htm file in the theme directory. This property is used in the RSS feed for creating links to the blog posts.
|
||||
|
||||
The component can be used on any page, it will hijack the entire page cycle to display the feed in RSS format. The next example shows how to use it:
|
||||
|
||||
title = "RSS Feed"
|
||||
url = "/blog/rss.xml"
|
||||
|
||||
[blogRssFeed]
|
||||
blogPage = "blog"
|
||||
postPage = "blog/post"
|
||||
==
|
||||
<!-- This markup will never be displayed -->
|
||||
|
||||
## Configuration
|
||||
|
||||
To overwrite the default configuration create a `config/rainlab/blog/config.php`. You can return only values you want to override.
|
||||
|
||||
### Summary
|
||||
|
||||
A summary attribute is generated for each post.
|
||||
|
||||
If you enter an excerpt manually, it gets used as summary. Alternatively, you can use the `summary_separator` (default is `<!-- more -->`) to mark the end of the summary. If a post contains no separator, the text gets truncated after the number of characters specified in `summary_default_length` (default is 600 characters).
|
||||
|
||||
## Markdown guide
|
||||
|
||||
October supports [standard markdown syntax](http://daringfireball.net/projects/markdown/) as well as [extended markdown syntax](http://michelf.ca/projects/php-markdown/extra/)
|
||||
|
||||
### Classes and IDs
|
||||
|
||||
Classes and IDs can be added to images and other elements as shown below:
|
||||
|
||||
```
|
||||
[link](url){#id .class}
|
||||
{#id .class}
|
||||
# October {#id .class}
|
||||
```
|
||||
|
||||
### Fenced code blogs
|
||||
|
||||
Markdown extra makes it possible to use fenced code blocks. With fenced code blocks you do not need indentation on the areas you want to mark as code:
|
||||
|
||||
|
||||
```
|
||||
Code goes here
|
||||
```
|
||||
|
||||
You can also use the `~` symbol:
|
||||
|
||||
~~~
|
||||
Code goes here
|
||||
~~~
|
||||
|
||||
### Tables
|
||||
|
||||
A *simple* table can be defined as follows:
|
||||
|
||||
```
|
||||
First Header | Second Header
|
||||
------------- | -------------
|
||||
Content Cell | Content Cell
|
||||
Content Cell | Content Cell
|
||||
```
|
||||
|
||||
If you want to you can also add a leading and tailing pipe:
|
||||
|
||||
```
|
||||
| First Header | Second Header |
|
||||
| ------------- | ------------- |
|
||||
| Content Cell | Content Cell |
|
||||
| Content Cell | Content Cell |
|
||||
```
|
||||
|
||||
To add alignment to the cells you simply need to add a `:` either at the start or end of a separator:
|
||||
|
||||
```
|
||||
| First Header | Second Header |
|
||||
| :------------ | ------------: |
|
||||
| Content Cell | Content Cell |
|
||||
| Content Cell | Content Cell |
|
||||
```
|
||||
|
||||
To center align cell just add `:` on both sides:
|
||||
|
||||
```
|
||||
| First Header | Second Header |
|
||||
| ------------- | :-----------: |
|
||||
| Content Cell | Content Cell |
|
||||
| Content Cell | Content Cell |
|
||||
```
|
||||
|
||||
### Definition lists
|
||||
|
||||
Below is an example of a simple definition list:
|
||||
|
||||
```
|
||||
Laravel
|
||||
: A popular PHP framework
|
||||
|
||||
October
|
||||
: Awesome CMS built on Laravel
|
||||
```
|
||||
|
||||
A term can also have multiple definitions:
|
||||
|
||||
```
|
||||
Laravel
|
||||
: A popular PHP framework
|
||||
|
||||
October
|
||||
: Awesome CMS built on Laravel
|
||||
: Supports markdown extra
|
||||
```
|
||||
|
||||
You can also associate more than 1 term to a definition:
|
||||
|
||||
```
|
||||
Laravel
|
||||
October
|
||||
: Built using PHP
|
||||
```
|
||||
|
||||
### Footnotes
|
||||
|
||||
With markdown extra it is possible to create reference style footnotes:
|
||||
|
||||
```
|
||||
This is some text with a footnote.[^1]
|
||||
|
||||
[^1]: And this is the footnote.
|
||||
```
|
||||
|
||||
### Abbreviations
|
||||
|
||||
With markdown extra you can add abbreviations to your markup. The use this functionality first create a definition list:
|
||||
|
||||
```
|
||||
*[HTML]: Hyper Text Markup Language
|
||||
*[PHP]: Hypertext Preprocessor
|
||||
```
|
||||
|
||||
Now markdown extra will convert all occurrences of `HTML` and `PHP` as follows:
|
||||
|
||||
```
|
||||
<abbr title="Hyper Text Markup Language">HTML</abbr>
|
||||
<abbr title="Hypertext Preprocessor">PHP</abbr>
|
||||
```
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.export-behavior .export-columns {
|
||||
max-height: 450px !important;
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
.blog-post-preview .editor-preview .preview-content {
|
||||
padding: 20px;
|
||||
}
|
||||
.blog-post-preview .editor-preview span.image-placeholder {
|
||||
display: block;
|
||||
}
|
||||
.blog-post-preview .editor-preview span.image-placeholder .upload-dropzone {
|
||||
background: #ecf0f1;
|
||||
display: block;
|
||||
border: 1px solid #e5e9ec;
|
||||
padding: 25px;
|
||||
min-height: 123px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.blog-post-preview .editor-preview span.image-placeholder .upload-dropzone span.label {
|
||||
color: #b1b9be;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
margin-top: 25px;
|
||||
}
|
||||
.blog-post-preview .editor-preview span.image-placeholder .upload-dropzone:before {
|
||||
display: inline-block;
|
||||
font-family: FontAwesome;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
text-decoration: inherit;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
*margin-right: .3em;
|
||||
content: "\f03e";
|
||||
position: absolute;
|
||||
left: 25px;
|
||||
top: 25px;
|
||||
line-height: 100%;
|
||||
font-size: 73px;
|
||||
color: #d1d3d4;
|
||||
}
|
||||
.blog-post-preview .editor-preview span.image-placeholder .upload-dropzone.hover,
|
||||
.blog-post-preview .editor-preview span.image-placeholder .upload-dropzone:hover {
|
||||
background: #2f99da;
|
||||
}
|
||||
.blog-post-preview .editor-preview span.image-placeholder .upload-dropzone.hover:before,
|
||||
.blog-post-preview .editor-preview span.image-placeholder .upload-dropzone:hover:before,
|
||||
.blog-post-preview .editor-preview span.image-placeholder .upload-dropzone.hover span.label,
|
||||
.blog-post-preview .editor-preview span.image-placeholder .upload-dropzone:hover span.label {
|
||||
color: white;
|
||||
}
|
||||
.blog-post-preview .editor-preview span.image-placeholder input[type=file] {
|
||||
position: absolute;
|
||||
left: -10000em;
|
||||
}
|
||||
.blog-post-preview-container .loading-indicator {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0!important;
|
||||
background: transparent;
|
||||
right: 10px;
|
||||
left: auto;
|
||||
top: 10px;
|
||||
}
|
||||
.blog-post-preview-container.loading-indicator-visible .loading-indicator {
|
||||
display: block;
|
||||
}
|
||||
html.cssanimations .blog-post-preview span.image-placeholder.loading .upload-dropzone:before {
|
||||
display: none;
|
||||
}
|
||||
html.cssanimations .blog-post-preview span.image-placeholder.loading .upload-dropzone .indicator {
|
||||
display: block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
left: 35px;
|
||||
top: 35px;
|
||||
background-image: url('../../../../../modules/system/assets/ui/images/loader-transparent.svg');
|
||||
background-size: 50px 50px;
|
||||
background-position: 50% 50%;
|
||||
-webkit-animation: spin 1s linear infinite;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="64px" height="64px" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.4.4 (17249) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>blog-icon</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Group-+-Group" sketch:type="MSLayerGroup">
|
||||
<g id="Group" sketch:type="MSShapeGroup">
|
||||
<path d="M0.754,3.84 C0.754,1.719 2.473,0 4.594,0 L32.754,0 L48.754,13.44 L48.754,60.16 C48.754,62.281 47.035,64 44.914,64 L4.594,64 C2.473,64 0.754,62.281 0.754,60.16 L0.754,3.84 Z" id="Fill-392" fill="#FFFFFF"></path>
|
||||
<path d="M32.754,0 L32.754,9.6 C32.754,11.721 34.473,13.44 36.594,13.44 L48.754,13.44 L32.754,0 Z" id="Fill-393" fill="#F0F1F1"></path>
|
||||
<path d="M38.0416209,23 L12.3559338,23 C11.4141253,23 10.6435547,22.55 10.6435547,22 C10.6435547,21.45 11.4141253,21 12.3559338,21 L38.0416209,21 C38.9834294,21 39.754,21.45 39.754,22 C39.754,22.55 38.9834294,23 38.0416209,23 Z" id="Fill-397" fill="#E2E4E5"></path>
|
||||
<path d="M29.4797252,27 C29.4797252,27.55 28.7091546,28 27.767346,28 L12.3559338,28 C11.4141253,28 10.6435547,27.55 10.6435547,27 C10.6435547,26.45 11.4141253,26 12.3559338,26 L27.767346,26 C28.7091546,26 29.4797252,26.45 29.4797252,27" id="Fill-398" fill="#E2E4E5"></path>
|
||||
<path d="M23.767346,11 L12.3559338,11 C11.4141253,11 10.6435547,10.55 10.6435547,10 C10.6435547,9.45 11.4141253,9 12.3559338,9 L23.767346,9 C24.7091546,9 25.4797252,9.45 25.4797252,10 C25.4797252,10.55 24.7091546,11 23.767346,11 Z" id="Fill-398-Copy" fill="#E1332C"></path>
|
||||
<path d="M39.754,35 C39.754,35.55 38.9834294,36 38.0416209,36 L12.3559338,36 C11.4141253,36 10.6435547,35.55 10.6435547,35 C10.6435547,34.45 11.4141253,34 12.3559338,34 L38.0416209,34 C38.9834294,34 39.754,34.45 39.754,35" id="Fill-399" fill="#E2E4E5"></path>
|
||||
<path d="M29.4797252,40 C29.4797252,40.55 28.7091546,41 27.767346,41 L12.3559338,41 C11.4141253,41 10.6435547,40.55 10.6435547,40 C10.6435547,39.45 11.4141253,39 12.3559338,39 L27.767346,39 C28.7091546,39 29.4797252,39.45 29.4797252,40" id="Fill-400" fill="#E2E4E5"></path>
|
||||
<path d="M39.754,48 C39.754,48.55 38.9834294,49 38.0416209,49 L12.3559338,49 C11.4141253,49 10.6435547,48.55 10.6435547,48 C10.6435547,47.45 11.4141253,47 12.3559338,47 L38.0416209,47 C38.9834294,47 39.754,47.45 39.754,48" id="Fill-401" fill="#E2E4E5"></path>
|
||||
<path d="M29.4797252,53 C29.4797252,53.55 28.7091546,54 27.767346,54 L12.3559338,54 C11.4141253,54 10.6435547,53.55 10.6435547,53 C10.6435547,52.45 11.4141253,52 12.3559338,52 L27.767346,52 C28.7091546,52 29.4797252,52.45 29.4797252,53" id="Fill-402" fill="#E2E4E5"></path>
|
||||
</g>
|
||||
<g id="Group" transform="translate(27.000000, 16.000000)" sketch:type="MSShapeGroup">
|
||||
<path d="M31.8979,11.4983 L10.6949,35.4653 L4.8179,36.6753 C4.6479,35.8573 4.2249,35.0823 3.5509,34.4863 C2.8769,33.8893 2.0559,33.5643 1.2229,33.4953 L1.7069,27.5143 L22.9099,3.5473 L31.8979,11.4983 Z" id="Fill-647" fill="#F4D0A1"></path>
|
||||
<path d="M31.8692,11.4729 C31.8852,11.4869 11.1512,34.9499 11.1512,34.9499 C11.7392,33.7029 11.5032,32.1759 10.4362,31.2309 C9.3092,30.2349 7.6512,30.2359 6.5312,31.1689 C7.2692,29.8979 7.0682,28.2519 5.9422,27.2549 C4.8622,26.3009 3.2942,26.2619 2.1802,27.0819 C2.1442,27.1089 22.8852,3.5759 22.8852,3.5759 C22.8992,3.5599 31.8692,11.4729 31.8692,11.4729 Z" id="Fill-648" fill="#FC5B55"></path>
|
||||
<path d="M31.8979,11.4983 L22.9099,3.5473 L24.2349,2.0493 L33.2229,10.0003 L31.8979,11.4983 Z" id="Fill-649" fill="#FACB1B"></path>
|
||||
<path d="M32.7497,1.5706 L32.6597,1.4916 C30.2027,-0.6824 26.4487,-0.4524 24.2747,2.0046 L24.2357,2.0496 L33.2227,10.0006 L33.2627,9.9556 C35.4367,7.4986 35.2067,3.7446 32.7497,1.5706" id="Fill-650" fill="#F89392"></path>
|
||||
<path d="M11.1258,34.9796 C11.7438,33.7326 11.5138,32.1846 10.4358,31.2306 C9.3118,30.2366 7.6588,30.2356 6.5388,31.1626 C6.5198,31.1786 27.3788,7.5516 27.3788,7.5516 C27.3928,7.5356 31.8698,11.4736 31.8698,11.4736 C31.8848,11.4866 11.1258,34.9796 11.1258,34.9796 Z" id="Fill-651" fill="#E1332C"></path>
|
||||
<path d="M4.8181,36.6754 L0.9001,37.4824 L1.2231,33.4954 C2.0561,33.5644 2.8771,33.8894 3.5511,34.4864 C4.2251,35.0824 4.6481,35.8574 4.8181,36.6754" id="Fill-652" fill="#3E3E3F"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
|
|
@ -0,0 +1,185 @@
|
|||
+function ($) { "use strict";
|
||||
var PostForm = function () {
|
||||
this.$form = $('#post-form')
|
||||
this.$markdownEditor = $('[data-field-name=content] [data-control=markdowneditor]:first', this.$form)
|
||||
this.$preview = $('.editor-preview', this.$markdownEditor)
|
||||
|
||||
this.formAction = this.$form.attr('action')
|
||||
this.sessionKey = $('input[name=_session_key]', this.$form).val()
|
||||
|
||||
if (this.$markdownEditor.length > 0) {
|
||||
this.codeEditor = this.$markdownEditor.markdownEditor('getEditorObject')
|
||||
|
||||
this.$markdownEditor.on('initPreview.oc.markdowneditor', $.proxy(this.initPreview, this))
|
||||
|
||||
this.initDropzones()
|
||||
this.initFormEvents()
|
||||
this.addToolbarButton()
|
||||
}
|
||||
|
||||
this.initLayout()
|
||||
}
|
||||
|
||||
PostForm.prototype.addToolbarButton = function() {
|
||||
this.buttonClickCount = 1
|
||||
|
||||
var self = this,
|
||||
$button = this.$markdownEditor.markdownEditor('findToolbarButton', 'image')
|
||||
|
||||
if (!$button.length) return
|
||||
|
||||
$button.data('button-action', 'insertLine')
|
||||
$button.data('button-template', '\n\n\n')
|
||||
|
||||
$button.on('click', function() {
|
||||
$button.data('button-template', '\n\n\n')
|
||||
self.buttonClickCount++
|
||||
})
|
||||
}
|
||||
|
||||
PostForm.prototype.initPreview = function() {
|
||||
this.initImageUploaders()
|
||||
}
|
||||
|
||||
PostForm.prototype.updateScroll = function() {
|
||||
// Reserved in case MarkdownEditor uses scrollbar plugin
|
||||
// this.$preview.data('oc.scrollbar').update()
|
||||
}
|
||||
|
||||
PostForm.prototype.initImageUploaders = function() {
|
||||
var self = this
|
||||
$('span.image-placeholder .upload-dropzone', this.$preview).each(function(){
|
||||
var
|
||||
$placeholder = $(this).parent(),
|
||||
$link = $('span.label', $placeholder),
|
||||
placeholderIndex = $placeholder.data('index')
|
||||
|
||||
var uploaderOptions = {
|
||||
url: self.formAction,
|
||||
clickable: [$(this).get(0), $link.get(0)],
|
||||
previewsContainer: $('<div />').get(0),
|
||||
paramName: 'file',
|
||||
headers: {}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add CSRF token to headers
|
||||
*/
|
||||
var token = $('meta[name="csrf-token"]').attr('content')
|
||||
if (token) {
|
||||
uploaderOptions.headers['X-CSRF-TOKEN'] = token
|
||||
}
|
||||
|
||||
var dropzone = new Dropzone($(this).get(0), uploaderOptions)
|
||||
|
||||
dropzone.on('error', function(file, error) {
|
||||
alert('Error uploading file: ' + error)
|
||||
})
|
||||
dropzone.on('success', function(file, data){
|
||||
if (data.error)
|
||||
alert(data.error)
|
||||
else {
|
||||
self.pauseUpdates()
|
||||
var $img = $('<img src="'+data.path+'">')
|
||||
$img.load(function(){
|
||||
self.updateScroll()
|
||||
})
|
||||
|
||||
$placeholder.replaceWith($img)
|
||||
|
||||
self.codeEditor.replace('', {
|
||||
needle: ''
|
||||
})
|
||||
self.resumeUpdates()
|
||||
}
|
||||
})
|
||||
dropzone.on('complete', function(){
|
||||
$placeholder.removeClass('loading')
|
||||
})
|
||||
dropzone.on('sending', function(file, xhr, formData) {
|
||||
formData.append('X_BLOG_IMAGE_UPLOAD', 1)
|
||||
formData.append('_session_key', self.sessionKey)
|
||||
$placeholder.addClass('loading')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
PostForm.prototype.pauseUpdates = function() {
|
||||
this.$markdownEditor.markdownEditor('pauseUpdates')
|
||||
}
|
||||
|
||||
PostForm.prototype.resumeUpdates = function() {
|
||||
this.$markdownEditor.markdownEditor('resumeUpdates')
|
||||
}
|
||||
|
||||
PostForm.prototype.initDropzones = function() {
|
||||
$(document).bind('dragover', function (e) {
|
||||
var dropZone = $('span.image-placeholder .upload-dropzone'),
|
||||
foundDropzone,
|
||||
timeout = window.dropZoneTimeout
|
||||
|
||||
if (!timeout)
|
||||
dropZone.addClass('in');
|
||||
else
|
||||
clearTimeout(timeout);
|
||||
|
||||
var found = false,
|
||||
node = e.target
|
||||
|
||||
do {
|
||||
if ($(node).hasClass('dropzone')) {
|
||||
found = true
|
||||
foundDropzone = $(node)
|
||||
break
|
||||
}
|
||||
|
||||
node = node.parentNode;
|
||||
|
||||
} while (node != null);
|
||||
|
||||
dropZone.removeClass('in hover')
|
||||
|
||||
if (found)
|
||||
foundDropzone.addClass('hover')
|
||||
|
||||
window.dropZoneTimeout = setTimeout(function () {
|
||||
window.dropZoneTimeout = null
|
||||
dropZone.removeClass('in hover')
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
PostForm.prototype.initFormEvents = function() {
|
||||
$(document).on('ajaxSuccess', '#post-form', function(event, context, data){
|
||||
if (context.handler == 'onSave' && !data.X_OCTOBER_ERROR_FIELDS) {
|
||||
$(this).trigger('unchange.oc.changeMonitor')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
PostForm.prototype.initLayout = function() {
|
||||
$('#Form-secondaryTabs .tab-pane.layout-cell:not(:first-child)').addClass('padded-pane')
|
||||
$('#Form-secondaryTabs .nav-tabs > li:not(:first-child)').addClass('tab-content-bg')
|
||||
}
|
||||
|
||||
PostForm.prototype.replacePlaceholder = function(placeholder, placeholderHtmlReplacement, mdCodePlaceholder, mdCodeReplacement) {
|
||||
this.pauseUpdates()
|
||||
placeholder.replaceWith(placeholderHtmlReplacement)
|
||||
|
||||
this.codeEditor.replace(mdCodeReplacement, {
|
||||
needle: mdCodePlaceholder
|
||||
})
|
||||
this.updateScroll()
|
||||
this.resumeUpdates()
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
var form = new PostForm()
|
||||
|
||||
if ($.oc === undefined)
|
||||
$.oc = {}
|
||||
|
||||
$.oc.blogPostForm = form
|
||||
})
|
||||
|
||||
}(window.jQuery);
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
@import "../../../../../modules/backend/assets/less/core/boot.less";
|
||||
|
||||
.blog-post-preview .editor-preview {
|
||||
.preview-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
span.image-placeholder {
|
||||
display: block;
|
||||
|
||||
.upload-dropzone {
|
||||
background: #ecf0f1;
|
||||
display: block;
|
||||
border: 1px solid #e5e9ec;
|
||||
padding: 25px;
|
||||
min-height: 123px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
.box-sizing(border-box);
|
||||
|
||||
span.label {
|
||||
color: #b1b9be;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
display: inline-block;
|
||||
.icon(@picture-o);
|
||||
position: absolute;
|
||||
left: 25px;
|
||||
top: 25px;
|
||||
line-height: 100%;
|
||||
font-size: 73px;
|
||||
color: #d1d3d4;
|
||||
}
|
||||
|
||||
&.hover, &:hover {
|
||||
background: #2f99da;
|
||||
|
||||
&:before, span.label {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
position: absolute;
|
||||
left: -10000em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blog-post-preview-container {
|
||||
.loading-indicator {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0!important;
|
||||
background: transparent;
|
||||
right: 10px;
|
||||
left: auto;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
&.loading-indicator-visible {
|
||||
.loading-indicator {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.cssanimations {
|
||||
.blog-post-preview {
|
||||
span.image-placeholder.loading {
|
||||
.upload-dropzone {
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
display: block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
left: 35px;
|
||||
top: 35px;
|
||||
background-image:url('../../../../../modules/system/assets/ui/images/loader-transparent.svg');
|
||||
background-size: 50px 50px;
|
||||
background-position: 50% 50%;
|
||||
.animation(spin 1s linear infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php namespace RainLab\Blog\Classes;
|
||||
|
||||
/**
|
||||
* Blog Markdown tag processor.
|
||||
*
|
||||
* @package rainlab\blog
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class TagProcessor
|
||||
{
|
||||
use \October\Rain\Support\Traits\Singleton;
|
||||
|
||||
/**
|
||||
* @var array Cache of processing callbacks.
|
||||
*/
|
||||
private $callbacks = [];
|
||||
|
||||
/**
|
||||
* Registers a callback function that handles blog post markup.
|
||||
* The callback function should accept two arguments - the HTML string
|
||||
* generated from Markdown contents and the preview flag determining whether
|
||||
* the function should return a markup for the blog post preview form or for the
|
||||
* front-end.
|
||||
* @param callable $callback A callable function.
|
||||
*/
|
||||
public function registerCallback(callable $callback)
|
||||
{
|
||||
$this->callbacks[] = $callback;
|
||||
}
|
||||
|
||||
public function processTags($markup, $preview)
|
||||
{
|
||||
foreach ($this->callbacks as $callback) {
|
||||
$markup = $callback($markup, $preview);
|
||||
}
|
||||
|
||||
return $markup;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
<?php namespace RainLab\Blog\Components;
|
||||
|
||||
use Db;
|
||||
use Carbon\Carbon;
|
||||
use Cms\Classes\Page;
|
||||
use Cms\Classes\ComponentBase;
|
||||
use RainLab\Blog\Models\Category as BlogCategory;
|
||||
|
||||
class Categories extends ComponentBase
|
||||
{
|
||||
/**
|
||||
* @var Collection A collection of categories to display
|
||||
*/
|
||||
public $categories;
|
||||
|
||||
/**
|
||||
* @var string Reference to the page name for linking to categories.
|
||||
*/
|
||||
public $categoryPage;
|
||||
|
||||
/**
|
||||
* @var string Reference to the current category slug.
|
||||
*/
|
||||
public $currentCategorySlug;
|
||||
|
||||
public function componentDetails()
|
||||
{
|
||||
return [
|
||||
'name' => 'rainlab.blog::lang.settings.category_title',
|
||||
'description' => 'rainlab.blog::lang.settings.category_description'
|
||||
];
|
||||
}
|
||||
|
||||
public function defineProperties()
|
||||
{
|
||||
return [
|
||||
'slug' => [
|
||||
'title' => 'rainlab.blog::lang.settings.category_slug',
|
||||
'description' => 'rainlab.blog::lang.settings.category_slug_description',
|
||||
'default' => '{{ :slug }}',
|
||||
'type' => 'string',
|
||||
],
|
||||
'displayEmpty' => [
|
||||
'title' => 'rainlab.blog::lang.settings.category_display_empty',
|
||||
'description' => 'rainlab.blog::lang.settings.category_display_empty_description',
|
||||
'type' => 'checkbox',
|
||||
'default' => 0,
|
||||
],
|
||||
'categoryPage' => [
|
||||
'title' => 'rainlab.blog::lang.settings.category_page',
|
||||
'description' => 'rainlab.blog::lang.settings.category_page_description',
|
||||
'type' => 'dropdown',
|
||||
'default' => 'blog/category',
|
||||
'group' => 'rainlab.blog::lang.settings.group_links',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getCategoryPageOptions()
|
||||
{
|
||||
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
|
||||
}
|
||||
|
||||
public function onRun()
|
||||
{
|
||||
$this->currentCategorySlug = $this->page['currentCategorySlug'] = $this->property('slug');
|
||||
$this->categoryPage = $this->page['categoryPage'] = $this->property('categoryPage');
|
||||
$this->categories = $this->page['categories'] = $this->loadCategories();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all categories or, depending on the <displayEmpty> option, only those that have blog posts
|
||||
* @return mixed
|
||||
*/
|
||||
protected function loadCategories()
|
||||
{
|
||||
$categories = BlogCategory::with('posts_count')->getNested();
|
||||
if (!$this->property('displayEmpty')) {
|
||||
$iterator = function ($categories) use (&$iterator) {
|
||||
return $categories->reject(function ($category) use (&$iterator) {
|
||||
if ($category->getNestedPostCount() == 0) {
|
||||
return true;
|
||||
}
|
||||
if ($category->children) {
|
||||
$category->children = $iterator($category->children);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
$categories = $iterator($categories);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a "url" helper attribute for linking to each category
|
||||
*/
|
||||
return $this->linkCategories($categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL on each category according to the defined category page
|
||||
* @return void
|
||||
*/
|
||||
protected function linkCategories($categories)
|
||||
{
|
||||
return $categories->each(function ($category) {
|
||||
$category->setUrl($this->categoryPage, $this->controller);
|
||||
|
||||
if ($category->children) {
|
||||
$this->linkCategories($category->children);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
<?php namespace RainLab\Blog\Components;
|
||||
|
||||
use Event;
|
||||
use BackendAuth;
|
||||
use Cms\Classes\Page;
|
||||
use Cms\Classes\ComponentBase;
|
||||
use RainLab\Blog\Models\Post as BlogPost;
|
||||
|
||||
class Post extends ComponentBase
|
||||
{
|
||||
/**
|
||||
* @var RainLab\Blog\Models\Post The post model used for display.
|
||||
*/
|
||||
public $post;
|
||||
|
||||
/**
|
||||
* @var string Reference to the page name for linking to categories.
|
||||
*/
|
||||
public $categoryPage;
|
||||
|
||||
public function componentDetails()
|
||||
{
|
||||
return [
|
||||
'name' => 'rainlab.blog::lang.settings.post_title',
|
||||
'description' => 'rainlab.blog::lang.settings.post_description'
|
||||
];
|
||||
}
|
||||
|
||||
public function defineProperties()
|
||||
{
|
||||
return [
|
||||
'slug' => [
|
||||
'title' => 'rainlab.blog::lang.settings.post_slug',
|
||||
'description' => 'rainlab.blog::lang.settings.post_slug_description',
|
||||
'default' => '{{ :slug }}',
|
||||
'type' => 'string',
|
||||
],
|
||||
'categoryPage' => [
|
||||
'title' => 'rainlab.blog::lang.settings.post_category',
|
||||
'description' => 'rainlab.blog::lang.settings.post_category_description',
|
||||
'type' => 'dropdown',
|
||||
'default' => 'blog/category',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getCategoryPageOptions()
|
||||
{
|
||||
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
Event::listen('translate.localePicker.translateParams', function ($page, $params, $oldLocale, $newLocale) {
|
||||
$newParams = $params;
|
||||
|
||||
if (isset($params['slug'])) {
|
||||
$records = BlogPost::transWhere('slug', $params['slug'], $oldLocale)->first();
|
||||
if ($records) {
|
||||
$records->translateContext($newLocale);
|
||||
$newParams['slug'] = $records['slug'];
|
||||
}
|
||||
}
|
||||
|
||||
return $newParams;
|
||||
});
|
||||
}
|
||||
|
||||
public function onRun()
|
||||
{
|
||||
$this->categoryPage = $this->page['categoryPage'] = $this->property('categoryPage');
|
||||
$this->post = $this->page['post'] = $this->loadPost();
|
||||
if (!$this->post) {
|
||||
$this->setStatusCode(404);
|
||||
return $this->controller->run('404');
|
||||
}
|
||||
}
|
||||
|
||||
public function onRender()
|
||||
{
|
||||
if (empty($this->post)) {
|
||||
$this->post = $this->page['post'] = $this->loadPost();
|
||||
}
|
||||
}
|
||||
|
||||
protected function loadPost()
|
||||
{
|
||||
$slug = $this->property('slug');
|
||||
|
||||
$post = new BlogPost;
|
||||
$query = $post->query();
|
||||
|
||||
if ($post->isClassExtendedWith('RainLab.Translate.Behaviors.TranslatableModel')) {
|
||||
$query->transWhere('slug', $slug);
|
||||
} else {
|
||||
$query->where('slug', $slug);
|
||||
}
|
||||
|
||||
if (!$this->checkEditor()) {
|
||||
$query->isPublished();
|
||||
}
|
||||
|
||||
$post = $query->first();
|
||||
|
||||
/*
|
||||
* Add a "url" helper attribute for linking to each category
|
||||
*/
|
||||
if ($post && $post->exists && $post->categories->count()) {
|
||||
$post->categories->each(function($category) {
|
||||
$category->setUrl($this->categoryPage, $this->controller);
|
||||
});
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function previousPost()
|
||||
{
|
||||
return $this->getPostSibling(-1);
|
||||
}
|
||||
|
||||
public function nextPost()
|
||||
{
|
||||
return $this->getPostSibling(1);
|
||||
}
|
||||
|
||||
protected function getPostSibling($direction = 1)
|
||||
{
|
||||
if (!$this->post) {
|
||||
return;
|
||||
}
|
||||
|
||||
$method = $direction === -1 ? 'previousPost' : 'nextPost';
|
||||
|
||||
if (!$post = $this->post->$method()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$postPage = $this->getPage()->getBaseFileName();
|
||||
|
||||
$post->setUrl($postPage, $this->controller);
|
||||
|
||||
$post->categories->each(function($category) {
|
||||
$category->setUrl($this->categoryPage, $this->controller);
|
||||
});
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
protected function checkEditor()
|
||||
{
|
||||
$backendUser = BackendAuth::getUser();
|
||||
|
||||
return $backendUser && $backendUser->hasAccess('rainlab.blog.access_posts');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
<?php namespace RainLab\Blog\Components;
|
||||
|
||||
use Lang;
|
||||
use Redirect;
|
||||
use BackendAuth;
|
||||
use Cms\Classes\Page;
|
||||
use Cms\Classes\ComponentBase;
|
||||
use October\Rain\Database\Model;
|
||||
use October\Rain\Database\Collection;
|
||||
use RainLab\Blog\Models\Post as BlogPost;
|
||||
use RainLab\Blog\Models\Category as BlogCategory;
|
||||
use RainLab\Blog\Models\Settings as BlogSettings;
|
||||
|
||||
class Posts extends ComponentBase
|
||||
{
|
||||
/**
|
||||
* A collection of posts to display
|
||||
*
|
||||
* @var Collection
|
||||
*/
|
||||
public $posts;
|
||||
|
||||
/**
|
||||
* Parameter to use for the page number
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $pageParam;
|
||||
|
||||
/**
|
||||
* If the post list should be filtered by a category, the model to use
|
||||
*
|
||||
* @var Model
|
||||
*/
|
||||
public $category;
|
||||
|
||||
/**
|
||||
* Message to display when there are no messages
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $noPostsMessage;
|
||||
|
||||
/**
|
||||
* Reference to the page name for linking to posts
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $postPage;
|
||||
|
||||
/**
|
||||
* Reference to the page name for linking to categories
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $categoryPage;
|
||||
|
||||
/**
|
||||
* If the post list should be ordered by another attribute
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $sortOrder;
|
||||
|
||||
public function componentDetails()
|
||||
{
|
||||
return [
|
||||
'name' => 'rainlab.blog::lang.settings.posts_title',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_description'
|
||||
];
|
||||
}
|
||||
|
||||
public function defineProperties()
|
||||
{
|
||||
return [
|
||||
'pageNumber' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_pagination',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_pagination_description',
|
||||
'type' => 'string',
|
||||
'default' => '{{ :page }}',
|
||||
],
|
||||
'categoryFilter' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_filter',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_filter_description',
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'postsPerPage' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_per_page',
|
||||
'type' => 'string',
|
||||
'validationPattern' => '^[0-9]+$',
|
||||
'validationMessage' => 'rainlab.blog::lang.settings.posts_per_page_validation',
|
||||
'default' => '10',
|
||||
],
|
||||
'noPostsMessage' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_no_posts',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_no_posts_description',
|
||||
'type' => 'string',
|
||||
'default' => Lang::get('rainlab.blog::lang.settings.posts_no_posts_default'),
|
||||
'showExternalParam' => false,
|
||||
],
|
||||
'sortOrder' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_order',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_order_description',
|
||||
'type' => 'dropdown',
|
||||
'default' => 'published_at desc',
|
||||
],
|
||||
'categoryPage' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_category',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_category_description',
|
||||
'type' => 'dropdown',
|
||||
'default' => 'blog/category',
|
||||
'group' => 'rainlab.blog::lang.settings.group_links',
|
||||
],
|
||||
'postPage' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_post',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_post_description',
|
||||
'type' => 'dropdown',
|
||||
'default' => 'blog/post',
|
||||
'group' => 'rainlab.blog::lang.settings.group_links',
|
||||
],
|
||||
'exceptPost' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_except_post',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_except_post_description',
|
||||
'type' => 'string',
|
||||
'validationPattern' => '^[a-z0-9\-_,\s]+$',
|
||||
'validationMessage' => 'rainlab.blog::lang.settings.posts_except_post_validation',
|
||||
'default' => '',
|
||||
'group' => 'rainlab.blog::lang.settings.group_exceptions',
|
||||
],
|
||||
'exceptCategories' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_except_categories',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_except_categories_description',
|
||||
'type' => 'string',
|
||||
'validationPattern' => '^[a-z0-9\-_,\s]+$',
|
||||
'validationMessage' => 'rainlab.blog::lang.settings.posts_except_categories_validation',
|
||||
'default' => '',
|
||||
'group' => 'rainlab.blog::lang.settings.group_exceptions',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getCategoryPageOptions()
|
||||
{
|
||||
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
|
||||
}
|
||||
|
||||
public function getPostPageOptions()
|
||||
{
|
||||
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
|
||||
}
|
||||
|
||||
public function getSortOrderOptions()
|
||||
{
|
||||
$options = BlogPost::$allowedSortingOptions;
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
$options[$key] = Lang::get($value);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function onRun()
|
||||
{
|
||||
$this->prepareVars();
|
||||
|
||||
$this->category = $this->page['category'] = $this->loadCategory();
|
||||
$this->posts = $this->page['posts'] = $this->listPosts();
|
||||
|
||||
/*
|
||||
* If the page number is not valid, redirect
|
||||
*/
|
||||
if ($pageNumberParam = $this->paramName('pageNumber')) {
|
||||
$currentPage = $this->property('pageNumber');
|
||||
|
||||
if ($currentPage > ($lastPage = $this->posts->lastPage()) && $currentPage > 1) {
|
||||
return Redirect::to($this->currentPageUrl([$pageNumberParam => $lastPage]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function prepareVars()
|
||||
{
|
||||
$this->pageParam = $this->page['pageParam'] = $this->paramName('pageNumber');
|
||||
$this->noPostsMessage = $this->page['noPostsMessage'] = $this->property('noPostsMessage');
|
||||
|
||||
/*
|
||||
* Page links
|
||||
*/
|
||||
$this->postPage = $this->page['postPage'] = $this->property('postPage');
|
||||
$this->categoryPage = $this->page['categoryPage'] = $this->property('categoryPage');
|
||||
}
|
||||
|
||||
protected function listPosts()
|
||||
{
|
||||
$category = $this->category ? $this->category->id : null;
|
||||
$categorySlug = $this->category ? $this->category->slug : null;
|
||||
|
||||
/*
|
||||
* List all the posts, eager load their categories
|
||||
*/
|
||||
$isPublished = !$this->checkEditor();
|
||||
|
||||
$posts = BlogPost::with(['categories', 'featured_images'])->listFrontEnd([
|
||||
'page' => $this->property('pageNumber'),
|
||||
'sort' => $this->property('sortOrder'),
|
||||
'perPage' => $this->property('postsPerPage'),
|
||||
'search' => trim(input('search')),
|
||||
'category' => $category,
|
||||
'published' => $isPublished,
|
||||
'exceptPost' => is_array($this->property('exceptPost'))
|
||||
? $this->property('exceptPost')
|
||||
: preg_split('/,\s*/', $this->property('exceptPost'), -1, PREG_SPLIT_NO_EMPTY),
|
||||
'exceptCategories' => is_array($this->property('exceptCategories'))
|
||||
? $this->property('exceptCategories')
|
||||
: preg_split('/,\s*/', $this->property('exceptCategories'), -1, PREG_SPLIT_NO_EMPTY),
|
||||
]);
|
||||
|
||||
/*
|
||||
* Add a "url" helper attribute for linking to each post and category
|
||||
*/
|
||||
$posts->each(function($post) use ($categorySlug) {
|
||||
$post->setUrl($this->postPage, $this->controller, ['category' => $categorySlug]);
|
||||
|
||||
$post->categories->each(function($category) {
|
||||
$category->setUrl($this->categoryPage, $this->controller);
|
||||
});
|
||||
});
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
protected function loadCategory()
|
||||
{
|
||||
if (!$slug = $this->property('categoryFilter')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$category = new BlogCategory;
|
||||
|
||||
$category = $category->isClassExtendedWith('RainLab.Translate.Behaviors.TranslatableModel')
|
||||
? $category->transWhere('slug', $slug)
|
||||
: $category->where('slug', $slug);
|
||||
|
||||
$category = $category->first();
|
||||
|
||||
return $category ?: null;
|
||||
}
|
||||
|
||||
protected function checkEditor()
|
||||
{
|
||||
$backendUser = BackendAuth::getUser();
|
||||
|
||||
return $backendUser &&
|
||||
$backendUser->hasAccess('rainlab.blog.access_posts') &&
|
||||
BlogSettings::get('show_all_posts', true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<?php namespace RainLab\Blog\Components;
|
||||
|
||||
use Lang;
|
||||
use Response;
|
||||
use Cms\Classes\Page;
|
||||
use Cms\Classes\ComponentBase;
|
||||
use RainLab\Blog\Models\Post as BlogPost;
|
||||
use RainLab\Blog\Models\Category as BlogCategory;
|
||||
|
||||
class RssFeed extends ComponentBase
|
||||
{
|
||||
/**
|
||||
* A collection of posts to display
|
||||
* @var Collection
|
||||
*/
|
||||
public $posts;
|
||||
|
||||
/**
|
||||
* If the post list should be filtered by a category, the model to use.
|
||||
* @var Model
|
||||
*/
|
||||
public $category;
|
||||
|
||||
/**
|
||||
* Reference to the page name for the main blog page.
|
||||
* @var string
|
||||
*/
|
||||
public $blogPage;
|
||||
|
||||
/**
|
||||
* Reference to the page name for linking to posts.
|
||||
* @var string
|
||||
*/
|
||||
public $postPage;
|
||||
|
||||
public function componentDetails()
|
||||
{
|
||||
return [
|
||||
'name' => 'rainlab.blog::lang.settings.rssfeed_title',
|
||||
'description' => 'rainlab.blog::lang.settings.rssfeed_description'
|
||||
];
|
||||
}
|
||||
|
||||
public function defineProperties()
|
||||
{
|
||||
return [
|
||||
'categoryFilter' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_filter',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_filter_description',
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
],
|
||||
'sortOrder' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_order',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_order_description',
|
||||
'type' => 'dropdown',
|
||||
'default' => 'created_at desc',
|
||||
],
|
||||
'postsPerPage' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_per_page',
|
||||
'type' => 'string',
|
||||
'validationPattern' => '^[0-9]+$',
|
||||
'validationMessage' => 'rainlab.blog::lang.settings.posts_per_page_validation',
|
||||
'default' => '10',
|
||||
],
|
||||
'blogPage' => [
|
||||
'title' => 'rainlab.blog::lang.settings.rssfeed_blog',
|
||||
'description' => 'rainlab.blog::lang.settings.rssfeed_blog_description',
|
||||
'type' => 'dropdown',
|
||||
'default' => 'blog/post',
|
||||
'group' => 'rainlab.blog::lang.settings.group_links',
|
||||
],
|
||||
'postPage' => [
|
||||
'title' => 'rainlab.blog::lang.settings.posts_post',
|
||||
'description' => 'rainlab.blog::lang.settings.posts_post_description',
|
||||
'type' => 'dropdown',
|
||||
'default' => 'blog/post',
|
||||
'group' => 'rainlab.blog::lang.settings.group_links',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getBlogPageOptions()
|
||||
{
|
||||
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
|
||||
}
|
||||
|
||||
public function getPostPageOptions()
|
||||
{
|
||||
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName');
|
||||
}
|
||||
|
||||
public function getSortOrderOptions()
|
||||
{
|
||||
$options = BlogPost::$allowedSortingOptions;
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
$options[$key] = Lang::get($value);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function onRun()
|
||||
{
|
||||
$this->prepareVars();
|
||||
|
||||
$xmlFeed = $this->renderPartial('@default');
|
||||
|
||||
return Response::make($xmlFeed, '200')->header('Content-Type', 'text/xml');
|
||||
}
|
||||
|
||||
protected function prepareVars()
|
||||
{
|
||||
$this->blogPage = $this->page['blogPage'] = $this->property('blogPage');
|
||||
$this->postPage = $this->page['postPage'] = $this->property('postPage');
|
||||
$this->category = $this->page['category'] = $this->loadCategory();
|
||||
$this->posts = $this->page['posts'] = $this->listPosts();
|
||||
|
||||
$this->page['link'] = $this->pageUrl($this->blogPage);
|
||||
$this->page['rssLink'] = $this->currentPageUrl();
|
||||
}
|
||||
|
||||
protected function listPosts()
|
||||
{
|
||||
$category = $this->category ? $this->category->id : null;
|
||||
|
||||
/*
|
||||
* List all the posts, eager load their categories
|
||||
*/
|
||||
$posts = BlogPost::with('categories')->listFrontEnd([
|
||||
'sort' => $this->property('sortOrder'),
|
||||
'perPage' => $this->property('postsPerPage'),
|
||||
'category' => $category
|
||||
]);
|
||||
|
||||
/*
|
||||
* Add a "url" helper attribute for linking to each post and category
|
||||
*/
|
||||
$posts->each(function($post) {
|
||||
$post->setUrl($this->postPage, $this->controller);
|
||||
});
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
protected function loadCategory()
|
||||
{
|
||||
if (!$categoryId = $this->property('categoryFilter')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$category = BlogCategory::whereSlug($categoryId)->first()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $category;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{% if __SELF__.categories|length > 0 %}
|
||||
<ul class="category-list">
|
||||
{% partial __SELF__ ~ "::items"
|
||||
categories = __SELF__.categories
|
||||
currentCategorySlug = __SELF__.currentCategorySlug
|
||||
%}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No categories were found.</p>
|
||||
{% endif %}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{% for category in categories %}
|
||||
{% set postCount = category.post_count %}
|
||||
<li {% if category.slug == currentCategorySlug %}class="active"{% endif %}>
|
||||
<a href="{{ category.url }}">{{ category.name }}</a>
|
||||
{% if postCount %}
|
||||
<span class="badge">{{ postCount }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if category.children|length > 0 %}
|
||||
<ul>
|
||||
{% partial __SELF__ ~ "::items"
|
||||
categories=category.children
|
||||
currentCategorySlug=currentCategorySlug
|
||||
%}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{% set post = __SELF__.post %}
|
||||
|
||||
<div class="content">{{ post.content_html|raw }}</div>
|
||||
|
||||
{% if post.featured_images|length %}
|
||||
<div class="featured-images text-center">
|
||||
{% for image in post.featured_images %}
|
||||
<p>
|
||||
<img
|
||||
data-src="{{ image.filename }}"
|
||||
src="{{ image.path }}"
|
||||
alt="{{ image.description }}"
|
||||
style="max-width: 100%" />
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p class="info">
|
||||
Posted
|
||||
{% if post.categories|length %} in
|
||||
{% for category in post.categories %}
|
||||
<a href="{{ category.url }}">{{ category.name }}</a>{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
on {{ post.published_at|date('M d, Y') }}
|
||||
</p>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{% set posts = __SELF__.posts %}
|
||||
|
||||
<ul class="post-list">
|
||||
{% for post in posts %}
|
||||
<li>
|
||||
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
|
||||
|
||||
<p class="info">
|
||||
Posted
|
||||
{% if post.categories|length %} in {% endif %}
|
||||
{% for category in post.categories %}
|
||||
<a href="{{ category.url }}">{{ category.name }}</a>{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
on {{ post.published_at|date('M d, Y') }}
|
||||
</p>
|
||||
|
||||
<p class="excerpt">{{ post.summary|raw }}</p>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="no-data">{{ __SELF__.noPostsMessage }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% if posts.lastPage > 1 %}
|
||||
<ul class="pagination">
|
||||
{% if posts.currentPage > 1 %}
|
||||
<li><a href="{{ this.page.baseFileName|page({ (__SELF__.pageParam): (posts.currentPage-1) }) }}">← Prev</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% for page in 1..posts.lastPage %}
|
||||
<li class="{{ posts.currentPage == page ? 'active' : null }}">
|
||||
<a href="{{ this.page.baseFileName|page({ (__SELF__.pageParam): page }) }}">{{ page }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if posts.lastPage > posts.currentPage %}
|
||||
<li><a href="{{ this.page.baseFileName|page({ (__SELF__.pageParam): (posts.currentPage+1) }) }}">Next →</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>{{ this.page.meta_title ?: this.page.title }}</title>
|
||||
<link>{{ link }}</link>
|
||||
<description>{{ this.page.meta_description ?: this.page.description }}</description>
|
||||
<atom:link href="{{ rssLink }}" rel="self" type="application/rss+xml" />
|
||||
{% for post in posts %}
|
||||
<item>
|
||||
<title>{{ post.title }}</title>
|
||||
<link>{{ post.url }}</link>
|
||||
<guid>{{ post.url }}</guid>
|
||||
<pubDate>{{ post.published_at.toRfc2822String }}</pubDate>
|
||||
<description>{{ post.summary }}</description>
|
||||
</item>
|
||||
{% endfor %}
|
||||
</channel>
|
||||
</rss>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "rainlab/blog-plugin",
|
||||
"type": "october-plugin",
|
||||
"description": "Blog plugin for October CMS",
|
||||
"homepage": "https://octobercms.com/plugin/rainlab-blog",
|
||||
"keywords": ["october", "octobercms", "blog"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alexey Bobkov",
|
||||
"email": "aleksey.bobkov@gmail.com",
|
||||
"role": "Co-founder"
|
||||
},
|
||||
{
|
||||
"name": "Samuel Georges",
|
||||
"email": "daftspunky@gmail.com",
|
||||
"role": "Co-founder"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"composer/installers": "~1.0"
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Summary Config
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify a custom tag and length for blog post summaries
|
||||
|
|
||||
*/
|
||||
|
||||
'summary_separator' => '<!-- more -->',
|
||||
|
||||
'summary_default_length' => 600
|
||||
|
||||
];
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?php namespace RainLab\Blog\Controllers;
|
||||
|
||||
use BackendMenu;
|
||||
use Flash;
|
||||
use Lang;
|
||||
use Backend\Classes\Controller;
|
||||
use RainLab\Blog\Models\Category;
|
||||
|
||||
class Categories extends Controller
|
||||
{
|
||||
public $implement = [
|
||||
\Backend\Behaviors\FormController::class,
|
||||
\Backend\Behaviors\ListController::class,
|
||||
\Backend\Behaviors\ReorderController::class
|
||||
];
|
||||
|
||||
public $formConfig = 'config_form.yaml';
|
||||
public $listConfig = 'config_list.yaml';
|
||||
public $reorderConfig = 'config_reorder.yaml';
|
||||
|
||||
public $requiredPermissions = ['rainlab.blog.access_categories'];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
BackendMenu::setContext('RainLab.Blog', 'blog', 'categories');
|
||||
}
|
||||
|
||||
public function index_onDelete()
|
||||
{
|
||||
if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) {
|
||||
|
||||
foreach ($checkedIds as $categoryId) {
|
||||
if ((!$category = Category::find($categoryId))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$category->delete();
|
||||
}
|
||||
|
||||
Flash::success(Lang::get('rainlab.blog::lang.category.delete_success'));
|
||||
}
|
||||
|
||||
return $this->listRefresh();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
<?php namespace RainLab\Blog\Controllers;
|
||||
|
||||
use Lang;
|
||||
use Flash;
|
||||
use BackendMenu;
|
||||
use RainLab\Blog\Models\Post;
|
||||
use RainLab\Blog\Models\Settings as BlogSettings;
|
||||
use Backend\Classes\Controller;
|
||||
|
||||
/**
|
||||
* Posts
|
||||
*/
|
||||
class Posts extends Controller
|
||||
{
|
||||
public $implement = [
|
||||
\Backend\Behaviors\FormController::class,
|
||||
\Backend\Behaviors\ListController::class,
|
||||
\Backend\Behaviors\ImportExportController::class
|
||||
];
|
||||
|
||||
public $formConfig = 'config_form.yaml';
|
||||
public $listConfig = 'config_list.yaml';
|
||||
public $importExportConfig = 'config_import_export.yaml';
|
||||
|
||||
/**
|
||||
* @var array requiredPermissions
|
||||
*/
|
||||
public $requiredPermissions = ['rainlab.blog.access_other_posts', 'rainlab.blog.access_posts'];
|
||||
|
||||
/**
|
||||
* @var bool turboVisitControl
|
||||
*/
|
||||
public $turboVisitControl = 'disable';
|
||||
|
||||
/**
|
||||
* __construct
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
BackendMenu::setContext('RainLab.Blog', 'blog', 'posts');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->vars['postsTotal'] = Post::count();
|
||||
$this->vars['postsPublished'] = Post::isPublished()->count();
|
||||
$this->vars['postsDrafts'] = $this->vars['postsTotal'] - $this->vars['postsPublished'];
|
||||
|
||||
$this->asExtension('ListController')->index();
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
BackendMenu::setContextSideMenu('new_post');
|
||||
|
||||
$this->bodyClass = 'compact-container';
|
||||
$this->addCss('/plugins/rainlab/blog/assets/css/rainlab.blog-preview.css');
|
||||
$this->addJs('/plugins/rainlab/blog/assets/js/post-form.js');
|
||||
|
||||
return $this->asExtension('FormController')->create();
|
||||
}
|
||||
|
||||
public function update($recordId = null)
|
||||
{
|
||||
$this->bodyClass = 'compact-container';
|
||||
$this->addCss('/plugins/rainlab/blog/assets/css/rainlab.blog-preview.css');
|
||||
$this->addJs('/plugins/rainlab/blog/assets/js/post-form.js');
|
||||
|
||||
return $this->asExtension('FormController')->update($recordId);
|
||||
}
|
||||
|
||||
public function export()
|
||||
{
|
||||
$this->addCss('/plugins/rainlab/blog/assets/css/rainlab.blog-export.css');
|
||||
|
||||
return $this->asExtension('ImportExportController')->export();
|
||||
}
|
||||
|
||||
public function listExtendQuery($query)
|
||||
{
|
||||
if (!$this->user->hasAnyAccess(['rainlab.blog.access_other_posts'])) {
|
||||
$query->where('user_id', $this->user->id);
|
||||
}
|
||||
}
|
||||
|
||||
public function formExtendQuery($query)
|
||||
{
|
||||
if (!$this->user->hasAnyAccess(['rainlab.blog.access_other_posts'])) {
|
||||
$query->where('user_id', $this->user->id);
|
||||
}
|
||||
}
|
||||
|
||||
public function formExtendFieldsBefore($widget)
|
||||
{
|
||||
if (!$model = $widget->model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($model instanceof Post && $model->isClassExtendedWith('RainLab.Translate.Behaviors.TranslatableModel')) {
|
||||
$widget->secondaryTabs['fields']['content']['type'] = 'RainLab\Blog\FormWidgets\MLBlogMarkdown';
|
||||
}
|
||||
|
||||
if (BlogSettings::get('use_legacy_editor', false)) {
|
||||
$widget->secondaryTabs['fields']['content']['legacyMode'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function index_onDelete()
|
||||
{
|
||||
if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) {
|
||||
|
||||
foreach ($checkedIds as $postId) {
|
||||
if ((!$post = Post::find($postId)) || !$post->canEdit($this->user)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$post->delete();
|
||||
}
|
||||
|
||||
Flash::success(Lang::get('rainlab.blog::lang.post.delete_success'));
|
||||
}
|
||||
|
||||
return $this->listRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listInjectRowClass($record, $definition = null)
|
||||
{
|
||||
if (!$record->published) {
|
||||
return 'safe disabled';
|
||||
}
|
||||
}
|
||||
|
||||
public function formBeforeCreate($model)
|
||||
{
|
||||
$model->user_id = $this->user->id;
|
||||
}
|
||||
|
||||
public function onRefreshPreview()
|
||||
{
|
||||
$data = post('Post');
|
||||
|
||||
$previewHtml = Post::formatHtml($data['content'], true);
|
||||
|
||||
return [
|
||||
'preview' => $previewHtml
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<div data-control="toolbar">
|
||||
<a href="<?= Backend::url('rainlab/blog/categories/create') ?>" class="btn btn-primary oc-icon-plus">
|
||||
<?= e(trans('rainlab.blog::lang.categories.new_category')) ?>
|
||||
</a>
|
||||
<button
|
||||
class="btn btn-default oc-icon-trash-o"
|
||||
disabled="disabled"
|
||||
onclick="$(this).data('request-data', {
|
||||
checked: $('.control-list').listWidget('getChecked')
|
||||
})"
|
||||
data-request="onDelete"
|
||||
data-request-confirm="<?= e(trans('rainlab.blog::lang.blog.delete_confirm')) ?>"
|
||||
data-trigger-action="enable"
|
||||
data-trigger=".control-list input[type=checkbox]"
|
||||
data-trigger-condition="checked"
|
||||
data-request-success="$(this).prop('disabled', false)"
|
||||
data-stripe-load-indicator>
|
||||
<?= e(trans('backend::lang.list.delete_selected')) ?>
|
||||
</button>
|
||||
<?php if (!class_exists('System')): ?>
|
||||
<a href="<?= Backend::url('rainlab/blog/categories/reorder') ?>" class="btn btn-default oc-icon-sitemap">
|
||||
<?= e(trans('rainlab.blog::lang.category.reorder')) ?>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<div data-control="toolbar">
|
||||
<a href="<?= Backend::url('rainlab/blog/categories') ?>" class="btn btn-primary oc-icon-caret-left">
|
||||
<?= e(trans('rainlab.blog::lang.category.return_to_categories')) ?>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# ===================================
|
||||
# Form Behavior Config
|
||||
# ===================================
|
||||
|
||||
name: rainlab.blog::lang.blog.create_category
|
||||
form: $/rainlab/blog/models/category/fields.yaml
|
||||
modelClass: RainLab\Blog\Models\Category
|
||||
defaultRedirect: rainlab/blog/categories
|
||||
|
||||
create:
|
||||
redirect: rainlab/blog/categories/update/:id
|
||||
redirectClose: rainlab/blog/categories
|
||||
|
||||
update:
|
||||
redirect: rainlab/blog/categories
|
||||
redirectClose: rainlab/blog/categories
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# ===================================
|
||||
# List Behavior Config
|
||||
# ===================================
|
||||
|
||||
# Model List Column configuration
|
||||
list: $/rainlab/blog/models/category/columns.yaml
|
||||
|
||||
# Model Class name
|
||||
modelClass: RainLab\Blog\Models\Category
|
||||
|
||||
# List Title
|
||||
title: rainlab.blog::lang.categories.list_title
|
||||
|
||||
# Link URL for each record
|
||||
recordUrl: rainlab/blog/categories/update/:id
|
||||
|
||||
# Message to display if the list is empty
|
||||
noRecordsMessage: backend::lang.list.no_records
|
||||
|
||||
# Records to display per page
|
||||
recordsPerPage: 5
|
||||
|
||||
# Display checkboxes next to each record
|
||||
showCheckboxes: true
|
||||
|
||||
# Toolbar widget configuration
|
||||
toolbar:
|
||||
# Partial for toolbar buttons
|
||||
buttons: list_toolbar
|
||||
|
||||
# Search widget configuration
|
||||
search:
|
||||
prompt: backend::lang.list.search_prompt
|
||||
|
||||
# Legacy (v1)
|
||||
showTree: true
|
||||
|
||||
# Reordering
|
||||
structure:
|
||||
showTree: true
|
||||
showReorder: true
|
||||
treeExpanded: true
|
||||
maxDepth: 0
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# ===================================
|
||||
# Reorder Behavior Config
|
||||
# ===================================
|
||||
|
||||
# Reorder Title
|
||||
title: rainlab.blog::lang.category.reorder
|
||||
|
||||
# Attribute name
|
||||
nameFrom: name
|
||||
|
||||
# Model Class name
|
||||
modelClass: RainLab\Blog\Models\Category
|
||||
|
||||
# Toolbar widget configuration
|
||||
toolbar:
|
||||
# Partial for toolbar buttons
|
||||
buttons: reorder_toolbar
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('rainlab/blog/posts') ?>"><?= e(trans('rainlab.blog::lang.blog.menu_label')) ?></a></li>
|
||||
<li><?= e(trans($this->pageTitle)) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.create')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-request="onSave"
|
||||
data-request-data="close:1"
|
||||
data-hotkey="ctrl+enter, cmd+enter"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-default">
|
||||
<?= e(trans('backend::lang.form.create_and_close')) ?>
|
||||
</button>
|
||||
<span class="btn-text">
|
||||
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('rainlab/blog/categories') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Form::close() ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('rainlab/blog/posts') ?>" class="btn btn-default"><?= e(trans('rainlab.blog::lang.category.return_to_categories')) ?></a></p>
|
||||
<?php endif ?>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
<?= $this->listRender() ?>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<?= $this->reorderRender() ?>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('rainlab/blog/posts') ?>"><?= e(trans('rainlab.blog::lang.blog.menu_label')) ?></a></li>
|
||||
<li><?= e(trans($this->pageTitle)) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-request-data="redirect:0"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.save')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-request="onSave"
|
||||
data-request-data="close:1"
|
||||
data-hotkey="ctrl+enter, cmd+enter"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-default">
|
||||
<?= e(trans('backend::lang.form.save_and_close')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="oc-icon-trash-o btn-icon danger pull-right"
|
||||
data-request="onDelete"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.deleting')) ?>"
|
||||
data-request-confirm="<?= e(trans('rainlab.blog::lang.category.delete_confirm')) ?>">
|
||||
</button>
|
||||
|
||||
<span class="btn-text">
|
||||
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('rainlab/blog/categories') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?= Form::close() ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('rainlab/blog/posts') ?>" class="btn btn-default"><?= e(trans('rainlab.blog::lang.category.return_to_categories')) ?></a></p>
|
||||
<?php endif ?>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<div data-control="toolbar">
|
||||
<a
|
||||
href="<?= Backend::url('rainlab/blog/posts/create') ?>"
|
||||
class="btn btn-primary oc-icon-plus">
|
||||
<?= e(trans('rainlab.blog::lang.posts.new_post')) ?>
|
||||
</a>
|
||||
<button
|
||||
class="btn btn-default oc-icon-trash-o"
|
||||
disabled="disabled"
|
||||
onclick="$(this).data('request-data', {
|
||||
checked: $('.control-list').listWidget('getChecked')
|
||||
})"
|
||||
data-request="onDelete"
|
||||
data-request-confirm="<?= e(trans('rainlab.blog::lang.blog.delete_confirm')) ?>"
|
||||
data-trigger-action="enable"
|
||||
data-trigger=".control-list input[type=checkbox]"
|
||||
data-trigger-condition="checked"
|
||||
data-request-success="$(this).prop('disabled', true)"
|
||||
data-stripe-load-indicator>
|
||||
<?= e(trans('backend::lang.list.delete_selected')) ?>
|
||||
</button>
|
||||
|
||||
<?php if ($this->user->hasAnyAccess(['rainlab.blog.access_import_export'])): ?>
|
||||
<div class="btn-group">
|
||||
<a
|
||||
href="<?= Backend::url('rainlab/blog/posts/export') ?>"
|
||||
class="btn btn-default oc-icon-download">
|
||||
<?= e(trans('rainlab.blog::lang.posts.export_post')) ?>
|
||||
</a>
|
||||
<a
|
||||
href="<?= Backend::url('rainlab/blog/posts/import') ?>"
|
||||
class="btn btn-default oc-icon-upload">
|
||||
<?= e(trans('rainlab.blog::lang.posts.import_post')) ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
$isCreate = $this->formGetContext() == 'create';
|
||||
$pageUrl = isset($pageUrl) ? $pageUrl : null;
|
||||
?>
|
||||
<div class="form-buttons loading-indicator-container">
|
||||
|
||||
<!-- Save -->
|
||||
<a
|
||||
href="javascript:;"
|
||||
class="btn btn-primary oc-icon-check save"
|
||||
data-request="onSave"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
|
||||
data-request-before-update="$(this).trigger('unchange.oc.changeMonitor')"
|
||||
<?php if (!$isCreate): ?>data-request-data="redirect:0"<?php endif ?>
|
||||
data-hotkey="ctrl+s, cmd+s">
|
||||
<?= e(trans('backend::lang.form.save')) ?>
|
||||
</a>
|
||||
|
||||
<?php if (!$isCreate): ?>
|
||||
<!-- Save and Close -->
|
||||
<a
|
||||
href="javascript:;"
|
||||
class="btn btn-primary oc-icon-check save"
|
||||
data-request-before-update="$(this).trigger('unchange.oc.changeMonitor')"
|
||||
data-request="onSave"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>">
|
||||
<?= e(trans('backend::lang.form.save_and_close')) ?>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
|
||||
<!-- Cancel -->
|
||||
<a
|
||||
href="<?= Backend::url('rainlab/blog/posts') ?>"
|
||||
class="btn btn-primary oc-icon-arrow-left cancel">
|
||||
<?= e(trans('backend::lang.form.cancel')) ?>
|
||||
</a>
|
||||
|
||||
<!-- Preview -->
|
||||
<a
|
||||
href="<?= URL::to($pageUrl) ?>"
|
||||
target="_blank"
|
||||
class="btn btn-primary oc-icon-crosshairs <?php if (!false): ?>hide<?php endif ?>"
|
||||
data-control="preview-button">
|
||||
<?= e(trans('rainlab.blog::lang.blog.preview')) ?>
|
||||
</a>
|
||||
|
||||
<?php if (!$isCreate): ?>
|
||||
<!-- Delete -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default empty oc-icon-trash-o"
|
||||
data-request="onDelete"
|
||||
data-request-confirm="<?= e(trans('rainlab.blog::lang.post.delete_confirm')) ?>"
|
||||
data-control="delete-button"></button>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# ===================================
|
||||
# Form Behavior Config
|
||||
# ===================================
|
||||
|
||||
name: rainlab.blog::lang.blog.create_post
|
||||
form: $/rainlab/blog/models/post/fields.yaml
|
||||
modelClass: RainLab\Blog\Models\Post
|
||||
defaultRedirect: rainlab/blog/posts
|
||||
|
||||
create:
|
||||
redirect: rainlab/blog/posts/update/:id
|
||||
redirectClose: rainlab/blog/posts
|
||||
|
||||
update:
|
||||
redirect: rainlab/blog/posts
|
||||
redirectClose: rainlab/blog/posts
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# ===================================
|
||||
# Import/Export Behavior Config
|
||||
# ===================================
|
||||
|
||||
import:
|
||||
# Page title
|
||||
title: rainlab.blog::lang.posts.import_post
|
||||
|
||||
# Import List Column configuration
|
||||
list: $/rainlab/blog/models/postimport/columns.yaml
|
||||
|
||||
# Import Form Field configuration
|
||||
form: $/rainlab/blog/models/postimport/fields.yaml
|
||||
|
||||
# Import Model class
|
||||
modelClass: RainLab\Blog\Models\PostImport
|
||||
|
||||
# Redirect when finished
|
||||
redirect: rainlab/blog/posts
|
||||
|
||||
# Required permissions
|
||||
permissions: rainlab.blog.access_import_export
|
||||
|
||||
export:
|
||||
# Page title
|
||||
title: rainlab.blog::lang.posts.export_post
|
||||
|
||||
# Output file name
|
||||
fileName: posts.csv
|
||||
|
||||
# Export List Column configuration
|
||||
list: $/rainlab/blog/models/postexport/columns.yaml
|
||||
|
||||
# Export Model class
|
||||
modelClass: RainLab\Blog\Models\PostExport
|
||||
|
||||
# Redirect when finished
|
||||
redirect: rainlab/blog/posts
|
||||
|
||||
# Required permissions
|
||||
permissions: rainlab.blog.access_import_export
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# ===================================
|
||||
# List Behavior Config
|
||||
# ===================================
|
||||
|
||||
# Model List Column configuration
|
||||
list: $/rainlab/blog/models/post/columns.yaml
|
||||
|
||||
# Filter widget configuration
|
||||
filter: $/rainlab/blog/models/post/scopes.yaml
|
||||
|
||||
# Model Class name
|
||||
modelClass: RainLab\Blog\Models\Post
|
||||
|
||||
# List Title
|
||||
title: rainlab.blog::lang.posts.list_title
|
||||
|
||||
# Link URL for each record
|
||||
recordUrl: rainlab/blog/posts/update/:id
|
||||
|
||||
# Message to display if the list is empty
|
||||
noRecordsMessage: backend::lang.list.no_records
|
||||
|
||||
# Records to display per page
|
||||
recordsPerPage: 25
|
||||
|
||||
# Displays the list column set up button
|
||||
showSetup: true
|
||||
|
||||
# Displays the sorting link on each column
|
||||
showSorting: true
|
||||
|
||||
# Default sorting column
|
||||
defaultSort:
|
||||
column: published_at
|
||||
direction: desc
|
||||
|
||||
# Display checkboxes next to each record
|
||||
showCheckboxes: true
|
||||
|
||||
# Toolbar widget configuration
|
||||
toolbar:
|
||||
# Partial for toolbar buttons
|
||||
buttons: list_toolbar
|
||||
|
||||
# Search widget configuration
|
||||
search:
|
||||
prompt: backend::lang.list.search_prompt
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<div class="layout fancy-layout">
|
||||
<?= Form::open([
|
||||
'class' => 'layout',
|
||||
'data-change-monitor' => 'true',
|
||||
'data-window-close-confirm' => e(trans('rainlab.blog::lang.post.close_confirm')),
|
||||
'id' => 'post-form'
|
||||
]) ?>
|
||||
<?= $this->formRender() ?>
|
||||
|
||||
<?= Form::close() ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="control-breadcrumb">
|
||||
<?= Block::placeholder('breadcrumb') ?>
|
||||
</div>
|
||||
<div class="padded-container">
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('rainlab/blog/posts') ?>" class="btn btn-default"><?= e(trans('rainlab.blog::lang.post.return_to_posts')) ?></a></p>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('rainlab/blog/posts') ?>"><?= e(trans('rainlab.blog::lang.blog.menu_label')) ?></a></li>
|
||||
<li><?= e(trans($this->pageTitle)) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->exportRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-control="popup"
|
||||
data-handler="onExportLoadForm"
|
||||
data-keyboard="false"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('rainlab.blog::lang.posts.export_post')) ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Form::close() ?>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('rainlab/blog/posts') ?>"><?= e(trans('rainlab.blog::lang.blog.menu_label')) ?></a></li>
|
||||
<li><?= e(trans($this->pageTitle)) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?= Form::open(['class' => 'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->importRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<button
|
||||
type="submit"
|
||||
data-control="popup"
|
||||
data-handler="onImportLoadForm"
|
||||
data-keyboard="false"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('rainlab.blog::lang.posts.import_post')) ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?= Form::close() ?>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<?= $this->listRender() ?>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<div class="layout fancy-layout">
|
||||
<?= Form::open([
|
||||
'class' => 'layout',
|
||||
'data-change-monitor' => 'true',
|
||||
'data-window-close-confirm' => e(trans('rainlab.blog::lang.post.close_confirm')),
|
||||
'id' => 'post-form'
|
||||
]) ?>
|
||||
<?= $this->formRender() ?>
|
||||
|
||||
<?= Form::close() ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<div class="control-breadcrumb">
|
||||
<?= Block::placeholder('breadcrumb') ?>
|
||||
</div>
|
||||
<div class="padded-container">
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('rainlab/blog/posts') ?>" class="btn btn-default"><?= e(trans('rainlab.blog::lang.post.return_to_posts')) ?></a></p>
|
||||
</div>
|
||||
|
||||
<?php endif ?>
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
<?php namespace RainLab\Blog\FormWidgets;
|
||||
|
||||
use Lang;
|
||||
use Input;
|
||||
use Response;
|
||||
use Validator;
|
||||
use RainLab\Blog\Models\Post as PostModel;
|
||||
use Backend\Classes\FormWidgetBase;
|
||||
use Backend\FormWidgets\MarkdownEditor;
|
||||
use System\Models\File;
|
||||
use ValidationException;
|
||||
use SystemException;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Special markdown editor for the Create/Edit Post form.
|
||||
*
|
||||
* @package rainlab\blog
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class BlogMarkdown extends MarkdownEditor
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->viewPath = base_path().'/modules/backend/formwidgets/markdowneditor/partials';
|
||||
|
||||
$this->checkUploadPostback();
|
||||
|
||||
parent::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function loadAssets()
|
||||
{
|
||||
$this->assetPath = '/modules/backend/formwidgets/markdowneditor/assets';
|
||||
parent::loadAssets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable HTML cleaning on the widget level since the PostModel will handle it
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function shouldCleanHtml()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function onRefresh()
|
||||
{
|
||||
$content = post($this->formField->getName());
|
||||
|
||||
$previewHtml = PostModel::formatHtml($content, true);
|
||||
|
||||
return [
|
||||
'preview' => $previewHtml
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle images being uploaded to the blog post
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function checkUploadPostback()
|
||||
{
|
||||
if (!post('X_BLOG_IMAGE_UPLOAD')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uploadedFileName = null;
|
||||
|
||||
try {
|
||||
$uploadedFile = Input::file('file');
|
||||
|
||||
if ($uploadedFile)
|
||||
$uploadedFileName = $uploadedFile->getClientOriginalName();
|
||||
|
||||
$validationRules = ['max:'.File::getMaxFilesize()];
|
||||
$validationRules[] = 'mimes:jpg,jpeg,bmp,png,gif';
|
||||
|
||||
$validation = Validator::make(
|
||||
['file_data' => $uploadedFile],
|
||||
['file_data' => $validationRules]
|
||||
);
|
||||
|
||||
if ($validation->fails()) {
|
||||
throw new ValidationException($validation);
|
||||
}
|
||||
|
||||
if (!$uploadedFile->isValid()) {
|
||||
throw new SystemException(Lang::get('cms::lang.asset.file_not_valid'));
|
||||
}
|
||||
|
||||
$fileRelation = $this->model->content_images();
|
||||
|
||||
$file = new File();
|
||||
$file->data = $uploadedFile;
|
||||
$file->is_public = true;
|
||||
$file->save();
|
||||
|
||||
$fileRelation->add($file, $this->sessionKey);
|
||||
$result = [
|
||||
'file' => $uploadedFileName,
|
||||
'path' => $file->getPath()
|
||||
];
|
||||
|
||||
$response = Response::make()->setContent($result);
|
||||
$this->controller->setResponse($response);
|
||||
|
||||
} catch (Exception $ex) {
|
||||
$message = $uploadedFileName
|
||||
? Lang::get('cms::lang.asset.error_uploading_file', ['name' => $uploadedFileName, 'error' => $ex->getMessage()])
|
||||
: $ex->getMessage();
|
||||
|
||||
$result = [
|
||||
'error' => $message,
|
||||
'file' => $uploadedFileName
|
||||
];
|
||||
|
||||
$response = Response::make()->setContent($result);
|
||||
$this->controller->setResponse($response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
<?php namespace RainLab\Blog\FormWidgets;
|
||||
|
||||
use RainLab\Blog\Models\Post;
|
||||
use RainLab\Translate\Models\Locale;
|
||||
|
||||
/**
|
||||
* A multi-lingual version of the blog markdown editor.
|
||||
* This class should never be invoked without the RainLab.Translate plugin.
|
||||
*
|
||||
* @package rainlab\blog
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MLBlogMarkdown extends BlogMarkdown
|
||||
{
|
||||
use \RainLab\Translate\Traits\MLControl;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected $defaultAlias = 'mlmarkdowneditor';
|
||||
|
||||
public $originalAssetPath;
|
||||
public $originalViewPath;
|
||||
|
||||
/**
|
||||
* @var bool legacyMode disables the Vue integration
|
||||
*/
|
||||
public $legacyMode = true;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->initLocale();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$this->actAsParent();
|
||||
$parentContent = parent::render();
|
||||
$this->actAsParent(false);
|
||||
|
||||
if (!$this->isAvailable) {
|
||||
return $parentContent;
|
||||
}
|
||||
|
||||
$this->vars['markdowneditor'] = $parentContent;
|
||||
|
||||
$this->actAsControl(true);
|
||||
|
||||
return $this->makePartial('mlmarkdowneditor');
|
||||
}
|
||||
|
||||
public function prepareVars()
|
||||
{
|
||||
parent::prepareVars();
|
||||
$this->prepareLocaleVars();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of translated values for this field
|
||||
* @param $value
|
||||
* @return array
|
||||
*/
|
||||
public function getSaveValue($value)
|
||||
{
|
||||
$localeData = $this->getLocaleSaveData();
|
||||
|
||||
/*
|
||||
* Set the translated values to the model
|
||||
*/
|
||||
if ($this->model->methodExists('setAttributeTranslated')) {
|
||||
foreach ($localeData as $locale => $value) {
|
||||
$this->model->setAttributeTranslated('content', $value, $locale);
|
||||
|
||||
$this->model->setAttributeTranslated(
|
||||
'content_html',
|
||||
Post::formatHtml($value),
|
||||
$locale
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return array_get($localeData, $this->defaultLocale->code, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function loadAssets()
|
||||
{
|
||||
$this->actAsParent();
|
||||
parent::loadAssets();
|
||||
$this->actAsParent(false);
|
||||
|
||||
if (Locale::isAvailable()) {
|
||||
$this->loadLocaleAssets();
|
||||
|
||||
$this->actAsControl(true);
|
||||
$this->addJs('js/mlmarkdowneditor.js');
|
||||
$this->actAsControl(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected function actAsParent($switch = true)
|
||||
{
|
||||
if ($switch) {
|
||||
$this->originalAssetPath = $this->assetPath;
|
||||
$this->originalViewPath = $this->viewPath;
|
||||
$this->assetPath = '/modules/backend/formwidgets/markdowneditor/assets';
|
||||
$this->viewPath = base_path('/modules/backend/formwidgets/markdowneditor/partials');
|
||||
}
|
||||
else {
|
||||
$this->assetPath = $this->originalAssetPath;
|
||||
$this->viewPath = $this->originalViewPath;
|
||||
}
|
||||
}
|
||||
|
||||
protected function actAsControl($switch = true)
|
||||
{
|
||||
if ($switch) {
|
||||
$this->originalAssetPath = $this->assetPath;
|
||||
$this->originalViewPath = $this->viewPath;
|
||||
$this->assetPath = '/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/assets';
|
||||
$this->viewPath = base_path('/plugins/rainlab/translate/formwidgets/mlmarkdowneditor/partials');
|
||||
}
|
||||
else {
|
||||
$this->assetPath = $this->originalAssetPath;
|
||||
$this->viewPath = $this->originalViewPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'plugin' => [
|
||||
'name' => 'Блог',
|
||||
'description' => 'Стабилната блог платформа.'
|
||||
],
|
||||
'blog' => [
|
||||
'menu_label' => 'Блог',
|
||||
'menu_description' => 'управление на публикациите',
|
||||
'posts' => 'публикации',
|
||||
'create_post' => 'създай публикация',
|
||||
'categories' => 'категории',
|
||||
'create_category' => 'създай категория',
|
||||
'tab' => 'Блог',
|
||||
'access_posts' => 'управление на публикациите',
|
||||
'access_categories' => 'управление на категории',
|
||||
'access_other_posts' => 'управление на други потребители публикации в блога',
|
||||
'delete_confirm' => 'Сигурни ли сте?',
|
||||
'chart_published' => 'Публикувано',
|
||||
'chart_drafts' => 'Чернови',
|
||||
'chart_total' => 'Общо'
|
||||
],
|
||||
'posts' => [
|
||||
'list_title' => 'Управление публикациите в блога',
|
||||
'filter_category' => 'Категория',
|
||||
'filter_published' => 'Скрий публикуваните',
|
||||
'new_post' => 'Нова публикация'
|
||||
],
|
||||
'post' => [
|
||||
'title' => 'Заглавие',
|
||||
'title_placeholder' => 'Ново заглавие на публикацията',
|
||||
'slug' => 'Slug',
|
||||
'slug_placeholder' => 'нов slug на публикацията',
|
||||
'categories' => 'Категории',
|
||||
'created' => 'Създаден',
|
||||
'updated' => 'Обновен',
|
||||
'published' => 'Публикуван',
|
||||
'published_validation' => 'Моля, посочете дата на публикуване',
|
||||
'tab_edit' => 'Промяна',
|
||||
'tab_categories' => 'Категории',
|
||||
'categories_comment' => 'Изберете категории към който пренадлежи публикацията ',
|
||||
'categories_placeholder' => 'Няма категирии, Създайте първата?!',
|
||||
'tab_manage' => 'Управление',
|
||||
'published_on' => 'публикувано в',
|
||||
'excerpt' => 'Откъс',
|
||||
'featured_images' => 'Избрани снимки',
|
||||
'delete_confirm' => 'Наистина ли искате да изтриете тази публикация?',
|
||||
'close_confirm' => 'Публикацията не е запазена.',
|
||||
'return_to_posts' => 'Върни ме към всички публикации'
|
||||
],
|
||||
'categories' => [
|
||||
'list_title' => 'Управление категориите в блога',
|
||||
'new_category' => 'Нова категория',
|
||||
'uncategorized' => 'Без категория'
|
||||
],
|
||||
'category' => [
|
||||
'name' => 'Име',
|
||||
'name_placeholder' => 'Ново име на категорията',
|
||||
'slug' => 'Slug',
|
||||
'slug_placeholder' => 'нов slug на категотията',
|
||||
'posts' => 'публикации',
|
||||
'delete_confirm' => 'Наистина ли искате да изтриете тази категория?',
|
||||
'return_to_categories' => 'Върни ме към всички категории'
|
||||
],
|
||||
'settings' => [
|
||||
'category_title' => 'Списък с категории',
|
||||
'category_description' => 'Показва списък с категориите на блога.',
|
||||
'category_slug' => 'категория slug',
|
||||
'category_slug_description' => "Look up the blog category using the supplied slug value. This property is used by the default component partial for marking the currently active category.",
|
||||
'category_display_empty' => 'Показване на празни категории',
|
||||
'category_display_empty_description' => 'Показване на категории, които нямат никакви публикации.',
|
||||
'category_page' => 'Страница на категория',
|
||||
'category_page_description' => 'Име на страницата за категирия. Това се използва подразбиране от компонента.',
|
||||
'post_title' => 'Публикация',
|
||||
'post_description' => 'Показване на Публикациите в блога на страницата.',
|
||||
'post_slug' => 'Post slug',
|
||||
'post_slug_description' => "Търсене на публикации по зададен slug.",
|
||||
'post_category' => 'Страница за Категория',
|
||||
'post_category_description' => 'Име на страница за категория за генериране на линк.Това се използва подразбиране от компонента.',
|
||||
'posts_title' => 'Лист с Публикации',
|
||||
'posts_description' => 'Показване на лист с публикации на страницата.',
|
||||
'posts_pagination' => 'Номер на страницата',
|
||||
'posts_pagination_description' => 'Тази стойност се използва за определяне на коя страница е потребителя.',
|
||||
'posts_filter' => 'Филтер Категория',
|
||||
'posts_filter_description' => 'Въведи slug на категория или URL адрес за филтриране по. Оставете празно за да се покажат всички публикации.',
|
||||
'posts_per_page' => 'Публикации на страница',
|
||||
'posts_per_page_validation' => 'Невалиден формат за публикации на страница',
|
||||
'posts_no_posts' => 'Няма публикации',
|
||||
'posts_no_posts_description' => 'Съобщение което да се покаже, в случай ,че няма публикации за показване.Това се използва подразбиране от компонента.',
|
||||
'posts_order' => 'подреждане на публикации',
|
||||
'posts_order_description' => 'Атрибут по който да бъдат подредени публикациите',
|
||||
'posts_category' => 'страница на категориите',
|
||||
'posts_category_description' => 'Име на страницата за категории , за "публикувано в". Това се използва подразбиране от компонента.',
|
||||
'posts_post' => 'Post page',
|
||||
'posts_post_description' => 'Име на страницата за публикации "Прочетете повече". Това се използва подразбиране от компонента.',
|
||||
'posts_except_post' => 'Except post',
|
||||
'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to except',
|
||||
]
|
||||
];
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'plugin' => [
|
||||
'name' => 'Blog',
|
||||
'description' => 'Robustní blogová platforma.'
|
||||
],
|
||||
'blog' => [
|
||||
'menu_label' => 'Blog',
|
||||
'menu_description' => 'Správa blogových příspěvků',
|
||||
'posts' => 'Příspěvky',
|
||||
'create_post' => 'Příspěvek',
|
||||
'categories' => 'Kategorie',
|
||||
'create_category' => 'Kategorie příspěvků',
|
||||
'tab' => 'Blog',
|
||||
'access_posts' => 'Správa blogových příspěvků',
|
||||
'access_categories' => 'Správa blogových kategorií',
|
||||
'access_other_posts' => 'Správa příspěvků ostatních uživatelů',
|
||||
'access_import_export' => 'Možnost importu a exportu příspěvků',
|
||||
'access_publish' => 'Možnost publikovat příspěvky',
|
||||
'delete_confirm' => 'Jste si jistí?',
|
||||
'chart_published' => 'Publikované',
|
||||
'chart_drafts' => 'Návrhy',
|
||||
'chart_total' => 'Celkem',
|
||||
],
|
||||
'posts' => [
|
||||
'list_title' => 'Správa blogových příspěvků',
|
||||
'filter_category' => 'Kategorie',
|
||||
'filter_published' => 'Schovat publikované',
|
||||
'filter_date' => 'Datum',
|
||||
'new_post' => 'Nový příspěvek',
|
||||
'export_post' => 'Export příspěvků',
|
||||
'import_post' => 'Import příspěvků',
|
||||
],
|
||||
'post' => [
|
||||
'title' => 'Název',
|
||||
'title_placeholder' => 'Zadejte název',
|
||||
'content' => 'Obsah',
|
||||
'content_html' => 'HTML obsah',
|
||||
'slug' => 'URL příspěvku',
|
||||
'slug_placeholder' => 'zadejte-url-prispevku',
|
||||
'categories' => 'Kategorie',
|
||||
'author_email' => 'E-mail autora',
|
||||
'created' => 'Vytvořeno',
|
||||
'created_date' => 'Vytvořeno dne',
|
||||
'updated' => 'Upraveno',
|
||||
'updated_date' => 'Upraveno dne',
|
||||
'published' => 'Publikováno',
|
||||
'published_date' => 'Publikováno dne',
|
||||
'published_validation' => 'Zadejte prosím datum publikace příspěvku',
|
||||
'tab_edit' => 'Upravit',
|
||||
'tab_categories' => 'Kategorie',
|
||||
'categories_comment' => 'Vyberte kategorie do kterých příspěvek patří',
|
||||
'categories_placeholder' => 'Nejsou zde žádné kategorie, nejdříve musíte nějaké vytvořit!',
|
||||
'tab_manage' => 'Nastavení',
|
||||
'published_on' => 'Publikováno dne',
|
||||
'excerpt' => 'Perex příspěvku',
|
||||
'summary' => 'Shrnutí',
|
||||
'featured_images' => 'Obrázky',
|
||||
'delete_confirm' => 'Opravdu chcete smazat tento příspěvek?',
|
||||
'delete_success' => 'Vybrané příspěvky úspěšně odstraněny.',
|
||||
'close_confirm' => 'Příspěvek není uložený.',
|
||||
'return_to_posts' => 'Zpět na seznam příspěvků',
|
||||
],
|
||||
'categories' => [
|
||||
'list_title' => 'Správa blogových kategorií',
|
||||
'new_category' => 'Nová kategorie',
|
||||
'uncategorized' => 'Nezařazeno',
|
||||
],
|
||||
'category' => [
|
||||
'name' => 'Název',
|
||||
'name_placeholder' => 'Název nové kategorie',
|
||||
'description' => 'Popis',
|
||||
'slug' => 'URL kategorie',
|
||||
'slug_placeholder' => 'zadejte-url-kategorie',
|
||||
'posts' => 'Počet příspěvků',
|
||||
'delete_confirm' => 'Opravdu chcete smazat tuto kategorii?',
|
||||
'delete_success' => 'Vybrané kategorie úspěšně odstraněny.',
|
||||
'return_to_categories' => 'Zpět na seznam blogových kategorií',
|
||||
'reorder' => 'Změnit pořadí',
|
||||
],
|
||||
'menuitem' => [
|
||||
'blog_category' => 'Blogová kategorie',
|
||||
'all_blog_categories' => 'Všechny blogové kategorie',
|
||||
'blog_post' => 'Blogový příspěvek',
|
||||
'all_blog_posts' => 'Všechny blogové příspěvky',
|
||||
'category_blog_posts' => 'Blog category posts'
|
||||
],
|
||||
'settings' => [
|
||||
'category_title' => 'Seznam kategorií',
|
||||
'category_description' => 'Zobrazí na stránce seznam blogových kategorií.',
|
||||
'category_slug' => 'URL kategorie',
|
||||
'category_slug_description' => "Najde blogovou kategorii s tímto URL. Používá se pro zobrazení aktivní kategorie.",
|
||||
'category_display_empty' => 'Zobrazit prázdné kategorie',
|
||||
'category_display_empty_description' => 'Zobrazit kategorie bez blogových příspěvků.',
|
||||
'category_page' => 'Stránka kategorií',
|
||||
'category_page_description' => 'Vyberte stránku která slouží k zobrazení všech kategorií (nebo detailu kategorie).',
|
||||
'post_title' => 'Příspěvek',
|
||||
'post_description' => 'Zobrazí blogový příspěvek na stránce.',
|
||||
'post_slug' => 'URL příspěvku',
|
||||
'post_slug_description' => "Najde příspěvek dle zadané URL.",
|
||||
'post_category' => 'Stránka kategorie',
|
||||
'post_category_description' => 'Vyberte stránku která slouží k zobrazení všech kategorií (nebo detailu kategorie).',
|
||||
'posts_title' => 'Seznam příspěvků',
|
||||
'posts_description' => 'Zobrazí na stránce seznam posledních příspěvků na stránkách.',
|
||||
'posts_pagination' => 'Číslo stránky',
|
||||
'posts_pagination_description' => 'Číslo stránky určující na které stránce se uživatel nachází. Použito pro stránkování.',
|
||||
'posts_filter' => 'Filtr kategorií',
|
||||
'posts_filter_description' => 'Zadejte URL kategorie, nebo URL parametr pro filtrování příspěvků. Nechte prázdné pro zobrazení všech příspěvků.',
|
||||
'posts_per_page' => 'Příspěvků na stránku',
|
||||
'posts_per_page_validation' => 'Špatný formát počtu příspěvků na stránku, musí být zadáno jako číslo',
|
||||
'posts_no_posts' => 'Hláška prázdné stránky',
|
||||
'posts_no_posts_description' => 'Zpráva se zobrazí pokud se nepovede najít žádné články.',
|
||||
'posts_no_posts_default' => 'Nenalezeny žádné příspěvky',
|
||||
'posts_order' => 'Řazení článků',
|
||||
'posts_order_decription' => 'Nastaví řazení článků ve výpisu',
|
||||
'posts_category' => 'Stránka kategorií',
|
||||
'posts_category_description' => 'Vyberte stránku která slouží k zobrazení všech kategorií (nebo detailu kategorie).',
|
||||
'posts_post' => 'Stránka příspěvků',
|
||||
'posts_post_description' => 'Vyberte stránku která slouží k zobrazení článků (nebo detailu článku).',
|
||||
'posts_except_post' => 'Vyloučit příspěvěk',
|
||||
'posts_except_post_description' => 'Zadejte ID nebo URL příspěvku který chcete vyloučit',
|
||||
'posts_except_categories' => 'Vyloučené kategorie',
|
||||
'posts_except_categories_description' => 'Pro vyloučení kategorií zadejte čárkou oddělené URL příspěvků nebo proměnnou, která tento seznam obsahuje.',
|
||||
'rssfeed_blog' => 'Blogová stránka',
|
||||
'rssfeed_blog_description' => 'Name of the main blog page file for generating links. This property is used by the default component partial.',
|
||||
'rssfeed_title' => 'RSS Kanál',
|
||||
'rssfeed_description' => 'Vygeneruje RSS kanál který obsahuje blogové příspěvky.',
|
||||
'group_links' => 'Odkazy',
|
||||
'group_exceptions' => 'Výjimky'
|
||||
],
|
||||
'sorting' => [
|
||||
'title_asc' => 'Název (sestupně)',
|
||||
'title_desc' => 'Název (vzestupně)',
|
||||
'created_asc' => 'Vytvořeno (sestupně)',
|
||||
'created_desc' => 'Vytvořeno (vzestupně)',
|
||||
'updated_asc' => 'Upraveno (sestupně)',
|
||||
'updated_desc' => 'Upraveno (vzestupně)',
|
||||
'published_asc' => 'Publikováno (sestupně)',
|
||||
'published_desc' => 'Publikováno (vzestupně)',
|
||||
'random' => 'Náhodně'
|
||||
],
|
||||
'import' => [
|
||||
'update_existing_label' => 'Uprav existující příspěvky',
|
||||
'update_existing_comment' => 'Zvolte pokud chcete upravit příspěvky se stejným ID, názvem nebo URL.',
|
||||
'auto_create_categories_label' => 'VYtvořit kategorie ze souboru',
|
||||
'auto_create_categories_comment' => 'Chcete-li tuto funkci použít, měli byste se shodovat se sloupcem Kategorie, jinak vyberte výchozí kategorie, které chcete použít z níže uvedených položek.',
|
||||
'categories_label' => 'Kategorie',
|
||||
'categories_comment' => 'Vyberte kategorie ke kterým budou příspěvky přiřazeny (volitelné).',
|
||||
'default_author_label' => 'Výchozí autor příspěvků (volitelné)',
|
||||
'default_author_comment' => 'Import se pokusí použít existujícího autora, pokud odpovídá sloupci email, jinak se použije výše uvedený autor.',
|
||||
'default_author_placeholder' => '-- vyberte autora --'
|
||||
]
|
||||
];
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'plugin' => [
|
||||
'name' => 'Blog',
|
||||
'description' => 'Eine robuste Blog Plattform.'
|
||||
],
|
||||
'blog' => [
|
||||
'menu_label' => 'Blog',
|
||||
'menu_description' => 'Blog Artikel bearbeiten',
|
||||
'posts' => 'Artikel',
|
||||
'create_post' => 'Blog Artikel',
|
||||
'categories' => 'Kategorien',
|
||||
'create_category' => 'Blog Kategorie',
|
||||
'tab' => 'Blog',
|
||||
'access_posts' => 'Blog Artikel verwalten',
|
||||
'access_categories' => 'Blog Kategorien verwalten',
|
||||
'access_other_posts' => 'Blog Artikel anderer Benutzer verwalten',
|
||||
'access_import_export' => 'Blog Artikel importieren oder exportieren',
|
||||
'access_publish' => 'Kann Artikel veröffentlichen',
|
||||
'delete_confirm' => 'Bist du sicher?',
|
||||
'chart_published' => 'Veröffentlicht',
|
||||
'chart_drafts' => 'Entwurf',
|
||||
'chart_total' => 'Gesamt'
|
||||
],
|
||||
'posts' => [
|
||||
'list_title' => 'Blog Artikel verwalten',
|
||||
'filter_category' => 'Kategorie',
|
||||
'filter_published' => 'Veröffentlichte ausblenden',
|
||||
'filter_date' => 'Date',
|
||||
'new_post' => 'Neuer Artikel',
|
||||
'export_post' => 'Exportiere Artikel',
|
||||
'import_post' => 'Importiere Artikel'
|
||||
],
|
||||
'post' => [
|
||||
'title' => 'Titel',
|
||||
'title_placeholder' => 'Neuer Titel',
|
||||
'content' => 'Inhalt',
|
||||
'content_html' => 'HTML-Inhalt',
|
||||
'slug' => 'Slug',
|
||||
'slug_placeholder' => 'neuer-artikel-slug',
|
||||
'categories' => 'Kategorien',
|
||||
'author_email' => 'Autor E-Mail',
|
||||
'created' => 'Erstellt',
|
||||
'created_date' => 'Erstellzeitpunkt',
|
||||
'updated' => 'Aktualisiert',
|
||||
'updated_date' => 'Aktualisierungszeitpunk',
|
||||
'published' => 'Veröffentlicht',
|
||||
'published_date' => 'Veröffentlichungszeitpunkt',
|
||||
'published_validation' => 'Bitte gebe das Datum der Veröffentlichung an',
|
||||
'tab_edit' => 'Bearbeiten',
|
||||
'tab_categories' => 'Kategorien',
|
||||
'categories_comment' => 'Wähle die zugehörigen Kategorien',
|
||||
'categories_placeholder' => 'Es existieren keine Kategorien. Bitte lege zuerst Kategorien an!',
|
||||
'tab_manage' => 'Verwalten',
|
||||
'published_on' => 'Veröffentlicht am',
|
||||
'excerpt' => 'Textauszug',
|
||||
'summary' => 'Zusammenfassung',
|
||||
'featured_images' => 'Zugehörige Bilder',
|
||||
'delete_confirm' => 'Möchtest du diesen Artikel wirklich löschen?',
|
||||
'close_confirm' => 'Der Artikel ist noch nicht gespeichert.',
|
||||
'return_to_posts' => 'Zurück zur Artikel-Übersicht',
|
||||
'posted_byline' => 'Veröffentlicht in :categories am :date',
|
||||
'posted_byline_no_categories' => 'Veröffentlicht am :date',
|
||||
'date_format' => 'd. F Y',
|
||||
],
|
||||
'categories' => [
|
||||
'list_title' => 'Blog Kategorien verwalten',
|
||||
'new_category' => 'Neue Kategorie',
|
||||
'uncategorized' => 'Allgemein'
|
||||
],
|
||||
'category' => [
|
||||
'name' => 'Name',
|
||||
'name_placeholder' => 'Neuer Kategorie Name',
|
||||
'description' => 'Beschreibung',
|
||||
'slug' => 'Slug',
|
||||
'slug_placeholder' => 'neuer-kategorie-slug',
|
||||
'posts' => 'Artikel',
|
||||
'delete_confirm' => 'Möchtest du die Kategorie wirklich löschen?',
|
||||
'return_to_categories' => 'Zurück zur Kategorie-Übersicht.',
|
||||
'reorder' => 'Kategorien sortieren'
|
||||
],
|
||||
'menuitem' => [
|
||||
'blog_category' => 'Blog Kategorie',
|
||||
'all_blog_categories' => 'Alle Blog Kategorien',
|
||||
'blog_post' => 'Blog Artikel',
|
||||
'all_blog_posts' => 'Alle Blog Artikel',
|
||||
'category_blog_posts' => 'Blog Kategorie Artikel'
|
||||
],
|
||||
'settings' => [
|
||||
'category_title' => 'Blog Kategorie-Übersicht',
|
||||
'category_description' => 'Zeigt eine Blog Kategorien-Übersicht.',
|
||||
'category_slug' => 'Slug Parametername',
|
||||
'category_slug_description' => 'Der URL-Routen-Parameter welcher verwendet wird um die aktuelle Kategorie zu bestimmen. Wird von der Standard-Komponente benötigt um die aktive Kategorie zu markieren.',
|
||||
'category_display_empty' => 'Leere Kategorien anzeigen',
|
||||
'category_display_empty_description' => 'Kategorien zeigen welche keine Artikel besitzen.',
|
||||
'category_page' => 'Kategorien Seite',
|
||||
'category_page_description' => 'Name der Kategorien-Seiten-Datei für die Kategorien Links. Wird von der Standard-Komponente benötigt.',
|
||||
'post_title' => 'Blog Artikel',
|
||||
'post_description' => 'Zeigt einen Blog Artikel auf der Seite.',
|
||||
'post_slug' => 'Slug Parametername',
|
||||
'post_slug_description' => 'Der URL-Routen-Parameter um den Post mittels "Slug" zu bestimmen.',
|
||||
'post_category' => 'Kategorien-Seite',
|
||||
'post_category_description' => 'Name der Kategorien-Seiten-Datei für Kategorie-Links.',
|
||||
'posts_title' => 'Blog Artikel-Übersicht',
|
||||
'posts_description' => 'Stellt eine Liste der neuesten Artikel auf der Seite dar.',
|
||||
'posts_pagination' => 'Blättern Parametername',
|
||||
'posts_pagination_description' => 'Der erwartete Parametername welcher für Seiten verwendet wird.',
|
||||
'posts_filter' => 'Kategorien-Filter',
|
||||
'posts_filter_description' => 'Bitte gebe ein Kategorien-Slug oder URL-Parameter an, mittels den die Artikel gefiltert werden. Wenn der Wert leer ist, werden alle Artikel angezeigt.',
|
||||
'posts_per_page' => 'Artikel pro Seite',
|
||||
'posts_per_page_validation' => 'Ungültiger "Artikel pro Seiten" Wert',
|
||||
'posts_no_posts' => 'Keine Artikel Nachricht',
|
||||
'posts_no_posts_description' => 'Nachricht welche dargestellt wird wenn keine Artikel vorhanden sind. Dieser Wert wird von der Standard-Komponente verwendet.',
|
||||
'posts_order' => 'Artikel Sortierung',
|
||||
'posts_order_description' => 'Attribute nach welchem Artikel sortiert werden.',
|
||||
'posts_category' => 'Kategorien-Seite',
|
||||
'posts_category_description' => 'Name der Kategorien-Seiten-Datei für "Veröffentlicht in" Kategorien-Links. Dieser Wert von der Standard-Komponente verwendet.',
|
||||
'posts_post' => 'Artikel Seite',
|
||||
'posts_post_description' => 'Name der Artikel-Seiten-Datei für die "Erfahre mehr" Links. Dieser Wert für von der Standard-Komponente verwendet.',
|
||||
'posts_except_post' => 'Artikel ausschließen',
|
||||
'posts_except_post_description' => 'Gebe direkt die ID/URL oder eine Variable mit der Artikel-ID/URL an um diesen Artikel auszuschließen. Dieser Wert für von der Standard-Komponente verwendet.',
|
||||
'posts_except_categories' => 'Kategorien ausschließen',
|
||||
'posts_except_categories_description' => 'Gebe eine kommagetrennte Liste von Kategorie-Slugs oder eine Variable mit einer solchen Liste an um deren Artikel auszuschließen. Die Dieser Wert für von der Standard-Komponente verwendet.',
|
||||
'rssfeed_blog' => 'Blog Seite',
|
||||
'rssfeed_blog_description' => 'Name der Artikel-Seiten-Datei für die Links. Dieser Wert für von der Standard-Komponente verwendet.',
|
||||
'rssfeed_title' => 'RSS-Feed',
|
||||
'rssfeed_description' => 'Erstellt einen RSS-Feed mit Artikeln aus dem Blog.',
|
||||
'group_links' => 'Links',
|
||||
'group_exceptions' => 'Ausnahmen'
|
||||
]
|
||||
];
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'plugin' => [
|
||||
'name' => 'Blog',
|
||||
'description' => 'A robust blogging platform.'
|
||||
],
|
||||
'blog' => [
|
||||
'menu_label' => 'Blog',
|
||||
'menu_description' => 'Manage Blog Posts',
|
||||
'posts' => 'Posts',
|
||||
'create_post' => 'Blog post',
|
||||
'categories' => 'Categories',
|
||||
'create_category' => 'Blog category',
|
||||
'tab' => 'Blog',
|
||||
'access_posts' => 'Manage the blog posts',
|
||||
'access_categories' => 'Manage the blog categories',
|
||||
'access_other_posts' => 'Manage other users blog posts',
|
||||
'access_import_export' => 'Allowed to import and export posts',
|
||||
'access_publish' => 'Allowed to publish posts',
|
||||
'manage_settings' => 'Manage blog settings',
|
||||
'delete_confirm' => 'Are you sure?',
|
||||
'chart_published' => 'Published',
|
||||
'chart_drafts' => 'Drafts',
|
||||
'chart_total' => 'Total',
|
||||
'settings_description' => 'Manage blog settings',
|
||||
'show_all_posts_label' => 'Show All Posts to Backend Users',
|
||||
'show_all_posts_comment' => 'Display both published and unpublished posts on the frontend to backend users',
|
||||
'use_legacy_editor_label' => 'Use the Legacy Markdown Editor',
|
||||
'use_legacy_editor_comment' => 'Enable the older version of the markdown editor when using October CMS v2 and above',
|
||||
'tab_general' => 'General',
|
||||
'preview' => 'Preview'
|
||||
],
|
||||
'posts' => [
|
||||
'list_title' => 'Manage the blog posts',
|
||||
'filter_category' => 'Category',
|
||||
'filter_published' => 'Published',
|
||||
'filter_date' => 'Date',
|
||||
'new_post' => 'New Post',
|
||||
'export_post' => 'Export Posts',
|
||||
'import_post' => 'Import Posts'
|
||||
],
|
||||
'post' => [
|
||||
'title' => 'Title',
|
||||
'title_placeholder' => 'New post title',
|
||||
'content' => 'Content',
|
||||
'content_html' => 'HTML Content',
|
||||
'slug' => 'Slug',
|
||||
'slug_placeholder' => 'new-post-slug',
|
||||
'categories' => 'Categories',
|
||||
'author_email' => 'Author Email',
|
||||
'created' => 'Created',
|
||||
'created_date' => 'Created date',
|
||||
'updated' => 'Updated',
|
||||
'updated_date' => 'Updated date',
|
||||
'published' => 'Published',
|
||||
'published_by' => 'Published by',
|
||||
'current_user' => 'Current user',
|
||||
'published_date' => 'Published date',
|
||||
'published_validation' => 'Please specify the published date',
|
||||
'tab_edit' => 'Edit',
|
||||
'tab_categories' => 'Categories',
|
||||
'categories_comment' => 'Select categories the blog post belongs to',
|
||||
'categories_placeholder' => 'There are no categories, you should create one first!',
|
||||
'tab_manage' => 'Manage',
|
||||
'published_on' => 'Published on',
|
||||
'excerpt' => 'Excerpt',
|
||||
'summary' => 'Summary',
|
||||
'featured_images' => 'Featured Images',
|
||||
'delete_confirm' => 'Delete this post?',
|
||||
'delete_success' => 'Successfully deleted those posts.',
|
||||
'close_confirm' => 'The post is not saved.',
|
||||
'return_to_posts' => 'Return to posts list',
|
||||
'posted_byline' => 'Posted in :categories on :date.',
|
||||
'posted_byline_no_categories' => 'Posted on :date.',
|
||||
'date_format' => 'M d, Y',
|
||||
],
|
||||
'categories' => [
|
||||
'list_title' => 'Manage the blog categories',
|
||||
'new_category' => 'New Category',
|
||||
'uncategorized' => 'Uncategorized'
|
||||
],
|
||||
'category' => [
|
||||
'name' => 'Name',
|
||||
'name_placeholder' => 'New category name',
|
||||
'description' => 'Description',
|
||||
'slug' => 'Slug',
|
||||
'slug_placeholder' => 'new-category-slug',
|
||||
'posts' => 'Posts',
|
||||
'delete_confirm' => 'Delete this category?',
|
||||
'delete_success' => 'Successfully deleted those categories.',
|
||||
'return_to_categories' => 'Return to the blog category list',
|
||||
'reorder' => 'Reorder Categories'
|
||||
],
|
||||
'menuitem' => [
|
||||
'blog_category' => 'Blog category',
|
||||
'all_blog_categories' => 'All blog categories',
|
||||
'blog_post' => 'Blog post',
|
||||
'all_blog_posts' => 'All blog posts',
|
||||
'category_blog_posts' => 'Blog category posts'
|
||||
],
|
||||
'settings' => [
|
||||
'category_title' => 'Category List',
|
||||
'category_description' => 'Displays a list of blog categories on the page.',
|
||||
'category_slug' => 'Category slug',
|
||||
'category_slug_description' => "Look up the blog category using the supplied slug value. This property is used by the default component partial for marking the currently active category.",
|
||||
'category_display_empty' => 'Display empty categories',
|
||||
'category_display_empty_description' => 'Show categories that do not have any posts.',
|
||||
'category_page' => 'Category page',
|
||||
'category_page_description' => 'Name of the category page file for the category links. This property is used by the default component partial.',
|
||||
'post_title' => 'Post',
|
||||
'post_description' => 'Displays a blog post on the page.',
|
||||
'post_slug' => 'Post slug',
|
||||
'post_slug_description' => "Look up the blog post using the supplied slug value.",
|
||||
'post_category' => 'Category page',
|
||||
'post_category_description' => 'Name of the category page file for the category links. This property is used by the default component partial.',
|
||||
'posts_title' => 'Post List',
|
||||
'posts_description' => 'Displays a list of latest blog posts on the page.',
|
||||
'posts_pagination' => 'Page number',
|
||||
'posts_pagination_description' => 'This value is used to determine what page the user is on.',
|
||||
'posts_filter' => 'Category filter',
|
||||
'posts_filter_description' => 'Enter a category slug or URL parameter to filter the posts by. Leave empty to show all posts.',
|
||||
'posts_per_page' => 'Posts per page',
|
||||
'posts_per_page_validation' => 'Invalid format of the posts per page value',
|
||||
'posts_no_posts' => 'No posts message',
|
||||
'posts_no_posts_description' => 'Message to display in the blog post list in case if there are no posts. This property is used by the default component partial.',
|
||||
'posts_no_posts_default' => 'No posts found',
|
||||
'posts_order' => 'Post order',
|
||||
'posts_order_description' => 'Attribute on which the posts should be ordered',
|
||||
'posts_category' => 'Category page',
|
||||
'posts_category_description' => 'Name of the category page file for the "Posted into" category links. This property is used by the default component partial.',
|
||||
'posts_post' => 'Post page',
|
||||
'posts_post_description' => 'Name of the blog post page file for the "Learn more" links. This property is used by the default component partial.',
|
||||
'posts_except_post' => 'Except post',
|
||||
'posts_except_post_description' => 'Enter ID/URL or variable with post ID/URL you want to exclude. You may use a comma-separated list to specify multiple posts.',
|
||||
'posts_except_post_validation' => 'Post exceptions must be a single slug or ID, or a comma-separated list of slugs and IDs',
|
||||
'posts_except_categories' => 'Except categories',
|
||||
'posts_except_categories_description' => 'Enter a comma-separated list of category slugs or variable with such a list of categories you want to exclude',
|
||||
'posts_except_categories_validation' => 'Category exceptions must be a single category slug, or a comma-separated list of slugs',
|
||||
'rssfeed_blog' => 'Blog page',
|
||||
'rssfeed_blog_description' => 'Name of the main blog page file for generating links. This property is used by the default component partial.',
|
||||
'rssfeed_title' => 'RSS Feed',
|
||||
'rssfeed_description' => 'Generates an RSS feed containing posts from the blog.',
|
||||
'group_links' => 'Links',
|
||||
'group_exceptions' => 'Exceptions'
|
||||
],
|
||||
'sorting' => [
|
||||
'title_asc' => 'Title (ascending)',
|
||||
'title_desc' => 'Title (descending)',
|
||||
'created_asc' => 'Created (ascending)',
|
||||
'created_desc' => 'Created (descending)',
|
||||
'updated_asc' => 'Updated (ascending)',
|
||||
'updated_desc' => 'Updated (descending)',
|
||||
'published_asc' => 'Published (ascending)',
|
||||
'published_desc' => 'Published (descending)',
|
||||
'random' => 'Random'
|
||||
],
|
||||
'import' => [
|
||||
'update_existing_label' => 'Update existing posts',
|
||||
'update_existing_comment' => 'Check this box to update posts that have exactly the same ID, title or slug.',
|
||||
'auto_create_categories_label' => 'Create categories specified in the import file',
|
||||
'auto_create_categories_comment' => 'You should match the Categories column to use this feature, otherwise select the default categories to use from the items below.',
|
||||
'categories_label' => 'Categories',
|
||||
'categories_comment' => 'Select the categories that imported posts will belong to (optional).',
|
||||
'default_author_label' => 'Default post author (optional)',
|
||||
'default_author_comment' => 'The import will try to use an existing author if you match the Author Email column, otherwise the author specified above is used.',
|
||||
'default_author_placeholder' => '-- select author --'
|
||||
]
|
||||
];
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'plugin' => [
|
||||
'name' => 'Blog',
|
||||
'description' => 'Una plataforma robusta de blogging.'
|
||||
],
|
||||
'blog' => [
|
||||
'menu_label' => 'Blog',
|
||||
'menu_description' => 'Administrar Publicaciones',
|
||||
'posts' => 'Publicaciones',
|
||||
'create_post' => 'Crear publicación',
|
||||
'categories' => 'Categorías',
|
||||
'create_category' => 'Categoría',
|
||||
'tab' => 'Blog',
|
||||
'access_posts' => 'Administrar las publicaciones',
|
||||
'access_categories' => 'Administrar las categorías',
|
||||
'access_other_posts' => 'Administrar publicaciones de otros usuarios',
|
||||
'access_import_export' => 'Autorizado para importar y exportar publicaciones',
|
||||
'access_publish' => 'Autorizado para publicar publicaciones',
|
||||
'manage_settings' => 'Administrar configuración del blog',
|
||||
'delete_confirm' => '¿Está seguro?',
|
||||
'chart_published' => 'Publicado',
|
||||
'chart_drafts' => 'Borradores',
|
||||
'chart_total' => 'Total',
|
||||
'settings_description' => 'Administrar configuración del blog',
|
||||
'show_all_posts_label' => 'Mostrar todas las publicaciones a los usuarios de backend',
|
||||
'show_all_posts_comment' => 'Mostrar las publicaciones publicados y los borradores a los usuarios de backend',
|
||||
'tab_general' => 'General'
|
||||
],
|
||||
'posts' => [
|
||||
'list_title' => 'Administrar publicaciones',
|
||||
'filter_category' => 'Categoría',
|
||||
'filter_published' => 'Publicado',
|
||||
'filter_date' => 'Fecha',
|
||||
'new_post' => 'Nueva publicación',
|
||||
'export_post' => 'Exportar publicaciones',
|
||||
'import_post' => 'Importar publicaciones'
|
||||
],
|
||||
'post' => [
|
||||
'title' => 'Título',
|
||||
'title_placeholder' => 'Título de la publicación',
|
||||
'content' => 'Contenido',
|
||||
'content_html' => 'Contenido HTML',
|
||||
'slug' => 'Identificador',
|
||||
'slug_placeholder' => 'nueva-publicacion',
|
||||
'categories' => 'Categorías',
|
||||
'author_email' => 'Email del Autor',
|
||||
'created' => 'Creado',
|
||||
'created_date' => 'Fecha de Creación',
|
||||
'updated' => 'Actualizado',
|
||||
'updated_date' => 'Fecha de Actualización',
|
||||
'published' => 'Publicado',
|
||||
'published_by' => 'Publicado por',
|
||||
'current_user' => 'Usuario actual',
|
||||
'published_date' => 'Fecha de publicación',
|
||||
'published_validation' => 'Por favor, especifique la fecha de publicación',
|
||||
'tab_edit' => 'Editar',
|
||||
'tab_categories' => 'Categorías',
|
||||
'categories_comment' => 'Seleccione las categorías para la publicación',
|
||||
'categories_placeholder' => 'No hay categorías, ¡crea una primero!',
|
||||
'tab_manage' => 'Administrar',
|
||||
'published_on' => 'Publicado el',
|
||||
'excerpt' => 'Resumen',
|
||||
'summary' => 'Resumen',
|
||||
'featured_images' => 'Imágenes Destacadas',
|
||||
'delete_confirm' => '¿Borrar la publicación?',
|
||||
'delete_success' => 'Publicación borrada correctamente',
|
||||
'close_confirm' => 'La publicación no está guardada.',
|
||||
'return_to_posts' => 'Volver a la lista de publicaciones',
|
||||
'posted_byline' => 'Publicado en :categories el :date.',
|
||||
'posted_byline_no_categories' => 'Publicado el :date.',
|
||||
'date_format' => 'd de M de Y',
|
||||
],
|
||||
'categories' => [
|
||||
'list_title' => 'Administrar las categorías',
|
||||
'new_category' => 'Nueva categoría',
|
||||
'uncategorized' => 'Sin Categoría'
|
||||
],
|
||||
'category' => [
|
||||
'name' => 'Nombre',
|
||||
'name_placeholder' => 'Nombre de la categoría',
|
||||
'description' => 'Descripción',
|
||||
'slug' => 'Identificador',
|
||||
'slug_placeholder' => 'nueva-categoría',
|
||||
'posts' => 'Publicaciones',
|
||||
'delete_confirm' => '¿Borrar esta categoría?',
|
||||
'delete_success' => 'Categorías borradas correctamente.',
|
||||
'return_to_categories' => 'Volver a la lista de categorías',
|
||||
'reorder' => 'Re-ordenar Categorías'
|
||||
],
|
||||
'menuitem' => [
|
||||
'blog_category' => 'Categoría del blog',
|
||||
'all_blog_categories' => 'Todas las categorías del blog',
|
||||
'blog_post' => 'Publicación del blog',
|
||||
'all_blog_posts' => 'Todas las publicaciones del blog',
|
||||
'category_blog_posts' => 'Publicaciones del blog por categorías'
|
||||
],
|
||||
'settings' => [
|
||||
'category_title' => 'Lista de Categorías',
|
||||
'category_description' => 'Muestra en la página una lista de las categorías.',
|
||||
'category_slug' => 'Identificador de la categoría',
|
||||
'category_slug_description' => "Localiza una categoría utilizando el identificador proporcionado. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente para marcar la categoría activa.",
|
||||
'category_display_empty' => 'Mostrar categorías vacías',
|
||||
'category_display_empty_description' => 'Mostrar categorías que no tienen ninguna publicación.',
|
||||
'category_page' => 'Página de categorías',
|
||||
'category_page_description' => 'Nombre del archivo de página utilizado para los enlaces de categorías. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
|
||||
'post_title' => 'Publicación',
|
||||
'post_description' => 'Muestra una publicación en la página.',
|
||||
'post_slug' => 'Identificador de la publicación',
|
||||
'post_slug_description' => "Se buscará la publicación utilizando el valor del identificador proporcionado.",
|
||||
'post_category' => 'Página de categoría',
|
||||
'post_category_description' => 'Nombre del archivo de página utilizado para los enlaces de categorías. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
|
||||
'posts_title' => 'Lista de publicaciones',
|
||||
'posts_description' => 'Muestra una lista de las últimas publicaciones en la página.',
|
||||
'posts_pagination' => 'Número de página',
|
||||
'posts_pagination_description' => 'Este valor se utiliza para determinar en que página se encuentra el usuario.',
|
||||
'posts_filter' => 'Filtro de categoría',
|
||||
'posts_filter_description' => 'Ingrese un identificador de categoría o parámetro URL. Se utilizará para filtrar las publicaciones. Deje el campo vacío para mostrar todas las publicaciones.',
|
||||
'posts_per_page' => 'Publicaciones por página',
|
||||
'posts_per_page_validation' => 'Formato inválido para el valor de publicaciones por página',
|
||||
'posts_no_posts' => 'Mensaje cuando no hay publicaciones',
|
||||
'posts_no_posts_description' => 'Mensaje que se mostrará en la lista de publicaciones del blog cuando no haya ningúno. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
|
||||
'posts_no_posts_default' => 'No se encontraron publicaciones.',
|
||||
'posts_order' => 'Ordenar publicaciones por',
|
||||
'posts_order_description' => 'Atributo mediante el cual se deberán ordenar las publicaciones',
|
||||
'posts_category' => 'Página de Categoría',
|
||||
'posts_category_description' => 'Nombre del archivo de página utilizado para los enlaces de categoría "Publicado en". Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
|
||||
'posts_post' => 'Página de las publicaciones',
|
||||
'posts_post_description' => 'Nombre del archivo de página utilizado para los enlaces "Saber más". Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
|
||||
'posts_except_post' => 'Exceptuar publicación',
|
||||
'posts_except_post_description' => 'Ingrese una ID/URL o variable que contenga una ID/URL de la publicación que se quiera excluir',
|
||||
'posts_except_post_validation' => 'La publicación a excluir debe ser una ID/URL, o una lista separada por comas de IDs/URLs',
|
||||
'posts_except_categories' => 'Excluir categorías',
|
||||
'posts_except_categories_description' => 'Introduce una lista separada por comas de IDs/URLs de categorías con las categorías a excluir.',
|
||||
'posts_except_categories_validation' => 'Las categorías excluidas deben ser una URL de categoría o una lista separada por comas',
|
||||
'rssfeed_blog' => 'Página del blog',
|
||||
'rssfeed_blog_description' => 'Nombre del archivo de página principal para generación de enlaces. Esta propiedad es utilizada dentro del parcial que viene por defecto en el componente.',
|
||||
'rssfeed_title' => 'RSS Feed',
|
||||
'rssfeed_description' => 'Genera un feed de RSS con las publicaciones del blog.',
|
||||
'group_links' => 'Enlaces',
|
||||
'group_exceptions' => 'Excepciones'
|
||||
],
|
||||
'sorting' => [
|
||||
'title_asc' => 'Título (ascendiente)',
|
||||
'title_desc' => 'Título (descendiente)',
|
||||
'created_asc' => 'Creado (ascendiente)',
|
||||
'created_desc' => 'Creado (descendiente)',
|
||||
'updated_asc' => 'Editado (ascendiente)',
|
||||
'updated_desc' => 'Editado (descendiente)',
|
||||
'published_asc' => 'Publicado (ascendiente)',
|
||||
'published_desc' => 'Publicado (descendiente)',
|
||||
'random' => 'Aleatorio'
|
||||
],
|
||||
'import' => [
|
||||
'update_existing_label' => 'Editar publicaciones existentes',
|
||||
'update_existing_comment' => 'Selecciona este check para actualizar las publicaciones con exactamente la misma ID, título o URL.',
|
||||
'auto_create_categories_label' => 'Crear categorías especificadas en el archivo a importar',
|
||||
'auto_create_categories_comment' => 'Debes hacer coincidir la columna Categoría para usar esta funcionalidad, sino selecciona la categoría por defecto para para usar para los elementos de abajo.',
|
||||
'categories_label' => 'Categorías',
|
||||
'categories_comment' => 'Selecciona las categorías a las que pertenecerán las publicaciones importadas (opcional).',
|
||||
'default_author_label' => 'Autor de publicación por defecto (opcional)',
|
||||
'default_author_comment' => 'La importación intentará usar un autor existente si coicide con la columna "Author Email", sino se usará el autor especificado arriba.',
|
||||
'default_author_placeholder' => '-- Selecciona Autor/a --'
|
||||
]
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue