1 <?php
2
3 /**
4 * A PHP version of TinyMCE's configuration, to allow various parameters to be configured on a site or section basis
5 *
6 * There can be multiple HtmlEditorConfig's, which should always be created / accessed using HtmlEditorConfig::get. You can then set
7 * the currently active config using set_active. Whichever config is active when HtmlEditorField#Field is called wins.
8 *
9 * @author "Hamish Friedlander" <hamish@silverstripe.com>
10 * @package forms
11 * @subpackage fields-formattedinput
12 */
13 class HtmlEditorConfig {
14
15 static $configs = array();
16 static $current = null;
17
18 /**
19 * Get the HtmlEditorConfig object for the given identifier. This is a correct way to get an HtmlEditorConfig instance - do not call 'new'
20 * @param $identifier string - the identifier for the config set
21 * @return HtmlEditorConfig - the configuration object. This will be created if it does not yet exist for that identifier
22 */
23 static function get($identifier = 'default') {
24 if (!array_key_exists($identifier, self::$configs)) self::$configs[$identifier] = new HtmlEditorConfig();
25 return self::$configs[$identifier];
26 }
27
28 /**
29 * Set the currently active configuration object
30 * @param $identifier string - the identifier for the config set
31 * @return null
32 */
33 static function set_active($identifier = null) {
34 self::$current = $identifier;
35 }
36
37 /**
38 * Get the currently active configuration object
39 * @return HtmlEditorConfig - the active configuration object
40 */
41 static function get_active() {
42 $identifier = self::$current ? self::$current : 'default';
43 return self::get($identifier);
44 }
45
46 /**
47 * Get the available configurations as a map of friendly_name to
48 * configuration name.
49 * @return array
50 */
51 static function get_available_configs_map() {
52 $configs = array();
53
54 foreach(self::$configs as $identifier => $config) {
55 $configs[$identifier] = $config->getOption('friendly_name');
56 }
57
58 return $configs;
59 }
60
61 /**
62 * Holder for all TinyMCE settings _except_ plugins and buttons
63 */
64 protected $settings = array(
65 'friendly_name' => '(Please set a friendly name for this config)',
66 'priority' => 0,
67 'mode' => 'none',
68 'width' => '100%',
69 'theme' => 'advanced',
70
71 'table_inline_editing' => true,
72
73 'relative_urls' => true,
74 'verify_html' => true,
75 );
76
77 /**
78 * Holder list of enabled plugins
79 */
80 protected $plugins = array(
81 'contextmenu' => null,
82 'table' => null,
83 'paste' => null,
84 'inlinepopups' => null,
85 // 'advcode' => '../../../sapphire/thirdparty/tinymce-advcode/editor_plugin_src.js',
86 // 'spellchecker' => null
87 );
88
89 /**
90 * Holder list of buttons, organised by line
91 */
92 protected $buttons = array(
93 1 => array('bold','italic','underline','strikethrough','separator','justifyleft','justifycenter','justifyright','justifyfull','formatselect','separator','bullist','numlist','outdent','indent','blockquote','hr','charmap'),
94 2 => array('undo','redo','separator','cut','copy','paste','pastetext','pasteword','spellchecker','separator','code','search','replace','selectall','visualaid','separator','tablecontrols'),
95 3 => array()
96 );
97
98 /**
99 * Get the current value of an option
100 * @param $k string - The key of the option to get
101 * @return mixed - The value of the specified option
102 */
103 function getOption($k) {
104 if(isset($this->settings[$k])) return $this->settings[$k];
105 }
106
107 /**
108 * Set the value of one option
109 * @param $k string - The key of the option to set
110 * @param $v mixed - The value of the option to set
111 * @return mixed - $v returned for chaining
112 */
113 function setOption($k,$v) {
114 return $this->settings[$k] = $v;
115 }
116
117 /**
118 * Set multiple options
119 * @param $a array - The options to set, as keys and values of the array
120 * @return null
121 */
122 function setOptions($a) {
123 foreach ($a as $k=>$v) {
124 $this->settings[$k] = $v;
125 }
126 }
127
128 /**
129 * get multiple options
130 * @return $a array - The options to set, as keys and values of the array
131 */
132 function getOptions() {
133 return $this->settings;
134 }
135
136 /**
137 * Enable one or several plugins. Will maintain unique list if already
138 * enabled plugin is re-passed. If passed in as a map of plugin-name to path,
139 * the plugin will be loaded by tinymce.PluginManager.load() instead of through tinyMCE.init().
140 * Keep in mind that these externals plugins require a dash-prefix in their name.
141 *
142 * @see http://wiki.moxiecode.com/index.php/TinyMCE:API/tinymce.PluginManager/load
143 *
144 * @param String [0..] a string, or several strings, or a single array of strings - The plugins to enable
145 * @return null
146 */
147 function enablePlugins() {
148 $plugins = func_get_args();
149 if (is_array(current($plugins))) $plugins = current($plugins);
150 foreach ($plugins as $plugin => $path) {
151 // if plugins are passed without a path
152 if(is_numeric($plugin)) {
153 $plugin = $path;
154 $path = null;
155 }
156 if (!array_key_exists($plugin, $this->plugins)) $this->plugins[$plugin] = $path;
157 }
158 }
159
160 /**
161 * Enable one or several plugins. Will properly handle being passed a plugin that is already disabled
162 * @param String [0..] a string, or several strings, or a single array of strings - The plugins to disable
163 * @return null
164 */
165 function disablePlugins() {
166 $plugins = func_get_args();
167 if (is_array(current($plugins))) $plugins = current($plugins);
168
169 foreach ($plugins as $plugin) {
170 if(array_key_exists($plugin, $this->plugins)) {
171 unset($this->plugins[$plugin]);
172 }
173 }
174 }
175
176 /**
177 * @return Array
178 */
179 function getPlugins() {
180 return $this->plugins;
181 }
182
183 /**
184 * Totally re-set the buttons on a given line
185 *
186 * @param integer from 1..3 - The line number to redefine
187 * @param string a string or several strings, or a single array of strings - The button names to make this line contain
188 * @return null
189 */
190 function setButtonsForLine() {
191 if (func_num_args() == 2) {
192 list($line, $buttons) = func_get_args();
193 }
194 else {
195 $buttons = func_get_args();
196 $line = array_shift($buttons);
197 }
198 $this->buttons[$line] = is_array($buttons) ? $buttons : array($buttons);
199 }
200
201 /**
202 * Add buttons to the end of a line
203 * @param integer from 1..3
204 * @param string a string, or several strings, or a single array of strings - The button names to add to the end of this line
205 * @return null
206 */
207 function addButtonsToLine() {
208 $inserts = func_get_args();
209 $line = array_shift($inserts);
210 if (is_array($inserts[0])) $inserts = $inserts[0];
211
212 foreach ($inserts as $button) {
213 $this->buttons[$line][] = $button;
214 }
215 }
216
217 /**
218 * Internal function for adding and removing buttons related to another button
219 * @param $name string - the name of the button to modify
220 * @param $offset integer - the offset relative to that button to perform an array_splice at - 0 for before $name, 1 for after
221 * @param $del integer - the number of buttons to remove at the position given by index(string) + offset
222 * @param $add mixed - an array or single item to insert at the position given by index(string) + offset, or null for no insertion
223 * @return boolean - true if $name matched a button, false otherwise
224 */
225 protected function modifyButtons($name, $offset, $del=0, $add=null) {
226 foreach ($this->buttons as &$buttons) {
227 if (($idx = array_search($name, $buttons)) !== false) {
228 if ($add) array_splice($buttons, $idx+$offset, $del, $add);
229 else array_splice($buttons, $idx+$offset, $del, $add);
230 return true;
231 }
232 }
233 return false;
234 }
235
236 /**
237 * Insert buttons before the first occurance of another button
238 * @param string - the name of the button to insert other buttons before
239 * @param string a string, or several strings, or a single array of strings - the button names to insert before that button
240 * @return boolean - true if insertion occured, false if it did not (because the given button name was not found)
241 */
242 function insertButtonsBefore() {
243 $inserts = func_get_args();
244 $before = array_shift($inserts);
245 return $this->modifyButtons($before, 0, 0, $inserts);
246 }
247
248 /**
249 * Insert buttons after the first occurance of another button
250 * @param string - the name of the button to insert other buttons after
251 * @param string a string, or several strings, or a single array of strings - the button names to insert after that button
252 * @return boolean - true if insertion occured, false if it did not (because the given button name was not found)
253 */
254 function insertButtonsAfter() {
255 $inserts = func_get_args();
256 $after = array_shift($inserts);
257 return $this->modifyButtons($after, 1, 0, $inserts);
258 }
259
260 /**
261 * Remove the first occurance of buttons
262 * @param string one or more strings - the name of the buttons to remove
263 * @return null
264 */
265 function removeButtons() {
266 $removes = func_get_args();
267 foreach ($removes as $button) {
268 $this->modifyButtons($button, 0, 1);
269 }
270 }
271
272 /**
273 * Generate the javascript that will set tinyMCE's configuration to that of the current settings of this object
274 * @return string - the javascript
275 */
276 function generateJS() {
277 $config = $this->settings;
278
279 // plugins
280 $internalPlugins = array();
281 $externalPluginsJS = '';
282 foreach($this->plugins as $plugin => $path) {
283 if(!$path) {
284 $internalPlugins[] = $plugin;
285 } else {
286 $internalPlugins[] = '-' . $plugin;
287 $externalPluginsJS .= sprintf(
288 'tinymce.PluginManager.load("%s", "%s");' . "\n",
289 $plugin,
290 $path
291 );
292 }
293 }
294 $config['plugins'] = implode(',', $internalPlugins);
295
296 foreach ($this->buttons as $i=>$buttons) {
297 $config['theme_advanced_buttons'.$i] = implode(',', $buttons);
298 }
299
300 return "
301 if((typeof tinyMCE != 'undefined')) {
302 $externalPluginsJS
303 tinyMCE.init(" . Convert::raw2json($config) . ");
304 }
305 ";
306 }
307 }
308