No uploader fino, como editar / atualizar os metadados do arquivo S3 para arquivos enviados nas sessões anteriores?

Eu tenho um requisito no qual o usuário precisa editar / atualizar os metadados do arquivo s3 carregados nas sessões anteriores. Eu implementei a Lista inicial de arquivos, mas preciso tornar os metadados do arquivo (nome do arquivo, legenda - novo campo no meu caso) editáveis na lista de exibição. Isso pode ser realizado?

Entendorecurso de edição de arquivos, mas isso é limitado a antes do upload do arquivo. Parece que minha exigência não é facilmente suportada fora da caixa FU. Eu segui a abordagem abaixo.

No modelo, tenho um botão com o texto 'Atualizar legenda', que possui onclick = "captionUpdate ()", que definirá a variável JS (isCaptionUpdate) como true.A atualização de legenda acionará o ponto de extremidade DeleteFile, exceto que definirá os dados dos parâmetros para o valor da legenda no campo de texto definido no modeloNo código do lado do servidor, o processo verifica os parâmetros de Legenda e, em seguida, chama a função updateObjectWithCaption ()

Todos os itens acima funcionam perfeitamente com os seguintesdesafios.Por favor veja ocaptura de tela.

Quando o usuário clica em 'Atualizar legenda', ele segue as etapas DELETE e, como estou passando o parâmetro Caption, ele atualiza o arquivo S3. Mas o problema está na lista de arquivos. Vejo um texto de status chamado 'Excluindo .....' aparecer por um breve período. Como posso alterar o status para 'Atualizando legenda ....' ou algo semelhanteOutro problema com o número 1 é que, assim que o S3 é atualizado, a lista Arquivo no arquivo é removida. A parte da interface do usuário ainda pensa que é a etapa DELETE por algum motivo, como posso dizer à interface do usuário que ela não é realmente excluída?Como você pode ver na seção deleteFile do JS, a legenda é obtida em document.getElementById ('caption'). Value; isso significa que, mesmo se eu clicar em 'Atualizar legenda' do 2º ou 3º ou 4º arquivos, será a primeira ocorrência do elemento Legenda. Como posso obter a legenda do arquivo específico?Por último, mas não menos importante, como posso mostrar o botão 'Atualizar legenda' apenas no arquivo enviado anteriormente. Não quero mostrar este botão em novos uploads.

Desculpe por muitas perguntas. Não pude separar essas perguntas, pois todas elas estão relacionadas ao tópico de atualização de metadados do arquivo S3.

Modelo:

<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
            <div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
                <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
            </div>
            <div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
                <span class="qq-upload-drop-area-text-selector"></span>
            </div>
            <div class="buttons">
                <div class="qq-upload-button-selector qq-upload-button">
                    <div>Select files</div>
                </div>
                <button type="button" id="trigger-upload-section1" class="btn btn-primary">
                    <i class="icon-upload icon-white"></i> Upload
                </button>
            </div>
            <span class="qq-drop-processing-selector qq-drop-processing">
                <span>Processing dropped files...</span>
                <span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
            </span>
            <ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
                <li>
                    <div class="qq-progress-bar-container-selector">
                        <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
                    </div>
                    <span class="qq-upload-spinner-selector qq-upload-spinner"></span>
                    <img class="qq-thumbnail-selector" qq-max-size="100" qq-server-scale>
                    <span class="qq-upload-file-selector qq-upload-file"></span>
                    <span class="qq-edit-filename-icon-selector qq-edit-filename-icon qq-editable" aria-label="Edit filename"></span>
                    <input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
                    <span class="qq-upload-caption-selector qq-upload-caption"></span>
                    <span class="qq-edit-caption-icon-selector qq-edit-caption-icon qq-editable" aria-label="Edit caption"></span>
                    <input class="qq-edit-caption-selector qq-edit-caption qq-editing" placeholder="Caption  here ..." tabindex="0" type="text" id="caption">
                    <span class="qq-upload-size-selector qq-upload-size"></span>
                    <button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">Cancel</button>
                    <button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">Retry</button>
                    <button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">Delete</button>  
                    <button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete" onclick="captionUpdate();">Update Caption</button>                                      
                    <span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
                </li>
            </ul>

            <dialog class="qq-alert-dialog-selector">
                <div class="qq-dialog-message-selector"></div>
                <div class="qq-dialog-buttons">
                    <button type="button" class="qq-cancel-button-selector">Close</button>
                </div>
            </dialog>

            <dialog class="qq-confirm-dialog-selector">
                <div class="qq-dialog-message-selector"></div>
                <div class="qq-dialog-buttons">
                    <button type="button" class="qq-cancel-button-selector">No</button>
                    <button type="button" class="qq-ok-button-selector">Yes</button>
                </div>
            </dialog>

            <dialog class="qq-prompt-dialog-selector">
                <div class="qq-dialog-message-selector"></div>
                <input type="text">
                <div class="qq-dialog-buttons">
                    <button type="button" class="qq-cancel-button-selector">Cancel</button>
                    <button type="button" class="qq-ok-button-selector">Ok</button>
                </div>
            </dialog>
        </div>

