Webylon 3.1 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • auth
  • Booking
  • cart
    • shipping
    • steppedcheckout
  • Catalog
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • AssetManager
  • CartTableListField_Item
  • ComplexTableField
  • ComplexTableField_Item
  • ComplexTableField_ItemRequest
  • ComplexTableField_Popup
  • CountryDropdownField
  • DataObjectManager
  • DataObjectManager_Item
  • DataObjectManager_ItemRequest
  • DocumentPageFiles_Manager
  • FileDataObjectManager
  • FileDataObjectManager_Item
  • FileDataObjectManager_ItemRequest
  • HasManyComplexTableField
  • HasManyComplexTableField_Item
  • HasManyDataObjectManager
  • HasManyDataObjectManager_Item
  • HasManyFileDataObjectManager
  • HasManyFileDataObjectManager_Item
  • HasOneComplexTableField
  • HasOneComplexTableField_Item
  • HasOneDataObjectManager
  • HasOneDataObjectManager_Item
  • HasOneFileDataObjectManager
  • HasOneFileDataObjectManager_Item
  • ImageAssetManager
  • ImageDataObjectManager
  • ImageDataObjectManager_Item
  • ImageDataObjectManager_ItemRequest
  • LanguageDropdownField
  • ManyManyComplexTableField
  • ManyManyComplexTableField_Item
  • ManyManyDataObjectManager
  • ManyManyDataObjectManager_Item
  • ManyManyFileDataObjectManager
  • ManyManyFileDataObjectManager_Item
  • Mediaweb3DPageFiles_Manager
  • MediawebPageFiles_Manager
  • MediawebPagePhoto_Manager
  • MediawebPageTexture_Manager
  • PhotoAlbumManager
  • ScaffoldingComplexTableField_Popup
  • SubpageListField_Item
  • SubPageListField_ItemRequest
  • SubsiteAgnosticTableListField
  • TableField
  • TableField_Item
  • TableListField
  • TableListField_Item
  • TableListField_ItemRequest
  • TreeDropdownField
  • TreeDropdownField_Readonly
  • TreeMultiselectField
  • TreeMultiselectField_Readonly
  • TreeSelectorField
   1 <?php
   2 
   3 /**
   4  * @package forms
   5  * @subpackage fields-relational
   6  */
   7 
   8 /**
   9  * Form field that embeds a list into a form, such as a member list or a file list.
  10  * 
  11  * All get variables are namespaced in the format ctf[MyFieldName][MyParameter] to avoid collisions
  12  * when multiple TableListFields are present in a form.
  13  * 
  14  * @param $name string The fieldname
  15  * @param $sourceClass string The source class of this field
  16  * @param $fieldList array An array of field headings of Fieldname => Heading Text (eg. heading1)
  17  * @param $sourceFilter string The filter field you wish to limit the objects by (eg. parentID)
  18  * @param $sourceSort string
  19  * @param $sourceJoin string
  20  * @package forms
  21  * @subpackage fields-relational
  22  */
  23 class TableListField extends FormField {
  24     
  25     /**
  26      * @var $cachedSourceItems DataObjectSet Prevent {@sourceItems()} from being called multiple times.
  27      */
  28     protected $cachedSourceItems;
  29 
  30     protected $sourceClass;
  31 
  32     protected $sourceFilter = "";
  33     
  34     protected $sourceSort = "";
  35     
  36     protected $sourceJoin = array();
  37     
  38     protected $fieldList;
  39     
  40     protected $disableSorting = false;
  41     
  42     /**
  43      * @var $fieldListCsv array
  44      */
  45     protected $fieldListCsv;
  46     
  47     /**
  48      * @var $clickAction
  49      */
  50     protected $clickAction;
  51     
  52     /**
  53      * @var bool
  54      */
  55     public $IsReadOnly;
  56     
  57     /**
  58      * Called method (needs to be retained for AddMode())
  59      */
  60     protected $methodName;
  61     
  62     /**
  63      * @var $summaryFieldList array Shows a row which summarizes the contents of a column by a predefined
  64      * Javascript-function 
  65      */
  66     protected $summaryFieldList;
  67     
  68     /**
  69      * @var $summaryTitle string The title which will be shown in the first column of the summary-row.
  70      * Accordingly, the first column can't be used for summarizing.
  71      */
  72     protected $summaryTitle;
  73     
  74     /**
  75      * @var $template string Template-Overrides
  76      */
  77     protected $template = "TableListField";
  78     
  79     /**
  80      * @var $itemClass string Class name for each item/row
  81      */
  82     public $itemClass = 'TableListField_Item';
  83     
  84     /**
  85      * @var bool Do we use checkboxes to mark records, or delete them one by one?
  86      */
  87     public $Markable;
  88     
  89     public $MarkableTitle = null;
  90     
  91     /**
  92      * @var $readOnly boolean Deprecated, please use $permssions instead
  93      */
  94     protected $readOnly;
  95     
  96     /**
  97      * @var $permissions array Influence output without having to subclass the template.
  98      * See $actions for adding your custom actions/permissions.
  99      */
 100     protected $permissions = array(
 101         //"print",
 102         //"export",
 103         "delete"
 104     );
 105     
 106     /**
 107      * @var $actions array Action that can be performed on a single row-entry.
 108      * Has to correspond to a method in a TableListField-class (or subclass).
 109      * Actions can be disabled through $permissions.
 110      * Format (key is used for the methodname and CSS-class): 
 111      * array(
 112      *  'delete' => array(
 113      *      'label' => 'Delete', 
 114      *      'icon' => 'cms/images/delete.gif',
 115      *      'icon_disabled' => 'cms/images/delete_disabled.gif',
 116      *      'class' => 'deletelink',
 117      *  )
 118      * )
 119      */
 120     public $actions = array(
 121         'delete' => array(
 122             'label' => 'Delete',
 123             'icon' => 'cms/images/delete.gif',
 124             'icon_disabled' => 'cms/images/delete_disabled.gif',
 125             'class' => 'deletelink' 
 126         )
 127     );
 128     
 129     /**
 130      * @var $defaultAction String Action being executed when clicking on table-row (defaults to "show").
 131      * Mostly needed in ComplexTableField-subclass.
 132      */
 133     public $defaultAction = '';
 134     
 135     /**
 136      * @var $customQuery Specify custom query, e.g. for complicated having/groupby-constructs.
 137      * Caution: TableListField automatically selects the ID from the {@sourceClass}, because it relies
 138      * on this information e.g. in saving a TableField. Please use a custom select if you want to filter
 139      * for other IDs in joined tables: $query->select[] = "MyJoinedTable.ID AS MyJoinedTableID"
 140      */
 141     protected $customQuery;
 142 
 143     /**
 144      * @var $customCsvQuery Query for CSV-export (might need different fields or further filtering)
 145      */
 146     protected $customCsvQuery;
 147     
 148     /**
 149      * @var $customSourceItems DataObjectSet Use the manual setting of a result-set only as a last-resort
 150      * for sets which can't be resolved in a single query.
 151      *
 152      * @todo Add pagination support for customSourceItems.
 153      */
 154     protected $customSourceItems;
 155     
 156     /**
 157      * Character to seperate exported columns in the CSV file
 158      */
 159     protected $csvSeparator = ";";
 160     
 161     /*
 162      * Boolean deciding whether to include a header row in the CSV file
 163      */
 164     protected $csvHasHeader = true;
 165     
 166     /**
 167      * @var array Specify custom escape for the fields.
 168      *
 169      * <code>
 170      * array("\""=>"\"\"","\r"=>"", "\r\n"=>"", "\n"=>"")
 171      * </code>
 172      */
 173     public $csvFieldEscape = array(
 174         "\""=>"\"\"",
 175         "\r\n"=>"", 
 176         "\r"=>"",
 177         "\n"=>"",
 178     );
 179     
 180     
 181     /**
 182      * @var int Shows total count regardless or pagination
 183      */
 184     protected $totalCount;
 185     
 186     /**
 187      * @var boolean Trigger pagination
 188      */
 189     protected $showPagination = false;
 190     
 191     /**
 192      * @var string Override the {@link Link()} method
 193      * for all pagination. Useful to force rendering of the field
 194      * in a different context.
 195      */
 196     public $paginationBaseLink = null;
 197     
 198     /**
 199      * @var int Number of items to show on a single page (needed for pagination)
 200      */
 201     protected $pageSize = 10;
 202     
 203     /**
 204      * @var array Definitions for highlighting table-rows with a specific class. You can use all column-names
 205      * in the result of a query. Use in combination with {@setCustomQuery} to select custom properties and joined objects.
 206      *  
 207      * Example:
 208      * array(
 209      *  array(
 210      *      "rule" => '$Flag == "red"',
 211      *      "class" => "red"
 212      *  ),
 213      *  array(
 214      *      "rule" => '$Flag == "orange"',
 215      *      "class" => "orange"
 216      *  )
 217      * )
 218      */
 219     public $highlightConditions = array();
 220     
 221     /**
 222      * @var array Specify castings with fieldname as the key, and the desired casting as value.
 223      * Example: array("MyCustomDate"=>"Date","MyShortText"=>"Text->FirstSentence")
 224      */
 225     public $fieldCasting = array();
 226     
 227     /**
 228      * @var array Specify custom formatting for fields, e.g. to render a link instead of pure text.
 229      * Caution: Make sure to escape special php-characters like in a normal php-statement. 
 230      * Example: "myFieldName" => '<a href=\"custom-admin/$ID\">$ID</a>'
 231      */
 232     public $fieldFormatting = array();
 233     
 234     public $csvFieldFormatting = array();
 235     
 236     /**
 237      * @var string
 238      */
 239     public $exportButtonLabel = 'Export as CSV';
 240     
 241     /**
 242      * @var string $groupByField Used to group by a specific column in the DataObject
 243      * and create partial summaries.
 244      */
 245     public $groupByField = null;
 246     
 247     /**
 248      * @var array
 249      */
 250     protected $extraLinkParams;
 251     
 252     protected $__cachedQuery;
 253     
 254     function __construct($name, $sourceClass, $fieldList = null, $sourceFilter = null, 
 255         $sourceSort = null, $sourceJoin = null) {
 256 
 257         $this->fieldList = ($fieldList) ? $fieldList : singleton($sourceClass)->summaryFields();
 258         $this->sourceClass = $sourceClass;
 259         $this->sourceFilter = $sourceFilter;
 260         $this->sourceSort = $sourceSort;
 261         $this->sourceJoin = $sourceJoin;
 262         $this->readOnly = false;
 263 
 264         parent::__construct($name);
 265     }
 266     
 267     /**
 268      * Get the filter
 269      */
 270     function sourceFilter() {
 271         return $this->sourceFilter;
 272     }
 273     
 274     function index() {
 275         return $this->FieldHolder();
 276     }
 277 
 278     static $url_handlers = array(
 279         'item/$ID' => 'handleItem',
 280         '$Action' => '$Action',
 281     );
 282 
 283     function sourceClass() {
 284         return $this->sourceClass;
 285     }
 286     
 287     function handleItem($request) {
 288         return new TableListField_ItemRequest($this, $request->param('ID'));
 289     }
 290     
 291     function FieldHolder() {
 292         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js');
 293         Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/behaviour/behaviour.js');
 294         Requirements::javascript(SAPPHIRE_DIR . '/javascript/prototype_improvements.js');
 295         Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/effects.js');
 296         Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
 297         Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableListField.js');
 298         Requirements::css(SAPPHIRE_DIR . '/css/TableListField.css');
 299         
 300         if($this->clickAction) {
 301             $id = $this->id();
 302             Requirements::customScript(<<<JS
 303                 Behaviour.register({
 304                     '#$id tr' : {
 305                         onclick : function() {
 306                             $this->clickAction
 307                             return false;
 308                         }
 309                 }
 310             });
 311 JS
 312         );}
 313         return $this->renderWith($this->template);
 314     }
 315     
 316     function Headings() {
 317         $headings = array();
 318         foreach($this->fieldList as $fieldName => $fieldTitle) {
 319             $isSorted = (isset($_REQUEST['ctf'][$this->Name()]['sort']) && $fieldName == $_REQUEST['ctf'][$this->Name()]['sort']);
 320             // we can't allow sorting with partial summaries (groupByField)
 321             $isSortable = ($this->form && $this->isFieldSortable($fieldName) && !$this->groupByField);
 322 
 323             // sorting links (only if we have a form to refresh with)
 324             if($this->form) {
 325                 $sortLink = $this->Link();
 326                 $sortLink = HTTP::setGetVar("ctf[{$this->Name()}][sort]", $fieldName, $sortLink,'&');
 327     
 328                 // Apply sort direction to the current sort field
 329                 if(!empty($_REQUEST['ctf'][$this->Name()]['sort']) && ($_REQUEST['ctf'][$this->Name()]['sort'] == $fieldName)) {
 330                     $dir = isset($_REQUEST['ctf'][$this->Name()]['dir']) ? $_REQUEST['ctf'][$this->Name()]['dir'] : null;
 331                     $dir = trim(strtolower($dir));
 332                     $newDir = ($dir == 'desc') ? null : 'desc';
 333                     $sortLink = HTTP::setGetVar("ctf[{$this->Name()}][dir]", Convert::raw2xml($newDir), $sortLink,'&');
 334                 }
 335 
 336                 if(isset($_REQUEST['ctf'][$this->Name()]['search']) && is_array($_REQUEST['ctf'][$this->Name()]['search'])) {
 337                     foreach($_REQUEST['ctf'][$this->Name()]['search'] as $parameter => $value) {
 338                         $XML_search = Convert::raw2xml($value);
 339                         $sortLink = HTTP::setGetVar("ctf[{$this->Name()}][search][$parameter]", $XML_search, $sortLink,'&');
 340                     }
 341                 }
 342             } else {
 343                 $sortLink = '#';
 344             }
 345             
 346             $headings[] = new ArrayData(array(
 347                 "Name" => $fieldName, 
 348                 "Title" => ($this->sourceClass) ? singleton($this->sourceClass)->fieldLabel($fieldTitle) : $fieldTitle,
 349                 "IsSortable" => $isSortable,
 350                 "SortLink" => $sortLink,
 351                 "SortBy" => $isSorted,
 352                 "SortDirection" => (isset($_REQUEST['ctf'][$this->Name()]['dir'])) ? $_REQUEST['ctf'][$this->Name()]['dir'] : null 
 353             ));
 354         }
 355         return new DataObjectSet($headings);
 356     }
 357     
 358     function disableSorting($to = true) {
 359         $this->disableSorting = $to;
 360     }
 361 
 362     /**
 363      * Determines if a field is "sortable".
 364      * If the field is generated by a custom getter, we can't sort on it
 365      * without generating all objects first (which would be a huge performance impact).
 366      * 
 367      * @param string $fieldName
 368      * @return bool
 369      */ 
 370     function isFieldSortable($fieldName) {
 371         if($this->customSourceItems || $this->disableSorting) {
 372             return false;
 373         }
 374         
 375         if(!$this->__cachedQuery) $this->__cachedQuery = $this->getQuery();
 376 
 377         return $this->__cachedQuery->canSortBy($fieldName);
 378     }
 379     
 380     /**
 381      * Dummy function to get number of actions originally generated in
 382      * TableListField_Item.
 383      * 
 384      * @return DataObjectSet
 385      */
 386     function Actions() {
 387         $allowedActions = new DataObjectSet();
 388         foreach($this->actions as $actionName => $actionSettings) {
 389             if($this->Can($actionName)) {
 390                 $allowedActions->push(new ViewableData());
 391             }
 392         }
 393 
 394         return $allowedActions;
 395     }
 396     
 397     /**
 398      * Provide a custom query to compute sourceItems. This is the preferred way to using
 399      * {@setSourceItems}, because we can still paginate.
 400      * Caution: Other parameters such as {@sourceFilter} will be ignored.
 401      * Please use this only as a fallback for really complex queries (e.g. involving HAVING and GROUPBY).  
 402      * 
 403      * @param $query SS_Query
 404      */
 405     function setCustomQuery(SQLQuery $query) {
 406         // The type-hinting above doesn't seem to work consistently
 407         if($query instanceof SQLQuery) {
 408             $this->customQuery = $query;
 409         } else {
 410             user_error('TableList::setCustomQuery() should be passed a SQLQuery', E_USER_WARNING);
 411         }
 412     }
 413 
 414     function setCustomCsvQuery(SQLQuery $query) {
 415         // The type-hinting above doesn't seem to work consistently
 416         if($query instanceof SQLQuery) {
 417             $this->customCsvQuery = $query;
 418         } else {
 419             user_error('TableList::setCustomCsvQuery() should be passed a SQLQuery', E_USER_WARNING);
 420         }
 421     }
 422     
 423     function setCustomSourceItems(DataObjectSet $items) {
 424         // The type-hinting above doesn't seem to work consistently
 425         if($items instanceof DataObjectSet) {
 426             $this->customSourceItems = $items;
 427         } else {
 428             user_error('TableList::setCustomSourceItems() should be passed a DataObjectSet', E_USER_WARNING);
 429         }
 430     }
 431     
 432     function sourceItems() {
 433         $SQL_limit = ($this->showPagination && $this->pageSize) ? "{$this->pageSize}" : null;
 434         if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
 435             $SQL_start = (isset($_REQUEST['ctf'][$this->Name()]['start'])) ? intval($_REQUEST['ctf'][$this->Name()]['start']) : "0";
 436         } else {
 437             $SQL_start = 0;
 438         }
 439         if(isset($this->customSourceItems)) {
 440             if($this->showPagination && $this->pageSize) {
 441                 $items = $this->customSourceItems->getRange($SQL_start, $SQL_limit);
 442             } else {
 443                 $items = $this->customSourceItems;
 444             }
 445         } elseif(isset($this->cachedSourceItems)) {
 446             $items = $this->cachedSourceItems;
 447         } else {
 448             // get query
 449             $dataQuery = $this->getQuery();
 450             
 451             // we don't limit when doing certain actions        T
 452             $methodName = isset($_REQUEST['url']) ? array_pop(explode('/', $_REQUEST['url'])) : null;
 453             if(!$methodName || !in_array($methodName,array('printall','export'))) {
 454                 $dataQuery->limit(array(
 455                     'limit' => $SQL_limit,
 456                     'start' => (isset($SQL_start)) ? $SQL_start : null 
 457                 ));
 458             }
 459 
 460             // get data
 461             $records = $dataQuery->execute();
 462             $sourceClass = $this->sourceClass;
 463             $dataobject = new $sourceClass();
 464             $items = $dataobject->buildDataObjectSet($records, 'DataObjectSet');
 465             
 466             $this->cachedSourceItems = $items;
 467         }
 468 
 469         return $items;
 470     }
 471     
 472     function Items() {
 473         $fieldItems = new DataObjectSet();
 474         if($items = $this->sourceItems()) foreach($items as $item) {
 475             if($item) $fieldItems->push(new $this->itemClass($item, $this));
 476         }
 477         return $fieldItems;
 478     }
 479     
 480     /**
 481      * Generates the query for sourceitems (without pagination/limit-clause)
 482      * 
 483      * @return string
 484      */
 485     function getQuery() {
 486         if($this->customQuery) {
 487             $query = clone $this->customQuery;
 488             $baseClass = ClassInfo::baseDataClass($this->sourceClass);
 489         } else {
 490             $query = singleton($this->sourceClass)->extendedSQL($this->sourceFilter(), $this->sourceSort, null, $this->sourceJoin);
 491         }
 492         
 493         if(!empty($_REQUEST['ctf'][$this->Name()]['sort'])) {
 494             $column = $_REQUEST['ctf'][$this->Name()]['sort'];
 495             $dir = 'ASC';
 496             if(!empty($_REQUEST['ctf'][$this->Name()]['dir'])) {
 497                 $dir = $_REQUEST['ctf'][$this->Name()]['dir'];
 498                 if(strtoupper(trim($dir)) == 'DESC') $dir = 'DESC';
 499             }
 500             if(!empty($_REQUEST['ctf'][$this->Name()]['sort_dir'])) {
 501                 $dir = $_REQUEST['ctf'][$this->Name()]['sort_dir'];
 502                 if(strtoupper(trim($dir)) == 'DESC') $dir = 'DESC';
 503             }
 504             if($query->canSortBy($column)) $query->orderby = $column.' '.$dir;
 505         }
 506         
 507         return $query;
 508     }
 509 
 510     function getCsvQuery() {
 511         $baseClass = ClassInfo::baseDataClass($this->sourceClass);
 512         if($this->customCsvQuery || $this->customQuery) {
 513             $query = $this->customCsvQuery ? $this->customCsvQuery : $this->customQuery;
 514         } else {
 515             $query = singleton($this->sourceClass)->extendedSQL($this->sourceFilter(), $this->sourceSort, null, $this->sourceJoin);
 516         }
 517         
 518         return clone $query;
 519     }
 520     
 521     function FieldList() {
 522         return $this->fieldList;
 523     }
 524     
 525     /**
 526      * Configure this table to load content into a subform via ajax
 527      */
 528     function setClick_AjaxLoad($urlBase, $formID) {
 529         $this->clickAction = "this.ajaxRequest('" . addslashes($urlBase) . "', '" . addslashes($formID) . "')";
 530     }
 531 
 532     /**
 533      * Configure this table to open a popup window
 534      */
 535     function setClick_PopupLoad($urlBase) {
 536         $this->clickAction = "var w = window.open(baseHref() + '$urlBase' + this.id.replace(/.*-(\d*)$/,'$1'), 'popup'); w.focus();";
 537     }
 538     
 539     function performReadonlyTransformation() {
 540         $clone = clone $this;
 541         $clone->setShowPagination(false);
 542         
 543         // Only include the show action if it was in the original CTF.
 544         $clone->setPermissions(in_array('show', $this->permissions) ? array('show') : array());
 545 
 546         $clone->addExtraClass( 'readonly' );
 547         $clone->setReadonly(true);
 548         return $clone;
 549     }
 550     
 551     /**
 552      * #################################
 553      *           CRUD
 554      * #################################
 555      */
 556     
 557     /**
 558      * @return String
 559      */
 560     function delete() {
 561         if($this->Can('delete') !== true) {
 562             return false;
 563         }
 564 
 565         $this->methodName = "delete";
 566         
 567         $childId = Convert::raw2sql($_REQUEST['ctf']['childID']);
 568 
 569         if (is_numeric($childId)) {
 570             $childObject = DataObject::get_by_id($this->sourceClass, $childId);
 571             if($childObject) $childObject->delete();
 572         }
 573 
 574         // TODO return status in JSON etc.
 575         //return $this->renderWith($this->template);
 576     }
 577      
 578      
 579     /**
 580      * #################################
 581      *           Summary-Row
 582      * #################################
 583      */
 584      
 585     /**
 586      * Can utilize some built-in summary-functions, with optional casting. 
 587      * Currently supported:
 588      * - sum
 589      * - avg
 590      * 
 591      * @param $summaryTitle string
 592      * @param $summaryFields array 
 593      * Simple Format: array("MyFieldName"=>"sum")
 594      * With Casting: array("MyFieldname"=>array("sum","Currency->Nice"))
 595      */
 596     function addSummary($summaryTitle, $summaryFieldList) {
 597         $this->summaryTitle = $summaryTitle;
 598         $this->summaryFieldList = $summaryFieldList;
 599     }
 600     
 601     function removeSummary() {
 602         $this->summaryTitle = null;
 603         $this->summaryFields = null;
 604     }
 605     
 606     function HasSummary() {
 607         return (isset($this->summaryFieldList));
 608     }
 609     
 610     function SummaryTitle() {
 611         return $this->summaryTitle;
 612     }
 613     
 614     /**
 615      * @param DataObjectSet $items Only used to pass grouped sourceItems for creating
 616      * partial summaries.
 617      */
 618     function SummaryFields($items = null) {
 619         if(!isset($this->summaryFieldList)) {
 620             return false;
 621         }
 622         $summaryFields = array();
 623         $fieldListWithoutFirst = $this->fieldList;
 624         if(!empty($this->summaryTitle)) {
 625             array_shift($fieldListWithoutFirst);
 626         }
 627         foreach($fieldListWithoutFirst as $fieldName => $fieldTitle) {
 628             
 629             if(in_array($fieldName, array_keys($this->summaryFieldList))) {
 630                 if(is_array($this->summaryFieldList[$fieldName])) {
 631                     $summaryFunction = "colFunction_{$this->summaryFieldList[$fieldName][0]}";
 632                     $casting = $this->summaryFieldList[$fieldName][1];
 633                 } else {
 634                     $summaryFunction = "colFunction_{$this->summaryFieldList[$fieldName]}";
 635                     $casting = null;
 636                 }
 637 
 638                 // fall back to integrated sourceitems if not passed
 639                 if(!$items) $items = $this->sourceItems();
 640 
 641                 $summaryValue = ($items) ? $this->$summaryFunction($items->column($fieldName)) : null;
 642                 
 643                 // Optional casting, Format: array('MyFieldName'=>array('sum','Currency->Nice'))
 644                 if(isset($casting)) {
 645                     $summaryValue = $this->getCastedValue($summaryValue, $casting); 
 646                 }
 647             } else {
 648                 $summaryValue = null;
 649                 $function = null;
 650             }
 651             
 652             $summaryFields[] = new ArrayData(array(
 653                 'Function' => $function,
 654                 'SummaryValue' => $summaryValue,
 655                 'Name' => DBField::create('Varchar', $fieldName),
 656                 'Title' => DBField::create('Varchar', $fieldTitle),
 657             ));
 658         }
 659         return new DataObjectSet($summaryFields);
 660     }
 661     
 662     function HasGroupedItems() {
 663         return ($this->groupByField);   
 664     }
 665     
 666     function GroupedItems() {
 667         if(!$this->groupByField) {
 668             return false; 
 669         }
 670         
 671         $items = $this->sourceItems();
 672         if(!$items || !$items->Count()) {
 673             return false;
 674         }
 675         
 676         $groupedItems = $items->groupBy($this->groupByField);
 677         $groupedArrItems = new DataObjectSet();
 678         foreach($groupedItems as $key => $group) {
 679             $fieldItems = new DataObjectSet();
 680             foreach($group as $item) {
 681                 if($item) $fieldItems->push(new $this->itemClass($item, $this));
 682             }
 683             $groupedArrItems->push(new ArrayData(array(
 684                 'Items' => $fieldItems,
 685                 'SummaryFields' => $this->SummaryFields($group)
 686             )));
 687         }
 688         
 689         return $groupedArrItems;
 690     }
 691     
 692     function colFunction_sum($values) {
 693         return array_sum($values);
 694     }
 695 
 696     function colFunction_avg($values) {
 697         return array_sum($values)/count($values);
 698     }
 699     
 700      
 701     /**
 702      * #################################
 703      *           Permissions
 704      * #################################
 705      */
 706     
 707     /**
 708      * Template accessor for Permissions.
 709      * See {@link TableListField_Item->Can()} for object-specific
 710      * permissions.
 711      * 
 712      * @return boolean
 713      */
 714     function Can($mode) {
 715         if($mode == 'add' && $this->isReadonly()) {
 716             return false;
 717         } else if($mode == 'delete' && $this->isReadonly()) {
 718             return false;
 719         } else if($mode == 'edit' && $this->isReadonly()) {
 720             return false;
 721         } else {
 722             return (in_array($mode, $this->permissions));
 723         }
 724         
 725     }
 726     
 727     function setPermissions($arr) {
 728         $this->permissions = $arr;
 729     }
 730 
 731     /**
 732      * @return array
 733      */
 734     function getPermissions() {
 735         return $this->permissions;
 736     }
 737 
 738     /**
 739      * #################################
 740      *           Pagination
 741      * #################################
 742      */
 743     function setShowPagination($bool) {
 744         $this->showPagination = (bool)$bool;
 745     }
 746 
 747     /**
 748      * @return boolean
 749      */
 750     function ShowPagination() {
 751         if($this->showPagination && !empty($this->summaryFieldList)) {
 752             user_error("You can't combine pagination and summaries - please disable one of them.", E_USER_ERROR);
 753         }
 754         return $this->showPagination;
 755     }
 756     
 757     function setPageSize($pageSize) {
 758         $this->pageSize = $pageSize;
 759     }
 760      
 761      function PageSize() {
 762         return $this->pageSize;
 763     }
 764      
 765     function ListStart() {
 766         return $_REQUEST['ctf'][$this->Name()]['start'];
 767     }
 768     
 769     /**
 770      * @param array
 771      * @deprecated Put the query string onto your form's link instead :-)
 772      */
 773     function setExtraLinkParams($params){
 774         user_error("TableListField::setExtraLinkParams() deprecated - put the query string onto your form's FormAction instead; it will be handed down to all field with special handlers", E_USER_NOTICE);
 775         $this->extraLinkParams = $params;
 776     }
 777     
 778     /**
 779      * @return array
 780      */
 781     function getExtraLinkParams(){
 782         return $this->extraLinkParams;
 783     }
 784     
 785     function FirstLink() {
 786         $start = 0;
 787         
 788         if(!isset($_REQUEST['ctf'][$this->Name()]['start']) || !is_numeric($_REQUEST['ctf'][$this->Name()]['start']) || $_REQUEST['ctf'][$this->Name()]['start'] == 0) {
 789             return null;
 790         }
 791         $baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
 792         $link = Controller::join_links($baseLink, "?ctf[{$this->Name()}][start]={$start}");
 793         if(isset($_REQUEST['ctf'][$this->Name()]['sort']) && ($sort = $_REQUEST['ctf'][$this->Name()]['sort'])) {
 794             $sortString = "ctf[{$this->Name()}][sort]={$sort}";
 795             if(isset($_REQUEST['ctf'][$this->Name()]['dir']) && ($dir = $_REQUEST['ctf'][$this->Name()]['dir'])) {
 796                 $sortString .= "&ctf[{$this->Name()}][dir]={$dir}";
 797             }
 798             $link .= "&" .$sortString;
 799         }
 800         if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
 801         return $link;
 802     }
 803     
 804     function PrevLink() {
 805         $currentStart = isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
 806 
 807         if($currentStart == 0) {
 808             return null;
 809         }
 810         
 811         $start = ($_REQUEST['ctf'][$this->Name()]['start'] - $this->pageSize < 0)  ? 0 : $_REQUEST['ctf'][$this->Name()]['start'] - $this->pageSize;
 812         
 813         $baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
 814         $link = Controller::join_links($baseLink, "?ctf[{$this->Name()}][start]={$start}");
 815         if(isset($_REQUEST['ctf'][$this->Name()]['sort']) && ($sort = $_REQUEST['ctf'][$this->Name()]['sort'])) {
 816             $sortString = "ctf[{$this->Name()}][sort]={$sort}";
 817             if(isset($_REQUEST['ctf'][$this->Name()]['dir']) && ($dir = $_REQUEST['ctf'][$this->Name()]['dir'])) {
 818                 $sortString .= "&ctf[{$this->Name()}][dir]={$dir}";
 819             }
 820             $link .= "&" .$sortString;
 821         }
 822         if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
 823         return $link;
 824     }
 825     
 826 
 827     function NextLink() {
 828         $currentStart = isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
 829         $start = ($currentStart + $this->pageSize < $this->TotalCount()) ? $currentStart + $this->pageSize : $this->TotalCount() % $this->pageSize > 0;
 830         if($currentStart >= $start-1) {
 831             return null;
 832         }
 833         $baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
 834         $link = Controller::join_links($baseLink, "?ctf[{$this->Name()}][start]={$start}");
 835         if(isset($_REQUEST['ctf'][$this->Name()]['sort']) && ($sort = $_REQUEST['ctf'][$this->Name()]['sort'])) {
 836             $sortString = "ctf[{$this->Name()}][sort]={$sort}";
 837             if(isset($_REQUEST['ctf'][$this->Name()]['dir']) && ($dir = $_REQUEST['ctf'][$this->Name()]['dir'])) {
 838                 $sortString .= "&ctf[{$this->Name()}][dir]={$dir}";
 839             }
 840             $link .= "&" .$sortString;
 841         }
 842         if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
 843         return $link;
 844     }
 845     
 846     function LastLink() {
 847         $pageSize = ($this->TotalCount() % $this->pageSize > 0) ? $this->TotalCount() % $this->pageSize : $this->pageSize;
 848         $start = $this->TotalCount() - $pageSize;
 849         // Check if there is only one page, or if we are on last page
 850         if($this->TotalCount() <= $pageSize || (isset($_REQUEST['ctf'][$this->Name()]['start']) &&  $_REQUEST['ctf'][$this->Name()]['start'] >= $start)) {
 851             return null;
 852         }
 853         
 854         $baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
 855         $link = Controller::join_links($baseLink, "?ctf[{$this->Name()}][start]={$start}");
 856         if(isset($_REQUEST['ctf'][$this->Name()]['sort']) && ($sort = $_REQUEST['ctf'][$this->Name()]['sort'])) {
 857             $sortString = "ctf[{$this->Name()}][sort]={$sort}";
 858             if(isset($_REQUEST['ctf'][$this->Name()]['dir']) && ($dir = $_REQUEST['ctf'][$this->Name()]['dir'])) {
 859                 $sortString .= "&ctf[{$this->Name()}][dir]={$dir}";
 860             }
 861             $link .= "&" .$sortString;
 862         }
 863         if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
 864         return $link;
 865     }
 866     
 867     function FirstItem() {
 868         if ($this->TotalCount() < 1) return 0;
 869         return isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] + 1 : 1;
 870     }
 871     
 872     function LastItem() {
 873         if(isset($_REQUEST['ctf'][$this->Name()]['start'])) {
 874             return $_REQUEST['ctf'][$this->Name()]['start'] + min($this->pageSize, $this->TotalCount() - $_REQUEST['ctf'][$this->Name()]['start']);
 875         } else {
 876             return min($this->pageSize, $this->TotalCount());
 877         }
 878     }
 879     
 880     function TotalCount() {
 881         if($this->totalCount) {
 882             return $this->totalCount;
 883         }
 884         if($this->customSourceItems) {
 885             return $this->customSourceItems->Count();
 886         }
 887 
 888         $this->totalCount = $this->getQuery()->unlimitedRowCount();
 889         return $this->totalCount;
 890     }
 891     
 892     
 893     
 894     /**
 895      * #################################
 896      *           Search
 897      * #################################
 898      * 
 899      * @todo Not fully implemented at the moment
 900      */
 901      
 902      /**
 903       * Compile all request-parameters for search and pagination
 904       * (except the actual list-positions) as a query-string.
 905       * 
 906       * @return String URL-parameters
 907       */
 908     function filterString() {
 909         
 910     }
 911     
 912     
 913     
 914     /**
 915      * #################################
 916      *           CSV Export
 917      * #################################
 918      */
 919      function setFieldListCsv($fields) {
 920         $this->fieldListCsv = $fields;
 921      }
 922     
 923     /**
 924      * Set the CSV separator character.  Defaults to ,
 925      */
 926     function setCsvSeparator($csvSeparator) {
 927         $this->csvSeparator = $csvSeparator;
 928     }
 929     
 930     /**
 931      * Get the CSV separator character.  Defaults to ,
 932      */
 933     function getCsvSeparator() {
 934         return $this->csvSeparator;
 935     }
 936     
 937     /**
 938      * Remove the header row from the CSV export
 939      */
 940     function removeCsvHeader() {
 941         $this->csvHasHeader = false;
 942     }
 943      
 944     /**
 945      * Exports a given set of comma-separated IDs (from a previous search-query, stored in a HiddenField).
 946      * Uses {$csv_columns} if present, and falls back to {$result_columns}.
 947      * We move the most filedata generation code to the function {@link generateExportFileData()} so that a child class
 948      * could reuse the filedata generation code while overwrite export function.
 949      * 
 950      * @todo Make relation-syntax available (at the moment you'll have to use custom sql) 
 951      */
 952     function export() {
 953         $now = Date("d-m-Y-H-i");
 954         $fileName = "export-$now.csv";
 955         
 956         if($fileData = $this->generateExportFileData($numColumns, $numRows)){
 957             return SS_HTTPRequest::send_file($fileData, $fileName);
 958         }else{
 959             user_error("No records found", E_USER_ERROR);
 960         }
 961     }
 962     
 963     function generateExportFileData(&$numColumns, &$numRows) {
 964         $separator = $this->csvSeparator;
 965         $csvColumns = ($this->fieldListCsv) ? $this->fieldListCsv : $this->fieldList;
 966         $fileData = "\xEF\xBB\xBF"; // Make UTF-8 + BOM
 967         $columnData = array();
 968         $fieldItems = new DataObjectSet();
 969         
 970         if($this->csvHasHeader) {
 971             $fileData .= "\"" . implode("\"{$separator}\"", array_keys($csvColumns)) . "\"";
 972             $fileData .= "\n";
 973         }
 974 
 975         if(isset($this->customSourceItems)) {
 976             $items = $this->customSourceItems;
 977         } else {
 978             $dataQuery = $this->getCsvQuery();
 979             $items = $dataQuery->execute();
 980         }
 981         
 982         // temporary override to adjust TableListField_Item behaviour
 983         $this->setFieldFormatting(array());
 984         $this->fieldList = $csvColumns;
 985 
 986         if($items) {
 987             foreach($items as $item) {
 988                 if(is_array($item)) {
 989                     $className = isset($item['RecordClassName']) ? $item['RecordClassName'] : $item['ClassName'];
 990                     $item = new $className($item);
 991                 }
 992                 $fieldItem = new $this->itemClass($item, $this);
 993                 
 994                 $fields = $fieldItem->Fields(false);
 995                 $columnData = array();
 996                 if($fields) foreach($fields as $field) {
 997                     $value = $field->Value;
 998                     
 999                     // TODO This should be replaced with casting
1000                     if(array_key_exists($field->Name, $this->csvFieldFormatting)) {
1001                         $format = str_replace('$value', "__VAL__", $this->csvFieldFormatting[$field->Name]);
1002                         $format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
1003                         $format = str_replace('__VAL__', '$value', $format);
1004                         eval('$value = "' . $format . '";');
1005                     }
1006                     
1007                     $value = str_replace(array("\r", "\n"), "\n", $value);
1008                     $value = str_replace("\n", "\r\n", $value);
1009                     $tmpColumnData = '"' . str_replace('"', '""', $value) . '"';
1010                     $columnData[] = $tmpColumnData;
1011                 }
1012                 $fileData .= implode($separator, $columnData);
1013                 $fileData .= "\r\n";
1014                 
1015                 $item->destroy();
1016                 unset($item);
1017                 unset($fieldItem);
1018             }
1019             
1020             $numColumns = count($columnData);
1021             $numRows = $fieldItems->count();
1022             return $fileData;
1023         } else {
1024             return null;
1025         }
1026     }
1027     
1028     /**
1029      * We need to instanciate this button manually as a normal button has no means of adding inline onclick-behaviour.
1030      */
1031     function ExportLink() {
1032         $exportLink = Controller::join_links($this->Link(), 'export');
1033         
1034         if($this->extraLinkParams) $exportLink .= "?" . http_build_query($this->extraLinkParams);
1035         return $exportLink;
1036     }
1037 
1038     function printall() {
1039         Requirements::clear();
1040         Requirements::css(CMS_DIR . '/css/typography.css');
1041         Requirements::css(CMS_DIR . '/css/cms_right.css');
1042         Requirements::css(SAPPHIRE_DIR . '/css/TableListField_print.css');
1043         
1044         $this->cachedSourceItems = null;
1045         $oldShowPagination = $this->showPagination;
1046         $this->showPagination = false;
1047 
1048         increase_time_limit_to();
1049         $this->Print = true;
1050         
1051         $result = $this->renderWith(array($this->template . '_printable', 'TableListField_printable'));
1052         
1053         $this->showPagination = $oldShowPagination;
1054         
1055         return $result;
1056     }
1057 
1058     function PrintLink() {
1059         $link = Controller::join_links($this->Link(), 'printall');
1060         if(isset($_REQUEST['ctf'][$this->Name()]['sort'])) {
1061             $link = HTTP::setGetVar("ctf[{$this->Name()}][sort]",Convert::raw2xml($_REQUEST['ctf'][$this->Name()]['sort']), $link);
1062         }
1063         return $link;
1064     }
1065     
1066     /**
1067      * #################################
1068      *           Utilty
1069      * #################################
1070      */
1071     function Utility() {
1072         $links = new DataObjectSet();
1073         if($this->can('export')) {
1074             $links->push(new ArrayData(array(
1075                 'Title' => _t('TableListField.CSVEXPORT', 'Export to CSV'),
1076                 'Link' => $this->ExportLink()
1077             )));
1078         }
1079         if($this->can('print')) {
1080             $links->push(new ArrayData(array(
1081                 'Title' => _t('TableListField.PRINT', 'Print'),
1082                 'Link' => $this->PrintLink()
1083             )));
1084         }
1085         return $links;
1086         
1087     }
1088     
1089     /**
1090      * Returns the content of the TableListField as a piece of FormResponse javascript
1091      * @deprecated Please use the standard URL through Link() which gives you the FieldHolder as an HTML fragment.
1092      */
1093     function ajax_refresh() {
1094         // compute sourceItems here instead of Items() to ensure that
1095         // pagination and filters are respected on template accessors
1096         //$this->sourceItems();
1097 
1098         $response = $this->renderWith($this->template);
1099         FormResponse::update_dom_id($this->id(), $response, 1);
1100         FormResponse::set_non_ajax_content($response);
1101         return FormResponse::respond();
1102     }
1103     
1104     function setFieldCasting($casting) {
1105         $this->fieldCasting = $casting;
1106     }
1107 
1108     function setFieldFormatting($formatting) {
1109         $this->fieldFormatting = $formatting;
1110     }
1111     
1112     function setCSVFieldFormatting($formatting) {
1113         $this->csvFieldFormatting = $formatting;
1114     }
1115     
1116     /**
1117      * Edit the field list
1118      */
1119     function setFieldList($fieldList) {
1120         $this->fieldList = $fieldList;
1121     }
1122     
1123     /**
1124      * @return String
1125      */
1126     function Name() {
1127         return $this->name;
1128     }
1129     
1130     function Title() {
1131       // adding translating functionality
1132       // this is a bit complicated, because this parameter is passed to this class
1133       // and should come here translated already
1134       // adding this to TODO probably add a method to the classes
1135       // to return they're translated string
1136       // added by ruibarreiros @ 27/11/2007
1137         return $this->sourceClass ? singleton($this->sourceClass)->i18n_plural_name() : $this->Name();
1138     }
1139     
1140     function NameSingular() {
1141       // same as Title()
1142       // added by ruibarreiros @ 27/11/2007
1143       return $this->sourceClass ? singleton($this->sourceClass)->i18n_singular_name() : $this->Name();
1144     }
1145 
1146     function NamePlural() {
1147       // same as Title()
1148       // added by ruibarreiros @ 27/11/2007
1149         return $this->sourceClass ? singleton($this->sourceClass)->i18n_plural_name() : $this->Name();
1150     } 
1151     
1152     function setTemplate($template) {
1153         $this->template = $template;
1154     }
1155     
1156     function CurrentLink() {
1157         $link = $this->Link();
1158         
1159         if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
1160             $start = ($_REQUEST['ctf'][$this->Name()]['start'] < 0)  ? 0 : $_REQUEST['ctf'][$this->Name()]['start'];
1161             $link = Controller::join_links($link, "?ctf[{$this->Name()}][start]={$start}");
1162         }
1163 
1164         if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
1165         
1166         return $link;
1167     }
1168     
1169     function BaseLink() {
1170         user_error("TableListField::BaseLink() deprecated, use Link() instead", E_USER_NOTICE);
1171         return $this->Link();
1172     }
1173     
1174     /**
1175      * @return Int
1176      */
1177     function sourceID() {
1178         $idField = $this->form->dataFieldByName('ID');
1179         if(!isset($idField)) {
1180             user_error("TableListField needs a formfield named 'ID' to be present", E_USER_ERROR);
1181         }
1182         return $idField->Value();
1183     }
1184     
1185     /**
1186      * Helper method to determine permissions for a scaffolded
1187      * TableListField (or subclasses) - currently used in {@link ModelAdmin} and {@link DataObject->scaffoldFormFields()}.
1188      * Returns true for each permission that doesn't have an explicit getter.
1189      * 
1190      * @todo Temporary method, implement directly in FormField subclasses with object-level permissions.
1191      *
1192      * @param string $class
1193      * @param numeric $id
1194      * @return array
1195      */
1196     public static function permissions_for_object($class, $id = null) {
1197         $permissions = array();
1198         $obj = ($id) ? DataObject::get_by_id($class, $id) : singleton($class);
1199         
1200         if(!$obj->hasMethod('canView') || $obj->canView()) $permissions[] = 'show';
1201         if(!$obj->hasMethod('canEdit') || $obj->canEdit()) $permissions[] = 'edit';
1202         if(!$obj->hasMethod('canDelete') || $obj->canDelete()) $permissions[] = 'delete';
1203         if(!$obj->hasMethod('canCreate') || $obj->canCreate()) $permissions[] = 'add';
1204         
1205         return $permissions;
1206     }
1207 
1208     /**
1209      * @param $value
1210      * 
1211      */
1212     function getCastedValue($value, $castingDefinition) {
1213         if(is_array($castingDefinition)) {
1214             $castingParams = $castingDefinition;
1215             array_shift($castingParams);
1216             $castingDefinition = array_shift($castingDefinition);
1217         } else {
1218             $castingParams = array();
1219         }
1220         if(strpos($castingDefinition,'->') === false) {
1221             $castingFieldType = $castingDefinition;
1222             $castingField = DBField::create($castingFieldType, $value);
1223             $value = call_user_func_array(array($castingField,'XML'),$castingParams);
1224         } else {
1225             $fieldTypeParts = explode('->', $castingDefinition);
1226             $castingFieldType = $fieldTypeParts[0]; 
1227             $castingMethod = $fieldTypeParts[1];
1228             $castingField = DBField::create($castingFieldType, $value);
1229             $value = call_user_func_array(array($castingField,$castingMethod),$castingParams);
1230         }
1231         
1232         return $value;
1233     }    
1234      
1235     /**
1236      * #########################
1237      *       Highlighting
1238      * #########################
1239      */
1240     function setHighlightConditions($conditions) {
1241         $this->highlightConditions = $conditions;
1242     }
1243 }
1244 
1245 /**
1246  * A single record in a TableListField.
1247  * @package forms
1248  * @subpackage fields-relational
1249  * @see TableListField
1250  */
1251 class TableListField_Item extends ViewableData {
1252     
1253     /**
1254      * @var DataObject The underlying data record,
1255      * usually an element of {@link TableListField->sourceItems()}.
1256      */
1257     protected $item;
1258     
1259     /**
1260      * @var TableListField
1261      */
1262     protected $parent;
1263     
1264     function __construct($item, $parent) {
1265         $this->failover = $this->item = $item;
1266         $this->parent = $parent;
1267         parent::__construct();
1268     }
1269     
1270     function ID() {
1271         return $this->item->ID;
1272     }
1273     
1274     function Parent() {
1275         return $this->parent;
1276     }
1277     
1278     function Fields($xmlSafe = true) {
1279         $list = $this->parent->FieldList();
1280         foreach($list as $fieldName => $fieldTitle) {
1281             $value = "";
1282 
1283             // This supports simple FieldName syntax
1284             if(strpos($fieldName,'.') === false) {
1285                 $value = ($this->item->XML_val($fieldName) && $xmlSafe) ? $this->item->XML_val($fieldName) : $this->item->RAW_val($fieldName);
1286             // This support the syntax fieldName = Relation.RelatedField
1287             } else {                    
1288                 $fieldNameParts = explode('.', $fieldName)  ;
1289                 $tmpItem = $this->item;
1290                 for($j=0;$j<sizeof($fieldNameParts);$j++) {
1291                     $relationMethod = $fieldNameParts[$j];
1292                     $idField = $relationMethod . 'ID';
1293                     if($j == sizeof($fieldNameParts)-1) {
1294                         if($tmpItem) $value = $tmpItem->$relationMethod;
1295                     } else {
1296                         if($tmpItem) $tmpItem = $tmpItem->$relationMethod();
1297                     }
1298                 }
1299             }
1300             
1301             // casting
1302             if(array_key_exists($fieldName, $this->parent->fieldCasting)) {
1303                 $value = $this->parent->getCastedValue($value, $this->parent->fieldCasting[$fieldName]);
1304             } elseif(is_object($value) && method_exists($value, 'Nice')) {
1305                 $value = $value->Nice();
1306             }
1307             
1308             // formatting
1309             $item = $this->item;
1310             if(array_key_exists($fieldName, $this->parent->fieldFormatting)) {
1311                 $format = str_replace('$value', "__VAL__", $this->parent->fieldFormatting[$fieldName]);
1312                 $format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
1313                 $format = str_replace('__VAL__', '$value', $format);
1314                 eval('$value = "' . $format . '";');
1315             }
1316             
1317             //escape
1318             if($escape = $this->parent->fieldEscape){
1319                 foreach($escape as $search => $replace){
1320                     $value = str_replace($search, $replace, $value);
1321                 }
1322             }
1323             
1324             $fields[] = new ArrayData(array(
1325                 "Name" => $fieldName, 
1326                 "Title" => $fieldTitle,
1327                 "Value" => $value,
1328                 "CsvSeparator" => $this->parent->getCsvSeparator(),
1329             ));
1330         }
1331         return new DataObjectSet($fields);
1332     }
1333     
1334     function Markable() {
1335         return $this->parent->Markable;
1336     }
1337     
1338     /**
1339      * Checks global permissions for field in  {@link TableListField->Can()}.
1340      * If they are allowed, it checks for object permissions by assuming
1341      * a method with "can" + $mode parameter naming, e.g. canDelete().
1342      * 
1343      * @param string $mode See {@link TableListField::$permissions} array.
1344      * @return boolean
1345      */
1346     function Can($mode) {
1347         $canMethod = "can" . ucfirst($mode);
1348         if(!$this->parent->Can($mode)) {
1349             // check global settings for the field instance
1350             return false;
1351         } elseif($this->item->hasMethod($canMethod)) {
1352             // if global allows, check object specific permissions (e.g. canDelete())
1353             return $this->item->$canMethod();
1354         } else {
1355             // otherwise global allowed this action, so return TRUE
1356             return true;
1357         }
1358     }
1359     
1360     function Link($action = null) {
1361         if($this->parent->getForm()) {
1362             $parentUrlParts = parse_url($this->parent->Link());
1363             $queryPart = (isset($parentUrlParts['query'])) ? '?' . $parentUrlParts['query'] : null;
1364             return Controller::join_links($parentUrlParts['path'], 'item', $this->item->ID, $action, $queryPart);
1365         } else {
1366             // allow for instanciation of this FormField outside of a controller/form
1367             // context (e.g. for unit tests)
1368             return false;
1369         }
1370     }
1371 
1372     /**
1373      * Returns all row-based actions not disallowed through permissions.
1374      * See TableListField->Action for a similiar dummy-function to work
1375      * around template-inheritance issues.
1376      * 
1377      * @return DataObjectSet
1378      */
1379     function Actions() {
1380         $allowedActions = new DataObjectSet();
1381         foreach($this->parent->actions as $actionName => $actionSettings) {
1382             if($this->parent->Can($actionName)) {
1383                 $allowedActions->push(new ArrayData(array(
1384                     'Name' => $actionName,
1385                     'Link' => $this->{ucfirst($actionName).'Link'}(),
1386                     'Icon' => $actionSettings['icon'],
1387                     'IconDisabled' => $actionSettings['icon_disabled'],
1388                     'Label' => $actionSettings['label'],
1389                     'Class' => $actionSettings['class'],
1390                     'Default' => ($actionName == $this->parent->defaultAction),
1391                     'IsAllowed' => $this->Can($actionName), 
1392                 )));
1393             }
1394         }
1395         
1396         return $allowedActions;
1397     }
1398    
1399     function BaseLink() {
1400         user_error("TableListField_Item::BaseLink() deprecated, use Link() instead", E_USER_NOTICE);
1401         return $this->Link();
1402     }
1403 
1404     function DeleteLink() {
1405         return Controller::join_links($this->Link(), "delete");
1406     }
1407     
1408     function MarkingCheckbox() {
1409         $name = $this->parent->Name() . '[]';
1410         
1411         if($this->parent->isReadonly())
1412             return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\" disabled=\"disabled\" />";
1413         else
1414             return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\" />";
1415     }
1416     
1417     function HighlightClasses() {
1418         $classes = array();
1419         foreach($this->parent->highlightConditions as $condition) {
1420             $rule = str_replace("\$","\$this->item->", $condition['rule']);
1421             $ruleApplies = null;
1422             eval('$ruleApplies = ('.$rule.');');
1423             if($ruleApplies) {
1424                 if(isset($condition['exclusive']) && $condition['exclusive']) {
1425                     return $condition['class'];
1426                 } else {
1427                     $classes[] = $condition['class'];                   
1428                 }
1429             }
1430         }
1431         
1432         return (count($classes) > 0) ? " " . implode(" ", $classes) : false;
1433     }
1434         
1435     /**
1436      * Legacy: Please use permissions instead
1437      */
1438     function isReadonly() {
1439         return $this->parent->Can('delete');
1440     }
1441 }
1442 
1443 /**
1444  * @package forms
1445  * @subpackage fields-relational
1446  */
1447 class TableListField_ItemRequest extends RequestHandler {
1448     protected $ctf;
1449     protected $itemID;
1450     protected $methodName;
1451     
1452     static $url_handlers = array(
1453         '$Action!' => '$Action',
1454         '' => 'index',
1455     );
1456     
1457     function Link() {
1458         return Controller::join_links($this->ctf->Link(), 'item/' . $this->itemID);
1459     }
1460     
1461     function __construct($ctf, $itemID) {
1462         $this->ctf = $ctf;
1463         $this->itemID = $itemID;
1464         
1465         parent::__construct();
1466     }
1467 
1468     function delete() {
1469         if($this->ctf->Can('delete') !== true) {
1470             return false;
1471         }
1472 
1473         $this->dataObj()->delete();
1474     }
1475 
1476     ///////////////////////////////////////////////////////////////////////////////////////////////////
1477 
1478     /**
1479      * Return the data object being manipulated
1480      */
1481     function dataObj() {
1482         // used to discover fields if requested and for population of field
1483         if(is_numeric($this->itemID)) {
1484             // we have to use the basedataclass, otherwise we might exclude other subclasses 
1485             return DataObject::get_by_id(ClassInfo::baseDataClass(Object::getCustomClass($this->ctf->sourceClass())), $this->itemID); 
1486         }
1487         
1488     }
1489 
1490     /**
1491      * Returns the db-fieldname of the currently used has_one-relationship.
1492      */
1493     function getParentIdName( $parentClass, $childClass ) {
1494         return $this->getParentIdNameRelation( $childClass, $parentClass, 'has_one' );
1495     }
1496     
1497     /**
1498      * Manually overwrites the parent-ID relations.
1499      * @see setParentClass()
1500      * 
1501      * @param String $str Example: FamilyID (when one Individual has_one Family)
1502      */
1503     function setParentIdName($str) {
1504         $this->parentIdName = $str;
1505     }
1506     
1507     /**
1508      * Returns the db-fieldname of the currently used relationship.
1509      */
1510     function getParentIdNameRelation($parentClass, $childClass, $relation) {
1511         if($this->parentIdName) return $this->parentIdName; 
1512         
1513         $relations = singleton($parentClass)->$relation();
1514         $classes = ClassInfo::ancestry($childClass);
1515         foreach($relations as $k => $v) {
1516             if(array_key_exists($v, $classes)) return $k . 'ID';
1517         }
1518         return false;
1519     }
1520     
1521     /**
1522      * @return TableListField
1523      */
1524     function getParentController() {
1525         return $this->ctf;
1526     }
1527 }
1528 ?>
1529 
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.1 API Docs API documentation generated by ApiGen 2.8.0