Index: branches/5.3.x/core/kernel/db/db_event_handler.php =================================================================== diff -u -N -r15698 -r15902 --- branches/5.3.x/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 15698) +++ branches/5.3.x/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 15902) @@ -1,6 +1,6 @@ getPrefixSpecial() . '] in checkItemStatus, leading to "404 Not Found"', E_USER_NOTICE); - $vars = $this->Application->UrlManager->prepare404(); - - foreach ($vars as $var_name => $var_value) { - $this->Application->SetVar($var_name, $var_value); - } - - // in case if missing item is recalled first from event (not from template) - $this->Application->QuickRun(); - $this->Application->Done(); - exit; + $this->Application->UrlManager->show404(); } /** @@ -640,7 +631,7 @@ if ( MOD_REWRITE ) { $redirect_params = Array ( 'm_cat_id' => 0, - 'next_template' => urlencode('external:' . $_SERVER['REQUEST_URI']), + 'next_template' => kUtil::escape('external:' . $_SERVER['REQUEST_URI'], kUtil::ESCAPE_URL), ); } else { @@ -668,6 +659,7 @@ /* @var $object kTempTablesHandler */ $parent_event = $event->getEventParam('parent_event'); + /* @var $parent_event kEvent */ if ( is_object($parent_event) ) { $object->setParentEvent($parent_event); @@ -1585,6 +1577,7 @@ list($id, $field_values) = each($items_info); $object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values)); + $event->setEventParam('form_data', $field_values); $this->customProcessing($event, 'before'); @@ -1638,6 +1631,7 @@ foreach ($items_info as $id => $field_values) { $object->Load($id); $object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values)); + $event->setEventParam('form_data', $field_values); $this->customProcessing($event, 'before'); if ( $object->Update($id) ) { @@ -1830,15 +1824,11 @@ $object = $event->getObject(Array('skip_autoload' => true)); /* @var $object kDBItem */ - $this->Application->RemoveVar($object->getPendingActionVariableName()); + $object->setPendingActions(null, true); $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $this->Application->RemoveVar($changes_var_name); - foreach ($ids as $id) { - $object->resetUploads($id); - } - $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ @@ -1848,10 +1838,10 @@ $event->SetRedirectParam($event->getPrefixSpecial() . '_id', array_shift($ids)); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); - $simultaneous_edit_message = $this->Application->GetVar('_simultanious_edit_message'); + $simultaneous_edit_message = $this->Application->GetVar('_simultaneous_edit_message'); if ( $simultaneous_edit_message ) { - $event->SetRedirectParam('_simultanious_edit_message', urlencode($simultaneous_edit_message)); + $event->SetRedirectParam('_simultaneous_edit_message', kUtil::escape($simultaneous_edit_message, kUtil::ESCAPE_URL)); } } @@ -2077,6 +2067,21 @@ } /** + * Analog of OnPreSave event for usage in AJAX request + * + * @param kEvent $event + * + * @return void + */ + protected function OnPreSaveAjax(kEvent $event) + { + $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); + /* @var $ajax_form_helper AjaxFormHelper */ + + $ajax_form_helper->transitEvent($event, 'OnPreSave'); + } + + /** * [HOOK] Saves sub-item * * @param kEvent $event @@ -2182,8 +2187,6 @@ $this->Application->SetVar($event->getPrefixSpecial() . '_id', 0); $this->Application->SetVar($event->getPrefixSpecial() . '_PreCreate', 1); - $object->resetUploads(); - $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $this->Application->RemoveVar($changes_var_name); @@ -2205,7 +2208,7 @@ $field_values = $this->getSubmittedFields($event); $object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values)); - + $event->setEventParam('form_data', $field_values); $this->customProcessing($event, 'before'); if ( $object->Create() ) { @@ -2421,7 +2424,7 @@ /* @var $object kDBItem */ if ( !$object->IsTempTable() ) { - $this->_proccessPendingActions($event); + $this->_processPendingActions($event); } } @@ -2450,7 +2453,7 @@ /* @var $object kDBItem */ if ( !$object->IsTempTable() ) { - $this->_proccessPendingActions($event); + $this->_processPendingActions($event); } } @@ -2601,7 +2604,13 @@ */ protected function OnAfterCopyToLive(kEvent $event) { - $this->_proccessPendingActions($event); + $object = $event->getObject(array('skip_autoload' => true)); + /* @var $object kDBItem */ + + $object->SwitchToLive(); + $object->Load($event->getEventParam('id')); + + $this->_processPendingActions($event); } /** @@ -2611,37 +2620,45 @@ * @return void * @access protected */ - protected function _proccessPendingActions(kEvent $event) + protected function _processPendingActions(kEvent $event) { $object = $event->getObject(); /* @var $object kDBItem */ - if ( $object->getUploaderFields() ) { - // this would prevent SQL error when loading "*-ci" prefix object - if ( $event->Name == 'OnAfterCopyToLive' ) { - $object->SwitchToLive(); - $object->Load($event->getEventParam('id')); + $update_required = false; + $temp_id = $event->getEventParam('temp_id'); + $id = $temp_id !== false ? $temp_id : $object->GetID(); - $object->processUploads($event->getEventParam('temp_id')); - } - else { - $object->processUploads(); - } - } + foreach ($object->getPendingActions($id) as $data) { + switch ( $data['action'] ) { + case 'delete': + unlink($data['file']); + break; - $var_name = $object->getPendingActionVariableName(); - $schedule = $this->Application->RecallVar($var_name); + case 'make_live': + $file_helper = $this->Application->recallObject('FileHelper'); + /* @var $file_helper FileHelper */ - if ( $schedule ) { - $schedule = unserialize($schedule); + $old_name = basename($data['file']); + $new_name = $file_helper->ensureUniqueFilename(dirname($data['file']), kUtil::removeTempExtension($old_name)); + rename($data['file'], dirname($data['file']) . '/' . $new_name); - foreach ($schedule as $data) { - if ( $data['action'] == 'delete' ) { - unlink($data['file']); - } + $db_value = $object->GetDBField($data['field']); + $object->SetDBField($data['field'], str_replace($old_name, $new_name, $db_value)); + $update_required = true; + break; + + default: + trigger_error('Unsupported pending action "' . $data['action'] . '" for "' . $event->getPrefixSpecial() . '" unit', E_USER_WARNING); + break; } + } - $this->Application->RemoveVar($var_name); + // remove pending actions before updating to prevent recursion + $object->setPendingActions(); + + if ( $update_required ) { + $object->Update(); } } @@ -2999,7 +3016,7 @@ */ public function getCustomExportColumns(kEvent $event) { - return Array(); + return Array (); } /** @@ -3152,18 +3169,13 @@ } $tmp_path = WRITEABLE . '/tmp/'; - $fname = $value['name']; + $filename = $value['name'] . '.tmp'; $id = $this->Application->GetVar('id'); if ( $id ) { - $fname = $id . '_' . $fname; + $filename = $id . '_' . $filename; } - $field_name = $this->Application->GetVar('field'); - $field_options = $this->_getUploadFieldDefinition($event, $field_name); - - $storage_format = array_key_exists('storage_format', $field_options) ? $field_options['storage_format'] : false; - if ( !is_writable($tmp_path) ) { // 500 Internal Server Error // check both temp and live upload directory @@ -3175,7 +3187,8 @@ $file_helper = $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ - $fname = $file_helper->ensureUniqueFilename($tmp_path, $fname); + $filename = $file_helper->ensureUniqueFilename($tmp_path, $filename); + $storage_format = $this->_getStorageFormat($this->Application->GetVar('field'), $event); if ( $storage_format ) { $image_helper = $this->Application->recallObject('ImageHelper'); @@ -3184,13 +3197,13 @@ move_uploaded_file($value['tmp_name'], $value['tmp_name'] . '.jpg'); // add extension, so ResizeImage can work $url = $image_helper->ResizeImage($value['tmp_name'] . '.jpg', $storage_format); $tmp_name = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', '/', $url); - rename($tmp_name, $tmp_path . $fname); + rename($tmp_name, $tmp_path . $filename); } else { - move_uploaded_file($value['tmp_name'], $tmp_path . $fname); + move_uploaded_file($value['tmp_name'], $tmp_path . $filename); } - echo preg_replace('/^' . preg_quote($id, '/') . '_/', '', $fname); + echo preg_replace('/^' . preg_quote($id, '/') . '_/', '', $filename); $this->deleteTempFiles($tmp_path); @@ -3200,23 +3213,23 @@ } /** - * Returns upload field definition + * Gets storage format for a given field * - * @param kEvent $event * @param string $field_name - * @return Array + * @param kEvent $event + * @return bool * @access protected */ - protected function _getUploadFieldDefinition(kEvent $event, $field_name) + protected function _getStorageFormat($field_name, kEvent $event) { $config = $event->getUnitConfig(); - $ret = $config->getFieldByName($field_name); + $field_options = $config->getFieldByName($field_name); - if ( !$ret ) { - $ret = $config->getVirtualFieldByName($field_name); + if ( !$field_options ) { + $field_options = $config->getVirtualFieldByName($field_name); } - return $ret; + return isset($field_options['storage_format']) ? $field_options['storage_format'] : false; } /** @@ -3290,21 +3303,31 @@ protected function OnDeleteFile(kEvent $event) { $event->status = kEvent::erSTOP; - $filename = $this->_getUploadedFileInfo($event, 'full_path'); + $filename = $this->_getSafeFilename(); - if ( $filename === false ) { + if ( !$filename ) { return; } $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ - $var_name = $object->getPendingActionVariableName(); - $schedule = $this->Application->RecallVar($var_name); - $schedule = $schedule ? unserialize($schedule) : Array (); - $schedule[] = Array ('action' => 'delete', 'file' => $filename); + $field_id = $this->Application->GetVar('field_id'); - $this->Application->StoreVar($var_name, serialize($schedule)); + if ( !preg_match_all('/\[([^\[\]]*)\]/', $field_id, $regs) ) { + return; + } + + $field = $regs[1][1]; + $record_id = $regs[1][0]; + $pending_actions = $object->getPendingActions($record_id); + $upload_dir = $object->GetFieldOption($field, 'upload_dir'); + + $pending_actions[] = Array ( + 'action' => 'delete', 'id' => $record_id, 'field' => $field, 'file' => FULL_PATH . $upload_dir . $filename + ); + + $object->setPendingActions($pending_actions, $record_id); } /** @@ -3317,21 +3340,37 @@ protected function OnViewFile(kEvent $event) { $event->status = kEvent::erSTOP; + $filename = $this->_getSafeFilename(); - if ( $this->Application->GetVar('thumb') ) { - $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ + if ( !$filename ) { + return; + } - $field = $this->Application->GetVar('field'); - $url = $this->_getUploadedFileInfo($event, $object->GetFieldOption($field, 'thumb_format')); + $object = $event->getObject(Array ('skip_autoload' => true)); + /* @var $object kDBItem */ + + $field = $this->Application->GetVar('field'); + $options = $object->GetFieldOptions($field); + + // set current uploaded file + if ( $this->Application->GetVar('tmp') ) { + $options['upload_dir'] = WRITEBALE_BASE . '/tmp/'; + unset($options['include_path']); + $object->SetFieldOptions($field, $options); + + $object->SetDBField($field, $this->Application->GetVar('id') . '_' . $filename); } else { - $url = $this->_getUploadedFileInfo($event, 'full_url'); + $object->SetDBField($field, $filename); } - if ( $url === false ) { - return; + // get url to uploaded file + if ( $this->Application->GetVar('thumb') ) { + $url = $object->GetField($field, $options['thumb_format']); } + else { + $url = $object->GetField($field, 'raw_url'); + } $file_helper = $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ @@ -3344,51 +3383,31 @@ header('Content-Length: ' . filesize($path)); $this->Application->setContentType(kUtil::mimeContentType($path), false); - header('Content-Disposition: inline; filename="' . basename($path) . '"'); + header('Content-Disposition: inline; filename="' . kUtil::removeTempExtension($filename) . '"'); readfile($path); } /** - * Returns information about uploaded file + * Returns safe version of filename specified in url * - * @param kEvent $event - * @param string $format - * @return bool + * @return bool|string * @access protected */ - protected function _getUploadedFileInfo(kEvent $event, $format) + protected function _getSafeFilename() { - $file = $this->Application->GetVar('file'); + $filename = $this->Application->GetVar('file'); if ( !$this->Application->isAdmin ) { - $file = htmlspecialchars_decode($file); + $filename = htmlspecialchars_decode($filename); } - if ( (strpos($file, '../') !== false) || (trim($file) !== $file) ) { + if ( (strpos($filename, '../') !== false) || (trim($filename) !== $filename) ) { // when relative paths or special chars are found template names from url, then it's hacking attempt return false; } - $object = $event->getObject(Array ('skip_autoload' => true)); - /* @var $object kDBItem */ - - $field = $this->Application->GetVar('field'); - $options = $object->GetFieldOptions($field); - - // set current uploaded file - if ( $this->Application->GetVar('tmp') ) { - $options['upload_dir'] = WRITEBALE_BASE . '/tmp/'; - unset($options['include_path']); - $object->SetFieldOptions($field, $options); - - $object->SetDBField($field, $this->Application->GetVar('id') . '_' . $file); - } - else { - $object->SetDBField($field, $file); - } - - return $object->GetField($field, $format); + return $filename; } /** @@ -3434,6 +3453,7 @@ list ($id, $field_values) = each($items_info); $object->Load($id); $object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values)); + $event->setEventParam('form_data', $field_values); $object->setID($id); $response = Array ('status' => 'OK'); @@ -3444,13 +3464,14 @@ $error_field = $object->GetFieldOption($field, 'error_field', false, $field); if ( !$object->Validate() && $object->GetErrorPseudo($error_field) ) { - $response['status'] = $object->GetErrorMsg($error_field); + $response['status'] = $object->GetErrorMsg($error_field, false); } $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); /* @var $ajax_form_helper AjaxFormHelper */ $response['other_errors'] = $ajax_form_helper->getErrorMessages($object); + $response['uploader_info'] = $ajax_form_helper->getUploaderInfo($object, array_keys($field_values)); $event->status = kEvent::erSTOP; // since event's OnBefore... events can change this event status echo json_encode($response); @@ -3499,7 +3520,7 @@ echo ''; foreach ($data as $item) { - echo '' . htmlspecialchars($item, null, CHARSET) . ''; + echo '' . kUtil::escape($item, kUtil::ESCAPE_HTML) . ''; } echo '';