[ckeditor4] filetools-respone-error

사용한 버전 ckeditor4.13.1

이미지를 업로드하면 파라미터값(CKEditorFuncNum 등)이 넘어오지 않고 filetools-respone-error 에러를 발생시킨다.

config.filebrowserUploadMethod: ‘form’ 옵션을 지정하면 된다.

<form action="upload.php">
    <textarea name="editor1" id="editor1" rows="10" cols="80">
            This is my textarea to be replaced with CKEditor.
    </textarea>
    <script>
        CKEDITOR.replace( 'editor1' , {
            filebrowserUploadUrl: 'upload.php',
            filebrowserUploadMethod: 'form'
        });
    </script>
</form>
#upload.php
<?php
if(isset($_FILES['upload']['name'])){
    $file = $_FILES['upload']['tmp_name'];
    $file_name = $_FILES['upload']['name'];
    $file_name_array = explode('.', $file_name);
    $extension = end($file_name_array);
    $new_image_name = rand().'.'.$extension;
    $allowed_extension = array('jpg', 'png', 'gif');
    if(in_array($extension, $allowed_extension)){
        $result = move_uploaded_file($file, 'upload/'.$new_image_name);
        $funcNum = $_GET['CKEditorFuncNum'];
        $url = 'upload/'.$new_image_name;
        $message = 'hello';
        echo "<script type='text/javascript'>window.parent.CKEDITOR.tools.callFunction($funcNum,'$url','$message');</script>";
    }
}
?>

https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-filebrowserUploadMethod

계층형 게시판 만들기

posts 테이블 구조

id아이디
subject제목
content내용
ori_id원글의 아이디값
grp_ord그룹(원글)내에서 순서
depth게시글의 깊이
  • 원글 : 최상위 글로 depth가 0이다.
  • 부모글 : 원글을 제외하고 자식을 갖는 글(depth 1이상이면서 자식을 갖는글), 첫번째 댓글(depth가 1)인 경우 부모글은 원글이 된다.
  • LAST_INSERT_ID() : 마지막으로 인서트된 id값
  • 최근에 작성한 글이 먼저 보이게 정렬한다.

게시글등록

  • 게시글을 인서트할때 반환되는 id값을 ori_id에 대입해준다.
  • grp_ord, depth는 원글이기 때문에 0을 준다.
INSERT INTO posts SET subject = '첫번째 글',
                     content = '',
                     grp_ord = 0,
                     depth = 0;
                     
UPDATE posts SET ori_id = (select last_insert_id()) WHERE id = (select last_insert_id());

댓글등록

UPDATE posts SET grp_ord = grp_order+1 WHERE ori_id = '부모글 ori_id' AND grp_ord > '부모글의 grp_ord';

INSERT INTO posts SET subject = '첫번째 글의 댓글',
                     content = '',
                     ori_id = '부모글의 ori_id',
                     grp_ord = '부모글의 grp_ord' + 1,
                     depth = '무보글의 depth' + 1;
  • 최근에 등록한 댓글이 상단에 위치하기 위해서 부모글의 grp_ord보다 큰 값을 찾아 1씩 증가시켜 순서를 뒤로 밀어준다.
  • 업데이트 문으로 숫자가 1씩 밀렸기 때문에 인서트할 때 grp_ord + 1를 줘서 댓글 순서를 처음으로 오게한다.

리스트쿼리

SELECT * FROM posts ORDER BY ori_id DESC, grp_ord

참고한 사이트

https://adgw.tistory.com/entry/계층형-게시판-알고리즘-댓글-알고리즘

[laravel] 나만보는 라라벨 튜토리얼

라라벨코리아, 라라캐스트를 보고 간단한 프로젝트를 만들면서 정리하는 글

  1. 프로젝트생성
  2. composer 의존성 설치
  3. 프론트앤드 스케폴딩
  4. npm 의존성 설치
  5. laravel-mix 사용하기
  6. model, controller, migration 생성하기
  7. restful한 route 만들기
  8. view 생성하기
  9. 기능구현하기
    • 글쓰기 폼작성
    • 저장하기
    • 글보기
    • 글 수정하기 폼작성
    • 글 업데이트하기
    • 글목록 페이지
    • 삭제하기
  10. 모델을 라우트에 바인딩 시키자
  11. 중복 제거

