| 1 |
<?php |
| 2 |
/** |
| 3 |
* @package VikBooking |
| 4 |
* @subpackage com_vikbooking |
| 5 |
* @author Alessio Gaggii - e4j - Extensionsforjoomla.com |
| 6 |
* @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved. |
| 7 |
* @license GNU General Public License version 2 or later; see LICENSE |
| 8 |
* @link https://vikwp.com |
| 9 |
*/ |
| 10 |
|
| 11 |
defined('ABSPATH') or die('No script kiddies please!'); |
| 12 |
|
| 13 |
/** |
| 14 |
* Helper class for the conditional rules. |
| 15 |
* |
| 16 |
* @since 1.4.0 |
| 17 |
*/ |
| 18 |
class VikBookingHelperConditionalRules |
| 19 |
{ |
| 20 |
/** |
| 21 |
* The singleton instance of the class. |
| 22 |
* |
| 23 |
* @var VikBookingHelperConditionalRules |
| 24 |
*/ |
| 25 |
protected static $instance = null; |
| 26 |
|
| 27 |
/** |
| 28 |
* A flag that indicates the rules debug mode. |
| 29 |
* |
| 30 |
* @var bool |
| 31 |
*/ |
| 32 |
public static $debugRules = false; |
| 33 |
|
| 34 |
/** |
| 35 |
* An array to store some cached/static values. |
| 36 |
* |
| 37 |
* @var array |
| 38 |
*/ |
| 39 |
protected static $helper = null; |
| 40 |
|
| 41 |
/** |
| 42 |
* Logs the execution of all the complex template |
| 43 |
* editing methods that manipulate the HTML/PHP DOM. |
| 44 |
* |
| 45 |
* @var array |
| 46 |
*/ |
| 47 |
protected static $editingLog = null; |
| 48 |
|
| 49 |
/** |
| 50 |
* The database handler instance. |
| 51 |
* |
| 52 |
* @var object |
| 53 |
*/ |
| 54 |
protected $dbo; |
| 55 |
|
| 56 |
/** |
| 57 |
* The list of rules instances loaded. |
| 58 |
* |
| 59 |
* @var array |
| 60 |
*/ |
| 61 |
protected $rules; |
| 62 |
|
| 63 |
/** |
| 64 |
* The VikBooking translation object. |
| 65 |
* |
| 66 |
* @var object |
| 67 |
*/ |
| 68 |
protected $vbo_tn; |
| 69 |
|
| 70 |
/** |
| 71 |
* Class constructor is protected. |
| 72 |
* |
| 73 |
* @see getInstance() |
| 74 |
*/ |
| 75 |
protected function __construct() |
| 76 |
{ |
| 77 |
static::$helper = array(); |
| 78 |
$this->dbo = JFactory::getDbo(); |
| 79 |
$this->rules = array(); |
| 80 |
$this->vbo_tn = VikBooking::getTranslator(); |
| 81 |
$this->load(); |
| 82 |
} |
| 83 |
|
| 84 |
/** |
| 85 |
* Returns the global object, either |
| 86 |
* a new instance or the existing instance |
| 87 |
* if the class was already instantiated. |
| 88 |
* |
| 89 |
* @return self A new instance of the class. |
| 90 |
*/ |
| 91 |
public static function getInstance() |
| 92 |
{ |
| 93 |
if (is_null(static::$instance)) { |
| 94 |
static::$instance = new static(); |
| 95 |
} |
| 96 |
|
| 97 |
return static::$instance; |
| 98 |
} |
| 99 |
|
| 100 |
/** |
| 101 |
* Loads a list of all available conditional rules. |
| 102 |
* |
| 103 |
* @return self |
| 104 |
*/ |
| 105 |
protected function load() |
| 106 |
{ |
| 107 |
// require main/parent conditional-rule class |
| 108 |
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'conditional_rule.php'); |
| 109 |
|
| 110 |
$rules_base = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'conditionalrules' . DIRECTORY_SEPARATOR; |
| 111 |
$rules_files = glob($rules_base . '*.php'); |
| 112 |
|
| 113 |
/** |
| 114 |
* Trigger event to let other plugins register additional rules. |
| 115 |
* |
| 116 |
* @return array A list of supported rules. |
| 117 |
*/ |
| 118 |
$list = VBOFactory::getPlatform()->getDispatcher()->filter('onLoadConditionalRules'); |
| 119 |
foreach ($list as $chunk) { |
| 120 |
// merge default rule files with the returned ones |
| 121 |
$rules_files = array_merge($rules_files, (array)$chunk); |
| 122 |
} |
| 123 |
|
| 124 |
foreach ($rules_files as $rf) { |
| 125 |
try { |
| 126 |
// require rule class file |
| 127 |
if (is_file($rf)) { |
| 128 |
require_once($rf); |
| 129 |
} |
| 130 |
|
| 131 |
// instantiate rule object |
| 132 |
$classname = 'VikBookingConditionalRule' . str_replace(' ', '', ucwords(str_replace('_', ' ', basename($rf, '.php')))); |
| 133 |
if (class_exists($classname)) { |
| 134 |
$rule = new $classname(); |
| 135 |
// push rule object |
| 136 |
array_push($this->rules, $rule); |
| 137 |
} |
| 138 |
} catch (Exception $e) { |
| 139 |
// do nothing |
| 140 |
} |
| 141 |
} |
| 142 |
|
| 143 |
return $this; |
| 144 |
} |
| 145 |
|
| 146 |
/** |
| 147 |
* Gets the list of conditional rules instantiated. |
| 148 |
* |
| 149 |
* @return array list of conditional rules objects. |
| 150 |
*/ |
| 151 |
public function getRules() |
| 152 |
{ |
| 153 |
return $this->rules; |
| 154 |
} |
| 155 |
|
| 156 |
/** |
| 157 |
* Gets a single conditional rule instantiated. |
| 158 |
* |
| 159 |
* @param string $id the rule identifier. |
| 160 |
* |
| 161 |
* @return mixed the conditional rule object, false otherwise. |
| 162 |
*/ |
| 163 |
public function getRule($id) |
| 164 |
{ |
| 165 |
foreach ($this->rules as $rule) { |
| 166 |
if ($rule->getIdentifier() != $id) { |
| 167 |
continue; |
| 168 |
} |
| 169 |
return $rule; |
| 170 |
} |
| 171 |
|
| 172 |
return false; |
| 173 |
} |
| 174 |
|
| 175 |
/** |
| 176 |
* Gets a list of sorted rule names, ids and descriptions. |
| 177 |
* |
| 178 |
* @return array associative and sorted rules list. |
| 179 |
*/ |
| 180 |
public function getRuleNames() |
| 181 |
{ |
| 182 |
$names = array(); |
| 183 |
$pool = array(); |
| 184 |
|
| 185 |
foreach ($this->rules as $rule) { |
| 186 |
$id = $rule->getIdentifier(); |
| 187 |
$name = $rule->getName(); |
| 188 |
$descr = $rule->getDescription(); |
| 189 |
$rdata = new stdClass; |
| 190 |
$rdata->id = $id; |
| 191 |
$rdata->name = $name; |
| 192 |
$rdata->descr = $descr; |
| 193 |
$names[$name] = $rdata; |
| 194 |
} |
| 195 |
|
| 196 |
// apply sorting by name |
| 197 |
ksort($names); |
| 198 |
|
| 199 |
// push sorted rules to pool |
| 200 |
foreach ($names as $rdata) { |
| 201 |
array_push($pool, $rdata); |
| 202 |
} |
| 203 |
|
| 204 |
return $pool; |
| 205 |
} |
| 206 |
|
| 207 |
/** |
| 208 |
* Tells whether the rule object overrides the method from its parent |
| 209 |
* class. Useful to distinguish action-rules from filter-rules. |
| 210 |
* |
| 211 |
* @param object $rule child class object of VikBookingConditionalRule. |
| 212 |
* @param string $method the name of the method to check if it was overridden. |
| 213 |
* |
| 214 |
* @return bool true if overridden, false otherwise. |
| 215 |
*/ |
| 216 |
public function supportsAction($rule, $method = 'callbackAction') |
| 217 |
{ |
| 218 |
if (!class_exists('ReflectionMethod')) { |
| 219 |
return false; |
| 220 |
} |
| 221 |
|
| 222 |
$reflect = new ReflectionMethod($rule, $method); |
| 223 |
|
| 224 |
return ($reflect->getDeclaringClass()->getName() == get_class($rule)); |
| 225 |
} |
| 226 |
|
| 227 |
/** |
| 228 |
* Helper method for the controller to compose the rules |
| 229 |
* of the conditional text by parsing all input values in the same order requested. |
| 230 |
* |
| 231 |
* @return array list of stdClass object with the various rules params. |
| 232 |
*/ |
| 233 |
public function composeRulesParamsFromRequest() |
| 234 |
{ |
| 235 |
$rules_list = array(); |
| 236 |
$raw_vals = JFactory::getApplication()->input->getArray(); |
| 237 |
|
| 238 |
foreach ($raw_vals as $raw_inp_key => $rule_inp_vals) { |
| 239 |
foreach ($this->rules as $rule) { |
| 240 |
$rule_id = $rule->getIdentifier(); |
| 241 |
$rule_inp_key = basename($rule_id, '.php'); |
| 242 |
if ($rule_inp_key != $raw_inp_key) { |
| 243 |
continue; |
| 244 |
} |
| 245 |
// rule found, make sure the settings are not empty |
| 246 |
$has_vals = false; |
| 247 |
foreach ($rule_inp_vals as $rule_inp_val) { |
| 248 |
if (is_array($rule_inp_val) && count($rule_inp_val)) { |
| 249 |
$has_vals = true; |
| 250 |
break; |
| 251 |
} |
| 252 |
if (is_string($rule_inp_val) && strlen($rule_inp_val)) { |
| 253 |
$has_vals = true; |
| 254 |
break; |
| 255 |
} |
| 256 |
} |
| 257 |
if (!$has_vals) { |
| 258 |
// do not store empty rule params |
| 259 |
continue 2; |
| 260 |
} |
| 261 |
// compose rule object |
| 262 |
$rule_data = new stdClass; |
| 263 |
$rule_data->id = $rule_id; |
| 264 |
$rule_data->params = $rule_inp_vals; |
| 265 |
// push rule to list |
| 266 |
array_push($rules_list, $rule_data); |
| 267 |
} |
| 268 |
} |
| 269 |
|
| 270 |
return $rules_list; |
| 271 |
} |
| 272 |
|
| 273 |
/** |
| 274 |
* Helper method to load all special tags and related records. |
| 275 |
* |
| 276 |
* @param string $orby_col the column to order by. |
| 277 |
* @param string $orby_dir the order by direction. |
| 278 |
* |
| 279 |
* @return array associative list of special-tags (key) records (value). |
| 280 |
*/ |
| 281 |
public function getSpecialTags($orby_col = 'name', $orby_dir = 'ASC') |
| 282 |
{ |
| 283 |
$special_tags = array(); |
| 284 |
|
| 285 |
$q = "SELECT `ct`.* FROM `#__vikbooking_condtexts` AS `ct` ORDER BY `ct`.`{$orby_col}` {$orby_dir};"; |
| 286 |
$this->dbo->setQuery($q); |
| 287 |
$this->dbo->execute(); |
| 288 |
if ($this->dbo->getNumRows()) { |
| 289 |
$records = $this->dbo->loadAssocList(); |
| 290 |
$this->vbo_tn->translateContents($records, '#__vikbooking_condtexts'); |
| 291 |
foreach ($records as $record) { |
| 292 |
// decode rules |
| 293 |
$record['rules'] = !empty($record['rules']) ? json_decode($record['rules']) : array(); |
| 294 |
$record['rules'] = !is_array($record['rules']) ? array() : $record['rules']; |
| 295 |
// push record |
| 296 |
$special_tags[$record['token']] = $record; |
| 297 |
} |
| 298 |
} |
| 299 |
|
| 300 |
return $special_tags; |
| 301 |
} |
| 302 |
|
| 303 |
/** |
| 304 |
* Helper method to load one precise conditional text from the given special tag. |
| 305 |
* |
| 306 |
* @param string $token the special tag (token) to look for. |
| 307 |
* |
| 308 |
* @return array the record of the conditional text found or an empty array. |
| 309 |
*/ |
| 310 |
public function getBySpecialTag($token) |
| 311 |
{ |
| 312 |
$cond_text = array(); |
| 313 |
|
| 314 |
$q = "SELECT * FROM `#__vikbooking_condtexts` WHERE `token`=" . $this->dbo->quote($token) . ";"; |
| 315 |
$this->dbo->setQuery($q); |
| 316 |
$this->dbo->execute(); |
| 317 |
if ($this->dbo->getNumRows()) { |
| 318 |
$cond_text = $this->dbo->loadAssoc(); |
| 319 |
$this->vbo_tn->translateContents($cond_text, '#__vikbooking_condtexts'); |
| 320 |
} |
| 321 |
|
| 322 |
return $cond_text; |
| 323 |
} |
| 324 |
|
| 325 |
/** |
| 326 |
* Helper method to store information in the helper array. |
| 327 |
* |
| 328 |
* @param mixed $key the key or array of keys to set. |
| 329 |
* @param mixed $val the value or array of values to set. |
| 330 |
* |
| 331 |
* @return self |
| 332 |
*/ |
| 333 |
public function set($key, $val = null) |
| 334 |
{ |
| 335 |
if (!is_string($key) && !is_array($key)) { |
| 336 |
return $this; |
| 337 |
} |
| 338 |
|
| 339 |
if (is_string($key)) { |
| 340 |
$key = array($key); |
| 341 |
} |
| 342 |
if (!is_array($val)) { |
| 343 |
$val = array($val); |
| 344 |
} |
| 345 |
|
| 346 |
foreach ($key as $i => $prop) { |
| 347 |
if (!isset($val[$i])) { |
| 348 |
continue; |
| 349 |
} |
| 350 |
static::$helper[$prop] = $val[$i]; |
| 351 |
} |
| 352 |
|
| 353 |
return $this; |
| 354 |
} |
| 355 |
|
| 356 |
/** |
| 357 |
* Helper method to get information from the helper array. |
| 358 |
* |
| 359 |
* @param string $key the key to get. |
| 360 |
* @param string $def the default value to get. |
| 361 |
* |
| 362 |
* @return mixed the requested key value. |
| 363 |
*/ |
| 364 |
public function get($key, $def = null) |
| 365 |
{ |
| 366 |
return isset(static::$helper[$key]) ? static::$helper[$key] : $def; |
| 367 |
} |
| 368 |
|
| 369 |
/** |
| 370 |
* Parses all tokens in the given template string and applies the |
| 371 |
* conditional texts found if all rules are compliant. |
| 372 |
* |
| 373 |
* @param string $tmpl the template string to parse, passed by reference. |
| 374 |
* |
| 375 |
* @return mixed false if no tokens found, integer for how many tokens were applied. |
| 376 |
*/ |
| 377 |
public function parseTokens(&$tmpl) |
| 378 |
{ |
| 379 |
preg_match_all('/\{condition: ?([a-zA-Z0-9_]+)\}/U', $tmpl, $matches); |
| 380 |
|
| 381 |
$tot_tokens = count($matches[0]); |
| 382 |
|
| 383 |
if (!$tot_tokens) { |
| 384 |
// no tokens to parse |
| 385 |
return false; |
| 386 |
} |
| 387 |
|
| 388 |
// default empty replacement |
| 389 |
$null_replace = ''; |
| 390 |
|
| 391 |
// load all helper property keys and values |
| 392 |
$prop_keys = array_keys(static::$helper); |
| 393 |
$prop_vals = array_values(static::$helper); |
| 394 |
|
| 395 |
// load all conditional text records |
| 396 |
$cond_texts = $this->getSpecialTags(); |
| 397 |
|
| 398 |
// iterate through all tokens found |
| 399 |
foreach ($matches[0] as $token) { |
| 400 |
if (!isset($cond_texts[$token])) { |
| 401 |
if (static::$debugRules) { |
| 402 |
// debuggining at this step cannot be enabled as the record was not found |
| 403 |
$null_replace = "{$token} was not found"; |
| 404 |
} |
| 405 |
// remove token from template file |
| 406 |
$tmpl = str_replace($token, $null_replace, $tmpl); |
| 407 |
// decrease total tokens applied |
| 408 |
$tot_tokens--; |
| 409 |
// iterate to the next token, if any |
| 410 |
continue; |
| 411 |
} |
| 412 |
|
| 413 |
// set debug mode according to record |
| 414 |
static::$debugRules = (bool)$cond_texts[$token]['debug']; |
| 415 |
|
| 416 |
// set flag to know whether the token was compliant |
| 417 |
$compliant = false; |
| 418 |
|
| 419 |
// parse all rules for this conditional text |
| 420 |
foreach ($cond_texts[$token]['rules'] as $rule_data) { |
| 421 |
if (empty($rule_data->id)) { |
| 422 |
continue; |
| 423 |
} |
| 424 |
$rule = $this->getRule($rule_data->id); |
| 425 |
if ($rule === false) { |
| 426 |
continue; |
| 427 |
} |
| 428 |
// inject params and booking to rule, then check if compliant |
| 429 |
$compliant = $rule->setParams($rule_data->params)->setProperties($prop_keys, $prop_vals)->isCompliant(); |
| 430 |
if (!$compliant) { |
| 431 |
if (static::$debugRules) { |
| 432 |
$null_replace = JText::sprintf('VBO_DEBUG_RULE_CONDTEXT', $rule->getName(), $token); |
| 433 |
} |
| 434 |
// all rules must be compliant with the booking |
| 435 |
break; |
| 436 |
} |
| 437 |
} |
| 438 |
|
| 439 |
if (!$compliant) { |
| 440 |
// remove token from template file |
| 441 |
$tmpl = str_replace($token, $null_replace, $tmpl); |
| 442 |
// decrease total tokens applied |
| 443 |
$tot_tokens--; |
| 444 |
// iterate to the next token, if any |
| 445 |
continue; |
| 446 |
} |
| 447 |
|
| 448 |
// all rules were compliant, trigger callback and manipulation actions |
| 449 |
foreach ($cond_texts[$token]['rules'] as $rule_data) { |
| 450 |
if (empty($rule_data->id)) { |
| 451 |
continue; |
| 452 |
} |
| 453 |
$rule = $this->getRule($rule_data->id); |
| 454 |
if ($rule === false) { |
| 455 |
continue; |
| 456 |
} |
| 457 |
// inject params and booking to rule |
| 458 |
$rule->setParams($rule_data->params)->setProperties($prop_keys, $prop_vals); |
| 459 |
// trigger callback action |
| 460 |
$rule->callbackAction(); |
| 461 |
// allow rule to manipulate the actual message |
| 462 |
$cond_texts[$token]['msg'] = $rule->manipulateMessage($cond_texts[$token]['msg']); |
| 463 |
} |
| 464 |
|
| 465 |
/** |
| 466 |
* The message of the conditional text rules is usually written through a WYSIWYG editor |
| 467 |
* which may contain HTML tags. However, the context where these texts are being used is |
| 468 |
* unknown to VBO, and so we can detect from the content whether plain text messages are |
| 469 |
* necessary, maybe for sending an SMS message. We allow the use of special strings to |
| 470 |
* detect if no HTML should ever be included in the message, like [sms] or [plain text]. |
| 471 |
* |
| 472 |
* @since 1.4.3 |
| 473 |
*/ |
| 474 |
$requires_plain_text = false; |
| 475 |
if (preg_match_all("/(\[sms\]|\[plain_? ?text\])+/i", $cond_texts[$token]['msg'], $plt_matches)) { |
| 476 |
$requires_plain_text = true; |
| 477 |
foreach ($plt_matches[1] as $plt_match) { |
| 478 |
$cond_texts[$token]['msg'] = str_replace($plt_match, '', $cond_texts[$token]['msg']); |
| 479 |
} |
| 480 |
$cond_texts[$token]['msg'] = strip_tags($cond_texts[$token]['msg']); |
| 481 |
} |
| 482 |
|
| 483 |
/** |
| 484 |
* @wponly we need to let WordPress parse the paragraphs in the message. |
| 485 |
*/ |
| 486 |
if (VBOPlatformDetection::isWordPress() && !empty($cond_texts[$token]['msg']) && !$requires_plain_text) { |
| 487 |
$cond_texts[$token]['msg'] = wpautop($cond_texts[$token]['msg']); |
| 488 |
} |
| 489 |
|
| 490 |
/** |
| 491 |
* Make sure any src/href attribute does not contain relative URLs. |
| 492 |
* |
| 493 |
* @since 1.5.0 |
| 494 |
*/ |
| 495 |
$cond_texts[$token]['msg'] = preg_replace_callback("/\s*(src|href)=([\"'])(.*?)[\"']/i", function($match) { |
| 496 |
// check if the URL starts with the base domain |
| 497 |
if (stripos($match[3], JUri::root()) !== 0 && !preg_match("/^(https?:\/\/|www\.)/i", $match[3])) { |
| 498 |
// prepend base domain to URL |
| 499 |
$match[0] = ' ' . $match[1] . '=' . $match[2] . JUri::root() . $match[3] . $match[2]; |
| 500 |
} |
| 501 |
return $match[0]; |
| 502 |
}, $cond_texts[$token]['msg']); |
| 503 |
|
| 504 |
// finally, apply the message to the template |
| 505 |
$tmpl = str_replace($token, $cond_texts[$token]['msg'], $tmpl); |
| 506 |
} |
| 507 |
|
| 508 |
return $tot_tokens; |
| 509 |
} |
| 510 |
|
| 511 |
/** |
| 512 |
* Toggles the rules debugging mode. |
| 513 |
* |
| 514 |
* @return self |
| 515 |
*/ |
| 516 |
public function toggleDebugging() |
| 517 |
{ |
| 518 |
static::$debugRules = !static::$debugRules; |
| 519 |
|
| 520 |
return $this; |
| 521 |
} |
| 522 |
|
| 523 |
/** |
| 524 |
* Returns the list of the template files paths supporting the conditional text tags. |
| 525 |
* |
| 526 |
* @return array |
| 527 |
*/ |
| 528 |
public static function getTemplateFilesPaths() |
| 529 |
{ |
| 530 |
return array( |
| 531 |
'email_tmpl.php' => VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'email_tmpl.php', |
| 532 |
'invoice_tmpl.php' => VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'invoices' . DIRECTORY_SEPARATOR . 'invoice_tmpl.php', |
| 533 |
'checkin_tmpl.php' => VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'checkins' . DIRECTORY_SEPARATOR . 'checkin_tmpl.php', |
| 534 |
); |
| 535 |
} |
| 536 |
|
| 537 |
/** |
| 538 |
* Returns the list of the template files names supporting the conditional text tags. |
| 539 |
* |
| 540 |
* @return array |
| 541 |
*/ |
| 542 |
public static function getTemplateFilesNames() |
| 543 |
{ |
| 544 |
return array( |
| 545 |
'email_tmpl.php' => JText::translate('VBOCONFIGEMAILTEMPLATE'), |
| 546 |
'invoice_tmpl.php' => JText::translate('VBOCONFIGINVOICETEMPLATE'), |
| 547 |
'checkin_tmpl.php' => JText::translate('VBOCONFIGCHECKINTEMPLATE'), |
| 548 |
); |
| 549 |
} |
| 550 |
|
| 551 |
/** |
| 552 |
* Returns the list of the template files contents supporting the conditional text tags. |
| 553 |
* |
| 554 |
* @param string $file the basename of the template file to read. |
| 555 |
* |
| 556 |
* @return array |
| 557 |
*/ |
| 558 |
public static function getTemplateFilesContents($file = null) |
| 559 |
{ |
| 560 |
$templates = self::getTemplateFilesPaths(); |
| 561 |
|
| 562 |
if (!empty($file) && isset($templates[$file])) { |
| 563 |
$templates = array( |
| 564 |
$file => $templates[$file] |
| 565 |
); |
| 566 |
} |
| 567 |
|
| 568 |
$contents = array(); |
| 569 |
foreach ($templates as $file => $path) { |
| 570 |
if (!is_file($path)) { |
| 571 |
continue; |
| 572 |
} |
| 573 |
switch ($file) { |
| 574 |
case 'email_tmpl.php': |
| 575 |
$contents[$file] = VikBooking::loadEmailTemplate(); |
| 576 |
break; |
| 577 |
case 'invoice_tmpl.php': |
| 578 |
$data = VikBooking::loadInvoiceTmpl(); |
| 579 |
$contents[$file] = $data[0]; |
| 580 |
break; |
| 581 |
case 'checkin_tmpl.php': |
| 582 |
$data = VikBooking::loadCheckinDocTmpl(); |
| 583 |
$contents[$file] = $data[0]; |
| 584 |
break; |
| 585 |
default: |
| 586 |
break; |
| 587 |
} |
| 588 |
} |
| 589 |
|
| 590 |
return $contents; |
| 591 |
} |
| 592 |
|
| 593 |
/** |
| 594 |
* Returns the list of the template files code supporting the conditional text tags. |
| 595 |
* |
| 596 |
* @param string $file the basename of the template file to read. |
| 597 |
* |
| 598 |
* @return mixed array of files code, or just the code of the requested file. |
| 599 |
*/ |
| 600 |
public static function getTemplateFileCode($file = null) |
| 601 |
{ |
| 602 |
$templates = self::getTemplateFilesPaths(); |
| 603 |
|
| 604 |
if (!empty($file) && isset($templates[$file])) { |
| 605 |
$templates = array( |
| 606 |
$file => $templates[$file] |
| 607 |
); |
| 608 |
} |
| 609 |
|
| 610 |
$contents = array(); |
| 611 |
foreach ($templates as $f => $path) { |
| 612 |
if (!is_file($path)) { |
| 613 |
continue; |
| 614 |
} |
| 615 |
switch ($f) { |
| 616 |
case 'email_tmpl.php': |
| 617 |
case 'invoice_tmpl.php': |
| 618 |
case 'checkin_tmpl.php': |
| 619 |
$fp = fopen($path, 'r'); |
| 620 |
if (!$fp) { |
| 621 |
break; |
| 622 |
} |
| 623 |
$fcode = ''; |
| 624 |
while (!feof($fp)) { |
| 625 |
$fcode .= fread($fp, 8192); |
| 626 |
} |
| 627 |
fclose($fp); |
| 628 |
if (empty($fcode)) { |
| 629 |
break; |
| 630 |
} |
| 631 |
$contents[$f] = $fcode; |
| 632 |
break; |
| 633 |
default: |
| 634 |
break; |
| 635 |
} |
| 636 |
} |
| 637 |
|
| 638 |
return !empty($file) && isset($contents[$file]) ? $contents[$file] : $contents; |
| 639 |
} |
| 640 |
|
| 641 |
/** |
| 642 |
* Updates the source code of the given template file name. |
| 643 |
* |
| 644 |
* @param string $file the basename of the template file to write. |
| 645 |
* @param string $code the new code of the template file to write. |
| 646 |
* |
| 647 |
* @return bool true on success, false otherwise. |
| 648 |
*/ |
| 649 |
public static function writeTemplateFileCode($file, $code) |
| 650 |
{ |
| 651 |
$templates = self::getTemplateFilesPaths(); |
| 652 |
|
| 653 |
if (!isset($templates[$file]) || empty($code)) { |
| 654 |
return false; |
| 655 |
} |
| 656 |
|
| 657 |
$fp = fopen($templates[$file], 'w+'); |
| 658 |
if (!$fp) { |
| 659 |
return false; |
| 660 |
} |
| 661 |
$bytes = fwrite($fp, $code); |
| 662 |
fclose($fp); |
| 663 |
|
| 664 |
return ($bytes !== false); |
| 665 |
} |
| 666 |
|
| 667 |
/** |
| 668 |
* Tells whether the given special tag is used in the passed content. |
| 669 |
* |
| 670 |
* @param string $tag the special tag to look for. |
| 671 |
* @param string $content the content of the template file. |
| 672 |
* |
| 673 |
* @return bool |
| 674 |
*/ |
| 675 |
public static function isTagInContent($tag, $content) |
| 676 |
{ |
| 677 |
if (empty($tag) || empty($content)) { |
| 678 |
return false; |
| 679 |
} |
| 680 |
|
| 681 |
if (strpos($tag, '{condition:') === false) { |
| 682 |
// invalid conditional text special tag |
| 683 |
return false; |
| 684 |
} |
| 685 |
|
| 686 |
return (strpos($content, $tag) !== false); |
| 687 |
} |
| 688 |
|
| 689 |
/** |
| 690 |
* Makes sure the path obtained to query the raw source code will produce |
| 691 |
* results in the raw php code. Some Libxml constants may skip html or |
| 692 |
* style tags, while the html source code may contain more tbody tags |
| 693 |
* than the php source code as it is parsed by the browser, where table |
| 694 |
* tags without a nested tbody tag will add it automatically. |
| 695 |
* |
| 696 |
* @param string $tag_path the node-path to the tag obtained |
| 697 |
* from the html source code. |
| 698 |
* @param DOMXpath $php_path DOMXpath object for the php code. |
| 699 |
* |
| 700 |
* @return string a valid query path to be used. |
| 701 |
* |
| 702 |
* @see addTagByComparingSources() and addStylesByComparingSources() |
| 703 |
*/ |
| 704 |
public static function adjustDOMXpathQuery($tag_path, $php_xpath) |
| 705 |
{ |
| 706 |
// Xpath query expressions require two leading slashes |
| 707 |
if (substr($tag_path, 0, 2) !== '//' && substr($tag_path, 0, 1) == '/') { |
| 708 |
$tag_path = '/' . $tag_path; |
| 709 |
} |
| 710 |
|
| 711 |
// take care of any count mismatch of tbody tags |
| 712 |
$tbody_in_html = substr_count($tag_path, 'tbody'); |
| 713 |
$tbody_in_php = $php_xpath->evaluate("count(//tbody)"); |
| 714 |
if ($tbody_in_html > 0 && (int)$tbody_in_php < $tbody_in_html) { |
| 715 |
// raw php code has got less tbody nodes than html code |
| 716 |
$tbody_in_php = (int)$tbody_in_php; |
| 717 |
$parts = explode('/tbody', $tag_path); |
| 718 |
$new_tag_path = ''; |
| 719 |
foreach ($parts as $k => $path_part) { |
| 720 |
// use only the amount of tbody found in php code |
| 721 |
$new_tag_path .= $path_part . ($k < $tbody_in_php ? '/tbody' : ''); |
| 722 |
} |
| 723 |
// set new path to tag |
| 724 |
$tag_path = $new_tag_path; |
| 725 |
} |
| 726 |
|
| 727 |
// foresee the result of the Xpath query |
| 728 |
$testcase = $php_xpath->query($tag_path); |
| 729 |
if (!$testcase || !$testcase->length) { |
| 730 |
// this Xpath query is about to fail, try to do something |
| 731 |
|
| 732 |
if (strpos($tag_path, '/style') !== false) { |
| 733 |
// style tags found in the HTML source code may not be available in the PHP source code |
| 734 |
$tag_path = str_replace('/style', '', $tag_path); |
| 735 |
} |
| 736 |
|
| 737 |
/** |
| 738 |
* BC with old invoice template file structure where no table is ever inside another. |
| 739 |
* |
| 740 |
* @since any version prior to 1.14 (J) - 1.4.0 (WP) |
| 741 |
*/ |
| 742 |
if (strpos($tag_path, '//table/table[2]') !== false) { |
| 743 |
$tag_path = str_replace('//table/table[2]', '//table[3]', $tag_path); |
| 744 |
} elseif (strpos($tag_path, '//table/table[1]') !== false) { |
| 745 |
$tag_path = str_replace('//table/table[1]', '//table[2]', $tag_path); |
| 746 |
} |
| 747 |
} |
| 748 |
|
| 749 |
|
| 750 |
return $tag_path; |
| 751 |
} |
| 752 |
|
| 753 |
/** |
| 754 |
* Earlier versions of PHP and Libxml may not support to load code strings |
| 755 |
* without adding the DOCTYPE, the html+body tags, and any missing/malformed tag. |
| 756 |
* This will break the entire PHP source code of the file by getting HTML entities |
| 757 |
* like ?> for the PHP closing tag, or => for the array key-val operator. |
| 758 |
* |
| 759 |
* @param string $php_code the raw source code generated by DOMDocument. |
| 760 |
* @param object $php_dom the DOMDocument object of the php code. |
| 761 |
* |
| 762 |
* @return string the clean PHP source code to write onto the file. |
| 763 |
*/ |
| 764 |
public static function cleanPHPSourceCode($php_code, $php_dom) |
| 765 |
{ |
| 766 |
/** |
| 767 |
* The following constants will produce a different nodePath, and they are available |
| 768 |
* starting from PHP 5.4 and Libxml >= 2.7.8. |
| 769 |
*/ |
| 770 |
$libxml_updated = defined('LIBXML_HTML_NOIMPLIED') && defined('LIBXML_HTML_NODEFDTD'); |
| 771 |
// |
| 772 |
|
| 773 |
// immediately restore the PHP tags that could have been converted to HTML entities |
| 774 |
$php_code = str_replace(array('<?php', '?>'), array('<?php', '?>'), $php_code); |
| 775 |
|
| 776 |
// remove doctype |
| 777 |
$php_code = preg_replace("/(^<!DOCTYPE.*\R)/i", '', $php_code); |
| 778 |
|
| 779 |
/** |
| 780 |
* Grab anything between PHP tags to make sure there are no syntax errors due to HTML entities. |
| 781 |
* |
| 782 |
* @see In the callback we should NEVER user strip_tags as PHP can contain HTML. |
| 783 |
* We can at most look for some HTML tags mixed to PHP code to remove only them. |
| 784 |
*/ |
| 785 |
$php_code = preg_replace_callback("/<\?php(.*?)\?>/si", function($match) { |
| 786 |
// use just what's inside the PHP tags |
| 787 |
$pure_code = $match[1]; |
| 788 |
/** |
| 789 |
* PHP comments in the check-in document template file may get for array declarations |
| 790 |
* one opening "<p>" tag next to the HTML entity for "=>". Therefore, we remove it. |
| 791 |
*/ |
| 792 |
if (strpos($pure_code, '=>') !== false && strpos($pure_code, 'array') !== false && preg_match_all("/(<[a-zA-Z]+>)/", $pure_code, $extra_tags)) { |
| 793 |
foreach ($extra_tags[0] as $extra_tag) { |
| 794 |
// strip the tag as well as its closing version |
| 795 |
$pure_code = str_replace(array($extra_tag, str_replace('<', '</', $extra_tag)), '', $pure_code); |
| 796 |
} |
| 797 |
} |
| 798 |
// return the decoded HTML entities needed by PHP |
| 799 |
return '<?php' . html_entity_decode($pure_code) . '?>'; |
| 800 |
}, $php_code); |
| 801 |
|
| 802 |
// get rid of html and body tags |
| 803 |
$php_code = str_replace(array('<html>', '<body>', '</html>', '</body>'), '', $php_code); |
| 804 |
|
| 805 |
// check if we have an HTML closing tag after the PHP closing tag due to previous manipulation of PHP code |
| 806 |
$php_code = preg_replace_callback("/\?>\R+(<\/?[a-zA-Z]+.*?>)/s", function($match) { |
| 807 |
if ($match[1] && strpos($match[1], '/') !== false) { |
| 808 |
return str_replace($match[1], '', $match[0]); |
| 809 |
} |
| 810 |
return $match[0]; |
| 811 |
}, $php_code); |
| 812 |
|
| 813 |
/** |
| 814 |
* If libxml is not updated, we load the HTML by enclosing the whole source within a placeholder DIV tag. |
| 815 |
* This is to avoid getting the HTML and BODY tags started inside PHP code maybe, because it has a > or <. |
| 816 |
*/ |
| 817 |
if (!$libxml_updated) { |
| 818 |
// get rid of the wrapper div tag, added as a placeholder to avoid getting html and body inside php code |
| 819 |
$wrapper = $php_dom->getElementsByTagName('div')->item(0); |
| 820 |
if ($wrapper) { |
| 821 |
// remove all children and store the element |
| 822 |
$wrapper = $wrapper->parentNode->removeChild($wrapper); |
| 823 |
while ($php_dom->firstChild) { |
| 824 |
$php_dom->removeChild($php_dom->firstChild); |
| 825 |
} |
| 826 |
// append children again |
| 827 |
while ($wrapper->firstChild ) { |
| 828 |
$php_dom->appendChild($wrapper->firstChild); |
| 829 |
} |
| 830 |
// get new HTML without the wrapper |
| 831 |
$php_code = $php_dom->saveHTML(); |
| 832 |
} |
| 833 |
} |
| 834 |
// |
| 835 |
|
| 836 |
return $php_code; |
| 837 |
} |
| 838 |
|
| 839 |
/** |
| 840 |
* Writes the source code of the template file onto a backup file. |
| 841 |
* |
| 842 |
* @param string $file the basename of the template file. |
| 843 |
* @param string $php_code the raw source code of the file. |
| 844 |
* |
| 845 |
* @return bool True on success, false otherwise. |
| 846 |
*/ |
| 847 |
public static function backupTemplateFileCode($file, $php_code) |
| 848 |
{ |
| 849 |
$fp = fopen(dirname(__FILE__) . DIRECTORY_SEPARATOR . $file . '.bkp', 'w+'); |
| 850 |
if (!$fp) { |
| 851 |
return false; |
| 852 |
} |
| 853 |
|
| 854 |
$bytes = fwrite($fp, $php_code); |
| 855 |
fclose($fp); |
| 856 |
|
| 857 |
return ($bytes !== false); |
| 858 |
} |
| 859 |
|
| 860 |
/** |
| 861 |
* Restores the source code of the template file from the backup file. |
| 862 |
* |
| 863 |
* @param string $file the basename of the template file. |
| 864 |
* |
| 865 |
* @return bool True on success, false otherwise. |
| 866 |
*/ |
| 867 |
public static function restoreTemplateFileCode($file) |
| 868 |
{ |
| 869 |
$backup_fpath = dirname(__FILE__) . DIRECTORY_SEPARATOR . $file . '.bkp'; |
| 870 |
if (!is_file($backup_fpath)) { |
| 871 |
return false; |
| 872 |
} |
| 873 |
|
| 874 |
$fp = fopen($backup_fpath, 'r'); |
| 875 |
if (!$fp) { |
| 876 |
return false; |
| 877 |
} |
| 878 |
|
| 879 |
$fcode = ''; |
| 880 |
while (!feof($fp)) { |
| 881 |
$fcode .= fread($fp, 8192); |
| 882 |
} |
| 883 |
fclose($fp); |
| 884 |
|
| 885 |
if (empty($fcode)) { |
| 886 |
return false; |
| 887 |
} |
| 888 |
|
| 889 |
return self::writeTemplateFileCode($file, $fcode); |
| 890 |
} |
| 891 |
|
| 892 |
/** |
| 893 |
* Compares the new HTML source code of the compiled template file |
| 894 |
* to the raw source code of the template file. Finds the newly added |
| 895 |
* tag in the HTML source code and adds it to the same position of the |
| 896 |
* raw source code of the same template file. Used to add a new tag to the code. |
| 897 |
* |
| 898 |
* @param string $tag the conditional text tag to add. |
| 899 |
* @param string $file the basename of the template file. |
| 900 |
* @param string $html_code the full HTML source code where the new tag is. |
| 901 |
* @param string $php_code the raw source code where the new tag should be added. |
| 902 |
* |
| 903 |
* @return string the new PHP source code to write onto the file. |
| 904 |
*/ |
| 905 |
public static function addTagByComparingSources($tag, $file, $html_code, $php_code) |
| 906 |
{ |
| 907 |
if (!class_exists('DOMDocument') || !class_exists('DOMXpath')) { |
| 908 |
// this sucks, we just append the tag to the end of the file |
| 909 |
$php_code .= "\n{$tag}\n"; |
| 910 |
|
| 911 |
// log the case |
| 912 |
self::setEditingLog("Classes DOMDocument or DOMXpath are not available (" . __LINE__ . ")"); |
| 913 |
|
| 914 |
return $php_code; |
| 915 |
} |
| 916 |
|
| 917 |
// backup the file source code no matter what |
| 918 |
self::backupTemplateFileCode($file, $php_code); |
| 919 |
|
| 920 |
/** |
| 921 |
* The following constants will produce a different nodePath, and they are available |
| 922 |
* starting from PHP 5.4 and Libxml >= 2.7.8. |
| 923 |
*/ |
| 924 |
$libxml_updated = defined('LIBXML_HTML_NOIMPLIED') && defined('LIBXML_HTML_NODEFDTD'); |
| 925 |
// |
| 926 |
|
| 927 |
// log data |
| 928 |
self::setEditingLog("Libxml support: " . (int)$libxml_updated); |
| 929 |
|
| 930 |
/** |
| 931 |
* Suppress warnings for bad markup by using libxml's error handling functions. |
| 932 |
* Errors could be retrieved by using print_r(libxml_get_errors(), true). |
| 933 |
*/ |
| 934 |
libxml_use_internal_errors(true); |
| 935 |
// |
| 936 |
|
| 937 |
// load HTML source code |
| 938 |
$html_dom = new DOMDocument(); |
| 939 |
if ($libxml_updated) { |
| 940 |
$html_dom->loadHTML($html_code, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); |
| 941 |
} else { |
| 942 |
$html_dom->loadHTML('<div>' . $html_code . '</div>'); |
| 943 |
} |
| 944 |
|
| 945 |
// get DOMXPath instance of the html DOM Document |
| 946 |
$html_xpath = new DOMXpath($html_dom); |
| 947 |
// find DOMNodeList from given tag (there should be just one tag) |
| 948 |
$found_nodelist = $html_xpath->query("//*[text()[contains(., '{$tag}')]]"); |
| 949 |
|
| 950 |
if (!$found_nodelist || !$found_nodelist->length) { |
| 951 |
// log the case |
| 952 |
self::setEditingLog("tag not found in html source code (" . __LINE__ . ")"); |
| 953 |
|
| 954 |
// tag not found in html source code |
| 955 |
return $php_code; |
| 956 |
} |
| 957 |
|
| 958 |
// find the path to the first node occurrence of the given tag string |
| 959 |
$tag_path = $found_nodelist->item(0)->getNodePath(); |
| 960 |
if (empty($tag_path)) { |
| 961 |
// log the case |
| 962 |
self::setEditingLog("unable to proceed without knowing the path to the tag (" . __LINE__ . ")"); |
| 963 |
|
| 964 |
// unable to proceed without knowing the path to the tag |
| 965 |
return $php_code; |
| 966 |
} |
| 967 |
|
| 968 |
// log data |
| 969 |
self::setEditingLog("Node Path to tag in HTML source code: " . $tag_path); |
| 970 |
|
| 971 |
// import the raw php code to DOMDocument |
| 972 |
$php_dom = new DOMDocument(); |
| 973 |
if ($libxml_updated) { |
| 974 |
$php_dom->loadHTML($php_code, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); |
| 975 |
} else { |
| 976 |
$php_dom->loadHTML('<div>' . $php_code . '</div>'); |
| 977 |
} |
| 978 |
|
| 979 |
// get DOMXPath instance of the php DOM Document |
| 980 |
$php_xpath = new DOMXpath($php_dom); |
| 981 |
|
| 982 |
// adjust html path to tag to comply with the expression for the raw code |
| 983 |
$tag_path = self::adjustDOMXpathQuery($tag_path, $php_xpath); |
| 984 |
|
| 985 |
// log data |
| 986 |
self::setEditingLog("Adjusted Node Path: " . $tag_path); |
| 987 |
|
| 988 |
// query the raw source code to find the same path as a DOMNodeList |
| 989 |
$found_nodelist = $php_xpath->query($tag_path); |
| 990 |
|
| 991 |
if (!$found_nodelist || !$found_nodelist->length) { |
| 992 |
// log the case |
| 993 |
self::setEditingLog("Node Path to tag not found in php source code: {$tag_path} must be invalid (" . __LINE__ . ")"); |
| 994 |
|
| 995 |
// path not found in php source code: $tag_path must be invalid |
| 996 |
return $php_code; |
| 997 |
} |
| 998 |
|
| 999 |
// create a text node with the special tag string |
| 1000 |
$tag_element = $php_dom->createTextNode($tag); |
| 1001 |
// append the tag string to the first (and only) path found |
| 1002 |
$found_nodelist->item(0)->appendChild($tag_element); |
| 1003 |
|
| 1004 |
// obtain the new php source code |
| 1005 |
$php_code = $php_dom->saveHTML(); |
| 1006 |
|
| 1007 |
// log data |
| 1008 |
self::setEditingLog("Tag appended to the given path. New template source code before cleaning:\n\n" . $php_code); |
| 1009 |
|
| 1010 |
// always clean up the PHP code to avoid breaking the file |
| 1011 |
$php_code = self::cleanPHPSourceCode($php_code, $php_dom); |
| 1012 |
|
| 1013 |
/** |
| 1014 |
* @see the following code can help debugging the source code and entire flow. |
| 1015 |
* |
| 1016 |
* $fp = fopen(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'debug.txt', 'w+'); |
| 1017 |
* fwrite($fp, implode("\n", self::getEditingLog()) . "\n\n\nclean source is:\n\n-------------\n" . $php_code); |
| 1018 |
* fclose($fp); |
| 1019 |
*/ |
| 1020 |
|
| 1021 |
return $php_code; |
| 1022 |
} |
| 1023 |
|
| 1024 |
/** |
| 1025 |
* Compares the new HTML source code of the compiled template file |
| 1026 |
* to the raw source code of the template file. Finds the newly added |
| 1027 |
* class attributes in the HTML source code, gets it styling and adds it |
| 1028 |
* to the raw source code of the same template file. Used by the CSS inspector. |
| 1029 |
* |
| 1030 |
* @param array $classes list of custom/temporary CSS classes to look for. |
| 1031 |
* @param string $file the basename of the template file. |
| 1032 |
* @param string $html_code the full HTML source code where the new classes are. |
| 1033 |
* @param string $php_code the raw source code where the new styles should be added. |
| 1034 |
* |
| 1035 |
* @return string the new PHP source code to write onto the file. |
| 1036 |
* |
| 1037 |
* @throws Exception if DOMDocument is not supported as nothing could be done. |
| 1038 |
*/ |
| 1039 |
public static function addStylesByComparingSources($classes, $file, $html_code, $php_code) |
| 1040 |
{ |
| 1041 |
if (!class_exists('DOMDocument') || !class_exists('DOMXpath')) { |
| 1042 |
// we cannot proceed without these classes |
| 1043 |
throw new Exception("DOMDocument or DOMXpath are missing in your PHP installation", 403); |
| 1044 |
} |
| 1045 |
|
| 1046 |
// backup the file source code no matter what |
| 1047 |
self::backupTemplateFileCode($file, $php_code); |
| 1048 |
|
| 1049 |
/** |
| 1050 |
* The following constants will produce a different nodePath, and they are available |
| 1051 |
* starting from PHP 5.4 and Libxml >= 2.7.8. |
| 1052 |
*/ |
| 1053 |
$libxml_updated = defined('LIBXML_HTML_NOIMPLIED') && defined('LIBXML_HTML_NODEFDTD'); |
| 1054 |
// |
| 1055 |
|
| 1056 |
// log data |
| 1057 |
self::setEditingLog("Libxml support: " . (int)$libxml_updated); |
| 1058 |
|
| 1059 |
/** |
| 1060 |
* Suppress warnings for bad markup by using libxml's error handling functions. |
| 1061 |
* Errors could be retrieved by using print_r(libxml_get_errors(), true). |
| 1062 |
*/ |
| 1063 |
libxml_use_internal_errors(true); |
| 1064 |
// |
| 1065 |
|
| 1066 |
// load HTML source code |
| 1067 |
$html_dom = new DOMDocument(); |
| 1068 |
if ($libxml_updated) { |
| 1069 |
$html_dom->loadHTML($html_code, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); |
| 1070 |
} else { |
| 1071 |
$html_dom->loadHTML('<div>' . $html_code . '</div>'); |
| 1072 |
} |
| 1073 |
|
| 1074 |
// get DOMXPath instance of the html DOM Document |
| 1075 |
$html_xpath = new DOMXpath($html_dom); |
| 1076 |
|
| 1077 |
// compose pool of styles |
| 1078 |
$styles_pool = array(); |
| 1079 |
|
| 1080 |
foreach ($classes as $css_class) { |
| 1081 |
// find DOMNodeList from given CSS class (there should be just one node) |
| 1082 |
$found_nodelist = $html_xpath->query("//*[contains(@class, '" . $css_class . "')]"); |
| 1083 |
if (!$found_nodelist || !$found_nodelist->length) { |
| 1084 |
// log the case |
| 1085 |
self::setEditingLog("given CSS class {$css_class} not found in html source code (" . __LINE__ . ")"); |
| 1086 |
|
| 1087 |
// given CSS class not found in html source code |
| 1088 |
continue; |
| 1089 |
} |
| 1090 |
|
| 1091 |
// log data |
| 1092 |
self::setEditingLog("CSS class {$css_class} found in HTML source code"); |
| 1093 |
|
| 1094 |
// get the first node |
| 1095 |
$node = $found_nodelist->item(0); |
| 1096 |
// make sure the node has a style attribute |
| 1097 |
if (!$node->hasAttribute('style')) { |
| 1098 |
// log the case |
| 1099 |
self::setEditingLog("style attribute not found in available tag with CSS class {$css_class} (" . __LINE__ . ")"); |
| 1100 |
|
| 1101 |
// style attribute not found |
| 1102 |
continue; |
| 1103 |
} |
| 1104 |
|
| 1105 |
// make sure the style attribute is not empty |
| 1106 |
$style_attr = $node->getAttribute('style'); |
| 1107 |
if (!$style_attr || empty($style_attr)) { |
| 1108 |
// log the case |
| 1109 |
self::setEditingLog("style attribute is empty in available tag with CSS class {$css_class} (" . __LINE__ . ")"); |
| 1110 |
|
| 1111 |
// style attribute is empty |
| 1112 |
continue; |
| 1113 |
} |
| 1114 |
|
| 1115 |
// compose style information |
| 1116 |
$style = new stdClass; |
| 1117 |
$style->node_path = $node->getNodePath(); |
| 1118 |
$style->attribute = $style_attr; |
| 1119 |
|
| 1120 |
// push style object to the pool |
| 1121 |
array_push($styles_pool, $style); |
| 1122 |
} |
| 1123 |
|
| 1124 |
if (!count($styles_pool)) { |
| 1125 |
// no style attributes found to add, unable to proceed |
| 1126 |
return $php_code; |
| 1127 |
} |
| 1128 |
|
| 1129 |
// import the raw php code to DOMDocument |
| 1130 |
$php_dom = new DOMDocument(); |
| 1131 |
if ($libxml_updated) { |
| 1132 |
$php_dom->loadHTML($php_code, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); |
| 1133 |
} else { |
| 1134 |
$php_dom->loadHTML('<div>' . $php_code . '</div>'); |
| 1135 |
} |
| 1136 |
|
| 1137 |
// get DOMXPath instance of the php DOM Document |
| 1138 |
$php_xpath = new DOMXpath($php_dom); |
| 1139 |
|
| 1140 |
// iterate all styles to add to the various nodes |
| 1141 |
foreach ($styles_pool as $style) { |
| 1142 |
// log data |
| 1143 |
self::setEditingLog("Node Path to tag to be styled: " . $style->node_path); |
| 1144 |
|
| 1145 |
// adjust html path to node to comply with the expression for the raw code |
| 1146 |
$node_path = self::adjustDOMXpathQuery($style->node_path, $php_xpath); |
| 1147 |
|
| 1148 |
// log data |
| 1149 |
self::setEditingLog("Adjusted node path is: " . $node_path); |
| 1150 |
|
| 1151 |
// query the raw source code to find the same path as a DOMNodeList |
| 1152 |
$found_nodelist = $php_xpath->query($node_path); |
| 1153 |
|
| 1154 |
if (!$found_nodelist || !$found_nodelist->length) { |
| 1155 |
// log the case |
| 1156 |
self::setEditingLog("Node Path not found in php source code for styling: {$node_path} must be invalid (" . __LINE__ . ")"); |
| 1157 |
|
| 1158 |
// path not found in php source code: $node_path must be invalid |
| 1159 |
continue; |
| 1160 |
} |
| 1161 |
|
| 1162 |
// get the first node |
| 1163 |
$node = $found_nodelist->item(0); |
| 1164 |
|
| 1165 |
// set the style attribute |
| 1166 |
$node->setAttribute('style', $style->attribute); |
| 1167 |
|
| 1168 |
// obtain the new php source code |
| 1169 |
$php_code = $php_dom->saveHTML(); |
| 1170 |
} |
| 1171 |
|
| 1172 |
// log data |
| 1173 |
self::setEditingLog("Style(s) added to the source code. New template source code before cleaning:\n\n" . $php_code); |
| 1174 |
|
| 1175 |
// always clean up the PHP code to avoid breaking the file |
| 1176 |
$php_code = self::cleanPHPSourceCode($php_code, $php_dom); |
| 1177 |
|
| 1178 |
return $php_code; |
| 1179 |
} |
| 1180 |
|
| 1181 |
/** |
| 1182 |
* Appends an execution log to the execution log array. |
| 1183 |
* |
| 1184 |
* @param string $log the execution string to append. |
| 1185 |
* |
| 1186 |
* @return void |
| 1187 |
*/ |
| 1188 |
public static function setEditingLog($log) |
| 1189 |
{ |
| 1190 |
if (static::$editingLog === null) { |
| 1191 |
static::$editingLog = array(); |
| 1192 |
} |
| 1193 |
|
| 1194 |
array_push(static::$editingLog, $log); |
| 1195 |
} |
| 1196 |
|
| 1197 |
/** |
| 1198 |
* Gets the execution log array for all editing operations. |
| 1199 |
* |
| 1200 |
* @return mixed the current editing log array or null. |
| 1201 |
*/ |
| 1202 |
public static function getEditingLog() |
| 1203 |
{ |
| 1204 |
return static::$editingLog; |
| 1205 |
} |
| 1206 |
} |
| 1207 |
|