1 <?php
2
3 class DataObjectManager extends ComplexTableField {
4
5 protected static $allow_assets_override = true;
6 protected static $allow_css_override = false;
7 protected static = 500;
8 protected static $confirm_delete = true;
9
10 protected $template = "DataObjectManager";
11 protected $start = "0";
12 protected $per_page = "20";
13 protected $showAll = "0";
14 protected $search = "";
15 protected $filter = "";
16 protected $sort_dir = "DESC";
17 protected $sort = "Created";
18 protected $filter_map = array();
19 protected $filtered_field;
20 protected $filter_label = "Filter results";
21 protected $filter_empty_string = "";
22 protected $column_widths = array();
23 protected $per_page_map = array('10','20','30','40','50');
24 protected $use_view_all = true;
25 protected ;
26 protected $confirmDelete;
27 public $itemClass = "DataObjectManager_Item";
28 public $addTitle;
29 public $singleTitle;
30 public $hasNested = false;
31 public $isNested = false;
32
33
34 public $actions = array(
35 'edit' => array(
36 'label' => 'Edit',
37 'icon' => null,
38 'class' => 'popuplink editlink',
39 ),
40 'delete' => array(
41 'label' => 'Delete',
42 'icon' => null,
43 'class' => 'deletelink',
44 )
45 );
46
47 static $url_handlers = array(
48 'duplicate/$ID' => 'handleDuplicate'
49 );
50
51
52 public = "DataObjectManager_Popup";
53 public = "DataObjectManager_popup";
54
55 public static function allow_assets_override($bool) {
56 if($bool) {
57 DataObject::add_extension("Folder","AssetManagerFolder");
58 SortableDataObject::add_sortable_class("File");
59 }
60 else
61 DataObject::remove_extension("Folder","AssetManagerFolder");
62 }
63
64 public static function allow_css_override($bool) {
65 self::$allow_css_override = $bool;
66 }
67
68 public static function ($width) {
69 self::$popup_width = $width;
70 }
71
72 public static function set_confirm_delete($bool) {
73 self::$confirm_delete = $bool;
74 }
75
76 function __construct($controller, $name = null, $sourceClass = null, $fieldList = null, $detailFormFields = null, $sourceFilter = "", $sourceSort = "Created DESC", $sourceJoin = "") {
77 if(!class_exists("ComplexTableField_ItemRequest"))
78 die("<strong>"._t('DataObjectManager.ERROR','Error')."</strong>: "._t('DataObjectManager.SILVERSTRIPEVERSION','DataObjectManager requires Silverstripe version 2.3 or higher.'));
79
80
81 if($name === null && $sourceClass === null) {
82 if($has_manys = $controller->stat('has_many')) {
83 foreach($has_manys as $relation => $value) {
84 $name = $relation;
85 $sourceClass = $value;
86 break;
87 }
88 }
89 }
90 $SNG = singleton($sourceClass);
91 if($fieldList === null) {
92 if($fields = $SNG->summaryFields()) {
93 $fieldList = $fields;
94 }
95 else if($db = $SNG->db()) {
96 $fieldList = array();
97 foreach($db as $field => $type) {
98 if($field != "SortOrder")
99 $fieldList[$field] = DOMUtil::readable_class($field);
100 }
101 }
102 }
103 parent::__construct($controller, $name, $sourceClass, $fieldList, $detailFormFields, $sourceFilter, $sourceSort, $sourceJoin);
104 Requirements::css('dataobject_manager/css/dataobject_manager.css');
105 Requirements::css('dataobject_manager/css/facebox.css');
106 if(self::$allow_css_override)
107 Requirements::css('dataobject_manager/css/dataobjectmanager_override.css');
108 Requirements::javascript(THIRDPARTY_DIR.'/jquery-livequery/jquery.livequery.js');
109 Requirements::javascript('dataobject_manager/javascript/facebox.js');
110 Requirements::javascript('dataobject_manager/javascript/dom_jquery_ui.js');
111 Requirements::javascript('dataobject_manager/javascript/tooltip.js');
112 Requirements::javascript('dataobject_manager/javascript/dataobject_manager.js');
113
114 $this->filter_empty_string = '-- '._t('DataObjectManager.NOFILTER','No filter').' --';
115
116 if($this->sourceSort) {
117 $parts = explode(" " , $this->sourceSort);
118 if(is_array($parts) && sizeof($parts) == 2) {
119 list($field,$dir) = $parts;
120 $this->sort = trim($field);
121 $this->sort_dir = trim($dir);
122 }
123 else {
124 $this->sort = $this->sourceSort;
125 $this->sort_dir = "ASC";
126 }
127 }
128
129 if(isset($_REQUEST['ctf'][$this->Name()])) {
130 $this->start = isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
131 $this->per_page = $_REQUEST['ctf'][$this->Name()]['per_page'];
132 $this->showAll = $_REQUEST['ctf'][$this->Name()]['showall'];
133 $this->search = $_REQUEST['ctf'][$this->Name()]['search'];
134 $this->filter = $_REQUEST['ctf'][$this->Name()]['filter'];
135 $this->sort = $_REQUEST['ctf'][$this->Name()]['sort'];
136 $this->sort_dir = $_REQUEST['ctf'][$this->Name()]['sort_dir'];
137 }
138
139
140 $this->setPageSize($this->per_page);
141 $this->loadSort();
142 $this->loadSourceFilter();
143
144 $fields = $this->getRawDetailFields(singleton($this->sourceClass()));
145 foreach($fields as $field) {
146 if($field instanceof DataObjectManager && !($field->controller instanceof SiteTree)) {
147 $this->hasNested = true;
148 $this->setPopupWidth(850);
149 }
150 }
151
152 }
153
154 public function setSourceFilter($filter) {
155 $this->sourceFilter = $filter;
156 }
157
158 public function setUseViewAll($bool) {
159 $this->use_view_all = $bool;
160 }
161
162 public function setPerPageMap($values) {
163 $this->per_page_map = $values;
164 }
165
166 public function setPluralTitle($title) {
167 $this->pluralTitle = $title;
168 }
169
170 public function setWideMode($bool) {
171 $this->hasNested = $bool;
172 }
173
174 public function PluralTitle() {
175 return $this->pluralTitle ? $this->pluralTitle : $this->AddTitle()."";
176 }
177
178
179 protected function loadSort() {
180 if($this->ShowAll())
181 $this->setPageSize(999);
182
183 if($this->Sortable() && (!isset($_REQUEST['ctf'][$this->Name()]['sort']) || $_REQUEST['ctf'][$this->Name()]['sort'] == "SortOrder")) {
184 $this->sort = "SortOrder";
185 $this->sourceSort = "SortOrder ASC";
186 $this->sort_dir = "ASC";
187 }
188 elseif(isset($_REQUEST['ctf'][$this->Name()]['sort'])) {
189 $this->sourceSort = $_REQUEST['ctf'][$this->Name()]['sort'] . " " . $this->sort_dir;
190 }
191
192 }
193
194 protected function loadSourceFilter() {
195 $filter_string = "";
196 if(!empty($this->filter)) {
197 $break = strpos($this->filter, "_");
198 $field = substr($this->filter, 0, $break);
199 $value = substr($this->filter, $break+1, strlen($this->filter) - strlen($field));
200 $filter_string = $field . "='$value'";
201 }
202
203 $search_string = "";
204 if(!empty($this->search)) {
205 $search = array();
206 $SNG = singleton($this->sourceClass);
207 foreach(parent::Headings() as $field) {
208 if($SNG->hasDatabaseField($field->Name))
209 $search[] = "UPPER($field->Name) LIKE '%".strtoupper($this->search)."%'";
210 }
211 $search_string = "(".implode(" OR ", $search).")";
212 }
213 $and = (!empty($this->filter) && !empty($this->search)) ? " AND " : "";
214 $source_filter = $filter_string.$and.$search_string;
215 if(!$this->sourceFilter) $this->sourceFilter = $source_filter;
216 else if($this->sourceFilter && !empty($source_filter)) $this->sourceFilter .= " AND " . $source_filter;
217 }
218
219 public function handleItem($request) {
220 return new DataObjectManager_ItemRequest($this, $request->param('ID'));
221 }
222
223 public function getQueryString($params = array()) {
224 $start = isset($params['start'])? $params['start'] : $this->start;
225 $per_page = isset($params['per_page'])? $params['per_page'] : $this->per_page;
226 $show_all = isset($params['show_all'])? $params['show_all'] : $this->showAll;
227 $sort = isset($params['sort'])? $params['sort'] : $this->sort;
228 $sort_dir = isset($params['sort_dir'])? $params['sort_dir'] : $this->sort_dir;
229 $filter = isset($params['filter'])? $params['filter'] : $this->filter;
230 $search = isset($params['search'])? $params['search'] : $this->search;
231
232 $url = "ctf[{$this->Name()}][start]={$start}&ctf[{$this->Name()}][per_page]={$per_page}&ctf[{$this->Name()}][showall]={$show_all}&ctf[{$this->Name()}][sort]={$sort}&ctf[{$this->Name()}][sort_dir]={$sort_dir}&ctf[{$this->Name()}][search]={$search}&ctf[{$this->Name()}][filter]={$filter}";
233 if (Translatable::get_allowed_locales()) {
234 $url .= "&locale=" . Translatable::get_current_locale();
235 }
236 return $url;
237 }
238
239 public function getSetting($setting) {
240 if($this->$setting) {
241 return $this->$setting;
242 }
243 return Object::get_static($this->class,DOMUtil::to_underscore($setting));
244 }
245
246 function FieldHolder() {
247 if(!$this->controller->ID && $this->isNested)
248 return $this->renderWith('DataObjectManager_holder');
249 return parent::FieldHolder();
250 }
251
252
253 public function Headings() {
254 $headings = array();
255 foreach($this->fieldList as $fieldName => $fieldTitle) {
256 if(isset($_REQUEST['ctf'][$this->Name()]['sort_dir']))
257 $dir = $_REQUEST['ctf'][$this->Name()]['sort_dir'] == "ASC" ? "DESC" : "ASC";
258 else
259 $dir = "ASC";
260 $headings[] = new ArrayData(array(
261 "Name" => $fieldName,
262 "Title" => ($this->sourceClass) ? singleton($this->sourceClass)->fieldLabel($fieldTitle) : $fieldTitle,
263 "IsSortable" => singleton($this->sourceClass)->hasDatabaseField($fieldName),
264 "SortLink" => $this->RelativeLink(array(
265 'sort_dir' => $dir,
266 'sort' => $fieldName
267 )),
268 "SortDirection" => $dir,
269 "IsSorted" => (isset($_REQUEST['ctf'][$this->Name()]['sort'])) && ($_REQUEST['ctf'][$this->Name()]['sort'] == $fieldName),
270 "ColumnWidthCSS" => !empty($this->column_widths) ? sprintf("style='width:%f%%;'",($this->column_widths[$fieldName] - 0.1)) : ""
271 ));
272 }
273 return new DataObjectSet($headings);
274 }
275
276 function saveComplexTableField($data, $form, $params) {
277 $className = $this->sourceClass();
278 $childData = new $className();
279 $form->saveInto($childData);
280 $childData->write();
281 if($childData->many_many()) {
282 $form->saveInto($childData);
283 $childData->write();
284 }
285 $form->sessionMessage(sprintf(_t('DataObjectManager.ADDEDNEW','Added new %s successfully'),$this->SingleTitle()), 'good');
286
287 if($form->getFileFields() || $form->getNestedDOMs()) {
288 $form->clearMessage();
289 Director::redirect(Controller::join_links($this->BaseLink(),'/item/'.$childData->ID.'/edit'));
290 }
291 else Director::redirectBack();
292
293 }
294
295 function sourceID() {
296 if($this->isNested)
297 return $this->controller->ID;
298 $idField = $this->form->dataFieldByName('ID');
299 return ($idField && is_numeric($idField->Value())) ? $idField->Value() : (isset($_REQUEST['ctf']['ID']) ? $_REQUEST['ctf']['ID'] : null);
300 }
301
302
303 protected function getRawDetailFields($childData) {
304 if(is_a($this->detailFormFields,"Fieldset"))
305 $fields = $this->detailFormFields;
306 else {
307 if(!is_string($this->detailFormFields)) $this->detailFormFields = "getCMSFields";
308 $functioncall = $this->detailFormFields;
309 if(!$childData->hasMethod($functioncall)) $functioncall = "getCMSFields";
310
311 $fields = $childData->$functioncall();
312 }
313 return $fields;
314 }
315
316 public function getCustomFieldsFor($childData) {
317 $fields = $this->getRawDetailFields($childData);
318 foreach($fields as $field) {
319 if($field->class == "CalendarDateField")
320 $fields->replaceField($field->Name(), new DatePickerField($field->Name(), $field->Title(), $field->attrValue()));
321 }
322 return $fields;
323 }
324
325 function AddForm($childID = null) {
326 $form = parent::AddForm($childID);
327 $actions = new FieldSet();
328 $titles = array();
329 if($files = $form->getFileFields()) {
330 foreach($files as $field) $titles[] = DOMUtil::readable_class($field->Title());
331 }
332 if($doms = $form->getNestedDOMs())
333 foreach($doms as $field) $titles[] = $field->PluralTitle();
334 if(empty($titles))
335 $text = _t('DataObjectManager.SAVE','Save');
336 elseif(sizeof($titles) > 3) {
337 $first_three = array_slice($titles,0,3);
338 $remaining = sizeof(array_slice($titles, 4));
339 $text = sprintf(_t('DataObjectManager.SAVEANDADD','Save and add %s'), implode(', ',$first_three));
340 $text .= ", " . sprintf(_t('DataObjectManager.ANDOTHERCOMPONENTS','and %d other components'),$remaining);
341 }
342 else
343 $text = sprintf(_t('DataObjectManager.SAVEANDADD','Save and add %s'), DOMUtil::readable_list($titles));
344
345 $actions->push(
346 $saveAction = new FormAction("saveComplexTableField", $text)
347 );
348 $saveAction->addExtraClass('save');
349 $form->setActions($actions);
350 return $form;
351 }
352
353 public function Link($action = null) {
354 return Controller::join_links(parent::Link($action),'?'.$this->getQueryString());
355 }
356
357 public function BaseLink() {
358 return parent::Link();
359 }
360
361 public function CurrentLink() {
362 return $this->Link();
363 }
364
365 public function RelativeLink($params = array()) {
366 return Controller::join_links(parent::Link(),'?'.$this->getQueryString($params));
367 }
368 public function FirstLink() {
369 return parent::FirstLink() ? $this->RelativeLink(array('start' => '0')) : false;
370 }
371
372 public function PrevLink() {
373 $start = ($this->start - $this->pageSize < 0) ? 0 : $this->start - $this->pageSize;
374 return parent::PrevLink() ? $this->RelativeLink(array('start' => $start)) : false;
375 }
376
377 public function NextLink() {
378 $currentStart = isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
379 $start = ($currentStart + $this->pageSize < $this->TotalCount()) ? $currentStart + $this->pageSize : $this->TotalCount() % $this->pageSize > 0;
380 return parent::NextLink() ? $this->RelativeLink(array('start' => $start)) : false;
381 }
382
383 public function LastLink() {
384 $pageSize = ($this->TotalCount() % $this->pageSize > 0) ? $this->TotalCount() % $this->pageSize : $this->pageSize;
385 $start = $this->TotalCount() - $pageSize;
386 return parent::LastLink() ? $this->RelativeLink(array('start' => $start)) : false;
387 }
388
389 public function ShowAllLink() {
390 return $this->RelativeLink(array('show_all' => '1'));
391 }
392
393 public function PaginatedLink() {
394 return $this->RelativeLink(array('show_all' => '0'));
395 }
396
397 public function AddLink() {
398 return Controller::join_links($this->BaseLink(), '/add');
399 }
400
401 public function ShowAll() {
402 return $this->showAll == "1";
403 }
404
405 public function Paginated() {
406 return $this->showAll == "0";
407 }
408
409 public function Sortable() {
410 return SortableDataObject::is_sortable_class($this->sourceClass());
411 }
412
413 public function setFilter($field, $label, $map, $default = null) {
414 if(is_array($map)) {
415 $this->filter_map = $map;
416 $this->filtered_field = $field;
417 $this->filter_label = $label;
418 }
419 if($default) {
420 $this->filter = $this->filtered_field.'_'.$default;
421 $this->loadSourceFilter();
422 }
423 }
424
425 public function HasFilter() {
426 return !empty($this->filter_map);
427 }
428
429 public function FilterDropdown() {
430 $map = $this->filter_empty_string ? array($this->RelativeLink(array('filter' => '')) => $this->filter_empty_string) : array();
431 foreach($this->filter_map as $k => $v) {
432 $map[$this->RelativeLink(array('filter' => $this->filtered_field.'_'.$k))] = $v;
433 }
434 $value = !empty($this->filter) ? $this->RelativeLink(array('filter' => $this->filter)) : null;
435 $dropdown = new DropdownField('Filter',$this->filter_label . " (<a href='#' class='refresh'>"._t('DataObjectManager.REFRESH','refresh')."</a>)", $map, $value);
436 return $dropdown->FieldHolder();
437 }
438
439 public function PerPageDropdown() {
440 $map = array();
441 foreach($this->per_page_map as $num) $map[$this->RelativeLink(array('per_page' => $num))] = $num;
442 if($this->use_view_all)
443 $map[$this->RelativeLink(array('per_page' => '9999'))] = _t('DataObjectManager.ALL','All');
444 $value = !empty($this->per_page) ? $this->RelativeLink(array('per_page' => $this->per_page)) : null;
445 return new FieldGroup(
446 new LabelField('show', _t('DataObjectManager.PERPAGESHOW','Show').' '),
447 new DropdownField('PerPage',_t('DataObjectManager.DDPERPAGESHOW'),$map, $value),
448 new LabelField('results', ' '._t('DataObjectManager.PERPAGERESULTS','results per page'))
449
450 );
451 }
452 public function SearchValue() {
453 return !empty($this->search) ? $this->search : false;
454 }
455
456 public function AddTitle() {
457 return $this->addTitle ? $this->addTitle : DOMUtil::readable_class($this->Title());
458 }
459
460 public function SingleTitle() {
461 return $this->singleTitle ? $this->singleTitle : DOMUtil::readable_class($this->AddTitle());
462 }
463
464 public function setAddTitle($title) {
465 $this->addTitle = $title;
466 }
467
468 public function setSingleTitle($title) {
469 $this->singleTitle = $title;
470 }
471
472 public function getColumnWidths() {
473 return $this->column_widths;
474 }
475
476 public function setColumnWidths($widths) {
477 if(is_array($widths)) {
478 $total = 0;
479 foreach($widths as $name => $value) $total += $value;
480 if($total != 100)
481 die('<strong>DataObjectManager::setColumnWidths()</strong>:' . sprintf(_t('DataObjectManager.TOTALNOT100','Column widths must total 100 and not %s'), $total));
482 else
483 $this->column_widths = $widths;
484 }
485 }
486
487 public function setFilterEmptyString($str) {
488 $this->filter_empty_string = $str;
489 }
490
491 public function addPermission($perm) {
492 if(!in_array($perm,$this->permissions))
493 $this->permissions[] = $perm;
494 }
495
496 public function removePermission($perm) {
497 $key = array_search($perm,$this->permissions);
498 if($key !== false)
499 unset($this->permissions[$key]);
500 }
501
502 public function NestedType() {
503 if($this->hasNested)
504 return "hasNested";
505 else if($this->isNested)
506 return "isNested";
507 else
508 return "";
509 }
510
511 public function handleDuplicate($request) {
512 return new DataObjectManager_ItemRequest($this,$request->param('ID'));
513 }
514
515 public function ($val) {
516 $this->popupWidth = $val;
517 }
518
519 public function setConfirmDelete($bool) {
520 $this->confirmDelete = $bool;
521 }
522
523 public function () {
524 return $this->getSetting('popupWidth');
525 }
526
527 public function ConfirmDelete() {
528 return $this->getSetting('confirmDelete');
529 }
530
531
532
533 }
534
535 class DataObjectManager_Item extends ComplexTableField_Item {
536 function __construct(DataObject $item, DataObjectManager $parent) {
537 parent::__construct($item, $parent);
538 }
539
540 function Link($action = NULL) {
541 return Controller::join_links($this->parent->BaseLink(), '/item/' . $this->item->ID);
542 }
543
544 function Fields($xmlSafe = true) {
545 $fields = parent::Fields($xmlSafe);
546 $widths = $this->parent->getColumnWidths();
547 if(!empty($widths)) {
548 foreach($fields as $field) {
549 $field->ColumnWidthCSS = sprintf("style='width:%f%%;'",($widths[$field->Name] - 0.1));
550 }
551 }
552 return $fields;
553 }
554
555 public function CanViewOrEdit() {
556 return $this->parent->Can('view') || $this->parent->Can('edit');
557 }
558
559 function CanDelete() {
560 return $this->parent->Can('delete');
561 }
562
563 public function ViewOrEdit() {
564 if($this->CanViewOrEdit())
565 return $this->parent->Can('edit') ? "edit" : "view";
566 return false;
567 }
568
569 public function ViewOrEdit_i18n() {
570 if($res = $this->ViewOrEdit()) {
571 return ($res == "edit") ? _t('DataObjectManager.EDIT','Edit') : _t('DataObjectManager.VIEW','View');
572 }
573 return null;
574 }
575
576 public function EditLink() {
577
578 return Controller::join_links($this->Link(), "edit?").$this->parent->getQueryString();
579 }
580
581 public function DuplicateLink() {
582 return Controller::join_links($this->Link(), "/duplicate");
583 }
584
585 public function CustomActions() {
586 if($this->item->hasMethod('customDOMActions'))
587 return $this->item->customDOMActions();
588 return false;
589 }
590
591 public function () {
592 return $this->parent->PopupWidth();
593 }
594
595 public function Actions() {
596 $actions = new DataObjectSet();
597
598
599
600
601 $permList = (array) $this->parent->permissions;
602 foreach($permList as $perm) {
603 $action = false;
604 switch($perm) {
605 case "edit":
606 case "view":
607 $actions->push(new DataObjectManagerAction(
608 $this->ViewOrEdit_i18n(),
609 $this->EditLink(),
610 "popup",
611 "dataobject_manager/images/page_white_{$this->ViewOrEdit()}.png",
612 "editlink" ,
613 $this->parent->PopupWidth()
614 ));
615 break;
616
617 case "delete":
618 $actions->push(new DataObjectManagerAction(
619 _t('DataObjectManager.DELETE','Delete'),
620 $this->DeleteLink(),
621 "delete",
622 "dataobject_manager/images/trash.gif",
623 null,
624 $this->parent->getSetting('confirmDelete') ? "confirm" : null
625 ));
626 break;
627
628 case "duplicate":
629 $actions->push(new DataObjectManagerAction(
630 _t('DataObjectManager.DUPLICATE','Duplicate'),
631 $this->DuplicateLink(),
632 "popup",
633 "dataobject_manager/images/page_copy.png",
634 null,
635 400
636 ));
637 break;
638 }
639 }
640 if($custom = $this->CustomActions()) {
641 if($custom instanceof DataObjectSet)
642 $actions->merge($custom);
643 else
644 $actions->push($custom);
645 }
646 return $actions;
647 }
648 }
649
650 class DataObjectManager_Controller extends Controller {
651 public function dosort() {
652 if(!empty($_POST) && is_array($_POST) && isset($this->urlParams['ID'])) {
653 $className = $this->urlParams['ID'];
654 if(stristr($className,"-") !== false) {
655 list($ownerClass, $className) = explode("-",$className);
656 }
657 $many_many = ((is_numeric($this->urlParams['OtherID'])) && SortableDataObject::is_sortable_many_many($className));
658 foreach($_POST as $group => $map) {
659 if(substr($group, 0, 7) == "record-") {
660 if($many_many) {
661 $controllerID = $this->urlParams['OtherID'];
662 $candidates = singleton($ownerClass)->many_many();
663 if(is_array($candidates)) {
664 foreach($candidates as $name => $class)
665 if($class == $className) {
666 $relationName = $name;
667 break;
668 }
669 }
670 if(!isset($relationName)) return false;
671 list($parentClass, $componentClass, $parentField, $componentField, $table) = singleton($ownerClass)->many_many($relationName);
672 foreach($map as $sort => $id)
673 DB::query("UPDATE `$table` SET SortOrder = $sort WHERE {$className}ID = $id AND {$ownerClass}ID = $controllerID");
674 }
675 else {
676 foreach($map as $sort => $id) {
677 $obj = DataObject::get_by_id($className, $id);
678 $obj->SortOrder = $sort;
679 $obj->write();
680 }
681 }
682 break;
683 }
684 }
685 }
686 }
687 public function i18n_js() {
688 return Convert::array2json(array(
689 'delete_confirm' => _t('DataObjectManager.CONFIRMDELETE','Delete?')
690 ));
691 }
692 }
693
694
695
696 class extends Form {
697 protected $sourceClass;
698 protected $dataObject;
699 public $NestedController = false;
700
701 function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {
702 $this->dataObject = $dataObject;
703 Requirements::clear();
704 Requirements::clear_combined_files();
705 Requirements::unblock_all();
706 Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js');
707 Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/behaviour/behaviour.js');
708 Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery/jquery.js');
709 Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js');
710 Requirements::javascript(THIRDPARTY_DIR.'/jquery-livequery/jquery.livequery.js');
711
712
713
714
715
716
717 Requirements::block(SAPPHIRE_DIR.'/javascript/ComplexTableField.js');
718 Requirements::block(SAPPHIRE_DIR.'/javascript/TableListField.js');
719 Requirements::block(THIRDPARTY_DIR.'/greybox/greybox.js');
720 Requirements::block(THIRDPARTY_DIR.'/greybox/AmiJS.js');
721 Requirements::block(THIRDPARTY_DIR.'/greybox/greybox.css');
722 Requirements::block(SAPPHIRE_DIR.'/css/TableListField.css');
723 Requirements::block(SAPPHIRE_DIR.'/css/ComplexTableField.css');
724 Requirements::block(ASSETS_DIR.'/leftandmain.js');
725 Requirements::block(ASSETS_DIR.'/base.js');
726 Requirements::block(SAPPHIRE_DIR.'/javascript/lang/en_US.js');
727 Requirements::css(SAPPHIRE_DIR . '/css/Form.css');
728 Requirements::css(CMS_DIR . '/css/typography.css');
729 Requirements::css(CMS_DIR . '/css/cms_right.css');
730 Requirements::css('dataobject_manager/css/dataobject_manager.css');
731
732
733 $parent = $controller;
734 if($parent instanceof DataObjectManager) {
735 $callback = $parent->requirementsForPopupCallback;
736 } else {
737 $callback = $parent->getParentController()->requirementsForPopupCallback;
738 }
739 if($callback) call_user_func($callback, $this);
740
741 if($this->dataObject->hasMethod('getRequirementsForPopup')) {
742 $this->dataObject->getRequirementsForPopup();
743 }
744 Requirements::javascript('dataobject_manager/javascript/dataobjectmanager_popup.js');
745
746
747 $actions = new FieldSet();
748 if(!$readonly) {
749 $actions->push(
750 $saveAction = new FormAction("saveComplexTableField", _t('DataObjectManager.SAVE','Save'))
751
752 );
753 $saveAction->addExtraClass('save');
754 }
755
756 parent::__construct($controller, $name, $fields, $actions, $validator);
757 $this->unsetValidator();
758
759 if($this->getNestedDOMs()) {
760 Requirements::javascript(THIRDPARTY_DIR.'/jquery-livequery/jquery.livequery.js');
761 Requirements::javascript('dataobject_manager/javascript/dom_jquery_ui.js');
762 Requirements::javascript('dataobject_manager/javascript/tooltip.js');
763 Requirements::javascript('dataobject_manager/javascript/dataobject_manager.js');
764 }
765 $this->NestedController = $this->controller->isNested;
766 }
767
768 function FieldHolder() {
769 return $this->renderWith('ComplexTableField_Form');
770 }
771
772 public function getFileFields() {
773 $file_fields = array();
774 foreach($this->Fields() as $field) {
775 if($field instanceof FileIFrameField || $field instanceof ImageField)
776 $file_fields[] = $field;
777 }
778 return !empty($file_fields)? $file_fields : false;
779 }
780
781 public function getNestedDOMs() {
782 $dom_fields = array();
783 foreach($this->Fields()->dataFields() as $field) {
784 if($field instanceof DataObjectManager) {
785 $field->isNested = true;
786 $dom_fields[] = $field;
787 }
788 elseif($field instanceof CompositeField) {
789 if($children = $field->children) {
790 foreach($children as $child) {
791 if($child instanceof DataObjectManager) {
792 $child->isNested = true;
793 $dom_fields[] = $child;
794 }
795 }
796 }
797 }
798 }
799 return !empty($dom_fields)? $dom_fields : false;
800 }
801 function getParentController() {
802 return $this->controller;
803 }
804
805 }
806
807
808
809 class DataObjectManager_ItemRequest extends ComplexTableField_ItemRequest {
810 public $isNested = false;
811 protected $itemList;
812 protected $currentIndex;
813
814 function __construct($ctf, $itemID) {
815 parent::__construct($ctf, $itemID);
816 $this->isNested = $this->ctf->isNested;
817 if($this->ctf->Items()) {
818 $this->itemList = $this->ctf->Items()->column();
819 $this->currentIndex = array_search($this->itemID,$this->itemList);
820 }
821 }
822
823 function Link($action = NULL) {
824 return Controller::join_links($this->ctf->BaseLink() , '/item/' . $this->itemID);
825 }
826
827 function saveComplexTableField($data, $form, $request) {
828 $dataObject = $this->dataObj();
829 $form->saveInto($dataObject);
830 $dataObject->write();
831
832 if(isset($data['ctf']['manyManyRelation'])) {
833 $parentRecord = DataObject::get_by_id($data['ctf']['parentClass'], (int) $data['ctf']['sourceID']);
834 $relationName = $data['ctf']['manyManyRelation'];
835 $componentSet = $parentRecord->getManyManyComponents($relationName);
836 $componentSet->add($dataObject);
837 }
838
839 $form->sessionMessage(sprintf(_t('DataObjectManager.SAVED','Saved %s successfully'),$this->ctf->SingleTitle()), 'good');
840
841 Director::redirectBack();
842 }
843
844 function DetailForm($childID = null) {
845 $form = parent::DetailForm($childID);
846 $form->Fields()->insertFirst(new LiteralField('open','<div id="field-holder"><div id="fade"></div>'));
847 $o = $form->Fields()->Last();
848 $form->Fields()->insertAfter(new LiteralField('close','</div>'),$o->Name());
849 if(!$this->ctf->Can('edit')) {
850 $form->makeReadonly();
851 $form->setActions(null);
852 }
853 return $form;
854 }
855
856 function edit() {
857 if(!$this->ctf->Can('view') && !$this->ctf->Can('edit'))
858 return false;
859
860 $this->methodName = "edit";
861
862 echo $this->renderWith($this->ctf->templatePopup);
863 }
864
865 public function duplicate() {
866 if(!$this->ctf->Can('duplicate'))
867 return false;
868 $this->methodName = "duplicate";
869
870 echo $this->renderWith(array('DataObjectManager_duplicate'));
871 }
872
873 public function DuplicateForm() {
874 return new Form(
875 $this,
876 "DuplicateForm",
877 new FieldSet(
878 new FieldGroup(
879 new LabelField('copy',_t('DataObjectManager.CREATE','Create ')),
880 new NumericField('Count','','1'),
881 new LabelField('times',sprintf(_t('DataObjectManager.COPIESOFOBJECT',' copies of this %s'),$this->ctf->SingleTitle()))
882 ),
883 new CheckboxField('Relations',_t('DataObjectManager.INCLUDERELATIONS','Include related objects'))
884 ),
885 new FieldSet(
886 new FormAction('doDuplicate',_t('DataObjectManager.DUPLICATE','Duplicate'))
887 )
888 );
889 }
890
891 public function doDuplicate($data,$form) {
892 if($obj = $this->dataObj()) {
893 for($i = 0;$i < $data['Count'];$i++) {
894 $new = $obj->duplicate();
895 if(isset($data['Relations']) && $data['Relations'] == "1") {
896 if($has_manys = $obj->has_many()) {
897 foreach($has_manys as $name => $class) {
898
899 if($has_ones = singleton($class)->has_one()) {
900 if($ownerRelation = array_search($this->ctf->SourceClass(),$has_ones)) {
901 $ownerID = $ownerRelation."ID";
902 if($related_objects = $obj->$name()) {
903 foreach($related_objects as $related_obj) {
904 $o = $related_obj->duplicate(false);
905 $o->$ownerID = $new->ID;
906 $o->write();
907 }
908 }
909 }
910 else
911 die(sprintf(_t('DataObjectManager.COULDNOTFINDRELATION','Could not find owner relation for class %s'),$this->ctf->SourceClass()));
912 }
913 else
914 die(sprintf(_t('DataObjectManager.COULDNOTFINDRELATION','Could not find owner relation for class %s'),$this->ctf->SourceClass()));
915 }
916 }
917 if($many_manys = $obj->many_many()) {
918 foreach($many_manys as $name => $class) {
919 if($obj->$name()) {
920 $new->$name()->setByIdList($obj->$name()->column());
921 }
922 }
923 $new->write();
924 }
925 }
926 }
927 $ret = "$i " . _t('DataObjectManager.DUPLICATESCREATED','duplicate(s) created');
928 if(isset($data['Relations']) && $data['Relations'] == "1") $ret .= ", " . _t('DataObjectManager.WITHRELATIONS','with relations included');
929 $form->sessionMessage($ret,'good');
930 }
931 else
932 $form->sessionMessage(_t('DataObjectManager.ERRORDUPLICATING','There was an error duplicating the object.'),'bad');
933 Director::redirectBack();
934 }
935
936
937 protected function getPrevID() {
938 return $this->itemList[$this->currentIndex - 1];
939 }
940
941 protected function getNextID() {
942 return $this->itemList[$this->currentIndex + 1];
943 }
944
945 function NextRecordLink() {
946 if(!$this->itemList || $this->currentIndex == sizeof($this->itemList)-1) return false;
947 return Controller::join_links($this->ctf->BaseLink() , '/item/' . $this->getNextID().'/edit')."?".$this->ctf->getQueryString();
948 }
949
950 function PrevRecordLink() {
951 if(!$this->itemList || $this->currentIndex == 0) return false;
952 return Controller::join_links($this->ctf->BaseLink() , '/item/' . $this->getPrevID().'/edit')."?".$this->ctf->getQueryString();
953 }
954
955 function () {
956 return $this->NextRecordLink() || $this->PrevRecordLink();
957 }
958
959 function HasDuplicate() {
960 return $this->ctf->Can('duplicate');
961 }
962
963 function SingleTitle() {
964 return $this->ctf->SingleTitle();
965 }
966
967 function DuplicateLink() {
968 return Controller::join_links($this->ctf->BaseLink(),'/duplicate/'.$this->itemID);
969 }
970
971 function HasRelated() {
972 $has_many = singleton($this->ctf->SourceClass())->has_many();
973 return is_array($has_many) && !empty($has_many);
974 }
975
976 }
977
978 class DataObjectManagerAction extends ViewableData {
979 static $behaviour_to_js = array (
980 'popup' => 'popup-button',
981 'delete' => 'delete-link',
982 'refresh' => 'refresh-button'
983 );
984
985 public $Title;
986 public $Behaviour;
987 public $ActionClass;
988 public $Link;
989 public $IconURL;
990
991 public function __construct($title, $link, $behaviour = "popup", $icon = null, $class = null, $rel = null) {
992 parent::__construct();
993 $this->Title = $title;
994 $this->Link = $link;
995 $this->Behaviour = self::$behaviour_to_js[$behaviour];
996 $this->IconURL = $icon;
997 $this->ActionClass = $class;
998 $this->Rel = $rel;
999 }
1000 }
1001
1002 class RussianUpLower {
1003 function str_to_upper($str) {
1004 return strtr($str,
1005 "abcdefghijklmnopqrstuvwxyz".
1006 "\xE0\xE1\xE2\xE3\xE4\xE5".
1007 "\xb8\xe6\xe7\xe8\xe9\xea".
1008 "\xeb\xeC\xeD\xeE\xeF\xf0".
1009 "\xf1\xf2\xf3\xf4\xf5\xf6".
1010 "\xf7\xf8\xf9\xfA\xfB\xfC".
1011 "\xfD\xfE\xfF",
1012 "ABCDEFGHIJKLMNOPQRSTUVWXYZ".
1013 "\xC0\xC1\xC2\xC3\xC4\xC5".
1014 "\xA8\xC6\xC7\xC8\xC9\xCA".
1015 "\xCB\xCC\xCD\xCE\xCF\xD0".
1016 "\xD1\xD2\xD3\xD4\xD5\xD6".
1017 "\xD7\xD8\xD9\xDA\xDB\xDC".
1018 "\xDD\xDE\xDF");
1019 }
1020
1021 function str_to_lower($str) {
1022 return strtr($str,
1023 "ABCDEFGHIJKLMNOPQRSTUVWXYZ".
1024 "\xC0\xC1\xC2\xC3\xC4\xC5".
1025 "\xA8\xC6\xC7\xC8\xC9\xCA".
1026 "\xCB\xCC\xCD\xCE\xCF\xD0".
1027 "\xD1\xD2\xD3\xD4\xD5\xD6".
1028 "\xD7\xD8\xD9\xDA\xDB\xDC".
1029 "\xDD\xDE\xDF",
1030 "abcdefghijklmnopqrstuvwxyz".
1031 "\xE0\xE1\xE2\xE3\xE4\xE5".
1032 "\xb8\xe6\xe7\xe8\xe9\xea".
1033 "\xeb\xeC\xeD\xeE\xeF\xf0".
1034 "\xf1\xf2\xf3\xf4\xf5\xf6".
1035 "\xf7\xf8\xf9\xfA\xfB\xfC".
1036 "\xfD\xfE\xfF");
1037 }
1038 }
1039
1040 class DOMUtil {
1041 public static function readable_list($array) {
1042 if(!is_array($array))
1043 return '';
1044 $and = _t('DataObjectManager.AND','and');
1045 switch(count($array)) {
1046 case 0:
1047 return '';
1048 case 1:
1049
1050 return reset($array);
1051 case 2:
1052 return reset($array)." $and ".end($array);
1053 default:
1054 $last = array_pop($array);
1055 return implode(', ', $array).", $and $last";
1056 }
1057 }
1058
1059 public static function readable_class($string) {
1060 1061 1062 1063 1064 1065
1066 return ucwords(trim(strtolower(preg_replace('!([A-Z])!',' \\1',$string))));
1067 }
1068
1069 1070 1071 1072 1073
1074 public static function to_underscore($str) {
1075 $str[0] = strtolower($str[0]);
1076 $func = function($c) { return "_" . strtolower($c[1]); };
1077 return preg_replace_callback('/([A-Z])/', $func, $str);
1078 }
1079
1080 1081 1082 1083 1084 1085
1086 public static function to_camel_case($str, $capitalise_first_char = false) {
1087 if($capitalise_first_char) {
1088 $str[0] = strtoupper($str[0]);
1089 }
1090 $func = function($c) { return strtoupper($c[1]); };
1091 return preg_replace_callback('/_([a-z])/', $func, $str);
1092 }
1093
1094 }
1095
1096
1097
[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.
-