1.프로젝트 생성

laravel new blog
cd ./blog

2.composer 의존성 설치

composer install

3.프론트앤드 스케폴딩
필요한 부분만 선택하여 설치하면 된다. (본 예제에서는 bootstrap만 스캐폴딩함)

composer require laravel/ui --dev
// Generate basic scaffolding...
php artisan ui bootstrap
php artisan ui vue
php artisan ui react
// Generate login / registration scaffolding...
php artisan ui bootstrap --auth
php artisan ui vue --auth
php artisan ui react --auth

4.npm 의존성 설치
프론트 앤드 스캐폴딩을 하게 되면 package.json에 의존성 목록의 추가된다. 프로젝트에 사용하기 위해서 의존성을 설치해준다.

npm install

5.laravel-mix 사용하기
루트디렉토리의 webpack.mix.js 파일을 열어보면 아래와 같이 기본설정이 되어있다.

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css');

특별히 추가 할게 없다면 npm run dev 명령어를 실행해서 컴파일 하자. 앞에서 부트스트랩을 스캐폴딩했기 때문에 컴파일 하면 부트스트랩도 같이 컴파일 되어 public/js/app.js, public/css/app.css 로 위치하게 된다. (laravel-mix 문서 보기)

추가로 browsersyc를 webpack.mix.js에 추가해서 브라우저 변경시 새로고침없이 자동으로 리로드되게 해주면 개발할때 편리하다.

//mix.browserSync('mydomain.com');
mix.browserSync('localhost:8000');

6.모델, 컨트롤러, 마이그레이션 생성하기
-r 옵션은 리소스컨트롤러로 컨트롤러에 기본적인 메서드가 자동으로 추가된다.

* 모델과 컨트롤러는 단수로, 마이그레이션은 복수로 표현한다.
모델          Article
컨트롤러      ArticleController
마이그레이션  create_articles_table
php artisan make:model Article
php artisan make:controller ArticleController
php artisan make:migration create_aticles_table
// 한번에 생성하기 -c: controller -r:resource controller -m:migration
php artisan make:model Article -c -m
또는
php artisan make:model Article -r -m

생성된 마이그레이션 파일을 열어 테이블 스키마를 지정해준다.

    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->text('title');
            $table->longText('content');
            $table->timestamps();
            $table->boolean('is_published')->nullable()->default(false);
        });
    }
    public function down()
    {
        Schema::dropIfExists('articles');
    }
//up 메서드는 migrate 명령어가 실행될 때 호출되고 down 메서드는 migrate:rollback 될 때 호출된다.

php artisan migrate 명령어를 실행해서 테이블을 생성하면된다. 이때 한번도 마이그레이션하지 않았으면 laravel에서 제공하는 테이블까지 함께 생성된다.

7. restful한 route 만들기

//인덱스 리스트
route::get('/article/', 'ArticleController@index')->name('article.index');
//글쓰기 폼
route::get('/article/create', 'ArticleController@create')->name('article.create');
//새로운글 저장
route::post('/article', 'ArticleController@store')->name('article.store');
//해당글 보기 
route::get('/article/{id}', 'ArticleController@show')->name('article.show');
//해당글 수정 폼
route::get('/article/{id}/edit', 'ArticleController@edit')->name('article.edit');
//해당글 업데이트
route::put('/article/{id}', 'ArticleController@update')->name('article.update');
//해당글 삭제
route::delete('/article/{id}/destroy', 'ArticleController@destroy')->name('article.destroy');

route파사드의 name메서드를 사용하는 편이 나중에 url 관리하기가 편하다.

8.view 생성하기
/resources/views 디렉토리에 이번프로젝트에 뼈대가 될 layout.blade.php 파일을 생성한다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="{{ asset('css/app.css')}}">
    <script src="{{ asset('js/app.js') }}"></script>
    <title>Document</title>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container">
            <a class="navbar-brand" href="{{ route('article.index') }}">Navbar</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav">
                <li class="nav-item {{Request::is('article*')? 'active' : ''}}">
                    <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
                </li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="container">
        @yield('content')
    </div>
    <div class="footer container mt-3 pt-3 text-center">
        <div class="text-muted">@laravel</div>
    </div>
