| [ Index ] |
|
Code source de Typo3 4.1.3 |
1 <?php 2 /*************************************************************** 3 * Copyright notice 4 * 5 * (c) 2006 Oliver Hader <oh@inpublica.de> 6 * All rights reserved 7 * 8 * This script is part of the TYPO3 project. The TYPO3 project is 9 * free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * The GNU General Public License can be found at 15 * http://www.gnu.org/copyleft/gpl.html. 16 * A copy is found in the textfile GPL.txt and important notices to the license 17 * from the author is found in LICENSE.txt distributed with these scripts. 18 * 19 * 20 * This script is distributed in the hope that it will be useful, 21 * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 * GNU General Public License for more details. 24 * 25 * This copyright notice MUST APPEAR in all copies of the script! 26 ***************************************************************/ 27 /** 28 * The Inline-Relational-Record-Editing (IRRE) functions as part of the TCEforms. 29 * 30 * $Id: class.t3lib_tceforms_inline.php 2482 2007-09-03 08:48:42Z ohader $ 31 * 32 * @author Oliver Hader <oh@inpublica.de> 33 */ 34 /** 35 * [CLASS/FUNCTION INDEX of SCRIPT] 36 * 37 * 38 * 39 * 88: class t3lib_TCEforms_inline 40 * 109: function init(&$tceForms) 41 * 127: function getSingleField_typeInline($table,$field,$row,&$PA) 42 * 43 * SECTION: Regular rendering of forms, fields, etc. 44 * 263: function renderForeignRecord($parentUid, $rec, $config = array()) 45 * 319: function renderForeignRecordHeader($parentUid, $foreign_table,$rec,$config = array()) 46 * 375: function renderForeignRecordHeaderControl($table,$row,$config = array()) 47 * 506: function renderCombinationTable(&$rec, $appendFormFieldNames, $config = array()) 48 * 560: function renderPossibleRecordsSelector($selItems, $conf, $uniqueIds=array()) 49 * 627: function addJavaScript() 50 * 643: function addJavaScriptSortable($objectId) 51 * 52 * SECTION: Handling of AJAX calls 53 * 665: function createNewRecord($domObjectId, $foreignUid = 0) 54 * 755: function getJSON($jsonArray) 55 * 770: function getNewRecordLink($objectPrefix, $conf = array()) 56 * 57 * SECTION: Get data from database and handle relations 58 * 807: function getRelatedRecords($table,$field,$row,&$PA,$config) 59 * 839: function getPossibleRecords($table,$field,$row,$conf,$checkForConfField='foreign_selector') 60 * 885: function getUniqueIds($records, $conf=array()) 61 * 905: function getRecord($pid, $table, $uid, $cmd='') 62 * 929: function getNewRecord($pid, $table) 63 * 64 * SECTION: Structure stack for handling inline objects/levels 65 * 951: function pushStructure($table, $uid, $field = '', $config = array()) 66 * 967: function popStructure() 67 * 984: function updateStructureNames() 68 * 1000: function getStructureItemName($levelData) 69 * 1015: function getStructureLevel($level) 70 * 1032: function getStructurePath($structureDepth = -1) 71 * 1057: function parseStructureString($string, $loadConfig = false) 72 * 73 * SECTION: Helper functions 74 * 1098: function checkConfiguration(&$config) 75 * 1123: function checkAccess($cmd, $table, $theUid) 76 * 1185: function compareStructureConfiguration($compare) 77 * 1199: function normalizeUid($string) 78 * 1213: function wrapFormsSection($section, $styleAttrs = array(), $tableAttrs = array()) 79 * 1242: function isInlineChildAndLabelField($table, $field) 80 * 1258: function getStructureDepth() 81 * 1295: function arrayCompareComplex($subjectArray, $searchArray, $type = '') 82 * 1349: function isAssociativeArray($object) 83 * 1364: function getPossibleRecordsFlat($possibleRecords) 84 * 1383: function skipField($table, $field, $row, $config) 85 * 86 * TOTAL FUNCTIONS: 35 87 * (This index is automatically created/updated by the extension "extdeveval") 88 * 89 */ 90 class t3lib_TCEforms_inline { 91 var $fObj; // Reference to the calling TCEforms instance 92 var $backPath; // Reference to $fObj->backPath 93 94 var $isAjaxCall = false; // Indicates if a field is rendered upon an AJAX call 95 var $inlineStructure = array(); // the structure/hierarchy where working in, e.g. cascading inline tables 96 var $inlineFirstPid; // the first call of an inline type appeared on this page (pid of record) 97 var $inlineNames = array(); // keys: form, object -> hold the name/id for each of them 98 var $inlineData = array(); // inline data array used for JSON output 99 var $inlineView = array(); // expanded/collapsed states for the current BE user 100 var $inlineCount = 0; // count the number of inline types used 101 var $inlineStyles = array(); 102 103 var $prependNaming = 'data'; // how the $this->fObj->prependFormFieldNames should be set ('data' is default) 104 var $prependFormFieldNames; // reference to $this->fObj->prependFormFieldNames 105 var $prependCmdFieldNames; // reference to $this->fObj->prependCmdFieldNames 106 107 108 /** 109 * Intialize an instance of t3lib_TCEforms_inline 110 * 111 * @param object $tceForms: Reference to an TCEforms instance 112 * @return void 113 */ 114 function init(&$tceForms) { 115 $this->fObj =& $tceForms; 116 $this->backPath =& $tceForms->backPath; 117 $this->prependFormFieldNames =& $this->fObj->prependFormFieldNames; 118 $this->prependCmdFieldNames =& $this->fObj->prependCmdFieldNames; 119 $this->inlineStyles['margin-right'] = '5'; 120 } 121 122 123 /** 124 * Generation of TCEform elements of the type "inline" 125 * This will render inline-relational-record sets. Relations. 126 * 127 * @param string $table: The table name of the record 128 * @param string $field: The field name which this element is supposed to edit 129 * @param array $row: The record data array where the value(s) for the field can be found 130 * @param array $PA: An array with additional configuration options. 131 * @return string The HTML code for the TCEform field 132 */ 133 function getSingleField_typeInline($table,$field,$row,&$PA) { 134 // check the TCA configuration - if false is returned, something was wrong 135 if ($this->checkConfiguration($PA['fieldConf']['config']) === false) return false; 136 137 // count the number of processed inline elements 138 $this->inlineCount++; 139 140 // Init: 141 $config = $PA['fieldConf']['config']; 142 $foreign_table = $config['foreign_table']; 143 t3lib_div::loadTCA($foreign_table); 144 145 $minitems = t3lib_div::intInRange($config['minitems'],0); 146 $maxitems = t3lib_div::intInRange($config['maxitems'],0); 147 if (!$maxitems) $maxitems=100000; 148 149 // Register the required number of elements: 150 $this->fObj->requiredElements[$PA['itemFormElName']] = array($minitems,$maxitems,'imgName'=>$table.'_'.$row['uid'].'_'.$field); 151 152 // remember the page id (pid of record) where inline editing started first 153 // we need that pid for ajax calls, so that they would know where the action takes place on the page structure 154 if (!isset($this->inlineFirstPid)) { 155 // if this record is not new, try to fetch the inlineView states 156 // @TODO: Add checking/cleaning for unused tables, records, etc. to save space in uc-field 157 if (t3lib_div::testInt($row['uid'])) { 158 $inlineView = unserialize($GLOBALS['BE_USER']->uc['inlineView']); 159 $this->inlineView = $inlineView[$table][$row['uid']]; 160 } 161 // If the parent is a page, use the uid(!) of the (new?) page as pid for the child records: 162 if ($table == 'pages') { 163 $this->inlineFirstPid = $row['uid']; 164 // If pid is negative, fetch the previous record and take its pid: 165 } elseif ($row['pid'] < 0) { 166 $prevRec = t3lib_BEfunc::getRecord($table, abs($row['pid'])); 167 $this->inlineFirstPid = $prevRec['pid']; 168 // Take the pid as it is: 169 } else { 170 $this->inlineFirstPid = $row['pid']; 171 } 172 } 173 // add the current inline job to the structure stack 174 $this->pushStructure($table, $row['uid'], $field, $config); 175 // e.g. inline[<table>][<uid>][<field>] 176 $nameForm = $this->inlineNames['form']; 177 // e.g. inline[<pid>][<table1>][<uid1>][<field1>][<table2>][<uid2>][<field2>] 178 $nameObject = $this->inlineNames['object']; 179 // get the records related to this inline record 180 $recordList = $this->getRelatedRecords($table,$field,$row,$PA,$config); 181 // set the first and last record to the config array 182 $config['inline']['first'] = $recordList[0]['uid']; 183 $config['inline']['last'] = $recordList[count($recordList)-1]['uid']; 184 185 // Tell the browser what we have (using JSON later): 186 $top = $this->getStructureLevel(0); 187 $this->inlineData['config'][$nameObject] = array('table' => $foreign_table); 188 $this->inlineData['config'][$nameObject.'['.$foreign_table.']'] = array( 189 'min' => $minitems, 190 'max' => $maxitems, 191 'sortable' => $config['appearance']['useSortable'], 192 'top' => array( 193 'table' => $top['table'], 194 'uid' => $top['uid'], 195 ), 196 ); 197 // Set a hint for nested IRRE and tab elements: 198 $this->inlineData['nested'][$nameObject] = $this->fObj->getDynNestedStack(false, $this->isAjaxCall); 199 200 // if relations are required to be unique, get the uids that have already been used on the foreign side of the relation 201 if ($config['foreign_unique']) { 202 // If uniqueness *and* selector are set, they should point to the same field - so, get the configuration of one: 203 $selConfig = $this->getPossibleRecordsSelectorConfig($config, $config['foreign_unique']); 204 // Get the used unique ids: 205 $uniqueIds = $this->getUniqueIds($recordList, $config, $selConfig['type']=='groupdb'); 206 $possibleRecords = $this->getPossibleRecords($table,$field,$row,$config,'foreign_unique'); 207 $uniqueMax = $config['appearance']['useCombination'] || $possibleRecords === false ? -1 : count($possibleRecords); 208 $this->inlineData['unique'][$nameObject.'['.$foreign_table.']'] = array( 209 'max' => $uniqueMax, 210 'used' => $uniqueIds, 211 'type' => $selConfig['type'], 212 'table' => $config['foreign_table'], 213 'elTable' => $selConfig['table'], // element/record table (one step down in hierarchy) 214 'field' => $config['foreign_unique'], 215 'selector' => $selConfig['selector'], 216 'possible' => $this->getPossibleRecordsFlat($possibleRecords), 217 ); 218 } 219 220 // if it's required to select from possible child records (reusable children), add a selector box 221 if ($config['foreign_selector']) { 222 // if not already set by the foreign_unique, set the possibleRecords here and the uniqueIds to an empty array 223 if (!$config['foreign_unique']) { 224 $possibleRecords = $this->getPossibleRecords($table,$field,$row,$config); 225 $uniqueIds = array(); 226 } 227 $selectorBox = $this->renderPossibleRecordsSelector($possibleRecords,$config,$uniqueIds); 228 $item .= $selectorBox; 229 } 230 231 // wrap all inline fields of a record with a <div> (like a container) 232 $item .= '<div id="'.$nameObject.'">'; 233 234 // define how to show the "Create new record" link - if there are more than maxitems, hide it 235 if (count($recordList) >= $maxitems || ($uniqueMax > 0 && count($recordList) >= $uniqueMax)) { 236 $config['inline']['inlineNewButtonStyle'] = 'display: none;'; 237 } 238 // add the "Create new record" link before all child records 239 if (in_array($config['appearance']['newRecordLinkPosition'], array('both', 'top'))) { 240 $item .= $this->getNewRecordLink($nameObject.'['.$foreign_table.']', $config); 241 } 242 243 $item .= '<div id="'.$nameObject.'_records">'; 244 $relationList = array(); 245 if (count($recordList)) { 246 foreach ($recordList as $rec) { 247 $item .= $this->renderForeignRecord($row['uid'],$rec,$config); 248 $relationList[] = $rec['uid']; 249 } 250 } 251 $item .= '</div>'; 252 253 // add the "Create new record" link after all child records 254 if (in_array($config['appearance']['newRecordLinkPosition'], array('both', 'bottom'))) { 255 $item .= $this->getNewRecordLink($nameObject.'['.$foreign_table.']', $config); 256 } 257 258 // add Drag&Drop functions for sorting to TCEforms::$additionalJS_post 259 if (count($relationList) > 1 && $config['appearance']['useSortable']) 260 $this->addJavaScriptSortable($nameObject.'_records'); 261 // publish the uids of the child records in the given order to the browser 262 $item .= '<input type="hidden" name="'.$nameForm.'" value="'.implode(',', $relationList).'" class="inlineRecord" />'; 263 // close the wrap for all inline fields (container) 264 $item .= '</div>'; 265 266 // on finishing this section, remove the last item from the structure stack 267 $this->popStructure(); 268 269 // if this was the first call to the inline type, restore the values 270 if (!$this->getStructureDepth()) { 271 unset($this->inlineFirstPid); 272 } 273 274 return $item; 275 } 276 277 278 /******************************************************* 279 * 280 * Regular rendering of forms, fields, etc. 281 * 282 *******************************************************/ 283 284 285 /** 286 * Render the form-fields of a related (foreign) record. 287 * 288 * @param string $parentUid: The uid of the parent (embedding) record (uid or NEW...) 289 * @param array $rec: The table record of the child/embedded table (normaly post-processed by t3lib_transferData) 290 * @param array $config: content of $PA['fieldConf']['config'] 291 * @return string The HTML code for this "foreign record" 292 */ 293 function renderForeignRecord($parentUid, $rec, $config = array()) { 294 $foreign_table = $config['foreign_table']; 295 $foreign_field = $config['foreign_field']; 296 $foreign_selector = $config['foreign_selector']; 297 298 // Send a mapping information to the browser via JSON: 299 // e.g. data[<curTable>][<curId>][<curField>] => data[<pid>][<parentTable>][<parentId>][<parentField>][<curTable>][<curId>][<curField>] 300 $this->inlineData['map'][$this->inlineNames['form']] = $this->inlineNames['object']; 301 302 // Set this variable if we handle a brand new unsaved record: 303 $isNewRecord = t3lib_div::testInt($rec['uid']) ? false : true; 304 // If there is a selector field, normalize it: 305 if ($foreign_selector) { 306 $rec[$foreign_selector] = $this->normalizeUid($rec[$foreign_selector]); 307 } 308 309 $hasAccess = $this->checkAccess($isNewRecord?'new':'edit', $foreign_table, $rec['uid']); 310 311 if(!$hasAccess) return false; 312 313 // Get the current naming scheme for DOM name/id attributes: 314 $nameObject = $this->inlineNames['object']; 315 $appendFormFieldNames = '['.$foreign_table.']['.$rec['uid'].']'; 316 $formFieldNames = $nameObject.$appendFormFieldNames; 317 // Put the current level also to the dynNestedStack of TCEforms: 318 $this->fObj->pushToDynNestedStack('inline', $this->inlineNames['object'].$appendFormFieldNames); 319 320 $header = $this->renderForeignRecordHeader($parentUid, $foreign_table, $rec, $config); 321 $combination = $this->renderCombinationTable($rec, $appendFormFieldNames, $config); 322 $fields = $this->fObj->getMainFields($foreign_table,$rec); 323 $fields = $this->wrapFormsSection($fields); 324 325 if ($isNewRecord) { 326 // show this record expanded or collapsed 327 $isExpanded = is_array($config['appearance']) && $config['appearance']['collapseAll'] ? 1 : 0; 328 // get the top parent table 329 $top = $this->getStructureLevel(0); 330 $ucFieldName = 'uc['.$top['table'].']['.$top['uid'].']'.$appendFormFieldNames; 331 // set additional fields for processing for saving 332 $fields .= '<input type="hidden" name="'.$this->prependFormFieldNames.$appendFormFieldNames.'[pid]" value="'.$rec['pid'].'"/>'; 333 $fields .= '<input type="hidden" name="'.$ucFieldName.'" value="'.$isExpanded.'" />'; 334 335 } else { 336 // show this record expanded or collapsed 337 $isExpanded = $this->getExpandedCollapsedState($foreign_table, $rec['uid']); 338 // set additional field for processing for saving 339 $fields .= '<input type="hidden" name="'.$this->prependCmdFieldNames.$appendFormFieldNames.'[delete]" value="1" disabled="disabled" />'; 340 } 341 342 // if this record should be shown collapsed 343 if (!$isExpanded) $appearanceStyleFields = ' style="display: none;"'; 344 345 // set the record container with data for output 346 $out = '<div id="'.$formFieldNames.'_header">'.$header.'</div>'; 347 $out .= '<div id="'.$formFieldNames.'_fields"'.$appearanceStyleFields.'>'.$fields.$combination.'</div>'; 348 // wrap the header, fields and combination part of a child record with a div container 349 $out = '<div id="'.$formFieldNames.'_div" class="inlineDiv'.($isNewRecord ? ' inlineIsNewRecord' : '').'">' . $out . '</div>'; 350 351 // Remove the current level also from the dynNestedStack of TCEforms: 352 $this->fObj->popFromDynNestedStack(); 353 354 return $out; 355 } 356 357 358 /** 359 * Renders the HTML header for a foreign record, such as the title, toggle-function, drag'n'drop, etc. 360 * Later on the command-icons are inserted here. 361 * 362 * @param string $parentUid: The uid of the parent (embedding) record (uid or NEW...) 363 * @param string $foreign_table: The foreign_table we create a header for 364 * @param array $rec: The current record of that foreign_table 365 * @param array $config: content of $PA['fieldConf']['config'] 366 * @return string The HTML code of the header 367 */ 368 function renderForeignRecordHeader($parentUid, $foreign_table, $rec, $config = array()) { 369 // Init: 370 $formFieldNames = $this->inlineNames['object'].'['.$foreign_table.']['.$rec['uid'].']'; 371 $expandSingle = $config['appearance']['expandSingle'] ? 1 : 0; 372 $onClick = "return inline.expandCollapseRecord('".htmlspecialchars($formFieldNames)."', $expandSingle)"; 373 374 // Pre-Processing: 375 $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $config, $rec); 376 $hasForeignLabel = !$isOnSymmetricSide && $config['foreign_label'] ? true : false; 377 $hasSymmetricLabel = $isOnSymmetricSide && $config['symmetric_label'] ? true : false; 378 // Get the record title/label for a record: 379 // render using a self-defined user function 380 if ($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc']) { 381 $params = array( 382 'table' => $foreign_table, 383 'row' => $rec, 384 'title' => '', 385 'isOnSymmetricSide' => $isOnSymmetricSide 386 ); 387 $null = null; // callUserFunction requires a third parameter, but we don't want to give $this as reference! 388 t3lib_div::callUserFunction($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc'], $params, $null); 389 $recTitle = $params['title']; 390 // render the special alternative title 391 } elseif ($hasForeignLabel || $hasSymmetricLabel) { 392 $titleCol = $hasForeignLabel ? $config['foreign_label'] : $config['symmetric_label']; 393 $foreignConfig = $this->getPossibleRecordsSelectorConfig($config, $titleCol); 394 // Render title for everything else than group/db: 395 if ($foreignConfig['type'] != 'groupdb') { 396 $recTitle = t3lib_BEfunc::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol], 0, 0, false); 397 // Render title for group/db: 398 } else { 399 // $recTitle could be something like: "tx_table_123|...", 400 $valueParts = t3lib_div::trimExplode('|', $rec[$titleCol]); 401 $itemParts = t3lib_div::revExplode('_', $valueParts[0], 2); 402 $recTemp = t3lib_befunc::getRecordWSOL($itemParts[0], $itemParts[1]); 403 $recTitle = t3lib_BEfunc::getRecordTitle($itemParts[0], $recTemp, true); 404 } 405 $recTitle = t3lib_BEfunc::getRecordTitlePrep($recTitle); 406 if (!strcmp(trim($recTitle),'')) { 407 $recTitle = t3lib_BEfunc::getNoRecordTitle(true); 408 } 409 // render the standard 410 } else { 411 $recTitle = t3lib_BEfunc::getRecordTitle($foreign_table, $rec, true); 412 } 413 414 $altText = t3lib_BEfunc::getRecordIconAltText($rec, $foreign_table); 415 $iconImg = 416 '<a href="#" onclick="'.htmlspecialchars($onClick).'">'.t3lib_iconWorks::getIconImage( 417 $foreign_table, $rec, $this->backPath, 418 'title="'.htmlspecialchars($altText).'" class="absmiddle"' 419 ).'</a>'; 420 421 $label = 422 '<a href="#" onclick="'.htmlspecialchars($onClick).'" style="display: block;">'. 423 '<span id="'.$formFieldNames.'_label">'.$recTitle.'</span>'. 424 '</a>'; 425 426 $ctrl = $this->renderForeignRecordHeaderControl($parentUid, $foreign_table, $rec, $config); 427 428 // @TODO: Check the table wrapping and the CSS definitions 429 $header = 430 '<table cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-right: '.$this->inlineStyles['margin-right'].'px;"'. 431 ($this->fObj->borderStyle[2] ? ' background="'.htmlspecialchars($this->backPath.$this->fObj->borderStyle[2]).'"':''). 432 ($this->fObj->borderStyle[3] ? ' class="'.htmlspecialchars($this->fObj->borderStyle[3]).'"':'').'>' . 433 '<tr class="class-main12"><td width="18">'.$iconImg.'</td><td align="left"><b>'.$label.'</b></td><td align="right">'.$ctrl.'</td></tr></table>'; 434 435 return $header; 436 } 437 438 439 /** 440 * Render the control-icons for a record header (create new, sorting, delete, disable/enable). 441 * Most of the parts are copy&paste from class.db_list_extra.inc and modified for the JavaScript calls here 442 * 443 * @param string $parentUid: The uid of the parent (embedding) record (uid or NEW...) 444 * @param string $foreign_table: The table (foreign_table) we create control-icons for 445 * @param array $rec: The current record of that foreign_table 446 * @param array $config: (modified) TCA configuration of the field 447 * @return string The HTML code with the control-icons 448 */ 449 function renderForeignRecordHeaderControl($parentUid, $foreign_table, $rec, $config = array()) { 450 // Initialize: 451 $cells=array(); 452 $isNewItem = substr($rec['uid'], 0, 3) == 'NEW'; 453 454 $tcaTableCtrl =& $GLOBALS['TCA'][$foreign_table]['ctrl']; 455 $tcaTableCols =& $GLOBALS['TCA'][$foreign_table]['columns']; 456 457 $isPagesTable = $foreign_table == 'pages' ? true : false; 458 $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $config, $rec); 459 $enableManualSorting = $tcaTableCtrl['sortby'] || $config['MM'] || (!$isOnSymmetricSide && $config['foreign_sortby']) || ($isOnSymmetricSide && $config['symmetric_sortby']) ? true : false; 460 461 $nameObjectFt = $this->inlineNames['object'].'['.$foreign_table.']'; 462 $nameObjectFtId = $nameObjectFt.'['.$rec['uid'].']'; 463 464 $calcPerms = $GLOBALS['BE_USER']->calcPerms( 465 t3lib_BEfunc::readPageAccess($rec['pid'], $GLOBALS['BE_USER']->getPagePermsClause(1)) 466 ); 467 468 // If the listed table is 'pages' we have to request the permission settings for each page: 469 if ($isPagesTable) { 470 $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages',$rec['uid'])); 471 } 472 473 // This expresses the edit permissions for this particular element: 474 $permsEdit = ($isPagesTable && ($localCalcPerms&2)) || (!$isPagesTable && ($calcPerms&16)); 475 476 // "Info": (All records) 477 if (!$isNewItem) 478 $cells[]='<a href="#" onclick="'.htmlspecialchars('top.launchView(\''.$foreign_table.'\', \''.$rec['uid'].'\'); return false;').'">'. 479 '<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="" />'. 480 '</a>'; 481 482 // If the table is NOT a read-only table, then show these links: 483 if (!$tcaTableCtrl['readOnly']) { 484 485 // "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): 486 if ($enableManualSorting || $tcaTableCtrl['useColumnsForDefaultValues']) { 487 if ( 488 (!$isPagesTable && ($calcPerms&16)) || // For NON-pages, must have permission to edit content on this parent page 489 ($isPagesTable && ($calcPerms&8)) // For pages, must have permission to create new pages here. 490 ) { 491 $onClick = "return inline.createNewRecord('".$nameObjectFt."','".$rec['uid']."')"; 492 if ($config['inline']['inlineNewButtonStyle']) { 493 $style = ' style="'.$config['inline']['inlineNewButtonStyle'].'"'; 494 } 495 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'" class="inlineNewButton"'.$style.'>'. 496 '<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="" />'. 497 '</a>'; 498 } 499 } 500 501 // Drag&Drop Sorting: Sortable handler for script.aculo.us 502 if ($permsEdit && $enableManualSorting && $config['appearance']['useSortable']) { 503 $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" />'; 504 } 505 506 // "Up/Down" links 507 if ($permsEdit && $enableManualSorting) { 508 $onClick = "return inline.changeSorting('".$nameObjectFtId."', '1')"; // Up 509 $style = $config['inline']['first'] == $rec['uid'] ? 'style="visibility: hidden;"' : ''; 510 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'" class="sortingUp" '.$style.'>'. 511 '<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="" />'. 512 '</a>'; 513 514 $onClick = "return inline.changeSorting('".$nameObjectFtId."', '-1')"; // Down 515 $style = $config['inline']['last'] == $rec['uid'] ? 'style="visibility: hidden;"' : ''; 516 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'" class="sortingDown" '.$style.'>'. 517 '<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="" />'. 518 '</a>'; 519 } 520 521 // "Hide/Unhide" links: 522 $hiddenField = $tcaTableCtrl['enablecolumns']['disabled']; 523 if ($permsEdit && $hiddenField && $tcaTableCols[$hiddenField] && (!$tcaTableCols[$hiddenField]['exclude'] || $GLOBALS['BE_USER']->check('non_exclude_fields',$foreign_table.':'.$hiddenField))) { 524 $onClick = "return inline.enableDisableRecord('".$nameObjectFtId."')"; 525 if ($rec[$hiddenField]) { 526 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'">'. 527 '<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" />'. 528 '</a>'; 529 } else { 530 $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'">'. 531 '<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" />'. 532 '</a>'; 533 } 534 } 535 536 // "Delete" link: 537 if ( 538 ($isPagesTable && ($localCalcPerms&4)) || (!$isPagesTable && ($calcPerms&16)) 539 ) { 540 $onClick = "inline.deleteRecord('".$nameObjectFtId."');"; 541 $cells[]='<a href="#" onclick="'.htmlspecialchars('if (confirm('.$GLOBALS['LANG']->JScharCode($GLOBALS['LANG']->getLL('deleteWarning')).')) { '.$onClick.' } return false;').'">'. 542 '<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="" />'. 543 '</a>'; 544 } 545 } 546 547 // If the record is edit-locked by another user, we will show a little warning sign: 548 if ($lockInfo=t3lib_BEfunc::isRecordLocked($foreign_table,$rec['uid'])) { 549 $cells[]='<a href="#" onclick="'.htmlspecialchars('alert('.$GLOBALS['LANG']->JScharCode($lockInfo['msg']).');return false;').'">'. 550 '<img'.t3lib_iconWorks::skinImg('','gfx/recordlock_warning3.gif','width="17" height="12"').' title="'.htmlspecialchars($lockInfo['msg']).'" alt="" />'. 551 '</a>'; 552 } 553 554 // Compile items into a DIV-element: 555 return ' 556 <!-- CONTROL PANEL: '.$foreign_table.':'.$rec['uid'].' --> 557 <div class="typo3-DBctrl">'.implode('',$cells).'</div>'; 558 } 559 560 561 /** 562 * Render a table with TCEforms, that occurs on a intermediate table but should be editable directly, 563 * so two tables are combined (the intermediate table with attributes and the sub-embedded table). 564 * -> This is a direct embedding over two levels! 565 * 566 * @param array $rec: The table record of the child/embedded table (normaly post-processed by t3lib_transferData) 567 * @param string $appendFormFieldNames: The [<table>][<uid>] of the parent record (the intermediate table) 568 * @param array $config: content of $PA['fieldConf']['config'] 569 * @return string A HTML string with <table> tag around. 570 */ 571 function renderCombinationTable(&$rec, $appendFormFieldNames, $config = array()) { 572 $foreign_table = $config['foreign_table']; 573 $foreign_selector = $config['foreign_selector']; 574 575 if ($foreign_selector && $config['appearance']['useCombination']) { 576 $comboConfig = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector]['config']; 577 $comboRecord = array(); 578 579 // record does already exist, so load it 580 if (t3lib_div::testInt($rec[$foreign_selector])) { 581 $comboRecord = $this->getRecord( 582 $this->inlineFirstPid, 583 $comboConfig['foreign_table'], 584 $rec[$foreign_selector] 585 ); 586 $isNewRecord = false; 587 // it's a new record, so get some default data 588 } else { 589 $comboRecord = $this->getNewRecord( 590 $this->inlineFirstPid, 591 $comboConfig['foreign_table'] 592 ); 593 $isNewRecord = true; 594 } 595 596 // get the TCEforms interpretation of the TCA of the child table 597 $out = $this->fObj->getMainFields($comboConfig['foreign_table'], $comboRecord); 598 $out = $this->wrapFormsSection($out, array(), array('class' => 'wrapperAttention')); 599 600 // if this is a new record, add a pid value to store this record and the pointer value for the intermediate table 601 if ($isNewRecord) { 602 $comboFormFieldName = $this->prependFormFieldNames.'['.$comboConfig['foreign_table'].']['.$comboRecord['uid'].'][pid]'; 603 $out .= '<input type="hidden" name="'.$comboFormFieldName.'" value="'.$this->inlineFirstPid.'"/>'; 604 } 605 606 // if the foreign_selector field is also responsible for uniqueness, tell the browser the uid of the "other" side of the relation 607 if ($isNewRecord || $config['foreign_unique'] == $foreign_selector) { 608 $parentFormFieldName = $this->prependFormFieldNames.$appendFormFieldNames.'['.$foreign_selector.']'; 609 $out .= '<input type="hidden" name="'.$parentFormFieldName.'" value="'.$comboRecord['uid'].'" />'; 610 } 611 } 612 613 return $out; 614 } 615 616 617 /** 618 * Get a selector as used for the select type, to select from all available 619 * records and to create a relation to the embedding record (e.g. like MM). 620 * 621 * @param array $selItems: Array of all possible records 622 * @param array $conf: TCA configuration of the parent(!) field 623 * @param array $uniqueIds: The uids that have already been used and should be unique 624 * @return string A HTML <select> box with all possible records 625 */ 626 function renderPossibleRecordsSelector($selItems, $conf, $uniqueIds=array()) { 627 $foreign_table = $conf['foreign_table']; 628 $foreign_selector = $conf['foreign_selector']; 629 630 $selConfig = $this->getPossibleRecordsSelectorConfig($conf, $foreign_selector); 631 $config = $selConfig['PA']['fieldConf']['config']; 632 633 if ($selConfig['type'] == 'select') { 634 $item = $this->renderPossibleRecordsSelectorTypeSelect($selItems, $conf, $selConfig['PA'], $uniqueIds); 635 } elseif ($selConfig['type'] == 'groupdb') { 636 $item = $this->renderPossibleRecordsSelectorTypeGroupDB($conf, $selConfig['PA']); 637 } 638 639 return $item; 640 } 641 642 643 /** 644 * Get a selector as used for the select type, to select from all available 645 * records and to create a relation to the embedding record (e.g. like MM). 646 * 647 * @param array $selItems: Array of all possible records 648 * @param array $conf: TCA configuration of the parent(!) field 649 * @param array $PA: An array with additional configuration options 650 * @param array $uniqueIds: The uids that have already been used and should be unique 651 * @return string A HTML <select> box with all possible records 652 */ 653 function renderPossibleRecordsSelectorTypeSelect($selItems, $conf, &$PA, $uniqueIds=array()) { 654 $foreign_table = $conf['foreign_table']; 655 $foreign_selector = $conf['foreign_selector']; 656 657 $PA = array(); 658 $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector]; 659 $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 660 $PA['fieldTSConfig'] = $this->fObj->setTSconfig($foreign_table,array(),$foreign_selector); 661 $config = $PA['fieldConf']['config']; 662 663 if(!$disabled) { 664 // Create option tags: 665 $opt = array(); 666 $styleAttrValue = ''; 667 foreach($selItems as $p) { 668 if ($config['iconsInOptionTags']) { 669 $styleAttrValue = $this->fObj->optionTagStyle($p[2]); 670 } 671 if (!in_array($p[1], $uniqueIds)) { 672 $opt[]= '<option value="'.htmlspecialchars($p[1]).'"'. 673 ' style="'.(in_array($p[1], $uniqueIds) ? '' : ''). 674 ($styleAttrValue ? ' style="'.htmlspecialchars($styleAttrValue) : '').'">'. 675 htmlspecialchars($p[0]).'</option>'; 676 } 677 } 678 679 // Put together the selector box: 680 $selector_itemListStyle = isset($config['itemListStyle']) ? ' style="'.htmlspecialchars($config['itemListStyle']).'"' : ' style="'.$this->fObj->defaultMultipleSelectorStyle.'"'; 681 $size = intval($conf['size']); 682 $size = $conf['autoSizeMax'] ? t3lib_div::intInRange(count($itemArray)+1,t3lib_div::intInRange($size,1),$conf['autoSizeMax']) : $size; 683 $onChange = "return inline.importNewRecord('".$this->inlineNames['object']."[".$conf['foreign_table']."]')"; 684 $item = ' 685 <select id="'.$this->inlineNames['object'].'['.$conf['foreign_table'].']_selector"'. 686 $this->fObj->insertDefStyle('select'). 687 ($size ? ' size="'.$size.'"' : ''). 688 ' onchange="'.htmlspecialchars($onChange).'"'. 689 $PA['onFocus']. 690 $selector_itemListStyle. 691 ($conf['foreign_unique'] ? ' isunique="isunique"' : '').'> 692 '.implode(' 693 ',$opt).' 694 </select>'; 695 696 // add a "Create new relation" link for adding new relations 697 // this is neccessary, if the size of the selector is "1" or if 698 // there is only one record item in the select-box, that is selected by default 699 // the selector-box creates a new relation on using a onChange event (see some line above) 700 $createNewRelationText = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createNewRelation',1); 701 $item .= 702 '<a href="#" onclick="'.htmlspecialchars($onChange).'" align="abstop">'. 703 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/edit2.gif','width="11" height="12"').' align="absmiddle" '.t3lib_BEfunc::titleAltAttrib($createNewRelationText).' /> '.$createNewRelationText. 704 '</a>'; 705 // wrap the selector and add a spacer to the bottom 706 $item = '<div style="margin-bottom: 20px;">'.$item.'</div>'; 707 } 708 709 return $item; 710 } 711 712 713 /** 714 * Generate a link that opens an element browser in a new window. 715 * For group/db there is no way o use a "selector" like a <select>|</select>-box. 716 * 717 * @param array $conf: TCA configuration of the parent(!) field 718 * @param array $PA: An array with additional configuration options 719 * @return string A HTML link that opens an element browser in a new window 720 */ 721 function renderPossibleRecordsSelectorTypeGroupDB($conf, &$PA) { 722 $foreign_table = $conf['foreign_table']; 723 724 $config = $PA['fieldConf']['config']; 725 $allowed = $config['allowed']; 726 $objectPrefix = $this->inlineNames['object'].'['.$foreign_table.']'; 727 728 $createNewRelationText = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createNewRelation',1); 729 $onClick = "setFormValueOpenBrowser('db','".('|||'.$allowed.'|'.$objectPrefix.'|inline.checkUniqueElement||inline.importElement')."'); return false;"; 730 $item = 731 '<a href="#" onclick="'.htmlspecialchars($onClick).'">'. 732 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/insert3.gif','width="14" height="14"').' align="absmiddle" '.t3lib_BEfunc::titleAltAttrib($createNewRelationText).' /> '.$createNewRelationText. 733 '</a>'; 734 735 return $item; 736 } 737 738 739 /** 740 * Creates a link/button to create new records 741 * 742 * @param string $objectPrefix: The "path" to the child record to create (e.g. '[parten_table][parent_uid][parent_field][child_table]') 743 * @param array $conf: TCA configuration of the parent(!) field 744 * @return string The HTML code for the new record link 745 */ 746 function getNewRecordLink($objectPrefix, $conf = array()) { 747 if ($conf['inline']['inlineNewButtonStyle']) $style = ' style="'.$conf['inline']['inlineNewButtonStyle'].'"'; 748 749 $onClick = "return inline.createNewRecord('$objectPrefix')"; 750 $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createnew',1); 751 752 if ($conf['appearance']['newRecordLinkAddTitle']) 753 $tableTitle .= ' '.$GLOBALS['LANG']->sL($GLOBALS['TCA'][$conf['foreign_table']]['ctrl']['title'],1); 754 755 $out = ' 756 <div class="typo3-newRecordLink"> 757 <a href="#" onClick="'.$onClick.'" class="inlineNewButton"'.$style.' title="'.$title.$tableTitle.'">'. 758 '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/new_el.gif','width="11" height="12"').' alt="'.$title.$tableTitle.'" />'. 759 $title.t3lib_div::fixed_lgd_cs($tableTitle, $this->fObj->titleLen). 760 '</a> 761 </div>'; 762 return $out; 763 } 764 765 766 /** 767 * Add Sortable functionality using script.acolo.us "Sortable". 768 * 769 * @param string $objectId: The container id of the object - elements inside will be sortable 770 * @return void 771 */ 772 function addJavaScriptSortable($objectId) { 773 $this->fObj->additionalJS_post[] = ' 774 inline.createDragAndDropSorting("'.$objectId.'"); 775 '; 776 } 777 778 779 /******************************************************* 780 * 781 * Handling of AJAX calls 782 * 783 *******************************************************/ 784 785 786 /** 787 * Initialize environment for AJAX calls 788 * 789 * @param string $method: Name of the method to be called 790 * @param array $arguments: Arguments to be delivered to the method 791 * @return void 792 */ 793 function initForAJAX($method, &$arguments) { 794 // Set t3lib_TCEforms::$RTEcounter to the given value: 795 if ($method == 'createNewRecord') { 796 $this->fObj->RTEcounter = intval(array_shift($arguments)); 797 } 798 } 799 800 801 /** 802 * Handle AJAX calls to show a new inline-record of the given table. 803 * Normally this method is never called from inside TYPO3. Always from outside by AJAX. 804 * 805 * @param string $domObjectId: The calling object in hierarchy, that requested a new record. 806 * @param string $foreignUid: If set, the new record should be inserted after that one. 807 * @return string A JSON string 808 */ 809 function createNewRecord($domObjectId, $foreignUid = 0) { 810 $this->isAjaxCall = true; 811 // parse the DOM identifier (string), add the levels to the structure stack (array) and load the TCA config 812 $this->parseStructureString($domObjectId, true); 813 // the current table - for this table we should add/import records 814 $current = $this->inlineStructure['unstable']; 815 // the parent table - this table embeds the current table 816 $parent = $this->getStructureLevel(-1); 817 // get TCA 'config' of the parent table 818 $config = $parent['config']; 819 820 // Put the current level also to the dynNestedStack of TCEforms: 821 $this->fObj->pushToDynNestedStack('inline', $this->inlineNames['object']); 822 823 // dynamically create a new record using t3lib_transferData 824 if (!$foreignUid || !t3lib_div::testInt($foreignUid) || $config['foreign_selector']) { 825 $record = $this->getNewRecord($this->inlineFirstPid, $current['table']); 826 827 // dynamically import an existing record (this could be a call from a select box) 828 } else { 829 $record = $this->getRecord($this->inlineFirstPid, $current['table'], $foreignUid); 830 } 831 832 // now there is a foreign_selector, so there is a new record on the intermediate table, but 833 // this intermediate table holds a field, which is responsible for the foreign_selector, so 834 // we have to set this field to the uid we get - or if none, to a new uid 835 if ($config['foreign_selector'] && $foreignUid) { 836 $selConfig = $this->getPossibleRecordsSelectorConfig($config, $config['foreign_selector']); 837 // For a selector of type group/db, prepend the tablename (<tablename>_<uid>): 838 $record[$config['foreign_selector']] = $selConfig['type'] != 'groupdb' ? '' : $selConfig['table'].'_'; 839 $record[$config['foreign_selector']] .= $foreignUid; 840 } 841 842 // the HTML-object-id's prefix of the dynamically created record 843 $objectPrefix = $this->inlineNames['object'].'['.$current['table'].']'; 844 $objectId = $objectPrefix.'['.$record['uid'].']'; 845 846 // render the foreign record that should passed back to browser 847 $item = $this->renderForeignRecord($parent['uid'], $record, $config); 848 if($item === false) { 849 $jsonArray = array( 850 'data' => 'Access denied', 851 'scriptCall' => array( 852 "alert('Access denied');", 853 ) 854 ); 855 return $this->getJSON($jsonArray); 856 } 857 858 // Encode TCEforms AJAX response with utf-8: 859 $item = $GLOBALS['LANG']->csConvObj->utf8_encode($item, $GLOBALS['LANG']->charSet); 860 861 if (!$current['uid']) { 862 $jsonArray = array( 863 'data' => $item, 864 'scriptCall' => array( 865 "inline.domAddNewRecord('bottom','".$this->inlineNames['object']."_records','$objectPrefix',json.data);", 866 "inline.memorizeAddRecord('$objectPrefix','".$record['uid']."',null,'$foreignUid');" 867 ) 868 ); 869 870 // append the HTML data after an existing record in the container 871 } else { 872 $jsonArray = array( 873 'data' => $item, 874 'scriptCall' => array( 875 "inline.domAddNewRecord('after','".$domObjectId.'_div'."','$objectPrefix',json.data);", 876 "inline.memorizeAddRecord('$objectPrefix','".$record['uid']."','".$current['uid']."','$foreignUid');" 877 ) 878 ); 879 } 880 881 // add the JavaScript data that would have been added at the bottom of a regular TCEforms calls 882 $jsonArray['scriptCall'][] = $this->fObj->JSbottom($this->fObj->formName, true); 883 // if script.aculo.us Sortable is used, update the Observer to know the the record 884 if ($config['appearance']['useSortable']) 885 $jsonArray['scriptCall'][] = "inline.createDragAndDropSorting('".$this->inlineNames['object']."_records');"; 886 // if TCEforms has some JavaScript code to be executed, just do it 887 if ($this->fObj->extJSCODE) 888 $jsonArray['scriptCall'][] = $this->fObj->extJSCODE; 889 // tell the browser to scroll to the newly created record 890 $jsonArray['scriptCall'][] = "Element.scrollTo('".$objectId."_div');"; 891 // fade out and fade in the new record in the browser view to catch the user's eye 892 $jsonArray['scriptCall'][] = "inline.fadeOutFadeIn('".$objectId."_div');"; 893 894 // Remove the current level also from the dynNestedStack of TCEforms: 895 $this->fObj->popFromDynNestedStack(); 896 897 // return the JSON string 898 return $this->getJSON($jsonArray); 899 } 900 901 902 /** 903 * Save the expanded/collapsed state of a child record in the BE_USER->uc. 904 * 905 * @param string $domObjectId: The calling object in hierarchy, that requested a new record. 906 * @param string $expand: Whether this record is expanded. 907 * @param string $collapse: Whether this record is collapsed. 908 * @return void 909 */ 910 function setExpandedCollapsedState($domObjectId, $expand, $collapse) { 911 // parse the DOM identifier (string), add the levels to the structure stack (array), but don't load TCA config 912 $this->parseStructureString($domObjectId, false); 913 // the current table - for this table we should add/import records 914 $current = $this->inlineStructure['unstable']; 915 // the top parent table - this table embeds the current table 916 $top = $this->getStructureLevel(0); 917 918 // only do some action if the top record and the current record were saved before 919 if (t3lib_div::testInt($top['uid'])) { 920 $inlineView = unserialize($GLOBALS['BE_USER']->uc['inlineView']); 921 $inlineViewCurrent =& $inlineView[$top['table']][$top['uid']]; 922 923 $expandUids = t3lib_div::trimExplode(',', $expand); 924 $collapseUids = t3lib_div::trimExplode(',', $collapse); 925 926 // set records to be expanded 927 foreach ($expandUids as $uid) { 928 $inlineViewCurrent[$current['table']][] = $uid; 929 } 930 // set records to be collapsed 931 foreach ($collapseUids as $uid) { 932 $inlineViewCurrent[$current['table']] = $this->removeFromArray($uid, $inlineViewCurrent[$current['table']]); 933 } 934 935 // save states back to database 936 if (is_array($inlineViewCurrent[$current['table']])) { 937 $GLOBALS['BE_USER']->uc['inlineView'] = serialize($inlineView); 938 $GLOBALS['BE_USER']->writeUC(); 939 } 940 } 941 } 942 943 944 /******************************************************* 945 * 946 * Get data from database and handle relations 947 * 948 *******************************************************/ 949 950 951 /** 952 * Get the related records of the embedding item, this could be 1:n, m:n. 953 * 954 * @param string $table: The table name of the record 955 * @param string $field: The field name which this element is supposed to edit 956 * @param array $row: The record data array where the value(s) for the field can be found 957 * @param array $PA: An array with additional configuration options. 958 * @param array $config: (Redundant) content of $PA['fieldConf']['config'] (for convenience) 959 * @return array The records related to the parent item 960 */ 961 function getRelatedRecords($table,$field,$row,&$PA,$config) { 962 $records = array(); 963 964 // Creating the label for the "No Matching Value" entry. 965 $nMV_label = isset($PA['fieldTSConfig']['noMatchingValue_label']) ? $this->fObj->sL($PA['fieldTSConfig']['noMatchingValue_label']) : '[ '.$this->fObj->getLL('l_noMatchingValue').' ]'; 966 967 // Register the required number of elements: 968 # $this->fObj->requiredElements[$PA['itemFormElName']] = array($minitems,$maxitems,'imgName'=>$table.'_'.$row['uid'].'_'.$field); 969 970 // Perform modification of the selected items array: 971 $itemArray = t3lib_div::trimExplode(',',$PA['itemFormElValue'],1); 972 foreach($itemArray as $tk => $tv) { 973 $tvP = explode('|',$tv,2); 974 // get the records for this uid using t3lib_transferdata 975 $records[] = $this->getRecord($row['pid'], $config['foreign_table'], $tvP[0]); 976 } 977 978 return $records; 979 } 980 981 982 /** 983 * Get possible records. 984 * Copied from TCEform and modified. 985 * 986 * @param string The table name of the record 987 * @param string The field name which this element is supposed to edit 988 * @param array The record data array where the value(s) for the field can be found 989 * @param array An array with additional configuration options. 990 * @param string $checkForConfField: For which field in the foreign_table the possible records should be fetched 991 * @return mixed Array of possible record items; false if type is "group/db", then everything could be "possible" 992 */ 993 function getPossibleRecords($table,$field,$row,$conf,$checkForConfField='foreign_selector') { 994 // ctrl configuration from TCA: 995 $tcaTableCtrl = $GLOBALS['TCA'][$table]['ctrl']; 996 // Field configuration from TCA: 997 $foreign_table = $conf['foreign_table']; 998 $foreign_check = $conf[$checkForConfField]; 999 1000 $foreignConfig = $this->getPossibleRecordsSelectorConfig($conf, $foreign_check); 1001 $PA = $foreignConfig['PA']; 1002 $config = $PA['fieldConf']['config']; 1003 1004 if ($foreignConfig['type'] == 'select') { 1005 // Getting the selector box items from the system 1006 $selItems = $this->fObj->addSelectOptionsToItemArray($this->fObj->initItemArray($PA['fieldConf']),$PA['fieldConf'],$this->fObj->setTSconfig($table,$row),$field); 1007 if ($config['itemsProcFunc']) $selItems = $this->fObj->procItems($selItems,$PA['fieldTSConfig']['itemsProcFunc.'],$config,$table,$row,$field); 1008 1009 // Possibly remove some items: 1010 $removeItems = t3lib_div::trimExplode(',',$PA['fieldTSConfig']['removeItems'],1); 1011 foreach($selItems as $tk => $p) { 1012 1013 // Checking languages and authMode: 1014 $languageDeny = $tcaTableCtrl['languageField'] && !strcmp($tcaTableCtrl['languageField'], $field) && !$GLOBALS['BE_USER']->checkLanguageAccess($p[1]); 1015 $authModeDeny = $config['form_type']=='select' && $config['authMode'] && !$GLOBALS['BE_USER']->checkAuthMode($table,$field,$p[1],$config['authMode']); 1016 if (in_array($p[1],$removeItems) || $languageDeny || $authModeDeny) { 1017 unset($selItems[$tk]); 1018 } elseif (isset($PA['fieldTSConfig']['altLabels.'][$p[1]])) { 1019 $selItems[$tk][0]=$this->fObj->sL($PA['fieldTSConfig']['altLabels.'][$p[1]]); 1020 } 1021 1022 // Removing doktypes with no access: 1023 if ($table.'.'.$field == 'pages.doktype') { 1024 if (!($GLOBALS['BE_USER']->isAdmin() || t3lib_div::inList($GLOBALS['BE_USER']->groupData['pagetypes_select'],$p[1]))) { 1025 unset($selItems[$tk]); 1026 } 1027 } 1028 } 1029 } else { 1030 $selItems = false; 1031 } 1032 1033 return $selItems; 1034 } 1035 1036 /** 1037 * Gets the uids of a select/selector that should be unique an have already been used. 1038 * 1039 * @param array $records: All inline records on this level 1040 * @param array $conf: The TCA field configuration of the inline field to be rendered 1041 * @param boolean $splitValue: for usage with group/db, values come like "tx_table_123|Title%20abc", but we need "tx_table" and "123" 1042 * @return array The uids, that have been used already and should be used unique 1043 */ 1044 function getUniqueIds($records, $conf=array(), $splitValue=false) { 1045 $uniqueIds = array(); 1046 1047 if ($conf['foreign_unique'] && count($records)) { 1048 foreach ($records as $rec) { 1049 $value = $rec[$conf['foreign_unique']]; 1050 // Split the value and extract the table and uid: 1051 if ($splitValue) { 1052 $valueParts = t3lib_div::trimExplode('|', $value); 1053 $itemParts = explode('_', $valueParts[0]); 1054 $value = array( 1055 'uid' => array_pop($itemParts), 1056 'table' => implode('_', $itemParts) 1057 ); 1058 } 1059 $uniqueIds[$rec['uid']] = $value; 1060 } 1061 } 1062 1063 return $uniqueIds; 1064 } 1065 1066 1067 /** 1068 * Get a single record row for a TCA table from the database. 1069 * t3lib_transferData is used for "upgrading" the values, especially the relations. 1070 * 1071 * @param integer $pid: The pid of the page the record should be stored (only relevant for NEW records) 1072 * @param string $table: The table to fetch data from (= foreign_table) 1073 * @param string $uid: The uid of the record to fetch, or the pid if a new record should be created 1074 * @param string $cmd: The command to perform, empty or 'new' 1075 * @return array A record row from the database post-processed by t3lib_transferData 1076 */ 1077 function getRecord($pid, $table, $uid, $cmd='') { 1078 $trData = t3lib_div::makeInstance('t3lib_transferData'); 1079 $trData->addRawData = TRUE; 1080 # $trData->defVals = $this->defVals; 1081 $trData->lockRecords=1; 1082 $trData->disableRTE = $GLOBALS['SOBE']->MOD_SETTINGS['disableRTE']; 1083 // if a new record should be created 1084 $trData->fetchRecord($table, $uid, ($cmd === 'new' ? 'new' : '')); 1085 reset($trData->regTableItems_data); 1086 $rec = current($trData->regTableItems_data); 1087 $rec['uid'] = $cmd == 'new' ? uniqid('NEW') : $uid; 1088 if ($cmd=='new') $rec['pid'] = $pid; 1089 1090 return $rec; 1091 } 1092 1093 1094 /** 1095 * Wrapper. Calls getRecord in case of a new record should be created. 1096 * 1097 * @param integer $pid: The pid of the page the record should be stored (only relevant for NEW records) 1098 * @param string $table: The table to fetch data from (= foreign_table) 1099 * @return array A record row from the database post-processed by t3lib_transferData 1100 */ 1101 function getNewRecord($pid, $table) { 1102 return $this->getRecord($pid, $table, $pid, 'new'); 1103 } 1104 1105 1106 /******************************************************* 1107 * 1108 * Structure stack for handling inline objects/levels 1109 * 1110 *******************************************************/ 1111 1112 1113 /** 1114 * Add a new level on top of the structure stack. Other functions can access the 1115 * stack and determine, if there's possibly a endless loop. 1116 * 1117 * @param string $table: The table name of the record 1118 * @param string $uid: The uid of the record that embeds the inline data 1119 * @param string $field: The field name which this element is supposed to edit 1120 * @param array $config: The TCA-configuration of the inline field 1121 * @return void 1122 */ 1123 function pushStructure($table, $uid, $field = '', $config = array()) { 1124 $this->inlineStructure['stable'][] = array( 1125 'table' => $table, 1126 'uid' => $uid, 1127 'field' => $field, 1128 'config' => $config, 1129 ); 1130 $this->updateStructureNames(); 1131 } 1132 1133 1134 /** 1135 * Remove the item on top of the structure stack and return it. 1136 * 1137 * @return array The top item of the structure stack - array(<table>,<uid>,<field>,<config>) 1138 */ 1139 function popStructure() { 1140 if (count($this->inlineStructure['stable'])) { 1141 $popItem = array_pop($this->inlineStructure['stable']); 1142 $this->updateStructureNames(); 1143 } 1144 return $popItem; 1145 } 1146 1147 1148 /** 1149 * For common use of DOM object-ids and form field names of a several inline-level, 1150 * these names/identifiers are preprocessed and set to $this->inlineNames. 1151 * This function is automatically called if a level is pushed to or removed from the 1152 * inline structure stack. 1153 * 1154 * @return void 1155 */ 1156 function updateStructureNames() { 1157 $current = $this->getStructureLevel(-1); 1158 // if there are still more inline levels available 1159 if ($current !== false) { 1160 $lastItemName = $this->getStructureItemName($current); 1161 $this->inlineNames = array( 1162 'form' => $this->prependFormFieldNames.$lastItemName, 1163 'object' => $this->prependNaming.'['.$this->inlineFirstPid.']'.$this->getStructurePath(), 1164 ); 1165 // if there are no more inline levels available 1166 } else { 1167 $this->inlineNames = array(); 1168 } 1169 } 1170 1171 1172 /** 1173 * Create a name/id for usage in HTML output of a level of the structure stack. 1174 * 1175 * @param array $levelData: Array of a level of the structure stack (containing the keys table, uid and field) 1176 * @return string The name/id of that level, to be used for HTML output 1177 */ 1178 function getStructureItemName($levelData) { 1179 if (is_array($levelData)) { 1180 $name = '['.$levelData['table'].']' . 1181 '['.$levelData['uid'].']' . 1182 (isset($levelData['field']) ? '['.$levelData['field'].']' : ''); 1183 } 1184 return $name; 1185 } 1186 1187 1188 /** 1189 * Get a level from the stack and return the data. 1190 * If the $level value is negative, this function works top-down, 1191 * if the $level value is positive, this function works bottom-up. 1192 * 1193 * @param integer $level: Which level to return 1194 * @return array The item of the stack at the requested level 1195 */ 1196 function getStructureLevel($level) { 1197 $inlineStructureCount = count($this->inlineStructure['stable']); 1198 if ($level < 0) $level = $inlineStructureCount+$level; 1199 if ($level >= 0 && $level < $inlineStructureCount) 1200 return $this->inlineStructure['stable'][$level]; 1201 else 1202 return false; 1203 } 1204 1205 1206 /** 1207 * Get the identifiers of a given depth of level, from the top of the stack to the bottom. 1208 * An identifier consists looks like [<table>][<uid>][<field>]. 1209 * 1210 * @param integer $structureDepth: How much levels to output, beginning from the top of the stack 1211 * @return string The path of identifiers 1212 */ 1213 function getStructurePath($structureDepth = -1) { 1214 $structureCount = count($this->inlineStructure['stable']); 1215 if ($structureDepth < 0 || $structureDepth > $structureCount) $structureDepth = $structureCount; 1216 1217 for ($i = 1; $i <= $structureDepth; $i++) { 1218 $current = $this->getStructureLevel(-$i); 1219 $string = $this->getStructureItemName($current).$string; 1220 } 1221 1222 return $string; 1223 } 1224 1225 1226 /** 1227 * Convert the DOM object-id of an inline container to an array. 1228 * The object-id could look like 'data[inline][tx_mmftest_company][1][employees]'. 1229 * The result is written to $this->inlineStructure. 1230 * There are two keys: 1231 * - 'stable': Containing full qualified identifiers (table, uid and field) 1232 * - 'unstable': Containting partly filled data (e.g. only table and possibly field) 1233 * 1234 * @param string $domObjectId: The DOM object-id 1235 * @param boolean $loadConfig: Load the TCA configuration for that level 1236 * @return void 1237 */ 1238 function parseStructureString($string, $loadConfig = false) { 1239 $unstable = array(); 1240 $vector = array('table', 'uid', 'field'); 1241 $pattern = '/^'.$this->prependNaming.'\[(.+?)\]\[(.+)\]$/'; 1242 if (preg_match($pattern, $string, $match)) { 1243 $this->inlineFirstPid = $match[1]; 1244 $parts = explode('][', $match[2]); 1245 $partsCnt = count($parts); 1246 for ($i = 0; $i < $partsCnt; $i++) { 1247 if ($i > 0 && $i % 3 == 0) { 1248 // load the TCA configuration of the table field and store it in the stack 1249 if ($loadConfig) { 1250 t3lib_div::loadTCA($unstable['table']); 1251 $unstable['config'] = $GLOBALS['TCA'][$unstable['table']]['columns'][$unstable['field']]['config']; 1252 // Fetch TSconfig: 1253 $TSconfig = $this->fObj->setTSconfig( 1254 $unstable['table'], 1255 array('uid' => $unstable['uid'], 'pid' => $this->inlineFirstPid), 1256 $unstable['field'] 1257 ); 1258 // Override TCA field config by TSconfig: 1259 if (!$TSconfig['disabled']) { 1260 $unstable['config'] = $this->fObj->overrideFieldConf($unstable['config'], $TSconfig); 1261 } 1262 } 1263 $this->inlineStructure['stable'][] = $unstable; 1264 $unstable = array(); 1265 } 1266 $unstable[$vector[$i % 3]] = $parts[$i]; 1267 } 1268 $this->updateStructureNames(); 1269 if (count($unstable)) $this->inlineStructure['unstable'] = $unstable; 1270 } 1271 } 1272 1273 1274 /******************************************************* 1275 * 1276 * Helper functions 1277 * 1278 *******************************************************/ 1279 1280 1281 /** 1282 * Does some checks on the TCA configuration of the inline field to render. 1283 * 1284 * @param array $config: Reference to the TCA field configuration 1285 * @return boolean If critical configuration errors were found, false is returned 1286 */ 1287 function checkConfiguration(&$config) { 1288 $foreign_table = $config['foreign_table']; 1289 1290 // An inline field must have a foreign_table, if not, stop all further inline actions for this field: 1291 if (!$foreign_table || !is_array($GLOBALS['TCA'][$foreign_table])) { 1292 return false; 1293 } 1294 // Init appearance if not set: 1295 if (!is_array($config['appearance'])) { 1296 $config['appearance'] = array(); 1297 } 1298 // Set the position/appearance of the "Create new record" link: 1299 if ($config['foreign_selector'] && !$config['appearance']['useCombination']) { 1300 $config['appearance']['newRecordLinkPosition'] = 'none'; 1301 } elseif (!in_array($config['appearance']['newRecordLinkPosition'], array('top', 'bottom', 'both', 'none'))) { 1302 $config['appearance']['newRecordLinkPosition'] = 'top'; 1303 } 1304 1305 return true; 1306 } 1307 1308 1309 /** 1310 * Checks the page access rights (Code for access check mostly taken from alt_doc.php) 1311 * as well as the table access rights of the user. 1312 * 1313 * @param string $cmd: The command that sould be performed ('new' or 'edit') 1314 * @param string $table: The table to check access for 1315 * @param string $theUid: The record uid of the table 1316 * @return boolean Returns true is the user has access, or false if not 1317 */ 1318 function checkAccess($cmd, $table, $theUid) { 1319 // 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...) 1320 // First, resetting flags. 1321 $hasAccess = 0; 1322 $deniedAccessReason = ''; 1323 1324 // If the command is to create a NEW record...: 1325 if ($cmd=='new') { 1326 // If the pid is numerical, check if it's possible to write to this page: 1327 if (t3lib_div::testInt($this->inlineFirstPid)) { 1328 $calcPRec = t3lib_BEfunc::getRecord('pages', $this->inlineFirstPid); 1329 if(!is_array($calcPRec)) { 1330 return false; 1331 } 1332 $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms($calcPRec); // Permissions for the parent page 1333 if ($table=='pages') { // If pages: 1334 $hasAccess = $CALC_PERMS&8 ? 1 : 0; // Are we allowed to create new subpages? 1335 } else { 1336 $hasAccess = $CALC_PERMS&16 ? 1 : 0; // Are we allowed to edit content on this page? 1337 } 1338 // If the pid is a NEW... value, the access will be checked on creating the page: 1339 // (if the page with the same NEW... value could be created in TCEmain, this child record can neither) 1340 } else { 1341 $hasAccess = 1; 1342 } 1343 } else { // Edit: 1344 $calcPRec = t3lib_BEfunc::getRecord($table,$theUid); 1345 t3lib_BEfunc::fixVersioningPid($table,$calcPRec); 1346 if (is_array($calcPRec)) { 1347 if ($table=='pages') { // If pages: 1348 $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms($calcPRec); 1349 $hasAccess = $CALC_PERMS&2 ? 1 : 0; 1350 } else { 1351 $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages',$calcPRec['pid'])); // Fetching pid-record first. 1352 $hasAccess = $CALC_PERMS&16 ? 1 : 0; 1353 } 1354 1355 // Check internals regarding access: 1356 if ($hasAccess) { 1357 $hasAccess = $GLOBALS['BE_USER']->recordEditAccessInternals($table, $calcPRec); 1358 } 1359 } 1360 } 1361 1362 if(!$GLOBALS['BE_USER']->check('tables_modify', $table)) { 1363 $hasAccess = 0; 1364 } 1365 1366 if(!$hasAccess) { 1367 $deniedAccessReason = $GLOBALS['BE_USER']->errorMsg; 1368 if($deniedAccessReason) { 1369 debug($deniedAccessReason); 1370 } 1371 } 1372 1373 return $hasAccess ? true : false; 1374 } 1375 1376 1377 /** 1378 * Check the keys and values in the $compare array against the ['config'] part of the top level of the stack. 1379 * A boolean value is return depending on how the comparison was successful. 1380 * 1381 * @param array $compare: keys and values to compare to the ['config'] part of the top level of the stack 1382 * @return boolean Whether the comparison was successful 1383 * @see arrayCompareComplex 1384 */ 1385 function compareStructureConfiguration($compare) { 1386 $level = $this->getStructureLevel(-1); 1387 $result = $this->arrayCompareComplex($level, $compare); 1388 1389 return $result; 1390 } 1391 1392 1393 /** 1394 * Normalize a relation "uid" published by transferData, like "1|Company%201" 1395 * 1396 * @param string $string: A transferData reference string, containing the uid 1397 * @return string The normalized uid 1398 */ 1399 function normalizeUid($string) { 1400 $parts = explode('|', $string); 1401 return $parts[0]; 1402 } 1403 1404 1405 /** 1406 * Wrap the HTML code of a section with a table tag. 1407 * 1408 * @param string $section: The HTML code to be wrapped 1409 * @param array $styleAttrs: Attributes for the style argument in the table tag 1410 * @param array $tableAttrs: Attributes for the table tag (like width, border, etc.) 1411 * @return string The wrapped HTML code 1412 */ 1413 function wrapFormsSection($section, $styleAttrs = array(), $tableAttrs = array()) { 1414 if (!$styleAttrs['margin-right']) $styleAttrs['margin-right'] = $this->inlineStyles['margin-right'].'px'; 1415 1416 foreach ($styleAttrs as $key => $value) $style .= ($style?' ':'').$key.': '.htmlspecialchars($value).'; '; 1417 if ($style) $style = ' style="'.$style.'"'; 1418 1419 if (!$tableAttrs['background'] && $this->fObj->borderStyle[2]) $tableAttrs['background'] = $this->backPath.$this->borderStyle[2]; 1420 if (!$tableAttrs['cellspacing']) $tableAttrs['cellspacing'] = '0'; 1421 if (!$tableAttrs['cellpadding']) $tableAttrs['cellpadding'] = '0'; 1422 if (!$tableAttrs['border']) $tableAttrs['border'] = '0'; 1423 if (!$tableAttrs['width']) $tableAttrs['width'] = '100%'; 1424 if (!$tableAttrs['class'] && $this->borderStyle[3]) $tableAttrs['class'] = $this->borderStyle[3]; 1425 1426 foreach ($tableAttrs as $key => $value) $table .= ($table?' ':'').$key.'="'.htmlspecialchars($value).'"'; 1427 1428 $out = '<table '.$table.$style.'>'.$section.'</table>'; 1429 return $out; 1430 } 1431 1432 1433 /** 1434 * Checks if the $table is the child of a inline type AND the $field is the label field of this table. 1435 * This function is used to dynamically update the label while editing. This has no effect on labels, 1436 * that were processed by a TCEmain-hook on saving. 1437 * 1438 * @param string $table: The table to check 1439 * @param string $field: The field on this table to check 1440 * @return boolean is inline child and field is responsible for the label 1441 */ 1442 function isInlineChildAndLabelField($table, $field) { 1443 $level = $this->getStructureLevel(-1); 1444 if ($level['config']['foreign_label']) 1445 $label = $level['config']['foreign_label']; 1446 else 1447 $label = $GLOBALS['TCA'][$table]['ctrl']['label']; 1448 return $level['config']['foreign_table'] === $table && $label == $field ? true : false; 1449 } 1450 1451 1452 /** 1453 * Get the depth of the stable structure stack. 1454 * (count($this->inlineStructure['stable']) 1455 * 1456 * @return integer The depth of the structure stack 1457 */ 1458 function getStructureDepth() { 1459 return count($this->inlineStructure['stable']); 1460 } 1461 1462 1463 /** 1464 * Handles complex comparison requests on an array. 1465 * A request could look like the following: 1466 * 1467 * $searchArray = array( 1468 * '%AND' => array( 1469 * 'key1' => 'value1', 1470 * 'key2' => 'value2', 1471 * '%OR' => array( 1472 * 'subarray' => array( 1473 * 'subkey' => 'subvalue' 1474 * ), 1475 * 'key3' => 'value3', 1476 * 'key4' => 'value4' 1477 * ) 1478 * ) 1479 * ); 1480 * 1481 * It is possible to use the array keys '%AND.1', '%AND.2', etc. to prevent 1482 * overwriting the sub-array. It could be neccessary, if you use complex comparisons. 1483 * 1484 * The example above means, key1 *AND* key2 (and their values) have to match with 1485 * the $subjectArray and additional one *OR* key3 or key4 have to meet the same 1486 * condition. 1487 * It is also possible to compare parts of a sub-array (e.g. "subarray"), so this 1488 * function recurses down one level in that sub-array. 1489 * 1490 * @param array $subjectArray: The array to search in 1491 * @param array $searchArray: The array with keys and values to search for 1492 * @param string $type: Use '%AND' or '%OR' for comparision 1493 * @return boolean The result of the comparison 1494 */ 1495 function arrayCompareComplex($subjectArray, $searchArray, $type = '') { 1496 $localMatches = 0; 1497 $localEntries = 0; 1498 1499 if (is_array($searchArray) && count($searchArray)) { 1500 // if no type was passed, try to determine 1501 if (!$type) { 1502 reset($searchArray); 1503 $type = key($searchArray); 1504 $searchArray = current($searchArray); 1505 } 1506 1507 // we use '%AND' and '%OR' in uppercase 1508 $type = strtoupper($type); 1509 1510 // split regular elements from sub elements 1511 foreach ($searchArray as $key => $value) { 1512 $localEntries++; 1513 1514 // process a sub-group of OR-conditions 1515 if ($key == '%OR') { 1516 $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0; 1517 // process a sub-group of AND-conditions 1518 } elseif ($key == '%AND') { 1519 $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0; 1520 // a part of an associative array should be compared, so step down in the array hierarchy 1521 } elseif (is_array($value) && $this->isAssociativeArray($searchArray)) { 1522 $localMatches += $this->arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0; 1523 // it is a normal array that is only used for grouping and indexing 1524 } elseif (is_array($value)) { 1525 $localMatches += $this->arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0; 1526 // directly compare a value 1527 } else { 1528 $localMatches += isset($subjectArray[$key]) && isset($value) && $subjectArray[$key] === $value ? 1 : 0; 1529 } 1530 1531 // if one or more matches are required ('OR'), return true after the first successful match 1532 if ($type == '%OR' && $localMatches > 0) return true; 1533 // if all matches are required ('AND') and we have no result after the first run, return false 1534 if ($type == '%AND' && $localMatches == 0) return false; 1535 } 1536 } 1537 1538 // return the result for '%AND' (if nothing was checked, true is returned) 1539 return $localEntries == $localMatches ? true : false; 1540 } 1541 1542 1543 /** 1544 * Checks whether an object is an associative array. 1545 * 1546 * @param mixed $object: The object to be checked 1547 * @return boolean Returns true, if the object is an associative array 1548 */ 1549 function isAssociativeArray($object) { 1550 return is_array($object) && count($object) && (array_keys($object) !== range(0, sizeof($object) - 1)) 1551 ? true 1552 : false; 1553 } 1554 1555 1556 /** 1557 * Remove an element from an array. 1558 * 1559 * @param mixed $needle: The element to be removed. 1560 * @param array $haystack: The array the element should be removed from. 1561 * @param mixed $strict: Search elements strictly. 1562 * @return array The array $haystack without the $needle 1563 */ 1564 function removeFromArray($needle, $haystack, $strict=null) { 1565 $pos = array_search($needle, $haystack, $strict); 1566 if ($pos !== false) unset($haystack[$pos]); 1567 return $haystack; 1568 } 1569 1570 1571 /** 1572 * Makes a flat array from the $possibleRecords array. 1573 * The key of the flat array is the value of the record, 1574 * the value of the flat array is the label of the record. 1575 * 1576 * @param array $possibleRecords: The possibleRecords array (for select fields) 1577 * @return mixed A flat array with key=uid, value=label; if $possibleRecords isn't an array, false is returned. 1578 */ 1579 function getPossibleRecordsFlat($possibleRecords) { 1580 $flat = false; 1581 if (is_array($possibleRecords)) { 1582 $flat = array(); 1583 foreach ($possibleRecords as $record) $flat[$record[1]] = $record[0]; 1584 } 1585 return $flat; 1586 } 1587 1588 1589 /** 1590 * Determine the configuration and the type of a record selector. 1591 * 1592 * @param array $conf: TCA configuration of the parent(!) field 1593 * @return array Associative array with the keys 'PA' and 'type', both are false if the selector was not valid. 1594 */ 1595 function getPossibleRecordsSelectorConfig($conf, $field = '') { 1596 $foreign_table = $conf['foreign_table']; 1597 $foreign_selector = $conf['foreign_selector']; 1598 1599 $PA = false; 1600 $type = false; 1601 $table = false; 1602 $selector = false; 1603 1604 if ($field) { 1605 $PA = array(); 1606 $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$field]; 1607 $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 1608 $PA['fieldTSConfig'] = $this->fObj->setTSconfig($foreign_table,array(),$field); 1609 $config = $PA['fieldConf']['config']; 1610 // Determine type of Selector: 1611 $type = $this->getPossibleRecordsSelectorType($config); 1612 // Return table on this level: 1613 $table = $type == 'select' ? $config['foreign_table'] : $config['allowed']; 1614 // Return type of the selector if foreign_selector is defined and points to the same field as in $field: 1615 if ($foreign_selector && $foreign_selector == $field && $type) { 1616 $selector = $type; 1617 } 1618 } 1619 1620 return array( 1621 'PA' => $PA, 1622 'type' => $type, 1623 'table' => $table, 1624 'selector' => $selector, 1625 ); 1626 } 1627 1628 1629 /** 1630 * Determine the type of a record selector, e.g. select or group/db. 1631 * 1632 * @param array $config: TCE configuration of the selector 1633 * @return mixed The type of the selector, 'select' or 'groupdb' - false not valid 1634 */ 1635 function getPossibleRecordsSelectorType($config) { 1636 $type = false; 1637 if ($config['type'] == 'select') { 1638 $type = 'select'; 1639 } elseif ($config['type'] == 'group' && $config['internal_type'] == 'db') { 1640 $type = 'groupdb'; 1641 } 1642 return $type; 1643 } 1644 1645 1646 /** 1647 * Check, if a field should be skipped, that was defined to be handled as foreign_field or foreign_sortby of 1648 * the parent record of the "inline"-type - if so, we have to skip this field - the rendering is done via "inline" as hidden field 1649 * 1650 * @param string $table: The table name 1651 * @param string $field: The field name 1652 * @param array $row: The record row from the database 1653 * @param array $config: TCA configuration of the field 1654 * @return boolean Determines whether the field should be skipped. 1655 */ 1656 function skipField($table, $field, $row, $config) { 1657 $skipThisField = false; 1658 1659 if ($this->getStructureDepth()) { 1660 $searchArray = array( 1661 '%OR' => array( 1662 'config' => array( 1663 0 => array( 1664 '%AND' => array( 1665 'foreign_table' => $table, 1666 '%OR' => array( 1667 '%AND' => array( 1668 'appearance' => array('useCombination' => 1), 1669 'foreign_selector' => $field, 1670 ), 1671 'MM' => $config['MM'] 1672 ), 1673 ), 1674 ), 1675 1 => array( 1676 '%AND' => array( 1677 'foreign_table' => $config['foreign_table'], 1678 'foreign_selector' => $config['foreign_field'], 1679 ), 1680 ), 1681 ), 1682 ), 1683 ); 1684 1685 // get the parent record from structure stack 1686 $level = $this->getStructureLevel(-1); 1687 1688 // If we have symmetric fields, check on which side we are and hide fields, that are set automatically: 1689 if (t3lib_loadDBGroup::isOnSymmetricSide($level['uid'], $level['config'], $row)) { 1690 $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $field; 1691 $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $field; 1692 // Hide fields, that are set automatically: 1693 } else { 1694 $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $field; 1695 $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $field; 1696 } 1697 1698 $skipThisField = $this->compareStructureConfiguration($searchArray, true); 1699 } 1700 1701 return $skipThisField; 1702 } 1703 1704 1705 /** 1706 * Creates recursively a JSON literal from a mulidimensional associative array. 1707 * Uses Services_JSON (http://mike.teczno.com/JSON/doc/) 1708 * 1709 * @param array $jsonArray: The array (or part of) to be transformed to JSON 1710 * @return string If $level>0: part of JSON literal; if $level==0: whole JSON literal wrapped with <script> tags 1711 */ 1712 function getJSON($jsonArray) { 1713 if (!$GLOBALS['JSON']) { 1714 require_once(PATH_typo3.'contrib/json.php'); 1715 $GLOBALS['JSON'] = t3lib_div::makeInstance('Services_JSON'); 1716 } 1717 return $GLOBALS['JSON']->encode($jsonArray); 1718 } 1719 1720 1721 /** 1722 * Checks if a uid of a child table is in the inline view settings. 1723 * 1724 * @param string $table: Name of the child table 1725 * @param integer $uid: uid of the the child record 1726 * @return boolean true=expand, false=collapse 1727 */ 1728 function getExpandedCollapsedState($table, $uid) { 1729 if (is_array($this->inlineView) && is_array($this->inlineView[$table])) { 1730 if (in_array($uid, $this->inlineView[$table]) !== false) return true; 1731 } 1732 return false; 1733 } 1734 1735 1736 /** 1737 * Update expanded/collapsed states on new inline records if any. 1738 * 1739 * @param array $uc: The uc array to be processed and saved (by reference) 1740 * @param object $tce: Instance of TCEmain that saved data before (by reference) 1741 * @return void 1742 */ 1743 function updateInlineView(&$uc, &$tce) { 1744 if (is_array($uc) && $uc['inlineView']) { 1745 $inlineView = unserialize($GLOBALS['BE_USER']->uc['inlineView']); 1746 1747 foreach ($uc['inlineView'] as $topTable => $topRecords) { 1748 foreach ($topRecords as $topUid => $childElements) { 1749 foreach ($childElements as $childTable => $childRecords) { 1750 $uids = array_keys($tce->substNEWwithIDs_table, $childTable); 1751 if (count($uids)) { 1752 foreach ($childRecords as $childUid => $state) { 1753 if ($state && in_array($childUid, $uids)) { 1754 $newChildUid = $tce->substNEWwithIDs[$childUid]; 1755 $inlineView[$topTable][$topUid][$childTable][$newChildUid] = 1; 1756 } 1757 } 1758 } 1759 } 1760 } 1761 } 1762 1763 $GLOBALS['BE_USER']->uc['inlineView'] = serialize($inlineView); 1764 $GLOBALS['BE_USER']->writeUC(); 1765 } 1766 } 1767 1768 1769 /** 1770 * Returns the the margin in pixels, that is used for each new inline level. 1771 * 1772 * @return integer A pixel value for the margin of each new inline level. 1773 */ 1774 function getLevelMargin() { 1775 $margin = ($this->inlineStyles['margin-right']+1)*2; 1776 return $margin; 1777 } 1778 } 1779 1780 1781 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_tceforms_inline.php']) { 1782 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_tceforms_inline.php']); 1783 } 1784 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
| Généré le : Sun Nov 25 17:13:16 2007 | par Balluche grâce à PHPXref 0.7 |
|