Extra Form
PHP PHP 7.4
CMS Rhymix 2.1

에디터 안에 이미지 파일을 drop하면 바로 파일 업로드를 하도록 하고 있습니다.

ajax를 통해 외부 php 파일을 연동했고 거기서 파일 컨트롤러의 procFileUpload를 활용해서 파일 업로드까지는 성공했습니다.

그리고 첨부 파일 목록도 새로고침해서 바로 디스플레이 되도록 했구요.

 

근데 이걸 본문 내에 바로 삽입을 하려면 파일의 url을 얻어내야 하겠더라구요.

procFileUpload 함수를 보면 파일 insert 후에 json으로 응답해주는 것 같은데요.

https://github.com/rhymix/rhymix/blob/10ab65e2f4181a28a40f6035469a40b0008b10ca/modules/file/file.controller.php#L147

이걸 어떻게 잡아내야 하는지 잘 모르겠습니다.

 

외부 php 파일에서 procFileUpload의 json 결과 데이터를 받아서 ajax 통신을 보냈던 js파일로 되돌려주고 싶습니다.

구글링을 해보면 Context::setRequestMethod('JSON');을 하라고 하는데, 어떻게 적용하거나 활용해야 하는지 모르겠어요ㅜ

외부 php 파일에서 json을 받을 수 있는 방법이 무엇인지 궁금합니다.

  • profile

    ajax으로 임베드 요청할때 사용한 것처럼 하면 될걸요.. 대신 파일 업로드를 다시 구현해야겟지요( 에디터스킨 새로 만들어야할듯)

    리이믹스 순정 에디터에서 업로드 하는게 아니라 별도의 방식으로 구현해서 해당 리턴값을 조회 해보세요. 

  • profile profile

    음, 어렵고 복잡하네요ㅜㅜ (스킨을 새로 만들어야 하다뇨ㅠㅠ)
    그렇다면 그냥 외부 php 파일에 procFileUpload() 함수의 소스를 복붙하듯 재활용하되, procFileUpload()에서 $this->add로 넣어주는 데이터들만 별도 array 변수에 담은 다음에 echo json_encode로 뿌려줘도 무방할까요?

  • profile profile

    아, 일단 그렇게 해서 대강 데이터는 받아왔습니다.

    당연히 합리적인 방법은 아닌 것 같아요ㅜㅜ

     

    소스는 procFileUpload 함수를 긁어다가 $this->add 대신 array에 담아서 아래와 같이 했구요.

    Context::setRequestMethod('JSON');
    $file_info = Context::get('Filedata');
    
    // An error appears if not a normally uploaded file
    if(!$file_info || !is_uploaded_file($file_info['tmp_name'])) exit();
    
    // Basic variables setting
    $editor_sequence = Context::get('editor_sequence');
    
    // Exit a session if there is neither upload permission nor information
    if(!$_SESSION['upload_info'][$editor_sequence]->enabled)
    {
        throw new Rhymix\Framework\Exceptions\NotPermitted;
    }
    
    // Get upload_target_srl
    $upload_target_srl = intval(Context::get('uploadTargetSrl')) ?: intval(Context::get('upload_target_srl'));
    if (!$upload_target_srl)
    {
        $upload_target_srl = $_SESSION['upload_info'][$editor_sequence]->upload_target_srl;
    }
    if (!$upload_target_srl)
    {
        $upload_target_srl = getNextSequence();
        $_SESSION['upload_info'][$editor_sequence]->upload_target_srl = $upload_target_srl;
    }
    
    // Handle chunking
    if (preg_match('!^bytes (\d+)-(\d+)/(\d+)$!', $_SERVER['HTTP_CONTENT_RANGE'], $matches))
    {
        // Check basic sanity
        $chunk_start = intval($matches[1]);
        $chunk_size = ($matches[2] - $matches[1]) + 1;
        $total_size = intval($matches[3]);
        if ($chunk_start < 0 || $chunk_size < 0 || $total_size < 0 || $chunk_start + $chunk_size > $total_size || $chunk_size != $file_info['size'])
        {
            throw new Rhymix\Framework\Exception('msg_upload_invalid_chunk');
        }
        $response['chunk_current_size'] = $chunk_size;
        $response['chunk_uploaded_size'] = $chunk_start;
    
        // Check existing chunks
        $temp_key = hash_hmac('sha1', sprintf('%d:%d:%d:%s:%s', $editor_sequence, $upload_target_srl, $module_srl, $file_info['name'], session_id()), config('crypto.authentication_key'));
        $temp_filename = RX_BASEDIR . 'files/attach/chunks/' . $temp_key;
        if ($chunk_start == 0 && Rhymix\Framework\Storage::isFile($temp_filename))
        {
            Rhymix\Framework\Storage::delete($temp_filename);
            $response['chunk_status'] = 11;
            throw new Rhymix\Framework\Exception('msg_upload_invalid_chunk');
        }
        if ($chunk_start != 0 && (!Rhymix\Framework\Storage::isFile($temp_filename) || Rhymix\Framework\Storage::getSize($temp_filename) != $chunk_start))
        {
            Rhymix\Framework\Storage::delete($temp_filename);
            $response['chunk_status'] = 12;
            throw new Rhymix\Framework\Exception('msg_upload_invalid_chunk');
        }
    
        // Check size limit
        $is_admin = (Context::get('logged_info')->is_admin === 'Y');
        if (!$is_admin)
        {
            $module_config = FileModel::getFileConfig($module_srl);
            $allowed_attach_size = $module_config->allowed_attach_size * 1024 * 1024;
            $allowed_filesize = $module_config->allowed_filesize * 1024 * 1024;
            if ($total_size > $allowed_filesize)
            {
                $response['chunk_status'] = 21;
                throw new Rhymix\Framework\Exception('msg_exceeds_limit_size');
            }
            $output = executeQuery('file.getAttachedFileSize', (object)array('upload_target_srl' => $upload_target_srl));
            if (intval($output->data->attached_size) + $total_size > $allowed_attach_size)
            {
                $response['chunk_status'] = 22;
                throw new Rhymix\Framework\Exception('msg_exceeds_limit_size');
            }
        }
    
        // Append to chunk
        $fp = fopen($file_info['tmp_name'], 'r');
        $success = Rhymix\Framework\Storage::write($temp_filename, $fp, 'a');
        if ($success && Rhymix\Framework\Storage::getSize($temp_filename) == $chunk_start + $chunk_size)
        {
            $response['chunk_status'] = 0;
            $response['chunk_uploaded_size'] = $chunk_start + $chunk_size;
            if ($chunk_start + $chunk_size == $total_size)
            {
                if (!Rhymix\Framework\Filters\FileContentFilter::check($temp_filename, $file_info['name']))
                {
                    throw new Rhymix\Framework\Exception('msg_security_violation');
                }
                $file_info['tmp_name'] = $temp_filename;
                $file_info['size'] = Rhymix\Framework\Storage::getSize($temp_filename);
            }
            else
            {
                return;
            }
        }
        else
        {
            Rhymix\Framework\Storage::delete($temp_filename);
            $response['chunk_status'] = 40;
            throw new Rhymix\Framework\Exception('msg_upload_invalid_chunk');
        }
    }
    else
    {
        $response['chunk_status'] = -1;
    }
    
    // Save the file
    $output = getController('file')->insertFile($file_info, $module_srl, $upload_target_srl);
    if($output->error != '0')
    {
        throw new Rhymix\Framework\Exception($output->message);
    }
    
    // Create the response
    Context::setResponseMethod('JSON');
    $response['file_srl'] = $output->get('file_srl');
    $response['file_size'] = $output->get('file_size');
    $response['direct_download'] = $output->get('direct_download');
    $response['source_filename'] = $output->get('source_filename');
    $response['upload_target_srl'] = $output->get('upload_target_srl');
    $response['thumbnail_filename'] = $output->get('thumbnail_filename');
    $response['original_type'] = $output->get('original_type');
    if ($output->get('direct_download') === 'Y')
    {
        $response['download_url'] = FileModel::getDirectFileUrl($output->get('uploaded_filename'));
    }
    else
    {
        $response['download_url'] = FileModel::getDownloadUrl($output->get('file_srl'), $output->get('sid'), $module_srl);
    }
    
    echo json_encode($response);
    exit;

     

    그 결과 ajax에서 다음과 같은 데이터를 리턴 받았습니다ㅠㅠ

    0.png

     

  • profile
    https://xetown.com/topics/14001

    저도 예전에 해당기능 구현해본적 있었는데 별도 php 없이 procFileUpload로 바로 올려서 구현했었습니다. 물론 플러그인 코드를 조금 수정하긴 했지만요(업로드 완료후 첨부파일 처리부분)
  • profile profile
    와... 저도 php 없이 바로 직통으로 처리하고 싶습니다ㅠㅠ
    근데 또 옛날 xe공홈 질답글을 보니까 procFileUpload는 exec_json은 안 된다고 해서 그냥 바로 php로 가버렸어요ㅜ
  • profile profile

    어라.... ?

    말씀 듣고 한번 해봤는데 이게 되네요?

    이거 이렇게 해도 괜찮은 거예요?

     

    var form_data = new FormData;
    form_data.append('act', 'procFileUpload');
    form_data.append('mid', params.mid);
    form_data.append('editor_sequence', params.editor_sequence);
    form_data.append('editor_target', params.editor_target);
    form_data.append('upload_target_srl', params.upload_target_srl);
    form_data.append('Filedata', e.data.$.dataTransfer.files[0]);
    
    $.ajax({
        url: '/',
        type: 'post',
        cache: false,
        contentType: false,
        processData: false,
        dataType: 'json',
        data: form_data,
        async: true,
        success: function(data) {
            console.log(data);
            $container.data('instance').loadFilelist($container);
        },
        complete: function() {
        },
        error: function(x, e) {
            if ( x.status == 0 ) {
                alert('네트워크 연결 상태를 체크해주세요.');
            } else if ( x.status === 404 ) {
                alert('요청받은 URL을 찾을 수 없습니다.');
            } else if ( x.status === 500 ) {
                alert('내부 서버 오류 : 관리자에게 문의해보세요.');
            } else if ( e === 'parsererror' ) {
                alert('요청받은 내용을 변환하는 데 실패했습니다.');
            } else if ( e === 'timeout' ) {
                alert('연결 시간이 초과됐습니다.');
            } else {
                alert('알 수 없는 에러가 발생했습니다.\n' + x.responseText);
            }
        }
    });

     

  • profile profile
    네. 파일업로드쪽이 아마 csrf 체크를 안할텐데... 비슷하게 구현했던걸로 기억합니다.
  • profile profile
    확인 감사합니다. 그럼 저는 덕분에 맘 놓고 이제 본문 삽입이라는 산으로 가보겠습니다;;;
  • profile
    야호, 이제 9부능선 넘었습니다. 성공적!
    아직 고민은 좀 남았습니다.

    - 비디오, 오디오 태그의 파일 첨부 ... 되면 좋을 것 같은데 좀 과하지 않나요? 웹호스팅 이용자들도 있으니..
    - 움직이는 gif ... 이것도 위와 같은 이유로 일단 막아놓긴 했는데... 현재 붙여넣기에선 안 되고 드롭 이벤트에선 될 겁니다ㅎ
    - 외부 파일 url 붙여넣기 ... 현재는 img 태그로만 파일 식별을 하는데 파일 주소만 있는 경우는 ...
  • profile profile
    사용자들이 자기도 모르게 파일을 업로드하게 되면 저작권 침해 문제도 생길 수 있으니 주의해야겠지요. 결국은 이 자료를 사용하는 사이트 운영자의 책임이 되겠지만요.
  • profile profile

    네, 그런 이유도 있고 해서 (기술적 구현의 역량 문제 때문에 많이는 못하고) 몇 가지 옵션을 둬서 운영자의 선택에 맡기고자 합니다.

    0. 기본적으로 일반 이미지는 '파일 드롭'이나 '복붙'시 파일 첨부 및 본문 삽입. 단, 움짤 gif는 파일 드롭 때만 기본 첨부 및 삽입되고, 복붙시에 한해 옵션에 따라 처리함.

    1. 파일 드롭
    - 비디오 파일 첨부(동시에 본문 삽입) 여부 선택
    - 오디오 파일 첨부(동시에 본문 삽입) 여부 선택
    - 일반 파일 첨부(본문 삽입은 불가) 여부 선택

    - (움짤 gif는 클라이언트 사이드에서 식별이 복잡해서ㅜ 일반 이미지로 취급하여 그냥 기본으로 파일 첨부)

    2. 붙여넣기
    - 움짤 gif 파일 첨부(동시에 본문 삽입) 여부 선택
    - 이미지 url 복붙시 파일 첨부(동시에 본문 삽입) 여부 선택

    등으로요.

    근데 저번에 어느 정도 되면 코어에 반영할 수도 있음을 시사해주셨던 것 같은데, 그럴 가능성이 아직 있을까요?
    만약 그렇다면 이쯤에서 마무리한 뒤 팁 게시판에 바로 공유하고, 아니면 좀 더 디벨롭한 다음에 자료 공유를 할까 해서요.

  • profile profile
    윤삼님 혹시 모바일 디바이스에서의 이미지 복사 > 붙여넣기도 가능할까요?!

    저는 구현하다가 어려워서 포기했었답니다 ㅠㅠ
  • profile profile

    하핫 저도 안 되네요;;
    근데 모바일에서 이미지 복붙이 굳이 필요할까요오...

     

    덧. ck에디터의 모바일 클립보드 붙여넣기는 paste가 아니라 input 이벤트로 잡았던 기억이 있습니다.

    미디어 임베드 애드온에 유사한 코드가 있을 텐데 응용 시도를 해보면 길이 있을지도 모르겠어요. 다만 확신은 없습니다ㅜ