PublishPublic.class.php (14650B)
1 <?php 2 3 namespace cms\publish; 4 5 use cms\model\BaseObject; 6 use cms\model\File; 7 use cms\model\Folder; 8 use cms\model\Link; 9 use cms\model\Page; 10 use cms\model\Project; 11 use cms\model\Url; 12 use util\exception\PublisherException; 13 use util\FileUtils; 14 use cms\publish\Ftp; 15 use logger\Logger; 16 use util\exception\UIException; 17 use util\Session; 18 19 20 21 /** 22 * User: dankert 23 * Date: 10.08.18 24 * Time: 23:47 25 */ 26 27 class PublishPublic extends Publish 28 { 29 const SCHEMA_ABSOLUTE = 1; 30 const SCHEMA_RELATIVE = 2; 31 32 const MAX_RECURSIVE_COUNT = 10; 33 34 35 /** 36 * Enthaelt bei Bedarf das FTP-Objekt. N�mlich dann, wenn 37 * zu einem FTP-Server veroeffentlicht werden soll. 38 * @var Object 39 */ 40 private $ftp; 41 42 private $localDestinationDirectory = ''; 43 44 /** 45 * Enthaelt die gleichnamige Einstellung aus dem Projekt. 46 * @var boolean 47 */ 48 private $contentNegotiation = false; 49 50 /** 51 * Enthaelt die gleichnamige Einstellung aus dem Projekt. 52 * @var boolean 53 */ 54 private $cutIndex = false; 55 56 /** 57 * Enthaelt die gleichnamige Einstellung aus dem Projekt. 58 * @var String 59 */ 60 private $commandAfterPublish = ''; 61 62 /** 63 * Enthaelt am Ende der Ver�ffentlichung ein Array mit den ver�ffentlichten Objekten. 64 * @var Array 65 */ 66 public $publishedObjects = array(); 67 68 /** 69 * Enthaelt im Fehlerfall (wenn 'ok' auf 'false' steht) eine 70 * Fehlermeldung. 71 * 72 * @var String 73 */ 74 public $log = array(); 75 76 /** 77 * Konstruktor.<br> 78 * <br> 79 * Oeffnet ggf. Verbindungen. 80 * 81 * @return Publish 82 */ 83 public function __construct( $projectid ) 84 { 85 $confPublish = config('publish'); 86 87 $project = Project::create( $projectid ); 88 $project->load(); 89 90 $this->linkSchema = ($project->linkAbsolute ? self::SCHEMA_ABSOLUTE : self::SCHEMA_RELATIVE); 91 92 // Feststellen, ob FTP benutzt wird. 93 // Dazu muss FTP aktiviert sein (enable=true) und eine URL vorhanden sein. 94 $ftpUrl = ''; 95 if ( $confPublish['ftp']['enable'] ) 96 { 97 if ( $confPublish['ftp']['per_project'] && !empty($project->ftp_url) ) 98 $ftpUrl = $project->ftp_url; 99 elseif ( !empty($confPublish['ftp']['host']) ) 100 $ftpUrl = $project->ftp_url; 101 } 102 103 if ( $ftpUrl && $ftpUrl[0]!='#' ) 104 { 105 $this->ftp = new \cms\publish\Ftp($project->ftp_url); // Aufbauen einer FTP-Verbindung 106 107 $this->ftp->passive = ( $project->ftp_passive == '1' ); 108 } 109 110 $targetDir = rtrim( $project->target_dir,'/' ); 111 112 if ( FileUtils::isAbsolutePath($targetDir) && $confPublish['filesystem']['per_project'] ) 113 { 114 $this->localDestinationDirectory = FileUtils::toAbsolutePath([$targetDir]); // Projekteinstellung verwenden. 115 } 116 else 117 { 118 // Konfiguriertes Verzeichnis verwenden. 119 $this->localDestinationDirectory = FileUtils::toAbsolutePath([$confPublish['filesystem']['directory'],$targetDir]); 120 } 121 122 123 // Sofort pruefen, ob das Zielverzeichnis ueberhaupt beschreibbar ist. 124 if ( $this->localDestinationDirectory && $this->localDestinationDirectory[0] == '#') 125 $this->localDestinationDirectory = ''; 126 127 $this->contentNegotiation = ( $project->content_negotiation == '1' ); 128 $this->cutIndex = ( $project->cut_index == '1' ); 129 130 if ( $confPublish['command']['enable'] ) 131 { 132 if ( $confPublish['command']['per_project'] && !empty($project->cmd_after_publish) ) 133 $this->commandAfterPublish = $project->cmd_after_publish; 134 else 135 $this->commandAfterPublish = @$confPublish['command']['command']; 136 } 137 138 // Im Systemkommando Variablen ersetzen 139 $this->commandAfterPublish = str_replace('{name}' ,$project->name ,$this->commandAfterPublish); 140 $this->commandAfterPublish = str_replace('{dir}' ,$this->localDestinationDirectory ,$this->commandAfterPublish); 141 $this->commandAfterPublish = str_replace('{dirbase}',basename($this->localDestinationDirectory),$this->commandAfterPublish); 142 143 if ( config('security','nopublish') ) 144 { 145 Logger::warn('publishing is disabled.'); 146 $this->commandAfterPublish = ''; 147 $this->localDestinationDirectory = ''; 148 $this->ftp = null; 149 } 150 } 151 152 153 154 /** 155 * @var int 156 */ 157 private $linkSchema; 158 159 /** 160 * @param $from \cms\model\BaseObject 161 * @param $to \cms\model\BaseObject 162 */ 163 public function linkToObject( BaseObject $from, BaseObject $to ) { 164 165 $schema = $this->linkSchema; 166 167 $counter = 0; 168 while( $to->typeid == BaseObject::TYPEID_LINK ) 169 { 170 if ( $counter++ > self::MAX_RECURSIVE_COUNT) 171 throw new \LogicException("Too much redirects while following a link. Stopped at #".$to->objectid ); 172 173 $link = new Link( $to->objectid ); 174 $link->load(); 175 176 $to = new BaseObject( $link->linkedObjectId ); 177 $to->objectLoad(); 178 } 179 180 switch( $to->typeid ) 181 { 182 case BaseObject::TYPEID_FILE: 183 case BaseObject::TYPEID_IMAGE: 184 case BaseObject::TYPEID_TEXT: 185 186 $f = new File( $to->objectid ); 187 188 $p = Project::create( $to->projectid )->load(); 189 $f->content_negotiation = $p->content_negotiation; 190 191 $f->load(); 192 $filename = $f->filename(); 193 break; 194 195 case BaseObject::TYPEID_PAGE: 196 197 $p = new Page( $to->objectid ); 198 $p->languageid = $from->languageid; 199 $p->modelid = $from->modelid; 200 $p->cut_index = $from->cut_index; 201 $p->content_negotiation = $from->content_negotiation; 202 $p->withLanguage = $from->withLanguage; 203 $p->withModel = $from->withModel; 204 $p->load(); 205 $filename = $p->getFilename(); 206 break; 207 208 case BaseObject::TYPEID_URL: 209 $url = new Url( $to->objectid ); 210 $url->load(); 211 return $url->url; 212 default: 213 throw new \LogicException("Could not build a link to the unknown Type ".$to->typeid.':'.$to->getType() ); 214 } 215 216 217 if ( $from->projectid != $to->projectid ) 218 { 219 // Target object is in another project. 220 // we have to use absolute URLs. 221 $schema = self::SCHEMA_ABSOLUTE; 222 223 // Target is in another Project. So we have to create an absolute URL. 224 $targetProject = Project::create( $to->projectid )->load(); 225 $host = $targetProject->url; 226 227 if ( ! strpos($host,'//' ) === FALSE ) { 228 // No protocol in hostname. So we have to prepend the URL with '//'. 229 $host = '//'.$host; 230 } 231 } 232 else { 233 $host = ''; 234 } 235 236 237 238 239 if ( $schema == self::SCHEMA_RELATIVE ) 240 { 241 $folder = new Folder( $from->getParentFolderId() ); 242 $folder->load(); 243 $fromPathFolders = $folder->parentObjectFileNames(false,true); 244 245 $folder = new Folder($to->getParentFolderId() ); 246 247 $toPathFolders = $folder->parentObjectFileNames(false, true); 248 249 // Shorten the relative URL 250 // if the actual page is /path/folder1/page1 251 // and the target page is /path/folder2/page2 252 // we shorten the link from ../../path/folder2/page2 253 // to ../folder2/page2 254 foreach( $fromPathFolders as $folderId => $folderFileName ) { 255 if ( count($toPathFolders) >= 1 && array_keys($toPathFolders)[0] == $folderId ) { 256 unset( $fromPathFolders[$folderId] ); 257 unset( $toPathFolders [$folderId] ); 258 }else { 259 break; 260 } 261 262 } 263 264 if ( $fromPathFolders ) 265 $path = str_repeat( '../',count($fromPathFolders) ); 266 else 267 $path = './'; // Just to clarify- this could be blank too. 268 269 if ( $toPathFolders ) 270 $path .= implode('/',$toPathFolders).'/'; 271 } 272 else { 273 // Absolute Pfadangaben 274 $folder = new Folder( $to->getParentFolderId() ); 275 $toPathFolders = $folder->parentObjectFileNames(false, true); 276 277 $path = '/'; 278 279 if ( $toPathFolders ) 280 $path .= implode('/',$toPathFolders).'/'; 281 } 282 283 284 $uri = $host . $path . $filename; 285 286 if( !$uri ) 287 $uri = '.'; 288 289 return $uri; 290 } 291 292 293 294 295 296 /** 297 * Kopieren einer Datei aus dem tempor�ren Verzeichnis in das Zielverzeichnis.<br> 298 * Falls notwenig, wird ein Hochladen per FTP ausgef�hrt. 299 * 300 * @param String $tmp_filename 301 * @param String $dest_filename 302 */ 303 public function copy( $tmp_filename,$dest_filename,$lastChangeDate=null ) 304 { 305 global $conf; 306 $source = $tmp_filename; 307 308 309 310 if ( $this->localDestinationDirectory ) 311 { 312 // Is the output directory writable? 313 if ( !is_writeable( $this->localDestinationDirectory ) ) 314 throw new PublisherException('directory not writable: ' . $this->localDestinationDirectory); 315 316 $dest = $this->localDestinationDirectory.'/'.$dest_filename; 317 318 // Is the destination writable? 319 if ( is_file($dest) && !is_writeable( $dest ) ) 320 throw new PublisherException('file not writable: ' . $this->dest); 321 322 // Copy file to destination 323 if (!@copy( $source,$dest )); 324 { 325 // Create directories, if necessary. 326 $this->mkdirs( dirname($dest) ); 327 328 if (!@copy( $source,$dest )) 329 throw new PublisherException( 'failed copying local file:' . "\n" . 330 'source : ' . $source . "\n" . 331 'destination: ' . $dest); 332 333 // Das Änderungsdatum der Datei auch in der Zieldatei setzen. 334 if ( $conf['publish']['set_modification_date'] ) 335 if ( ! is_null($lastChangeDate) ) 336 @touch( $dest,$lastChangeDate ); 337 338 Logger::debug("published: $dest"); 339 } 340 341 if (!empty($conf['security']['chmod'])) 342 { 343 // CHMOD auf der Datei ausfuehren. 344 if ( ! @chmod($dest,octdec($conf['security']['chmod'])) ) 345 throw new PublisherException('Unable to CHMOD file ' . $dest); 346 } 347 } 348 349 if ( $this->ftp ) // Falls FTP aktiviert 350 { 351 $dest = $dest_filename; 352 $this->ftp->put( $source,$dest ); 353 } 354 } 355 356 357 358 /** 359 * Rekursives Anlagen von Verzeichnisse 360 * Nett gemacht. 361 * Quelle: http://de3.php.net/manual/de/function.mkdir.php 362 * Thx to acroyear at io dot com 363 * 364 * @param String Verzeichnis 365 * @return boolean 366 */ 367 private function mkdirs($path ) 368 { 369 global $conf; 370 371 if ( is_dir($path) ) 372 return; // Path exists 373 374 $parentPath = dirname($path); 375 376 $this->mkdirs($parentPath); 377 378 // 379 if ( ! @mkdir($path) ) 380 throw new PublisherException( 'Cannot create directory: ' . $path); 381 382 // CHMOD auf dem Verzeichnis ausgef�hren. 383 if (!empty($conf['security']['chmod_dir'])) 384 { 385 if ( ! @chmod($path,octdec($conf['security']['chmod_dir'])) ) 386 throw new PublisherException('Unable to CHMOD directory: ' . $path); 387 } 388 } 389 390 391 392 /** 393 * Beenden des Ver�ffentlichungs-Vorganges.<br> 394 * Eine vorhandene FTP-Verbindung wird geschlossen.<br> 395 * Falls entsprechend konfiguriert, wird ein Systemkommando ausgef�hrt. 396 */ 397 public function close() 398 { 399 if ( $this->ftp ) 400 { 401 Logger::debug('Closing FTP connection' ); 402 $this->ftp->close(); 403 } 404 405 // Ausfuehren des Systemkommandos. 406 if ( !empty($this->commandAfterPublish) ) 407 { 408 $ausgabe = array(); 409 $rc = false; 410 Logger::debug('Executing system command: '.Logger::sanitizeInput($this->commandAfterPublish) ); 411 $user = Session::getUser(); 412 putenv("CMS_USER_NAME=".$user->name ); 413 putenv("CMS_USER_ID=" .$user->userid); 414 putenv("CMS_USER_MAIL=".$user->mail ); 415 exec( $this->commandAfterPublish,$ausgabe,$rc ); 416 417 if ( $rc != 0 ) // Wenn Returncode ungleich 0, dann Fehler melden. 418 throw new PublisherException('System command failed - returncode is ' . $rc . "\n" . 419 $ausgabe); 420 else 421 Logger::debug('System command successful' ); 422 423 } 424 } 425 426 427 428 /** 429 * Aufraeumen des Zielverzeichnisses.<br><br> 430 * Es wird der komplette Zielordner samt Unterverzeichnissen durchsucht. Jede 431 * Datei, die laenger existiert als der aktuelle Request alt ist, wird geloescht.<br> 432 * Natuerlich darf diese Funktion nur nach einem Gesamt-Veroeffentlichen ausgefuehrt werden. 433 */ 434 public function clean() 435 { 436 if ( !empty($this->localDestinationDirectory) ) 437 $this->cleanFolder($this->localDestinationDirectory); 438 } 439 440 441 442 /** 443 * Aufr�umen eines Verzeichnisses.<br><br> 444 * Dateien, die l�nger existieren als der aktuelle Request alt ist, werden gel�scht.<br> 445 * 446 * @param String Verzeichnis 447 */ 448 private function cleanFolder( $folderName ) 449 { 450 $dh = opendir( $folderName ); 451 452 while( $file = readdir($dh) ) 453 { 454 if ( $file != '.' && $file != '..') 455 { 456 $fullpath = $folderName.'/'.$file; 457 458 // Wenn eine Datei beschreibbar und entsprechend alt 459 // ist, dann entfernen 460 if ( is_file($fullpath) && 461 is_writable($fullpath) && 462 filemtime($fullpath) < START_TIME ) 463 unlink($fullpath); 464 465 // Bei Ordnern rekursiv absteigen 466 if ( is_dir( $fullpath) ) 467 { 468 $this->cleanFolder($fullpath); 469 @rmdir($fullpath); 470 } 471 } 472 } 473 } 474 475 476 public function isSimplePreview() 477 { 478 return false; 479 } 480 481 public function isPublic() 482 { 483 return true; 484 } 485 }