JS

var isCaptionUpdate = false;
function captionUpdate(){
    isCaptionUpdate = true; 
};
var manualUploaderSection1 = new qq.s3.FineUploader({
    element: document.getElementById('fine-uploader-manual-trigger-section1'),
    template: 'qq-template-manual-trigger-section1',
    autoUpload: false,
    debug: true,
    request: {
        endpoint: "http://xx_my_bucket_xx.s3.amazonaws.com",
        accessKey: "AKIAIAABIA",        
    },
    signature: {
        endpoint: "http://localhost/app/ci/php-s3-server/endpoint-cors.php"
    },
    uploadSuccess: {
        endpoint: "http://localhost/app/ci/php-s3-server/endpoint-cors.php?success",
        params: {
            isBrowserPreviewCapable: qq.supportedFeatures.imagePreviews
        }
    },
    session: {
        endpoint: "http://localhost/app/ci/php-s3-server/endpoint-cors.php?filelist"
    },
    iframeSupport: {
        localBlankPagePath: "success.html"
    },
    cors: {
        expected: true
    },
    chunking: {
        enabled: true
    },
    resume: {
        enabled: true
    },
    deleteFile: {
        enabled: true,
        method: "POST",
        endpoint: "http://localhost/app/ci/php-s3-server/endpoint-cors.php",
        params: {
            caption: function() {
                if (isCaptionUpdate === true) {
                    isCaptionUpdate = false;
                    return document.getElementById('caption').value;
                }
            }
        }
    },
    validation: {
        itemLimit: 5,
        sizeLimit: 15000000
    },
    thumbnails: {
        placeholders: {
            notAvailablePath: "http://localhost/app/ci/s3.fine-uploader/placeholders/not_available-generic.png",
            waitingPath: "http://localhost/app/ci/s3.fine-uploader/placeholders/waiting-generic.png"
        }
    },
    callbacks: {
        onComplete: function(id, name, response) {
            var previewLink = qq(this.getItemByFileId(id)).getByClass('preview-link')[0];

            if (response.success) {
                previewLink.setAttribute("href", response.tempLink)
            }
        },
        onUpload: function(id, fileName) {          
            var caption = document.getElementById('caption').value;
            this.setParams({'caption':caption});
        }
    }    
});

qq(document.getElementById("trigger-upload-section1")).attach("click", function() {
    manualUploaderSection1.uploadStoredFiles();
});

Código do lado do servidor:

require '/vendor/autoload.php';
use Aws\S3\S3Client;

$clientPrivateKey = 'LB7r54Rgh9sCuTAC8V5F';
$serverPublicKey = 'AKIAU2ZEQ';
$serverPrivateKey = '8Xu6lxcDfKifHfn4pdELnM1E';

$expectedBucketName = 'xx_my_bucket_xx';
$expectedHostName = 'http://s3.amazonaws.com'; // v4-only
$expectedMaxSize = 15000000;

$method = getRequestMethod();