</body>
</html>

/resources/views/article 디렉토리를 생성하고 앞에서 지정한 라우트에 필요한 뷰파일을 생성해준다.

//index.blade.php
@extends('layout')
@section('content')
리스트페이지
@endsection
//create.blade.php
@extends('layout')
@section('content')
글쓰기 폼
@endsection
//show.blade.php
@extends('layout')
@section('content')
글보기
@endsection
//edit.blade.php
@extends('layout')
@section('content')
글쓰기 수정 폼
@endsection
뷰파일은 /resources/views 내에 어디서든 위치해도 상관없는데 일관성을 위해서 article 디렉토리를 추가했다. 

9.기능구현하기

1)글쓰기 폼 (create.blade.php) 작성

  • ArticleController@create
    public function create()
    {
        return view('article.create');
    }
  • create.blade.php
@extends('layout')
@section('content')
    <form action="{{ route('article.store') }}" method="post">
        @csrf
        <div class="form-group">
          <label for="title">제목</label>
          <input 
          type="text"
          name="title"
          id="title"
          class="form-control @error('title') is-invalid @enderror"
          placeholder=""
          aria-describedby="title"
          value="{{ old('title') }}">
          @error('title')
          <p class="invalid-feedback">제목을 입력하세요</p>
          @enderror
        </div>
        <div class="form-group">
          <label for="content">내용</label>
          <textarea class="form-control @error('content') is-invalid  @enderror" name="content" id="content" rows="3">{{ old('content') }}</textarea>
          @error('content')
          <p class="invalid-feedback">내용을 입력하세요</p>
          @enderror
        </div>
        <button type="submit" class="btn btn-primary">저장</button>
    </form>
@endsection

form안에 @csrf 를 사용해서 csrf 토큰을 꼭 생성해야 한다.
validation처리를 위해서 @error, 글로벌 헬퍼함수 old() 부분을 잘 확인하자.
old()는 넘어온값을 세션에 저장해 두는데 validation에 통과하지 못했을 때 양식에 이전에 입력한 값을 다시 넣어주기 위해 사용한다.

2)저장하기 ArticleController@store

use App\Article; 
public function store()
    {
        request()->validate([
            'title' => 'required',
            'content' => 'required'
        ]);
        $article = new Article();
        $article->title = request('title');
        $article->content= request('content');
        $article->save();
        return redirect()->route('article.index');
    }

3)글보기

  • ArticleController@show 작성
    public function show($id)
    {
        $article = Article::find($id);
        return view('article.show', compact('article'));
    }
  • show.blade.php 작성
@extends('layout')
@section('content')
<h1>{{ $article->title }}</h1>
<small class="small text-muted">{{ $article->updated_at }}</small>
<div class="mt-3 pt-3 border-top">{{ $article->content }}</div>
@endsection

4)글 수정하기폼 ArticleController@edit

  • ArticleController@edit
   public function edit($id)
    {   
       $article = Article::find($id); 
        return view('article.edit', compact('article'));
    }
  • edit.blade.php
@extends('layout')
@section('content')
    <form action="{{ route('article.update', $article->id) }}" method="POST">
        @csrf
        @method('PUT')
        <div class="form-group">
          <label for="title">제목</label>
          <input 
          type="text"
          name="title"
          id="title"
          class="form-control @error('title') is-invalid @enderror"
          placeholder=""
          aria-describedby="title"
          value="{{ $erros->has('title') ? old('title') : $article->title }}">
          @error('title')
          <p class="invalid-feedback">제목을 입력하세요</p>
          @enderror
        </div>
        <div class="form-group">
          <label for="content">내용</label>
          <textarea 
          class="form-control @error('content') is-invalid  @enderror" 
          name="content" 
          id="content" 
          rows="3">{{ $erros->has('content') ? old('content') : $article->content}}</textarea>
          @error('content')
          <p class="invalid-feedback">내용을 입력하세요</p>
          @enderror
        </div>
        <button type="submit" class="btn btn-primary">저장</button>
    </form>
    <form action="{{ route('article.destroy', $article->id) }}" method="POST">
      @csrf
      @method('DELETE')
      <button type="submit" class="btn btn-danger">삭제</button>
    </form>
