1 | <?php |
---|
2 | |
---|
3 | /***************************************************************************\ |
---|
4 | * SPIP, Systeme de publication pour l'internet * |
---|
5 | * * |
---|
6 | * Copyright (c) 2001-2013 * |
---|
7 | * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * |
---|
8 | * * |
---|
9 | * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * |
---|
10 | * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * |
---|
11 | \***************************************************************************/ |
---|
12 | |
---|
13 | /** |
---|
14 | * Gestion de syndication (RSS,...) |
---|
15 | * |
---|
16 | * @package SPIP\Sites\Syndication |
---|
17 | **/ |
---|
18 | |
---|
19 | if (!defined("_ECRIRE_INC_VERSION")) return; |
---|
20 | |
---|
21 | // ATTENTION |
---|
22 | // Cette inclusion charge executer_une_syndication pour compatibilite, |
---|
23 | // mais cette fonction ne doit plus etre invoquee directement: |
---|
24 | // il faut passer par cron() pour avoir un verrou portable |
---|
25 | // Voir un exemple dans action/editer/site |
---|
26 | include_spip('genie/syndic'); |
---|
27 | |
---|
28 | |
---|
29 | /** |
---|
30 | * Analyse un texte de backend |
---|
31 | * |
---|
32 | * @param string $rss |
---|
33 | * Texte du fichier de backend |
---|
34 | * @return array|string |
---|
35 | * - array : tableau des items lus, |
---|
36 | * - string : texte d'erreur |
---|
37 | **/ |
---|
38 | function analyser_backend($rss, $url_syndic='') { |
---|
39 | include_spip('inc/texte'); # pour couper() |
---|
40 | |
---|
41 | $rss = pipeline('pre_syndication', $rss); |
---|
42 | |
---|
43 | if (!defined('_SYNDICATION_DEREFERENCER_URL')) { |
---|
44 | /** si true, les URLs de type feedburner sont déréférencées */ |
---|
45 | define('_SYNDICATION_DEREFERENCER_URL', false); |
---|
46 | } |
---|
47 | |
---|
48 | // Echapper les CDATA |
---|
49 | cdata_echappe($rss, $echappe_cdata); |
---|
50 | |
---|
51 | // supprimer les commentaires |
---|
52 | $rss = preg_replace(',<!--.*-->,Ums', '', $rss); |
---|
53 | |
---|
54 | // simplifier le backend, en supprimant les espaces de nommage type "dc:" |
---|
55 | $rss = preg_replace(',<(/?)(dc):,i', '<\1', $rss); |
---|
56 | |
---|
57 | // chercher auteur/lang dans le fil au cas ou les items n'en auraient pas |
---|
58 | list($header) = preg_split(',<(item|entry)\b,', $rss, 2); |
---|
59 | if (preg_match_all( |
---|
60 | ',<(author|creator)\b(.*)</\1>,Uims', |
---|
61 | $header, $regs, PREG_SET_ORDER)) { |
---|
62 | $les_auteurs_du_site = array(); |
---|
63 | foreach ($regs as $reg) { |
---|
64 | $nom = $reg[2]; |
---|
65 | if (preg_match(',<name>(.*)</name>,Uims', $nom, $reg)) |
---|
66 | $nom = $reg[1]; |
---|
67 | $les_auteurs_du_site[] = trim(textebrut(filtrer_entites($nom))); |
---|
68 | } |
---|
69 | $les_auteurs_du_site = join(', ', array_unique($les_auteurs_du_site)); |
---|
70 | } else { |
---|
71 | $les_auteurs_du_site = ''; |
---|
72 | } |
---|
73 | |
---|
74 | $langue_du_site = ''; |
---|
75 | |
---|
76 | if ((preg_match(',<([^>]*xml:)?lang(uage)?'.'>([^<>]+)<,i', |
---|
77 | $header, $match) AND $l = $match[3]) |
---|
78 | OR ($l = extraire_attribut(extraire_balise($header, 'feed'), 'xml:lang')) |
---|
79 | ) { |
---|
80 | $langue_du_site = $l; |
---|
81 | } |
---|
82 | // atom |
---|
83 | elseif (preg_match(',<feed\s[^>]*xml:lang=[\'"]([^<>\'"]+)[\'"],i', $header, $match)) { |
---|
84 | $langue_du_site = $match[1]; |
---|
85 | } |
---|
86 | |
---|
87 | // Recuperer les blocs item et entry |
---|
88 | $items = array_merge(extraire_balises($rss, 'item'), extraire_balises($rss, 'entry')); |
---|
89 | |
---|
90 | |
---|
91 | // |
---|
92 | // Analyser chaque <item>...</item> du backend et le transformer en tableau |
---|
93 | // |
---|
94 | |
---|
95 | if (!count($items)) return _T('sites:avis_echec_syndication_01'); |
---|
96 | |
---|
97 | foreach ($items as $item) { |
---|
98 | $data = array(); |
---|
99 | |
---|
100 | // URL (semi-obligatoire, sert de cle) |
---|
101 | |
---|
102 | // guid n'est un URL que si marque de <guid ispermalink="true"> ; |
---|
103 | // attention la valeur par defaut est 'true' ce qui oblige a quelque |
---|
104 | // gymnastique |
---|
105 | if (preg_match(',<guid.*>[[:space:]]*(https?:[^<]*)</guid>,Uims', |
---|
106 | $item, $regs) AND preg_match(',^(true|1)?$,i', |
---|
107 | extraire_attribut($regs[0], 'ispermalink'))) |
---|
108 | $data['url'] = $regs[1]; |
---|
109 | // contourner les redirections feedburner |
---|
110 | else if (_SYNDICATION_DEREFERENCER_URL |
---|
111 | AND preg_match(',<feedburner:origLink>(.*)<,Uims', |
---|
112 | $item, $regs)) |
---|
113 | $data['url'] = $regs[1]; |
---|
114 | // <link>, plus classique |
---|
115 | else if (preg_match( |
---|
116 | ',<link[^>]*[[:space:]]rel=["\']?alternate[^>]*>(.*)</link>,Uims', |
---|
117 | $item, $regs)) |
---|
118 | $data['url'] = $regs[1]; |
---|
119 | else if (preg_match(',<link[^>]*[[:space:]]rel=.alternate[^>]*>,Uims', |
---|
120 | $item, $regs)) |
---|
121 | $data['url'] = extraire_attribut($regs[0], 'href'); |
---|
122 | else if (preg_match(',<link[^>]*>(.*)</link>,Uims', $item, $regs)) |
---|
123 | $data['url'] = $regs[1]; |
---|
124 | else if (preg_match(',<link[^>]*>,Uims', $item, $regs)) |
---|
125 | $data['url'] = extraire_attribut($regs[0], 'href'); |
---|
126 | |
---|
127 | // Aucun link ni guid, mais une enclosure |
---|
128 | else if (preg_match(',<enclosure[^>]*>,ims', $item, $regs) |
---|
129 | AND $url = extraire_attribut($regs[0], 'url')) |
---|
130 | $data['url'] = $url; |
---|
131 | |
---|
132 | // pas d'url, c'est genre un compteur... |
---|
133 | else |
---|
134 | $data['url'] = ''; |
---|
135 | |
---|
136 | // Titre (semi-obligatoire) |
---|
137 | if (preg_match(",<title[^>]*>(.*?)</title>,ims",$item,$match)) |
---|
138 | $data['titre'] = $match[1]; |
---|
139 | else if (preg_match(',<link[[:space:]][^>]*>,Uims',$item,$mat) |
---|
140 | AND $title = extraire_attribut($mat[0], 'title')) |
---|
141 | $data['titre'] = $title; |
---|
142 | if (!strlen($data['titre'] = trim($data['titre']))) |
---|
143 | $data['titre'] = _T('ecrire:info_sans_titre'); |
---|
144 | |
---|
145 | // Date |
---|
146 | $la_date = ''; |
---|
147 | if (preg_match(',<(published|modified|issued)>([^<]*)<,Uims', |
---|
148 | $item,$match)) { |
---|
149 | cdata_echappe_retour($match[2], $echappe_cdata); |
---|
150 | $la_date = my_strtotime($match[2]); |
---|
151 | } |
---|
152 | if (!$la_date AND |
---|
153 | preg_match(',<(pubdate)>([^<]*)<,Uims',$item, $match)) { |
---|
154 | cdata_echappe_retour($match[2], $echappe_cdata); |
---|
155 | $la_date = my_strtotime($match[2]); |
---|
156 | } |
---|
157 | if (!$la_date AND |
---|
158 | preg_match(',<([a-z]+:date)>([^<]*)<,Uims',$item,$match)) { |
---|
159 | cdata_echappe_retour($match[2], $echappe_cdata); |
---|
160 | $la_date = my_strtotime($match[2], $echappe_cdata); |
---|
161 | } |
---|
162 | if (!$la_date AND |
---|
163 | preg_match(',<date>([^<]*)<,Uims',$item,$match)) { |
---|
164 | cdata_echappe_retour($match[1], $echappe_cdata); |
---|
165 | $la_date = my_strtotime($match[1]); |
---|
166 | } |
---|
167 | |
---|
168 | // controle de validite de la date |
---|
169 | // pour eviter qu'un backend errone passe toujours devant |
---|
170 | // (note: ca pourrait etre defini site par site, mais ca risque d'etre |
---|
171 | // plus lourd que vraiment utile) |
---|
172 | if ($GLOBALS['controler_dates_rss']) { |
---|
173 | if ($la_date > time() + 48 * 3600) |
---|
174 | $la_date = time(); |
---|
175 | } |
---|
176 | |
---|
177 | if ($la_date) |
---|
178 | $data['date'] = $la_date; |
---|
179 | |
---|
180 | // Honorer le <lastbuilddate> en forcant la date |
---|
181 | if (preg_match(',<(lastbuilddate|updated|modified)>([^<>]+)</\1>,i', |
---|
182 | $item, $regs) |
---|
183 | AND $lastbuilddate = my_strtotime(trim($regs[2])) |
---|
184 | // pas dans le futur |
---|
185 | AND $lastbuilddate < time()) |
---|
186 | $data['lastbuilddate'] = $lastbuilddate; |
---|
187 | |
---|
188 | // Auteur(s) |
---|
189 | if (preg_match_all( |
---|
190 | ',<(author|creator)\b[^>]*>(.*)</\1>,Uims', |
---|
191 | $item, $regs, PREG_SET_ORDER)) { |
---|
192 | $auteurs = array(); |
---|
193 | foreach ($regs as $reg) { |
---|
194 | $nom = $reg[2]; |
---|
195 | if (preg_match(',<name\b[^>]*>(.*)</name>,Uims', $nom, $reg)) |
---|
196 | $nom = $reg[1]; |
---|
197 | // Cas particulier d'un auteur Flickr |
---|
198 | if (preg_match(',nobody@flickr.com \((.*)\),Uims', $nom, $reg)) |
---|
199 | $nom = $reg[1]; |
---|
200 | $auteurs[] = trim(textebrut(filtrer_entites($nom))); |
---|
201 | } |
---|
202 | $data['lesauteurs'] = join(', ', array_unique($auteurs)); |
---|
203 | } |
---|
204 | else |
---|
205 | $data['lesauteurs'] = $les_auteurs_du_site; |
---|
206 | |
---|
207 | // Description |
---|
208 | if (preg_match(',<(description|summary)\b.*' |
---|
209 | .'>(.*)</\1\b,Uims',$item,$match)) { |
---|
210 | $data['descriptif'] = trim($match[2]); |
---|
211 | } |
---|
212 | if (preg_match(',<(content)\b.*' |
---|
213 | .'>(.*)</\1\b,Uims',$item,$match)) { |
---|
214 | $data['content'] = trim($match[2]); |
---|
215 | } |
---|
216 | |
---|
217 | // lang |
---|
218 | if (preg_match(',<([^>]*xml:)?lang(uage)?'.'>([^<>]+)<,i', |
---|
219 | $item, $match)) |
---|
220 | $data['lang'] = trim($match[3]); |
---|
221 | else if ($lang = trim(extraire_attribut($item, 'xml:lang'))) |
---|
222 | $data['lang'] = $lang; |
---|
223 | else |
---|
224 | $data['lang'] = trim($langue_du_site); |
---|
225 | |
---|
226 | // source et url_source (pas trouve d'exemple en ligne !!) |
---|
227 | # <source url="http://www.truc.net/music/uatsap.mp3" length="19917" /> |
---|
228 | # <source url="http://www.truc.net/rss">Site source</source> |
---|
229 | if (preg_match(',(<source[^>]*>)(([^<>]+)</source>)?,i', |
---|
230 | $item, $match)) { |
---|
231 | $data['source'] = trim($match[3]); |
---|
232 | $data['url_source'] = str_replace('&', '&', |
---|
233 | trim(extraire_attribut($match[1], 'url'))); |
---|
234 | } |
---|
235 | |
---|
236 | // tags |
---|
237 | # a partir de "<dc:subject>", (del.icio.us) |
---|
238 | # ou <media:category> (flickr) |
---|
239 | # ou <itunes:category> (apple) |
---|
240 | # on cree nos tags microformat <a rel="directory" href="url">titre</a> |
---|
241 | # http://microformats.org/wiki/rel-directory-fr |
---|
242 | $tags = array(); |
---|
243 | if (preg_match_all( |
---|
244 | ',<(([a-z]+:)?(subject|category|directory|keywords?|tags?|type))[^>]*>' |
---|
245 | .'(.*?)</\1>,ims', |
---|
246 | $item, $matches, PREG_SET_ORDER)) |
---|
247 | $tags = ajouter_tags($matches, $item); # array() |
---|
248 | elseif (preg_match_all( |
---|
249 | ',<(([a-z]+:)?(subject|category|directory|keywords?|tags?|type))[^>]*/>' |
---|
250 | .',ims', |
---|
251 | $item, $matches, PREG_SET_ORDER)) |
---|
252 | $tags = ajouter_tags($matches, $item); # array() |
---|
253 | // Pieces jointes : |
---|
254 | // chercher <enclosure> au format RSS et les passer en microformat |
---|
255 | // ou des microformats relEnclosure, |
---|
256 | // ou encore les media:content |
---|
257 | if (!afficher_enclosures(join(', ', $tags))) { |
---|
258 | if (preg_match_all(',<enclosure[[:space:]][^<>]+>,i', |
---|
259 | $item, $matches, PREG_PATTERN_ORDER)) { |
---|
260 | $data['enclosures'] = join(', ', |
---|
261 | array_map('enclosure2microformat', $matches[0])); |
---|
262 | } |
---|
263 | else if ( |
---|
264 | preg_match_all(',<link\b[^<>]+rel=["\']?enclosure["\']?[^<>]+>,i', |
---|
265 | $item, $matches, PREG_PATTERN_ORDER)) { |
---|
266 | $data['enclosures'] = join(', ', |
---|
267 | array_map('enclosure2microformat', $matches[0])); |
---|
268 | } |
---|
269 | else if ( |
---|
270 | preg_match_all(',<media:content\b[^<>]+>,i', |
---|
271 | $item, $matches, PREG_PATTERN_ORDER)) { |
---|
272 | $data['enclosures'] = join(', ', |
---|
273 | array_map('enclosure2microformat', $matches[0])); |
---|
274 | } |
---|
275 | } |
---|
276 | $data['item'] = $item; |
---|
277 | |
---|
278 | // Nettoyer les donnees et remettre les CDATA en place |
---|
279 | cdata_echappe_retour($data, $echappe_cdata); |
---|
280 | cdata_echappe_retour($tags, $echappe_cdata); |
---|
281 | |
---|
282 | // passer l'url en absolue |
---|
283 | $data['url'] = url_absolue(filtrer_entites($data['url']), $url_syndic); |
---|
284 | |
---|
285 | // Trouver les microformats (ecrase les <category> et <dc:subject>) |
---|
286 | if (preg_match_all( |
---|
287 | ',<a[[:space:]]([^>]+[[:space:]])?rel=[^>]+>.*</a>,Uims', |
---|
288 | $data['item'], $regs, PREG_PATTERN_ORDER)) { |
---|
289 | $tags = $regs[0]; |
---|
290 | } |
---|
291 | // Cas particulier : tags Connotea sous la forme <a class="postedtag"> |
---|
292 | if (preg_match_all( |
---|
293 | ',<a[[:space:]][^>]+ class="postedtag"[^>]*>.*</a>,Uims', |
---|
294 | $data['item'], $regs, PREG_PATTERN_ORDER)) |
---|
295 | $tags = preg_replace(', class="postedtag",i', |
---|
296 | ' rel="tag"', $regs[0]); |
---|
297 | |
---|
298 | $data['tags'] = $tags; |
---|
299 | // enlever le html des titre pour etre homogene avec les autres objets spip |
---|
300 | $data['titre'] = textebrut($data['titre']); |
---|
301 | |
---|
302 | $articles[] = $data; |
---|
303 | } |
---|
304 | |
---|
305 | return $articles; |
---|
306 | } |
---|
307 | |
---|
308 | |
---|
309 | /** |
---|
310 | * Strtotime même avec le format W3C ! |
---|
311 | * |
---|
312 | * Car hélàs, strtotime ne le reconnait pas tout seul ! |
---|
313 | * @link http://www.w3.org/TR/NOTE-datetime Format datetime du W3C |
---|
314 | * |
---|
315 | * @param string $la_date |
---|
316 | * Date à parser |
---|
317 | * @return int |
---|
318 | * Timestamp |
---|
319 | **/ |
---|
320 | function my_strtotime($la_date) { |
---|
321 | |
---|
322 | // format complet |
---|
323 | if (preg_match( |
---|
324 | ',^(\d+-\d+-\d+[T ]\d+:\d+(:\d+)?)(\.\d+)?' |
---|
325 | .'(Z|([-+]\d{2}):\d+)?$,', |
---|
326 | $la_date, $match)) { |
---|
327 | $la_date = str_replace("T", " ", $match[1])." GMT"; |
---|
328 | return strtotime($la_date) - intval($match[5]) * 3600; |
---|
329 | } |
---|
330 | |
---|
331 | // YYYY |
---|
332 | if (preg_match(',^\d{4}$,', $la_date, $match)) |
---|
333 | return strtotime($match[0]."-01-01"); |
---|
334 | |
---|
335 | // YYYY-MM |
---|
336 | if (preg_match(',^\d{4}-\d{2}$,', $la_date, $match)) |
---|
337 | return strtotime($match[0]."-01"); |
---|
338 | |
---|
339 | // utiliser strtotime en dernier ressort |
---|
340 | $s = strtotime($la_date); |
---|
341 | if ($s > 0) |
---|
342 | return $s; |
---|
343 | |
---|
344 | // YYYY-MM-DD hh:mm:ss |
---|
345 | if (preg_match(',^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\b,', $la_date, $match)) |
---|
346 | return strtotime($match[0]); |
---|
347 | |
---|
348 | |
---|
349 | // erreur |
---|
350 | spip_log("Impossible de lire le format de date '$la_date'"); |
---|
351 | return false; |
---|
352 | } |
---|
353 | |
---|
354 | // A partir d'un <dc:subject> ou autre essayer de recuperer |
---|
355 | // le mot et son url ; on cree <a href="url" rel="tag">mot</a> |
---|
356 | // http://doc.spip.org/@creer_tag |
---|
357 | function creer_tag($mot,$type,$url) { |
---|
358 | if (!strlen($mot = trim($mot))) return ''; |
---|
359 | $mot = "<a rel=\"tag\">$mot</a>"; |
---|
360 | if ($url) |
---|
361 | $mot = inserer_attribut($mot, 'href', $url); |
---|
362 | if ($type) |
---|
363 | $mot = inserer_attribut($mot, 'rel', $type); |
---|
364 | return $mot; |
---|
365 | } |
---|
366 | |
---|
367 | |
---|
368 | // http://doc.spip.org/@ajouter_tags |
---|
369 | function ajouter_tags($matches, $item) { |
---|
370 | include_spip('inc/filtres'); |
---|
371 | $tags = array(); |
---|
372 | foreach ($matches as $match) { |
---|
373 | $type = ($match[3] == 'category' OR $match[3] == 'directory') |
---|
374 | ? 'directory':'tag'; |
---|
375 | $mot = supprimer_tags($match[0]); |
---|
376 | if (!strlen($mot) |
---|
377 | AND !strlen($mot = extraire_attribut($match[0], 'label'))) |
---|
378 | break; |
---|
379 | // rechercher un url |
---|
380 | if ($url = extraire_attribut($match[0], 'domain')) { |
---|
381 | // category@domain est la racine d'une url qui se prolonge |
---|
382 | // avec le contenu text du tag <category> ; mais dans SPIP < 2.0 |
---|
383 | // on donnait category@domain = #URL_RUBRIQUE, et |
---|
384 | // text = #TITRE_RUBRIQUE ; d'ou l'heuristique suivante sur le slash |
---|
385 | if (substr($url, -1) == '/') |
---|
386 | $url .= rawurlencode($mot); |
---|
387 | } |
---|
388 | else if ($url = extraire_attribut($match[0], 'resource') |
---|
389 | OR $url = extraire_attribut($match[0], 'url') |
---|
390 | ) |
---|
391 | {} |
---|
392 | |
---|
393 | ## cas particuliers |
---|
394 | else if (extraire_attribut($match[0], 'scheme') == 'urn:flickr:tags') { |
---|
395 | foreach(explode(' ', $mot) as $petit) |
---|
396 | if ($t = creer_tag($petit, $type, |
---|
397 | 'http://www.flickr.com/photos/tags/'.rawurlencode($petit).'/')) |
---|
398 | $tags[] = $t; |
---|
399 | $mot = ''; |
---|
400 | } |
---|
401 | else if ( |
---|
402 | // cas atom1, a faire apres flickr |
---|
403 | $term = extraire_attribut($match[0], 'term') |
---|
404 | ) { |
---|
405 | if ($scheme = extraire_attribut($match[0], 'scheme')) |
---|
406 | $url = suivre_lien($scheme,$term); |
---|
407 | else |
---|
408 | $url = $term; |
---|
409 | } |
---|
410 | else { |
---|
411 | # type delicious.com |
---|
412 | foreach(explode(' ', $mot) as $petit) |
---|
413 | if (preg_match(',<rdf\b[^>]*\bresource=["\']([^>]*/' |
---|
414 | .preg_quote(rawurlencode($petit),',').')["\'],i', |
---|
415 | $item, $m)) { |
---|
416 | $mot = ''; |
---|
417 | if ($t = creer_tag($petit, $type, $m[1])) |
---|
418 | $tags[] = $t; |
---|
419 | } |
---|
420 | } |
---|
421 | |
---|
422 | if ($t = creer_tag($mot, $type, $url)) |
---|
423 | $tags[] = $t; |
---|
424 | } |
---|
425 | return $tags; |
---|
426 | } |
---|
427 | |
---|
428 | |
---|
429 | // Lit contenu des blocs [[CDATA]] dans un flux |
---|
430 | // http://doc.spip.org/@cdata_echappe_retour |
---|
431 | function cdata_echappe(&$rss, &$echappe_cdata) { |
---|
432 | $echappe_cdata = array(); |
---|
433 | if (preg_match_all(',<!\[CDATA\[(.*)]]>,Uims', $rss, |
---|
434 | $regs, PREG_SET_ORDER)) { |
---|
435 | foreach ($regs as $n => $reg) { |
---|
436 | if (preg_match(',[<>],', $reg[1])) { |
---|
437 | $echappe_cdata[$n] = $reg[1]; |
---|
438 | $rss = str_replace($reg[0], "@@@SPIP_CDATA$n@@@", $rss); |
---|
439 | } else |
---|
440 | $rss = str_replace($reg[0], $reg[1], $rss); |
---|
441 | } |
---|
442 | } |
---|
443 | } |
---|
444 | |
---|
445 | // Retablit le contenu des blocs [[CDATA]] dans une chaine ou un tableau |
---|
446 | // http://doc.spip.org/@cdata_echappe_retour |
---|
447 | function cdata_echappe_retour(&$x, &$echappe_cdata) { |
---|
448 | if (is_string($x)) { |
---|
449 | if (strpos($x, '@@@SPIP_CDATA') !== false |
---|
450 | OR strpos($x, '<') !== false) { |
---|
451 | $x = filtrer_entites($x); |
---|
452 | foreach ($echappe_cdata as $n => $e) |
---|
453 | $x = str_replace("@@@SPIP_CDATA$n@@@", $e, $x); |
---|
454 | } |
---|
455 | } |
---|
456 | |
---|
457 | else if (is_array($x)) { |
---|
458 | foreach($x as $k => &$v) |
---|
459 | cdata_echappe_retour($v, $echappe_cdata); |
---|
460 | } |
---|
461 | } |
---|
462 | ?> |
---|