// This first conditional will only ever evaluate to true in a
// CORS environment
if ($method == 'OPTIONS') {
    handlePreflight();
}
// This second conditional will only ever evaluate to true if
// the delete file feature is enabled
else if ($method == "DELETE") { 
    handleCorsRequest();
    if (isset($_REQUEST['caption'])) {
        updateObjectWithCaption();
    } else {
        deleteObject();
    }
}
// This is all you really need if not using the delete file feature
// and not working in a CORS environment
else if ($method == 'POST') {
    handleCorsRequest();

    // Assumes the successEndpoint has a parameter of "success" associated with it,
    // to allow the server to differentiate between a successEndpoint request
    // and other POST requests (all requests are sent to the same endpoint in this exa,mple).
    // This condition is not needed if you don't require a callback on upload success.
    if (isset($_REQUEST["success"])) {
        verifyFileInS3(shouldIncludeThumbnail());
    }
    else {
        signRequest();
    }
}
//filelist - this is to list already uploaded files
else if ($method == 'GET') {
    if (isset($_REQUEST["filelist"])) {     
        getFileList('test/');
    }
}

function getFileList($filePrefix) { 
    global $expectedBucketName;

    $objects = getS3Client()->getIterator('ListObjects', array(
    //$objects = getS3Client()->ListObjects(array(
            'Bucket' => $expectedBucketName,
            'Prefix' => $filePrefix //must have the trailing forward slash "/"
    ));

    $object_list = array();
    foreach ($objects as $object) {
        //echo $object['Key'] . "<br>";     
        $object_metadata = getHeadObject($expectedBucketName, $object['Key']);      

        if (isset($object_metadata['Metadata']['qqfilename'])) {            
            $keyArr = explode("/", $object['Key']);         
            $posOfLastString = sizeof($keyArr) - 1;
            $uuidArry = explode(".", $keyArr[$posOfLastString]);
            $link = getTempLink($expectedBucketName, $object['Key']);

            $object_new = array();

            $object_new['name'] = $object_metadata['Metadata']['qqfilename'];
            $object_new['uuid'] = $uuidArry[0];
            $object_new['s3Key'] = $object['Key'];
            $object_new['size'] = $object['Size'];
            $object_new['s3Bucket'] = $expectedBucketName;
            $object_new['thumbnailUrl'] = $link;

            array_push($object_list, (object)$object_new);
        }
    }   
    echo json_encode($object_list); 
}

// This will retrieve the "intended" request method.  Normally, this is the
// actual method of the request.  Sometimes, though, the intended request method
// must be hidden in the parameters of the request.  For example, when attempting to
// send a DELETE request in a cross-origin environment in IE9 or older, it is not
// possible to send a DELETE request.  So, we send a POST with the intended method,
// DELETE, in a "_method" parameter.
function getRequestMethod() {
    global $HTTP_RAW_POST_DATA;

    // This should only evaluate to true if the Content-Type is undefined
    // or unrecognized, such as when XDomainRequest has been used to
    // send the request.
    if(isset($HTTP_RAW_POST_DATA)) {
        parse_str($HTTP_RAW_POST_DATA, $_POST);
    }

    if (isset($_REQUEST['_method'])) {
        return $_REQUEST['_method'];
    }

    return $_SERVER['REQUEST_METHOD'];
}

// Only needed in cross-origin setups
function handleCorsRequest() 
    // If you are relying on CORS, you will need to adjust the allowed domain here.
    header('Access-Control-Allow-Origin: http://localhost');
}

// Only needed in cross-origin setups
function handlePreflight() {
    handleCorsRequest();
    header('Access-Control-Allow-Methods: POST');
    header('Access-Control-Allow-Headers: Content-Type');
}

function getS3Client() {    
    global $serverPublicKey, $serverPrivateKey;

    return S3Client::factory(array(
        'key' => $serverPublicKey,
        'secret' => $serverPrivateKey
    ));
}

// Only needed if the delete file feature is enabled
function deleteObject() {
    getS3Client()->deleteObject(array(
        'Bucket' => $_REQUEST['bucket'],
        'Key' => $_REQUEST['key']
    ));
}

function getHeadObject($bucket, $key) { 
    $object_metadata = getS3Client()->headObject(array('Bucket' => $bucket,'Key' => $key));
    $object_metadata = $object_metadata->toArray();

    return $object_metadata;
}

