Page.class.php (21558B)
1 <?php 2 namespace cms\model; 3 // OpenRat Content Management System 4 // Copyright (C) 2002-2012 Jan Dankert, cms@jandankert.de 5 // 6 // This program is free software; you can redistribute it and/or 7 // modify it under the terms of the GNU General Public License 8 // as published by the Free Software Foundation; either version 2 9 // of the License, or (at your option) any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 // 16 // You should have received a copy of the GNU General Public License 17 // along with this program; if not, write to the Free Software 18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 19 use cms\mustache\Mustache; 20 use cms\publish\PublishPreview;use cms\publish\PublishPublic; 21 use http\Exception\RuntimeException; 22 use Logger; 23 use util\FileCache; 24 25 26 /** 27 * Darstellen einer Seite 28 * 29 * @author Jan Dankert 30 * @package openrat.objects 31 */ 32 33 class Page extends BaseObject 34 { 35 var $enclosingObjectId = -1; //Id der Seite in die diese Seite im Rahmen der Generierung eingefügt wird 36 //Wichtig für include-Values 37 var $pageid; 38 var $templateid; 39 40 /** 41 * @var Template 42 */ 43 var $template; 44 45 /** 46 * @deprecated 47 */ 48 var $simple = false; 49 50 /** 51 * @deprecated replaced by publish->isPublic() 52 */ 53 var $public = false; 54 55 var $el = array(); 56 57 /** 58 * Stellt fest, ob die Editier-Icons angezeigt werden sollen. Dies ist 59 * nur der Fall, wenn die Seite auch zum Bearbeiten generiert wird. 60 * Wird die Seite zum Veröffentlichen generiert, muss diese Eigenschaft 61 * natürlich "false" sein. 62 * @var boolean 63 */ 64 var $icons = false; 65 var $src = ''; 66 var $edit = false; 67 68 var $content_negotiation = false; 69 var $cut_index = false; 70 var $default_language = false; 71 // var $withLanguage = false; 72 var $withLanguage = true; 73 var $withModel = true; 74 // var $withModel = false; 75 var $link = false; 76 var $fullFilename = ''; 77 78 var $log_filenames = array(); 79 var $modelid = 0; 80 81 /** 82 * @var Value[] 83 */ 84 public $values; 85 86 /** 87 * Inhalt der Seite. 88 */ 89 public $value; 90 91 function __construct( $objectid='' ) 92 { 93 parent::__construct( $objectid ); 94 $this->isPage = true; 95 $this->typeid = BaseObject::TYPEID_PAGE; 96 97 $this->publisher = new PublishPreview(); 98 99 } 100 101 102 /** 103 * @return FileCache 104 */ 105 public function getCache() { 106 $cacheKey = array('db'=>db()->id, 107 'page' =>$this->objectid, 108 'language' =>$this->languageid, 109 'model' =>$this->modelid, 110 'publish' =>\ClassUtils::getSimpleClassName($this->publisher) ); 111 112 return new FileCache( $cacheKey,function() { 113 return $this->generateValue(); 114 }, $this->lastchangeDate ); 115 116 } 117 118 /** 119 * Ermitteln der Objekt-ID (Tabelle object) anhand der Seiten-ID (Tablle page) 120 * 121 * @deprecated pageid sollte nicht mehr benutzt werden 122 * @return Integer objectid 123 */ 124 function getObjectIdFromPageId( $pageid ) 125 { 126 $db = db_connection(); 127 128 $sql = $db->sql( 'SELECT objectid FROM {{page}} '. 129 ' WHERE id={pageid}' ); 130 $sql->setInt('pageid',$pageid); 131 132 return $sql->getOne(); 133 } 134 135 136 /** 137 * Ermitteln der Seiten-ID anhand der Objekt-ID 138 * 139 * @deprecated pageid sollte nicht mehr benutzt werden 140 * @return Integer pageid 141 */ 142 public static function getPageIdFromObjectId( $objectid ) 143 { 144 $sql = db()->sql( 'SELECT id FROM {{page}} '. 145 ' WHERE objectid={objectid}' ); 146 $sql->setInt('objectid',$objectid); 147 148 return $sql->getOne(); 149 } 150 151 152 /** 153 * Ermitteln aller Eigenschaften 154 * 155 * @return Array 156 */ 157 function getProperties() 158 { 159 return array_merge( parent::getProperties(), 160 array('full_filename'=>$this->realFilename(), 161 'pageid' =>$this->pageid, 162 'templateid' =>$this->templateid, 163 'mime_type' =>$this->mimeType() ) ); 164 } 165 166 167 /** 168 * Ermitteln der Ordner, in dem sich die Seite befindet 169 * @return array 170 */ 171 function parentfolder() 172 { 173 $folder = new Folder(); 174 $folder->folderid = $this->parentid; 175 176 return $folder->parentObjectFileNames( false,true ); 177 } 178 179 180 181 182 /** 183 * Ermittelt den Pfad zu einem beliebigen Objekt 184 * 185 * @param Integer Objekt-ID des Zielobjektes 186 * @return String Relative Link-angabe, Beispiel: '../../pfad/datei.jpeg' 187 */ 188 public function path_to_object( $objectid ) 189 { 190 if ( ! BaseObject::available( $objectid) ) 191 return 'about:blank'; 192 193 $to = new BaseObject($objectid); 194 $to->load(); 195 $inhalt = $this->publisher->linkToObject( $this, $to ); 196 197 return $inhalt; 198 } 199 200 201 202 203 /** 204 * Eine Seite hinzufuegen 205 */ 206 function add() 207 { 208 parent::add(); // Hinzuf?gen von Objekt (dabei wird Objekt-ID ermittelt) 209 210 $sql = db()->sql('SELECT MAX(id) FROM {{page}}'); 211 $this->pageid = intval($sql->getOne())+1; 212 213 $sql = db()->sql(<<<SQL 214 INSERT INTO {{page}} 215 (id,objectid,templateid) 216 VALUES( {pageid},{objectid},{templateid} ) 217 SQL 218 ); 219 $sql->setInt ('pageid' ,$this->pageid ); 220 $sql->setInt ('objectid' ,$this->objectid ); 221 $sql->setInt ('templateid',$this->templateid ); 222 223 $sql->query(); 224 } 225 226 227 /** 228 * Seite laden 229 */ 230 function load() 231 { 232 $sql = db()->sql( 'SELECT * FROM {{page}} '. 233 ' WHERE objectid={objectid}' ); 234 $sql->setInt('objectid',$this->objectid); 235 $row = $sql->getRow(); 236 237 if ( count($row)==0 ) 238 throw new \ObjectNotFoundException("Page with Id $this->objectid not found."); 239 240 $this->pageid = $row['id' ]; 241 $this->templateid = $row['templateid']; 242 243 $this->objectLoad(); 244 } 245 246 247 function delete() 248 { 249 $db = db_connection(); 250 251 $sql = $db->sql( 'DELETE FROM {{value}} '. 252 ' WHERE pageid={pageid}' ); 253 $sql->setInt('pageid',$this->pageid); 254 $sql->query(); 255 256 $sql = $db->sql( 'DELETE FROM {{page}} '. 257 ' WHERE objectid={objectid}' ); 258 $sql->setInt('objectid',$this->objectid); 259 $sql->query(); 260 261 $this->objectDelete(); 262 } 263 264 265 /** 266 * Kopieren der Inhalts von einer anderen Seite 267 * @param $otherpageid integer ID der Seite, von der der Inhalt kopiert werden soll 268 */ 269 function copyValuesFromPage( $otherpageid ) 270 { 271 $this->load(); 272 273 foreach( $this->getElementIds() as $elementid ) 274 { 275 $project = new Project( $this->projectid ); 276 foreach( $project->getLanguages() as $lid=>$lname ) 277 { 278 $val = new Value(); 279 $val->element = new Element( $elementid ); 280 281 $val->objectid = $otherpageid; 282 $val->pageid = Page::getPageIdFromObjectId( $otherpageid ); 283 $val->languageid = $lid; 284 $val->load(); 285 286 // Inhalt nur speichern, wenn vorher vorhanden 287 if ( $val->valueid != 0 ) 288 { 289 $val->objectid = $this->objectid; 290 $val->pageid = Page::getPageIdFromObjectId( $this->objectid ); 291 $val->save(); 292 } 293 } 294 } 295 } 296 297 298 299 300 function save() 301 { 302 $db = db_connection(); 303 304 $sql = $db->sql('UPDATE {{page}}'. 305 ' SET templateid ={templateid}'. 306 ' WHERE objectid={objectid}' ); 307 $sql->setInt('templateid' ,$this->templateid); 308 $sql->setInt('objectid' ,$this->objectid ); 309 $sql->query(); 310 311 $this->objectSave(); 312 } 313 314 315 316 function replaceTemplate( $newTemplateId,$replaceElementMap ) 317 { 318 $oldTemplateId = $this->templateid; 319 320 $db = db_connection(); 321 322 // Template-id dieser Seite aendern 323 $this->templateid = $newTemplateId; 324 325 $sql = $db->sql('UPDATE {{page}}'. 326 ' SET templateid ={templateid}'. 327 ' WHERE objectid={objectid}' ); 328 $sql->setInt('templateid' ,$this->templateid); 329 $sql->setInt('objectid' ,$this->objectid ); 330 $sql->query(); 331 332 333 // Inhalte umschluesseln, d.h. die Element-Ids aendern 334 $template = new Template( $oldTemplateId ); 335 foreach( $template->getElementIds() as $oldElementId ) 336 { 337 if ( !isset($replaceElementMap[$oldElementId]) || 338 intval($replaceElementMap[$oldElementId]) < 1 ) 339 { 340 \Logger::debug( 'deleting value of elementid '.$oldElementId ); 341 $sql = $db->sql('DELETE FROM {{value}}'. 342 ' WHERE pageid={pageid}'. 343 ' AND elementid={elementid}' ); 344 $sql->setInt('pageid' ,$this->pageid); 345 $sql->setInt('elementid',$oldElementId ); 346 347 $sql->query(); 348 } 349 else 350 { 351 $newElementId = intval($replaceElementMap[$oldElementId]); 352 353 \Logger::debug( 'updating elementid '.$oldElementId.' -> '.$newElementId ); 354 $sql = $db->sql('UPDATE {{value}}'. 355 ' SET elementid ={newelementid}'. 356 ' WHERE pageid ={pageid}'. 357 ' AND elementid={oldelementid}' ); 358 $sql->setInt('pageid' ,$this->pageid); 359 $sql->setInt('oldelementid',$oldElementId ); 360 $sql->setInt('newelementid',$newElementId ); 361 $sql->query(); 362 } 363 } 364 } 365 366 367 368 /** 369 * Ermitteln des Dateinamens dieser Seite. 370 * 371 * Wenn '$this->content_negotiation' auf 'true' steht, wird der Dateiname ggf. gekürzt, 372 * so wie er für HTML-Links verwendet wird. Sonst wird immer der echte Dateiname 373 * ermittelt. 374 * 375 * @return String Kompletter Dateiname, z.B. '/pfad/seite.en.html' 376 */ 377 function full_filename() 378 { 379 $filename = $this->path().'/'.$this->getFilename(); 380 381 $this->fullFilename = $filename; 382 return $filename; 383 } 384 385 386 /** 387 * Ermitteln des Dateinamens dieser Seite. 388 * 389 * Wenn '$this->content_negotiation' auf 'true' steht, wird der Dateiname ggf. gekürzt, 390 * so wie er für HTML-Links verwendet wird. Sonst wird immer der echte Dateiname 391 * ermittelt. 392 * 393 * @return String Kompletter Dateiname, z.B. '/pfad/seite.en.html' 394 */ 395 function getFilename() 396 { 397 return self::filename(); 398 } 399 400 401 402 /** 403 * Ermitteln des Dateinamens dieser Seite. 404 * 405 * Wenn '$this->content_negotiation' auf 'true' steht, wird der Dateiname ggf. gekürzt, 406 * so wie er für HTML-Links verwendet wird. Sonst wird immer der echte Dateiname 407 * ermittelt. 408 * 409 * @return String Kompletter Dateiname, z.B. '/pfad/seite.en.html' 410 */ 411 function filename() 412 { 413 if ( $this->cut_index && $this->filename == config('publish','default') ) 414 { 415 return ''; // Link auf Index-Datei, der Dateiname bleibt leer. 416 } 417 else 418 { 419 $format = config('publish','format'); 420 $format = str_replace('{filename}',parent::filename(),$format ); 421 422 if ( !$this->withLanguage || $this->content_negotiation && config('publish','negotiation','page_negotiate_language' ) ) 423 { 424 $format = str_replace('{language}' ,'',$format ); 425 $format = str_replace('{language_sep}','',$format ); 426 } 427 else 428 { 429 $l = new Language( $this->languageid ); 430 $l->load(); 431 $format = str_replace('{language}' ,$l->isoCode ,$format ); 432 $format = str_replace('{language_sep}',config('publish','language_sep'),$format ); 433 } 434 435 if ( !$this->withModel || $this->content_negotiation && config('publish','negotiation','page_negotiate_type' ) ) 436 { 437 $format = str_replace('{type}' ,'',$format ); 438 $format = str_replace('{type_sep}','',$format ); 439 } 440 else 441 { 442 $t = new Template( $this->templateid ); 443 $t->modelid = $this->modelid; 444 $t->load(); 445 $format = str_replace('{type}' ,$t->extension ,$format ); 446 $format = str_replace('{type_sep}',config('publish','type_sep'),$format ); 447 } 448 return $format; 449 } 450 } 451 452 453 454 // function language_filename() 455 // { 456 // global $SESS; 457 // 458 // $db = db_connection(); 459 // 460 // $sql = $db->sql( 'SELECT COUNT(*) FROM {{language}}'. 461 // ' WHERE projectid={projectid}' ); 462 // $sql->setInt('projectid',$SESS['projectid']); 463 // 464 // if ( $sql->getOne( $sql ) == 1 ) 465 // { 466 // // Wenn es nur eine Sprache gibt, keine Sprachangabe im Dateinamen 467 // return ''; 468 // } 469 // else 470 // { 471 // $sql = $db->sql( 'SELECT isocode FROM {{language}}'. 472 // ' WHERE id={languageid}' ); 473 // $sql->setInt('languageid',$this->languageid); 474 // $isocode = $sql->getOne( $sql ); 475 // 476 // return strtolower( $isocode ); 477 // } 478 // } 479 480 481 /** 482 * Erzeugen der Inhalte zu allen Elementen dieser Seite 483 * wird von generate() aufgerufen 484 * 485 * @access private 486 */ 487 function getElementIds() 488 { 489 $t = new Template( $this->templateid ); 490 491 return $t->getElementIds(); 492 } 493 494 495 496 /** 497 * Erzeugen der Inhalte zu allen Elementen dieser Seite 498 * wird von generate() aufgerufen 499 * 500 * @access private 501 */ 502 function getElements() 503 { 504 if ( !isset($this->template) ) 505 $this->template = new Template( $this->templateid ); 506 507 return $this->template->getElements(); 508 } 509 510 511 512 /** 513 * Erzeugen der Inhalte zu allen Elementen dieser Seite 514 * wird von generate() aufgerufen 515 * 516 * @access private 517 */ 518 function getWritableElements() 519 { 520 if ( !isset($this->template) ) 521 $this->template = new Template( $this->templateid ); 522 523 return $this->template->getWritableElements(); 524 } 525 526 527 528 /** 529 * Erzeugen der Inhalte zu allen Elementen dieser Seite 530 * wird von generate() aufgerufen 531 */ 532 public function generate_elements() 533 { 534 $this->values = array(); 535 536 if ( $this->publisher->isSimplePreview() ) 537 $elements = $this->getWritableElements(); 538 else 539 $elements = $this->getElements(); 540 541 foreach( $elements as $elementid=>$element ) 542 { 543 // neues Inhaltobjekt erzeugen 544 $val = new Value(); 545 $val->element = $element; 546 547 $val->publisher = $this->publisher; 548 $val->objectid = $this->objectid; 549 $val->pageid = $this->pageid; 550 $val->languageid = $this->languageid; 551 $val->modelid = $this->modelid; 552 $val->page = $this; 553 try { 554 $val->generate(); 555 } catch( \Exception $e ) { 556 // Unrecoverable Error while generating the content. 557 558 Logger::warn('Could not generate Value '.$val->__toString() . ': '.$e->getMessage() ); 559 560 if ( $this->publisher->isPublic() ) 561 $val->value = ''; 562 else 563 $val->value = '[Warning: '.$e->getMessage().']'; 564 } 565 566 $this->values[$elementid] = $val; 567 } 568 } 569 570 571 572 public function generate() { 573 574 return $this->getCache()->get(); 575 } 576 577 /** 578 * Erzeugen des Inhaltes der gesamten Seite. 579 * 580 * @return String Inhalt 581 */ 582 private function generateValue() 583 { 584 global $conf; 585 586 // Setzen der 'locale', damit sprachabhängige Systemausgaben (wie z.B. die 587 // Ausgabe von strftime()) in der korrekten Sprache dargestellt werden. 588 $language = new Language($this->languageid); 589 $language->load(); 590 591 $language->setCurrentLocale(); 592 593 594 $this->template = new Template( $this->templateid ); 595 $this->template->modelid = $this->modelid; 596 $this->template->load(); 597 $this->ext = $this->template->extension; 598 599 $this->generate_elements(); 600 601 // Get a List with ElementId->ElementName 602 $elements = array_map(function($element) { 603 return $element->name; 604 },$this->getElements() ); 605 606 $templatemodel = new TemplateModel( $this->template->templateid, $this->modelid ); 607 $templatemodel->load(); 608 $src = $templatemodel->src; 609 610 $data = array(); 611 612 // Template should have access to the page properties. 613 // Template should have access to the settings of this node object. 614 $data['_page' ] = $this->getProperties() ; 615 $data['_localsettings'] = $this->getSettings() ; 616 $data['_settings' ] = $this->getTotalSettings(); 617 618 // No we are collecting the data and are fixing some old stuff. 619 620 foreach( $elements as $elementId=>$elementName ) 621 { 622 $data[ $elementName ] = $this->values[$elementId]->generate(); 623 624 // The following code is for old template values: 625 626 // convert {{<id>}} to {{<name>}} 627 $src = str_replace( '{{'.$elementId.'}}','{{'.$elementName.'}}',$src ); 628 629 $src = str_replace( '{{IFNOTEMPTY:'.$elementId.':BEGIN}}','{{#'.$elementName.'}}',$src ); 630 $src = str_replace( '{{IFNOTEMPTY:'.$elementId.':END}}' ,'{{/'.$elementName.'}}',$src ); 631 $src = str_replace( '{{IFEMPTY:' .$elementId.':BEGIN}}','{{^'.$elementName.'}}',$src ); 632 $src = str_replace( '{{IFEMPTY:' .$elementId.':END}}' ,'{{/'.$elementName.'}}',$src ); 633 634 if ( $this->icons ) 635 $src = str_replace( '{{->'.$elementId.'}}','<a href="javascript:parent.openNewAction(\''.$elementName.'\',\'pageelement\',\''.$this->objectid.'_'.$value->element->elementid.'\');" title="'.$value->element->desc.'"><img src="'.OR_THEMES_DIR.$conf['interface']['theme'].'/images/icon_el_'.$value->element->type.IMG_ICON_EXT.'" border="0" align="left"></a>',$src ); 636 else 637 $src = str_replace( '{{->'.$elementId.'}}','',$src ); 638 } 639 640 Logger::trace( 'pagedata: '.print_r($data,true) ); 641 642 // Now we have collected all data, lets call the template engine: 643 644 $template = new Mustache(); 645 $template->escape = null; // No HTML escaping, this is the job of this CMS ;) 646 $template->partialLoader = function( $name ) { 647 648 if ( substr($name,0,5) == 'file:') { 649 $fileid = intval( substr($name,5) ); 650 $file = new File( $fileid ); 651 return $file->loadValue(); 652 } 653 654 655 $project = new Project($this->projectid); 656 $templateid = array_search($name,$project->getTemplates() ); 657 658 if ( ! $templateid ) 659 return $this->publisher->isPublic()?'':"template '$name' not found"; 660 661 if ( $templateid == $this->template->templateid ) 662 return $this->publisher->isPublic()?'':'Template recursion is not supported'; 663 664 665 $templatemodel = new TemplateModel( $templateid, $this->modelid ); 666 $templatemodel->load(); 667 668 return $templatemodel->src; 669 }; 670 671 try { 672 $template->parse($src); 673 } catch (\Exception $e) { 674 // Should we throw it to the caller? 675 // No, because it is not a technical error. So let's only log it. 676 Logger::warn("Template rendering failed: ".$e->getMessage() ); 677 return $e->getMessage(); 678 } 679 $src = $template->render( $data ); 680 681 // now we have the fully generated source. 682 683 // should we do a UTF-8-escaping here? 684 // Default should be off, because if you are fully using utf-8 (you should do), this is unnecessary. 685 if ( config('publish','escape_8bit_characters') ) 686 if ( substr($this->mimeType(),-4) == 'html' ) 687 { 688 /* 689 * 690 $src = htmlentities($src,ENT_NOQUOTES,'UTF-8'); 691 $src = str_replace('<' , '<', $src); 692 $src = str_replace('>' , '>', $src); 693 $src = str_replace('&', '&', $src); 694 */ 695 $src = translateutf8tohtml($src); 696 } 697 698 return $src; 699 } 700 701 702 /** 703 * Schreiben des Seiteninhaltes in die temporaere Datei 704 */ 705 public function write() 706 { 707 $this->getCache()->load(); 708 } 709 710 711 /** 712 * Generieren dieser Seite in Dateisystem und/oder auf FTP-Server 713 */ 714 public function publish() 715 { 716 $project = Project::create( $this->projectid ); 717 718 $allLanguages = $project->getLanguageIds(); 719 $allModels = $project->getModelIds(); 720 721 // Schleife ueber alle Sprachvarianten 722 foreach( $allLanguages as $languageid ) 723 { 724 $this->languageid = $languageid; 725 $this->withLanguage = count($allLanguages) > 1 || config('publish','filename_language') == 'always'; 726 $this->withModel = count($allModels ) > 1 || config('publish','filename_type' ) == 'always'; 727 728 // Schleife ueber alle Projektvarianten 729 foreach( $allModels as $projectmodelid ) 730 { 731 $this->modelid = $projectmodelid; 732 733 $this->load(); 734 $this->getCache()->load(); 735 736 // Vorlage ermitteln. 737 $t = new Template( $this->templateid ); 738 $t->modelid = $this->modelid; 739 $t->load(); 740 741 // Nur wenn eine Datei-Endung vorliegt wird die Seite veroeffentlicht 742 if ( !empty($t->extension) ) 743 { 744 $this->publisher->copy( $this->getCache()->getFilename(),$this->full_filename() ); 745 $this->publisher->publishedObjects[] = $this->getProperties(); 746 } 747 } 748 } 749 750 parent::setPublishedTimestamp(); 751 752 } 753 754 755 /** 756 * Ermittelt den Mime-Type zu dieser Seite 757 * 758 * @return String Mime-Type 759 */ 760 function mimeType() 761 { 762 if ( ! is_object($this->template) ) 763 { 764 $this->template = new Template( $this->templateid ); 765 $this->template->modelid = $this->modelid; 766 $this->template->load(); 767 } 768 769 $this->mime_type = $this->template->mimeType(); 770 771 return( $this->mime_type ); 772 } 773 774 775 776 function setTimestamp() 777 { 778 $this->getCache()->invalidate(); 779 780 parent::setTimestamp(); 781 } 782 783 784 /** 785 * Ermittelt den Dateinamen dieser Seite, so wie sie auch im Dateisystem steht. 786 */ 787 function realFilename() 788 { 789 $project = new Project( $this->projectid ); 790 $this->withLanguage = config('publish','filename_language') == 'always' || count($project->getLanguageIds()) > 1; 791 $this->withModel = config('publish','filename_type' ) == 'always' || count($project->getModelIds() ) > 1; 792 793 return $this->full_filename(); 794 } 795 796 797 /** 798 * Stellt fest, ob diese Seite im HTML-Format veröffentlicht wird. 799 * @return boolean 800 */ 801 public function isHtml() 802 { 803 return $this->mimeType()=='text/html'; 804 } 805 806 public function __toString() 807 { 808 return 'Id '.$this->pageid.' (filename='.$this->filename.',language='.$this->languageid.', modelid='.$this->modelid.', templateid='.$this->templateid.')'; 809 } 810 } 811 812 813 ?>