Archive for the ‘PHP’ Category

I'm not a designer, and I've never been happy with the color scheme for kavanot.name, so I decided to try something new. I use scssphp with scssphp server (and yes, you need both) to generate the CSS, and it allows variables, so I can do:


$hue = 250;
$saturation = '40%';
$textcolor: hsl($hue, $saturation, 10%);
$displaytextcolor: hsl($hue, $saturation, 40%);
$backcolor: hsl($hue, $saturation, 95%);
$bordercolor: hsl($hue, $saturation, 90%);
$deepbackcolor: hsl($hue, $saturation, 85%);

to make all the colors a variation on one single color. And I like that. But what color? Fortunately, scssphp allows for importing variables, so my `styles.php` includes:


setcookie('hue', $_GET['hue'], time()+12*60*60*24*30, '/'); // one year expiration
setcookie('saturation', $_GET['saturation'], time()+12*60*60*24*30, '/'); // one year expiration

$scss = new Compiler();
$scss->setVariables(array(
	'hue' => intval($_GET['hue']),
	'saturation' => intval($_GET['saturation']).'%',
));

instead of the in-file $hue = 250; $saturation = '40%';. Now I can include those values in the URL for the stylesheet itself,
and the cookies mean that I don't have to write it each time.

To get the query variables to the stylesheet, I have to pass them from the main page:


	<link rel="stylesheet" id=mainstylesheet type="text/css" href="/css/style.php/style.scss?hue=<?=$_GET['hue']?>&saturation=<?=$_GET['saturation']?>" />

(I am ignoring the input sanitization you need to avoid XSS. intval is your friend here!)

But retyping http://kavanot.name?hue=250&saturation=40 with minor variations is a pain. Can I make it interactive? Yes, with a Javascript
color picker. <input type=color /> is supported in all the major browsers, so I can do:


var colorPicker = $('input[type=color]');
let baseHSL = getBaseHSL(); // get the body color as [hue, saturation, lightness]
baseHSL[2] = 50; // start with a color that can be easily seen, 50% lightness
colorPicker.val(hsl2hex(...baseHSL)); // set the initial value

const stylesheet = $('#mainstylesheet')[0];

colorPicker.on('input', function(){
	let [h, s] = hex2hsl(this.value);
	// change the stylesheet href and the styles change on the fly!
	stylesheet.href = stylesheet.href.replace(/hue=\d+/, `hue=${h}`).replace(/saturation=\d+/, `saturation=${s}`);
});

function getBaseHSL(){
	// getComputedStyle.color is rgb(r,g,b)
	let [, r, g, b] = /(\d+),\s*(\d+),\s*(\d+)/.exec(window.getComputedStyle(document.body).color);
	return rgb2hsl (r, g, b);
}

Now I need the functions rgb2hsl, hex2hsl and hsl2hex to convert between the various color spaces. Luckily,
Jon Kantner has done the work for me.


function hex2hsl(hex){
	let [, r, g, b] = /#(..)(..)(..)/.exec(hex);
	return rgb2hsl (+('0x'+r), +('0x'+g), +('0x'+b));
}
	
function rgb2hsl (r, g, b){
	// convert to fractions
	r = r/255;
	b = b/255;
	g = g/255;

	// find greatest and smallest channel values
	const cmin = Math.min(r,g,b);
	const cmax = Math.max(r,g,b);
	const delta = cmax - cmin;
	let h;

	// calculate hue
	if (delta == 0){ // no difference
		h = 0;
	}else if (cmax == r){ // red is max
		h = ((g - b) / delta) % 6;
	}else if (cmax == g){
		h = (b - r) / delta + 2; // green is max
	}else{
		h = (r - g) / delta + 4; // blue is max
	}
	h = Math.round(h * 60);
	if (h < 0) h += 360;

	// calculate lightness
	let l = (cmax + cmin) / 2;

	// calculate saturation
	let s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));

	// make s and l percents
	return [h, Math.round(s*100), Math.round(l*100)];
}

