Documentation TYPO3 par Ameos |
00001 <?php 00002 /*************************************************************** 00003 * Copyright notice 00004 * 00005 * (c) 2006 Oliver Hader <oh@inpublica.de> 00006 * All rights reserved 00007 * 00008 * This script is part of the TYPO3 project. The TYPO3 project is 00009 * free software; you can redistribute it and/or modify 00010 * it under the terms of the GNU General Public License as published by 00011 * the Free Software Foundation; either version 2 of the License, or 00012 * (at your option) any later version. 00013 * 00014 * The GNU General Public License can be found at 00015 * http://www.gnu.org/copyleft/gpl.html. 00016 * A copy is found in the textfile GPL.txt and important notices to the license 00017 * from the author is found in LICENSE.txt distributed with these scripts. 00018 * 00019 * 00020 * This script is distributed in the hope that it will be useful, 00021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00023 * GNU General Public License for more details. 00024 * 00025 * This copyright notice MUST APPEAR in all copies of the script! 00026 ***************************************************************/ 00090 class t3lib_TCEforms_inline { 00091 var $fObj; // Reference to the calling TCEforms instance 00092 var $backPath; // Reference to $fObj->backPath 00093 00094 var $inlineStructure = array(); // the structure/hierarchy where working in, e.g. cascading inline tables 00095 var $inlineFirstPid; // the first call of an inline type appeared on this page (pid of record) 00096 var $inlineNames = array(); // keys: form, object -> hold the name/id for each of them 00097 var $inlineData = array(); // inline data array used for JSON output 00098 var $inlineView = array(); // expanded/collapsed states for the current BE user 00099 var $inlineCount = 0; // count the number of inline types used 00100 var $inlineStyles = array(); 00101 00102 var $prependNaming = 'data'; // how the $this->fObj->prependFormFieldNames should be set ('data' is default) 00103 var $prependFormFieldNames; // reference to $this->fObj->prependFormFieldNames 00104 var $prependCmdFieldNames; // reference to $this->fObj->prependCmdFieldNames 00105 00106 00113 function init(&$tceForms) { 00114 $this->fObj =& $tceForms; 00115 $this->backPath =& $tceForms->backPath; 00116 $this->prependFormFieldNames =& $this->fObj->prependFormFieldNames; 00117 $this->prependCmdFieldNames =& $this->fObj->prependCmdFieldNames; 00118 $this->inlineStyles['margin-right'] = '5'; 00119 } 00120 00121 00132 function getSingleField_typeInline($table,$field,$row,&$PA) { 00133 // check the TCA configuration - if false is returned, something was wrong 00134 if ($this->checkConfiguration($PA['fieldConf']['config']) === false) return false; 00135 00136 // count the number of processed inline elements 00137 $this->inlineCount++; 00138 00139 // Init: 00140 $config = $PA['fieldConf']['config']; 00141 $foreign_table = $config['foreign_table']; 00142 t3lib_div::loadTCA($foreign_table); 00143 00144 $minitems = t3lib_div::intInRange($config['minitems'],0); 00145 $maxitems = t3lib_div::intInRange($config['maxitems'],0); 00146 if (!$maxitems) $maxitems=100000; 00147 00148 // Register the required number of elements: 00149 $this->fObj->requiredElements[$PA['itemFormElName']] = array($minitems,$maxitems,'imgName'=>$table.'_'.$row['uid'].'_'.$field); 00150 00151 // remember the page id (pid of record) where inline editing started first 00152 // we need that pid for ajax calls, so that they would know where the action takes place on the page structure 00153 if (!isset($this->inlineFirstPid)) { 00154 // if this record is not new, try to fetch the inlineView states 00155 // @TODO: Add checking/cleaning for unused tables, records, etc. to save space in uc-field 00156 if (t3lib_div::testInt($row['uid'])) { 00157 $inlineView = unserialize($GLOBALS['BE_USER']->uc['inlineView']); 00158 $this->inlineView = $inlineView[$table][$row['uid']]; 00159 } 00160 // if pid is negative, fetch the previous record and take its pid 00161 if ($row['pid'] < 0) { 00162 $prevRec = t3lib_BEfunc::getRecord($table, abs($row['pid'])); 00163 $this->inlineFirstPid = $prevRec['pid']; 00164 // take the pid as it is 00165 } else { 00166 $this->inlineFirstPid = $row['pid']; 00167 } 00168 } 00169 // add the current inline job to the structure stack 00170 $this->pushStructure($table, $row['uid'], $field, $config); 00171 // e.g. inline[<table>][<uid>][<field>] 00172 $nameForm = $this->inlineNames['form']; 00173 // e.g. inline[<pid>][<table1>][<uid1>][<field1>][<table2>][<uid2>][<field2>] 00174 $nameObject = $this->inlineNames['object']; 00175 // get the records related to this inline record 00176 $recordList = $this->getRelatedRecords($table,$field,$row,$PA,$config); 00177 // set the first and last record to the config array 00178 $config['inline']['first'] = $recordList[0]['uid']; 00179 $config['inline']['last'] = $recordList[count($recordList)-1]['uid']; 00180 00181 // tell the browser what we have (using JSON later) 00182 $top = $this->getStructureLevel(0); 00183 $this->inlineData['config'][$nameObject] = array('table' => $foreign_table); 00184 $this->inlineData['config'][$nameObject.'['.$foreign_table.']'] = array( 00185 'min' => $minitems, 00186 'max' => $maxitems, 00187 'sortable' => $config['appearance']['useSortable'], 00188 'top' => array( 00189 'table' => $top['table'], 00190 'uid' => $top['uid'], 00191 ), 00192 ); 00193 00194 // if relations are required to be unique, get the uids that have already been used on the foreign side of the relation 00195 if ($config['foreign_unique']) { 00196 // If uniqueness *and* selector are set, they should point to the same field - so, get the configuration of one: 00197 $selConfig = $this->getPossibleRecordsSelectorConfig($config, $config['foreign_unique']); 00198 // Get the used unique ids: 00199 $uniqueIds = $this->getUniqueIds($recordList, $config, $selConfig['type']=='groupdb'); 00200 $possibleRecords = $this->getPossibleRecords($table,$field,$row,$config,'foreign_unique'); 00201 $uniqueMax = $config['appearance']['useCombination'] || $possibleRecords === false ? -1 : count($possibleRecords); 00202 $this->inlineData['unique'][$nameObject.'['.$foreign_table.']'] = array( 00203 'max' => $uniqueMax, 00204 'used' => $uniqueIds, 00205 'type' => $selConfig['type'], 00206 'table' => $config['foreign_table'], 00207 'elTable' => $selConfig['table'], // element/record table (one step down in hierarchy) 00208 'field' => $config['foreign_unique'], 00209 'selector' => $selConfig['selector'], 00210 'possible' => $this->getPossibleRecordsFlat($possibleRecords), 00211 ); 00212 } 00213 00214 // if it's required to select from possible child records (reusable children), add a selector box 00215 if ($config['foreign_selector']) { 00216 // if not already set by the foreign_unique, set the possibleRecords here and the uniqueIds to an empty array 00217 if (!$config['foreign_unique']) { 00218 $possibleRecords = $this->getPossibleRecords($table,$field,$row,$config); 00219 $uniqueIds = array(); 00220 } 00221 $selectorBox = $this->renderPossibleRecordsSelector($possibleRecords,$config,$uniqueIds); 00222 $item .= $selectorBox; 00223 } 00224 00225 // wrap all inline fields of a record with a <div> (like a container) 00226 $item .= '<div id="'.$nameObject.'">'; 00227 00228 // define how to show the "Create new record" link - if there are more than maxitems, hide it 00229 if (count($recordList) >= $maxitems || ($uniqueMax > 0 && count($recordList) >= $uniqueMax)) { 00230 $config['inline']['inlineNewButtonStyle'] = 'display: none;'; 00231 } 00232 // add the "Create new record" link before all child records 00233 if (in_array($config['appearance']['newRecordLinkPosition'], array('both', 'top'))) { 00234 $item .= $this->getNewRecordLink($nameObject.'['.$foreign_table.']', $config); 00235 } 00236 00237 $item .= '<div id="'.$nameObject.'_records">'; 00238 $relationList = array(); 00239 if (count($recordList)) { 00240 foreach ($recordList as $rec) { 00241 $item .= $this->renderForeignRecord($row['uid'],$rec,$config); 00242 $relationList[] = $rec['uid']; 00243 } 00244 } 00245 $item .= '</div>'; 00246 00247 // add the "Create new record" link after all child records 00248 if (in_array($config['appearance']['newRecordLinkPosition'], array('both', 'bottom'))) { 00249 $item .= $this->getNewRecordLink($nameObject.'['.$foreign_table.']', $config); 00250 } 00251 00252 // add Drag&Drop functions for sorting to TCEforms::$additionalJS_post 00253 if (count($relationList) > 1 && $config['appearance']['useSortable']) 00254 $this->addJavaScriptSortable($nameObject.'_records'); 00255 // publish the uids of the child records in the given order to the browser 00256 $item .= '<input type="hidden" name="'.$nameForm.'" value="'.implode(',', $relationList).'" class="inlineRecord" />'; 00257 // close the wrap for all inline fields (container) 00258 $item .= '</div>'; 00259 00260 // on finishing this section, remove the last item from the structure stack 00261 $this->popStructure(); 00262 00263 // if this was the first call to the inline type, restore the values 00264 if (!$this->getStructureDepth()) { 00265 unset($this->inlineFirstPid); 00266 } 00267 00268 return $item; 00269 } 00270 00271 00272 /******************************************************* 00273 * 00274 * Regular rendering of forms, fields, etc. 00275 * 00276 *******************************************************/ 00277 00278 00287 function renderForeignRecord($parentUid, $rec, $config = array()) { 00288 $foreign_table = $config['foreign_table']; 00289 $foreign_field = $config['foreign_field']; 00290 $foreign_selector = $config['foreign_selector']; 00291 00292 // Send a mapping information to the browser via JSON: 00293 // e.g. data[<curTable>][<curId>][<curField>] => data[<pid>][<parentTable>][<parentId>][<parentField>][<curTable>][<curId>][<curField>] 00294 $this->inlineData['map'][$this->inlineNames['form']] = $this->inlineNames['object']; 00295 00296 // Set this variable if we handle a brand new unsaved record: 00297 $isNewRecord = t3lib_div::testInt($rec['uid']) ? false : true; 00298 // If there is a selector field, normalize it: 00299 if ($foreign_selector) { 00300 $rec[$foreign_selector] = $this->normalizeUid($rec[$foreign_selector]); 00301 } 00302 00303 $hasAccess = $this->checkAccess($isNewRecord?'new':'edit', $foreign_table, $rec['uid']); 00304 00305 if(!$hasAccess) return false; 00306 00307 // Get the current naming scheme for DOM name/id attributes: 00308 $nameObject = $this->inlineNames['object']; 00309 $appendFormFieldNames = '['.$foreign_table.']['.$rec['uid'].']'; 00310 $formFieldNames = $nameObject.$appendFormFieldNames; 00311 00312 $header = $this->renderForeignRecordHeader($parentUid, $foreign_table, $rec, $config); 00313 $combination = $this->renderCombinationTable($rec, $appendFormFieldNames, $config); 00314 $fields = $this->fObj->getMainFields($foreign_table,$rec); 00315 $fields = $this->wrapFormsSection($fields); 00316 00317 if ($isNewRecord) { 00318 // show this record expanded or collapsed 00319 $isExpanded = is_array($config['appearance']) && $config['appearance']['collapseAll'] ? 1 : 0; 00320 // get the top parent table 00321 $top = $this->getStructureLevel(0); 00322 $ucFieldName = 'uc['.$top['table'].']['.$top['uid'].']'.$appendFormFieldNames; 00323 // set additional fields for processing for saving 00324 $fields .= '<input type="hidden" name="'.$this->prependFormFieldNames.$appendFormFieldNames.'[pid]" value="'.$rec['pid'].'"/>'; 00325 $fields .= '<input type="hidden" name="'.$ucFieldName.'" value="'.$isExpanded.'" />'; 00326 00327 } else { 00328 // show this record expanded or collapsed 00329 $isExpanded = $this->getExpandedCollapsedState($foreign_table, $rec['uid']); 00330 // set additional field for processing for saving 00331 $fields .= '<input type="hidden" name="'.$this->prependCmdFieldNames.$appendFormFieldNames.'[delete]" value="1" disabled="disabled" />'; 00332 } 00333 00334 // if this record should be shown collapsed 00335 if (!$isExpanded) $appearanceStyleFields = ' style="display: none;"'; 00336 00337 // set the record container with data for output 00338 $out = '<div id="'.$formFieldNames.'_header">'.$header.'</div>'; 00339 $out .= '<div id="'.$formFieldNames.'_fields"'.$appearanceStyleFields.'>'.$fields.$combination.'</div>'; 00340 // wrap the header, fields and combination part of a child record with a div container 00341 $out = '<div id="'.$formFieldNames.'_div"'.($isNewRecord ? ' class="inlineIsNewRecord"' : '').'>' . $out . '</div>'; 00342 00343 return $out; 00344 } 00345 00346 00357 function renderForeignRecordHeader($parentUid, $foreign_table, $rec, $config = array()) { 00358 // Init: 00359 $formFieldNames = $this->inlineNames['object'].'['.$foreign_table.']['.$rec['uid'].']'; 00360 $expandSingle = $config['appearance']['expandSingle'] ? 1 : 0; 00361 $onClick = "return inline.expandCollapseRecord('".htmlspecialchars($formFieldNames)."', $expandSingle)"; 00362 00363 // Pre-Processing: 00364 $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $config, $rec); 00365 $hasForeignLabel = !$isOnSymmetricSide && $config['foreign_label'] ? true : false; 00366 $hasSymmetricLabel = $isOnSymmetricSide && $config['symmetric_label'] ? true : false; 00367 // Get the record title/label for a record: 00368 // render using a self-defined user function 00369 if ($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc']) { 00370 $params = array( 00371 'table' => $foreign_table, 00372 'row' => $rec, 00373 'title' => '', 00374 'isOnSymmetricSide' => $isOnSymmetricSide 00375 ); 00376 $null = null; // callUserFunction requires a third parameter, but we don't want to give $this as reference! 00377 t3lib_div::callUserFunction($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc'], $params, $null); 00378 $recTitle = $params['title']; 00379 // render the special alternative title 00380 } elseif ($hasForeignLabel || $hasSymmetricLabel) { 00381 $titleCol = $hasForeignLabel ? $config['foreign_label'] : $config['symmetric_label']; 00382 $foreignConfig = $this->getPossibleRecordsSelectorConfig($config, $titleCol); 00383 // Render title for everything else than group/db: 00384 if ($foreignConfig['type'] != 'groupdb') { 00385 $recTitle = t3lib_BEfunc::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol], 0, 0, false); 00386 // Render title for group/db: 00387 } else { 00388 // $recTitle could be something like: "tx_table_123|...", 00389 $valueParts = t3lib_div::trimExplode('|', $rec[$titleCol]); 00390 $itemParts = t3lib_div::revExplode('_', $valueParts[0], 2); 00391 $recTemp = t3lib_befunc::getRecordWSOL($itemParts[0], $itemParts[1]); 00392 $recTitle = t3lib_BEfunc::getRecordTitle($itemParts[0], $recTemp, true); 00393 } 00394 $recTitle = t3lib_BEfunc::getRecordTitlePrep($recTitle); 00395 if (!strcmp(trim($recTitle),'')) { 00396 $recTitle = t3lib_BEfunc::getNoRecordTitle(true); 00397 } 00398 // render the standard 00399 } else { 00400 $recTitle = t3lib_BEfunc::getRecordTitle($foreign_table, $rec, true); 00401 } 00402 00403 $altText = t3lib_BEfunc::getRecordIconAltText($rec, $foreign_table); 00404 $iconImg = 00405 '<a href="#" onclick="'.htmlspecialchars($onClick).'">'.t3lib_iconWorks::getIconImage( 00406 $foreign_table, $rec, $this->backPath, 00407 'title="'.htmlspecialchars($altText).'" class="absmiddle"' 00408 ).'</a>'; 00409 00410 $label = 00411 '<a href="#" onclick="'.htmlspecialchars($onClick).'" style="display: block;">'. 00412 '<span id="'.$formFieldNames.'_label">'.$recTitle.'</span>'. 00413 '</a>'; 00414 00415 $ctrl = $this->renderForeignRecordHeaderControl($parentUid, $foreign_table, $rec, $config); 00416 00417 // @TODO: Check the table wrapping and the CSS definitions 00418 $header = 00419 '<table cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-right: '.$this->inlineStyles['margin-right'].'px;"'. 00420 ($this->fObj->borderStyle[2] ? ' background="'.htmlspecialchars($this->backPath.$this->fObj->borderStyle[2]).'"':''). 00421 ($this->fObj->borderStyle[3] ? ' class="'.htmlspecialchars($this->fObj->borderStyle[3]).'"':'').'>' . 00422 '<tr class="class-main12"><td width="18">'.$iconImg.'</td><td align="left"><b>'.$label.'</b></td><td align="right">'.$ctrl.'</td></tr></table>'; 00423 00424 return $header; 00425 } 00426 00427 00438 function renderForeignRecordHeaderControl($parentUid, $foreign_table, $rec, $config = array()) { 00439 // Initialize: 00440 $cells=array(); 00441 $isNewItem = substr($rec['uid'], 0, 3) == 'NEW'; 00442 00443 $tcaTableCtrl =& $GLOBALS['TCA'][$foreign_table]['ctrl']; 00444 $tcaTableCols =& $GLOBALS['TCA'][$foreign_table]['columns']; 00445 00446 $isPagesTable = $foreign_table == 'pages' ? true : false; 00447 $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $config, $rec); 00448 $enableManualSorting = $tcaTableCtrl['sortby'] || $config['MM'] || (!$isOnSymmetricSide && $config['foreign_sortby']) || ($isOnSymmetricSide && $config['symmetric_sortby']) ? true : false; 00449 00450 $nameObjectFt = $this->inlineNames['object'].'['.$foreign_table.']'; 00451 $nameObjectFtId = $nameObjectFt.'['.$rec['uid'].']'; 00452 00453 $calcPerms = $GLOBALS['BE_USER']->calcPerms( 00454 t3lib_BEfunc::readPageAccess($rec['pid'], $GLOBALS['BE_USER']->getPagePermsClause(1)) 00455 ); 00456 00457 // If the listed table is 'pages' we have to request the permission settings for each page: 00458 if ($isPagesTable) { 00459 $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages',$rec['uid'])); 00460 } 00461 00462 // This expresses the edit permissions for this particular element: 00463 $permsEdit = ($isPagesTable && ($localCalcPerms&2)) || (!$isPagesTable && ($calcPerms&16)); 00464 00465 // "Info": (All records) 00466 if (!$isNewItem) 00467 $cells[]='<a href="#" onclick="'.htmlspecialchars('top.launchView(\''.$foreign_table.'\', \''.$rec['uid'].'\'); return false;').'">'. 00468 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/zoom2.gif','width="12" height="12"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:showInfo',1).'" alt="" />'. 00469 '</a>'; 00470 00471 // If the table is NOT a read-only table, then show these links: 00472 if (!$tcaTableCtrl['readOnly']) { 00473 00474 // "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row or if default values can depend on previous record): 00475 if ($enableManualSorting || $tcaTableCtrl['useColumnsForDefaultValues']) { 00476 if ( 00477 (!$isPagesTable && ($calcPerms&16)) || // For NON-pages, must have permission to edit content on this parent page 00478 ($isPagesTable && ($calcPerms&8)) // For pages, must have permission to create new pages here. 00479 ) { 00480 $onClick = "return inline.createNewRecord('".$nameObjectFt."','".$rec['uid']."')"; 00481 if ($config['inline']['inlineNewButtonStyle']) { 00482 $style = ' style="'.$config['inline']['inlineNewButtonStyle'].'"'; 00483 } 00484 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'" class="inlineNewButton"'.$style.'>'. 00485 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/new_'.($isPagesTable?'page':'el').'.gif','width="'.($isPagesTable?13:11).'" height="12"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:new'.($isPagesTable?'Page':'Record'),1).'" alt="" />'. 00486 '</a>'; 00487 } 00488 } 00489 00490 // Drag&Drop Sorting: Sortable handler for script.aculo.us 00491 if ($permsEdit && $enableManualSorting && $config['appearance']['useSortable']) { 00492 $cells[] = '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/move.gif','width="16" height="16" hspace="2"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.move',1).'" alt="" style="cursor: move;" class="sortableHandle" />'; 00493 } 00494 00495 // "Up/Down" links 00496 if ($permsEdit && $enableManualSorting) { 00497 $onClick = "return inline.changeSorting('".$nameObjectFtId."', '1')"; // Up 00498 $style = $config['inline']['first'] == $rec['uid'] ? 'style="visibility: hidden;"' : ''; 00499 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'" class="sortingUp" '.$style.'>'. 00500 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/button_up.gif','width="11" height="10"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:moveUp',1).'" alt="" />'. 00501 '</a>'; 00502 00503 $onClick = "return inline.changeSorting('".$nameObjectFtId."', '-1')"; // Down 00504 $style = $config['inline']['last'] == $rec['uid'] ? 'style="visibility: hidden;"' : ''; 00505 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'" class="sortingDown" '.$style.'>'. 00506 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/button_down.gif','width="11" height="10"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:moveDown',1).'" alt="" />'. 00507 '</a>'; 00508 } 00509 00510 // "Hide/Unhide" links: 00511 $hiddenField = $tcaTableCtrl['enablecolumns']['disabled']; 00512 if ($permsEdit && $hiddenField && $tcaTableCols[$hiddenField] && (!$tcaTableCols[$hiddenField]['exclude'] || $GLOBALS['BE_USER']->check('non_exclude_fields',$foreign_table.':'.$hiddenField))) { 00513 $onClick = "return inline.enableDisableRecord('".$nameObjectFtId."')"; 00514 if ($rec[$hiddenField]) { 00515 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'">'. 00516 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/button_unhide.gif','width="11" height="10"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:unHide'.($isPagesTable?'Page':''),1).'" alt="" id="'.$nameObjectFtId.'_disabled" />'. 00517 '</a>'; 00518 } else { 00519 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'">'. 00520 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/button_hide.gif','width="11" height="10"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:hide'.($isPagesTable?'Page':''),1).'" alt="" id="'.$nameObjectFtId.'_disabled" />'. 00521 '</a>'; 00522 } 00523 } 00524 00525 // "Delete" link: 00526 if ( 00527 ($isPagesTable && ($localCalcPerms&4)) || (!$isPagesTable && ($calcPerms&16)) 00528 ) { 00529 $onClick = "inline.deleteRecord('".$nameObjectFtId."');"; 00530 $cells[]='<a href="#" onclick="'.htmlspecialchars('if (confirm('.$GLOBALS['LANG']->JScharCode($GLOBALS['LANG']->getLL('deleteWarning')).')) { '.$onClick.' } return false;').'">'. 00531 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/garbage.gif','width="11" height="12"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:delete',1).'" alt="" />'. 00532 '</a>'; 00533 } 00534 } 00535 00536 // If the record is edit-locked by another user, we will show a little warning sign: 00537 if ($lockInfo=t3lib_BEfunc::isRecordLocked($foreign_table,$rec['uid'])) { 00538 $cells[]='<a href="#" onclick="'.htmlspecialchars('alert('.$GLOBALS['LANG']->JScharCode($lockInfo['msg']).');return false;').'">'. 00539 '<img'.t3lib_iconWorks::skinImg('','gfx/recordlock_warning3.gif','width="17" height="12"').' title="'.htmlspecialchars($lockInfo['msg']).'" alt="" />'. 00540 '</a>'; 00541 } 00542 00543 // Compile items into a DIV-element: 00544 return ' 00545 <!-- CONTROL PANEL: '.$foreign_table.':'.$rec['uid'].' --> 00546 <div class="typo3-DBctrl">'.implode('',$cells).'</div>'; 00547 } 00548 00549 00560 function renderCombinationTable(&$rec, $appendFormFieldNames, $config = array()) { 00561 $foreign_table = $config['foreign_table']; 00562 $foreign_selector = $config['foreign_selector']; 00563 00564 if ($foreign_selector && $config['appearance']['useCombination']) { 00565 $comboConfig = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector]['config']; 00566 $comboRecord = array(); 00567 00568 // record does already exist, so load it 00569 if (t3lib_div::testInt($rec[$foreign_selector])) { 00570 $comboRecord = $this->getRecord( 00571 $this->inlineFirstPid, 00572 $comboConfig['foreign_table'], 00573 $rec[$foreign_selector] 00574 ); 00575 $isNewRecord = false; 00576 // it's a new record, so get some default data 00577 } else { 00578 $comboRecord = $this->getNewRecord( 00579 $this->inlineFirstPid, 00580 $comboConfig['foreign_table'] 00581 ); 00582 $isNewRecord = true; 00583 } 00584 00585 // get the TCEforms interpretation of the TCA of the child table 00586 $out = $this->fObj->getMainFields($comboConfig['foreign_table'], $comboRecord); 00587 $out = $this->wrapFormsSection($out, array(), array('class' => 'wrapperAttention')); 00588 00589 // if this is a new record, add a pid value to store this record and the pointer value for the intermediate table 00590 if ($isNewRecord) { 00591 $comboFormFieldName = $this->prependFormFieldNames.'['.$comboConfig['foreign_table'].']['.$comboRecord['uid'].'][pid]'; 00592 $out .= '<input type="hidden" name="'.$comboFormFieldName.'" value="'.$this->inlineFirstPid.'"/>'; 00593 } 00594 00595 // if the foreign_selector field is also responsible for uniqueness, tell the browser the uid of the "other" side of the relation 00596 if ($isNewRecord || $config['foreign_unique'] == $foreign_selector) { 00597 $parentFormFieldName = $this->prependFormFieldNames.$appendFormFieldNames.'['.$foreign_selector.']'; 00598 $out .= '<input type="hidden" name="'.$parentFormFieldName.'" value="'.$comboRecord['uid'].'" />'; 00599 } 00600 } 00601 00602 return $out; 00603 } 00604 00605 00615 function renderPossibleRecordsSelector($selItems, $conf, $uniqueIds=array()) { 00616 $foreign_table = $conf['foreign_table']; 00617 $foreign_selector = $conf['foreign_selector']; 00618 00619 $selConfig = $this->getPossibleRecordsSelectorConfig($conf, $foreign_selector); 00620 $config = $selConfig['PA']['fieldConf']['config']; 00621 00622 if ($selConfig['type'] == 'select') { 00623 $item = $this->renderPossibleRecordsSelectorTypeSelect($selItems, $conf, $selConfig['PA'], $uniqueIds); 00624 } elseif ($selConfig['type'] == 'groupdb') { 00625 $item = $this->renderPossibleRecordsSelectorTypeGroupDB($conf, $selConfig['PA']); 00626 } 00627 00628 return $item; 00629 } 00630 00631 00642 function renderPossibleRecordsSelectorTypeSelect($selItems, $conf, &$PA, $uniqueIds=array()) { 00643 $foreign_table = $conf['foreign_table']; 00644 $foreign_selector = $conf['foreign_selector']; 00645 00646 $PA = array(); 00647 $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector]; 00648 $PA['fieldConf']['config']['form_type'] = $PA['fieldConf']['config']['form_type'] ? $PA['fieldConf']['config']['form_type'] : $PA['fieldConf']['config']['type']; // Using "form_type" locally in this script 00649 $PA['fieldTSConfig'] = $this->fObj->setTSconfig($foreign_table,array(),$foreign_selector); 00650 $config = $PA['fieldConf']['config']; 00651 00652 if(!$disabled) { 00653 // Create option tags: 00654 $opt = array(); 00655 $styleAttrValue = ''; 00656 foreach($selItems as $p) { 00657 if ($config['iconsInOptionTags']) { 00658 $styleAttrValue = $this->fObj->optionTagStyle($p[2]); 00659 } 00660 if (!in_array($p[1], $uniqueIds)) { 00661 $opt[]= '<option value="'.htmlspecialchars($p[1]).'"'. 00662 ' style="'.(in_array($p[1], $uniqueIds) ? '' : ''). 00663 ($styleAttrValue ? ' style="'.htmlspecialchars($styleAttrValue) : '').'">'. 00664 htmlspecialchars($p[0]).'</option>'; 00665 } 00666 } 00667 00668 // Put together the selector box: 00669 $selector_itemListStyle = isset($config['itemListStyle']) ? ' style="'.htmlspecialchars($config['itemListStyle']).'"' : ' style="'.$this->fObj->defaultMultipleSelectorStyle.'"'; 00670 $size = intval($conf['size']); 00671 $size = $conf['autoSizeMax'] ? t3lib_div::intInRange(count($itemArray)+1,t3lib_div::intInRange($size,1),$conf['autoSizeMax']) : $size; 00672 $onChange = "return inline.importNewRecord('".$this->inlineNames['object']."[".$conf['foreign_table']."]')"; 00673 $item = ' 00674 <select id="'.$this->inlineNames['object'].'['.$conf['foreign_table'].']_selector"'. 00675 $this->fObj->insertDefStyle('select'). 00676 ($size ? ' size="'.$size.'"' : ''). 00677 ' onchange="'.htmlspecialchars($onChange).'"'. 00678 $PA['onFocus']. 00679 $selector_itemListStyle. 00680 ($conf['foreign_unique'] ? ' isunique="isunique"' : '').'> 00681 '.implode(' 00682 ',$opt).' 00683 </select>'; 00684 00685 // add a "Create new relation" link for adding new relations 00686 // this is neccessary, if the size of the selector is "1" or if 00687 // there is only one record item in the select-box, that is selected by default 00688 // the selector-box creates a new relation on using a onChange event (see some line above) 00689 $createNewRelationText = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createNewRelation',1); 00690 $item .= 00691 '<a href="#" onclick="'.htmlspecialchars($onChange).'" align="abstop">'. 00692 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/edit2.gif','width="11" height="12"').' align="absmiddle" '.t3lib_BEfunc::titleAltAttrib($createNewRelationText).' /> '.$createNewRelationText. 00693 '</a>'; 00694 // wrap the selector and add a spacer to the bottom 00695 $item = '<div style="margin-bottom: 20px;">'.$item.'</div>'; 00696 } 00697 00698 return $item; 00699 } 00700 00701 00710 function renderPossibleRecordsSelectorTypeGroupDB($conf, &$PA) { 00711 $foreign_table = $conf['foreign_table']; 00712 00713 $config = $PA['fieldConf']['config']; 00714 $allowed = $config['allowed']; 00715 $objectPrefix = $this->inlineNames['object'].'['.$foreign_table.']'; 00716 00717 $createNewRelationText = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createNewRelation',1); 00718 $onClick = "setFormValueOpenBrowser('db','".('|||'.$allowed.'|'.$objectPrefix.'|inline.checkUniqueElement||inline.importElement')."'); return false;"; 00719 $item = 00720 '<a href="#" onclick="'.htmlspecialchars($onClick).'">'. 00721 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/insert3.gif','width="14" height="14"').' align="absmiddle" '.t3lib_BEfunc::titleAltAttrib($createNewRelationText).' /> '.$createNewRelationText. 00722 '</a>'; 00723 00724 return $item; 00725 } 00726 00727 00735 function getNewRecordLink($objectPrefix, $conf = array()) { 00736 if ($conf['inline']['inlineNewButtonStyle']) $style = ' style="'.$conf['inline']['inlineNewButtonStyle'].'"'; 00737 00738 $onClick = "return inline.createNewRecord('$objectPrefix')"; 00739 $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createnew',1); 00740 00741 if ($conf['appearance']['newRecordLinkAddTitle']) 00742 $tableTitle .= ' '.$GLOBALS['LANG']->sL($GLOBALS['TCA'][$conf['foreign_table']]['ctrl']['title'],1); 00743 00744 $out = ' 00745 <div class="typo3-newRecordLink"> 00746 <a href="#" onClick="'.$onClick.'" class="inlineNewButton"'.$style.' title="'.$title.$tableTitle.'">'. 00747 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/new_el.gif','width="11" height="12"').' alt="'.$title.$tableTitle.'" />'. 00748 $title.t3lib_div::fixed_lgd_cs($tableTitle, $this->fObj->titleLen). 00749 '</a> 00750 </div>'; 00751 return $out; 00752 } 00753 00754 00761 function addJavaScriptSortable($objectId) { 00762 $this->fObj->additionalJS_post[] = ' 00763 inline.createDragAndDropSorting("'.$objectId.'"); 00764 '; 00765 } 00766 00767 00768 /******************************************************* 00769 * 00770 * Handling of AJAX calls 00771 * 00772 *******************************************************/ 00773 00774 00783 function createNewRecord($domObjectId, $foreignUid = 0) { 00784 // parse the DOM identifier (string), add the levels to the structure stack (array) and load the TCA config 00785 $this->parseStructureString($domObjectId, true); 00786 // the current table - for this table we should add/import records 00787 $current = $this->inlineStructure['unstable']; 00788 // the parent table - this table embeds the current table 00789 $parent = $this->getStructureLevel(-1); 00790 // get TCA 'config' of the parent table 00791 $config = $parent['config']; 00792 00793 // dynamically create a new record using t3lib_transferData 00794 if (!$foreignUid || !t3lib_div::testInt($foreignUid) || $config['foreign_selector']) { 00795 $record = $this->getNewRecord($this->inlineFirstPid, $current['table']); 00796 00797 // dynamically import an existing record (this could be a call from a select box) 00798 } else { 00799 $record = $this->getRecord($this->inlineFirstPid, $current['table'], $foreignUid); 00800 } 00801 00802 // now there is a foreign_selector, so there is a new record on the intermediate table, but 00803 // this intermediate table holds a field, which is responsible for the foreign_selector, so 00804 // we have to set this field to the uid we get - or if none, to a new uid 00805 if ($config['foreign_selector'] && $foreignUid) { 00806 $selConfig = $this->getPossibleRecordsSelectorConfig($config, $config['foreign_selector']); 00807 // For a selector of type group/db, prepend the tablename (<tablename>_<uid>): 00808 $record[$config['foreign_selector']] = $selConfig['type'] != 'groupdb' ? '' : $selConfig['table'].'_'; 00809 $record[$config['foreign_selector']] .= $foreignUid; 00810 } 00811 00812 // the HTML-object-id's prefix of the dynamically created record 00813 $objectPrefix = $this->inlineNames['object'].'['.$current['table'].']'; 00814 $objectId = $objectPrefix.'['.$record['uid'].']'; 00815 00816 // render the foreign record that should passed back to browser 00817 $item = $this->renderForeignRecord($parent['uid'], $record, $config); 00818 if($item === false) { 00819 $jsonArray = array( 00820 'data' => 'Access denied', 00821 'scriptCall' => array( 00822 "alert('Access denied');", 00823 ) 00824 ); 00825 return $this->getJSON($jsonArray); 00826 } 00827 00828 // Encode TCEforms AJAX response with utf-8: 00829 $item = $GLOBALS['LANG']->csConvObj->utf8_encode($item, $GLOBALS['LANG']->charSet); 00830 00831 if (!$current['uid']) { 00832 $jsonArray = array( 00833 'data' => $item, 00834 'scriptCall' => array( 00835 "inline.domAddNewRecord('bottom','".$this->inlineNames['object']."_records','$objectPrefix',json.data);", 00836 "inline.memorizeAddRecord('$objectPrefix','".$record['uid']."',null,'$foreignUid');" 00837 ) 00838 ); 00839 00840 // append the HTML data after an existing record in the container 00841 } else { 00842 $jsonArray = array( 00843 'data' => $item, 00844 'scriptCall' => array( 00845 "inline.domAddNewRecord('after','".$domObjectId.'_div'."','$objectPrefix',json.data);", 00846 "inline.memorizeAddRecord('$objectPrefix','".$record['uid']."','".$current['uid']."','$foreignUid');" 00847 ) 00848 ); 00849 } 00850 00851 // add the JavaScript data that would have been added at the bottom of a regular TCEforms calls 00852 $jsonArray['scriptCall'][] = $this->fObj->JSbottom($this->fObj->formName, true); 00853 // if script.aculo.us Sortable is used, update the Observer to know the the record 00854 if ($config['appearance']['useSortable']) 00855 $jsonArray['scriptCall'][] = "inline.createDragAndDropSorting('".$this->inlineNames['object']."_records');"; 00856 // if TCEforms has some JavaScript code to be executed, just do it 00857 if ($this->fObj->extJSCODE) 00858 $jsonArray['scriptCall'][] = $this->fObj->extJSCODE; 00859 // tell the browser to scroll to the newly created record 00860 $jsonArray['scriptCall'][] = "Element.scrollTo('".$objectId."_div');"; 00861 // fade out and fade in the new record in the browser view to catch the user's eye 00862 $jsonArray['scriptCall'][] = "inline.fadeOutFadeIn('".$objectId."_div');"; 00863 00864 // return the JSON string 00865 return $this->getJSON($jsonArray); 00866 } 00867 00868 00877 function setExpandedCollapsedState($domObjectId, $expand, $collapse) { 00878 // parse the DOM identifier (string), add the levels to the structure stack (array), but don't load TCA config 00879 $this->parseStructureString($domObjectId, false); 00880 // the current table - for this table we should add/import records 00881 $current = $this->inlineStructure['unstable']; 00882 // the top parent table - this table embeds the current table 00883 $top = $this->getStructureLevel(0); 00884 00885 // only do some action if the top record and the current record were saved before 00886 if (t3lib_div::testInt($top['uid'])) { 00887 $inlineView = unserialize($GLOBALS['BE_USER']->uc['inlineView']); 00888 $inlineViewCurrent =& $inlineView[$top['table']][$top['uid']]; 00889 00890 $expandUids = t3lib_div::trimExplode(',', $expand); 00891 $collapseUids = t3lib_div::trimExplode(',', $collapse); 00892 00893 // set records to be expanded 00894 foreach ($expandUids as $uid) { 00895 $inlineViewCurrent[$current['table']][] = $uid; 00896 } 00897 // set records to be collapsed 00898 foreach ($collapseUids as $uid) { 00899 $inlineViewCurrent[$current['table']] = $this->removeFromArray($uid, $inlineViewCurrent[$current['table']]); 00900 } 00901 00902 // save states back to database 00903 if (is_array($inlineViewCurrent[$current['table']])) { 00904 $GLOBALS['BE_USER']->uc['inlineView'] = serialize($inlineView); 00905 $GLOBALS['BE_USER']->writeUC(); 00906 } 00907 } 00908 } 00909 00910 00911 /******************************************************* 00912 * 00913 * Get data from database and handle relations 00914 * 00915 *******************************************************/ 00916 00917 00928 function getRelatedRecords($table,$field,$row,&$PA,$config) { 00929 $records = array(); 00930 00931 // Creating the label for the "No Matching Value" entry. 00932 $nMV_label = isset($PA['fieldTSConfig']['noMatchingValue_label']) ? $this->fObj->sL($PA['fieldTSConfig']['noMatchingValue_label']) : '[ '.$this->fObj->getLL('l_noMatchingValue').' ]'; 00933 00934 // Register the required number of elements: 00935 # $this->fObj->requiredElements[$PA['itemFormElName']] = array($minitems,$maxitems,'imgName'=>$table.'_'.$row['uid'].'_'.$field); 00936 00937 // Perform modification of the selected items array: 00938 $itemArray = t3lib_div::trimExplode(',',$PA['itemFormElValue'],1); 00939 foreach($itemArray as $tk => $tv) { 00940 $tvP = explode('|',$tv,2); 00941 // get the records for this uid using t3lib_transferdata 00942 $records[] = $this->getRecord($row['pid'], $config['foreign_table'], $tvP[0]); 00943 } 00944 00945 return $records; 00946 } 00947 00948 00960 function getPossibleRecords($table,$field,$row,$conf,$checkForConfField='foreign_selector') { 00961 // ctrl configuration from TCA: 00962 $tcaTableCtrl = $GLOBALS['TCA'][$table]['ctrl']; 00963 // Field configuration from TCA: 00964 $foreign_table = $conf['foreign_table']; 00965 $foreign_check = $conf[$checkForConfField]; 00966 00967 $foreignConfig = $this->getPossibleRecordsSelectorConfig($conf, $foreign_check); 00968 $PA = $foreignConfig['PA']; 00969 $config = $PA['fieldConf']['config']; 00970 00971 if ($foreignConfig['type'] == 'select') { 00972 // Getting the selector box items from the system 00973 $selItems = $this->fObj->addSelectOptionsToItemArray($this->fObj->initItemArray($PA['fieldConf']),$PA['fieldConf'],$this->fObj->setTSconfig($table,$row),$field); 00974 if ($config['itemsProcFunc']) $selItems = $this->fObj->procItems($selItems,$PA['fieldTSConfig']['itemsProcFunc.'],$config,$table,$row,$field); 00975 00976 // Possibly remove some items: 00977 $removeItems = t3lib_div::trimExplode(',',$PA['fieldTSConfig']['removeItems'],1); 00978 foreach($selItems as $tk => $p) { 00979 00980 // Checking languages and authMode: 00981 $languageDeny = $tcaTableCtrl['languageField'] && !strcmp($tcaTableCtrl['languageField'], $field) && !$GLOBALS['BE_USER']->checkLanguageAccess($p[1]); 00982 $authModeDeny = $config['form_type']=='select' && $config['authMode'] && !$GLOBALS['BE_USER']->checkAuthMode($table,$field,$p[1],$config['authMode']); 00983 if (in_array($p[1],$removeItems) || $languageDeny || $authModeDeny) { 00984 unset($selItems[$tk]); 00985 } elseif (isset($PA['fieldTSConfig']['altLabels.'][$p[1]])) { 00986 $selItems[$tk][0]=$this->fObj->sL($PA['fieldTSConfig']['altLabels.'][$p[1]]); 00987 } 00988 00989 // Removing doktypes with no access: 00990 if ($table.'.'.$field == 'pages.doktype') { 00991 if (!($GLOBALS['BE_USER']->isAdmin() || t3lib_div::inList($GLOBALS['BE_USER']->groupData['pagetypes_select'],$p[1]))) { 00992 unset($selItems[$tk]); 00993 } 00994 } 00995 } 00996 } else { 00997 $selItems = false; 00998 } 00999 01000 return $selItems; 01001 } 01002 01011 function getUniqueIds($records, $conf=array(), $splitValue=false) { 01012 $uniqueIds = array(); 01013 01014 if ($conf['foreign_unique'] && count($records)) { 01015 foreach ($records as $rec) { 01016 $value = $rec[$conf['foreign_unique']]; 01017 // Split the value and extract the table and uid: 01018 if ($splitValue) { 01019 $valueParts = t3lib_div::trimExplode('|', $value); 01020 $itemParts = explode('_', $valueParts[0]); 01021 $value = array( 01022 'uid' => array_pop($itemParts), 01023 'table' => implode('_', $itemParts) 01024 ); 01025 } 01026 $uniqueIds[$rec['uid']] = $value; 01027 } 01028 } 01029 01030 return $uniqueIds; 01031 } 01032 01033 01044 function getRecord($pid, $table, $uid, $cmd='') { 01045 $trData = t3lib_div::makeInstance('t3lib_transferData'); 01046 $trData->addRawData = TRUE; 01047 # $trData->defVals = $this->defVals; 01048 $trData->lockRecords=1; 01049 $trData->disableRTE = $GLOBALS['SOBE']->MOD_SETTINGS['disableRTE']; 01050 // if a new record should be created 01051 $trData->fetchRecord($table, $uid, ($cmd === 'new' ? 'new' : '')); 01052 reset($trData->regTableItems_data); 01053 $rec = current($trData->regTableItems_data); 01054 $rec['uid'] = $cmd == 'new' ? uniqid('NEW') : $uid; 01055 if ($cmd=='new') $rec['pid'] = $pid; 01056 01057 return $rec; 01058 } 01059 01060 01068 function getNewRecord($pid, $table) { 01069 return $this->getRecord($pid, $table, '', 'new'); 01070 } 01071 01072 01073 /******************************************************* 01074 * 01075 * Structure stack for handling inline objects/levels 01076 * 01077 *******************************************************/ 01078 01079 01090 function pushStructure($table, $uid, $field = '', $config = array()) { 01091 $this->inlineStructure['stable'][] = array( 01092 'table' => $table, 01093 'uid' => $uid, 01094 'field' => $field, 01095 'config' => $config, 01096 ); 01097 $this->updateStructureNames(); 01098 } 01099 01100 01106 function popStructure() { 01107 if (count($this->inlineStructure['stable'])) { 01108 $popItem = array_pop($this->inlineStructure['stable']); 01109 $this->updateStructureNames(); 01110 } 01111 return $popItem; 01112 } 01113 01114 01123 function updateStructureNames() { 01124 $current = $this->getStructureLevel(-1); 01125 // if there are still more inline levels available 01126 if ($current !== false) { 01127 $lastItemName = $this->getStructureItemName($current); 01128 $this->inlineNames = array( 01129 'form' => $this->prependFormFieldNames.$lastItemName, 01130 'object' => $this->prependNaming.'['.$this->inlineFirstPid.']'.$this->getStructurePath(), 01131 ); 01132 // if there are no more inline levels available 01133 } else { 01134 $this->inlineNames = array(); 01135 } 01136 } 01137 01138 01145 function getStructureItemName($levelData) { 01146 if (is_array($levelData)) { 01147 $name = '['.$levelData['table'].']' . 01148 '['.$levelData['uid'].']' . 01149 (isset($levelData['field']) ? '['.$levelData['field'].']' : ''); 01150 } 01151 return $name; 01152 } 01153 01154 01163 function getStructureLevel($level) { 01164 $inlineStructureCount = count($this->inlineStructure['stable']); 01165 if ($level < 0) $level = $inlineStructureCount+$level; 01166 if ($level >= 0 && $level < $inlineStructureCount) 01167 return $this->inlineStructure['stable'][$level]; 01168 else 01169 return false; 01170 } 01171 01172 01180 function getStructurePath($structureDepth = -1) { 01181 $structureCount = count($this->inlineStructure['stable']); 01182 if ($structureDepth < 0 || $structureDepth > $structureCount) $structureDepth = $structureCount; 01183 01184 for ($i = 1; $i <= $structureDepth; $i++) { 01185 $current = $this->getStructureLevel(-$i); 01186 $string = $this->getStructureItemName($current).$string; 01187 } 01188 01189 return $string; 01190 } 01191 01192 01205 function parseStructureString($string, $loadConfig = false) { 01206 $unstable = array(); 01207 $vector = array('table', 'uid', 'field'); 01208 $pattern = '/^'.$this->prependNaming.'\[(.+?)\]\[(.+)\]$/'; 01209 if (preg_match($pattern, $string, $match)) { 01210 $this->inlineFirstPid = $match[1]; 01211 $parts = explode('][', $match[2]); 01212 $partsCnt = count($parts); 01213 for ($i = 0; $i < $partsCnt; $i++) { 01214 if ($i > 0 && $i % 3 == 0) { 01215 // load the TCA configuration of the table field and store it in the stack 01216 if ($loadConfig) { 01217 t3lib_div::loadTCA($unstable['table']); 01218 $unstable['config'] = $GLOBALS['TCA'][$unstable['table']]['columns'][$unstable['field']]['config']; 01219 // Fetch TSconfig: 01220 $TSconfig = $this->fObj->setTSconfig( 01221 $unstable['table'], 01222 array('uid' => $unstable['uid'], 'pid' => $this->inlineFirstPid), 01223 $unstable['field'] 01224 ); 01225 // Override TCA field config by TSconfig: 01226 if (!$TSconfig['disabled']) { 01227 $unstable['config'] = $this->fObj->overrideFieldConf($unstable['config'], $TSconfig); 01228 } 01229 } 01230 $this->inlineStructure['stable'][] = $unstable; 01231 $unstable = array(); 01232 } 01233 $unstable[$vector[$i % 3]] = $parts[$i]; 01234 } 01235 $this->updateStructureNames(); 01236 if (count($unstable)) $this->inlineStructure['unstable'] = $unstable; 01237 } 01238 } 01239 01240 01241 /******************************************************* 01242 * 01243 * Helper functions 01244 * 01245 *******************************************************/ 01246 01247 01254 function checkConfiguration(&$config) { 01255 $foreign_table = $config['foreign_table']; 01256 01257 // An inline field must have a foreign_table, if not, stop all further inline actions for this field: 01258 if (!$foreign_table || !is_array($GLOBALS['TCA'][$foreign_table])) { 01259 return false; 01260 } 01261 // Init appearance if not set: 01262 if (!is_array($config['appearance'])) { 01263 $config['appearance'] = array(); 01264 } 01265 // Set the position/appearance of the "Create new record" link: 01266 if ($config['foreign_selector'] && !$config['appearance']['useCombination']) { 01267 $config['appearance']['newRecordLinkPosition'] = 'none'; 01268 } elseif (!in_array($config['appearance']['newRecordLinkPosition'], array('top', 'bottom', 'both', 'none'))) { 01269 $config['appearance']['newRecordLinkPosition'] = 'top'; 01270 } 01271 01272 return true; 01273 } 01274 01275 01285 function checkAccess($cmd, $table, $theUid) { 01286 // Checking if the user has permissions? (Only working as a precaution, because the final permission check is always down in TCE. But it's good to notify the user on beforehand...) 01287 // First, resetting flags. 01288 $hasAccess = 0; 01289 $deniedAccessReason = ''; 01290 01291 // If the command is to create a NEW record...: 01292 if ($cmd=='new') { 01293 $calcPRec = t3lib_BEfunc::getRecord('pages',$this->inlineFirstPid); 01294 if(!is_array($calcPRec)) { 01295 return false; 01296 } 01297 $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms($calcPRec); // Permissions for the parent page 01298 if ($table=='pages') { // If pages: 01299 $hasAccess = $CALC_PERMS&8 ? 1 : 0; // Are we allowed to create new subpages? 01300 } else { 01301 $hasAccess = $CALC_PERMS&16 ? 1 : 0; // Are we allowed to edit content on this page? 01302 } 01303 } else { // Edit: 01304 $calcPRec = t3lib_BEfunc::getRecord($table,$theUid); 01305 t3lib_BEfunc::fixVersioningPid($table,$calcPRec); 01306 if (is_array($calcPRec)) { 01307 if ($table=='pages') { // If pages: 01308 $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms($calcPRec); 01309 $hasAccess = $CALC_PERMS&2 ? 1 : 0; 01310 } else { 01311 $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages',$calcPRec['pid'])); // Fetching pid-record first. 01312 $hasAccess = $CALC_PERMS&16 ? 1 : 0; 01313 } 01314 01315 // Check internals regarding access: 01316 if ($hasAccess) { 01317 $hasAccess = $GLOBALS['BE_USER']->recordEditAccessInternals($table, $calcPRec); 01318 } 01319 } 01320 } 01321 01322 if(!$GLOBALS['BE_USER']->check('tables_modify', $table)) { 01323 $hasAccess = 0; 01324 } 01325 01326 if(!$hasAccess) { 01327 $deniedAccessReason = $GLOBALS['BE_USER']->errorMsg; 01328 if($deniedAccessReason) { 01329 debug($deniedAccessReason); 01330 } 01331 } 01332 01333 return $hasAccess ? true : false; 01334 } 01335 01336 01345 function compareStructureConfiguration($compare) { 01346 $level = $this->getStructureLevel(-1); 01347 $result = $this->arrayCompareComplex($level, $compare); 01348 01349 return $result; 01350 } 01351 01352 01359 function normalizeUid($string) { 01360 $parts = explode('|', $string); 01361 return $parts[0]; 01362 } 01363 01364 01373 function wrapFormsSection($section, $styleAttrs = array(), $tableAttrs = array()) { 01374 if (!$styleAttrs['margin-right']) $styleAttrs['margin-right'] = $this->inlineStyles['margin-right'].'px'; 01375 01376 foreach ($styleAttrs as $key => $value) $style .= ($style?' ':'').$key.': '.htmlspecialchars($value).'; '; 01377 if ($style) $style = ' style="'.$style.'"'; 01378 01379 if (!$tableAttrs['background'] && $this->fObj->borderStyle[2]) $tableAttrs['background'] = $this->backPath.$this->borderStyle[2]; 01380 if (!$tableAttrs['cellspacing']) $tableAttrs['cellspacing'] = '0'; 01381 if (!$tableAttrs['cellpadding']) $tableAttrs['cellpadding'] = '0'; 01382 if (!$tableAttrs['border']) $tableAttrs['border'] = '0'; 01383 if (!$tableAttrs['width']) $tableAttrs['width'] = '100%'; 01384 if (!$tableAttrs['class'] && $this->borderStyle[3]) $tableAttrs['class'] = $this->borderStyle[3]; 01385 01386 foreach ($tableAttrs as $key => $value) $table .= ($table?' ':'').$key.'="'.htmlspecialchars($value).'"'; 01387 01388 $out = '<table '.$table.$style.'>'.$section.'</table>'; 01389 return $out; 01390 } 01391 01392 01402 function isInlineChildAndLabelField($table, $field) { 01403 $level = $this->getStructureLevel(-1); 01404 if ($level['config']['foreign_label']) 01405 $label = $level['config']['foreign_label']; 01406 else 01407 $label = $GLOBALS['TCA'][$table]['ctrl']['label']; 01408 return $level['config']['foreign_table'] === $table && $label == $field ? true : false; 01409 } 01410 01411 01418 function getStructureDepth() { 01419 return count($this->inlineStructure['stable']); 01420 } 01421 01422 01455 function arrayCompareComplex($subjectArray, $searchArray, $type = '') { 01456 $localMatches = 0; 01457 $localEntries = 0; 01458 01459 if (is_array($searchArray) && count($searchArray)) { 01460 // if no type was passed, try to determine 01461 if (!$type) { 01462 reset($searchArray); 01463 $type = key($searchArray); 01464 $searchArray = current($searchArray); 01465 } 01466 01467 // we use '%AND' and '%OR' in uppercase 01468 $type = strtoupper($type); 01469 01470 // split regular elements from sub elements 01471 foreach ($searchArray as $key => $value) { 01472 $localEntries++; 01473 01474 // process a sub-group of OR-conditions 01475 if ($key == '%OR') { 01476 $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0; 01477 // process a sub-group of AND-conditions 01478 } elseif ($key == '%AND') { 01479 $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0; 01480 // a part of an associative array should be compared, so step down in the array hierarchy 01481 } elseif (is_array($value) && $this->isAssociativeArray($searchArray)) { 01482 $localMatches += $this->arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0; 01483 // it is a normal array that is only used for grouping and indexing 01484 } elseif (is_array($value)) { 01485 $localMatches += $this->arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0; 01486 // directly compare a value 01487 } else { 01488 $localMatches += isset($subjectArray[$key]) && isset($value) && $subjectArray[$key] === $value ? 1 : 0; 01489 } 01490 01491 // if one or more matches are required ('OR'), return true after the first successful match 01492 if ($type == '%OR' && $localMatches > 0) return true; 01493 // if all matches are required ('AND') and we have no result after the first run, return false 01494 if ($type == '%AND' && $localMatches == 0) return false; 01495 } 01496 } 01497 01498 // return the result for '%AND' (if nothing was checked, true is returned) 01499 return $localEntries == $localMatches ? true : false; 01500 } 01501 01502 01509 function isAssociativeArray($object) { 01510 return is_array($object) && count($object) && (array_keys($object) !== range(0, sizeof($object) - 1)) 01511 ? true 01512 : false; 01513 } 01514 01515 01524 function removeFromArray($needle, $haystack, $strict=null) { 01525 $pos = array_search($needle, $haystack, $strict); 01526 if ($pos !== false) unset($haystack[$pos]); 01527 return $haystack; 01528 } 01529 01530 01539 function getPossibleRecordsFlat($possibleRecords) { 01540 $flat = false; 01541 if (is_array($possibleRecords)) { 01542 $flat = array(); 01543 foreach ($possibleRecords as $record) $flat[$record[1]] = $record[0]; 01544 } 01545 return $flat; 01546 } 01547 01548 01555 function getPossibleRecordsSelectorConfig($conf, $field = '') { 01556 $foreign_table = $conf['foreign_table']; 01557 $foreign_selector = $conf['foreign_selector']; 01558 01559 $PA = false; 01560 $type = false; 01561 $table = false; 01562 $selector = false; 01563 01564 if ($field) { 01565 $PA = array(); 01566 $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$field]; 01567 $PA['fieldConf']['config']['form_type'] = $PA['fieldConf']['config']['form_type'] ? $PA['fieldConf']['config']['form_type'] : $PA['fieldConf']['config']['type']; // Using "form_type" locally in this script 01568 $PA['fieldTSConfig'] = $this->fObj->setTSconfig($foreign_table,array(),$field); 01569 $config = $PA['fieldConf']['config']; 01570 // Determine type of Selector: 01571 $type = $this->getPossibleRecordsSelectorType($config); 01572 // Return table on this level: 01573 $table = $type == 'select' ? $config['foreign_table'] : $config['allowed']; 01574 // Return type of the selector if foreign_selector is defined and points to the same field as in $field: 01575 if ($foreign_selector && $foreign_selector == $field && $type) { 01576 $selector = $type; 01577 } 01578 } 01579 01580 return array( 01581 'PA' => $PA, 01582 'type' => $type, 01583 'table' => $table, 01584 'selector' => $selector, 01585 ); 01586 } 01587 01588 01595 function getPossibleRecordsSelectorType($config) { 01596 $type = false; 01597 if ($config['type'] == 'select') { 01598 $type = 'select'; 01599 } elseif ($config['type'] == 'group' && $config['internal_type'] == 'db') { 01600 $type = 'groupdb'; 01601 } 01602 return $type; 01603 } 01604 01605 01616 function skipField($table, $field, $row, $config) { 01617 $skipThisField = false; 01618 01619 if ($this->getStructureDepth()) { 01620 $searchArray = array( 01621 '%OR' => array( 01622 'config' => array( 01623 0 => array( 01624 '%AND' => array( 01625 'foreign_table' => $table, 01626 '%OR' => array( 01627 '%AND' => array( 01628 'appearance' => array('useCombination' => 1), 01629 'foreign_selector' => $field, 01630 ), 01631 'MM' => $config['MM'] 01632 ), 01633 ), 01634 ), 01635 1 => array( 01636 '%AND' => array( 01637 'foreign_table' => $config['foreign_table'], 01638 'foreign_selector' => $config['foreign_field'], 01639 ), 01640 ), 01641 ), 01642 ), 01643 ); 01644 01645 // get the parent record from structure stack 01646 $level = $this->getStructureLevel(-1); 01647 01648 // If we have symmetric fields, check on which side we are and hide fields, that are set automatically: 01649 if (t3lib_loadDBGroup::isOnSymmetricSide($level['uid'], $level['config'], $row)) { 01650 $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $field; 01651 $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $field; 01652 // Hide fields, that are set automatically: 01653 } else { 01654 $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $field; 01655 $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $field; 01656 } 01657 01658 $skipThisField = $this->compareStructureConfiguration($searchArray, true); 01659 } 01660 01661 return $skipThisField; 01662 } 01663 01664 01672 function getJSON($jsonArray) { 01673 if (!$GLOBALS['JSON']) { 01674 require_once(PATH_typo3.'contrib/json.php'); 01675 $GLOBALS['JSON'] = t3lib_div::makeInstance('Services_JSON'); 01676 } 01677 return $GLOBALS['JSON']->encode($jsonArray); 01678 } 01679 01680 01688 function getExpandedCollapsedState($table, $uid) { 01689 if (is_array($this->inlineView) && is_array($this->inlineView[$table])) { 01690 if (in_array($uid, $this->inlineView[$table]) !== false) return true; 01691 } 01692 return false; 01693 } 01694 01695 01703 function updateInlineView(&$uc, &$tce) { 01704 if (is_array($uc) && $uc['inlineView']) { 01705 $inlineView = unserialize($GLOBALS['BE_USER']->uc['inlineView']); 01706 01707 foreach ($uc['inlineView'] as $topTable => $topRecords) { 01708 foreach ($topRecords as $topUid => $childElements) { 01709 foreach ($childElements as $childTable => $childRecords) { 01710 $uids = array_keys($tce->substNEWwithIDs_table, $childTable); 01711 if (count($uids)) { 01712 foreach ($childRecords as $childUid => $state) { 01713 if ($state && in_array($childUid, $uids)) { 01714 $newChildUid = $tce->substNEWwithIDs[$childUid]; 01715 $inlineView[$topTable][$topUid][$childTable][$newChildUid] = 1; 01716 } 01717 } 01718 } 01719 } 01720 } 01721 } 01722 01723 $GLOBALS['BE_USER']->uc['inlineView'] = serialize($inlineView); 01724 $GLOBALS['BE_USER']->writeUC(); 01725 } 01726 } 01727 01728 01734 function getLevelMargin() { 01735 $margin = $this->inlineStyles['margin-right']; 01736 return $margin; 01737 } 01738 } 01739 ?>