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

  • AjaxUniqueTextField
  • AutocompleteTextField
  • ConfirmedPasswordField
  • CreditCardField
  • CurrencyField
  • CurrencyField_Disabled
  • CurrencyField_Readonly
  • EmailField
  • HtmlEditorConfig
  • HtmlEditorField
  • HtmlEditorField_Readonly
  • HtmlEditorField_Toolbar
  • NumericField
  • PasswordField
  • PhoneNumberField
  • UniqueRestrictedTextField
  • UniqueTextField
  1 <?php
  2 /**
  3  * A TinyMCE-powered WYSIWYG HTML editor field with image and link insertion and tracking capabilities. Editor fields
  4  * are created from <textarea> tags, which are then converted with JavaScript.
  5  *
  6  * @package forms
  7  * @subpackage fields-formattedinput
  8  */
  9 class HtmlEditorField extends TextareaField {
 10     
 11     /** 
 12      * Height and width of an asset in a popup 
 13      */ 
 14     public static $image_popup_width = 1200; 
 15     public static $image_popup_height = 800; 
 16 
 17     /**
 18      * Includes the JavaScript neccesary for this field to work using the {@link Requirements} system.
 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      * @see TextareaField::__construct()
 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         //$this->addExtraClass('typography');
 32         //$this->addExtraClass('htmleditor');
 33         
 34         self::include_js();
 35     }
 36     
 37     /**
 38      * @return string
 39      */
 40     function Field() {
 41         // mark up broken links
 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', // prevents horizontal scrollbars
 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         // Populate link tracking for internal links & links to asset files.
 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                     // clear out any broken link classes
 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                     // clear out any broken link classes
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                 // see if the link has the class meaning it should be resampled
121                 if (($class = $link->getAttribute('class')) && strpos($class, 'ssImagePopup') !== false) {
122                     $href = preg_replace('/([^\?]*)\?r=[0-9]+$/i', '$1', $href);
123                     // get the size to resample to
124                     if(!$image = File::find($href)) {
125                         continue;
126                     }
127 
128                     $width  = self::$image_popup_width;
129                     $height  = self::$image_popup_height;
130                     
131                     // Only resize if the original image is actually large enough
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         // Resample images, add default attributes and add to assets tracking.
140         if($images = $htmlValue->getElementsByTagName('img')) foreach($images as $img) {
141             // strip any ?r=n data from the src attribute
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             // Resample the images if the width & height have changed.
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                     //Make sure that the resized image actually returns an image:
158                     $resized=$image->ResizedImage($width, $height);
159                     if($resized)
160                         $img->setAttribute('src', $resized->getRelativePath());
161                 }
162             }
163             
164             // Add default empty title & alt attributes.
165             if(!$img->getAttribute('alt')) $img->setAttribute('alt', '');
166             if(!$img->getAttribute('title')) $img->setAttribute('title', '');
167             
168             //If the src attribute is not set, then we won't add this to the list:
169             if($img->getAttribute('src')){
170                 // Add to the tracked files.
171                 $linkedFiles[] = $image->ID;
172             }
173         }
174         
175         // Save file & link tracking data.
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         // вместо ImageTracking сохраняем трекинг через DataObjectFileTrackingDecorator
188         if ($record->hasMethod('updateFieldTracking')) {
189             $record->updateFieldTracking($this->name, $linkedFiles);
190         }
191         
192         $record->{$this->name} = $htmlValue->getContent();
193     }
194 
195     /**
196      * @return HtmlEditorField_Readonly
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  * Readonly version of an {@link HTMLEditorField}.
209  * @package forms
210  * @subpackage fields-formattedinput
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  * External toolbar for the HtmlEditorField.
224  * This is used by the CMS
225  * @package forms
226  * @subpackage fields-formattedinput
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      * Searches the SiteTree for display in the dropdown
245      *  
246      * @return callback
247      */ 
248     function siteTreeSearchCallback($sourceObject, $labelField, $search) {
249         $search = Convert::raw2sql($search);
250         return DataObject::get($sourceObject, "\"$labelField\" LIKE '%$search%'");
251     }
252     
253     /**
254      * Фильтрует файлы без Title (служебные) !!! вообще говоря - костыль
255      *  
256      * @return boolean
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      * Return a {@link Form} instance allowing a user to
266      * add links in the TinyMCE content editor.
267      *  
268      * @return Form
269      */
270     function LinkForm() {
271         $siteTree = new TreeDropdownField('internal', _t('HtmlEditorField.PAGE', "Page"), 'SiteTree', 'ID', 'MenuTitle', true);
272         // mimic the SiteTree::getMenuTitle(), which is bypassed when the search is performed
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      * Return a {@link Form} instance allowing a user to
319      * add images to the TinyMCE content editor.
320      *  
321      * @return Form
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') // used as a hook for doUpload switching
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             // note: using a Literal field here because the checkbox field default structure
354             // doesn't play nice in the toolbar layout, and we're only accessing
355             // via JS
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         // Allow other people to extend the fields being added to the imageform 
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. -
Webylon 3.1 API Docs API documentation generated by ApiGen 2.8.0