/** * Saves $data to the cache * * @param string $data * @return bool * */ public function save($data) { $filename = $this->buildFilename(); if (!is_file($filename)) { $dirname = dirname($filename); if (is_dir($dirname)) { $files = glob("{$dirname}/*.*"); $numFiles = count($files); if ($numFiles >= self::maxCacheFiles) { // if the cache file doesn't already exist, and there are too many files here // then abort the cache save for security (we don't want to fill up the disk) // also go through and remove any expired files while we are here, to avoid // this limit interrupting more cache saves. foreach ($files as $file) { if (self::isCacheFile($file) && $this->isCacheFileExpired($file)) { $this->removeFilename($file); } } return false; } } else { wireMkdir("{$dirname}/", true); } } $result = file_put_contents($filename, $data); wireChmod($filename); return $result; }
/** * Start a new backup file, adding our info header to the top * * @param string $file * @param array $options * @return bool * */ protected function backupStartFile($file, array $options) { $fp = fopen($file, 'w+'); if (!$fp) { $this->error("Unable to write header to file: {$file}"); return false; } $info = array('time' => date('Y-m-d H:i:s'), 'user' => $options['user'], 'dbName' => $this->databaseConfig['dbName'], 'description' => $options['description'], 'tables' => $options['tables'], 'excludeTables' => $options['excludeTables'], 'excludeCreateTables' => $options['excludeCreateTables'], 'excludeExportTables' => $options['excludeExportTables']); $json = json_encode($info); $json = str_replace(array("\r", "\n"), " ", $json); fwrite($fp, self::fileHeader . " {$json}\n"); fclose($fp); if (function_exists('wireChmod')) { wireChmod($file); } return true; }
/** * Hookable version of size() with implementation * * See comments for size() method above. * * @param int $width * @param int $height * @param array|string|int $options * @return Pageimage * */ protected function ___size($width, $height, $options) { // I was getting unnecessarily resized images without this code below, // but this may be better solved in ImageSizer? /* $w = $this->width(); $h = $this->height(); if($w == $width && $h == $height) return $this; if(!$height && $w == $width) return $this; if(!$width && $h == $height) return $this; */ if ($this->ext == 'svg') { return $this; } if (!is_array($options)) { if (is_string($options)) { // optionally allow a string to be specified with crop direction, for shorter syntax if (strpos($options, ',') !== false) { $options = explode(',', $options); } // 30,40 $options = array('cropping' => $options); } else { if (is_int($options)) { // optionally allow an integer to be specified with quality, for shorter syntax $options = array('quality' => $options); } else { if (is_bool($options)) { // optionally allow a boolean to be specified with upscaling toggle on/off $options = array('upscaling' => $options); } else { // unknown options type $options = array(); } } } } $defaultOptions = array('upscaling' => true, 'cropping' => true, 'quality' => 90, 'hidpiQuality' => 40, 'suffix' => array(), 'forceNew' => false, 'hidpi' => false, 'cleanFilename' => false, 'rotate' => 0, 'flip' => ''); $this->error = ''; $configOptions = wire('config')->imageSizerOptions; if (!is_array($configOptions)) { $configOptions = array(); } $options = array_merge($defaultOptions, $configOptions, $options); $width = (int) $width; $height = (int) $height; if (is_string($options['cropping']) && strpos($options['cropping'], 'x') === 0 && preg_match('/^x(\\d+)[yx](\\d+)/', $options['cropping'], $matches)) { $options['cropping'] = true; $options['cropExtra'] = array((int) $matches[1], (int) $matches[2], $width, $height); $crop = ''; } else { $crop = ImageSizer::croppingValueStr($options['cropping']); } if (!is_array($options['suffix'])) { // convert to array $options['suffix'] = empty($options['suffix']) ? array() : explode(' ', $options['suffix']); } if ($options['rotate'] && !in_array(abs((int) $options['rotate']), array(90, 180, 270))) { $options['rotate'] = 0; } if ($options['rotate']) { $options['suffix'][] = ($options['rotate'] > 0 ? "rot" : "tor") . abs($options['rotate']); } if ($options['flip']) { $options['suffix'][] = strtolower(substr($options['flip'], 0, 1)) == 'v' ? 'flipv' : 'fliph'; } $suffixStr = ''; if (!empty($options['suffix'])) { $suffix = $options['suffix']; sort($suffix); foreach ($suffix as $key => $s) { $s = strtolower($this->wire('sanitizer')->fieldName($s)); if (empty($s)) { unset($suffix[$key]); } else { $suffix[$key] = $s; } } if (count($suffix)) { $suffixStr = '-' . implode('-', $suffix); } } if ($options['hidpi']) { $suffixStr .= '-hidpi'; if ($options['hidpiQuality']) { $options['quality'] = $options['hidpiQuality']; } } //$basename = $this->pagefiles->cleanBasename($this->basename(), false, false, false); // cleanBasename($basename, $originalize = false, $allowDots = true, $translate = false) $basename = basename($this->basename(), "." . $this->ext()); // i.e. myfile if ($options['cleanFilename'] && strpos($basename, '.') !== false) { $basename = substr($basename, 0, strpos($basename, '.')); } $basename .= '.' . $width . 'x' . $height . $crop . $suffixStr . "." . $this->ext(); // i.e. myfile.100x100.jpg or myfile.100x100nw-suffix1-suffix2.jpg $filenameFinal = $this->pagefiles->path() . $basename; $filenameUnvalidated = ''; $exists = file_exists($filenameFinal); if (!$exists || $options['forceNew']) { $filenameUnvalidated = $this->pagefiles->page->filesManager()->getTempPath() . $basename; if ($exists && $options['forceNew']) { @unlink($filenameFinal); } if (file_exists($filenameUnvalidated)) { @unlink($filenameUnvalidated); } if (@copy($this->filename(), $filenameUnvalidated)) { try { $sizer = new ImageSizer($filenameUnvalidated); $sizer->setOptions($options); if ($sizer->resize($width, $height) && @rename($filenameUnvalidated, $filenameFinal)) { wireChmod($filenameFinal); } else { $this->error = "ImageSizer::resize({$width}, {$height}) failed for {$filenameUnvalidated}"; } } catch (Exception $e) { $this->trackException($e, false); $this->error = $e->getMessage(); } } else { $this->error("Unable to copy {$this->filename} => {$filenameUnvalidated}"); } } $pageimage = clone $this; // if desired, user can check for property of $pageimage->error to see if an error occurred. // if an error occurred, that error property will be populated with details if ($this->error) { // error condition: unlink copied file if (is_file($filenameFinal)) { @unlink($filenameFinal); } if ($filenameUnvalidated && is_file($filenameUnvalidated)) { @unlink($filenameUnvalidated); } // write an invalid image so it's clear something failed // todo: maybe return a 1-pixel blank image instead? $data = "This is intentionally invalid image data.\n{$this->error}"; if (file_put_contents($filenameFinal, $data) !== false) { wireChmod($filenameFinal); } // we also tell PW about it for logging and/or admin purposes $this->error($this->error); } $pageimage->setFilename($filenameFinal); $pageimage->setOriginal($this); return $pageimage; }
/** * Change the mode of a file or directory, consistent with PW's chmodFile/chmodDir settings * * @param string $path May be a directory or a filename * @param bool $recursive If set to true, all files and directories in $path will be recursively set as well. * @param string If you want to set the mode to something other than PW's chmodFile/chmodDir settings, you may override it by specifying it here. Ignored otherwise. Format should be a string, like "0755". * @return bool Returns true if all changes were successful, or false if at least one chmod failed. * @throws WireException when it receives incorrect chmod format * */ function wireChmod($path, $recursive = false, $chmod = null) { if (is_null($chmod)) { // default: pull values from PW config $chmodFile = wire('config')->chmodFile; $chmodDir = wire('config')->chmodDir; } else { // optional, manually specified string if (!is_string($chmod)) { throw new WireException("chmod must be specified as a string like '0755'"); } $chmodFile = $chmod; $chmodDir = $chmod; } $numFails = 0; if (is_dir($path)) { // $path is a directory if ($chmodDir) { if (!@chmod($path, octdec($chmodDir))) { $numFails++; } } // change mode of files in directory, if recursive if ($recursive) { foreach (new DirectoryIterator($path) as $file) { if ($file->isDot()) { continue; } $mod = $file->isDir() ? $chmodDir : $chmodFile; if ($mod) { if (!@chmod($file->getPathname(), octdec($mod))) { $numFails++; } } if ($file->isDir()) { if (!wireChmod($file->getPathname(), true, $chmod)) { $numFails++; } } } } } else { // $path is a file $mod = $chmodFile; if ($mod) { if (!@chmod($path, octdec($mod))) { $numFails++; } } } return $numFails == 0; }
/** * Save submitted admin account form * */ protected function adminAccountSave($wire) { $input = $wire->input; $sanitizer = $wire->sanitizer; if (!$input->post->username || !$input->post->userpass) { $this->err("Missing account information"); } if ($input->post->userpass !== $input->post->userpass_confirm) { $this->err("Passwords do not match"); } if (strlen($input->post->userpass) < 6) { $this->err("Password must be at least 6 characters long"); } $username = $sanitizer->pageName($input->post->username); if ($username != $input->post->username) { $this->err("Username must be only a-z 0-9"); } if (strlen($username) < 2) { $this->err("Username must be at least 2 characters long"); } $adminName = $sanitizer->pageName($input->post->admin_name); if ($adminName != $input->post->admin_name) { $this->err("Admin login URL must be only a-z 0-9"); } if ($adminName == 'wire' || $adminName == 'site') { $this->err("Admin name may not be 'wire' or 'site'"); } if (strlen($adminName) < 2) { $this->err("Admin login URL must be at least 2 characters long"); } $email = strtolower($sanitizer->email($input->post->useremail)); if ($email != strtolower($input->post->useremail)) { $this->err("Email address did not validate"); } if ($this->numErrors) { return $this->adminAccount($wire); } $superuserRole = $wire->roles->get("name=superuser"); $user = $wire->users->get($wire->config->superUserPageID); if (!$user->id) { $user = new User(); $user->id = $wire->config->superUserPageID; } $user->name = $username; $user->pass = $input->post->userpass; $user->email = $email; if (!$user->roles->has("superuser")) { $user->roles->add($superuserRole); } $admin = $wire->pages->get($wire->config->adminRootPageID); $admin->of(false); $admin->name = $adminName; try { if (self::TEST_MODE) { $this->ok("TEST MODE: skipped user creation"); } else { $wire->users->save($user); $wire->pages->save($admin); } } catch (Exception $e) { $this->err($e->getMessage()); return $this->adminAccount($wire); } $adminName = htmlentities($adminName, ENT_QUOTES, "UTF-8"); $this->h("Admin Account Saved"); $this->ok("User account saved: <b>{$user->name}</b>"); $colors = $wire->sanitizer->pageName($input->post->colors); if (!in_array($colors, $this->colors)) { $colors = reset($this->colors); } $theme = $wire->modules->getInstall('AdminThemeDefault'); $configData = $wire->modules->getModuleConfigData('AdminThemeDefault'); $configData['colors'] = $colors; $wire->modules->saveModuleConfigData('AdminThemeDefault', $configData); $this->ok("Saved admin color set <b>{$colors}</b> - you will see this when you login."); $this->h("Complete & Secure Your Installation"); $this->getRemoveableItems($wire, false, true); $this->ok("Note that future runtime errors are logged to <b>/site/assets/logs/errors.txt</b> (not web accessible)."); $this->ok("For more configuration options see <b>/wire/config.php</b>."); $this->warn("Please make your <b>/site/config.php</b> file non-writable, and readable only to you and Apache."); $this->p("<a target='_blank' href='https://processwire.com/docs/security/file-permissions/#securing-your-site-config.php-file'>How to secure your /site/config.php file <i class='fa fa-angle-right'></i></a>"); if (is_writable("./site/modules/")) { wireChmod("./site/modules/", true); } $this->h("Use The Site!"); $this->ok("Your admin URL is <a href='./{$adminName}/'>/{$adminName}/</a>"); $this->p("If you'd like, you may change this later by editing the admin page and changing the name.", "detail"); $this->btn("Login to Admin", 1, 'sign-in', false, true, "./{$adminName}/"); $this->btn("View Site ", 1, 'angle-right', true, false, "./"); // set a define that indicates installation is completed so that this script no longer runs if (!self::TEST_MODE) { file_put_contents("./site/assets/installed.php", "<?php // The existence of this file prevents the installer from running. Don't delete it unless you want to re-run the install or you have deleted ./install.php."); } }
/** * Prune log file to specified number of bytes (from the end) * * @param int $bytes * @return int|bool positive integer on success, 0 if no prune necessary, or boolean false on failure. * */ public function pruneBytes($bytes) { $filename = $this->logFilename; if (!$filename || filesize($filename) <= $bytes) { return 0; } $fpr = fopen($filename, "r"); $fpw = fopen("{$filename}.new", "w"); if (!$fpr || !$fpw) { return false; } fseek($fpr, $bytes * -1, SEEK_END); fgets($fpr, self::maxLineLength); // first line likely just a partial line, so skip it $cnt = 0; while (!feof($fpr)) { $line = fgets($fpr, self::maxLineLength); fwrite($fpw, $line); $cnt++; } fclose($fpw); fclose($fpr); if ($cnt) { unlink($filename); rename("{$filename}.new", $filename); wireChmod($filename); } else { @unlink("{$filename}.new"); } return $cnt; }
/** * do the resizes like Pageimage does it * * @param object $caller * @param pageimage $img * @param string $targetFilename * @param array $options1 * @param array $options2 * @return pageimage */ public static function renderImage(&$caller, &$img, $sourceFilename, $targetFilename, $width, $height, $options) { $filenameFinal = $targetFilename; $filenameUnvalidated = $img->pagefiles->page->filesManager()->getTempPath() . basename($targetFilename); if (file_exists($filenameFinal)) { @unlink($filenameFinal); } if (file_exists($filenameUnvalidated)) { @unlink($filenameUnvalidated); } if (@copy($sourceFilename, $filenameUnvalidated)) { try { $sizer = new ImageSizer($filenameUnvalidated); $sizer->setOptions($options); if ($sizer->resize($width, $height) && @rename($filenameUnvalidated, $filenameFinal)) { // if script runs into a timeout while in ImageSizer, we never will reach this line and we will stay with $filenameUnvalidated if ($caller->config->chmodFile) { chmod($filenameFinal, octdec($caller->config->chmodFile)); } } else { $caller->error = "ImageSizer::resize({$width}, {$height}) failed for {$filenameUnvalidated}"; } } catch (Exception $e) { $caller->error = $e->getMessage(); } } else { $caller->error("Unable to copy {$sourceFilename} => {$filenameUnvalidated}"); } $pageimage = clone $img; // if desired, user can check for property of $pageimage->error to see if an error occurred. // if an error occurred, that error property will be populated with details if ($caller->error) { // error condition: unlink copied file if (is_file($filenameFinal)) { @unlink($filenameFinal); } if (is_file($filenameUnvalidated)) { @unlink($filenameUnvalidated); } // write an invalid image so it's clear something failed $data = "This is intentionally invalid image data.\n{$caller->error}"; if (file_put_contents($filenameFinal, $data) !== false) { wireChmod($filenameFinal); } // we also tell PW about it for logging and/or admin purposes $caller->error($caller->error); } $pageimage->setFilename($filenameFinal); $pageimage->setOriginal($img); return $pageimage; }
/** * Unzip the module file to tempDir and then copy to destination directory * * @param string $file File to unzip * @param string $destinationDir Directory to copy completed files into. Optionally omit to determine automatically. * @return bool|string Returns destinationDir on success, false on failure * @throws WireException * */ public function unzipModule($file, $destinationDir = '') { $success = false; $tempDir = $this->getTempDir(); $mkdirDestination = false; try { $files = wireUnzipFile($file, $tempDir); if (is_file($file)) { unlink($file); } foreach ($files as $f) { $this->message("Extracted: {$f}", Notice::debug); } } catch (Exception $e) { $this->error($e->getMessage()); if (is_file($file)) { unlink($file); } return false; } if (!$destinationDir) { $destinationDir = $this->determineDestinationDir($files); if (!$destinationDir) { throw new WireException($this->_('Unable to find any module files')); } } $this->message("Destination directory: {$destinationDir}", Notice::debug); $files0 = trim($files[0], '/'); $extractedDir = is_dir("{$tempDir}/{$files0}") && substr($files0, 0, 1) != '.' ? "{$files0}/" : ""; // now create module directory and copy files over if (is_dir($destinationDir)) { // destination dir already there, perhaps an older version of same module? // create a backup of it $hasBackup = $this->backupDir($destinationDir); if ($hasBackup) { wireMkdir($destinationDir, true); } } else { if (wireMkdir($destinationDir, true)) { $mkdirDestination = true; } $hasBackup = false; } // label to identify destinationDir in messages and errors $dirLabel = str_replace($this->config->paths->root, '/', $destinationDir); if (is_dir($destinationDir)) { $from = $tempDir . $extractedDir; if (wireCopy($from, $destinationDir)) { $this->message($this->_('Successfully copied files to new directory:') . ' ' . $dirLabel); wireChmod($destinationDir, true); $success = true; } else { $this->error($this->_('Unable to copy files to new directory:') . ' ' . $dirLabel); if ($hasBackup) { $this->restoreDir($destinationDir); } } } else { $this->error($this->_('Could not create directory:') . ' ' . $dirLabel); } if (!$success) { $this->error($this->_('Unable to copy module files:') . ' ' . $dirLabel); if ($mkdirDestination && !wireRmdir($destinationDir, true)) { $this->error($this->_('Could not delete failed module dir:') . ' ' . $destinationDir, Notice::log); } } return $success ? $destinationDir : false; }
/** * Hookable version of size() with implementation * * See comments for size() method above. * */ protected function ___size($width, $height, $options) { // I was getting unnecessarily resized images without this code below, // but this may be better solved in ImageSizer? /* $w = $this->width(); $h = $this->height(); if($w == $width && $h == $height) return $this; if(!$height && $w == $width) return $this; if(!$width && $h == $height) return $this; */ if ($this->ext == 'svg') { return $this; } if (!is_array($options)) { if (is_string($options)) { // optionally allow a string to be specified with crop direction, for shorter syntax if (strpos($options, ',') !== false) { $options = explode(',', $options); } // 30,40 $options = array('cropping' => $options); } else { if (is_int($options)) { // optionally allow an integer to be specified with quality, for shorter syntax $options = array('quality' => $options); } else { if (is_bool($options)) { // optionally allow a boolean to be specified with upscaling toggle on/off $options = array('upscaling' => $options); } } } } $defaultOptions = array('upscaling' => true, 'cropping' => true, 'quality' => 90, 'suffix' => array(), 'forceNew' => false); $this->error = ''; $configOptions = wire('config')->imageSizerOptions; if (!is_array($configOptions)) { $configOptions = array(); } $options = array_merge($defaultOptions, $configOptions, $options); $width = (int) $width; $height = (int) $height; $crop = ImageSizer::croppingValueStr($options['cropping']); $suffixStr = ''; if (!empty($options['suffix'])) { $suffix = is_array($options['suffix']) ? $options['suffix'] : array($options['suffix']); sort($suffix); foreach ($suffix as $key => $s) { $s = strtolower($this->wire('sanitizer')->fieldName($s)); if (empty($s)) { unset($suffix[$key]); } else { $suffix[$key] = $s; } } if (count($suffix)) { $suffixStr = '-' . implode('-', $suffix); } } $basename = basename($this->basename(), "." . $this->ext()); // i.e. myfile $basename .= '.' . $width . 'x' . $height . $crop . $suffixStr . "." . $this->ext(); // i.e. myfile.100x100.jpg or myfile.100x100nw-suffix1-suffix2.jpg $filename = $this->pagefiles->path() . $basename; $exists = file_exists($filename); if (!$exists || $options['forceNew']) { if ($exists && $options['forceNew']) { unlink($filename); } if (@copy($this->filename(), $filename)) { try { $sizer = new ImageSizer($filename); $sizer->setOptions($options); if ($sizer->resize($width, $height)) { if ($this->config->chmodFile) { chmod($filename, octdec($this->config->chmodFile)); } } else { $this->error = "ImageSizer::resize({$width}, {$height}) failed for {$filename}"; } } catch (Exception $e) { $this->error = $e->getMessage(); } } else { $this->error("Unable to copy {$this->filename} => {$filename}"); } } $pageimage = clone $this; // if desired, user can check for property of $pageimage->error to see if an error occurred. // if an error occurred, that error property will be populated with details if ($this->error) { // error condition: unlink copied file if (is_file($filename)) { unlink($filename); } // write an invalid image so it's clear something failed // todo: maybe return a 1-pixel blank image instead? $data = "This is intentionally invalid image data.\n{$this->error}"; if (file_put_contents($filename, $data) !== false) { wireChmod($filename); } // we also tell PW about it for logging and/or admin purposes $this->error($this->error); } $pageimage->setFilename($filename); $pageimage->setOriginal($this); return $pageimage; }