function hsl2hex(h, s, l){
	let c = (1 - Math.abs(2 * l/100 - 1)) * s/100;
	let x = c * (1 - Math.abs((h / 60) % 2 - 1));
	let m = l/100 - c/2;
	let r, g, b;
	
	if (0 <= h && h < 60) {
		r = c; g = x; b = 0;
	} else if (60 <= h && h < 120) {
		r = x; g = c; b = 0;
	} else if (120 <= h && h < 180) {
		r = 0; g = c; b = x;
	} else if (180 <= h && h < 240) {
		r = 0; g = x; b = c;
	} else if (240 <= h && h < 300) {
		r = x; g = 0; b = c;
	} else if (300 <= h && h < 360) {
		r = c; g = 0; b = x;
	}

	r = Math.round((r + m) * 255);
	g = Math.round((g + m) * 255);
	b = Math.round((b + m) * 255);
	
	return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}

function toHex(d) {
	d = d.toString(16);
	if (d.length == 1) d = `0${d}`;
	return d.toUpperCase();
}

and now I can play with colors to my heart's content!

Markdown Extra (including Parsedown Extra) allows for attributes to be applied to certain elements: headers, fenced code blocks, links, and images. I'd like to be able to apply them to any element. I'm going to use the syntax of Python Markdown attributes, but the attribute lists go before the elements. For block level elements, they go on their own line before the element.

