[SPIP] ~maj v3.0.14-->v3.0.17
[ptitvelo/web/www.git] / www / ecrire / public / assembler.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2014 *
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 if (!defined('_ECRIRE_INC_VERSION')) return;
14
15 if (!defined('_CONTEXTE_IGNORE_VARIABLES')) define('_CONTEXTE_IGNORE_VARIABLES', "/(^var_|^PHPSESSID$)/");
16 //
17 // calcule la page et les entetes
18 // determine le contexte donne par l'URL (en tenant compte des reecritures)
19 // grace a la fonction de passage d'URL a id (reciproque dans urls/*php)
20 //
21
22 // http://doc.spip.org/@assembler
23 function assembler($fond, $connect='') {
24
25 // flag_preserver est modifie ici, et utilise en globale
26 // use_cache sert a informer le bouton d'admin pr savoir s'il met un *
27 // contexte est utilise en globale dans le formulaire d'admin
28 global $flag_preserver, $use_cache, $contexte;
29
30 $contexte = calculer_contexte();
31 $page = array('contexte_implicite'=>calculer_contexte_implicite());
32 $page['contexte_implicite']['cache'] = $fond . preg_replace(',\.[a-zA-Z0-9]*$,', '', preg_replace('/[?].*$/', '', $GLOBALS['REQUEST_URI']));
33 // Cette fonction est utilisee deux fois
34 $cacher = charger_fonction('cacher', 'public', true);
35 // Les quatre derniers parametres sont modifies par la fonction:
36 // emplacement, validite, et, s'il est valide, contenu & age
37 if ($cacher)
38 $res = $cacher($GLOBALS['contexte'], $use_cache, $chemin_cache, $page, $lastmodified);
39 else
40 $use_cache = -1;
41 // Si un resultat est retourne, c'est un message d'impossibilite
42 if ($res) {return array('texte' => $res);}
43
44 if (!$chemin_cache || !$lastmodified) $lastmodified = time();
45
46 $headers_only = ($_SERVER['REQUEST_METHOD'] == 'HEAD');
47 $calculer_page = true;
48
49 // Pour les pages non-dynamiques (indiquees par #CACHE{duree,cache-client})
50 // une perennite valide a meme reponse qu'une requete HEAD (par defaut les
51 // pages sont dynamiques)
52 if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
53 AND (!defined('_VAR_MODE') OR !_VAR_MODE)
54 AND $chemin_cache
55 AND isset($page['entetes'])
56 AND isset($page['entetes']['Cache-Control'])
57 AND strstr($page['entetes']['Cache-Control'],'max-age=')
58 AND !strstr($_SERVER['SERVER_SOFTWARE'],'IIS/')
59 ) {
60 $since = preg_replace('/;.*/', '',
61 $_SERVER['HTTP_IF_MODIFIED_SINCE']);
62 $since = str_replace('GMT', '', $since);
63 if (trim($since) == gmdate("D, d M Y H:i:s", $lastmodified)) {
64 $page['status'] = 304;
65 $headers_only = true;
66 $calculer_page = false;
67 }
68 }
69
70 // Si requete HEAD ou Last-modified compatible, ignorer le texte
71 // et pas de content-type (pour contrer le bouton admin de inc-public)
72 if (!$calculer_page) {
73 $page['texte'] = "";
74 } else {
75 // si la page est prise dans le cache
76 if (!$use_cache) {
77 // Informer les boutons d'admin du contexte
78 // (fourni par urls_decoder_url ci-dessous lors de la mise en cache)
79 $contexte = $page['contexte'];
80
81 // vider les globales url propres qui ne doivent plus etre utilisees en cas
82 // d'inversion url => objet
83 // plus necessaire si on utilise bien la fonction urls_decoder_url
84 #unset($_SERVER['REDIRECT_url_propre']);
85 #unset($_ENV['url_propre']);
86 }
87 else {
88 // Compat ascendante :
89 // 1. $contexte est global
90 // (a evacuer car urls_decoder_url gere ce probleme ?)
91 // et calculer la page
92 if (!test_espace_prive()) {
93 include_spip('inc/urls');
94 list($fond,$contexte,$url_redirect) = urls_decoder_url(nettoyer_uri(),$fond,$contexte,true);
95 }
96 // squelette par defaut
97 if (!strlen($fond))
98 $fond = 'sommaire';
99
100 // produire la page : peut mettre a jour $lastmodified
101 $produire_page = charger_fonction('produire_page','public');
102 $page = $produire_page($fond, $contexte, $use_cache, $chemin_cache, NULL, $page, $lastmodified, $connect);
103 if ($page === '') {
104 $erreur = _T('info_erreur_squelette2',
105 array('fichier'=>spip_htmlspecialchars($fond).'.'._EXTENSION_SQUELETTES));
106 erreur_squelette($erreur);
107 // eviter des erreurs strictes ensuite sur $page['cle'] en PHP >= 5.4
108 $page = array('texte' => '', 'erreur' => $erreur);
109 }
110 }
111
112 if ($page AND $chemin_cache) $page['cache'] = $chemin_cache;
113
114 auto_content_type($page);
115
116 $flag_preserver |= headers_sent();
117
118 // Definir les entetes si ce n'est fait
119 if (!$flag_preserver) {
120 if ($GLOBALS['flag_ob']) {
121 // Si la page est vide, produire l'erreur 404 ou message d'erreur pour les inclusions
122 if (trim($page['texte']) === ''
123 AND _VAR_MODE != 'debug'
124 AND !isset($page['entetes']['Location']) // cette page realise une redirection, donc pas d'erreur
125 ) {
126 $contexte['fond_erreur'] = $fond;
127 $page = message_page_indisponible($page, $contexte);
128 }
129 // pas de cache client en mode 'observation'
130 if (defined('_VAR_MODE') AND _VAR_MODE) {
131 $page['entetes']["Cache-Control"]= "no-cache,must-revalidate";
132 $page['entetes']["Pragma"] = "no-cache";
133 }
134 }
135 }
136 }
137
138 // Entete Last-Modified:
139 // eviter d'etre incoherent en envoyant un lastmodified identique
140 // a celui qu'on a refuse d'honorer plus haut (cf. #655)
141 if ($lastmodified
142 AND !isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
143 AND !isset($page['entetes']["Last-Modified"]))
144 $page['entetes']["Last-Modified"]=gmdate("D, d M Y H:i:s", $lastmodified)." GMT";
145
146 // fermer la connexion apres les headers si requete HEAD
147 if ($headers_only)
148 $page['entetes']["Connection"] = "close";
149
150 return $page;
151 }
152
153 //
154 // Contexte : lors du calcul d'une page spip etablit le contexte a partir
155 // des variables $_GET et $_POST, purgees des fausses variables var_*
156 // Note : pour hacker le contexte depuis le fichier d'appel (page.php),
157 // il est recommande de modifier $_GET['toto'] (meme si la page est
158 // appelee avec la methode POST).
159 //
160 // http://doc.spip.org/@calculer_contexte
161 function calculer_contexte() {
162
163 $contexte = array();
164 foreach($_GET as $var => $val) {
165 if (!preg_match(_CONTEXTE_IGNORE_VARIABLES,$var))
166 $contexte[$var] = $val;
167 }
168 foreach($_POST as $var => $val) {
169 if (!preg_match(_CONTEXTE_IGNORE_VARIABLES,$var))
170 $contexte[$var] = $val;
171 }
172
173 return $contexte;
174 }
175
176 /**
177 * Calculer le contexte implicite, qui n'apparait pas dans le ENV d'un cache
178 * mais est utilise pour distinguer deux caches differents
179 *
180 * @staticvar string $notes
181 * @return array
182 */
183 function calculer_contexte_implicite(){
184 static $notes = null;
185 if (is_null($notes))
186 $notes = charger_fonction('notes','inc',true);
187 $contexte_implicite = array(
188 'squelettes' => $GLOBALS['dossier_squelettes'], // devrait etre 'chemin' => $GLOBALS['path_sig'], ?
189 'host' => $_SERVER['HTTP_HOST'],
190 'https' => (isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : ''),
191 'espace' => test_espace_prive(),
192 'marqueur' => (isset($GLOBALS['marqueur']) ? $GLOBALS['marqueur'] : ''),
193 'marqueur_skel' => (isset($GLOBALS['marqueur_skel']) ? $GLOBALS['marqueur_skel'] : ''),
194 'notes' => $notes?$notes('','contexter_cache'):'',
195 'spip_version_code' => $GLOBALS['spip_version_code'],
196 );
197 return $contexte_implicite;
198 }
199
200 //
201 // fonction pour compatibilite arriere, probablement superflue
202 //
203
204 // http://doc.spip.org/@auto_content_type
205 function auto_content_type($page)
206 {
207 global $flag_preserver;
208 if (!isset($flag_preserver))
209 {
210 $flag_preserver = ($page && preg_match("/header\s*\(\s*.content\-type:/isx",$page['texte']) || (isset($page['entetes']['Content-Type'])));
211 }
212 }
213
214 // http://doc.spip.org/@inclure_page
215 function inclure_page($fond, $contexte, $connect='') {
216 static $cacher, $produire_page;
217 global $lastmodified;
218
219 // enlever le fond de contexte inclus car sinon il prend la main
220 // dans les sous inclusions -> boucle infinie d'inclusion identique
221 // (cette precaution n'est probablement plus utile)
222 unset($contexte['fond']);
223 $page = array('contexte_implicite'=>calculer_contexte_implicite());
224 $page['contexte_implicite']['cache'] = $fond;
225 if (is_null($cacher))
226 $cacher = charger_fonction('cacher', 'public', true);
227 // Les quatre derniers parametres sont modifies par la fonction:
228 // emplacement, validite, et, s'il est valide, contenu & age
229 if ($cacher)
230 $res = $cacher($contexte, $use_cache, $chemin_cache, $page, $lastinclude);
231 else
232 $use_cache = -1;
233 // $res = message d'erreur : on sort de la
234 if ($res) {return array('texte' => $res);}
235
236 // Si use_cache ne vaut pas 0, la page doit etre calculee
237 // produire la page : peut mettre a jour $lastinclude
238 // le contexte_cache envoye a cacher() a ete conserve et est passe a produire
239 if ($use_cache) {
240 if (is_null($produire_page))
241 $produire_page = charger_fonction('produire_page','public');
242 $page = $produire_page($fond, $contexte, $use_cache, $chemin_cache, $contexte, $page, $lastinclude, $connect);
243 }
244 // dans tous les cas, mettre a jour $lastmodified
245 $lastmodified = max($lastmodified, $lastinclude);
246
247 return $page;
248 }
249
250 /**
251 * Produire la page et la mettre en cache
252 * lorsque c'est necessaire
253 *
254 * @param string $fond
255 * @param array $contexte
256 * @param int $use_cache
257 * @param string $chemin_cache
258 * @param array $contexte_cache
259 * @param array $page
260 * @param int $lastinclude
261 * @param string $connect
262 * @return array
263 */
264 function public_produire_page_dist($fond, $contexte, $use_cache, $chemin_cache, $contexte_cache, &$page, &$lastinclude, $connect=''){
265 static $parametrer,$cacher;
266 if (!$parametrer)
267 $parametrer = charger_fonction('parametrer', 'public');
268 $page = $parametrer($fond, $contexte, $chemin_cache, $connect);
269 // et on l'enregistre sur le disque
270 if ($chemin_cache
271 AND $use_cache>-1
272 AND is_array($page)
273 AND count($page)
274 AND $page['entetes']['X-Spip-Cache'] > 0){
275 if (is_null($cacher))
276 $cacher = charger_fonction('cacher', 'public', true);
277 $lastinclude = time();
278 if ($cacher)
279 $cacher($contexte_cache, $use_cache, $chemin_cache, $page, $lastinclude);
280 else
281 $use_cache = -1;
282 }
283 return $page;
284 }
285
286
287 // Fonction inseree par le compilateur dans le code compile.
288 // Elle recoit un contexte pour inclure un squelette,
289 // et les valeurs du contexte de compil prepare par memoriser_contexte_compil
290 // elle-meme appelee par calculer_balise_dynamique dans references.php:
291 // 0: sourcefile
292 // 1: codefile
293 // 2: id_boucle
294 // 3: ligne
295 // 4: langue
296
297 function inserer_balise_dynamique($contexte_exec, $contexte_compil)
298 {
299 if (!is_array($contexte_exec))
300 echo $contexte_exec; // message d'erreur etc
301 else {
302 inclure_balise_dynamique($contexte_exec, true, $contexte_compil);
303 }
304 }
305
306 // Attention, un appel explicite a cette fonction suppose certains include
307 // $echo = faut-il faire echo ou return
308
309 // http://doc.spip.org/@inclure_balise_dynamique
310 function inclure_balise_dynamique($texte, $echo=true, $contexte_compil=array())
311 {
312 if (is_array($texte)) {
313
314 list($fond, $delainc, $contexte_inclus) = $texte;
315
316 // delais a l'ancienne, c'est pratiquement mort
317 $d = isset($GLOBALS['delais']) ? $GLOBALS['delais'] : NULL;
318 $GLOBALS['delais'] = $delainc;
319
320 $page = recuperer_fond($fond,$contexte_inclus,array('trim'=>false, 'raw' => true, 'compil' => $contexte_compil));
321
322 $texte = $page['texte'];
323
324 $GLOBALS['delais'] = $d;
325 // Faire remonter les entetes
326 if (is_array($page['entetes'])) {
327 // mais pas toutes
328 unset($page['entetes']['X-Spip-Cache']);
329 unset($page['entetes']['Content-Type']);
330 if (isset($GLOBALS['page']) AND is_array($GLOBALS['page'])) {
331 if (!is_array($GLOBALS['page']['entetes']))
332 $GLOBALS['page']['entetes'] = array();
333 $GLOBALS['page']['entetes'] =
334 array_merge($GLOBALS['page']['entetes'],$page['entetes']);
335 }
336 }
337 // _pipelines au pluriel array('nom_pipeline' => $args...) avec une syntaxe permettant plusieurs pipelines
338 if (isset($page['contexte']['_pipelines'])
339 AND is_array($page['contexte']['_pipelines'])
340 AND count($page['contexte']['_pipelines'])) {
341 foreach($page['contexte']['_pipelines'] as $pipe=>$args){
342 $args['contexte'] = $page['contexte'];
343 unset($args['contexte']['_pipelines']); // par precaution, meme si le risque de boucle infinie est a priori nul
344 $texte = pipeline(
345 $pipe,
346 array(
347 'data'=>$texte,
348 'args'=>$args
349 ),
350 false
351 );
352 }
353 }
354 }
355
356 if (defined('_VAR_MODE') AND _VAR_MODE == 'debug') {
357 // compatibilite : avant on donnait le numero de ligne ou rien.
358 $ligne = intval(isset($contexte_compil[3]) ? $contexte_compil[3] : $contexte_compil);
359 $GLOBALS['debug_objets']['resultat'][$ligne] = $texte;
360 }
361 if ($echo)
362 echo $texte;
363 else
364 return $texte;
365
366 }
367
368 // http://doc.spip.org/@message_page_indisponible
369 function message_page_indisponible ($page, $contexte) {
370 static $deja = false;
371 if ($deja) return "erreur";
372 $codes = array(
373 '404' => '404 Not Found',
374 '503' => '503 Service Unavailable',
375 );
376
377 $contexte['status'] = ($page !== false) ? '404' : '503';
378 $contexte['code'] = $codes[$contexte['status']];
379 $contexte['fond'] = '404'; // gere les 2 erreurs
380 if (!isset($contexte['lang']))
381 $contexte['lang'] = $GLOBALS['spip_lang'];
382
383 $deja = true;
384 // passer aux plugins qui peuvent decider d'une page d'erreur plus pertinent
385 // ex restriction d'acces => 401
386 $contexte = pipeline('page_indisponible',$contexte);
387
388 // produire la page d'erreur
389 $page = inclure_page($contexte['fond'], $contexte);
390 if (!$page)
391 $page = inclure_page('404', $contexte);
392 $page['status'] = $contexte['status'];
393
394 return $page;
395 }
396
397 // temporairement ici : a mettre dans le futur inc/modeles
398 // creer_contexte_de_modele('left', 'autostart=true', ...) renvoie un array()
399 // http://doc.spip.org/@creer_contexte_de_modele
400 function creer_contexte_de_modele($args) {
401 $contexte = array();
402 foreach ($args as $var=>$val) {
403 if (is_int($var)){ // argument pas formate
404 if (in_array($val, array('left', 'right', 'center'))) {
405 $var = 'align';
406 $contexte[$var] = $val;
407 } else {
408 $args = explode('=', $val);
409 if (count($args)>=2) // Flashvars=arg1=machin&arg2=truc genere plus de deux args
410 $contexte[trim($args[0])] = substr($val,strlen($args[0])+1);
411 else // notation abregee
412 $contexte[trim($val)] = trim($val);
413 }
414 }
415 else
416 $contexte[$var] = $val;
417 }
418
419 return $contexte;
420 }
421
422 // Calcule le modele et retourne la mini-page ainsi calculee
423 // http://doc.spip.org/@inclure_modele
424 function inclure_modele($type, $id, $params, $lien, $connect='', $env=array()) {
425
426 static $compteur;
427 if (++$compteur>10) return ''; # ne pas boucler indefiniment
428
429 $type = strtolower($type);
430
431 $fond = $class = '';
432
433 $params = array_filter(explode('|', $params));
434 if ($params) {
435 list(,$soustype) = each($params);
436 $soustype = strtolower(trim($soustype));
437 if (in_array($soustype,
438 array('left', 'right', 'center', 'ajax'))) {
439 list(,$soustype) = each($params);
440 $soustype = strtolower($soustype);
441 }
442
443 if (preg_match(',^[a-z0-9_]+$,', $soustype)) {
444 if (!trouve_modele($fond = ($type.'_'.$soustype))) {
445 $fond = '';
446 $class = $soustype;
447 }
448 // enlever le sous type des params
449 $params = array_diff($params,array($soustype));
450 }
451 }
452
453 // Si ca marche pas en precisant le sous-type, prendre le type
454 if (!$fond AND !trouve_modele($fond = $type)){
455 spip_log("Modele $type introuvable",_LOG_INFO_IMPORTANTE);
456 return false;
457 }
458 $fond = 'modeles/'.$fond;
459 // Creer le contexte
460 $contexte = $env;
461 $contexte['dir_racine'] = _DIR_RACINE; # eviter de mixer un cache racine et un cache ecrire (meme si pour l'instant les modeles ne sont pas caches, le resultat etant different il faut que le contexte en tienne compte
462
463 // Le numero du modele est mis dans l'environnement
464 // d'une part sous l'identifiant "id"
465 // et d'autre part sous l'identifiant de la cle primaire
466 // par la fonction id_table_objet,
467 // (<article1> =>> article =>> id_article =>> id_article=1)
468 $_id = id_table_objet($type);
469 $contexte['id'] = $contexte[$_id] = $id;
470
471 if (isset($class))
472 $contexte['class'] = $class;
473
474 // Si un lien a ete passe en parametre, ex: [<modele1>->url]
475 if ($lien) {
476 # un eventuel guillemet (") sera reechappe par #ENV
477 $contexte['lien'] = str_replace("&quot;",'"', $lien['href']);
478 $contexte['lien_class'] = $lien['class'];
479 $contexte['lien_mime'] = $lien['mime'];
480 }
481
482 // Traiter les parametres
483 // par exemple : <img1|center>, <emb12|autostart=true> ou <doc1|lang=en>
484 $arg_list = creer_contexte_de_modele($params);
485 $contexte['args'] = $arg_list; // on passe la liste des arguments du modeles dans une variable args
486 $contexte = array_merge($contexte,$arg_list);
487
488
489 // Appliquer le modele avec le contexte
490 $retour = recuperer_fond($fond, $contexte, array(), $connect);
491
492
493 // Regarder si le modele tient compte des liens (il *doit* alors indiquer
494 // spip_lien_ok dans les classes de son conteneur de premier niveau ;
495 // sinon, s'il y a un lien, on l'ajoute classiquement
496 if (strstr(' ' . ($classes = extraire_attribut($retour, 'class')).' ',
497 'spip_lien_ok')) {
498 $retour = inserer_attribut($retour, 'class',
499 trim(str_replace(' spip_lien_ok ', ' ', " $classes ")));
500 } else if ($lien)
501 $retour = "<a href='".$lien['href']."' class='".$lien['class']."'>".$retour."</a>";
502
503 $compteur--;
504
505 return (isset($arg_list['ajax'])AND $arg_list['ajax']=='ajax')
506 ? encoder_contexte_ajax($contexte,'',$retour)
507 : $retour;
508 }
509
510 // Un inclure_page qui marche aussi pour l'espace prive
511 // fonction interne a spip, ne pas appeler directement
512 // pour recuperer $page complet, utiliser:
513 // recuperer_fond($fond,$contexte,array('raw'=>true))
514 // http://doc.spip.org/@evaluer_fond
515 function evaluer_fond ($fond, $contexte=array(), $connect=null) {
516
517 $page = inclure_page($fond, $contexte, $connect);
518
519 if (!$page) return $page;
520 // eval $page et affecte $res
521 include _ROOT_RESTREINT."public/evaluer_page.php";
522
523 // Lever un drapeau (global) si le fond utilise #SESSION
524 // a destination de public/parametrer
525 // pour remonter vers les inclusions appelantes
526 // il faut bien lever ce drapeau apres avoir evalue le fond
527 // pour ne pas faire descendre le flag vers les inclusions appelees
528 if (isset($page['invalideurs'])
529 AND isset($page['invalideurs']['session']))
530 $GLOBALS['cache_utilise_session'] = $page['invalideurs']['session'];
531
532 return $page;
533 }
534
535
536 // http://doc.spip.org/@page_base_href
537 function page_base_href(&$texte){
538 static $set_html_base = null;
539 if (is_null($set_html_base)){
540 if (!defined('_SET_HTML_BASE'))
541 // si la profondeur est superieure a 1
542 // est que ce n'est pas une url page ni une url action
543 // activer par defaut
544 $set_html_base = ((
545 $GLOBALS['profondeur_url'] >= (_DIR_RESTREINT?1:2)
546 AND _request(_SPIP_PAGE) !== 'login'
547 AND !_request('action'))?true:false);
548 else
549 $set_html_base = _SET_HTML_BASE;
550 }
551
552 if ($set_html_base
553 AND isset($GLOBALS['html']) AND $GLOBALS['html']
554 AND $GLOBALS['profondeur_url']>0
555 AND ($poshead = strpos($texte,'</head>'))!==FALSE){
556 $head = substr($texte,0,$poshead);
557 $insert = false;
558 if (strpos($head, '<base')===false)
559 $insert = true;
560 else {
561 // si aucun <base ...> n'a de href c'est bon quand meme !
562 $insert = true;
563 include_spip('inc/filtres');
564 $bases = extraire_balises($head,'base');
565 foreach ($bases as $base)
566 if (extraire_attribut($base,'href'))
567 $insert = false;
568 }
569 if ($insert) {
570 include_spip('inc/filtres_mini');
571 // ajouter un base qui reglera tous les liens relatifs
572 $base = url_absolue('./');
573 $bbase = "\n<base href=\"$base\" />";
574 if (($pos = strpos($head, '<head>')) !== false)
575 $head = substr_replace($head, $bbase, $pos+6, 0);
576 elseif(preg_match(",<head[^>]*>,i",$head,$r)){
577 $head = str_replace($r[0], $r[0].$bbase, $head);
578 }
579 $texte = $head . substr($texte,$poshead);
580 // gerer les ancres
581 $base = $_SERVER['REQUEST_URI'];
582 if (strpos($texte,"href='#")!==false)
583 $texte = str_replace("href='#","href='$base#",$texte);
584 if (strpos($texte, "href=\"#")!==false)
585 $texte = str_replace("href=\"#","href=\"$base#",$texte);
586 }
587 }
588 }
589
590
591 // Envoyer les entetes, en retenant ceux qui sont a usage interne
592 // et demarrent par X-Spip-...
593 // http://doc.spip.org/@envoyer_entetes
594 function envoyer_entetes($entetes) {
595 foreach ($entetes as $k => $v)
596 # if (strncmp($k, 'X-Spip-', 7))
597 @header(strlen($v)?"$k: $v":$k);
598 }
599
600 ?>