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

  • AdditionalMenuWidget_Item
  • AdvancedSliderHomepageWidget_Item
  • AssetManagerFolder
  • BannerWidget_Item
  • BaseObjectDecorator
  • BookingOrder
  • BookingPaymentMethod
  • BookingService
  • Boolean
  • ButtonsBlockHomepageWidget_Item
  • CarouselHomepageWidget_Item
  • CatalogRubricsHomepageWidget_CatalogDecorator
  • ClientEmailOrderNotification
  • ClientVKOrderNotification
  • ComponentSet
  • Currency
  • DatabaseAdmin
  • DataObject
  • DataObjectDecorator
  • DataObjectLog
  • DataObjectSet
  • DataObjectSet_Iterator
  • Date
  • DB
  • DBField
  • Decimal
  • DocumentItem
  • DocumentPage_File
  • Double
  • Enum
  • ErrorPageSubsite
  • FileDataObjectTrackingDecorator
  • FileImportDecorator
  • Float
  • ForeignKey
  • Hierarchy
  • HTMLText
  • HTMLVarchar
  • ImportLog_Item
  • Int
  • ManagerEmailOrderNotification
  • Material3D_File
  • MediawebPage_File
  • MediawebPage_Photo
  • MobileContentDecorator
  • Money
  • MultiEnum
  • MySQLDatabase
  • MySQLQuery
  • OrderDataObject
  • OrderHandlersDecorator
  • OrderItemVariationDecorator
  • OrderService
  • OrderServiceOrder
  • OrdersExportDecorator
  • PageIcon
  • PageWidgets
  • Payment
  • PaymentMethodShippingDecorator
  • PaymentOrderExtension
  • Percentage
  • PhotoAlbumItem
  • PhotoAlbumProductLinkDecorator
  • PhotoAlbumWidgetLinkDecorator
  • PhotoGalleryHomepageWidget_Item
  • PrimaryKey
  • Product3DDecorator
  • ProductCatalogCatalogLinkedDecorator
  • RatePeriod
  • RealtyImportLog
  • RealtyImportLog_Item
  • RedirectEntry
  • RoomOrder
  • RoomOrderPerson
  • RoomRate
  • RoomService
  • RoomServiceOrder
  • SberbankPaymentDecorator
  • SeoOpenGraphPageDecorator
  • ServiceOrder
  • ShippingMethodPaymentDecorator
  • ShopCountry
  • SimpleOrderCatalogDecorator
  • SimpleOrderProductDecorator
  • SiteConfigWidgets
  • SiteTreeDecorator
  • SiteTreeImportDecorator
  • SliderHomepageWidget_Item
  • SMSCOrderNotification
  • SMSOrderNotification
  • SortableDataObject
  • SQLMap
  • SQLMap_Iterator
  • SQLQuery
  • SS_Database
  • SS_Datetime
  • SS_Query
  • StringField
  • SubsiteDomain
  • Text
  • TextAnonsWidget_Item
  • Texture3D_File
  • Time
  • Varchar
  • Versioned
  • Versioned_Version
  • VideoCategory
  • VideoEntry
  • VKNotificationQueue
  • WebylonWidget_Item
  • YaMoneyPaymentDecorator
  • Year