function updateObjectWithCaption() {        
    $bucket = $_REQUEST['bucket'];
    $key = $_REQUEST['key'];
    $caption = $_REQUEST['caption'];
    $object_metadata = getHeadObject($bucket, $key);
    $filename = $object_metadata['Metadata']['qqfilename'];
    $fileType = getFileType($key);

    getS3Client()->copyObject(array(
            'Bucket' => $bucket,
            'Key' => $key,
            'CopySource' => urlencode($_REQUEST['bucket'] . '/' . $key),
            'MetadataDirective' => 'REPLACE',
            //'CacheControl' => 'max-age=31536000',
            //'Expires' => gmdate('D, d M Y H:i:s T', strtotime('+1 years')), // Set EXPIRES and CACHE-CONTROL headers to +1 year (RFC guidelines max.)
            'ContentType' => $fileType,
            'Metadata'=>array(
                'qqcaption' => $caption,    
                'qqfilename' => $filename,
            ),
    ));
}

function getFileType($key) {
    $file_parts = pathinfo($key);
    $filetype = "";
    switch($file_parts['extension'])
    {
        case "jpg":         
            $filetype = "image/jpeg";
            break;  
        case "jpeg":            
            $filetype = "image/jpeg";
            break;  
        case "png":         
            $filetype = "image/png";
            break;  
        case "gif":         
            $filetype = "image/gif";
            break;  
        case "tif":         
            $filetype = "image/tiff";
            break;  
        case "tiff":            
            $filetype = "image/tiff";
            break;  
        case "bmp":         
            $filetype = "image/bmp";
            break;  
    }
    return $filetype;
}

function signRequest() {
    header('Content-Type: application/json');

    $responseBody = file_get_contents('php://input');
    $contentAsObject = json_decode($responseBody, true);
    $jsonContent = json_encode($contentAsObject);

    if (!empty($contentAsObject["headers"])) {
        signRestRequest($contentAsObject["headers"]);
    }
    else {
        signPolicy($jsonContent);
    }
}

function signRestRequest($headersStr) {
    $version = isset($_REQUEST["v4"]) ? 4 : 2;
    if (isValidRestRequest($headersStr, $version)) {
        if ($version == 4) {
            $response = array('signature' => signV4RestRequest($headersStr));
        }
        else {
            $response = array('signature' => sign($headersStr));
        }

        echo json_encode($response);
    }
    else {
        echo json_encode(array("invalid" => true));
    }
}

function isValidRestRequest($headersStr, $version) {    
    if ($version == 2) {
        global $expectedBucketName;
        $pattern = "/\/$expectedBucketName\/.+$/";
    }
    else {
        global $expectedHostName;
        $pattern = "/host:$expectedHostName/";
    }

    preg_match($pattern, $headersStr, $matches);

    return count($matches) > 0;
}

function signPolicy($policyStr) {   
    $policyObj = json_decode($policyStr, true);

    if (isPolicyValid($policyObj)) {
        $encodedPolicy = base64_encode($policyStr);
        if (isset($_REQUEST["v4"])) {
            $response = array('policy' => $encodedPolicy, 'signature' => signV4Policy($encodedPolicy, $policyObj));
        }
        else {
            $response = array('policy' => $encodedPolicy, 'signature' => sign($encodedPolicy));
        }
        echo json_encode($response);
    }
    else {
        echo json_encode(array("invalid" => true));
    }
}

function isPolicyValid($policy) {   
    global $expectedMaxSize, $expectedBucketName;

    $conditions = $policy["conditions"];
    $bucket = null;
    $parsedMaxSize = null;

    for ($i = 0; $i < count($conditions); ++$i) {
        $condition = $conditions[$i];

        if (isset($condition["bucket"])) {
            $bucket = $condition["bucket"];
        }
        else if (isset($condition[0]) && $condition[0] == "content-length-range") {
            $parsedMaxSize = $condition[2];
        }
    }

    return $bucket == $expectedBucketName && $parsedMaxSize == (string)$expectedMaxSize;
}

function sign($stringToSign) {  
    global $clientPrivateKey;

    return base64_encode(hash_hmac(
            'sha1',
            $stringToSign,
            $clientPrivateKey,
            true
        ));
}

