/[drupal]/contributions/modules/inline/inline.module
ViewVC logotype

Contents of /contributions/modules/inline/inline.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.46 - (show annotations) (download) (as text)
Wed Aug 12 07:52:29 2009 UTC (3 months, 1 week ago) by sun
Branch: MAIN
CVS Tags: HEAD
Changes since 1.45: +2 -1 lines
File MIME type: text/x-php
by sun: Fixed Wysiwyg integration for Node breaks Image Assist.
1 <?php
2 // $Id: inline.module,v 1.45 2009/08/12 06:21:13 sun Exp $
3
4 /**
5 * @file
6 * Inline macro tag processing and rendering API for Drupal.
7 *
8 * @todo Major bug: Macros get updated in the database when being altered
9 * during preview. When canceling node edit, the node contains the new,
10 * unintended output.
11 * @todo Catch and delete removed inline macros.
12 */
13
14 /**
15 * Implementation of hook_perm().
16 *
17 * @todo Add a central settings page other implementations can append to.
18 */
19 function inline_perm() {
20 return array('administer inline settings');
21 }
22
23 /**
24 * Implementation of hook_inline_info().
25 *
26 * @see hook_inline_info()
27 */
28 function inline_inline_info() {
29 $info['node'] = array(
30 'file' => 'inline.node.inc',
31 );
32 // @todo (Temporarily?) moved into inline_upload.module.
33 // if (module_exists('upload')) {
34 // $info['upload'] = array(
35 // 'file' => 'inline.upload.inc',
36 // );
37 // }
38 return $info;
39 }
40
41 /**
42 * @defgroup inline_macro Inline input filter/macro parsing
43 * @{
44 */
45
46 /**
47 * Implementation of hook_filter().
48 */
49 function inline_filter($op, $delta = 0, $format = -1, $text = '') {
50 switch ($op) {
51 case 'list':
52 return array(0 => t('Inline contents'));
53
54 case 'description':
55 return t('Substitutes [inline] tags.');
56
57 case 'no cache':
58 // Development. 13/01/2008 sun
59 return TRUE;
60
61 case 'prepare':
62 return $text;
63
64 case 'process':
65 // @todo This is a typical *prepare* alteration!
66 // @todo Use Inline API for our own tags; only difference is that we probably
67 // want to keep any macros that cannot be "rendered" for any reason --
68 // or move this into inline_get_macros() ?
69 preg_match_all('/\[inline\|iid=(\d+)\]/', $text, $matches);
70 foreach ($matches[1] as $key => $iid) {
71 if ($macro = inline_macro_load($iid)) {
72 $text = str_replace($matches[0][$key], inline_macro_build($macro), $text);
73 }
74 }
75 foreach (inline_get_macros($text) as $original => $macro) {
76 // @todo Move this into inline_macro_render()?
77 $validation = inline_macro_validate($macro);
78 if (is_bool($validation) && $validation) {
79 $text = str_replace($original, inline_macro_render($macro), $text);
80 }
81 else {
82 $text = str_replace($original, (is_string($validation) ? $validation : ''), $text);
83 }
84 }
85 return $text;
86 }
87 }
88
89 /**
90 * Invoke a hook_inline() implementation.
91 */
92 function inline_invoke($op, &$macro) {
93 // @todo Add static caching for function lookups here.
94 if (!function_exists($macro->callback)) {
95 if (isset($macro->file)) {
96 if (!isset($macro->path)) {
97 $macro->path = drupal_get_path('module', $macro->module);
98 }
99 require_once $macro->path . '/' . $macro->file;
100 }
101 }
102 if (function_exists($macro->callback)) {
103 $function = $macro->callback;
104 return $function($op, $macro);
105 }
106 }
107
108 /**
109 * Return all inline macros as an array.
110 *
111 * @todo Use placeholders for #default_value and/or additional properties.
112 * Or introduce a new $op 'alter' for hook_inline?
113 */
114 function inline_get_macros($text) {
115 static $implementations;
116 $macros = array();
117
118 if (!isset($implementations)) {
119 // Collect possible inline tag names.
120 $implementations = array();
121 foreach (module_implements('inline_info') as $module) {
122 $module_tags = module_invoke($module, 'inline_info');
123 if (!is_array($module_tags)) {
124 continue;
125 }
126 foreach ($module_tags as $tag => $info) {
127 $module_tags[$tag]['module'] = $module;
128 $module_tags[$tag]['tag'] = $tag;
129 // By default, we assume a hook_TAGNAME_inline() implementation.
130 if (!isset($info['callback'])) {
131 $module_tags[$tag]['callback'] = $module . '_' . $tag . '_inline';
132 }
133 }
134 $implementations = array_merge($implementations, $module_tags);
135 }
136 }
137
138 $tagnames = '('. implode('|', array_keys($implementations)) .')';
139 // @todo Add support for escaped [, ] chars
140 preg_match_all('@\['. $tagnames .'\s*\|([^\[\]]+)\]@', $text, $matches);
141 // Don't process duplicates.
142 $tags = array_unique($matches[2]);
143
144 foreach ($tags as $n => $parameters) {
145 $macro = (object) $implementations[$matches[1][$n]];
146 // @todo De-$opify hook_inline().
147 $args = inline_invoke('args', $macro);
148 // @todo Add support for escaped | characters.
149 $macro_params = array_map('trim', explode('|', $parameters));
150 // @todo Add a macro counter for each found tag *per module* to allow stuff
151 // like odd/even classes (f.e. $macro['#count']).
152 foreach ($macro_params as $param) {
153 list($key, $value) = explode('=', $param, 2);
154 $key = trim($key);
155 // All parameter values are strings by default.
156 $value = trim($value);
157 // Convert numeric values.
158 if (is_numeric($value)) {
159 if (strpos($value, '.') !== FALSE) {
160 $value = (float)$value;
161 }
162 else {
163 $value = (int)$value;
164 }
165 }
166 // Convert boolean values.
167 else if (in_array(strtolower($value), array('true', 'false'))) {
168 $value = (bool)$value;
169 }
170 // Stack multiple occurences.
171 if (isset($macro->params[$key]) && isset($args[$key]['#multiple']) && $args[$key]['#multiple']) {
172 if (!is_array($macro->params[$key])) {
173 $macro->params[$key] = array($macro->params[$key]);
174 }
175 $macro->params[$key][] = $value;
176 }
177 else {
178 $macro->params[$key] = $value;
179 }
180 }
181 // Copy over internal inline macro id, if existent.
182 if (isset($macro->params['iid'])) {
183 $macro->iid = $macro->params['iid'];
184 }
185 // The full unaltered tag is the key for the filter attributes array.
186 $macros[$matches[0][$n]] = $macro;
187 }
188
189 return $macros;
190 }
191
192 /**
193 * Retrieve macro parameter value formats and validate user values against them.
194 *
195 * @param $macro
196 * An inline macro object containing user supplied values.
197 * @return
198 * Whether the supplied input is valid (TRUE) or not (FALSE or an error
199 * message).
200 */
201 function inline_macro_validate($macro) {
202 // Perform basic validation of tag arguments.
203 $args = inline_invoke('args', $macro);
204 foreach ($args as $arg => $info) {
205 // Check if required arguments are set.
206 if (isset($info['#required']) && $info['#required'] && !isset($info['#default_value']) && (!isset($macro->params[$arg]) || $macro->params[$arg] === '')) {
207 return t('Missing argument %arg.', array('%arg' => $arg));
208 }
209 if (isset($macro->params[$arg])) {
210 // Keep only the first value if multiple flag is not set.
211 if ((!isset($info['#multiple']) || !$info['#multiple']) && is_array($macro->params[$arg])) {
212 $macro->params[$arg] = $macro->params[$arg][0];
213 }
214 // Check if supplied arguments are of an expected type.
215 // @todo Use a custom #validate property for this instead.
216 if (isset($info['#type'])) {
217 $typecheck = 'is_' . $info['#type'];
218 if (!function_exists($typecheck) || !$typecheck($macro->params[$arg])) {
219 return t('Wrong value type supplied for argument %arg.', array('%arg' => $arg));
220 }
221 }
222 }
223 }
224 // Extended validation check by given module.
225 $module_validation = inline_invoke('validate', $macro);
226 if (isset($module_validation) && !$module_validation) {
227 return FALSE;
228 }
229
230 return TRUE;
231 }
232
233 /**
234 * Generate HTML for an inline macro.
235 *
236 * @param $macro
237 * A validated inline macro.
238 * @return
239 * HTML presentation of the user supplied values.
240 *
241 * @todo Evaluate use of drupal_render().
242 */
243 function inline_macro_render($macro) {
244 // Merge in default values.
245 $args = inline_invoke('args', $macro);
246 foreach ($args as $arg => $info) {
247 if (empty($macro->params[$arg]) && isset($info['#default_value'])) {
248 // @todo Allow special defaults like 'current user' or 'current node'.
249 $macro->params[$arg] = $info['#default_value'];
250 }
251 }
252
253 // Allow module to prepare tag parameters.
254 inline_invoke('prepare', $macro);
255
256 // @todo Prepare operation may not be successful; final validation before
257 // rendering required.
258
259 // Generate a rendered representation for tag replacement.
260 $output = inline_invoke('render', $macro);
261
262 // If an error occured during rendering, we expect the result to be FALSE.
263 if (!is_bool($output)) {
264 return $output;
265 }
266 else {
267 return '';
268 }
269 }
270
271 /**
272 * Generate a string representation of an inline macro object.
273 *
274 * @todo That summary already sounds like a magic PHP object toString method. :)
275 */
276 function inline_macro_build($macro) {
277 $macro_params = array();
278
279 $macro_params[] = $macro->tag;
280 // Prepend internal inline macro id as first parameter to all macros.
281 if (isset($macro->iid)) {
282 $macro->params = array_merge(array('iid' => $macro->iid), $macro->params);
283 }
284 // @todo Support for #multiple values.
285 // @todo Escape |, [, ] chars in values.
286 foreach ($macro->params as $key => $value) {
287 $macro_params[] = $key . '=' . $value;
288 }
289 return '[' . implode('|', $macro_params) . ']';
290 }
291
292 /**
293 * @} End of "defgroup inline_macro".
294 */
295
296 /**
297 * @defgroup inline_crud Inline macro CRUD functions.
298 * @{
299 */
300
301 /**
302 * Load an inline macro object by id.
303 */
304 function inline_macro_load($iid) {
305 if ($macro = db_fetch_object(db_query("SELECT * FROM {inline} WHERE iid = %d", $iid))) {
306 $macro->params = unserialize($macro->params);
307 }
308 return $macro;
309 }
310
311 /**
312 * Store an inline macro object in the database.
313 */
314 function inline_macro_save(&$macro) {
315 inline_invoke('presave', $macro);
316 $update = array();
317 if (isset($macro->iid)) {
318 $update[] = 'iid';
319 // @todo Vanished macros also need to be marked as temporary.
320 $macro->status = 0;
321 }
322 else {
323 $macro->status = 1; // temporary
324 }
325 $macro->timestamp = time();
326 return drupal_write_record('inline', $macro, $update);
327 }
328
329 /**
330 * @} End of "defgroup inline_crud".
331 */
332
333 /**
334 * @defgroup inline_fapi Inline Form API handling
335 * @{
336 * Inline API solves a few major issues:
337 *
338 * - Provide context for input filters: Inline macros can be used anywhere, but
339 * hook_filter() does not provide any context about the text being processed.
340 * An possible inline macro parameter 'nid' may want to reference to the node
341 * that is currently edited.
342 * - Handling of self-references: A macro parameter 'nid' may want to reference
343 * to the node that is currently created. During creation, the node has no nid
344 * yet. $node->nid is first assigned by node_save() after the final form
345 * submission. But the user already entered inline macros that may have to
346 * reference to this nid (without the nid, the inline macro could not be
347 * rendered on other paths than node/%nid). Kind of a chicken-and-egg problem.
348 * - Handling inline macro parameter updates: Existing inline macros in contents
349 * may have to be updated at some time to accommodate for bugfixes or changing
350 * APIs of third-party modules.
351 * - Client-side editor integration: Client-side editors may need further
352 * information to render an inline macro properly, such as a width and height
353 * for images. They are unable to build something out of a [inline|iid=#] tag.
354 *
355 * Inline API therefore stores all inline macros upon form submission in the
356 * database, optionally flagged with a temporary status. Each time a form is
357 * displayed, all internal '[inline|iid=#]' macros are converted into their
358 * respective macro tags, for example '[upload|file=foo.jpg]'.
359 * When the form is submitted, all macros are converted back into internal
360 * inline macros.
361 *
362 * For temporary macros, the implementing module has to update the macro to flag
363 * it as permanent when the corresponding system object (f.e. a node) has been
364 * created. While doing so, implementations are also able to alter the stored
365 * inline macro tag in the database.
366 * All macros flagged as temporary will be purged during a subsequent cron run.
367 *
368 * This system allows inline macro implementations to inject certain system
369 * object ids into inline macro parameters when the object id is known. For
370 * example, the Upload module implementation shipped with Inline API (also)
371 * allows the user to refer to a file uploaded to the currently edited node by
372 * using its numeric (human) count (starting from 1), i.e. '[upload|file=2]'
373 * refers to the second uploaded file. The inline macro is automatically
374 * converted into '[upload|file=2nd-attachment.jpg]' upon preview or submit.
375 *
376 * @todo Old; anything worth to keep in the following?
377 * Inline needs to
378 * - insert the node id for new nodes in Inline tags that refer to nid=0, but
379 * must not create a new node revision if revisions are enabled.
380 * - clear the filter cache if nodes of a certain type are updated, f.e. if an
381 * image node is updated and the image is referenced in other nodes via
382 * img_assist.
383 * - allow hook_inline() implementations to react on nodeapi operations, f.e. to
384 * replace numeric file references of inline_upload tags (i.e. file=1) with
385 * named file references (i.e. file=foo.jpg) upon node preview and node save.
386 */
387
388 /**
389 * Implementation of hook_elements().
390 */
391 function inline_elements() {
392 $type['textarea'] = array('#process' => 'inline_process_textarea');
393 return $type;
394 }
395
396 /**
397 * Converts internal [inline] macros into their original macros.
398 *
399 * @see inline_form_submit()
400 * @see inline_nodeapi()
401 */
402 function inline_process_textarea(&$form, $edit, &$form_state, $complete_form) {
403 // @todo Only input format-enabled textareas may be processed.
404 /*
405 $parents = $form['#array_parents'];
406 array_pop($parents);
407 foreach ($parents as $key) {
408 $complete_form = $complete_form[$key];
409 }
410 if (isset($complete_form['format'])) {
411 $element = $complete_form['format'];
412 // Make sure we either match a input format selector or input format
413 // guidelines (displayed if user has access to one input format only).
414 if ((isset($element['#type']) && $element['#type'] == 'fieldset') || isset($element['format']['guidelines'])) {
415 }
416 }
417 */
418
419 // Store a reference to this textarea for our submit handler.
420 $form_state['inline_elements'][] = $form['#parents'];
421
422 // @todo Use Inline API for our own tags; only difference is that we probably
423 // want to keep any macros that cannot be "rendered" for any reason.
424 preg_match_all('/\[inline\|iid=(\d+)\]/', $form['#value'], $matches);
425 foreach ($matches[1] as $key => $iid) {
426 if ($macro = inline_macro_load($iid)) {
427 $value = str_replace($matches[0][$key], inline_macro_build($macro), $form['#value']);
428 $form['#value'] = $value;
429 }
430 }
431
432 return $form;
433 }
434
435 /**
436 * Implementation of hook_form_alter().
437 */
438 function inline_form_alter(&$form, &$form_state) {
439 $form['#submit'][] = 'inline_form_submit';
440 }
441
442 /**
443 * Form submit handler to save and convert inline macros into internal [inline] macros.
444 *
445 * This iterates over all form element references stored in
446 * $form_state['inline_elements'] and saves any new inline macros in the
447 * database with a "temporary" status.
448 *
449 * @see inline_process_textarea()
450 * @see inline_nodeapi()
451 */
452 function inline_form_submit($form, &$form_state) {
453 // When inline_process_textarea() did not store any references, then there is
454 // nothing to do.
455 if (!isset($form_state['inline_elements'])) {
456 return;
457 }
458 foreach ($form_state['inline_elements'] as $parents) {
459 // @todo FAPI provides a form_set_value(), but no form_get_value().
460 eval('$content = &$form_state[\'values\'][\'' . implode("']['", $parents) . "'];");
461
462 foreach (inline_get_macros($content) as $original => $macro) {
463 inline_macro_save($macro);
464 // @todo Do not use a global.
465 $GLOBALS['inline'][$macro->iid] = $macro;
466 $content = str_replace($original, '[inline|iid=' . $macro->iid . ']', $content);
467 }
468 }
469 }
470
471 /**
472 * Implementation of hook_nodeapi().
473 *
474 * Marks new inline macros as permanent and automatically assigns the parameter
475 * 'nid' for macros that need them (if not referencing another node).
476 */
477 function inline_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
478 switch ($op) {
479 case 'update':
480 // @todo Some inline tags are referencing to other system objects (f.e.
481 // nodes). If the referenced object is updated, we need to clear the
482 // filter cache for all contents referencing it. As long as there is
483 // no generic solution, we completely clear the filter cache.
484 cache_clear_all(NULL, 'cache_filter');
485 // Break intentionally left out.
486 case 'insert':
487 // Update references to 'current' system objects, e.g. the nid after
488 // inserting a new node.
489 // @todo Assign the currently processed field name to $macro['#field'] to
490 // allow certain hook_inline implementations to add/ensure this
491 // information in their macros.
492 // @todo Introduce a reference table?
493 if (empty($GLOBALS['inline'])) {
494 return;
495 }
496 foreach ($GLOBALS['inline'] as $iid => $macro) {
497 // inline_macro_save() invokes op 'presave'.
498 $macro->context['node'] = $node;
499 inline_macro_save($macro);
500 }
501 return;
502 }
503 }
504
505 /**
506 * Implementation of hook_cron().
507 */
508 function inline_cron() {
509 // Remove old temporary inline macros older than one day (86400 seconds).
510 db_query('DELETE FROM {inline} WHERE status = %d AND timestamp < %d', 1, time() - 86400);
511 }
512
513 /**
514 * @} End of "defgroup inline_fapi".
515 */
516
517 /**
518 * @defgroup inline_help Inline help
519 * @{
520 */
521
522 /**
523 * Implementation of hook_help().
524 */
525 function inline_help($path, $arg) {
526 switch ($path) {
527 case 'admin/help#inline':
528 return t('<p>Sometimes a user may want to add an image or a file inside the body of a node. This can be done with special tags that are replaced by links to the corresponding uploaded file. If the file is an image, it will be displayed inline, otherwise a link to the file will be inserted. To enable this feature and learn the proper syntax, visit the <a href="!filters">filters configuration screen</a>.</p>', array('!filters' => url('admin/filters')));
529
530 case 'filter#short-tip':
531 return t('You may add links to files uploaded with this node <a href="!explanation-url">using special tags</a>', array('!explanation-url' => url('filter/tips', array('fragment' => 'image'))));
532
533 case 'filter#long-tip':
534 return t('<p>You may link to files uploaded with the current node using special tags. The tags will be replaced by the corresponding files. Syntax: !syntax. Parameters: file represents the file uploaded with the node in which to link, assuming that the first uploaded file is labeled as 1 and so on. Title is optional and used instead of the filename.</p>
535 <p>If the file is an image, it will be displayed inline, otherwise a link to the file will be inserted.</p>', array('!syntax' => '<code>[inline_upload|file=&lt;FILE-ID&gt;|title=&lt;TITLE-TEXT&gt;]</code>'));
536 }
537 }
538
539 /**
540 * Implementation of hook_filter_tips().
541 *
542 * @todo Allow hook_inline() implementations to add (long) filter tips.
543 */
544 function inline_filter_tips($delta, $format, $long = FALSE) {
545 if ($long) {
546 return '<p><a id="filter-inline" name="filter-inline"></a>'. t('
547 You may link to files uploaded with the current node using special tags. The tags will be replaced by the corresponding files. For example:
548
549 Suppose you uploaded three files (in this order):
550 <ul>
551 <li>imag1.png (referred as file #1)
552 <li>file1.pdf (referred as file #2)
553 <li>imag2.png (referred as file #3)
554 </ul>
555
556 <pre>[inline_upload|file=1|title=test] or [inline_upload|file=imag1.png|title=test]</pre>
557 will be replaced by <em><code>&lt;img src=imag1.png alt=test&gt;</code></em>') .'</p>';
558 }
559 else {
560 return t('You may use <a href="!inline_help">[inline] tags</a> to display contents inline.', array('!inline_help' => url("filter/tips/$format", array('query' => 'filter-inline'))));
561 }
562 }
563
564 /**
565 * @} End of "defgroup inline_help".
566 */
567
568 /**
569 * Implementation of hook_wysiwyg_include_directory().
570 */
571 function inline_wysiwyg_include_directory($type) {
572 switch ($type) {
573 case 'plugins':
574 return $type;
575 }
576 }
577

  ViewVC Help
Powered by ViewVC 1.1.2