Index: trunk/core/kernel/startup.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/startup.php (.../startup.php) (revision 932) +++ trunk/core/kernel/startup.php (.../startup.php) (revision 1339) @@ -1,50 +1,91 @@ ',$label,'
',print_r($data,true),'
'; - //echo var_dump($data); -} -function safeDefine($const_name, $const_value) -{ - if(!defined($const_name)) define($const_name,$const_value); -} +// global constants +define('HAVING_FILTER', 1); +define('WHERE_FILTER', 2); +define('FLT_TYPE_AND', 'AND'); +define('FLT_TYPE_OR', 'OR'); + ?> \ No newline at end of file Index: trunk/core/kernel/db/db_tag_processor.php =================================================================== diff -u -r938 -r1339 --- trunk/core/kernel/db/db_tag_processor.php (.../db_tag_processor.php) (revision 938) +++ trunk/core/kernel/db/db_tag_processor.php (.../db_tag_processor.php) (revision 1339) @@ -3,6 +3,108 @@ class kDBTagProcessor extends TagProcessor { /** + * Description + * + * @var DBConnection + * @access public + */ + var $Conn; + + function kDBTagProcessor() + { + parent::kBase(); + $this->Conn =& $this->Application->GetADODBConnection(); + } + + /** + * Returns view menu name for current prefix + * + * @param Array $params + * @return string + */ + function GetItemName($params) + { + $item_name = $this->Application->getUnitOption($this->Prefix,'ViewMenuPhrase'); + return $this->Application->Phrase($item_name); + } + + /** + * Draw filter menu content (for ViewMenu) based on filters defined in config + * + * @param Array $params + * @return string + */ + function DrawFilterMenu($params) + { + $block_params = $this->prepareTagParams($params); + $block_params['name'] = $params['spearator_block']; + $separator = $this->Application->ParseBlock($block_params); + $filter_menu = $this->Application->getUnitOption($this->Prefix,'FilterMenu'); + + // Params: label, filter_action, filter_status + $block_params['name'] = $params['item_block']; + + $view_filter = $this->Application->RecallVar($this->getPrefixSpecial().'_view_filter'); + if($view_filter === false) + { + $event_params = Array('prefix'=>$this->Prefix,'special'=>$this->Special,'name'=>'OnRemoveFilters'); + $this->Application->HandleEvent( new kEvent($event_params) ); + $view_filter = $this->Application->RecallVar($this->getPrefixSpecial().'_view_filter'); + } + $view_filter = unserialize($view_filter); + + $filters = Array(); + $prefix_special = $this->getPrefixSpecial(); + foreach($filter_menu['Filters'] as $filter_key => $filter_params) + { + if(!$filter_params) + { + $filters[] = $separator; + continue; + } + + $block_params['label'] = addslashes( $this->Application->Phrase($filter_params['label']) ); + if( getArrayValue($view_filter,$filter_key) ) + { + $submit = 0; + $status = 1; + } + else + { + $submit = 1; + $status = 0; + } + $block_params['filter_action'] = 'set_filter("'.$prefix_special.'","'.$filter_key.'","'.$submit.'");'; + $block_params['filter_status'] = $status; + $filters[] = $this->Application->ParseBlock($block_params); + } + return implode('', $filters); + } + + function IterateGridFields($params) + { + $mode = $params['mode']; + $def_block = $params['block']; + + $grids = $this->Application->getUnitOption($this->Prefix,'Grids'); + $grid_config = $grids[$params['grid']]['Fields']; + + $std_params['pass_params']='true'; + $std_params['PrefixSpecial']=$this->getPrefixSpecial(); + + $o = ''; + foreach ($grid_config as $field => $options) { + $block_params = Array(); + $block_params['name'] = isset($options[$mode.'_block']) ? $options[$mode.'_block'] : $def_block; + $block_params['field'] = $field; + $block_params['sort_field'] = isset($options['sort_field']) ? $options['sort_field'] : $field; + $block_params = array_merge($std_params, $block_params, $options); + $o.= $this->Application->ParseBlock($block_params, 1); + } + return $o; + } + + /** * Prints list content using block specified * * @param Array $params @@ -13,29 +115,99 @@ { $list =& $this->Application->recallObject( $this->getPrefixSpecial(), $this->Prefix.'_List',$params); $id_field = $this->Application->getUnitOption($this->Prefix,'IDField'); - // + + $list->Query(); + $o = ''; + $list->GoFirst(); - /*$parser =& $this->Application->recallObject('TemplateParser'); + $block_params=$this->prepareTagParams($params); + $block_params['name']=$params['block']; + $block_params['pass_params']='true'; - // only useful in case in inside prinklist block we have another - // tagprocessor who wants to findout out printlist prefix and special - $parser->SetParam('prefix', $this->Prefix); - $parser->SetParam('special', $this->Special);*/ + while (!$list->EOL()) + { + $this->Application->SetVar( $this->getPrefixSpecial().'_id', $list->GetDBField($id_field) ); // for edit/delete links using GET + $o.= $this->Application->ParseBlock($block_params, 1); + $list->GoNext(); + } + $this->Application->SetVar( $this->getPrefixSpecial().'_id', ''); + return $o; + } + + /** + * Prints list content using block specified + * + * @param Array $params + * @return string + * @access public + */ + function PrintList2($params) + { + $prefix_special = $this->getPrefixSpecial(); + $list =& $this->Application->recallObject( $prefix_special, $this->Prefix.'_List',$params); + + if ( !($list->OriginalParams == $params) ) { + $this->Application->removeObject($prefix_special); + $list =& $this->Application->recallObject($prefix_special,$this->Prefix.'_List',$params); + } + + $id_field = $this->Application->getUnitOption($this->Prefix,'IDField'); + $list->Query(); $o = ''; + + $direction = (isset($params['direction']) && $params['direction']=="H")?"H":"V"; + $columns = (isset($params['columns']))?$params['columns']:1; + + if ($columns>1 && $direction=="V") { + $list->Records = $this->LinearToVertical($list->Records, $columns, $params['per_page']); + $list->SelectedCount=count($list->Records); + ksort($list->Records); + } + + $list->GoFirst(); + + $block_params=$this->prepareTagParams($params); $block_params['name']=$params['block']; $block_params['pass_params']='true'; + $block_start_row_params=$this->prepareTagParams($params); + $block_start_row_params['name']=$params['row_start_block']; + + $block_end_row_params=$this->prepareTagParams($params); + $block_end_row_params['name']=$params['row_end_block']; + + $i=0; + + $backup_id=$this->Application->GetVar($this->Prefix."_id"); + while (!$list->EOL()) { - $this->Application->SetVar( $this->getPrefixSpecial().'_id', $list->GetDBField($id_field) ); + if (!$list->getCurrentRecord()){ + //$list->GoNext(); + //continue; + $block_params['name']=$params['block_empty_cell']; + }else{ + $block_params['name']=$params['block']; + } + $this->Application->SetVar( $this->getPrefixSpecial().'_id', $list->GetDBField($id_field) ); // for edit/delete links using GET + $this->Application->SetVar( $this->Prefix.'_id', $list->GetDBField($id_field) ); + + if ($i%$params['columns'] == 0) $o.= $this->Application->ParseBlock($block_start_row_params, 1); $o.= $this->Application->ParseBlock($block_params, 1); + if (($i+1)%$params['columns'] == 0) $o.= $this->Application->ParseBlock($block_end_row_params, 1); + $list->GoNext(); + + $i++; } + + $this->Application->SetVar( $this->Prefix.'_id', $backup_id); + $this->Application->SetVar( $this->getPrefixSpecial().'_id', ''); return $o; } @@ -58,7 +230,7 @@ } /** - * Get's reuested field value + * Get's requested field value * * @param Array $params * @return string @@ -69,13 +241,105 @@ $field = $params['field']; $object =& $this->Application->recallObject($this->getPrefixSpecial(),$this->Prefix, $params); - $value = $object->GetField($field); + if ( getArrayValue($params, 'db') !== false ) + { + $value = $object->GetDBField($field); + } + else + { + $format = getArrayValue($params, 'format'); + if (!$format) { + $format = null; + } + else { + if (preg_match("/_regional_(.*)/", $format, $regs)) { + $lang = $this->Application->recallObject('lang'); + $format = $lang->GetDBField($regs[1]); + } + } + $value = $object->GetField($field, $format); + } - if (isset($params['nl2br'])) $value = nl2br($value); + if( getArrayValue($params,'nl2br' ) ) $value = nl2br($value); + if( !getArrayValue($params,'no_special') ) $value = htmlspecialchars($value); + if( getArrayValue($params,'checked' ) ) $value = ($value == 1) ? 'checked' : ''; + if( getArrayValue($params,'as_label') ) $value = $this->Application->Phrase($value); + $cut_first = getArrayValue($params,'cut_first'); + if($cut_first) + { + $needs_cut = strlen($value) > $cut_first; + $value = substr($value,0,$cut_first); + if($needs_cut) $value .= ' ...'; + } + + if ($value != '') $this->Application->Parser->DataExists = true; + return $value; } + function Error($params) + { + $field = $params['field']; + $object =& $this->Application->recallObject($this->getPrefixSpecial(),$this->Prefix, $params); + + $msg = $object->GetErrorMsg($field); + + return $msg; + } + + function HasError($params) + { + return $this->Error($params) != ''; + } + + function IsRequired($params) + { + $field = $params['field']; + $object =& $this->Application->recallObject($this->getPrefixSpecial(),$this->Prefix, $params); + + $options = $object->GetFieldOptions($field); + return getArrayValue($options,'required'); + } + + function PredefinedOptions($params) + { + $field = $params['field']; + $object =& $this->Application->recallObject($this->getPrefixSpecial(),$this->Prefix, $params); + + $value = $object->GetDBField($field); + $options = $object->GetFieldOptions($field); + + $block_params['name'] = $params['block']; + $block_params['pass_params'] = 'true'; + $selected = $params['selected']; + + $o = ''; + foreach ($options['options'] as $key => $val) { + $block_params['key'] = $key; + $block_params['option'] = $val; + $block_params[$selected] = ( $key == $value ? ' '.$selected : ''); + $o .= $this->Application->ParseBlock($block_params, 1); + } + + return $o; + } + + function Format($params) + { + $field = $params['field']; + $object =& $this->Application->recallObject($this->getPrefixSpecial(),$this->Prefix, $params); + + $options = $object->GetFieldOptions($field); + if (isset($options['formatter']) && (isset($params['human']) || isset($params['edit_size'])) ) { + $formatter =& $this->Application->recallObject($options['formatter']); + $format = $formatter->HumanFormat($options['format']); + return isset($params['edit_size']) ? strlen($format) : $format; + } + + return $options['format']; + } + /** * Print grid pagination using * block names specified @@ -86,37 +350,90 @@ */ function PrintPages($params) { + $prefix_special=$this->getPrefixSpecial(); $object =& $this->Application->recallObject($prefix_special,$this->Prefix.'_List',$params); + + if ( !($object->OriginalParams == $params) ) { + $this->Application->removeObject($prefix_special); + $object =& $this->Application->recallObject($prefix_special,$this->Prefix.'_List',$params); + } + $total = $object->GetTotalPages(); $o = ''; + // what are these 2 lines for? $this->Application->SetVar($prefix_special.'_event',''); $this->Application->SetVar($prefix_special.'_id',''); - $current_page = $this->Application->RecallVar($prefix_special.'_page'); + $current_page = $this->Application->RecallVar($prefix_special.'_Page'); - $block_params=$this->prepareTagParams($params); + $block_params = $this->prepareTagParams($params); - for ($i=1; $i<=$total; $i++) + $split = ( isset($params['split'])?$params['split']:10 ); + + $split_start = $current_page - ceil($split/2); + if ($split_start < 1){ + $split_start = 1; + } + + $split_end = $split_start + $split-1; + if ($split_end > $total) { + $split_end = $total; + $split_start = max($split_end - $split + 1, 1); + } + + if ($current_page > 1){ + $prev_block_params=array(); + + if ($total > $split){ + $prev_block_params['page']=max($current_page-$split, 1); + $prev_block_params['name'] = getArrayValue($params, 'prev_page_split_block'); + if ($prev_block_params['name']){ + $o .= $this->Application->ParseBlock($prev_block_params, 1); + } + } + + $prev_block_params['name']="page"; + $prev_block_params['page']=$current_page-1; + $prev_block_params['name'] = getArrayValue($params, 'prev_page_block'); + if ($prev_block_params['name']){ + $o .= $this->Application->ParseBlock($prev_block_params, 1); + } + } + + + for ($i = $split_start; $i<=$split_end; $i++) { - $this->Application->SetVar($prefix_special.'_page',$i); + //$this->Application->SetVar($prefix_special.'_Page',$i); $block = $params[ (($i==$current_page)?'active':'inactive').'_block' ]; $block_params['name']=$block; $block_params['page']=$i; $o .= $this->Application->ParseBlock($block_params, 1); } + if ($current_page < $total){ + $next_block_params=array(); + $next_block_params['page']=$current_page+1; + $next_block_params['name'] = getArrayValue($params, 'next_page_block'); + if ($next_block_params['name']){ + $o .= $this->Application->ParseBlock($next_block_params, 1); + } + if ($total > $split){ + $next_block_params['page']=min($current_page+$split, $total); + $next_block_params['name'] = getArrayValue($params, 'next_page_split_block'); + if ($next_block_params['name']){ + $o .= $this->Application->ParseBlock($next_block_params, 1); + } + } + } + + + + return $o; } - - /*function MapField($params) - { - $object =& $this->Application->recallObject($this->Prefix.'.'.$this->Special); - $value = $object->GetField($params['field']); - $this->Application->SetVar($params['var_name'],$value); - }*/ /** * Returns input field name to @@ -131,11 +448,398 @@ { $prefix_special=$this->getPrefixSpecial(); $item = $this->Application->recallObject($prefix_special); - return $prefix_special.'['.$item->GetID().']['.$params['field'].']'; + + if ( $formatter = getArrayValue($item->Fields, $params['field'], 'formatter') ) { + if ( $formatter == 'kMultiLanguage' ) { + $formatter =& $this->Application->recallObject($formatter); + $params['field'] = $formatter->LangFieldName($params['field']); + } + } + + if ( $idfield = getArrayValue($params, 'IdField') ) { + $id = $item->GetDBField($idfield); + } + else { + $id = $item->GetID(); + } + + return $prefix_special.'['.$id.']['.$params['field'].']'; } + + + /** + * Returns index where 1st changable sorting field begins + * + * @return int + * @access private + */ + function getUserSortIndex() + { + $list_sortings = $this->Application->getUnitOption($this->Prefix, 'ListSortings'); + $sorting_prefix = getArrayValue($list_sortings, $this->Special) ? $this->Special : ''; + + $user_sorting_start = 0; + if ( $forced_sorting = getArrayValue($list_sortings, $sorting_prefix, 'ForcedSorting') ) { + $user_sorting_start = count($forced_sorting); + } + return $user_sorting_start; + } + + /** + * Returns order direction for given field + * + * + * + * @param Array $params + * @return string + * @access public + */ + function Order($params) + { + $field = $params['field']; + $user_sorting_start = $this->getUserSortIndex(); + + $object =& $this->Application->recallObject( $this->getPrefixSpecial() ); + + if ($object->GetOrderField($user_sorting_start) == $field) + { + return $object->GetOrderDirection($user_sorting_start); + } + elseif($object->GetOrderField($user_sorting_start+1) == $field) + { + return '2_'.$object->GetOrderDirection($user_sorting_start+1); + } + else + { + return 'no'; + } + } + /** + * Get's information of sorting field at "pos" position, + * like sorting field name (type="field") or sorting direction (type="direction") + * + * @param Array $params + * @return mixed + */ + function OrderInfo($params) + { + $user_sorting_start = $this->getUserSortIndex() + --$params['pos']; + $object =& $this->Application->recallObject( $this->getPrefixSpecial() ); + + if($params['type'] == 'field') return $object->GetOrderField($user_sorting_start); + if($params['type'] == 'direction') return $object->GetOrderDirection($user_sorting_start); + } + /** + * Checks if sorting field/direction matches passed field/direction parameter + * + * @param Array $params + * @return bool + */ + function IsOrder($params) + { + $params['type'] = isset($params['field']) ? 'field' : 'direction'; + $value = $this->OrderInfo($params); + + if( isset($params['field']) ) return $params['field'] == $value; + if( isset($params['direction']) ) return $params['direction'] == $value; + } + + /** + * Returns list perpage + * + * @param Array $params + * @return int + */ + function PerPage($params) + { + $object =& $this->Application->recallObject( $this->getPrefixSpecial() ); + return $object->PerPage; + } + + /** + * Checks if list perpage matches value specified + * + * @param Array $params + * @return bool + */ + function PerPageEquals($params) + { + $object =& $this->Application->recallObject( $this->getPrefixSpecial() ); + return $object->PerPage == $params['value']; + } + + function SaveEvent($params) + { + // SaveEvent is set during onbuild, but we may need it before any other tag calls onBuild + $prefix_special = $this->getPrefixSpecial(); + $item = $this->Application->recallObject($prefix_special); + + return $this->Application->GetVar($prefix_special.'_SaveEvent'); + } + + function NextId($params) + { + $prefix_special=$this->getPrefixSpecial(); + $ids = explode(',', $this->Application->RecallVar($prefix_special.'_selected_ids')); + $item = $this->Application->recallObject($prefix_special); + $cur_id = $item->GetId(); + + $i = array_search($cur_id,$ids); + if ($i !== false) { + return $i < count($ids)-1 ? $ids[$i+1] : ''; + } + return ''; + } + + function PrevId($params) + { + $prefix_special=$this->getPrefixSpecial(); + $ids = explode(',', $this->Application->RecallVar($prefix_special.'_selected_ids')); + $item = $this->Application->recallObject($prefix_special); + $cur_id = $item->GetId(); + + $i = array_search($cur_id,$ids); + if ($i !== false) { + return $i > 0 ? $ids[$i-1] : ''; + } + return ''; + } + + function IsSingle($params) + { + return ($this->NextId($params) === '' && $this->PrevId($params) === ''); + } + + function IsLast($params) + { + return ($this->NextId($params) === ''); + } + + function IsFirst($params) + { + return ($this->PrevId($params) === ''); + } + + /** + * Checks if field value is equal to proposed one + * + * @param Array $params + * @return bool + */ + function FieldEquals($params) + { + $object =& $this->Application->recallObject($this->getPrefixSpecial(),$this->Prefix, $params); + $ret = $object->GetDBField($params['field']) == $params['value']; + if( getArrayValue($params,'inverse') ) $ret = !$ret; + return $ret; + } + + function ItemIcon($params) + { + $object =& $this->Application->recallObject($this->getPrefixSpecial(),$this->Prefix, $params); + + $grids = $this->Application->getUnitOption($this->Prefix,'Grids'); + $icons =& $grids[ $params['grid'] ]['Icons']; + + $key = ''; + $status_fields = $this->Application->getUnitOption($this->Prefix,'StatusField'); + if(!$status_fields) return $icons['default']; + + foreach($status_fields as $status_field) + { + $key .= $object->GetDBField($status_field).'_'; + } + $key = rtrim($key,'_'); + $value = ($key !== false) ? $key : 'default'; + + return isset($icons[$value]) ? $icons[$value] : $icons['default']; + } + + function SectionTitle($params) + { + $title_presets = $this->Application->getUnitOption($this->Prefix,'TitlePresets'); + $title_info = getArrayValue($title_presets, $params['title_preset'] ); + if($title_info === false) return $params['title']; + + if( getArrayValue($title_presets,'default') ) + { + // use default labels + custom labels specified in preset used + $title_info = array_merge_recursive2($title_presets['default'], $title_info); + } + + $title = $title_info['format']; + + // 1. get objects in use for title construction + $objects = Array(); + $object_status = Array(); + $status_labels = Array(); + + $prefixes = getArrayValue($title_info,'prefixes'); + if($prefixes) + { + foreach($prefixes as $prefix_special) + { + $prefix_data = $this->Application->processPrefix($prefix_special); + $prefix_data['prefix_special'] = rtrim($prefix_data['prefix_special'],'.'); + $objects[ $prefix_data['prefix_special'] ] =& $this->Application->recallObject($prefix_data['prefix_special'], $prefix_data['prefix'], $params); + $object_status[ $prefix_data['prefix_special'] ] = $objects[ $prefix_data['prefix_special'] ]->GetID() ? 'edit' : 'new'; + + // a. set object's status field (adding item/editing item) for each object in title + if( getArrayValue($title_info[ $object_status[ $prefix_data['prefix_special'] ].'_status_labels' ],$prefix_data['prefix_special']) ) + { + $status_labels[ $prefix_data['prefix_special'] ] = $title_info[ $object_status[ $prefix_data['prefix_special'] ].'_status_labels' ][ $prefix_data['prefix_special'] ]; + $title = str_replace('#'.$prefix_data['prefix_special'].'_status#', $status_labels[ $prefix_data['prefix_special'] ], $title); + } + + // b. setting object's titlefield value (in titlebar ONLY) to default in case if object beeing created with no titlefield filled in + if( $object_status[ $prefix_data['prefix_special'] ] == 'new' ) + { + $new_value = $this->getInfo( $objects[ $prefix_data['prefix_special'] ], 'titlefield' ); + if(!$new_value && getArrayValue($title_info['new_titlefield'],$prefix_data['prefix_special']) ) $new_value = $this->Application->Phrase($title_info['new_titlefield'][ $prefix_data['prefix_special'] ]); + $title = str_replace('#'.$prefix_data['prefix_special'].'_titlefield#', $new_value, $title); + } + } + } + + // 2. replace phrases if any found in format string + $title = $this->Application->ReplaceLanguageTags($title); + + // 3. find and replace any replacement vars + preg_match_all('/#(.*_.*)#/Uis',$title,$rets); + if($rets[1]) + { + $replacement_vars = array_keys( array_flip($rets[1]) ); + foreach($replacement_vars as $replacement_var) + { + $var_info = explode('_',$replacement_var,2); + $object =& $objects[ $var_info[0] ]; + $new_value = $this->getInfo($object,$var_info[1]); + $title = str_replace('#'.$replacement_var.'#', $new_value, $title); + } + } + + return $title; + } + + function getInfo(&$object, $info_type) + { + switch ($info_type) + { + case 'titlefield': + $field = $this->Application->getUnitOption($object->Prefix,'TitleField'); + return $field !== false ? $object->GetField($field) : 'TitleField Missing'; + break; + + case 'recordcount': + $of_phrase = $this->Application->Phrase('la_of'); + return $object->NoFilterCount != $object->RecordsCount ? $object->RecordsCount.' '.$of_phrase.' '.$object->NoFilterCount : $object->RecordsCount; + break; + + default: + break; + } + } + + /** + * Parses block depending on its element type. + * For radio and select elements values are taken from 'value_list_field' in key1=value1,key2=value2 + * format. key=value can be substituted by SELECT f1 AS OptionName, f2 AS OptionValue... FROM TableName + * where prefix is TABLE_PREFIX + * + * @param Array $params + * @return string + */ + function ConfigFormElement($params) + { + $object =& $this->Application->recallObject( $this->getPrefixSpecial() ); + $field = $params['field']; + + $helper = $this->Application->recallObject('InpCustomFieldsHelper'); + + $element_type = $object->GetDBField($params['element_type_field']); + + if($element_type=='label') $element_type='text'; + $params['name']= $params['blocks_prefix'] . $element_type; + + switch ($element_type){ + case 'select': + case 'radio': + $field_options = $object->GetFieldOptions($field, 'options'); + $field_options['options'] = $helper->GetValuesHash( $object->GetDBField($params['value_list_field']) ); + $object->SetFieldOptions($field, $field_options); + break; + + case 'textarea': + $params['field_params'] = $helper->ParseConfigSQL($object->GetDBField($params['value_list_field'])); + break; + + case 'password': + case 'text': + case 'checkbox': + default: + break; + } + return $this->Application->ParseBlock($params, 1); + } + + /** + * Get's requested custom field value + * + * @param Array $params + * @return string + * @access public + */ + function CustomField($params) + { + $prefix = $this->getPrefixSpecial(); + $object =& $this->Application->recallObject( $prefix ); + + $sql = ' SELECT cv.Value FROM '.TABLE_PREFIX.'CustomField cf + LEFT JOIN '.TABLE_PREFIX.'CustomMetaData cv + ON cf.CustomFieldId = cv.CustomFieldId + WHERE cf.Type = '.$this->Application->getUnitOption($prefix, 'ItemType').' + AND cv.ResourceId = '.$object->GetDBField('ResourceId').' + AND cf.FieldName = "'.$params['field'].'"'; + return $this->Conn->GetOne($sql); + } + + /** + * transposes 1-dimensional array elements for vertical alignment according to given columns and per_page parameters + * + * @param array $arr + * @param int $columns + * @param int $per_page + * @return array + */ + function LinearToVertical(&$arr, $columns, $per_page){ + $rows=$columns; + $cols=min(ceil($per_page/$columns), ceil(sizeof($arr)/$columns)); + $imatrix=array(); + for ($row=0; $row<$rows; $row++) { + for ($col=0; $col<$cols; $col++){ + $imatrix[$col*$rows+$row]=$arr[$row*$cols+$col]; + } + } + ksort($imatrix); + reset($imatrix); + return $imatrix; + } + + function SaveWarning($params){ + + $top_prefix = $this->Application->GetTopmostPrefix($this->Prefix); + + if ( $this->Application->GetVar($top_prefix.'_mode') == 't' && $this->Application->RecallVar($top_prefix."_modified")=="1"){ + return $this->Application->ParseBlock($params); + }else{ + $this->Application->RemoveVar($top_prefix."_modified"); + return ""; + } + } + } ?> \ No newline at end of file Index: trunk/core/kernel/session/session.php =================================================================== diff -u -r939 -r1339 --- trunk/core/kernel/session/session.php (.../session.php) (revision 939) +++ trunk/core/kernel/session/session.php (.../session.php) (revision 1339) @@ -61,52 +61,60 @@ class SessionStorage extends kDBBase { var $Expiration; + var $SessionTimeout=0; var $OriginalData=Array(); + var $TimestampField; + var $SessionDataTable; + var $DataValueField; + var $DataVarField; + + function Init($prefix,$special) + { + parent::Init($prefix,$special); + $this->setTableName('sessions'); + $this->setIDField('sid'); + $this->TimestampField = 'expire'; + $this->SessionDataTable = 'SessionData'; + $this->DataValueField = 'value'; + $this->DataVarField = 'var'; + } + + function setSessionTimeout($new_timeout) + { + $this->SessionTimeout = $new_timeout; + } + function StoreSession(&$session) { - $query = sprintf( "INSERT INTO %sSessions (sid, expire) VALUES (%s, %s)", - TABLE_PREFIX, - $session->SID, - $session->Expiration); + $query = ' INSERT INTO '.$this->TableName.' ('.$this->IDField.', '.$this->TimestampField.')'. + ' VALUES ('.$this->Conn->qstr($session->SID).', '.$session->Expiration.')'; $this->Conn->Query($query); } function DeleteSession(&$session) { - $query = sprintf( "DELETE FROM %sSessions WHERE %s = %s", - TABLE_PREFIX, - 'sid', - $session->SID); + $query = ' DELETE FROM '.$this->TableName.' WHERE '.$this->IDField.' = '.$this->Conn->qstr($session->SID); $this->Conn->Query($query); - $query = sprintf( "DELETE FROM %sSessionData WHERE %s = %s", - TABLE_PREFIX, - 'sid', - $session->SID); + $query = ' DELETE FROM '.$this->SessionDataTable.' WHERE '.$this->IDField.' = '.$this->Conn->qstr($session->SID); $this->Conn->Query($query); $this->OriginalData = Array(); } - function UpdateSession(&$session) + function UpdateSession(&$session, $timeout=0) { - $query = sprintf( "UPDATE %sSessions SET expire = %s WHERE %s = %s", - TABLE_PREFIX, - $session->Expiration, - 'sid', - $session->SID); + $query = ' UPDATE '.$this->TableName.' SET '.$this->TimestampField.' = '.$session->Expiration.' WHERE '.$this->IDField.' = '.$this->Conn->qstr($session->SID); $this->Conn->Query($query); } function LocateSession($sid) { - $query = sprintf( "SELECT expire FROM %sSessions WHERE %s = %s", - TABLE_PREFIX, - 'sid', - $sid); + $query = ' SELECT '.$this->TimestampField.' FROM '.$this->TableName.' WHERE '.$this->IDField.' = '.$this->Conn->qstr($sid); $result = $this->Conn->GetOne($query); + if($result===false) return false; $this->Expiration = $result; @@ -120,16 +128,27 @@ function LoadData(&$session) { - $query = sprintf( "SELECT value,name FROM %sSessionData WHERE %s = %s", - TABLE_PREFIX, - 'sid', - $session->SID); - $this->OriginalData = $this->Conn->GetCol($query,'name'); + $query = 'SELECT '.$this->DataValueField.','.$this->DataVarField.' FROM '.$this->SessionDataTable.' WHERE '.$this->IDField.' = '.$this->Conn->qstr($session->SID); + + $this->OriginalData = $this->Conn->GetCol($query, $this->DataVarField); return $this->OriginalData; } + /** + * Enter description here... + * + * @param Session $session + * @param string $var_name + */ + function GetField(&$session, $var_name) + { + return $this->Conn->GetOne('SELECT '.$var_name.' FROM '.$this->TableName.' WHERE `'.$this->IDField.'` = '.$this->Conn->qstr($session->GetID()) ); + } + function SaveData(&$session) { + if(!$session->SID) return false; // can't save without sid + $ses_data = $session->Data->GetParams(); $replace = ''; @@ -142,31 +161,46 @@ else { $replace .= sprintf("(%s, %s, %s),", - $session->SID, + $this->Conn->qstr($session->SID), $this->Conn->qstr($key), $this->Conn->qstr($value)); } } $replace = rtrim($replace, ','); if ($replace != '') { - $query = sprintf( 'REPLACE INTO %sSessionData (sid, name, value) VALUES %s', - TABLE_PREFIX, - $replace); + $query = ' REPLACE INTO '.$this->SessionDataTable. ' ('.$this->IDField.', '.$this->DataVarField.', '.$this->DataValueField.') VALUES '.$replace; $this->Conn->Query($query); } } function RemoveFromData(&$session, $var) { - $query = sprintf( "DELETE FROM %sSessionData WHERE %s = %s AND %s = %s", - TABLE_PREFIX, - 'sid', - $session->SID, - 'name', - $this->Conn->qstr($var)); + $query = 'DELETE FROM '.$this->SessionDataTable.' WHERE '.$this->IDField.' = '.$this->Conn->qstr($session->SID). + ' AND '.$this->DataVarField.' = '.$this->Conn->qstr($var); $this->Conn->Query($query); unset($this->OriginalData[$var]); } + + function GetExpiredSIDs() + { + $query = ' SELECT '.$this->IDField.' FROM '.$this->TableName.' WHERE '.$this->TimestampField.' > '.time(); + return $this->Conn->GetCol($query); + } + + function DeleteExpired() + { + $expired_sids = $this->GetExpiredSIDs(); + if($expired_sids) + { + $where_clause=' WHERE '.$this->IDField.' IN ("'.implode('","',$expired_sids).'")'; + $sql = 'DELETE FROM '.$this->SessionDataTable.$where_clause; + $this->Conn->Query($sql); + + $sql = 'DELETE FROM '.$this->TableName.$where_clause; + $this->Conn->Query($sql); + } + return $expired_sids; + } } define('smAUTO', 1); @@ -231,7 +265,8 @@ function InitStorage() { - $this->Storage =& new SessionStorage(); + $this->Storage =& $this->Application->recallObject('SessionStorage'); + $this->Storage->setSessionTimeout($this->SessionTimeout); } function Init($prefix,$special) @@ -242,6 +277,19 @@ $this->Checkers = Array(); $this->InitStorage(); $this->Data =& new Params(); + + $tmp_sid = $this->GetPassedSIDValue(); + $expired_sids = $this->DeleteExpired(); + if( ( $expired_sids && in_array($tmp_sid,$expired_sids) ) || ( $tmp_sid && !$this->Check() ) ) + { + $event = new kEvent(); + $event->Init('login',''); + $event->Name = 'OnSessionExpire'; + $this->SID=''; + $this->SetSessionCookie(); + $this->Application->HandleEvent($event); + } + if ($this->Check()) { $this->SID = $this->GetPassedSIDValue(); $this->Refresh(); @@ -260,13 +308,14 @@ function CheckIfCookiesAreOn() { - if ($this->Mode == smGET_ONLY) { + if ($this->Mode == smGET_ONLY || (defined('INPORTAL_ENV')&&INPORTAL_ENV && defined('ADMIN')&&ADMIN) ) + { //we don't need to bother checking if we would not use it $this->CookiesEnabled = false; return; } $http_query =& $this->Application->recallObject('HTTPQuery'); - $cookies_on = $http_query->Cookie['cookies_on']; // not good here + $cookies_on = isset($http_query->Cookie['cookies_on']); // not good here if (!$cookies_on) { //If referer is our server, but we don't have our cookies_on, it's definetly off @@ -313,7 +362,7 @@ function LoadSession($sid) { - if ($this->Storage->LocateSession($sid)) { + if( $this->Storage->LocateSession($sid) ) { //if we have session with such SID - get its expiration $this->Expiration = $this->Storage->GetExpiration(); @@ -336,7 +385,7 @@ switch ($this->Mode) { case smAUTO: //Cookies has the priority - we ignore everything else - $sid=$this->CookiesEnabled?$http_query->Cookie[$this->CookieName]:$get_sid; + $sid=$this->CookiesEnabled ? getArrayValue($http_query->Cookie,$this->CookieName) : $get_sid; break; case smCOOKIES_ONLY: $sid = $http_query->Cookie[$this->CookieName]; @@ -487,8 +536,34 @@ $this->Data->AddParams($this->Storage->LoadData($this)); } + function PrintSession($comment='') + { + if( $this->Application->isDebugMode() && dbg_ConstOn('DBG_SHOW_SESSIONDATA') ) + { + global $debugger; + $debugger->appendHTML('SessionStorage ('.$comment.'):'); + $session_data = $this->Data->GetParams(); + ksort($session_data); + foreach($session_data as $session_key => $session_value) + { + if( preg_match('/a:([\d]+):{/',$session_value) ) + { + $session_data[$session_key] = unserialize($session_value); + } + } + $debugger->dumpVars($session_data); + + // to insert after HTTPQuery if it's visible + $new_row = dbg_ConstOn('DBG_SHOW_HTTPQUERY') ? 4 : 2; + + //$debugger->moveAfterRow($new_row,2); + } + } + function SaveData() { + $this->StoreVar('last_template', basename($_SERVER['PHP_SELF']).'|'.substr($this->Application->BuildEnv($this->Application->GetVar('t'), Array('m_opener' => 'u'), 'all', true), strlen(ENV_VAR_NAME)+1 )); + $this->PrintSession('after save'); $this->Storage->SaveData($this); } @@ -497,17 +572,43 @@ $this->Data->Set($name, $value); } + function StoreVarDefault($name, $value) + { + $tmp = $this->RecallVar($name); + if($tmp === false || $tmp == '') + { + $this->StoreVar($name, $value); + } + } + function RecallVar($name,$default=false) { - $ret=$this->Data->Get($name); - return ($ret===false)?$default:$ret; + $ret = $this->Data->Get($name); + return ($ret===false) ? $default : $ret; } function RemoveVar($name) { $this->Storage->RemoveFromData($this, $name); $this->Data->Remove($name); } + + function GetField($var_name) + { + return $this->Storage->GetField($this, $var_name); + } + + /** + * Deletes expired sessions + * + * @return Array expired sids if any + * @access private + */ + function DeleteExpired() + { + return $this->Storage->DeleteExpired(); + } + } ?> \ No newline at end of file Index: trunk/core/kernel/utility/http_query.php =================================================================== diff -u -r939 -r1339 --- trunk/core/kernel/utility/http_query.php (.../http_query.php) (revision 939) +++ trunk/core/kernel/utility/http_query.php (.../http_query.php) (revision 1339) @@ -57,6 +57,8 @@ */ var $Files; + var $specialsToRemove = Array(); + /** * Loads info from $_POST, $_GET and * related arrays into common place @@ -70,10 +72,34 @@ parent::Params(); $this->Order = $order; $this->AddAllVars(); - $this->processQueryString(); - ini_set("magic_quotes_gpc", 0); + + $this->specialsToRemove = $this->Get('remove_specials'); + if($this->specialsToRemove) + { + $this->_Params = $this->removeSpecials($this->_Params); + } + ini_set('magic_quotes_gpc', 0); } + function removeSpecials($array) + { + $ret = Array(); + $removed = false; + foreach($this->specialsToRemove as $prefix_special => $flag) + { + if($flag) + { + $removed = true; + list($prefix,$special) = explode('.',$prefix_special); + foreach ($array as $key => $val) { + $new_key = preg_match("/^".$prefix."[._]{1}".$special."(.*)/", $key, $regs) ? $prefix.$regs[1] : $key; + $ret[$new_key] = is_array($val) ? $this->removeSpecials($val) : $val; + } + } + } + return $removed ? $ret : $array; + } + /** * All all requested vars to * common storage place @@ -88,10 +114,12 @@ switch ($current) { case 'G': $this->Get =$this->AddVars($_GET); + $this->processQueryString(); break; case 'P': - $my_post = $this->post_convert($_POST); // needed ? + //$my_post = $this->post_convert($_POST); // needed ? $this->Post = $this->AddVars($_POST); + $this->convertPostEvents(); break; case 'C': $this->Cookie = $this->AddVars($_COOKIE); @@ -103,11 +131,106 @@ $this->Server = $this->AddVars($_SERVER); break; case 'F'; - $this->Files = $this->AddVars($_FILES); + $this->convertFiles(); + $this->Files = $this->MergeVars($_FILES, false); //do not strip slashes! break; } } } + + function convertFiles() + { + if (!$_FILES) + { + return false; + } + + $file_keys = Array('error','name','size','tmp_name','type'); + + foreach($_FILES as $file_name => $file_info) + { + if( is_array($file_info['error']) ) + { + $tmp[$file_name] = $this->getArrayLevel( $file_info['error'], $file_name ); + } + else + { + $normal_files[$file_name] = $file_info; + } + } + + $files = $_FILES; + $_FILES = Array(); + + foreach($tmp as $prefix => $prefix_files) + { + $anchor =& $_FILES; + foreach($prefix_files['keys'] as $key) + { + $anchor =& $anchor[$key]; + } + foreach($prefix_files['value'] as $field_name) + { + unset($inner_anchor); + unset($copy); + $work_copy = $prefix_files['keys']; + foreach($file_keys as $file_key) + { + $inner_anchor =& $files[$prefix][$file_key]; + if (isset($copy)) + { + $work_copy = $copy; + } + else + { + $copy = $work_copy; + } + array_shift($work_copy); + foreach($work_copy as $prefix_file_key) + { + $inner_anchor =& $inner_anchor[$prefix_file_key]; + } + $anchor[$field_name][$file_key] = $inner_anchor[$field_name]; + } + } + } + + // keys: img_temp, 0, values: LocalPath, ThumbPath + } + + function getArrayLevel(&$level, $prefix='') + { + $ret['keys'] = $prefix ? Array($prefix) : Array(); + $ret['value'] = Array(); + + foreach($level as $level_key => $level_value) + { + if( is_array($level_value) ) + { + $ret['keys'][] = $level_key; + $tmp = $this->getArrayLevel($level_value); + + $ret['keys'] = array_merge($ret['keys'], $tmp['keys']); + $ret['value'] = array_merge($ret['value'], $tmp['value']); + } + else + { + $ret['value'][] = $level_key; + } + } + + return $ret; + } + + function convertPostEvents() + { + $events = $this->Get('events'); + if (is_array($events)) { + foreach ($events as $prefix_special => $event) { + $this->Set($prefix_special.'_event', $event); + } + } + } /** * Process QueryString only, create @@ -120,39 +243,84 @@ function processQueryString() { // env=SID:TEMPLATE:m-1-1-1-1:l0-0-0:n-0-0-0:bb-0-0-1-1-1-0 - + $env_var =& $this->Get(ENV_VAR_NAME); if($env_var) { + $env_var = str_replace('\:','_&+$$+&_',$env_var); // replace escaped "=" with spec-chars :) + $parts=explode(':',$env_var); - // Save Session ID - $sid=array_shift($parts); - if($sid) $this->Set('sid',$sid); + if (defined('INPORTAL_ENV')) { + $sub_parts = array_shift($parts); + + list($sid, $t) = explode('-', $sub_parts, 2); + + + // Save Session ID + if($sid) $this->Set('sid',$sid); + + // Save Template Name + $t=$this->getTemplateName( $t ); + if(!$t) $t='index'; + $this->Set('t',$t); + } + else { + // Save Session ID + $sid=array_shift($parts); + if($sid) $this->Set('sid',$sid); + + // Save Template Name + $t=$this->getTemplateName( array_shift($parts) ); + if(!$t) $t='index'; + $this->Set('t',$t); + } - // Save Template Name - $t=$this->getTemplateName( array_shift($parts) ); - if(!$t) $t='index'; - $this->Set('t',$t); - if($parts) { $query_maps=Array(); $event_manger =& $this->Application->recallObject('EventManager'); + $passed = Array(); + foreach($parts as $mixed_part) { - $mixed_part=explode('-',$mixed_part); + //In-portal old style env conversion - adds '-' between prefix and first var + $mixed_part = str_replace('_&+$$+&_',':',$mixed_part); + $mixed_part = preg_replace("/^([a-zA-Z]+)([0-9]+)-(.*)/", "$1-$2-$3", $mixed_part); + + $escaped_part = str_replace('\-', '_&+$$+&_', $mixed_part); + $escaped_part = explode('-', $escaped_part); + + $mixed_part = array(); + foreach ($escaped_part as $escaped_val) { + $mixed_part[] = str_replace('_&+$$+&_', '-', $escaped_val); + } + $prefix_special=array_shift($mixed_part); // l.pick, l list($prefix)=explode('.',$prefix_special); - + $query_maps[$prefix_special]=$this->Application->getUnitOption($prefix,'QueryString'); - foreach($query_maps[$prefix_special] as $index => $var_name) + + // if config is not defined for prefix in QueryString, then don't process it + if( $query_maps[$prefix_special] ) { - // l_id, l_page, l_bla-bla-bla - $this->Set($prefix_special.'_'.$var_name,$mixed_part[$index-1]); + array_push($passed, $prefix); + foreach($query_maps[$prefix_special] as $index => $var_name) + { + // l_id, l_page, l_bla-bla-bla + $val = $mixed_part[$index-1]; + if ($val == '') $val = false; + $this->Set($prefix_special.'_'.$var_name, $val); + } } + else + { + unset($query_maps[$prefix_special]); + } + } + $this->Set('passed', implode(',', $passed)); $event_manger->setQueryMaps($query_maps); } } @@ -173,8 +341,15 @@ */ function getTemplateName($querystring_template) { - $t_from_post=$this->Get('t'); - $t=$t_from_post?$t_from_post:$querystring_template; + $t_from_post = $this->Get('t'); + $t= $t_from_post ? $t_from_post : $querystring_template; + + if ( is_numeric($t) ) { + $t = $this->Application->DB->GetOne('SELECT CONCAT(FilePath, \'/\', FileName) FROM '.TABLE_PREFIX.'ThemeFiles + WHERE FileId = '.$t); + } + $t = preg_replace("/\.tpl$/", '', $t); + return $t; } @@ -200,40 +375,34 @@ */ function AddVars($array) { - foreach ($array as $key => $val) { - if (get_magic_quotes_gpc()) - { - - if ( is_array($val) ) - { - foreach ($val as $key_array => $val_array) - { - if( is_array($val_array) ) - { - $array[$key][$key_array] = $this->AddVars($val_array); - } - else - { - $array[$key][$key_array] = stripslashes($val_array); - } - - } - $this->Set($key, $array[$key]); - } - else { - $array[$key] = stripslashes($val); - $this->Set($key, $array[$key]); - } - - - } - else { - $this->Set($key, $val); - } + $array = $this->StripSlashes($array); + foreach($array as $key => $value) + { + $this->Set($key,$value); } return $array; } + function MergeVars($array, $strip_slashes=true) + { + if ($strip_slashes) $array = $this->StripSlashes($array); + foreach($array as $key => $value) + { + $this->_Params = array_merge_recursive2($this->_Params, Array($key=>$value)); + } + return $array; + } + + function StripSlashes($array) + { + if( !get_magic_quotes_gpc() ) return $array; + foreach($array as $key=>$value) + { + $array[$key]=is_array($value)?$this->StripSlashes($value):stripslashes($value); + } + return $array; + } + /** * Returns the hash of http params * matching the mask with values Index: trunk/core/kernel/parser/template_parser.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/parser/template_parser.php (.../template_parser.php) (revision 932) +++ trunk/core/kernel/parser/template_parser.php (.../template_parser.php) (revision 1339) @@ -1,6 +1,6 @@ Ses =& $this->Application->recallObject('Session'); + + if (defined('EXPERIMENTAL_PRE_PARSE')) { + $conn =& $this->Application->GetADODBConnection(); + if (isset($this->Application->PreParsedBlocks) && is_array($this->Application->PreParsedBlocks)) return; + $data = $conn->GetRow('SELECT Data, Cached FROM '.TABLE_PREFIX.'Cache WHERE VarName = "blocks_cache"'); + if ($data && $data['Cached'] > (time() - 3600) ) { + $blocks = unserialize($data['Data']); + foreach ($blocks as $name => $f_body) { + $func = create_function('$params', $f_body); + $this->Application->PreParsedBlocks[$name] = $func; + } + $cached = $data['Cached']; + } + else { + $cached = 0; + } + } } function AddParam($pattern, $value, $dont_sort=0) @@ -57,22 +80,20 @@ if (!is_array($params)) $params = Array(); $this->Params = $params; foreach ($params as $key => $val) { - $this->AddParam('/\$'.$key.'/', $val, 1); //Do not sort every time + $this->AddParam('/[{]{0,1}\$'.$key.'[}]{0,1}/i', $val, 1); //Do not sort every time } $this->SortParams(); //Sort once after adding is done } function GetParam($name) { - if (isset($this->Params[$name])) - return $this->Params[$name]; - else - return false; + //return isset($this->Params[strtolower($name)]) ? $this->Params[strtolower($name)] : false; + return isset($this->Params[$name]) ? $this->Params[$name] : false; } function SetParam($name, $value) { - $this->Params[$name] = $value; + $this->Params[strtolower($name)] = $value; } function SetBuffer($body) @@ -85,20 +106,82 @@ return $this->Buffers[$this->RecursionIndex]; } + function GetCode() + { + return $this->Code[$this->RecursionIndex]; + } + function AppendBuffer($append) { $this->Buffers[$this->RecursionIndex] .= $append; + $this->AppendCode( $this->ConvertToCode($append) ); } - function AppendOutput($append) + function AppendOutput($append, $append_code=false) { - if ($this->SkipMode == parse) + if ($this->SkipMode == parse) { $this->Output .= $append; //append to Ouput only if we are parsing + if ($append_code) $this->AppendCompiledHTML($append); + } elseif ($this->SkipMode == skip_tags) { $this->AppendBuffer($append); //append to buffer if we are skipping tags } } + function ConvertToCode($data) + { + $code = '$o .= \''. str_replace("'", "\'", $data) .'\';'; + $code = explode("\n", $code); + return $code; + } + + function AppendCode($code) + { + if (defined('EXPERIMENTAL_PRE_PARSE')) { + if (!isset($this->Code[$this->RecursionIndex])) { + $this->Code[$this->RecursionIndex] = Array(); + } + if (is_array($code)) { + foreach ($code as $line) { + $this->Code[$this->RecursionIndex][] = rtrim($line, "\n")."\n"; + } + } + else { + $this->Code[$this->RecursionIndex][] .= rtrim($code, "\n")."\n"; + } + } + } + + function AppendCompiledFunction($f_name, $f_body) + { + if (defined('EXPERIMENTAL_PRE_PARSE')) { + $this->CompiledBuffer .= 'function '.$f_name.'($params)'."\n{\n"; + $this->CompiledBuffer .= $f_body; + $this->CompiledBuffer .= "}\n\n"; + } + } + + function AppendCompiledCode($code) + { + if (defined('EXPERIMENTAL_PRE_PARSE')) { + $this->CompiledBuffer .= $code; + } + } + + function AppendCompiledHTML($append) + { + if (defined('EXPERIMENTAL_PRE_PARSE')) { + $this->CompiledBuffer .= '?'.'>'."\n"; + $this->CompiledBuffer .= $append; + $this->CompiledBuffer .= '<'.'?php'."\n"; + } + } + + function ResetCode() + { + $this->Code[$this->RecursionIndex] = Array(); + } + function FindTag() { $tagOpen = strpos($this->Template, '<%', $this->Position); //Finding tag start @@ -128,57 +211,77 @@ return $tag; } - function Parse($template) + function CurrentLineNumber() { + return substr_count(substr($this->Template, 0, $this->Position), "\n")+1; + } + + function SkipModeName() + { + switch ($this->SkipMode) { + case skip: return 'skip'; + case skip_tags: return 'skip_tags'; + case parse: return 'parse'; + } + } + + function Parse($template, $name='unknown') + { $this->Template = $template; + $this->TemplateName = $name; $this->Position = 0; $this->Output = ''; + $this->CompiledBuffer .= '<'.'?php'."\n"; //While we have more tags while ($tag_data = $this->FindTag()) { //Create tag object from passed tag data + if( $this->Application->isDebugMode() && dbg_ConstOn('DBG_SHOW_TAGS') ) + { + global $debugger; + $debugger->appendHTML('mode: '.$this->SkipModeName().' tag '.$debugger->highlightString($tag_data).' in '.$debugger->getFileLink($debugger->getLocalFile(DOC_ROOT.BASE_PATH.TEMPLATES_PATH.'/'.$this->TemplateName).'.tpl', $this->CurrentLineNumber(), '', true)); + } $tag =& new Tag($tag_data, $this); if (!$this->CheckRecursion($tag)) //we do NOT process closing tags { $tag->Process(); } } + $this->CompiledBuffer .= '?'.'>'."\n"; return $this->Output; } - function ParseBlock($params, $force_pass_params=0) + function ParseBlock($params, $force_pass_params=0, $as_template=false) { - $BlockParser =& $this->Application->Factory->makeClass('TemplateParser'); + if (defined('EXPERIMENTAL_PRE_PARSE')) { + if (isset($this->Application->PreParsedBlocks[$params['name']]) ) { + $f = $this->Application->PreParsedBlocks[$params['name']]; + //$this->SetParams($params); + return $f($params); + } + } + + $BlockParser =& $this->Application->makeClass('TemplateParser'); if (isset($params['pass_params']) || $force_pass_params) { $BlockParser->SetParams(array_merge($this->Params, $params)); } else $BlockParser->SetParams($params); $this->Application->Parser =& $BlockParser; - if (!isset($params['name'])) die("***Error: Block name not passed to ParseBlock
"); + if (!isset($params['name'])) trigger_error('***Error: Block name not passed to ParseBlock', E_USER_ERROR); $templates_cache =& $this->Application->recallObject('TemplatesCache'); + + $template_name = $as_template ? $params['name'] : $templates_cache->GetTemplateFileName($params['name']) . '-block:'.$params['name']; + $o = $BlockParser->Parse( - $templates_cache->GetTemplateBody($params['name']) + $templates_cache->GetTemplateBody($params['name']), + $template_name ); $this->Application->Parser =& $this; return $o; } - function ParseXML($template, $params) - { - $templates_cache =& $this->Application->recallObject('TemplatesCache'); - $xml = $templates_cache->GetTemplateBody($template); - - $PreParser =& new TemplateParser(); - $PreParser->SetParams($params); - $this->Application->Parser =& $PreParser; - $xml = $PreParser->Parse($xml); - $this->Application->Parser =& $this; - $XMLParser =& new XMLParser($params); - $this->AppendOutput($XMLParser->Parse($xml)); - } - function Recurve(&$tag) { $this->Recursion[++$this->RecursionIndex] =& $tag; @@ -201,32 +304,4 @@ } } -// ################### TESTS ############################ - -global $suite; -if (isset($suite)) { - class TestTemplateParser extends TestCase { - function testFindTag() - { - $parser =& new TemplateParser(); - $parser->Template = 'before<%first_tag%>between<%second_tag%>after'; - $this->assertEquals('first_tag', $parser->FindTag()); - $this->assertEquals('second_tag', $parser->FindTag()); - $parser->FindTag(); - $this->assertEquals('beforebetweenafter', $parser->Output); - } - - function testAppendOutput() - { - $parser =& new TemplateParser(); - $parser->AppendOutput('1'); - $this->assertEquals('1', $parser->Output); - $parser->SetSkipMode(skip); - $parser->AppendOutput('2'); - $this->assertEquals('1', $parser->Output); - } - } - $suite->addTest(new TestSuite("TestTemplateParser")); -} - ?> \ No newline at end of file Index: trunk/core/kernel/db/dbitem.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/db/dbitem.php (.../dbitem.php) (revision 932) +++ trunk/core/kernel/db/dbitem.php (.../dbitem.php) (revision 1339) @@ -15,6 +15,11 @@ */ var $FieldValues; + + var $FieldErrors; + + var $ErrorMsgs = Array(); + /** * Holds item' primary key value * @@ -23,34 +28,34 @@ */ var $ID; - /** - * Fields allowed to be set (from table + virtual) - * - * @var Array - * @access private - */ - var $Fields=Array(); + function kDBItem() + { + parent::kDBBase(); + $this->ErrorMsgs['required'] = 'Field is required'; + $this->ErrorMsgs['unique'] = 'Field value must be unique'; + $this->ErrorMsgs['value_out_of_range'] = 'Field is out of range, possible values from %s to %s'; + $this->ErrorMsgs['length_out_of_range'] = 'Field is out of range'; + $this->ErrorMsgs['bad_type'] = 'Incorrect data format, please use %s'; + $this->ErrorMsgs['bad_date_format'] = 'Incorrect date format, please use (%s) ex. (%s)'; + } /** - * All virtual field names + * Set's default values for all fields * - * @var Array - * @access private - */ - var $VirtualFields=Array(); - - /** - * Set's field names from table - * from config - * - * @param Array $fields * @access public */ - function setConfigFields($fields) + function SetDefaultValues() { - $this->Fields=$fields; + foreach ($this->Fields as $field => $params) { + if ( isset($params['default']) ) { + $this->SetDBField($field, $params['default']); + } + else { + $this->SetDBField($field, NULL); + } + } } - + /** * Sets current item field value * (applies formatting) @@ -62,7 +67,15 @@ */ function SetField($name,$value) { - $this->SetDBField($name,$value); + $options = $this->GetFieldOptions($name); + $parsed = $value; + if ($value == '') $parsed = NULL; + if (isset($options['formatter'])) { + $formatter =& $this->Application->recallObject($options['formatter']); +// $parsed = $formatter->Parse($value, $options, $err); + $parsed = $formatter->Parse($value, $name, $this); + } + $this->SetDBField($name,$parsed); } /** @@ -79,22 +92,8 @@ $this->FieldValues[$name] = $value; } - /** * Return current item' field value by field name - * (apply formatter) - * - * @access public - * @param string $name field name to return - * @return mixed - */ - function GetField($name) - { - return $this->GetDBField($name); - } - - /** - * Return current item' field value by field name * (doesn't apply formatter) * * @access public @@ -106,6 +105,16 @@ return $this->FieldValues[$name]; } + function HasField($name) + { + return isset($this->FieldValues[$name]); + } + + function GetFieldValues() + { + return $this->FieldValues; + } + /** * Sets item' fields corresponding to elements in passed $hash values. * @@ -114,17 +123,29 @@ * * @access public * @param Array $hash + * @param Array $set_fields Optional param, field names in target object to set, other fields will be skipped * @return void */ - function SetFieldsFromHash($hash) + function SetFieldsFromHash($hash, $set_fields=null) { foreach ($hash as $field_name => $field_value) { - if( eregi("^[0-9]+$", $field_name) || !in_array($field_name,$this->Fields) ) continue; + if( eregi("^[0-9]+$", $field_name) || !array_key_exists($field_name,$this->Fields) ) continue; + if ( is_array($set_fields) && !in_array($field_name, $set_fields) ) continue; $this->SetField($field_name,$field_value); } } + function SetDBFieldsFromHash($hash, $set_fields=null) + { + foreach ($hash as $field_name => $field_value) + { + if( eregi("^[0-9]+$", $field_name) || !array_key_exists($field_name,$this->Fields) ) continue; + if ( is_array($set_fields) && !in_array($field_name, $set_fields) ) continue; + $this->SetDBField($field_name,$field_value); + } + } + /** * Returns part of SQL WHERE clause identifing the record, ex. id = 25 * @@ -137,7 +158,7 @@ */ function GetKeyClause($method=null) { - return $this->IDField.' = '.$this->Conn->qstr($this->ID); + return '`'.$this->TableName.'`.'.$this->IDField.' = '.$this->Conn->qstr($this->ID); } /** @@ -150,36 +171,94 @@ */ function Load($id, $id_field_name=null) { + if (is_array($id)) { + $keys = $id; + foreach ($keys as $field => $value) { + $sqls[] = '`'.$this->TableName.'`.'.$field.' = '.$this->Conn->qstr($value); + } + $keys_sql = '('.implode(') AND (', $sqls).')'; + } + if (isset($id_field_name)) $this->SetIDField($id_field_name); + if (!isset($id) && !isset($keys_sql)) return false; - if (!isset($id)) return false; + if( !$this->raiseEvent('OnBeforeItemLoad',$id) ) return false; $this->ID = $id; - $q = $this->GetSelectSQL().' WHERE '.$this->GetKeyClause('load'); + $q = $this->GetSelectSQL().' WHERE '.(isset($keys_sql) ? $keys_sql : $this->GetKeyClause('load')); if ($this->DisplayQueries) { echo get_class($this)." Load SQL: $q
"; } - $this->FieldValues = $this->Conn->GetRow($q); - + $this->FieldValues = array_merge_recursive2( $this->FieldValues, $this->Conn->GetRow($q) ); + if ($this->FieldValues === false) { //Error handling could be here return false; } - $this->setID($id); + if (isset($keys_sql)) { + $this->setID($this->FieldValues[$this->IDField]); + } + else { + $this->setID($id); + } + + $this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example) + + $this->raiseEvent('OnAfterItemLoad'); return true; } + + /** + * Builds select sql, SELECT ... FROM parts only + * + * @access public + * @return string + */ + function GetSelectSQL() + { + $sql = $this->addCalculatedFields($this->SelectClause); + return parent::GetSelectSQL($sql); + } + function UpdateFormattersMasterFields() + { + foreach ($this->Fields as $field => $options) { + if (isset($options['formatter'])) { + $formatter =& $this->Application->recallObject($options['formatter']); + $formatter->UpdateMasterFields($field, $this->GetDBField($field), $options, $this); + } + } + } + + function SkipField($field_name, $force_id=false) + { + $skip = false; + $skip = $skip || ( isset($this->VirtualFields[$field_name]) ); //skipping 'virtual' field + $skip = $skip || ( !getArrayValue($this->FieldValues, $field_name) && getArrayValue($this->Fields[$field_name], 'skip_empty') ); //skipping 'virtual' field + $skip = $skip || ($field_name == $this->IDField && !$force_id); //skipping Primary Key + +// $table_name = preg_replace("/^(.*)\./", "$1", $field_name); +// $skip = $skip || ($table_name && ($table_name != $this->TableName)); //skipping field from other tables + + $skip = $skip || ( !isset($this->Fields[$field_name]) ); //skipping field not in Fields (nor virtual, nor real) + + return $skip; + } + /** * Updates previously loaded record with current item' values * * @access public * @param int Primery Key Id to update - * @return void + * @return bool */ - function Update($id=null) + function Update($id=null, $system_update=false) { - if( isset($id) ) $this->ID=$id; + if( isset($id) ) $this->setID($id); + + if( !$this->raiseEvent('OnBeforeItemUpdate') ) return false; + if( !isset($this->ID) ) return false; // Validate before updating @@ -191,13 +270,22 @@ $sql = sprintf('UPDATE %s SET ',$this->TableName); foreach ($this->FieldValues as $field_name => $field_value) { - if ( isset($this->VirtualFields[$field_name]) ) continue; //skipping 'virtual' field - if ($field_name == $this->IDField) continue; //skipping Primary Key + if ($this->SkipField($field_name)) continue; $real_field_name = eregi_replace("^.*\.", '',$field_name); //removing table names from field names //Adding part of SET clause for current field, escaping data with ADODB' qstr - $sql.= sprintf('%s=%s, ',$real_field_name,$this->Conn->qstr($this->FieldValues[$field_name], 0)); + if (is_null( $this->FieldValues[$field_name] )) { + if (isset($this->Fields[$field_name]['not_null']) && $this->Fields[$field_name]['not_null']) { + $sql .= '`'.$real_field_name.'` = '.$this->Conn->qstr($this->Fields[$field_name]['default']).', '; + } + else { + $sql .= '`'.$real_field_name.'` = NULL, '; + } + } + else { + $sql.= sprintf('`%s`=%s, ', $real_field_name, $this->Conn->qstr($this->FieldValues[$field_name], 0)); + } } $sql = ereg_replace(", $", '', $sql); //Removing last comma and space @@ -212,34 +300,256 @@ } return false; } + + $affected = $this->Conn->getAffectedRows(); + if (!$system_update && $affected == 1){ + $this->setModifiedFlag(); + } + $this->raiseEvent('OnAfterItemUpdate'); return true; } + /** + * Validate all item fields based on + * constraints set in each field options + * in config + * + * @return bool + * @access private + */ function Validate() { - return true; + $this->UpdateFormattersMasterFields(); //order is critical - should be called BEFORE checking errors + $global_res = true; + foreach ($this->Fields as $field => $params) { + $res = true; + $res = $res && $this->ValidateRequired($field, $params); + $res = $res && $this->ValidateType($field, $params); + $res = $res && $this->ValidateRange($field, $params); + $res = $res && $this->ValidateUnique($field, $params); + + // If Formatter has set some error messages during values parsing + if (isset($this->FieldErrors[$field]['pseudo']) && $this->FieldErrors[$field] != '') { + $global_res = false; + } + + $global_res = $global_res && $res; + } + + if (!$global_res && $this->Application->isDebugMode() ) + { + global $debugger; + $error_msg = "Validation failed in prefix ".$this->Prefix.", FieldErrors follow (look at items with 'pseudo' key set)
+ You may ignore this notice if submitted data really has a validation error "; + trigger_error( $error_msg, E_USER_NOTICE); + $debugger->dumpVars($this->FieldErrors); + } + + return $global_res; } /** + * Check if value in field matches field type specified in config + * + * @param string $field field name + * @param Array $params field options from config + * @return bool + */ + function ValidateType($field, $params) + { + $res = true; + $val = $this->FieldValues[$field]; + if ( $val != '' && + isset($params['type']) && + preg_match("#int|integer|double|float|real|numeric|string#", $params['type']) + ) { + $res = is_numeric($val); + if($params['type']=='string' || $res) + { + $f = 'is_'.$params['type']; + settype($val, $params['type']); + $res = $f($val) && ($val==$this->FieldValues[$field]); + } + if (!$res) + { + $this->FieldErrors[$field]['pseudo'] = 'bad_type'; + $this->FieldErrors[$field]['params'] = $params['type']; + } + } + return $res; + } + + /** + * Check if value is set for required field + * + * @param string $field field name + * @param Array $params field options from config + * @return bool + * @access private + */ + function ValidateRequired($field, $params) + { + $res = true; + if ( getArrayValue($params,'required') ) + { + $res = ( (string) $this->FieldValues[$field] != ''); + } + if (!$res) $this->FieldErrors[$field]['pseudo'] = 'required'; + return $res; + } + + /** + * Validates that current record has unique field combination among other table records + * + * @param string $field field name + * @param Array $params field options from config + * @return bool + * @access private + */ + function ValidateUnique($field, $params) + { + $res = true; + $unique_fields = getArrayValue($params,'unique'); + if($unique_fields !== false) + { + $where = Array(); + array_push($unique_fields,$field); + foreach($unique_fields as $unique_field) + { + $where[] = '`'.$unique_field.'` = '.$this->Conn->qstr( $this->GetDBField($unique_field) ); + } + + $sql = 'SELECT COUNT(*) FROM %s WHERE ('.implode(') AND (',$where).') AND ('.$this->IDField.' <> '.(int)$this->ID.')'; + + $res_temp = $this->Conn->GetOne( sprintf($sql, $this->TableName ) ); + $res_live = $this->Conn->GetOne( sprintf($sql, kTempTablesHandler::GetLiveName($this->TableName) ) ); + $res = ($res_temp == 0) && ($res_live == 0); + + if(!$res) $this->FieldErrors[$field]['pseudo'] = 'unique'; + } + return $res; + } + + /** + * Check if field value is in range specified in config + * + * @param string $field field name + * @param Array $params field options from config + * @return bool + * @access private + */ + function ValidateRange($field, $params) + { + $res = true; + $val = $this->FieldValues[$field]; + if ( isset($params['type']) && preg_match("#int|integer|double|float|real#", $params['type']) && strlen($val) > 0 ) { + if ( isset($params['max_value_inc'])) { + $res = $res && $val <= $params['max_value_inc']; + $max_val = $params['max_value_inc'].' (inclusive)'; + } + if ( isset($params['min_value_inc'])) { + $res = $res && $val >= $params['min_value_inc']; + $min_val = $params['min_value_inc'].' (inclusive)'; + } + if ( isset($params['max_value_exc'])) { + $res = $res && $val < $params['max_value_exc']; + $max_val = $params['max_value_exc'].' (exclusive)'; + } + if ( isset($params['min_value_exc'])) { + $res = $res && $val > $params['min_value_exc']; + $min_val = $params['min_value_exc'].' (exclusive)'; + } + } + if (!$res) { + $this->FieldErrors[$field]['pseudo'] = 'value_out_of_range'; + + if ( !isset($min_val) ) $min_val = '-∞'; + if ( !isset($max_val) ) $max_val = '∞'; + + $this->FieldErrors[$field]['params'] = Array( $min_val, $max_val ); + return $res; + } + if ( isset($params['max_len'])) { + $res = $res && strlen($val) <= $params['max_len']; + } + if ( isset($params['min_len'])) { + $res = $res && strlen($val) >= $params['min_len']; + } + if (!$res) { + $this->FieldErrors[$field]['pseudo'] = 'length_out_of_range'; + $this->FieldErrors[$field]['params'] = Array($params['min_len'], $params['max_len']); + return $res; + } + return $res; + } + + /** + * Return error message for field + * + * @param string $field + * @return string + * @access public + */ + function GetErrorMsg($field) + { + if( !isset($this->FieldErrors[$field]) ) return ''; + + $err = getArrayValue($this->FieldErrors[$field], 'pseudo'); + if( isset($this->Fields[$field]['error_msgs'][$err]) ) + { + $msg = $this->Fields[$field]['error_msgs'][$err]; + } + else + { + if( !isset($this->ErrorMsgs[$err]) ) return $err; + $msg = $this->ErrorMsgs[$err]; + } + + if ( isset($this->FieldErrors[$field]['params']) ) + { + return vsprintf($msg, $this->FieldErrors[$field]['params']); + } + return $msg; + } + + /** * Creates a record in the database table with current item' values * + * @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE! * @access public - * @return void + * @return bool */ - function Create() + function Create($force_id=false, $system_create=false) { + if( !$this->raiseEvent('OnBeforeItemCreate') ) return false; + if(!$this->Validate()) //Validating fields before attempting to create record return false; + if (is_int($force_id)) { + $this->FieldValues[$this->IDField] = $force_id; + } + $fields_sql = ''; $values_sql = ''; foreach ($this->FieldValues as $field_name => $field_value) { - if ( isset($this->VirtualFields[$field_name]) ) continue; //skipping 'virtual' field + if ($this->SkipField($field_name, $force_id)) continue; $fields_sql .= sprintf('%s, ',$field_name); //Adding field name to fields block of Insert statement //Adding field' value to Values block of Insert statement, escaping it with ADODB' qstr - $values_sql .= sprintf('%s, ',$this->Conn->qstr($this->FieldValues[$field_name], 0)); + if (is_null( $this->FieldValues[$field_name] )) { + if (isset($this->Fields[$field_name]['not_null']) && $this->Fields[$field_name]['not_null']) { + $values_sql .= $this->Conn->qstr($this->Fields[$field_name]['default']).', '; + } + else { + $values_sql .= 'NULL, '; + } + } + else { + $values_sql .= sprintf('%s, ',$this->Conn->qstr($this->FieldValues[$field_name], 0)); + } + } //Cutting last commas and spaces $fields_sql = ereg_replace(", $", '', $fields_sql); @@ -256,27 +566,100 @@ return false; } $this->setID( $this->Conn->getInsertID() ); - //$this->SetInsertID(); //Setting Primary Key ($this->id) for futher using the object + + if (!$system_create){ + $this->setModifiedFlag(); + } + + $this->raiseEvent('OnAfterItemCreate'); return true; } /** * Deletes the record from databse * * @access public - * @return void + * @return bool */ - function Delete() + function Delete($id=null) { + if( isset($id) ) { + $this->setID($id); + } + + if( !$this->raiseEvent('OnBeforeItemDelete') ) return false; + $q = 'DELETE FROM '.$this->TableName.' WHERE '.$this->GetKeyClause('Delete'); if ($this->DisplayQueries) { echo get_class($this).' Delete SQL: '.$q.'
'; } - return $this->Conn->ChangeQuery($q); + $ret = $this->Conn->ChangeQuery($q); + + $this->setModifiedFlag(); + + $this->raiseEvent('OnAfterItemDelete'); + + return $ret; } /** + * Sets new name for item in case if it is beeing copied + * in same table + * + * @param array $master Table data from TempHandler + * @param int $foreign_key ForeignKey value to filter name check query by + * @access private + */ + function NameCopy($master=null, $foreign_key=null) + { + $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); + if (!$title_field) return; + + $new_name = $this->GetDBField($title_field); + $original_checked = false; + do { + if ( preg_match("/Copy ([0-9]*)[ ]*of(.*)/", $new_name, $regs) ) { + $new_name = 'Copy '.($regs[1]+1).' of '.$regs[2]; + } + elseif ($original_checked) { + $new_name = 'Copy of '.$new_name; + } + + // if we are cloning in temp table this will look for names in temp table, + // since object' TableName contains correct TableName (for temp also!) + // if we are cloning live - look in live + $query = 'SELECT '.$title_field.' FROM '.$this->TableName.' + WHERE '.$title_field.' = '.$this->Conn->qstr($new_name); + + if (getArrayValue($master, 'ForeignKey') && isset($foreign_key)) { + $query .= ' AND '.$master['ForeignKey'].' = '.$foreign_key; + } + + $res = $this->Conn->GetOne($query); + + /*// if not found in live table, check in temp table if applicable + if ($res === false && $object->Special == 'temp') { + $query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).' + WHERE '.$name_field.' = '.$this->Conn->qstr($new_name); + $res = $this->Conn->GetOne($query); + }*/ + + $original_checked = true; + } while ($res !== false); + $this->SetDBField($title_field, $new_name); + } + + function raiseEvent($name, $id=null) + { + if( !isset($id) ) $id = $this->GetID(); + $event = new kEvent( Array('name'=>$name,'prefix'=>$this->Prefix,'special'=>$this->Special) ); + $event->setEventParam('id', $id); + $this->Application->HandleEvent($event); + return $event->status == erSUCCESS ? true : false; + } + + /** * Set's new ID for item * * @param int $new_id @@ -287,6 +670,26 @@ $this->ID=$new_id; $this->SetDBField($this->IDField,$new_id); } + + /** + * Generate and set new temporary id + * + * @access private + */ + function setTempID() + { + $new_id = (int)$this->Conn->GetOne('SELECT MIN('.$this->IDField.') FROM '.$this->TableName); + if($new_id > 0) $new_id = 0; + --$new_id; + + $this->Conn->Query('UPDATE '.$this->TableName.' SET `'.$this->IDField.'` = '.$new_id.' WHERE `'.$this->IDField.'` = '.$this->GetID()); + $this->SetID($new_id); + } + + function setModifiedFlag(){ + + $this->Application->StoreVar($this->Application->GetTopmostPrefix($this->Prefix).'_modified', "1"); + } } Index: trunk/core/kernel/event_handler.php =================================================================== diff -u -r958 -r1339 --- trunk/core/kernel/event_handler.php (.../event_handler.php) (revision 958) +++ trunk/core/kernel/event_handler.php (.../event_handler.php) (revision 1339) @@ -27,21 +27,54 @@ class kEventHandler extends kBase { /** + * In case if event should be handled with mehod, + * which name differs from event name, then it + * should be specified here. + * key - event name, value - event method + * + * @var Array + * @access protected + */ + var $eventMethods=Array(); + + /** + * Define alternative event processing method names + * + * @see $eventMethods + * @access protected + */ + function mapEvents() + { + + } + + /** + * Set's prefix and special + * + * @param string $prefix + * @param string $special + * @access public + */ + function Init($prefix,$special) + { + parent::Init($prefix,$special); + $this->mapEvents(); + } + + /** * Process Event * * @param kEvent $event * @access public */ function processEvent(&$event) { - if( method_exists($this,$event->Name) ) + $event_name=$event->Name; + if( isset($this->eventMethods[$event_name]) ) $event_name=$this->eventMethods[$event_name]; + + if( method_exists($this,$event_name) ) { - $event_name = $event->Name; - $this->$event_name(&$event); - if($event->status==erSUCCESS && $event->redirect && strlen($event->redirect) > 0 ) - { - $this->Application->Redirect($event->redirect); - } + $this->$event_name($event); } else { @@ -82,13 +115,14 @@ * @param kEvent $event * @access protected */ - function &inheritEvent(&$event) + function &inheritEvent(&$event, $name=null) { $child_event = new kEvent(); $child_event->MasterEvent =& $event; $child_event->Prefix=$event->Prefix; $child_event->Special=$event->Special; $child_event->Prefix_Special=$event->Prefix_Special; + $child_event->Name = $name; return $child_event; } } Index: trunk/core/kernel/utility/factory.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/utility/factory.php (.../factory.php) (revision 932) +++ trunk/core/kernel/utility/factory.php (.../factory.php) (revision 1339) @@ -1,8 +1,5 @@ $regs[1].$regs[3].$regs[4], 'special'=>$regs[2]); + $tmp=explode('_',$prefix_special,2); $tmp[0]=explode('.',$tmp[0]); $prefix=$tmp[0][0]; + $prefix_special=$prefix; // new1 if( isset($tmp[1]) ) { $prefix.='_'.$tmp[1]; } - $special=isset($tmp[0][1])?$tmp[0][1]:''; - - return Array('prefix'=>$prefix,'special'=>$special); + $special= isset($tmp[0][1]) ? $tmp[0][1] : ''; + $prefix_special.='.'.$special; // new2 + return Array('prefix'=>$prefix,'special'=>$special,'prefix_special'=>$prefix_special); } @@ -74,7 +76,7 @@ // $name = 'l.pick', $pseudo_class = 'l' //echo 'N: '.$name.' - P: '.$pseudo_class."\n"; $ret=$this->processPrefix($name); - if(!$pseudo_class)$pseudo_class=$ret['prefix']; + if (!$pseudo_class) $pseudo_class = $ret['prefix']; $name=rtrim($name,'.'); if( isset($this->Storage[$name]) ) return $this->Storage[$name]; @@ -83,9 +85,12 @@ $this->Application->KernelDie('RealClass not defined for pseudo_class '.$pseudo_class.''); } - $this->Storage[$name] =& $this->makeClass($pseudo_class); - $this->Storage[$name]->Init($ret['prefix'],$ret['special']); + $funs_args = func_get_args(); + array_splice($funs_args, 0, 3, Array($pseudo_class) ); + $this->Storage[$name] =& call_user_func_array( Array(&$this,'makeClass'), $funs_args); + $this->Storage[$name]->Init($ret['prefix'],$ret['special'],$event_params); + $prefix=$this->Storage[$name]->Prefix; $special=$this->Storage[$name]->Special; @@ -104,7 +109,18 @@ return $this->Storage[$name]; } + /** + * Removes object from storage, so next time it could be created from scratch + * + * @param string $name Object's name in the Storage + */ + function DestroyObject($name) + { + unset($this->Storage[$name]); + } + + /** * Includes file containing class * definition for real class name * @@ -113,15 +129,26 @@ */ function includeClassFile($real_class) { + if (class_exists($real_class)) return; + if(!$this->Files[$real_class]) $this->Application->KernelDie('Fatal error: Real Class '.$real_class.' is not registered with the Factory
'); if(!file_exists($this->Files[$real_class])) $this->Application->KernelDie('Fatal error: Include file for class '.$real_class.' ('.$this->Files[$real_class].') does not exists
'); - include_once($this->Files[$real_class]); + + if ( $deps = getArrayValue($this->Dependencies, $real_class) ) { + foreach ($deps as $filename) { + k4_include_once($filename); + } + } + + k4_include_once($this->Files[$real_class]); } /** * Get's real class name for pseudo class, * includes class file and creates class - * instance + * instance. + * All parameters except first one are passed to object constuctor + * through mediator method makeClass that creates instance of class * * @param string $pseudo_class * @return Object @@ -131,11 +158,17 @@ { $real_class=$this->realClasses[$pseudo_class]; $this->includeClassFile($real_class); - /*if (!class_exists($real_class)) + + if( func_num_args() == 1 ) { - $this->Application->KernelDie ("Fatal error: Real Class $real_class (pseudo class $pseudo_class) not found in its registered file ".$this->Files[$pseudo_class].'
'); - }*/ - return new $real_class(); + return new $real_class(); + } + else + { + $func_args = func_get_args(); + $pseudo_class = array_shift($func_args); + return call_user_func_array( Array($real_class,'makeClass'), $func_args ); + } } /** @@ -150,6 +183,11 @@ { if(!isset($pseudo_class)) $pseudo_class = $real_class; if(!isset($this->Files[$real_class])) $this->Files[$real_class]=$file; + + if (getArrayValue($this->realClasses, $pseudo_class)) { + $this->Dependencies[$real_class][] = $this->Files[ $this->realClasses[$pseudo_class] ]; + } + $this->realClasses[$pseudo_class]=$real_class; } Index: trunk/core/kernel/event_manager.php =================================================================== diff -u -r939 -r1339 --- trunk/core/kernel/event_manager.php (.../event_manager.php) (revision 939) +++ trunk/core/kernel/event_manager.php (.../event_manager.php) (revision 1339) @@ -88,12 +88,23 @@ */ function HandleEvent(&$event) { + if (!$event->SkipBeforeHooks) { + $this->processHooks($event, hBEFORE); + if ($event->status == erFATAL) return; + } + $event_handler =& $this->Application->recallObject($event->Prefix.'_EventHandler'); - $event_handler->processEvent(&$event); + $event_handler->processEvent($event); + if ($event->status == erFATAL) return; + if (!$event->SkipAfterHooks) { + $this->processHooks($event, hAFTER); + } } function ProcessRequest() { + $this->processOpener(); + // 1. get events from $_POST $events=$this->Application->GetVar('events'); if($events===false) $events=Array(); @@ -111,7 +122,7 @@ } } - //print_pre($events); + $passed = explode(',', $this->Application->GetVar('passed')); foreach($events as $prefix_special => $event_name) { if(!$event_name) continue; @@ -121,27 +132,93 @@ $prefix_special=explode('.',$prefix_special); $event->Prefix=$prefix_special[0]; + array_push($passed, $prefix_special[0]); $event->Special=isset($prefix_special[1])?$prefix_special[1]:''; - $event->redirect=$this->Application->RecallVar('redirect_to'); - $this->HandleEvent(&$event); + $event->redirect_params = Array('opener'=>'s', 'pass'=>'all'); + $event->redirect = true; + $this->HandleEvent($event); + + if($event->status==erSUCCESS && ($event->redirect === true || strlen($event->redirect) > 0) ) + { + $this->Application->Redirect($event->redirect, $event->redirect_params, null, $event->redirect_script); + } } + $this->Application->SetVar('passed', implode(',', $passed)); } - function registerHook($hookto_unit, $hookto_event_name, $mode, $do_unit, $do_event_name) + function processOpener() { - - - - + $opener_action=$this->Application->GetVar('m_opener'); + $opener_stack=$this->Application->RecallVar('opener_stack'); + $opener_stack=$opener_stack?unserialize($opener_stack):Array(); + switch($opener_action) + { + case 'r': // "reset" opener stack + $opener_stack=Array(); + break; + + case 'd': // "down/push" new template to opener stack, deeplevel++ + array_push($opener_stack, $this->Application->RecallVar('last_template') ); + break; + + case 'u': // "up/pop" last template from opener stack, deeplevel-- + array_pop($opener_stack); + break; + + default: // "s/0," stay on same deep level + break; + } + $this->Application->SetVar('m_opener','s'); + $this->Application->StoreVar('opener_stack',serialize($opener_stack)); } + function registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional) + { + $hookto_prefix_special = rtrim($hookto_prefix.'.'.$hookto_special, '.'); + if ($mode == hBEFORE) { + $this->beforeHooks[strtolower($hookto_prefix_special.'.'.$hookto_event)][] = Array( + 'DoPrefix' => $do_prefix, + 'DoSpecial' => $do_special, + 'DoEvent' => $do_event, + 'Conditional' => $conditional, + ); + } + elseif ($mode == hAFTER) { + $this->afterHooks[strtolower($hookto_prefix_special.'.'.$hookto_event)][] = Array( + 'DoPrefix' => $do_prefix, + 'DoSpecial' => $do_special, + 'DoEvent' => $do_event, + 'Conditional' => $conditional, + ); + } + } + + /** + * Enter description here... + * + * @param kEvent $event + * @param int $mode hBEFORE or hAFTER + */ function processHooks(&$event, $mode) { - - - - + if ($mode == hBEFORE) { + $mode_hooks =& $this->beforeHooks; + } + else { + $mode_hooks =& $this->afterHooks; + } + if ( $hooks = getArrayValue($mode_hooks, strtolower($event->Prefix_Special.'.'.$event->Name)) ) { + foreach($hooks as $hook) + { + $prefix_special = rtrim($hook['DoPrefix'].'_'.$hook['DoSpecial'],'_'); + if( $hook['Conditional'] && !$this->Application->GetVar($prefix_special) ) continue; + $hook_event = new kEvent( Array('name'=>$hook['DoEvent'],'prefix'=>$hook['DoPrefix'],'special'=>$hook['DoSpecial']) ); + $hook_event->MasterEvent =& $event; + + $this->HandleEvent($hook_event); + } + } } /** Index: trunk/core/kernel/parser/tags.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/parser/tags.php (.../tags.php) (revision 932) +++ trunk/core/kernel/parser/tags.php (.../tags.php) (revision 1339) @@ -1,8 +1,8 @@ Parser->Pattern, $tag_data); + $values = $this->Parser->Values; + foreach($values as $param_name => $param_value) + { + $values[$param_name] = $this->EscapeReservedChars($param_value); + } + if (is_array($this->Parser->Params)) { - $tag_data = preg_replace($this->Parser->Pattern, $this->Parser->Values, $tag_data); + $tag_data = preg_replace($this->Parser->Pattern, $values, $tag_data); } //echo "got: $tag_data
"; return $tag_data; } + function PreParseReplaceParams($tag_data) + { + //print_pre($this->Parser->Pattern, $tag_data); + $values = $this->Parser->Values; + foreach($values as $param_name => $param_value) + { + $values[$param_name] = $this->EscapeReservedChars($param_value); + } + + /*$patterns = Array(); + if ( is_array($this->Parser->Args) ) { + foreach ($this->Parser->Args as $arg) { + + } + }*/ + + if ($this->Parser->SkipMode == parse) { + if (is_array($this->Parser->Params)) { + $tag_data = preg_replace($this->Parser->Pattern, $values, $tag_data); + } + } + //echo "got: $tag_data
"; + return $tag_data; + } + function CmpParams($a, $b) { $a_len = strlen($a); @@ -68,32 +119,20 @@ return $a_len > $b_len ? -1 : 1; } - function ReplaceLanguageTags($tag_data) - { - preg_match_all("(!(la|lu)[^!]+!)", $tag_data, $res, PREG_PATTERN_ORDER); - $language_tags = $res[0]; - uasort($language_tags, array ("Tag", "CmpParams")); - - $values = Array(); - $i = 0; - foreach ($language_tags as $label) { - array_push($values, $this->Application->Phrase($label)); - $language_tags[$i] = '/' . $language_tags[$i] . '/'; - $i++; - } - $tag_data = preg_replace($language_tags, $values, $tag_data); - return $tag_data; - } - function ParseTagData($tag_data) { - $tag_data = $this->ReplaceParams($tag_data) . ' '; - $tag_data = $this->ReplaceLanguageTags($tag_data); + if (defined('EXPERIMENTAL_PRE_PARSE') ) { + $tag_data = $this->PreParseReplaceParams($tag_data) . ' '; + } + else { + $tag_data = $this->ReplaceParams($tag_data) . ' '; + $tag_data = $this->Application->ReplaceLanguageTags($tag_data); + } list ($key_data, $params) = explode(' ', $tag_data, 2); list($this->Processor, $this->Tag) = explode(':', $key_data); - + if ($params != '') $this->ParseNamedParams($params); } @@ -132,21 +171,14 @@ $tag->CopyFrom($this); $tag->Process(); } - elseif ($this->Tag == 'xml') { - $tag =& new XMLTag('', $this->Parser); - $tag->CopyFrom($this); - $tag->Process(); - } else { if ($this->Parser->SkipMode == skip) return; - //if (!$this->ProcessMainTag()) //other main tags - $this->ProcessTag(); + $this->ProcessTag(); } } else { //normal tags - processors other than main if ($this->Parser->SkipMode == skip_tags || $this->Parser->SkipMode == skip) return; //do not parse if we skipping tags $this->ProcessTag(); - //$this->Parser->AppendOutput(''.$this->Tag.''); } } @@ -166,64 +198,57 @@ } else { - echo "Warning: can't process tag ".$this->Tag."
"; + trigger_error('can\'t process tag '.$this->Tag,E_USER_WARNING); } } -} + + function GetCode($echo=false) + { + $pass_params = $this->NP; + + $code = Array(); - -// ################### TESTS ############################ - -global $suite; -if (isset($suite)) { - class TestTags extends TestCase { + $to_pass = 'Array('; + foreach ($pass_params as $name => $val) { + $to_pass .= '"'.$name.'" => "'.$val.'",'; + } + $to_pass .= ')'; - function testTagParsing() - { - global $application; - - $tag_data = "m:test param1=\"123\""; - $tag =& new Tag($tag_data, $application->Parser); - $this->assertEquals('m', $tag->Processor); - $this->assertEquals('test', $tag->Tag); - $this->assertEquals('123', $tag->GetParam('param1')); - - $this->assertFalse($tag->GetParam('no_such_param')); - } + if ($echo) $code[] = '$o = '."'';\n"; - function testTagParamEscaping() - { - global $application; + switch ( $this->Tag ) { + case 'param': + $code[] = '$o .= $params["'.$this->NP['name'].'"];'; + return $code; + case 'if': + $code[] = ' $__tag_processor = "'.$pass_params['prefix'].'".\'_TagProcessor\';'."\n"; + $code[] = ' $processor =& $application->recallObject($__tag_processor);'."\n"; + $code[] = ' $if_result = $processor->ProcessParsedTag(\''.$pass_params['function'].'\', '.$to_pass.');'."\n"; + $code[] = ' if ($if_result) {'; + return $code; - $tag_data = "m:test escape1='-\"-' \t\t \n \t\n escape2=\"+\\\"+\" \n escape3='*\'*' escape4='=\='"; - //$tag_data = "m:test escape1='-\"-' escape2=\"+\\\"+\" escape3='*\'*' escape4='=\='"; - $tag =& new Tag($tag_data, $application->Parser); - $this->assertEquals('-"-', $tag->GetParam('escape1')); - $this->assertEquals('+"+', $tag->GetParam('escape2')); - $this->assertEquals("*'*", $tag->GetParam('escape3')); - $this->assertEquals('=\=', $tag->GetParam('escape4')); + case 'endif': + $code[] = ' }'; + return $code; + + case 'else': + $code[] = ' }'; + $code[] = ' else {'; + return $code; } - function testTagParamSubstitution() - { - global $application; - - $application->Parser->SetParams( Array( - 'prefix' => 'a_prefix', - 'tag' => 'a_tag', - 'param_name' => 'a_param_name', - 'param_value' => 'a_param_value' - ) - ); - - $tag_data = '$prefix:$tag $param_name="$param_value"'; - $tag =& new Tag($tag_data, $application->Parser); - $this->assertEquals('a_prefix', $tag->Processor); - $this->assertEquals('a_tag', $tag->Tag); - $this->assertEquals('a_param_value', $tag->GetParam('a_param_name')); - } + $code[] = ' $__tag_processor = "'.$this->Processor.'".\'_TagProcessor\';'."\n"; + $code[] = ' $processor =& $application->recallObject($__tag_processor);'."\n"; + $code[] = ' $o .= $processor->ProcessParsedTag(\''.$this->Tag.'\', '.$to_pass.');'."\n"; + + /*$code = ' $processor =& $application->recallObject(\''.$this->Processor.'_TagProcessor\'); + $o .= $processor->ProcessParsedTag(\''.$this->Tag.'\', unserialize(\''.serialize($this->NP).'\'));';*/ + + if ($echo) $code[] = ' echo $o;'."\n"; + + return $code; + //return '$o .= \'tag:'. $this->Tag .'\''; } - $suite->addTest(new TestSuite("TestTags")); } ?> \ No newline at end of file Index: trunk/core/kernel/languages/phrases_cache.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/languages/phrases_cache.php (.../phrases_cache.php) (revision 932) +++ trunk/core/kernel/languages/phrases_cache.php (.../phrases_cache.php) (revision 1339) @@ -3,10 +3,13 @@ class PhrasesCache extends kDBBase { var $Phrases = Array(); - var $Ids; + var $Ids = Array(); + var $OriginalIds = Array(); //for comparing cache var $LanguageId = 1; + var $fromTag = false; + function PhrasesCache($LanguageId=1) { parent::kDBBase(); @@ -18,7 +21,7 @@ function GetCachedIds() { $query = sprintf("SELECT PhraseList FROM %s WHERE Template = %s", - 'PhraseCache', + TABLE_PREFIX.'PhraseCache', $this->Conn->Qstr($this->Application->GetVar('t'))); $phrases_ids = $this->Conn->GetOne($query); if ($phrases_ids === false) return Array(); @@ -27,9 +30,9 @@ function LoadPhrases($ids) { - if (!is_array($ids) || count($ids) == 0) return; + if ( !is_array($ids) || !implode('', $ids) ) return; $query = sprintf("SELECT Translation,Phrase FROM %s WHERE LanguageId = %s AND PhraseId IN (%s)", - 'Phrase', + TABLE_PREFIX.'Phrase', $this->LanguageId, join(',', $ids)); $phrases = $this->Conn->GetCol($query,'Phrase'); @@ -38,6 +41,7 @@ $this->AddCachedPhrase(strtoupper($phrase), $tanslation); } $this->Ids = $ids; + $this->OriginalIds = $ids; } function AddCachedPhrase($label, $value) @@ -48,10 +52,11 @@ function UpdateCache() { - if (!is_array($this->Ids) || count($Ids) == 0) return; + if (!is_array($this->Ids) || count($this->Ids) == 0) return; + if ($this->Ids == $this->OriginalIds) return; //nothing changed $query = sprintf("REPLACE %s (PhraseList, CacheDate, Template) VALUES (%s, %s, %s)", - 'PhraseCache', + TABLE_PREFIX.'PhraseCache', $this->Conn->Qstr(join(',', $this->Ids)), mktime(), $this->Conn->Qstr($this->Application->GetVar('t'))); @@ -60,31 +65,99 @@ function GetPhrase($label) { + if (ereg("^!.+!$", $label) > 0) { + $label = substr($label, 1, -1); //cut exclamation marks + } + + $original_label = $label; $label = strtoupper($label); - if (array_key_exists($label, $this->Phrases)) - return $this->Phrases[$label]; + if( isset($this->Phrases[$label]) ) return $this->Phrases[$label]; - $this->LoadPhraseByLabel($label); + $this->LoadPhraseByLabel($label, $original_label); return $this->GetPhrase($label); } - function LoadPhraseByLabel($label) + function LoadPhraseByLabel($label, $original_label) { $query = sprintf("SELECT PhraseId, Translation FROM %s WHERE LanguageId = %s AND UPPER(Phrase) = UPPER(%s)", - Phrase, + TABLE_PREFIX.'Phrase', $this->LanguageId, $this->Conn->qstr($label)); $res = $this->Conn->GetRow($query); - if ($res === false || count($res) == 0) { - $this->AddCachedPhrase($label, '!'.$label.'!'); //add it as already cached, as long as we dont need to cache not found phrase + if ($res === false || count($res) == 0) + { + $translation = '!'.$label.'!'; + if( defined('DEBUG_MODE') && defined('ADMIN') && ADMIN && dbg_ConstOn('DBG_PHRASES')) + { + $edit_url = $this->Application->HREF('in-commerce/regional/phrases_edit','',Array('m_opener'=>'d','phrases_label'=>$original_label,'phrases_event'=>'OnNew', 'pass'=>'all,phrases','index_file'=>'index4.php') ); + $translation = '!'.$label.'!'; + if($this->fromTag) $translation = $this->escapeTagReserved($translation); + } + $this->AddCachedPhrase($label, $translation); //add it as already cached, as long as we dont need to cache not found phrase return false; } $this->Phrases[$label] = $res['Translation']; array_push($this->Ids, $res['PhraseId']); - $this->Ids = array_unique ( $this->Ids ); //just to make sure + $this->Ids = array_unique($this->Ids); //just to make sure return true; } + + /** + * Sort params by name and then by length + * + * @param string $a + * @param string $b + * @return int + * @access private + */ + function CmpParams($a, $b) + { + $a_len = strlen($a); + $b_len = strlen($b); + if ($a_len == $b_len) return 0; + return $a_len > $b_len ? -1 : 1; + } + + /** + * Replace language tags in exclamation marks found in text + * + * @param string $text + * @return string + * @access public + */ + function ReplaceLanguageTags($text) + { + $this->fromTag = true; + preg_match_all("(!(la|lu)[^!]+!)", $text, $res, PREG_PATTERN_ORDER); + $language_tags = $res[0]; + uasort($language_tags, Array(&$this, 'CmpParams') ); + + $values = Array(); + $i = 0; + foreach ($language_tags as $label) { + array_push($values, $this->GetPhrase($label) ); + //array_push($values, $this->Application->Phrase($label) ); + $language_tags[$i] = '/' . $language_tags[$i] . '/'; + $i++; + } + $this->fromTag = false; + return preg_replace($language_tags, $values, $text); + } + + /** + * Escape chars in phrase translation, that could harm parser to process tag + * + * @param string $text + * @return string + * @access private + */ + function escapeTagReserved($text) + { + $reserved = Array('"',"'"); // = + $replacement = Array('\"',"\'"); // \= + return str_replace($reserved,$replacement,$text); + } } Index: trunk/core/kernel/parser/template.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/parser/template.php (.../template.php) (revision 932) +++ trunk/core/kernel/parser/template.php (.../template.php) (revision 1339) @@ -5,12 +5,12 @@ var $BasePath = ''; var $Filename = ''; - function Template($base_path=null, $filename=null) + function Template($base_path=null, $filename=null, $silent=0) { if ($this->SetBasePath($base_path)) { if (isset($filename)) { $this->Filename = $filename; - $this->LoadTemplate(); + $this->LoadTemplate($silent); } } } @@ -34,14 +34,18 @@ { $filename = $this->GetFullPath(); if(file_exists($filename)) { + if (filesize ($filename) == 0) { + trigger_error("Template file size is 0: $filename", ($silent ? E_USER_NOTICE : E_USER_ERROR) ); + } + $handle = fopen ($filename, "r"); $contents = fread ($handle, filesize ($filename)); $this->SetBody($contents); fclose ($handle); return true; } else { - if (!$silent) echo "File or block not found: $filename ($filename)
"; + trigger_error("File or block not found: $filename", ($silent ? E_USER_NOTICE : E_USER_ERROR) ); return false; } } @@ -60,39 +64,92 @@ class TemplatesCache extends kBase { var $Templates = Array(); var $BasePath; + var $FileNames = Array(); + var $ModulesCache = Array(); + function TemplatesCache() { parent::kBase(); $this->BasePath = DOC_ROOT.BASE_PATH.THEMES_PATH; + + $conn =& $this->Application->GetADODBConnection(); + $this->ModulesCache = $conn->GetCol('SELECT LOWER(Name) FROM '.TABLE_PREFIX.'Modules'); } - function LoadTemplate($filename, $title=NULL) + function LoadTemplate($filename, $title=NULL, $silent=0) { - $template =& new Template($this->BasePath, $filename); + if (preg_match('#^[\/]{0,1}([^\/]*)\/(.*)#', $filename, $regs)) { + $module_filename = $regs[2]; + $first_dir = $regs[1]; + } + else { + $first_dir = ''; + $module_filename = $filename; + } + + if ( defined('ADMIN') && ADMIN && in_array(strtolower($first_dir), $this->ModulesCache)) { + $path = MODULES_PATH.'/'.strtolower($first_dir).'/admin_templates'; + } + else { + $path = $this->BasePath; + $module_filename = $first_dir.'/'.$module_filename; + } + + $template =& new Template($path, $module_filename, $silent); if (!isset($title)) $title = $filename; $this->SetTemplate($title, $template); } - function SetTemplate($title, &$template) + function GetRealFilename($filename) { + if (preg_match('#^[\/]{0,1}([^\/]*)\/(.*)#', $filename, $regs)) { + $module_filename = $regs[2]; + $first_dir = $regs[1]; + } + else { + $first_dir = ''; + $module_filename = $filename; + } + + if ( defined('ADMIN') && ADMIN && in_array(strtolower($first_dir), $this->ModulesCache)) { + $path = MODULES_PATH.'/'.strtolower($first_dir).'/admin_templates'; + } + else { + $path = $this->BasePath; + $module_filename = $first_dir.'/'.$module_filename; + } + return $path.'/'.$module_filename; + } + + function SetTemplate($title, &$template, $filename=null) { + if (!isset($filename)) $filename=$title; $this->Templates[$title] = $template; + $this->FileNames[$title] = $filename; } - function &GetTemplate($title) + function &GetTemplate($title, $silent=0) { if (!isset($this->Templates[$title])) { - $this->LoadTemplate($title); + $this->LoadTemplate($title, null, $silent); } return $this->Templates[$title]; } - function GetTemplateBody($title) + function GetTemplateBody($title, $silent=0) { - $template =& $this->GetTemplate($title); + $template =& $this->GetTemplate($title, $silent); + if ( !is_object($template) ) { + return ''; + } return $template->GetBody(); } + function GetTemplateFileName($title) + { + return $this->FileNames[$title]; + } + function SetTemplateBody($title, $body) { $template =& new Template(); Index: trunk/core/kernel/db/dblist.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/db/dblist.php (.../dblist.php) (revision 932) +++ trunk/core/kernel/db/dblist.php (.../dblist.php) (revision 1339) @@ -1,5 +1,29 @@ null, FLT_NORMAL => null, FLT_SEARCH => null, FLT_VIEW => null); + + /** + * Holds list HAVING filter object + * + * @var kMultipleFilter + * @access private + */ + var $HavingFilter = Array(FLT_SYSTEM => null, FLT_NORMAL => null, FLT_SEARCH => null, FLT_VIEW => null); + + /** * Creates kDBList * * @return kDBList */ function kDBList() { parent::kDBBase(); $this->OrderFields = Array(); + + $this->WhereFilter[FLT_SYSTEM] =& $this->Application->makeClass('kMultipleFilter', FLT_TYPE_AND); + $this->WhereFilter[FLT_NORMAL] =& $this->Application->makeClass('kMultipleFilter', FLT_TYPE_OR); + + $this->WhereFilter[FLT_SEARCH] =& $this->Application->makeClass('kMultipleFilter', FLT_TYPE_OR); + $this->WhereFilter[FLT_SEARCH]->setType(FLT_TYPE_OR); + + $this->WhereFilter[FLT_VIEW] =& $this->Application->makeClass('kMultipleFilter', FLT_TYPE_AND); + + $this->HavingFilter[FLT_SYSTEM] =& $this->Application->makeClass('kMultipleFilter', FLT_TYPE_AND); + $this->HavingFilter[FLT_NORMAL] =& $this->Application->makeClass('kMultipleFilter', FLT_TYPE_OR); + + $this->HavingFilter[FLT_SEARCH] =& $this->Application->makeClass('kMultipleFilter', FLT_TYPE_OR); + $this->HavingFilter[FLT_SEARCH]->setType(FLT_TYPE_OR); + + $this->HavingFilter[FLT_VIEW] =& $this->Application->makeClass('kMultipleFilter', FLT_TYPE_AND); + $this->PerPage = -1; } /** - * Sets counted part of SELECT query used in grouped queries - * - * When using queries containing GROUP BY clause, the SELECT part may contain aggregate functions such as SUM(), COUNT() etc. - * the part of the query with this functions should be set as 'counted SQL' for {@link dDBList::CountRecs()} to build the correct - * count query. - * Example: - * - * ... - * $this->SetSelectSQL('SELECT product_id, SUM(amount) FROM sales'); - * $this->SetCounterSQL('SUM(amount)'); - * ... - * - * - * {@link kDBList::CountRecs()} will replace the SELECT part with COUNT(*), SUM(amount) when counting records, which will give the accurate - * number of records. The number of records is used in list pagination - * - * @access public - * @param string - * @return void - */ - function SetCountedSQL($sql) + * Adds new or replaces old filter with same name + * + * @param string $name filter name (for internal use) + * @param string $clause where/having clause part (no OR/AND allowed) + * @param int $filter_type is filter having filter or where filter + * @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW + * @access public + */ + function addFilter($name, $clause, $filter_type = WHERE_FILTER, $filter_scope = FLT_SYSTEM) { - $this->CountedSQL = $sql; + $filter_name = ($filter_type == WHERE_FILTER) ? 'WhereFilter' : 'HavingFilter'; + + $filter =& $this->$filter_name; + $filter =& $filter[$filter_scope]; + $filter->addFilter($name,$clause); } /** + * Removes specified filter from filters list + * + * @param string $name filter name (for internal use) + * @param int $filter_type is filter having filter or where filter + * @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW + * @access public + */ + function removeFilter($name, $filter_type = WHERE_FILTER, $filter_scope = FLT_SYSTEM) + { + $filter_name = ($filter_type == WHERE_FILTER) ? 'WhereFilter' : 'HavingFilter'; + + $filter =& $this->$filter_name; + $filter =& $filter[$filter_scope]; + $filter->removeFilter($name); + } + + /** + * Clear list filters + * + * @param bool $user clear user filters + * @param bool $system clear system filters + */ + function clearFilters($user=true,$system=true,$search=true,$view=true) + { + if($system) + { + $this->WhereFilter[FLT_SYSTEM]->clearFilters(); + $this->HavingFilter[FLT_SYSTEM]->clearFilters(); + } + if($user) + { + $this->WhereFilter[FLT_NORMAL]->clearFilters(); + $this->HavingFilter[FLT_NORMAL]->clearFilters(); + } + if($search) + { + $this->WhereFilter[FLT_SEARCH]->clearFilters(); + $this->HavingFilter[FLT_SEARCH]->clearFilters(); + } + if($view) + { + $this->WhereFilter[FLT_VIEW]->clearFilters(); + $this->HavingFilter[FLT_VIEW]->clearFilters(); + } + } + + /** * Counts the total number of records base on the query resulted from {@link kDBList::GetSelectSQL()} * * The method modifies the query to substitude SELECT part (fields listing) with COUNT(*). @@ -134,24 +245,25 @@ */ function CountRecs() { - $q = $this->GetSelectSQL(); - $counted_sql = ''; - if ($this->hasCounted) { - $counted_sql = $this->GetCountedSQL(); - $counted_sql = ", $counted_sql"; + $sql = $this->GetSelectSQL(true,false); + $sql = $this->getCountSQL($sql); + $this->RecordsCount = (int)$this->Conn->GetOne($sql); + + $sql = $this->GetSelectSQL(true,true); + $sql = $this->getCountSQL($sql); + $this->NoFilterCount = (int)$this->Conn->GetOne($sql); + } + + function getCountSQL($sql) + { + if ( preg_match("/DISTINCT(.*?)FROM(?!_)/is",$sql,$regs ) ) + { + return preg_replace("/^.*SELECT DISTINCT(.*?)FROM(?!_)/is", "SELECT COUNT(DISTINCT ".$regs[1].") AS count FROM", $sql); } - - if ( preg_match("/DISTINCT(.*?)FROM(?!_)/is",$q,$regs ) ) - $q = preg_replace("/^.*SELECT DISTINCT(.*?)FROM(?!_)/is", "SELECT COUNT(DISTINCT ".$regs[1].") AS count FROM", $q); else - $q = preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count $counted_sql FROM ", $q); - - if ($this->DisplayQueries) { - echo get_class($this)." Count SQL: $q
"; + { + return preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql); } - - $this->RecordsCount = (int)$this->Conn->GetOne($q); - $this->hasCounted=true; } /** @@ -174,7 +286,7 @@ $sql = $q.' '.$this->Conn->getLimitClause($this->Offset,$this->PerPage); $this->Records = $this->Conn->Query($sql); - $this->SelectedCount=count($this->Records); + $this->SelectedCount = count($this->Records); if ($this->Records === false) { //handle errors here @@ -189,43 +301,107 @@ * @access public * @return string */ - function GetSelectSQL() + function GetSelectSQL($for_counting=false,$system_filters_only=false) { - $q = parent::GetSelectSQL(); + $q = parent::GetSelectSQL($this->SelectClause); + if(!$for_counting) $q = $this->addCalculatedFields($q); - $where = $this->GetWhereClause(); - $having = $this->GetHavingClause(); + $where = $this->GetWhereClause($for_counting,$system_filters_only); + $having = $this->GetHavingClause($for_counting,$system_filters_only); $order = $this->GetOrderClause(); $group = $this->GetGroupClause(); if (!empty($where)) $q .= ' WHERE ' . $where; - if (!empty($having)) $q .= ' HAVING ' . $having; if (!empty($group)) $q .= ' GROUP BY ' . $group; - if (!empty($order)) $q .= ' ORDER BY ' . $order; + if (!empty($having)) $q .= ' HAVING ' . $having; + if ( !$for_counting && !empty($order) ) $q .= ' ORDER BY ' . $order; - return $q; + return str_replace('%1$s',$this->TableName,$q); } + function extractCalculatedFields($clause) + { + if ( is_array($this->CalculatedFields) ) { + foreach($this->CalculatedFields as $field_name => $field_expression) + { + $clause = preg_replace('/`'.$field_name.'`/', $field_expression, $clause); + } + } + return $clause; + } + /** * Returns WHERE clause of the query * * @access public + * @param bool $for_counting merge where filters with having filters + replace field names for having fields with their values * @return string */ - function GetWhereClause() + function GetWhereClause($for_counting=false,$system_filters_only=false) { - return ''; + $where =& $this->Application->makeClass('kMultipleFilter'); + + $where->addFilter('system_where', $this->WhereFilter[FLT_SYSTEM] ); + + if (!$system_filters_only) { + $where->addFilter('view_where', $this->WhereFilter[FLT_VIEW] ); + $search_w = $this->WhereFilter[FLT_SEARCH]->getSQL(); + if( $search_w || $for_counting ) // move search_having to search_where in case search_where isset or we are counting + { + $search_h = $this->extractCalculatedFields( $this->HavingFilter[FLT_SEARCH]->getSQL() ); + $search_w = ($search_w && $search_h) ? $search_w.' OR '.$search_h : $search_w.$search_h; + $where->addFilter('search_where', $search_w ); + } + } + + if( $for_counting ) // add system_having and view_having to where + { + $where->addFilter('system_having', $this->extractCalculatedFields( $this->HavingFilter[FLT_SYSTEM]->getSQL() ) ); + if (!$system_filters_only) $where->addFilter('view_having', $this->extractCalculatedFields( $this->HavingFilter[FLT_VIEW]->getSQL() ) ); + } + + return $where->getSQL(); } /** + * Depricated method + * + * @param string $clause + * @todo REMOVE + */ + function SetWhereClause($clause) + { + if( $this->Application->isDebugMode() ) + { + global $debugger; + $debugger->appendTrace(); + } + trigger_error('Depricated method kDBList->SetWhereClause. Use kDBList->addFilter instead.', E_USER_ERROR); + } + + /** * Returns HAVING clause of the query * - * @access public + * @param bool $for_counting don't return having filter in case if this is counting sql * @return string + * @access public */ - function GetHavingClause() + function GetHavingClause($for_counting=false, $system_filters_only=false) { - return ''; + if( $for_counting ) return ''; + + $having =& $this->Application->makeClass('kMultipleFilter'); + + $having->addFilter('system_having', $this->HavingFilter[FLT_SYSTEM] ); + if (!$system_filters_only) { + $having->addFilter('view_having', $this->HavingFilter[FLT_VIEW] ); + $search_w = $this->WhereFilter[FLT_SEARCH]->getSQL(); + if (!$search_w) { + $having->addFilter('search_having', $this->HavingFilter[FLT_SEARCH] ); + } + } + + return $having->getSQL(); } /** @@ -275,30 +451,55 @@ { $ret = ''; foreach ($this->OrderFields as $field) { - $ret .= $field[0] . ' ' . $field[1] . ','; + + $name = $field[0]; + $ret .= isset($this->Fields[$name]) && !isset($this->VirtualFields[$name]) ? '`'.$this->TableName.'`.' : ''; + $ret .= '`'.$field[0] . '` ' . $field[1] . ','; } $ret = rtrim($ret, ','); return $ret; } + function GetOrderField($pos=NULL) + { + if(!(isset($this->OrderFields[$pos]) && $this->OrderFields[$pos]) ) + { + $pos = 0; + } + return isset($this->OrderFields[$pos][0]) ? $this->OrderFields[$pos][0] : ''; + } + + function GetOrderDirection($pos=NULL) + { + if(!$this->OrderFields[$pos]) + $pos = 0; + + return $this->OrderFields[$pos][1]; + } + /** - * Description + * Return unformatted field value * - * @access public * @param string - * @return void + * @return mixed + * @access public */ function GetDBField($name) { $row =& $this->getCurrentRecord(); return $row[$name]; } - function GetField($name) + function HasField($name) { - return $this->GetDBField($name); + $row =& $this->getCurrentRecord(); + return isset($row[$name]); } + function GetFieldValues() + { + return $this->getCurrentRecord(); + } function &getCurrentRecord() { @@ -314,7 +515,7 @@ */ function GoFirst() { - $this->CurrentIndex=0; + $this->CurrentIndex = 0; } /** @@ -332,6 +533,18 @@ * Description * * @access public + * @return void + */ + function GoPrev() + { + if ($this->CurrentIndex>0) + $this->CurrentIndex--; + } + + /** + * Description + * + * @access public * @return bool */ function EOL() @@ -388,6 +601,41 @@ } //$this->GoFirst(); } + + /** + * Sets current item field value + * (doesn't apply formatting) + * + * @access public + * @param string $name Name of the field + * @param mixed $value Value to set the field to + * @return void + */ + function SetDBField($name,$value) + { + $this->Records[$this->CurrentIndex][$name] = $value; + } + + /** + * Apply where clause, that links this object ti it's parent item + * + * @param string $special + * @access public + */ + function linkToParent($special) + { + $parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix'); + if($parent_prefix) + { + $parent_table_key = $this->Application->getUnitOption($this->Prefix, 'ParentTableKey'); + $foreign_key_field = $this->Application->getUnitOption($this->Prefix, 'ForeignKey'); + + $parent_object =& $this->Application->recallObject($parent_prefix.'.'.$special); + $parent_id = $parent_object->GetDBField($parent_table_key); + + $this->addFilter('parent_filter', $foreign_key_field.' = '.$parent_id); // only for list in this case + } + } } ?> \ No newline at end of file Index: trunk/core/kernel/processors/tag_processor.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/processors/tag_processor.php (.../tag_processor.php) (revision 932) +++ trunk/core/kernel/processors/tag_processor.php (.../tag_processor.php) (revision 1339) @@ -19,10 +19,45 @@ } else { + if ($this->Application->hasObject('TagsAggregator')) { + $aggregator = $this->Application->recallObject('TagsAggregator'); + $tag_mapping = $aggregator->GetArrayValue($tag->Prefix, $Method); + if ($tag_mapping) { + + $mapped_tag =& new MyTag('', $this->Application->Parser); + $mapped_tag->CopyFrom($tag); + $mapped_tag->Processor = $tag_mapping[0]; + $mapped_tag->Tag = $tag_mapping[1]; + $mapped_tag->NP['PrefixSpecial'] = $tag->getPrefixSpecial(); + $mapped_tag->RebuildTagData(); + return $mapped_tag->DoProcessTag(); + } + } $this->Application->trigerError('Tag Undefined:
'.$tag->RebuildTagData().''); return false; } } + + function ProcessParsedTag($tag, $params) + { + $Method=$tag; + if(method_exists($this, $Method)) + { + if( $this->Application->isDebugMode() && dbg_ConstOn('DBG_SHOW_TAGS') ) + { + global $debugger; + $debugger->appendHTML('Processing PreParsed Tag '.$Method.' in '.$this->Prefix); + } + + //echo htmlspecialchars($tag->GetFullTag()).'
'; + return $this->$Method($params); + } + else + { + $this->Application->trigerError('Tag Undefined:
'.$tag.''); + return false; + } + } } /*class ProcessorsPool { Index: trunk/core/kernel/application.php =================================================================== diff -u -r966 -r1339 --- trunk/core/kernel/application.php (.../application.php) (revision 966) +++ trunk/core/kernel/application.php (.../application.php) (revision 1339) @@ -1,29 +1,5 @@ DB = new DBConnection(SQL_TYPE, Array(&$this,'handleSQLError') ); + $this->DB = new kDBConnection(SQL_TYPE, Array(&$this,'handleSQLError') ); $this->DB->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB); $this->DB->debugMode = $this->isDebugMode(); $this->SetDefaultConstants(); - setcookie('CookiesOn', 1, time()+600); - $this->Factory = new kFactory(); - + $this->registerDefaultClasses(); // 1. to read configs before doing any recallObject $config_reader =& $this->recallObject('kUnitConfigReader'); - $this->Phrases = new PhrasesCache( $this->RecallVar('LanguageId', DEFAULT_LANGUAGE_ID) ); + if (!$this->GetVar('m_lang')) $this->SetVar('m_lang', $this->GetDefaultLanguageId()); + $this->Phrases = new PhrasesCache( $this->GetVar('m_lang') ); + $language =& $this->recallObject('lang'); + $language->Load($this->GetVar('m_lang')); + $this->ValidateLogin(); // TODO: write that method + + if (defined('DEBUG_MODE')) { + global $debugger; + $debugger->profileFinish('kernel4_startup'); + } + } + function GetDefaultLanguageId() + { + return 1; + } + /** * Registers default classes such as ItemController, GridController and LoginController * @@ -182,18 +171,33 @@ //$this->registerClass('Utilites',KERNEL_PATH.'/utility/utilities.php'); $this->registerClass('HTTPQuery',KERNEL_PATH.'/utility/http_query.php'); $this->registerClass('Session',KERNEL_PATH.'/session/session.php'); + $this->registerClass('SessionStorage',KERNEL_PATH.'/session/session.php'); + $this->registerClass('LoginEventHandler',KERNEL_PATH.'/session/login_event_handler.php','login_EventHandler'); $this->registerClass('kEventManager',KERNEL_PATH.'/event_manager.php','EventManager'); $this->registerClass('kUnitConfigReader',KERNEL_PATH.'/utility/unit_config_reader.php'); + $this->registerClass('Params',KERNEL_PATH.'/utility/params.php','kActions'); + $this->registerClass('kArray',KERNEL_PATH.'/utility/params.php','kArray'); + $this->registerClass('kFormatter', KERNEL_PATH.'/utility/formatters.php'); + $this->registerClass('kOptionsFormatter', KERNEL_PATH.'/utility/formatters.php'); + $this->registerClass('kPictureFormatter', KERNEL_PATH.'/utility/formatters.php'); + $this->registerClass('kDateFormatter', KERNEL_PATH.'/utility/formatters.php'); + $this->registerClass('kLEFTFormatter', KERNEL_PATH.'/utility/formatters.php'); + $this->registerClass('kMultiLanguage', KERNEL_PATH.'/utility/formatters.php'); + $this->registerClass('kTempTablesHandler', KERNEL_PATH.'/utility/temp_handler.php'); + + $event_manager =& $this->recallObject('EventManager'); + $event_manager->registerBuildEvent('kTempTablesHandler','OnTempHandlerBuild'); //$this->registerClass('Configuration',KERNEL_PATH.'/utility/configuration.php'); $this->registerClass('TemplatesCache',KERNEL_PATH.'/parser/template.php'); $this->registerClass('TemplateParser',KERNEL_PATH.'/parser/template_parser.php'); $this->registerClass('MainProcessor', KERNEL_PATH.'/processors/main_processor.php','m_TagProcessor'); + $this->registerClass('kMultipleFilter', KERNEL_PATH.'/utility/filters.php'); $this->registerClass('kDBList', KERNEL_PATH.'/db/dblist.php'); $this->registerClass('kDBItem', KERNEL_PATH.'/db/dbitem.php'); $this->registerClass('kDBEventHandler', KERNEL_PATH.'/db/db_event_handler.php'); @@ -233,12 +237,21 @@ function Run() { $event_manager =& $this->recallObject('EventManager'); + + if( $this->isDebugMode() && dbg_ConstOn('DBG_SHOW_HTTPQUERY') ) + { + global $debugger; + $http_query =& $this->recallObject('HTTPQuery'); + $debugger->appendHTML('HTTPQuery:'); + $debugger->dumpVars($http_query->_Params); + } + $event_manager->ProcessRequest(); $this->Parser =& $this->recallObject('TemplateParser'); $template_cache =& $this->recallObject('TemplatesCache'); $t = $this->GetVar('t'); - $this->HTML = $this->Parser->Parse( $template_cache->GetTemplateBody($t) ); + $this->HTML = $this->Parser->Parse( $template_cache->GetTemplateBody($t), $t ); } /** @@ -256,8 +269,18 @@ $session =& $this->recallObject('Session'); $session->SaveData(); + //$this->SaveBlocksCache(); } + function SaveBlocksCache() + { + if (defined('EXPERIMENTAL_PRE_PARSE')) { + $data = serialize($this->PreParsedCache); + + $this->DB->Query('REPLACE '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("blocks_cache", '.$this->DB->qstr($data).', '.time().')'); + } + } + // Facade /** @@ -326,8 +349,20 @@ $session =& $this->recallObject('Session'); return $session->RemoveVar($var); } - + /** + * Deletes HTTPQuery variable + * + * @param string $var + * @todo think about method name + */ + function DeleteVar($var) + { + $http_query =& $this->recallObject('HTTPQuery'); + return $http_query->Remove($var); + } + + /** * Returns session variable value * * Return value of $var variable stored in Session. An optional default value could be passed as second parameter. @@ -338,7 +373,7 @@ * @param mixed $default Default value to return if no $var variable found in session * @return mixed */ - function RecallVar($var,$default='') + function RecallVar($var,$default=false) { $session =& $this->recallObject('Session'); return $session->RecallVar($var,$default); @@ -382,11 +417,14 @@ function LinkVar($var, $ses_var=null, $default='') { if (!isset($ses_var)) $ses_var = $var; - if ($this->GetVar($var) !== false) { + if ($this->GetVar($var) !== false) + { $this->StoreVar($ses_var, $this->GetVar($var)); } else + { $this->SetVar($var, $this->RecallVar($ses_var, $default)); + } } /** @@ -471,6 +509,15 @@ return $a_tag->DoProcessTag(); } + function ProcessParsedTag($prefix, $tag, $params) + { + $a_tag = new Tag('',$this->Parser); + $a_tag->Tag = $tag; + $a_tag->Processor = $prefix.'_TagProcessor'; + $a_tag->NamedParams = $params; + return $a_tag->DoProcessTag(); + } + /*function &GetProcessor($prefix) { $this->KernelDie('GetProcessor is DEPRICATED, use recallObject'); @@ -545,9 +592,9 @@ return $this->DB; } - function ParseBlock($params,$pass_params=0) + function ParseBlock($params,$pass_params=0,$as_template=false) { - return $this->Parser->ParseBlock($params,$pass_params); + return $this->Parser->ParseBlock($params, $pass_params, $as_template); } function &GetXMLFactory() @@ -562,47 +609,94 @@ * @param string $t Template path * @var string $prefix index.php prefix - could be blank, 'admin' */ - function HREF($t, $prefix='') + function HREF($t, $prefix='', $params=null, $index_file=null) { global $HTTP_SERVER_VARS; if (defined('ADMIN') && $prefix == '') $prefix='/admin'; if (defined('ADMIN') && $prefix == '_FRONT_END_') $prefix = ''; - $index_file = defined('INDEX_FILE') ? INDEX_FILE : 'index.php'; - $t_path = !empty($t) ? 't='.$t : ''; + $index_file = isset($index_file) ? $index_file : (defined('INDEX_FILE') ? INDEX_FILE : basename($_SERVER['PHP_SELF'])); + if( isset($params['index_file']) ) $index_file = $params['index_file']; + + if (getArrayValue($params, 'opener') == 'u') { + $opener_stack=$this->RecallVar('opener_stack'); + if($opener_stack) { + $opener_stack=unserialize($opener_stack); + list($index_file, $env) = explode('|', $opener_stack[count($opener_stack)-1]); + $ret = $this->BaseURL($prefix).$index_file.'?'.ENV_VAR_NAME.'='.$env; + return $ret; + } + } + + $pass = isset($params['pass']) ? $params['pass'] : ''; + $pass_events = isset($params['pass_events']) ? $params['pass_events'] : false; // pass events with url + + $ret = $this->BaseURL($prefix).$index_file.'?'.$this->BuildEnv($t, $params, $pass, $pass_events); + + return $ret; + } + + function BuildEnv($t, $params, $pass='all', $pass_events=false) + { $session =& $this->recallObject('Session'); $sid = $session->NeedQueryString()?$this->GetSID():''; + if (defined('INPORTAL_ENV')) { + $ret = ENV_VAR_NAME.'='.$sid.'-'.$t; + } + else { + $ret = ENV_VAR_NAME.'='.$sid.':'.$t; + } - $ret = $this->BaseURL($prefix).$index_file.'?'.ENV_VAR_NAME.'='.$sid.':'.$t; + $pass = str_replace('all', trim($this->GetVar('passed'), ','), $pass); - $t_pass=$this->GetVar('t_pass'); - $t_pass_events=$this->GetVar('t_pass_events'); // pass events with url - - if($t_pass) + if(strlen($pass) > 0) { - $pass_info=explode(',',$t_pass); // array( prefix[.special], prefix[.special] ... + $pass_info = array_unique( explode(',',$pass) ); // array( prefix[.special], prefix[.special] ... foreach($pass_info as $pass_element) { $ret.=':'; list($prefix)=explode('.',$pass_element); $query_vars = $this->getUnitOption($prefix,'QueryString'); - if(!$t_pass_events) $this->SetVar($pass_element.'_event',''); // remove event from url if requested + //if pass events is off and event is not implicity passed + if(!$pass_events && !isset($params[$pass_element.'_event'])) { + $params[$pass_element.'_event'] = ''; // remove event from url if requested + //otherwise it will use value from get_var + } if($query_vars) { $tmp_string=Array(0=>$pass_element); foreach($query_vars as $index => $var_name) { - $tmp_string[$index]=$this->GetVar($pass_element.'_'.$var_name); + //if value passed in params use it, otherwise use current from application + $tmp_string[$index] = isset( $params[$pass_element.'_'.$var_name] ) ? $params[$pass_element.'_'.$var_name] : $this->GetVar($pass_element.'_'.$var_name); + if ( isset($params[$pass_element.'_'.$var_name]) ) { + unset( $params[$pass_element.'_'.$var_name] ); + } } - $ret.=implode('-',$tmp_string); + $escaped = array(); + foreach ($tmp_string as $tmp_val) { + $escaped[] = str_replace(Array('-',':'), Array('\-','\:'), $tmp_val); + } + + if ($this->getUnitOption($prefix, 'PortalStyleEnv') == true) { + $ret.= array_shift($escaped).array_shift($escaped).'-'.implode('-',$escaped); + } + else { + $ret.=implode('-',$escaped); + } } } } - $this->SetVar('t_pass',''); // don't pass any prefixes_specials in url by default - $this->SetVar('t_pass_events',0); // don't pass events in url by default + unset($params['pass']); + unset($params['opener']); + unset($params['m_event']); + foreach ($params as $param => $value) { + $ret .= '&'.$param.'='.$value; + } + if( getArrayValue($params,'escape') ) $ret = addslashes($ret); return $ret; } @@ -611,40 +705,28 @@ return PROTOCOL.SERVER_NAME.(defined('PORT')?':'.PORT : '').BASE_PATH.$prefix.'/'; } - /** - * Build enviroment variable based on - * data submitted from previous template - * - * @access public - */ - function ReBuildENV() + function Redirect($t='', $params=null, $prefix='', $index_file=null) { - $event_manager =& $this->recallObject('EventManager'); - $prefix_specials = array_keys($event_manager->queryMaps); - $this->SetVar('t_pass', implode(',',$prefix_specials) ); - } - - function Redirect($t='', $params='', $prefix='') - { - if ($t == '') $t = $this->GetVar('t'); + if ($t == '' || $t === true) $t = $this->GetVar('t'); // pass prefixes and special from previous url - $this->ReBuildENV(); + if (!isset($params['pass'])) $params['pass'] = 'all'; - $location = $this->HREF($t, $prefix); + $location = $this->HREF($t, $prefix, $params, $index_file); $a_location = $location; - $location = sprintf("Location: %s".($params ? "&" : '')."%s",$location, $params); + $location = "Location: $location"; //echo " location : $location
"; - - if (headers_sent() != '') { + if (headers_sent() != '' || ($this->isDebugMode() && dbg_ConstOn('DBG_REDIRECT')) ) { echo "Debug output above!!! Proceed to redirect: $a_location
"; } - else + else { header("$location"); - + } + $session =& $this->recallObject('Session'); $session->SaveData(); + $this->SaveBlocksCache(); exit; } @@ -656,13 +738,22 @@ function Phrase($label) { - if (ereg("^!.+!$", $label) > 0) { - $label = substr($label, 1, -1); //cut exclamation marks - } return $this->Phrases->GetPhrase($label); } /** + * Replace language tags in exclamation marks found in text + * + * @param string $text + * @return string + * @access public + */ + function ReplaceLanguageTags($text) + { + return $this->Phrases->ReplaceLanguageTags($text); + } + + /** * Validtates user in session if required * */ @@ -700,7 +791,7 @@ function HandleEvent(&$event) { $event_manager =& $this->recallObject('EventManager'); - $event_manager->HandleEvent(&$event); + $event_manager->HandleEvent($event); } /** @@ -716,6 +807,18 @@ $this->Factory->registerClass($real_class,$file,$pseudo_class); } + function registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional) + { + $event_manager =& $this->recallObject('EventManager'); + $event_manager->registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional); + } + + function registerAggregateTag($tag_info) + { + $aggregator =& $this->recallObject('TagsAggregator', 'kArray'); + $aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], Array($tag_info['LocalPrefix'], $tag_info['LocalTagName'])); + } + /** * Returns object using params specified, * creates it if is required @@ -727,10 +830,48 @@ */ function &recallObject($name,$pseudo_class=null,$event_params=Array()) { - return $this->Factory->getObject($name,$pseudo_class,$event_params); + $o1 =& $this->Factory->getObject($name,$pseudo_class,$event_params); + //$o1->param1 = 'one'; + + /*$func_args = func_get_args(); + $factory =& $this->Factory; + $o2 =& call_user_func_array( Array(&$factory, 'getObject'), $func_args );*/ + + //$o2->param1 = 'two'; + return $o1; } + function &hasObject($name) + { + return isset($this->Factory->Storage[$name]); + } + /** + * Removes object from storage by given name + * + * @param string $name Object's name in the Storage + */ + function removeObject($name) + { + return $this->Factory->DestroyObject($name); + } + + /** + * Get's real class name for pseudo class, + * includes class file and creates class + * instance + * + * @param string $pseudo_class + * @return Object + * @access private + */ + function &makeClass($pseudo_class) + { + $func_args = func_get_args(); + return call_user_func_array( Array(&$this->Factory, 'makeClass'), $func_args); + } + + /** * Checks if application is in debug mode * * @return bool @@ -771,6 +912,19 @@ } /** + * Read all unit with $prefix options + * + * @param string $prefix + * @return Array + * @access public + */ + function getUnitOptions($prefix) + { + $unit_config_reader =& $this->recallObject('kUnitConfigReader'); + return $unit_config_reader->getUnitOptions($prefix); + } + + /** * Splits any mixing of prefix and * special into correct ones * @@ -812,7 +966,7 @@ global $debugger; if($debugger) { - $errorLevel=defined('DBG_SQL_FAILURE')&&DBG_SQL_FAILURE?E_USER_ERROR:E_USER_WARNING; + $errorLevel=defined('DBG_SQL_FAILURE') && DBG_SQL_FAILURE ? E_USER_ERROR : E_USER_WARNING; $debugger->dumpVars($_REQUEST); $debugger->appendTrace(); @@ -828,6 +982,23 @@ return false; } } + + function NextResourceId() + { + $this->DB->Query('LOCK TABLES '.TABLE_PREFIX.'IdGenerator WRITE'); + $this->DB->Query('UPDATE '.TABLE_PREFIX.'IdGenerator SET lastid = lastid+1'); + $id = $this->DB->GetOne("SELECT lastid FROM ".TABLE_PREFIX."IdGenerator"); + $this->DB->Query('UNLOCK TABLES'); + return $id; + } + + function GetTopmostPrefix($current_prefix) + { + while ( $parent_prefix = $this->getUnitOption($current_prefix, 'ParentPrefix') ) { + $current_prefix = $parent_prefix; + } + return $current_prefix; + } } ?> \ No newline at end of file Index: trunk/core/kernel/utility/temp_handler.php =================================================================== diff -u --- trunk/core/kernel/utility/temp_handler.php (revision 0) +++ trunk/core/kernel/utility/temp_handler.php (revision 1339) @@ -0,0 +1,546 @@ +Conn =& $this->Application->GetADODBConnection(); + } + + function SetTables($tables) + { + // set tablename as key for tables array + $ret = Array(); + $this->Tables = $tables; + $this->MasterTable = $tables['TableName']; + } + + /** + * Get temp table name + * + * @param string $table + * @return string + */ + function GetTempName($table) + { + return TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_edit_'.$table; + } + + /** + * Return live table name based on temp table name + * + * @param string $temp_table + * @return string + */ + function GetLiveName($temp_table) + { + if( preg_match('/'.TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_edit_(.*)/',$temp_table,$rets) ) + { + return $rets[1]; + } + else + { + return $temp_table; + } + } + + function IsTempTable($table) + { + return strpos($table, TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_edit_') !== false; + } + + /** + * Return temporary table name for master table + * + * @return string + * @access public + */ + function GetMasterTempName() + { + return $this->GetTempName($this->MasterTable); + } + + function CreateTempTable($table) + { + $query = sprintf("CREATE TABLE %s SELECT * FROM %s WHERE 0", + $this->GetTempName($table), + $table); + $this->Conn->Query($query); + } + + function BuildTables($prefix, $ids) + { + $tables = Array( + 'TableName' => $this->Application->getUnitOption($prefix,'TableName'), + 'IdField' => $this->Application->getUnitOption($prefix,'IDField'), + 'IDs' => $ids, + 'Prefix' => $prefix, + ); + + $SubItems = $this->Application->getUnitOption($prefix,'SubItems'); + if (is_array($SubItems)) { + foreach ($SubItems as $prefix) { + $this->AddTables($prefix, $tables); + } + } + $this->SetTables($tables); + } + + function AddTables($prefix, &$tables) + { + $tmp = Array( + 'TableName' => $this->Application->getUnitOption($prefix,'TableName'), + 'IdField' => $this->Application->getUnitOption($prefix,'IDField'), + 'ForeignKey' => $this->Application->getUnitOption($prefix,'ForeignKey'), + 'ParentTableKey' => $this->Application->getUnitOption($prefix,'ParentTableKey'), + 'Prefix' => $prefix, + 'AutoClone' => $this->Application->getUnitOption($prefix,'AutoClone'), + 'AutoDelete' => $this->Application->getUnitOption($prefix,'AutoDelete'), + ); + + $SubItems = $this->Application->getUnitOption($prefix,'SubItems'); + if (is_array($SubItems)) { + foreach ($SubItems as $prefix) { + $this->AddTables($prefix, $tmp); + } + } + + if ( !is_array(getArrayValue($tables, 'SubTables')) ) { + $tables['SubTables'] = array(); + } + + $tables['SubTables'][] = $tmp; + } + + function CloneItems($prefix, $special, $ids, $master=null, $foreign_key=null, $parent_prefix=null) + { + if (!isset($master)) $master = $this->Tables; + $prefix_special = rtrim($prefix.'.'.$special, '.'); + + //recalling by different name, because we may get kDBList, if we recall just by prefix + $recall_prefix = $prefix_special.($special ? '' : '.').'-item'; + $object =& $this->Application->recallObject($recall_prefix, $prefix); + + foreach ($ids as $id) + { + $mode = 'create'; + if ( $cloned_ids = getArrayValue($this->AlreadyProcessed, $master['TableName']) ) { + // if we have already cloned the id, replace it with cloned id and set mode to update + // update mode is needed to update second ForeignKey for items cloned by first ForeignKey + if ( getArrayValue($cloned_ids, $id) ) { + $id = $cloned_ids[$id]; + $mode = 'update'; + } + } + + $object->Load($id); + $original_values = $object->FieldValues; + + $object->NameCopy($master, $foreign_key); + + if (isset($foreign_key)) { + $master_foreign_key_field = is_array($master['ForeignKey']) ? $master['ForeignKey'][$parent_prefix] : $master['ForeignKey']; + $object->SetDBField($master_foreign_key_field, $foreign_key); + } + + if ($mode == 'create') { + $this->RaiseEvent('OnBeforeClone', $master['Prefix'], Array($object->GetId()) ); + } + + $res = $mode == 'update' ? $object->Update() : $object->Create(); + + if( $res ) + { + if ( $mode == 'create' && is_array( getArrayValue($master, 'ForeignKey')) ) { + // remember original => clone mapping for dual ForeignKey updating + $this->AlreadyProcessed[$master['TableName']][$id] = $object->GetId(); + } + if($object->mode == 't') $object->setTempID(); + if ($mode == 'create') { + $this->RaiseEvent('OnAfterClone', $master['Prefix'], Array($object->GetId()) ); + } + + if ( is_array(getArrayValue($master, 'SubTables')) ) { + foreach($master['SubTables'] as $sub_table) { + if (!getArrayValue($sub_table, 'AutoClone')) continue; + $sub_TableName = ($object->mode == 't') ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName']; + + $foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey']; + $parent_key_field = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey']; + + $query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_TableName.' + WHERE '.$foreign_key_field.' = '.$original_values[$parent_key_field]; + + $sub_ids = $this->Conn->GetCol($query); + + if ( is_array(getArrayValue($sub_table, 'ForeignKey')) ) { + // $sub_ids could containt newly cloned items, we need to remove it here + // to escape double cloning + + $cloned_ids = getArrayValue($this->AlreadyProcessed, $sub_table['TableName']); + if ( !$cloned_ids ) $cloned_ids = Array(); + $new_ids = array_values($cloned_ids); + $sub_ids = array_diff($sub_ids, $new_ids); + } + + $parent_key = $object->GetDBField($parent_key_field); + + $this->CloneItems($sub_table['Prefix'], '', $sub_ids, $sub_table, $parent_key, $master['Prefix']); + } + } + } + } + } + + function DeleteItems($prefix, $special, $ids, $master=null, $foreign_key=null) + { + if (!isset($master)) $master = $this->Tables; + $prefix_special = rtrim($prefix.'.'.$special, '.'); + + //recalling by different name, because we may get kDBList, if we recall just by prefix + $recall_prefix = $prefix_special.($special ? '' : '.').'-item'; + $this->Application->setUnitOption($prefix,'AutoLoad',false); + $object =& $this->Application->recallObject($recall_prefix, $prefix); + + foreach ($ids as $id) + { + $object->Load($id); + $original_values = $object->FieldValues; + $object->Delete($id); + + if ( is_array(getArrayValue($master, 'SubTables')) ) { + foreach($master['SubTables'] as $sub_table) { + if (!getArrayValue($sub_table, 'AutoDelete')) continue; + $sub_TableName = ($object->mode == 't') ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName']; + + $foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey']; + $parent_key_field = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey']; + + $query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_TableName.' + WHERE '.$foreign_key_field.' = '.$original_values[$parent_key_field]; + + $sub_ids = $this->Conn->GetCol($query); + + $parent_key = $object->GetDBField($sub_table['ParentTableKey']); + + $this->DeleteItems($sub_table['Prefix'], '', $sub_ids, $sub_table, $parent_key); + } + } + + } + } + + function DoCopyLiveToTemp($master, $ids, $parent_prefix=null) + { + // when two tables refers the same table as sub-sub-table, and ForeignKey and ParentTableKey are arrays + // the table will be first copied by first sub-table, then dropped and copied over by last ForeignKey in the array + // this should not do any problems :) + $this->DropTempTable($master['TableName']); + $this->CreateTempTable($master['TableName']); + + if (is_array($ids)) { + $ids = join(',', $ids); + } + + if ($ids != '') { + if ( getArrayValue($master, 'ForeignKey') ) { + if ( is_array($master['ForeignKey']) ) { + $key_field = $master['ForeignKey'][$parent_prefix]; + } + else { + $key_field = $master['ForeignKey']; + } + } + else { + $key_field = $master['IdField']; + } + + $query = 'INSERT INTO '.$this->GetTempName($master['TableName']).' + SELECT * FROM '.$master['TableName'].' + WHERE '.$key_field.' IN ('.$ids.')'; + $this->Conn->Query($query); + + $query = 'SELECT '.$master['IdField'].' FROM '.$master['TableName'].' + WHERE '.$key_field.' IN ('.$ids.')'; + + $this->RaiseEvent( 'OnAfterCopyToTemp', $master['Prefix'], $this->Conn->GetCol($query) ); + } + + if ( getArrayValue($master, 'SubTables') ) { + foreach ($master['SubTables'] as $sub_table) { + + $parent_key = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey']; + + if ( $ids != '' && $parent_key != $key_field ) { + $query = 'SELECT '.$parent_key.' FROM '.$master['TableName'].' + WHERE '.$key_field.' IN ('.$ids.')'; + $sub_foreign_keys = join(',', $this->Conn->GetCol($query)); + } + else { + $sub_foreign_keys = $ids; + } + $this->DoCopyLiveToTemp($sub_table, $sub_foreign_keys, $master['Prefix']); + } + } + } + + function GetForeignKeys($master, $sub_table, $live_id, $temp_id=null) + { + $mode = 1; //multi + if (!is_array($live_id)) { + $live_id = Array($live_id); + $mode = 2; //single + } + if (isset($temp_id) && !is_array($temp_id)) $temp_id = Array($temp_id); + + if ( isset($sub_table['ParentTableKey']) ) { + if ( is_array($sub_table['ParentTableKey']) ) { + $parent_key_field = $sub_table['ParentTableKey'][$master['Prefix']]; + } + else { + $parent_key_field = $sub_table['ParentTableKey']; + } + } + else { + $parent_key_field = $master['IdField']; + } + + if ( $cached = getArrayValue($this->FKeysCache, $master['TableName'].'.'.$parent_key_field) ) { + if ( array_key_exists(serialize($live_id), $cached) ) { + list($live_foreign_key, $temp_foreign_key) = $cached[serialize($live_id)]; + if ($mode == 1) { + return $live_foreign_key; + } + else { + return Array($live_foreign_key[0], $temp_foreign_key[0]); + } + } + } + + if ($parent_key_field != $master['IdField']) { + $query = 'SELECT '.$parent_key_field.' FROM '.$master['TableName'].' + WHERE '.$master['IdField'].' IN ('.join(',', $live_id).')'; + $live_foreign_key = $this->Conn->GetCol($query); + + if (isset($temp_id)) { + $query = 'SELECT '.$parent_key_field.' FROM '.$this->GetTempName($master['TableName']).' + WHERE '.$master['IdField'].' IN ('.join(',', $temp_id).')'; + $temp_foreign_key = $this->Conn->GetCol($query); + } + else { + $temp_foreign_key = Array(); + } + } + else { + $live_foreign_key = $live_id; + $temp_foreign_key = $temp_id; + } + + $this->FKeysCache[$master['TableName'].'.'.$parent_key_field][serialize($live_id)] = Array($live_foreign_key, $temp_foreign_key); + + if ($mode == 1) { + return $live_foreign_key; + } + else { + return Array($live_foreign_key[0], $temp_foreign_key[0]); + } + } + + function DoCopyTempToOriginal($master, $parent_prefix=null) + { + $query = 'SELECT '.$master['IdField'].' FROM '.$this->GetTempName($master['TableName']); + $current_ids = $this->Conn->GetCol($query); + + if ($current_ids) { + // delete all ids from live table - for MasterTable ONLY! + // because items from Sub Tables get deteleted in CopySubTablesToLive !BY ForeignKey! + if ($master['TableName'] == $this->MasterTable) { + $this->RaiseEvent( 'OnBeforeDeleteFromLive', $master['Prefix'], $current_ids ); + + $query = 'DELETE FROM '.$master['TableName'].' WHERE '.$master['IdField'].' IN ('.join(',', $current_ids).')'; + $this->Conn->Query($query); + } + + if ( getArrayValue($master, 'SubTables') ) { + foreach ($current_ids AS $id) { + $this->RaiseEvent( 'OnBeforeCopyToLive', $master['Prefix'], Array($id) ); + + //reset negative ids to 0, so autoincrement in live table works fine + if ($id < 0) { + $query = 'UPDATE '.$this->GetTempName($master['TableName']).' + SET '.$master['IdField'].' = 0 + WHERE '.$master['IdField'].' = '.$id; + $this->Conn->Query($query); + $id_to_copy = 0; + } + else { + $id_to_copy = $id; + } + + //copy current id_to_copy (0 for new or real id) to live table + $query = 'INSERT INTO '.$master['TableName'].' + SELECT * FROM '.$this->GetTempName($master['TableName']).' + WHERE '.$master['IdField'].' = '.$id_to_copy; + $this->Conn->Query($query); + $insert_id = $id_to_copy == 0 ? $this->Conn->getInsertID() : $id_to_copy; + + $this->RaiseEvent( 'OnAfterCopyToLive', $master['Prefix'], Array($insert_id) ); + + $this->UpdateForeignKeys($master, $insert_id, $id); + + //delete already copied record from master temp table + $query = 'DELETE FROM '.$this->GetTempName($master['TableName']).' + WHERE '.$master['IdField'].' = '.$id_to_copy; + $this->Conn->Query($query); + } + // when all of ids in current master has been processed, copy all sub-tables data + $this->CopySubTablesToLive($master, $current_ids); + } + else { //If current master doesn't have sub-tables - we could use mass operations + // We don't need to delete items from live here, as it get deleted in the beggining of the method for MasterTable + // or in parent table processing for sub-tables + + $this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], $current_ids); + + // reset ALL negative IDs to 0 so it get inserted into live table with autoincrement + $query = 'UPDATE '.$this->GetTempName($master['TableName']).' + SET '.$master['IdField'].' = 0 + WHERE '.$master['IdField'].' < 0'; + $this->Conn->Query($query); + + // copy ALL records to live table + $query = 'INSERT INTO '.$master['TableName'].' + SELECT * FROM '.$this->GetTempName($master['TableName']); + $this->Conn->Query($query); + + /* + + !!! WE NEED TO FIND A WAY TO DETERMINE IF OnAfterCopyToLive is not an empty method, and do on-by-one insert + and pass Ids to OnAfterCopyToLive, otherwise it's not smart to do on-by-one insert for any object + OR WE COULD FIND A WAY TO GET ALL INSERTED IDS as an array and iterate them !!! + + $this->RaiseEvent('OnAfterCopyToLive', IDS ??? ); + + */ + + // no need to clear temp table - it will be dropped by next statement + } + } + if ( is_array(getArrayValue($master, 'ForeignKey')) ) { //if multiple ForeignKeys + if ( $master['ForeignKey'][$parent_prefix] != end($master['ForeignKey']) ) { + return; // Do not delete temp table if not all ForeignKeys have been processed (current is not the last) + } + } + $this->DropTempTable($master['TableName']); + } + + function UpdateForeignKeys($master, $live_id, $temp_id) { + foreach ($master['SubTables'] as $sub_table) { + list ($live_foreign_key, $temp_foreign_key) = $this->GetForeignKeys($master, $sub_table, $live_id, $temp_id); + + $foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey']; + + //Update ForeignKey in sub TEMP table + if ($live_foreign_key != $temp_foreign_key) { + $query = 'UPDATE '.$this->GetTempName($sub_table['TableName']).' + SET '.$foreign_key_field.' = '.$live_foreign_key.' + WHERE '.$foreign_key_field.' = '.$temp_foreign_key; + $this->Conn->Query($query); + } + } + } + + function CopySubTablesToLive($master, $current_ids) { + foreach ($master['SubTables'] as $sub_table) { + + // delete records from live table by foreign key, so that records deleted from temp table + // get deleted from live + if (count($current_ids) > 0) { + $foreign_keys = $this->GetForeignKeys($master, $sub_table, $current_ids); + $foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey']; + if (count($foreign_keys) > 0) { + $query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_table['TableName'].' + WHERE '.$foreign_key_field.' IN ('.join(',', $foreign_keys).')'; + + $this->RaiseEvent( 'OnBeforeDeleteFromLive', $sub_table['Prefix'], $this->Conn->GetCol($query) ); + + $query = 'DELETE FROM '.$sub_table['TableName'].' + WHERE '.$foreign_key_field.' IN ('.join(',', $foreign_keys).')'; + $this->Conn->Query($query); + } + } + + //sub_table passed here becomes master in the method, and recursively updated and copy its sub tables + $this->DoCopyTempToOriginal($sub_table, $master['Prefix']); + } + } + + function RaiseEvent($name, $prefix, $ids) + { + if ( !is_array($ids) ) return; + foreach ($ids as $id) { + $event = new kEvent( Array('name'=>$name, 'prefix'=>$prefix, 'special'=>'') ); + $event->setEventParam('id', $id); + $this->Application->HandleEvent($event); + } + } + + function DropTempTable($table) + { + $query = sprintf("DROP TABLE IF EXISTS %s", + $this->GetTempName($table) + ); + $this->Conn->Query($query); + } + + function PrepareEdit() + { + $this->DoCopyLiveToTemp($this->Tables, $this->Tables['IDs']); + } + + function SaveEdit($skip_master=0) + { + $this->DoCopyTempToOriginal($this->Tables); + } + + function CancelEdit($master=null) + { + if (!isset($master)) $master = $this->Tables; + $this->DropTempTable($master['TableName']); + if ( getArrayValue($master, 'SubTables') ) { + foreach ($master['SubTables'] as $sub_table) { + $this->CancelEdit($sub_table); + } + } + } +} + +?> \ No newline at end of file Index: trunk/core/kernel/globals.php =================================================================== diff -u --- trunk/core/kernel/globals.php (revision 0) +++ trunk/core/kernel/globals.php (revision 1339) @@ -0,0 +1,189 @@ + $sValue2) + { + $paArray1[$sKey2] = array_merge_recursive2( getArrayValue($paArray1,$sKey2), $sValue2); + } + return $paArray1; + } + + if (!function_exists('print_pre')) { + /** + * Same as print_r, budet designed for viewing in web page + * + * @param Array $data + * @param string $label + */ + function print_pre($data, $label='') + { + if( defined('DEBUG_MODE') && DEBUG_MODE ) + { + global $debugger; + if($label) $debugger->appendHTML(''.$label.''); + $debugger->dumpVars($data); + } + else + { + if($label) echo '',$label,'
'; + echo '
',print_r($data,true),'
'; + } + } + } + + if (!function_exists('getArrayValue')) { + /** + * Returns array value if key exists + * + * @param Array $array + * @param int $key + * @return string + * @access public + */ + function getArrayValue(&$array,$key) + { + $ret = isset($array[$key]) ? $array[$key] : false; + if ($ret && func_num_args() > 2) { + for ($i = 2; $i < func_num_args(); $i++) { + $cur_key = func_get_arg($i); + $ret = getArrayValue( $ret, $cur_key ); + if ($ret === false) break; + } + } + return $ret; + } + } + + /** + * Rename key in associative array, maintaining keys order + * + * @param Array $array Associative Array + * @param mixed $old Old key name + * @param mixed $new New key name + * @access public + */ + function array_rename_key(&$array, $old, $new) + { + foreach ($array as $key => $val) + { + $new_array[ $key == $old ? $new : $key] = $val; + } + $array = $new_array; + } + + /** + * Define constant if it was not already defined before + * + * @param string $const_name + * @param string $const_value + * @access public + */ + function safeDefine($const_name, $const_value) + { + if(!defined($const_name)) define($const_name,$const_value); + } + + +if (!function_exists('parse_portal_ini')) { + function parse_portal_ini($file, $parse_section = false) { + if(!file_exists($file) && !is_readable($file)) + die('Could Not Open Ini File'); + + $contents = file($file); + + $retval = array(); + + $section = ''; + $ln = 1; + $resave = false; + foreach($contents as $line) { + if ($ln == 1 && $line != '<'.'?'.'php die() ?'.">\n") { + $resave = true; + } + $ln++; + $line = trim($line); + $line = eregi_replace(';[.]*','',$line); + if(strlen($line) > 0) { + //echo $line . " - "; + if(eregi('^[[a-z]+]$',str_replace(' ', '', $line))) { + //echo 'section'; + $section = substr($line,1,(strlen($line)-2)); + if ($parse_section) { + $retval[$section] = array(); + } + continue; + } elseif(eregi('=',$line)) { + //echo 'main element'; + list($key,$val) = explode(' = ',$line); + if (!$parse_section) { + $retval[trim($key)] = str_replace('"', '', $val); + } + else { + $retval[$section][trim($key)] = str_replace('"', '', $val); + } + } //end if + //echo '
'; + } //end if + } //end foreach + if ($resave) { + $fp = fopen($file, "w"); + reset($contents); + fwrite($fp,'<'.'?'.'php die() ?'.">\n\n"); + foreach($contents as $line) fwrite($fp,"$line"); + fclose($fp); + } + + return $retval; + } + +if (!function_exists( 'getmicrotime' ) ) { + function getmicrotime() + { + list($usec, $sec) = explode(" ",microtime()); + return ((float)$usec + (float)$sec); + } +} + + function k4_include_once($file) + { + if ( defined('DEBUG_MODE') && dbg_ConstOn('DBG_PROFILE_INCLUDES')) { + + if ( in_array($file, get_required_files()) ) return; + global $debugger; + + $debugger->IncludeLevel++; + $before_time = getmicrotime(); + $before_mem = memory_get_usage(); + include_once($file); + $used_time = getmicrotime() - $before_time; + $used_mem = memory_get_usage() - $before_mem; + + $debugger->IncludeLevel--; + $debugger->IncludesData['file'][] = str_replace(DOC_ROOT.BASE_PATH, '', $file); + $debugger->IncludesData['mem'][] = $used_mem; + $debugger->IncludesData['time'][] = $used_time; + $debugger->IncludesData['level'][] = $debugger->IncludeLevel; + + + } + else { + include_once($file); + } + } +} + +?> \ No newline at end of file Index: trunk/core/kernel/parser/construct_tags.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/parser/construct_tags.php (.../construct_tags.php) (revision 932) +++ trunk/core/kernel/parser/construct_tags.php (.../construct_tags.php) (revision 1339) @@ -1,7 +1,7 @@ CheckEndRecursion($tag)) { + if (defined('EXPERIMENTAL_PRE_PARSE')) { + if ($tag->Tag == 'endif') { + $this->Parser->AppendCompiledCode('}'); + } + } $this->RestoreSkipMode(); //Restoring original SkipMode return true; } + else { + if (defined('EXPERIMENTAL_PRE_PARSE')) { + $this->Parser->AppendCode($tag->GetCode()); + $this->Parser->AppendCompiledCode(join("\n", $tag->GetCode())); + } + } return false; } @@ -131,6 +142,9 @@ $this->Parser->Recurve($this); //Saving current tag in parser recursion array $this->Parser->SetBuffer(''); $this->BlockName = $this->NP['name']; //Stroing BlockName + if (isset($this->NP['args']) ) { + $this->Parser->Args = explode(',', $this->NP['args']); + } $this->StoreSkipMode(); $this->SuggestSkipMode(skip_tags); //We need to skip tags from now break; @@ -141,128 +155,72 @@ { if (parent::CheckRecursion($tag)) { //if endtag matches (SkipMode would be restored then) //Creating template from buffer + + if (defined('EXPERIMENTAL_PRE_PARSE') && isset($this->Application->PreParsedBlocks[$this->BlockName])) return true; + $template = new Template(); $template->SetBody($this->Parser->GetBuffer()); $templates_cache =& $this->Application->recallObject('TemplatesCache'); //Adding template to application' cache - $templates_cache->SetTemplate($this->BlockName,$template); + $templates_cache->SetTemplate($this->BlockName, $template, $this->Parser->TemplateName); + + if (defined('EXPERIMENTAL_PRE_PARSE')) { + $code = $this->Parser->GetCode(); + array_unshift($code, '$o = \'\';'); + array_unshift($code, '$application =& kApplication::Instance();'); + array_unshift($code, 'extract($params);'); + + $code[] = 'return $o;'; + + global $debugger; + + $dbg_functions = $this->Application->isDebugMode() && dbg_ConstOn('DBG_PRINT_PREPARSED'); + + $f_body = ''; + //echo "
";
+				$l = 0;
+				if ($dbg_functions) echo "function ".$this->BlockName." {
"; + foreach ($code as $line) { + $l++; + if ($dbg_functions) { + echo $l.' '.$debugger->highlightString(trim($line)."\n", true); + } + $f_body .= rtrim($line, "\n")."\n"; + } + if ($dbg_functions) echo "} // function ".$this->BlockName." end