My attribute lists start with {: (not just {) and end with }. Anything that would be legal in HTML (as is fine, since I didn't write my own parser. I just used DOMDocument. There are three special cases:

  • .foo is changed to class="foo". Note that this is different from the Python code, which appends class names that start with .. Repeated attribute names in actual HTML are ignored, so to use two classes, use class="foo bar", not .foo .bar.
  • #foo is changed to id="foo".
  • Two letters alone are changed to lang=xx, since I use that attribute so much.

Continue reading ‘Extending Parsedown: attributes’ »

Continuing work on extending Parsedown.

See the actual code.

Adding block-level elements is not much different from adding inline elements. Kavanot.name originally used <footer> elements to indicate the source of a block quote:

<blockquote>
    Do or do not. There is no try.
  </blockquote>
  <footer>Yoda</footer>
</blockquote>

While marking blockquotes this way is now acceptable, for a long time it wasn't, and the recommended way was with <figure> and <figcaption>. KavanotParsedown uses the latter model:

<figure>
  <blockquote>
    Do or do not. There is no try.
  <figcaption>Yoda</figcaption>
<figure>

But I start with creating a <footer> and then modifying the DOM. So I want to have a block element that I will indicate with "--" at the start of the line.

function __construct(){
  $this->BlockTypes['-'][] = 'Source'; // only line needed to indicate a block level element
  // ... rest of the constructor
}

protected function blockSource($Line, $Block = null){
  if (preg_match('/^--[ ]*(.+)/', $Line['text'], $matches)) {
    return array(
      'element' => array(
        'name' => 'footer',
        'handler' => array(
          'function' => 'lineElements',
          'argument' => $matches[1],
          'destination' => 'elements'
        ),
        'attributes' => array('class' => 'source') // for styling, add a class automatically
      )
    );
  }
}

so

>Do or do not. There is no try.
--Yoda

becomes

<blockquote>
    Do or do not. There is no try.
  <footer class="source" >Yoda</footer>
</blockquote>

I realized that I might want to add an attribution to an image as well, without it being in a <blockquote>, as

<p>
  <img src=/blog/blogfiles/pdf/smiley.png alt="Smile!"/>
  <footer class="source" >Some file I found on the web</footer>
</p>

But as it stands,

[Smile!](/blog/blogfiles/pdf/smiley.png)
--Some file I found on the web

doesn't work; the <p> ends before the <footer> starts:

<p>
  <img src=/blog/blogfiles/pdf/smiley.png alt="Smile!"/>
</p>
<footer class="source" >Some file I found on the web</footer>

so we need to check that the previous block wasn't a paragraph. If it was, then parse this line and add it to the paragraph as an internal element:

protected function blockSource($Line, $Block = null){
  if (preg_match('/^--[ ]*(.+)/', $Line['text'], $matches)) {
    if ($Block && $Block['type'] === 'Paragraph'){
      $Block['element']['handler']['argument'] .= "\n".$this->element($this->blockSource($Line)['element']);
      return $Block;
    }
    return array(
      'element' => array(
        'name' => 'footer',
        'handler' => array(
          'function' => 'lineElements',
          'argument' => $matches[1],
          'destination' => 'elements'
        ),
        'attributes' => array('class' => 'source') // for styling, add a class automatically
      )
    );
  }
}

and now it works, except that the footer is a child of the <p> instead of the <blockquote>. We'll have to fix that.

Extending Parsedown involves adding elements to the $InlineTypes and $BlockTypes arrays, then adding methods to handle them.

See the actual code.

Italics

I use <i> a lot, to indicate transliterated words. So I use could use "/" to indicate that:
/Shabbat/ is a Hebrew word becomes <i>Shabbat</i> is a Hebrew word. To do that:
do

class myParsedown extends Parsedown{
  function __construct(){
    $this->InlineTypes['/'] []= 'Italic';
    // after adding all the new inline types, create the list of characters
    $this->inlineMarkerList = implode ('', array_keys($this->InlineTypes));
    // allow the character to be escaped by '\'
    $this->specialCharacters []= '/';
  }

  protected function inlineItalic($excerpt){
    if (preg_match('#^/(.+?)/#', $excerpt['text'], $matches)) {
      return array(
        'extent' => strlen($matches[0]), 
        'element' => array(
          'name' => 'i',
          'handler' => array(
            'function' => 'lineElements',
            'argument' => $matches[1],
            'destination' => 'elements'
          )
        )
      );
    }
}

Now, my transliterated words are almost always Hebrew, so I can automatically add the lang=he attribute:


  protected function inlineItalic($excerpt){
    if (preg_match('#^/(.+?)/#', $excerpt['text'], $matches)) {
      return array(
        'extent' => strlen($matches[0]), 
        'element' => array(
          'name' => 'i',
          'handler' => array(
            'function' => 'lineElements',
            'argument' => $matches[1],
            'destination' => 'elements'
          ),
          'attributes' => array('lang' => 'he') // Add attributes
        )
      );
    }
}

and now /Shabbat/ is a Hebrew word becomes <i lang=he>Shabbat</i> is a Hebrew word.

Cite

I also use the <cite>. I'm running out of single characters to indicate elements, so I'm going to redefine "-". I don't need two different markers for <em>.

  function __construct(){
    $this->InlineTypes['_'] = ['Cite']; // redefinition; I am replacing the old array (which was ['Emphasis'])
    // ... rest of the constructor as above
  }

  protected function inlineCite($excerpt){
    if (preg_match('#^_(.+?)_#', $excerpt['text'], $matches)) {
      return array(
        'extent' => strlen($matches[0]), 
        'element' => array(
          'name' => 'cite',
          'handler' => array(
            'function' => 'lineElements',
            'argument' => $matches[1],
            'destination' => 'elements'
          )
        )
      );
    }

And now _A Tale of Two Cities_ becomes <cite>A Tale of Two Cities</cite>

Working with Parsedown, I want to string manipulation but only in certain parts. For instance, on text not in HTML tags or not in quotes. The right way to do that is with a real parser. The easy way is by removing the unwanted strings, replacing them with a marker that won't come up in normal text, doing the manipulation, then replacing the markers (it is the replacement step that requires "a marker that won't come up in normal text"; you don't want to replace text that was present in the original).

I would use a marker that can't be typed but still is legal HTML; turns out that U+FFFC (OBJECT REPLACEMENT CHARACTER, ) is perfect for that. So I made a pair of functions, `StringReplace\remove` and `StringReplace\restore` to make that easy.

StringReplace\remove ($re, $target)
Any string that matches the regular expression $re in $target is replaced by a numbered marker, "{number}". The new string is returned. So for instance,

$rawtext = StringReplace\remove ('#</?[^>]*>#', $html);

will remove tags.

StringReplace\restore ($target)
Returns a string with the markers replaced by their original versions.

The code

namespace StringReplace;

define ('OBJECT_REPLACEMENT_CHARACTER', '');
define ('RE_REPLACEMENT', '/'.OBJECT_REPLACEMENT_CHARACTER.'(\d+)'.OBJECT_REPLACEMENT_CHARACTER.'/');

$strings = array();

$remover = function ($matches){
  global $strings;
  $strings []= $matches[0];
  return OBJECT_REPLACEMENT_CHARACTER.count($strings).OBJECT_REPLACEMENT_CHARACTER;
};

$replacer = function ($matches){
  global $strings;
  return $strings[$matches[1]-1];
};

function remove ($re, $target){
  global $remover;
  return preg_replace_callback ($re, $remover, $target);
}

function restore ($target){
  global $replacer;
  return preg_replace_callback (RE_REPLACEMENT, $replacer, $target);
}

I've been spending all my intellectual free time on working on my Kavanot site, so I haven't been doing any independent programming. But that site uses raw HTML, which is a pain to type. So I decided to start using Markdown to make writing easier. After a little trial and error, I decided to use Parsedown with Parsedown Extra.

See the code.

Continue reading ‘Extending Parsedown’ »

Nearly Free Speech has been a great hosting service, and they upgrade the stack consistently, which usually doesn't cause problems. But with the most recent upgrade, I started getting the above error. Looks like they turned on compression at the server level, so doing it on each page is redundant.

Evidently they announced it on the blog, but I never keep up with that.

Changing all my ob_start('ob_gzhandler'); to ob_start(); fixes it. Hope this helps someone else.

I use the WordPress HTML editor exclusively, with the Text Control plugin with no formatting, so the posts contain exactly the markup I want. The editor comes with "Quick Tag" buttons, that let you insert HTML tags with one click, and allows you to add custom buttons. So, for all my code samples, I want to have a <pre> button, I just create a javascript file (say, called quicktags.custom.js):

QTags.addButton('pre', 'pre', '<pre>', '</pre>\n');

And include it with the following PHP either in a plugin or my theme functions.php:

if(is_admin()){
	// it's an admin page. Add the custom editor buttons
	wp_register_script('customeditor', plugins_url('quicktags.custom.js', __FILE__), array('quicktags')); // this script depends on 'quicktags'
	wp_enqueue_script('customeditor');
}

And now I have a button that inserts <pre></pre> pairs. But there's much more you can do.

Continue reading ‘Custom Buttons in the WordPress HTML Editor’ »

Michael Tyson had a cool idea: instead of the search results page showing an excerpt of the first words of the post, show an excerpt that contains the search terms and highlight them (say, by making them bold). I thought his method was too complex—it requires replacing your theme's search.php with a custom page, and it shows the context of every occurrence of the search terms. I thought it would be more straightforward to use the existing search page, which should be using the_excerpt to show an extract of the found page, and use the existing filters to change the text. Also, there's no need to show every occurrence of the search terms; the first ones should be fine.

Continue reading ‘Contextual Search Results in WordPress’ »

I wanted a way to highlight search terms on the search results page (the way search engines do), by surrounding the text with <span class=whatever></span>, but simply doing preg_replace($re, '<span class=whatever>$0</span>', $text) replaces things inside any HTML tags as well. There are some solutions on the web, but the simplest one I found involves generating a new regular expression. I'd like something I can pass in the regular expression to match and the replacement text, something exactly equivalent to preg_replace.

What I came up with:

function preg_replace_text ($pattern, $replacement, $subject){
	// only replace text that is not inside tags. Assumes $subject is valid HTML and surrounded by tags
	// the (?<=>) is a lookbehind assertion to make sure the match starts with a >
	return preg_replace_callback('/(?<=>)[^<]*/', function($matches) use ($pattern, $replacement) {
		return preg_replace($pattern, $replacement, $matches[0]);
	}, $subject);
}

Hope this is useful to someone.