Interfaces

  • CompositeDBField
  • CurrentPageIdentifier
  • DataObjectInterface
   1 <?php
   2 /**
   3  * This class represents a set of {@link ViewableData} subclasses (mostly {@link DataObject} or {@link ArrayData}).
   4  * It is used by the ORM-layer of Silverstripe to return query-results from {@link SQLQuery}.
   5  * @package sapphire
   6  * @subpackage model
   7  */
   8 class DataObjectSet extends ViewableData implements IteratorAggregate, Countable {
   9     /**
  10      * The DataObjects in this set.
  11      * @var array
  12      */
  13     protected $items = array();
  14     
  15     protected $odd = 0;
  16     
  17     /**
  18      * True if the current DataObject is the first in this set.
  19      * @var boolean
  20      */
  21     protected $first = true;
  22     
  23     /**
  24      * True if the current DataObject is the last in this set.
  25      * @var boolean
  26      */
  27     protected $last = false;
  28     
  29     /**
  30      * The current DataObject in this set.
  31      * @var DataObject
  32      */
  33     protected $current = null;
  34 
  35     /**
  36      * The number object the current page starts at.
  37      * @var int
  38      */
  39     protected $pageStart;
  40     
  41     /**
  42      * The number of objects per page.
  43      * @var int
  44      */
  45     protected $pageLength;
  46     
  47     /**
  48      * Total number of DataObjects in this set.
  49      * @var int
  50      */
  51     protected $totalSize;
  52     
  53     /**
  54      * The pagination GET variable that controls the start of this set.
  55      * @var string
  56      */
  57     protected $paginationGetVar = "start";
  58     
  59     /**
  60      * Create a new DataObjectSet. If you pass one or more arguments, it will try to convert them into {@link ArrayData} objects. 
  61      * @todo Does NOT automatically convert objects with complex datatypes (e.g. converting arrays within an objects to its own DataObjectSet)                          
  62      * 
  63      * @param ViewableData|array|mixed $items Parameters to use in this set, either as an associative array, object with simple properties, or as multiple parameters.
  64      */
  65     public function __construct($items = null) {
  66         if($items) {
  67             // if the first parameter is not an array, or we have more than one parameter, collate all parameters to an array
  68             // otherwise use the passed array
  69             $itemsArr = (!is_array($items) || count(func_get_args()) > 1) ? func_get_args() : $items;
  70             
  71             // We now have support for using the key of a data object set
  72             foreach($itemsArr as $i => $item) {
  73                 if(is_subclass_of($item, 'ViewableData')) {
  74                     $this->items[$i] = $item;
  75                 } elseif(is_object($item) || ArrayLib::is_associative($item)) {
  76                     $this->items[$i] = new ArrayData($item);
  77                 } else {
  78                     user_error(
  79                         "DataObjectSet::__construct: Passed item #{$i} is not an object or associative array, 
  80                         can't be properly iterated on in templates", 
  81                         E_USER_WARNING
  82                     );                      
  83                     $this->items[$i] = $item;
  84                 }
  85             }
  86 
  87             
  88         }
  89         parent::__construct();
  90     }
  91     
  92     /**
  93      * Destory all of the DataObjects in this set.
  94      */
  95     public function destroy() {
  96         foreach($this->items as $item) {
  97             $item->destroy();
  98         }
  99     }
 100 
 101     /**
 102      * Removes all the items in this set.
 103      */
 104     public function emptyItems() {
 105         $this->items = array();
 106     }
 107     
 108     /**
 109      * Convert this DataObjectSet to an array of DataObjects.
 110      * @param string $index Index the array by this field.
 111      * @return array
 112      */
 113     public function toArray($index = null) {
 114         if(!$index) {
 115             return $this->items;
 116         }
 117         
 118         $map = array();
 119         
 120         foreach($this->items as $item) {
 121             $map[$item->$index] = $item;
 122         }
 123             
 124         return $map;
 125     }
 126 
 127     /**
 128     * Convert this DataObjectSet to an array of maps.
 129     * @param string $index Index the array by this field.
 130     * @return array
 131     */
 132     public function toNestedArray($index = null){
 133         if(!$index) {
 134             $index = "ID";
 135         }
 136         
 137         $map = array();
 138         
 139         foreach( $this->items as $item ) {
 140             $map[$item->$index] = $item->getAllFields();
 141         }
 142         
 143         return $map;
 144     }
 145 
 146     /**
 147      * Returns an array of ID => Title for the items in this set.
 148      * 
 149      * This is an alias of {@link DataObjectSet->map()}
 150      * 
 151      * @deprecated 2.5 Please use map() instead
 152      * 
 153      * @param string $index The field to use as a key for the array
 154      * @param string $titleField The field (or method) to get values for the map
 155      * @param string $emptyString Empty option text e.g "(Select one)"
 156      * @param bool $sort Sort the map alphabetically based on the $titleField value
 157      * @return array
 158      */
 159     public function toDropDownMap($index = 'ID', $titleField = 'Title', $emptyString = null, $sort = false) {
 160         return $this->map($index, $titleField, $emptyString, $sort);
 161     }
 162     
 163     /**
 164      * Set number of objects on each page.
 165      * @param int $length Number of objects per page
 166      */
 167     public function setPageLength($length) {
 168         $this->pageLength = $length;
 169     }
 170     
 171     /**
 172      * Set the page limits.
 173      * @param int $pageStart The start of this page.
 174      * @param int $pageLength Number of objects per page
 175      * @param int $totalSize Total number of objects.
 176      */
 177     public function setPageLimits($pageStart, $pageLength, $totalSize) {
 178         $this->pageStart = $pageStart;
 179         $this->pageLength = $pageLength;
 180         $this->totalSize = $totalSize;
 181     }
 182     
 183     /**
 184      * Get the page limits
 185      * @return array
 186      */
 187     public function getPageLimits() {
 188         return array(
 189             'pageStart' => $this->pageStart,
 190             'pageLength' => $this->pageLength,
 191             'totalSize' => $this->totalSize,
 192         );
 193     }
 194 
 195     /**
 196      * Use the limit from the given query to add prev/next buttons to this DataObjectSet.
 197      * @param SQLQuery $query The query used to generate this DataObjectSet
 198      */
 199     public function parseQueryLimit(SQLQuery $query) {
 200         if($query->limit) {
 201             if(is_array($query->limit)) {
 202                 $length = $query->limit['limit'];
 203                 $start = $query->limit['start'];
 204             } else if(stripos($query->limit, 'OFFSET')) {
 205                 list($length, $start) = preg_split("/ +OFFSET +/i", trim($query->limit));
 206             } else {
 207                 $result = preg_split("/ *, */", trim($query->limit));
 208                 $start = $result[0];
 209                 $length = isset($result[1]) ? $result[1] : null;
 210             }
 211             
 212             if(!$length) { 
 213                 $length = $start;
 214                 $start = 0;
 215             }   
 216             $this->setPageLimits($start, $length, $query->unlimitedRowCount());
 217         }
 218     }
 219     
 220     /**
 221      * Returns the number of the current page.
 222      * @return int
 223      */
 224     public function CurrentPage() {
 225         return floor($this->pageStart / $this->pageLength) + 1;
 226     }
 227     
 228     /**
 229      * Returns the total number of pages.
 230      * @return int
 231      */
 232     public function TotalPages() {
 233         if($this->totalSize == 0) {
 234             $this->totalSize = $this->Count();
 235         }
 236         if($this->pageLength == 0) {
 237             $this->pageLength = 10;
 238         }
 239         
 240         return ceil($this->totalSize / $this->pageLength);
 241     }
 242 
 243     /**
 244      * Return a datafeed of page-links, good for use in search results, etc.
 245      * $maxPages will put an upper limit on the number of pages to return.  It will
 246      * show the pages surrounding the current page, so you can still get to the deeper pages.
 247      * @param int $maxPages The maximum number of pages to return
 248      * @return DataObjectSet
 249      */
 250     public function Pages($maxPages = 0){
 251         $ret = new DataObjectSet();
 252 
 253         if($maxPages) {
 254             $startPage = ($this->CurrentPage() - floor($maxPages / 2)) - 1;
 255             $endPage = $this->CurrentPage() + floor($maxPages / 2);
 256 
 257             if($startPage < 0) {
 258                 $startPage = 0;
 259                 $endPage = $maxPages;
 260             }
 261             if($endPage > $this->TotalPages()) {
 262                 $endPage = $this->TotalPages();
 263                 $startPage = max(0, $endPage - $maxPages);
 264             }
 265 
 266         } else {
 267             $startPage = 0;
 268             $endPage = $this->TotalPages();
 269         }
 270 
 271         for($i=$startPage; $i < $endPage; $i++){
 272             $link = HTTP::setGetVar($this->paginationGetVar, ($i > 0) ? $i * $this->pageLength : null);
 273             $currentBool = ($this->CurrentPage() == $i+1) ? true : false;
 274             $thePage = new ArrayData(array(
 275                     "PageNum" => $i+1,
 276                     "Link" => $link,
 277                     "CurrentBool" => $currentBool,
 278                     "TotalPages" => $this->TotalPages(),
 279                     'StartItem' => $i * $this->pageLength + 1,
 280                     'EndItem' => ($i + 1) * $this->pageLength,
 281                     'LinkOrCurrent' => ($currentBool) ? 'current' : 'link',
 282             ));
 283             $ret->push($thePage);
 284         }
 285         
 286         return $ret;
 287     }
 288     
 289     /*
 290      * Display a summarized pagination which limits the number of pages shown
 291      * "around" the currently active page for visual balance.
 292      * In case more paginated pages have to be displayed, only 
 293      * 
 294      * Example: 25 pages total, currently on page 6, context of 4 pages
 295      * [prev] [1] ... [4] [5] [[6]] [7] [8] ... [25] [next]
 296      * 
 297      * Example template usage:
 298      * <code>
 299      * <% if MyPages.MoreThanOnePage %>
 300      *  <% if MyPages.NotFirstPage %>
 301      *      <a class="prev" href="$MyPages.PrevLink">Prev</a>
 302      *  <% end_if %>
 303      *  <% control MyPages.PaginationSummary(4) %>
 304      *      <% if CurrentBool %>
 305      *          $PageNum
 306      *      <% else %>
 307      *          <% if Link %>
 308      *              <a href="$Link">$PageNum</a>
 309      *          <% else %>
 310      *              ...
 311      *          <% end_if %>
 312      *      <% end_if %>
 313      *  <% end_control %>
 314      *  <% if MyPages.NotLastPage %>
 315      *      <a class="next" href="$MyPages.NextLink">Next</a>
 316      *  <% end_if %>
 317      * <% end_if %>
 318      * </code>
 319      * 
 320      * @param integer $context Number of pages to display "around" the current page. Number should be even,
 321      *  because its halved to either side of the current page.
 322      * @return  DataObjectSet
 323      */
 324     public function PaginationSummary($context = 4) {
 325         $ret = new DataObjectSet();
 326         
 327         // convert number of pages to even number for offset calculation
 328         if($context % 2) $context--;
 329         
 330         // find out the offset
 331         $current = $this->CurrentPage();
 332         $totalPages = $this->TotalPages();
 333         
 334         // if the first or last page is shown, use all content on one side (either left or right of current page)
 335         // otherwise half the number for usage "around" the current page
 336         $offset = ($current == 1 || $current == $totalPages) ? $context : floor($context/2);
 337         
 338         $leftOffset = $current - ($offset);
 339         if($leftOffset < 1) $leftOffset = 1;
 340         if($leftOffset + $context > $totalPages) $leftOffset = $totalPages - $context;
 341 
 342         for($i=0; $i < $totalPages; $i++) {
 343             $link = HTTP::setGetVar($this->paginationGetVar, $i*$this->pageLength);
 344             $num = $i+1;
 345             $currentBool = ($current == $i+1) ? true : false;
 346             if(
 347                 ($num == $leftOffset-1 && $num != 1 && $num != $totalPages)
 348                 || ($num == $leftOffset+$context+1 && $num != 1 && $num != $totalPages)
 349             ) {
 350                 $ret->push(new ArrayData(array(
 351                         "PageNum" => null,
 352                         "Link" => null,
 353                         "CurrentBool" => $currentBool,
 354                         'StartItem' => $i * $this->pageLength + 1,
 355                         'EndItem' => ($i + 1) * $this->pageLength,
 356                         'LinkOrCurrent' => ($this->CurrentPage() == $i+1) ? 'current' : 'link',
 357                     )
 358                 ));
 359             } else if($num == 1 || $num == $totalPages || in_array($num, range($current-$offset,$current+$offset))) { 
 360                 $ret->push(new ArrayData(array(
 361                         "PageNum" => $num,
 362                         "Link" => $link,
 363                         "CurrentBool" => $currentBool,
 364                         'StartItem' => $i * $this->pageLength + 1,
 365                         'EndItem' => ($i + 1) * $this->pageLength,
 366                         'LinkOrCurrent' => ($currentBool) ? 'current' : 'link',
 367                     )
 368                 ));
 369             }
 370         }
 371         return $ret;
 372     }
 373     
 374     /**
 375      * Returns true if the current page is not the first page.
 376      * @return boolean
 377      */
 378     public function NotFirstPage(){
 379         return $this->CurrentPage() != 1;
 380     }
 381     
 382     /**
 383      * Returns true if the current page is not the last page.
 384      * @return boolean
 385      */
 386     public function NotLastPage(){
 387         return $this->CurrentPage() != $this->TotalPages();
 388     }
 389     
 390     /**
 391      * Returns true if there is more than one page.
 392      * @return boolean
 393      */
 394     public function MoreThanOnePage(){
 395         return $this->TotalPages() > 1;
 396     }
 397     
 398     function FirstItem() {
 399         return isset($this->pageStart) ? $this->pageStart + 1 : 1;
 400     }
 401     
 402     function LastItem() {
 403         if(isset($this->pageStart)) {
 404             return min($this->pageStart + $this->pageLength, $this->totalSize);
 405         } else {
 406             return min($this->pageLength, $this->totalSize);
 407         }
 408     }
 409     
 410     /**
 411      * Returns the URL of the previous page.
 412      * @return string
 413      */
 414     public function PrevLink() {
 415         if($this->pageStart - $this->pageLength > 0) {
 416             return HTTP::setGetVar($this->paginationGetVar, $this->pageStart - $this->pageLength);
 417         }
 418         if($this->pageStart == $this->pageLength) {
 419             return HTTP::setGetVar($this->paginationGetVar, null);
 420         }
 421     }
 422     
 423     /**
 424      * Returns the URL of the next page.
 425      * @return string
 426      */
 427     public function NextLink() {
 428         if($this->pageStart + $this->pageLength < $this->totalSize) {
 429             return HTTP::setGetVar($this->paginationGetVar, $this->pageStart + $this->pageLength);
 430         }
 431     }
 432 
 433     /**
 434      * Allows us to use multiple pagination GET variables on the same page (eg. if you have search results and page comments on a single page)
 435      *
 436      * Example: @see PageCommentInterface::Comments()
 437      * @param string $var The variable to go in the GET string (Defaults to 'start')
 438      */
 439     public function setPaginationGetVar($var) {
 440             $this->paginationGetVar = $var;
 441     }
 442 
 443     /**
 444      * Add an item to the DataObject Set.
 445      * @param DataObject $item Item to add.
 446      * @param string $key Key to index this DataObject by.
 447      */
 448     public function push($item, $key = null) {
 449         if($key != null) {
 450             unset($this->items[$key]);
 451             $this->items[$key] = $item;
 452         } else {
 453             $this->items[] = $item;
 454         }
 455     }
 456     
 457     /**
 458      * Add an item to the beginning of the DataObjectSet
 459      * @param DataObject $item Item to add
 460      * @param string $key Key to index this DataObject by.
 461      */
 462     public function insertFirst($item, $key = null) {
 463         if($key == null) {
 464             array_unshift($this->items, $item);
 465         } else {
 466             $this->items = array_merge(array($key=>$item), $this->items); 
 467         }
 468     }
 469 
 470     /**
 471      * Insert a DataObject at the beginning of this set.
 472      * @param DataObject $item Item to insert.
 473      */
 474     public function unshift($item) {
 475         $this->insertFirst($item);
 476     }
 477     
 478     /**
 479      * Remove a DataObject from the beginning of this set and return it.
 480      * This is the equivalent of pop() but acts on the head of the set.
 481      * Opposite of unshift().
 482      * 
 483      * @return DataObject (or null if there are no items in the set)
 484      */
 485     public function shift() {
 486         return array_shift($this->items);
 487     }
 488 
 489     /**
 490      * Remove a DataObject from the end of this set and return it.
 491      * This is the equivalent of shift() but acts on the tail of the set.
 492      * Opposite of push().
 493      * 
 494      * @return DataObject (or null if there are no items in the set)
 495      */
 496     public function pop() {
 497         return array_pop($this->items);
 498     }
 499 
 500     /**
 501      * Remove a DataObject from this set.
 502      * @param DataObject $itemObject Item to remove.
 503      */
 504     public function remove($itemObject) {
 505         foreach($this->items as $key=>$item){
 506             if($item === $itemObject){
 507                 unset($this->items[$key]);
 508             }
 509         }
 510     }
 511     
 512     /**
 513      * Replaces $itemOld with $itemNew
 514      *
 515      * @param DataObject $itemOld
 516      * @param DataObject $itemNew
 517      */ 
 518     public function replace($itemOld, $itemNew) {
 519         foreach($this->items as $key => $item) {
 520             if($item === $itemOld) {
 521                 $this->items[$key] = $itemNew;
 522                 return;
 523             }
 524         }
 525     }
 526     
 527     /**
 528      * Merge another set onto the end of this set.
 529      * @param DataObjectSet $anotherSet Set to mege onto this set.
 530      */
 531     public function merge($anotherSet){
 532         if($anotherSet) {
 533             foreach($anotherSet as $item){
 534                 $this->push($item);
 535             }
 536         }
 537     }
 538 
 539     /**
 540      * Gets a specific slice of an existing set.
 541      * 
 542      * @param int $offset
 543      * @param int $length
 544      * @return DataObjectSet
 545      */
 546     public function getRange($offset, $length) {
 547         $set = array_slice($this->items, (int)$offset, (int)$length);
 548         return new DataObjectSet($set);
 549     }
 550 
 551     /**
 552      * Returns an Iterator for this DataObjectSet.
 553      * This function allows you to use DataObjectSets in foreach loops
 554      * @return DataObjectSet_Iterator
 555      */
 556     public function getIterator() {
 557         return new DataObjectSet_Iterator($this->items);
 558     }
 559     
 560     /**
 561      * Returns false if the set is empty.
 562      * @return boolean
 563      */
 564     public function exists() {
 565         return (bool)$this->items;
 566     }
 567     
 568     /**
 569      * Return the first item in the set.
 570      * @return DataObject
 571      */
 572     public function First() {
 573         if(count($this->items) < 1)
 574             return null;
 575 
 576         $keys = array_keys($this->items);
 577         return $this->items[$keys[0]];
 578     }
 579     
 580     /**
 581      * Return the last item in the set.
 582      * @return DataObject
 583      */
 584     public function Last() {
 585         if(count($this->items) < 1)
 586             return null;
 587 
 588         $keys = array_keys($this->items);
 589         return $this->items[$keys[sizeof($keys)-1]];
 590     }
 591 
 592     /**
 593      * Return the total number of items in this dataset.
 594      * @return int
 595      */
 596     public function TotalItems() {
 597         return $this->totalSize ? $this->totalSize : sizeof($this->items);
 598     }
 599     
 600     /**
 601      * Returns the actual number of items in this dataset.
 602      * @return int
 603      */
 604     public function Count() {
 605         return sizeof($this->items);
 606     }
 607     
 608     /**
 609      * Returns this set as a XHTML unordered list.
 610      * @return string
 611      */
 612     public function UL() {
 613         if($this->items) {
 614             $result = "<ul id=\"Menu1\">\n";
 615             foreach($this->items as $item) {
 616                 $result .= "<li onclick=\"location.href = this.getElementsByTagName('a')[0].href\"><a href=\"$item->Link\">$item->Title</a></li>\n";
 617             }
 618             $result .= "</ul>\n";
 619 
 620             return $result;
 621         }
 622     }
 623     
 624     /**
 625      * Returns this set as a XHTML unordered list.
 626      * @return string
 627      */
 628     public function forTemplate() {
 629         return $this->UL();
 630     }
 631 
 632     /**
 633      * Returns an array of ID => Title for the items in this set.
 634      * 
 635      * @param string $index The field to use as a key for the array
 636      * @param string $titleField The field (or method) to get values for the map
 637      * @param string $emptyString Empty option text e.g "(Select one)"
 638      * @param bool $sort Sort the map alphabetically based on the $titleField value
 639      * @return array
 640      */
 641     public function map($index = 'ID', $titleField = 'Title', $emptyString = null, $sort = false) {
 642         $map = array();
 643         
 644         if($this->items) {
 645             foreach($this->items as $item) {
 646                 $map[$item->$index] = ($item->hasMethod($titleField)) ? $item->$titleField() : $item->$titleField;
 647             }
 648         }
 649         
 650         if($emptyString) $map = array('' => "$emptyString") + $map;
 651         if($sort) asort($map);
 652         
 653         return $map;
 654     }
 655     
 656     /**
 657      * Find an item in this list where the field $key is equal to $value
 658      * Eg: $doSet->find('ID', 4);
 659      * @return ViewableData The first matching item.
 660      */
 661     public function find($key, $value) {
 662         foreach($this->items as $item) {
 663             if($item->$key == $value) return $item;
 664         }
 665     }
 666     
 667     /**
 668      * Return a column of the given field
 669      * @param string $value The field name
 670      * @return array
 671      */
 672     public function column($value = "ID") {
 673         $list = array();
 674         foreach($this->items as $item ){
 675             $list[] = ($item->hasMethod($value)) ? $item->$value() : $item->$value;
 676         }
 677         return $list;
 678     }
 679 
 680     /**
 681      * Returns an array of DataObjectSets. The array is keyed by index.
 682      * 
 683      * @param string $index The field name to index the array by.
 684      * @return array
 685      */
 686     public function groupBy($index) {
 687         $result = array();
 688         foreach($this->items as $item) {
 689             $key = ($item->hasMethod($index)) ? $item->$index() : $item->$index;
 690             
 691             if(!isset($result[$key])) {
 692                 $result[$key] = new DataObjectSet();
 693             }
 694             $result[$key]->push($item);
 695         }
 696         return $result;
 697     }
 698 
 699     /**
 700      * Groups the items by a given field.
 701      * Returns a DataObjectSet suitable for use in a nested template.
 702      * @param string $index The field to group by
 703      * @param string $childControl The name of the nested page control
 704      * @return DataObjectSet
 705      */
 706     public function GroupedBy($index, $childControl = "Children") {
 707         $grouped = $this->groupBy($index);
 708         $groupedAsSet = new DataObjectSet();
 709         foreach($grouped as $group) {
 710             $groupedAsSet->push($group->First()->customise(array(
 711                 $childControl => $group
 712             )));
 713         }
 714         return $groupedAsSet;
 715     }
 716 
 717     /**
 718      * Returns a nested unordered list out of a "chain" of DataObject-relations,
 719      * using the automagic ComponentSet-relation-methods to find subsequent DataObjectSets.
 720      * The formatting of the list can be different for each level, and is evaluated as an SS-template
 721      * with access to the current DataObjects attributes and methods.
 722      *
 723      * Example: Groups (Level 0, the "calling" DataObjectSet, needs to be queried externally)
 724      * and their Members (Level 1, determined by the Group->Members()-relation).
 725      * 
 726      * @param array $nestingLevels
 727      * Defines relation-methods on DataObjects as a string, plus custom
 728      * SS-template-code for the list-output. Use "Root" for the current DataObjectSet (is will not evaluate into
 729      * a function).
 730      * Caution: Don't close the list-elements (determined programatically).
 731      * You need to escape dollar-signs that need to be evaluated as SS-template-code.
 732      * Use $EvenOdd to get appropriate classes for CSS-styling.
 733      * Format:
 734      * array(
 735      *  array(
 736      *      "dataclass" => "Root",
 737      *      "template" => "<li class=\"\$EvenOdd\"><a href=\"admin/crm/show/\$ID\">\$AccountName</a>"
 738      *  ),
 739      *  array(
 740      *      "dataclass" => "GrantObjects",
 741      *      "template" => "<li class=\"\$EvenOdd\"><a href=\"admin/crm/showgrant/\$ID\">#\$GrantNumber: \$TotalAmount.Nice, \$ApplicationDate.ShortMonth \$ApplicationDate.Year</a>"
 742      *  )
 743      * );
 744      * @param string $ulExtraAttributes Extra attributes
 745      * 
 746      * @return string Unordered List (HTML)
 747      */
 748     public function buildNestedUL($nestingLevels, $ulExtraAttributes = "") {
 749         return $this->getChildrenAsUL($nestingLevels, 0, "", $ulExtraAttributes);
 750     }
 751 
 752     /**
 753      * Gets called recursively on the child-objects of the chain.
 754      * 
 755      * @param array $nestingLevels see {@buildNestedUL}
 756      * @param int $level Current nesting level
 757      * @param string $template Template for list item
 758      * @param string $ulExtraAttributes Extra attributes
 759      * @param int $itemCount Max item count
 760      * @return string
 761      */
 762     public function getChildrenAsUL($nestingLevels, $level = 0, $template = "<li id=\"record-\$ID\" class=\"\$EvenOdd\">\$Title", $ulExtraAttributes = null, &$itemCount = 0) {
 763         $output = "";
 764         $hasNextLevel = false;
 765         $ulExtraAttributes = " $ulExtraAttributes";
 766         $output = "<ul" . eval($ulExtraAttributes) . ">\n";
 767 
 768         $currentNestingLevel = $nestingLevels[$level];
 769 
 770         // either current or default template
 771         $currentTemplate = (!empty($currentNestingLevel)) ? $currentNestingLevel['template'] : $template;
 772         $myViewer = SSViewer::fromString($currentTemplate);
 773 
 774         if(isset($nestingLevels[$level+1]['dataclass'])){
 775             $childrenMethod = $nestingLevels[$level+1]['dataclass'];
 776         }
 777         // sql-parts
 778 
 779         $filter = (isset($nestingLevels[$level+1]['filter'])) ? $nestingLevels[$level+1]['filter'] : null;
 780         $sort = (isset($nestingLevels[$level+1]['sort'])) ? $nestingLevels[$level+1]['sort'] : null;
 781         $join = (isset($nestingLevels[$level+1]['join'])) ? $nestingLevels[$level+1]['join'] : null;
 782         $limit = (isset($nestingLevels[$level+1]['limit'])) ? $nestingLevels[$level+1]['limit'] : null;
 783         $having = (isset($nestingLevels[$level+1]['having'])) ? $nestingLevels[$level+1]['having'] : null;
 784 
 785         foreach($this as $parent) {
 786             $evenOdd = ($itemCount % 2 == 0) ? "even" : "odd";
 787             $parent->setField('EvenOdd', $evenOdd);
 788             $template = $myViewer->process($parent);
 789 
 790             // if no output is selected, fall back to the id to keep the item "clickable"
 791             $output .= $template . "\n";
 792 
 793             if(isset($childrenMethod)) {
 794                 // workaround for missing groupby/having-parameters in instance_get
 795                 // get the dataobjects for the next level
 796                 $children = $parent->$childrenMethod($filter, $sort, $join, $limit, $having);
 797                 if($children) {
 798                     $output .= $children->getChildrenAsUL($nestingLevels, $level+1, $currentTemplate, $ulExtraAttributes);
 799                 }
 800             }
 801             $output .= "</li>\n";
 802             $itemCount++;
 803         }
 804 
 805         $output .= "</ul>\n";
 806 
 807         return $output;
 808     }
 809 
 810     /**
 811     * Sorts the current DataObjectSet instance.
 812     * @param string $fieldname The name of the field on the DataObject that you wish to sort the set by.
 813     * @param string $direction Direction to sort by, either "ASC" or "DESC".
 814     */
 815     public function sort($fieldname, $direction = "ASC") {
 816         if($this->items) {
 817             if (preg_match('/(.+?)(\s+?)(A|DE)SC$/', $fieldname, $matches)) {
 818                 $fieldname = $matches[1];
 819                 $direction = $matches[3].'SC';
 820             }
 821             column_sort($this->items, $fieldname, $direction, false);
 822         }
 823     }
 824 
 825     /**
 826     * Remove duplicates from this set based on the dataobjects field.
 827     * Assumes all items contained in the set all have that field.
 828     * @param string $field the field to check for duplicates
 829     */
 830     public function removeDuplicates($field = 'ID') {
 831         $exists = array();
 832         foreach($this->items as $key => $item) {
 833             if(isset($exists[$fullkey = ClassInfo::baseDataClass($item) . ":" . $item->$field])) {
 834                 unset($this->items[$key]);
 835             }
 836             $exists[$fullkey] = true;
 837         }
 838     }
 839     
 840     /**
 841      * Returns information about this set in HTML format for debugging.
 842      * @return string
 843      */
 844     public function debug() {
 845         $val = "<h2>" . $this->class . "</h2><ul>";
 846         foreach($this as $item) {
 847             $val .= "<li style=\"list-style-type: disc; margin-left: 20px\">" . Debug::text($item) . "</li>";
 848         }
 849         $val .= "</ul>";
 850         return $val;
 851     }
 852 
 853     /**
 854      * Groups the set by $groupField and returns the parent of each group whose class
 855      * is $groupClassName. If $collapse is true, the group will be collapsed up until an ancestor with the
 856      * given class is found.
 857      * @param string $groupField The field to group by.
 858      * @param string $groupClassName Classname.
 859      * @param string $sortParents SORT clause to insert into the parents SQL.
 860      * @param string $parentField Parent field.
 861      * @param boolean $collapse Collapse up until an ancestor with the given class is found.
 862      * @param string $requiredParents Required parents
 863      * @return DataObjectSet
 864      */
 865     public function groupWithParents($groupField, $groupClassName, $sortParents = null, $parentField = 'ID', $collapse = false, $requiredParents = null) {
 866         // Each item in this DataObjectSet is grouped into a multidimensional array
 867         // indexed by it's parent. The parent IDs are later used to find the parents
 868         // that make up the returned set.
 869         $groupedSet = array();
 870 
 871         // Array to store the subgroups matching the requirements
 872         $resultsArray = array();
 873 
 874         // Put this item into the array indexed by $groupField.
 875         // the keys are later used to retrieve the top-level records
 876         foreach( $this->items as $item ) {
 877             $groupedSet[$item->$groupField][] = $item;
 878         }
 879 
 880         $parentSet = null;
 881 
 882         // retrieve parents for this set
 883 
 884         // TODO How will we collapse the hierarchy to bridge the gap?
 885 
 886         // if collapse is specified, then find the most direct ancestor of type
 887         // $groupClassName
 888         if($collapse) {
 889             // The most direct ancestors with the type $groupClassName
 890             $parentSet = array();
 891 
 892             // get direct parents
 893             $parents = DataObject::get( 'SiteTree', "\"SiteTree\".\"$parentField\" IN( " . implode( ",", array_keys( $groupedSet ) ) . ")", $sortParents ); 
 894             
 895             // for each of these parents...
 896             foreach($parents as $parent) {
 897                 // store the old parent ID. This is required to change the grouped items
 898                 // in the $groupSet array
 899                 $oldParentID = $parent->ID;
 900 
 901                 // get the parental stack
 902                 $parentObjects= $parent->parentStack();
 903                 $parentStack = array();
 904 
 905                 foreach( $parentObjects as $parentObj )
 906                     $parentStack[] = $parentObj->ID;
 907 
 908                 // is some particular IDs are required, then get the intersection
 909                 if($requiredParents && count($requiredParents)) {
 910                     $parentStack = array_intersect($requiredParents, $parentStack);
 911                 }
 912 
 913                 $newParent = null;
 914 
 915                 // If there are no parents, the group can be omitted
 916                 if(empty($parentStack)) {
 917                     $newParent = new DataObjectSet();
 918                 } else {
 919                     $newParent = DataObject::get_one( $groupClassName, "\"SiteTree\".\"$parentField\" IN( " . implode( ",", $parentStack ) . ")" );     
 920                 }
 921             
 922                 // change each of the descendant's association from the old parent to
 923                 // the new parent. This effectively collapses the hierarchy
 924                 foreach( $groupedSet[$oldParentID] as $descendant ) {
 925                     $groupedSet[$newParent->ID][] = $descendant;
 926                 }
 927 
 928                 // Add the most direct ancestor of type $groupClassName
 929                 $parentSet[] = $newParent;
 930             }
 931         // otherwise get the parents of these items
 932         } else {
 933 
 934             $requiredIDs = array_keys( $groupedSet );
 935             
 936             if( $requiredParents && cont($requiredParents)) {
 937                 $requiredIDs = array_intersect($requiredParents, $requiredIDs);
 938             }
 939                 
 940             if(empty($requiredIDs)) {
 941                 $parentSet = new DataObjectSet();
 942             } else {
 943                 $parentSet = DataObject::get( $groupClassName, "\"$groupClassName\".\"$parentField\" IN( " . implode( ",", $requiredIDs ) . ")", $sortParents );    
 944             }
 945             if ($parentSet) {
 946                 $parentSet = $parentSet->toArray();
 947             }
 948         }
 949         
 950         if ($parentSet) {
 951             foreach($parentSet as $parent) {
 952                 $resultsArray[] = $parent->customise(array(
 953                     "GroupItems" => new DataObjectSet($groupedSet[$parent->$parentField])
 954                 ));
 955             }
 956         }
 957             
 958         return new DataObjectSet($resultsArray);
 959     }
 960     
 961     /**
 962      * Add a field to this set without writing it to the database
 963      * @param DataObject $field Field to add
 964      */
 965     function addWithoutWrite($field) {
 966         $this->items[] = $field;
 967     }
 968     
 969     /**
 970      * Returns true if the DataObjectSet contains all of the IDs givem
 971      * @param $idList An array of object IDs
 972      */
 973     function containsIDs($idList) {
 974         foreach($idList as $item) $wants[$item] = true;
 975         foreach($this->items as $item) if($item) unset($wants[$item->ID]);
 976         return !$wants;
 977     }
 978     
 979     /**
 980      * Returns true if the DataObjectSet contains all of and *only* the IDs given.
 981      * Note that it won't like duplicates very much.
 982      * @param $idList An array of object IDs
 983      */
 984     function onlyContainsIDs($idList) {
 985         return $this->containsIDs($idList) && sizeof($idList) == sizeof($this->items); 
 986     }
 987 
 988     public function hasValue($field = false, $arguments = null, $cache = true) {
 989         if ($field)
 990             return parent::hasValue($field, $arguments, $cache);
 991         return ($this->Count() > 0);
 992     }
 993 }
 994 
 995 /**
 996  * Sort a 2D array by particular column.
 997  *
 998  * @param array $data The array to sort.
 999  * @param string $column The name of the column you wish to sort by.
1000  * @param string $direction Direction to sort by, either "ASC" or "DESC".
1001  * @param boolean $preserveIndexes Preserve indexes
1002  */
1003 function column_sort(&$data, $column, $direction = "ASC", $preserveIndexes = true) {
1004     global $column_sort_field, $column_sort_multiplier;
1005 
1006     // We have to keep numeric diretions for legacy
1007     if(is_numeric($direction)) {
1008         $column_sort_multiplier = $direction;
1009     } elseif($direction == "ASC") {
1010         $column_sort_multiplier = 1;
1011     } elseif($direction == "DESC") {
1012         $column_sort_multiplier = -1;
1013     } else {
1014         $column_sort_multiplier = 0;
1015     }
1016     $column_sort_field = $column;
1017     if($preserveIndexes) {
1018         uasort($data, "column_sort_callback_basic");
1019     } else {
1020         usort($data, "column_sort_callback_basic");
1021     }
1022 }
1023 
1024 /**
1025  * Callback used by column_sort
1026  */
1027 function column_sort_callback_basic($a, $b) {
1028     global $column_sort_field, $column_sort_multiplier;
1029 
1030     if($a->$column_sort_field == $b->$column_sort_field) {
1031         $result  = 0;
1032     } else {
1033         $result = ($a->$column_sort_field < $b->$column_sort_field) ? -1 * $column_sort_multiplier : 1 * $column_sort_multiplier;
1034     }
1035     
1036     return $result;
1037 }
1038 
1039 /**
1040  * An Iterator for a DataObjectSet
1041  * 
1042  * @package sapphire
1043  * @subpackage model
1044  */
1045 class DataObjectSet_Iterator implements Iterator {
1046     function __construct($items) {
1047         $this->items = $items;
1048         
1049         $this->current = $this->prepareItem(current($this->items));
1050     }
1051     
1052     /**
1053      * Prepare an item taken from the internal array for 
1054      * output by this iterator.  Ensures that it is an object.
1055      * @param DataObject $item Item to prepare
1056      * @return DataObject
1057      */
1058     protected function prepareItem($item) {
1059         if(is_object($item)) {
1060             $item->iteratorProperties(key($this->items), sizeof($this->items));
1061         }
1062         // This gives some reliablity but it patches over the root cause of the bug...
1063         // else if(key($this->items) !== null) $item = new ViewableData();
1064         return $item;
1065     }
1066     
1067     
1068     /**
1069      * Return the current object of the iterator.
1070      * @return DataObject
1071      */
1072     public function current() {
1073         return $this->current;
1074     }
1075     
1076     /**
1077      * Return the key of the current object of the iterator.
1078      * @return mixed
1079      */
1080     public function key() {
1081         return key($this->items);
1082     }
1083     
1084     /**
1085      * Return the next item in this set.
1086      * @return DataObject
1087      */
1088     public function next() {
1089         $this->current = $this->prepareItem(next($this->items));
1090         return $this->current;
1091     }
1092     
1093     /**
1094      * Rewind the iterator to the beginning of the set.
1095      * @return DataObject The first item in the set.
1096      */
1097     public function rewind() {
1098         $this->current = $this->prepareItem(reset($this->items));
1099         return $this->current;
1100     }
1101     
1102     /**
1103      * Check the iterator is pointing to a valid item in the set.
1104      * @return boolean
1105      */
1106     public function valid() {
1107         return $this->current !== false;
1108     }
1109     
1110     /**
1111      * Return the next item in this set without progressing the iterator.
1112      * @return DataObject
1113      */
1114     public function peekNext() {
1115         return $this->getOffset(1);
1116     }
1117     
1118     /**
1119      * Return the prvious item in this set, without affecting the iterator.
1120      * @return DataObject
1121      */
1122     public function peekPrev() {
1123         return $this->getOffset(-1);
1124     }
1125     
1126     /**
1127      * Return the object in this set offset by $offset from the iterator pointer.
1128      * @param int $offset The offset.
1129      * @return DataObject|boolean DataObject of offset item, or boolean FALSE if not found
1130      */
1131     public function getOffset($offset) {
1132         $keys = array_keys($this->items);
1133         foreach($keys as $i => $key) {
1134             if($key == key($this->items)) break;
1135         }
1136         
1137         if(isset($keys[$i + $offset])) {
1138             $requiredKey = $keys[$i + $offset];
1139             return $this->items[$requiredKey];
1140         }
1141         
1142         return false;
1143     }
1144 }
1145 
1146 ?>
[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