@endsection

@method 지시자를 이용해서 PUT메서드를 스푸핑하는것을 확인하자.

5)글 업데이트 ArticleController@update

public function update($id)
    {
        request()->validate([
            'title' => 'required',
            'content' => 'required'
        ]);
        $article = Article::find($id);
        $article->title = request('title');
        $article->content= request('content');
        $article->save();
        return redirect()->route('article.show', $article->id);
    }

5)글목록 페이지

  • ArticleController@index
public function index(){
        $articles = Article::orderBy('created_at', 'desc')->paginate(15);
        return view('article.index', compact('articles'));
    }
  • inedx.blade.php
@extends('layout')
@section('content')
@foreach($articles as $article)
<div class="card mb-3">
  <div class="row no-gutters">
    <div class="col-md-3">
      <img src="..." class="card-img" alt="">
    </div>
    <div class="col-md-9">
      <div class="card-body">
        <h5 class="card-title">
          <a href="{{ route('article.show', $article->id) }}">{{ $article->title }}</a>
        </h5>
        <p class="card-text">{{ $article->content }}</p>
        <p class="card-text"><small class="text-muted">{{ $article->updated_at }}</small></p>
      </div>
    </div>
  </div>
</div>
@endforeach
@endsection

6)해당글 삭제하기 ArticleController@destroy

    public function destroy($id)
    {
        $article = Article::find($id);
        $article->delete();
        return redirect()->route('article.index');
    }

10. 모델을 라우트에 바인딩 시키자

라우트의 {id} 부분을 {article}로 변경하고 컨트롤러 메서드에 $id로 전달한 파라미터를 Article $article 타입 힌트로 지정한다.

만약 route의 {article}을 {foo}로 지정했다면 컨트롤러의 타입힌트도 Article $foo로 변경해야 한다.

//web.php
route::get('/article/', 'ArticleController@index')->name('article.index');
route::get('/article/create', 'ArticleController@create')->name('article.create');
route::post('/article', 'ArticleController@store')->name('article.store');
route::get('/article/{article}', 'ArticleController@show')->name('article.show');
route::get('/article/{article}/edit', 'ArticleController@edit')->name('article.edit');
route::put('/article/{article}', 'ArticleController@update')->name('article.update');
route::delete('/article/{article}/destroy', 'ArticleController@destroy')->name('article.destroy');
//ArticleController.php
    public function index(){
        $articles = Article::orderBy('created_at', 'desc')->paginate(15);
        return view('article.index', compact('articles'));
    }
    public function create()
    {
        return view('article.create');
    }
    public function store()
    {
        request()->validate([
            'title' => 'required',
            'content' => 'required'
        ]);
        $article = new Article();
        $article->title = request('title');
        $article->content= request('content');
        $article->save();
        return redirect()->route('article.index');
    }
    public function show(Article $article)
    {
        $article = Article::find(Article $article);
        return view('article.show', compact('article'));
    }
    public function edit(Article $article)
    {   
       $article = Article::find(Article $article); 
        return view('article.edit', compact('article'));
    }
    public function update(Article $article)
    {
       request()->validate([
            'title' => 'required',
            'content' => 'required'
        ]);
        $article = Article::find(Article $article);
        $article->title = request('title');
        $article->content= request('content');
        $article->save();
        return redirect()->route('article.show', $article->id);
    }
    public function destroy(Article $article)
    {
        $article = Article::find(Article $article);
        $article->delete();
        return redirect()->route('article.index');
    }
라우트에 모델을 바인딩하면 라라벨은 자동으로 테이블의 id칼럼을 조회하게 된다. id가 아닌 다른 칼럼을 조회하게 하려면 model에 getRouteKeyName메서드를 오버라이드 해준다.
class Article extends Model
{
  function getRouteKeyName(){
    return 'title';
  }
}

11. 중복제거

