I've done some looking around, but I'm still confused a bit.
I tried Crockford's JSMin, but Win XP can't unzip the executable file for some reason.
What I really want though is a simple and easy-to-use JS minifier that uses PHP to minify JS code--and return the result.
The reason why is because:
I have 2 files (for example) that I'm working between: scripts.js and scripts_template.js
scripts_template is normal code that I write out--then I have to minify it and paste the minified script into scripts.js--the one that I actually USE on my website.
I want to eradicate the middle man by simply doing something like this on my page:
<script type="text/javascript" src="scripts.php"></script>
And then for the contents of scripts.php:
<?php include("include.inc"); header("Content-type:text/javascript"); echo(minify_js(file_get_contents("scripts_template.js")));
This way, whenever I update my JS, I don't have to constantly go to a website to minify it and re-paste it into scripts.js--everything is automatically updated.
Yes, I have also tried Crockford's PHP Minifier and I've taken a look at PHP Speedy, but I don't understand PHP classes just yet...Is there anything out there that a monkey could understand, maybe something with RegExp?
How about we make this even simpler?
I just want to remove tab spaces--I still want my code to be readable.
It's not like the script makes my site lag epically, it's just anything is better than nothing.
Tab removal, anyone? And if possible, how about removing completely BLANK lines?
I have used a PHP implementation of JSMin by Douglas Crockford for quite some time. It can be a little risky when concatenating files, as there may be a missing semicolon on the end of a closure.
It'd be a wise idea to cache the minified output and echo what is cached so long as it's newer than the source file.
require 'jsmin.php';
if(filemtime('scripts_template.js') < filemtime('scripts_template.min.js')) {
read_file('scripts_template.min.js');
} else {
$output = JSMin::minify(file_get_contents('scripts_template.js'));
file_put_contents('scripts_template.min.js', $output);
echo $output;
}
You could also try JShrink. I haven't ever used it before, since I haven't had difficulty with JSMin before, but this code below should do the trick. I hadn't realized this, but JShrink requires PHP 5.3 and namespaces.
require 'JShrink/Minifier.php';
if(filemtime('scripts_template.js') < filemtime('scripts_template.min.js')) {
read_file('scripts_template.min.js');
} else {
$output = \JShrink\Minifier::minify(file_get_contents('scripts_template.js'));
file_put_contents('scripts_template.min.js', $output);
echo $output;
}
Take a look at Assetic, a great asset management library in PHP. It is well integrated with Symfony2 and widely used.
https://github.com/kriswallsmith/assetic
Depending on the restrictions of your server (eg, not running in safe mode), perhaps you can also look beyond PHP for a minifier and run it using shell_exec(). For instance, if you can run Java on your server, put a copy of YUI Compressor on the server and use it directly.
Then scripts.php would be something like:
<?php
$cmd = "java -cp [path-to-yui-dir] -jar [path-to-yuicompressor.jar] [path-to-scripts_template.js]";
echo(shell_exec($cmd));
?>
Other suggestion: build the minification step into your development workflow, before you deploy to the server. For example I set up my Eclipse PHP projects to compress JS and CSS files into a "build" folder. Works like a charm.
Using "PHPWee": https://github.com/searchturbine/phpwee-php-minifier
(which also uses JSmin), I pushed #Robert K solution a little bit further.
This solution allows minifying both CSS and JS files. If the non-minified file cannot be found, it will return an empty string. If the minified file is older than the non-minified, it will try to create it. It will create a sub-folder for the minified file if it doesn't exist. If the method can minify the file successfully, it returns it either in a <script> (javascript) or a <link> (CSS) tag. Otherwise, the method will return the non-minified version in the proper tag.
Note: tested with PHP 7.0.13
/**
* Try to minify the JS/CSS file. If we are not able to minify,
* returns the path of the full file (if it exists).
*
* #param $matches Array
* 0 = Full partial path
* 1 = Path without the file
* 2 = File name and extension
*
* #param $fileType Boolean
* FALSE: css file.
* TRUE: js file
*
* #return String
*/
private static function createMinifiedFile(array $matches, bool $fileType)
{
if (strpos($matches[1], 'shared_code') !== false) {
$path = realpath(dirname(__FILE__)) . str_replace(
'shared_code',
'..',
$matches[1]
);
} else {
$path = realpath(dirname(__FILE__)) .
"/../../" . $matches[1];
}
if (is_file($path . $matches[2])) {
$filePath = $link = $matches[0];
$min = 'min/' . str_replace(
'.',
'.min.',
$matches[2]
);
if (!is_file($path . $min) or
filemtime($path . $matches[2]) >
filemtime($path . $min)
) {
if (!is_dir($path . 'min')) {
mkdir($path . 'min');
}
if ($fileType) { // JS
$minified = preg_replace(
array(
'/(\))\R({)/',
'/(})\R/'
),
array(
'$1$2',
'$1'
),
Minify::js(
(string) file_get_contents(
$path . $matches[2]
)
)
);
} else { // CSS
$minified = preg_replace(
'#/\*(?:[\r\s\S](?!\*/))+\R?\*/#', //deal with multiline comments
'',
Minify::css(
(string) file_get_contents(
$path . $matches[2]
)
)
);
}
if (!empty($minified) and file_put_contents(
$path . $min,
$minified
)
) {
$filePath = $matches[1] . $min;
}
} else { // up-to-date
$filePath = $matches[1] . $min;
}
} else { // full file doesn't exists
$filePath = "";
}
return $filePath;
}
/**
* Return the minified version of a CSS file (must end with the .css extension).
* If the minified version of the file is older than the full CSS file,
* the CSS file will be shrunk.
*
* Note: An empty string will be return if the CSS file doesn't exist.
*
* Note 2: If the file exists, but the minified file cannot be created,
* we will return the path of the full file.
*
* #link https://github.com/searchturbine/phpwee-php-minifier Source
*
* #param $path String name or full path to reach the CSS file.
* If only the file name is specified, we assume that you refer to the shared path.
*
* #return String
*/
public static function getCSSMin(String $path)
{
$link = "";
$matches = array();
if (preg_match(
'#^(/[\w-]+/view/css/)?([\w-]+\.css)$#',
$path,
$matches
)
) {
if (empty($matches[1])) { // use the default path
$matches[1] = self::getCssPath();
$matches[0] = $matches[1] . $matches[2];
}
$link = self::createMinifiedFile($matches, false);
} else {
$link = "";
}
return (empty($link) ?
'' :
'<link rel="stylesheet" href="' . $link . '">'
);
}
/**
* Return the path to fetch CSS sheets.
*
* #return String
*/
public static function getCssPath()
{
return '/shared_code/css/' . self::getCurrentCSS() . "/";
}
/**
* Return the minified version of a JS file (must end with the .css extension).
* If the minified version of the file is older than the full JS file,
* the JS file will be shrunk.
*
* Note: An empty string will be return if the JS file doesn't exist.
*
* Note 2: If the file exists, but the minified file cannot be created,
* we will return the path of the full file.
*
* #link https://github.com/searchturbine/phpwee-php-minifier Source
*
* #param $path String name or full path to reach the js file.
*
* #return String
*/
public static function getJSMin(String $path)
{
$matches = array();
if (preg_match(
'#^(/[\w-]+(?:/view)?/js/)([\w-]+\.js)$#',
$path,
$matches
)
) {
$script = self::createMinifiedFile($matches, true);
} else {
$script = "";
}
return (empty($script) ?
'' :
'<script src="' . $script . '"></script>'
);
}
In a (Smarty) template, you might use those methods like this:
{$PageController->getCSSMin("main_frame.css")}
//Output: <link rel="stylesheet" href="/shared_code/css/default/min/main_frame.min.css">
{$PageController->getCSSMin("/gem-mechanic/view/css/gem_mechanic.css")}
//Output: <link rel="stylesheet" href="/gem-mechanic/view/css/min/gem_mechanic.min.css">
{$PageController->getJSMin("/shared_code/js/control_utilities.js")}
//Output: <script src="/shared_code/js/min/control_utilities.min.js"></script>
{$PageController->getJSMin("/PC_administration_interface/view/js/error_log.js")}
//Output: <script src="/PC_administration_interface/view/js/min/error_log.min.js"></script>
Unit tests:
/**
* Test that we can minify CSS files successfully.
*/
public function testGetCSSMin()
{
//invalid style
$this->assertEmpty(
PageController::getCSSMin('doh!!!')
);
//shared style
$path = realpath(dirname(__FILE__)) . '/../css/default/min/main_frame.min.css';
if (is_file($path)) {
unlink ($path);
}
$link = PageController::getCSSMin("main_frame.css");
$this->assertNotEmpty($link);
$this->assertEquals(
'<link rel="stylesheet" href="/shared_code/css/default/min/main_frame.min.css">',
$link
);
$this->validateMinifiedFile($path);
//project style
$path = realpath(dirname(__FILE__)) . '/../../gem-mechanic/view/css/min/gem_mechanic.min.css';
if (is_file($path)) {
unlink ($path);
}
$link = PageController::getCSSMin("/gem-mechanic/view/css/gem_mechanic.css");
$this->assertNotEmpty($link);
$this->assertEquals(
'<link rel="stylesheet" href="/gem-mechanic/view/css/min/gem_mechanic.min.css">',
$link
);
$this->validateMinifiedFile($path);
}
/**
* Test that we can minify JS files successfully.
*/
public function testGetJSMin()
{
//invalid script
$this->assertEmpty(
PageController::getJSMin('doh!!!')
);
//shared script
$path = realpath(dirname(__FILE__)) . '/../js/min/control_utilities.min.js';
if (is_file($path)) {
unlink ($path);
}
$script = PageController::getJSMin("/shared_code/js/control_utilities.js");
$this->assertNotEmpty($script);
$this->assertEquals(
'<script src="/shared_code/js/min/control_utilities.min.js"></script>',
$script
);
$this->validateMinifiedFile($path);
//project script
$path = realpath(dirname(__FILE__)) . '/../../PC_administration_interface/view/js/min/error_log.min.js';
if (is_file($path)) {
unlink ($path);
}
$script = PageController::getJSMin("/PC_administration_interface/view/js/error_log.js");
$this->assertNotEmpty($script);
$this->assertEquals(
'<script src="/PC_administration_interface/view/js/min/error_log.min.js"></script>',
$script
);
$this->validateMinifiedFile($path);
}
/**
* Make sure that the minified file exists and that its content is valid.
*
* #param $path String the path to reach the file
*/
private function validateMinifiedFile(string $path)
{
$this->assertFileExists($path);
$content = (string) file_get_contents($path);
$this->assertNotEmpty($content);
$this->assertNotContains('/*', $content);
$this->assertEquals(
0,
preg_match(
'/\R/',
$content
)
);
}
Additional notes:
In phpwee.php I had to replace <? by <?php.
I had problems with the namespace (the function class_exists() was not able to find the classes even though they were in the same file). I solved this problem by removing the namespace in every file.
JavaScriptPacker works since 2008, and is quite simple
I know this question is really old, but after having some trouble with newer syntax and older formatting solutions, I came up with this PHP function that removes comments and unnecessary spaces in a JS string.
For this to work, the JS code needs to have semicolons after function definitions, and this won't work if there's a regular expression with two forward slashes (//) in it. I'm open to ideas on how to detect that, haven't come up with anything yet.
//echo a minified version of the $source JavaScript
function echo_minified($source){
//a list of characters that don't need spaces around them
$NO_SPACE_NEAR = ' +=-*/%&|^!~?:;,.<>(){}[]';
//loop through each line of the source, removing comments and unnecessary whitespace
$lines = explode("\n", $source);
//keep track of whether we're in a string or not
$in_string = false;
//keep track of whether we're in a comment or not
$multiline_comment = false;
foreach($lines as $line){
//remove whitespace from the start and end of the line
$line = trim($line);
//skip blank lines
if($line == '') continue;
//remove "use strict" statements
if(!$in_string && str_starts_with($line, '"use strict"')) continue;
//loop through the current line
$string_len = strlen($line);
for($position = 0; $position < $string_len; $position++){
//if currently in a string, check if the string ended (making sure to ignore escaped quotes)
if($in_string && $line[$position] === $in_string && ($position < 1 || $line[$position - 1] !== '\\')){
$in_string = false;
}
else if($multiline_comment){
//check if this is the end of a multiline comment
if($position > 0 && $line[$position] === "/" && $line[$position - 1] === "*"){
$multiline_comment = false;
}
continue;
}
//check everything else
else if(!$in_string && !$multiline_comment){
//check if this is the start of a string
if($line[$position] == '"' || $line[$position] == "'" || $line[$position] == '`'){
//record the type of string
$in_string = $line[$position];
}
//check if this is the start of a single-line comment
else if($position < $string_len - 1 && $line[$position] == '/' && $line[$position + 1] == '/'){
//this is the start of a single line comment, so skip the rest of the line
break;
}
//check if this is the start of a multiline comment
else if($position < $string_len - 1 && $line[$position] == '/' && $line[$position + 1] == '*'){
$multiline_comment = true;
continue;
}
else if(
$line[$position] == ' ' && (
//if this is not the first character, check if the character before it requires a space
($position > 0 && strpos($NO_SPACE_NEAR, $line[$position - 1]) !== false)
//if this is not the last character, check if the character after it requires a space
|| ($position < $string_len - 1 && strpos($NO_SPACE_NEAR, $line[$position + 1]) !== false)
)
){
//there is no need for this space, so keep going
continue;
}
}
//print the current character and continue
echo $line[$position];
}
//if this is a multi-line string, preserve the line break
if($in_string){
echo "\\n";
}
}
}
Related
At work, we have a website on which we publish PDF files which are available for download (mostly user notices etc.). We also manage our internal database for updates and to generate CD's and USB Sticks with all PDF files on it, which are shipped out with the products.
So I build a platform where all plants that produce these PDF files can upload them. From time to time, a person will take care of updating the system (sync both servers).
I would like to have a link next to the files with a delete option. I already have that using a simple php script.
<?php
$deleter = new Deleter("./");
class Deleter {
var $filename;
var $ignorelist = array (
'.',
'..',
'index.php',
'del.php',
'deletefile.php'
);
function Deleter($path="./") {
$this->__construct($path);
}
function __construct($path="./") {
$this->filename = basename(__FILE__);
array_push($this->ignorelist, $this->filename);
// insert page header
$this->createHeader();
// condition: create initial file list?
if (isset($_GET['delete']) && !empty($_GET['delete'])) {
// a path has been set, escape and use
$path = basename($_GET['delete']);
$path = urldecode($path);
//$path = mysql_real_escape_string($path);
// condition : Step 2: seek deletion confirmation?
if (!isset($_GET['confirm']) || $_GET['confirm'] != 'aye') {
$this->createConfirmationStep($path);
// step 3: delete!
} else {
$this->createShowDelete($path);
}
// step 1: no files selected, create file list
} else {
echo '
<p>These files are on the server:</p>
<ul>
';
$this->createFileList($path);
echo '</ul>';
}
// insert page footer
$this->createFooter();
}
/**
* Step 1: Create a list of all files within a specific directory
*
* #param string $path The server path to look for files in
* #return array $fileList Array of all files, with file/directory details
* #access public
*/
function createFileList($path) {
// condition : if the path isn't set, assume one
if (!isset($path)) {
$path = "./";
}
// temporary arrays to hold separate file and directory content
$filelist = array();
$directorylist = array();
// get the ignore list, in local scope
$ignorelist = $this->ignorelist;
// Open directory and read contents
if (is_dir($path)) {
// loop through the contents (PHP4 compat)
$dh = opendir($path);
while (false !== ($file = readdir($dh))) {
// skip over any files in the ignore list
if (!in_array($file, $ignorelist)) {
// condition : if it is a directory, add to dir list array
if (is_dir($path.$file)) {
$directorylist[] = array(
"path" => $path,
"file" => $file,
"filetype" => 'directory',
"date" => date("M d Y, H:i", filemtime($path.$file."")),
"filecount" => $this->countRelevantFiles($path.$file),
"filesize" => 0
);
// file, add to file array
} else {
$filelist[] = array(
"path" => $path,
"file" => $file,
"filetype" => $this->getFileType($path.$file) . " file",
"date" => date("M d Y, H:i", filemtime($path.$file."")),
"filecount" => 0,
"filesize" => $this->getFileSize(filesize($path.$file))
);
}
}
}
}
// merge file and directory lists
$finalList = array_merge($directorylist, $filelist);
// loop through each file
foreach ($finalList as $key => $value) {
// condition : add trailing slash for directories
$trailingslash = ($value['filetype'] == 'directory' ) ? '/' : '';
// condition : if it is a directory, display count of subfiles
if ($value['filetype'] == 'directory') {
$fileending = ($value['filecount'] == 1) ? 'item' : 'items';
$filedetails = ' (contains '.$value['filecount'].' '.$fileending.')';
// else, if it is a file, display file size
} else {
$filedetails = ' ('.$value['filesize'].')';
}
// create the html for each project
echo '
<li class="' . $value['filetype'].'" id="file_' . urlencode($value['file']) . '">
<strong>' . $value['file'] . '</strong> /
';
echo '
<a href="./'.$this->filename.'?delete='.urlencode($value['file'].$trailingslash).'">
Delete
</a>
</li>
';
}
}
/**
* count the number of files in a directory, not including the list of ignorable files
*
* #param string $path The server path to look for files in
* #return int $count The number of relevant files
* #access private
*/
function countRelevantFiles($path, $count = 0) {
// open the directory
if (is_dir($path)) {
// loop through all files, checking if we should count the current one
$dh = opendir($path);
while (false !== ($file = readdir($dh))) {
if (!in_array($file, $this->ignorelist)) {
$count++;
if(is_dir($path."/".$file)) {
$count = $this->countRelevantFiles($path."/".$file, $count);
}
}
}
}
// return the result
return $count;
}
/**
* list all sub-files of a directory
*
* #param string $path The server path to look for files in
* #return void
* #access private
*/
function listFilesToDelete($path) {
// open the directory
if (is_dir($path)) {
// loop through all files, checking if we should count the current one
$dh = opendir($path);
while (false !== ($file = readdir($dh))) {
if (!in_array($file, $this->ignorelist)) {
echo '<li>'.$path.'/'.$file.'</li>';
if(is_dir($path."/".$file)) {
$this->listFilesToDelete($path."/".$file);
}
}
}
}
}
/**
* Delete files
*
* #param string $path The server path to delete
* #return void
* #access private
*/
function delete($path) {
// Simple delete for a file
if (is_file($path)) {
echo '<li>deleting file: ' . $path . '</li>';
if (copy($path, "../trash/".$path)) {
unlink($path);
}
}
}
/**
* Create a nice readable filesize from the number of bytes in a file
*
* #param int $size the size in bytes
* #param string $retstring
*
* #return string the size in nice words
*/
function getFileSize($size, $retstring = null)
{
$sizes = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
if ($retstring === null) { $retstring = '%01.2f %s'; }
$lastsizestring = end($sizes);
foreach ($sizes as $sizestring) {
if ($size < 1024) { break; }
if ($sizestring != $lastsizestring) { $size /= 1024; }
}
if ($sizestring == $sizes[0]) { $retstring = '%01d %s'; } // Bytes aren't normally fractional
return sprintf($retstring, $size, $sizestring);
}
/**
* Function to find a file type for a given filename
*
* #param string $file filename/path
* #return string $extension file type
*/
function getFileType($file="") {
// get file name
$filearray = explode("/", $file);
$filename = array_pop($filearray);
// condition : if no file extension, return
if(strpos($filename, ".") === false) return false;
// get file extension
$filenamearray = explode(".", $filename);
$extension = $filenamearray[(count($filenamearray) - 1)];
return $extension;
}
/* Page Building Methods */
/**
* Create page header
*/
function createHeader(){
echo '
';
}
/**
* Create page footer
*/
function createFooter(){
echo '
';
}
/**
* Create confirmation step
*/
function createConfirmationStep($path){
echo '
<p>� back to file list</p>
<p>Please confirm that you want to delete the following files:</p>
<p class="confirm">Delete</p>
<ol>
<li>'.$path.'</li>
';
$this->listFilesToDelete($path);
echo '
</ol>
<p class="confirm">Delete</p>
';
}
/**
* Show the files you're deleting
*/
function createShowDelete($path){
echo '
<p>� back to file list</p>
<p>The following items have been removed:</p>
<ol>
';
$this->delete($path);
echo '
</ol>
<p><strong>Deletion complete.</strong></p>
<p>� back to file list</p>
';
}
}
?>
Now what I would like this to do is delete a file on one server e.g. server1.com/files/ and move the same file from server2.com/files/ to server2.com/trash/
I have full access to both servers. Is there any way to do that?
since you didn't tell us on what OS you're running you php script, I'm assuming you have a linux.
since you need to do that from one server, you'd need to have a passwordless ssh access to the other server. then you'd need to create a bash script that utilizes mv and rsync to do the file manipulations you need. and then you can use php's exec() function to execute and send the filename param to the shell script from a web page.
I think it's better to configure rsync over ssh with --delete option. Something like this
/usr/bin/rsync -avz --delete /path/files -e "ssh" userforsync#$SECONDARYSERVER:/path/files
where is $SECONDARYSERVER is dns name or ip. To make this work you should accept authentication by public key on secondary server and add public key to authorized keys
I'm trying to enforce a root directory in a filesystem abstraction. The problem I'm encountering is the following:
The API lets you read and write files, not only to local but also remote storages. So there's all kinds of normalisation going on under the hood. At the moment it doesn't support relative paths, so something like this isn't possible:
$filesystem->write('path/to/some/../relative/file.txt', 'file contents');
I want to be able to securely resolve the path so the output is would be: path/to/relative/file.txt.
As is stated in a github issue which was created for this bug/enhancement (https://github.com/FrenkyNet/Flysystem/issues/36#issuecomment-30319406) , it needs to do more that just splitting up segments and removing them accordingly.
Also, since the package handles remote filesystems and non-existing files, realpath is out of the question.
So, how should one go about when dealing with these paths?
To quote Jame Zawinski:
Some people, when confronted with a problem, think "I know, I'll use regular expressions."
Now they have two problems.
protected function getAbsoluteFilename($filename) {
$path = [];
foreach(explode('/', $filename) as $part) {
// ignore parts that have no value
if (empty($part) || $part === '.') continue;
if ($part !== '..') {
// cool, we found a new part
array_push($path, $part);
}
else if (count($path) > 0) {
// going back up? sure
array_pop($path);
} else {
// now, here we don't like
throw new \Exception('Climbing above the root is not permitted.');
}
}
// prepend my root directory
array_unshift($path, $this->getPath());
return join('/', $path);
}
I've resolved how to do this, this is my solution:
/**
* Normalize path
*
* #param string $path
* #param string $separator
* #return string normalized path
*/
public function normalizePath($path, $separator = '\\/')
{
// Remove any kind of funky unicode whitespace
$normalized = preg_replace('#\p{C}+|^\./#u', '', $path);
// Path remove self referring paths ("/./").
$normalized = preg_replace('#/\.(?=/)|^\./|\./$#', '', $normalized);
// Regex for resolving relative paths
$regex = '#\/*[^/\.]+/\.\.#Uu';
while (preg_match($regex, $normalized)) {
$normalized = preg_replace($regex, '', $normalized);
}
if (preg_match('#/\.{2}|\.{2}/#', $normalized)) {
throw new LogicException('Path is outside of the defined root, path: [' . $path . '], resolved: [' . $normalized . ']');
}
return trim($normalized, $separator);
}
./ current location
../ one level up
function normalize_path($str){
$N = 0;
$A =explode("/",preg_replace("/\/\.\//",'/',$str)); // remove current_location
$B=[];
for($i = sizeof($A)-1;$i>=0;--$i){
if(trim($A[$i]) ===".."){
$N++;
}else{
if($N>0){
$N--;
}
else{
$B[] = $A[$i];
}
}
}
return implode("/",array_reverse($B));
}
so:
"a/b/c/../../d" -> "a/d"
"a/./b" -> "a/b"
/**
* Remove '.' and '..' path parts and make path absolute without
* resolving symlinks.
*
* Examples:
*
* resolvePath("test/./me/../now/", false);
* => test/now
*
* resolvePath("test///.///me///../now/", true);
* => /home/example/test/now
*
* resolvePath("test/./me/../now/", "/www/example.com");
* => /www/example.com/test/now
*
* resolvePath("/test/./me/../now/", "/www/example.com");
* => /test/now
*
* #access public
* #param string $path
* #param mixed $basePath resolve paths realtively to this path. Params:
* STRING: prefix with this path;
* TRUE: use current dir;
* FALSE: keep relative (default)
* #return string resolved path
*/
function resolvePath($path, $basePath=false) {
// Make absolute path
if (substr($path, 0, 1) !== DIRECTORY_SEPARATOR) {
if ($basePath === true) {
// Get PWD first to avoid getcwd() resolving symlinks if in symlinked folder
$path=(getenv('PWD') ?: getcwd()).DIRECTORY_SEPARATOR.$path;
} elseif (strlen($basePath)) {
$path=$basePath.DIRECTORY_SEPARATOR.$path;
}
}
// Resolve '.' and '..'
$components=array();
foreach(explode(DIRECTORY_SEPARATOR, rtrim($path, DIRECTORY_SEPARATOR)) as $name) {
if ($name === '..') {
array_pop($components);
} elseif ($name !== '.' && !(count($components) && $name === '')) {
// … && !(count($components) && $name === '') - we want to keep initial '/' for abs paths
$components[]=$name;
}
}
return implode(DIRECTORY_SEPARATOR, $components);
}
I've been working with a handy php script, which scans my site and spits out links to all the pages it finds. The problem is, it's also scanning my 'includes' folder, which contains all my .inc.php files.
Obviously I'd rather it ignore this folder when scanning the site, but for the life of me I can't see how to edit the script to tell it to do so.
The script is:
<?php
// starting directory. Dot means current directory
$basedir = ".";
// function to count depth of directory
function getdepth($fn){
return (($p = strpos($fn, "/")) === false) ? 0 : (1 + getdepth(substr($fn, $p+1)));
}
// function to print a line of html for the indented hyperlink
function printlink($fn){
$indent = getdepth($fn); // get indent value
echo "<li class=\"$indent\"><a href=\"$fn\">"; //print url
$handle = fopen($fn, "r"); //open web page file
$filestr = fread($handle, 1024); //read top part of html
fclose($handle); //clos web page file
if (preg_match("/<title>.+<\/title>/i",$filestr,$title)) { //get page title
echo substr($title[0], 7, strpos($title[0], '/')-8); //print title
} else {
echo "No title";
}
echo "</a></li><br>\n"; //finish html
}
// main function that scans the directory tree for web pages
function listdir($basedir){
if ($handle = #opendir($basedir)) {
while (false !== ($fn = readdir($handle))){
if ($fn != '.' && $fn != '..'){ // ignore these
$dir = $basedir."/".$fn;
if (is_dir($dir)){
listdir($dir); // recursive call to this function
} else { //only consider .html etc. files
if (preg_match("/[^.\/].+\.(htm|html|php)$/",$dir,$fname)) {
printlink($fname[0]); //generate the html code
}
}
}
}
closedir($handle);
}
}
// function call
listdir($basedir); //this line starts the ball rolling
?>
Now I can see where the script is told to ignore certain files, and I've tried appending:
&& $dir != 'includes'
...to it in numerous places, but my php knowledge is simply too shaky to know exactly how to integrate that code into the script.
If anyone can help, then you'd be saving me an awfully large headache. Cheers.
Add it this line:
if ($fn != '.' && $fn != '..'){ // ignore these
so it's
if ($fn != '.' && $fn != '..' && $fn != 'includes'){ // ignore these
Your path needs to be absolute. Add it at the top of listdir
function listdir($basedir){
if($basedir == '/path/to/includes') {
return;
} [...]
This also makes sure that only one includes folder will be ignored.
I have a Drupal site that needs to display a unique header image based on the path. I have found some helpful code. It gets me close to where I need to be, but not all the way. I have pasted it at the end of this post.
The issue I am having is that it bases the banner image off of the characters after the first "/" after example.com in the URL. For example, example.com/forum returns a banner of header-FORUM.png.
I need it to work a little differently. I would like it to base the banner returned off the characters after the second "/" after example.com in the URL. For example, example.com/category/term should return a banner of header-TERM.png.
Any help that you can offer with this is much appreciated.
Here's the code I mentioned earlier via AdaptiveThemes (FYI, there is a comment on that page that attempts to solve a similar issue to mine but I can't get it to work).
<?php
// Return a file based on the URL alias, else return a default file
function unique_section_header() {
$path = drupal_get_path_alias($_GET['q']);
list($sections, ) = explode('/', $path, 2);
$section = safe_string($sections);
$filepath = path_to_theme() . '/images/sections/header-' . $section .'.png';
if (file_exists($filepath)) {
$output = $filepath;
}
else {
$output = path_to_theme() . '/images/sections/header-default.png';
}
return $output;
}
//Make a string safe
function safe_string($string) {
$string = strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $string));
return $string;
}
?>
Thanks!
Not exactly sure what the output of drupal_get_path_alias is, but try this:
<?php
// Return a file based on the URL alias, else return a default file
function unique_section_header() {
$path = drupal_get_path_alias($_GET['q']);
$pathSegments = explode('/', $path, 3);
$section = safe_string($pathSegments[2]);
$filepath = path_to_theme() . '/images/sections/header-' . $section .'.png';
if (file_exists($filepath)) {
$output = $filepath;
}
else {
$output = path_to_theme() . '/images/sections/header-default.png';
}
return $filepath;//$output;
}
//Make a string safe
function safe_string($string) {
$string = strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $string));
return $string;
}
The only changes made were to the usage of explode. explode will separate the path based on the /, so you just need to access a different element in that array. The last parameter of explode is the maximum number of elements to be returned and may also need to be tweaked
I'm adding an answer so I can include code. This is based on Gilean's response.
/** Return a file based on the URL alias, else return a default file
*/
function unique_section_header() {
$path = drupal_get_path_alias($_GET['q']);
$pathSegments = explode('/', $path, 3);
$section = safe_string($pathSegments[1]);
$filepath = path_to_theme() . '/images/sections/header-' . $section .'.png';
if (file_exists($filepath)) {
$output = $filepath;
}
else {
$output = path_to_theme() . '/images/sections/header-default.jpg';
}
return $output;
}
/** Make a string safe
*/
function safe_string($string) {
$string = strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $string));
return $string;
}
This is what I would do:
In your theme's template.php, create the function THEMENAME_preprocess_page (replace THEMENAME with the name of your theme) as follows. If it already exists, add the following code to that function. (disclamer: untested code)
function THEMENAME_preprocess_page(&$variables) {
$path = drupal_get_path_alias($_GET['q']);
$path_segments = explode('/', $path, 3);
if ($path_segments[0] == 'category' && !empty($path_segments[1])) {
$safe_term = strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '-', $path_segments[1]));
$filepath = path_to_theme() . '/images/sections/header-' . $safe_term .'.png';
if (!file_exists($filepath)) {
$filepath = path_to_theme() . '/images/sections/header-default.png';
}
$variables['header_image'] = theme('image', $filepath);
}
}
Using a preprocess function (like the one above) is the Drupal way to make extra variables available for a template file. You only have to add a new element to the $variables array. Once you have done the above, you can simply put the following line in your page.tpl.php:
<?php print $header_image; ?>
This will print the complete <img> element.
PS. Usually, I advice not to base code like this on path aliases. It's a method that breaks easily because path aliases can change.
Are there any good implementations of Minify integration with Zend Framework? I'm looking for examples.
I'd love to have a plugin that overrides $this->headLink() and spits out the correct minified url/content.
Edit:
It seems most examples I find aren't fully optimized in one form or fashion. I'm looking for a solution that meets the following requirements:
Reduces multiple links and script tags to one request (one for link and one for scripts)
The closest I've seen is a request path that passes a comma-delimited string to /min/ like so:
<script src="/min?f=file1.js,file2,js,file3.js" type="text/javascript"></script>
Why not something that combines all scripts into one file on disk on the fly and then caches it so that you aren't doing the minification on every request?
<script src="/js/app.js?someRandomStringHere" type="text/javascript"></script>
The combining aspect should maintain order (in reference to prepend, append, etc)
While I don't care so much about sending correct expires headers because I force gzipping, etags, and expires headers on the server-side, having that optional would be beneficial to other users.
Lastly, having a build script that generates the minified assets isn't necessary bad - as long as it is easy to do and doesn't require a code change after every build.
This is what I use, the class is shown followed by use cases. I've commented it quickly, some of it might need changing to match your paths or define() the PUBLICPATH
class View_Helper_Minify extends Zend_View_Helper_Abstract
{
public function minify($files, $ext, $folderName)
{
// The folder of the files your about to minify
// PUBLICPATH should be the path to your public ZF folder
$folder = PUBLICPATH . $folderName . "/";
// Set update needed flag to false
$update_needed = false;
// This is the file ext of the cached files
$cacheFileExt = "." . $ext;
// The list of files sent is exploded into an array
$filesExploded = explode(',', $files);
// The full cached file path is an md5 of the files string
$cacheFilePath = $folder . md5($files) . $cacheFileExt;
// The filename of the cached file
$cacheFileName = preg_replace("#[^a-zA-Z0-9\.]#", "", end(explode("/", $cacheFilePath)));
// Obtains the modified time of the cache file
$cacheFileDate = is_file($cacheFilePath) ? filemtime($cacheFilePath) : 0;
// Create new array for storing the list of valid files
$fileList = array();
// For each file
foreach($filesExploded as $f)
{
// determine full path of the full and append extension
$f = $folder . $f . '.' . $ext;
// If the determined path is a file
if(is_file($f))
{
// If the file's modified time is after the cached file's modified time
// Then an update of the cached file is needed
if(filemtime($f) > $cacheFileDate)
$update_needed = true;
// File is valid add to list
$fileList[] = $f;
}
}
// If the cache folder's modified time is after the cached file's modified time
// Then an update is needed
if(filemtime($folder) > $cacheFileDate)
$update_needed = true;
// If an update is needed then optmise the valid files
if($update_needed)
$this->optmiseFiles($fileList, $cacheFilePath, $ext);
// Finally check if the cached file path is valid and return the absolute URL
// for te cached file
if(is_file($cacheFilePath))
return "/" . $folderName . "/" . $cacheFileName;
// Throw Exception
throw new Exception("No minified file cached");
}
private function optimise($code, $ext)
{
// Do not optmise JS files
// I had problems getting JS files optmised and still function
if($ext == "js")
return $code;
// Remove comments from CSS
while(($i = strpos($code, '/*')) !== false)
{
$i2 = strpos($code, '*/',$i);
if($i2 === false)
break;
$code = substr($code, 0, $i).substr($code, $i2 + 2);
}
// Remove other elements from CSS
$code = str_replace('/*','',$code);
$code = str_replace("\n",' ',$code);
$code = str_replace("\r",' ',$code);
$code = str_replace("\t",' ',$code);
$code = #ereg_replace('[ ]+',' ',$code);
$code = str_replace(': ',':', $code);
$code = str_replace('; ',';', $code);
$code = str_replace(', ',',', $code);
$code = str_replace(' :',':', $code);
$code = str_replace(' ;',';', $code);
$code = str_replace(' ,',',', $code);
// Return optimised code
return $code;
}
// Optmise the list of files
private function optmiseFiles($fileList, $cacheFilePath, $ext)
{
// Empty String to start with
$code = '';
// Check files list in case just one file was passed
if(is_array($fileList))
{
// Foreach of the valid files optmise the code if the file is valid
foreach($fileList as $f)
$code .= is_file($f) ? $this->optimise(implode('', file($f)), $ext) : '';
}
// Else if a valid file is passed optmise the code
else
$code = is_file($fileList) ? $this->optimise(implode('', file($fileList)), $ext) : '';
// Open the cache file
$f = #fopen($cacheFilePath, 'w');
// If open successful
if(is_resource($f))
{
// Write code to the cache file
fwrite($f, $code);
// close cache file
fclose($f);
}
}
}
You would use the helper like this in your view
// Define an array of files, note you do not define the ext of those files
// The ext is defined as a param for the helper as this effects the optmisation
$files = array("jquery-ui-1.8.7.custom",
"jquery.pnotify.default",
"jquery.pnotify.default.icons",
"tipTip",
"prettyPhoto",
"custom");
// Get the absolute URL of the files which are imploded also pass the directory 'css' and ext 'css'
$cssString = $this->minify(implode("," , $files), "css", "css");
// use baseURL() to output the full URL of the cached file and use it as normal with headLink()
echo $this->headLink()
->appendStylesheet($this->baseUrl($cssString));
And here is a javascript version
$files = array("jquery-1.4.4.min",
"jquery.pnotify.min",
"jquery.tipTip.minified",
"jquery.countdown.min",
"jquery.prettyPhoto",
"jquery.typewatch",
"default.functions");
$jsString = $this->minify(implode("," , $files), "js", "scripts");
echo $this->headScript()->appendFile($this->baseUrl($jsString));
I am trying to do the same thing right now. I am looking at NC State University's OT Framework, based on Zend Framework. This is implemented as a view helper. It has a nice class to minify all headscripts and headlinks via the Minify on Google Code:
http://ot.ncsu.edu/2010/03/03/getting-started-with-ot-framework/
Headscripts:
<?php
/**
* Minifies the javascript files added via the minifyHeadScript helper using
* minify (http://code.google.com/p/minify/)
*
*/
class Ot_View_Helper_MinifyHeadScript extends Zend_View_Helper_HeadScript
{
protected $_regKey = 'Ot_View_Helper_MinifyHeadScript';
public function minifyHeadScript($mode = Zend_View_Helper_HeadScript::FILE, $spec = null, $placement = 'APPEND', array $attrs = array(), $type = 'text/javascript')
{
return parent::headScript($mode, $spec, $placement, $attrs, $type);
}
public function toString()
{
$items = array();
$scripts = array();
$baseUrl = $this->getBaseUrl();
// we can only support files
foreach ($this as $item) {
if (isset($item->attributes['src']) && !empty($item->attributes['src'])) {
$scripts[] = str_replace($baseUrl, '', $item->attributes['src']);
}
}
//remove the slash at the beginning if there is one
if (substr($baseUrl, 0, 1) == '/') {
$baseUrl = substr($baseUrl, 1);
}
$item = new stdClass();
$item->type = 'text/javascript';
$item->attributes['src'] = $this->getMinUrl() . '?b=' . $baseUrl . '&f=' . implode(',', $scripts);
$scriptTag = $this->itemToString($item, '', '', '');
return $scriptTag;
}
public function getMinUrl() {
return $this->getBaseUrl() . '/min/';
}
public function getBaseUrl(){
return Zend_Controller_Front::getInstance()->getBaseUrl();
}
}
And here is the code for headlinks:
<?php
/**
* Minifies the stylesheets added via the minifyHeadLink helper using
* minify (http://code.google.com/p/minify/)
*
*/
class Ot_View_Helper_MinifyHeadLink extends Zend_View_Helper_HeadLink
{
protected $_regKey = 'Ot_View_Helper_MinifyHeadLink';
public function minifyHeadLink(array $attributes = null, $placement = Zend_View_Helper_Placeholder_Container_Abstract::APPEND)
{
return parent::headlink($attributes, $placement);
}
public function toString()
{
$items = array();
$stylesheets = array();
$baseUrl = $this->getBaseUrl();
foreach ($this as $item) {
if ($item->type == 'text/css' && $item->conditionalStylesheet === false) {
$stylesheets[$item->media][] = str_replace($baseUrl, '', $item->href);
} else {
$items[] = $this->itemToString($item);
}
}
//remove the slash at the beginning if there is one
if (substr($baseUrl, 0, 1) == '/') {
$baseUrl = substr($baseUrl, 1);
}
foreach ($stylesheets as $media=>$styles) {
$item = new stdClass();
$item->rel = 'stylesheet';
$item->type = 'text/css';
$item->href = $this->getMinUrl() . '?b=' . $baseUrl . '&f=' . implode(',', $styles);
$item->media = $media;
$item->conditionalStylesheet = false;
$items[] = $this->itemToString($item);
}
$link = implode($this->_escape($this->getSeparator()), $items);
return $link;
}
public function getMinUrl() {
return $this->getBaseUrl() . '/min/';
}
public function getBaseUrl(){
return Zend_Controller_Front::getInstance()->getBaseUrl();
}
}
I ran across the same problem and ended up writing two drop-in helpers to manage it for me. You can see them at http://blog.hines57.com/2011/03/13/zendframework-minify/ - thanks again. Quick overview for one of them:
* * ** PREREQUISITES **
* This file expects that you have installed minify in ../ZendFramworkProject/Public/min
* and that it is working. If your location has changed, modify
* $this->$_minifyLocation to your current location.
*
* ** INSTALLATION **
* Simply drop this file into your ../ZendFramworkProject/application/views/helpers
* directory.
*
* ** USAGE **
* In your Layout or View scripts, you can simply call minifyHeadLink
* in the same way that you used to call headLink. Here is an example:
*
echo $this->minifyHeadLink('/favicon.ico') // Whatever was already loaded from Controller.
->prependStylesheet('http://example.com/js/sample.css')// 6th
->prependStylesheet('/js/jqModal.css') // 5th
->prependStylesheet('/js/jquery.alerts.css') // 4th
->prependStylesheet('/templates/main.css') // 3rd
->prependStylesheet('/css/project.css.php') // 2nd
->prependStylesheet('/css/jquery.autocomplete.css') // 1st
->appendStylesheet('/css/ie6.css','screen','lt IE 7'); // APPEND to make it Last
*
*
* This can be interesting because you will notice that 2nd is a php file, and we
* have a reference to a favicon link in there as well as a reference to a css file on
* another website. Because minify can't do anything with that php file (runtime configured
* css file) nor with CSS on other websites, and order is important,you would notice that
* the output in your browser will looks something like:
*
<link href="/min/?f=/css/jquery.autocomplete.css" media="screen" rel="stylesheet" type="text/css" />
<link href="/css/project.css.php" media="screen" rel="stylesheet" type="text/css" />
<link href="/min/?f=/templates/main.css,/js/jquery.alerts.css,/js/jqModal.css" media="screen"
rel="stylesheet" type="text/css" />
<link href="http://example.com/js/sample.css" media="screen" rel="stylesheet" type="text/css" />
<link href="/favicon.ico" rel="shortcut icon" />
<!--[if lt IE 7]> <link href="/css/ie6.css" media="screen" rel="stylesheet" type="text/css" /><![endif]-->
mmm , sorry i don't have examples but i can help you explaining how ,
my simple workflow would be like this :
1- as a view helper
in your custom library folder , create a class that extends the static function of my_minify::minify()
you may create a viewhelper that override the functionality of both headLink() and minfy class
2- as a plugin :
you might create plugin that runs postdispatch to minify the whole resulting view , more explanation