opentbs

Форк
0
/
tbs_class.php 
5597 строк · 167.6 Кб
1
<?php
2
/**
3
 *
4
 * TinyButStrong - Template Engine for Pro and Beginners
5
 *
6
 * @version 3.15.1 for PHP 5, 7, 8
7
 * @date    2023-05-15
8
 * @link    http://www.tinybutstrong.com Web site
9
 * @author  http://www.tinybutstrong.com/onlyyou.html
10
 * @license http://opensource.org/licenses/LGPL-3.0 LGPL-3.0
11
 *
12
 * This library is free software.
13
 * You can redistribute and modify it even for commercial usage,
14
 * but you must accept and respect the LPGL License version 3.
15
 */
16

17
// Check PHP version
18
if (version_compare(PHP_VERSION,'5.0')<0) echo '<br><b>TinyButStrong Error</b> (PHP Version Check) : Your PHP version is '.PHP_VERSION.' while TinyButStrong needs PHP version 5.0 or higher. You should try with TinyButStrong Edition for PHP 4.';
19

20
// Render flags
21
define('TBS_NOTHING', 0);
22
define('TBS_OUTPUT', 1);
23
define('TBS_EXIT', 2);
24

25
// Plug-ins actions
26
define('TBS_INSTALL', -1);
27
define('TBS_ISINSTALLED', -3);
28

29
// *********************************************
30

31
class clsTbsLocator {
32

33
	public $PosBeg = false;
34
	public $PosEnd = false;
35
	public $Enlarged = false;
36
	public $FullName = false;
37
	public $SubName = '';
38
	public $SubOk = false;
39
	public $SubLst = array();
40
	public $SubNbr = 0;
41
	public $PrmLst = array();
42
	public $PrmPos; // positions of the parameters, if asked
43
	public $PrmIfNbr = false;
44
	public $MagnetId = false;
45
	public $BlockFound = false;
46
	public $FirstMerge = true;
47
	public $ConvProtect = true;
48
	public $ConvStr = true;
49
	public $ConvMode = 1; // Normal
50
	public $ConvBr = true;
51
	
52
	// Compatibility with PHP 8.2
53
	public $Prop = array(); // dynamic properties, used by OpenTBS
54
	
55
	public $Ope;
56
	public $OpeEnd;
57
	public $PosNext;
58
	public $PrmIf;
59
	public $PrmThen;
60
	public $PrmThenVar;
61
	public $PrmIfVar;
62
	public $PrmElseVar;
63

64
	// other
65
	public $ConvEsc;
66
	public $ConvWS;
67
	public $ConvJS;
68
	public $ConvUrl;
69
	public $ConvUtf8;
70

71
	public $OnFrmInfo;
72
	public $OnFrmArg;
73
	
74
	public $OpeUtf8;
75
	public $OpeAct;
76
	public $OpePrm;
77
	public $OpeArg;
78
    
79
    public $OpeMOK;
80
    public $OpeMKO;
81
    public $MSave;
82

83
	// Sub-template
84
	public $SaveSrc;
85
	public $SaveMode;
86
	public $SaveVarRef;
87
	public $SaveRender;
88

89
	// Att
90
	public $AttForward;
91
	public $AttTagBeg;
92
	public $AttTagEnd;
93
	public $AttDelimChr;
94
	public $AttName;
95
	public $AttBeg;
96
	public $AttEnd;
97
	public $AttDelimCnt;
98
	public $AttValBeg;
99
	public $PosBeg0;
100
	public $PosEnd0;
101
	public $InsPos;
102
	public $InsLen;
103
	public $DelPos;
104
	public $DelLen;
105
	public $PosBeg2;
106
	public $PosEnd2;
107

108
	// blocks
109
	public $P1;
110
	public $FieldOutside;
111
	public $FOStop;
112
	public $BDefLst;
113
	public $NoData;
114
	public $Special;
115
	public $HeaderFound;
116
	public $FooterFound;
117
	public $SerialEmpty;
118
	public $GrpBreak;
119
	public $BoundFound;
120
	public $CheckNext;
121
	public $CheckPrev;
122
	public $WhenFound;
123
	public $WhenDefault;
124
    public $WhenDefaultBeforeNS;
125
	public $SectionNbr;
126
	public $SectionLst;
127
	public $PosDefBeg;
128
	public $RightLevel;
129
	public $BlockSrc;
130
	public $PosDefEnd;
131
	public $IsRecInfo;
132
	public $RecInfo;
133
	public $WhenSeveral;
134
	public $WhenNbr;
135
	public $WhenLst;
136
	public $FooterNbr;
137
	public $FooterDef;
138
	public $HeaderNbr;
139
	public $HeaderDef;
140
	public $ValPrev;
141
	public $BoundLst;
142
	public $BoundNb;
143
	public $BoundSingleNb;
144
	public $ValNext;
145

146
}
147

148
// *********************************************
149

150
class clsTbsDataSource {
151

152
public $Type = false;
153
public $SubType = 0;
154
public $SrcId = false;
155
public $Query = '';
156
public $RecSet = false;
157
public $RecNumInit = 0; // Used by ByPage plugin
158
public $RecSaving = false;
159
public $RecSaved = false;
160
public $RecBuffer = false;
161
public $TBS = false;
162
public $OnDataOk = false;
163
public $OnDataPrm = false;
164
public $OnDataPrmDone = array();
165
public $OnDataPi = false;
166

167
// Info relative to the current record :
168
public $CurrRec = false; // Used by ByPage plugin
169
public $RecKey = '';     // Used by ByPage plugin
170
public $RecNum = 0;      // Used by ByPage plugin
171

172
public $PrevRec = null;
173
public $NextRec = null;
174

175
public $PrevSave = false;
176
public $NextSave = false;
177

178
// Compatibility with PHP 8.2
179
public $Prop = array(); // Used by ByPage plugin
180
public $RecNbr;
181
public $RSIsFirst;
182
public $NumMin;
183
public $NumMax;
184
public $NumStep;
185
public $NumVal;
186
public $OnDataPrmRef;
187
public $OnDataArgs;
188

189
public function DataAlert($Msg) {
190
	if (is_array($this->TBS->_CurrBlock)) {
191
		return $this->TBS->meth_Misc_Alert('when merging block "'.implode(',',$this->TBS->_CurrBlock).'"',$Msg);
192
	} else {
193
		return $this->TBS->meth_Misc_Alert('when merging block '.$this->TBS->_ChrOpen.$this->TBS->_CurrBlock.$this->TBS->_ChrClose,$Msg);
194
	}
195
}
196

197
public function DataPrepare(&$SrcId,&$TBS) {
198

199
	$this->SrcId = &$SrcId;
200
	$this->TBS = &$TBS;
201
	$FctInfo = false;
202
	$FctObj = false;
203
	
204
	if (is_array($SrcId)) {
205
		$this->Type = 0;
206
	} elseif (is_resource($SrcId)) {
207

208
		$Key = get_resource_type($SrcId);
209
		switch ($Key) {
210
		case 'mysql link'            : $this->Type = 6; break;
211
		case 'mysql link persistent' : $this->Type = 6; break;
212
		case 'mysql result'          : $this->Type = 6; $this->SubType = 1; break;
213
		case 'pgsql link'            : $this->Type = 7; break;
214
		case 'pgsql link persistent' : $this->Type = 7; break;
215
		case 'pgsql result'          : $this->Type = 7; $this->SubType = 1; break;
216
		case 'sqlite database'       : $this->Type = 8; break;
217
		case 'sqlite database (persistent)'	: $this->Type = 8; break;
218
		case 'sqlite result'         : $this->Type = 8; $this->SubType = 1; break;
219
		default :
220
			$FctInfo = $Key;
221
			$FctCat = 'r';
222
		}
223

224
	} elseif (is_string($SrcId)) {
225

226
		switch (strtolower($SrcId)) {
227
		case 'array' : $this->Type = 0; $this->SubType = 1; break;
228
		case 'clear' : $this->Type = 0; $this->SubType = 3; break;
229
		case 'mysql' : $this->Type = 6; $this->SubType = 2; break;
230
		case 'text'  : $this->Type = 2; break;
231
		case 'num'   : $this->Type = 1; break;
232
		default :
233
			$FctInfo = $SrcId;
234
			$FctCat = 'k';
235
		}
236

237
	} elseif ($SrcId instanceof Iterator) {
238
		$this->Type = 9; $this->SubType = 1;
239
	} elseif ($SrcId instanceof ArrayObject) {
240
		$this->Type = 9; $this->SubType = 2;
241
	} elseif ($SrcId instanceof IteratorAggregate) {
242
		$this->Type = 9; $this->SubType = 3;
243
	} elseif ($SrcId instanceof MySQLi) {
244
		$this->Type = 10;
245
	} elseif ($SrcId instanceof PDO) {
246
		$this->Type = 11;
247
	} elseif ($SrcId instanceof Zend_Db_Adapter_Abstract) {
248
		$this->Type = 12;
249
	} elseif ($SrcId instanceof SQLite3) {
250
		$this->Type = 13; $this->SubType = 1;
251
	} elseif ($SrcId instanceof SQLite3Stmt) {
252
		$this->Type = 13; $this->SubType = 2;
253
	} elseif ($SrcId instanceof SQLite3Result) {
254
		$this->Type = 13; $this->SubType = 3;
255
	} elseif (is_a($SrcId, 'Doctrine\DBAL\Connection')) {
256
		$this->Type = 14;
257
	} elseif (is_object($SrcId)) {
258
		$FctInfo = get_class($SrcId);
259
		$FctCat = 'o';
260
		$FctObj = &$SrcId;
261
		$this->SrcId = &$SrcId;
262
	} elseif ($SrcId===false) {
263
		$this->DataAlert('the specified source is set to FALSE. Maybe your connection has failed.');
264
	} else {
265
		$this->DataAlert('unsupported variable type : \''.gettype($SrcId).'\'.');
266
	}
267

268
	if ($FctInfo!==false) {
269
		$ErrMsg = false;
270
		if ($TBS->meth_Misc_UserFctCheck($FctInfo,$FctCat,$FctObj,$ErrMsg,false)) {
271
			$this->Type = $FctInfo['type'];
272
			if ($this->Type!==5) {
273
				if ($this->Type===4) {
274
					$this->FctPrm = array(false,0);
275
					$this->SrcId = &$FctInfo['open'][0];
276
				}
277
				$this->FctOpen  = &$FctInfo['open'];
278
				$this->FctFetch = &$FctInfo['fetch'];
279
				$this->FctClose = &$FctInfo['close'];
280
			}
281
		} else {
282
			$this->Type = $this->DataAlert($ErrMsg);
283
		}
284
	}
285

286
	return ($this->Type!==false);
287

288
}
289

290
public function DataOpen(&$Query,$QryPrms=false) {
291

292
	// Init values
293
	unset($this->CurrRec);
294
	$this->CurrRec = true;
295
	
296
	if ($this->RecSaved) {
297
		$this->RSIsFirst = true;
298
		unset($this->RecKey); $this->RecKey = '';
299
		$this->RecNum = $this->RecNumInit;
300
		if ($this->OnDataOk) $this->OnDataArgs[1] = &$this->CurrRec;
301
		return true;
302
	}
303
	
304
	unset($this->RecSet);
305
	$this->RecSet = false;
306
	$this->RecNumInit = 0;
307
	$this->RecNum = 0;
308

309
	// Previous and next records
310
	$this->PrevRec = (object) null;
311
	$this->NextRec = false;
312

313
	if (isset($this->TBS->_piOnData)) {
314
		$this->OnDataPi = true;
315
		$this->OnDataPiRef = &$this->TBS->_piOnData;
316
		$this->OnDataOk = true;
317
	}
318
	if ($this->OnDataOk) {
319
		$this->OnDataArgs = array();
320
		$this->OnDataArgs[0] = &$this->TBS->_CurrBlock;
321
		$this->OnDataArgs[1] = &$this->CurrRec;
322
		$this->OnDataArgs[2] = &$this->RecNum;
323
		$this->OnDataArgs[3] = &$this->TBS;
324
	}
325

326
	switch ($this->Type) {
327
	case 0: // Array
328
		if (($this->SubType===1) && (is_string($Query))) $this->SubType = 2;
329
		if ($this->SubType===0) {
330
			$this->RecSet = &$this->SrcId;
331
		} elseif ($this->SubType===1) {
332
			if (is_array($Query)) {
333
				$this->RecSet = &$Query;
334
			} else {
335
				$this->DataAlert('type \''.gettype($Query).'\' not supported for the Query Parameter going with \'array\' Source Type.');
336
			}
337
		} elseif ($this->SubType===2) {
338
			// TBS query string for array and objects, syntax: "var[item1][item2]->item3[item4]..."
339
			$x = trim($Query);
340
			$z = chr(0);
341
			$x = str_replace(array(']->','][','->','['),$z,$x);
342
			if (substr($x,strlen($x)-1,1)===']') $x = substr($x,0,strlen($x)-1);
343
			$ItemLst = explode($z,$x);
344
			$ItemNbr = count($ItemLst);
345
			$Item0 = &$ItemLst[0];
346
			// Check first item
347
			if ($Item0[0]==='~') {
348
				$Item0 = substr($Item0,1);
349
				if ($this->TBS->ObjectRef!==false) {
350
					$Var = &$this->TBS->ObjectRef;
351
					$i = 0;
352
				} else {
353
					$i = $this->DataAlert('invalid query \''.$Query.'\' because property ObjectRef is not set.');
354
				}
355
			} else {
356
				if ( is_null($this->TBS->VarRef) && isset($GLOBALS[$Item0]) ) {
357
					$Var = &$GLOBALS[$Item0];
358
					$i = 1;
359
				} elseif (isset($this->TBS->VarRef[$Item0])) {
360
					$Var = &$this->TBS->VarRef[$Item0];
361
					$i = 1;
362
				} else {
363
					$i = $this->DataAlert('invalid query \''.$Query.'\' because VarRef item \''.$Item0.'\' is not found.');
364
				}
365
			}
366
			// Check sub-items
367
			$Empty = false;
368
			while (($i!==false) && ($i<$ItemNbr) && ($Empty===false)) {
369
				$x = $ItemLst[$i];
370
				if (is_array($Var)) {
371
					if (isset($Var[$x])) {
372
						$Var = &$Var[$x];
373
					} else {
374
						$Empty = true;
375
					}
376
				} elseif (is_object($Var)) {
377
					$form = $this->TBS->f_Misc_ParseFctForm($x);
378
					$n = $form['name'];
379
					if ( method_exists($Var, $n) || ($form['as_fct'] && method_exists($Var,'__call')) ) {
380
						$f = array(&$Var,$n); unset($Var);
381
						$Var = call_user_func_array($f,$form['args']);
382
					} elseif (property_exists(get_class($Var),$n)) {
383
						if (isset($Var->$n)) $Var = &$Var->$n;
384
					} elseif (isset($Var->$n)) {
385
						$Var = $Var->$n; // useful for overloaded property
386
					} else {
387
						$Empty = true;
388
					}
389
				} else {
390
					$i = $this->DataAlert('invalid query \''.$Query.'\' because item \''.$ItemLst[$i].'\' is neither an Array nor an Object. Its type is \''.gettype($Var).'\'.');
391
				}
392
				if ($i!==false) $i++;
393
			}
394
			// Assign data
395
			if ($i!==false) {
396
				if ($Empty) {
397
					$this->RecSet = array();
398
				} else {
399
					$this->RecSet = &$Var;
400
				}
401
			}
402
		} elseif ($this->SubType===3) { // Clear
403
			$this->RecSet = array();
404
		}
405
		// First record
406
		if ($this->RecSet!==false) {
407
			$this->RecNbr = $this->RecNumInit + count($this->RecSet);
408
			$this->RSIsFirst = true;
409
			$this->RecSaved = true;
410
			$this->RecSaving = false;
411
		}
412
		break;
413
	case 6: // MySQL
414
		switch ($this->SubType) {
415
		case 0: $this->RecSet = @mysql_query($Query,$this->SrcId); break;
416
		case 1: $this->RecSet = $this->SrcId; break;
417
		case 2: $this->RecSet = @mysql_query($Query); break;
418
		}
419
		if ($this->RecSet===false) $this->DataAlert('MySql error message when opening the query: '.mysql_error());
420
		break;
421
	case 1: // Num
422
		$this->RecSet = true;
423
		$this->NumMin = 1;
424
		$this->NumMax = 1;
425
		$this->NumStep = 1;
426
		if (is_array($Query)) {
427
			if (isset($Query['min'])) $this->NumMin = $Query['min'];
428
			if (isset($Query['step'])) $this->NumStep = $Query['step'];
429
			if (isset($Query['max'])) {
430
				$this->NumMax = $Query['max'];
431
			} else {
432
				$this->RecSet = $this->DataAlert('the \'num\' source is an array that has no value for the \'max\' key.');
433
			}
434
			if ($this->NumStep==0) $this->RecSet = $this->DataAlert('the \'num\' source is an array that has a step value set to zero.');
435
		} else {
436
			$this->NumMax = ceil($Query);
437
		}
438
		if ($this->RecSet) {
439
			if ($this->NumStep>0) {
440
				$this->NumVal = $this->NumMin;
441
			} else {
442
				$this->NumVal = $this->NumMax;
443
			}
444
		}
445
		break;
446
	case 2: // Text
447
		if (is_string($Query)) {
448
			$this->RecSet = &$Query;
449
		} else {
450
			$this->RecSet = $this->TBS->meth_Misc_ToStr($Query);
451
		}
452
		break;
453
	case 3: // Custom function
454
		$FctOpen = $this->FctOpen;
455
		$this->RecSet = $FctOpen($this->SrcId,$Query,$QryPrms);
456
		if ($this->RecSet===false) $this->DataAlert('function '.$FctOpen.'() has failed to open query {'.$Query.'}');
457
		break;
458
	case 4: // Custom method from ObjectRef
459
		$this->RecSet = call_user_func_array($this->FctOpen,array(&$this->SrcId,&$Query,&$QryPrms));
460
		if ($this->RecSet===false) $this->DataAlert('method '.get_class($this->FctOpen[0]).'::'.$this->FctOpen[1].'() has failed to open query {'.$Query.'}');
461
		break;
462
	case 5: // Custom method of object
463
		$this->RecSet = $this->SrcId->tbsdb_open($this->SrcId,$Query,$QryPrms);
464
		if ($this->RecSet===false) $this->DataAlert('method '.get_class($this->SrcId).'::tbsdb_open() has failed to open query {'.$Query.'}');
465
		break;
466
	case 7: // PostgreSQL
467
		switch ($this->SubType) {
468
		case 0: $this->RecSet = @pg_query($this->SrcId,$Query); break;
469
		case 1: $this->RecSet = $this->SrcId; break;
470
		}
471
		if ($this->RecSet===false) $this->DataAlert('PostgreSQL error message when opening the query: '.pg_last_error($this->SrcId));
472
		break;
473
	case 8: // SQLite
474
		switch ($this->SubType) {
475
		case 0: $this->RecSet = @sqlite_query($this->SrcId,$Query); break;
476
		case 1: $this->RecSet = $this->SrcId; break;
477
		}
478
		if ($this->RecSet===false) $this->DataAlert('SQLite error message when opening the query:'.sqlite_error_string(sqlite_last_error($this->SrcId)));
479
		break;
480
	case 9: // Iterator
481
		if ($this->SubType==1) {
482
			$this->RecSet = $this->SrcId;
483
		} else { // 2 or 3
484
			$this->RecSet = $this->SrcId->getIterator();
485
		}
486
		$this->RecSet->rewind();
487
		break;
488
	case 10: // MySQLi
489
		$this->RecSet = $this->SrcId->query($Query);
490
		if ($this->RecSet===false) $this->DataAlert('MySQLi error message when opening the query:'.$this->SrcId->error);
491
		break;
492
	case 11: // PDO
493
		$this->RecSet = $this->SrcId->prepare($Query);
494
		if ($this->RecSet===false) {
495
			$ok = false;
496
		} else {
497
			if (!is_array($QryPrms)) $QryPrms = array();
498
			$ok = $this->RecSet->execute($QryPrms);
499
		}
500
		if (!$ok) {
501
			$err = $this->SrcId->errorInfo();
502
			$this->DataAlert('PDO error message when opening the query:'.$err[2]);
503
		}
504
		break;
505
	case 12: // Zend_DB_Adapter
506
		try {
507
			if (!is_array($QryPrms)) $QryPrms = array();
508
			$this->RecSet = $this->SrcId->query($Query, $QryPrms);
509
		} catch (Exception $e) {
510
			$this->DataAlert('Zend_DB_Adapter error message when opening the query: '.$e->getMessage());
511
		}
512
		break;
513
	case 13: // SQLite3
514
		try {
515
			if ($this->SubType==3) {
516
				$this->RecSet = $this->SrcId;
517
			} elseif (($this->SubType==1) && (!is_array($QryPrms))) {
518
				// SQL statement without parameters
519
				$this->RecSet = $this->SrcId->query($Query);
520
			} else {
521
				if ($this->SubType==2) {
522
					$stmt = $this->SrcId;
523
					$prms = $Query;
524
				} else {
525
					// SQL statement with parameters
526
					$stmt = $this->SrcId->prepare($Query);
527
					$prms = $QryPrms;
528
				}
529
				// bind parameters
530
				if (is_array($prms)) {
531
					foreach ($prms as $p => $v) {
532
						if (is_numeric($p)) {
533
							$p = $p + 1;
534
						}
535
						if (is_array($v)) {
536
							$stmt->bindValue($p, $v[0], $v[1]);
537
						} else {
538
							$stmt->bindValue($p, $v);
539
						}
540
					}
541
				}
542
				$this->RecSet = $stmt->execute();
543
			}
544
		} catch (Exception $e) {
545
			$this->DataAlert('SQLite3 error message when opening the query: '.$e->getMessage());
546
		}
547
		break;
548
	case 14: // Doctrine DBAL
549
		try {
550
			if (!is_array($QryPrms)) $QryPrms = array();
551
			$this->RecSet = $this->SrcId->executeQuery($Query, $QryPrms);
552
		} catch (Exception $e) {
553
			$this->DataAlert('Doctrine DBAL error message when opening the query: '.$e->getMessage());
554
		}
555
		break;
556
	}
557

558
	if (($this->Type===0) || ($this->Type===9)) {
559
		unset($this->RecKey); $this->RecKey = '';
560
	} else {
561
		if ($this->RecSaving) {
562
			unset($this->RecBuffer); $this->RecBuffer = array();
563
		}
564
		$this->RecKey = &$this->RecNum; // Not array: RecKey = RecNum
565
	}
566

567
	return ($this->RecSet!==false);
568

569
}
570

571
public function DataFetch() {
572

573
	// Save previous record
574
	if ($this->PrevSave) {
575
		$this->_CopyRec($this, $this->PrevRec);
576
	}
577
	
578
	if ($this->NextSave) {
579
		// set current record
580
		if ($this->NextRec === false) {
581
			// first record
582
			$this->NextRec = (object) array('RecNum' => 1); // prepare for getting properties, RecNum needed for the first fetch
583
			$this->_DataFetchOn($this);
584
		} else {
585
			// other records
586
			$this->_CopyRec($this->NextRec, $this);
587
		}
588
		// set next record
589
		if ($this->CurrRec === false) {
590
			// no more record
591
			$this->NextRec = (object) null; // clear properties
592
		} else {
593
			$this->_DataFetchOn($this->NextRec);
594
		}
595
	} else {
596
		// Classic fetch
597
		$this->_DataFetchOn($this);
598
	}
599

600
}
601

602
public function DataClose() {
603
	$this->OnDataOk = false;
604
	$this->OnDataPrm = false;
605
	$this->OnDataPi = false;
606
	if ($this->RecSaved) return;
607
	switch ($this->Type) {
608
	case 6: mysql_free_result($this->RecSet); break;
609
	case 3: $FctClose=$this->FctClose; $FctClose($this->RecSet); break;
610
	case 4: call_user_func_array($this->FctClose,array(&$this->RecSet)); break;
611
	case 5: $this->SrcId->tbsdb_close($this->RecSet); break;
612
	case 7: pg_free_result($this->RecSet); break;
613
	case 10: $this->RecSet->free(); break; // MySQLi
614
	case 13: // SQLite3
615
		if ($this->SubType!=3) {
616
			$this->RecSet->finalize();
617
		}
618
		break;
619
	//case 11: $this->RecSet->closeCursor(); break; // PDO
620
	}
621
	if ($this->RecSaving) {
622
		$this->RecSet = &$this->RecBuffer;
623
		$this->RecNbr = $this->RecNumInit + count($this->RecSet);
624
		$this->RecSaving = false;
625
		$this->RecSaved = true;
626
	}
627
}
628

629
/**
630
 * Copy the record information from an object to another.
631
 */
632
private function _CopyRec($from, $to) {
633
	
634
	$to->CurrRec = $from->CurrRec;
635
	$to->RecNum  = $from->RecNum;
636
	$to->RecKey  = $from->RecKey;
637
	
638
}
639

640
/**
641
 * Fetch the next record on the object $obj.
642
 * This wil set the proiperties :
643
 *   $obj->CurrRec
644
 *   $obj->RecKey
645
 *   $obj->RecNum
646
 */
647
private function _DataFetchOn($obj) {
648

649
	// Check if the records are saved in an array
650
	if ($this->RecSaved) {
651
		if ($obj->RecNum < $this->RecNbr) {
652
			if ($this->RSIsFirst) {
653
				if ($this->SubType===2) { // From string
654
					reset($this->RecSet);
655
					$obj->RecKey = key($this->RecSet);
656
					$obj->CurrRec = &$this->RecSet[$obj->RecKey];
657
				} else {
658
					$obj->CurrRec = reset($this->RecSet);
659
					$obj->RecKey = key($this->RecSet);
660
				}
661
				$this->RSIsFirst = false;
662
			} else {
663
				if ($this->SubType===2) { // From string
664
					next($this->RecSet);
665
					$obj->RecKey = key($this->RecSet);
666
					$obj->CurrRec = &$this->RecSet[$obj->RecKey];
667
				} else {
668
					$obj->CurrRec = next($this->RecSet);
669
					$obj->RecKey = key($this->RecSet);
670
				}
671
			}
672
			if ((!is_array($obj->CurrRec)) && (!is_object($obj->CurrRec))) $obj->CurrRec = array('key'=>$obj->RecKey, 'val'=>$obj->CurrRec);
673
			$obj->RecNum++;
674
			if ($this->OnDataOk) {
675
				$this->OnDataArgs[1] = &$obj->CurrRec; // Reference has changed if ($this->SubType===2)
676
				if ($this->OnDataPrm) call_user_func_array($this->OnDataPrmRef,$this->OnDataArgs);
677
				if ($this->OnDataPi) $this->TBS->meth_PlugIn_RunAll($this->OnDataPiRef,$this->OnDataArgs);
678
				if ($this->SubType!==2) $this->RecSet[$obj->RecKey] = $obj->CurrRec; // save modifications because array reading is done without reference :(
679
			}
680
		} else {
681
			unset($obj->CurrRec); $obj->CurrRec = false;
682
		}
683
		return;
684
	}
685

686
	switch ($this->Type) {
687
	case 6: // MySQL
688
		$obj->CurrRec = mysql_fetch_assoc($this->RecSet);
689
		break;
690
	case 1: // Num
691
		if (($this->NumVal>=$this->NumMin) && ($this->NumVal<=$this->NumMax)) {
692
			$obj->CurrRec = array('val'=>$this->NumVal);
693
			$this->NumVal += $this->NumStep;
694
		} else {
695
			$obj->CurrRec = false;
696
		}
697
		break;
698
	case 2: // Text
699
		if ($obj->RecNum===0) {
700
			if ($this->RecSet==='') {
701
				$obj->CurrRec = false;
702
			} else {
703
				$obj->CurrRec = &$this->RecSet;
704
			}
705
		} else {
706
			$obj->CurrRec = false;
707
		}
708
		break;
709
	case 3: // Custom function
710
		$FctFetch = $this->FctFetch;
711
		$obj->CurrRec = $FctFetch($this->RecSet,$obj->RecNum+1);
712
		break;
713
	case 4: // Custom method from ObjectRef
714
		$this->FctPrm[0] = &$this->RecSet; $this->FctPrm[1] = $obj->RecNum+1;
715
		$obj->CurrRec = call_user_func_array($this->FctFetch,$this->FctPrm);
716
		break;
717
	case 5: // Custom method of object
718
		$obj->CurrRec = $this->SrcId->tbsdb_fetch($this->RecSet,$obj->RecNum+1);
719
		break;
720
	case 7: // PostgreSQL
721
		$obj->CurrRec = pg_fetch_assoc($this->RecSet);
722
		break;
723
	case 8: // SQLite
724
		$obj->CurrRec = sqlite_fetch_array($this->RecSet,SQLITE_ASSOC);
725
		break;
726
	case 9: // Iterator
727
		if ($this->RecSet->valid()) {
728
			$obj->CurrRec = $this->RecSet->current();
729
			$obj->RecKey = $this->RecSet->key();
730
			$this->RecSet->next();
731
		} else {
732
			$obj->CurrRec = false;
733
		}
734
		break;
735
	case 10: // MySQLi
736
		$obj->CurrRec = $this->RecSet->fetch_assoc();
737
		if (is_null($obj->CurrRec)) $obj->CurrRec = false;
738
		break;
739
	case 11: // PDO
740
		$obj->CurrRec = $this->RecSet->fetch(PDO::FETCH_ASSOC);
741
		break;
742
	case 12: // Zend_DB_Adapater
743
		$obj->CurrRec = $this->RecSet->fetch(Zend_Db::FETCH_ASSOC);
744
		break;
745
	case 13: // SQLite3
746
		$obj->CurrRec = $this->RecSet->fetchArray(SQLITE3_ASSOC);
747
		break;
748
	case 14: // Doctrine DBAL
749
		$obj->CurrRec = $this->RecSet->fetchAssociative();
750
		break;
751
	}
752

753
	// Set the row count
754
	if ($obj->CurrRec!==false) {
755
		$obj->RecNum++;
756
		if ($this->OnDataOk) {
757
			if ($this->OnDataPrm) call_user_func_array($this->OnDataPrmRef,$this->OnDataArgs);
758
			if ($this->OnDataPi) $this->TBS->meth_PlugIn_RunAll($this->OnDataPiRef,$this->OnDataArgs);
759
		}
760
		if ($this->RecSaving) $this->RecBuffer[$obj->RecKey] = $obj->CurrRec;
761
	}
762

763
}
764

765
}
766

767
// *********************************************
768

