nggMeta

Image METADATA PHP class for the WordPress plugin NextGEN Gallery nggmeta.lib.php.

Defined (1)

The class is defined in the following location(s).

/products/photocrati_nextgen/modules/ngglegacy/lib/meta.php  
  1. class nggMeta{ 
  2.  
  3. /**** Image Data ****/ 
  4. var $image = ''; // The image object 
  5. var $size = false; // The image size 
  6. var $exif_data = false; // EXIF data array 
  7. var $iptc_data = false; // IPTC data array 
  8. var $xmp_data = false; // XMP data array 
  9. /**** Filtered Data ****/ 
  10. var $exif_array = false; // EXIF data array 
  11. var $iptc_array = false; // IPTC data array 
  12. var $xmp_array = false; // XMP data array 
  13.  
  14. var $sanitize = false; // sanitize meta data on request 
  15.  
  16. /** 
  17. * Parses the nggMeta data only if needed 
  18. * @param int $image path to a image 
  19. * @param bool $onlyEXIF parse only exif if needed 
  20. * @return 
  21. */ 
  22. function __construct($image_or_id, $onlyEXIF = false) 
  23. if (is_int($image_or_id)) { 
  24. //get the path and other data about the image 
  25. $this->image = C_Image_Mapper::get_instance()->find( $image_or_id); 
  26. else $this->image = $image_or_id; 
  27.  
  28. $imagePath = C_Gallery_Storage::get_instance()->get_image_abspath($this->image); 
  29.  
  30. if ( !file_exists($imagePath ) ) 
  31. return false; 
  32.  
  33. $this->size = @getimagesize ( $imagePath , $metadata ); 
  34.  
  35. if ($this->size && is_array($metadata)) { 
  36.  
  37. // get exif - data 
  38. if ( is_callable('exif_read_data')) 
  39. $this->exif_data = @exif_read_data($imagePath , NULL, TRUE); 
  40.  
  41. // stop here if we didn't need other meta data 
  42. if ($onlyEXIF) 
  43. return true; 
  44.  
  45. // get the iptc data - should be in APP13 
  46. if ( is_callable('iptcparse') && isset($metadata['APP13']) ) 
  47. $this->iptc_data = @iptcparse($metadata['APP13']); 
  48.  
  49. // get the xmp data in a XML format 
  50. if ( is_callable('xml_parser_create')) 
  51. $this->xmp_data = $this->extract_XMP($imagePath ); 
  52.  
  53. return true; 
  54.  
  55. return false; 
  56.  
  57. /** 
  58. * return the saved meta data from the database 
  59. * @since 1.4.0 
  60. * @param string $object (optional) 
  61. * @return array|mixed return either the complete array or the single object 
  62. */ 
  63. function get_saved_meta($object = false) { 
  64.  
  65. $meta = $this->image->meta_data; 
  66.  
  67. if (!isset($meta['saved'])) $meta['saved'] = FALSE; 
  68.  
  69. //check if we already import the meta data to the database 
  70. if (!is_array($meta) || ($meta['saved'] != true)) 
  71. return false; 
  72.  
  73. // return one element if requested 
  74. if ($object) 
  75. return $meta[$object]; 
  76.  
  77. //removed saved parameter we don't need that to show 
  78. unset($meta['saved']); 
  79.  
  80. // and remove empty tags or arrays 
  81. foreach ($meta as $key => $value) { 
  82. if ( empty($value) OR is_array($value)) 
  83. unset($meta[$key]); 
  84.  
  85. // on request sanitize the output 
  86. if ( $this->sanitize == true ) 
  87. array_walk( $meta , create_function('&$value', '$value = esc_html($value);')); 
  88.  
  89. return $meta; 
  90.  
  91. /** 
  92. * nggMeta::get_EXIF() 
  93. * See also http://trac.wordpress.org/changeset/6313 
  94. * @return structured EXIF data 
  95. */ 
  96. function get_EXIF($object = false) { 
  97.  
  98. if ( !$this->exif_data ) 
  99. return false; 
  100.  
  101. if (!is_array($this->exif_array)) { 
  102.  
  103. $meta= array(); 
  104.  
  105. $exif = isset($this->exif_data['EXIF']) ? $this->exif_data['EXIF'] : array(); 
  106. if (count($exif)) { 
  107.  
  108. if (!empty($exif['FNumber'])) 
  109. $meta['aperture'] = 'F ' . round( $this->exif_frac2dec( $exif['FNumber'] ), 2 ); 
  110. if (!empty($exif['Model'])) 
  111. $meta['camera'] = trim( $exif['Model'] ); 
  112. if (!empty($exif['DateTimeDigitized'])) 
  113. $meta['created_timestamp'] = $this->exif_date2ts($exif['DateTimeDigitized']); 
  114. else if (!empty($exif['DateTimeOriginal'])) 
  115. $meta['created_timestamp'] = $this->exif_date2ts($exif['DateTimeOriginal']); 
  116. else if (!empty($exif['FileDateTime'])) 
  117. $meta['created_timestamp'] = $this->exif_date2ts($exif['FileDateTime']); 
  118. if (!empty($exif['FocalLength'])) 
  119. $meta['focal_length'] = $this->exif_frac2dec( $exif['FocalLength'] ) . __(' mm', 'nggallery'); 
  120. if (!empty($exif['ISOSpeedRatings'])) 
  121. $meta['iso'] = $exif['ISOSpeedRatings']; 
  122. if (!empty($exif['ExposureTime'])) { 
  123. $meta['shutter_speed'] = $this->exif_frac2dec ($exif['ExposureTime']); 
  124. $meta['shutter_speed'] =($meta['shutter_speed'] > 0.0 and $meta['shutter_speed'] < 1.0) ? ( '1/' . round( 1 / $meta['shutter_speed'], -1) ) : ($meta['shutter_speed']); 
  125. $meta['shutter_speed'] .= __(' sec', 'nggallery'); 
  126. //Bit 0 indicates the flash firing status 
  127. if (!empty($exif['Flash'])) 
  128. $meta['flash'] = ( $exif['Flash'] & 1 ) ? __('Fired', 'nggallery') : __('Not fired', ' nggallery'); 
  129.  
  130. // additional information 
  131. if ( isset($this->exif_data['IFD0']) ) { 
  132. $exif = $this->exif_data['IFD0']; 
  133.  
  134. if (!empty($exif['Model'])) 
  135. $meta['camera'] = $exif['Model']; 
  136. if (!empty($exif['Make'])) 
  137. $meta['make'] = $exif['Make']; 
  138. if (!empty($exif['ImageDescription'])) 
  139. $meta['title'] = $exif['ImageDescription']; 
  140. if (!empty($exif['Orientation'])) 
  141. $meta['Orientation'] = $exif['Orientation']; 
  142.  
  143. // this is done by Windows 
  144. if ( isset($this->exif_data['WINXP']) ) { 
  145. $exif = $this->exif_data['WINXP']; 
  146.  
  147. if (!empty($exif['Title']) && empty($meta['title'])) 
  148. $meta['title'] = $this->utf8_encode($exif['Title']); 
  149. if (!empty($exif['Author'])) 
  150. $meta['author'] = $this->utf8_encode($exif['Author']); 
  151. if (!empty($exif['Keywords'])) 
  152. $meta['tags'] = $this->utf8_encode($exif['Keywords']); 
  153. if (!empty($exif['Subject'])) 
  154. $meta['subject'] = $this->utf8_encode($exif['Subject']); 
  155. if (!empty($exif['Comments'])) 
  156. $meta['caption'] = $this->utf8_encode($exif['Comments']); 
  157.  
  158. $this->exif_array = $meta; 
  159.  
  160. // return one element if requested 
  161. if ( $object == true ) { 
  162. $value = isset($this->exif_array[$object]) ? $this->exif_array[$object] : false; 
  163. return $value; 
  164.  
  165. // on request sanitize the output 
  166. if ( $this->sanitize == true ) 
  167. array_walk( $this->exif_array , create_function('&$value', '$value = esc_html($value);')); 
  168.  
  169. return $this->exif_array; 
  170.  
  171.  
  172. // convert a fraction string to a decimal 
  173. function exif_frac2dec($str) { 
  174. @list( $n, $d ) = explode( '/', $str ); 
  175. if ( !empty($d) ) 
  176. return $n / $d; 
  177. return $str; 
  178.  
  179. // convert the exif date format to a unix timestamp 
  180. function exif_date2ts($str) 
  181. $retval = is_numeric($str) ? $str : @strtotime($str); 
  182. if (!$retval && $str) { 
  183. @list( $date, $time ) = explode( ' ', trim($str) ); 
  184. @list( $y, $m, $d ) = explode( ':', $date ); 
  185. $retval = strtotime( "{$y}-{$m}-{$d} {$time}" ); 
  186.  
  187. return $retval; 
  188.  
  189. /** 
  190. * nggMeta::readIPTC() - IPTC Data Information for EXIF Display 
  191. * @param mixed $output_tag 
  192. * @return IPTC-tags 
  193. */ 
  194. function get_IPTC($object = false) { 
  195.  
  196. if (!$this->iptc_data) 
  197. return false; 
  198.  
  199. if (!is_array($this->iptc_array)) { 
  200.  
  201. // --------- Set up Array Functions --------- // 
  202. $iptcTags = array ( 
  203. "2#005" => 'title',  
  204. "2#007" => 'status',  
  205. "2#012" => 'subject',  
  206. "2#015" => 'category',  
  207. "2#025" => 'keywords',  
  208. "2#055" => 'created_date',  
  209. "2#060" => 'created_time',  
  210. "2#080" => 'author',  
  211. "2#085" => 'position',  
  212. "2#090" => 'city',  
  213. "2#092" => 'location',  
  214. "2#095" => 'state',  
  215. "2#100" => 'country_code',  
  216. "2#101" => 'country',  
  217. "2#105" => 'headline',  
  218. "2#110" => 'credit',  
  219. "2#115" => 'source',  
  220. "2#116" => 'copyright',  
  221. "2#118" => 'contact',  
  222. "2#120" => 'caption' 
  223. ); 
  224.  
  225. $meta = array(); 
  226. foreach ($iptcTags as $key => $value) { 
  227. if (isset ( $this->iptc_data[$key] ) ) 
  228. $meta[$value] = trim($this->utf8_encode(implode(", ", $this->iptc_data[$key]))); 
  229.  
  230. $this->iptc_array = $meta; 
  231.  
  232. // return one element if requested 
  233. if ($object) 
  234. return (isset($this->iptc_array[$object])) ? $this->iptc_array[$object] : NULL; 
  235.  
  236. // on request sanitize the output 
  237. if ( $this->sanitize == true ) 
  238. array_walk( $this->iptc_array , create_function('&$value', '$value = esc_html($value);')); 
  239.  
  240. return $this->iptc_array; 
  241.  
  242. /** 
  243. * nggMeta::extract_XMP() 
  244. * get XMP DATA 
  245. * code by Pekka Saarinen http://photography-on-the.net 
  246. * @param mixed $filename 
  247. * @return XML data 
  248. */ 
  249. function extract_XMP( $filename ) { 
  250.  
  251. //TODO:Require a lot of memory, could be better 
  252. ob_start(); 
  253. @readfile($filename); 
  254. $source = ob_get_contents(); 
  255. ob_end_clean(); 
  256.  
  257. $start = strpos( $source, "<x:xmpmeta" ); 
  258. $end = strpos( $source, "</x:xmpmeta>" ); 
  259. if ((!$start === false) && (!$end === false)) { 
  260. $lenght = $end - $start; 
  261. $xmp_data = substr($source, $start, $lenght+12 ); 
  262. unset($source); 
  263. return $xmp_data; 
  264.  
  265. unset($source); 
  266. return false; 
  267.  
  268. /** 
  269. * nggMeta::get_XMP() 
  270. * @package Taken from http://php.net/manual/en/function.xml-parse-into-struct.php 
  271. * @author Alf Marius Foss Olsen & Alex Rabe 
  272. * @return XML Array or object 
  273. */ 
  274. function get_XMP($object = false) { 
  275.  
  276. if(!$this->xmp_data) 
  277. return false; 
  278.  
  279. if (!is_array($this->xmp_array)) { 
  280.  
  281. $parser = xml_parser_create(); 
  282. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); // Dont mess with my cAsE sEtTings 
  283. xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); // Dont bother with empty info 
  284. xml_parse_into_struct($parser, $this->xmp_data, $values); 
  285. xml_parser_free($parser); 
  286.  
  287. $xmlarray = array(); // The XML array 
  288. $this->xmp_array = array(); // The returned array 
  289. $stack = array(); // tmp array used for stacking 
  290. $list_array = array(); // tmp array for list elements 
  291. $list_element = false; // rdf:li indicator 
  292.  
  293. foreach($values as $val) { 
  294.  
  295. if($val['type'] == "open") { 
  296. array_push($stack, $val['tag']); 
  297.  
  298. } elseif($val['type'] == "close") { 
  299. // reset the compared stack 
  300. if ($list_element == false) 
  301. array_pop($stack); 
  302. // reset the rdf:li indicator & array 
  303. $list_element = false; 
  304. $list_array = array(); 
  305.  
  306. } elseif($val['type'] == "complete") { 
  307. if ($val['tag'] == "rdf:li") { 
  308. // first go one element back 
  309. if ($list_element == false) 
  310. array_pop($stack); 
  311. $list_element = true; 
  312. // do not parse empty tags 
  313. if ( empty($val['value']) ) continue; 
  314. // save it in our temp array 
  315. $list_array[] = $val['value']; 
  316. // in the case it's a list element we seralize it 
  317. $value = implode(", ", $list_array); 
  318. $this->setArrayValue($xmlarray, $stack, $value); 
  319. } else { 
  320. array_push($stack, $val['tag']); 
  321. // do not parse empty tags 
  322. if ( !empty($val['value']) ) 
  323. $this->setArrayValue($xmlarray, $stack, $val['value']); 
  324. array_pop($stack); 
  325.  
  326. } // foreach 
  327.  
  328. // don't parse a empty array 
  329. if( empty($xmlarray) || empty($xmlarray['x:xmpmeta']) ) 
  330. return false; 
  331.  
  332. // cut off the useless tags 
  333. $xmlarray = $xmlarray['x:xmpmeta']['rdf:RDF']['rdf:Description']; 
  334.  
  335. // --------- Some values from the XMP format--------- // 
  336. $xmpTags = array ( 
  337. 'xap:CreateDate' => 'created_timestamp',  
  338. 'xap:ModifyDate' => 'last_modfied',  
  339. 'xap:CreatorTool' => 'tool',  
  340. 'dc:format' => 'format',  
  341. 'dc:title' => 'title',  
  342. 'dc:creator' => 'author',  
  343. 'dc:subject' => 'keywords',  
  344. 'dc:description' => 'caption',  
  345. 'photoshop:AuthorsPosition' => 'position',  
  346. 'photoshop:City' => 'city',  
  347. 'photoshop:Country' => 'country' 
  348. ); 
  349.  
  350. foreach ($xmpTags as $key => $value) { 
  351. // if the kex exist 
  352. if ( isset($xmlarray[$key]) ) { 
  353. switch ($key) { 
  354. case 'xap:CreateDate': 
  355. case 'xap:ModifyDate': 
  356. $this->xmp_array[$value] = $this->exif_date2ts($xmlarray[$key]); 
  357. break; 
  358. default : 
  359. $this->xmp_array[$value] = $xmlarray[$key]; 
  360.  
  361.  
  362. // return one element if requested 
  363. if ($object != false ) 
  364. return isset($this->xmp_array[$object]) ? $this->xmp_array[$object] : false; 
  365.  
  366. // on request sanitize the output 
  367. if ( $this->sanitize == true ) 
  368. array_walk( $this->xmp_array , create_function('&$value', '$value = esc_html($value);')); 
  369.  
  370. return $this->xmp_array; 
  371.  
  372. function setArrayValue(&$array, $stack, $value) { 
  373. if ($stack) { 
  374. $key = array_shift($stack); 
  375. $this->setArrayValue($array[$key], $stack, $value); 
  376. return $array; 
  377. } else { 
  378. $array = $value; 
  379.  
  380. /** 
  381. * nggMeta::get_META() - return a meta value form the available list 
  382. * @param string $object 
  383. * @return mixed $value 
  384. */ 
  385. function get_META($object = false) { 
  386.  
  387. // defined order first look into database, then XMP, IPTC and EXIF. 
  388. if ($value = $this->get_saved_meta($object)) 
  389. return $value; 
  390. if ($value = $this->get_XMP($object)) 
  391. return $value; 
  392. if ($object == 'created_timestamp' && ($d = $this->get_IPTC('created_date')) && ($t = $this->get_IPTC('created_time'))) { 
  393. return $this->exif_date2ts($d . ' '.$t); 
  394. if ($value = $this->get_IPTC($object)) 
  395. return $value; 
  396. if ($value = $this->get_EXIF($object)) 
  397. return $value; 
  398.  
  399. // nothing found ? 
  400. return false; 
  401.  
  402. /** 
  403. * nggMeta::i8n_name() - localize the tag name 
  404. * @param mixed $key 
  405. * @return translated $key 
  406. */ 
  407. function i18n_name($key) { 
  408.  
  409. $tagnames = array( 
  410. 'aperture' => __('Aperture', 'nggallery'),  
  411. 'credit' => __('Credit', 'nggallery'),  
  412. 'camera' => __('Camera', 'nggallery'),  
  413. 'caption' => __('Caption', 'nggallery'),  
  414. 'created_timestamp' => __('Date/Time', 'nggallery'),  
  415. 'copyright' => __('Copyright', 'nggallery'),  
  416. 'focal_length' => __('Focal length', 'nggallery'),  
  417. 'iso' => __('ISO', 'nggallery'),  
  418. 'shutter_speed' => __('Shutter speed', 'nggallery'),  
  419. 'title' => __('Title', 'nggallery'),  
  420. 'author' => __('Author', 'nggallery'),  
  421. 'tags' => __('Tags', 'nggallery'),  
  422. 'subject' => __('Subject', 'nggallery'),  
  423. 'make' => __('Make', 'nggallery'),  
  424. 'status' => __('Edit Status', 'nggallery'),  
  425. 'category' => __('Category', 'nggallery'),  
  426. 'keywords' => __('Keywords', 'nggallery'),  
  427. 'created_date' => __('Date Created', 'nggallery'),  
  428. 'created_time' => __('Time Created', 'nggallery'),  
  429. 'position' => __('Author Position', 'nggallery'),  
  430. 'city' => __('City', 'nggallery'),  
  431. 'location' => __('Location', 'nggallery'),  
  432. 'state' => __('Province/State', 'nggallery'),  
  433. 'country_code' => __('Country code', 'nggallery'),  
  434. 'country' => __('Country', 'nggallery'),  
  435. 'headline' => __('Headline', 'nggallery'),  
  436. 'credit' => __('Credit', 'nggallery'),  
  437. 'source' => __('Source', 'nggallery'),  
  438. 'copyright' => __('Copyright Notice', 'nggallery'),  
  439. 'contact' => __('Contact', 'nggallery'),  
  440. 'last_modfied' => __('Last modified', 'nggallery'),  
  441. 'tool' => __('Program tool', 'nggallery'),  
  442. 'format' => __('Format', 'nggallery'),  
  443. 'width' => __('Image Width', 'nggallery'),  
  444. 'height' => __('Image Height', 'nggallery'),  
  445. 'flash' => __('Flash', 'nggallery') 
  446. ); 
  447.  
  448. if ( isset($tagnames[$key]) ) 
  449. $key = $tagnames[$key]; 
  450.  
  451. return($key); 
  452.  
  453.  
  454. /** 
  455. * Return the Timestamp from the image , if possible it's read from exif data 
  456. * @return int 
  457. */ 
  458. function get_date_time() { 
  459.  
  460. $date = time(); 
  461.  
  462. $date = $this->exif_date2ts($this->get_META('created_timestamp')); 
  463. if (!$date) { 
  464. $image_path = C_Gallery_Storage::get_instance()->get_backup_abspath($this->image); 
  465. $date = @filectime($image_path); 
  466.  
  467. // Failback 
  468. if (!$date) $date = time(); 
  469.  
  470. // Return the MySQL format 
  471. $date_time = date( 'Y-m-d H:i:s', $date); 
  472.  
  473. return $date_time; 
  474.  
  475. /** 
  476. * This function return the most common metadata, via a filter we can add more 
  477. * Reason : GD manipulation removes that options 
  478. * @since V1.4.0 
  479. * @return void 
  480. */ 
  481. function get_common_meta() { 
  482. global $wpdb; 
  483.  
  484. $meta = array( 
  485. 'aperture' => 0,  
  486. 'credit' => '',  
  487. 'camera' => '',  
  488. 'caption' => '',  
  489. 'created_timestamp' => 0,  
  490. 'copyright' => '',  
  491. 'focal_length' => 0,  
  492. 'iso' => 0,  
  493. 'shutter_speed' => 0,  
  494. 'flash' => 0,  
  495. 'title' => '',  
  496. 'keywords' => '' 
  497. ); 
  498.  
  499. $meta = apply_filters( 'ngg_read_image_metadata', $meta ); 
  500.  
  501. // meta should be still an array 
  502. if ( !is_array($meta) ) 
  503. return false; 
  504.  
  505. foreach ($meta as $key => $value) { 
  506. $meta[$key] = $this->get_META($key); 
  507.  
  508. //let's add now the size of the image 
  509. $meta['width'] = $this->size[0]; 
  510. $meta['height'] = $this->size[1]; 
  511.  
  512. return $meta; 
  513.  
  514. /** 
  515. * If needed sanitize each value before output 
  516. * @return void 
  517. */ 
  518. function sanitize () { 
  519. $this->sanitize = true; 
  520.  
  521. /** 
  522. * Wrapper to utf8_encode() that avoids double encoding 
  523. * Regex adapted from http://www.w3.org/International/questions/qa-forms-utf-8.en.php 
  524. * to determine if the given string is already UTF-8. mb_detect_encoding() is not 
  525. * always available and is limited in accuracy 
  526. * @param string $str 
  527. * @return string 
  528. */ 
  529. function utf8_encode($str) 
  530. $is_utf8 = preg_match( 
  531. '%^(?: 
  532. [\x09\x0A\x0D\x20-\x7E] # ASCII 
  533. | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte 
  534. | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs 
  535. | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte 
  536. | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates 
  537. | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 
  538. | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 
  539. | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 
  540. )*$%xs', $str); 
  541. if (!$is_utf8) 
  542. utf8_encode($str); 
  543. return $str;