validate와 게시글 저장, 업데이트 하는부분이 store와 update에서 중복이 발생했다. validation 부분을 메서드로 작성하여 중복을 제거하자.

public function index(){
        $articles = Article::orderBy('created_at', 'desc')->paginate(15);
        return view('article.index', compact('articles'));
    }
    public function create()
    {
        return view('article.create');
    }
    public function store()
    {
        Article::create($this->validateArticle());
        return redirect()->route('article.index');
    }
    public function show(Article $article)
    {
        return view('article.show', compact('article'));
    }
    public function edit(Article $article)
    {   
        return view('article.edit', compact('article'));
    }
    public function update(Article $article)
    {
        $article->update($this->validateArticle());
        return redirect()->route('article.show', $article->id);
    }
    public function destroy(Article $article)
    {
        $article->delete();
        return redirect()->route('article.index');
    }
    protected function validateArticle(){
        return request()->validate([
            'title' => 'required',
            'content' => 'required'
        ]);
    }

중복을 제거 했지만 글을 작성하거나 업데이트 하면 아래와 같이 에러가 발생할 것이다.

Add [title] to fillable property to allow mass assignment on [App\Article]. 

기본적으로 모든 Eloquent 모델은 대량 할당-Mass Assignment 으로부터 보호되기 때문에, 모델의 ‘fillable’나 ‘guarded’속성을 지정해야 주어야 한다.
fillable은 화이트리스트방식이고, guarded는 블랙리스트 방식이라고 생각하면 된다.

class Article extends Model
{
  protected $guarded = [];
  또는
  protected $fillable = ['title', 'content'];
}

[laravel/storage] 업로드된 파일 저장하기

파일스토리지 설정하기

  • 파일 시스템 설정파일위치 config/filesystem.php
  • 설정파일에서 이미지 저장 path, url등을 수정할 수 있다.

Public 디스크

  • public 디스크는 local드라이버를 사용하고 storage/app/public에 저장함.
  • 웹에서 파일을 서빙하려면 public/storage를 storage/app/public으로 심볼릭 링크를 생성해야한다. 아티즌명령으로 심볼릭링크를 생성할 수 있다.
php artisan storage:link

명령을 실행하고 public/storage를 확인하면 심볼링 링크가 생성된걸 확인 할 수 있다. 이제 파일을 업로드하고 처리하는 코드를 작성해보자.

#write.blade.php

<form enctype="multipart/form-data">
    <input type="file" name="file">
</form>
#controller

$request->file('file')->store('images', 'public');

store메소드의 첫번째 인자는 파일이 저장될 폴더명이다. 예제에서는 ‘images’로 지정했기 때문에 /storage/app/public에 images 폴더를 생성한 뒤 파일을 저장한다.
파일명은 자동으로 랜덤한 값이 들어가고 경로와함께 반환된다. 예) images/임의이름.jpg
두번째 인자는 저장에 사용할 디스크를 지정한다.

#view.blade.php // 파일 서빙

{{asset('storage/file.jpg')}}

asset(‘storage/file.jpg’)을 통해서 반환되는 이미지 경로는 mysite.com/storage/images/file.jpg 가 된다.

실제로사용하기

  • 글을저장하는 post테이블, 파일을 저장하는 file테이블이 있다.
  • post모델에 1:다 관계(hasMany)를 지정한다.
  • file모델에는 1:1 관계( belongsTo)를 지정한다.

뷰에서 값을 확인하면 객체를 포함한 배열을 볼수 있다. 아래와 같이 배열의 길이를 확인하고 처리하면된다.

@if(count($post->file))
    {{$post->file[0]->ori_fname}}
@endif

참고

파일스토리지
업로드된 파일 저장하기

[그누보드5]관리자 권한관리 기능추가

문제인식

  • 관리권한설정은 권한을 하나씩 추가해야함
  • 아이디에 부여된 권한을 수정하려면 개별로 수정해야하는 불편함이 있음
  • 여러계정에서 동일한 수준의 권한을 부여하거나 수정하는데 불편함이 있음

해결방안

  • 권한그룹을 생성하고 아이디에 권한그룹을 부여
  • 권한그룹을 수정하면 권한이 설정된 계정들은 별도의 수정없이 변경된 권한적용

