function smd_qr_code($atts, $thing=NULL) { extract(lAtts(array( 'type' => 'text', // text/link/bookmark/sms/phone/email/contact/geo/wifi 'data' => 'Hello world!', 'delim' => '|', 'layout' => '', 'transform' => '', 'validate' => '1', // 0=no, 1=yes, or 'validate,these,fields' or 'validate, these|^[0-9. \-]+$, fields 'validelim' => ',', 'format' => 'view', // view/download/raw 'prefix' => 'SMD_QR_', 'width' => '200', 'height' => '200', 'quality' => 'L', // L/M/Q/H 'margin' => '4', 'bgcol' => '', 'alpha' => '', 'escape' => '', 'title' => '', 'class' => '', 'html_id' => '', 'wraptag' => '', 'debug' => 0, ),$atts)); // Default layouts $layouts = array( 'text' => '{text}', 'link' => '{url}', 'bookmark' => 'MEBKM:TITLE:{title};URL:{url};;', 'sms' => 'SMSTO:{phone}:{text}', 'phone' => 'TEL:{phone}', 'contact' => 'MECARD:N:{name};ADR:{address};TEL:{phone};EMAIL:{email};;', 'vcard' => 'BEGIN:VCARD\r\nN:{'.$prefix.'formatted_name}\r\nFN:{name}\r\nEMAIL:{email}\r\nURL:{url}\r\nADR:{address}\r\nTEL:{phone}\r\nEND:VCARD', 'email' => 'MATMSG:TO:{email};SUB:{subject};BODY:{message};;', 'geo' => 'GEO:{lat},{lon},{height}', 'wifi' => 'WIFI:T:{wifi_protocol};S{ssid};{pass};;', ); // Default regexes $re_url = '@^(http://|https://).+@'; $re_email = "<^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$>"; $re_phone = '/^[0-9()\-+ ]+$/'; $re_msg = '/^.+$/'; $re_geo = '/^[0-9.\-]+$/'; $re_digits = '/^[0-9\+\-]+$/'; $re_wifi_protocol = '/^(WEP|WPA)$/'; // regex array: can be overridden on a case by case basis by the validate attribute $regexes = array( 'text' => $re_msg, 'url' => $re_url, 'title' => $re_msg, 'phone' => $re_phone, 'name' => $re_msg, 'address' => $re_msg, 'email' => $re_email, 'subject' => $re_msg, 'message' => $re_msg, 'lat' => $re_geo, 'lon' => $re_geo, 'height' => $re_digits, 'wifi_protocol' => $re_wifi_protocol, 'ssid' => $re_msg, 'pass' => $re_msg, ); // Override / new layout if ($layout) { $layouts[$type] = $layout; } // Build the array of permitted types // Each entry links the 'type' attribute with an array of expected attributes. // The regex to use for each can be looked up in the $regexes array foreach($layouts as $lay => $def) { preg_match_all('/\{((?!'.$prefix.').*?)\}/', $def, $matches); $allowed_types[$lay] = $matches[1]; // As a convenience, if any new fields are defined, set a default (wide-open) regex for each // These default regexes can be overriden by the next code block if ($layout) { foreach ($matches[1] as $newfield) { if(!isset($regexes[$newfield])) { $regexes[$newfield] = $re_msg; } } } } // Build the list of fields to validate and inject / overwrite any custom regexes $validRest = false; if ($validate == '0') { $toValidate = array(); } if ($validate == '1') { $toValidate[] = 'SMD_ALL'; } else { $fields = do_list($validate, $validelim); foreach ($fields as $fieldinfo) { $fieldParts = explode($delim, $fieldinfo); if ($fieldParts[0] == '1') { $validRest = true; } else { $toValidate[] = $fieldParts[0]; if (isset($fieldParts[1])) { if (strpos($fieldParts[1], 'SMD_REGEX_') !== false) { $copyFromField = strtolower(str_replace('SMD_REGEX_', '', $fieldParts[1])); if (isset($regexes[$copyFromField])) { $regexes[$fieldParts[0]] = $regexes[$copyFromField]; } } else { $regexes[$fieldParts[0]] = $fieldParts[1]; } } } } if ($validRest && array_key_exists($type, $allowed_types)) { foreach($allowed_types[$type] as $fld) { if (!in_array($fld, $regexes)) { $toValidate[] = $fld; } } } } // Default transforms $transforms = array( '*.url' => 'linkify', 'vcard.name' => 'splitrevjoin|formatted_name| |;', ); // Add custom transform rules // New methods must use register_callback('my_custom_transform', 'smd_qr_code', 'add_transform'); // Function signature is: 1) data value to transform, 2) optional array of any other args packaged as name=>val entries $transform = do_list($transform); foreach ($transform as $xform) { if (empty($xform)) continue; $xformParams = do_list($xform, $delim); $xformid = array_shift($xformParams); $transforms[$xformid] = join($delim, $xformParams); } if ($debug) { echo '++ TRANSFORMATIONS ++'; dmp($transforms); } if (array_key_exists($type, $allowed_types)) { $qrc = new smd_qrcode($width, $height, $quality, $margin, $bgcol, $alpha, $escape); $params = smd_qr_code_extract($allowed_types[$type], $transforms, $regexes, $toValidate, $data, $delim, $type, $prefix, $debug); if ($params === false) { return ''; } else { // Run the function that prepares the data object if (array_key_exists($type, $layouts)) { $qrc->get_data($layouts[$type], $params, $debug); } else { trigger_error(smd_qr_code_gTxt('smd_qr_code_invalid_layout', array('{type}' => $type)), E_USER_NOTICE); return ''; } } if ($format == 'view') { $tribs = ''; if (!$wraptag) { $tribs .= ($class) ? ' class="'.$class.'"' : ''; $tribs .= ($html_id) ? ' id="'.$html_id.'"' : ''; $tribs .= ($title) ? ' title="'.$title.'"' : ''; } $content = 'QR code'; return ($wraptag) ? doTag($content, $wraptag, $class, '', $html_id) : $content; } elseif ($format == 'download') { $file = $qrc->get_image(); $qrc->download_image($file); return ''; } elseif ($format == 'raw') { $content = $qrc->get_link(); return ($wraptag) ? doTag($content, $wraptag, $class, '', $html_id) : $content; } } else { trigger_error(smd_qr_code_gTxt('smd_qr_code_invalid_type', array('{type}' => $type)), E_USER_NOTICE); return ''; } } //------------------------ function smd_qr_code_extract($permitted, $transforms, $regexList, $validateList, $data, $dlm, $tag, $prefix, $debug=false) { $opts = do_list($data, $dlm); $opts = (empty($opts[0])) ? array() : $opts; $xformlist = new smd_qr_code_transforms(); $out = array(); $count = 0; if ($debug) { echo '++ PERMITTED ITEMS / SUPPLIED ITEMS / REGEXES / FIELDS TO VALIDATE ++'; dmp($permitted, $opts, $regexList, $validateList); } if (count($permitted) != count($opts)) { trigger_error(smd_qr_code_gTxt('smd_qr_code_attribute_mismatch', array('{type}' => $tag, '{count}' => count($opts), '{expected}' => count($permitted), '{fields}' => join(', ', $permitted))), E_USER_NOTICE); return false; } foreach ($permitted as $idx) { if (isset($opts[$count])) { $dataval = $opts[$count]; if ($dataval == '{smd_empty}') { $out[$idx] = ''; } else { // Perform any transformations $apply_to = $tag.'.'.$idx; $xkey = ''; if (array_key_exists($apply_to, $transforms)) { $xkey = $apply_to; } else if (array_key_exists('*.'.$idx, $transforms)) { $xkey = '*.'.$idx; } if ($xkey) { $xform = $transforms[$xkey]; $xformVals = do_list($xkey, '.'); $xformArgs = explode($dlm, $xform); $fn = array_shift($xformArgs); if ($idx == $xformVals[1]) { // Pass the remaining args to the nominated function... $result = $xformlist->execute($fn, $dataval, $xformArgs); // .. and either override the current value or add new ones if (is_array($result)) { foreach($result as $inarg => $inval) { if ($inarg == $idx) { $dataval = $inval; } else { // Inject new tags created by transforms directly into the outstream $out[$prefix.$inarg] = $inval; } } } else { $dataval = $result; } } } // Perform validation if required if (in_array('SMD_ALL', $validateList) || in_array($idx, $validateList)) { $re = isset($regexList[$idx]) ? $regexList[$idx] : ''; if ($re) { if ($debug > 1) { dmp('VALIDATING ' . $idx . ' using ' . $re); } if (preg_match($re, $dataval)) { $out[$idx] = $dataval; } else { trigger_error(smd_qr_code_gTxt('smd_qr_code_invalid_attribute', array('{attrib}' => $idx, '{tag}' => $tag)), E_USER_NOTICE); return false; } } else { $out[$idx] = $dataval; trigger_error(smd_qr_code_gTxt('smd_qr_code_no_regex', array('{attrib}' => $idx, '{tag}' => $tag)), E_USER_WARNING); } } else { // Not validating, so store it verbatim if ($debug > 1) { dmp('NOT VALIDATING ' . $idx); } $out[$idx] = $dataval; } } } else { // TODO: erm...? } $count++; } return $out; } //------------------------ function smd_qr_code_gTxt($what, $atts = array()) { $lang = array( 'smd_qr_code_attribute_mismatch' => "Number of parameters don't match in '{type}' QR code: given {count}, expecting {expected} (Fields: {fields})", 'smd_qr_code_invalid_attribute' => "Invalid attribute '{attrib}' supplied in '{tag}' QR code. Check the format and order of data items", 'smd_qr_code_invalid_layout' => "Invalid QR code layout '{type}'", 'smd_qr_code_invalid_transform' => "Invalid or unregistered transform function '{fn}' -- ignoring", 'smd_qr_code_invalid_type' => "Invalid QR code type '{type}'", 'smd_qr_code_no_regex' => "No regex found for '{attrib}' in '{tag}' QR code -- ignoring", 'smd_qr_code_too_large' => "QR code data request too large", ); return strtr($lang[$what], $atts); } //------------------------ class smd_qrcode { private $api = 'http://chart.apis.google.com/chart'; private $data; private $dimensions = ''; private $quality = ''; private $colour = ''; private $escape = ''; // Constructor public function smd_qrcode($width, $height, $qual, $margin, $bgcol, $alpha, $esc) { $this->escape = $esc; // Build API strings $w = (is_numeric($width)) ? $width : 200; $h = (is_numeric($height)) ? $height : 200; $this->dimensions = $w.'x'.$h; $colours = array(); if ($alpha) $colours[] = 'a,s,000000'.substr($alpha, -2); if ($bgcol) $colours[] = 'bg,s,'.$bgcol; $this->colour = ($colours) ? join('|', $colours) : ''; $qual = strtoupper($qual); $szopts[] = ($qual && in_array($qual, array('L', 'M', 'Q', 'H'))) ? $qual : 'L'; if (is_numeric($margin)) $szopts[] = $margin; $this->quality = join('|', $szopts); } // Create the data object from the given layout, plugging in the replacement variables and applying any transformations public function get_data($layout, $args, $debug=0) { // Create the replacements $reps = array(); foreach ($args as $arg => $val) { $reps['{'.$arg.'}'] = $val; } if ($debug) { echo '++ REPLACEMENTS ++'; dmp($reps); } $this->data = strtr($layout, $reps); // Barf if above roughly 2KB (the limit of the Charts API's GET request) if (strlen($this->data) > 2000) { trigger_error(smd_qr_code_gTxt('smd_qr_code_too_large'), E_USER_NOTICE); } } private function get_url_params() { $params = array( 'cht' => 'qr', 'chs' => $this->dimensions, 'chld' => $this->quality, 'chl' => (($this->escape == 'html') ? urlencode($this->data) : $this->data), ); // Colour is optional if ($this->colour) { $params['chf'] = $this->colour; } return http_build_query($params, '', '&'); } // get image public function get_image(){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->api); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $this->get_url_params()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $response = curl_exec($ch); curl_close($ch); return $response; } // get link for image public function get_link($follow=false){ return $this->api.'?'.$this->get_url_params(); } // force image download public function download_image($file){ header('Content-Description: File Transfer'); header('Content-Type: image/png'); header('Content-Disposition: attachment; filename=qr_code.png'); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); header('Content-Length: ' . filesize($file)); ob_clean(); flush(); echo $file; } } // *************** // Transformations // *************** //------------------------ class smd_qr_code_transforms { private $xforms = array( 'linkify', 'splitrevjoin', ); // Constructor : register callbacks and built-in transforms public function smd_qr_code_transforms() { global $plugin_callback; foreach ($plugin_callback as $cb) { if ($cb['event'] == 'smd_qr_code' && $cb['step'] == 'add_transform') { $this->xforms[] = $cb['function']; } } } // Run a registered custom function. Local functions trump externals public function execute($fn, $item, $args=array()) { if (in_array($fn, $this->xforms)) { if (method_exists($this, $fn)) { return $this->$fn($item, $args); } else if (is_callable($fn)) { if (empty($args)) { return $fn($item); } else { return $fn($item, $args); } } } trigger_error(smd_qr_code_gTxt('smd_qr_code_invalid_transform', array('{fn}' => $fn)), E_USER_WARNING); return $item; } // Prefix links public function linkify($item, $args=array()) { $outvar = (isset($args[0])) ? $args[0] : 'url'; if (preg_match('/^http:\/\//', $item) || preg_match('/^https:\/\//', $item)) { $outval = $item; } else { $outval = "http://".$item; } return array($outvar => $outval); } // Split a variable, reverse the elements and join them again public function splitrevjoin($item, $args=array()) { // $args: 0 = variable name, 1 = split char, 2 = join char if (count($args) == 3) { $outval = join($args[2], array_reverse(explode($args[1], $item))); return array($args[0] => $outval); } else { return $item; } } }