/** * Uncompress this file contents and return the result. * Obviously, if a multi-gigibyte file is read with no immediate destination, * you'll probably run out of memory. * * @param Filestore\File|bool $dst The destination to write the uncompressed data to * If not provided, just returns the data. * * @return mixed */ public function uncompress($dst = false) { $zd = gzopen($this->_file->getLocalFilename(), "r"); if (!$zd) return false; $contents = ''; while (!feof($zd)) { $contents .= gzread($zd, 2048); } gzclose($zd); if($dst){ $dst->putContents($contents); } else{ return $contents; } }
/** * Get the remote file for this update site * * @return \Core\Filestore\File */ public function getFile(){ if($this->_remotefile === null){ $this->_remotefile = new \Core\Filestore\Backends\FileRemote(); $this->_remotefile->password = $this->get('password'); $this->_remotefile->username = $this->get('username'); // Set the license information if set. if(defined('SERVER_ID') && strlen(SERVER_ID) == 32){ $this->_remotefile->setRequestHeader('X-Core-Server-ID', SERVER_ID); } $this->_remotefile->setFilename($this->get('url') . '/repo.xml.gz'); } return $this->_remotefile; }
/** * Setup the internal DOMDocument for usage. * * This MUST be called before any operations are applied to this object! * * @return bool */ public function load() { // I need a filename. // Actually I don't........ creating a DOM on-the-fly is a possible use of this class too...... 0.o //if(!$this->_filename) return false; // I need a root node name. if (!$this->_rootname) return false; // w00t, new support for a schema declaration! if($this->_schema){ $implementation = new DOMImplementation(); $dtd = $implementation->createDocumentType($this->_rootname, 'SYSTEM', $this->_schema); $this->_DOM = $implementation->createDocument('', '', $dtd); } else{ $this->_DOM = new DOMDocument(); } $this->_DOM->encoding = 'UTF-8'; // we want a nice output $this->_DOM->formatOutput = true; if ($this->_file) { $contents = $this->_file->getContentsObject(); if (is_a($contents, '\Core\Filestore\Contents\ContentGZ')) { $dat = $contents->uncompress(); } else { $dat = $contents->getContents(); } // If an empty string is submitted... if(!$dat){ return false; } $this->_DOM->loadXML($dat); } elseif ($this->_filename) { if (!$this->_DOM->load($this->_filename)) return false; } else { return false; } return true; }
/** * Decrypt the encrypted/signed file and return a valid File object * * @return mixed */ public function decrypt($dest = false) { if ($dest) { if (is_a($dest, 'File') || $dest instanceof Filestore\File) { // Don't need to do anything! The object either is a File // Or is an implmentation of the File interface. } else { // Well it should be damnit!.... $file = $dest; // Is the destination a directory or filename? // If it's a directory just tack on this current file's basename. if (substr($file, -1) == '/') { $file .= $this->_file->getBaseFilename(); } // Drop the .asc extension if it's there. if ($this->_file->getExtension() == 'asc') $file = substr($file, 0, -4); $dest = Filestore\Factory::File($file); } // And load up the contents! $dest->putContents($this->decrypt()); return $dest; } else { // Extract and return the file contents ob_start(); passthru('gpg --homedir "' . GPG_HOMEDIR . '" --no-permission-warning --decrypt "' . $this->_file->getLocalFilename() . '"'); $content = ob_get_contents(); ob_end_clean(); return $content; } }
/** * Make a copy of a source Filestore\File into this Filestore\File. * * (Generally only useful internally) * * @param Filestore\File $src Source file backend * @param bool $overwrite true to overwrite existing file * * @throws \Exception * @return bool True or False if succeeded. */ public function copyFrom(Filestore\File $src, $overwrite = false) { // Don't overwrite existing files unless told otherwise... if (!$overwrite) { $c = 0; $ext = $this->getExtension(); $base = $this->getBaseFilename(true); $dir = dirname($this->_filename); $prefix = $dir . '/' . $base; $suffix = (($ext == '') ? '' : '.' . $ext); $thathash = $src->getHash(); $f = $prefix . $suffix; while(file_exists($f) && md5_file($f) != $thathash){ $f = $prefix . ' (' . ++$c . ')' . $suffix; } $this->_filename = $f; } // And do the actual copy! // To save memory, try to use as low-level functions as possible. $localfilename = $src->getLocalFilename(); $localhash = $src->getHash(); $localmodified = $src->getMTime(); $localsize = $src->getFilesize(); // Resolve it from its default. // This is provided from a config define, (probably). $mode = (defined('DEFAULT_FILE_PERMS') ? DEFAULT_FILE_PERMS : 0644); // Make sure the directory exists first! $this->_mkdir(dirname($this->_filename), null, true); // FTP requires a filename, not data... // WELL how bout that! I happen to have a local filename ;) if (!ftp_put($this->_ftp->getConn(), $this->_filename, $localfilename, FTP_BINARY)) { throw new \Exception(error_get_last()['message']); } if (!ftp_chmod($this->_ftp->getConn(), $mode, $this->_filename)){ throw new \Exception(error_get_last()['message']); } // Don't forget to save the metadata for this file! $filename = $this->getFilename(); $this->_ftp->setFileHash($filename, $localhash); $this->_ftp->setFileModified($filename, $localmodified); $this->_ftp->setFileSize($filename, $localsize); // woot... return true; }
/** * Verify the signature on a given file * * If only one argument is provided, it is expected that file contains both the file and signature as an attached sig. * * If two arguments are provided, the detached signature is the first argument and the content to verify is the second. * * @throws \Exception * * @param string|\Core\Filestore\File $file Filename or File object of the file to verify * @param string|\Core\Filestore\File $verifyFile Filename or File object of any detached signature * * @return Signature */ public function verifyFileSignature($file, $verifyFile = null){ if($file instanceof \Core\Filestore\File){ $filename = $file->getFilename(); } else{ $filename = $file; } if(!file_exists($filename)){ throw new \Exception('Requested file does not exist, unable to verify signature!'); } if($verifyFile === null){ // Standard attached sig $result = $this->_exec('--with-fingerprint --batch --no-tty --verify ' . escapeshellarg($filename)); } else{ // Detached signature if($verifyFile instanceof \Core\Filestore\File){ $sourceFilename = $verifyFile->getFilename(); } else{ $sourceFilename = $verifyFile; } $result = $this->_exec('--with-fingerprint --batch --no-tty --verify ' . escapeshellarg($filename) . ' ' . escapeshellarg($sourceFilename)); } // If the result failed, then nothing else to do here. if($result['return'] !== 0){ throw new \Exception($result['error']); } // Else, the calling script may want to know the results of the verification, eg: the key and date. // The metadata here is send to STDERR. _Shrugs_ $sig = new Signature(); $sig->_parseOutputText($result['error']); return $sig; }
/** * Add a file as an attachment! * * @param \Core\Filestore\File $file * * @throws phpmailerException */ public function addAttachment(\Core\Filestore\File $file){ $this->getMailer()->AddAttachment( $file->getFilename(), // Full Path $file->getBasename(), // Base Filename (to be exposed in client) 'base64', // Yup, just do this $file->getMimetype() // Mimetype, try to use correct hinting for client ); }
/** * Resize this image and save the output as another File object. * * This is used on conjunction with getPreview* and getQuickPreview. * QuickPreview creates the destination file in the correct directory * and getPreview* methods request the actual resizing. * * @param Filestore\File $file The destination file * @param int $width Width of the final image (in px) * @param int $height Height of the final image (in px) * @param string $mode Mode (part of the geometry) */ private function _resizeTo(Filestore\File $file, $width, $height, $mode){ if(!$this->isImage()){ // :/ return; } \Core\Utilities\Logger\write_debug('Resizing image ' . $this->getFilename('') . ' to ' . $width . 'x' . $height . $mode); $m = $this->getMimetype(); // Make sure the directory of the destination file exists! // By touching the file, Core will create all parent directories as necessary. $file->putContents(''); if($m == 'image/gif' && exec('which convert 2>/dev/null')){ // The GIF resizer handles EVERYTHING :) // Granted of course, that imagemagick's convert is available on the server. $resize = escapeshellarg($mode . $width . 'x' . $height); exec('convert ' . escapeshellarg($this->getFilename()) . ' -resize ' . $resize . ' ' . escapeshellarg($file->getFilename())); \Core\Utilities\Logger\write_debug('Resizing complete (via convert)'); return; } // Traditional resizing logic. switch ($m) { case 'image/jpeg': $thumbType = 'JPEG'; $thumbWidth = $width; $thumbHeight = $height; if($width <= 200 && $height <= 200 && function_exists('exif_thumbnail')){ // Try to write out from the thumbnail img instead of the full size. // This is done to increase server performance. // eg: resizing a 5MB JPEG can take upwards of 50-100ms, // whereas the embedded thumbnail will take only 2-10ms. // Not to mention professional JPEG management tools such as PS and Gimp // produce marginally higher-quality thumbnails than GD will. // (The resulting filesize is negligible.) // Of course if the requested image is larger than a thumbnail size, (200x200 in this case), // using the thumbnail is counter-productive! $img = exif_thumbnail($this->getFilename(), $thumbWidth, $thumbHeight, $thumbType); if($img){ \Core\Utilities\Logger\write_debug('JPEG has thumbnail data of ' . $thumbWidth . 'x' . $thumbHeight . '!'); $file->putContents($img); $img = imagecreatefromjpeg($file->getFilename()); } else{ $img = imagecreatefromjpeg($this->getFilename()); } } else{ $img = imagecreatefromjpeg($this->getFilename()); } break; case 'image/png': $img = imagecreatefrompng($this->getFilename()); break; case 'image/gif': $img = imagecreatefromgif($this->getFilename()); break; default: // Hmmm... \Core\Utilities\Logger\write_debug('Resizing complete (failed, not sure what it was)'); return; } if ($img) { $sW = imagesx($img); $sH = imagesy($img); $nW = $sW; $nH = $sH; switch($mode){ // Standard mode, images are scaled down (only) while preserving aspect ratio case '': case '<': if ($nW > $width) { $nH = $width * $sH / $sW; $nW = $width; } if ($nH > $height) { $nW = $height * $sW / $sH; $nH = $height; } break; // Only resize up case '>': if ($nW < $width) { $nH = $width * $sH / $sW; $nW = $width; } if ($nH < $height) { $nW = $height * $sW / $sH; $nH = $height; } break; // Resize to new size, regardless about aspect ratio case '!': $nW = $width; $nH = $height; break; // Resize image based on smallest dimension case '^': $ratioheight = $sW / $height; $ratiowidth = $sH / $width; if($ratioheight > 1 && $ratiowidth > 1){ // The image is larger than any of the dimensions, I can use the reduction logic. if(($width * $sH / $sW) > ($height * $sW / $sH)){ $nH = $width * $sH / $sW; $nW = $width; } else{ $nH = $height; $nW = $height * $sW / $sH; } } elseif($ratiowidth > $ratioheight){ // The image needs to be increased in size, this logic is slightly different. $nW = $width; $nH = round($width * $sH / $sW); } else{ $nH = $height; $nW = round($height * $sW / $sH); } } // If it's a JPEG, try to find the original thumbnail. /*if(false && $m == 'image/jpeg'){ $type = 'JPEG'; $img = exif_thumbnail($this->getFilename(), $nW, $nH, $type); $file->putContents($img); return; }*/ $img2 = imagecreatetruecolor($nW, $nH); imagealphablending($img2, false); imagesavealpha($img2, true); imagealphablending($img, true); // Assign a transparency color. //$trans = imagecolorallocatealpha($img2, 0, 0, 0, 0); //imagefill($img2, 0, 0, $trans); imagecopyresampled($img2, $img, 0, 0, 0, 0, $nW, $nH, $sW, $sH); imagedestroy($img); switch ($m) { case 'image/jpeg': imagejpeg($img2, $file->getFilename(), 60); \Core\Utilities\Logger\write_debug('Resizing complete (via imagejpeg)'); break; case 'image/png': imagepng($img2, $file->getFilename(), 9); \Core\Utilities\Logger\write_debug('Resizing complete (via imagepng)'); break; case 'image/gif': imagegif($img2, $file->getFilename()); \Core\Utilities\Logger\write_debug('Resizing complete (via imagegif)'); break; default: // Hmmm... \Core\Utilities\Logger\write_debug('Resizing complete (failed, not sure what it was)'); return; } } }
/** * Get an array of the various resize components from a given dimension set. * These include: width, height, mode, key. * * @param string|int $dimensions * @param File $file * * @return array */ function get_resized_key_components($dimensions, $file){ // The legacy support for simply a number. if (is_numeric($dimensions)) { $width = $dimensions; $height = $dimensions; $mode = ''; } elseif ($dimensions === null) { $width = 300; $height = 300; $mode = ''; } elseif($dimensions === false){ $width = false; $height = false; $mode = ''; } else { // Allow some special modifiers. if(strpos($dimensions, '^') !== false){ // Fit the smallest dimension instead of the largest, (useful for overflow tricks) $mode = '^'; $dimensions = str_replace('^', '', $dimensions); } elseif(strpos($dimensions, '!') !== false){ // Absolutely resize, regardless of aspect ratio $mode = '!'; $dimensions = str_replace('!', '', $dimensions); } elseif(strpos($dimensions, '>') !== false){ // Only increase images. $mode = '>'; $dimensions = str_replace('>', '', $dimensions); } elseif(strpos($dimensions, '<') !== false){ // Only decrease images. $mode = '<'; $dimensions = str_replace('<', '', $dimensions); } else{ // Default mode $mode = ''; } // New method. Split on the "x" and that should give me the width/height. $vals = explode('x', strtolower($dimensions)); $width = (int)$vals[0]; $height = (int)$vals[1]; } $ext = $file->getExtension(); // Ensure that an extension is used if none present, (may happen with temporary files). if(!$ext){ $ext = mimetype_to_extension($file->getMimetype()); } // The basename is for SEO purposes, that way even resized images still contain the filename. // The hash is just to ensure that no two files conflict, ie: /public/a/file1.png and /public/b/file1.png // might conflict without this hash. // Finally, the width and height dimensions are there just because as well; it gives more of a human // touch to the file. :p // Also, keep the original file extension, this way PNGs remain PNGs, GIFs remain GIFs, JPEGs remain JPEGs. // This is critical particularly when it comes to animated GIFs. $key = str_replace(' ', '-', $file->getBasename(true)) . '-' . $file->getHash() . '-' . $width . 'x' . $height . $mode . '.' . $ext; // The directory can be used with the new File backend to create this file in a correctly nested subdirectory. $dir = dirname($file->getFilename(false)) . '/'; if(substr($dir, 0, 7) == 'public/'){ // Replace the necessary prefix with a more useful one. // Anything within public/ needs to be remapped to public/tmp $dir = 'public/tmp/' . substr($dir, 7); } else{ // Everything else gets prepended to public/tmp/ // so if the original file is in themes/blah/imgs/blah.jpg, // it will be copied to public/tmp/blah.jpg $dir = 'public/tmp/'; } return array( 'width' => $width, 'height' => $height, 'mode' => $mode, 'key' => $key, 'ext' => $ext, 'dir' => $dir, ); }
/** * Save this component metadata back to its XML file. * Useful in packager scripts. */ public function save($minified = false) { // Set the schema version to the newest API version. $this->_xmlloader->setSchema('http://corepl.us/api/2_4/component.dtd'); // Ensure there's a required namespace on the root node. $this->_xmlloader->getRootDOM()->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance"); // Hack // If there is an empty smartydir set, don't let that get saved. if(!$this->getSmartyPluginDirectory()){ $this->_xmlloader->removeElements('//smartyplugins'); } /* /////////////// Handle the hard-set pages, ie: admin ones \\\\\\\\\\\\\ if(!isset($viewclasses)) $viewclasses = array(); foreach($viewclasses as $c){ // Should end in Controller. if(strlen($c) - strpos($c, 'Controller') == 10) $c = substr($c, 0, -10); $data = Dataset::Init()->table('page')->select('*')->where("baseurl = /$c", 'admin=1', 'fuzzy=0')->execute(); //$rs = DB::Execute("SELECT * FROM " . DB_PREFIX . "page WHERE ( `baseurl` = '/$c' OR `baseurl` LIKE '/$c/%' ) AND `fuzzy` = '0' AND `admin` = '1'"); foreach($data as $row){ $node = $this->_xmlloader->getElement('/pages/page[@baseurl="' . $row['baseurl'] . '"]'); $node->setAttribute('admin', $row['admin']); $node->setAttribute('widget', $row['widget']); $node->setAttribute('access', $row['access']); $node->setAttribute('title', $row['title']); } $data = Dataset::Init()->table('page')->select('*')->where("baseurl LIKE /$c/%", 'admin=1', 'fuzzy=0')->execute(); //$rs = DB::Execute("SELECT * FROM " . DB_PREFIX . "page WHERE ( `baseurl` = '/$c' OR `baseurl` LIKE '/$c/%' ) AND `fuzzy` = '0' AND `admin` = '1'"); foreach($data as $row){ $node = $this->_xmlloader->getElement('/pages/page[@baseurl="' . $row['baseurl'] . '"]'); $node->setAttribute('admin', $row['admin']); $node->setAttribute('widget', $row['widget']); $node->setAttribute('access', $row['access']); $node->setAttribute('title', $row['title']); } } */ /* /////////////////////// Handle the config options \\\\\\\\\\\\\\\\\\\\\ $data = Dataset::Init()->table('config')->select('*')->where('key LIKE /' . $this->getName() . '/%')->execute(); //$rs = DB::Execute("SELECT * FROM " . DB_PREFIX . "config WHERE `key` LIKE '/" . $this->getName() . "/%'"); foreach($data as $row){ $node = $this->_xmlloader->getElement('/configs/config[@key="' . $row['key'] . '"]'); $node->setAttribute('type', $row['type']); $node->setAttribute('default', $row['default_value']); $node->setAttribute('description', $row['description']); if($row['options']) $node->setAttribute('options', $row['options']); else $node->removeAttribute('options'); } */ // This needs to be the final step... write the XML doc back to the file. $XMLFilename = $this->_file->getFilename(); //echo $this->asPrettyXML(); // DEBUG // if ($minified) { file_put_contents($XMLFilename, $this->_xmlloader->asMinifiedXML()); } else { file_put_contents($XMLFilename, $this->_xmlloader->asPrettyXML()); } }