"; + //echo "
"; + + //caching func body + $this->Application->PreParsedCache[$this->BlockName] = $f_body; + + $func = create_function('$params', $f_body); + $this->Application->PreParsedBlocks[$this->BlockName] = $func; + $this->Parser->Args = null; + $this->Parser->ResetCode(); + + $this->Parser->AppendCompiledFunction($this->BlockName, $f_body); + } return true; } else { // append the tag itself to the block - while in block, we check every tag to be 'blockend' // if it is not - we need to append the tag to the buffer, which we'll parse later in 'parse_block' if ($tag->Tag != 'block') { - $this->Parser->AppendOutput($tag->GetFullTag()); + if (defined('EXPERIMENTAL_PRE_PARSE') && isset($this->Application->PreParsedBlocks[$this->BlockName])) { + return; + } + if (defined('EXPERIMENTAL_PRE_PARSE')) { + // $this->Parser->AppendCode($tag->GetCode()); + } + else { + $this->Parser->AppendOutput($tag->GetFullTag()); + } } return false; } } } -class XMLTag extends ConstructTag { - var $BlockName = ''; - - function Process() - { - switch ($this->Tag) { - case 'xml': - $this->SetStopTag('xmlend'); //This recursion level should end when 'blockend' is found - $this->Parser->Recurve($this); //Saving current tag in parser recursion array - $this->BlockName = $this->NP['name']; //Storing BlockName - $this->StoreSkipMode(); - $this->SuggestSkipMode(skip_tags); //We need to skip tags from now - $this->Parser->SetBuffer(''); - break; - } - } - - function CheckRecursion(&$tag) - { - if (parent::CheckRecursion($tag)) { //if endtag matches (SkipMode would be restored then) - //Creating template from buffer - $template = new Template(); - $template->SetBody($this->Parser->GetBuffer()); - - //Adding template to application' cache - $this->Parser->Application->Templates->SetTemplate( - $this->BlockName, - $template - ); - $this->Parser->ParseXML($this->BlockName, $this->NP); - return true; - } - else { - if ($tag->Tag != 'xml') { - $this->Parser->AppendOutput($tag->GetFullTag()); - } - return false; - } - } -} - -class IterateTag extends ConstructTag { - - function Process() - { - switch ($this->Tag) { - case 'iterate': - $this->SetStopTag('enditerate'); //This recursion level should end when 'blockend' is found - $this->Parser->Recurve($this); //Saving current tag in parser recursion array - $this->BlockName = $this->NP['block']; //Storing BlockName - $this->StoreSkipMode(); - $this->SuggestSkipMode(skip_tags); //We need to skip tags from now - $this->Parser->SetBuffer(''); - break; - } - } - - function CheckRecursion(&$tag) - { - if (parent::CheckRecursion($tag)) { //if endtag matches (SkipMode would be restored then) - //Creating template from buffer - $template = new Template(); - $template->SetBody($this->Parser->GetBuffer()); - - //Adding template to application' cache - $this->Parser->Application->Templates->SetTemplate( - $this->BlockName, - $template - ); - $this->Parser->ParseXML($this->BlockName, $this->NP); - return true; - } - else { - if ($tag->Tag != 'xml') { - $this->Parser->AppendOutput($tag->GetFullTag()); - } - return false; - } - } - -} - -global $suite; -if (isset($suite)) { - class TestConstructTag extends TestCase { - - function testIFLogic() - { - global $application; - - $tag =& new ConstructTag('m:if prefix="m" function="true"', $application->Parser); - $tag->GetLogic(); - $this->assertTrue($tag->Logic); - - - $tag =& new ConstructTag('m:if prefix="m" function="false"', $application->Parser); - $tag->GetLogic(); - $this->assertFalse($tag->Logic); - } - - } - $suite->addTest(new TestSuite("TestConstructTag")); -} - ?> \ No newline at end of file Index: trunk/core/kernel/utility/debugger.php =================================================================== diff -u -r1088 -r1339 --- trunk/core/kernel/utility/debugger.php (.../debugger.php) (revision 1088) +++ trunk/core/kernel/utility/debugger.php (.../debugger.php) (revision 1339) @@ -1,798 +1,893 @@ 'c:\Program Files\UltraEdit\uedit32.exe', 'params' => '%F/%L'); - $dbg_editors[1] = Array('editor' => 'c:\Program Files\Zend\ZendStudio-4.0Beta\bin\ZDE.exe', 'params' => '%F'); - define('WINDOWS_EDITOR',$dbg_editors[$dbg_editor]['editor'].' '.$dbg_editors[$dbg_editor]['params']); - unset($dbg_editors,$dbg_editor); - } - - class Debugger - { - /** - * Debugger data for building report - * - * @var Array - */ - var $Data = Array(); - var $ProfilerData = Array(); - var $RecursionStack = Array(); // prevent recursion when processing debug_backtrace() function results - - var $TraceNextError=false; - - var $Options = 0; - var $OptionsMap = Array('shutdown_func' => 1, 'error_handler' => 2, - 'output_buffer' => 4, 'highlight_output' => 8); - - - var $longErrors=Array(); - - /** - * Amount of memory used by debugger itself - * - * @var Array - * @access private - */ - var $memoryUsage=Array(); - - function Debugger() + function dbg_ConstOn($const_name) { - $this->memoryUsage['error_handling']=0; // memory amount used by error handler - $this->appendRequest(); + return defined($const_name)&&constant($const_name); } - function initOptions() + function dbg_safeDefine($const_name,$const_value) { - + if(!defined($const_name)) define($const_name,$const_value); } - function mapLongError($msg) - { - $key=$this->generateID(); - $this->longErrors[$key]=$msg; - return $key; - } + unset($_REQUEST['debug_host']); // this var messed up whole detection stuff :( - function setOption($name,$value) + // Detect fact, that this session beeing debugged by Zend Studio + foreach($_REQUEST as $rq_name=>$rq_value) { - if( !isset($this->OptionsMap[$name]) ) die('undefined debugger option: ['.$name.']
'); - if($value) + if( substr($rq_name,0,6)=='debug_' ) { - $this->Options|=$this->OptionsMap[$name]; - } - else - { - $this->Options=$this->Options&~$this->OptionsMap[$name]; - } + define('DBG_ZEND_PRESENT',1); + break; + } } - function getOption($name) - { - if( !isset($this->OptionsMap[$name]) ) die('undefined debugger option: ['.$name.']
'); - return ($this->Options & $this->OptionsMap[$name]) == $this->OptionsMap[$name]; - } + dbg_safeDefine('DBG_ZEND_PRESENT',0); - /** - * Set's flag, that next error that occurs will - * be prepended by backtrace results - * - */ - function traceNext() + // set default values for debugger constants + $dbg_constMap=Array('DBG_OPTIONS'=>0, + 'DBG_USE_HIGHLIGHT'=>1, + 'DBG_USE_SHUTDOWN_FUNC'=>DBG_ZEND_PRESENT?0:1, + 'DBG_HANDLE_ERRORS'=>DBG_ZEND_PRESENT?0:1, + 'DBG_SHOW_MEMORY_USAGE'=>1, + 'DBG_IGNORE_STRICT_ERRORS'=>1, + 'DOC_ROOT'=>$_SERVER['DOCUMENT_ROOT'], + 'DBG_LOCAL_BASE_PATH'=>'w:'); + + foreach($dbg_constMap as $dbg_constName=>$dbg_constValue) { - $this->TraceNextError=true; + dbg_safeDefine($dbg_constName,$dbg_constValue); } - function dumpVars() + // only for IE, in case if no windows php script editor defined + /*if(!defined('WINDOWS_EDITOR')) { - $dumpVars = func_get_args(); - foreach($dumpVars as $varValue) + $dbg_editor = 0; + $dbg_editors[0] = Array('editor' => 'c:\Program Files\UltraEdit\uedit32.exe', 'params' => '%F/%L'); + $dbg_editors[1] = Array('editor' => 'c:\Program Files\Zend\ZendStudio-4.0Beta\bin\ZDE.exe', 'params' => '%F'); + define('WINDOWS_EDITOR',$dbg_editors[$dbg_editor]['editor'].' '.$dbg_editors[$dbg_editor]['params']); + unset($dbg_editors,$dbg_editor); + }*/ + + class Debugger + { + /** + * Debugger data for building report + * + * @var Array + */ + var $Data = Array(); + var $ProfilerData = Array(); + var $RecursionStack = Array(); // prevent recursion when processing debug_backtrace() function results + + var $TraceNextError=false; + + var $Options = 0; + var $OptionsMap = Array('shutdown_func' => 1, 'error_handler' => 2, + 'output_buffer' => 4, 'highlight_output' => 8); + + + var $longErrors=Array(); + + /** + * Amount of memory used by debugger itself + * + * @var Array + * @access private + */ + var $memoryUsage=Array(); + + var $IncludesData=Array(); + var $IncludeLevel=0; + + function Debugger() { - $this->Data[] = Array('value' => $varValue, 'debug_type' => 'var_dump'); + $this->profileStart('kernel4_startup', 'Startup and Initialization of kernel4'); + ini_set('display_errors',dbg_ConstOn('DBG_ZEND_PRESENT')?0:1); + $this->memoryUsage['error_handling']=0; // memory amount used by error handler + $this->appendRequest(); } - } - - function prepareHTML($dataIndex) - { - $Data =& $this->Data[$dataIndex]; - if($Data['debug_type'] == 'html') return $Data['html']; - switch($Data['debug_type']) + function initOptions() { - case 'error': - $fileLink = $this->getFileLink($Data['file'],$Data['line']); - $ret = ''.$this->getErrorNameByCode($Data['no']).': '.$Data['str']; - $ret .= ' in '.$fileLink.' on line '.$Data['line'].''; - return $ret; - break; - case 'var_dump': - $ret = $this->highlightString( print_r($Data['value'], true) ); - return addslashes($ret); - break; + } + + function mapLongError($msg) + { + $key=$this->generateID(); + $this->longErrors[$key]=$msg; + return $key; + } + + function setOption($name,$value) + { + if( !isset($this->OptionsMap[$name]) ) die('undefined debugger option: ['.$name.']
'); + if($value) + { + $this->Options|=$this->OptionsMap[$name]; + } + else + { + $this->Options=$this->Options&~$this->OptionsMap[$name]; + } + } + + function getOption($name) + { + if( !isset($this->OptionsMap[$name]) ) die('undefined debugger option: ['.$name.']
'); + return ($this->Options & $this->OptionsMap[$name]) == $this->OptionsMap[$name]; + } + + /** + * Set's flag, that next error that occurs will + * be prepended by backtrace results + * + */ + function traceNext() + { + $this->TraceNextError=true; + } + + function dumpVars() + { + $dumpVars = func_get_args(); + foreach($dumpVars as $varValue) + { + $this->Data[] = Array('value' => $varValue, 'debug_type' => 'var_dump'); + } + } + + function prepareHTML($dataIndex) + { + $Data =& $this->Data[$dataIndex]; + if($Data['debug_type'] == 'html') return $Data['html']; + + switch($Data['debug_type']) + { + case 'error': + $fileLink = $this->getFileLink($Data['file'],$Data['line']); + $ret = ''.$this->getErrorNameByCode($Data['no']).': '.$Data['str']; + $ret .= ' in '.$fileLink.' on line '.$Data['line'].''; + return $ret; + break; - case 'trace': - ini_set('memory_limit','500M'); - $trace =& $Data['trace']; + case 'var_dump': + return $this->highlightString( print_r($Data['value'], true) ); + break; + + case 'trace': + ini_set('memory_limit','500M'); + $trace =& $Data['trace']; + + //return 'sorry'; + //return $this->highlightString(print_r($trace,true)); + + + $i = 0; $traceCount = count($trace); + $ret = ''; + while($i < $traceCount) + { + $traceRec =& $trace[$i]; + $argsID = 'trace_args_'.$dataIndex.'_'.$i; + if(isset($traceRec['file'])) + { + $func_name=isset($traceRec['class'])?$traceRec['class'].$traceRec['type'].$traceRec['function']:$traceRec['function']; + $ret .= 'Function: '.$this->getFileLink($traceRec['file'],$traceRec['line'],$func_name); + $ret .= ' in '.basename($traceRec['file']).' on line '.$traceRec['line'].'
'; + } + else + { + $ret .= 'no file information available'; + } + + // ensure parameter value is not longer then 200 symbols + $this->processTraceArguments($traceRec['args']); + $args = $this->highlightString(print_r($traceRec['args'], true)); + $ret .= ''; + $i++; + } + return $ret; + break; - //return 'sorry'; - //return $this->highlightString(print_r($trace,true)); - - - $i = 0; $traceCount = count($trace); - $ret = ''; - while($i < $traceCount) + case 'profiler': + $profileKey = $Data['profile_key']; + $Data =& $this->ProfilerData[$profileKey]; + $runtime = ($Data['ends'] - $Data['begins']); // in seconds + return 'Name: '.$Data['description'].'
Runtime: '.$runtime.'s'; + break; + + default: + return 'incorrect debug data'; + break; + } + } + + function processTraceArguments(&$traceArgs) + { + if(!$traceArgs) return ''; + foreach ($traceArgs as $argID => $argValue) + { + if( is_array($argValue) || is_object($argValue) ) { - $traceRec =& $trace[$i]; - $argsID = 'trace_args_'.$dataIndex.'_'.$i; - if(isset($traceRec['file'])) + if(is_object($argValue) && !in_array(get_class($argValue),$this->RecursionStack) ) + { + // object & not in stack - ok + array_push($this->RecursionStack, get_class($argValue)); + settype($argValue,'array'); + $this->processTraceArguments($argValue); + array_pop($this->RecursionStack); + } + elseif(is_object($argValue) && in_array(get_class($argValue),$this->RecursionStack) ) { - $func_name=isset($traceRec['class'])?$traceRec['class'].$traceRec['type'].$traceRec['function']:$traceRec['function']; - $ret .= 'Function: '.$this->getFileLink($traceRec['file'],$traceRec['line'],$func_name); - $ret .= ' in '.basename($traceRec['file']).' on line '.$traceRec['line'].'
'; + // object & in stack - recursion + $traceArgs[$argID] = '**** RECURSION ***'; } - else + else { - $ret .= 'no file information available'; + // normal array here + $this->processTraceArguments($argValue); } - - // ensure parameter value is not longer then 200 symbols - $this->processTraceArguments($traceRec['args']); - $args = $this->highlightString(print_r($traceRec['args'], true)); - $ret .= ''; - $i++; } - return $ret; - break; - - case 'profiler': - $profileKey = $Data['profile_key']; - $Data =& $this->ProfilerData[$profileKey]; - $runtime = ($Data['ends'] - $Data['begins']); // in seconds - return 'Name: '.$Data['description'].'
Runtime: '.$runtime.'s'; - break; - - default: - return 'incorrect debug data'; - break; - } - } - - function processTraceArguments(&$traceArgs) - { - if(!$traceArgs) return ''; - foreach ($traceArgs as $argID => $argValue) - { - if( is_array($argValue) || is_object($argValue) ) - { - if(is_object($argValue) && !in_array(get_class($argValue),$this->RecursionStack) ) - { - // object & not in stack - ok - array_push($this->RecursionStack, get_class($argValue)); - settype($argValue,'array'); - $this->processTraceArguments($argValue); - array_pop($this->RecursionStack); - } - elseif(is_object($argValue) && in_array(get_class($argValue),$this->RecursionStack) ) - { - // object & in stack - recursion - $traceArgs[$argID] = '**** RECURSION ***'; - } else { - // normal array here - $this->processTraceArguments($argValue); + $traceArgs[$argID] = $this->cutStringForHTML($traceArgs[$argID]); } } + } + + function cutStringForHTML($string) + { + if( strlen($string) > 200 ) $string = substr($string,0,50).' ...'; + return $string; + } + + /** + * Format SQL Query using predefined formatting + * and highlighting techniques + * + * @param string $sql + * @return string + */ + function formatSQL($sql) + { + $sql = preg_replace('/(\n|\t| )+/is',' ',$sql); + $sql = preg_replace('/(CREATE TABLE|DROP TABLE|SELECT|UPDATE|SET|REPLACE|INSERT|DELETE|VALUES|FROM|LEFT JOIN|INNER JOIN|LIMIT|WHERE|HAVING|GROUP BY|ORDER BY) /is', "\n\t$1 ",$sql); + return $this->highlightString($sql); + } + + function highlightString($string) + { + if( dbg_ConstOn('DBG_USE_HIGHLIGHT') ) + { + $string = str_replace('\\','_no_match_string_',$string); + $string = highlight_string('', true); + $string = str_replace('_no_match_string_','\\',$string); + return preg_replace('/<\?(.*)php (.*)\?>/s','$2',$string); + } else { - $traceArgs[$argID] = $this->cutStringForHTML($traceArgs[$argID]); + return $string; } } - } - - function cutStringForHTML($string) - { - if( strlen($string) > 200 ) $string = substr($string,0,50).' ...'; - return $string; - } - - /** - * Format SQL Query using predefined formatting - * and highlighting techniques - * - * @param string $sql - * @return string - */ - function formatSQL($sql) - { - $sql = preg_replace('/(\n|\t| )+/is',' ',$sql); - $sql = preg_replace('/(CREATE TABLE|DROP TABLE|SELECT|UPDATE|SET|REPLACE|INSERT|DELETE|VALUES|FROM|LEFT JOIN|INNER JOIN|LIMIT|WHERE|HAVING|GROUP BY|ORDER BY) /is', "\n\t$1 ",$sql); - return $this->highlightString($sql); - } - - function highlightString($string) - { - if( defined('DBG_USE_HIGHLIGHT')&&DBG_USE_HIGHLIGHT ) + + function getFileLink($file, $lineno = 1, $title = '') { - $string = highlight_string('', true); - return preg_replace('/<\?(.*)php (.*)\?>/s','$2',$string); + if(!$title) $title = $file; + $is_mozilla=strpos(strtolower($_SERVER['HTTP_USER_AGENT']),'firefox')!==false?true:false; + if($is_mozilla) + { + return ''.$title.''; + } + else + { + return ''.$title.''; + } + } - else + + function getLocalFile($remoteFile) { - return $string; + return str_replace(DOC_ROOT, DBG_LOCAL_BASE_PATH, $remoteFile); } - } - - function getFileLink($file, $lineno = 1, $title = '') - { - if(!$title) $title = $file; - $is_mozilla=strpos(strtolower($_SERVER['HTTP_USER_AGENT']),'firefox')!==false?true:false; - if($is_mozilla) + + function appendTrace() { - return ''.$title.''; + $trace = debug_backtrace(); + array_shift($trace); + $this->Data[] = Array('trace' => $trace, 'debug_type' => 'trace'); } - else + + function appendHTML($html) { - return ''.$title.''; + $this->Data[] = Array('html' => $html,'debug_type' => 'html'); } - } - - function getLocalFile($remoteFile) - { - return str_replace(DOC_ROOT, WINDOWS_ROOT, $remoteFile); - } - - function appendTrace() - { - $trace = debug_backtrace(); - array_shift($trace); - $this->Data[] = Array('trace' => $trace, 'debug_type' => 'trace'); - } - - function appendHTML($html) - { - $this->Data[] = Array('html' => $html,'debug_type' => 'html'); - } - - /** - * Change debugger info that was already generated before. - * Returns true if html was set. - * - * @param int $index - * @param string $html - * @param string $type = {'append','prepend','replace'} - * @return bool - */ - function setHTMLByIndex($index,$html,$type='append') - { - if( !isset($this->Data[$index]) || $this->Data[$index]['debug_type'] != 'html' ) + /** + * Change debugger info that was already generated before. + * Returns true if html was set. + * + * @param int $index + * @param string $html + * @param string $type = {'append','prepend','replace'} + * @return bool + */ + function setHTMLByIndex($index,$html,$type='append') { - return false; + if( !isset($this->Data[$index]) || $this->Data[$index]['debug_type'] != 'html' ) + { + return false; + } + + switch ($type) + { + case 'append': + $this->Data[$index]['html'] .= '
'.$html; + break; + case 'prepend': + $this->Data[$index]['html'] = $this->Data[$index]['html'].'
'.$html; + break; + case 'replace': + $this->Data[$index]['html'] = $html; + break; + } + return true; } - switch ($type) + /** + * Move $debugLineCount lines of input from debug output + * end to beginning. + * + * @param int $debugLineCount + */ + function moveToBegin($debugLineCount) { - case 'append': - $this->Data[$index]['html'] .= '
'.$html; - break; - case 'prepend': - $this->Data[$index]['html'] = $this->Data[$index]['html'].'
'.$html; - break; - case 'replace': - $this->Data[$index]['html'] = $html; - break; + $lines = array_splice($this->Data,count($this->Data)-$debugLineCount,$debugLineCount); + $this->Data = array_merge($lines,$this->Data); } - return true; - } - - /** - * Move $debugLineCount lines of input from debug output - * end to beginning. - * - * @param int $debugLineCount - */ - function moveToBegin($debugLineCount) - { - $lines = array_splice($this->Data,count($this->Data)-$debugLineCount,$debugLineCount); - $this->Data = array_merge($lines,$this->Data); - } - - function appendRequest() - { - $script = $_SERVER['PATH_TRANSLATED']; - $this->appendHTML('ScriptName: '.$this->getFileLink($script,1,basename($script)).' ('.dirname($script).')'); - ob_start(); - ?> - - - - - $value) - { - if( !is_array($value) && trim($value) == '' ) + + function moveAfterRow($new_row, $debugLineCount) + { + $lines = array_splice($this->Data,count($this->Data)-$debugLineCount,$debugLineCount); + $rows_before = array_splice($this->Data,0,$new_row,$lines); + $this->Data = array_merge($rows_before,$this->Data); + } + + function appendRequest() + { + $script = $_SERVER['PATH_TRANSLATED']; + $this->appendHTML('ScriptName: '.$this->getFileLink($script,1,basename($script)).' ('.dirname($script).')'); + ob_start(); + ?> +
SrcNameValue
+ + + + $value) { - $value = 'no value'; + if( !is_array($value) && trim($value) == '' ) + { + $value = 'no value'; + } + else + { + $value = htmlspecialchars(print_r($value, true)); + } + $src = isset($_GET[$key]) ? 'GE' : (isset($_POST[$key]) ? 'PO' : (isset($_COOKIE[$key]) ? 'CO' : '?') ); + echo ''; } - else - { - $value = htmlspecialchars(print_r($value, true)); - } - $src = isset($_GET[$key]) ? 'GE' : (isset($_POST[$key]) ? 'PO' : (isset($_COOKIE[$key]) ? 'CO' : '?') ); - echo ''; - } - ?> -
SrcNameValue
'.$src.''.$key.''.$value.'
'.$src.''.$key.''.$value.'
- appendHTML( ob_get_contents() ); - ob_end_clean(); - } - - function appendSession() - { - if( isset($_SESSION)&&$_SESSION ) + ?> + + appendHTML( ob_get_contents() ); + ob_end_clean(); + } + + function appendSession() { - $this->appendHTML('PHP Session: ['.ini_get('session.name').']'); - $this->dumpVars($_SESSION); - $this->moveToBegin(2); - } - } - - function profileStart($key, $description) - { - $timeStamp = $this->getMoment(); - $this->ProfilerData[$key] = Array('begins' => $timeStamp, 'ends' => 5000, 'debuggerRowID' => count($this->Data), 'description' => $description); - $this->Data[] = array('profile_key' => $key, 'debug_type' => 'profiler'); - } - - function profileFinish($key) - { - $this->ProfilerData[$key]['ends'] = $this->getMoment(); - } - - function getMoment() - { - list($usec, $sec) = explode(' ', microtime()); - return ((float)$usec + (float)$sec); - } - - function generateID() - { - list($usec, $sec) = explode(" ",microtime()); - - $id_part_1 = substr($usec, 4, 4); - $id_part_2 = mt_rand(1,9); - $id_part_3 = substr($sec, 6, 4); - $digit_one = substr($id_part_1, 0, 1); - if ($digit_one == 0) { - $digit_one = mt_rand(1,9); - $id_part_1 = ereg_replace("^0","",$id_part_1); - $id_part_1=$digit_one.$id_part_1; + if( isset($_SESSION)&&$_SESSION ) + { + $this->appendHTML('PHP Session: ['.ini_get('session.name').']'); + $this->dumpVars($_SESSION); + $this->moveToBegin(2); + } } - return $id_part_1.$id_part_2.$id_part_3; - } - - - function getErrorNameByCode($errorCode) - { - switch($errorCode) + + function profileStart($key, $description) { - case E_USER_ERROR: - return 'Fatal Error'; - break; - - case E_WARNING: - case E_USER_WARNING: - return 'Warning'; - break; - - case E_NOTICE: - case E_USER_NOTICE: - return 'Notice'; - break; - - default: - return ''; - break; + $timeStamp = $this->getMoment(); + $this->ProfilerData[$key] = Array('begins' => $timeStamp, 'ends' => 5000, 'debuggerRowID' => count($this->Data), 'description' => $description); + $this->Data[] = array('profile_key' => $key, 'debug_type' => 'profiler'); } - } - - /** - * Generates report - * - */ - function printReport($returnResult = false) - { - $this->memoryUsage['debugger_start']=memory_get_usage(); - // show php session if any - $this->appendSession(); + function profileFinish($key) + { + $this->ProfilerData[$key]['ends'] = $this->getMoment(); + } - // ensure, that 1st line of debug output always is this one: - $this->appendHTML('Hide Debugger'); - $this->moveToBegin(1); + function getMoment() + { + list($usec, $sec) = explode(' ', microtime()); + return ((float)$usec + (float)$sec); + } - $i = 0; $lineCount = count($this->Data); - ob_start(); - ?> - - - - memoryUsage['debugger_finish']=memory_get_usage(); - $this->memoryUsage['print_report']=$this->memoryUsage['debugger_finish']-$this->memoryUsage['debugger_start']; - $this->memoryUsage['total']=$this->memoryUsage['print_report']+$this->memoryUsage['error_handling']; - $this->memoryUsage['application']=memory_get_usage()-$this->memoryUsage['total']; - if($returnResult) - { - $ret = ob_get_contents(); - ob_clean(); - if( ConstOn('DBG_SHOW_MEMORY_USAGE') ) $ret.=$this->getMemoryUsageReport(); - return $ret; + + + document.onkeydown = keyProcessor; + window.onresize = resizeDebugLayer; + window.onscroll = resizeDebugLayer; + window.focus(); + if( typeof($isFatalError) != 'undefined' && $isFatalError == 1 || ) + { + toggleDebugLayer(); + } + if( typeof($isFatalError) != 'undefined' && $isFatalError == 1) + { + document.getElementById('debug_layer').scrollTop = 10000000; + } + + memoryUsage['debugger_finish']=memory_get_usage(); + $this->memoryUsage['print_report']=$this->memoryUsage['debugger_finish']-$this->memoryUsage['debugger_start']; + $this->memoryUsage['total']=$this->memoryUsage['print_report']+$this->memoryUsage['error_handling']; + $this->memoryUsage['application']=memory_get_usage()-$this->memoryUsage['total']; + if($returnResult) + { + $ret = ob_get_contents(); + ob_clean(); + if( dbg_ConstOn('DBG_SHOW_MEMORY_USAGE') ) $ret.=$this->getMemoryUsageReport(); + return $ret; + } + else + { + ob_end_flush(); + if( dbg_ConstOn('DBG_SHOW_MEMORY_USAGE') ) echo $this->getMemoryUsageReport(); + } } - else + + /** + * Format's memory usage report by debugger + * + * @return string + * @access private + */ + function getMemoryUsageReport() { - ob_end_flush(); - if( ConstOn('DBG_SHOW_MEMORY_USAGE') ) echo $this->getMemoryUsageReport(); + $info=Array('printReport'=>'print_report', + 'saveError'=>'error_handling', + 'Total'=>'total', + 'Application'=>'application'); + $ret=Array(); + foreach($info as $title => $value_key) + { + $ret[]=''.$title.':'.$this->formatSize($this->memoryUsage[$value_key]).''; + } + return ''.implode('',$ret).'
'; } - } - - /** - * Format's memory usage report by debugger - * - * @return string - * @access private - */ - function getMemoryUsageReport() - { - $info=Array('printReport'=>'print_report', - 'saveError'=>'error_handling', - 'Total'=>'total', - 'Application'=>'application'); - $ret=Array(); - foreach($info as $title => $value_key) - { - $ret[]=$title.': '.$this->formatSize($this->memoryUsage[$value_key]).''; - } - return implode('
',$ret); - } - - - /** - * User-defined error handler - * - * @param int $errno - * @param string $errstr - * @param string $errfile - * @param int $errline - * @param array $errcontext - */ - function saveError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '') - { - $memory_used=Array(); - $memory_used['begin']=memory_get_usage(); - $errorType = $this->getErrorNameByCode($errno); - if(!$errorType) + + /** + * User-defined error handler + * + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param int $errline + * @param array $errcontext + */ + function saveError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '') { - trigger_error('Unknown error type ['.$errno.']', E_USER_ERROR); - return false; + $memory_used=Array(); + $memory_used['begin']=memory_get_usage(); + + $errorType = $this->getErrorNameByCode($errno); + if(!$errorType) + { + trigger_error('Unknown error type ['.$errno.']', E_USER_ERROR); + return false; + } + + if( dbg_ConstOn('DBG_IGNORE_STRICT_ERRORS') && defined('E_STRICT') && ($errno == E_STRICT) ) return; + + $long_id_pos=strrpos($errstr,'#'); + if($long_id_pos!==false) + { + // replace short message with long one (due triger_error limitations on message size) + $long_id=substr($errstr,$long_id_pos+1,strlen($errstr)); + $errstr=$this->longErrors[$long_id]; + unset($this->longErrors[$long_id]); + } + + + /*in /www/kostja/in-commerce4/kernel/kernel4/parser/construct_tags.php(177) : runtime-created function on line + + [PRE-PARSED block, $line 13]: Undefined variable: IdField*/ + + if( strpos($errfile,'runtime-created') !== false ) { + $errfile = ' PRE-PARSED block '.$this->CurrentPreParsedBlock.' '; + } + + if( strpos($errfile,'eval()\'d code') !== false ) + { + $errstr = '[EVAL, line '.$errline.']: '.$errstr; + $tmpStr = $errfile; + $pos = strpos($tmpStr,'('); + $errfile = substr($tmpStr,0,$pos); + $pos++; + $errline = substr($tmpStr,$pos,strpos($tmpStr,')',$pos)-$pos); + } + if($this->TraceNextError) + { + $this->appendTrace(); + $this->TraceNextError=false; + } + $this->Data[] = Array('no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline, 'context' => $errcontext, 'debug_type' => 'error'); + $memory_used['end']=memory_get_usage(); + $this->memoryUsage['error_handling']+=$memory_used['end']-$memory_used['begin']; + if( substr($errorType,0,5) == 'Fatal') + { + echo ''; + exit; + } } - $long_id_pos=strrpos($errstr,'#'); - if($long_id_pos!==false) + function saveToFile($msg) { - // replace short message with long one (due triger_error limitations on message size) - $long_id=substr($errstr,$long_id_pos+1,strlen($errstr)); - $errstr=$this->longErrors[$long_id]; - unset($this->longErrors[$long_id]); + $fp = fopen($_SERVER['DOCUMENT_ROOT'].'/vb_debug.txt', 'a'); + fwrite($fp,$msg."\n"); + fclose($fp); } - if( strpos($errfile,'eval()\'d code') !== false ) + /** + * Formats file/memory size in nice way + * + * @param int $bytes + * @return string + * @access public + */ + function formatSize($bytes) { - $errstr = '[EVAL, line '.$errline.']: '.$errstr; - $tmpStr = $errfile; - $pos = strpos($tmpStr,'('); - $errfile = substr($tmpStr,0,$pos); - $pos++; - $errline = substr($tmpStr,$pos,strpos($tmpStr,')',$pos)-$pos); + if ($bytes >= 1099511627776) { + $return = round($bytes / 1024 / 1024 / 1024 / 1024, 2); + $suffix = "TB"; + } elseif ($bytes >= 1073741824) { + $return = round($bytes / 1024 / 1024 / 1024, 2); + $suffix = "GB"; + } elseif ($bytes >= 1048576) { + $return = round($bytes / 1024 / 1024, 2); + $suffix = "MB"; + } elseif ($bytes >= 1024) { + $return = round($bytes / 1024, 2); + $suffix = "KB"; + } else { + $return = $bytes; + $suffix = "Byte"; + } + $return .= ' '.$suffix; + return $return; } - if($this->TraceNextError) - { - $this->appendTrace(); - $this->TraceNextError=false; - } - $this->Data[] = Array('no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline, 'context' => $errcontext, 'debug_type' => 'error'); - $memory_used['end']=memory_get_usage(); - $this->memoryUsage['error_handling']+=$memory_used['end']-$memory_used['begin']; - if( substr($errorType,0,5) == 'Fatal') - { - echo ''; - exit; - } + } - - function saveToFile($msg) + + if( !function_exists('memory_get_usage') ) { - $fp = fopen($_SERVER['DOCUMENT_ROOT'].'/vb_debug.txt', 'a'); - fwrite($fp,$msg."\n"); - fclose($fp); + function memory_get_usage(){ return -1; } } - /** - * Formats file/memory size in nice way - * - * @param int $bytes - * @return string - * @access public - */ - function formatSize($bytes) - { - if ($bytes >= 1099511627776) { - $return = round($bytes / 1024 / 1024 / 1024 / 1024, 2); - $suffix = "TB"; - } elseif ($bytes >= 1073741824) { - $return = round($bytes / 1024 / 1024 / 1024, 2); - $suffix = "GB"; - } elseif ($bytes >= 1048576) { - $return = round($bytes / 1024 / 1024, 2); - $suffix = "MB"; - } elseif ($bytes >= 1024) { - $return = round($bytes / 1024, 2); - $suffix = "KB"; - } else { - $return = $bytes; - $suffix = "Byte"; - } - $return .= ' '.$suffix; - return $return; + $debugger = new Debugger(); + if(dbg_ConstOn('DBG_HANDLE_ERRORS')) set_error_handler( array(&$debugger,'saveError') ); + if(dbg_ConstOn('DBG_USE_SHUTDOWN_FUNC')) { + register_shutdown_function( array(&$debugger,'printReport') ); } - } - - if( !function_exists('memory_get_usage') ) - { - function memory_get_usage(){ return -1; } - } - - function ConstOn($const_name) - { - return defined($const_name)&&constant($const_name); - } - - $debugger = new Debugger(); - if(ConstOn('DBG_HANDLE_ERRORS')) set_error_handler( array(&$debugger,'saveError') ); - if(ConstOn('DBG_USE_SHUTDOWN_FUNC')) register_shutdown_function( array(&$debugger,'printReport') ); - - ?> \ No newline at end of file Index: trunk/core/kernel/db/db_event_handler.php =================================================================== diff -u -r958 -r1339 --- trunk/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 958) +++ trunk/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 1339) @@ -1,5 +1,8 @@ Conn =& $this->Application->GetADODBConnection(); + } + + function mapEvents() + { + $events_map = Array('OnRemoveFilters' => 'FilterAction', + 'OnApplyFilters' => 'FilterAction'); + + $this->eventMethods = array_merge($this->eventMethods, $events_map); + } + + /** + * Returns ID of current item to be edited + * by checking ID passed in get/post as prefix_id + * or by looking at first from selected ids, stored. + * Returned id is also stored in Session in case + * it was explicitly passed as get/post + * * @param kEvent $event * @return int */ function getPassedID(&$event) { - $ret=$this->Application->GetVar($event->getPrefixSpecial().'_id'); - if($ret===false) - { - $ids=$this->Application->GetVar($event->getPrefixSpecial().'_selected_ids'); + //$ret = $this->Application->GetLinkedVar($event->getPrefixSpecial(true).'_id', $event->getPrefixSpecial().'_id'); + + // ?? We don't need to store selected id in session, as long as we have pass=all by default, which + // means that main item id will be passed to all sub-item screens by default + // another prove of that is that sub-items relay on main item '_mode' = 't' for switching to temp tables + // Also having id in session raised problems with the id of deleted item stuck in session + $ret = $this->Application->GetVar($event->getPrefixSpecial(true).'_id'); + if ($ret) return $ret; // if id was passed explicity as prefix[_special]_id use it, and store it in session + + // recall selected ids array and use the first one + $ids=$this->Application->GetVar($event->getPrefixSpecial().'_selected_ids'); + if ($ids != '') { $ids=explode(',',$ids); if($ids) $ret=array_shift($ids); } + else { // if selected ids are not yet stored + $this->StoreSelectedIDs($event); + return $this->Application->GetVar($event->getPrefixSpecial(true).'_id'); // StoreSelectedIDs sets this variable + } return $ret; } /** + * Prepares and stores selected_ids string + * in Session and Application Variables + * by getting all checked ids from grid plus + * id passed in get/post as prefix_id + * + * @param kEvent $event + */ + function StoreSelectedIDs(&$event) + { + $ret = Array(); + + // May be we don't need this part: ? + $passed = $this->Application->GetVar($event->getPrefixSpecial(true).'_id'); + if($passed !== false && $passed != '') + { + array_push($ret, $passed); + } + + $ids = Array(); + + // get selected ids from post & save them to session + $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + if($items_info) + { + $id_field = $this->Application->getUnitOption($event->Prefix,'IDField'); + foreach($items_info as $id => $field_values) + { + if( getArrayValue($field_values,$id_field) ) array_push($ids,$id); + } + //$ids=array_keys($items_info); + } + + $ret = array_unique(array_merge($ret, $ids)); + + $this->Application->SetVar($event->getPrefixSpecial().'_selected_ids',implode(',',$ret)); + $this->Application->LinkVar($event->getPrefixSpecial().'_selected_ids'); + + // This is critical - otherwise getPassedID will return last ID stored in session! (not exactly true) + // this smells... needs to be refactored + $first_id = getArrayValue($ret,0); + if($first_id === false) trigger_error('Requested ID for prefix '.$event->getPrefixSpecial().' not passed',E_USER_NOTICE); + $this->Application->SetVar($event->getPrefixSpecial(true).'_id', $first_id); + } + + /** + * Returns stored selected ids as an array + * + * @param kEvent $event + * @return array + */ + function getSelectedIDs(&$event) + { + return explode(',', $this->Application->GetVar($event->getPrefixSpecial().'_selected_ids')); + } + + /** + * Returs associative array of submitted fields for current item + * Could be used while creating/editing single item - + * meaning on any edit form, except grid edit + * + * @param kEvent $event + */ + function getSubmittedFields(&$event) + { + $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + $field_values = $items_info ? array_shift($items_info) : Array(); + return $field_values; + } + + /** + * Removes any information about current/selected ids + * from Application variables and Session + * + * @param kEvent $event + */ + function clearSelectedIDs(&$event) + { + $prefix_special = $event->getPrefixSpecial(); + $this->Application->RemoveVar($prefix_special.'_selected_ids'); + $this->Application->SetVar($prefix_special.'_selected_ids', ''); + $this->Application->RemoveVar($prefix_special.'_id'); // not needed anymore ?? + $this->Application->SetVar($prefix_special.'_id', ''); + //$this->Application->RemoveVar($prefix_special.'_modified'); + } + + /*function SetSaveEvent(&$event) + { + $this->Application->SetVar($event->Prefix_Special.'_SaveEvent','OnUpdate'); + $this->Application->LinkVar($event->Prefix_Special.'_SaveEvent'); + }*/ + + /** + * Common builder part for Item & List + * + * @param kDBBase $object + * @param kEvent $event + * @access private + */ + function dbBuild(&$object,&$event) + { + $object->Configure(); + + if( $this->UseTempTables($event) ) + { + $object->SwitchToTemp(); + } + + // This strange constuction creates hidden field for storing event name in form submit + // It pass SaveEvent to next screen, otherwise after unsuccsefull create it will try to update rather than create + $current_event = $this->Application->GetVar($event->Prefix_Special.'_event'); + $this->Application->setEvent($event->Prefix_Special, $current_event); + + $save_event = $this->UseTempTables($event) && $this->Application->GetTopmostPrefix($event->Prefix) == $event->Prefix ? 'OnSave' : 'OnUpdate'; + $this->Application->SetVar($event->Prefix_Special.'_SaveEvent',$save_event); + } + + /** * Builds item (loads if needed) * * @param kEvent $event * @access protected */ function OnItemBuild(&$event) { - $object =& $event->createObject(); - $this->dbBuild(&$object,&$event); + $object =& $event->getObject(); + $this->dbBuild($object,$event); - $sql=$this->getSelectSQL($event,'OnItemPrepareQuery'); + $sql = $this->ItemPrepareQuery($event); + $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); - // 1. set from config what needed - $fields = $this->Application->getUnitOption($event->Prefix,'Fields'); - $object->setConfigFields( array_keys($fields) ); - // 2. loads if allowed $auto_load = $this->Application->getUnitOption($event->Prefix,'AutoLoad'); - if($auto_load) - { - $id = $this->getPassedID(&$event); - $object->Load($id); + if($auto_load) $this->LoadItem($event); + + $actions =& $this->Application->recallObject('kActions'); + $actions->Set($event->Prefix_Special.'_GoTab', ''); + + $actions->Set($event->Prefix_Special.'_GoId', ''); + } + + /** + * Build subtables array from configs + * + * @param kEvent $event + */ + function OnTempHandlerBuild(&$event) + { + $object =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); + $object->BuildTables( $event->Prefix, $this->getSelectedIDs($event) ); + } + + /** + * Enter description here... + * + * @param kEvent $event + * @return unknown + */ + function UseTempTables(&$event) + { + $object = &$event->getObject(); + $top_prefix = $this->Application->GetTopmostPrefix($event->Prefix); + return $this->Application->GetVar($top_prefix.'_mode') == 't'; + } + + /** + * Enter description here... + * + * @param kEvent $event + * @return unknown + */ + function __GetTopmostPrefix(&$event) + { + $current_prefix = $event->Prefix; + while ( $parent_prefix = $this->Application->getUnitOption($current_prefix, 'ParentPrefix') ) { + $current_prefix = $parent_prefix; } - $this->Application->SetVar($event->Prefix_Special.'_SaveEvent','OnUpdate'); + return $current_prefix; } + function LoadItem(&$event) + { + $object =& $event->getObject(); + if ( $event->getEventParam('ByParent') ) { + $parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix'); + $parent_table_key = $this->Application->getUnitOption($event->Prefix, 'ParentTableKey'); + $parent_object =& $this->Application->recallObject($parent_prefix); + $id = $parent_object->GetDBField($parent_table_key); + $id_field = $this->Application->getUnitOption($event->Prefix, 'ForeignKey'); + } + else { + $id = $this->getPassedID($event); + $id_field = null; + } + $object->Load($id, $id_field); + + $actions =& $this->Application->recallObject('kActions'); + $actions->Set($event->Prefix_Special.'_id', $object->GetId()); + } + /** * Builds list * @@ -83,22 +301,53 @@ */ function OnListBuild(&$event) { - $event->setPseudoClass('_List'); - $object =& $event->createObject(); + //$event->setPseudoClass('_List'); + $object =& $event->getObject(); - $this->dbBuild(&$object,&$event); + $this->dbBuild($object,$event); - $sql=$this->getSelectSQL($event,'OnListPrepareQuery'); + $sql = $this->ListPrepareQuery($event); + $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); - $t=$this->Application->GetVar('t'); - $this->Application->StoreVar('redirect_to',$t); + $object->linkToParent(''); - $this->SetPagination(&$event); - $this->SetSorting(&$event); + $this->AddFilters($event); + $this->SetCustomQuery($event); // new!, use this for dynamic queries based on specials for ex. + $this->SetPagination($event); + $this->SetSorting($event); + + $actions =& $this->Application->recallObject('kActions'); + $actions->Set('remove_specials['.$event->Prefix_Special.']', '0'); + $actions->Set($event->Prefix_Special.'_GoTab', ''); } + + /** + * Apply any custom changes to list's sql query + * + * @param kEvent $event + * @access protected + * @see OnListBuild + */ + function SetCustomQuery(&$event) + { + + } + + /** + * Set's new perpage for grid + * + * @param kEvent $event + */ + function OnSetPerPage(&$event) + { + $per_page = $this->Application->GetVar($event->getPrefixSpecial(true).'_PerPage'); + $this->Application->StoreVar( $event->getPrefixSpecial().'_PerPage', $per_page ); + } + + /** * Set's correct page for list * based on data provided with event * @@ -108,22 +357,40 @@ */ function SetPagination(&$event) { + // get PerPage (forced -> session -> config -> 10) $per_page = $event->getEventParam('per_page'); if(!$per_page) { - $per_page=$this->Application->RecallVar($event->Prefix_Special.'_PerPage'); + $per_page_var = $event->getPrefixSpecial().'_PerPage'; + + $per_page = $this->Application->RecallVar($per_page_var); if(!$per_page) { - $per_page=10; + $per_page = $this->Application->ConfigValue($per_page_var); + if(!$per_page) $per_page = 10; } } - $event->setPseudoClass('_List'); - $object =& $event->createObject(); + $object =& $event->getObject(); $object->SetPerPage($per_page); - $object->CountRecs(); - - $object->SetPage( $this->Application->GetLinkedVar( $event->Prefix_Special.'_Page' ) ); + + if( !$event->getEventParam('skip_counting') ) + { + $object->CountRecs(); + + $this->Application->StoreVarDefault($event->getPrefixSpecial().'_Page', 1); + + $page = $this->Application->GetLinkedVar($event->getPrefixSpecial(true).'_Page', $event->getPrefixSpecial().'_Page'); + $pages = $object->GetTotalPages(); + + if($page > $pages) + { + $this->Application->StoreVar($event->getPrefixSpecial().'_Page', 1); + $page = 1; + } + + $object->SetPage($page); + } } /** @@ -137,20 +404,35 @@ function SetSorting(&$event) { $event->setPseudoClass('_List'); - $object =& $event->createObject(); + $object =& $event->getObject(); $cur_sort1 = $this->Application->RecallVar($event->Prefix_Special.'_Sort1'); $cur_sort1_dir = $this->Application->RecallVar($event->Prefix_Special.'_Sort1_Dir'); $cur_sort2 = $this->Application->RecallVar($event->Prefix_Special.'_Sort2'); $cur_sort2_dir = $this->Application->RecallVar($event->Prefix_Special.'_Sort2_Dir'); - //Use default if not specified - /*if ( $cur_sort1 === false || $cur_sort1_dir == false ) { - $cur_sort1 = $this->Application->ConfigOption($event->Prefix_Special.'_Sort1'); - $cur_sort1_dir = $this->Application->ConfigOption($event->Prefix_Special.'_Sort1_Dir'); - $cur_sort2 = $this->Application->ConfigOption($event->Prefix_Special.'_Sort2'); - $cur_sort2_dir = $this->Application->ConfigOption($event->Prefix_Special.'_Sort2_Dir'); - }*/ + $list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings'); + $sorting_prefix = getArrayValue($list_sortings, $event->Special) ? $event->Special : ''; + + // Use default if not specified + if ( !$cur_sort1 || !$cur_sort1_dir) + { + if ( $sorting = getArrayValue($list_sortings, $sorting_prefix, 'Sorting') ) { + reset($sorting); + $cur_sort1 = key($sorting); + $cur_sort1_dir = current($sorting); + if (next($sorting)) { + $cur_sort2 = key($sorting); + $cur_sort2_dir = current($sorting); + } + } + } + + if ( $forced_sorting = getArrayValue($list_sortings, $sorting_prefix, 'ForcedSorting') ) { + foreach ($forced_sorting as $field => $dir) { + $object->AddOrderField($field, $dir); + } + } if($cur_sort1 != '' && $cur_sort1_dir != '') { @@ -162,14 +444,53 @@ } } + /** - * Some kind of filter processing stuff. - * Not in use by now + * Add filters found in session * + * @param kEvent $event */ - function AddFilters() + function AddFilters(&$event) { - + $object =& $event->getObject(); + + $search_filter = $this->Application->RecallVar($event->getPrefixSpecial().'_search_filter'); + if($search_filter) + { + $search_filter = unserialize($search_filter); + foreach($search_filter as $search_field => $filter_params) + { + $filter_type = ($filter_params['type'] == 'having') ? HAVING_FILTER : WHERE_FILTER; + $object->addFilter($search_field, $filter_params['value'], $filter_type, FLT_SEARCH); + } + } + + $view_filter = $this->Application->RecallVar($event->getPrefixSpecial().'_view_filter'); + if($view_filter) + { + $view_filter = unserialize($view_filter); + $temp_filter =& $this->Application->makeClass('kMultipleFilter'); + $filter_menu = $this->Application->getUnitOption($event->Prefix,'FilterMenu'); + + $group_key = 0; $group_count = count($filter_menu['Groups']); + while($group_key < $group_count) + { + $group_info = $filter_menu['Groups'][$group_key]; + + $temp_filter->setType( constant('FLT_TYPE_'.$group_info['mode']) ); + $temp_filter->clearFilters(); + foreach ($group_info['filters'] as $flt_id) + { + $sql_key = getArrayValue($view_filter,$flt_id) ? 'on_sql' : 'off_sql'; + if ($filter_menu['Filters'][$flt_id][$sql_key] != '') + { + $temp_filter->addFilter('view_filter_'.$flt_id, $filter_menu['Filters'][$flt_id][$sql_key]); + } + } + $object->addFilter('view_group_'.$group_key, $temp_filter, $group_info['type'] , FLT_VIEW); + $group_key++; + } + } } /** @@ -180,56 +501,51 @@ */ function OnSetSorting(&$event) { - $this->Application->LinkVar($event->getPrefixSpecial(true).'_Sort1',$event->Prefix_Special.'_Sort1'); - $this->Application->LinkVar($event->getPrefixSpecial(true).'_Sort1_Dir',$event->Prefix_Special.'_Sort1_Dir'); + $cur_sort1 = $this->Application->RecallVar($event->Prefix_Special.'_Sort1'); + $cur_sort1_dir = $this->Application->RecallVar($event->Prefix_Special.'_Sort1_Dir'); + $cur_sort2 = $this->Application->RecallVar($event->Prefix_Special.'_Sort2'); + $cur_sort2_dir = $this->Application->RecallVar($event->Prefix_Special.'_Sort2_Dir'); - //$event->setPseudoClass('_List'); - //$object =& $event->createObject(); + $passed_sort1 = $this->Application->GetVar($event->getPrefixSpecial(true).'_Sort1'); + if ($cur_sort1 == $passed_sort1) { + $cur_sort1_dir = $cur_sort1_dir == 'asc' ? 'desc' : 'asc'; + } + else { + $cur_sort2 = $cur_sort1; + $cur_sort2_dir = $cur_sort1_dir; + $cur_sort1 = $passed_sort1; + $cur_sort1_dir = 'asc'; + } + + $this->Application->StoreVar($event->Prefix_Special.'_Sort1', $cur_sort1); + $this->Application->StoreVar($event->Prefix_Special.'_Sort1_Dir', $cur_sort1_dir); + $this->Application->StoreVar($event->Prefix_Special.'_Sort2', $cur_sort2); + $this->Application->StoreVar($event->Prefix_Special.'_Sort2_Dir', $cur_sort2_dir); } /** - * Common builder part for Item & List + * Set sorting directly to session * - * @param Object $object - * @access private + * @param kEvent $event */ - function dbBuild(&$object,&$event) + function OnSetSortingDirect(&$event) { - // set Item & List common parameters from config - $table = $this->Application->getUnitOption($event->Prefix,'TableName'); - $object->setTableName($table); - - $id_field = $this->Application->getUnitOption($event->Prefix,'IDField'); - $object->setIDField($id_field); - - // get selected ids from post & save them to session - $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); - if($items_info) - { - $ids=array_keys($items_info); - $this->Application->SetVar($event->getPrefixSpecial().'_selected_ids',implode(',',$ids)); - } - $this->Application->LinkVar($event->getPrefixSpecial().'_selected_ids'); - - // set's any possible hidden fields needed for both Item & List - $current_event = $this->Application->GetVar($event->Prefix_Special.'_event'); - $this->Application->setEvent($event->Prefix_Special,$current_event); + $field_pos = $this->Application->GetVar($event->getPrefixSpecial(true).'_SortPos'); + $this->Application->LinkVar( $event->getPrefixSpecial(true).'_Sort'.$field_pos, $event->Prefix_Special.'_Sort'.$field_pos); + $this->Application->LinkVar( $event->getPrefixSpecial(true).'_Sort'.$field_pos.'_Dir', $event->Prefix_Special.'_Sort'.$field_pos.'_Dir'); } /** - * Returns select query for loading item/list + * Reset grid sorting to default (from config) * * @param kEvent $event - * @param string $event_name - * @return string - * @access protected */ - function getSelectSQL(&$event,$event_name) + function OnResetSorting(&$event) { - $new_event =& $this->inheritEvent(&$event); - $new_event->Name=$event_name; - $this->Application->HandleEvent(&$new_event); - return $event->getEventParam('SQLQuery'); + $this->Application->RemoveVar($event->Prefix_Special.'_Sort1'); + $this->Application->RemoveVar($event->Prefix_Special.'_Sort1_Dir'); + $this->Application->RemoveVar($event->Prefix_Special.'_Sort2'); + $this->Application->RemoveVar($event->Prefix_Special.'_Sort2_Dir'); } /** @@ -241,11 +557,10 @@ * @param kEvent $event * @access protected */ - function OnItemPrepareQuery(&$event) + function ItemPrepareQuery(&$event) { $sqls = $this->Application->getUnitOption($event->Prefix,'ItemSQLs'); - $sql = isset($sqls[$event->Special])?$sqls[$event->Special]:$sqls['']; - $event->MasterEvent->setEventParam('SQLQuery',$sql); + return isset($sqls[$event->Special]) ? $sqls[$event->Special] : $sqls['']; } /** @@ -257,11 +572,10 @@ * @param kEvent $event * @access protected */ - function OnListPrepareQuery(&$event) + function ListPrepareQuery(&$event) { $sqls = $this->Application->getUnitOption($event->Prefix,'ListSQLs'); - $sql = isset($sqls[$event->Special])?$sqls[$event->Special]:$sqls['']; - $event->MasterEvent->setEventParam('SQLQuery',$sql); + return isset( $sqls[$event->Special] ) ? $sqls[$event->Special] : $sqls['']; } /** @@ -272,22 +586,29 @@ */ function OnCreate(&$event) { - $this->Application->setUnitOption($this->getPrefixSpecial(),'AutoLoad',false); - $object =& $event->createObject(); - $this->prepareObject(&$object,&$event); + $this->Application->setUnitOption($event->Prefix,'AutoLoad',false); + $object =& $event->getObject(); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if($items_info) $field_values = array_shift($items_info); $object->SetFieldsFromHash($field_values); - if( $object->Create() ) + $this->customProcessing($event,'before'); + + //look at kDBItem' Create for ForceCreateId description, it's rarely used and is NOT set by default + if( $object->Create($event->getEventParam('ForceCreateId')) ) { + if ($this->UseTempTables($event)) $object->setTempID(); + $this->customProcessing($event,'after'); $event->status=erSUCCESS; + $event->redirect_params = Array('opener'=>'u'); } else { - $event->status=erFATAL; + $event->status=erFAIL; $event->redirect=false; + $this->Application->SetVar($event->Prefix_Special.'_SaveEvent','OnCreate'); + $object->setID(0); } } @@ -299,30 +620,31 @@ */ function OnUpdate(&$event) { - $this->Application->setUnitOption($this->getPrefixSpecial(),'AutoLoad',false); - $object =& $event->createObject(); - $this->prepareObject(&$object,&$event); + $this->Application->setUnitOption($event->Prefix,'AutoLoad',false); + $object =& $event->getObject(); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if($items_info) { foreach($items_info as $id => $field_values) { - //$object->Load($id); + $object->Load($id); $object->SetFieldsFromHash($field_values); + $this->customProcessing($event, 'before'); if( $object->Update($id) ) { + $this->customProcessing($event, 'after'); $event->status=erSUCCESS; } else { - $event->status=erFATAL; + $event->status=erFAIL; $event->redirect=false; break; } } } - + $event->redirect_params = Array('opener'=>'u'); } /** @@ -333,18 +655,17 @@ */ function OnDelete(&$event) { - $this->Application->setUnitOption($this->getPrefixSpecial(),'AutoLoad',false); - $object =& $event->createObject(); - $object->ID=$this->Application->GetVar($event->Prefix_Special.'_id'); + $this->Application->setUnitOption($event->Prefix,'AutoLoad',false); + $object =& $event->getObject(); + $object->ID = $this->getPassedID($event); if( $object->Delete() ) { - $event->status=erSUCCESS; + $event->status = erSUCCESS; } else { - $event->status=erFATAL; - $event->redirect=false; - break; + $event->status = erFAIL; + $event->redirect = false; } } @@ -356,11 +677,14 @@ */ function OnNew(&$event) { - $this->Application->setUnitOption($this->getPrefixSpecial(),'AutoLoad',false); - $object =& $event->createObject(); - $this->prepareObject(&$object,&$event); + $this->Application->setUnitOption($event->Prefix,'AutoLoad',false); + $object =& $event->getObject(); $object->setID(0); $this->Application->SetVar($event->Prefix_Special.'_SaveEvent','OnCreate'); + + $table_info = $object->getLinkedInfo(); + $object->SetDBField($table_info['ForeignKey'], $table_info['ParentId']); + $event->redirect=false; } @@ -372,9 +696,605 @@ */ function OnCancel(&$event) { + $event->redirect_params = Array('opener'=>'u'); + } + + + /** + * Deletes all selected items. + * Automatically recurse into sub-items using temp handler, and deletes sub-items + * by calling its Delete method if sub-item has AutoDelete set to true in its config file + * + * @param kEvent $event + */ + function OnMassDelete(&$event) + { + $event->status=erSUCCESS; + $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); + $this->StoreSelectedIDs($event); + + $event->setEventParam('ids', $this->getSelectedIDs($event) ); + $this->customProcessing($event, 'before'); + $ids = $event->getEventParam('ids'); + + if($ids) + { + $temp->DeleteItems($event->Prefix, $event->Special, $ids); + } + $this->clearSelectedIDs($event); } + + /** + * Prepare temp tables and populate it + * with items selected in the grid + * + * @param kEvent $event + */ + function OnEdit(&$event) + { + $this->StoreSelectedIDs($event); + + $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); + $temp->PrepareEdit(); + + $event->redirect=false; + } + + /** + * Saves content of temp table into live and + * redirects to event' default redirect (normally grid template) + * + * @param kEvent $event + */ + function OnSave(&$event) + { + $event->CallSubEvent('OnPreSave'); + if ($event->status==erSUCCESS) { + $skip_master=false; + $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); + + // newly created item + /*if($this->getPassedID($event) == 0) + { + $master_id = $temp->CopyMasterToOriginal(); + $temp->UpdateForeignKeys($master_id); // save linked field values + $skip_master = true; //we've already copied master table to get the id + }*/ + + $temp->SaveEdit($skip_master); + $this->clearSelectedIDs($event); + + $event->redirect_params = Array('opener'=>'u'); + $this->Application->RemoveVar($event->getPrefixSpecial().'_modified'); + } + } + + /** + * Cancels edit + * Removes all temp tables and clears selected ids + * + * @param kEvent $event + */ + function OnCancelEdit(&$event) + { + $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); + $temp->CancelEdit(); + + $this->clearSelectedIDs($event); + $event->redirect_params = Array('opener'=>'u'); + $this->Application->RemoveVar($event->getPrefixSpecial().'_modified'); + } + + /** + * Saves edited item into temp table + * If there is no id, new item is created in temp table + * + * @param kEvent $event + */ + function OnPreSave(&$event) + { + //$event->redirect = false; + // if there is no id - it means we need to create an item + $item_id = $this->getPassedID($event); + if ($item_id == '') { + $event->CallSubEvent('OnPreSaveCreated'); + return; + } + + $object =& $event->getObject(); + + $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + if($items_info) + { + foreach($items_info as $id => $field_values) + { + $object->SetDefaultValues(); + $object->Load($id); + $object->SetFieldsFromHash($field_values); + if( $object->Update($id) ) + { + $event->status=erSUCCESS; + } + else + { + $event->status=erFAIL; + $event->redirect=false; + break; + } + } + } + } + + /** + * Saves edited item in temp table and loads + * item with passed id in current template + * Used in Prev/Next buttons + * + * @param kEvent $event + */ + function OnPreSaveAndGo(&$event) + { + $event->CallSubEvent('OnPreSave'); + if ($event->status==erSUCCESS) { + $event->redirect_params[$event->getPrefixSpecial(true).'_id'] = $this->Application->GetVar($event->Prefix_Special.'_GoId'); + } + } + + /** + * Saves edited item in temp table and goes + * to passed tabs, by redirecting to it with OnPreSave event + * + * @param kEvent $event + */ + function OnPreSaveAndGoToTab(&$event) + { + $event->CallSubEvent('OnPreSave'); + if ($event->status==erSUCCESS) { + $event->redirect=$this->Application->GetVar($event->getPrefixSpecial(true).'_GoTab'); + } + } + + /** + * Saves editable list and goes to passed tab, + * by redirecting to it with empty event + * + * @param kEvent $event + */ + function OnUpdateAndGoToTab(&$event) + { + $event->setPseudoClass('_List'); + $event->CallSubEvent('OnUpdate'); + if ($event->status==erSUCCESS) { + $event->redirect=$this->Application->GetVar($event->getPrefixSpecial(true).'_GoTab'); + } + } + + /** + * Prepare temp tables for creating new item + * but does not create it. Actual create is + * done in OnPreSaveCreated + * + * @param kEvent $event + */ + function OnPreCreate(&$event) + { + $this->clearSelectedIDs($event); + + $this->Application->setUnitOption($event->Prefix,'AutoLoad',false); + $object =& $event->getObject(); + + $temp =& $this->Application->recallObject($event->Prefix.'_TempHandler', 'kTempTablesHandler'); + $temp->PrepareEdit(); + + $object->setID(0); + + $event->redirect=false; + } + + /** + * Apply custom processing to item + * + * @param kEvent $event + */ + function customProcessing(&$event, $type) + { + + } + + /** + * Creates a new item in temp table and + * stores item id in App vars and Session on succsess + * + * @param kEvent $event + */ + function OnPreSaveCreated(&$event) + { + $this->Application->setUnitOption($event->Prefix,'AutoLoad',false); + + $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); + if($items_info) $field_values = array_shift($items_info); + + $object =& $event->getObject(); + $object->SetFieldsFromHash($field_values); + + $this->customProcessing($event, 'before'); + + if( $object->Create() ) + { + $this->customProcessing($event, 'after'); + $event->redirect_params[$event->getPrefixSpecial(true).'_id'] = $object->GetId(); + $event->status=erSUCCESS; + } + else + { + $event->status=erFAIL; + $event->redirect=false; + $object->setID(0); + } + + } + + // III. Events that allow to put some code before and after Update,Load,Create and Delete methods of item + + /** + * Occurse before loading item, 'id' parameter + * allows to get id of item beeing loaded + * + * @param kEvent $event + * @access public + */ + function OnBeforeItemLoad(&$event) + { + + } + + /** + * Occurse after loading item, 'id' parameter + * allows to get id of item that was loaded + * + * @param kEvent $event + * @access public + */ + function OnAfterItemLoad(&$event) + { + + } + + /** + * Occurse before creating item + * + * @param kEvent $event + * @access public + */ + function OnBeforeItemCreate(&$event) + { + + } + + /** + * Occurse after creating item + * + * @param kEvent $event + * @access public + */ + function OnAfterItemCreate(&$event) + { + + } + + /** + * Occurse before updating item + * + * @param kEvent $event + * @access public + */ + function OnBeforeItemUpdate(&$event) + { + + } + + /** + * Occurse after updating item + * + * @param kEvent $event + * @access public + */ + function OnAfterItemUpdate(&$event) + { + + } + + /** + * Occurse before deleting item, id of item beeing + * deleted is stored as 'id' event param + * + * @param kEvent $event + * @access public + */ + function OnBeforeItemDelete(&$event) + { + + } + + /** + * Occurse after deleting item, id of deleted item + * is stored as 'id' param of event + * + * @param kEvent $event + * @access public + */ + function OnAfterItemDelete(&$event) + { + + } + + + /** + * Occures after an item has been copied to temp + * Id of copied item is passed as event' 'id' param + * + * @param kEvent $event + */ + function OnAfterCopyToTemp(&$event) + { + + } + + /** + * Occures before an item is deleted from live table when copying from temp + * (temp handler deleted all items from live and then copy over all items from temp) + * Id of item being deleted is passed as event' 'id' param + * + * @param kEvent $event + */ + function OnBeforeDeleteFromLive(&$event) + { + + } + + /** + * Occures before an item is copied to live table (after all foreign keys have been updated) + * Id of item being copied is passed as event' 'id' param + * + * @param kEvent $event + */ + function OnBeforeCopyToLive(&$event) + { + + } + + /** + * !!! NOT FULLY IMPLEMENTED - SEE TEMP HANDLER COMMENTS (search by event name)!!! + * Occures after an item has been copied to live table + * Id of copied item is passed as event' 'id' param + * + * @param kEvent $event + */ + function OnAfterCopyToLive(&$event) + { + + } + + /** + * Occures before an item is cloneded + * Id of ORIGINAL item is passed as event' 'id' param + * Do not call object' Update method in this event, just set needed fields! + * + * @param kEvent $event + */ + function OnBeforeClone(&$event) + { + + } + + /** + * Occures after an item has been cloned + * Id of newly created item is passed as event' 'id' param + * + * @param kEvent $event + */ + function OnAfterClone(&$event) + { + + } + + /** + * Ensures that popup will be closed automatically + * and parent window will be refreshed with template + * passed + * + * @param kEvent $event + * @access public + */ + function finalizePopup(&$event, $main_prefix, $t) + { + $event->redirect = 'incs/close_popup'; + + // 2. substitute opener + $opener_stack = $this->Application->RecallVar('opener_stack'); + $opener_stack = $opener_stack ? unserialize($opener_stack) : Array(); + //array_pop($opener_stack); + + $new_level = 'index4.php|'.ltrim($this->Application->BuildEnv($t, Array('m_opener' => 'u'), 'all'), ENV_VAR_NAME.'='); + array_push($opener_stack,$new_level); + $this->Application->StoreVar('opener_stack',serialize($opener_stack)); + } + + /** + * Create search filters based on search query + * + * @param kEvent $event + * @access protected + */ + function OnSearch(&$event) + { + $event->setPseudoClass('_List'); + $object =& $event->getObject(); + + $keyword = $this->Application->GetVar( $event->getPrefixSpecial(true).'_search_keyword'); + $this->Application->StoreVar( $event->getPrefixSpecial().'_search_keyword', $keyword); + + if(!$keyword) + { + $this->OnSearchReset($event); + return true; + } + + $grid_name = $this->Application->GetVar('grid_name'); + $grids = $this->Application->getUnitOption($event->Prefix,'Grids'); + $search_fields = array_keys($grids[$grid_name]['Fields']); + + $search_filter = Array(); + + foreach($search_fields as $search_field) + { + $filter_type = isset($object->VirtualFields[$search_field]) ? 'having' : 'where'; + $field_type = getArrayValue($object->Fields[$search_field],'type'); + if(!$field_type) $field_type = 'string'; // default LIKE filter for all fields without type + $keyword = trim($keyword); + $filter_value = ''; + $table_name = ($filter_type == 'where') ? '`'.$object->TableName.'`.' : ''; + + // get field clause by formatter name and/or parameters + $formatter = getArrayValue($object->Fields[$search_field],'formatter'); + switch ($formatter) + { + case 'kOptionsFormatter': + $search_keys = Array(); + $use_phrases = getArrayValue($object->Fields[$search_field], 'use_phrases'); + foreach($object->Fields[$search_field]['options'] as $key => $val) + { + $pattern = '#'.$keyword.'#i'; + if ( preg_match($pattern, $use_phrases ? $this->Application->Phrase($val) : $val) ) { + array_push($search_keys, $key); + } + } + if (count($search_keys) > 0) { + $filter_value = $table_name.'`'.$search_field.'` IN ('.implode(',', $search_keys).')'; + } + + $field_processed = true; + break; + + default: + $field_processed = false; + break; + } + + // if not already processed by formatter, then get clause by field type + + if(!$field_processed) + { + switch($field_type) + { + case 'int': + case 'integer': + case 'numeric': + if( !is_numeric($keyword) ) break; + $filter_value = $table_name.'`'.$search_field.'` = \''.$keyword.'\''; + break; + + case 'double': + case 'float': + case 'real': + if( !is_numeric($keyword) ) break; + $filter_value = 'ABS('.$table_name.'`'.$search_field.'` - \''.str_replace(',','.',$keyword).'\') <= 0.0001'; + break; + + case 'string': + $keywords = explode(' ',$keyword); + foreach($keywords as $keyword_pos => $keyword_value) + { + $keywords[$keyword_pos] = $table_name.'`'.$search_field.'` LIKE \'%'.trim($keyword_value).'%\''; + } + $filter_value = '('.implode(') OR (',$keywords).')'; + break; + } + } + if($filter_value) $search_filter[$search_field] = Array('type' => $filter_type, 'value' => $filter_value); + } + + $this->Application->StoreVar($event->getPrefixSpecial().'_search_filter', serialize($search_filter) ); + } + + /** + * Clear search keywords + * + * @param kEvent $event + * @access protected + */ + function OnSearchReset(&$event) + { + $this->Application->RemoveVar($event->getPrefixSpecial().'_search_filter'); + $this->Application->RemoveVar($event->getPrefixSpecial().'_search_keyword'); + } + + /** + * Set's new filter value (filter_id meaning from config) + * + * @param kEvent $event + */ + function OnSetFilter(&$event) + { + $filter_id = $this->Application->GetVar('filter_id'); + $filter_value = $this->Application->GetVar('filter_value'); + + $view_filter = $this->Application->RecallVar($event->getPrefixSpecial().'_view_filter'); + $view_filter = $view_filter ? unserialize($view_filter) : Array(); + + $view_filter[$filter_id] = $filter_value; + + $this->Application->StoreVar( $event->getPrefixSpecial().'_view_filter', serialize($view_filter) ); + } + + /** + * Add/Remove all filters applied to list from "View" menu + * + * @param kEvent $event + */ + function FilterAction(&$event) + { + $view_filter = Array(); + $filter_menu = $this->Application->getUnitOption($event->Prefix,'FilterMenu'); + switch ($event->Name) + { + case 'OnRemoveFilters': + $filter_value = 1; + break; + + case 'OnApplyFilters': + $filter_value = 0; + break; + } + + foreach($filter_menu['Filters'] as $filter_key => $filter_params) + { + if(!$filter_params) continue; + $view_filter[$filter_key] = $filter_value; + } + $this->Application->StoreVar( $event->getPrefixSpecial().'_view_filter', serialize($view_filter) ); + } + + function OnPreSaveAndOpenTranslator(&$event) + { + $this->Application->SetVar('allow_translation', true); + $event->CallSubEvent('OnPreSave'); + $wnd_name = $this->Application->GetVar('translator_wnd_name'); + if ($event->status == erSUCCESS) { + $t = $this->Application->GetVar('translator_t'); + $object = $this->Application->recallObject($event->getPrefixSpecial()); + $url = $this->Application->HREF($t, '', Array('pass'=>'all', $event->getPrefixSpecial(true).'_id' => $object->GetId())); + $field = $this->Application->GetVar('translator_field'); + $after_script = "openTranslator('".$event->getPrefixSpecial()."', '".$field."', '".$url."', '".$wnd_name."')"; + } + else { + $after_script = "wnd = window.open('', '".$wnd_name."'); wnd.close()"; + } + $this->Application->SetVar('after_script', $after_script); + $event->redirect = false; + } } Index: trunk/core/kernel/session/login_event_handler.php =================================================================== diff -u --- trunk/core/kernel/session/login_event_handler.php (revision 0) +++ trunk/core/kernel/session/login_event_handler.php (revision 1339) @@ -0,0 +1,12 @@ +Application->Redirect('logout'); + } + } + + +?> \ No newline at end of file Index: trunk/core/kernel/utility/unit_config_reader.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/utility/unit_config_reader.php (.../unit_config_reader.php) (revision 932) +++ trunk/core/kernel/utility/unit_config_reader.php (.../unit_config_reader.php) (revision 1339) @@ -9,7 +9,10 @@ * @access private */ var $configData=Array(); + var $configFiles=Array(); + var $CacheExpired = false; + /** * Module names found during * config reading @@ -37,37 +40,170 @@ * @param string $folderPath * @access public */ - function processFolder($folderPath) + function processFolder($folderPath, $cached) { $fh=opendir($folderPath); while(($sub_folder=readdir($fh))) { $full_path=$folderPath.'/'.$sub_folder; if( $this->isDir($full_path) && file_exists($this->getConfigName($full_path)) ) { - include_once $this->getConfigName($full_path); - $prefix=$config['Prefix']; - $config['BasePath']=$full_path; - $this->configData[$prefix] = $config; - $this->parseConfig($prefix); + if (filemtime($full_path) > $cached) { + $this->CacheExpired = true; + + $file = $this->getConfigName($full_path); + if ( defined('DEBUG_MODE') && dbg_ConstOn('DBG_PROFILE_INCLUDES')) { + + if ( in_array($file, get_required_files()) ) return; + global $debugger; + $debugger->IncludeLevel++; + $before_time = getmicrotime(); + $before_mem = memory_get_usage(); + include_once($file); + $used_time = getmicrotime() - $before_time; + $used_mem = memory_get_usage() - $before_mem; + $debugger->IncludeLevel--; + $debugger->IncludesData['file'][] = str_replace(DOC_ROOT.BASE_PATH, '', $file); + $debugger->IncludesData['mem'][] = $used_mem; + $debugger->IncludesData['time'][] = $used_time; + $debugger->IncludesData['level'][] = -1; + } + else { + include_once($file); + } + + $prefix=$config['Prefix']; + $config['BasePath']=$full_path; + $this->configData[$prefix] = $config; + } } } } + function ParseConfigs() + { + foreach ($this->configData as $prefix => $config) + { + $this->parseConfig($prefix); + } + } - function scanModules($folderPath) + function findConfigFiles($folderPath) { $fh=opendir($folderPath); while(($sub_folder=readdir($fh))) { $full_path=$folderPath.'/'.$sub_folder; - if( $this->isDir($full_path) ) + if( $this->isDir($full_path)) { - $this->processFolder($full_path); + if ( file_exists($this->getConfigName($full_path)) ) { + $this->configFiles[] = $this->getConfigName($full_path); + } + $this->findConfigFiles($full_path); + +// if (filemtime($full_path) > $cached) { } + } } } + function includeConfigFiles() + { + foreach ($this->configFiles as $filename) { + if ( defined('DEBUG_MODE') && dbg_ConstOn('DBG_PROFILE_INCLUDES')) { + + if ( in_array($filename, get_required_files()) ) return; + global $debugger; + $debugger->IncludeLevel++; + $before_time = getmicrotime(); + $before_mem = memory_get_usage(); + include_once($filename); + $used_time = getmicrotime() - $before_time; + $used_mem = memory_get_usage() - $before_mem; + $debugger->IncludeLevel--; + $debugger->IncludesData['file'][] = str_replace(DOC_ROOT.BASE_PATH, '', $filename); + $debugger->IncludesData['mem'][] = $used_mem; + $debugger->IncludesData['time'][] = $used_time; + $debugger->IncludesData['level'][] = -1; + } + else { + include_once($filename); + } + + $prefix=$config['Prefix']; + $config['BasePath']=dirname($filename); + $this->configData[$prefix] = $config; + + } + } + + function scanModules($folderPath) + { + global $debugger; + + if (defined('CACHE_CONFIGS_FILES')) { + $conn =& $this->Application->GetADODBConnection(); + $data = $conn->GetRow('SELECT Data, Cached FROM '.TABLE_PREFIX.'Cache WHERE VarName = "config_files"'); + if ($data && $data['Cached'] > (time() - 3600) ) { + $this->configFiles = unserialize($data['Data']); + $files_cached = $data['Cached']; + } + else { + $files_cached = 0; + } + } + else { + $files_cached = 0; + } + + + if (defined('CACHE_CONFIGS_DATA')) { + $conn =& $this->Application->GetADODBConnection(); + $data = $conn->GetRow('SELECT Data, Cached FROM '.TABLE_PREFIX.'Cache WHERE VarName = "config_data"'); + if ($data && $data['Cached'] > (time() - 3600) ) { + $this->configData = unserialize($data['Data']); + $data_cached = $data['Cached']; + } + else { + $data_cached = 0; + } + } + else { + $data_cached = 0; + } + + if ( !defined('CACHE_CONFIGS_FILES') || $files_cached == 0 ) { + $this->findConfigFiles($folderPath); + } + + if ( !defined('CACHE_CONFIGS_DATA') || $data_cached == 0) { + $this->includeConfigFiles(); + } + + /*// && (time() - $cached) > 600) - to skip checking files modified dates + if ( !defined('CACHE_CONFIGS') ) { + $fh=opendir($folderPath); + while(($sub_folder=readdir($fh))) + { + $full_path=$folderPath.'/'.$sub_folder.'/units'; + if( $this->isDir($full_path) ) + { + $this->processFolder($full_path, $cached); + } + } + }*/ + $this->ParseConfigs(); + + if (defined('CACHE_CONFIGS_FILES') && $files_cached == 0) { + $conn->Query('REPLACE '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("config_files", '.$conn->qstr(serialize($this->configFiles)).', '.time().')'); + } + + if (defined('CACHE_CONFIGS_DATA') && $data_cached == 0) { + $conn->Query('REPLACE '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("config_data", '.$conn->qstr(serialize($this->configData)).', '.time().')'); + } + + } + /** * Register nessasary classes * @@ -81,13 +217,70 @@ $class_params=Array('ItemClass','ListClass','EventHandlerClass','TagProcessorClass'); foreach($class_params as $param_name) { + if ( !(isset($config[$param_name]) ) ) continue; $class_info =& $config[$param_name]; $pseudo_class = $this->getPrefixByParamName($param_name,$prefix); $this->Application->registerClass( $class_info['class'], $config['BasePath'].'/'.$class_info['file'], $pseudo_class); $event_manager->registerBuildEvent($pseudo_class,$class_info['build_event']); } + + if ( is_array(getArrayValue($config, 'Hooks')) ) { + foreach ($config['Hooks'] as $hook) { + $do_prefix = $hook['DoPrefix'] == '' ? $config['Prefix'] : $hook['DoPrefix']; + + if ( !is_array($hook['HookToEvent']) ) { + $hook_events = Array( $hook['HookToEvent'] ); + } + else { + $hook_events = $hook['HookToEvent']; + } + foreach ($hook_events as $hook_event) { + $this->Application->registerHook($hook['HookToPrefix'], $hook['HookToSpecial'], $hook_event, $hook['Mode'], $do_prefix, $hook['DoSpecial'], $hook['DoEvent'], $hook['Conditional']); + } + } + } + + if ( is_array(getArrayValue($config, 'AggregateTags')) ) { + foreach ($config['AggregateTags'] as $aggregate_tag) { + $aggregate_tag['LocalPrefix'] = $config['Prefix']; + $this->Application->registerAggregateTag($aggregate_tag); + } + } + + if ( $this->Application->isDebugMode() && dbg_ConstOn('DBG_VALIDATE_CONFIGS') && isset($config['TableName']) ) + { + global $debugger; + $tablename = $config['TableName']; + + $conn =& $this->Application->GetADODBConnection(); + $res = $conn->Query("DESCRIBE $tablename"); + + foreach ($res as $field) { + $f_name = $field['Field']; + if (getArrayValue($config, 'Fields')) { + if ( !array_key_exists ($f_name, $config['Fields']) ) { + $debugger->appendHTML("Config Warning: Field $f_name exists in the database, but is not defined in config file for prefix ".$config['Prefix']."!"); + safeDefine('DBG_RAISE_ON_WARNINGS', 1); + } + else { + $options = $config['Fields'][$f_name]; + if ($field['Null'] == '') { + if ( $f_name != $config['IDField'] && !isset($options['not_null']) && !isset($options['required']) ) { + $debugger->appendHTML("Config Error: Field $f_name in config for prefix ".$config['Prefix']." is NOT NULL in the database, but is not configured as not_null or required!"); + safeDefine('DBG_RAISE_ON_WARNINGS', 1); + } + if ( isset($options['not_null']) && !isset($options['default']) ) { + $debugger->appendHTML("Config Error: Field $f_name in config for prefix ".$config['Prefix']." is described as NOT NULL, but does not have DEFAULT value!"); + safeDefine('DBG_RAISE_ON_WARNINGS', 1); + } + } + + } + } + } + } } /** @@ -101,10 +294,22 @@ */ function getUnitOption($prefix,$name) { - return $this->configData[$prefix][$name]; + return isset($this->configData[$prefix][$name])?$this->configData[$prefix][$name]:false; } /** + * Read all unit with $prefix options + * + * @param string $prefix + * @return Array + * @access public + */ + function getUnitOptions($prefix) + { + return isset($this->configData[$prefix])?$this->configData[$prefix]:false; + } + + /** * Set's new unit option value * * @param string $prefix Index: trunk/core/kernel/utility/event.php =================================================================== diff -u -r958 -r1339 --- trunk/core/kernel/utility/event.php (.../event.php) (revision 958) +++ trunk/core/kernel/utility/event.php (.../event.php) (revision 1339) @@ -1,7 +1,20 @@ Init($prefix,$special); + $this->Name = getArrayValue($params,'name'); + } + } + function setEventParam($name,$value) { $this->specificParams[$name]=$value; } function getEventParam($name) { - return isset($this->specificParams[$name])?$this->specificParams[$name]:false; + return getArrayValue($this->specificParams, $name); } function getPrefixSpecial($from_submit=false) @@ -123,7 +181,7 @@ $this->pseudoClass=$this->Prefix.$appendix; } - function Init($prefix,$special) + function Init($prefix,$special='') { $this->Prefix=$prefix; $this->pseudoClass=$prefix; // default value @@ -135,12 +193,45 @@ * Returns object used in event * * @access public + * @return kDBBase */ - function &createObject() + function &getObject() { - return $this->Application->recallObject($this->Prefix_Special,$this->pseudoClass); + return $this->Application->recallObject($this->Prefix_Special,$this->pseudoClass); } + /** + * Calls passed event by name in current prefix/special environment + * Called event gets this event as MasterEvent, + * but its results (status and redirect* properties are copied back to current event) + * + * @param string $name EventName to call + */ + function CallSubEvent($name) + { + $child_event = new kEvent(); + $child_event->MasterEvent =& $this; + $child_event->Prefix = $this->Prefix; + $child_event->Special = $this->Special; + $child_event->Prefix_Special = $this->Prefix_Special; + $child_event->redirect = $this->redirect; + $child_event->redirect_params = $this->redirect_params; + $child_event->redirect_script = $this->redirect_script; + $child_event->Name = $name; + + $this->Application->HandleEvent( $child_event ); + + $this->status = $child_event->status; + $this->redirect = $child_event->redirect; + $this->redirect_params = $child_event->redirect_params; + $this->redirect_script = $child_event->redirect_script; + } + + function SetRedirectParam($name, $value) + { + $this->redirect_params = array_merge_recursive2( $this->redirect_params, Array($name => $value) ); + } + } ?> \ No newline at end of file Index: trunk/core/kernel/utility/params.php =================================================================== diff -u -r939 -r1339 --- trunk/core/kernel/utility/params.php (.../params.php) (revision 939) +++ trunk/core/kernel/utility/params.php (.../params.php) (revision 1339) @@ -39,7 +39,8 @@ function Set($name, $val) { //echo "sessing params: [$name] = [$val] (class: ".get_class($this).")
"; - $this->_Params[strtolower($name)] = $val; +// $this->_Params[strtolower($name)] = $val; + $this->_Params[$name] = $val; } /** @@ -64,7 +65,7 @@ function Get($name, $mode=FALSE_ON_NULL) { // echo " name : '$name' || mode : $mode
"; - $name = strtolower($name); + //$name = strtolower($name); if (array_key_exists($name, $this->_Params)) return $this->_Params[$name]; else @@ -85,7 +86,8 @@ } else {*/ foreach ($params as $name => $val) - $this->Set(strtolower($name), $val); +// $this->Set(strtolower($name), $val); + $this->Set($name, $val); //} } @@ -101,6 +103,39 @@ } } +class kArray extends kBase { + var $_Array; + + /** + * Returns array value with any deep key + * + * @return mixed + * @todo array_unshift doesn't accept parameters by reference, fix it's usage in this method + */ + function GetArrayValue() + { + $args = func_get_args(); + array_unshift($args, &$this->_Array); + return call_user_func_array('getArrayValue', $args); + } + + function SetArrayValue() + { + $args = func_get_args(); + $value = array_pop($args); + + $arr =& $this->_Array; + for ($i=0; $isetType($type); + } + + /** + * Enter description here... + * + * @param unknown_type $new_type + */ + function setType($new_type) + { + $this->type = $new_type; + } + + /** + * Adds new or replaces old filter with same name + * + * @param string $name + * @param mixed $clause kMultipleFilter object or where clause ifself + * @access public + */ + function addFilter($name, $clause) + { + if( is_object($clause) && $clause->hasFilters() ) + { + $this->filters[$name] = $clause->getSQL(); + } + elseif( !is_object($clause) && $clause ) + { + $this->filters[$name] = $clause; + } + } + + /** + * Removes specified filter from filters list + * + * @param string $name + * @access public + */ + function removeFilter($name) + { + unset($this->filters[$name]); + } + + /** + * Remove all added filters + * + * @access public + */ + function clearFilters() + { + $this->filters = Array(); + } + + /** + * Build where clause based on added filters and multiple filter type + * + * @return string + * @access public + */ + function getSQL() + { + $filter_count = count($this->filters); + if(!$filter_count) return ''; + + return '('.implode(') '.$this->type.' (',$this->filters).')'; + } + + /** + * Allows to check if some filters are added to multiple filter + * + * @return bool + * @access public + */ + function hasFilters() + { + return $this->filters ? true : false; + } + } +?> \ No newline at end of file Index: trunk/core/kernel/processors/main_processor.php =================================================================== diff -u -r950 -r1339 --- trunk/core/kernel/processors/main_processor.php (.../main_processor.php) (revision 950) +++ trunk/core/kernel/processors/main_processor.php (.../main_processor.php) (revision 1339) @@ -9,6 +9,8 @@ $actions =& $this->Application->recallObject('kActions'); $actions->Set('t', $this->Application->GetVar('t')); $actions->Set('sid', $this->Application->GetSID()); + $actions->Set('m_opener', $this->Application->GetVar('m_opener') ); + } /** @@ -34,18 +36,33 @@ */ function Base_Ref($params) { - $templates_path = substr(THEMES_PATH,1); - return ""; + $url = $this->Application->BaseURL().substr(THEMES_PATH,1).'/'; + return ''; } - /*function Base_URL($params) + /** + * Returns base url for web-site + * + * @return string + * @access public + */ + function BaseURL() { - $templates_path = substr(THEMES_PATH,1); - return $this->Application->BaseURL().$templates_path; + return $this->Application->BaseURL(); } - function Base($params) + function TemplatesBase($params) { + return $this->Application->BaseURL().THEMES_PATH; + } + + function ProjectBase($params) + { + return $this->Application->BaseURL(); + } + + /*function Base($params) + { return $this->Application->BaseURL().$params['add']; }*/ @@ -61,17 +78,20 @@ */ function T($params) { - $t=isset($params['t'])&&$params['t']?$params['t']:$this->Application->GetVar('t'); unset($params['t']); - $prefix=isset($params['prefix'])?$params['prefix']:''; unset($params['prefix']); + //by default link to current template + $t=isset($params['t'])&&$params['t'] ? $params['t'] : $this->Application->GetVar('t'); unset($params['t']); + $prefix=isset($params['prefix']) ? $params['prefix'] : ''; unset($params['prefix']); + $index_file = isset($params['index_file']) ? $params['index_file'] : null; unset($params['index_file']); - $pass=isset($params['pass'])?$params['pass']:$this->Application->GetVar('t_pass'); unset($params['pass']); - $this->Application->SetVar('t_pass',$pass); + /*$pass=isset($params['pass']) ? $params['pass'] : $this->Application->GetVar('t_pass'); unset($params['pass']); + $this->Application->SetVar('t_pass', $pass); - $pass_events=isset($params['pass_events'])&&$params['pass_events']?1:0; unset($params['pass_events']); - $this->Application->SetVar('t_pass_events',$pass_events); + $pass_events = isset($params['pass_events']) && $params['pass_events'] ? 1 : 0; unset($params['pass_events']); + $this->Application->SetVar('t_pass_events', $pass_events);*/ - $this->Set($params); // set other params as application vars - return $this->Application->HREF($t,$prefix); + //Use only implicit params passing, do not set into APP +// $this->Set($params); // set other params as application vars + return $this->Application->HREF($t,$prefix,$params,$index_file); } /*// NEEDS TEST @@ -146,7 +166,7 @@ * @return bool * @access public */ - function Param_Equals($params) + function ParamEquals($params) { //$parser =& $this->Application->recallObject('TemplateParser'); $name = $this->SelectParam($params, 'name,var,param'); @@ -243,6 +263,19 @@ return $this->Application->GetVar($this->SelectParam($params, 'name,var,param'), EMPTY_ON_NULL); } + /** + * Retrieves application constant + * value by name + * + * @param Array $params + * @return string + * @access public + */ + function GetConst($params) + { + return defined($this->SelectParam($params, 'name,const')) ? constant($this->SelectParam($params, 'name,const,param')) : ''; + } + /*function Config_Equals($params) { foreach ($params as $name => $val) { @@ -274,20 +307,20 @@ return $o; } - /*function Odd_Even($params) + function Odd_Even($params) { $odd = $params['odd']; $even = $params['even']; - if ($this->Session->GetProperty('odd_even') == 'even') { - $this->Session->SetProperty('odd_even', 'odd'); + if ($this->Application->GetVar('odd_even') == 'even') { + $this->Application->SetVar('odd_even', 'odd'); return $even; } else { - $this->Session->SetProperty('odd_even', 'even'); + $this->Application->SetVar('odd_even', 'even'); return $odd; } - }*/ + } /** * Returns phrase translation by name @@ -307,13 +340,15 @@ function is_active($params) { $test_templ = $params["templ"]; - if (!$params['allow_empty']) { - $if_true = $params["true"] != '' ? $params["true"] : 1; - $if_false = $params["false"] != '' ? $params["false"] : 0; + if ( !getArrayValue($params,'allow_empty') ) + { + $if_true=getArrayValue($params,'true')?$params['true']:1; + $if_false=getArrayValue($params,'false')?$params['false']:0; } - else { - $if_true = $params["true"]; - $if_false = $params["false"]; + else + { + $if_true=$params['true']; + $if_false=$params['false']; } if ( eregi("^$test_templ", $this->Application->GetVar('t'))) @@ -336,7 +371,7 @@ * @return string * @access public */ - function Recall_Equals($params) + function RecallEquals($params) { $name = $params['var']; $value = $params['value']; @@ -352,7 +387,7 @@ * @return bool * @access public */ - function Get_Equals($params) + function GetEquals($params) { $name = $this->SelectParam($params, 'var,name,param'); $value = $params['value']; @@ -372,7 +407,7 @@ */ function MyInclude($params) { - $BlockParser =& $this->Application->Factory->makeClass('TemplateParser'); + $BlockParser =& $this->Application->makeClass('TemplateParser'); $BlockParser->SetParams($params); $parser =& $this->Application->Parser; @@ -381,7 +416,7 @@ $templates_cache =& $this->Application->recallObject('TemplatesCache'); - $res = $BlockParser->Parse( $templates_cache->GetTemplateBody($t) ); + $res = $BlockParser->Parse( $templates_cache->GetTemplateBody($t), $t ); $this->Application->Parser =& $parser; return $res; @@ -461,17 +496,67 @@ } /** - * Find out what object were in link - * used to move here and copy them to - * form submit url + * Checks if debug mode is on * - * @param unknown_type $params + * @return bool + * @access public */ - function PrepareSubmitURL($params) + function IsDebugMode() { - $this->Application->ReBuildENV(); + return $this->Application->isDebugMode(); } + + function MassParse($params) + { + $qty = $params['qty']; + $block = $params['block']; + $mode = $params['mode']; + + $o = ''; + if ($mode == 'func') { + $func = create_function('$params', ' + $o = \'\'; + $o.= \'a\'.$params[\'param1\'].\'\'; + $o.= \'a\'.$params[\'param2\'].\'\'; + $o.= \'a\'.$params[\'param3\'].\'\'; + $o.= \'a\'.$params[\'param4\'].\'\'; + $o.= \'\'; + return $o; + '); + for ($i=1; $i<$qty; $i++) { + $block_params['param1'] = rand(1, 10000); + $block_params['param2'] = rand(1, 10000); + $block_params['param3'] = rand(1, 10000); + $block_params['param4'] = rand(1, 10000); + $o .= $func($block_params); + } + return $o; + } + $block_params['name'] = $block; + + for ($i=0; $i<$qty; $i++) { + $block_params['param1'] = rand(1, 10000); + $block_params['param2'] = rand(1, 10000); + $block_params['param3'] = rand(1, 10000); + $block_params['param4'] = rand(1, 10000); + $block_params['passed'] = $params['passed']; + $block_params['prefix'] = 'm'; + + $o.= $this->Application->ParseBlock($block_params, 1); + } + return $o; + } + + function AfterScript($params) + { + $after_script = $this->Application->GetVar('after_script'); + if ( $after_script ) { + return ''; + } + return ''; + } + /* function Login($params) { Index: trunk/core/kernel/utility/formatters.php =================================================================== diff -u --- trunk/core/kernel/utility/formatters.php (revision 0) +++ trunk/core/kernel/utility/formatters.php (revision 1339) @@ -0,0 +1,758 @@ +GetFieldOptions($field_name); + if ( isset($format) ) $options['format'] = $format; + $tc_value = $this->TypeCast($value,$options); + if( ($tc_value === false) || ($tc_value != $value) ) return $value; // for leaving badly formatted date on the form + + if (isset($options['format'])) return sprintf($options['format'], $tc_value); + + return $tc_value; + } + +//function Parse($value, $options, &$errors) + function Parse($value, $field_name, &$object) + { + if ($value == '') return NULL; + + $options = $object->GetFieldOptions($field_name); + $tc_value = $this->TypeCast($value,$options); + if($tc_value === false) return $value; // for leaving badly formatted date on the form + + if( isset($options['type']) ) + { + if( preg_match('#double|float|real|numeric#', $options['type']) ) $tc_value = str_replace(',', '.', $tc_value); + } + return $tc_value; + } + + function HumanFormat($format) + { + return $format; + } + + + /** + * The method is supposed to alter config options or cofigure object in some way based on its usage of formatters + * The methods is called for every field with formatter defined when configuring item. + * Could be used for adding additional VirtualFields to an object required by some special Formatter + * + * @param string $field_name + * @param array $field_options + * @param kDBBase $object + */ + function PrepareOptions($field_name, &$field_options, &$object) + { + + } + + /** + * Used for split fields like timestamp -> date, time + * Called from DBItem to update sub fields values after loading item + * + * @param unknown_type $field + * @param unknown_type $value + * @param unknown_type $options + * @param unknown_type $object + */ + function UpdateSubFields($field, $value, &$options, &$object) + { + + } + + /** + * Used for split fields like timestamp -> date, time + * Called from DBItem Validate (before validation) to get back master field value from its sub_fields + * + * @param unknown_type $field + * @param unknown_type $value + * @param unknown_type $options + * @param unknown_type $object + */ + function UpdateMasterFields($field, $value, &$options, &$object) + { + + } + +/* function GetErrorMsg($pseudo_error, $options) + { + if ( isset($options['error_msgs'][$pseudo_error]) ) { + return $options['error_msgs'][$pseudo_error]; + } + else { + return $this->ErrorMsgs[$pseudo_error]; + } + }*/ + +} + +class kOptionsFormatter extends kFormatter { + +//function Format($value, $options, &$errors) + function Format($value, $field_name, &$object, $format=null) + { + if ( is_null($value) ) return ''; + + $options = $object->GetFieldOptions($field_name); + if ( isset($format) ) $options['format'] = $format; + + $label = $options['options'][$value]; + if( getArrayValue($options,'use_phrases') ) + { + return $this->Application->Phrase($label); + } + else + { + return $label; + } + } + +} + +/** + * Replacement for kOptionsFormatter in case if options + * should be selected from database. Use this formatter + * only in case if formatter attached field is in edit form. + * + * For usage in grid just use LEFT JOIN clause to table + * where requested options are located. + */ +class kLEFTFormatter extends kFormatter { + +//function Format($value, $options, &$errors) + function Format($value, $field_name, &$object, $format=null) + { + if ( is_null($value) ) return ''; + + $options = $object->GetFieldOptions($field_name); + if ( isset($format) ) $options['format'] = $format; + + if( !isset($options['options'][$value]) ) + { + // required option is not defined in config => query for it + $db =& $this->Application->GetADODBConnection(); + $sql = sprintf($options['left_sql'],$options['left_title_field'],$options['left_key_field'],$value); + $options['options'][$value] = $db->GetOne($sql); + } + return $options['options'][$value]; + } + +//function Parse($value, $options, &$errors) + function Parse($value, $field_name, &$object) + { + if ($value == '') return NULL; + + $options = $object->GetFieldOptions($field_name); + if( !array_search($value,$options['options']) ) + { + // required option is not defined in config => query for it + $db =& $this->Application->GetADODBConnection(); + $sql = sprintf($options['left_sql'],$options['left_key_field'],$options['left_title_field'],$value); + $found = $db->GetOne($sql); + if($found !== false) $options['options'][$found] = $value; + } + else + { + $found = array_search($value,$options['options']); + } + if($found === false) $found = $options['default']; + return $found; + } +} + + +class kDateFormatter extends kFormatter { + +/* function kDateFormatter() + { + parent::kFormatter(); + $this->ErrorMsgs['bad_dformat'] = 'Please use correct date format (%s) ex. (%s)'; + } + */ + + function PrepareOptions($field_name, &$field_options, &$object) + { + $date_format = getArrayValue($field_options, 'date_format'); + $time_format = getArrayValue($field_options, 'time_format'); + + $language = $this->Application->recallObject('lang'); + + if ($date_format === false) $date_format = $language->GetDBField('DateFormat'); + if ($time_format === false) $time_format = $language->GetDBField('TimeFormat'); + + if (!isset($field_options['date_time_separator'])) $field_options['date_time_separator'] = ' '; + $field_options['format'] = $date_format.$field_options['date_time_separator'].$time_format; + $field_options['sub_fields'] = Array('date' => $field_name.'_date', 'time' => $field_name.'_time'); + + $add_fields = Array(); + + $opts = Array('master_field' => $field_name, 'formatter'=>'kDateFormatter', 'format'=>$date_format); + if ( isset($field_options['default']) ) $opts['default'] = $field_options['default']; + if ( isset($field_options['required']) ) $opts['required'] = $field_options['required']; + + $add_fields[$field_name.'_date'] = $opts; + $opts['format'] = $time_format; + $add_fields[$field_name.'_time'] = $opts; + + if ( !isset($object->VirtualFields[$field_name]) ) { + // adding caluclated field to format date directly in the query + if ( !isset($object->CalculatedFields) || !is_array($object->CalculatedFields) ) { + $object->CalculatedFields = Array(); + } + $object->CalculatedFields[$field_name.'_formatted'] = 'FROM_UNIXTIME('.'`%1$s`.'.$field_name.', \''.$this->SQLFormat($field_options['format']).'\')'; + $opts['format'] = $field_options['format']; + $opts['required'] = 0; + unset($opts['master_field']); + $add_fields[$field_name.'_formatted'] = $opts; + } + + $add_fields = array_merge_recursive2($add_fields, $object->VirtualFields); + $object->setVirtualFields($add_fields); + } + + function UpdateSubFields($field, $value, &$options, &$object) + { + if ( $sub_fields = getArrayValue($options, 'sub_fields') ) { + if( isset($value) && $value ) + { + $object->SetDBField( $sub_fields['date'], $value ); + $object->SetDBField( $sub_fields['time'], $value ); + } + } + } + + function UpdateMasterFields($field, $value, &$options, &$object) + { + // when in master field - set own value from sub_fields + if ( $sub_fields = getArrayValue($options, 'sub_fields') ) { + // if date is not empty, but time is empty - set time to 0, otherwise master field fomratter will complain + // when we have only date field on form, we need time hidden field always empty, don't ask me why! + if ( $object->GetDBField($sub_fields['date']) != '' && $object->GetDBField($sub_fields['time']) == '' ) { + $object->SetDBField($sub_fields['time'], mktime(0, 0, 0)); + } + $object->SetField($field, $object->GetField($sub_fields['date']).$options['date_time_separator'].$object->GetField($sub_fields['time'])); + } + // when in one of sub_fields - call update for master_field to update its value from sub_fields [are you following ? :) ] + elseif ($master_field = getArrayValue($options, 'master_field') ) { + $this->UpdateMasterFields($master_field, null, $object->GetFieldOptions($master_field), $object); + } + } + +//function Format($value, $options, &$errors) + function Format($value, $field_name, &$object, $format=null) + { + if ( is_null($value) ) return ''; + if ( !is_numeric($value) ) return $value; // for leaving badly formatted date on the form + settype($value, 'int'); + if ( !is_int($value) ) return $value; + + $options = $object->GetFieldOptions($field_name); + if ( isset($format) ) $options['format'] = $format; + + return date($options['format'], $value); + } + + function HumanFormat($format) + { + $patterns = Array('/m/', + '/n/', + '/d/', + '/j/', + '/y/', + '/Y/', + '/h|H/', + '/g|G/', + '/i/', + '/s/', + '/a|A/'); + $replace = Array( 'mm', + 'm', + 'dd', + 'd', + 'yy', + 'yyyy', + 'hh', + 'h', + 'mm', + 'ss', + 'AM'); + $res = preg_replace($patterns, $replace, $format); + return $res; + } + + function SQLFormat($format) + { + $mapping = Array( + '/%/' => '%%', + '/(? '%p', // Lowercase Ante meridiem and Post meridiem => MySQL provides only uppercase + '/(? '%p', // Uppercase Ante meridiem and Post meridiem + '/(? '%d', // Day of the month, 2 digits with leading zeros + '/(? '%a', // A textual representation of a day, three letters + '/(? '%M', // A full textual representation of a month, such as January or March + '/(? '%l', // 12-hour format of an hour without leading zeros + '/(? '%k', // 24-hour format of an hour without leading zeros + '/(? '%h', // 12-hour format of an hour with leading zeros + '/(? '%H', // 24-hour format of an hour with leading zeros + '/(? '%i', // Minutes with leading zeros + '/(? 'N/A', // Whether or not the date is in daylights savings time + + '/(? 'N/A', // English ordinal suffix for the day of the month, 2 characters, see below + '/jS/' => '%D', // MySQL can't return separate suffix, but could return date with suffix + '/(? '%e', // Day of the month without leading zeros + '/(? '%W', // A full textual representation of the day of the week + '/(? 'N/A', // Whether it's a leap year + '/(? '%m', // Numeric representation of a month, with leading zeros + '/(? '%b', // A short textual representation of a month, three letters + '/(? '%c', // Numeric representation of a month, without leading zeros + '/(? 'N/A', // Difference to Greenwich time (GMT) in hours + '/(? 'N/A', // RFC 2822 formatted date + '/(? '%s', // Seconds, with leading zeros + // S and jS moved before j - see above + '/(? 'N/A', // Number of days in the given month + '/(? 'N/A', // Timezone setting of this machine + '/(? 'N/A', // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) + '/(? '%w', // Numeric representation of the day of the week + '/(? '%v', // ISO-8601 week number of year, weeks starting on Monday (added in PHP 4.1.0) + '/(? '%Y', // A full numeric representation of a year, 4 digits + '/(? '%y', // A two digit representation of a year + '/(? 'N/A', // The day of the year (starting from 0) => MySQL starts from 1 + '/(? 'N/A', // Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. + ); + + $patterns = array_keys($mapping); + $replacements = array_values($mapping); + + $res = preg_replace($patterns, $replacements, $format); + return $res; + } + +//function Parse($value, $options, &$errors) + function Parse($value, $field_name, &$object) + { + $options = $object->GetFieldOptions($field_name); + + $dt_separator = getArrayValue($options,'date_time_separator'); + if($dt_separator) $value = trim($value, $dt_separator); + if($value == '') return NULL; + //return strtotime($value); + + $format = $options['format']; + if($dt_separator) $format = trim($format, $dt_separator); + + $object->FieldErrors[$field_name]['params'] = Array( $this->HumanFormat($format), date($format) ); + $object->FieldErrors[$field_name]['value'] = $value; + + $hour = 0; + $minute = 0; + $second = 0; + $month = 1; + $day = 1; + $year = 1970; + + $patterns['n'] = '([0-9]{1,2})'; + $patterns['m'] = '([0-9]{1,2})'; + $patterns['d'] = '([0-9]{1,2})'; + $patterns['j'] = '([0-9]{1,2})'; + $patterns['Y'] = '([0-9]{4})'; + $patterns['y'] = '([0-9]{2})'; + $patterns['G'] = '([0-9]{1,2})'; + $patterns['g'] = '([0-9]{1,2})'; + $patterns['H'] = '([0-9]{2})'; + $patterns['h'] = '([0-9]{2})'; + $patterns['i'] = '([0-9]{2})'; + $patterns['s'] = '([0-9]{2})'; + $patterns['a'] = '(am|pm)'; + $patterns['A'] = '(AM|PM)'; + + $holders_mask = eregi_replace('[a-zA-Z]{1}', '([a-zA-Z]{1})', $format); + if (!ereg($holders_mask, $format, $holders)) { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_date_format'; + return $value; + } + + $values_mask = '/^'.$format.'$/'; + foreach ($patterns as $key => $val) { + $values_mask = ereg_replace($key, $val, $values_mask); + } + // echo " values_mask : $values_mask
"; + + if (!preg_match($values_mask, $value, $values)) { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_date_format'; + return $value; + } + + for ($i = 1; $i < count($holders); $i++) { + switch ($holders[$i]) { + case 'n': + case 'm': + $month = $values[$i]; + $month = ereg_replace("^0{1}", '', $month); + break; + case 'd': + $day = $values[$i]; + $day = ereg_replace("^0{1}", '', $day); + break; + case 'Y': + $year = $values[$i]; + break; + case 'y': + $year = $values[$i] >= 70 ? 1900 + $values[$i] : 2000 + $values[$i]; + break; + case 'H': + case 'h': + case 'G': + case 'g': + $hour = $values[$i]; + $hour = ereg_replace("^0{1}", '', $hour); + break; + case 'i': + $minute = $values[$i]; + $minute = ereg_replace("^0{1}", '', $minute); + break; + case 's': + $second = $values[$i]; + $second = ereg_replace("^0{1}", '', $second); + break; + case 'a': + case 'A': + if ($hour <= 12) { // if AM/PM used with 24-hour - could happen :) + if ($values[$i] == 'pm' || $values[$i] == 'PM') { + $hour += 12; + if ($hour == 24) $hour = 12; + } + elseif ($values[$i] == 'am' || $values[$i] == 'AM') { + if ($hour == 12) $hour = 0; + } + } + break; + } + } + + //echo "day: $day, month: $month, year: $year, hour: $hour, minute: $minute
"; + + if (!($year >= 1970 && $year <= 2037)) { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_date_format'; + return $value; + } + + if (!($month >= 1 && $month <= 12)) { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_date_format'; + return $value; + } + + $months_days = Array ( 1 => 31,2 => 28, 3 => 31, 4 => 30,5 => 31,6 => 30, 7 => 31, 8 => 31,9 => 30,10 => 31,11 => 30,12 => 31); + if ($year % 4 == 0) $months_days[2] = 29; + + if (!($day >=1 && $day <= $months_days[$month])) { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_date_format'; + return $value; + } + + if (!($hour >=0 && $hour <= 23)) { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_date_format'; + return $value; + } + + if (!($minute >=0 && $minute <= 59)) { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_date_format'; + return $value; + } + + if (!($second >=0 && $second <= 59)) { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_date_format'; + return $value; + } + // echo "day: $day, month: $month, year: $year, hour: $hour, minute: $minute
"; + return (mktime($hour, $minute, $second, $month, $day, $year)); + } +} + +class kUploadFormatter extends kFormatter +{ + var $DestinationPath; + var $FullPath; + + function kUploadFormatter() + { + if ($this->DestinationPath) + { + $this->FullPath = DOC_ROOT.BASE_PATH.$this->DestinationPath; + } + parent::kBase(); + } + + +//function Parse($value, $options, &$errors) + function Parse($value, $field_name, &$object) + { + $ret = ''; + $options = $object->GetFieldOptions($field_name); + + if (getArrayValue($value, 'upload') && getArrayValue($value, 'error') == UPLOAD_ERR_NO_FILE) + { + return getArrayValue($value, 'upload'); + } + + if ( is_array($value) && $value['size'] ) + { + if ( is_array($value) && $value['error'] === UPLOAD_ERR_OK ) + { + if ( !in_array($value['type'], $options['allowed_types']) ) + { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_file_format'; + } + elseif ( $value['size'] > ($options['max_size'] ? $options['max_size'] : MAX_UPLOAD_SIZE) ) + { + $object->FieldErrors[$field_name]['pseudo'] = 'bad_file_size'; + } + elseif ( !is_writable($this->FullPath) ) + { + $object->FieldErrors[$field_name]['pseudo'] = 'cant_save_file'; + } + else + { + $real_name = $this->ValidateFileName($this->FullPath, $value['name']); + $file_name = $this->FullPath.$real_name; + if ( !move_uploaded_file($value['tmp_name'], $file_name) ) + { + $object->FieldErrors[$field_name]['pseudo'] = 'cant_save_file'; + } + else + { + $ret = $this->DestinationPath.$real_name; + } + } + } + else + { + $object->FieldErrors[$field_name]['pseudo'] = 'cant_save_file'; + } + } + + if ($value['error'] && !( $value['error'] == UPLOAD_ERR_NO_FILE ) && !$object->FieldErrors[$field_name]['pseudo']) + { + $object->FieldErrors[$field_name]['pseudo'] = 'cant_save_file'; + } + + return $ret; + } + + function ValidateFileName($path, $name) + { + $parts = pathinfo($name); + $ext = '.'.$parts['extension']; + $filename = substr($parts['basename'], 0, -strlen($ext)); + $new_name = $filename.$ext; + while ( file_exists($path.'/'.$new_name) ) + { + if ( preg_match("/({$filename}_)([0-9]*)($ext)/", $new_name, $regs) ) { + $new_name = $regs[1].($regs[2]+1).$regs[3]; + } + else { + $new_name = $filename.'_1'.$ext; + } + } + return $new_name; + } + +} + +class kPictureFormatter extends kUploadFormatter +{ + + function kPictureFormatter() + { + $this->NakeLookupPath = IMAGES_PATH; + $this->DestinationPath = IMAGES_PENDING_PATH; + parent::kUploadFormatter(); + } + +} + + +class kMultiLanguage extends kFormatter +{ + + function LangFieldName($field_name) + { + $lang = $this->Application->GetVar('m_lang'); + return 'l'.$lang.'_'.$field_name; + } + + function PrepareOptions($field_name, &$field_options, &$object) + { + if (getArrayValue($object->Fields, $field_name, 'master_field')) return; + + $lang_field_name = $this->LangFieldName($field_name); + + //substitude title field + $title_field = $this->Application->getUnitOption($object->Prefix, 'TitleField'); + if ($title_field = $field_name) { + $this->Application->setUnitOption($object->Prefix, 'TitleField', $lang_field_name); + } + + //substitude fields + $fields = $this->Application->getUnitOption($object->Prefix, 'Fields'); + if ( isset($fields[$field_name]) ) { + + $fields[$lang_field_name] = $fields[$field_name]; + $fields[$lang_field_name]['master_field'] = $field_name; + $object->Fields[$lang_field_name] = $fields[$lang_field_name]; + $fields[$field_name]['required'] = false; + $object->Fields[$field_name]['required'] = false; + $object->VirtualFields[$field_name] = $object->Fields[$field_name]; + } + $this->Application->setUnitOption($object->Prefix, 'Fields', $fields); + + //substitude virtual fields + $virtual_fields = $this->Application->getUnitOption($object->Prefix, 'VirtualFields'); + if ( isset($virtual_fields[$field_name]) ) { + $virtual_fields[$lang_field_name] = $virtual_fields[$field_name]; + $virtual_fields[$lang_field_name]['master_field'] = $field_name; + $object->VirtualFields[$lang_field_name] = $virtual_fields[$lang_field_name]; + $virtual_fields[$field_name]['required'] = false; + $object->VirtualFields[$field_name]['required'] = false; + } + $this->Application->setUnitOption($object->Prefix, 'VirtualFields', $virtual_fields); + + //substitude grid fields + $grids = $this->Application->getUnitOption($object->Prefix, 'Grids'); + foreach ($grids as $name => $grid) { + if ( getArrayValue($grid, 'Fields', $field_name) ) { + array_rename_key($grids[$name]['Fields'], $field_name, $lang_field_name); + } + } + $this->Application->setUnitOption($object->Prefix, 'Grids', $grids); + + //substitude default sortings + $sortings = $this->Application->getUnitOption($object->Prefix, 'ListSortings'); + foreach ($sortings as $special => $the_sortings) { + if (isset($the_sortings['ForcedSorting'])) { + array_rename_key($sortings[$special]['ForcedSorting'], $field_name, $lang_field_name); + } + if (isset($the_sortings['Sorting'])) { + array_rename_key($sortings[$special]['Sorting'], $field_name, $lang_field_name); + } + } + $this->Application->setUnitOption($object->Prefix, 'ListSortings', $sortings); + + //TODO: substitude possible language-fields sortings after changing language + } + + function UpdateSubFields($field, $value, &$options, &$object) + { + /*if ( $sub_fields = getArrayValue($options, 'sub_fields') ) { + if( isset($value) && $value ) + { + $object->SetDBField( $sub_fields['date'], $value ); + $object->SetDBField( $sub_fields['time'], $value ); + } + }*/ + } + + function UpdateMasterFields($field, $value, &$options, &$object) + { + /*if ( $master_field = getArrayValue($object->Fields, $field, 'master_field') ) // this is Language field + { + $object->FieldErrors[$master_field] = $object->FieldErrors[$field]; // copy possible errors to master field + } + else { // THIS is master field + // do nothing + }*/ + + + /*// when in master field - set own value from sub_fields + if ( $sub_fields = getArrayValue($options, 'sub_fields') ) { + // if date is not empty, but time is empty - set time to 0, otherwise master field fomratter will complain + // when we have only date field on form, we need time hidden field always empty, don't ask me why! + if ( $object->GetDBField($sub_fields['date']) != '' && $object->GetDBField($sub_fields['time']) == '' ) { + $object->SetDBField($sub_fields['time'], mktime(0, 0, 0)); + } + $object->SetField($field, $object->GetField($sub_fields['date']).$options['date_time_separator'].$object->GetField($sub_fields['time'])); + } + // when in one of sub_fields - call update for master_field to update its value from sub_fields [are you following ? :) ] + elseif ($master_field = getArrayValue($options, 'master_field') ) { + $this->UpdateMasterFields($master_field, null, $object->GetFieldOptions($master_field), $object); + }*/ + } + + function Format($value, $field_name, &$object, $format=null) + { + $master_field = getArrayValue($object->Fields, $field_name, 'master_field'); + if (!$master_field) { // if THIS field is master it does NOT have reference to it's master_field + $lang = $this->Application->GetVar('m_lang'); + $value = $object->GetDBField('l'.$lang.'_'.$field_name); //getting value of current language + $master_field = $field_name; // THIS is master_field + } + if ( $value == '' && $format != 'no_default') { // try to get default language value + $def_lang_value = $object->GetDBField('l'.$this->Application->GetDefaultLanguageId().'_'.$master_field); + if ($def_lang_value == '') return NULL; + return $def_lang_value; //return value from default language + } + return $value; + } + + function Parse($value, $field_name, &$object) + { + $lang = $this->Application->GetVar('m_lang'); + $def_lang = $this->Application->GetDefaultLanguageId(); + $master_field = getArrayValue($object->Fields, $field_name, 'master_field'); + + if ( getArrayValue($object->Fields, $field_name, 'required') && ( (string) $value == '' ) ) { + $object->FieldErrors[$master_field]['pseudo'] = 'required'; + }; + + if (!$this->Application->GetVar('allow_translation') && $lang != $def_lang && getArrayValue($object->Fields, $field_name, 'required')) { + $def_lang_field = 'l'.$def_lang.'_'.$master_field; + if ( !$object->ValidateRequired($def_lang_field, $object->Fields[$field_name]) ) { + $object->FieldErrors[$master_field]['pseudo'] = 'primary_lang_required'; + } + } + + if ($value == '') return NULL; + return $value; + } + +} + +?> \ No newline at end of file Index: trunk/core/kernel/kbase.php =================================================================== diff -u -r932 -r1339 --- trunk/core/kernel/kbase.php (.../kbase.php) (revision 932) +++ trunk/core/kernel/kbase.php (.../kbase.php) (revision 1339) @@ -31,18 +31,42 @@ */ var $Special=''; + var $OriginalParams; + /** + * Set's application + * + * @return kBase + * @access public + */ + function kBase() + { + $this->Application =& kApplication::Instance(); + } + + /** + * Create new instance of object + * + * @return kBase + */ + function &makeClass() + { + return new kBase(); + } + + /** * Set's prefix and special * * @param string $prefix * @param string $special * @access public */ - function Init($prefix,$special) + function Init($prefix,$special,$event_params=null) { $prefix=explode('_',$prefix,2); $this->Prefix=$prefix[0]; $this->Special=$special; + $this->OriginalParams = $event_params; } /** @@ -57,16 +81,15 @@ return rtrim($this->Prefix.'.'.$this->Special,'.'); } - /** - * Set's application - * - * @return kBase - * @access public - */ - function kBase() + function &getProperty($property_name) { - $this->Application =& kApplication::Instance(); + return $this->$property_name; } + + function setProperty($property_name, &$property_value) + { + $this->$property_name =& $property_value; + } } class kDBBase extends kBase { @@ -103,22 +126,45 @@ var $DisplayQueries = false; /** - * Object that holds all - * formatters created + * Fields allowed to be set (from table + virtual) + * + * @var Array + * @access private + */ + var $Fields=Array(); + + /** + * All virtual field names * - * @var kItemFormatter + * @var Array * @access private */ - var $ItemFormatter; + var $VirtualFields=Array(); /** + * Fields that need to be queried using custom expression, e.g. IF(...) AS value + * + * @var Array + * @access private + */ + var $CalculatedFields = Array(); + + /** * Description * * @var string Item' database table name, without prefix * @access public */ var $TableName; + /** + * Allows to determine object's table status ('temp' - temp table, '' - live table) + * + * @var string + * @access public + */ + var $mode=''; + function kDBBase() { parent::kBase(); @@ -138,6 +184,28 @@ } /** + * Set object' TableName to Live table from config + * + * @access public + */ + function SwitchToLive() + { + $this->TableName = $this->Application->getUnitOption($this->Prefix, 'TableName'); + } + + /** + * Set object' TableName to Temp table from config + * + * @access public + */ + function SwitchToTemp() + { + $this->TableName = $this->Application->getUnitOption($this->Prefix, 'TableName'); + $this->SetTableName( kTempTablesHandler::GetTempName($this->TableName) ); + $this->mode = 't'; + } + + /** * Sets SELECT part of list' query * * @access public @@ -149,12 +217,38 @@ $this->SelectClause = $sql; } - function GetSelectSQL() + function GetSelectSQL($base_query=null) { - return sprintf($this->SelectClause,$this->TableName); + if( !isset($base_query) ) $base_query = $this->SelectClause; + return $q = str_replace( Array('%1$s','%s'), $this->TableName, $base_query); } /** + * Insert calculated fields sql into query in place of %2$s, + * return processed query. + * + * @param string $query + * @return string + */ + function addCalculatedFields($query) + { + if($this->CalculatedFields) + { + $sql = Array(); + foreach($this->CalculatedFields as $field_name => $field_expression) + { + $sql[] = '('.$field_expression.') AS '.$field_name; + } + $sql = implode(',',$sql); + return $this->Application->ReplaceLanguageTags( str_replace('%2$s', ','.$sql, $query) ); + } + else + { + return str_replace('%2$s', '', $query); + } + } + + /** * Sets ID Field name used as primary key for loading items * * @access public @@ -167,18 +261,123 @@ $this->IDField = $field_name; } + function Configure() + { + $this->setTableName( $this->Application->getUnitOption($this->Prefix, 'TableName') ); + $this->setIDField( $this->Application->getUnitOption($this->Prefix, 'IDField') ); + $this->setConfigFields( $this->Application->getUnitOption($this->Prefix, 'Fields') ); + $this->setVirtualFields( $this->Application->getUnitOption($this->Prefix, 'VirtualFields') ); + $this->setCalculatedFields( $this->Application->getUnitOption($this->Prefix, 'CalculatedFields') ); + $this->prepareConfigOptions(); // this should go last, but before setDefaultValues, order is significant! + $this->SetDefaultValues(); + } + + function setCalculatedFields($fields) + { + $this->CalculatedFields = isset($fields[$this->Special]) ? $fields[$this->Special] : (isset($fields['']) ? $fields[''] : false); + } + /** + * Set's field names from table + * from config + * + * @param Array $fields + * @access public + */ + function setConfigFields($fields) + { + $this->Fields=$fields; + } + + /** + * Set fields (+options) for fields that physically doesn't exist in database + * + * @param Array $fields + * @access public + */ + function setVirtualFields($fields) + { + if($fields) + { + $this->VirtualFields = $fields; + $this->Fields = array_merge_recursive2($this->VirtualFields, $this->Fields); + } + } + + function SetDefaultValues() + { + + } + + function SetFieldOptions($field, $options) + { + $this->Fields[$field] = $options; + } + + function GetFieldOptions($field) + { + return isset($this->Fields[$field]) ? $this->Fields[$field] : Array(); + } + + /** * Returns formatted field value * * @param string $field * @return string * @access public */ - function GetField($field) + function GetField($name, $format=null) { + $options = $this->GetFieldOptions($name); + $val = $this->GetDBField($name); + $res = $val; + if (isset($options['formatter'])) { + $formatter =& $this->Application->recallObject($options['formatter']); + $res = $formatter->Format($val, $name, $this, $format ); + } + return $res; + } + + function HasField($name) + { + + } + + function GetFieldValues() + { } + function UpdateFormattersSubFields() + { + foreach ($this->Fields as $field => $options) { + if (isset($options['formatter'])) { + $formatter =& $this->Application->recallObject($options['formatter']); + $formatter->UpdateSubFields($field, $this->GetDBField($field), $options, $this); + } + } + } + + function prepareConfigOptions() + { + foreach (array_keys($this->Fields) as $field_name) + { + $field_options =& $this->Fields[$field_name]; + if( isset($field_options['options_sql']) ) + { + // replace with query result + $select_clause = $field_options['option_title_field'].','.$field_options['option_key_field']; + $sql = sprintf($field_options['options_sql'], $select_clause); + $field_options['options'] = $this->Conn->GetCol($sql, $field_options['option_key_field']); + unset($field_options['options_sql']); + } + if ( $formatter_class = getArrayValue($field_options, 'formatter') ) { + $formatter =& $this->Application->recallObject($formatter_class); + $formatter->PrepareOptions($field_name, $field_options, $this); + } + } + } + /** * Returns unformatted field value * @@ -201,6 +400,33 @@ { return $this->GetDBField($this->IDField); } + + /** + * Returns parent table information + * + * @param bool $from_temp load parent item from temp table + * @return Array + */ + function getLinkedInfo() + { + $parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix'); + if ( $parent_prefix ) + { + // if this is linked table, then set id from main table + $table_info = Array( + 'TableName' => $this->Application->getUnitOption($this->Prefix,'TableName'), + 'IdField' => $this->Application->getUnitOption($this->Prefix,'IDField'), + 'ForeignKey' => $this->Application->getUnitOption($this->Prefix,'ForeignKey'), + 'ParentTableKey' => $this->Application->getUnitOption($this->Prefix,'ParentTableKey'), + 'ParentPrefix' => $parent_prefix + ); + + $main_object = $this->Application->recallObject($parent_prefix); + return array_merge($table_info, Array('ParentId'=> $main_object->GetDBField( $table_info['ParentTableKey'] ) ) ); + } + return false; + } + } ?> \ No newline at end of file Index: trunk/core/kernel/db/db_connection.php =================================================================== diff -u -r944 -r1339 --- trunk/core/kernel/db/db_connection.php (.../db_connection.php) (revision 944) +++ trunk/core/kernel/db/db_connection.php (.../db_connection.php) (revision 1339) @@ -4,7 +4,7 @@ * Multi database connection class * */ - class DBConnection { + class kDBConnection { /** * Current database type @@ -75,7 +75,7 @@ * @return DBConnection * @access public */ - function DBConnection($dbType, $errorHandler = '') + function kDBConnection($dbType, $errorHandler = '') { $this->dbType = $dbType; $this->initMetaFunctions(); @@ -168,17 +168,24 @@ * @param string $db * @access public */ - function Connect($host,$user,$pass,$db) + function Connect($host,$user,$pass,$db,$force_new=false) { $func = $this->getMetaFunction('connect'); - $this->connectionID = $func($host,$user,$pass) or die('Can\'t connect to db'); + $this->connectionID = $func($host,$user,$pass,$force_new) or die('Can\'t connect to db'); if($this->connectionID) { $this->setDB($db); $this->showError(); } } + function ReConnect($host,$user,$pass,$db) + { + $func = $this->getMetaFunction('close'); + $func($this->connectionID); + $this->Connect($host,$user,$pass,$db); + } + /** * Shows error message from previous operation * if it failed @@ -269,7 +276,7 @@ { $sql .= ' '.$this->getLimitClause(0,1); $ret = $this->Query($sql); - if(!$ret) return $ret; + if(!$ret) return false; return array_shift($ret); } @@ -482,18 +489,32 @@ */ function qstr($s,$magic_quotes=false) { - $replace_quote = "\\'"; + $replaceQuote = "\\'"; if (!$magic_quotes) { - return "'".str_replace("'",$replace_quote,$s)."'"; + if ($replaceQuote[0] == '\\') + { + // only since php 4.0.5 + $s = str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s); + //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s)); + } + return "'".str_replace("'",$replaceQuote,$s)."'"; } // undo magic quotes for " $s = str_replace('\\"','"',$s); - return "'$s'"; + + if($replaceQuote == "\\'") // ' already quoted, no need to change anything + { + return "'$s'"; + } + else // change \' to '' for sybase/mssql + { + $s = str_replace('\\\\','\\',$s); + return "'".str_replace("\\'",$replaceQuote,$s)."'"; + } } - /** * Returns last error message *