C_NextGen_Metadata

The NextGEN Gallery C NextGen Metadata class.

Defined (1)

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

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