그누보드 인증기능을 그대로 유지하면서 권한그룹부분을 추가하였기 때문에 권한을 부여하였더라도 소스코드에 $is_admin == ‘super’ 로 인증하는 부분은 접근이 불가능합니다.

권한그룹관리 페이지
회원관리페이지(권한그룹 지정)

소스는 이전에 작성한 ‘[그누보드5] 관리자에 게시판관리 기능추가’에서 추가 하였습니다.

https://blog.ujsstudio.com/?p=214

설치방법
https://github.com/jisung87kr/gnuboard-yjs
위 주소에서 파일 클론하거나 다운로드받아 그누보드 설치하듯이 사용하면 됩니다.

* 권한부분은 민감하기 때문에 사용하시는 분들은 문제가 없는지 확인하시고 사용하시기 바랍니다. 해당 파일 사용으로 인한 어떤 문제도 책임지지 않습니다.

[php]간단한 달력만들기

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>달력</title>
</head>
<body>
    <?php
    if(empty($_GET['date'])){ // 입력값이 없으면 오늘을 기준으로 한다
        $thisDay = date("Y-m-d");    
    } else {
        if(isset($_GET['btn'])){
            if($_GET['btn'] == 'prev'){ // 이전달 구하기
                $thisDay = date("Y-m-d", strtotime($_GET['date']." -1 month"));
            } elseif($_GET['btn'] == 'next'){ // 다음달 구하기
                $thisDay = date("Y-m-d", strtotime($_GET['date']." +1 month"));
            }
        } else {
            $thisDay = $_GET['date']; //입력한 날짜 가져오기
        }
    }
    
    $thisDayArry = explode('-', $thisDay);
    $thisY = $thisDayArry[0];
    $thisM = $thisDayArry[1];
    $thisD = $thisDayArry[2];

    $maxDay = date("t", strtotime($thisDay)); // 총일수
    $startWeek = date("w", strtotime($thisY."-".$thisM."-01")); // 1일은 무슨 요일인가
    $maxWeek = ceil(($maxDay+$startWeek)/7); // 총주수
    $endWeek = date("w", strtotime($thisY."-".$thisM."-".$maxDay));// 마지막일은 무슨 요일인가

    ?>
    <form action="./cal.php" method="GET">
        <a href="./cal.php?date=<?php echo $thisDay?>&btn=prev">prev</a>
        <input type="text" name="date" value="<?php echo $thisDay?>">
        <a href="./cal.php?date=<?php echo $thisDay?>&btn=next">next</a>
    </form>

    <table>
        <thead>
            <tr>
                <th>일</th>
                <th>월</th>
                <th>화</th>
                <th>수</th>
                <th>목</th>
                <th>금</th>
                <th>토</th>
            </tr>
        </thead>
        <tbody>
            <?php $day=1; ?>
            <?php for($i=1; $i<=$maxWeek; $i++){?>
                <tr>
                <?php for($j=0; $j<7; $j++){ ?>
                    <td>
                        <?php 
                        if(($i==1 && $j < $startWeek) || ($i==$maxWeek && $j> $endWeek) ){ // 첫째 주이고 j가 1일의 요일보다 작은 경우 패스 || 마지막 주 이고 j가 마지막일의 요일보다 크면 패스
                            echo '';
                        } else {
                            if($day == $thisD){ echo '<b>'; }
                            echo $day;
                            if($day == $thisD){ echo '</b>'; }
                            $day++;
                        }
                        ?>
                    </td>
                <?php } ?>
                </tr>
            <?php } ?>
        </tbody>
    </table>
</body>
</html>

참고사이트:
https://phpheaven.tistory.com/104

[그누보드5] 관리자에 게시판관리 기능 추가

그누보드관리자페이지에서 게시판명을 클릭하면 사용자 화면으로 링크가 이동되는데 이걸 관리자페이지에서 관리 할 수 있으면 편할거 같아 공유할 목적으로 만들어진 소스입니다.

사용방법:

링크를 참조하여 제작하였습니다.

https://sir.kr/g5_tip/2692