PageRenderTime 30ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/t3lib/class.t3lib_tceforms_inline.php

https://github.com/andreaswolf/typo3-tceforms
PHP | 2550 lines | 1431 code | 285 blank | 834 comment | 355 complexity | fa1d9ef6d86be102ccb11bf272ba9809 MD5 | raw file
Possible License(s): Apache-2.0, BSD-2-Clause, LGPL-3.0
  1. <?php
  2. /***************************************************************
  3. * Copyright notice
  4. *
  5. * (c) 2006-2011 Oliver Hader <oliver@typo3.org>
  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$
  31. *
  32. * @author Oliver Hader <oliver@typo3.org>
  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. const Structure_Separator = '-';
  92. const Disposal_AttributeName = 'Disposal_AttributeName';
  93. const Disposal_AttributeId = 'Disposal_AttributeId';
  94. /**
  95. * Reference to the calling TCEforms instance
  96. *
  97. * @var t3lib_TCEforms
  98. */
  99. var $fObj;
  100. var $backPath; // Reference to $fObj->backPath
  101. var $isAjaxCall = FALSE; // Indicates if a field is rendered upon an AJAX call
  102. var $inlineStructure = array(); // the structure/hierarchy where working in, e.g. cascading inline tables
  103. var $inlineFirstPid; // the first call of an inline type appeared on this page (pid of record)
  104. var $inlineNames = array(); // keys: form, object -> hold the name/id for each of them
  105. var $inlineData = array(); // inline data array used for JSON output
  106. var $inlineView = array(); // expanded/collapsed states for the current BE user
  107. var $inlineCount = 0; // count the number of inline types used
  108. var $inlineStyles = array();
  109. var $prependNaming = 'data'; // how the $this->fObj->prependFormFieldNames should be set ('data' is default)
  110. var $prependFormFieldNames; // reference to $this->fObj->prependFormFieldNames
  111. var $prependCmdFieldNames; // reference to $this->fObj->prependCmdFieldNames
  112. protected $hookObjects = array(); // array containing instances of hook classes called once for IRRE objects
  113. /**
  114. * Intialize an instance of t3lib_TCEforms_inline
  115. *
  116. * @param t3lib_TCEforms $tceForms: Reference to an TCEforms instance
  117. * @return void
  118. */
  119. function init(&$tceForms) {
  120. $this->fObj = $tceForms;
  121. $this->backPath =& $tceForms->backPath;
  122. $this->prependFormFieldNames =& $this->fObj->prependFormFieldNames;
  123. $this->prependCmdFieldNames =& $this->fObj->prependCmdFieldNames;
  124. $this->inlineStyles['margin-right'] = '5';
  125. $this->initHookObjects();
  126. }
  127. /**
  128. * Initialized the hook objects for this class.
  129. * Each hook object has to implement the interface t3lib_tceformsInlineHook.
  130. *
  131. * @return void
  132. */
  133. protected function initHookObjects() {
  134. $this->hookObjects = array();
  135. if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms_inline.php']['tceformsInlineHook'])) {
  136. $tceformsInlineHook =& $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms_inline.php']['tceformsInlineHook'];
  137. if (is_array($tceformsInlineHook)) {
  138. foreach ($tceformsInlineHook as $classData) {
  139. $processObject = t3lib_div::getUserObj($classData);
  140. if (!($processObject instanceof t3lib_tceformsInlineHook)) {
  141. throw new UnexpectedValueException('$processObject must implement interface t3lib_tceformsInlineHook', 1202072000);
  142. }
  143. $processObject->init($this);
  144. $this->hookObjects[] = $processObject;
  145. }
  146. }
  147. }
  148. }
  149. /**
  150. * Generation of TCEform elements of the type "inline"
  151. * This will render inline-relational-record sets. Relations.
  152. *
  153. * @param string $table: The table name of the record
  154. * @param string $field: The field name which this element is supposed to edit
  155. * @param array $row: The record data array where the value(s) for the field can be found
  156. * @param array $PA: An array with additional configuration options.
  157. * @return string The HTML code for the TCEform field
  158. */
  159. function getSingleField_typeInline($table, $field, $row, &$PA) {
  160. // check the TCA configuration - if false is returned, something was wrong
  161. if ($this->checkConfiguration($PA['fieldConf']['config']) === FALSE) {
  162. return FALSE;
  163. }
  164. // count the number of processed inline elements
  165. $this->inlineCount++;
  166. // Init:
  167. $config = $PA['fieldConf']['config'];
  168. $foreign_table = $config['foreign_table'];
  169. t3lib_div::loadTCA($foreign_table);
  170. if (t3lib_BEfunc::isTableLocalizable($table)) {
  171. $language = intval($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
  172. }
  173. $minitems = t3lib_div::intInRange($config['minitems'], 0);
  174. $maxitems = t3lib_div::intInRange($config['maxitems'], 0);
  175. if (!$maxitems) {
  176. $maxitems = 100000;
  177. }
  178. // Register the required number of elements:
  179. $this->fObj->requiredElements[$PA['itemFormElName']] = array($minitems, $maxitems, 'imgName' => $table . '_' . $row['uid'] . '_' . $field);
  180. // remember the page id (pid of record) where inline editing started first
  181. // we need that pid for ajax calls, so that they would know where the action takes place on the page structure
  182. if (!isset($this->inlineFirstPid)) {
  183. // if this record is not new, try to fetch the inlineView states
  184. // @TODO: Add checking/cleaning for unused tables, records, etc. to save space in uc-field
  185. if (t3lib_div::testInt($row['uid'])) {
  186. $inlineView = unserialize($GLOBALS['BE_USER']->uc['inlineView']);
  187. $this->inlineView = $inlineView[$table][$row['uid']];
  188. }
  189. // If the parent is a page, use the uid(!) of the (new?) page as pid for the child records:
  190. if ($table == 'pages') {
  191. $liveVersionId = t3lib_BEfunc::getLiveVersionIdOfRecord('pages', $row['uid']);
  192. $this->inlineFirstPid = (is_null($liveVersionId) ? $row['uid'] : $liveVersionId);
  193. // If pid is negative, fetch the previous record and take its pid:
  194. } elseif ($row['pid'] < 0) {
  195. $prevRec = t3lib_BEfunc::getRecord($table, abs($row['pid']));
  196. $this->inlineFirstPid = $prevRec['pid'];
  197. // Take the pid as it is:
  198. } else {
  199. $this->inlineFirstPid = $row['pid'];
  200. }
  201. }
  202. // add the current inline job to the structure stack
  203. $this->pushStructure($table, $row['uid'], $field, $config);
  204. // e.g. data[<table>][<uid>][<field>]
  205. $nameForm = $this->inlineNames['form'];
  206. // e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
  207. $nameObject = $this->inlineNames['object'];
  208. // get the records related to this inline record
  209. $relatedRecords = $this->getRelatedRecords($table, $field, $row, $PA, $config);
  210. // set the first and last record to the config array
  211. $relatedRecordsUids = array_keys($relatedRecords['records']);
  212. $config['inline']['first'] = reset($relatedRecordsUids);
  213. $config['inline']['last'] = end($relatedRecordsUids);
  214. // Tell the browser what we have (using JSON later):
  215. $top = $this->getStructureLevel(0);
  216. $this->inlineData['config'][$nameObject] = array(
  217. 'table' => $foreign_table,
  218. 'md5' => md5($nameObject),
  219. );
  220. $this->inlineData['config'][$nameObject . self::Structure_Separator . $foreign_table] = array(
  221. 'min' => $minitems,
  222. 'max' => $maxitems,
  223. 'sortable' => $config['appearance']['useSortable'],
  224. 'top' => array(
  225. 'table' => $top['table'],
  226. 'uid' => $top['uid'],
  227. ),
  228. );
  229. // Set a hint for nested IRRE and tab elements:
  230. $this->inlineData['nested'][$nameObject] = $this->fObj->getDynNestedStack(FALSE, $this->isAjaxCall);
  231. // if relations are required to be unique, get the uids that have already been used on the foreign side of the relation
  232. if ($config['foreign_unique']) {
  233. // If uniqueness *and* selector are set, they should point to the same field - so, get the configuration of one:
  234. $selConfig = $this->getPossibleRecordsSelectorConfig($config, $config['foreign_unique']);
  235. // Get the used unique ids:
  236. $uniqueIds = $this->getUniqueIds($relatedRecords['records'], $config, $selConfig['type'] == 'groupdb');
  237. $possibleRecords = $this->getPossibleRecords($table, $field, $row, $config, 'foreign_unique');
  238. $uniqueMax = $config['appearance']['useCombination'] || $possibleRecords === FALSE ? -1 : count($possibleRecords);
  239. $this->inlineData['unique'][$nameObject . self::Structure_Separator . $foreign_table] = array(
  240. 'max' => $uniqueMax,
  241. 'used' => $uniqueIds,
  242. 'type' => $selConfig['type'],
  243. 'table' => $config['foreign_table'],
  244. 'elTable' => $selConfig['table'], // element/record table (one step down in hierarchy)
  245. 'field' => $config['foreign_unique'],
  246. 'selector' => $selConfig['selector'],
  247. 'possible' => $this->getPossibleRecordsFlat($possibleRecords),
  248. );
  249. }
  250. // if it's required to select from possible child records (reusable children), add a selector box
  251. if ($config['foreign_selector']) {
  252. // if not already set by the foreign_unique, set the possibleRecords here and the uniqueIds to an empty array
  253. if (!$config['foreign_unique']) {
  254. $possibleRecords = $this->getPossibleRecords($table, $field, $row, $config);
  255. $uniqueIds = array();
  256. }
  257. $selectorBox = $this->renderPossibleRecordsSelector($possibleRecords, $config, $uniqueIds);
  258. $item .= $selectorBox;
  259. }
  260. // wrap all inline fields of a record with a <div> (like a container)
  261. $item .= '<div id="' . $nameObject . '">';
  262. // define how to show the "Create new record" link - if there are more than maxitems, hide it
  263. if ($relatedRecords['count'] >= $maxitems || ($uniqueMax > 0 && $relatedRecords['count'] >= $uniqueMax)) {
  264. $config['inline']['inlineNewButtonStyle'] = 'display: none;';
  265. }
  266. // Render the level links (create new record, localize all, synchronize):
  267. if ($config['appearance']['levelLinksPosition'] != 'none') {
  268. $levelLinks = $this->getLevelInteractionLink('newRecord', $nameObject . self::Structure_Separator . $foreign_table, $config);
  269. if ($language > 0) {
  270. // Add the "Localize all records" link before all child records:
  271. if (isset($config['appearance']['showAllLocalizationLink']) && $config['appearance']['showAllLocalizationLink']) {
  272. $levelLinks .= $this->getLevelInteractionLink('localize', $nameObject . self::Structure_Separator . $foreign_table, $config);
  273. }
  274. // Add the "Synchronize with default language" link before all child records:
  275. if (isset($config['appearance']['showSynchronizationLink']) && $config['appearance']['showSynchronizationLink']) {
  276. $levelLinks .= $this->getLevelInteractionLink('synchronize', $nameObject . self::Structure_Separator . $foreign_table, $config);
  277. }
  278. }
  279. }
  280. // Add the level links before all child records:
  281. if (in_array($config['appearance']['levelLinksPosition'], array('both', 'top'))) {
  282. $item .= $levelLinks;
  283. }
  284. $item .= '<div id="' . $nameObject . '_records">';
  285. $relationList = array();
  286. if (count($relatedRecords['records'])) {
  287. foreach ($relatedRecords['records'] as $rec) {
  288. $item .= $this->renderForeignRecord($row['uid'], $rec, $config);
  289. if (!isset($rec['__virtual']) || !$rec['__virtual']) {
  290. $relationList[] = $rec['uid'];
  291. }
  292. }
  293. }
  294. $item .= '</div>';
  295. // Add the level links after all child records:
  296. if (in_array($config['appearance']['levelLinksPosition'], array('both', 'bottom'))) {
  297. $item .= $levelLinks;
  298. }
  299. // add Drag&Drop functions for sorting to TCEforms::$additionalJS_post
  300. if (count($relationList) > 1 && $config['appearance']['useSortable']) {
  301. $this->addJavaScriptSortable($nameObject . '_records');
  302. }
  303. // publish the uids of the child records in the given order to the browser
  304. $item .= '<input type="hidden" name="' . $nameForm . '" value="' . implode(',', $relationList) . '" class="inlineRecord" />';
  305. // close the wrap for all inline fields (container)
  306. $item .= '</div>';
  307. // on finishing this section, remove the last item from the structure stack
  308. $this->popStructure();
  309. // if this was the first call to the inline type, restore the values
  310. if (!$this->getStructureDepth()) {
  311. unset($this->inlineFirstPid);
  312. }
  313. return $item;
  314. }
  315. /*******************************************************
  316. *
  317. * Regular rendering of forms, fields, etc.
  318. *
  319. *******************************************************/
  320. /**
  321. * Render the form-fields of a related (foreign) record.
  322. *
  323. * @param string $parentUid: The uid of the parent (embedding) record (uid or NEW...)
  324. * @param array $rec: The table record of the child/embedded table (normaly post-processed by t3lib_transferData)
  325. * @param array $config: content of $PA['fieldConf']['config']
  326. * @return string The HTML code for this "foreign record"
  327. */
  328. function renderForeignRecord($parentUid, $rec, $config = array()) {
  329. $foreign_table = $config['foreign_table'];
  330. $foreign_field = $config['foreign_field'];
  331. $foreign_selector = $config['foreign_selector'];
  332. // Register default localization content:
  333. $parent = $this->getStructureLevel(-1);
  334. if (isset($parent['localizationMode']) && $parent['localizationMode'] != FALSE) {
  335. $this->fObj->registerDefaultLanguageData($foreign_table, $rec);
  336. }
  337. // Send a mapping information to the browser via JSON:
  338. // e.g. data[<curTable>][<curId>][<curField>] => data-<pid>-<parentTable>-<parentId>-<parentField>-<curTable>-<curId>-<curField>
  339. $this->inlineData['map'][$this->inlineNames['form']] = $this->inlineNames['object'];
  340. // Set this variable if we handle a brand new unsaved record:
  341. $isNewRecord = t3lib_div::testInt($rec['uid']) ? FALSE : TRUE;
  342. // Set this variable if the record is virtual and only show with header and not editable fields:
  343. $isVirtualRecord = (isset($rec['__virtual']) && $rec['__virtual']);
  344. // If there is a selector field, normalize it:
  345. if ($foreign_selector) {
  346. $rec[$foreign_selector] = $this->normalizeUid($rec[$foreign_selector]);
  347. }
  348. if (!$this->checkAccess(($isNewRecord ? 'new' : 'edit'), $foreign_table, $rec['uid'])) {
  349. return FALSE;
  350. }
  351. // Get the current naming scheme for DOM name/id attributes:
  352. $nameObject = $this->inlineNames['object'];
  353. $appendFormFieldNames = '[' . $foreign_table . '][' . $rec['uid'] . ']';
  354. $objectId = $nameObject . self::Structure_Separator . $foreign_table . self::Structure_Separator . $rec['uid'];
  355. // Put the current level also to the dynNestedStack of TCEforms:
  356. $this->fObj->pushToDynNestedStack('inline', $objectId);
  357. if (!$isVirtualRecord) {
  358. // Get configuration:
  359. $collapseAll = (isset($config['appearance']['collapseAll']) && $config['appearance']['collapseAll']);
  360. $expandAll = (isset($config['appearance']['collapseAll']) && !$config['appearance']['collapseAll']);
  361. $ajaxLoad = (isset($config['appearance']['ajaxLoad']) && !$config['appearance']['ajaxLoad']) ? FALSE : TRUE;
  362. if ($isNewRecord) {
  363. // show this record expanded or collapsed
  364. $isExpanded = ($expandAll || (!$collapseAll ? 1 : 0));
  365. } else {
  366. $isExpanded = ($config['renderFieldsOnly'] || (!$collapseAll && $this->getExpandedCollapsedState($foreign_table, $rec['uid'])) || $expandAll);
  367. }
  368. // Render full content ONLY IF this is a AJAX-request, a new record, the record is not collapsed or AJAX-loading is explicitly turned off
  369. if ($isNewRecord || $isExpanded || !$ajaxLoad) {
  370. $combination = $this->renderCombinationTable($rec, $appendFormFieldNames, $config);
  371. $fields = $this->renderMainFields($foreign_table, $rec);
  372. $fields = $this->wrapFormsSection($fields);
  373. // Replace returnUrl in Wizard-Code, if this is an AJAX call
  374. $ajaxArguments = t3lib_div::_GP('ajax');
  375. if (isset($ajaxArguments[2]) && trim($ajaxArguments[2]) != '') {
  376. $fields = str_replace('P[returnUrl]=%2F' . rawurlencode(TYPO3_mainDir) . '%2Fajax.php', 'P[returnUrl]=' . rawurlencode($ajaxArguments[2]), $fields);
  377. }
  378. } else {
  379. $combination = '';
  380. // This string is the marker for the JS-function to check if the full content has already been loaded
  381. $fields = '<!--notloaded-->';
  382. }
  383. if ($isNewRecord) {
  384. // get the top parent table
  385. $top = $this->getStructureLevel(0);
  386. $ucFieldName = 'uc[inlineView][' . $top['table'] . '][' . $top['uid'] . ']' . $appendFormFieldNames;
  387. // set additional fields for processing for saving
  388. $fields .= '<input type="hidden" name="' . $this->prependFormFieldNames . $appendFormFieldNames . '[pid]" value="' . $rec['pid'] . '"/>';
  389. $fields .= '<input type="hidden" name="' . $ucFieldName . '" value="' . $isExpanded . '" />';
  390. } else {
  391. // set additional field for processing for saving
  392. $fields .= '<input type="hidden" name="' . $this->prependCmdFieldNames . $appendFormFieldNames . '[delete]" value="1" disabled="disabled" />';
  393. }
  394. // if this record should be shown collapsed
  395. if (!$isExpanded) {
  396. $appearanceStyleFields = ' style="display: none;"';
  397. }
  398. }
  399. if ($config['renderFieldsOnly']) {
  400. $out = $fields . $combination;
  401. } else {
  402. // set the record container with data for output
  403. $out = '<div class="t3-form-field-record-inline" id="' . $objectId . '_fields"' . $appearanceStyleFields . '>' . $fields . $combination . '</div>';
  404. $header = $this->renderForeignRecordHeader($parentUid, $foreign_table, $rec, $config, $isVirtualRecord);
  405. $out = '<div class="t3-form-field-header-inline" id="' . $objectId . '_header">' . $header . '</div>' . $out;
  406. // wrap the header, fields and combination part of a child record with a div container
  407. $classMSIE = ($this->fObj->clientInfo['BROWSER'] == 'msie' && $this->fObj->clientInfo['VERSION'] < 8 ? 'MSIE' : '');
  408. $class = 'inlineDiv' . $classMSIE . ($isNewRecord ? ' inlineIsNewRecord' : '');
  409. $out = '<div id="' . $objectId . '_div" class="t3-form-field-container-inline ' . $class . '">' . $out . '</div>';
  410. }
  411. // Remove the current level also from the dynNestedStack of TCEforms:
  412. $this->fObj->popFromDynNestedStack();
  413. return $out;
  414. }
  415. /**
  416. * Wrapper for TCEforms::getMainFields().
  417. *
  418. * @param string $table: The table name
  419. * @param array $row: The record to be rendered
  420. * @return string The rendered form
  421. */
  422. protected function renderMainFields($table, $row) {
  423. // The current render depth of t3lib_TCEforms:
  424. $depth = $this->fObj->renderDepth;
  425. // If there is some information about already rendered palettes of our parent, store this info:
  426. if (isset($this->fObj->palettesRendered[$depth][$table])) {
  427. $palettesRendered = $this->fObj->palettesRendered[$depth][$table];
  428. }
  429. // Render the form:
  430. $content = $this->fObj->getMainFields($table, $row, $depth);
  431. // If there was some info about rendered palettes stored, write it back for our parent:
  432. if (isset($palettesRendered)) {
  433. $this->fObj->palettesRendered[$depth][$table] = $palettesRendered;
  434. }
  435. return $content;
  436. }
  437. /**
  438. * Renders the HTML header for a foreign record, such as the title, toggle-function, drag'n'drop, etc.
  439. * Later on the command-icons are inserted here.
  440. *
  441. * @param string $parentUid: The uid of the parent (embedding) record (uid or NEW...)
  442. * @param string $foreign_table: The foreign_table we create a header for
  443. * @param array $rec: The current record of that foreign_table
  444. * @param array $config: content of $PA['fieldConf']['config']
  445. * @param boolean $isVirtualRecord:
  446. * @return string The HTML code of the header
  447. */
  448. function renderForeignRecordHeader($parentUid, $foreign_table, $rec, $config, $isVirtualRecord = FALSE) {
  449. // Init:
  450. $objectId = $this->inlineNames['object'] . self::Structure_Separator . $foreign_table . self::Structure_Separator . $rec['uid'];
  451. $expandSingle = $config['appearance']['expandSingle'] ? 1 : 0;
  452. // we need the returnUrl of the main script when loading the fields via AJAX-call (to correct wizard code, so include it as 3rd parameter)
  453. $onClick = "return inline.expandCollapseRecord('" . htmlspecialchars($objectId) . "', $expandSingle, '" . rawurlencode(t3lib_div::getIndpEnv('REQUEST_URI')) . "')";
  454. // Pre-Processing:
  455. $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $config, $rec);
  456. $hasForeignLabel = !$isOnSymmetricSide && $config['foreign_label'] ? TRUE : FALSE;
  457. $hasSymmetricLabel = $isOnSymmetricSide && $config['symmetric_label'] ? TRUE : FALSE;
  458. // Get the record title/label for a record:
  459. // render using a self-defined user function
  460. if ($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc']) {
  461. $params = array(
  462. 'table' => $foreign_table,
  463. 'row' => $rec,
  464. 'title' => '',
  465. 'isOnSymmetricSide' => $isOnSymmetricSide,
  466. 'parent' => array(
  467. 'uid' => $parentUid,
  468. 'config' => $config,
  469. ),
  470. );
  471. $null = NULL; // callUserFunction requires a third parameter, but we don't want to give $this as reference!
  472. t3lib_div::callUserFunction($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc'], $params, $null);
  473. $recTitle = $params['title'];
  474. // render the special alternative title
  475. } elseif ($hasForeignLabel || $hasSymmetricLabel) {
  476. $titleCol = $hasForeignLabel ? $config['foreign_label'] : $config['symmetric_label'];
  477. $foreignConfig = $this->getPossibleRecordsSelectorConfig($config, $titleCol);
  478. // Render title for everything else than group/db:
  479. if ($foreignConfig['type'] != 'groupdb') {
  480. $recTitle = t3lib_BEfunc::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol], 0, 0, FALSE);
  481. // Render title for group/db:
  482. } else {
  483. // $recTitle could be something like: "tx_table_123|...",
  484. $valueParts = t3lib_div::trimExplode('|', $rec[$titleCol]);
  485. $itemParts = t3lib_div::revExplode('_', $valueParts[0], 2);
  486. $recTemp = t3lib_befunc::getRecordWSOL($itemParts[0], $itemParts[1]);
  487. $recTitle = t3lib_BEfunc::getRecordTitle($itemParts[0], $recTemp, FALSE);
  488. }
  489. $recTitle = t3lib_BEfunc::getRecordTitlePrep($recTitle);
  490. if (!strcmp(trim($recTitle), '')) {
  491. $recTitle = t3lib_BEfunc::getNoRecordTitle(TRUE);
  492. }
  493. // render the standard
  494. } else {
  495. $recTitle = t3lib_BEfunc::getRecordTitle($foreign_table, $rec, TRUE);
  496. }
  497. $altText = t3lib_BEfunc::getRecordIconAltText($rec, $foreign_table);
  498. $iconImg = t3lib_iconWorks::getSpriteIconForRecord($foreign_table, $rec, array('title' => htmlspecialchars($altText), 'id' => $objectId . '_icon'));
  499. $label = '<span id="' . $objectId . '_label">' . $recTitle . '</span>';
  500. if (!$isVirtualRecord) {
  501. $iconImg = $this->wrapWithAnchor($iconImg, '#', array('onclick' => $onClick));
  502. $label = $this->wrapWithAnchor($label, '#', array('onclick' => $onClick, 'style' => 'display: block;'));
  503. }
  504. $ctrl = $this->renderForeignRecordHeaderControl($parentUid, $foreign_table, $rec, $config, $isVirtualRecord);
  505. // @TODO: Check the table wrapping and the CSS definitions
  506. $header =
  507. '<table cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-right: ' . $this->inlineStyles['margin-right'] . 'px;"' .
  508. ($this->fObj->borderStyle[2] ? ' background="' . htmlspecialchars($this->backPath . $this->fObj->borderStyle[2]) . '"' : '') .
  509. ($this->fObj->borderStyle[3] ? ' class="' . htmlspecialchars($this->fObj->borderStyle[3]) . '"' : '') . '>' .
  510. '<tr class="class-main12"><td width="18" id="' . $objectId . '_iconcontainer">' . $iconImg . '</td><td align="left"><strong>' . $label . '</strong></td><td align="right">' . $ctrl . '</td></tr></table>';
  511. return $header;
  512. }
  513. /**
  514. * Render the control-icons for a record header (create new, sorting, delete, disable/enable).
  515. * Most of the parts are copy&paste from class.db_list_extra.inc and modified for the JavaScript calls here
  516. *
  517. * @param string $parentUid: The uid of the parent (embedding) record (uid or NEW...)
  518. * @param string $foreign_table: The table (foreign_table) we create control-icons for
  519. * @param array $rec: The current record of that foreign_table
  520. * @param array $config: (modified) TCA configuration of the field
  521. * @return string The HTML code with the control-icons
  522. */
  523. function renderForeignRecordHeaderControl($parentUid, $foreign_table, $rec, $config = array(), $isVirtualRecord = FALSE) {
  524. // Initialize:
  525. $cells = array();
  526. $isNewItem = substr($rec['uid'], 0, 3) == 'NEW';
  527. $tcaTableCtrl =& $GLOBALS['TCA'][$foreign_table]['ctrl'];
  528. $tcaTableCols =& $GLOBALS['TCA'][$foreign_table]['columns'];
  529. $isPagesTable = $foreign_table == 'pages' ? TRUE : FALSE;
  530. $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $config, $rec);
  531. $enableManualSorting = $tcaTableCtrl['sortby'] || $config['MM'] || (!$isOnSymmetricSide && $config['foreign_sortby']) || ($isOnSymmetricSide && $config['symmetric_sortby']) ? TRUE : FALSE;
  532. $nameObject = $this->inlineNames['object'];
  533. $nameObjectFt = $nameObject . self::Structure_Separator . $foreign_table;
  534. $nameObjectFtId = $nameObjectFt . self::Structure_Separator . $rec['uid'];
  535. $calcPerms = $GLOBALS['BE_USER']->calcPerms(
  536. t3lib_BEfunc::readPageAccess($rec['pid'], $GLOBALS['BE_USER']->getPagePermsClause(1))
  537. );
  538. // If the listed table is 'pages' we have to request the permission settings for each page:
  539. if ($isPagesTable) {
  540. $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages', $rec['uid']));
  541. }
  542. // This expresses the edit permissions for this particular element:
  543. $permsEdit = ($isPagesTable && ($localCalcPerms & 2)) || (!$isPagesTable && ($calcPerms & 16));
  544. // Controls: Defines which controls should be shown
  545. $enabledControls = $config['appearance']['enabledControls'];
  546. // Hook: Can disable/enable single controls for specific child records:
  547. foreach ($this->hookObjects as $hookObj) {
  548. $hookObj->renderForeignRecordHeaderControl_preProcess($parentUid, $foreign_table, $rec, $config, $isVirtualRecord, $enabledControls);
  549. }
  550. // Icon to visualize that a required field is nested in this inline level:
  551. $cells['required'] = '<img name="' . $nameObjectFtId . '_req" src="clear.gif" width="10" height="10" hspace="4" vspace="3" alt="" />';
  552. if (isset($rec['__create'])) {
  553. $cells['localize.isLocalizable'] = t3lib_iconWorks::getSpriteIcon('actions-edit-localize-status-low', array('title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:localize.isLocalizable', TRUE)));
  554. } elseif (isset($rec['__remove'])) {
  555. $cells['localize.wasRemovedInOriginal'] = t3lib_iconWorks::getSpriteIcon('actions-edit-localize-status-high', array('title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:localize.wasRemovedInOriginal', 1)));
  556. }
  557. // "Info": (All records)
  558. if ($enabledControls['info'] && !$isNewItem) {
  559. $cells['info'] = '<a href="#" onclick="' . htmlspecialchars('top.launchView(\'' . $foreign_table . '\', \'' . $rec['uid'] . '\'); return false;') . '">' .
  560. t3lib_iconWorks::getSpriteIcon('status-dialog-information', array('title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:showInfo', TRUE))) .
  561. '</a>';
  562. }
  563. // If the table is NOT a read-only table, then show these links:
  564. if (!$tcaTableCtrl['readOnly'] && !$isVirtualRecord) {
  565. // "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):
  566. if ($enabledControls['new'] && ($enableManualSorting || $tcaTableCtrl['useColumnsForDefaultValues'])) {
  567. if (
  568. (!$isPagesTable && ($calcPerms & 16)) || // For NON-pages, must have permission to edit content on this parent page
  569. ($isPagesTable && ($calcPerms & 8)) // For pages, must have permission to create new pages here.
  570. ) {
  571. $onClick = "return inline.createNewRecord('" . $nameObjectFt . "','" . $rec['uid'] . "')";
  572. $class = ' class="inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"';
  573. if ($config['inline']['inlineNewButtonStyle']) {
  574. $style = ' style="' . $config['inline']['inlineNewButtonStyle'] . '"';
  575. }
  576. $cells['new'] = '<a href="#" onclick="' . htmlspecialchars($onClick) . '"' . $class . $style . '>' .
  577. t3lib_iconWorks::getSpriteIcon('actions-' . ($isPagesTable ? 'page' : 'document') . '-new',
  578. array(
  579. 'title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:new' . ($isPagesTable ? 'Page' : 'Record'), 1)
  580. )
  581. ) .
  582. '</a>';
  583. }
  584. }
  585. // Drag&Drop Sorting: Sortable handler for script.aculo.us
  586. if ($enabledControls['dragdrop'] && $permsEdit && $enableManualSorting && $config['appearance']['useSortable']) {
  587. $cells['dragdrop'] = t3lib_iconWorks::getSpriteIcon('actions-move-move', array('class' => 'sortableHandle', 'title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.move', TRUE)));
  588. }
  589. // "Up/Down" links
  590. if ($enabledControls['sort'] && $permsEdit && $enableManualSorting) {
  591. $onClick = "return inline.changeSorting('" . $nameObjectFtId . "', '1')"; // Up
  592. $style = $config['inline']['first'] == $rec['uid'] ? 'style="visibility: hidden;"' : '';
  593. $cells['sort.up'] = '<a href="#" onclick="' . htmlspecialchars($onClick) . '" class="sortingUp" ' . $style . '>' .
  594. t3lib_iconWorks::getSpriteIcon('actions-move-up', array('title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:moveUp', TRUE))) .
  595. '</a>';
  596. $onClick = "return inline.changeSorting('" . $nameObjectFtId . "', '-1')"; // Down
  597. $style = $config['inline']['last'] == $rec['uid'] ? 'style="visibility: hidden;"' : '';
  598. $cells['sort.down'] = '<a href="#" onclick="' . htmlspecialchars($onClick) . '" class="sortingDown" ' . $style . '>' .
  599. t3lib_iconWorks::getSpriteIcon('actions-move-down', array('title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:moveDown', TRUE))) .
  600. '</a>';
  601. }
  602. // "Hide/Unhide" links:
  603. $hiddenField = $tcaTableCtrl['enablecolumns']['disabled'];
  604. if ($enabledControls['hide'] && $permsEdit && $hiddenField && $tcaTableCols[$hiddenField] && (!$tcaTableCols[$hiddenField]['exclude'] || $GLOBALS['BE_USER']->check('non_exclude_fields', $foreign_table . ':' . $hiddenField))) {
  605. $onClick = "return inline.enableDisableRecord('" . $nameObjectFtId . "')";
  606. if ($rec[$hiddenField]) {
  607. $cells['hide.unhide'] = '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' .
  608. t3lib_iconWorks::getSpriteIcon(
  609. 'actions-edit-unhide',
  610. array(
  611. 'title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:unHide' . ($isPagesTable ? 'Page' : ''), 1),
  612. 'id' => $nameObjectFtId . '_disabled'
  613. )
  614. ) .
  615. '</a>';
  616. } else {
  617. $cells['hide.hide'] = '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' .
  618. t3lib_iconWorks::getSpriteIcon(
  619. 'actions-edit-hide',
  620. array(
  621. 'title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:hide' . ($isPagesTable ? 'Page' : ''), 1),
  622. 'id' => $nameObjectFtId . '_disabled'
  623. )
  624. ) .
  625. '</a>';
  626. }
  627. }
  628. // "Delete" link:
  629. if ($enabledControls['delete'] && ($isPagesTable && $localCalcPerms & 4 || !$isPagesTable && $calcPerms & 16)) {
  630. $onClick = "inline.deleteRecord('" . $nameObjectFtId . "');";
  631. $cells['delete'] = '<a href="#" onclick="' . htmlspecialchars('if (confirm(' . $GLOBALS['LANG']->JScharCode($GLOBALS['LANG']->getLL('deleteWarning')) . ')) { ' . $onClick . ' } return false;') . '">' .
  632. t3lib_iconWorks::getSpriteIcon('actions-edit-delete', array('title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:delete', TRUE))) .
  633. '</a>';
  634. }
  635. // If this is a virtual record offer a minimized set of icons for user interaction:
  636. } elseif ($isVirtualRecord) {
  637. if ($enabledControls['localize'] && isset($rec['__create'])) {
  638. $onClick = "inline.synchronizeLocalizeRecords('" . $nameObjectFt . "', " . $rec['uid'] . ");";
  639. $cells['localize'] = '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' .
  640. t3lib_iconWorks::getSpriteIcon('actions-document-localize', array('title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:localize', TRUE))) .
  641. '</a>';
  642. }
  643. }
  644. // If the record is edit-locked by another user, we will show a little warning sign:
  645. if ($lockInfo = t3lib_BEfunc::isRecordLocked($foreign_table, $rec['uid'])) {
  646. $cells['locked'] = '<a href="#" onclick="' . htmlspecialchars('alert(' . $GLOBALS['LANG']->JScharCode($lockInfo['msg']) . ');return false;') . '">' .
  647. t3lib_iconWorks::getSpriteIcon('status-warning-in-use', array('title' => htmlspecialchars($lockInfo['msg']))) .
  648. '</a>';
  649. }
  650. // Hook: Post-processing of single controls for specific child records:
  651. foreach ($this->hookObjects as $hookObj) {
  652. $hookObj->renderForeignRecordHeaderControl_postProcess($parentUid, $foreign_table, $rec, $config, $isVirtualRecord, $cells);
  653. }
  654. // Compile items into a DIV-element:
  655. return '
  656. <!-- CONTROL PANEL: ' . $foreign_table . ':' . $rec['uid'] . ' -->
  657. <div class="typo3-DBctrl">' . implode('', $cells) . '</div>';
  658. }
  659. /**
  660. * Render a table with TCEforms, that occurs on a intermediate table but should be editable directly,
  661. * so two tables are combined (the intermediate table with attributes and the sub-embedded table).
  662. * -> This is a direct embedding over two levels!
  663. *
  664. * @param array $rec: The table record of the child/embedded table (normaly post-processed by t3lib_transferData)
  665. * @param string $appendFormFieldNames: The [<table>][<uid>] of the parent record (the intermediate table)
  666. * @param array $config: content of $PA['fieldConf']['config']
  667. * @return string A HTML string with <table> tag around.
  668. */
  669. function renderCombinationTable(&$rec, $appendFormFieldNames, $config = array()) {
  670. $foreign_table = $config['foreign_table'];
  671. $foreign_selector = $config['foreign_selector'];
  672. if ($foreign_selector && $config['appearance']['useCombination']) {
  673. $comboConfig = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector]['config'];
  674. $comboRecord = array();
  675. // If record does already exist, load it:
  676. if ($rec[$foreign_selector] && t3lib_div::testInt($rec[$foreign_selector])) {
  677. $comboRecord = $this->getRecord(
  678. $this->inlineFirstPid,
  679. $comboConfig['foreign_table'],
  680. $rec[$foreign_selector]
  681. );
  682. $isNewRecord = FALSE;
  683. // It is a new record, create a new record virtually:
  684. } else {
  685. $comboRecord = $this->getNewRecord(
  686. $this->inlineFirstPid,
  687. $comboConfig['foreign_table']
  688. );
  689. $isNewRecord = TRUE;
  690. }
  691. // get the TCEforms interpretation of the TCA of the child table
  692. $out = $this->renderMainFields($comboConfig['foreign_table'], $comboRecord);
  693. $out = $this->wrapFormsSection($out, array(), array('class' => 'wrapperAttention'));
  694. // if this is a new record, add a pid value to store this record and the pointer value for the intermediate table
  695. if ($isNewRecord) {
  696. $comboFormFieldName = $this->prependFormFieldNames . '[' . $comboConfig['foreign_table'] . '][' . $comboRecord['uid'] . '][pid]';
  697. $out .= '<input type="hidden" name="' . $comboFormFieldName . '" value="' . $comboRecord['pid'] . '" />';
  698. }
  699. // if the foreign_selector field is also responsible for uniqueness, tell the browser the uid of the "other" side of the relation
  700. if ($isNewRecord || $config['foreign_unique'] == $foreign_selector) {
  701. $parentFormFieldName = $this->prependFormFieldNames . $appendFormFieldNames . '[' . $foreign_selector . ']';
  702. $out .= '<input type="hidden" name="' . $parentFormFieldName . '" value="' . $comboRecord['uid'] . '" />';
  703. }
  704. }
  705. return $out;
  706. }
  707. /**
  708. * Get a selector as used for the select type, to select from all available
  709. * records and to create a relation to the embedding record (e.g. like MM).
  710. *
  711. * @param array $selItems: Array of all possible records
  712. * @param array $conf: TCA configuration of the parent(!) field
  713. * @param array $uniqueIds: The uids that have already been used and should be unique
  714. * @return string A HTML <select> box with all possible records
  715. */
  716. function renderPossibleRecordsSelector($selItems, $conf, $uniqueIds = array()) {
  717. $foreign_table = $conf['foreign_table'];
  718. $foreign_selector = $conf['foreign_selector'];
  719. $selConfig = $this->getPossibleRecordsSelectorConfig($conf, $foreign_selector);
  720. $config = $selConfig['PA']['fieldConf']['config'];
  721. if ($selConfig['type'] == 'select') {
  722. $item = $this->renderPossibleRecordsSelectorTypeSelect($selItems, $conf, $selConfig['PA'], $uniqueIds);
  723. } elseif ($selConfig['type'] == 'groupdb') {
  724. $item = $this->renderPossibleRecordsSelectorTypeGroupDB($conf, $selConfig['PA']);
  725. }
  726. return $item;
  727. }
  728. /**
  729. * Get a selector as used for the select type, to select from all available
  730. * records and to create a relation to the embedding record (e.g. like MM).
  731. *
  732. * @param array $selItems: Array of all possible records
  733. * @param array $conf: TCA configuration of the parent(!) field
  734. * @param array $PA: An array with additional configuration options
  735. * @param array $uniqueIds: The uids that have already been used and should be unique
  736. * @return string A HTML <select> box with all possible records
  737. */
  738. function renderPossibleRecordsSelectorTypeSelect($selItems, $conf, &$PA, $uniqueIds = array()) {
  739. $foreign_table = $conf['foreign_table'];
  740. $foreign_selector = $conf['foreign_selector'];
  741. $PA = array();
  742. $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector];
  743. $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
  744. $PA['fieldTSConfig'] = $this->fObj->setTSconfig($foreign_table, array(), $foreign_selector);
  745. $config = $PA['fieldConf']['config'];
  746. //TODO: $disabled is not present - should be read from config?
  747. $disabled = FALSE;
  748. if (!$disabled) {
  749. // Create option tags:
  750. $opt = array();
  751. $styleAttrValue = '';
  752. foreach ($selItems as $p) {
  753. if ($config['iconsInOptionTags']) {
  754. $styleAttrValue = $this->fObj->optionTagStyle($p[2]);
  755. }
  756. if (!in_array($p[1], $uniqueIds)) {
  757. $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' .
  758. ' style="' . (in_array($p[1], $uniqueIds) ? '' : '') .
  759. ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) : '') . '">' .
  760. htmlspecialchars($p[0]) . '</option>';
  761. }
  762. }
  763. // Put together the selector box:
  764. $selector_itemListStyle = isset($config['itemListStyle']) ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"' : ' style="' . $this->fObj->defaultMultipleSelectorStyle . '"';
  765. $size = intval($conf['size']);
  766. $size = $conf['autoSizeMax'] ? t3lib_div::intInRange(count($selItems) + 1, t3lib_div::intInRange($size, 1), $conf['autoSizeMax']) : $size;
  767. $onChange = "return inline.importNewRecord('" . $this->inlineNames['object'] . self::Structure_Separator . $conf['foreign_table'] . "')";
  768. $item = '
  769. <select id="' . $this->inlineNames['object'] . self::Structure_Separator . $conf['foreign_table'] . '_selector"' .
  770. $this->fObj->insertDefStyle('select') .
  771. ($size ? ' size="' . $size . '"' : '') .
  772. ' onchange="' . htmlspecialchars($onChange) . '"' .
  773. $PA['onFocus'] .
  774. $selector_itemListStyle .
  775. ($conf['foreign_unique'] ? ' isunique="isunique"' : '') . '>
  776. ' . implode('
  777. ', $opt) . '
  778. </select>';
  779. // add a "Create new relation" link for adding new relations
  780. // this is neccessary, if the size of the selector is "1" or if
  781. // there is only one record item in the select-box, that is selected by default
  782. // the selector-box creates a new relation on using a onChange event (see some line above)
  783. $createNewRelationText = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createNewRelation', 1);
  784. $item .=
  785. '<a href="#" onclick="' . htmlspecialchars($onChange) . '" align="abstop">' .
  786. t3lib_iconWorks::getSpriteIcon('actions-document-new', array('title' => $createNewRelationText)) . $createNewRelationText .
  787. '</a>';
  788. // wrap the selector and add a spacer to the bottom
  789. $item = '<div style="margin-bottom: 20px;">' . $item . '</div>';
  790. }
  791. return $item;
  792. }
  793. /**
  794. * Generate a link that opens an element browser in a new window.
  795. * For group/db there is no way o use a "selector" like a <select>|</select>-box.
  796. *
  797. * @param array $conf: TCA configuration of the parent(!) field
  798. * @param array $PA: An array with additional configuration options
  799. * @return string A HTML link that opens an element browser in a new window
  800. */
  801. function renderPossibleRecordsSelectorTypeGroupDB($conf, &$PA) {
  802. $foreign_table = $conf['foreign_table'];
  803. $config = $PA['fieldConf']['config'];
  804. $allowed = $config['allowed'];
  805. $objectPrefix = $this->inlineNames['object'] . self::Structure_Separator . $foreign_table;
  806. $createNewRelationText = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createNewRelation', 1);
  807. $onClick = "setFormValueOpenBrowser('db','" . ('|||' . $allowed . '|' . $objectPrefix . '|inline.checkUniqueElement||inline.importElement') . "'); return false;";
  808. $item =
  809. '<a href="#" onclick="' . htmlspecialchars($onClick) . '">' .
  810. t3lib_iconWorks::getSpriteIcon('actions-insert-record', array('title' => $createNewRelationText)) . $createNewRelationText .
  811. '</a>';
  812. return $item;
  813. }
  814. /**
  815. * Creates the HTML code of a general link to be used on a level of inline children.
  816. * The possible keys for the parameter $type are 'newRecord', 'localize' and 'synchronize'.
  817. *
  818. * @param string $type: The link type, values are 'newRecord', 'localize' and 'synchronize'.
  819. * @param string $objectPrefix: The "path" to the child record to create (e.g. 'data-parentPageId-partenTable-parentUid-parentField-childTable]')
  820. * @param array $conf: TCA configuration of the parent(!) field
  821. * @return string The HTML code of the new link, wrapped in a div
  822. */
  823. protected function getLevelInteractionLink($type, $objectPrefix, $conf = array()) {
  824. $nameObject = $this->inlineNames['object'];
  825. $attributes = array();
  826. switch ($type) {
  827. case 'newRecord':
  828. $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:cm.createnew', 1);
  829. $icon = 'actions-document-new';
  830. $className = 'typo3-newRecordLink';
  831. $attributes['class'] = 'inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'];
  832. $attributes['onclick'] = "return inline.createNewRecord('$objectPrefix')";
  833. if (isset($conf['inline']['inlineNewButtonStyle']) && $conf['inline']['inlineNewButtonStyle']) {
  834. $attributes['style'] = $conf['inline']['inlineNewButtonStyle'];
  835. }
  836. if (isset($conf['appearance']['newRecordLinkAddTitle']) && $conf['appearance']['newRecordLinkAddTitle']) {
  837. $titleAddon = ' ' . $GLOBALS['LANG']->sL($GLOBALS['TCA'][$conf['foreign_table']]['ctrl']['title'], 1);
  838. }
  839. break;
  840. case 'localize':
  841. $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:localizeAllRecords', 1);
  842. $icon = 'actions-document-localize';
  843. $className = 'typo3-localizationLink';
  844. $attributes['onclick'] = "return inline.synchronizeLocalizeRecords('$objectPrefix', 'localize')";
  845. break;
  846. case 'synchronize':
  847. $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:synchronizeWithOriginalLanguage', 1);
  848. $icon = 'actions-document-synchronize';
  849. $className = 'typo3-synchronizationLink';
  850. $attributes['class'] = 'inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'];
  851. $attributes['onclick'] = "return inline.synchronizeLocalizeRecords('$objectPrefix', 'synchronize')";
  852. break;
  853. }
  854. // Create the link:
  855. $icon = ($icon ? t3lib_iconWorks::getSpriteIcon($icon, array('title' => htmlspecialchars($title . $titleAddon))) : '');
  856. $link = $this->wrapWithAnchor($icon . $title . $titleAddon, '#', $attributes);
  857. return '<div' . ($className ? ' class="' . $className . '"' : '') . '>' . $link . '</div>';
  858. }
  859. /**
  860. * Add Sortable functionality using script.acolo.us "Sortable".
  861. *
  862. * @param string $objectId: The container id of the object - elements inside will be sortable
  863. * @return void
  864. */
  865. function addJavaScriptSortable($objectId) {
  866. $this->fObj->additionalJS_post[] = '
  867. inline.createDragAndDropSorting("' . $objectId . '");
  868. ';
  869. }
  870. /*******************************************************
  871. *
  872. * Handling of AJAX calls
  873. *
  874. *******************************************************/
  875. /**
  876. * General processor for AJAX requests concerning IRRE.
  877. * (called by typo3/ajax.php)
  878. *
  879. * @param array $params: additional parameters (not used here)
  880. * @param TYPO3AJAX $ajaxObj: the TYPO3AJAX object of this request
  881. * @return void
  882. */
  883. public function processAjaxRequest($params, $ajaxObj) {
  884. $ajaxArguments = t3lib_div::_GP('ajax');
  885. $ajaxIdParts = explode('::', $GLOBALS['ajaxID'], 2);
  886. if (isset($ajaxArguments) && is_array($ajaxArguments) && count($ajaxArguments)) {
  887. $ajaxMethod = $ajaxIdParts[1];
  888. switch ($ajaxMethod) {
  889. case 'createNewRecord':
  890. case 'synchronizeLocalizeRecords':
  891. case 'getRecordDetails':
  892. $this->isAjaxCall = TRUE;
  893. // Construct runtime environment for Inline Relational Record Editing:
  894. $this->processAjaxRequestConstruct($ajaxArguments);
  895. // Parse the DOM identifier (string), add the levels to the structure stack (array) and load the TCA config:
  896. $this->parseStructureString($ajaxArguments[0], TRUE);
  897. // Render content:
  898. $ajaxObj->setContentFormat('jsonbody');
  899. $ajaxObj->setContent(
  900. call_user_func_array(array(&$this, $ajaxMethod), $ajaxArguments)
  901. );
  902. break;
  903. case 'setExpandedCollapsedState':
  904. $ajaxObj->setContentFormat('jsonbody');
  905. call_user_func_array(array(&$this, $ajaxMethod), $ajaxArguments);
  906. break;
  907. }
  908. }
  909. }
  910. /**
  911. * Construct runtime environment for Inline Relational Record Editing.
  912. * - creates an anoymous SC_alt_doc in $GLOBALS['SOBE']
  913. * - creates a t3lib_TCEforms in $GLOBALS['SOBE']->tceforms
  914. * - sets ourself as reference to $GLOBALS['SOBE']->tceforms->inline
  915. * - sets $GLOBALS['SOBE']->tceforms->RTEcounter to the current situation on client-side
  916. *
  917. * @param array &$ajaxArguments: The arguments to be processed by the AJAX request
  918. * @return void
  919. */
  920. protected function processAjaxRequestConstruct(&$ajaxArguments) {
  921. global $SOBE, $BE_USER, $TYPO3_CONF_VARS;
  922. require_once(PATH_typo3 . 'template.php');
  923. $GLOBALS['LANG']->includeLLFile('EXT:lang/locallang_alt_doc.xml');
  924. // Create a new anonymous object:
  925. $SOBE = new stdClass();
  926. $SOBE->MOD_MENU = array(
  927. 'showPalettes' => '',
  928. 'showDescriptions' => '',
  929. 'disableRTE' => ''
  930. );
  931. // Setting virtual document name
  932. $SOBE->MCONF['name'] = 'xMOD_alt_doc.php';
  933. // CLEANSE SETTINGS
  934. $SOBE->MOD_SETTINGS = t3lib_BEfunc::getModuleData(
  935. $SOBE->MOD_MENU,
  936. t3lib_div::_GP('SET'),
  937. $SOBE->MCONF['name']
  938. );
  939. // Create an instance of the document template object
  940. $SOBE->doc = t3lib_div::makeInstance('template');
  941. $SOBE->doc->backPath = $GLOBALS['BACK_PATH'];
  942. // Initialize TCEforms (rendering the forms)
  943. $SOBE->tceforms = t3lib_div::makeInstance('t3lib_TCEforms');
  944. $SOBE->tceforms->inline = $this;
  945. $SOBE->tceforms->RTEcounter = intval(array_shift($ajaxArguments));
  946. $SOBE->tceforms->initDefaultBEMode();
  947. $SOBE->tceforms->palettesCollapsed = !$SOBE->MOD_SETTINGS['showPalettes'];
  948. $SOBE->tceforms->disableRTE = $SOBE->MOD_SETTINGS['disableRTE'];
  949. $SOBE->tceforms->enableClickMenu = TRUE;
  950. $SOBE->tceforms->enableTabMenu = TRUE;
  951. // Clipboard is initialized:
  952. $SOBE->tceforms->clipObj = t3lib_div::makeInstance('t3lib_clipboard'); // Start clipboard
  953. $SOBE->tceforms->clipObj->initializeClipboard(); // Initialize - reads the clipboard content from the user session
  954. // Setting external variables:
  955. if ($BE_USER->uc['edit_showFieldHelp'] != 'text' && $SOBE->MOD_SETTINGS['showDescriptions']) {
  956. $SOBE->tceforms->edit_showFieldHelp = 'text';
  957. }
  958. }
  959. /**
  960. * Determines and sets several script calls to a JSON array, that would have been executed if processed in non-AJAX mode.
  961. *
  962. * @param array &$jsonArray: Reference of the array to be used for JSON
  963. * @param array $config: The configuration of the IRRE field of the parent record
  964. * @return void
  965. */
  966. protected function getCommonScriptCalls(&$jsonArray, $config) {
  967. // Add data that would have been added at the top of a regular TCEforms call:
  968. if ($headTags = $this->getHeadTags()) {
  969. $jsonArray['headData'] = $headTags;
  970. }
  971. // Add the JavaScript data that would have been added at the bottom of a regular TCEforms call:
  972. $jsonArray['scriptCall'][] = $this->fObj->JSbottom($this->fObj->formName, TRUE);
  973. // If script.aculo.us Sortable is used, update the Observer to know the record:
  974. if ($config['appearance']['useSortable']) {
  975. $jsonArray['scriptCall'][] = "inline.createDragAndDropSorting('" . $this->inlineNames['object'] . "_records');";
  976. }
  977. // if TCEforms has some JavaScript code to be executed, just do it
  978. if ($this->fObj->extJSCODE) {
  979. $jsonArray['scriptCall'][] = $this->fObj->extJSCODE;
  980. }
  981. }
  982. /**
  983. * Generates an error message that transferred as JSON for AJAX calls
  984. *
  985. * @param string $message: The error message to be shown
  986. * @return array The error message in a JSON array
  987. */
  988. protected function getErrorMessageForAJAX($message) {
  989. $jsonArray = array(
  990. 'data' => $message,
  991. 'scriptCall' => array(
  992. 'alert("' . $message . '");'
  993. )
  994. );
  995. return $jsonArray;
  996. }
  997. /**
  998. * Handle AJAX calls to show a new inline-record of the given table.
  999. * Normally this method is never called from inside TYPO3. Always from outside by AJAX.
  1000. *
  1001. * @param string $domObjectId: The calling object in hierarchy, that requested a new record.
  1002. * @param string $foreignUid: If set, the new record should be inserted after that one.
  1003. * @return array An array to be used for JSON
  1004. */
  1005. function createNewRecord($domObjectId, $foreignUid = 0) {
  1006. // the current table - for this table we should add/import records
  1007. $current = $this->inlineStructure['unstable'];
  1008. // the parent table - this table embeds the current table
  1009. $parent = $this->getStructureLevel(-1);
  1010. // get TCA 'config' of the parent table
  1011. if (!$this->checkConfiguration($parent['config'])) {
  1012. return $this->getErrorMessageForAJAX('Wrong configuration in table ' . $parent['table']);
  1013. }
  1014. $config = $parent['config'];
  1015. $collapseAll = (isset($config['appearance']['collapseAll']) && $config['appearance']['collapseAll']);
  1016. $expandSingle = (isset($config['appearance']['expandSingle']) && $config['appearance']['expandSingle']);
  1017. // Put the current level also to the dynNestedStack of TCEforms:
  1018. $this->fObj->pushToDynNestedStack('inline', $this->inlineNames['object']);
  1019. // dynamically create a new record using t3lib_transferData
  1020. if (!$foreignUid || !t3lib_div::testInt($foreignUid) || $config['foreign_selector']) {
  1021. $record = $this->getNewRecord($this->inlineFirstPid, $current['table']);
  1022. // Set language of new child record to the language of the parent record:
  1023. if ($config['localizationMode'] == 'select') {
  1024. $parentRecord = $this->getRecord(0, $parent['table'], $parent['uid']);
  1025. $parentLanguageField = $GLOBALS['TCA'][$parent['table']]['ctrl']['languageField'];
  1026. $childLanguageField = $GLOBALS['TCA'][$current['table']]['ctrl']['languageField'];
  1027. if ($parentRecord[$parentLanguageField] > 0) {
  1028. $record[$childLanguageField] = $parentRecord[$parentLanguageField];
  1029. }
  1030. }
  1031. // dynamically import an existing record (this could be a call from a select box)
  1032. } else {
  1033. $record = $this->getRecord($this->inlineFirstPid, $current['table'], $foreignUid);
  1034. }
  1035. // now there is a foreign_selector, so there is a new record on the intermediate table, but
  1036. // this intermediate table holds a field, which is responsible for the foreign_selector, so
  1037. // we have to set this field to the uid we get - or if none, to a new uid
  1038. if ($config['foreign_selector'] && $foreignUid) {
  1039. $selConfig = $this->getPossibleRecordsSelectorConfig($config, $config['foreign_selector']);
  1040. // For a selector of type group/db, prepend the tablename (<tablename>_<uid>):
  1041. $record[$config['foreign_selector']] = $selConfig['type'] != 'groupdb' ? '' : $selConfig['table'] . '_';
  1042. $record[$config['foreign_selector']] .= $foreignUid;
  1043. }
  1044. // the HTML-object-id's prefix of the dynamically created record
  1045. $objectPrefix = $this->inlineNames['object'] . self::Structure_Separator . $current['table'];
  1046. $objectId = $objectPrefix . self::Structure_Separator . $record['uid'];
  1047. // render the foreign record that should passed back to browser
  1048. $item = $this->renderForeignRecord($parent['uid'], $record, $config);
  1049. if ($item === FALSE) {
  1050. return $this->getErrorMessageForAJAX('Access denied');
  1051. }
  1052. if (!$current['uid']) {
  1053. $jsonArray = array(
  1054. 'data' => $item,
  1055. 'scriptCall' => array(
  1056. "inline.domAddNewRecord('bottom','" . $this->inlineNames['object'] . "_records','$objectPrefix',json.data);",
  1057. "inline.memorizeAddRecord('$objectPrefix','" . $record['uid'] . "',null,'$foreignUid');"
  1058. )
  1059. );
  1060. // append the HTML data after an existing record in the container
  1061. } else {
  1062. $jsonArray = array(
  1063. 'data' => $item,
  1064. 'scriptCall' => array(
  1065. "inline.domAddNewRecord('after','" . $domObjectId . '_div' . "','$objectPrefix',json.data);",
  1066. "inline.memorizeAddRecord('$objectPrefix','" . $record['uid'] . "','" . $current['uid'] . "','$foreignUid');"
  1067. )
  1068. );
  1069. }
  1070. $this->getCommonScriptCalls($jsonArray, $config);
  1071. // Collapse all other records if requested:
  1072. if (!$collapseAll && $expandSingle) {
  1073. $jsonArray['scriptCall'][] = "inline.collapseAllRecords('$objectId', '$objectPrefix', '" . $record['uid'] . "');";
  1074. }
  1075. // tell the browser to scroll to the newly created record
  1076. $jsonArray['scriptCall'][] = "Element.scrollTo('" . $objectId . "_div');";
  1077. // fade out and fade in the new record in the browser view to catch the user's eye
  1078. $jsonArray['scriptCall'][] = "inline.fadeOutFadeIn('" . $objectId . "_div');";
  1079. // Remove the current level also from the dynNestedStack of TCEforms:
  1080. $this->fObj->popFromDynNestedStack();
  1081. // Return the JSON array:
  1082. return $jsonArray;
  1083. }
  1084. /**
  1085. * Handle AJAX calls to localize all records of a parent, localize a single record or to synchronize with the original language parent.
  1086. *
  1087. * @param string $domObjectId: The calling object in hierarchy, that requested a new record.
  1088. * @param mixed $type: Defines the type 'localize' or 'synchronize' (string) or a single uid to be localized (integer)
  1089. * @return array An array to be used for JSON
  1090. */
  1091. protected function synchronizeLocalizeRecords($domObjectId, $type) {
  1092. $jsonArray = FALSE;
  1093. if (t3lib_div::inList('localize,synchronize', $type) || t3lib_div::testInt($type)) {
  1094. // The current level:
  1095. $current = $this->inlineStructure['unstable'];
  1096. // The parent level:
  1097. $parent = $this->getStructureLevel(-1);
  1098. $parentRecord = $this->getRecord(0, $parent['table'], $parent['uid']);
  1099. $cmd = array();
  1100. $cmd[$parent['table']][$parent['uid']]['inlineLocalizeSynchronize'] = $parent['field'] . ',' . $type;
  1101. /* @var t3lib_TCEmain */
  1102. $tce = t3lib_div::makeInstance('t3lib_TCEmain');
  1103. $tce->stripslashes_values = FALSE;
  1104. $tce->start(array(), $cmd);
  1105. $tce->process_cmdmap();
  1106. $newItemList = $tce->registerDBList[$parent['table']][$parent['uid']][$parent['field']];
  1107. unset($tce);
  1108. $jsonArray = $this->getExecuteChangesJsonArray($parentRecord[$parent['field']], $newItemList);
  1109. $this->getCommonScriptCalls($jsonArray, $parent['config']);
  1110. }
  1111. return $jsonArray;
  1112. }
  1113. /**
  1114. * Handle AJAX calls to dynamically load the form fields of a given record.
  1115. * (basically a copy of "createNewRecord")
  1116. * Normally this method is never called from inside TYPO3. Always from outside by AJAX.
  1117. *
  1118. * @param string $domObjectId: The calling object in hierarchy, that requested a new record.
  1119. * @return array An array to be used for JSON
  1120. */
  1121. function getRecordDetails($domObjectId) {
  1122. // the current table - for this table we should add/import records
  1123. $current = $this->inlineStructure['unstable'];
  1124. // the parent table - this table embeds the current table
  1125. $parent = $this->getStructureLevel(-1);
  1126. // get TCA 'config' of the parent table
  1127. if (!$this->checkConfiguration($parent['config'])) {
  1128. return $this->getErrorMessageForAJAX('Wrong configuration in table ' . $parent['table']);
  1129. }
  1130. $config = $parent['config'];
  1131. // set flag in config so that only the fields are rendered
  1132. $config['renderFieldsOnly'] = TRUE;
  1133. $collapseAll = (isset($config['appearance']['collapseAll']) && $config['appearance']['collapseAll']);
  1134. $expandSingle = (isset($config['appearance']['expandSingle']) && $config['appearance']['expandSingle']);
  1135. // Put the current level also to the dynNestedStack of TCEforms:
  1136. $this->fObj->pushToDynNestedStack('inline', $this->inlineNames['object']);
  1137. $record = $this->getRecord($this->inlineFirstPid, $current['table'], $current['uid']);
  1138. // the HTML-object-id's prefix of the dynamically created record
  1139. $objectPrefix = $this->inlineNames['object'] . self::Structure_Separator . $current['table'];
  1140. $objectId = $objectPrefix . self::Structure_Separator . $record['uid'];
  1141. $item = $this->renderForeignRecord($parent['uid'], $record, $config);
  1142. if ($item === FALSE) {
  1143. return $this->getErrorMessageForAJAX('Access denied');
  1144. }
  1145. $jsonArray = array(
  1146. 'data' => $item,
  1147. 'scriptCall' => array(
  1148. 'inline.domAddRecordDetails(\'' . $domObjectId . '\',\'' . $objectPrefix . '\',' . ($expandSingle ? '1' : '0') . ',json.data);',
  1149. )
  1150. );
  1151. $this->getCommonScriptCalls($jsonArray, $config);
  1152. // Collapse all other records if requested:
  1153. if (!$collapseAll && $expandSingle) {
  1154. $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(\'' . $objectId . '\',\'' . $objectPrefix . '\',\'' . $record['uid'] . '\');';
  1155. }
  1156. // Remove the current level also from the dynNestedStack of TCEforms:
  1157. $this->fObj->popFromDynNestedStack();
  1158. // Return the JSON array:
  1159. return $jsonArray;
  1160. }
  1161. /**
  1162. * Generates a JSON array which executes the changes and thus updates the forms view.
  1163. *
  1164. * @param string $oldItemList: List of related child reocrds before changes were made (old)
  1165. * @param string $newItemList: List of related child records after changes where made (new)
  1166. * @return array An array to be used for JSON
  1167. */
  1168. protected function getExecuteChangesJsonArray($oldItemList, $newItemList) {
  1169. $parent = $this->getStructureLevel(-1);
  1170. $current = $this->inlineStructure['unstable'];
  1171. $jsonArray = array('scriptCall' => array());
  1172. $jsonArrayScriptCall =& $jsonArray['scriptCall'];
  1173. $nameObject = $this->inlineNames['object'];
  1174. $nameObjectForeignTable = $nameObject . self::Structure_Separator . $current['table'];
  1175. // Get the name of the field pointing to the original record:
  1176. $transOrigPointerField = $GLOBALS['TCA'][$current['table']]['ctrl']['transOrigPointerField'];
  1177. // Get the name of the field used as foreign selector (if any):
  1178. $foreignSelector = (isset($parent['config']['foreign_selector']) && $parent['config']['foreign_selector'] ? $parent['config']['foreign_selector'] : FALSE);
  1179. // Convert lists to array with uids of child records:
  1180. $oldItems = $this->getRelatedRecordsUidArray($oldItemList);
  1181. $newItems = $this->getRelatedRecordsUidArray($newItemList);
  1182. // Determine the items that were localized or localized:
  1183. $removedItems = array_diff($oldItems, $newItems);
  1184. $localizedItems = array_diff($newItems, $oldItems);
  1185. // Set the items that should be removed in the forms view:
  1186. foreach ($removedItems as $item) {
  1187. $jsonArrayScriptCall[] = "inline.deleteRecord('" . $nameObjectForeignTable . self::Structure_Separator . $item . "', {forceDirectRemoval: true});";
  1188. }
  1189. // Set the items that should be added in the forms view:
  1190. foreach ($localizedItems as $item) {
  1191. $row = $this->getRecord($this->inlineFirstPid, $current['table'], $item);
  1192. $selectedValue = ($foreignSelector ? "'" . $row[$foreignSelector] . "'" : 'null');
  1193. $data .= $this->renderForeignRecord($parent['uid'], $row, $parent['config']);
  1194. $jsonArrayScriptCall[] = "inline.memorizeAddRecord('$nameObjectForeignTable', '" . $item . "', null, $selectedValue);";
  1195. // Remove possible virtual records in the form which showed that a child records could be localized:
  1196. if (isset($row[$transOrigPointerField]) && $row[$transOrigPointerField]) {
  1197. $jsonArrayScriptCall[] = "inline.fadeAndRemove('" . $nameObjectForeignTable . self::Structure_Separator . $row[$transOrigPointerField] . '_div' . "');";
  1198. }
  1199. }
  1200. if ($data) {
  1201. $data = $GLOBALS['LANG']->csConvObj->utf8_encode($data, $GLOBALS['LANG']->charSet);
  1202. $jsonArray['data'] = $data;
  1203. array_unshift(
  1204. $jsonArrayScriptCall,
  1205. "inline.domAddNewRecord('bottom', '" . $nameObject . "_records', '$nameObjectForeignTable', json.data);"
  1206. );
  1207. }
  1208. return $jsonArray;
  1209. }
  1210. /**
  1211. * Save the expanded/collapsed state of a child record in the BE_USER->uc.
  1212. *
  1213. * @param string $domObjectId: The calling object in hierarchy, that requested a new record.
  1214. * @param string $expand: Whether this record is expanded.
  1215. * @param string $collapse: Whether this record is collapsed.
  1216. * @return void
  1217. */
  1218. function setExpandedCollapsedState($domObjectId, $expand, $collapse) {
  1219. // parse the DOM identifier (string), add the levels to the structure stack (array), but don't load TCA config
  1220. $this->parseStructureString($domObjectId, FALSE);
  1221. // the current table - for this table we should add/import records
  1222. $current = $this->inlineStructure['unstable'];
  1223. // the top parent table - this table embeds the current table
  1224. $top = $this->getStructureLevel(0);
  1225. // only do some action if the top record and the current record were saved before
  1226. if (t3lib_div::testInt($top['uid'])) {
  1227. $inlineView = (array) unserialize($GLOBALS['BE_USER']->uc['inlineView']);
  1228. $inlineViewCurrent =& $inlineView[$top['table']][$top['uid']];
  1229. $expandUids = t3lib_div::trimExplode(',', $expand);
  1230. $collapseUids = t3lib_div::trimExplode(',', $collapse);
  1231. // set records to be expanded
  1232. foreach ($expandUids as $uid) {
  1233. $inlineViewCurrent[$current['table']][] = $uid;
  1234. }
  1235. // set records to be collapsed
  1236. foreach ($collapseUids as $uid) {
  1237. $inlineViewCurrent[$current['table']] = $this->removeFromArray($uid, $inlineViewCurrent[$current['table']]);
  1238. }
  1239. // save states back to database
  1240. if (is_array($inlineViewCurrent[$current['table']])) {
  1241. $inlineViewCurrent[$current['table']] = array_unique($inlineViewCurrent[$current['table']]);
  1242. $GLOBALS['BE_USER']->uc['inlineView'] = serialize($inlineView);
  1243. $GLOBALS['BE_USER']->writeUC();
  1244. }
  1245. }
  1246. }
  1247. /*******************************************************
  1248. *
  1249. * Get data from database and handle relations
  1250. *
  1251. *******************************************************/
  1252. /**
  1253. * Get the related records of the embedding item, this could be 1:n, m:n.
  1254. * Returns an associative array with the keys records and count. 'count' contains only real existing records on the current parent record.
  1255. *
  1256. * @param string $table: The table name of the record
  1257. * @param string $field: The field name which this element is supposed to edit
  1258. * @param array $row: The record data array where the value(s) for the field can be found
  1259. * @param array $PA: An array with additional configuration options.
  1260. * @param array $config: (Redundant) content of $PA['fieldConf']['config'] (for convenience)
  1261. * @return array The records related to the parent item as associative array.
  1262. */
  1263. function getRelatedRecords($table, $field, $row, &$PA, $config) {
  1264. $records = array();
  1265. $pid = $row['pid'];
  1266. $elements = $PA['itemFormElValue'];
  1267. $foreignTable = $config['foreign_table'];
  1268. $localizationMode = t3lib_BEfunc::getInlineLocalizationMode($table, $config);
  1269. if ($localizationMode != FALSE) {
  1270. $language = intval($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
  1271. $transOrigPointer = intval($row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']]);
  1272. if ($language > 0 && $transOrigPointer) {
  1273. // Localization in mode 'keep', isn't a real localization, but keeps the children of the original parent record:
  1274. if ($localizationMode == 'keep') {
  1275. $transOrigRec = $this->getRecord(0, $table, $transOrigPointer);
  1276. $elements = $transOrigRec[$field];
  1277. $pid = $transOrigRec['pid'];
  1278. // Localization in modes 'select', 'all' or 'sync' offer a dynamic localization and synchronization with the original language record:
  1279. } elseif ($localizationMode == 'select') {
  1280. $transOrigRec = $this->getRecord(0, $table, $transOrigPointer);
  1281. $pid = $transOrigRec['pid'];
  1282. $recordsOriginal = $this->getRelatedRecordsArray($pid, $foreignTable, $transOrigRec[$field]);
  1283. }
  1284. }
  1285. }
  1286. $records = $this->getRelatedRecordsArray($pid, $foreignTable, $elements);
  1287. $relatedRecords = array('records' => $records, 'count' => count($records));
  1288. // Merge original language with current localization and show differences:
  1289. if (is_array($recordsOriginal)) {
  1290. $options = array(
  1291. 'showPossible' => (isset($config['appearance']['showPossibleLocalizationRecords']) && $config['appearance']['showPossibleLocalizationRecords']),
  1292. 'showRemoved' => (isset($config['appearance']['showRemovedLocalizationRecords']) && $config['appearance']['showRemovedLocalizationRecords']),
  1293. );
  1294. if ($options['showPossible'] || $options['showRemoved']) {
  1295. $relatedRecords['records'] = $this->getLocalizationDifferences($foreignTable, $options, $recordsOriginal, $records);
  1296. }
  1297. }
  1298. return $relatedRecords;
  1299. }
  1300. /**
  1301. * Gets the related records of the embedding item, this could be 1:n, m:n.
  1302. *
  1303. * @param integer $pid: The pid of the parent record
  1304. * @param string $table: The table name of the record
  1305. * @param string $itemList: The list of related child records
  1306. * @return array The records related to the parent item
  1307. */
  1308. protected function getRelatedRecordsArray($pid, $table, $itemList) {
  1309. $records = array();
  1310. $itemArray = $this->getRelatedRecordsUidArray($itemList);
  1311. // Perform modification of the selected items array:
  1312. foreach ($itemArray as $uid) {
  1313. // Get the records for this uid using t3lib_transferdata:
  1314. if ($record = $this->getRecord($pid, $table, $uid)) {
  1315. $records[$uid] = $record;
  1316. }
  1317. }
  1318. return $records;
  1319. }
  1320. /**
  1321. * Gets an array with the uids of related records out of a list of items.
  1322. * This list could contain more information than required. This methods just
  1323. * extracts the uids.
  1324. *
  1325. * @param string $itemList: The list of related child records
  1326. * @return array An array with uids
  1327. */
  1328. protected function getRelatedRecordsUidArray($itemList) {
  1329. $itemArray = t3lib_div::trimExplode(',', $itemList, 1);
  1330. // Perform modification of the selected items array:
  1331. foreach ($itemArray as $key => &$value) {
  1332. $parts = explode('|', $value, 2);
  1333. $value = $parts[0];
  1334. }
  1335. return $itemArray;
  1336. }
  1337. /**
  1338. * Gets the difference between current localized structure and the original language structure.
  1339. * If there are records which once were localized but don't exist in the original version anymore, the record row is marked with '__remove'.
  1340. * If there are records which can be localized and exist only in the original version, the record row is marked with '__create' and '__virtual'.
  1341. *
  1342. * @param string $table: The table name of the parent records
  1343. * @param array $options: Options defining what kind of records to display
  1344. * @param array $recordsOriginal: The uids of the child records of the original language
  1345. * @param array $recordsLocalization: The uids of the child records of the current localization
  1346. * @return array Merged array of uids of the child records of both versions
  1347. */
  1348. protected function getLocalizationDifferences($table, array $options, array $recordsOriginal, array $recordsLocalization) {
  1349. $records = array();
  1350. $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
  1351. // Compare original to localized version of the records:
  1352. foreach ($recordsLocalization as $uid => $row) {
  1353. // If the record points to a original translation which doesn't exist anymore, it could be removed:
  1354. if (isset($row[$transOrigPointerField]) && $row[$transOrigPointerField] > 0) {
  1355. $transOrigPointer = $row[$transOrigPointerField];
  1356. if (isset($recordsOriginal[$transOrigPointer])) {
  1357. unset($recordsOriginal[$transOrigPointer]);
  1358. } elseif ($options['showRemoved']) {
  1359. $row['__remove'] = TRUE;
  1360. }
  1361. }
  1362. $records[$uid] = $row;
  1363. }
  1364. // Process the remaining records in the original unlocalized parent:
  1365. if ($options['showPossible']) {
  1366. foreach ($recordsOriginal as $uid => $row) {
  1367. $row['__create'] = TRUE;
  1368. $row['__virtual'] = TRUE;
  1369. $records[$uid] = $row;
  1370. }
  1371. }
  1372. return $records;
  1373. }
  1374. /**
  1375. * Get possible records.
  1376. * Copied from TCEform and modified.
  1377. *
  1378. * @param string The table name of the record
  1379. * @param string The field name which this element is supposed to edit
  1380. * @param array The record data array where the value(s) for the field can be found
  1381. * @param array An array with additional configuration options.
  1382. * @param string $checkForConfField: For which field in the foreign_table the possible records should be fetched
  1383. * @return mixed Array of possible record items; false if type is "group/db", then everything could be "possible"
  1384. */
  1385. function getPossibleRecords($table, $field, $row, $conf, $checkForConfField = 'foreign_selector') {
  1386. // ctrl configuration from TCA:
  1387. $tcaTableCtrl = $GLOBALS['TCA'][$table]['ctrl'];
  1388. // Field configuration from TCA:
  1389. $foreign_table = $conf['foreign_table'];
  1390. $foreign_check = $conf[$checkForConfField];
  1391. $foreignConfig = $this->getPossibleRecordsSelectorConfig($conf, $foreign_check);
  1392. $PA = $foreignConfig['PA'];
  1393. $config = $PA['fieldConf']['config'];
  1394. if ($foreignConfig['type'] == 'select') {
  1395. // Getting the selector box items from the system
  1396. $selItems = $this->fObj->addSelectOptionsToItemArray(
  1397. $this->fObj->initItemArray($PA['fieldConf']),
  1398. $PA['fieldConf'],
  1399. $this->fObj->setTSconfig($table, $row),
  1400. $field
  1401. );
  1402. // Possibly filter some items:
  1403. $keepItemsFunc = create_function('$value', 'return $value[1];');
  1404. $selItems = t3lib_div::keepItemsInArray($selItems, $PA['fieldTSConfig']['keepItems'], $keepItemsFunc);
  1405. // Possibly add some items:
  1406. $selItems = $this->fObj->addItems($selItems, $PA['fieldTSConfig']['addItems.']);
  1407. if (isset($config['itemsProcFunc']) && $config['itemsProcFunc']) {
  1408. $selItems = $this->fObj->procItems($selItems, $PA['fieldTSConfig']['itemsProcFunc.'], $config, $table, $row, $field);
  1409. }
  1410. // Possibly remove some items:
  1411. $removeItems = t3lib_div::trimExplode(',', $PA['fieldTSConfig']['removeItems'], 1);
  1412. foreach ($selItems as $tk => $p) {
  1413. // Checking languages and authMode:
  1414. $languageDeny = $tcaTableCtrl['languageField'] && !strcmp($tcaTableCtrl['languageField'], $field) && !$GLOBALS['BE_USER']->checkLanguageAccess($p[1]);
  1415. $authModeDeny = $config['form_type'] == 'select' && $config['authMode'] && !$GLOBALS['BE_USER']->checkAuthMode($table, $field, $p[1], $config['authMode']);
  1416. if (in_array($p[1], $removeItems) || $languageDeny || $authModeDeny) {
  1417. unset($selItems[$tk]);
  1418. } elseif (isset($PA['fieldTSConfig']['altLabels.'][$p[1]])) {
  1419. $selItems[$tk][0] = $this->fObj->sL($PA['fieldTSConfig']['altLabels.'][$p[1]]);
  1420. }
  1421. // Removing doktypes with no access:
  1422. if (($table === 'pages' || $table === 'pages_language_overlay') && $field === 'doktype') {
  1423. if (!($GLOBALS['BE_USER']->isAdmin() || t3lib_div::inList($GLOBALS['BE_USER']->groupData['pagetypes_select'], $p[1]))) {
  1424. unset($selItems[$tk]);
  1425. }
  1426. }
  1427. }
  1428. } else {
  1429. $selItems = FALSE;
  1430. }
  1431. return $selItems;
  1432. }
  1433. /**
  1434. * Gets the uids of a select/selector that should be unique an have already been used.
  1435. *
  1436. * @param array $records: All inline records on this level
  1437. * @param array $conf: The TCA field configuration of the inline field to be rendered
  1438. * @param boolean $splitValue: for usage with group/db, values come like "tx_table_123|Title%20abc", but we need "tx_table" and "123"
  1439. * @return array The uids, that have been used already and should be used unique
  1440. */
  1441. function getUniqueIds($records, $conf = array(), $splitValue = FALSE) {
  1442. $uniqueIds = array();
  1443. if (isset($conf['foreign_unique']) && $conf['foreign_unique'] && count($records)) {
  1444. foreach ($records as $rec) {
  1445. // Skip virtual records (e.g. shown in localization mode):
  1446. if (!isset($rec['__virtual']) || !$rec['__virtual']) {
  1447. $value = $rec[$conf['foreign_unique']];
  1448. // Split the value and extract the table and uid:
  1449. if ($splitValue) {
  1450. $valueParts = t3lib_div::trimExplode('|', $value);
  1451. $itemParts = explode('_', $valueParts[0]);
  1452. $value = array(
  1453. 'uid' => array_pop($itemParts),
  1454. 'table' => implode('_', $itemParts)
  1455. );
  1456. }
  1457. $uniqueIds[$rec['uid']] = $value;
  1458. }
  1459. }
  1460. }
  1461. return $uniqueIds;
  1462. }
  1463. /**
  1464. * Determines the corrected pid to be used for a new record.
  1465. * The pid to be used can be defined by a Page TSconfig.
  1466. *
  1467. * @param string $table: The table name
  1468. * @param integer $parentPid: The pid of the parent record
  1469. * @return integer The corrected pid to be used for a new record
  1470. */
  1471. protected function getNewRecordPid($table, $parentPid = NULL) {
  1472. $newRecordPid = $this->inlineFirstPid;
  1473. $pageTS = t3lib_beFunc::getPagesTSconfig($parentPid, TRUE);
  1474. if (isset($pageTS['TCAdefaults.'][$table . '.']['pid']) && t3lib_div::testInt($pageTS['TCAdefaults.'][$table . '.']['pid'])) {
  1475. $newRecordPid = $pageTS['TCAdefaults.'][$table . '.']['pid'];
  1476. } elseif (isset($parentPid) && t3lib_div::testInt($parentPid)) {
  1477. $newRecordPid = $parentPid;
  1478. }
  1479. return $newRecordPid;
  1480. }
  1481. /**
  1482. * Get a single record row for a TCA table from the database.
  1483. * t3lib_transferData is used for "upgrading" the values, especially the relations.
  1484. *
  1485. * @param integer $pid: The pid of the page the record should be stored (only relevant for NEW records)
  1486. * @param string $table: The table to fetch data from (= foreign_table)
  1487. * @param string $uid: The uid of the record to fetch, or the pid if a new record should be created
  1488. * @param string $cmd: The command to perform, empty or 'new'
  1489. * @return array A record row from the database post-processed by t3lib_transferData
  1490. */
  1491. function getRecord($pid, $table, $uid, $cmd = '') {
  1492. // Fetch workspace version of a record (if any):
  1493. if ($cmd !== 'new' && $GLOBALS['BE_USER']->workspace !== 0) {
  1494. $workspaceVersion = t3lib_BEfunc::getWorkspaceVersionOfRecord($GLOBALS['BE_USER']->workspace, $table, $uid, 'uid');
  1495. if ($workspaceVersion !== FALSE) {
  1496. $uid = $workspaceVersion['uid'];
  1497. }
  1498. }
  1499. $trData = t3lib_div::makeInstance('t3lib_transferData');
  1500. $trData->addRawData = TRUE;
  1501. $trData->lockRecords = 1;
  1502. $trData->disableRTE = $GLOBALS['SOBE']->MOD_SETTINGS['disableRTE'];
  1503. // if a new record should be created
  1504. $trData->fetchRecord($table, $uid, ($cmd === 'new' ? 'new' : ''));
  1505. reset($trData->regTableItems_data);
  1506. $rec = current($trData->regTableItems_data);
  1507. return $rec;
  1508. }
  1509. /**
  1510. * Wrapper. Calls getRecord in case of a new record should be created.
  1511. *
  1512. * @param integer $pid: The pid of the page the record should be stored (only relevant for NEW records)
  1513. * @param string $table: The table to fetch data from (= foreign_table)
  1514. * @return array A record row from the database post-processed by t3lib_transferData
  1515. */
  1516. function getNewRecord($pid, $table) {
  1517. $rec = $this->getRecord($pid, $table, $pid, 'new');
  1518. $rec['uid'] = uniqid('NEW');
  1519. $rec['pid'] = $this->getNewRecordPid($table, $pid);
  1520. return $rec;
  1521. }
  1522. /*******************************************************
  1523. *
  1524. * Structure stack for handling inline objects/levels
  1525. *
  1526. *******************************************************/
  1527. /**
  1528. * Add a new level on top of the structure stack. Other functions can access the
  1529. * stack and determine, if there's possibly a endless loop.
  1530. *
  1531. * @param string $table: The table name of the record
  1532. * @param string $uid: The uid of the record that embeds the inline data
  1533. * @param string $field: The field name which this element is supposed to edit
  1534. * @param array $config: The TCA-configuration of the inline field
  1535. * @return void
  1536. */
  1537. function pushStructure($table, $uid, $field = '', $config = array()) {
  1538. $this->inlineStructure['stable'][] = array(
  1539. 'table' => $table,
  1540. 'uid' => $uid,
  1541. 'field' => $field,
  1542. 'config' => $config,
  1543. 'localizationMode' => t3lib_BEfunc::getInlineLocalizationMode($table, $config),
  1544. );
  1545. $this->updateStructureNames();
  1546. }
  1547. /**
  1548. * Remove the item on top of the structure stack and return it.
  1549. *
  1550. * @return array The top item of the structure stack - array(<table>,<uid>,<field>,<config>)
  1551. */
  1552. function popStructure() {
  1553. if (count($this->inlineStructure['stable'])) {
  1554. $popItem = array_pop($this->inlineStructure['stable']);
  1555. $this->updateStructureNames();
  1556. }
  1557. return $popItem;
  1558. }
  1559. /**
  1560. * For common use of DOM object-ids and form field names of a several inline-level,
  1561. * these names/identifiers are preprocessed and set to $this->inlineNames.
  1562. * This function is automatically called if a level is pushed to or removed from the
  1563. * inline structure stack.
  1564. *
  1565. * @return void
  1566. */
  1567. function updateStructureNames() {
  1568. $current = $this->getStructureLevel(-1);
  1569. // if there are still more inline levels available
  1570. if ($current !== FALSE) {
  1571. $this->inlineNames = array(
  1572. 'form' => $this->prependFormFieldNames . $this->getStructureItemName($current, self::Disposal_AttributeName),
  1573. 'object' => $this->prependNaming . self::Structure_Separator . $this->inlineFirstPid . self::Structure_Separator . $this->getStructurePath(),
  1574. );
  1575. // if there are no more inline levels available
  1576. } else {
  1577. $this->inlineNames = array();
  1578. }
  1579. }
  1580. /**
  1581. * Create a name/id for usage in HTML output of a level of the structure stack to be used in form names.
  1582. *
  1583. * @param array $levelData: Array of a level of the structure stack (containing the keys table, uid and field)
  1584. * @param string $disposal: How the structure name is used (e.g. as <div id="..."> or <input name="..." />)
  1585. * @return string The name/id of that level, to be used for HTML output
  1586. */
  1587. function getStructureItemName($levelData, $disposal = self::Disposal_AttributeId) {
  1588. if (is_array($levelData)) {
  1589. $parts = array($levelData['table'], $levelData['uid']);
  1590. if (isset($levelData['field'])) {
  1591. $parts[] = $levelData['field'];
  1592. }
  1593. // Use in name attributes:
  1594. if ($disposal === self::Disposal_AttributeName) {
  1595. $name = '[' . implode('][', $parts) . ']';
  1596. // Use in id attributes:
  1597. } else {
  1598. $name = implode(self::Structure_Separator, $parts);
  1599. }
  1600. }
  1601. return $name;
  1602. }
  1603. /**
  1604. * Get a level from the stack and return the data.
  1605. * If the $level value is negative, this function works top-down,
  1606. * if the $level value is positive, this function works bottom-up.
  1607. *
  1608. * @param integer $level: Which level to return
  1609. * @return array The item of the stack at the requested level
  1610. */
  1611. function getStructureLevel($level) {
  1612. $inlineStructureCount = count($this->inlineStructure['stable']);
  1613. if ($level < 0) {
  1614. $level = $inlineStructureCount + $level;
  1615. }
  1616. if ($level >= 0 && $level < $inlineStructureCount) {
  1617. return $this->inlineStructure['stable'][$level];
  1618. }
  1619. else
  1620. {
  1621. return FALSE;
  1622. }
  1623. }
  1624. /**
  1625. * Get the identifiers of a given depth of level, from the top of the stack to the bottom.
  1626. * An identifier looks like "<table>-<uid>-<field>".
  1627. *
  1628. * @param integer $structureDepth: How much levels to output, beginning from the top of the stack
  1629. * @return string The path of identifiers
  1630. */
  1631. function getStructurePath($structureDepth = -1) {
  1632. $structureLevels = array();
  1633. $structureCount = count($this->inlineStructure['stable']);
  1634. if ($structureDepth < 0 || $structureDepth > $structureCount) {
  1635. $structureDepth = $structureCount;
  1636. }
  1637. for ($i = 1; $i <= $structureDepth; $i++) {
  1638. array_unshift(
  1639. $structureLevels,
  1640. $this->getStructureItemName(
  1641. $this->getStructureLevel(-$i),
  1642. self::Disposal_AttributeId
  1643. )
  1644. );
  1645. }
  1646. return implode(self::Structure_Separator, $structureLevels);
  1647. }
  1648. /**
  1649. * Convert the DOM object-id of an inline container to an array.
  1650. * The object-id could look like 'data-parentPageId-tx_mmftest_company-1-employees'.
  1651. * The result is written to $this->inlineStructure.
  1652. * There are two keys:
  1653. * - 'stable': Containing full qualified identifiers (table, uid and field)
  1654. * - 'unstable': Containting partly filled data (e.g. only table and possibly field)
  1655. *
  1656. * @param string $domObjectId: The DOM object-id
  1657. * @param boolean $loadConfig: Load the TCA configuration for that level (default: true)
  1658. * @return void
  1659. */
  1660. function parseStructureString($string, $loadConfig = TRUE) {
  1661. $unstable = array();
  1662. $vector = array('table', 'uid', 'field');
  1663. $pattern = '/^' . $this->prependNaming . self::Structure_Separator . '(.+?)' . self::Structure_Separator . '(.+)$/';
  1664. if (preg_match($pattern, $string, $match)) {
  1665. $this->inlineFirstPid = $match[1];
  1666. $parts = explode(self::Structure_Separator, $match[2]);
  1667. $partsCnt = count($parts);
  1668. for ($i = 0; $i < $partsCnt; $i++) {
  1669. if ($i > 0 && $i % 3 == 0) {
  1670. // load the TCA configuration of the table field and store it in the stack
  1671. if ($loadConfig) {
  1672. t3lib_div::loadTCA($unstable['table']);
  1673. $unstable['config'] = $GLOBALS['TCA'][$unstable['table']]['columns'][$unstable['field']]['config'];
  1674. // Fetch TSconfig:
  1675. $TSconfig = $this->fObj->setTSconfig(
  1676. $unstable['table'],
  1677. array('uid' => $unstable['uid'], 'pid' => $this->inlineFirstPid),
  1678. $unstable['field']
  1679. );
  1680. // Override TCA field config by TSconfig:
  1681. if (!$TSconfig['disabled']) {
  1682. $unstable['config'] = $this->fObj->overrideFieldConf($unstable['config'], $TSconfig);
  1683. }
  1684. $unstable['localizationMode'] = t3lib_BEfunc::getInlineLocalizationMode($unstable['table'], $unstable['config']);
  1685. }
  1686. $this->inlineStructure['stable'][] = $unstable;
  1687. $unstable = array();
  1688. }
  1689. $unstable[$vector[$i % 3]] = $parts[$i];
  1690. }
  1691. $this->updateStructureNames();
  1692. if (count($unstable)) {
  1693. $this->inlineStructure['unstable'] = $unstable;
  1694. }
  1695. }
  1696. }
  1697. /*******************************************************
  1698. *
  1699. * Helper functions
  1700. *
  1701. *******************************************************/
  1702. /**
  1703. * Does some checks on the TCA configuration of the inline field to render.
  1704. *
  1705. * @param array $config: Reference to the TCA field configuration
  1706. * @param string $table: The table name of the record
  1707. * @param string $field: The field name which this element is supposed to edit
  1708. * @param array $row: The record data array of the parent
  1709. * @return boolean If critical configuration errors were found, false is returned
  1710. */
  1711. function checkConfiguration(&$config) {
  1712. $foreign_table = $config['foreign_table'];
  1713. // An inline field must have a foreign_table, if not, stop all further inline actions for this field:
  1714. if (!$foreign_table || !is_array($GLOBALS['TCA'][$foreign_table])) {
  1715. return FALSE;
  1716. }
  1717. // Init appearance if not set:
  1718. if (!isset($config['appearance']) || !is_array($config['appearance'])) {
  1719. $config['appearance'] = array();
  1720. }
  1721. // 'newRecordLinkPosition' is deprecated since TYPO3 4.2.0-beta1, this is for backward compatibility:
  1722. if (!isset($config['appearance']['levelLinksPosition']) && isset($config['appearance']['newRecordLinkPosition']) && $config['appearance']['newRecordLinkPosition']) {
  1723. t3lib_div::deprecationLog('TCA contains a deprecated definition using "newRecordLinkPosition"');
  1724. $config['appearance']['levelLinksPosition'] = $config['appearance']['newRecordLinkPosition'];
  1725. }
  1726. // Set the position/appearance of the "Create new record" link:
  1727. if (isset($config['foreign_selector']) && $config['foreign_selector'] && (!isset($config['appearance']['useCombination']) || !$config['appearance']['useCombination'])) {
  1728. $config['appearance']['levelLinksPosition'] = 'none';
  1729. } elseif (!isset($config['appearance']['levelLinksPosition']) || !in_array($config['appearance']['levelLinksPosition'], array('top', 'bottom', 'both', 'none'))) {
  1730. $config['appearance']['levelLinksPosition'] = 'top';
  1731. }
  1732. // Defines which controls should be shown in header of each record:
  1733. $enabledControls = array(
  1734. 'info' => TRUE,
  1735. 'new' => TRUE,
  1736. 'dragdrop' => TRUE,
  1737. 'sort' => TRUE,
  1738. 'hide' => TRUE,
  1739. 'delete' => TRUE,
  1740. 'localize' => TRUE,
  1741. );
  1742. if (isset($config['appearance']['enabledControls']) && is_array($config['appearance']['enabledControls'])) {
  1743. $config['appearance']['enabledControls'] = array_merge($enabledControls, $config['appearance']['enabledControls']);
  1744. } else {
  1745. $config['appearance']['enabledControls'] = $enabledControls;
  1746. }
  1747. return TRUE;
  1748. }
  1749. /**
  1750. * Checks the page access rights (Code for access check mostly taken from alt_doc.php)
  1751. * as well as the table access rights of the user.
  1752. *
  1753. * @param string $cmd: The command that sould be performed ('new' or 'edit')
  1754. * @param string $table: The table to check access for
  1755. * @param string $theUid: The record uid of the table
  1756. * @return boolean Returns true is the user has access, or false if not
  1757. */
  1758. function checkAccess($cmd, $table, $theUid) {
  1759. // 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...)
  1760. // First, resetting flags.
  1761. $hasAccess = 0;
  1762. $deniedAccessReason = '';
  1763. // Admin users always have acces:
  1764. if ($GLOBALS['BE_USER']->isAdmin()) {
  1765. return TRUE;
  1766. }
  1767. // If the command is to create a NEW record...:
  1768. if ($cmd == 'new') {
  1769. // If the pid is numerical, check if it's possible to write to this page:
  1770. if (t3lib_div::testInt($this->inlineFirstPid)) {
  1771. $calcPRec = t3lib_BEfunc::getRecord('pages', $this->inlineFirstPid);
  1772. if (!is_array($calcPRec)) {
  1773. return FALSE;
  1774. }
  1775. $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms($calcPRec); // Permissions for the parent page
  1776. if ($table == 'pages') { // If pages:
  1777. $hasAccess = $CALC_PERMS & 8 ? 1 : 0; // Are we allowed to create new subpages?
  1778. } else {
  1779. $hasAccess = $CALC_PERMS & 16 ? 1 : 0; // Are we allowed to edit content on this page?
  1780. }
  1781. // If the pid is a NEW... value, the access will be checked on creating the page:
  1782. // (if the page with the same NEW... value could be created in TCEmain, this child record can neither)
  1783. } else {
  1784. $hasAccess = 1;
  1785. }
  1786. } else { // Edit:
  1787. $calcPRec = t3lib_BEfunc::getRecord($table, $theUid);
  1788. t3lib_BEfunc::fixVersioningPid($table, $calcPRec);
  1789. if (is_array($calcPRec)) {
  1790. if ($table == 'pages') { // If pages:
  1791. $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms($calcPRec);
  1792. $hasAccess = $CALC_PERMS & 2 ? 1 : 0;
  1793. } else {
  1794. $CALC_PERMS = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages', $calcPRec['pid'])); // Fetching pid-record first.
  1795. $hasAccess = $CALC_PERMS & 16 ? 1 : 0;
  1796. }
  1797. // Check internals regarding access:
  1798. if ($hasAccess) {
  1799. $hasAccess = $GLOBALS['BE_USER']->recordEditAccessInternals($table, $calcPRec);
  1800. }
  1801. }
  1802. }
  1803. if (!$GLOBALS['BE_USER']->check('tables_modify', $table)) {
  1804. $hasAccess = 0;
  1805. }
  1806. if (!$hasAccess) {
  1807. $deniedAccessReason = $GLOBALS['BE_USER']->errorMsg;
  1808. if ($deniedAccessReason) {
  1809. debug($deniedAccessReason);
  1810. }
  1811. }
  1812. return $hasAccess ? TRUE : FALSE;
  1813. }
  1814. /**
  1815. * Check the keys and values in the $compare array against the ['config'] part of the top level of the stack.
  1816. * A boolean value is return depending on how the comparison was successful.
  1817. *
  1818. * @param array $compare: keys and values to compare to the ['config'] part of the top level of the stack
  1819. * @return boolean Whether the comparison was successful
  1820. * @see arrayCompareComplex
  1821. */
  1822. function compareStructureConfiguration($compare) {
  1823. $level = $this->getStructureLevel(-1);
  1824. $result = $this->arrayCompareComplex($level, $compare);
  1825. return $result;
  1826. }
  1827. /**
  1828. * Normalize a relation "uid" published by transferData, like "1|Company%201"
  1829. *
  1830. * @param string $string: A transferData reference string, containing the uid
  1831. * @return string The normalized uid
  1832. */
  1833. function normalizeUid($string) {
  1834. $parts = explode('|', $string);
  1835. return $parts[0];
  1836. }
  1837. /**
  1838. * Wrap the HTML code of a section with a table tag.
  1839. *
  1840. * @param string $section: The HTML code to be wrapped
  1841. * @param array $styleAttrs: Attributes for the style argument in the table tag
  1842. * @param array $tableAttrs: Attributes for the table tag (like width, border, etc.)
  1843. * @return string The wrapped HTML code
  1844. */
  1845. function wrapFormsSection($section, $styleAttrs = array(), $tableAttrs = array()) {
  1846. if (!$styleAttrs['margin-right']) {
  1847. $styleAttrs['margin-right'] = $this->inlineStyles['margin-right'] . 'px';
  1848. }
  1849. foreach ($styleAttrs as $key => $value) $style .= ($style ? ' ' : '') . $key . ': ' . htmlspecialchars($value) . '; ';
  1850. if ($style) {
  1851. $style = ' style="' . $style . '"';
  1852. }
  1853. if (!$tableAttrs['background'] && $this->fObj->borderStyle[2]) {
  1854. $tableAttrs['background'] = $this->backPath . $this->borderStyle[2];
  1855. }
  1856. if (!$tableAttrs['cellspacing']) {
  1857. $tableAttrs['cellspacing'] = '0';
  1858. }
  1859. if (!$tableAttrs['cellpadding']) {
  1860. $tableAttrs['cellpadding'] = '0';
  1861. }
  1862. if (!$tableAttrs['border']) {
  1863. $tableAttrs['border'] = '0';
  1864. }
  1865. if (!$tableAttrs['width']) {
  1866. $tableAttrs['width'] = '100%';
  1867. }
  1868. if (!$tableAttrs['class'] && $this->borderStyle[3]) {
  1869. $tableAttrs['class'] = $this->borderStyle[3];
  1870. }
  1871. foreach ($tableAttrs as $key => $value) $table .= ($table ? ' ' : '') . $key . '="' . htmlspecialchars($value) . '"';
  1872. $out = '<table ' . $table . $style . '>' . $section . '</table>';
  1873. return $out;
  1874. }
  1875. /**
  1876. * Checks if the $table is the child of a inline type AND the $field is the label field of this table.
  1877. * This function is used to dynamically update the label while editing. This has no effect on labels,
  1878. * that were processed by a TCEmain-hook on saving.
  1879. *
  1880. * @param string $table: The table to check
  1881. * @param string $field: The field on this table to check
  1882. * @return boolean is inline child and field is responsible for the label
  1883. */
  1884. function isInlineChildAndLabelField($table, $field) {
  1885. $level = $this->getStructureLevel(-1);
  1886. if ($level['config']['foreign_label']) {
  1887. $label = $level['config']['foreign_label'];
  1888. }
  1889. else
  1890. {
  1891. $label = $GLOBALS['TCA'][$table]['ctrl']['label'];
  1892. }
  1893. return $level['config']['foreign_table'] === $table && $label == $field ? TRUE : FALSE;
  1894. }
  1895. /**
  1896. * Get the depth of the stable structure stack.
  1897. * (count($this->inlineStructure['stable'])
  1898. *
  1899. * @return integer The depth of the structure stack
  1900. */
  1901. function getStructureDepth() {
  1902. return count($this->inlineStructure['stable']);
  1903. }
  1904. /**
  1905. * Handles complex comparison requests on an array.
  1906. * A request could look like the following:
  1907. *
  1908. * $searchArray = array(
  1909. * '%AND' => array(
  1910. * 'key1' => 'value1',
  1911. * 'key2' => 'value2',
  1912. * '%OR' => array(
  1913. * 'subarray' => array(
  1914. * 'subkey' => 'subvalue'
  1915. * ),
  1916. * 'key3' => 'value3',
  1917. * 'key4' => 'value4'
  1918. * )
  1919. * )
  1920. * );
  1921. *
  1922. * It is possible to use the array keys '%AND.1', '%AND.2', etc. to prevent
  1923. * overwriting the sub-array. It could be neccessary, if you use complex comparisons.
  1924. *
  1925. * The example above means, key1 *AND* key2 (and their values) have to match with
  1926. * the $subjectArray and additional one *OR* key3 or key4 have to meet the same
  1927. * condition.
  1928. * It is also possible to compare parts of a sub-array (e.g. "subarray"), so this
  1929. * function recurses down one level in that sub-array.
  1930. *
  1931. * @param array $subjectArray: The array to search in
  1932. * @param array $searchArray: The array with keys and values to search for
  1933. * @param string $type: Use '%AND' or '%OR' for comparision
  1934. * @return boolean The result of the comparison
  1935. */
  1936. function arrayCompareComplex($subjectArray, $searchArray, $type = '') {
  1937. $localMatches = 0;
  1938. $localEntries = 0;
  1939. if (is_array($searchArray) && count($searchArray)) {
  1940. // if no type was passed, try to determine
  1941. if (!$type) {
  1942. reset($searchArray);
  1943. $type = key($searchArray);
  1944. $searchArray = current($searchArray);
  1945. }
  1946. // we use '%AND' and '%OR' in uppercase
  1947. $type = strtoupper($type);
  1948. // split regular elements from sub elements
  1949. foreach ($searchArray as $key => $value) {
  1950. $localEntries++;
  1951. // process a sub-group of OR-conditions
  1952. if ($key == '%OR') {
  1953. $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0;
  1954. // process a sub-group of AND-conditions
  1955. } elseif ($key == '%AND') {
  1956. $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0;
  1957. // a part of an associative array should be compared, so step down in the array hierarchy
  1958. } elseif (is_array($value) && $this->isAssociativeArray($searchArray)) {
  1959. $localMatches += $this->arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0;
  1960. // it is a normal array that is only used for grouping and indexing
  1961. } elseif (is_array($value)) {
  1962. $localMatches += $this->arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0;
  1963. // directly compare a value
  1964. } else {
  1965. if (isset($subjectArray[$key]) && isset($value)) {
  1966. // Boolean match:
  1967. if (is_bool($value)) {
  1968. $localMatches += (!($subjectArray[$key] xor $value) ? 1 : 0);
  1969. // Value match for numbers:
  1970. } elseif (is_numeric($subjectArray[$key]) && is_numeric($value)) {
  1971. $localMatches += ($subjectArray[$key] == $value ? 1 : 0);
  1972. // Value and type match:
  1973. } else {
  1974. $localMatches += ($subjectArray[$key] === $value ? 1 : 0);
  1975. }
  1976. }
  1977. }
  1978. // if one or more matches are required ('OR'), return true after the first successful match
  1979. if ($type == '%OR' && $localMatches > 0) {
  1980. return TRUE;
  1981. }
  1982. // if all matches are required ('AND') and we have no result after the first run, return false
  1983. if ($type == '%AND' && $localMatches == 0) {
  1984. return FALSE;
  1985. }
  1986. }
  1987. }
  1988. // return the result for '%AND' (if nothing was checked, true is returned)
  1989. return $localEntries == $localMatches ? TRUE : FALSE;
  1990. }
  1991. /**
  1992. * Checks whether an object is an associative array.
  1993. *
  1994. * @param mixed $object: The object to be checked
  1995. * @return boolean Returns true, if the object is an associative array
  1996. */
  1997. function isAssociativeArray($object) {
  1998. return is_array($object) && count($object) && (array_keys($object) !== range(0, sizeof($object) - 1))
  1999. ? TRUE
  2000. : FALSE;
  2001. }
  2002. /**
  2003. * Remove an element from an array.
  2004. *
  2005. * @param mixed $needle: The element to be removed.
  2006. * @param array $haystack: The array the element should be removed from.
  2007. * @param mixed $strict: Search elements strictly.
  2008. * @return array The array $haystack without the $needle
  2009. */
  2010. function removeFromArray($needle, $haystack, $strict = NULL) {
  2011. $pos = array_search($needle, $haystack, $strict);
  2012. if ($pos !== FALSE) {
  2013. unset($haystack[$pos]);
  2014. }
  2015. return $haystack;
  2016. }
  2017. /**
  2018. * Makes a flat array from the $possibleRecords array.
  2019. * The key of the flat array is the value of the record,
  2020. * the value of the flat array is the label of the record.
  2021. *
  2022. * @param array $possibleRecords: The possibleRecords array (for select fields)
  2023. * @return mixed A flat array with key=uid, value=label; if $possibleRecords isn't an array, false is returned.
  2024. */
  2025. function getPossibleRecordsFlat($possibleRecords) {
  2026. $flat = FALSE;
  2027. if (is_array($possibleRecords)) {
  2028. $flat = array();
  2029. foreach ($possibleRecords as $record) $flat[$record[1]] = $record[0];
  2030. }
  2031. return $flat;
  2032. }
  2033. /**
  2034. * Determine the configuration and the type of a record selector.
  2035. *
  2036. * @param array $conf: TCA configuration of the parent(!) field
  2037. * @return array Associative array with the keys 'PA' and 'type', both are false if the selector was not valid.
  2038. */
  2039. function getPossibleRecordsSelectorConfig($conf, $field = '') {
  2040. $foreign_table = $conf['foreign_table'];
  2041. $foreign_selector = $conf['foreign_selector'];
  2042. $PA = FALSE;
  2043. $type = FALSE;
  2044. $table = FALSE;
  2045. $selector = FALSE;
  2046. if ($field) {
  2047. $PA = array();
  2048. $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$field];
  2049. $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
  2050. $PA['fieldTSConfig'] = $this->fObj->setTSconfig($foreign_table, array(), $field);
  2051. $config = $PA['fieldConf']['config'];
  2052. // Determine type of Selector:
  2053. $type = $this->getPossibleRecordsSelectorType($config);
  2054. // Return table on this level:
  2055. $table = $type == 'select' ? $config['foreign_table'] : $config['allowed'];
  2056. // Return type of the selector if foreign_selector is defined and points to the same field as in $field:
  2057. if ($foreign_selector && $foreign_selector == $field && $type) {
  2058. $selector = $type;
  2059. }
  2060. }
  2061. return array(
  2062. 'PA' => $PA,
  2063. 'type' => $type,
  2064. 'table' => $table,
  2065. 'selector' => $selector,
  2066. );
  2067. }
  2068. /**
  2069. * Determine the type of a record selector, e.g. select or group/db.
  2070. *
  2071. * @param array $config: TCE configuration of the selector
  2072. * @return mixed The type of the selector, 'select' or 'groupdb' - false not valid
  2073. */
  2074. function getPossibleRecordsSelectorType($config) {
  2075. $type = FALSE;
  2076. if ($config['type'] == 'select') {
  2077. $type = 'select';
  2078. } elseif ($config['type'] == 'group' && $config['internal_type'] == 'db') {
  2079. $type = 'groupdb';
  2080. }
  2081. return $type;
  2082. }
  2083. /**
  2084. * Check, if a field should be skipped, that was defined to be handled as foreign_field or foreign_sortby of
  2085. * the parent record of the "inline"-type - if so, we have to skip this field - the rendering is done via "inline" as hidden field
  2086. *
  2087. * @param string $table: The table name
  2088. * @param string $field: The field name
  2089. * @param array $row: The record row from the database
  2090. * @param array $config: TCA configuration of the field
  2091. * @return boolean Determines whether the field should be skipped.
  2092. */
  2093. function skipField($table, $field, $row, $config) {
  2094. $skipThisField = FALSE;
  2095. if ($this->getStructureDepth()) {
  2096. $searchArray = array(
  2097. '%OR' => array(
  2098. 'config' => array(
  2099. 0 => array(
  2100. '%AND' => array(
  2101. 'foreign_table' => $table,
  2102. '%OR' => array(
  2103. '%AND' => array(
  2104. 'appearance' => array('useCombination' => TRUE),
  2105. 'foreign_selector' => $field,
  2106. ),
  2107. 'MM' => $config['MM']
  2108. ),
  2109. ),
  2110. ),
  2111. 1 => array(
  2112. '%AND' => array(
  2113. 'foreign_table' => $config['foreign_table'],
  2114. 'foreign_selector' => $config['foreign_field'],
  2115. ),
  2116. ),
  2117. ),
  2118. ),
  2119. );
  2120. // get the parent record from structure stack
  2121. $level = $this->getStructureLevel(-1);
  2122. // If we have symmetric fields, check on which side we are and hide fields, that are set automatically:
  2123. if (t3lib_loadDBGroup::isOnSymmetricSide($level['uid'], $level['config'], $row)) {
  2124. $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $field;
  2125. $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $field;
  2126. // Hide fields, that are set automatically:
  2127. } else {
  2128. $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $field;
  2129. $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $field;
  2130. }
  2131. $skipThisField = $this->compareStructureConfiguration($searchArray, TRUE);
  2132. }
  2133. return $skipThisField;
  2134. }
  2135. /**
  2136. * Checks if a uid of a child table is in the inline view settings.
  2137. *
  2138. * @param string $table: Name of the child table
  2139. * @param integer $uid: uid of the the child record
  2140. * @return boolean true=expand, false=collapse
  2141. */
  2142. function getExpandedCollapsedState($table, $uid) {
  2143. if (isset($this->inlineView[$table]) && is_array($this->inlineView[$table])) {
  2144. if (in_array($uid, $this->inlineView[$table]) !== FALSE) {
  2145. return TRUE;
  2146. }
  2147. }
  2148. return FALSE;
  2149. }
  2150. /**
  2151. * Update expanded/collapsed states on new inline records if any.
  2152. *
  2153. * @param array $uc: The uc array to be processed and saved (by reference)
  2154. * @param t3lib_TCEmain $tce: Instance of TCEmain that saved data before
  2155. * @return void
  2156. */
  2157. function updateInlineView(&$uc, $tce) {
  2158. if (isset($uc['inlineView']) && is_array($uc['inlineView'])) {
  2159. $inlineView = (array) unserialize($GLOBALS['BE_USER']->uc['inlineView']);
  2160. foreach ($uc['inlineView'] as $topTable => $topRecords) {
  2161. foreach ($topRecords as $topUid => $childElements) {
  2162. foreach ($childElements as $childTable => $childRecords) {
  2163. $uids = array_keys($tce->substNEWwithIDs_table, $childTable);
  2164. if (count($uids)) {
  2165. $newExpandedChildren = array();
  2166. foreach ($childRecords as $childUid => $state) {
  2167. if ($state && in_array($childUid, $uids)) {
  2168. $newChildUid = $tce->substNEWwithIDs[$childUid];
  2169. $newExpandedChildren[] = $newChildUid;
  2170. }
  2171. }
  2172. // Add new expanded child records to UC (if any):
  2173. if (count($newExpandedChildren)) {
  2174. $inlineViewCurrent =& $inlineView[$topTable][$topUid][$childTable];
  2175. if (is_array($inlineViewCurrent)) {
  2176. $inlineViewCurrent = array_unique(array_merge($inlineViewCurrent, $newExpandedChildren));
  2177. } else {
  2178. $inlineViewCurrent = $newExpandedChildren;
  2179. }
  2180. }
  2181. }
  2182. }
  2183. }
  2184. }
  2185. $GLOBALS['BE_USER']->uc['inlineView'] = serialize($inlineView);
  2186. $GLOBALS['BE_USER']->writeUC();
  2187. }
  2188. }
  2189. /**
  2190. * Returns the the margin in pixels, that is used for each new inline level.
  2191. *
  2192. * @return integer A pixel value for the margin of each new inline level.
  2193. */
  2194. function getLevelMargin() {
  2195. $margin = ($this->inlineStyles['margin-right'] + 1) * 2;
  2196. return $margin;
  2197. }
  2198. /**
  2199. * Parses the HTML tags that would have been inserted to the <head> of a HTML document and returns the found tags as multidimensional array.
  2200. *
  2201. * @return array The parsed tags with their attributes and innerHTML parts
  2202. */
  2203. protected function getHeadTags() {
  2204. $headTags = array();
  2205. $headDataRaw = $this->fObj->JStop() . $this->getJavaScriptAndStyleSheetsOfPageRenderer();
  2206. if ($headDataRaw) {
  2207. // Create instance of the HTML parser:
  2208. $parseObj = t3lib_div::makeInstance('t3lib_parsehtml');
  2209. // Removes script wraps:
  2210. $headDataRaw = str_replace(array('/*<![CDATA[*/', '/*]]>*/'), '', $headDataRaw);
  2211. // Removes leading spaces of a multiline string:
  2212. $headDataRaw = trim(preg_replace('/(^|\r|\n)( |\t)+/', '$1', $headDataRaw));
  2213. // Get script and link tags:
  2214. $tags = array_merge(
  2215. $parseObj->getAllParts($parseObj->splitTags('link', $headDataRaw)),
  2216. $parseObj->getAllParts($parseObj->splitIntoBlock('script', $headDataRaw))
  2217. );
  2218. foreach ($tags as $tagData) {
  2219. $tagAttributes = $parseObj->get_tag_attributes($parseObj->getFirstTag($tagData), TRUE);
  2220. $headTags[] = array(
  2221. 'name' => $parseObj->getFirstTagName($tagData),
  2222. 'attributes' => $tagAttributes[0],
  2223. 'innerHTML' => $parseObj->removeFirstAndLastTag($tagData),
  2224. );
  2225. }
  2226. }
  2227. return $headTags;
  2228. }
  2229. /**
  2230. * Gets the JavaScript of the pageRenderer.
  2231. * This can be used to extract newly added files which have been added
  2232. * during an AJAX request. Due to the spread possibilities of the pageRenderer
  2233. * to add JavaScript rendering and extracting seems to be the easiest way.
  2234. *
  2235. * @return string
  2236. */
  2237. protected function getJavaScriptAndStyleSheetsOfPageRenderer() {
  2238. /** @var $pageRenderer t3lib_PageRenderer */
  2239. $pageRenderer = clone $GLOBALS['SOBE']->doc->getPageRenderer();
  2240. $pageRenderer->setTemplateFile(TYPO3_mainDir . 'templates/helper_javascript_css.html');
  2241. $javaScriptAndStyleSheets = $pageRenderer->render();
  2242. return $javaScriptAndStyleSheets;
  2243. }
  2244. /**
  2245. * Wraps a text with an anchor and returns the HTML representation.
  2246. *
  2247. * @param string $text: The text to be wrapped by an anchor
  2248. * @param string $link: The link to be used in the anchor
  2249. * @param array $attributes: Array of attributes to be used in the anchor
  2250. * @return string The wrapped texted as HTML representation
  2251. */
  2252. protected function wrapWithAnchor($text, $link, $attributes = array()) {
  2253. $link = trim($link);
  2254. $result = '<a href="' . ($link ? $link : '#') . '"';
  2255. foreach ($attributes as $key => $value) {
  2256. $result .= ' ' . $key . '="' . htmlspecialchars(trim($value)) . '"';
  2257. }
  2258. $result .= '>' . $text . '</a>';
  2259. return $result;
  2260. }
  2261. }
  2262. if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_tceforms_inline.php'])) {
  2263. include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_tceforms_inline.php']);
  2264. }
  2265. ?>