769
class clsTinyButStrong {
770

771
// Public properties
772
public $Source = '';
773
public $Render = 3;
774
public $TplVars = array();
775
public $ObjectRef = false;
776
public $NoErr = false;
777
public $Assigned = array();
778
public $ExtendedMethods = array();
779
public $ErrCount = 0;
780
// Undocumented (can change at any version)
781
public $Version = '3.15.0';
782
public $Charset = '';
783
public $TurboBlock = true;
784
public $VarPrefix = '';
785
public $VarRef = null;
786
public $FctPrefix = '';
787
public $Protect = true;
788
public $ErrMsg = '';
789
public $AttDelim = false;
790
public $MethodsAllowed = false;
791
public $ScriptsAllowed = false;
792
public $OnLoad = true;
793
public $OnShow = true;
794
public $IncludePath = array();
795
public $TplStore = array();
796
public $OldSubTpl = false; // turn to true to have compatibility with the old way to perform subtemplates, that is get output buffuring
797
// Private
798
public $_ErrMsgName = '';
799
public $_LastFile = '';
800
public $_CharsetFct = false;
801
public $_Mode = 0;
802
public $_CurrBlock = '';
803
public $_ChrOpen = '[';
804
public $_ChrClose = ']';
805
public $_ChrVal = '[val]';
806
public $_ChrProtect = '&#91;';
807
public $_PlugIns = array();
808
public $_PlugIns_Ok = false;
809
public $_piOnFrm_Ok = false;
810

811
// Compatibility with PHP 8.2
812
private $_UserFctLst;
813
private $_Subscript;
814
public  $CurrPrm;
815

816
private $_piOnData;
817
private $_piBeforeLoadTemplate;
818
private $_piAfterLoadTemplate;
819
private $_piOnMergeField;
820
private $_piBeforeShow;
821
private $_piAfterShow;
822
private $_piOnCommand;
823
private $_piOnOperation;
824
private $_piOnCacheField;
825
private $_PlugIns_Ok_save;
826
private $_piOnFrm_Ok_save;
827
private $_piOnFormat;
828
private $_piBeforeMergeBlock;
829
private $_piOnMergeSection;
830
private $_piOnMergeGroup;
831
private $_piAfterMergeBlock;
832
private $_piOnSpecialVar;
833

834
// OpenTBS
835
public $OtbsAutoLoad;
836
public $OtbsConvBr;
837
public $OtbsAutoUncompress;
838
public $OtbsConvertApostrophes;
839
public $OtbsSpacePreserve;
840
public $OtbsClearWriter;
841
public $OtbsClearMsWord;
842
public $OtbsMsExcelConsistent;
843
public $OtbsMsExcelExplicitRef;
844
public $OtbsClearMsPowerpoint;
845
public $OtbsGarbageCollector;
846
public $OtbsMsExcelCompatibility;
847
public $OtbsCurrFile;
848
public $OtbsSubFileLst;
849
public $TbsZip;
850

851
function __construct($Options=null,$VarPrefix='',$FctPrefix='') {
852

853
	// Compatibility
854
	if (is_string($Options)) {
855
		$Chrs = $Options;
856
		$Options = array('var_prefix'=>$VarPrefix, 'fct_prefix'=>$FctPrefix);
857
		if ($Chrs!=='') {
858
			$Err = true;
859
			$Len = strlen($Chrs);
860
			if ($Len===2) { // For compatibility
861
				$Options['chr_open']  = $Chrs[0];
862
				$Options['chr_close'] = $Chrs[1];
863
				$Err = false;
864
			} else {
865
				$Pos = strpos($Chrs,',');
866
				if (($Pos!==false) && ($Pos>0) && ($Pos<$Len-1)) {
867
					$Options['chr_open']  = substr($Chrs,0,$Pos);
868
					$Options['chr_close'] = substr($Chrs,$Pos+1);
869
					$Err = false;
870
				}
871
			}
872
			if ($Err) $this->meth_Misc_Alert('with clsTinyButStrong() function','value \''.$Chrs.'\' is a bad tag delimitor definition.');
873
		}
874
	} 
875

876
	// Set VarRef initial value
877
	$this->ResetVarRef(true);
878
	
879
	// Set options
880
	if (is_array($Options)) $this->SetOption($Options);
881

882
	// Links to global variables (cannot be converted to static yet because of compatibility)
883
	global $_TBS_FormatLst, $_TBS_UserFctLst, $_TBS_BlockAlias, $_TBS_PrmCombo, $_TBS_AutoInstallPlugIns, $_TBS_ParallelLst;
884
	if (!isset($_TBS_FormatLst))   $_TBS_FormatLst  = array();
885
	if (!isset($_TBS_UserFctLst))  $_TBS_UserFctLst = array();
886
	if (!isset($_TBS_BlockAlias))  $_TBS_BlockAlias = array();
887
	if (!isset($_TBS_PrmCombo))    $_TBS_PrmCombo = array();
888
	if (!isset($_TBS_ParallelLst)) $_TBS_ParallelLst = array();
889
	$this->_UserFctLst = &$_TBS_UserFctLst;
890

891
	// Auto-installing plug-ins
892
	if (isset($_TBS_AutoInstallPlugIns)) foreach ($_TBS_AutoInstallPlugIns as $pi) $this->PlugIn(TBS_INSTALL,$pi);
893

894
}
895

896
function __call($meth, $args) {
897
	if (isset($this->ExtendedMethods[$meth])) {
898
		if ( is_array($this->ExtendedMethods[$meth]) || is_string($this->ExtendedMethods[$meth]) ) {
899
			return call_user_func_array($this->ExtendedMethods[$meth], $args);
900
		} else {
901
			return call_user_func_array(array(&$this->ExtendedMethods[$meth], $meth), $args);
902
		}
903
	} else {
904
		$this->meth_Misc_Alert('Method not found','\''.$meth.'\' is neither a native nor an extended method of TinyButStrong.');
905
	}
906
}
907

908
function SetOption($o, $v=false, $d=false) {
909
	if (!is_array($o)) $o = array($o=>$v);
910
	if (isset($o['var_prefix'])) $this->VarPrefix = $o['var_prefix'];
911
	if (isset($o['fct_prefix'])) $this->FctPrefix = $o['fct_prefix'];
912
	if (isset($o['noerr'])) $this->NoErr = $o['noerr'];
913
	if (isset($o['old_subtemplate'])) $this->OldSubTpl = $o['old_subtemplate'];
914
	if (isset($o['auto_merge'])) {
915
		$this->OnLoad = $o['auto_merge'];
916
		$this->OnShow = $o['auto_merge'];
917
	}
918
	if (isset($o['onload'])) $this->OnLoad = $o['onload'];
919
	if (isset($o['onshow'])) $this->OnShow = $o['onshow'];
920
	if (isset($o['att_delim'])) $this->AttDelim = $o['att_delim'];
921
	if (isset($o['protect'])) $this->Protect = $o['protect'];
922
	if (isset($o['turbo_block'])) $this->TurboBlock = $o['turbo_block'];
923
	if (isset($o['charset'])) $this->meth_Misc_Charset($o['charset']);
924

925
	$UpdateChr = false;
926
	if (isset($o['chr_open'])) {
927
		$this->_ChrOpen = $o['chr_open'];
928
		$UpdateChr = true;
929
	}
930
	if (isset($o['chr_close'])) {
931
		$this->_ChrClose = $o['chr_close'];
932
		$UpdateChr = true;
933
	}
934
	if ($UpdateChr) {
935
		$this->_ChrVal = $this->_ChrOpen.'val'.$this->_ChrClose;
936
		$this->_ChrProtect = '&#'.ord($this->_ChrOpen[0]).';'.substr($this->_ChrOpen,1);
937
	}
938
	if (array_key_exists('tpl_frms',$o))      self::f_Misc_UpdateArray($GLOBALS['_TBS_FormatLst'], 'frm', $o['tpl_frms'], $d);
939
	if (array_key_exists('block_alias',$o))   self::f_Misc_UpdateArray($GLOBALS['_TBS_BlockAlias'], false, $o['block_alias'], $d);
940
	if (array_key_exists('prm_combo',$o))     self::f_Misc_UpdateArray($GLOBALS['_TBS_PrmCombo'], 'prm', $o['prm_combo'], $d);
941
	if (array_key_exists('parallel_conf',$o)) self::f_Misc_UpdateArray($GLOBALS['_TBS_ParallelLst'], false, $o['parallel_conf'], $d);
942
	if (array_key_exists('include_path',$o))  self::f_Misc_UpdateArray($this->IncludePath, true, $o['include_path'], $d);
943
	if (isset($o['render'])) $this->Render = $o['render'];
944
	if (isset($o['methods_allowed'])) $this->MethodsAllowed = $o['methods_allowed'];
945
	if (isset($o['scripts_allowed'])) $this->ScriptsAllowed = $o['scripts_allowed'];
946
}
947

948
function GetOption($o) {
949
	if ($o==='all') {
950
		$x = explode(',', 'var_prefix,fct_prefix,noerr,auto_merge,onload,onshow,att_delim,protect,turbo_block,charset,chr_open,chr_close,tpl_frms,block_alias,parallel_conf,include_path,render,prm_combo');
951
		$r = array();
952
		foreach ($x as $o) $r[$o] = $this->GetOption($o);
953
		return $r;
954
	}
955
	if ($o==='var_prefix') return $this->VarPrefix;
956
	if ($o==='fct_prefix') return $this->FctPrefix;
957
	if ($o==='noerr') return $this->NoErr;
958
	if ($o==='auto_merge') return ($this->OnLoad && $this->OnShow);
959
	if ($o==='onload') return $this->OnLoad;
960
	if ($o==='onshow') return $this->OnShow;
961
	if ($o==='att_delim') return $this->AttDelim;
962
	if ($o==='protect') return $this->Protect;
963
	if ($o==='turbo_block') return $this->TurboBlock;
964
	if ($o==='charset') return $this->Charset;
965
	if ($o==='chr_open') return $this->_ChrOpen;
966
	if ($o==='chr_close') return $this->_ChrClose;
967
	if ($o==='tpl_frms') {
968
		// simplify the list of formats
969
		$x = array();
970
		foreach ($GLOBALS['_TBS_FormatLst'] as $s=>$i) $x[$s] = $i['Str'];
971
		return $x;
972
	}
973
	if ($o==='include_path') return $this->IncludePath;
974
	if ($o==='render') return $this->Render;
975
	if ($o==='methods_allowed') return $this->MethodsAllowed;
976
	if ($o==='scripts_allowed') return $this->ScriptsAllowed;
977
	if ($o==='parallel_conf') return $GLOBALS['_TBS_ParallelLst'];
978
	if ($o==='block_alias') return $GLOBALS['_TBS_BlockAlias'];
979
	if ($o==='prm_combo') return $GLOBALS['_TBS_PrmCombo'];
980
	return $this->meth_Misc_Alert('with GetOption() method','option \''.$o.'\' is not supported.');;
981
}
982

983
public function ResetVarRef($ToGlobal) {
984
	// We set a new variable in order to force the reference
985
	// value NULL means that VarRef refers to $GLOBALS
986
	$x = ($ToGlobal) ? null : array();
987
	$this->VarRef = &$x;
988
}
989

990
/**
991
 * Get an item value from VarRef.
992
 * Ensure the compatibility with PHP 8.1 if VarRef is set to Global.
993
 * 
994
 * @param string $key      The item key.
995
 * @param mixed  $default  The default value.
996
 *
997
 * @return mixed
998
 */
999
public function GetVarRefItem($key, $default) {
1000

1001
	if (is_null($this->VarRef)) {
1002
		
1003
		if (array_key_exists($key, $GLOBALS)) {
1004
			return $GLOBALS[$key];
1005
		} else {
1006
			return $default;
1007
		}
1008

1009
	} else {
1010

1011
		if (array_key_exists($key, $this->VarRef)) {
1012
			return $this->VarRef[$key];
1013
		} else {
1014
			return $default;
1015
		}
1016

1017
	}
1018
	
1019
}
1020

1021
/**
1022
 * Set an item value to VarRef.
1023
 * Ensure the compatibility with PHP 8.1 if VarRef is set to Global.
1024
 * 
1025
 * @param string|array $keyOrList   A list of keys and items to add, or the item key.
1026
 * @param mixed        $value       (optional) The item value. Use NULL in order to delete the item.
1027
 */
1028
public function SetVarRefItem($keyOrList, $value = null) {
1029

1030
	if (is_array($keyOrList)) {
1031
		$list = $keyOrList;
1032
	} else {
1033
		$list = array($keyOrList => $value);
1034
	}
1035

1036
	if (is_null($this->VarRef)) {
1037
		
1038
		foreach ($list as $key => $value) {
1039
			if (is_null($value)) {
1040
				unset($GLOBALS[$key]);
1041
			} else {
1042
				$GLOBALS[$key] = $value;
1043
			}
1044
		}
1045

1046
	} else {
1047

1048
		foreach ($list as $key => $value) {
1049
			if (is_null($value)) {
1050
				unset($this->VarRef[$key]);
1051
			} else {
1052
				$this->VarRef[$key] = $value;
1053
			}
1054
		}
1055

1056
	}
1057
	
1058
}
1059

1060
// Public methods
1061
public function LoadTemplate($File,$Charset='') {
1062
	if ($File==='') {
1063
		$this->meth_Misc_Charset($Charset);
1064
		return true;
1065
	}
1066
	$Ok = true;
1067
	if ($this->_PlugIns_Ok) {
1068
		if (isset($this->_piBeforeLoadTemplate) || isset($this->_piAfterLoadTemplate)) {
1069
			// Plug-ins
1070
			$ArgLst = func_get_args();
1071
			$ArgLst[0] = &$File;
1072
			$ArgLst[1] = &$Charset;
1073
			if (isset($this->_piBeforeLoadTemplate)) $Ok = $this->meth_PlugIn_RunAll($this->_piBeforeLoadTemplate,$ArgLst);
1074
		}
1075
	}
1076
	// Load the file
1077
	if ($Ok!==false) {
1078
		if (!is_null($File)) {
1079
			$x = '';
1080
			if (!$this->f_Misc_GetFile($x, $File, $this->_LastFile, $this->IncludePath)) return $this->meth_Misc_Alert('with LoadTemplate() method','file \''.$File.'\' is not found or not readable.');
1081
			if ($Charset==='+') {
1082
				$this->Source .= $x;
1083
			} else {
1084
				$this->Source = $x;
1085
			}
1086
		}
1087
		if ($this->meth_Misc_IsMainTpl()) {
1088
			if (!is_null($File)) $this->_LastFile = $File;
1089
			if ($Charset!=='+') $this->TplVars = array();
1090
			$this->meth_Misc_Charset($Charset);
1091
		}
1092
		// Automatic fields and blocks
1093
		if ($this->OnLoad) $this->meth_Merge_AutoOn($this->Source,'onload',true,true);
1094
	}
1095
	// Plug-ins
1096
	if ($this->_PlugIns_Ok && isset($ArgLst) && isset($this->_piAfterLoadTemplate)) $Ok = $this->meth_PlugIn_RunAll($this->_piAfterLoadTemplate,$ArgLst);
1097
	return $Ok;
1098
}
1099

1100
public function GetBlockSource($BlockName,$AsArray=false,$DefTags=true,$ReplaceWith=false) {
1101
	$RetVal = array();
1102
	$Nbr = 0;
1103
	$Pos = 0;
1104
	$FieldOutside = false;
1105
	$P1 = false;
1106
	$Mode = ($DefTags) ? 3 : 2;
1107
	$PosBeg1 = 0;
1108
	while ($Loc = $this->meth_Locator_FindBlockNext($this->Source,$BlockName,$Pos,'.',$Mode,$P1,$FieldOutside)) {
1109
		$Nbr++;
1110
		$Sep = '';
1111
		if ($Nbr==1) {
1112
			$PosBeg1 = $Loc->PosBeg;
1113
		} elseif (!$AsArray) {
1114
			$Sep = substr($this->Source,$PosSep,$Loc->PosBeg-$PosSep); // part of the source between sections
1115
		}
1116
		$RetVal[$Nbr] = $Sep.$Loc->BlockSrc;
1117
		$Pos = $Loc->PosEnd;
1118
		$PosSep = $Loc->PosEnd+1;
1119
		$P1 = false;
1120
	}
1121
	if ($Nbr==0) return false;
1122
	if (!$AsArray) {
1123
		if ($DefTags)  {
1124
			// Return the true part of the template
1125
			$RetVal = substr($this->Source,$PosBeg1,$Pos-$PosBeg1+1);
1126
		} else {
1127
			// Return the concatenated section without def tags
1128
			$RetVal = implode('', $RetVal);
1129
		}
1130
	}
1131
	if ($ReplaceWith!==false) $this->Source = substr($this->Source,0,$PosBeg1).$ReplaceWith.substr($this->Source,$Pos+1);
1132
	return $RetVal;
1133
}
1134

1135
/**
1136
 * Get the value of a XML-HTML attribute targeted thanks to a TBS fields having parameter att.
1137
 * @param  string  $Name       Name of the TBS fields. It must have parameter att.
1138
 * @param  boolean $delete     (optional, true by default) Use true to delete the TBS field.
1139
 * @return string|true|null|false  The value of the attribute,
1140
 *                                 true if the attribute is found without value,
1141
 *                                 null if the TBS field, the target element is not found,
1142
 *                                 or false for other error.
1143
 */
1144
public function GetAttValue($Name, $delete = true) {
1145
	$Pos = 0;
1146
	$val = null;
1147
	while ($Loc = $this->meth_Locator_FindTbs($this->Source,$Name,$Pos,'.')) {
1148
		if (isset($Loc->PrmLst['att'])) {
1149
			if ($this->f_Xml_AttFind($this->Source,$Loc,false,$this->AttDelim)) {
1150
				$val = false;
1151
				if ($Loc->AttBeg !== false) {
1152
					if ($Loc->AttValBeg !== false) {
1153
						$val = substr($this->Source, $Loc->AttValBeg, $Loc->AttEnd - $Loc->AttValBeg + 1);
1154
						$val = substr($val, 1, -1);
1155
					} else {
1156
						$val = true;
1157
					}
1158
				} else {
1159
					// not found
1160
				}
1161
			} else {
1162
				// att not found
1163
			}
1164
		} else {
1165
			// no att parameter
1166
		}
1167

1168
		if ($delete) {
1169
			$this->Source = substr_replace($this->Source, '', $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1); 
1170
			$Pos = $Loc->PosBeg;
1171
		} else {
1172
			$Pos = $Loc->PosEnd;
1173
		}
1174
	}
1175
	return $val;
1176
}
1177

1178
public function MergeBlock($BlockLst,$SrcId='assigned',$Query='',$QryPrms=false) {
1179

1180
	if ($SrcId==='assigned') {
1181
		$Arg = array($BlockLst,&$SrcId,&$Query,&$QryPrms);
1182
		if (!$this->meth_Misc_Assign($BlockLst, $Arg, 'MergeBlock')) return 0;
1183
		$BlockLst = $Arg[0]; $SrcId = &$Arg[1]; $Query = &$Arg[2];
1184
	}
1185

1186
	if (is_string($BlockLst)) $BlockLst = explode(',',$BlockLst);
1187

1188
	if ($SrcId==='cond') {
1189
		$Nbr = 0;
1190
		foreach ($BlockLst as $Block) {
1191
			$Block = trim($Block);
1192
			if ($Block!=='') $Nbr += $this->meth_Merge_AutoOn($this->Source,$Block,true,true);
1193
		}
1194
		return $Nbr;
1195
	} else {
1196
		return $this->meth_Merge_Block($this->Source,$BlockLst,$SrcId,$Query,false,0,$QryPrms);
1197
	}
1198

1199
}
1200

1201
public function MergeField($NameLst,$Value='assigned',$IsUserFct=false,$DefaultPrm=false) {
1202

1203
	$FctCheck = $IsUserFct;
1204
	if ($PlugIn = isset($this->_piOnMergeField)) $ArgPi = array('','',&$Value,0,&$this->Source,0,0);
1205
	$SubStart = 0;
1206
	$Ok = true;
1207
	$Prm = is_array($DefaultPrm);
1208

1209
	if ( ($Value==='assigned') && ($NameLst!=='var') && ($NameLst!=='onshow') && ($NameLst!=='onload') ) {
1210
		$Arg = array($NameLst,&$Value,&$IsUserFct,&$DefaultPrm);
1211
		if (!$this->meth_Misc_Assign($NameLst, $Arg, 'MergeField')) return false;
1212
		$NameLst = $Arg[0]; $Value = &$Arg[1]; $IsUserFct = &$Arg[2]; $DefaultPrm = &$Arg[3];
1213
	}
1214

1215
	$NameLst = explode(',',$NameLst);
1216

1217
	foreach ($NameLst as $Name) {
1218
		$Name = trim($Name);
1219
		$Cont = false;
1220
		switch ($Name) {
1221
		case '': $Cont=true;break;
1222
		case 'onload': $this->meth_Merge_AutoOn($this->Source,'onload',true,true);$Cont=true;break;
1223
		case 'onshow': $this->meth_Merge_AutoOn($this->Source,'onshow',true,true);$Cont=true;break;
1224
		case 'var':	$this->meth_Merge_AutoVar($this->Source,true);$Cont=true;break;
1225
		}
1226
		if ($Cont) continue;
1227
		if ($PlugIn) $ArgPi[0] = $Name;
1228
		$PosBeg = 0;
1229
		// Initilize the user function (only once)
1230
		if ($FctCheck) {
1231
			$FctInfo = $Value;
1232
			$ErrMsg = false;
1233
			if (!$this->meth_Misc_UserFctCheck($FctInfo,'f',$ErrMsg,$ErrMsg,false)) return $this->meth_Misc_Alert('with MergeField() method',$ErrMsg);
1234
			$FctArg = array('','');
1235
			$SubStart = false;
1236
			$FctCheck = false;
1237
		}
1238
		while ($Loc = $this->meth_Locator_FindTbs($this->Source,$Name,$PosBeg,'.')) {
1239
			if ($Prm) $Loc->PrmLst = array_merge($DefaultPrm,$Loc->PrmLst);
1240
			// Apply user function
1241
			if ($IsUserFct) {
1242
				$FctArg[0] = &$Loc->SubName; $FctArg[1] = &$Loc->PrmLst;
1243
				$Value = call_user_func_array($FctInfo,$FctArg);
1244
			}
1245
			// Plug-ins
1246
			if ($PlugIn) {
1247
				$ArgPi[1] = $Loc->SubName; $ArgPi[3] = &$Loc->PrmLst; $ArgPi[5] = &$Loc->PosBeg; $ArgPi[6] = &$Loc->PosEnd;
1248
				$Ok = $this->meth_PlugIn_RunAll($this->_piOnMergeField,$ArgPi);
1249
			}
1250
			// Merge the field
1251
			if ($Ok) {
1252
				$PosBeg = $this->meth_Locator_Replace($this->Source,$Loc,$Value,$SubStart);
1253
			} else {
1254
				$PosBeg = $Loc->PosEnd;
1255
			}
1256
		}
1257
	}
1258
}
1259

1260
/**
1261
 * Replace a set of simple TBS fields (that is fields without any parameters) with more complexe TBS fields.
1262
 * @param array  $fields     An associative array of items to replace.
1263
 *                           Keys are the name of the simple field to replace.
1264
 *                           Values are the parameters of the field as an array or as a string.
1265
 *                           Parameter 'name' will be used as the new name of the field, by default it is the same name as the simple field.
1266
 * @param string $blockName (optional) The name of the block for prefixing fields.
1267
 */
1268
public function ReplaceFields($fields, $blockName = false) {
1269
	
1270
	$prefix = ($blockName) ? $blockName . '.' : '';
1271
	
1272
	// calling the replace using array is faster than a loop
1273
	$what = array();
1274
	$with = array();
1275
	foreach ($fields as $name => $prms) {
1276
		$what[] = $this->_ChrOpen . $name . $this->_ChrClose; 
1277
		if (is_array($prms)) {
1278
			// field replace
1279
			$lst = '';
1280
			foreach ($prms as $p => $v) {
1281
				if ($p === 'name') {
1282
					$name = $v;
1283
				} else {
1284
					if ($v === true) {
1285
						$lst .= ';' . $p;
1286
					} elseif (is_array($v)) {
1287
						foreach($v as $x) {
1288
							$lst .= ';' . $p . '=' . $x;
1289
						} 
1290
					} else {
1291
						$lst .= ';' . $p . '=' . $v;
1292
					}
1293
				}
1294
			}
1295
			$with[] = $this->_ChrOpen . $prefix . $name . $lst . $this->_ChrClose; 
1296
		} else {
1297
			// simple string replace
1298
			$with[] = $prms; 
1299
		}
1300
	}
1301
	
1302
	$this->Source = str_replace($what, $with, $this->Source);
1303
	
1304
}
1305

1306
public function Show($Render=false) {
1307
	$Ok = true;
1308
	if ($Render===false) $Render = $this->Render;
1309
	if ($this->_PlugIns_Ok) {
1310
		if (isset($this->_piBeforeShow) || isset($this->_piAfterShow)) {
1311
			// Plug-ins
1312
			$ArgLst = func_get_args();
1313
			$ArgLst[0] = &$Render;
1314
			if (isset($this->_piBeforeShow)) $Ok = $this->meth_PlugIn_RunAll($this->_piBeforeShow,$ArgLst);
1315
		}
1316
	}
1317
	if ($Ok!==false) {
1318
		if ($this->OnShow) $this->meth_Merge_AutoOn($this->Source,'onshow',true,true);
1319
		$this->meth_Merge_AutoVar($this->Source,true);
1320
	}
1321
	if ($this->_PlugIns_Ok && isset($ArgLst) && isset($this->_piAfterShow)) $this->meth_PlugIn_RunAll($this->_piAfterShow,$ArgLst);
1322
	if ($this->_ErrMsgName!=='') $this->MergeField($this->_ErrMsgName, $this->ErrMsg);
1323
	if ($this->meth_Misc_IsMainTpl()) {
1324
		if (($Render & TBS_OUTPUT)==TBS_OUTPUT) echo $this->Source;
1325
		if (($Render & TBS_EXIT)==TBS_EXIT) exit;
1326
	} elseif ($this->OldSubTpl) {
1327
		if (($Render & TBS_OUTPUT)==TBS_OUTPUT) echo $this->Source;
1328
	}
1329
	return $Ok;
1330
}
1331

1332
public function PlugIn($Prm1,$Prm2=0) {
1333

1334
	if (is_numeric($Prm1)) {
1335
		switch ($Prm1) {
1336
		case TBS_INSTALL: // Try to install the plug-in
1337
			$PlugInId = $Prm2;
1338
			if (isset($this->_PlugIns[$PlugInId])) {
1339
				return $this->meth_Misc_Alert('with PlugIn() method','plug-in \''.$PlugInId.'\' is already installed.');
1340
			} else {
1341
				$ArgLst = func_get_args();
1342
				array_shift($ArgLst); array_shift($ArgLst);
1343
				return $this->meth_PlugIn_Install($PlugInId,$ArgLst,false);
1344
			}
1345
		case TBS_ISINSTALLED: // Check if the plug-in is installed
1346
			return isset($this->_PlugIns[$Prm2]);
1347
		case -4: // Deactivate special plug-ins
1348
			$this->_PlugIns_Ok_save = $this->_PlugIns_Ok;
1349
			$this->_PlugIns_Ok = false;
1350
			return true;
1351
		case -5: // Deactivate OnFormat
1352
			$this->_piOnFrm_Ok_save = $this->_piOnFrm_Ok;
1353
			$this->_piOnFrm_Ok = false;
1354
			return true;
1355
		case -10:  // Restore
1356
			if (isset($this->_PlugIns_Ok_save)) $this->_PlugIns_Ok = $this->_PlugIns_Ok_save;
1357
			if (isset($this->_piOnFrm_Ok_save)) $this->_piOnFrm_Ok = $this->_piOnFrm_Ok_save;
1358
			return true;
1359
		}
1360

1361
	} elseif (is_string($Prm1)) {
1362
		// Plug-in's command
1363
		$p = strpos($Prm1,'.');
1364
		if ($p===false) {
1365
			$PlugInId = $Prm1;
1366
		} else {
1367
			$PlugInId = substr($Prm1,0,$p); // direct command
1368
		}
1369
		if (!isset($this->_PlugIns[$PlugInId])) {
1370
			if (!$this->meth_PlugIn_Install($PlugInId,array(),true)) return false;
1371
		}
1372
		if (!isset($this->_piOnCommand[$PlugInId])) return $this->meth_Misc_Alert('with PlugIn() method','plug-in \''.$PlugInId.'\' can\'t run any command because the OnCommand event is not defined or activated.');
1373
		$ArgLst = func_get_args();
1374
		if ($p===false) array_shift($ArgLst);
1375
		$Ok = call_user_func_array($this->_piOnCommand[$PlugInId],$ArgLst);
1376
		if (is_null($Ok)) $Ok = true;
1377
		return $Ok;
1378
	}
1379
	return $this->meth_Misc_Alert('with PlugIn() method','\''.$Prm1.'\' is an invalid plug-in key, the type of the value is \''.gettype($Prm1).'\'.');
1380

1381
}
1382

1383
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
1384

1385
function meth_Locator_FindTbs(&$Txt,$Name,$Pos,$ChrSub) {
1386
// Find a TBS Locator
1387

1388
	$PosEnd = false;
1389
	$PosMax = strlen($Txt) -1;
1390
	$Start = $this->_ChrOpen.$Name;
1391

1392
	do {
1393
		// Search for the opening char
1394
		if ($Pos>$PosMax) return false;
1395
		$Pos = strpos($Txt,$Start,$Pos);
1396

1397
		// If found => next chars are analyzed
1398
		if ($Pos===false) {
1399
			return false;
1400
		} else {
1401
			$Loc = new clsTbsLocator;
1402
			$ReadPrm = false;
1403
			$PosX = $Pos + strlen($Start);
1404
			$x = $Txt[$PosX];
1405

1406
			if ($x===$this->_ChrClose) {
1407
				$PosEnd = $PosX;
1408
			} elseif ($x===$ChrSub) {
1409
				$Loc->SubOk = true; // it is no longer the false value
1410
				$ReadPrm = true;
1411
				$PosX++;
1412
			} elseif (strpos(';',$x)!==false) {
1413
				$ReadPrm = true;
1414
				$PosX++;
1415
			} else {
1416
				$Pos++;
1417
			}
1418

1419
			$Loc->PosBeg = $Pos;
1420
			if ($ReadPrm) {
1421
				self::f_Loc_PrmRead($Txt,$PosX,false,'\'',$this->_ChrOpen,$this->_ChrClose,$Loc,$PosEnd);
1422
				if ($PosEnd===false) {
1423
					$this->meth_Misc_Alert('','can\'t found the end of the tag \''.substr($Txt,$Pos,$PosX-$Pos+10).'...\'.');
1424
					$Pos++;
1425
				} else {
1426
					self::meth_Misc_ApplyPrmCombo($Loc->PrmLst, $Loc);
1427
				}
1428
			}
1429

1430
		}
1431

1432
	} while ($PosEnd===false);
1433

1434
	$Loc->PosEnd = $PosEnd;
1435
	if ($Loc->SubOk) {
1436
		$Loc->FullName = $Name.'.'.$Loc->SubName;
1437
		$Loc->SubLst = explode('.',$Loc->SubName);
1438
		$Loc->SubNbr = count($Loc->SubLst);
1439
	} else {
1440
		$Loc->FullName = $Name;
1441
	}
1442
	if ( $ReadPrm && ( isset($Loc->PrmLst['enlarge']) || isset($Loc->PrmLst['comm']) ) ) {
1443
		$Loc->PosBeg0 = $Loc->PosBeg;
1444
		$Loc->PosEnd0 = $Loc->PosEnd;
1445
		$enlarge = (isset($Loc->PrmLst['enlarge'])) ? $Loc->PrmLst['enlarge'] : $Loc->PrmLst['comm'];
1446
		if (($enlarge===true) || ($enlarge==='')) {
1447
			$Loc->Enlarged = self::f_Loc_EnlargeToStr($Txt,$Loc,'<!--' ,'-->');
1448
		} else {
1449
			$Loc->Enlarged = self::f_Loc_EnlargeToTag($Txt,$Loc,$enlarge,false);
1450
		}
1451
	}
1452

1453
	return $Loc;
1454

1455
}
1456

1457
/**
1458
 * Note: keep the « & » if the function is called with it.
1459
 *
1460
 * @return object
1461
 */
1462
function meth_Locator_SectionNewBDef(&$LocR,$BlockName,$Txt,$PrmLst,$Cache) {
1463

1464
	$Chk = true;
1465
	$LocLst = array();
1466
	$Pos = 0;
1467
	$Sort = false;
1468
	
1469
	if ($this->_PlugIns_Ok && isset($this->_piOnCacheField)) {
1470
		$pi = true;
1471
		$ArgLst = array(0=>$BlockName, 1=>false, 2=>&$Txt, 3=>array('att'=>true), 4=>&$LocLst, 5=>&$Pos);
1472
	} else {
1473
		$pi = false;
1474
	}
1475

1476
	// Cache TBS locators
1477
	$Cache = ($Cache && $this->TurboBlock);
1478
	if ($Cache) {
1479

1480
		$Chk = false;
1481
		while ($Loc = $this->meth_Locator_FindTbs($Txt,$BlockName,$Pos,'.')) {
1482

1483
			$LocNbr = 1 + count($LocLst);
1484
			$LocLst[$LocNbr] = &$Loc;
1485
			
1486
			// Next search position : always ("original PosBeg" + 1).
1487
			// Must be done here because loc can be moved by the plug-in.
1488
			if ($Loc->Enlarged) {
1489
				// Enlarged
1490
				$Pos = $Loc->PosBeg0 + 1;
1491
				$Loc->Enlarged = false;
1492
			} else {
1493
				// Normal
1494
				$Pos = $Loc->PosBeg + 1;
1495
			}
1496

1497
			// Note: the plug-in may move, delete and add one or several locs.
1498
			// Move   : backward or forward (will be sorted)
1499
			// Delete : add property DelMe=true
1500
			// Add    : at the end of $LocLst (will be sorted)
1501
			if ($pi) {
1502
				$ArgLst[1] = &$Loc;
1503
				$this->meth_Plugin_RunAll($this->_piOnCacheField,$ArgLst);
1504
			}
1505

1506
			if (($Loc->SubName==='#') || ($Loc->SubName==='$')) {
1507
				$Loc->IsRecInfo = true;
1508
				$Loc->RecInfo = $Loc->SubName;
1509
				$Loc->SubName = '';
1510
			} else {
1511
				$Loc->IsRecInfo = false;
1512
			}
1513
			
1514
			// Process parameter att for new added locators.
1515
			$NewNbr = count($LocLst);
1516
			for ($i=$LocNbr;$i<=$NewNbr;$i++) {
1517
				$li = &$LocLst[$i];
1518
				if (isset($li->PrmLst['att'])) {
1519
					$LocSrc = substr($Txt,$li->PosBeg,$li->PosEnd-$li->PosBeg+1); // for error message
1520
					if ($this->f_Xml_AttFind($Txt,$li,$LocLst,$this->AttDelim)) {
1521
						if (isset($Loc->PrmLst['atttrue'])) {
1522
							$li->PrmLst['magnet'] = '#';
1523
							$li->PrmLst['ope'] = (isset($li->PrmLst['ope'])) ? $li->PrmLst['ope'].',attbool' : 'attbool';
1524
						}
1525
						if ($i==$LocNbr) {
1526
							$Pos = $Loc->DelPos;
1527
						}
1528
					} else {
1529
						$this->meth_Misc_Alert('','TBS is not able to merge the field '.$LocSrc.' because the entity targeted by parameter \'att\' cannot be found.');
1530
					}
1531
				}
1532
			}
1533

1534
			unset($Loc);
1535
			
1536
		}
1537

1538
		// Re-order loc
1539
		$e = self::f_Loc_Sort($LocLst, true, 1);
1540
		$Chk = ($e > 0);
1541
		
1542
	}
1543

1544
	// Create the object
1545
	$o = (object) null;
1546
	$o->Prm = $PrmLst;
1547
	$o->LocLst = $LocLst;
1548
	$o->LocNbr = count($LocLst);
1549
	$o->Name = $BlockName;
1550
	$o->Src = $Txt;
1551
	$o->Chk = $Chk;
1552
	$o->IsSerial = false;
1553
	$o->AutoSub = false;
1554
	$i = 1;
1555
	while (isset($PrmLst['sub'.$i])) {
1556
		$o->AutoSub = $i;
1557
		$i++;
1558
	}
1559

1560
	$LocR->BDefLst[] = &$o; // Can be usefull for plug-in
1561
	return $o;
1562

1563
}
1564

1565
/**
1566
 * Add a special section to the LocR.
1567
 *
1568
 * @param object $LocR 
1569
 * @param string $BlockName
1570
 * @param object $BDef 
1571
 * @param string $Field   Name of the field on which the group of values is defined.
1572
 * @param string $FromPrm Parameter that induced the section.
1573
 * 
1574
 * @return object
1575
 */
1576
function meth_Locator_MakeBDefFromField(&$LocR,$BlockName,$Field,$FromPrm) {
1577

1578
	if (strpos($Field,$this->_ChrOpen)===false) {
1579
		// The field is a simple colmun name
1580
		$src = $this->_ChrOpen.$BlockName.'.'.$Field.';tbstype='.$FromPrm.$this->_ChrClose; // tbstype is an internal parameter for catching errors
1581
	} else {
1582
		// The fields is a TBS field's expression
1583
		$src = $Field;
1584
	}
1585
	
1586
	$BDef = $this->meth_Locator_SectionNewBDef($LocR,$BlockName,$src,array(),true);
1587
	
1588
	if ($BDef->LocNbr==0) $this->meth_Misc_Alert('Parameter '.$FromPrm,'The value \''.$Field.'\' is unvalide for this parameter.');
1589

1590
	return $BDef;
1591

1592
}
1593

1594
/**
1595
 * Add a special section to the LocR.
1596
 *
1597
 * @param object $LocR 
1598
 * @param string $BlockName
1599
 * @param object $BDef 
1600
 * @param string $Type    Type of behavior: 'H' header, 'F' footer, 'S' splitter.
1601
 * @param string $Field   Name of the field on which the group of values is defined.
1602
 * @param string $FromPrm Parameter that induced the section.
1603
 */
1604
function meth_Locator_SectionAddGrp(&$LocR,$BlockName,&$BDef,$Type,$Field,$FromPrm) {
1605

1606
	$BDef->PrevValue = false;
1607
	$BDef->Type = $Type; // property not used in native, but designed for plugins
1608

1609
	// Save sub items in a structure near to Locator.
1610
	$BDef->FDef = $this->meth_Locator_MakeBDefFromField($LocR,$BlockName,$Field,$FromPrm);
1611

1612
	if ($Type==='H') {
1613
		// Header behavior
1614
		if ($LocR->HeaderFound===false) {
1615
			$LocR->HeaderFound = true;
1616
			$LocR->HeaderNbr = 0;
1617
			$LocR->HeaderDef = array(); // 1 to HeaderNbr
1618
		}
1619
		$i = ++$LocR->HeaderNbr;
1620
		$LocR->HeaderDef[$i] = $BDef;
1621
	} else {
1622
		// Footer behavior (footer or splitter)
1623
		if ($LocR->FooterFound===false) {
1624
			$LocR->FooterFound = true;
1625
			$LocR->FooterNbr = 0;
1626
			$LocR->FooterDef = array(); // 1 to FooterNbr
1627
		}
1628
		$BDef->AddLastGrp = ($Type==='F');
1629
		$i = ++$LocR->FooterNbr;
1630
		$LocR->FooterDef[$i] = $BDef;
1631
	}
1632

1633
}
1634

1635
/**
1636
 * Merge a locator with a text.
1637
 *
1638
 * @param string $Txt   The source string to modify.
1639
 * @param object $Loc   The locator of the field to replace.
1640
 * @param mixed  $Value The value to merge with.
1641
 * @param integer|false $SubStart The offset of subname to considere.
1642
 *
1643
 * @return integer The position just after the replaced field. Or the position of the start if the replace is canceled.
1644
 *                 This position can be useful because we don't know in advance how $Value will be replaced.
1645
 *                 $Loc->PosNext is also set to the next search position when embedded fields are allowed.
1646
 */
1647
function meth_Locator_Replace(&$Txt,&$Loc,&$Value,$SubStart) {
1648

1649
	// Found the value if there is a subname
1650
	if (($SubStart!==false) && $Loc->SubOk) {
1651
		for ($i=$SubStart;$i<$Loc->SubNbr;$i++) {
1652
			$x = $Loc->SubLst[$i]; // &$Loc... brings an error with Event Example, I don't know why.
1653
			if (is_array($Value)) {
1654
				if (isset($Value[$x])) {
1655
					$Value = &$Value[$x];
1656
				} elseif (array_key_exists($x,$Value)) {// can happens when value is NULL
1657
					$Value = &$Value[$x];
1658
				} else {
1659
					if (!isset($Loc->PrmLst['noerr'])) $this->meth_Misc_Alert($Loc,'item \''.$x.'\' is not an existing key in the array.',true);
1660
					unset($Value); $Value = ''; break;
1661
				}
1662
			} elseif (is_object($Value)) {
1663
				$form = $this->f_Misc_ParseFctForm($x);
1664
				$n = $form['name'];
1665
				if ( method_exists($Value,$n) || ($form['as_fct'] && method_exists($Value,'__call')) ) {
1666
					if ($this->MethodsAllowed || !in_array(strtok($Loc->FullName,'.'),array('onload','onshow','var')) ) {
1667
						$x = call_user_func_array(array(&$Value,$n),$form['args']);
1668
					} else {
1669
						if (!isset($Loc->PrmLst['noerr'])) $this->meth_Misc_Alert($Loc,'\''.$n.'\' is a method and the current TBS settings do not allow to call methods on automatic fields.',true);
1670
						$x = '';	
1671
					}
1672
				} elseif (property_exists($Value,$n)) {
1673
					$x = &$Value->$n;
1674
				} elseif (isset($Value->$n)) {
1675
					$x = $Value->$n; // useful for overloaded property
1676
				} else {
1677
					if (!isset($Loc->PrmLst['noerr'])) $this->meth_Misc_Alert($Loc,'item '.$n.'\' is neither a method nor a property in the class \''.get_class($Value).'\'. Overloaded properties must also be available for the __isset() magic method.',true);
1678
					unset($Value); $Value = ''; break;
1679
				}
1680
				$Value = &$x; unset($x); $x = '';
1681
			} else {
1682
				if (!isset($Loc->PrmLst['noerr'])) $this->meth_Misc_Alert($Loc,'item before \''.$x.'\' is neither an object nor an array. Its type is '.gettype($Value).'.',true);
1683
				unset($Value); $Value = ''; break;
1684
			}
1685
		}
1686
	}
1687

1688
	$CurrVal = $Value; // Unlink
1689
	
1690
	if (isset($Loc->PrmLst['onformat'])) {
1691
		if ($Loc->FirstMerge) {
1692
			$Loc->OnFrmInfo = $Loc->PrmLst['onformat'];
1693
			$Loc->OnFrmArg = array($Loc->FullName,'',&$Loc->PrmLst,&$this);
1694
			$ErrMsg = false;
1695
			if (!$this->meth_Misc_UserFctCheck($Loc->OnFrmInfo,'f',$ErrMsg,$ErrMsg,true)) {
1696
				unset($Loc->PrmLst['onformat']);
1697
				if (!isset($Loc->PrmLst['noerr'])) $this->meth_Misc_Alert($Loc,'(parameter onformat) '.$ErrMsg);
1698
				$Loc->OnFrmInfo = false; 
1699
			}
1700
		} else {
1701
			$Loc->OnFrmArg[3] = &$this; // bugs.php.net/51174
1702
		}
1703
		if ($Loc->OnFrmInfo !== false) {
1704
			$Loc->OnFrmArg[1] = &$CurrVal;
1705
			if (isset($Loc->PrmLst['subtpl'])) {
1706
				$this->meth_Misc_ChangeMode(true,$Loc,$CurrVal);
1707
				call_user_func_array($Loc->OnFrmInfo,$Loc->OnFrmArg);
1708
				$this->meth_Misc_ChangeMode(false,$Loc,$CurrVal);
1709
				$Loc->ConvProtect = false;
1710
				$Loc->ConvStr = false;
1711
			} else {
1712
				call_user_func_array($Loc->OnFrmInfo,$Loc->OnFrmArg);
1713
			}
1714
		}
1715
	}
1716

1717
	if ($Loc->FirstMerge) {
1718
		if (isset($Loc->PrmLst['frm'])) {
1719
			$Loc->ConvMode = 0; // Frm
1720
			$Loc->ConvProtect = false;
1721
		} else {
1722
			// Analyze parameter 'strconv'
1723
			if (isset($Loc->PrmLst['strconv'])) {
1724
				$this->meth_Conv_Prepare($Loc, $Loc->PrmLst['strconv']);
1725
			} elseif (isset($Loc->PrmLst['htmlconv'])) { // compatibility
1726
				$this->meth_Conv_Prepare($Loc, $Loc->PrmLst['htmlconv']);
1727
			} else {
1728
				if ($this->Charset===false) $Loc->ConvStr = false; // No conversion
1729
			}
1730
			// Analyze parameter 'protect'
1731
			if (isset($Loc->PrmLst['protect'])) {
1732
				$x = strtolower($Loc->PrmLst['protect']);
1733
				if ($x==='no') {
1734
					$Loc->ConvProtect = false;
1735
				} elseif ($x==='yes') {
1736
					$Loc->ConvProtect = true;
1737
				}
1738
			} elseif ($this->Protect===false) {
1739
				$Loc->ConvProtect = false;
1740
			}
1741
		}
1742
		if ($Loc->Ope = isset($Loc->PrmLst['ope'])) {
1743
			$OpeLst = explode(',',$Loc->PrmLst['ope']);
1744
			$Loc->OpeAct = array();
1745
			$Loc->OpeArg = array();
1746
			$Loc->OpeUtf8 = false;
1747
			foreach ($OpeLst as $i=>$ope) {
1748
				if ($ope==='list') {
1749
					$Loc->OpeAct[$i] = 1;
1750
					$Loc->OpePrm[$i] = (isset($Loc->PrmLst['valsep'])) ? $Loc->PrmLst['valsep'] : ',';
1751
					if (($Loc->ConvMode===1) && $Loc->ConvStr) $Loc->ConvMode = -1; // special mode for item list conversion
1752
				} elseif ($ope==='minv') {
1753
					$Loc->OpeAct[$i] = 11;
1754
					$Loc->MSave = $Loc->MagnetId;
1755
				} elseif ($ope==='attbool') { // this operation key is set when a loc is cached with paremeter atttrue
1756
					$Loc->OpeAct[$i] = 14;
1757
				} elseif ($ope==='utf8')  { $Loc->OpeUtf8 = true;
1758
				} elseif ($ope==='upper') { $Loc->OpeAct[$i] = 15;
1759
				} elseif ($ope==='lower') { $Loc->OpeAct[$i] = 16;
1760
				} elseif ($ope==='upper1') { $Loc->OpeAct[$i] = 17;
1761
				} elseif ($ope==='upperw') { $Loc->OpeAct[$i] = 18;
1762
				} else {
1763
					$x = substr($ope,0,4);
1764
					if ($x==='max:') {
1765
						$Loc->OpeAct[$i] = (isset($Loc->PrmLst['maxhtml'])) ? 2 : 3;
1766
						if (isset($Loc->PrmLst['maxutf8'])) $Loc->OpeUtf8 = true;
1767
						$Loc->OpePrm[$i] = intval(trim(substr($ope,4)));
1768
						$Loc->OpeEnd = (isset($Loc->PrmLst['maxend'])) ? $Loc->PrmLst['maxend'] : '...';
1769
						if ($Loc->OpePrm[$i]<=0) $Loc->Ope = false;
1770
					} elseif ($x==='mod:') {$Loc->OpeAct[$i] = 5; $Loc->OpePrm[$i] = '0'+trim(substr($ope,4));
1771
					} elseif ($x==='add:') {$Loc->OpeAct[$i] = 6; $Loc->OpePrm[$i] = '0'+trim(substr($ope,4));
1772
					} elseif ($x==='mul:') {$Loc->OpeAct[$i] = 7; $Loc->OpePrm[$i] = '0'+trim(substr($ope,4));
1773
					} elseif ($x==='div:') {$Loc->OpeAct[$i] = 8; $Loc->OpePrm[$i] = '0'+trim(substr($ope,4));
1774
					} elseif ($x==='mok:') {$Loc->OpeAct[$i] = 9; $Loc->OpeMOK[] = trim(substr($ope,4)); $Loc->MSave = $Loc->MagnetId;
1775
					} elseif ($x==='mko:') {$Loc->OpeAct[$i] =10; $Loc->OpeMKO[] = trim(substr($ope,4)); $Loc->MSave = $Loc->MagnetId;
1776
					} elseif ($x==='nif:') {$Loc->OpeAct[$i] =12; $Loc->OpePrm[$i] = trim(substr($ope,4));
1777
					} elseif ($x==='msk:') {$Loc->OpeAct[$i] =13; $Loc->OpePrm[$i] = trim(substr($ope,4));
1778
					} elseif (isset($this->_piOnOperation)) {
1779
						$Loc->OpeAct[$i] = 0;
1780
						$Loc->OpePrm[$i] = $ope;
1781
						$Loc->OpeArg[$i] = array($Loc->FullName,&$CurrVal,&$Loc->PrmLst,&$Txt,$Loc->PosBeg,$Loc->PosEnd,&$Loc);
1782
						$Loc->PrmLst['_ope'] = $Loc->PrmLst['ope'];
1783
					} elseif (!isset($Loc->PrmLst['noerr'])) {
1784
						$this->meth_Misc_Alert($Loc,'parameter ope doesn\'t support value \''.$ope.'\'.',true);
1785
					}
1786
				}
1787
			}
1788
		}
1789
		$Loc->FirstMerge = false;
1790
	}
1791
	$ConvProtect = $Loc->ConvProtect;
1792

1793
	// Plug-in OnFormat
1794
	if ($this->_piOnFrm_Ok) {
1795
		if (isset($Loc->OnFrmArgPi)) {
1796
			$Loc->OnFrmArgPi[1] = &$CurrVal;
1797
			$Loc->OnFrmArgPi[3] = &$this; // bugs.php.net/51174
1798
		} else {
1799
			$Loc->OnFrmArgPi = array($Loc->FullName,&$CurrVal,&$Loc->PrmLst,&$this);
1800
		}
1801
		$this->meth_PlugIn_RunAll($this->_piOnFormat,$Loc->OnFrmArgPi);
1802
	}
1803

1804
	// Operation
1805
	if ($Loc->Ope) {
1806
		foreach ($Loc->OpeAct as $i=>$ope) {
1807
			switch ($ope) {
1808
			case 0:
1809
				$Loc->PrmLst['ope'] = $Loc->OpePrm[$i]; // for compatibility
1810
				$OpeArg = &$Loc->OpeArg[$i];
1811
				$OpeArg[1] = &$CurrVal; $OpeArg[3] = &$Txt;
1812
				if (!$this->meth_PlugIn_RunAll($this->_piOnOperation,$OpeArg)) {
1813
					$Loc->PosNext = $Loc->PosBeg + 1; // +1 in order to avoid infinit loop
1814
					return $Loc->PosNext;
1815
				}
1816
				break;
1817
			case  1:
1818
				if ($Loc->ConvMode===-1) {
1819
					if (is_array($CurrVal)) {
1820
						foreach ($CurrVal as $k=>$v) {
1821
							$v = $this->meth_Misc_ToStr($v);
1822
							$this->meth_Conv_Str($v,$Loc->ConvBr);
1823
							$CurrVal[$k] = $v;
1824
						}
1825
						$CurrVal = implode($Loc->OpePrm[$i],$CurrVal);
1826
					} else {
1827
						$CurrVal = $this->meth_Misc_ToStr($CurrVal);
1828
						$this->meth_Conv_Str($CurrVal,$Loc->ConvBr);
1829
					}
1830
				} else {
1831
					if (is_array($CurrVal)) $CurrVal = implode($Loc->OpePrm[$i],$CurrVal);
1832
				}
1833
				break;
1834
			case  2:
1835
				$x = $this->meth_Misc_ToStr($CurrVal);
1836
				if (strlen($x)>$Loc->OpePrm[$i]) {
1837
					$this->f_Xml_Max($x,$Loc->OpePrm[$i],$Loc->OpeEnd);
1838
				}
1839
				break;
1840
			case  3:
1841
				$x = $this->meth_Misc_ToStr($CurrVal);
1842
				if (strlen($x)>$Loc->OpePrm[$i]) {
1843
					if ($Loc->OpeUtf8) {
1844
						$CurrVal = mb_substr($x,0,$Loc->OpePrm[$i],'UTF-8').$Loc->OpeEnd;
1845
					} else {
1846
						$CurrVal = substr($x,0,$Loc->OpePrm[$i]).$Loc->OpeEnd;
1847
					}
1848
				}
1849
				break;
1850
			case  5: $CurrVal = ('0'+$CurrVal) % $Loc->OpePrm[$i]; break;
1851
			case  6: $CurrVal = ('0'+$CurrVal) + $Loc->OpePrm[$i]; break;
1852
			case  7: $CurrVal = ('0'+$CurrVal) * $Loc->OpePrm[$i]; break;
1853
			case  8: $CurrVal = ('0'+$CurrVal) / $Loc->OpePrm[$i]; break;
1854
			case  9; case 10:
1855
				if ($ope===9) {
1856
				 $CurrVal = (in_array($this->meth_Misc_ToStr($CurrVal),$Loc->OpeMOK)) ? ' ' : '';
1857
				} else {
1858
				 $CurrVal = (in_array($this->meth_Misc_ToStr($CurrVal),$Loc->OpeMKO)) ? '' : ' ';
1859
				} // no break here
1860
			case 11:
1861
				if ($this->meth_Misc_ToStr($CurrVal)==='') {
1862
					if ($Loc->MagnetId===0) $Loc->MagnetId = $Loc->MSave;
1863
				} else {
1864
					if ($Loc->MagnetId!==0) {
1865
						$Loc->MSave = $Loc->MagnetId;
1866
						$Loc->MagnetId = 0;
1867
					}
1868
					$CurrVal = '';
1869
				}
1870
				break;
1871
			case 12: if ($this->meth_Misc_ToStr($CurrVal)===$Loc->OpePrm[$i]) $CurrVal = ''; break;
1872
			case 13: $CurrVal = str_replace('*',$CurrVal,$Loc->OpePrm[$i]); break;
1873
			case 14: $CurrVal = self::f_Loc_AttBoolean($CurrVal, $Loc->PrmLst['atttrue'], $Loc->AttName); break;
1874
			case 15: $CurrVal = ($Loc->OpeUtf8) ? mb_convert_case($CurrVal, MB_CASE_UPPER, 'UTF-8') : strtoupper($CurrVal); break;
1875
			case 16: $CurrVal = ($Loc->OpeUtf8) ? mb_convert_case($CurrVal, MB_CASE_LOWER, 'UTF-8') : strtolower($CurrVal); break;
1876
			case 17: $CurrVal = ucfirst($CurrVal); break;
1877
			case 18: $CurrVal = ($Loc->OpeUtf8) ? mb_convert_case($CurrVal, MB_CASE_TITLE, 'UTF-8') : ucwords(strtolower($CurrVal)); break;
1878
			}
1879
		}
1880
	}
1881

1882
	// String conversion or format
1883
	if ($Loc->ConvMode===1) { // Usual string conversion
1884
		$CurrVal = $this->meth_Misc_ToStr($CurrVal);
1885
		if ($Loc->ConvStr) $this->meth_Conv_Str($CurrVal,$Loc->ConvBr);
1886
	} elseif ($Loc->ConvMode===0) { // Format
1887
		$CurrVal = $this->meth_Misc_Format($CurrVal,$Loc->PrmLst);
1888
	} elseif ($Loc->ConvMode===2) { // Special string conversion
1889
		$CurrVal = $this->meth_Misc_ToStr($CurrVal);
1890
		if ($Loc->ConvStr) $this->meth_Conv_Str($CurrVal,$Loc->ConvBr);
1891
		if ($Loc->ConvEsc) $CurrVal = str_replace('\'','\'\'',$CurrVal);
1892
		if ($Loc->ConvWS) {
1893
			$check = '  ';
1894
			$nbsp = '&nbsp;';
1895
			do {
1896
				$pos = strpos($CurrVal,$check);
1897
				if ($pos!==false) $CurrVal = substr_replace($CurrVal,$nbsp,$pos,1);
1898
			} while ($pos!==false);
1899
		}
1900
		if ($Loc->ConvJS) {
1901
			$CurrVal = addslashes($CurrVal); // apply to ('), ("), (\) and (null)
1902
			$CurrVal = str_replace(array("\n","\r","\t"),array('\n','\r','\t'),$CurrVal);
1903
		}
1904
		if ($Loc->ConvUrl) $CurrVal = urlencode($CurrVal);
1905
		if ($Loc->ConvUtf8) $CurrVal = iconv('ISO-8859-1', 'UTF-8', $CurrVal);
1906
	}
1907

1908
	// if/then/else process, there may be several if/then
1909
	if ($Loc->PrmIfNbr) {
1910
		$z = false;
1911
		$i = 1;
1912
		while ($i!==false) {
1913
			if ($Loc->PrmIfVar[$i]) $Loc->PrmIfVar[$i] = $this->meth_Merge_AutoVar($Loc->PrmIf[$i],true);
1914
			$x = str_replace($this->_ChrVal,$CurrVal,$Loc->PrmIf[$i]);
1915
			if ($this->f_Misc_CheckCondition($x)) {
1916
				if (isset($Loc->PrmThen[$i])) {
1917
					if ($Loc->PrmThenVar[$i]) $Loc->PrmThenVar[$i] = $this->meth_Merge_AutoVar($Loc->PrmThen[$i],true);
1918
					$z = $Loc->PrmThen[$i];
1919
				}
1920
				$i = false;
1921
			} else {
1922
				$i++;
1923
				if ($i>$Loc->PrmIfNbr) {
1924
					if (isset($Loc->PrmLst['else'])) {
1925
						if ($Loc->PrmElseVar) $Loc->PrmElseVar = $this->meth_Merge_AutoVar($Loc->PrmLst['else'],true);
1926
						$z =$Loc->PrmLst['else'];
1927
					}
1928
					$i = false;
1929
				}
1930
			}
1931
		}
1932
		if ($z!==false) {
1933
			if ($ConvProtect) {
1934
				$CurrVal = str_replace($this->_ChrOpen,$this->_ChrProtect,$CurrVal); // TBS protection
1935
				$ConvProtect = false;
1936
			}
1937
			$CurrVal = str_replace($this->_ChrVal,$CurrVal,$z);
1938
		}
1939
	}
1940

1941
	$IsTpl = false; // Indicates is $CurrVal is a sub-template
1942

1943
	if (isset($Loc->PrmLst['file'])) {
1944
		$x = $Loc->PrmLst['file'];
1945
		if ($x===true) $x = $CurrVal;
1946
		$this->meth_Merge_AutoVar($x,false);
1947
		$x = trim(str_replace($this->_ChrVal,$CurrVal,$x));
1948
		$CurrVal = '';
1949
		if ($x!=='') {
1950
			if ($this->f_Misc_GetFile($CurrVal, $x, $this->_LastFile, $this->IncludePath)) {
1951
				$this->meth_Locator_PartAndRename($CurrVal, $Loc->PrmLst);
1952
				$IsTpl = true;
1953
			} else {
1954
				if (!isset($Loc->PrmLst['noerr'])) $this->meth_Misc_Alert($Loc,'the file \''.$x.'\' given by parameter file is not found or not readable.',true);
1955
			}
1956
			$ConvProtect = false;
1957
		}
1958
	}
1959

1960
	if (isset($Loc->PrmLst['script'])) {// Include external PHP script
1961
		$x = $Loc->PrmLst['script'];
1962
		if ($this->ScriptsAllowed) {
1963
			if ($x===true) $x = $CurrVal;
1964
			$this->meth_Merge_AutoVar($x,false);
1965
			$x = trim(str_replace($this->_ChrVal,$CurrVal,$x));
1966
			if (basename($x) == basename($this->_LastFile)) {
1967
				if (!isset($Loc->PrmLst['noerr'])) $this->meth_Misc_Alert($Loc,'the file \''.$x.'\' given by parameter script cannot be called because it has the same name as the current template and this is suspicious.',true);
1968
				$x= '';
1969
			}
1970
		} else {
1971
			if (!isset($Loc->PrmLst['noerr'])) $this->meth_Misc_Alert($Loc,'parameter \'script\' is forbidden by default. It can be allowed by a TBS option.',true);
1972
			$x = '';	
1973
		}
1974
		if ($x!=='') {
1975
			$this->_Subscript = $x;
1976
			$this->CurrPrm = &$Loc->PrmLst;
1977
			$sub = isset($Loc->PrmLst['subtpl']);
1978
			if ($sub) $this->meth_Misc_ChangeMode(true,$Loc,$CurrVal);
1979
			if ($this->meth_Misc_RunSubscript($CurrVal,$Loc->PrmLst)===false) {
1980
				if (!isset($Loc->PrmLst['noerr'])) $this->meth_Misc_Alert($Loc,'the file \''.$x.'\' given by parameter script is not found or not readable.',true);
1981
			}
1982
			if ($sub) $this->meth_Misc_ChangeMode(false,$Loc,$CurrVal);
1983
			$this->meth_Locator_PartAndRename($CurrVal, $Loc->PrmLst);
1984
			$IsTpl = true;
1985
			unset($this->CurrPrm);
1986
			$ConvProtect = false;
1987
		}
1988
	}
1989

1990
	if (isset($Loc->PrmLst['att'])) {
1991
		$this->f_Xml_AttFind($Txt,$Loc,true,$this->AttDelim);
1992
		if (isset($Loc->PrmLst['atttrue'])) {
1993
			$CurrVal = self::f_Loc_AttBoolean($CurrVal, $Loc->PrmLst['atttrue'], $Loc->AttName);
1994
			$Loc->PrmLst['magnet'] = '#';
1995
		}
1996
	}
1997

1998
	// Case when it's an empty string
1999
	if ($CurrVal==='') {
2000

2001
		if ($Loc->MagnetId===false) {
2002
			if (isset($Loc->PrmLst['.'])) {
2003
				$Loc->MagnetId = -1;
2004
			} elseif (isset($Loc->PrmLst['ifempty'])) {
2005
				$Loc->MagnetId = -2;
2006
			} elseif (isset($Loc->PrmLst['magnet'])) {
2007
				$Loc->MagnetId = 1;
2008
				$Loc->PosBeg0 = $Loc->PosBeg;
2009
				$Loc->PosEnd0 = $Loc->PosEnd;
2010
				if ($Loc->PrmLst['magnet']==='#') {
2011
					if (!isset($Loc->AttBeg)) {
2012
						$Loc->PrmLst['att'] = '.';
2013
						// no moving because att info would be modified and thus become wrong regarding to the eventually cached source
2014
						$this->f_Xml_AttFind($Txt,$Loc,false,$this->AttDelim);
2015
					}
2016
					if (isset($Loc->AttBeg)) {
2017
						$Loc->MagnetId = -3;
2018
					} else {
2019
						$this->meth_Misc_Alert($Loc,'parameter \'magnet=#\' cannot be processed because the corresponding attribute is not found.',true);
2020
					}
2021
				} elseif (isset($Loc->PrmLst['mtype'])) {
2022
					switch ($Loc->PrmLst['mtype']) {
2023
					case 'm+m': $Loc->MagnetId = 2; break;
2024
					case 'm*': $Loc->MagnetId = 3; break;
2025
					case '*m': $Loc->MagnetId = 4; break;
2026
					}
2027
				}
2028
			} elseif (isset($Loc->PrmLst['attadd'])) {
2029
				// In order to delete extra space
2030
				$Loc->PosBeg0 = $Loc->PosBeg;
2031
				$Loc->PosEnd0 = $Loc->PosEnd;
2032
				$Loc->MagnetId = 5;
2033
			} else {
2034
				$Loc->MagnetId = 0;
2035
			}
2036
		}
2037

2038
		switch ($Loc->MagnetId) {
2039
		case 0: break;
2040
		case -1: $CurrVal = '&nbsp;'; break; // Enables to avoid null cells in HTML tables
2041
		case -2: $CurrVal = $Loc->PrmLst['ifempty']; break;
2042
		case -3:
2043
			// magnet=#
2044
			$Loc->Enlarged = true;
2045
			$Loc->PosBeg = ($Txt[$Loc->AttBeg-1]===' ') ? $Loc->AttBeg-1 : $Loc->AttBeg;
2046
			$Loc->PosEnd = $Loc->AttEnd;
2047
			break;
2048
		case 1:
2049
			$Loc->Enlarged = true;
2050
			$this->f_Loc_EnlargeToTag($Txt,$Loc,$Loc->PrmLst['magnet'],false);
2051
			break;
2052
		case 2:
2053
			$Loc->Enlarged = true;
2054
			$CurrVal = $this->f_Loc_EnlargeToTag($Txt,$Loc,$Loc->PrmLst['magnet'],true);
2055
			break;
2056
		case 3:
2057
			$Loc->Enlarged = true;
2058
			$Loc2 = $this->f_Xml_FindTag($Txt,$Loc->PrmLst['magnet'],true,$Loc->PosBeg,false,false,false);
2059
			if ($Loc2!==false) {
2060
				$Loc->PosBeg = $Loc2->PosBeg;
2061
				if ($Loc->PosEnd<$Loc2->PosEnd) $Loc->PosEnd = $Loc2->PosEnd;
2062
			}
2063
			break;
2064
		case 4:
2065
			$Loc->Enlarged = true;
2066
			$Loc2 = $this->f_Xml_FindTag($Txt,$Loc->PrmLst['magnet'],true,$Loc->PosBeg,true,false,false);
2067
			if ($Loc2!==false) $Loc->PosEnd = $Loc2->PosEnd;
2068
			break;
2069
		case 5:
2070
			$Loc->Enlarged = true;
2071
			if (substr($Txt,$Loc->PosBeg-1,1)==' ') $Loc->PosBeg--;
2072
			break;
2073
		}
2074
		$NewEnd = $Loc->PosBeg; // Useful when mtype='m+m'
2075
	} else {
2076

2077
		if ($ConvProtect) $CurrVal = str_replace($this->_ChrOpen,$this->_ChrProtect,$CurrVal); // TBS protection
2078
		$NewEnd = $Loc->PosBeg + ($IsTpl ? 0 : strlen($CurrVal));
2079

2080
	}
2081

2082
	$Txt = substr_replace($Txt,$CurrVal,$Loc->PosBeg,$Loc->PosEnd-$Loc->PosBeg+1);
2083
	
2084
	$Loc->PosNext = $NewEnd;
2085
	return $NewEnd; // Return the new end position of the field
2086

2087
}
2088

2089
/**
2090
 * Return the first block locator just after the PosBeg position
2091
 *
2092
 * @param integer $Mode 
2093
 *                1 : Merge_Auto => doesn't save $Loc->BlockSrc, save the bounds of TBS Def tags instead, return also fields
2094
 *                2 : FindBlockLst or GetBlockSource => save $Loc->BlockSrc without TBS Def tags
2095
 *                3 : GetBlockSource => save $Loc->BlockSrc with TBS Def tags
2096
 */
2097
function meth_Locator_FindBlockNext(&$Txt,$BlockName,$PosBeg,$ChrSub,$Mode,&$P1,&$FieldBefore) {
2098

2099
	$SearchDef = true;
2100
	$FirstField = false;
2101
	// Search for the first tag with parameter "block"
2102
	while ($SearchDef && ($Loc = $this->meth_Locator_FindTbs($Txt,$BlockName,$PosBeg,$ChrSub))) {
2103
		if (isset($Loc->PrmLst['block'])) {
2104
			if (isset($Loc->PrmLst['p1'])) {
2105
				if ($P1) return false;
2106
				$P1 = true;
2107
			}
2108
			$Block = $Loc->PrmLst['block'];
2109
			$SearchDef = false;
2110
		} elseif ($Mode===1) {
2111
			return $Loc;
2112
		} elseif ($FirstField===false) {
2113
			$FirstField = $Loc;
2114
		}
2115
		$PosBeg = $Loc->PosEnd;
2116
	}
2117

2118
	if ($SearchDef) {
2119
		if ($FirstField!==false) $FieldBefore = true;
2120
		return false;
2121
	}
2122

2123
	$Loc->PosDefBeg = -1;
2124

2125
	if ($Block==='begin') { // Block definied using begin/end
2126

2127
		if (($FirstField!==false) && ($FirstField->PosEnd<$Loc->PosBeg)) $FieldBefore = true;
2128

2129
		$Opened = 1;
2130
		while ($Loc2 = $this->meth_Locator_FindTbs($Txt,$BlockName,$PosBeg,$ChrSub)) {
2131
			if (isset($Loc2->PrmLst['block'])) {
2132
				switch ($Loc2->PrmLst['block']) {
2133
				case 'end':   $Opened--; break;
2134
				case 'begin': $Opened++; break;
2135
				}
2136
				if ($Opened==0) {
2137
					if ($Mode===1) {
2138
						$Loc->PosBeg2 = $Loc2->PosBeg;
2139
						$Loc->PosEnd2 = $Loc2->PosEnd;
2140
					} else {
2141
						if ($Mode===2) {
2142
							$Loc->BlockSrc = substr($Txt,$Loc->PosEnd+1,$Loc2->PosBeg-$Loc->PosEnd-1);
2143
						} else {
2144
							$Loc->BlockSrc = substr($Txt,$Loc->PosBeg,$Loc2->PosEnd-$Loc->PosBeg+1);
2145
						}
2146
						$Loc->PosEnd = $Loc2->PosEnd;
2147
					}
2148
					$Loc->BlockFound = true;
2149
					return $Loc;
2150
				}
2151
			}
2152
			$PosBeg = $Loc2->PosEnd;
2153
		}
2154

2155
		return $this->meth_Misc_Alert($Loc,'a least one tag with parameter \'block=end\' is missing.',false,'in block\'s definition');
2156

2157
	}
2158

2159
	if ($Mode===1) {
2160
		$Loc->PosBeg2 = false;
2161
	} else {
2162
		$beg = $Loc->PosBeg;
2163
		$end = $Loc->PosEnd;
2164
		if ($this->f_Loc_EnlargeToTag($Txt,$Loc,$Block,false)===false) return $this->meth_Misc_Alert($Loc,'at least one tag corresponding to '.$Loc->PrmLst['block'].' is not found. Check opening tags, closing tags and embedding levels.',false,'in block\'s definition');
2165
		if ($Loc->SubOk || ($Mode===3)) {
2166
			$Loc->BlockSrc = substr($Txt,$Loc->PosBeg,$Loc->PosEnd-$Loc->PosBeg+1);
2167
			$Loc->PosDefBeg = $beg - $Loc->PosBeg;
2168
			$Loc->PosDefEnd = $end - $Loc->PosBeg;
2169
		} else {
2170
			$Loc->BlockSrc = substr($Txt,$Loc->PosBeg,$beg-$Loc->PosBeg).substr($Txt,$end+1,$Loc->PosEnd-$end);
2171
		}
2172
	}
2173

2174
	$Loc->BlockFound = true;
2175
	if (($FirstField!==false) && ($FirstField->PosEnd<$Loc->PosBeg)) $FieldBefore = true;
2176
	return $Loc; // methods return by ref by default
2177

2178
}
2179

2180
function meth_Locator_PartAndRename(&$CurrVal, &$PrmLst) {
2181

2182
	// Store part
2183
	if (isset($PrmLst['store'])) {
2184
		$storename = (isset($PrmLst['storename'])) ? $PrmLst['storename'] : 'default';
2185
		if (!isset($this->TplStore[$storename])) $this->TplStore[$storename] = '';
2186
		$this->TplStore[$storename] .= $this->f_Xml_GetPart($CurrVal, $PrmLst['store'], false);
2187
	}
2188

2189
	// Get part
2190
	if (isset($PrmLst['getpart'])) {
2191
		$part = $PrmLst['getpart'];
2192
	} elseif (isset($PrmLst['getbody'])) {
2193
		$part = $PrmLst['getbody'];
2194
	} else {
2195
		$part = false;
2196
	}
2197
	if ($part!=false) {
2198
		$CurrVal = $this->f_Xml_GetPart($CurrVal, $part, true);
2199
	}
2200

2201
	// Rename or delete TBS tags names
2202
	if (isset($PrmLst['rename'])) {
2203
	
2204
		$Replace = $PrmLst['rename'];
2205

2206
		if (is_string($Replace)) $Replace = explode(',',$Replace);
2207
		foreach ($Replace as $x) {
2208
			if (is_string($x)) $x = explode('=', $x);
2209
			if (count($x)==2) {
2210
				$old = trim($x[0]);
2211
				$new = trim($x[1]);
2212
				if ($old!=='') {
2213
					if ($new==='') {
2214
						$q = false;
2215
						$s = 'clear';
2216
						$this->meth_Merge_Block($CurrVal, $old, $s, $q, false, false, false);
2217
					} else {
2218
						$old = $this->_ChrOpen.$old;
2219
						$old = array($old.'.', $old.' ', $old.';', $old.$this->_ChrClose);
2220
						$new = $this->_ChrOpen.$new;
2221
						$new = array($new.'.', $new.' ', $new.';', $new.$this->_ChrClose);
2222
						$CurrVal = str_replace($old,$new,$CurrVal);
2223
					}
2224
				}
2225
			}
2226
		} 
2227

2228
	}
2229

2230
}
2231

2232
/**
2233
 * Retrieve the list of all sections and their finition for a given block name.
2234
 *
2235
 * @param string  $Txt
2236
 * @param string  $BlockName
2237
 * @param integer $Pos        
2238
 * @param string|false $SpePrm The parameter's name for Special section (used for navigation bar), or false if none.
2239
 *
2240
 * @return object
2241
 */
2242
function meth_Locator_FindBlockLst(&$Txt,$BlockName,$Pos,$SpePrm) {
2243
// Return a locator object covering all block definitions, even if there is no block definition found.
2244

2245
	$LocR = new clsTbsLocator;
2246
	$LocR->P1 = false;
2247
	$LocR->FieldOutside = false;
2248
	$LocR->FOStop = false;
2249
	$LocR->BDefLst = array();
2250

2251
	$LocR->NoData = false;
2252
	$LocR->Special = false;
2253
	$LocR->HeaderFound = false;
2254
	$LocR->FooterFound = false;
2255
	$LocR->SerialEmpty = false;
2256
	$LocR->GrpBreak = false; // Only for plug-ins
2257

2258
	$LocR->BoundFound = false;
2259
	$LocR->CheckNext = false;
2260
	$LocR->CheckPrev = false;
2261
	
2262
	$LocR->WhenFound = false;
2263
	$LocR->WhenDefault = false;
2264

2265
	$LocR->SectionNbr = 0;       // Normal sections
2266
	$LocR->SectionLst = array(); // 1 to SectionNbr
2267

2268
	$BDef = false;
2269
	$ParentLst = array();
2270
	$Pid = 0;
2271

2272
	do {
2273

2274
		if ($BlockName==='') {
2275
			$Loc = false;
2276
		} else {
2277
			$Loc = $this->meth_Locator_FindBlockNext($Txt,$BlockName,$Pos,'.',2,$LocR->P1,$LocR->FieldOutside);
2278
		}
2279

2280
		if ($Loc===false) {
2281

2282
			if ($Pid>0) { // parentgrp mode => disconnect $Txt from the source
2283
				$Parent = &$ParentLst[$Pid];
2284
				$Src = $Txt;
2285
				$Txt = &$Parent->Txt;
2286
				if ($LocR->BlockFound) {
2287
					// Redefine the Header block
2288
					$Parent->Src = substr($Src,0,$LocR->PosBeg);
2289
					// Add a Footer block
2290
					$BDef = $this->meth_Locator_SectionNewBDef($LocR,$BlockName,substr($Src,$LocR->PosEnd+1),$Parent->Prm,true);
2291
					$this->meth_Locator_SectionAddGrp($LocR,$BlockName,$BDef,'F',$Parent->Fld,'parentgrp');
2292
				}
2293
				// Now go down to previous level
2294
				$Pos = $Parent->Pos;
2295
				$LocR->PosBeg = $Parent->Beg;
2296
				$LocR->PosEnd = $Parent->End;
2297
				$LocR->BlockFound = true;
2298
				unset($Parent);
2299
				unset($ParentLst[$Pid]);
2300
				$Pid--;
2301
				$Loc = true;
2302
			}
2303

2304
		} else {
2305

2306
			$Pos = $Loc->PosEnd;
2307

2308
			// Define the block limits
2309
			if ($LocR->BlockFound) {
2310
				if ( $LocR->PosBeg > $Loc->PosBeg ) $LocR->PosBeg = $Loc->PosBeg;
2311
				if ( $LocR->PosEnd < $Loc->PosEnd ) $LocR->PosEnd = $Loc->PosEnd;
2312
			} else {
2313
				$LocR->BlockFound = true;
2314
				$LocR->PosBeg = $Loc->PosBeg;
2315
				$LocR->PosEnd = $Loc->PosEnd;
2316
			}
2317

2318
			// Merge block parameters
2319
			if (count($Loc->PrmLst)>0) $LocR->PrmLst = array_merge($LocR->PrmLst,$Loc->PrmLst);
2320

2321
			// Force dynamic parameter to be cachable
2322
			if ($Loc->PosDefBeg>=0) {
2323
				$dynprm = array('when','headergrp','footergrp','parentgrp');
2324
				foreach($dynprm as $dp) {
2325
					$n = 0;
2326
					if ((isset($Loc->PrmLst[$dp])) && (strpos($Loc->PrmLst[$dp],$this->_ChrOpen.$BlockName)!==false)) {
2327
						$n++;
2328
						if ($n==1) {
2329
							$len = $Loc->PosDefEnd - $Loc->PosDefBeg + 1;
2330
							$x = substr($Loc->BlockSrc,$Loc->PosDefBeg,$len);
2331
						}
2332
						$x = str_replace($Loc->PrmLst[$dp],'',$x);
2333
					}
2334
					if ($n>0) $Loc->BlockSrc = substr_replace($Loc->BlockSrc,$x,$Loc->PosDefBeg,$len);
2335
				}
2336
			}
2337
			// Save the block and cache its tags
2338
			$IsParentGrp = isset($Loc->PrmLst['parentgrp']);
2339
			$BDef = $this->meth_Locator_SectionNewBDef($LocR,$BlockName,$Loc->BlockSrc,$Loc->PrmLst,!$IsParentGrp);
2340

2341
			// Bounds
2342
			$BoundPrm = false;
2343
			$lst = array('firstingrp'=>1, 'lastingrp'=>2, 'singleingrp'=>3); // 1=prev, 2=next, 3=1+2=prev+next
2344
			foreach ($lst as $prm => $chk) {
2345
				if (isset($Loc->PrmLst[$prm])) {
2346
					$BoundPrm = $prm;
2347
					$BoundChk = $chk;
2348
				}
2349
			}
2350

2351
			// Add the text in the list of blocks
2352
			if (isset($Loc->PrmLst['nodata'])) { // Nodata section
2353
				$LocR->NoData = $BDef;
2354
			} elseif (($SpePrm!==false) && isset($Loc->PrmLst[$SpePrm])) { // Special section (used for navigation bar)
2355
				$LocR->Special = $BDef;
2356
			} elseif (isset($Loc->PrmLst['when'])) {
2357
				if ($LocR->WhenFound===false) {
2358
					$LocR->WhenFound = true;
2359
					$LocR->WhenSeveral = false;
2360
					$LocR->WhenNbr = 0;
2361
					$LocR->WhenLst = array();
2362
				}
2363
				$this->meth_Merge_AutoVar($Loc->PrmLst['when'],false);
2364
				$BDef->WhenCond = $this->meth_Locator_SectionNewBDef($LocR,$BlockName,$Loc->PrmLst['when'],array(),true);
2365
				$BDef->WhenBeforeNS = ($LocR->SectionNbr===0); // position of the When section relativley to the Normal Section
2366
				$i = ++$LocR->WhenNbr;
2367
				$LocR->WhenLst[$i] = $BDef;
2368
				if (isset($Loc->PrmLst['several'])) $LocR->WhenSeveral = true;
2369
			} elseif (isset($Loc->PrmLst['default'])) {
2370
				$LocR->WhenDefault = $BDef;
2371
				$LocR->WhenDefaultBeforeNS = ($LocR->SectionNbr===0);
2372
			} elseif (isset($Loc->PrmLst['headergrp'])) {
2373
				$this->meth_Locator_SectionAddGrp($LocR,$BlockName,$BDef,'H',$Loc->PrmLst['headergrp'],'headergrp');
2374
			} elseif (isset($Loc->PrmLst['footergrp'])) {
2375
				$this->meth_Locator_SectionAddGrp($LocR,$BlockName,$BDef,'F',$Loc->PrmLst['footergrp'],'footergrp');
2376
			} elseif (isset($Loc->PrmLst['splittergrp'])) {
2377
				$this->meth_Locator_SectionAddGrp($LocR,$BlockName,$BDef,'S',$Loc->PrmLst['splittergrp'],'splittergrp');
2378
			} elseif ($IsParentGrp) {
2379
				$this->meth_Locator_SectionAddGrp($LocR,$BlockName,$BDef,'H',$Loc->PrmLst['parentgrp'],'parentgrp');
2380
				$BDef->Fld = $Loc->PrmLst['parentgrp'];
2381
				$BDef->Txt = &$Txt;
2382
				$BDef->Pos = $Pos;
2383
				$BDef->Beg = $LocR->PosBeg;
2384
				$BDef->End = $LocR->PosEnd;
2385
				$Pid++;
2386
				$ParentLst[$Pid] = $BDef;
2387
				$Txt = &$BDef->Src;
2388
				$Pos = $Loc->PosDefBeg + 1;
2389
				$LocR->BlockFound = false;
2390
				$LocR->PosBeg = false;
2391
				$LocR->PosEnd = false;
2392
			} elseif (isset($Loc->PrmLst['serial'])) {
2393
				// Section	with serial subsections
2394
				$SrSrc = &$BDef->Src;
2395
				// Search the empty item
2396
				if ($LocR->SerialEmpty===false) {
2397
					$SrName = $BlockName.'_0';
2398
					$x = false;
2399
					$SrLoc = $this->meth_Locator_FindBlockNext($SrSrc,$SrName,0,'.',2,$x,$x);
2400
					if ($SrLoc!==false) {
2401
						$LocR->SerialEmpty = $SrLoc->BlockSrc;
2402
						$SrSrc = substr_replace($SrSrc,'',$SrLoc->PosBeg,$SrLoc->PosEnd-$SrLoc->PosBeg+1);
2403
					}
2404
				}
2405
				$SrName = $BlockName.'_1';
2406
				$x = false;
2407
				$SrLoc = $this->meth_Locator_FindBlockNext($SrSrc,$SrName,0,'.',2,$x,$x);
2408
				if ($SrLoc!==false) {
2409
					$SrId = 1;
2410
					do {
2411
						// Save previous subsection
2412
						$SrBDef = $this->meth_Locator_SectionNewBDef($LocR,$SrName,$SrLoc->BlockSrc,$SrLoc->PrmLst,true);
2413
						$SrBDef->SrBeg = $SrLoc->PosBeg;
2414
						$SrBDef->SrLen = $SrLoc->PosEnd - $SrLoc->PosBeg + 1;
2415
						$SrBDef->SrTxt = false;
2416
						$BDef->SrBDefLst[$SrId] = $SrBDef;
2417
						// Put in order
2418
						$BDef->SrBDefOrdered[$SrId] = $SrBDef;
2419
						$i = $SrId;
2420
						while (($i>1) && ($SrBDef->SrBeg<$BDef->SrBDefOrdered[$SrId-1]->SrBeg)) {
2421
							$BDef->SrBDefOrdered[$i] = $BDef->SrBDefOrdered[$i-1];
2422
							$BDef->SrBDefOrdered[$i-1] = $SrBDef;
2423
							$i--;
2424
						}
2425
						// Search next subsection
2426
						$SrId++;
2427
						$SrName = $BlockName.'_'.$SrId;
2428
						$x = false;
2429
						$SrLoc = $this->meth_Locator_FindBlockNext($SrSrc,$SrName,0,'.',2,$x,$x);
2430
					} while ($SrLoc!==false);
2431
					$BDef->SrBDefNbr = $SrId-1;
2432
					$BDef->IsSerial = true;
2433
					$i = ++$LocR->SectionNbr;
2434
					$LocR->SectionLst[$i] = $BDef;
2435
				}
2436
			} elseif (isset($Loc->PrmLst['parallel'])) {
2437
				$BlockLst = $this->meth_Locator_FindParallel($Txt, $Loc->PosBeg, $Loc->PosEnd, $Loc->PrmLst['parallel']);
2438
				if ($BlockLst) {
2439
					// Store BDefs
2440
					foreach ($BlockLst as $i => $Blk) {
2441
						if ($Blk['IsRef']) {
2442
							$PrBDef = $BDef;
2443
						} else {
2444
							$PrBDef = $this->meth_Locator_SectionNewBDef($LocR,$BlockName,$Blk['Src'],array(),true);
2445
						}
2446
						$PrBDef->PosBeg = $Blk['PosBeg'];
2447
						$PrBDef->PosEnd = $Blk['PosEnd'];
2448
						$i = ++$LocR->SectionNbr;
2449
						$LocR->SectionLst[$i] = $PrBDef;
2450
					}
2451
					$LocR->PosBeg = $BlockLst[0]['PosBeg'];
2452
					$LocR->PosEnd = $BlockLst[$LocR->SectionNbr-1]['PosEnd'];
2453
				}
2454
			} elseif ($BoundPrm !== false) {
2455
				$BDef->BoundExpr = $this->meth_Locator_MakeBDefFromField($LocR,$BlockName,$Loc->PrmLst[$BoundPrm],$BoundPrm);
2456
				$BDef->ValCurr = null;
2457
				$BDef->ValNext = null;
2458
				$BDef->CheckPrev = (($BoundChk & 1) != 0); // bitwise check
2459
				if ($BDef->CheckPrev) {
2460
					$LocR->CheckPrev = true;
2461
					$LocR->ValPrev = null;
2462
				}
2463
				$BDef->CheckNext = (($BoundChk & 2) != 0); // bitwise check
2464
				if ($BDef->CheckNext) {
2465
					$LocR->CheckNext = true;
2466
					$LocR->ValNext = null;
2467
				}
2468
				if (!$LocR->BoundFound) {
2469
					$LocR->BoundFound = true;
2470
					$LocR->BoundLst = array();
2471
					$LocR->BoundNb = 0;
2472
					$LocR->BoundSingleNb = 0;
2473
				}
2474
				if ($BoundChk == 3) {
2475
					// Insert the singleingrp before all other types
2476
					array_splice($LocR->BoundLst, $LocR->BoundSingleNb, 0, array($BDef));
2477
					$LocR->BoundSingleNb++;
2478
				} else {
2479
					// Insert other types at the end
2480
					$LocR->BoundLst[] = $BDef;
2481
				}
2482
				$LocR->BoundNb++;
2483
			} else {
2484
				// Normal section
2485
				$i = ++$LocR->SectionNbr;
2486
				$LocR->SectionLst[$i] = $BDef;
2487
			}
2488

2489
		}
2490

2491
	} while ($Loc!==false);
2492

2493
	if ($LocR->WhenFound && ($LocR->SectionNbr===0)) {
2494
		// Add a blank section if When is used without a normal section
2495
		$BDef = $this->meth_Locator_SectionNewBDef($LocR,$BlockName,'',array(),false);
2496
		$LocR->SectionNbr = 1;
2497
		$LocR->SectionLst[1] = &$BDef;
2498
	}
2499

2500
	return $LocR; // methods return by ref by default
2501

2502
}
2503

2504
function meth_Locator_FindParallel(&$Txt, $ZoneBeg, $ZoneEnd, $ConfId) {
2505

2506
	// Define configurations
2507
	global $_TBS_ParallelLst;
2508

2509
	if ( ($ConfId=='tbs:table')  && (!isset($_TBS_ParallelLst['tbs:table'])) ) {
2510
		$_TBS_ParallelLst['tbs:table'] = array(
2511
			'parent' => 'table',
2512
			'ignore' => array('!--', 'caption', 'thead', 'tbody', 'tfoot'),
2513
			'cols' => array(),
2514
			'rows' => array('tr', 'colgroup'),
2515
			'cells' => array('td'=>'colspan', 'th'=>'colspan', 'col'=>'span'),
2516
		);
2517
	}
2518

2519
	if (!isset($_TBS_ParallelLst[$ConfId])) return $this->meth_Misc_Alert("Parallel", "The configuration '$ConfId' is not found.");
2520

2521
	$conf = $_TBS_ParallelLst[$ConfId];
2522

2523
	$Parent = $conf['parent'];
2524

2525
	// Search parent bounds
2526
	$par_o = self::f_Xml_FindTag($Txt,$Parent,true ,$ZoneBeg,false,1,false);
2527
	if ($par_o===false) return $this->meth_Misc_Alert("Parallel", "The opening tag '$Parent' is not found.");
2528

2529
	$par_c = self::f_Xml_FindTag($Txt,$Parent,false,$ZoneBeg,true,-1,false);
2530
	if ($par_c===false) return $this->meth_Misc_Alert("Parallel", "The closing tag '$Parent' is not found.");
2531

2532
	$SrcPOffset = $par_o->PosEnd + 1;
2533
	$SrcP = substr($Txt, $SrcPOffset, $par_c->PosBeg - $SrcPOffset);
2534

2535
	// temporary variables
2536
	$tagR = '';
2537
	$tagC = '';
2538
	$z = '';
2539
	$pRO  = false;
2540
	$pROe = false;
2541
	$pCO  = false;
2542
	$pCOe = false;
2543
	$p = false;
2544
	$Loc = new clsTbsLocator;
2545

2546
	$Rows  = array();
2547
	$RowIdx = 0;
2548
	$RefRow = false;
2549
	$RefCellB= false;
2550
	$RefCellE = false;
2551
	
2552
	$RowType = array();
2553

2554
	// Loop on entities inside the parent entity
2555
	$PosR = 0;
2556

2557
	$mode_column = true;
2558
	$Cells = array();
2559
	$ColNum = 1;
2560
	$IsRef = false;
2561
	
2562
	// Search for the next Row Opening tag
2563
	while (self::f_Xml_GetNextEntityName($SrcP, $PosR, $tagR, $pRO, $p)) {
2564

2565
		$pROe = strpos($SrcP, '>', $p) + 1;
2566
		$singleR = ($SrcP[$pROe-2] === '/');
2567

2568
		// If the tag is not a closing, a self-closing and has a name
2569
		if ($tagR!=='') {
2570

2571
			if (in_array($tagR, $conf['ignore'])) {
2572
				// This tag must be ignored
2573
				$PosR = $p;
2574
			} elseif (isset($conf['cols'][$tagR])) {
2575
				// Column definition that must be merged as a cell
2576
				if ($mode_column === false)  return $this->meth_Misc_Alert("Parallel", "There is a column definition ($tagR) after a row (".$Rows[$RowIdx-1]['tag'].").");
2577
				if (isset($RowType['_column'])) {
2578
					$RowType['_column']++;
2579
				} else {
2580
					$RowType['_column'] = 1;
2581
				}
2582
				$att = $conf['cols'][$tagR];
2583
				$this->meth_Locator_FindParallelCol($SrcP, $PosR, $tagR, $pRO, $p, $SrcPOffset, $RowIdx, $ZoneBeg, $ZoneEnd, $att, $Loc, $Cells, $ColNum, $IsRef, $RefCellB, $RefCellE, $RefRow);
2584

2585
			} elseif (!$singleR) {
2586

2587
				// Search the Row Closing tag
2588
				$locRE = self::f_Xml_FindTag($SrcP, $tagR, false, $pROe, true, -1, false);
2589
				if ($locRE===false) return $this->meth_Misc_Alert("Parallel", "The row closing tag is not found. (tagR=$tagR, p=$p, pROe=$pROe)");
2590

2591
				// Inner source
2592
				$SrcR = substr($SrcP, $pROe, $locRE->PosBeg - $pROe);
2593
				$SrcROffset = $SrcPOffset + $pROe;
2594

2595
				if (in_array($tagR, $conf['rows'])) {
2596

2597
					if ( $mode_column && isset($RowType['_column']) ) {
2598
						$Rows[$RowIdx] = array('tag'=>'_column', 'cells' => $Cells, 'isref' => $IsRef, 'count' => $RowType['_column']);
2599
						$RowIdx++;
2600
					}
2601

2602
					$mode_column = false;
2603

2604
					if (isset($RowType[$tagR])) {
2605
						$RowType[$tagR]++;
2606
					} else {
2607
						$RowType[$tagR] = 1;
2608
					}
2609

2610
					// Now we've got the row entity, we search for cell entities
2611
					$Cells = array();
2612
					$ColNum = 1;
2613
					$PosC = 0;
2614
					$IsRef = false;
2615

2616
					// Loop on Cell Opening tags
2617
					while (self::f_Xml_GetNextEntityName($SrcR, $PosC, $tagC, $pCO, $p)) {
2618
						if (isset($conf['cells'][$tagC]) ) {
2619
							$att = $conf['cells'][$tagC];
2620
							$this->meth_Locator_FindParallelCol($SrcR, $PosC, $tagC, $pCO, $p, $SrcROffset, $RowIdx, $ZoneBeg, $ZoneEnd, $att, $Loc, $Cells, $ColNum, $IsRef, $RefCellB, $RefCellE, $RefRow);
2621
						} else {
2622
							$PosC = $p;
2623
						}
2624
					}
2625

2626
					$Rows[$RowIdx] = array('tag'=>$tagR, 'cells' => $Cells, 'isref' => $IsRef, 'count' => $RowType[$tagR]);
2627
					$RowIdx++;
2628

2629
				}
2630

2631
				$PosR = $locRE->PosEnd; 
2632

2633
			} else {
2634
				$PosR = $pROe;
2635
			}
2636
		} else {
2637
			$PosR = $pROe;
2638
		}
2639
	}
2640

2641
	//return $Rows;
2642

2643
	$Blocks = array();
2644
	$rMax = count($Rows) -1;
2645
	foreach ($Rows as $r=>$Row) {
2646
		$Cells = $Row['cells'];
2647
		if (isset($Cells[$RefCellB]) && $Cells[$RefCellB]['IsBegin']) {
2648
			if ( isset($Cells[$RefCellE]) &&  $Cells[$RefCellE]['IsEnd'] ) {
2649
				$PosBeg = $Cells[$RefCellB]['PosBeg'];
2650
				$PosEnd = $Cells[$RefCellE]['PosEnd'];
2651
				$Blocks[$r] = array(
2652
					'PosBeg' => $PosBeg,
2653
					'PosEnd' => $PosEnd,
2654
					'IsRef'  => $Row['isref'],
2655
					'Src' => substr($Txt, $PosBeg, $PosEnd - $PosBeg + 1),
2656
				);
2657
			} else {
2658
				return $this->meth_Misc_Alert("Parallel", "At row ".$Row['count']." having entity [".$Row['tag']."], the column $RefCellE is missing or is not the last in a set of spanned columns. (The block is defined from column $RefCellB to $RefCellE)");
2659
			}
2660
		} else {
2661
			return $this->meth_Misc_Alert("Parallel", "At row ".$Row['count']." having entity [".$Row['tag']."],the column $RefCellB is missing or is not the first in a set of spanned columns. (The block is defined from column $RefCellB to $RefCellE)");
2662
		}
2663
	}
2664

2665
	return $Blocks;
2666

2667
}
2668

2669
function meth_Locator_FindParallelCol($SrcR, &$PosC, $tagC, $pCO, $p, $SrcROffset, $RowIdx, $ZoneBeg, $ZoneEnd, &$att, &$Loc, &$Cells, &$ColNum, &$IsRef, &$RefCellB, &$RefCellE, &$RefRow) {
2670

2671
	$pCOe = false;
2672

2673
	// Read parameters
2674
	$Loc->PrmLst = array();
2675
	self::f_Loc_PrmRead($SrcR,$p,true,'\'"','<','>',$Loc,$pCOe,true);
2676

2677
	$singleC = ($SrcR[$pCOe-1] === '/');
2678
	if ($singleC) {
2679
		$pCEe = $pCOe;
2680
	} else {
2681
		// Find the Cell Closing tag
2682
		$locCE = self::f_Xml_FindTag($SrcR, $tagC, false, $pCOe, true, -1, false);
2683
		if ($locCE===false) return $this->meth_Misc_Alert("Parallel", "The cell closing tag is not found. (pCOe=$pCOe)");
2684
		$pCEe = $locCE->PosEnd;
2685
	}
2686
	
2687
	// Check the cell of reference
2688
	$Width = (isset($Loc->PrmLst[$att])) ? intval($Loc->PrmLst[$att]) : 1;
2689
	$ColNumE = $ColNum + $Width -1; // Ending Cell
2690
	$PosBeg = $SrcROffset + $pCO;
2691
	$PosEnd = $SrcROffset + $pCEe;
2692
	$OnZone = false;
2693
	if ( ($PosBeg <= $ZoneBeg) && ($ZoneBeg <= $PosEnd) && ($RefRow===false) ) {
2694
		$RefRow = $RowIdx;
2695
		$RefCellB = $ColNum;
2696
		$OnZone = true;
2697
		$IsRef = true;
2698
	}
2699
	if ( ($PosBeg <= $ZoneEnd) && ($ZoneEnd <= $PosEnd) ) {
2700
		$RefCellE = $ColNum;
2701
		$OnZone = true;
2702
	}
2703
	
2704
	// Save info
2705
	$Cell = array(
2706
		//'_tagR' => $tagR, '_tagC' => $tagC, '_att' => $att, '_OnZone' => $OnZone, '_PrmLst' => $Loc->PrmLst, '_Offset' => $SrcROffset, '_Src' => substr($SrcR, $pCO, $locCE->PosEnd - $pCO + 1),
2707
		'PosBeg' => $PosBeg,
2708
		'PosEnd' => $PosEnd,
2709
		'ColNum' => $ColNum,
2710
		'Width' => $Width,
2711
		'IsBegin' => true,
2712
		'IsEnd' => false,
2713
	);
2714
	$Cells[$ColNum] = $Cell;
2715
	
2716
	// add a virtual column to say if its a ending
2717
	if (!isset($Cells[$ColNumE])) $Cells[$ColNumE] = array('IsBegin' => false);
2718
	
2719
	$Cells[$ColNumE]['IsEnd'] = true;
2720
	$Cells[$ColNumE]['PosEnd'] = $Cells[$ColNum]['PosEnd'];
2721
	
2722
	$PosC = $pCEe;
2723
	$ColNum += $Width;
2724

2725
}
2726

2727
function meth_Merge_Block(&$Txt,$BlockLst,&$SrcId,&$Query,$SpePrm,$SpeRecNum,$QryPrms=false) {
2728

2729
	$BlockSave = $this->_CurrBlock;
2730
	$this->_CurrBlock = $BlockLst;
2731

2732
	// Get source type and info
2733
	$Src = new clsTbsDataSource;
2734
	if (!$Src->DataPrepare($SrcId,$this)) {
2735
		$this->_CurrBlock = $BlockSave;
2736
		return 0;
2737
	}
2738

2739
	if (is_string($BlockLst)) $BlockLst = explode(',', $BlockLst);
2740
	$BlockNbr = count($BlockLst);
2741
	$BlockId = 0;
2742
	$WasP1 = false;
2743
	$NbrRecTot = 0;
2744
	$QueryZ = &$Query;
2745
	$ReturnData = false;
2746
	$Nothing = true;
2747

2748
	while ($BlockId<$BlockNbr) {
2749

2750
		$RecSpe = 0;  // Row with a special block's definition (used for the navigation bar)
2751
		$QueryOk = true;
2752
		$this->_CurrBlock = trim($BlockLst[$BlockId]);
2753
		if ($this->_CurrBlock==='*') {
2754
			$ReturnData = true;
2755
			if ($Src->RecSaved===false) $Src->RecSaving = true;
2756
			$this->_CurrBlock = '';
2757
		}
2758

2759
		// Search the block
2760
		$LocR = $this->meth_Locator_FindBlockLst($Txt,$this->_CurrBlock,0,$SpePrm);
2761

2762
		if ($LocR->BlockFound) {
2763

2764
			$Nothing = false;
2765

2766
			if ($LocR->Special!==false) $RecSpe = $SpeRecNum;
2767
			// OnData
2768
			if ($Src->OnDataPrm = isset($LocR->PrmLst['ondata'])) {
2769
				$Src->OnDataPrmRef = $LocR->PrmLst['ondata'];
2770
				if (isset($Src->OnDataPrmDone[$Src->OnDataPrmRef])) {
2771
					$Src->OnDataPrm = false;
2772
				} else {
2773
					$ErrMsg = false;
2774
					if ($this->meth_Misc_UserFctCheck($Src->OnDataPrmRef,'f',$ErrMsg,$ErrMsg,true)) {
2775
						$Src->OnDataOk = true;
2776
					} else {
2777
						$LocR->FullName = $this->_CurrBlock;
2778
						$Src->OnDataPrm = $this->meth_Misc_Alert($LocR,'(parameter ondata) '.$ErrMsg,false,'block');
2779
					}
2780
				}
2781
			}
2782
			// Dynamic query
2783
			if ($LocR->P1) {
2784
				if ( ($LocR->PrmLst['p1']===true) && ((!is_string($Query)) || (strpos($Query,'%p1%')===false)) ) { // p1 with no value is a trick to perform new block with same name
2785
					if ($Src->RecSaved===false) $Src->RecSaving = true;
2786
				} elseif (is_string($Query)) {
2787
					$Src->RecSaved = false;
2788
					unset($QueryZ); $QueryZ = ''.$Query;
2789
					$i = 1;
2790
					do {
2791
						$x = 'p'.$i;
2792
						if (isset($LocR->PrmLst[$x])) {
2793
							$QueryZ = str_replace('%p'.$i.'%',$LocR->PrmLst[$x],$QueryZ);
2794
							$i++;
2795
						} else {
2796
							$i = false;
2797
						}
2798
					} while ($i!==false);
2799
				}
2800
				$WasP1 = true;
2801
			} elseif (($Src->RecSaved===false) && ($BlockNbr-$BlockId>1)) {
2802
				$Src->RecSaving = true;
2803
			}
2804
		} elseif ($WasP1) {
2805
			$QueryOk = false;
2806
			$WasP1 = false;
2807
		}
2808

2809
		// Open the recordset
2810
		if ($QueryOk) {
2811
			if ((!$LocR->BlockFound) && (!$LocR->FieldOutside)) {
2812
				// Special case: return data without any block to merge
2813
				$QueryOk = false;
2814
				if ($ReturnData && (!$Src->RecSaved)) {
2815
					if ($Src->DataOpen($QueryZ,$QryPrms)) {
2816
						do {$Src->DataFetch();} while ($Src->CurrRec!==false);
2817
						$Src->DataClose();
2818
					}
2819
				}
2820
			}	else {
2821
				$QueryOk = $Src->DataOpen($QueryZ,$QryPrms);
2822
				if (!$QueryOk) {
2823
					if ($WasP1) {	$WasP1 = false;} else {$LocR->FieldOutside = false;} // prevent from infinit loop
2824
				}
2825
			}
2826
		}
2827

2828
		// Merge sections
2829
		if ($QueryOk) {
2830
			if ($Src->Type===2) { // Special for Text merge
2831
				if ($LocR->BlockFound) {
2832
					$Txt = substr_replace($Txt,$Src->RecSet,$LocR->PosBeg,$LocR->PosEnd-$LocR->PosBeg+1);
2833
					$Src->DataFetch(); // store data, may be needed for multiple blocks
2834
					$Src->RecNum = 1;
2835
					$Src->CurrRec = false;
2836
				} else {
2837
					$Src->DataAlert('can\'t merge the block with a text value because the block definition is not found.');
2838
				}
2839
			} elseif ($LocR->BlockFound===false) {
2840
				$Src->DataFetch(); // Merge first record only
2841
			} elseif (isset($LocR->PrmLst['parallel'])) {
2842
				$this->meth_Merge_BlockParallel($Txt,$LocR,$Src);
2843
			} else {
2844
				$this->meth_Merge_BlockSections($Txt,$LocR,$Src,$RecSpe);
2845
			}
2846
			$Src->DataClose(); // Close the resource
2847
		}
2848

2849
		if (!$WasP1) {
2850
			$NbrRecTot += $Src->RecNum;
2851
			$BlockId++;
2852
		}
2853
		if ($LocR->FieldOutside) {
2854
			$Nothing = false;
2855
			$this->meth_Merge_FieldOutside($Txt,$Src->CurrRec,$Src->RecNum,$LocR->FOStop);
2856
		}
2857

2858
	}
2859

2860
	// End of the merge
2861
	unset($LocR);
2862
	$this->_CurrBlock = $BlockSave;
2863
	if ($ReturnData) {
2864
		return $Src->RecSet;
2865
	} else {
2866
		unset($Src);
2867
		return ($Nothing) ? false : $NbrRecTot;
2868
	}
2869

2870
}
2871

2872
function meth_Merge_BlockParallel(&$Txt,&$LocR,&$Src) {
2873

2874
	// Main loop
2875
	$Src->DataFetch();
2876
	
2877
	// Prepare sources
2878
	$BlockRes = array();
2879
	for ($i=1 ; $i<=$LocR->SectionNbr ; $i++) {
2880
		if ($i>1) {
2881
			// Add txt source between the BDefs
2882
			$BlockRes[$i] = substr($Txt, $LocR->SectionLst[$i-1]->PosEnd + 1, $LocR->SectionLst[$i]->PosBeg - $LocR->SectionLst[$i-1]->PosEnd -1); 
2883
		} else {
2884
			$BlockRes[$i] = '';
2885
		}
2886
	}
2887
	
2888
	while($Src->CurrRec!==false) {
2889
		// Merge the current record with all sections
2890
		for ($i=1 ; $i<=$LocR->SectionNbr ; $i++) {
2891
			$SecDef = &$LocR->SectionLst[$i];
2892
			$SecSrc = $this->meth_Merge_SectionNormal($SecDef,$Src);
2893
			$BlockRes[$i] .= $SecSrc;
2894
		}
2895
		// Next row
2896
		$Src->DataFetch();
2897
	}
2898
	
2899
	$BlockRes = implode('', $BlockRes);
2900
	$Txt = substr_replace($Txt,$BlockRes,$LocR->PosBeg,$LocR->PosEnd-$LocR->PosBeg+1);
2901

2902
}
2903

2904
function meth_Merge_BlockSections(&$Txt,&$LocR,&$Src,&$RecSpe) {
2905

2906
	// Initialise
2907
	$SecId = 0;
2908
	$SecOk = ($LocR->SectionNbr>0);
2909
	$SecSrc = '';
2910
	$BlockRes = ''; // The result of the chained merged blocks
2911
	$IsSerial = false;
2912
	$SrId = 0;
2913
	$SrNbr = 0;
2914
	$GrpFound = false;
2915
	if ($LocR->HeaderFound || $LocR->FooterFound) {
2916
		$GrpFound = true;
2917
		$piOMG = false;
2918
		if ($LocR->FooterFound) {
2919
			$Src->PrevSave = true; // $Src->PrevRec will be saved then
2920
		}
2921
	}
2922
	if ($LocR->CheckPrev) $Src->PrevSave = true;
2923
	if ($LocR->CheckNext) $Src->NextSave = true;
2924
	// Plug-ins
2925
	$piOMS = false;
2926
	if ($this->_PlugIns_Ok) {
2927
		if (isset($this->_piBeforeMergeBlock)) {
2928
			$ArgLst = array(&$Txt,&$LocR->PosBeg,&$LocR->PosEnd,$LocR->PrmLst,&$Src,&$LocR);
2929
			$this->meth_Plugin_RunAll($this->_piBeforeMergeBlock,$ArgLst);
2930
		}
2931
		if (isset($this->_piOnMergeSection)) {
2932
			$ArgLst = array(&$BlockRes,&$SecSrc);
2933
			$piOMS = true;
2934
		}
2935
		if ($GrpFound && isset($this->_piOnMergeGroup)) {
2936
			$ArgLst2 = array(0,0,&$Src,&$LocR);
2937
			$piOMG = true;
2938
		}
2939
	}
2940

2941
	// Main loop
2942
	$Src->DataFetch();
2943

2944
	while($Src->CurrRec!==false) {
2945

2946
		// Headers and Footers
2947
		if ($GrpFound) {
2948
			$brk_any = false;
2949
			$brk_src = ''; // concatenated source to insert as of breaked groups (header and footer)
2950
			if ($LocR->FooterFound) {
2951
				$brk = false;
2952
				for ($i=$LocR->FooterNbr;$i>=1;$i--) {
2953
					$GrpDef = &$LocR->FooterDef[$i];
2954
					$x = $this->meth_Merge_SectionNormal($GrpDef->FDef,$Src); // value of the group expression for the current record
2955
					if ($Src->RecNum===1) {
2956
						// no footer break on first record
2957
						$GrpDef->PrevValue = $x;
2958
						$brk_i = false;
2959
					} else {
2960
						// default state for breaked group
2961
						if ($GrpDef->AddLastGrp) {
2962
							$brk_i = &$brk; // cascading breakings
2963
						} else {
2964
							unset($brk_i); $brk_i = false; // independent breaking
2965
						}
2966
						if (!$brk_i) $brk_i = !($GrpDef->PrevValue===$x);
2967
						if ($brk_i) {
2968
							$brk_any = true;
2969
							$ok = true;
2970
							if ($piOMG) {$ArgLst2[0]=&$Src->PrevRec; $ArgLst2[1]=&$GrpDef; $ok = $this->meth_PlugIn_RunAll($this->_piOnMergeGroup,$ArgLst2);}
2971
							if ($ok!==false) $brk_src = $this->meth_Merge_SectionNormal($GrpDef,$Src->PrevRec).$brk_src;
2972
							$GrpDef->PrevValue = $x;
2973
						}
2974
					}
2975
				}
2976
			}
2977
			if ($LocR->HeaderFound) {
2978
				// Check if the current record breaks any header group
2979
				$brk = ($Src->RecNum===1); // there is always a header break on first record
2980
				for ($i=1;$i<=$LocR->HeaderNbr;$i++) {
2981
					$GrpDef = &$LocR->HeaderDef[$i];
2982
					$x = $this->meth_Merge_SectionNormal($GrpDef->FDef,$Src); // value of the group expression for the current record
2983
					if (!$brk) $brk = !($GrpDef->PrevValue===$x); // cascading breakings
2984
					if ($brk) {
2985
						$ok = true;
2986
						if ($piOMG) {$ArgLst2[0]=&$Src; $ArgLst2[1]=&$GrpDef; $ok = $this->meth_PlugIn_RunAll($this->_piOnMergeGroup,$ArgLst2);}
2987
						if ($ok!==false) $brk_src .= $this->meth_Merge_SectionNormal($GrpDef,$Src);
2988
						$GrpDef->PrevValue = $x;
2989
					}
2990
				}
2991
				$brk_any = ($brk_any || $brk);
2992
			}
2993
			if ($brk_any) {
2994
				if ($IsSerial) {
2995
					$BlockRes .= $this->meth_Merge_SectionSerial($SecDef,$SrId,$LocR);
2996
					$IsSerial = false;
2997
				}
2998
				$BlockRes .= $brk_src;
2999
			}
3000
		} // end of header and footer
3001

3002
		// Increment Section
3003
		if (($IsSerial===false) && $SecOk) {
3004
			$SecId++;
3005
			if ($SecId>$LocR->SectionNbr) $SecId = 1;
3006
			$SecDef = &$LocR->SectionLst[$SecId];
3007
			$IsSerial = $SecDef->IsSerial;
3008
			if ($IsSerial) {
3009
				$SrId = 0;
3010
				$SrNbr = $SecDef->SrBDefNbr;
3011
			}
3012
		}
3013

3014
		// Serial Mode Activation
3015
		if ($IsSerial) { // Serial Merge
3016
			$SrId++;
3017
			$SrBDef = &$SecDef->SrBDefLst[$SrId];
3018
			$SrBDef->SrTxt = $this->meth_Merge_SectionNormal($SrBDef,$Src);
3019
			if ($SrId>=$SrNbr) {
3020
				$SecSrc = $this->meth_Merge_SectionSerial($SecDef,$SrId,$LocR);
3021
				$BlockRes .= $SecSrc;
3022
				$IsSerial = false;
3023
			}
3024
		} else { // Classic merge
3025
			if ($SecOk) {
3026
				// There is some normal sections
3027
				if ($Src->RecNum===$RecSpe) {
3028
					$SecDef = &$LocR->Special;
3029
				} elseif ($LocR->BoundFound) {
3030
					$first = true;
3031
					for ($i = 0 ; $i < $LocR->BoundNb ; $i++) {
3032
						// all bounds must be tested in order to update next and prev values, but only the first found must be kept
3033
						if ($this->meth_Merge_CheckBounds($LocR->BoundLst[$i],$Src)) {
3034
							if ($first) $SecDef = &$LocR->BoundLst[$i];
3035
							$first = false;
3036
						}
3037
					}
3038
				}
3039
				$SecSrc = $this->meth_Merge_SectionNormal($SecDef,$Src);
3040
			} else {
3041
				// No normal section
3042
				$SecSrc = '';
3043
			}
3044
			 // Conditional blocks
3045
			if ($LocR->WhenFound) {
3046
				$found = false;
3047
				$continue = true;
3048
				$i = 1;
3049
				do {
3050
					$WhenBDef = &$LocR->WhenLst[$i];
3051
					$cond = $this->meth_Merge_SectionNormal($WhenBDef->WhenCond,$Src); // conditional expression for the current record 
3052
					if ($this->f_Misc_CheckCondition($cond)) {
3053
						$x_when = $this->meth_Merge_SectionNormal($WhenBDef,$Src);
3054
						$SecSrc = ($WhenBDef->WhenBeforeNS) ? $x_when.$SecSrc : $SecSrc.$x_when;
3055
						$found = true;
3056
						if ($LocR->WhenSeveral===false) $continue = false;
3057
					}
3058
					$i++;
3059
					if ($i>$LocR->WhenNbr) $continue = false;
3060
				} while ($continue);
3061
				if (($found===false) && ($LocR->WhenDefault!==false)) {
3062
					$x_when = $this->meth_Merge_SectionNormal($LocR->WhenDefault,$Src);
3063
					$SecSrc = ($LocR->WhenDefaultBeforeNS) ? $x_when.$SecSrc : $SecSrc.$x_when;
3064
				}
3065
			}
3066
			if ($piOMS) $this->meth_PlugIn_RunAll($this->_piOnMergeSection,$ArgLst);
3067
			$BlockRes .= $SecSrc;
3068
		}
3069

3070
		// Next record
3071
		$Src->DataFetch();
3072

3073
	} //--> while($CurrRec!==false) {
3074

3075
	// At this point, all data has been fetched.
3076

3077
	// Source to add after the last record
3078
	$SecSrc = '';
3079

3080
	// Serial: merge the extra the sub-blocks
3081
	if ($IsSerial) $SecSrc .= $this->meth_Merge_SectionSerial($SecDef,$SrId,$LocR);
3082

3083
	// Add all footers after the last record
3084
	if ($LocR->FooterFound) {
3085
		if ($Src->RecNum>0) {
3086
			for ($i=1;$i<=$LocR->FooterNbr;$i++) {
3087
				$GrpDef = &$LocR->FooterDef[$i];
3088
				if ($GrpDef->AddLastGrp) {
3089
					$ok = true;
3090
					if ($piOMG) {$ArgLst2[0]=&$Src->PrevRec; $ArgLst2[1]=&$GrpDef; $ok = $this->meth_PlugIn_RunAll($this->_piOnMergeGroup,$ArgLst2);}
3091
					if ($ok!==false) $SecSrc .= $this->meth_Merge_SectionNormal($GrpDef,$Src->PrevRec);
3092
				}
3093
			}
3094
		}
3095
	}
3096

3097
	// NoData
3098
	if ($Src->RecNum===0) {
3099
		if ($LocR->NoData!==false) {
3100
			$SecSrc = $LocR->NoData->Src;
3101
		} elseif(isset($LocR->PrmLst['bmagnet'])) {
3102
			$this->f_Loc_EnlargeToTag($Txt,$LocR,$LocR->PrmLst['bmagnet'],false);
3103
		}
3104
	}
3105

3106
	// Plug-ins
3107
	if ($piOMS && ($SecSrc!=='')) $this->meth_PlugIn_RunAll($this->_piOnMergeSection,$ArgLst);
3108

3109
	$BlockRes .= $SecSrc;
3110

3111
	// Plug-ins
3112
	if ($this->_PlugIns_Ok && isset($ArgLst) && isset($this->_piAfterMergeBlock)) {
3113
		$ArgLst = array(&$BlockRes,&$Src,&$LocR);
3114
		$this->meth_PlugIn_RunAll($this->_piAfterMergeBlock,$ArgLst);
3115
	}
3116

3117
	// Merge the result
3118
	$Txt = substr_replace($Txt,$BlockRes,$LocR->PosBeg,$LocR->PosEnd-$LocR->PosBeg+1);
3119
	if ($LocR->P1) $LocR->FOStop = $LocR->PosBeg + strlen($BlockRes) -1;
3120

3121
}
3122

3123
function meth_Merge_AutoVar(&$Txt,$ConvStr,$Id='var') {
3124
// Merge automatic fields with VarRef
3125

3126
	$Pref = &$this->VarPrefix;
3127
	$PrefL = strlen($Pref);
3128
	$PrefOk = ($PrefL>0);
3129

3130
	if ($ConvStr===false) {
3131
		$Charset = $this->Charset;
3132
		$this->Charset = false;
3133
	}
3134

3135
	// Then we scann all fields in the model
3136
	$x = '';
3137
	$Pos = 0;
3138
	while ($Loc = $this->meth_Locator_FindTbs($Txt,$Id,$Pos,'.')) {
3139
		if ($Loc->SubNbr==0) $Loc->SubLst[0]=''; // In order to force error message
3140
		if ($Loc->SubLst[0]==='') {
3141
			$Pos = $this->meth_Merge_AutoSpe($Txt,$Loc);
3142
		} elseif ($Loc->SubLst[0][0]==='~') {
3143
			if (!isset($ObjOk)) $ObjOk = (is_object($this->ObjectRef) || is_array($this->ObjectRef));
3144
			if ($ObjOk) {
3145
				$Loc->SubLst[0] = substr($Loc->SubLst[0],1);
3146
				$Pos = $this->meth_Locator_Replace($Txt,$Loc,$this->ObjectRef,0);
3147
			} elseif (isset($Loc->PrmLst['noerr'])) {
3148
				$Pos = $this->meth_Locator_Replace($Txt,$Loc,$x,false);
3149
			} else {
3150
				$this->meth_Misc_Alert($Loc,'property ObjectRef is neither an object nor an array. Its type is \''.gettype($this->ObjectRef).'\'.',true);
3151
				$Pos = $Loc->PosEnd + 1;
3152
			}
3153
		} elseif ($PrefOk && (substr($Loc->SubLst[0],0,$PrefL)!==$Pref)) {
3154
			if (isset($Loc->PrmLst['noerr'])) {
3155
				$Pos = $this->meth_Locator_Replace($Txt,$Loc,$x,false);
3156
			} else {
3157
				$this->meth_Misc_Alert($Loc,'does not match the allowed prefix.',true);
3158
				$Pos = $Loc->PosEnd + 1;
3159
			}
3160
		} elseif ( isset($this->VarRef) && isset($this->VarRef[$Loc->SubLst[0]])) {
3161
			$Pos = $this->meth_Locator_Replace($Txt,$Loc, $this->VarRef[$Loc->SubLst[0]], 1);
3162
		} elseif ( is_null($this->VarRef) && isset($GLOBALS[$Loc->SubLst[0]]) ) {
3163
			$Pos = $this->meth_Locator_Replace($Txt,$Loc, $GLOBALS[$Loc->SubLst[0]], 1);
3164
		} else {
3165
			if (isset($Loc->PrmLst['noerr'])) {
3166
				$Pos = $this->meth_Locator_Replace($Txt,$Loc,$x,false);
3167
			} else {
3168
				$Pos = $Loc->PosEnd + 1;
3169
				$msg = (is_null($this->VarRef)) ? 'VarRef seems refers to $GLOBALS' : 'VarRef seems refers to a custom array of values';
3170
				$this->meth_Misc_Alert($Loc,'the key \''.$Loc->SubLst[0].'\' does not exist or is not set in VarRef. ('.$msg.')',true);
3171
			}
3172
		}
3173
	}
3174

3175
	if ($ConvStr===false) $this->Charset = $Charset;
3176

3177
	return false; // Useful for properties PrmIfVar & PrmThenVar
3178

3179
}
3180

3181
function meth_Merge_AutoSpe(&$Txt,&$Loc) {
3182
// Merge Special Var Fields ([var..*])
3183

3184
	$ErrMsg = false;
3185
	$SubStart = false;
3186
	if (isset($Loc->SubLst[1])) {
3187
		switch ($Loc->SubLst[1]) {
3188
		case 'now': $x = time(); break;
3189
		case 'version': $x = $this->Version; break;
3190
		case 'script_name': $x = basename(((isset($_SERVER)) ? $_SERVER['PHP_SELF'] : $GLOBALS['HTTP_SERVER_VARS']['PHP_SELF'] )); break;
3191
		case 'template_name': $x = $this->_LastFile; break;
3192
		case 'template_date': $x = ''; if ($this->f_Misc_GetFile($x,$this->_LastFile,'',array(),false)) $x = $x['mtime']; break;
3193
		case 'template_path': $x = dirname($this->_LastFile).'/'; break;
3194
		case 'name': $x = 'TinyButStrong'; break;
3195
		case 'logo': $x = '**TinyButStrong**'; break;
3196
		case 'charset': $x = $this->Charset; break;
3197
		case 'error_msg': $this->_ErrMsgName = $Loc->FullName; return $Loc->PosEnd;	break;
3198
		case '': $ErrMsg = 'it doesn\'t have any keyword.'; break;
3199
		case 'tplvars':
3200
			if ($Loc->SubNbr==2) {
3201
				$SubStart = 2;
3202
				$x = implode(',',array_keys($this->TplVars)); // list of all template variables
3203
			} else {
3204
				if (isset($this->TplVars[$Loc->SubLst[2]])) {
3205
					$SubStart = 3;
3206
					$x = &$this->TplVars[$Loc->SubLst[2]];
3207
				} else {
3208
					$ErrMsg = 'property TplVars doesn\'t have any item named \''.$Loc->SubLst[2].'\'.';
3209
				}
3210
			}
3211
			break;
3212
		case 'store':
3213
			if ($Loc->SubNbr==2) {
3214
				$SubStart = 2;
3215
				$x = implode('',$this->TplStore); // concatenation of all stores
3216
			} else {
3217
				if (isset($this->TplStore[$Loc->SubLst[2]])) {
3218
					$SubStart = 3;
3219
					$x = &$this->TplStore[$Loc->SubLst[2]];
3220
				} else {
3221
					$ErrMsg = 'Store named \''.$Loc->SubLst[2].'\' is not defined yet.';
3222
				}
3223
			}
3224
			if (!isset($Loc->PrmLst['strconv'])) {$Loc->PrmLst['strconv'] = 'no'; $Loc->PrmLst['protect'] = 'no';}
3225
			break;
3226
		case 'cst': $x = @constant($Loc->SubLst[2]); break;
3227
		case 'tbs_info':
3228
			$x = 'TinyButStrong version '.$this->Version.' for PHP 5';
3229
			$x .= "\r\nInstalled plug-ins: ".count($this->_PlugIns);
3230
			foreach (array_keys($this->_PlugIns) as $pi) {
3231
				$o = &$this->_PlugIns[$pi];
3232
				$x .= "\r\n- plug-in [".(isset($o->Name) ? $o->Name : $pi ).'] version '.(isset($o->Version) ? $o->Version : '?' );
3233
			}
3234
			break;
3235
		case 'php_info':
3236
			ob_start();
3237
			phpinfo();
3238
			$x = ob_get_contents();
3239
			ob_end_clean();
3240
			$x = self::f_Xml_GetPart($x, '(style)+body', false);
3241
			if (!isset($Loc->PrmLst['strconv'])) {$Loc->PrmLst['strconv'] = 'no'; $Loc->PrmLst['protect'] = 'no';}
3242
			break;
3243
		default:
3244
			$IsSupported = false;
3245
			if (isset($this->_piOnSpecialVar)) {
3246
				$x = '';
3247
				$ArgLst = array(substr($Loc->SubName,1),&$IsSupported ,&$x, &$Loc->PrmLst,&$Txt,&$Loc->PosBeg,&$Loc->PosEnd,&$Loc);
3248
				$this->meth_PlugIn_RunAll($this->_piOnSpecialVar,$ArgLst);
3249
			}
3250
			if (!$IsSupported) $ErrMsg = '\''.$Loc->SubLst[1].'\' is an unsupported keyword.';
3251
		}
3252
	} else {
3253
		$ErrMsg = 'it doesn\'t have any subname.';
3254
	}
3255
	if ($ErrMsg!==false) {
3256
		$this->meth_Misc_Alert($Loc,$ErrMsg);
3257
		$x = '';
3258
	}
3259
	if ($Loc->PosBeg===false) {
3260
		return $Loc->PosEnd;
3261
	} else {
3262
		return $this->meth_Locator_Replace($Txt,$Loc,$x,$SubStart);
3263
	}
3264
}
3265

3266
function meth_Merge_FieldOutside(&$Txt, &$CurrRec, $RecNum, $PosMax) {
3267
	$Pos = 0;
3268
	$SubStart = ($CurrRec===false) ? false : 0;
3269
	do {
3270
		$Loc = $this->meth_Locator_FindTbs($Txt,$this->_CurrBlock,$Pos,'.');
3271
		if ($Loc!==false) {
3272
			if (($PosMax!==false) && ($Loc->PosEnd>$PosMax)) return;
3273
			if ($Loc->SubName==='#') {
3274
				$NewEnd = $this->meth_Locator_Replace($Txt,$Loc,$RecNum,false);
3275
			} else {
3276
				$NewEnd = $this->meth_Locator_Replace($Txt,$Loc,$CurrRec,$SubStart);
3277
			}
3278
			if ($PosMax!==false) $PosMax += $NewEnd - $Loc->PosEnd;
3279
			$Pos = $NewEnd;
3280
		}
3281
	} while ($Loc!==false);
3282
}
3283

3284
/**
3285
 * Check the values of previous and next record for expression.
3286
 *
3287
 * @return boolean
3288
 */
3289
function meth_Merge_CheckBounds($BDef,$Src) {
3290
		
3291
	// Retrieve values considering that a new record is fetched
3292
	// The order is important
3293
	if ($BDef->CheckPrev) {
3294
	   $BDef->ValPrev = $BDef->ValCurr;
3295
	}
3296
	if ($BDef->CheckNext) {
3297
		if (is_null($BDef->ValNext)) {
3298
			// ValNext is not set at this point for the very first record
3299
			$BDef->ValCurr = $this->meth_Merge_SectionNormal($BDef->BoundExpr,$Src);
3300
		} else {
3301
			$BDef->ValCurr = $BDef->ValNext;
3302
		}
3303
		if ($Src->NextRec->CurrRec === false) {
3304
			// No next record
3305
			$diff_next = true;
3306
		} else {
3307
			$BDef->ValNext = $this->meth_Merge_SectionNormal($BDef->BoundExpr,$Src->NextRec); // merge with next record
3308
			$diff_next = ($BDef->ValCurr !== $BDef->ValNext);
3309
		}
3310
	} else {
3311
		$BDef->ValCurr = $this->meth_Merge_SectionNormal($BDef->BoundExpr,$Src); // merge with current record
3312
	}
3313

3314
	// Check values
3315
	$result = false; // this state must never happen
3316
	if ($BDef->CheckPrev) {
3317
		$diff_prev = ($BDef->ValCurr !== $BDef->ValPrev);
3318
	   if ($BDef->CheckNext) {
3319
		   $result = $diff_prev && $diff_next;
3320
	   } else {
3321
		   $result = $diff_prev;
3322
	   }
3323
	} elseif ($BDef->CheckNext) {
3324
		$result = $diff_next;
3325
	}
3326
	
3327
	return $result;
3328
	
3329
}
3330

3331
function meth_Merge_SectionNormal(&$BDef,&$Src) {
3332

3333
	$Txt = $BDef->Src;
3334
	$LocLst = &$BDef->LocLst;
3335
	$iMax = $BDef->LocNbr;
3336
	$PosMax = strlen($Txt);
3337

3338
	if ($Src===false) { // Erase all fields
3339

3340
		$x = '';
3341

3342
		// Chached locators
3343
		for ($i=$iMax;$i>0;$i--) {
3344
			if ($LocLst[$i]->PosBeg<$PosMax) {
3345
				$this->meth_Locator_Replace($Txt,$LocLst[$i],$x,false);
3346
				if ($LocLst[$i]->Enlarged) {
3347
					$PosMax = $LocLst[$i]->PosBeg;
3348
					$LocLst[$i]->PosBeg = $LocLst[$i]->PosBeg0;
3349
					$LocLst[$i]->PosEnd = $LocLst[$i]->PosEnd0;
3350
					$LocLst[$i]->Enlarged = false;
3351
				}
3352
			}
3353
		}
3354

3355
		// Uncached locators
3356
		if ($BDef->Chk) {
3357
			$BlockName = &$BDef->Name;
3358
			$Pos = 0;
3359
			while ($Loc = $this->meth_Locator_FindTbs($Txt,$BlockName,$Pos,'.')) $Pos = $this->meth_Locator_Replace($Txt,$Loc,$x,false);
3360
		}
3361

3362
	} else {
3363

3364
		// Cached locators
3365
		for ($i=$iMax;$i>0;$i--) {
3366
			if ($LocLst[$i]->PosBeg<$PosMax) {
3367
				if ($LocLst[$i]->IsRecInfo) {
3368
					if ($LocLst[$i]->RecInfo==='#') {
3369
						$this->meth_Locator_Replace($Txt,$LocLst[$i],$Src->RecNum,false);
3370
					} else {
3371
						$this->meth_Locator_Replace($Txt,$LocLst[$i],$Src->RecKey,false);
3372
					}
3373
				} else {
3374
					$this->meth_Locator_Replace($Txt,$LocLst[$i],$Src->CurrRec,0);
3375
				}
3376
				if ($LocLst[$i]->Enlarged) {
3377
					$PosMax = $LocLst[$i]->PosBeg;
3378
					$LocLst[$i]->PosBeg = $LocLst[$i]->PosBeg0;
3379
					$LocLst[$i]->PosEnd = $LocLst[$i]->PosEnd0;
3380
					$LocLst[$i]->Enlarged = false;
3381
				}
3382
			}
3383
		}
3384

3385
		// Unchached locators
3386
		if ($BDef->Chk) {
3387
			$BlockName = &$BDef->Name;
3388
			foreach ($Src->CurrRec as $key => $val) {
3389
				$Pos = 0;
3390
				$Name = $BlockName.'.'.$key;
3391
				while ($Loc = $this->meth_Locator_FindTbs($Txt,$Name,$Pos,'.')) $Pos = $this->meth_Locator_Replace($Txt,$Loc,$val,0);
3392
			}
3393
			$Pos = 0;
3394
			$Name = $BlockName.'.#';
3395
			while ($Loc = $this->meth_Locator_FindTbs($Txt,$Name,$Pos,'.')) $Pos = $this->meth_Locator_Replace($Txt,$Loc,$Src->RecNum,0);
3396
			$Pos = 0;
3397
			$Name = $BlockName.'.$';
3398
			while ($Loc = $this->meth_Locator_FindTbs($Txt,$Name,$Pos,'.')) $Pos = $this->meth_Locator_Replace($Txt,$Loc,$Src->RecKey,0);
3399
		}
3400

3401
	}
3402

3403
	// Automatic sub-blocks
3404
	if (isset($BDef->AutoSub)) {
3405
		for ($i=1;$i<=$BDef->AutoSub;$i++) {
3406
			$name = $BDef->Name.'_sub'.$i;
3407
			$query = '';
3408
			$col = $BDef->Prm['sub'.$i];
3409
			if ($col===true) $col = '';
3410
			$col_opt = (substr($col,0,1)==='(') && (substr($col,-1,1)===')');
3411
			if ($col_opt) $col = substr($col,1,strlen($col)-2);
3412
			if ($col==='') {
3413
				// $col_opt cannot be used here because values which are not array nore object are reformated by $Src into an array with keys 'key' and 'val'
3414
				$data = &$Src->CurrRec;
3415
			} elseif (is_object($Src->CurrRec)) {
3416
				$data = &$Src->CurrRec->$col;
3417
			} else {
3418
				if (array_key_exists($col, $Src->CurrRec)) {
3419
					$data = &$Src->CurrRec[$col];
3420
				} else {
3421
					if (!$col_opt) $this->meth_Misc_Alert('for merging the automatic sub-block ['.$name.']','key \''.$col.'\' is not found in record #'.$Src->RecNum.' of block ['.$BDef->Name.']. This key can become optional if you designate it with parenthesis in the main block, i.e.: sub'.$i.'=('.$col.')');
3422
					unset($data); $data = array();
3423
				}
3424
			}
3425
			if (is_string($data)) {
3426
				$data = explode(',',$data);
3427
			} elseif (is_null($data) || ($data===false)) {
3428
				$data = array();
3429
			}
3430
			$this->meth_Merge_Block($Txt, $name, $data, $query, false, 0, false);
3431
		}
3432
	}
3433

3434
	return $Txt;
3435

3436
}
3437

3438
function meth_Merge_SectionSerial(&$BDef,&$SrId,&$LocR) {
3439

3440
	$Txt = $BDef->Src;
3441
	$SrBDefOrdered = &$BDef->SrBDefOrdered;
3442
	$Empty = &$LocR->SerialEmpty;
3443

3444
	// All Items
3445
	$F = false;
3446
	for ($i=$BDef->SrBDefNbr;$i>0;$i--) {
3447
		$SrBDef = &$SrBDefOrdered[$i];
3448
		if ($SrBDef->SrTxt===false) { // Subsection not merged with a record
3449
			if ($Empty===false) {
3450
				$SrBDef->SrTxt = $this->meth_Merge_SectionNormal($SrBDef,$F);
3451
			} else {
3452
				$SrBDef->SrTxt = $Empty;
3453
			}
3454
		}
3455
		$Txt = substr_replace($Txt,$SrBDef->SrTxt,$SrBDef->SrBeg,$SrBDef->SrLen);
3456
		$SrBDef->SrTxt = false;
3457
	}
3458

3459
	$SrId = 0;
3460
	return $Txt;
3461

3462
}
3463

3464
/**
3465
 * Merge [onload] or [onshow] fields and blocks
3466
 */
3467
function meth_Merge_AutoOn(&$Txt,$Name,$TplVar,$MergeVar) {
3468

3469
	$GrpDisplayed = array();
3470
	$GrpExclusive = array();
3471
	$P1 = false;
3472
	$FieldBefore = false;
3473
	$Pos = 0;
3474

3475
	while ($LocA=$this->meth_Locator_FindBlockNext($Txt,$Name,$Pos,'_',1,$P1,$FieldBefore)) {
3476

3477
		if ($LocA->BlockFound) {
3478

3479
			if (!isset($GrpDisplayed[$LocA->SubName])) {
3480
				$GrpDisplayed[$LocA->SubName] = false;
3481
				$GrpExclusive[$LocA->SubName] = ($LocA->SubName!=='');
3482
			}
3483
			$Displayed = &$GrpDisplayed[$LocA->SubName];
3484
			$Exclusive = &$GrpExclusive[$LocA->SubName];
3485

3486
			$DelBlock = false;
3487
			$DelField = false;
3488
			if ($Displayed && $Exclusive) {
3489
				$DelBlock = true;
3490
			} else {
3491
				if (isset($LocA->PrmLst['when'])) {
3492
					if (isset($LocA->PrmLst['several'])) $Exclusive=false;
3493
					$x = $LocA->PrmLst['when'];
3494
					$this->meth_Merge_AutoVar($x,false);
3495
					if ($this->f_Misc_CheckCondition($x)) {
3496
						$DelField = true;
3497
						$Displayed = true;
3498
					} else {
3499
						$DelBlock = true;
3500
					}
3501
				} elseif(isset($LocA->PrmLst['default'])) {
3502
					if ($Displayed) {
3503
						$DelBlock = true;
3504
					} else {
3505
						$Displayed = true;
3506
						$DelField = true;
3507
					}
3508
					$Exclusive = true; // No more block displayed for the group after
3509
				}
3510
			}
3511

3512
			// Del parts
3513
			if ($DelField) {
3514
				if ($LocA->PosBeg2!==false) $Txt = substr_replace($Txt, '', $LocA->PosBeg2, $LocA->PosEnd2 - $LocA->PosBeg2 + 1);
3515
				$Txt = substr_replace($Txt,'',$LocA->PosBeg,$LocA->PosEnd-$LocA->PosBeg+1);
3516
				$Pos = $LocA->PosBeg;
3517
			} else {
3518
				$FldPos = $LocA->PosBeg;
3519
				$FldLen = $LocA->PosEnd - $LocA->PosBeg + 1;
3520
				if ($LocA->PosBeg2===false) {
3521
					if ($this->f_Loc_EnlargeToTag($Txt,$LocA,$LocA->PrmLst['block'],false)===false) $this->meth_Misc_Alert($LocA,'at least one tag corresponding to '.$LocA->PrmLst['block'].' is not found. Check opening tags, closing tags and embedding levels.',false,'in block\'s definition');
3522
				} else {
3523
					$LocA->PosEnd = $LocA->PosEnd2;
3524
				}
3525
				if ($DelBlock) {
3526
					$parallel = false;
3527
					if (isset($LocA->PrmLst['parallel'])) {
3528
						// may return false if error
3529
						$parallel = $this->meth_Locator_FindParallel($Txt, $LocA->PosBeg, $LocA->PosEnd, $LocA->PrmLst['parallel']);
3530
						if ($parallel===false) {
3531
							$Txt = substr_replace($Txt,'',$FldPos,$FldLen);
3532
						} else {
3533
							// delete in reverse order
3534
							for ($r = count($parallel)-1 ; $r >= 0 ; $r--) {
3535
								$p = $parallel[$r];
3536
								$Txt = substr_replace($Txt,'',$p['PosBeg'],$p['PosEnd']-$p['PosBeg']+1);
3537
							}
3538
						}
3539
					} else {
3540
						$Txt = substr_replace($Txt,'',$LocA->PosBeg,$LocA->PosEnd-$LocA->PosBeg+1);
3541
					}
3542
					$Pos = $LocA->PosBeg;
3543
				} else {
3544
					// Merge the block as if it was a field
3545
					$x = '';
3546
					$this->meth_Locator_Replace($Txt,$LocA,$x,false);
3547
					$Pos = $LocA->PosNext;
3548
				}
3549
			}
3550

3551
		} else { // Field (the Loc has no subname at this point)
3552

3553
			// Check for Template Var
3554
			if ($TplVar) {
3555
				if (isset($LocA->PrmLst['tplvars']) || isset($LocA->PrmLst['tplfrms'])) {
3556
					$Scan = '';
3557
					foreach ($LocA->PrmLst as $Key => $Val) {
3558
						if ($Scan=='v') {
3559
							$this->TplVars[$Key] = $Val;
3560
						} elseif ($Scan=='f') {
3561
							self::f_Misc_FormatSave($Val,$Key);
3562
						} elseif ($Key==='tplvars') {
3563
							$Scan = 'v';
3564
						} elseif ($Key==='tplfrms') {
3565
							$Scan = 'f';
3566
						}
3567
					}
3568
				}
3569
			}
3570

3571
			$x = '';
3572
			$this->meth_Locator_Replace($Txt,$LocA,$x,false);
3573
			$Pos = $LocA->PosNext; // continue at the start so embedded fields can be merged
3574

3575
		}
3576

3577
	}
3578

3579
	if ($MergeVar) $this->meth_Merge_AutoVar($Txt,true,$Name); // merge other fields (must have subnames)
3580

3581
	foreach ($this->Assigned as $n=>$a) {
3582
		if (isset($a['auto']) && ($a['auto']===$Name)) {
3583
			$x = array();
3584
			$this->meth_Misc_Assign($n,$x,false);
3585
		}
3586
	}
3587

3588
}
3589

3590
// Prepare the strconv parameter
3591
function meth_Conv_Prepare(&$Loc, $StrConv) {
3592
	$x = strtolower($StrConv);
3593
	$x = '+'.str_replace(' ','',$x).'+';
3594
	if (strpos($x,'+esc+')!==false)  {$this->f_Misc_ConvSpe($Loc); $Loc->ConvStr = false; $Loc->ConvEsc = true; }
3595
	if (strpos($x,'+wsp+')!==false)  {$this->f_Misc_ConvSpe($Loc); $Loc->ConvWS = true; }
3596
	if (strpos($x,'+js+')!==false)   {$this->f_Misc_ConvSpe($Loc); $Loc->ConvStr = false; $Loc->ConvJS = true; }
3597
	if (strpos($x,'+url+')!==false)  {$this->f_Misc_ConvSpe($Loc); $Loc->ConvStr = false; $Loc->ConvUrl = true; }
3598
	if (strpos($x,'+utf8+')!==false)  {$this->f_Misc_ConvSpe($Loc); $Loc->ConvStr = false; $Loc->ConvUtf8 = true; }
3599
	if (strpos($x,'+no+')!==false)   $Loc->ConvStr = false;
3600
	if (strpos($x,'+yes+')!==false)  $Loc->ConvStr = true;
3601
	if (strpos($x,'+nobr+')!==false) {$Loc->ConvStr = true; $Loc->ConvBr = false; }
3602
}
3603

3604
// Convert a string with charset or custom function
3605
function meth_Conv_Str(&$Txt,$ConvBr=true) {
3606
	if ($this->Charset==='') { // Html by default
3607
		$Txt = htmlspecialchars($Txt, ENT_COMPAT); // ENT_COMPAT is no more the default value since PHP 8.1
3608
		if ($ConvBr) $Txt = nl2br($Txt);
3609
	} elseif ($this->_CharsetFct) {
3610
		$Txt = call_user_func($this->Charset, $Txt,$ConvBr);
3611
	} else {
3612
		$Txt = htmlspecialchars($Txt, ENT_COMPAT, $this->Charset);
3613
		if ($ConvBr) $Txt = nl2br($Txt);
3614
	}
3615
}
3616

3617
// Standard alert message provided by TinyButStrong, return False is the message is cancelled.
3618
function meth_Misc_Alert($Src,$Msg,$NoErrMsg=false,$SrcType=false) {
3619
	$this->ErrCount++;
3620
	if ($this->NoErr || (PHP_SAPI==='cli') ) {
3621
		$t = array('','','','','');
3622
	} else {
3623
		$t = array('<br /><b>','</b>','<em>','</em>','<br />');
3624
		$Msg = htmlentities($Msg);
3625
	}
3626
	if (!is_string($Src)) {
3627
		if ($SrcType===false) $SrcType='in field';
3628
		if (isset($Src->PrmLst['tbstype'])) {
3629
			$Msg = 'Column \''.$Src->SubName.'\' is expected but missing in the current record.';
3630
			$Src = 'Parameter \''.$Src->PrmLst['tbstype'].'='.$Src->SubName.'\'';
3631
			$NoErrMsg = false;
3632
		} else {
3633
			$Src = $SrcType.' '.$this->_ChrOpen.$Src->FullName.'...'.$this->_ChrClose;
3634
		}
3635
	}
3636
	$x = $t[0].'TinyButStrong Error'.$t[1].' '.$Src.': '.$Msg;
3637
	if ($NoErrMsg) $x = $x.' '.$t[2].'This message can be cancelled using parameter \'noerr\'.'.$t[3];
3638
	$x = $x.$t[4]."\n";
3639
	if ($this->NoErr) {
3640
		$this->ErrMsg .= $x;
3641
	} else {
3642
		if (PHP_SAPI!=='cli') {
3643
			$x = str_replace($this->_ChrOpen,$this->_ChrProtect,$x);
3644
		}
3645
		echo $x;
3646
	}
3647
	return false;
3648
}
3649

3650
function meth_Misc_Assign($Name,&$ArgLst,$CallingMeth) {
3651
// $ArgLst must be by reference in order to have its inner items by reference too.
3652

3653
	if (!isset($this->Assigned[$Name])) {
3654
		if ($CallingMeth===false) return true;
3655
		return $this->meth_Misc_Alert('with '.$CallingMeth.'() method','key \''.$Name.'\' is not defined in property Assigned.');
3656
	}
3657

3658
	$a = &$this->Assigned[$Name];
3659
	$meth = (isset($a['type'])) ? $a['type'] : 'MergeBlock';
3660
	if (($CallingMeth!==false) && (strcasecmp($CallingMeth,$meth)!=0)) return $this->meth_Misc_Alert('with '.$CallingMeth.'() method','the assigned key \''.$Name.'\' cannot be used with method '.$CallingMeth.' because it is defined to run with '.$meth.'.');
3661

3662
	$n = count($a);
3663
	for ($i=0;$i<$n;$i++) {
3664
		if (isset($a[$i])) $ArgLst[$i] = &$a[$i];
3665
	}
3666

3667
	if ($CallingMeth===false) {
3668
		if (in_array(strtolower($meth),array('mergeblock','mergefield'))) {
3669
			call_user_func_array(array(&$this,$meth), $ArgLst);
3670
		} else {
3671
			return $this->meth_Misc_Alert('', 'The assigned field \''.$Name.'\'. cannot be merged because its type \''.$a[0].'\' is not supported.');
3672
		}
3673
	}
3674
	if (!isset($a['merged'])) $a['merged'] = 0;
3675
	$a['merged']++;
3676
	return true;
3677
}
3678

3679
function meth_Misc_IsMainTpl() {
3680
	return ($this->_Mode==0);
3681
}
3682

3683
function meth_Misc_ChangeMode($Init,&$Loc,&$CurrVal) {
3684
	if ($Init) {
3685
		// Save contents configuration
3686
		$Loc->SaveSrc = &$this->Source;
3687
		$Loc->SaveMode = $this->_Mode;
3688
		$Loc->SaveVarRef = &$this->VarRef;
3689
		unset($this->Source); $this->Source = '';
3690
		$this->_Mode++; // Mode>0 means subtemplate mode
3691
		if ($this->OldSubTpl) {
3692
			ob_start(); // Start buffuring output
3693
			$Loc->SaveRender = $this->Render;
3694
		}
3695
		$this->Render = TBS_OUTPUT;
3696
	} else {
3697
		// Restore contents configuration
3698
		if ($this->OldSubTpl) {
3699
			$CurrVal = ob_get_contents();
3700
			ob_end_clean();
3701
			$this->Render = $Loc->SaveRender;
3702
		} else {
3703
			$CurrVal = $this->Source;
3704
		}
3705
		$this->Source = &$Loc->SaveSrc;
3706
		$this->_Mode = $Loc->SaveMode;
3707
		$this->VarRef = &$Loc->SaveVarRef;
3708
	}
3709
}
3710

3711
function meth_Misc_UserFctCheck(&$FctInfo,$FctCat,&$FctObj,&$ErrMsg,$FctCheck=false) {
3712

3713
	$FctId = $FctCat.':'.$FctInfo;
3714
	if (isset($this->_UserFctLst[$FctId])) {
3715
		$FctInfo = $this->_UserFctLst[$FctId];
3716
		return true;
3717
	}
3718

3719
	// Check and put in cache
3720
	$FctStr = $FctInfo;
3721
	$IsData = ($FctCat!=='f');
3722
	$Save = true;
3723
	if ($FctStr[0]==='~') {
3724
		$ObjRef = &$this->ObjectRef;
3725
		$Lst = explode('.',substr($FctStr,1));
3726
		$iMax = count($Lst) - 1;
3727
		$Suff = 'tbsdb';
3728
		$iMax0 = $iMax;
3729
		if ($IsData) {
3730
			$Suff = $Lst[$iMax];
3731
			$iMax--;
3732
		}
3733
		// Reading sub items
3734
		for ($i=0;$i<=$iMax;$i++) {
3735
			$x = &$Lst[$i];
3736
			if (is_object($ObjRef)) {
3737
				$form = $this->f_Misc_ParseFctForm($x);
3738
				$n = $form['name'];
3739
				if ($i === $iMax0) {
3740
					// last item is supposed to be a function's name, without parenthesis
3741
					if ( method_exists($ObjRef,$n)  || (method_exists($ObjRef, '__call'))) {
3742
						// Ok, continue. If $form['as_fct'] is true, then it will produce an error when try to call function $x
3743
					} else {
3744
						$ErrMsg = 'Expression \''.$FctStr.'\' is invalid because \''.$n.'\' is not a method in the class \''.get_class($ObjRef).'\'.';
3745
						return false;
3746
					}
3747
				} elseif ( method_exists($ObjRef,$n) || ($form['as_fct'] && method_exists($ObjRef, 'x__call')) ) {
3748
					$f = array(&$ObjRef,$n);
3749
					unset($ObjRef);
3750
					$ObjRef = call_user_func_array($f,$form['args']);
3751
				} elseif (isset($ObjRef->$n)) {
3752
					$ObjRef = &$ObjRef->$n;
3753
				} else {
3754
					$ErrMsg = 'Expression \''.$FctStr.'\' is invalid because sub-item \''.$n.'\' is neither a method nor a property in the class \''.get_class($ObjRef).'\'.';
3755
					return false;
3756
				}
3757
			} elseif (($i<$iMax0) && is_array($ObjRef)) {
3758
				if (isset($ObjRef[$x])) {
3759
					$ObjRef = &$ObjRef[$x];
3760
				} else {
3761
					$ErrMsg = 'Expression \''.$FctStr.'\' is invalid because sub-item \''.$x.'\' is not a existing key in the array.';
3762
					return false;
3763
				}
3764
			} else {
3765
				$ErrMsg = 'Expression \''.$FctStr.'\' is invalid because '.(($i===0)?'property ObjectRef':'sub-item \''.$x.'\'').' is not an object'.(($i<$iMax)?' or an array.':'.');
3766
				return false;
3767
			}
3768
		}
3769
		// Referencing last item
3770
		if ($IsData) {
3771
			$FctInfo = array('open'=>'','fetch'=>'','close'=>'');
3772
			foreach ($FctInfo as $act=>$x) {
3773
				$FctName = $Suff.'_'.$act;
3774
				if (method_exists($ObjRef,$FctName)) {
3775
					$FctInfo[$act] = array(&$ObjRef,$FctName);
3776
				} else {
3777
					$ErrMsg = 'Expression \''.$FctStr.'\' is invalid because method '.$FctName.' is not found.';
3778
					return false;
3779
				}
3780
			}
3781
			$FctInfo['type'] = 4;
3782
			if (isset($this->RecheckObj) && $this->RecheckObj) $Save = false;
3783
		} else {
3784
			$FctInfo = array(&$ObjRef,$x);
3785
		}
3786
	} elseif ($IsData) {
3787

3788
		$IsObj = ($FctCat==='o');
3789

3790
		if ($IsObj && method_exists($FctObj,'tbsdb_open') && (!method_exists($FctObj,'+'))) { // '+' avoid a bug in PHP 5
3791

3792
			if (!method_exists($FctObj,'tbsdb_fetch')) {
3793
				$ErrMsg = 'the expected method \'tbsdb_fetch\' is not found for the class '.$Cls.'.';
3794
				return false;
3795
			}
3796
			if (!method_exists($FctObj,'tbsdb_close')) {
3797
				$ErrMsg = 'the expected method \'tbsdb_close\' is not found for the class '.$Cls.'.';
3798
				return false;
3799
			}
3800
			$FctInfo = array('type'=>5);
3801

3802
		}	else {
3803

3804
			if ($FctCat==='r') { // Resource
3805
				$x = strtolower($FctStr);
3806
				$x = str_replace('-','_',$x);
3807
				$Key = '';
3808
				$i = 0;
3809
				$iMax = strlen($x);
3810
				while ($i<$iMax) {
3811
					if (($x[$i]==='_') || (($x[$i]>='a') && ($x[$i]<='z')) || (($x[$i]>='0') && ($x[$i]<='9'))) {
3812
						$Key .= $x[$i];
3813
						$i++;
3814
					} else {
3815
						$i = $iMax;
3816
					}
3817
				}
3818
			} else {
3819
				$Key = $FctStr;
3820
			}
3821

3822
			$FctInfo = array('open'=>'','fetch'=>'','close'=>'');
3823
			foreach ($FctInfo as $act=>$x) {
3824
				$FctName = 'tbsdb_'.$Key.'_'.$act;
3825
				if (function_exists($FctName)) {
3826
					$FctInfo[$act] = $FctName;
3827
				} else {
3828
					$err = true;
3829
					if ($act==='open') { // Try simplified key
3830
						$p = strpos($Key,'_');
3831
						if ($p!==false) {
3832
							$Key2 = substr($Key,0,$p);
3833
							$FctName2  = 'tbsdb_'.$Key2.'_'.$act;
3834
							if (function_exists($FctName2)) {
3835
								$err = false;
3836
								$Key = $Key2;
3837
								$FctInfo[$act] = $FctName2;
3838
							}
3839
						}
3840
					}
3841
					if ($err) {
3842
						$ErrMsg = 'Data source Id \''.$FctStr.'\' is unsupported because function \''.$FctName.'\' is not found.';
3843
						return false;
3844
					}
3845
				}
3846
			}
3847

3848
			$FctInfo['type'] = 3;
3849

3850
		}
3851

3852
	} else {
3853
		if ( $FctCheck && ($this->FctPrefix!=='') && (strncmp($this->FctPrefix,$FctStr,strlen($this->FctPrefix))!==0) ) {
3854
			$ErrMsg = 'user function \''.$FctStr.'\' does not match the allowed prefix.'; return false;
3855
		} else if (!function_exists($FctStr)) {
3856
			$x = explode('.',$FctStr);
3857
			if (count($x)==2) {
3858
				if (class_exists($x[0])) {
3859
					$FctInfo = $x;
3860
				} else {
3861
					$ErrMsg = 'user function \''.$FctStr.'\' is not correct because \''.$x[0].'\' is not a class name.'; return false;
3862
				}
3863
			} else {
3864
				$ErrMsg = 'user function \''.$FctStr.'\' is not found.'; return false;
3865
			}
3866
		}
3867
	}
3868

3869
	if ($Save) $this->_UserFctLst[$FctId] = $FctInfo;
3870
	return true;
3871

3872
}
3873

3874
function meth_Misc_RunSubscript(&$CurrVal,$CurrPrm) {
3875
// Run a subscript without any local variable damage
3876
	return @include($this->_Subscript);
3877
}
3878

3879
function meth_Misc_Charset($Charset) {
3880
	if ($Charset==='+') return;
3881
	$this->_CharsetFct = false;
3882
	if (is_string($Charset)) {
3883
		if (($Charset!=='') && ($Charset[0]==='=')) {
3884
			$ErrMsg = false;
3885
			$Charset = substr($Charset,1);
3886
			if ($this->meth_Misc_UserFctCheck($Charset,'f',$ErrMsg,$ErrMsg,false)) {
3887
				$this->_CharsetFct = true;
3888
			} else {
3889
				$this->meth_Misc_Alert('with charset option',$ErrMsg);
3890
				$Charset = '';
3891
			}
3892
		}
3893
	} elseif (is_array($Charset)) {
3894
		$this->_CharsetFct = true;
3895
	} elseif ($Charset===false) {
3896
		$this->Protect = false;
3897
	} else {
3898
		$this->meth_Misc_Alert('with charset option','the option value is not a string nor an array.');
3899
		$Charset = '';
3900
	}
3901
	$this->Charset = $Charset;
3902
}
3903

3904
function meth_PlugIn_RunAll(&$FctBank,&$ArgLst) {
3905
	$OkAll = true;
3906
	foreach ($FctBank as $FctInfo) {
3907
		$Ok = call_user_func_array($FctInfo,$ArgLst);
3908
		if (!is_null($Ok)) $OkAll = ($OkAll && $Ok);
3909
	}
3910
	return $OkAll;
3911
}
3912

3913
function meth_PlugIn_Install($PlugInId,$ArgLst,$Auto) {
3914

3915
	$ErrMsg = 'with plug-in \''.$PlugInId.'\'';
3916

3917
	if (class_exists($PlugInId)) {
3918
		// Create an instance
3919
		$IsObj = true;
3920
		$PiRef = new $PlugInId;
3921
		$PiRef->TBS = &$this; // public $TBS property is madatory since PHP 8.2
3922
		if (!method_exists($PiRef,'OnInstall')) return $this->meth_Misc_Alert($ErrMsg,'OnInstall() method is not found.');
3923
		$FctRef = array(&$PiRef,'OnInstall');
3924
	} else {
3925
		$FctRef = 'tbspi_'.$PlugInId.'_OnInstall';
3926
		if(function_exists($FctRef)) {
3927
			$IsObj = false;
3928
			$PiRef = true;
3929
		} else {
3930
			return $this->meth_Misc_Alert($ErrMsg,'no class named \''.$PlugInId.'\' is found, and no function named \''.$FctRef.'\' is found.');
3931
		}
3932
	}
3933

3934
	$this->_PlugIns[$PlugInId] = &$PiRef;
3935

3936
	$EventLst = call_user_func_array($FctRef,$ArgLst);
3937
	if (is_string($EventLst)) $EventLst = explode(',',$EventLst);
3938
	if (!is_array($EventLst)) return $this->meth_Misc_Alert($ErrMsg,'OnInstall() method does not return an array.');
3939

3940
	// Add activated methods
3941
	foreach ($EventLst as $Event) {
3942
		$Event = trim($Event);
3943
		if (!$this->meth_PlugIn_SetEvent($PlugInId, $Event)) return false;
3944
	}
3945

3946
	return true;
3947

3948
}
3949

3950
function meth_PlugIn_SetEvent($PlugInId, $Event, $NewRef='') {
3951
// Enable or disable a plug-in event. It can be called by a plug-in, even during the OnInstall event. $NewRef can be used to change the method associated to the event.
3952

3953
	// Check the event's name
3954
	if (strpos(',OnCommand,BeforeLoadTemplate,AfterLoadTemplate,BeforeShow,AfterShow,OnData,OnFormat,OnOperation,BeforeMergeBlock,OnMergeSection,OnMergeGroup,AfterMergeBlock,OnSpecialVar,OnMergeField,OnCacheField,', ','.$Event.',')===false) return $this->meth_Misc_Alert('with plug-in \''.$PlugInId.'\'','The plug-in event named \''.$Event.'\' is not supported by TinyButStrong (case-sensitive). This event may come from the OnInstall() method.');
3955

3956
	$PropName = '_pi'.$Event;
3957

3958
	if ($NewRef===false) {
3959
		// Disable the event
3960
		if (!isset($this->$PropName)) return false;
3961
		$PropRef = &$this->$PropName;
3962
		unset($PropRef[$PlugInId]);
3963
		return true;
3964
	}
3965
	
3966
	// Prepare the reference to be called
3967
	$PiRef = &$this->_PlugIns[$PlugInId];
3968
	if (is_object($PiRef)) {
3969
		if ($NewRef==='') $NewRef = $Event;
3970
		if (!method_exists($PiRef, $NewRef)) return $this->meth_Misc_Alert('with plug-in \''.$PlugInId.'\'','The plug-in event named \''.$Event.'\' is declared but its corresponding method \''.$NewRef.'\' is found.');
3971
		$FctRef = array(&$PiRef, $NewRef);
3972
	} else {
3973
		$FctRef = ($NewRef==='') ? 'tbspi_'.$PlugInId.'_'.$Event : $NewRef;
3974
		if (!function_exists($FctRef)) return $this->meth_Misc_Alert('with plug-in \''.$PlugInId.'\'','The expected function \''.$FctRef.'\' is not found.');
3975
	}
3976

3977
	// Save information into the corresponding property
3978
	if (!isset($this->$PropName)) $this->$PropName = array();
3979
	$PropRef = &$this->$PropName;
3980
	$PropRef[$PlugInId] = $FctRef;
3981

3982
	// Flags saying if a plugin is installed
3983
	switch ($Event) {
3984
	case 'OnCommand': break;
3985
	case 'OnSpecialVar': break;
3986
	case 'OnOperation': break;
3987
	case 'OnFormat': $this->_piOnFrm_Ok = true; break;
3988
	default: $this->_PlugIns_Ok = true; break;
3989
	}
3990
		
3991
	return true;
3992

3993
}
3994

3995
/**
3996
 * Convert any value to a string without specific formating.
3997
 */
3998
static function meth_Misc_ToStr($Value) {
3999
	if (is_string($Value)) {
4000
		return $Value;
4001
	} elseif(is_object($Value)) {
4002
		if (method_exists($Value,'__toString')) {
4003
			return $Value->__toString();
4004
		} elseif (is_a($Value, 'DateTime')) {
4005
			// ISO date-time format
4006
			return $Value->format('c');
4007
		}
4008
	}
4009
	return @(string)$Value; // (string) is faster than strval() and settype()
4010
}
4011

4012
/**
4013
 * Return the formated representation of a Date/Time or numeric variable using a 'VB like' format syntax instead of the PHP syntax.
4014
 */
4015
function meth_Misc_Format(&$Value,&$PrmLst) {
4016

4017
	$FrmStr = $PrmLst['frm'];
4018
	$CheckNumeric = true;
4019
	if (is_string($Value)) $Value = trim($Value);
4020

4021
	if ($FrmStr==='') return '';
4022
	$Frm = self::f_Misc_FormatSave($FrmStr);
4023

4024
	// Manage Multi format strings
4025
	if ($Frm['type']=='multi') {
4026

4027
		// Found the format according to the value (positive|negative|zero|null)
4028
		
4029
		if (is_numeric($Value)) {
4030
			// Numeric:
4031
			if (is_string($Value)) $Value = 0.0 + $Value;
4032
			if ($Value>0) {
4033
				$FrmStr = &$Frm[0];
4034
			} elseif ($Value<0) {
4035
				$FrmStr = &$Frm[1];
4036
				if ($Frm['abs']) $Value = abs($Value);
4037
			} else {
4038
				// zero
4039
				$FrmStr = &$Frm[2];
4040
				$Minus = '';
4041
			}
4042
			$CheckNumeric = false;
4043
		} else {
4044
			// date|
4045
			$Value = $this->meth_Misc_ToStr($Value);
4046
			if ($Value==='') {
4047
				// Null value
4048
				return $Frm[3];
4049
			} else {
4050
				// Date conversion
4051
				$t = strtotime($Value); // We look if it's a date
4052
				if (($t===-1) || ($t===false)) {
4053
					// Date not recognized
4054
					return $Frm[1];
4055
				} elseif ($t===943916400) {
4056
					// Date to zero in some softwares
4057
					return $Frm[2];
4058
				} else {
4059
					// It's a date
4060
					$Value = $t;
4061
					$FrmStr = &$Frm[0];
4062
				}
4063
			}
4064
		}
4065

4066
		// Retrieve the correct simple format
4067
		if ($FrmStr==='') return '';
4068
		$Frm = self::f_Misc_FormatSave($FrmStr);
4069

4070
	}
4071

4072
	switch ($Frm['type']) {
4073
	case 'num':
4074
		// NUMERIC
4075
		if ($CheckNumeric) {
4076
			if (is_numeric($Value)) {
4077
				if (is_string($Value)) $Value = 0.0 + $Value;
4078
			} else {
4079
				return $this->meth_Misc_ToStr($Value);
4080
			}
4081
		}
4082
		if ($Frm['PerCent']) $Value = $Value * 100;
4083
		$Value = number_format($Value,$Frm['DecNbr'],$Frm['DecSep'],$Frm['ThsSep']);
4084
		if ($Frm['Pad']!==false) $Value = str_pad($Value, $Frm['Pad'], '0', STR_PAD_LEFT);
4085
		if ($Frm['ThsRpl']!==false) $Value = str_replace($Frm['ThsSep'], $Frm['ThsRpl'], $Value);
4086
		$Value = substr_replace($Frm['Str'],$Value,$Frm['Pos'],$Frm['Len']);
4087
		return $Value;
4088
		break;
4089
	case 'date':
4090
		// DATE
4091
		return $this->meth_Misc_DateFormat($Value, $Frm);
4092
		break;
4093
	default:
4094
		return $Frm['string'];
4095
		break;
4096
	}
4097

4098
}
4099

4100
function meth_Misc_DateFormat(&$Value, $Frm) {
4101
	
4102
	if (is_object($Value)) {
4103
		$Value = $this->meth_Misc_ToStr($Value);
4104
	}
4105

4106
	if ($Value==='') return '';
4107
	
4108
	// Note : DateTime object is supported since PHP 5.2
4109
	// So we could simplify this function using only DateTime instead of timestamp.
4110
	
4111
	// Now we try to get the timestamp
4112
	if (is_string($Value)) {
4113
		// Any string value is assumed to be a formated date.
4114
		// If you whant a string value to be a considered to a a time stamp, then use prefixe '@' accordding to the 
4115
		$x = strtotime($Value);
4116
		// In case of error return false (return -1 for PHP < 5.1.0)
4117
		if (($x===false) || ($x===-1)) {
4118
			if (!is_numeric($Value)) {
4119
				// At this point the value is not recognized as a date
4120
				// Special fix for PHP 32-bit and date > '2038-01-19 03:14:07' => strtotime() failes
4121
				if (PHP_INT_SIZE === 4) { // 32-bit
4122
					try {
4123
						$date = new DateTime($Value);
4124
						return $date->format($Frm['str_us']);
4125
						// 'locale' cannot be supported in this case because strftime() has to equilavent with DateTime
4126
					} catch (Exception $e) {
4127
						// We take an arbitrary value in order to avoid formating error
4128
						$Value = 0; // '1970-01-01'
4129
						// echo $e->getMessage();
4130
					}                
4131
				} else {
4132
					// We take an arbirtary value in order to avoid formating error
4133
					$Value = 0; // '1970-01-01'
4134
				}
4135
			}
4136
		} else {
4137
			$Value = &$x;
4138
		}
4139
	} else {
4140
		if (!is_numeric($Value)) {
4141
			// It’s not a timestamp, thus we return the non formated value 
4142
			return $this->meth_Misc_ToStr($Value);
4143
		}
4144
	}
4145
	
4146
	if ($Frm['loc'] || isset($PrmLst['locale'])) {
4147
		$x = strftime($Frm['str_loc'],$Value);
4148
		$this->meth_Conv_Str($x,false); // may have accent
4149
		return $x;
4150
	} else {
4151
		return date($Frm['str_us'],$Value);
4152
	}
4153
	
4154
}
4155

4156
/**
4157
 * Apply combo parameters.
4158
 * @param array        $PrmLst The existing list of combo
4159
 * @param object|false $Loc    The current locator, of false if called from an combo definition
4160
 */
4161
static function meth_Misc_ApplyPrmCombo(&$PrmLst, $Loc) {
4162
	
4163
	if (isset($PrmLst['combo'])) {
4164
		
4165
		$name_lst = explode(',', $PrmLst['combo']);
4166
		$DefLst = &$GLOBALS['_TBS_PrmCombo'];
4167
		
4168
		foreach ($name_lst as $name) {
4169
			if (isset($DefLst[$name])) {
4170
				$ap = $DefLst[$name];
4171
				if (isset($PrmLst['ope']) && isset($ap['ope'])) {
4172
					$PrmLst['ope'] .= ',' . $ap['ope']; // ope will be processed fifo
4173
					unset($ap['ope']);
4174
				}
4175
				if ($Loc !== false) {
4176
					if ( isset($ap['if']) && is_array($ap['if']) ) {
4177
						foreach($ap['if'] as $v) {
4178
							self::f_Loc_PrmIfThen($Loc, true, $v, false);
4179
						}
4180
						unset($ap['if']);
4181
					}
4182
					if ( isset($ap['then']) && is_array($ap['then'])) {
4183
						foreach($ap['then'] as $v) {
4184
							self::f_Loc_PrmIfThen($Loc, false, $v, false);
4185
						}
4186
						unset($ap['then']);
4187
					}
4188
				}
4189
				$PrmLst = array_merge($ap, $PrmLst);
4190
			} else {
4191
				$this->meth_Misc_Alert("with parameter 'combo'", "Combo '". $a. "' is not yet set.");
4192
			}
4193
		}
4194
		
4195
		$PrmLst['_combo'] = $PrmLst['combo']; // for debug
4196
		unset($PrmLst['combo']); // for security
4197
		
4198
	}
4199
}
4200

4201
/**
4202
 * Simply update an array with another array.
4203
 * It works for both indexed or associativ arrays.
4204
 * NULL value will be deleted from the target array. 
4205
 * 
4206
 * @param array $array     The target array to be updated.
4207
 * @param mixed $numerical True if the keys ar numerical. Use special keyword 'frm' for TBS formats, and 'prm' for a set of parameters.
4208
 * @param mixed $v         An associative array of items to modify. Use value NULL for reset $array to an empty array. Other single value will be used with $d.
4209
 * @param mixed $d         To be used when $v is a single not null value. Will apply the key $v with value $d.
4210
 */
4211
 static function f_Misc_UpdateArray(&$array, $numerical, $v, $d) {
4212
	if (!is_array($v)) {
4213
		if (is_null($v)) {
4214
			$array = array();
4215
			return;
4216
		} else {
4217
			$v = array($v=>$d);
4218
		}
4219
	}
4220
	foreach ($v as $p=>$a) {
4221
		if ($numerical===true) { // numerical keys
4222
			if (is_string($p)) {
4223
				// syntax: item => true/false
4224
				$i = array_search($p, $array, true);
4225
				if ($i===false) {
4226
					if (!is_null($a)) $array[] = $p;
4227
				} else {
4228
					if (is_null($a)) array_splice($array, $i, 1);
4229
				}
4230
			} else {
4231
				// syntax: i => item
4232
				$i = array_search($a, $array, true);
4233
				if ($i==false) $array[] = $a;
4234
			}
4235
		} else { // string keys
4236
			if (is_null($a)) {
4237
				unset($array[$p]);
4238
			} elseif ($numerical==='frm') {
4239
				self::f_Misc_FormatSave($a, $p);
4240
			} else {
4241
				if ($numerical==='prm') {
4242
					// apply existing combo on the new combo, so that all combo are translated into basic parameters
4243
					if ( isset($a['if']) && (!is_array($a['if'])) ) {
4244
						$a['if'] = array($a['if']);
4245
					}
4246
					if ( isset($a['then']) && (!is_array($a['then'])) ) {
4247
						$a['then'] = array($a['then']);
4248
					}
4249
					self::meth_Misc_ApplyPrmCombo($a, false);
4250
				}
4251
				$array[$p] = $a;
4252
			}
4253
		}
4254
	}
4255
}
4256

4257
static function f_Misc_FormatSave(&$FrmStr,$Alias='') {
4258

4259
	$FormatLst = &$GLOBALS['_TBS_FormatLst'];
4260

4261
	if (isset($FormatLst[$FrmStr])) {
4262
		if ($Alias!='') $FormatLst[$Alias] = &$FormatLst[$FrmStr];
4263
		return $FormatLst[$FrmStr];
4264
	}
4265

4266
	if (strpos($FrmStr,'|')!==false) {
4267

4268
		// Multi format
4269
		$Frm = explode('|',$FrmStr); // syntax: Postive|Negative|Zero|Null
4270
		$FrmNbr = count($Frm);
4271
		$Frm['abs'] = ($FrmNbr>1);
4272
		if ($FrmNbr<3) $Frm[2] = &$Frm[0]; // zero
4273
		if ($FrmNbr<4) $Frm[3] = ''; // null
4274
		$Frm['type'] = 'multi';
4275
		$FormatLst[$FrmStr] = $Frm;
4276

4277
	} elseif (($nPosEnd = strrpos($FrmStr,'0'))!==false) {
4278

4279
		// Numeric format
4280
		$nDecSep = '.';
4281
		$nDecNbr = 0;
4282
		$nDecOk = true;
4283
		$nPad = false;
4284
		$nPadZ = 0;
4285

4286
		if (substr($FrmStr,$nPosEnd+1,1)==='.') {
4287
			$nPosEnd++;
4288
			$nPos = $nPosEnd;
4289
			$nPadZ = 1;
4290
		} else {
4291
			$nPos = $nPosEnd - 1;
4292
			while (($nPos>=0) && ($FrmStr[$nPos]==='0')) {
4293
				$nPos--;
4294
			}
4295
			if (($nPos>=1) && ($FrmStr[$nPos-1]==='0')) {
4296
				$nDecSep = $FrmStr[$nPos];
4297
				$nDecNbr = $nPosEnd - $nPos;
4298
			} else {
4299
				$nDecOk = false;
4300
			}
4301
		}
4302

4303
		// Thousand separator
4304
		$nThsSep = '';
4305
		$nThsRpl = false;
4306
		if (($nDecOk) && ($nPos>=5)) {
4307
			if ((substr($FrmStr,$nPos-3,3)==='000') && ($FrmStr[$nPos-4]!=='0')) {
4308
				$p = strrpos(substr($FrmStr,0,$nPos-4), '0');
4309
				if ($p!==false) {
4310
					$len = $nPos-4-$p;
4311
					$x = substr($FrmStr, $p+1, $len);
4312
					if ($len>1) {
4313
						// for compatibility for number_format() with PHP < 5.4.0
4314
						$nThsSep = ($nDecSep=='*') ? '.' : '*';
4315
						$nThsRpl = $x;
4316
					} else {
4317
						$nThsSep = $x;
4318
					}
4319
					$nPos = $p+1;
4320
				}
4321
			}
4322
		}
4323

4324
		// Pass next zero
4325
		if ($nDecOk) $nPos--;
4326
		while (($nPos>=0) && ($FrmStr[$nPos]==='0')) {
4327
			$nPos--;
4328
		}
4329

4330
		$nLen = $nPosEnd-$nPos;
4331
		if ( ($nThsSep==='') && ($nLen>($nDecNbr+$nPadZ+1)) )	$nPad = $nLen - $nPadZ;
4332

4333
		// Percent
4334
		$nPerCent = (strpos($FrmStr,'%')===false) ? false : true;
4335

4336
		$FormatLst[$FrmStr] = array('type'=>'num','Str'=>$FrmStr,'Pos'=>($nPos+1),'Len'=>$nLen,'ThsSep'=>$nThsSep,'ThsRpl'=>$nThsRpl,'DecSep'=>$nDecSep,'DecNbr'=>$nDecNbr,'PerCent'=>$nPerCent,'Pad'=>$nPad);
4337

4338
	} else {
4339

4340
		// Date format
4341
		$x = $FrmStr;
4342
		$FrmPHP = '';
4343
		$FrmLOC = '';
4344
		$StrIn = false;
4345
		$Cnt = 0;
4346
		$i = strpos($FrmStr,'(locale)');
4347
		$Locale = ($i!==false);
4348
		if ($Locale) $x = substr_replace($x,'',$i,8);
4349

4350
		$iEnd = strlen($x);
4351
		for ($i=0;$i<$iEnd;$i++) {
4352

4353
			if ($StrIn) {
4354
				// We are in a string part
4355
				if ($x[$i]==='"') {
4356
					if (substr($x,$i+1,1)==='"') {
4357
						$FrmPHP .= '\\"'; // protected char
4358
						$FrmLOC .= $x[$i];
4359
						$i++;
4360
					} else {
4361
						$StrIn = false;
4362
					}
4363
				} else {
4364
					$FrmPHP .= '\\'.$x[$i]; // protected char
4365
					$FrmLOC .= $x[$i];
4366
				}
4367
			} else {
4368
				if ($x[$i]==='"') {
4369
					$StrIn = true;
4370
				} else {
4371
					$Cnt++;
4372
					if     (strcasecmp(substr($x,$i,2),'hh'  )===0) { $FrmPHP .= 'H'; $FrmLOC .= '%H'; $i += 1;}
4373
					elseif (strcasecmp(substr($x,$i,2),'hm'  )===0) { $FrmPHP .= 'h'; $FrmLOC .= '%I'; $i += 1;} // for compatibility
4374
					elseif (strcasecmp(substr($x,$i,1),'h'   )===0) { $FrmPHP .= 'G'; $FrmLOC .= '%H';}
4375
					elseif (strcasecmp(substr($x,$i,2),'rr'  )===0) { $FrmPHP .= 'h'; $FrmLOC .= '%I'; $i += 1;}
4376
					elseif (strcasecmp(substr($x,$i,1),'r'   )===0) { $FrmPHP .= 'g'; $FrmLOC .= '%I';}
4377
					elseif (strcasecmp(substr($x,$i,4),'ampm')===0) { $FrmPHP .= substr($x,$i,1); $FrmLOC .= '%p'; $i += 3;} // $Fmp = 'A' or 'a'
4378
					elseif (strcasecmp(substr($x,$i,2),'nn'  )===0) { $FrmPHP .= 'i'; $FrmLOC .= '%M'; $i += 1;}
4379
					elseif (strcasecmp(substr($x,$i,2),'ss'  )===0) { $FrmPHP .= 's'; $FrmLOC .= '%S'; $i += 1;}
4380
					elseif (strcasecmp(substr($x,$i,2),'xx'  )===0) { $FrmPHP .= 'S'; $FrmLOC .= ''  ; $i += 1;}
4381
					elseif (strcasecmp(substr($x,$i,4),'yyyy')===0) { $FrmPHP .= 'Y'; $FrmLOC .= '%Y'; $i += 3;}
4382
					elseif (strcasecmp(substr($x,$i,2),'yy'  )===0) { $FrmPHP .= 'y'; $FrmLOC .= '%y'; $i += 1;}
4383
					elseif (strcasecmp(substr($x,$i,4),'mmmm')===0) { $FrmPHP .= 'F'; $FrmLOC .= '%B'; $i += 3;}
4384
					elseif (strcasecmp(substr($x,$i,3),'mmm' )===0) { $FrmPHP .= 'M'; $FrmLOC .= '%b'; $i += 2;}
4385
					elseif (strcasecmp(substr($x,$i,2),'mm'  )===0) { $FrmPHP .= 'm'; $FrmLOC .= '%m'; $i += 1;}
4386
					elseif (strcasecmp(substr($x,$i,1),'m'   )===0) { $FrmPHP .= 'n'; $FrmLOC .= '%m';}
4387
					elseif (strcasecmp(substr($x,$i,4),'wwww')===0) { $FrmPHP .= 'l'; $FrmLOC .= '%A'; $i += 3;}
4388
					elseif (strcasecmp(substr($x,$i,3),'www' )===0) { $FrmPHP .= 'D'; $FrmLOC .= '%a'; $i += 2;}
4389
					elseif (strcasecmp(substr($x,$i,1),'w'   )===0) { $FrmPHP .= 'w'; $FrmLOC .= '%u';}
4390
					elseif (strcasecmp(substr($x,$i,4),'dddd')===0) { $FrmPHP .= 'l'; $FrmLOC .= '%A'; $i += 3;}
4391
					elseif (strcasecmp(substr($x,$i,3),'ddd' )===0) { $FrmPHP .= 'D'; $FrmLOC .= '%a'; $i += 2;}
4392
					elseif (strcasecmp(substr($x,$i,2),'dd'  )===0) { $FrmPHP .= 'd'; $FrmLOC .= '%d'; $i += 1;}
4393
					elseif (strcasecmp(substr($x,$i,1),'d'   )===0) { $FrmPHP .= 'j'; $FrmLOC .= '%d';}
4394
					else {
4395
						$FrmPHP .= '\\'.$x[$i]; // protected char
4396
						$FrmLOC .= $x[$i]; // protected char
4397
						$Cnt--;
4398
					}
4399
				}
4400
			}
4401

4402
		}
4403

4404
		if ($Cnt>0) {
4405
			$FormatLst[$FrmStr] = array('type'=>'date','str_us'=>$FrmPHP,'str_loc'=>$FrmLOC,'loc'=>$Locale);
4406
		} else {
4407
			$FormatLst[$FrmStr] = array('type'=>'else','string'=>$FrmStr);
4408
		}
4409

4410
	}
4411

4412
	if ($Alias!='') $FormatLst[$Alias] = &$FormatLst[$FrmStr];
4413

4414
	return $FormatLst[$FrmStr];
4415

4416
}
4417

4418
static function f_Misc_ConvSpe(&$Loc) {
4419
	if ($Loc->ConvMode!==2) {
4420
		$Loc->ConvMode = 2;
4421
		$Loc->ConvEsc = false;
4422
		$Loc->ConvWS = false;
4423
		$Loc->ConvJS = false;
4424
		$Loc->ConvUrl = false;
4425
		$Loc->ConvUtf8 = false;
4426
	}
4427
}
4428

4429
/**
4430
 * Return the information if parsing a form which can be either a property of a function.
4431
 * @param  string $Str The form.              Example : 'my_func(aaa,bbb)'
4432
 * @return array  Information about the form. Example : array('name' => 'my_func', 'as_fct' => true, 'args' => array('aaa', 'bbb'),)
4433
 *                name:   the name of the function of the property.
4434
 *                as_fct: true if the form is as a function
4435
 *                args:   arguments of the function, or empty array if it's a property
4436
 */
4437
static function f_Misc_ParseFctForm($Str) {
4438
	$info = array('name' => $Str, 'as_fct' => false, 'args' => array());
4439
	if (substr($Str,-1,1)===')') {
4440
		$pos = strpos($Str,'(');
4441
		if ($pos!==false) {
4442
			$info['args'] = explode(',',substr($Str,$pos+1,strlen($Str)-$pos-2));
4443
			$info['name'] = substr($Str,0,$pos);
4444
			$info['as_fct'] = true;
4445
		}
4446
	}
4447
	return $info;
4448
}
4449

4450
/**
4451
 * Check if a string condition is true.
4452
 * @param  string  $Str The condition to check.
4453
 * @return boolean True if the condition if checked.
4454
 */
4455
static function f_Misc_CheckCondition($Str) {
4456
// Check if an expression like "exrp1=expr2" is true or false.
4457

4458
	// Bluid $StrZ, wich is the same as $Str but with 'z' for each character that is protected with "'".
4459
	// This will help to search for operators outside protected strings.
4460
	$StrZ = $Str;
4461
	$Max = strlen($Str)-1;
4462
	$p = strpos($Str,'\'');
4463
	if ($Esc=($p!==false)) {
4464
		$In = true;
4465
		for ($p=$p+1;$p<=$Max;$p++) {
4466
			if ($StrZ[$p]==='\'') {
4467
				$In = !$In;
4468
			} elseif ($In) {
4469
				$StrZ[$p] = 'z';
4470
			}
4471
		}
4472
	}
4473

4474
	// Find operator and position
4475
	$Ope = '=';
4476
	$Len = 1;
4477
	$p = strpos($StrZ,$Ope);
4478
	if ($p===false) {
4479
		$Ope = '+';
4480
		$p = strpos($StrZ,$Ope);
4481
		if ($p===false) return false;
4482
		if (($p>0) && ($StrZ[$p-1]==='-')) {
4483
			$Ope = '-+'; $p--; $Len=2;
4484
		} elseif (($p<$Max) && ($StrZ[$p+1]==='-')) {
4485
			$Ope = '+-'; $Len=2;
4486
		} else {
4487
			return false;
4488
		}
4489
	} else {
4490
		if ($p>0) {
4491
			$x = $StrZ[$p-1];
4492
			if ($x==='!') {
4493
				$Ope = '!='; $p--; $Len=2;
4494
			} elseif ($x==='~') {
4495
				$Ope = '~='; $p--; $Len=2;
4496
			} elseif ($p<$Max) {
4497
				$y = $StrZ[$p+1];
4498
				if ($y==='=') {
4499
					$Len=2;
4500
				} elseif (($x==='+') && ($y==='-')) {
4501
					$Ope = '+=-'; $p--; $Len=3;
4502
				} elseif (($x==='-') && ($y==='+')) {
4503
					$Ope = '-=+'; $p--; $Len=3;
4504
				}
4505
			} else {
4506
			}
4507
		}
4508
	}
4509

4510
	// Read values
4511
	$Val1  = trim(substr($Str,0,$p));
4512
	$Val2  = trim(substr($Str,$p+$Len));
4513
	if ($Esc) {
4514
		$NoDelim1 = self::f_Misc_DelDelimiter($Val1,'\'');
4515
		$NoDelim2 = self::f_Misc_DelDelimiter($Val2,'\'');
4516
	} else {
4517
		$NoDelim1 = $NoDelim2 = false;
4518
	}
4519

4520
	// Compare values
4521
	if ($Ope==='=') {
4522
		return (strcasecmp($Val1,$Val2)==0);
4523
	} elseif ($Ope==='!=') {
4524
		return (strcasecmp($Val1,$Val2)!=0);
4525
	} elseif ($Ope==='~=') {
4526
		return (preg_match($Val2,$Val1)>0);
4527
	} else {
4528
		// If a value has no string delimiter, we assume it is supposed to be a numerical comparison.
4529
		if ($NoDelim1 && ($Val1 === '') ) $Val1 = '0';
4530
		if ($NoDelim2 && ($Val2 === '') ) $Val2 ='0';
4531
		// PHP makes a numerical comparison when each item is independently either a numeric value or a numeric string. Otherwise it makes a string comparison.
4532
		// So we let PHP doing the comparison on its onw way.
4533
		if ($Ope==='+-') {
4534
			return ($Val1 > $Val2);
4535
		} elseif ($Ope==='-+') {
4536
			return ($Val1 < $Val2);
4537
		} elseif ($Ope==='+=-') {
4538
			return ($Val1 >= $Val2);
4539
		} elseif ($Ope==='-=+') {
4540
			return ($Val1<=$Val2);
4541
		} else {
4542
			return false;
4543
		}
4544
	}
4545

4546
}
4547

4548
/**
4549
 * Delete the string delimiters that surrounds the string, if any. But not inside (no need).
4550
 * @param  string $Txt    The string to modifiy.
4551
 * @param  string $Delim  The character that can delimit the string.
4552
 * @return boolean True if the given string was not delimited with $Delim.
4553
 */
4554
static function f_Misc_DelDelimiter(&$Txt,$Delim) {
4555
// Delete the string delimiters
4556
	$len = strlen($Txt);
4557
	if (($len>1) && ($Txt[0]===$Delim)) {
4558
		if ($Txt[$len-1]===$Delim) $Txt = substr($Txt,1,$len-2);
4559
		return false;
4560
	} else {
4561
		return true;
4562
	}
4563
}
4564

4565
static function f_Misc_GetFile(&$Res, &$File, $LastFile='', $IncludePath=false, $Contents=true) {
4566
// Load the content of a file into the text variable.
4567

4568
	$Res = '';
4569
	$fd = self::f_Misc_TryFile($File, false); 
4570
	if ($fd===false) {
4571
		if (is_array($IncludePath)) {
4572
			foreach ($IncludePath as $d) {
4573
				$fd = self::f_Misc_TryFile($File, $d);
4574
				if ($fd!==false) break;
4575
			}
4576
		}
4577
		if (($fd===false) && ($LastFile!='')) $fd = self::f_Misc_TryFile($File, dirname($LastFile));
4578
		if ($fd===false) return false;
4579
	}
4580

4581
	$fs = fstat($fd);
4582
	if ($Contents) {
4583
		// Return contents
4584
		if (isset($fs['size'])) {
4585
			if ($fs['size']>0) $Res = fread($fd,$fs['size']);
4586
		} else {
4587
			while (!feof($fd)) $Res .= fread($fd,4096);
4588
		}
4589
	} else {
4590
		// Return stats
4591
		$Res = $fs;
4592
	}
4593

4594
	fclose($fd);
4595
	return true;
4596

4597
}
4598

4599
/**
4600
 * Try to open the file for reading.
4601
 * @param string        $File The file name.
4602
 * @param string|bolean $Dir  A The directory where to search, of false to omit directory.
4603
 * @return ressource Return the file pointer, of false on error. Note that urgument $File will be updated to the file with directory.
4604
 */
4605
static function f_Misc_TryFile(&$File, $Dir) {
4606
	if ($Dir==='') return false;
4607
	$FileSearch = ($Dir===false) ? $File : $Dir.'/'.$File;
4608
	// 'rb' if binary for some OS. fopen() uses include_path and search on the __FILE__ directory while file_exists() doesn't.
4609
	$f = @fopen($FileSearch, 'r', true);
4610
	if ($f!==false) $File = $FileSearch;
4611
	return $f;
4612
}
4613

4614
/**
4615
 * Read TBS or XML tags, starting to the begining of the tag.
4616
 */
4617
static function f_Loc_PrmRead(&$Txt,$Pos,$XmlTag,$DelimChrs,$BegStr,$EndStr,&$Loc,&$PosEnd,$WithPos=false) {
4618

4619
	$BegLen = strlen($BegStr);
4620
	$BegChr = $BegStr[0];
4621
	$BegIs1 = ($BegLen===1);
4622

4623
	$DelimIdx = false;
4624
	$DelimCnt = 0;
4625
	$DelimChr = '';
4626
	$BegCnt = 0;
4627
	$SubName = $Loc->SubOk;
4628

4629
	$Status = 0; // 0: name not started, 1: name started, 2: name ended, 3: equal found, 4: value started
4630
	$PosName = 0;
4631
	$PosNend = 0;
4632
	$PosVal = 0;
4633

4634
	// Variables for checking the loop
4635
	$PosEnd = strpos($Txt,$EndStr,$Pos);
4636
	if ($PosEnd===false) return;
4637
	$Continue = ($Pos<$PosEnd);
4638

4639
	while ($Continue) {
4640

4641
		$Chr = $Txt[$Pos];
4642

4643
		if ($DelimIdx) { // Reading in the string
4644

4645
			if ($Chr===$DelimChr) { // Quote found
4646
				if ($Chr===$Txt[$Pos+1]) { // Double Quote => the string continue with un-double the quote
4647
					$Pos++;
4648
				} else { // Simple Quote => end of string
4649
					$DelimIdx = false;
4650
				}
4651
			}
4652

4653
		} else { // Reading outside the string
4654

4655
			if ($BegCnt===0) {
4656

4657
				// Analyzing parameters
4658
				$CheckChr = false;
4659
				if (($Chr===' ') || ($Chr==="\r") || ($Chr==="\n")) {
4660
					if ($Status===1) {
4661
						if ($SubName && ($XmlTag===false)) {
4662
							// Accept spaces in TBS subname.
4663
						} else {
4664
							$Status = 2;
4665
							$PosNend = $Pos;
4666
						}
4667
					} elseif ($XmlTag && ($Status===4)) {
4668
						self::f_Loc_PrmCompute($Txt,$Loc,$SubName,$Status,$XmlTag,$DelimChr,$DelimCnt,$PosName,$PosNend,$PosVal,$Pos,$WithPos);
4669
						$Status = 0;
4670
					}
4671
				} elseif (($XmlTag===false) && ($Chr===';')) {
4672
					self::f_Loc_PrmCompute($Txt,$Loc,$SubName,$Status,$XmlTag,$DelimChr,$DelimCnt,$PosName,$PosNend,$PosVal,$Pos,$WithPos);
4673
					$Status = 0;
4674
				} elseif ($Status===4) {
4675
					$CheckChr = true;
4676
				} elseif ($Status===3) {
4677
					$Status = 4;
4678
					$DelimCnt = 0;
4679
					$PosVal = $Pos;
4680
					$CheckChr = true;
4681
				} elseif ($Status===2) {
4682
					if ($Chr==='=') {
4683
						$Status = 3;
4684
					} elseif ($XmlTag) {
4685
						self::f_Loc_PrmCompute($Txt,$Loc,$SubName,$Status,$XmlTag,$DelimChr,$DelimCnt,$PosName,$PosNend,$PosVal,$Pos,$WithPos);
4686
						$Status = 1;
4687
						$PosName = $Pos;
4688
						$CheckChr = true;
4689
					} else {
4690
						$Status = 4;
4691
						$DelimCnt = 0;
4692
						$PosVal = $Pos;
4693
						$CheckChr = true;
4694
					}
4695
				} elseif ($Status===1) {
4696
					if ($Chr==='=') {
4697
						$Status = 3;
4698
						$PosNend = $Pos;
4699
					} else {
4700
						$CheckChr = true;
4701
					}
4702
				} else {
4703
					$Status = 1;
4704
					$PosName = $Pos;
4705
					$CheckChr = true;
4706
				}
4707

4708
				if ($CheckChr) {
4709
					$DelimIdx = strpos($DelimChrs,$Chr);
4710
					if ($DelimIdx===false) {
4711
						if ($Chr===$BegChr) {
4712
							if ($BegIs1) {
4713
								$BegCnt++;
4714
							} elseif(substr($Txt,$Pos,$BegLen)===$BegStr) {
4715
								$BegCnt++;
4716
							}
4717
						}
4718
					} else {
4719
						$DelimChr = $DelimChrs[$DelimIdx];
4720
						$DelimCnt++;
4721
						$DelimIdx = true;
4722
					}
4723
				}
4724

4725
			} else {
4726
				if ($Chr===$BegChr) {
4727
					if ($BegIs1) {
4728
						$BegCnt++;
4729
					} elseif(substr($Txt,$Pos,$BegLen)===$BegStr) {
4730
						$BegCnt++;
4731
					}
4732
				}
4733
			}
4734

4735
		}
4736

4737
		// Next char
4738
		$Pos++;
4739

4740
		// We check if it's the end
4741
		if ($Pos===$PosEnd) {
4742
			if ($XmlTag) {
4743
				$Continue = false;
4744
			} elseif ($DelimIdx===false) {
4745
				if ($BegCnt>0) {
4746
					$BegCnt--;
4747
				} else {
4748
					$Continue = false;
4749
				}
4750
			}
4751
			if ($Continue) {
4752
				$PosEnd = strpos($Txt,$EndStr,$PosEnd+1);
4753
				if ($PosEnd===false) return;
4754
			} else {
4755
				if ($XmlTag && ($Txt[$Pos-1]==='/')) $Pos--; // In case last attribute is stuck to "/>"
4756
				self::f_Loc_PrmCompute($Txt,$Loc,$SubName,$Status,$XmlTag,$DelimChr,$DelimCnt,$PosName,$PosNend,$PosVal,$Pos,$WithPos);
4757
			}
4758
		}
4759

4760
	}
4761

4762
	$PosEnd = $PosEnd + (strlen($EndStr)-1);
4763

4764
}
4765

4766
static function f_Loc_PrmCompute(&$Txt,&$Loc,&$SubName,$Status,$XmlTag,$DelimChr,$DelimCnt,$PosName,$PosNend,$PosVal,$Pos,$WithPos) {
4767

4768
	if ($Status===0) {
4769
		$SubName = false;
4770
	} else {
4771
		if ($Status===1) {
4772
			$x = substr($Txt,$PosName,$Pos-$PosName);
4773
		} else {
4774
			$x = substr($Txt,$PosName,$PosNend-$PosName);
4775
		}
4776
		if ($XmlTag) $x = strtolower($x);
4777
		if ($SubName) {
4778
			$Loc->SubName = trim($x);
4779
			$SubName = false;
4780
		} else {
4781
			if ($Status===4) {
4782
				$v = trim(substr($Txt,$PosVal,$Pos-$PosVal));
4783
				if ($DelimCnt===1) { // Delete quotes inside the value
4784
					if ($v[0]===$DelimChr) {
4785
						$len = strlen($v);
4786
						if ($v[$len-1]===$DelimChr) {
4787
							$v = substr($v,1,$len-2);
4788
							$v = str_replace($DelimChr.$DelimChr,$DelimChr,$v);
4789
						}
4790
					}
4791
				}
4792
			} else {
4793
				$v = true;
4794
			}
4795
			if ($x==='if') {
4796
				self::f_Loc_PrmIfThen($Loc, true, $v, true);
4797
			} elseif ($x==='then') {
4798
				self::f_Loc_PrmIfThen($Loc, false, $v, true);
4799
			} else {
4800
				$Loc->PrmLst[$x] = $v;
4801
				if ($WithPos) $Loc->PrmPos[$x] = array($PosName,$PosNend,$PosVal,$Pos,$DelimChr,$DelimCnt);
4802
			}
4803
		}
4804
	}
4805

4806
}
4807

4808
/**
4809
 * Add a new parameter 'if or 'then' to the locator.
4810
 * 
4811
 * @param object  $Loc     The locator.
4812
 * @param boolean $IsIf    Concerned parameter. True means 'if', false means 'then'.
4813
 * @param string  $Val     The value of the parameter.
4814
 * @param boolean $Ordered True means the parameter comes from the template and order must be checked. False means it comes from PHP and order is free.
4815
 *
4816
 */
4817
static function f_Loc_PrmIfThen(&$Loc, $IsIf, $Val, $Ordered) {
4818
	$nb_if = &$Loc->PrmIfNbr;
4819
	if ($nb_if===false) {
4820
		$nb_if = 0;
4821
		$Loc->PrmIf = array();
4822
		$Loc->PrmIfVar = array();
4823
		$Loc->PrmThen = array();
4824
		$Loc->PrmThenVar = array();
4825
		$Loc->PrmElseVar = true;
4826
	}
4827
	if ($IsIf) {
4828
		$nb_if++;
4829
		$Loc->PrmIf[$nb_if] = $Val;
4830
		$Loc->PrmIfVar[$nb_if] = true;
4831
	} else {
4832
		if ($Ordered) {
4833
			$nb_then = $nb_if;
4834
			if ($nb_then===false) $nb_then = 1; // Only the first 'then' can be placed before its 'if'. This is for compatibility.
4835
		} else {
4836
			$nb_then = count($Loc->PrmThen) + 1;
4837
		}
4838
		$Loc->PrmThen[$nb_then] = $Val;
4839
		$Loc->PrmThenVar[$nb_then] = true;
4840
	}
4841
}
4842

4843
/*
4844
This function enables to enlarge the pos limits of the Locator.
4845
If the search result is not correct, $PosBeg must not change its value, and $PosEnd must be False.
4846
This is because of the calling function.
4847
*/
4848
static function f_Loc_EnlargeToStr(&$Txt,&$Loc,$StrBeg,$StrEnd) {
4849

4850
	// Search for the begining string
4851
	$Pos = $Loc->PosBeg;
4852
	$Ok = false;
4853
	do {
4854
		$Pos = strrpos(substr($Txt,0,$Pos),$StrBeg[0]);
4855
		if ($Pos!==false) {
4856
			if (substr($Txt,$Pos,strlen($StrBeg))===$StrBeg) $Ok = true;
4857
		}
4858
	} while ( (!$Ok) && ($Pos!==false) );
4859

4860
	if ($Ok) {
4861
		$PosEnd = strpos($Txt,$StrEnd,$Loc->PosEnd + 1);
4862
		if ($PosEnd===false) {
4863
			$Ok = false;
4864
		} else {
4865
			$Loc->PosBeg = $Pos;
4866
			$Loc->PosEnd = $PosEnd + strlen($StrEnd) - 1;
4867
		}
4868
	}
4869

4870
	return $Ok;
4871

4872
}
4873

4874
static function f_Loc_EnlargeToTag(&$Txt,&$Loc,$TagStr,$RetInnerSrc) {
4875
//Modify $Loc, return false if tags not found, returns the inner source of tag if $RetInnerSrc=true
4876

4877
	$AliasLst = &$GLOBALS['_TBS_BlockAlias'];
4878

4879
	// Analyze string
4880
	$Ref = 0;
4881
	$LevelStop = 0;
4882
	$i = 0;
4883
	$TagFct = array();
4884
	$TagLst = array();
4885
	$TagBnd = array();
4886
	while ($TagStr!=='') {
4887
		// get next tag
4888
		$p = strpos($TagStr, '+');
4889
		if ($p===false) {
4890
			$t = $TagStr;
4891
			$TagStr = '';
4892
		} else {
4893
			$t = substr($TagStr,0,$p);
4894
			$TagStr = substr($TagStr,$p+1);
4895
		}
4896
		// Check parentheses, relative position and single tag
4897
		do {
4898
			$t = trim($t);
4899
	 		$e = strlen($t) - 1; // pos of last char
4900
	 		if (($e>1) && ($t[0]==='(') && ($t[$e]===')')) {
4901
	 			if ($Ref===0) $Ref = $i;
4902
	 			if ($Ref===$i) $LevelStop++;
4903
	 			$t = substr($t,1,$e-1);
4904
	 		} else {
4905
	 			if (($e>=0) && ($t[$e]==='/')) $t = substr($t,0,$e); // for compatibilty
4906
	 			$e = false;
4907
	 		}
4908
		} while ($e!==false);
4909
		// Check for multiples
4910
		$p = strpos($t, '*');
4911
		if ($p!==false) {
4912
			$n = intval(substr($t, 0, $p));
4913
			$t = substr($t, $p + 1);
4914
			$n = max($n ,1); // prevent for error: minimum valu is 1
4915
			$TagStr = str_repeat($t . '+', $n-1) . $TagStr;
4916
		}
4917
		// Reference
4918
		if (($t==='.') && ($Ref===0)) $Ref = $i;
4919
		// Take of the (!) prefix
4920
		$b = '';
4921
		if (($t!=='') && ($t[0]==='!')) {
4922
			$t = substr($t, 1);
4923
			$b = '!';
4924
		}
4925
		// Block alias
4926
		$a = false;
4927
		if (isset($AliasLst[$t])) {
4928
			$a = $AliasLst[$t]; // a string or a function
4929
			if (is_string($a)) {
4930
				if ($i>999) return false; // prevent from circular alias
4931
				$TagStr = $b . $a . (($TagStr==='') ? '' : '+') . $TagStr;
4932
				$t = false;
4933
			}
4934
		}
4935
		if ($t!==false) {
4936
			$TagLst[$i] = $t; // with prefix ! if specified
4937
			$TagFct[$i] = $a;
4938
			$TagBnd[$i] = ($b==='');
4939
			$i++;
4940
		}
4941
	}
4942
	
4943
	$TagMax = $i-1;
4944

4945
	// Find tags that embeds the locator
4946
	if ($LevelStop===0) $LevelStop = 1;
4947

4948
	// First tag of reference
4949
	if ($TagLst[$Ref] === '.') {
4950
		$TagO = new clsTbsLocator;
4951
		$TagO->PosBeg = $Loc->PosBeg;
4952
		$TagO->PosEnd = $Loc->PosEnd;
4953
		$PosBeg = $Loc->PosBeg;
4954
		$PosEnd = $Loc->PosEnd;
4955
	} else {
4956
		$TagO = self::f_Loc_Enlarge_Find($Txt,$TagLst[$Ref],$TagFct[$Ref],$Loc->PosBeg-1,false,$LevelStop);
4957
		if ($TagO===false) return false;
4958
		$PosBeg = $TagO->PosBeg;
4959
		$LevelStop += -$TagO->RightLevel; // RightLevel=1 only if the tag is single and embeds $Loc, otherwise it is 0 
4960
		if ($LevelStop>0) {
4961
			$TagC = self::f_Loc_Enlarge_Find($Txt,$TagLst[$Ref],$TagFct[$Ref],$Loc->PosEnd+1,true,-$LevelStop);
4962
			if ($TagC==false) return false;
4963
			$PosEnd = $TagC->PosEnd;
4964
			$InnerLim = $TagC->PosBeg;
4965
			if ((!$TagBnd[$Ref]) && ($TagMax==0)) {
4966
				$PosBeg = $TagO->PosEnd + 1;
4967
				$PosEnd = $TagC->PosBeg - 1;
4968
			}
4969
		} else {
4970
			$PosEnd = $TagO->PosEnd;
4971
			$InnerLim = $PosEnd + 1;
4972
		}
4973
	}
4974

4975
	$RetVal = true;
4976
	if ($RetInnerSrc) {
4977
		$RetVal = '';
4978
		if ($Loc->PosBeg>$TagO->PosEnd) $RetVal .= substr($Txt,$TagO->PosEnd+1,min($Loc->PosBeg,$InnerLim)-$TagO->PosEnd-1);
4979
		if ($Loc->PosEnd<$InnerLim) $RetVal .= substr($Txt,max($Loc->PosEnd,$TagO->PosEnd)+1,$InnerLim-max($Loc->PosEnd,$TagO->PosEnd)-1);
4980
	}
4981

4982
	// Other tags forward
4983
	$TagC = true;
4984
	for ($i=$Ref+1;$i<=$TagMax;$i++) {
4985
		$x = $TagLst[$i];
4986
		if (($x!=='') && ($TagC!==false)) {
4987
			$level = ($TagBnd[$i]) ? 0 : 1;
4988
			$TagC = self::f_Loc_Enlarge_Find($Txt,$x,$TagFct[$i],$PosEnd+1,true,$level);
4989
			if ($TagC!==false) {
4990
				$PosEnd = ($TagBnd[$i]) ? $TagC->PosEnd : $TagC->PosBeg -1 ;
4991
			}
4992
		}
4993
	}
4994

4995
	// Other tags backward
4996
	$TagO = true;
4997
	for ($i=$Ref-1;$i>=0;$i--) {
4998
		$x = $TagLst[$i];
4999
		if (($x!=='') && ($TagO!==false)) {
5000
			$level = ($TagBnd[$i]) ? 0 : -1;
5001
			$TagO = self::f_Loc_Enlarge_Find($Txt,$x,$TagFct[$i],$PosBeg-1,false,$level);
5002
			if ($TagO!==false) {
5003
				$PosBeg = ($TagBnd[$i]) ? $TagO->PosBeg : $TagO->PosEnd + 1;
5004
			}
5005
		}
5006
	}
5007

5008
	$Loc->PosBeg = $PosBeg;
5009
	$Loc->PosEnd = $PosEnd;
5010
	return $RetVal;
5011

5012
}
5013

5014
static function f_Loc_Enlarge_Find($Txt, $Tag, $Fct, $Pos, $Forward, $LevelStop) {
5015
	if ($Fct===false) {
5016
		return self::f_Xml_FindTag($Txt,$Tag,(!$Forward),$Pos,$Forward,$LevelStop,false);
5017
	} else {
5018
		$p = call_user_func_array($Fct,array($Tag,$Txt,$Pos,$Forward,$LevelStop));
5019
		if ($p===false) {
5020
			return false;
5021
		} else {
5022
			return (object) array('PosBeg'=>$p, 'PosEnd'=>$p, 'RightLevel'=> 0); // it's a trick
5023
		}	
5024
	}
5025
}
5026

5027
/**
5028
 * Return the expected value for a boolean attribute
5029
 */
5030
static function f_Loc_AttBoolean($CurrVal, $AttTrue, $AttName) {
5031
	
5032
	if ($AttTrue===true) {
5033
		if (self::meth_Misc_ToStr($CurrVal)==='') {
5034
			return '';
5035
		} else {
5036
			return $AttName;
5037
		}
5038
	} elseif (self::meth_Misc_ToStr($CurrVal)===$AttTrue) {
5039
		return $AttName;
5040
	} else {
5041
		return '';
5042
	}
5043
	
5044
}
5045

5046
/**
5047
 * Affects the positions of a list of locators regarding to a specific moving locator.
5048
 */
5049
static function f_Loc_Moving(&$LocM, &$LocLst) {
5050
	foreach ($LocLst as &$Loc) {
5051
		if ($Loc !== $LocM) {
5052
			if ($Loc->PosBeg >= $LocM->InsPos) {
5053
				$Loc->PosBeg += $LocM->InsLen;
5054
				$Loc->PosEnd += $LocM->InsLen;
5055
			}
5056
			if ($Loc->PosBeg > $LocM->DelPos) {
5057
				$Loc->PosBeg -= $LocM->DelLen;
5058
				$Loc->PosEnd -= $LocM->DelLen;
5059
			}
5060
		}
5061
	}
5062
	return true;
5063
}
5064

5065
/**
5066
 * Sort the locators in the list. Apply the bubble algorithm.
5067
 * Deleted locators maked with DelMe.
5068
 * @param array   $LocLst An array of locators.
5069
 * @param boolean $DelEmbd True to deleted locators that embded other ones.
5070
 * @param boolean $iFirst Index of the first item.
5071
 * @return integer Return the number of met embedding locators.
5072
 */
5073
static function f_Loc_Sort(&$LocLst, $DelEmbd, $iFirst = 0) {
5074

5075
	$iLast = $iFirst + count($LocLst) - 1;
5076
	$embd = 0;
5077
	
5078
	for ($i = $iLast ; $i>=$iFirst ; $i--) {
5079
		$Loc = $LocLst[$i];
5080
		$d = (isset($Loc->DelMe) && $Loc->DelMe);
5081
		$b = $Loc->PosBeg;
5082
		$e = $Loc->PosEnd;
5083
		for ($j=$i+1; $j<=$iLast ; $j++) {
5084
			// If DelMe, then the loc will be put at the end and deleted
5085
			$jb = $LocLst[$j]->PosBeg;
5086
			if ($d || ($b > $jb)) {
5087
				$LocLst[$j-1] = $LocLst[$j];
5088
				$LocLst[$j] = $Loc;
5089
			} elseif ($e > $jb) {
5090
				$embd++;
5091
				if ($DelEmbd) {
5092
					$d = true;
5093
					$j--; // replay the current position
5094
				} else {
5095
					$j = $iLast; // quit the loop
5096
				}
5097
			} else {
5098
				$j = $iLast; // quit the loop
5099
			}
5100
		}
5101
		if ($d) {
5102
			unset($LocLst[$iLast]);
5103
			$iLast--;
5104
		}
5105
	}
5106
	
5107
	return $embd;
5108
}
5109

5110
/**
5111
 * Prepare all informations to move a locator according to parameter "att".
5112
 *
5113
 * @param false|true|array $MoveLocLst true to simple move the loc, or an array of loc to rearrange the list after the move.
5114
 *                          Note: rearrange doest not work with PHP4.
5115
 */
5116
static function f_Xml_AttFind(&$Txt,&$Loc,$MoveLocLst=false,$AttDelim=false,$LocLst=false) {
5117
// att=div#class ; att=((div))#class ; att=+((div))#class
5118

5119
	$Att = $Loc->PrmLst['att'];
5120
	unset($Loc->PrmLst['att']); // prevent from processing the field twice
5121
	$Loc->PrmLst['att;'] = $Att; // for debug
5122

5123
	$p = strrpos($Att,'#');
5124
	if ($p===false) {
5125
		$TagLst = '';
5126
	} else {
5127
		$TagLst = substr($Att,0,$p);
5128
		$Att = substr($Att,$p+1);
5129
	}
5130

5131
	$Forward = (substr($TagLst,0,1)==='+');
5132
	if ($Forward) $TagLst = substr($TagLst,1);
5133
	$TagLst = explode('+',$TagLst);
5134

5135
	$iMax = count($TagLst)-1;
5136
	$WithPrm = false;
5137
	$LocO = &$Loc;
5138
	foreach ($TagLst as $i=>$Tag) {
5139
		$LevelStop = false;
5140
		while ((strlen($Tag)>1) && (substr($Tag,0,1)==='(') && (substr($Tag,-1,1)===')')) {
5141
			if ($LevelStop===false) $LevelStop = 0;
5142
			$LevelStop++;
5143
			$Tag = trim(substr($Tag,1,strlen($Tag)-2));
5144
		}
5145
		if ($i==$iMax) $WithPrm = true;
5146
		$Pos = ($Forward) ? $LocO->PosEnd+1 : $LocO->PosBeg-1;
5147
		unset($LocO);
5148
		$LocO = self::f_Xml_FindTag($Txt,$Tag,true,$Pos,$Forward,$LevelStop,$WithPrm,$WithPrm);
5149
		if ($LocO===false) return false;
5150
	}
5151

5152
	$Loc->AttForward = $Forward;
5153
	$Loc->AttTagBeg = $LocO->PosBeg;
5154
	$Loc->AttTagEnd = $LocO->PosEnd;
5155
	$Loc->AttDelimChr = false;
5156

5157
	if ($Att==='.') {
5158
		// this indicates that the TBS field is supposed to be inside an attribute's value
5159
		foreach ($LocO->PrmPos as $a=>$p ) {
5160
			if ( ($p[0]<$Loc->PosBeg) && ($Loc->PosEnd<$p[3]) ) $Att = $a;
5161
		}
5162
		if ($Att==='.') return false;
5163
	}
5164
	$Loc->AttName = $Att;
5165
	
5166
	$AttLC = strtolower($Att);
5167
	if (isset($LocO->PrmLst[$AttLC])) {
5168
		// The attribute is existing
5169
		$p = $LocO->PrmPos[$AttLC];
5170
		$Loc->AttBeg = $p[0];
5171
		$p[3]--; while ($Txt[$p[3]]===' ') $p[3]--; // external end of the attribute, may has an extra spaces
5172
		$Loc->AttEnd = $p[3];
5173
		$Loc->AttDelimCnt = $p[5];
5174
		$Loc->AttDelimChr = $p[4];
5175
		if (($p[1]>$p[0]) && ($p[2]>$p[1])) {
5176
			//$Loc->AttNameEnd =  $p[1];
5177
			$Loc->AttValBeg = $p[2];
5178
		} else { // attribute without value
5179
			//$Loc->AttNameEnd =  $p[3];
5180
			$Loc->AttValBeg = false;
5181
		}
5182
	} else {
5183
		// The attribute is not yet existing
5184
		$Loc->AttDelimCnt = 0;
5185
		$Loc->AttBeg = false;
5186
	}
5187
	
5188
	// Search for a delimitor
5189
	if (($Loc->AttDelimCnt==0) && (isset($LocO->PrmPos))) {
5190
		foreach ($LocO->PrmPos as $p) {
5191
			if ($p[5]>0) $Loc->AttDelimChr = $p[4];
5192
		}
5193
	}
5194

5195
	if ($MoveLocLst) return self::f_Xml_AttMove($Txt,$Loc,$AttDelim,$MoveLocLst);
5196

5197
	return true;
5198

5199
}
5200

5201
/**
5202
 * Move a locator in the source from its original location to the attribute location.
5203
 * The new locator string is only '[]', no need to copy the full source since all parameters are saved in $Loc.*
5204
 *
5205
 * @param false|true|array $MoveLocLst If the function is called from the caching process, then this value is an array.
5206
 */
5207
static function f_Xml_AttMove(&$Txt, &$Loc, $AttDelim, &$MoveLocLst) {
5208

5209
	if ($AttDelim===false) $AttDelim = $Loc->AttDelimChr;
5210
	if ($AttDelim===false) $AttDelim = '"';
5211

5212
	$DelPos = $Loc->PosBeg;
5213
	$DelLen = $Loc->PosEnd - $Loc->PosBeg + 1;
5214
	$Txt = substr_replace($Txt,'',$DelPos,$DelLen); // delete the current locator
5215
	if ($Loc->AttForward) {
5216
		$Loc->AttTagBeg += -$DelLen;
5217
		$Loc->AttTagEnd += -$DelLen;
5218
	} elseif ($Loc->PosBeg<$Loc->AttTagEnd) {
5219
		$Loc->AttTagEnd += -$DelLen;
5220
	}
5221

5222
	$InsPos = false;
5223
	if ($Loc->AttBeg===false) {
5224
		$InsPos = $Loc->AttTagEnd;
5225
		if ($Txt[$InsPos-1]==='/') $InsPos--;
5226
		if ($Txt[$InsPos-1]===' ') $InsPos--;
5227
		$Ins1 = ' '.$Loc->AttName.'='.$AttDelim;
5228
		$Ins2 = $AttDelim;
5229
		$Loc->AttBeg = $InsPos + 1;
5230
		$Loc->AttValBeg = $InsPos + strlen($Ins1) - 1;
5231
	} else {
5232
		if ($Loc->PosEnd<$Loc->AttBeg) $Loc->AttBeg += -$DelLen;
5233
		if ($Loc->PosEnd<$Loc->AttEnd) $Loc->AttEnd += -$DelLen;
5234
		if ($Loc->AttValBeg===false) {
5235
			$InsPos = $Loc->AttEnd+1;
5236
			$Ins1 = '='.$AttDelim;
5237
			$Ins2 = $AttDelim;
5238
			$Loc->AttValBeg = $InsPos+1;
5239
		} elseif (isset($Loc->PrmLst['attadd'])) {
5240
			$InsPos = $Loc->AttEnd;
5241
			$Ins1 = ' ';
5242
			$Ins2 = '';
5243
		} else {
5244
			// value already existing
5245
			if ($Loc->PosEnd<$Loc->AttValBeg) $Loc->AttValBeg += -$DelLen;
5246
			$PosBeg = $Loc->AttValBeg;
5247
			$PosEnd = $Loc->AttEnd;
5248
			if ($Loc->AttDelimCnt>0) {$PosBeg++; $PosEnd--;}
5249
		}
5250
	}
5251

5252
	if ($InsPos===false) {
5253
		$InsLen = 0;
5254
	} else {
5255
		$InsTxt = $Ins1.'[]'.$Ins2;
5256
		$InsLen = strlen($InsTxt);
5257
		$PosBeg = $InsPos + strlen($Ins1);
5258
		$PosEnd = $PosBeg + 1;
5259
		$Txt = substr_replace($Txt,$InsTxt,$InsPos,0);
5260
		$Loc->AttEnd = $InsPos + $InsLen - 1;
5261
		$Loc->AttTagEnd += $InsLen;
5262
	}
5263

5264
	$Loc->PosBeg = $PosBeg;
5265
	$Loc->PosEnd = $PosEnd;
5266

5267
	// for CacheField
5268
	if (is_array($MoveLocLst)) {
5269
		$Loc->InsPos = $InsPos;
5270
		$Loc->InsLen = $InsLen;
5271
		$Loc->DelPos = $DelPos;
5272
		if ($Loc->InsPos < $Loc->DelPos) $Loc->DelPos += $InsLen;
5273
		$Loc->DelLen = $DelLen;
5274
		self::f_Loc_Moving($Loc, $MoveLocLst);
5275
	}
5276
	
5277
	return true;
5278

5279
}
5280

5281
static function f_Xml_Max(&$Txt,&$Nbr,$MaxEnd) {
5282
// Limit the number of HTML chars
5283

5284
	$pMax =  strlen($Txt)-1;
5285
	$p=0;
5286
	$n=0;
5287
	$in = false;
5288
	$ok = true;
5289

5290
	while ($ok) {
5291
		if ($in) {
5292
			if ($Txt[$p]===';') {
5293
				$in = false;
5294
				$n++;
5295
			}
5296
		} else {
5297
			if ($Txt[$p]==='&') {
5298
				$in = true;
5299
			} else {
5300
				$n++;
5301
			}
5302
		}
5303
		if (($n>=$Nbr) || ($p>=$pMax)) {
5304
			$ok = false;
5305
		} else {
5306
			$p++;
5307
		}
5308
	}
5309

5310
	if (($n>=$Nbr) && ($p<$pMax)) $Txt = substr($Txt,0,$p).$MaxEnd;
5311

5312
}
5313

5314
static function f_Xml_GetPart(&$Txt, $TagLst, $AllIfNothing=false) {
5315
// Returns parts of the XML/HTML content, default is BODY.
5316

5317
	if (($TagLst===true) || ($TagLst==='')) $TagLst = 'body';
5318

5319
	$x = '';
5320
	$nothing = true;
5321
	$TagLst = explode('+',$TagLst);
5322

5323
	// Build a clean list of tags
5324
	foreach ($TagLst as $i=>$t) {
5325
		if ((substr($t,0,1)=='(') && (substr($t,-1,1)==')')) {
5326
			$t = substr($t,1,strlen($t)-2);
5327
			$Keep = true;
5328
		} else {
5329
			$Keep = false;
5330
		}
5331
		$TagLst[$i] = array('t'=>$t, 'k'=>$Keep, 'b'=>-1, 'e'=>-1, 's'=>false);
5332
	}
5333

5334
	$PosOut = strlen($Txt);
5335
	$Pos = 0;
5336
	
5337
	// Optimized search for all tag types
5338
	do {
5339

5340
		// Search next positions of each tag type
5341
		$TagMin = false;   // idx of the tag at first position
5342
		$PosMin = $PosOut; // pos of the tag at first position
5343
		foreach ($TagLst as $i=>$Tag) {
5344
			if ($Tag['b']<$Pos) {
5345
				$Loc = self::f_Xml_FindTag($Txt,$Tag['t'],true,$Pos,true,false,false);
5346
				if ($Loc===false) {
5347
					$Tag['b'] = $PosOut; // tag not found, no more search on this tag
5348
				} else {
5349
					$Tag['b'] = $Loc->PosBeg;
5350
					$Tag['e'] = $Loc->PosEnd;
5351
					$Tag['s'] = (substr($Txt,$Loc->PosEnd-1,1)==='/'); // true if it's a single tag
5352
				}
5353
				$TagLst[$i] = $Tag; // update
5354
			}
5355
			if ($Tag['b']<$PosMin) {
5356
				$TagMin = $i;
5357
				$PosMin = $Tag['b'];
5358
			}
5359
		}
5360

5361
		// Add the part of tag types
5362
		if ($TagMin!==false) {
5363
			$Tag = &$TagLst[$TagMin];
5364
			$Pos = $Tag['e']+1;
5365
			if ($Tag['s']) {
5366
				// single tag
5367
				if ($Tag['k']) $x .= substr($Txt,$Tag['b']  ,$Tag['e'] - $Tag['b'] + 1);
5368
			} else {
5369
				// search the closing tag
5370
				$Loc = self::f_Xml_FindTag($Txt,$Tag['t'],false,$Pos,true,false,false);
5371
				if ($Loc===false) {
5372
					$Tag['b'] = $PosOut; // closing tag not found, no more search on this tag
5373
				} else {
5374
					$nothing = false;
5375
					if ($Tag['k']) {
5376
						$x .= substr($Txt,$Tag['b']  ,$Loc->PosEnd - $Tag['b'] + 1);
5377
					} else {
5378
						$x .= substr($Txt,$Tag['e']+1,$Loc->PosBeg - $Tag['e'] - 1);
5379
					}
5380
					$Pos = $Loc->PosEnd + 1;
5381
				}
5382
			}
5383
		}
5384

5385
	} while ($TagMin!==false);
5386
	
5387
	if ($AllIfNothing && $nothing) return $Txt;
5388
	return $x;
5389

5390
}
5391

5392
/**
5393
 * Find the start position of an XML tag. Used by OpenTBS.
5394
 * $Case=false can be useful for HTML.
5395
 * $Tag=''  should work and found the start of the first opening tag of any name.
5396
 * $Tag='/' should work and found the start of the first closing tag of any name.
5397
 * Encapsulation levels are not featured yet.
5398
 */
5399
static function f_Xml_FindTagStart(&$Txt,$Tag,$Opening,$PosBeg,$Forward,$Case=true) {
5400

5401
	if ($Txt==='') return false;
5402

5403
	$x = '<'.(($Opening) ? '' : '/').$Tag;
5404
	$xl = strlen($x);
5405

5406
	$p = $PosBeg - (($Forward) ? 1 : -1);
5407

5408
	if ($Case) {
5409
		do {
5410
			if ($Forward) $p = strpos($Txt,$x,$p+1);  else $p = strrpos(substr($Txt,0,$p+1),$x);
5411
			if ($p===false) return false;
5412
			$z = substr($Txt,$p+$xl,1);
5413
		} while ( ($z!==' ') && ($z!=="\r") && ($z!=="\n") && ($z!=='>') && ($z!=='/') && ($Tag!=='/') && ($Tag!=='') );
5414
	} else {
5415
		do {
5416
			if ($Forward) $p = stripos($Txt,$x,$p+1);  else $p = strripos(substr($Txt,0,$p+1),$x);
5417
			if ($p===false) return false;
5418
			$z = substr($Txt,$p+$xl,1);
5419
		} while ( ($z!==' ') && ($z!=="\r") && ($z!=="\n") && ($z!=='>') && ($z!=='/') && ($Tag!=='/') && ($Tag!=='') );
5420
	}
5421

5422
	return $p;
5423

5424
}
5425

5426
/**
5427
 * This function is a smart solution to find an XML tag.
5428
 * It allows to ignore full opening/closing couple of tags that could be inserted before the searched tag.
5429
 * It allows also to pass a number of encapsulations.
5430
 * To ignore encapsulation and opengin/closing just set $LevelStop=false.
5431
 * $Opening is used only when $LevelStop=false.
5432
 */
5433
static function f_Xml_FindTag(&$Txt,$Tag,$Opening,$PosBeg,$Forward,$LevelStop,$WithPrm,$WithPos=false) {
5434

5435
	if ($Tag==='_') { // New line
5436
		$p = self::f_Xml_FindNewLine($Txt,$PosBeg,$Forward,($LevelStop!==0));
5437
		$Loc = new clsTbsLocator;
5438
		$Loc->PosBeg = ($Forward) ? $PosBeg : $p;
5439
		$Loc->PosEnd = ($Forward) ? $p : $PosBeg;
5440
		$Loc->RightLevel = 0;
5441
		return $Loc;
5442
	}
5443

5444
	$Pos = $PosBeg + (($Forward) ? -1 : +1);
5445
	$TagIsOpening = false;
5446
	$TagClosing = '/'.$Tag;
5447
	$LevelNum = 0;
5448
	$TagOk = false;
5449
	$PosEnd = false;
5450
	$TagL = strlen($Tag);
5451
	$TagClosingL = strlen($TagClosing);
5452
	$RightLevel = 0;
5453
	
5454
	do {
5455

5456
		// Look for the next tag def
5457
		if ($Forward) {
5458
			$Pos = strpos($Txt,'<',$Pos+1);
5459
		} else {
5460
			if ($Pos<=0) {
5461
				$Pos = false;
5462
			} else {
5463
				$Pos = strrpos(substr($Txt,0,$Pos - 1),'<'); // strrpos() syntax compatible with PHP 4
5464
			}
5465
		}
5466

5467
		if ($Pos!==false) {
5468

5469
			// Check the name of the tag
5470
			if (strcasecmp(substr($Txt,$Pos+1,$TagL),$Tag)==0) {
5471
				// It's an opening tag
5472
				$PosX = $Pos + 1 + $TagL; // The next char
5473
				$TagOk = true;
5474
				$TagIsOpening = true;
5475
			} elseif (strcasecmp(substr($Txt,$Pos+1,$TagClosingL),$TagClosing)==0) {
5476
				// It's a closing tag
5477
				$PosX = $Pos + 1 + $TagClosingL; // The next char
5478
				$TagOk = true;
5479
				$TagIsOpening = false;
5480
			}
5481

5482
			if ($TagOk) {
5483
				// Check the next char
5484
				$x = $Txt[$PosX];
5485
				if (($x===' ') || ($x==="\r") || ($x==="\n") || ($x==='>') || ($x==='/') || ($Tag==='/') || ($Tag==='')) {
5486
					// Check the encapsulation count
5487
					if ($LevelStop===false) { // No encapsulation check
5488
						if ($TagIsOpening!==$Opening) $TagOk = false;
5489
					} else { // Count the number of level
5490
						if ($TagIsOpening) {
5491
							$PosEnd = strpos($Txt,'>',$PosX);
5492
							if ($PosEnd!==false) {
5493
								if ($Txt[$PosEnd-1]==='/') {
5494
									if (($Pos<$PosBeg) && ($PosEnd>$PosBeg)) {$RightLevel=1; $LevelNum++;}
5495
								} else {
5496
									$LevelNum++;
5497
								}
5498
							}
5499
						} else {
5500
							$LevelNum--;
5501
						}
5502
						// Check if it's the expected level
5503
						if ($LevelNum!=$LevelStop) {
5504
							$TagOk = false;
5505
							$PosEnd = false;
5506
						}
5507
					}
5508
				} else {
5509
					$TagOk = false;
5510
				}
5511
			} //--> if ($TagOk)
5512

5513
		}
5514
	} while (($Pos!==false) && ($TagOk===false));
5515

5516
	// Search for the end of the tag
5517
	if ($TagOk) {
5518
		$Loc = new clsTbsLocator;
5519
		if ($WithPrm) {
5520
			self::f_Loc_PrmRead($Txt,$PosX,true,'\'"','<','>',$Loc,$PosEnd,$WithPos);
5521
		} elseif ($PosEnd===false) {
5522
			$PosEnd = strpos($Txt,'>',$PosX);
5523
			if ($PosEnd===false) {
5524
				$TagOk = false;
5525
			}
5526
		}
5527
	}
5528

5529
	// Result
5530
	if ($TagOk) {
5531
		$Loc->PosBeg = $Pos;
5532
		$Loc->PosEnd = $PosEnd;
5533
		$Loc->RightLevel = $RightLevel;
5534
		return $Loc;
5535
	} else {
5536
		return false;
5537
	}
5538

5539
}
5540

5541
static function f_Xml_FindNewLine(&$Txt,$PosBeg,$Forward,$IsRef) {
5542

5543
	$p = $PosBeg;
5544
	if ($Forward) {
5545
		$Inc = 1;
5546
		$Inf = &$p;
5547
		$Sup = strlen($Txt)-1;
5548
	} else {
5549
		$Inc = -1;
5550
		$Inf = 0;
5551
		$Sup = &$p;
5552
	}
5553

5554
	do {
5555
		if ($Inf>$Sup) return max($Sup,0);
5556
		$x = $Txt[$p];
5557
		if (($x==="\r") || ($x==="\n")) {
5558
			$x2 = ($x==="\n") ? "\r" : "\n";
5559
			$p0 = $p;
5560
			if (($Inf<$Sup) && ($Txt[$p+$Inc]===$x2)) $p += $Inc; // Newline char can have two chars.
5561
			if ($Forward) return $p; // Forward => return pos including newline char.
5562
			if ($IsRef || ($p0!=$PosBeg)) return $p0+1; // Backwars => return pos without newline char. Ignore newline if it is the very first char of the search.
5563
		}
5564
		$p += $Inc;
5565
	} while (true);
5566

5567
}
5568

5569
static function f_Xml_GetNextEntityName($Txt, $Pos, &$tag, &$PosBeg, &$p) {
5570
/* 
5571
 $tag : tag name
5572
 $PosBeg : position of the tag
5573
 $p   : position where the read has stop
5574
 $z   : first char after the name
5575
*/
5576

5577
	$tag = '';
5578
	$PosBeg = strpos($Txt, '<', $Pos);
5579
	
5580
	if ($PosBeg===false) return false;
5581
	
5582
	// Read the name of the tag
5583
	$go = true;
5584
	$p = $PosBeg;
5585
	while ($go) {
5586
		$p++;
5587
		$z = $Txt[$p];
5588
		if ($go = ($z!==' ') && ($z!=="\r") && ($z!=="\n") && ($z!=='>') && ($z!=='/') ) {
5589
			$tag .= $z;
5590
		}
5591
	}
5592
	
5593
	return true;
5594
	
5595
}
5596

5597
}
5598

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.