1 <?php
2 3 4 5 6 7 8
9 class HtmlEditorField extends TextareaField {
10
11 12 13
14 public static = 1200;
15 public static = 800;
16
17 18 19
20 public static function include_js() {
21 Requirements::javascript(MCE_ROOT . 'tiny_mce_src.js');
22 Requirements::customScript(HtmlEditorConfig::get_active()->generateJS(), 'htmlEditorConfig');
23 }
24
25 26 27
28 public function __construct($name, $title = null, $rows = 30, $cols = 20, $value = '', $form = null) {
29 parent::__construct($name, $title, $rows, $cols, $value, $form);
30
31
32
33
34 self::include_js();
35 }
36
37 38 39
40 function Field() {
41
42 $value = new SS_HTMLValue($this->value);
43
44 if($links = $value->getElementsByTagName('a')) foreach($links as $link) {
45 $matches = array();
46
47 if(preg_match('/\[sitetree_link id=([0-9]+)\]/i', $link->getAttribute('href'), $matches)) {
48 if(!DataObject::get_by_id('SiteTree', $matches[1])) {
49 $class = $link->getAttribute('class');
50 $link->setAttribute('class', ($class ? "$class ss-broken" : 'ss-broken'));
51 }
52 }
53 }
54
55 return $this->createTag (
56 'textarea',
57 array (
58 'class' => 'htmleditor typography' . $this->extraClass(),
59 'rows' => $this->rows,
60 'cols' => $this->cols,
61 'style' => 'width: 97%; height: ' . ($this->rows * 16) . 'px',
62 'tinymce' => 'true',
63 'id' => $this->id(),
64 'name' => $this->name
65 ),
66 htmlentities($value->getContent(), ENT_COMPAT, 'UTF-8')
67 );
68 }
69
70 public function saveInto($record) {
71 if($record->escapeTypeForField($this->name) != 'xml') {
72 throw new Exception (
73 'HtmlEditorField->saveInto(): This field should save into a HTMLText or HTMLVarchar field.'
74 );
75 }
76
77 $linkedPages = array();
78 $linkedFiles = array();
79
80 $htmlValue = new SS_HTMLValue($this->value);
81
82
83 if($links = $htmlValue->getElementsByTagName('a')) foreach($links as $link) {
84 $href = Director::makeRelative($link->getAttribute('href'));
85
86 if($href) {
87 if(preg_match('/\[sitetree_link id=([0-9]+)\]/i', $href, $matches)) {
88 $ID = $matches[1];
89
90
91 if($class = $link->getAttribute('class')) {
92 $link->setAttribute('class', preg_replace('/(^ss-broken|ss-broken$| ss-broken )/', null, $class));
93 }
94
95 $linkedPages[] = $ID;
96 if(!DataObject::get_by_id('SiteTree', $ID)) $record->HasBrokenLink = true;
97
98 } else if(preg_match('/\[userform id=([0-9]+)\]/i', $href, $matches)) {
99 $ID = $matches[1];
100
101
102 if($class = $link->getAttribute('class')) {
103 $link->setAttribute('class', preg_replace('/(^ss-broken|ss-broken$| ss-broken )/', null, $class));
104 }
105
106 $linkedPages[] = $ID;
107 if(!DataObject::get_by_id('SiteTree', $ID)) $record->HasBrokenLink = true;
108
109 } else if(substr($href, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR.'/') {
110 $candidateFile = File::find(Convert::raw2sql(urldecode($href)));
111 if($candidateFile) {
112 $linkedFiles[] = $candidateFile->ID;
113 } else {
114 $record->HasBrokenFile = true;
115 }
116 } else if($href == '' || $href[0] == '/') {
117 $record->HasBrokenLink = true;
118 }
119
120
121 if (($class = $link->getAttribute('class')) && strpos($class, 'ssImagePopup') !== false) {
122 $href = preg_replace('/([^\?]*)\?r=[0-9]+$/i', '$1', $href);
123
124 if(!$image = File::find($href)) {
125 continue;
126 }
127
128 $width = self::$image_popup_width;
129 $height = self::$image_popup_height;
130
131
132 if($width && $height && ($width < $image->getWidth() || $height < $image->getHeight())) {
133 $link->setAttribute('href', $image->SetRatioSize($width, $height)->getRelativePath());
134 }
135 }
136 }
137 }
138
139
140 if($images = $htmlValue->getElementsByTagName('img')) foreach($images as $img) {
141
142 $img->setAttribute('src', preg_replace('/([^\?]*)\?r=[0-9]+$/i', '$1', $img->getAttribute('src')));
143 if(!$image = File::find($path = urldecode(Director::makeRelative($img->getAttribute('src'))))) {
144 if(substr($path, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR . '/') {
145 $record->HasBrokenFile = true;
146 }
147
148 continue;
149 }
150
151
152 $width = $img->getAttribute('width');
153 $height = $img->getAttribute('height');
154
155 if($image){
156 if($width && $height && ($width != $image->getWidth() || $height != $image->getHeight())) {
157
158 $resized=$image->ResizedImage($width, $height);
159 if($resized)
160 $img->setAttribute('src', $resized->getRelativePath());
161 }
162 }
163
164
165 if(!$img->getAttribute('alt')) $img->setAttribute('alt', '');
166 if(!$img->getAttribute('title')) $img->setAttribute('title', '');
167
168
169 if($img->getAttribute('src')){
170
171 $linkedFiles[] = $image->ID;
172 }
173 }
174
175
176 if($record->ID && $record->many_many('LinkTracking') && $tracker = $record->LinkTracking()) {
177 $filter = sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', $this->name, $record->ID);
178 DB::query("DELETE FROM \"$tracker->tableName\" WHERE $filter");
179
180 if($linkedPages) foreach($linkedPages as $item) {
181 $SQL_fieldName = Convert::raw2sql($this->name);
182 DB::query("INSERT INTO \"SiteTree_LinkTracking\" (\"SiteTreeID\",\"ChildID\", \"FieldName\")
183 VALUES ($record->ID, $item, '$SQL_fieldName')");
184 }
185 }
186
187
188 if ($record->hasMethod('updateFieldTracking')) {
189 $record->updateFieldTracking($this->name, $linkedFiles);
190 }
191
192 $record->{$this->name} = $htmlValue->getContent();
193 }
194
195 196 197
198 public function performReadonlyTransformation() {
199 $field = new HtmlEditorField_Readonly($this->name, $this->title, $this->value);
200 $field->setForm($this->form);
201 $field->dontEscape = true;
202 return $field;
203 }
204
205 }
206
207 208 209 210 211
212 class HtmlEditorField_Readonly extends ReadonlyField {
213 function Field() {
214 $valforInput = $this->value ? Convert::raw2att($this->value) : "";
215 return "<span class=\"readonly typography\" id=\"" . $this->id() . "\">" . ( $this->value && $this->value != '<p></p>' ? $this->value : '<i>(not set)</i>' ) . "</span><input type=\"hidden\" name=\"".$this->name."\" value=\"".$valforInput."\" />";
216 }
217 function Type() {
218 return 'htmleditorfield readonly';
219 }
220 }
221
222 223 224 225 226 227
228 class HtmlEditorField_Toolbar extends RequestHandler {
229 protected $controller, $name;
230
231 function __construct($controller, $name) {
232 parent::__construct();
233 Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
234 Requirements::javascript(SAPPHIRE_DIR . "/javascript/tiny_mce_improvements.js");
235
236 Requirements::javascript(SAPPHIRE_DIR ."/thirdparty/jquery-form/jquery.form.js");
237 Requirements::javascript(SAPPHIRE_DIR ."/javascript/HtmlEditorField.js");
238
239 $this->controller = $controller;
240 $this->name = $name;
241 }
242
243 244 245 246 247
248 function siteTreeSearchCallback($sourceObject, $labelField, $search) {
249 $search = Convert::raw2sql($search);
250 return DataObject::get($sourceObject, "\"$labelField\" LIKE '%$search%'");
251 }
252
253 254 255 256 257
258 function fileTreeFilterCallback($item) {
259 if (!$item->Title) return false;
260 if (substr($item->FileName, 0, strlen(ASSETS_DIR)) != ASSETS_DIR) return false;
261 return true;
262 }
263
264 265 266 267 268 269
270 function LinkForm() {
271 $siteTree = new TreeDropdownField('internal', _t('HtmlEditorField.PAGE', "Page"), 'SiteTree', 'ID', 'MenuTitle', true);
272
273 $siteTree->setSearchFunction(array($this, 'siteTreeSearchCallback'));
274
275 $fileTree = new TreeDropdownField('file', _t('HtmlEditorField.FILE', 'File'), 'File', 'Filename', 'Title', true);
276 $fileTree->setFilterFunction(array($this, 'fileTreeFilterCallback'));
277
278 $form = new Form(
279 $this->controller,
280 "{$this->name}/LinkForm",
281 new FieldSet(
282 new LiteralField('Heading', '<h2><img src="cms/images/closeicon.gif" alt="' . _t('HtmlEditorField.CLOSE', 'close').'" title="' . _t('HtmlEditorField.CLOSE', 'close') . '" />' . _t('HtmlEditorField.LINK', 'Link') . '</h2>'),
283 new OptionsetField(
284 'LinkType',
285 _t('HtmlEditorField.LINKTO', 'Link to'),
286 array(
287 'internal' => _t('HtmlEditorField.LINKINTERNAL', 'Page on the site'),
288 'external' => _t('HtmlEditorField.LINKEXTERNAL', 'Another website'),
289 'anchor' => _t('HtmlEditorField.LINKANCHOR', 'Anchor on this page'),
290 'email' => _t('HtmlEditorField.LINKEMAIL', 'Email address'),
291 'file' => _t('HtmlEditorField.LINKFILE', 'Download a file'),
292 )
293 ),
294 $siteTree,
295 new TextField('external', _t('HtmlEditorField.URL', 'URL'), 'http://'),
296 new EmailField('email', _t('HtmlEditorField.EMAIL', 'Email address')),
297 $fileTree,
298 new TextField('Anchor', _t('HtmlEditorField.ANCHORVALUE', 'Anchor')),
299 new TextField('LinkText', _t('HtmlEditorField.LINKTEXT', 'Link text')),
300 new TextField('Description', _t('HtmlEditorField.LINKDESCR', 'Link description')),
301 new CheckboxField('TargetBlank', _t('HtmlEditorField.LINKOPENNEWWIN', 'Open link in a new window?')),
302 new HiddenField('Locale', null, $this->controller->Locale)
303 ),
304 new FieldSet(
305 new FormAction('insert', _t('HtmlEditorField.BUTTONINSERTLINK', 'Insert link')),
306 new FormAction('remove', _t('HtmlEditorField.BUTTONREMOVELINK', 'Remove link'))
307 )
308 );
309
310 $form->loadDataFrom($this);
311
312 $this->extend('updateLinkForm', $form);
313
314 return $form;
315 }
316
317 318 319 320 321 322
323 function ImageForm() {
324 $fields = new FieldSet(
325 new LiteralField('Heading', '<h2><img src="cms/images/closeicon.gif" alt="' . _t('HtmlEditorField.CLOSE', 'close') . '" title="' . _t('HtmlEditorField.CLOSE', 'close') . '" />' . _t('HtmlEditorField.IMAGE', 'Image') . '</h2>'),
326 new TreeDropdownField('FolderID', _t('HtmlEditorField.FOLDER', 'Folder'), 'Folder'),
327 new CompositeField(new FieldSet(
328 new LiteralField('ShowUpload', '<p class="showUploadField"><a href="#">'. _t('HtmlEditorField.SHOWUPLOADFORM', 'Upload File') .'</a></p>'),
329 new FileField("Files[0]" , _t('AssetAdmin.CHOOSEFILE','Choose file: ')),
330 new LiteralField('Response', '<div id="UploadFormResponse"></div>'),
331 new HiddenField('UploadMode', 'Upload Mode', 'CMSEditor')
332 )),
333 new TextField('getimagesSearch', _t('HtmlEditorField.SEARCHFILENAME', 'Search by file name')),
334 new ThumbnailStripField('FolderImages', 'FolderID', 'getimages'),
335 new TextField('AltText', _t('HtmlEditorField.IMAGEALTTEXT', 'Alternative text (alt) - shown if image cannot be displayed'), '', 80),
336 new TextField('ImageTitle', _t('HtmlEditorField.IMAGETITLE', 'Title text (tooltip) - for additional information about the image')),
337 new TextField('CaptionText', _t('HtmlEditorField.CAPTIONTEXT', 'Caption text')),
338 new DropdownField(
339 'CSSClass',
340 _t('HtmlEditorField.CSSCLASS', 'Alignment / style'),
341 array(
342 'left' => _t('HtmlEditorField.CSSCLASSLEFT', 'On the left, with text wrapping around.'),
343 'leftAlone' => _t('HtmlEditorField.CSSCLASSLEFTALONE', 'On the left, on its own.'),
344 'right' => _t('HtmlEditorField.CSSCLASSRIGHT', 'On the right, with text wrapping around.'),
345 'center' => _t('HtmlEditorField.CSSCLASSCENTER', 'Centered, on its own.'),
346 '' => _t('HtmlEditorField.CSSCLASSNONE', 'No alignment'),
347 )
348 ),
349 new FieldGroup(_t('HtmlEditorField.IMAGEDIMENSIONS', 'Dimensions'),
350 new TextField('Width', _t('HtmlEditorField.IMAGEWIDTHPX', 'Width'), 100),
351 new TextField('Height', " x " . _t('HtmlEditorField.IMAGEHEIGHTPX', 'Height'), 100)
352 ),
353
354
355
356 new FieldGroup(
357 new LiteralField('PopupLargeImage',
358 '<div id="PopupLargeImage" class="field checkbox">
359 <input type="checkbox" id="PopupLargeImage_Checkbox" />
360 <label class="right" for="PopupLargeImage_Checkbox">'._t('HtmlEditorField.POPUPLARGE', 'Image can be clicked for larger version').'</label>
361 </div>
362 '
363 )
364 )
365 );
366
367 $actions = new FieldSet(
368 new FormAction('insertimage', _t('HtmlEditorField.BUTTONINSERTIMAGE', 'Insert image'))
369 );
370
371 $form = new Form(
372 $this->controller,
373 "{$this->name}/ImageForm",
374 $fields,
375 $actions
376 );
377
378 $form->disableSecurityToken();
379 $form->loadDataFrom($this);
380
381
382 $this->extend('updateImageForm', $form);
383
384 return $form;
385 }
386
387 function FlashForm() {
388 $form = new Form(
389 $this->controller,
390 "{$this->name}/FlashForm",
391 new FieldSet(
392 new LiteralField('Heading', '<h2><img src="cms/images/closeicon.gif" alt="'._t('HtmlEditorField.CLOSE', 'close').'" title="'._t('HtmlEditorField.CLOSE', 'close').'" />'._t('HtmlEditorField.FLASH', 'Flash').'</h2>'),
393 new TreeDropdownField("FolderID", _t('HtmlEditorField.FOLDER'), "Folder"),
394 new TextField('getflashSearch', _t('HtmlEditorField.SEARCHFILENAME', 'Search by file name')),
395 new ThumbnailStripField("Flash", "FolderID", "getflash"),
396 new FieldGroup(_t('HtmlEditorField.IMAGEDIMENSIONS', "Dimensions"),
397 new TextField("Width", _t('HtmlEditorField.IMAGEWIDTHPX', "Width"), 100),
398 new TextField("Height", "x " . _t('HtmlEditorField.IMAGEHEIGHTPX', "Height"), 100)
399 )
400 ),
401 new FieldSet(
402 new FormAction("insertflash", _t('HtmlEditorField.BUTTONINSERTFLASH', 'Insert Flash'))
403 )
404 );
405
406 $form->disableSecurityToken();
407 $form->loadDataFrom($this);
408 $form->disableSecurityToken();
409
410 $this->extend('updateFlashForm', $form);
411
412 return $form;
413 }
414
415 function getfileinfo($request) {
416 $fileName = $request->requestVar('fileName');
417 if ($file = File::find($fileName)) {
418 if ($file->ClassName == 'Folder') return '';
419 return sprintf(_t('HtmlEditorField.FILE_INFO', '%s file, %s'), strtoupper($file->Extension), $file->Size);
420 }
421 return '';
422 }
423
424 }
425
426
[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.
-