function signV4Policy($stringToSign, $policyObj) {
    global $clientPrivateKey;

    foreach ($policyObj["conditions"] as $condition) {
        if (isset($condition["x-amz-credential"])) {
            $credentialCondition = $condition["x-amz-credential"];
        }
    }

    $pattern = "/.+\/(.+)\\/(.+)\/s3\/aws4_request/";
    preg_match($pattern, $credentialCondition, $matches);

    $dateKey = hash_hmac('sha256', $matches[1], 'AWS4' . $clientPrivateKey, true);
    $dateRegionKey = hash_hmac('sha256', $matches[2], $dateKey, true);
    $dateRegionServiceKey = hash_hmac('sha256', 's3', $dateRegionKey, true);
    $signingKey = hash_hmac('sha256', 'aws4_request', $dateRegionServiceKey, true);

    return hash_hmac('sha256', $stringToSign, $signingKey);
}

function signV4RestRequest($rawStringToSign) {
    global $clientPrivateKey;

    $pattern = "/.+\\n.+\\n(\\d+)\/(.+)\/s3\/aws4_request\\n(.+)/s";
    preg_match($pattern, $rawStringToSign, $matches);

    $hashedCanonicalRequest = hash('sha256', $matches[3]);
    $stringToSign = preg_replace("/^(.+)\/s3\/aws4_request\\n.+$/s", '$1/s3/aws4_request'."\n".$hashedCanonicalRequest, $rawStringToSign);

    $dateKey = hash_hmac('sha256', $matches[1], 'AWS4' . $clientPrivateKey, true);
    $dateRegionKey = hash_hmac('sha256', $matches[2], $dateKey, true);
    $dateRegionServiceKey = hash_hmac('sha256', 's3', $dateRegionKey, true);
    $signingKey = hash_hmac('sha256', 'aws4_request', $dateRegionServiceKey, true);

    return hash_hmac('sha256', $stringToSign, $signingKey);
}

// This is not needed if you don't require a callback on upload success.
function verifyFileInS3($includeThumbnail) {
    global $expectedMaxSize;

    $bucket = $_REQUEST["bucket"];
    $key = $_REQUEST["key"];

    // If utilizing CORS, we return a 200 response with the error message in the body
    // to ensure Fine Uploader can parse the error message in IE9 and IE8,
    // since XDomainRequest is used on those browsers for CORS requests.  XDomainRequest
    // does not allow access to the response body for non-success responses.
    if (isset($expectedMaxSize) && getObjectSize($bucket, $key) > $expectedMaxSize) {
        // You can safely uncomment this next line if you are not depending on CORS
        header("HTTP/1.0 500 Internal Server Error");
        deleteObject();
        echo json_encode(array("error" => "File is too big!", "preventRetry" => true));
    }
    else {
        $link = getTempLink($bucket, $key);
        $response = array("tempLink" => $link);

        if ($includeThumbnail) {
            $response["thumbnailUrl"] = $link;
        }

        echo json_encode($response);
    }
}

// Provide a time-bombed public link to the file.
function getTempLink($bucket, $key) {   
    $client = getS3Client();
    $url = "{$bucket}/{$key}";
    $request = $client->get($url);

    return $client->createPresignedUrl($request, '+15 minutes');
}

function getObjectSize($bucket, $key) { 
    $objInfo = getS3Client()->headObject(array(
            'Bucket' => $bucket,
            'Key' => $key
        ));
    return $objInfo['ContentLength'];
}

// Return true if it's likely that the associate file is natively
// viewable in a browser.  For simplicity, just uses the file extension
// to make this determination, along with an array of extensions that one
// would expect all supported browsers are able to render natively.
function isFileViewableImage($filename) {   
    $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    $viewableExtensions = array("jpeg", "jpg", "gif", "png", "tif", "tiff");

    return in_array($ext, $viewableExtensions);
}

// Returns true if we should attempt to include a link
// to a thumbnail in the uploadSuccess response.  In it's simplest form
// (which is our goal here - keep it simple) we only include a link to
// a viewable image and only if the browser is not capable of generating a client-side preview.
function shouldIncludeThumbnail() { 
    $filename = $_REQUEST["name"];
    $isPreviewCapable = $_REQUEST["isBrowserPreviewCapable"] == "true";
    $isFileViewableImage = isFileViewableImage($filename);

    return !$isPreviewCapable && $isFileViewableImage;
}

questionAnswers(1)

yourAnswerToTheQuestion