3 /***************************************************************************
5 * phpfspot, presents your F-Spot photo collection in Web browsers.
7 * Copyright (c) by Andreas Unterkircher
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 ***************************************************************************/
25 require_once "phpfspot_cfg.php";
26 require_once "phpfspot_db.php";
31 * this class contains the most functions which will to the major
39 * phpfspot configuration
47 * SQLite database handle to f-spot database
55 * SQLite database handle to phpfspot database
63 * Smarty template engine
64 * @link http://smarty.php.net smarty.php.net
65 * @see PHPFSPOT_TMPL()
79 * list of available, not-selected, tags
86 * true if runtime error occued
90 private $runtime_error = false;
93 * F-Spot database version
100 * class constructor ($cfg, $db, $cfg_db, $tmpl, $db_ver)
102 * this function will be called on class construct
103 * and will check requirements, loads configuration,
104 * open databases and start the user session
106 public function __construct()
109 * register PHPFSPOT class global
111 * @global PHPFSPOT $GLOBALS['phpfspot']
114 $GLOBALS['phpfspot'] =& $this;
116 $this->cfg = new PHPFSPOT_CFG;
118 /* verify config settings */
119 if($this->check_config_options()) {
123 /* set application name and version information */
124 $this->cfg->product = "phpfspot";
125 $this->cfg->version = "1.7";
126 $this->cfg->db_version = 2;
128 $this->sort_orders= array(
129 'date_asc' => 'Date ↑',
130 'date_desc' => 'Date ↓',
131 'name_asc' => 'Name ↑',
132 'name_desc' => 'Name ↓',
133 'tags_asc' => 'Tags ↑',
134 'tags_desc' => 'Tags ↓',
137 /* Check necessary requirements */
138 if(!$this->check_requirements()) {
142 /******* Opening F-Spot's sqlite database *********/
144 /* Check if database file exists and is readable */
145 if(!file_exists($this->cfg->fspot_db) || !is_readable($this->cfg->fspot_db)) {
146 print "Error: ". $this->cfg->fspot_db ." does not exist or is not readable for user ". $this->getuid() .".\n";
150 /* Check if database file is writeable */
151 if(!is_writeable($this->cfg->fspot_db)) {
152 print "Error: ". $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() .".\n";
153 print "Please fix permissions so phpfspot can create indices within the F-Spot database to"
154 ." speed up some database operations.\n";
158 /* open the database */
159 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
161 /* change sqlite temp directory, if requested */
162 if(isset($this->cfg->sqlite_temp_dir)) {
165 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
169 /* get F-Spot database version */
170 $this->dbver = $this->getFspotDBVersion();
172 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
173 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
177 if(!is_writeable($this->cfg->thumb_path)) {
178 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
182 /******* Opening phpfspot's sqlite database *********/
184 /* Check if directory where the database file is stored is writeable */
185 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
186 print "Error: ". dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() .".\n";
187 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
191 /* Check if database file is writeable */
192 if(file_exists($this->cfg->phpfspot_db) && !is_writeable($this->cfg->phpfspot_db)) {
193 print "Error: ". $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() .".\n";
194 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
198 /* open the database */
199 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
201 /* change sqlite temp directory, if requested */
202 if(isset($this->cfg->sqlite_temp_dir)) {
203 $this->cfg_db->db_exec("
205 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
209 /* Check if some tables need to be created */
210 $this->check_phpfspot_db();
212 /* overload Smarty class with our own template handler */
213 require_once "phpfspot_tmpl.php";
214 $this->tmpl = new PHPFSPOT_TMPL();
216 /* pre-set some template variables */
217 $this->tmpl->assign('web_path', $this->cfg->web_path);
219 /* Starting with F-Spot 0.4.2, the rating-feature was available */
220 if($this->dbver > 10) {
221 $this->tmpl->assign('has_rating', true);
222 $this->sort_orders = array_merge($this->sort_orders, array(
223 'rate_asc' => 'Rate ↑',
224 'rate_desc' => 'Rate ↓',
228 /* check if all necessary indices exist */
229 $this->checkDbIndices();
231 /* if session is not yet started, do it now */
232 if(session_id() == "")
235 if(!isset($_SESSION['tag_condition']))
236 $_SESSION['tag_condition'] = 'or';
238 /* if sort-order has not been set yet, get the one specified in the config */
239 if(!isset($_SESSION['sort_order']))
240 $_SESSION['sort_order'] = $this->cfg->sort_order;
242 if(!isset($_SESSION['searchfor_tag']))
243 $_SESSION['searchfor_tag'] = '';
245 // if begin_with is still set but thumbs_per_page is now 0, unset it
246 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
247 unset($_SESSION['begin_with']);
249 // if user-friendly-url's are enabled, set also a flag for the template handler
250 if($this->is_user_friendly_url()) {
251 $this->tmpl->assign('user_friendly_url', 'true');
256 public function __destruct()
262 * show - generate html output
264 * this function can be called after the constructor has
265 * prepared everyhing. it will load the index.tpl smarty
266 * template. if necessary it will registere pre-selects
267 * (photo index, photo, tag search, date search) into
270 public function show()
272 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
273 $this->tmpl->assign('page_title', $this->cfg->page_title);
274 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
275 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
278 if($this->is_user_friendly_url()) {
279 $content = $this->parse_user_friendly_url($_SERVER['REQUEST_URI']);
282 if(isset($_GET['mode'])) {
284 $_SESSION['start_action'] = $_GET['mode'];
286 switch($_GET['mode']) {
288 if(isset($_GET['tags'])) {
289 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
291 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
292 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
294 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
295 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
299 if(isset($_GET['tags'])) {
300 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
301 $_SESSION['start_action'] = 'showp';
303 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
304 if($_SESSION['current_photo'] != $_GET['id'])
305 unset($_SESSION['current_version']);
306 $_SESSION['current_photo'] = $_GET['id'];
307 $_SESSION['start_action'] = 'showp';
309 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
310 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
312 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
313 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
317 /* fetch export template */
318 print $this->tmpl->fetch("export.tpl");
319 /* no further execution necessary. */
323 /* fetch slideshow template */
324 print $this->tmpl->show("slideshow.tpl");
325 /* no further execution necessary. */
329 if(isset($_GET['tags'])) {
330 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
332 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
333 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
335 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
336 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
344 /* if date-search variables are registered in the session, set the check
345 for "consider date-range" in the html output
347 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
348 $this->tmpl->assign('date_search_enabled', true);
350 /* if rate-search variables are registered in the session, set the check
351 for "consider rate-range" in the html output
353 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
354 $this->tmpl->assign('rate_search_enabled', true);
357 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
358 $this->tmpl->assign('search_from_date', $this->get_date_text_field('from'));
359 $this->tmpl->assign('search_to_date', $this->get_date_text_field('to'));
361 $this->tmpl->assign('preset_selected_tags', $this->getSelectedTags());
362 $this->tmpl->assign('preset_available_tags', $this->getAvailableTags());
363 $this->tmpl->assign('rate_search', $this->get_rate_search());
365 /* if no site-content has been set yet... */
366 if(!isset($content)) {
367 /* if tags are already selected, we can immediately display photo-index */
368 if((isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags']) &&
369 isset($_SESSION['start_action']) && $_SESSION['start_action'] != 'showp') ||
370 (isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi'))
371 $this->tmpl->assign('initial_content', $this->showPhotoIndex());
373 /* if a photo is already selected, we can immediately display single-photo */
374 if(isset($_SESSION['current_photo']) && !empty($_SESSION['current_photo']))
375 $this->tmpl->assign('initial_content', $this->showPhoto($_SESSION['current_photo']));
377 /* ok, then let us show the welcome page... */
378 $this->tmpl->assign('initial_content', $this->tmpl->fetch('welcome.tpl'));
383 $this->tmpl->assign('initial_content', $content);
385 $this->tmpl->show("index.tpl");
390 * get_tags - grab all tags of f-spot's database
392 * this function will get all available tags from
393 * the f-spot database and store them within two
394 * arrays within this class for later usage. in
395 * fact, if the user requests (hide_tags) it will
396 * opt-out some of them.
398 * this function is getting called once by show()
400 private function get_tags()
402 $this->avail_tags = Array();
405 /* if show_tags has been set in the configuration (only show photos
406 which are tagged by these tags) they following will take care,
407 that only these other tags are displayed where the photo is also
408 tagged with one of show_tags.
410 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
413 DISTINCT t1.id as id, t1.name as name
416 INNER JOIN photo_tags
417 pt2 ON pt1.photo_id=pt2.photo_id
423 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
425 t1.sort_priority ASC";
427 $result = $this->db->db_query($query_str);
431 $result = $this->db->db_query("
432 SELECT id as id,name as name
434 ORDER BY sort_priority ASC
438 while($row = $this->db->db_fetch_object($result)) {
440 $tag_id = $row['id'];
441 $tag_name = $row['name'];
443 /* if the user has specified to ignore this tag in phpfspot's
444 configuration, ignore it here so it does not get added to
447 if(in_array($row['name'], $this->cfg->hide_tags))
450 /* if you include the following if-clause and the user has specified
451 to only show certain tags which are specified in phpfspot's
452 configuration, ignore all others so they will not be added to the
454 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
455 !in_array($row['name'], $this->cfg->show_tags))
459 $this->tags[$tag_id] = $tag_name;
460 $this->avail_tags[$count] = $tag_id;
468 * get all photo details from F-Spot database
470 * this function queries the F-Spot database for all available
471 * details of the requested photo. It returns them as a object.
473 * Furthermore it takes care of the photo version to be requested.
474 * If photo version is not yet, it queries information for the
477 * @param integer $idx
478 * @return object|null
480 public function get_photo_details($idx, $version_idx = NULL)
482 /* ~ F-Spot version 0.3.x */
483 if($this->dbver < 9) {
489 p.directory_path as directory_path,
490 p.description as description
496 /* till F-Spot version 0.4.1 */
497 if($this->dbver < 11) {
503 p.description as description
508 elseif($this->dbver < 17) {
509 /* rating value got introduced */
515 p.description as description,
522 /* path & filename now splited in base_uri & filename */
526 p.base_uri || p.filename as uri,
528 p.description as description,
536 /* if show_tags is set, only return details of photos which are
537 tagged with a tag that has been specified to be shown.
539 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
541 INNER JOIN photo_tags pt
545 WHERE p.id='". $idx ."'
546 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
550 WHERE p.id='". $idx ."'
554 if($row = $this->db->db_fetchSingleRow($query_str)) {
556 /* before F-Spot db version 9 there was no uri column but
557 seperated fields for directory_path and name (= filename).
559 if($this->dbver < 9) {
560 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
562 /* starting with dbversion >= 17 we need to rawurldecode() uri */
563 elseif($this->dbver >= 17) {
564 $row['uri'] = rawurldecode($row['uri']);
567 /* if version-idx has not yet been set, get the latest photo version */
568 if(!isset($version_idx) || !$this->is_valid_version($idx, $version_idx))
569 $version_idx = $this->get_latest_version($idx);
571 /* if an alternative version has been requested. But we
572 support this only for F-Spot database versions from
575 if($version_idx > 0 && $this->dbver >= 9) {
576 if ($this->dbver < 17) {
577 /* check for alternative versions */
578 if($version = $this->db->db_fetchSingleRow("
580 version_id, name, uri
584 photo_id LIKE '". $idx ."'
586 version_id LIKE '". $version_idx ."'")) {
588 $row['name'] = $version['name'];
589 $row['uri'] = $version['uri'];
593 /* path & filename now splited in base_uri & filename */
594 if($version = $this->db->db_fetchSingleRow("
598 base_uri || filename as uri
602 photo_id LIKE '". $idx ."'
604 version_id LIKE '". $version_idx ."'")) {
606 $row['name'] = $version['name'];
607 $row['uri'] = rawurldecode($version['uri']);
618 } // get_photo_details()
621 * returns aligned photo names
623 * this function returns aligned (length) names for a specific photo.
624 * If the length of the name exceeds $limit the name will bei
627 * @param integer $idx
628 * @param integer $limit
629 * @return string|null
631 public function getPhotoName($idx, $limit = 0)
633 if($details = $this->get_photo_details($idx)) {
634 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
635 $name = $this->shrink_text($long_name, $limit);
645 * get photo rating level
647 * this function will return the integer-based rating level of a
648 * photo. This can only be done, if the F-Spot database is at a
649 * specific version. If rating value can not be found, zero will
650 * be returned indicating no rating value is available.
655 public function get_photo_rating($idx)
657 if($detail = $this->get_photo_details($idx)) {
658 if(isset($detail['rating']))
659 return $detail['rating'];
664 } // get_photo_rating()
667 * get rate-search bars
669 * this function will return the rating-bars for the search field.
673 public function get_rate_search()
677 for($i = 1; $i <= 5; $i++) {
679 $bar.= "<img id=\"rate_from_". $i ."\" src=\"";
681 if(isset($_SESSION['rate_from']) && $i <= $_SESSION['rate_from'])
682 $bar.= $this->cfg->web_path ."/resources/star.png";
684 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
687 onmouseover=\"show_rate('from', ". $i .");\"
688 onmouseout=\"reset_rate('from');\"
689 onclick=\"set_rate('from', ". $i .")\" />";
694 for($i = 1; $i <= 5; $i++) {
696 $bar.= "<img id=\"rate_to_". $i ."\" src=\"";
698 if(isset($_SESSION['rate_to']) && $i <= $_SESSION['rate_to'])
699 $bar.= $this->cfg->web_path ."/resources/star.png";
701 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
704 onmouseover=\"show_rate('to', ". $i .");\"
705 onmouseout=\"reset_rate('to');\"
706 onclick=\"set_rate('to', ". $i .");\" />";
711 } // get_rate_search()
714 * shrink text according provided limit
716 * If the length of the name exceeds $limit, text will be shortend
717 * and inner content will be replaced with "...".
720 * @param integer $limit
723 private function shrink_text($text, $limit)
725 if($limit != 0 && strlen($text) > $limit) {
726 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
734 * translate f-spoth photo path
736 * as the full-qualified path recorded in the f-spot database
737 * is usally not the same as on the webserver, this function
738 * will replace the path with that one specified in the cfg
739 * @param string $path
742 public function translate_path($path)
744 if($this->cfg->enable_replace_path == true)
746 $this->cfg->path_replace_from,
747 $this->cfg->path_replace_to, $path);
754 * control HTML ouput for a single photo
756 * this function provides all the necessary information
757 * for the single photo template.
758 * @param integer photo
760 public function showPhoto($photo)
762 /* get all photos from the current photo selection */
763 $all_photos = $this->getPhotoSelection();
764 $count = count($all_photos);
766 for($i = 0; $i < $count; $i++) {
768 // $get_next will be set, when the photo which has to
769 // be displayed has been found - this means that the
770 // next available is in fact the NEXT image (for the
772 if(isset($get_next)) {
773 $next_img = $all_photos[$i];
777 /* the next photo is our NEXT photo */
778 if($all_photos[$i] == $photo) {
782 $previous_img = $all_photos[$i];
785 if($photo == $all_photos[$i]) {
790 $details = $this->get_photo_details($photo);
797 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
799 /* if current version is already set, use it */
800 if($this->get_current_version() !== false)
801 $version = $this->get_current_version();
803 /* if version not set yet, we assume to display the latest version */
804 if(!isset($version) || !$this->is_valid_version($photo, $version))
805 $version = $this->get_latest_version($photo);
807 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
809 if(!file_exists($orig_path)) {
810 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
814 if(!is_readable($orig_path)) {
815 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
819 /* If the thumbnail doesn't exist yet, try to create it */
820 if(!file_exists($thumb_path)) {
821 $this->gen_thumb($photo, true);
822 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
825 /* get mime-type, height and width from the original photo */
826 $info = getimagesize($orig_path);
828 /* get EXIF information if JPEG */
829 if(isset($info['mime']) && $info['mime'] == "image/jpeg") {
830 $meta = $this->get_meta_informations($orig_path);
833 /* If EXIF data are available, use them */
834 if(isset($meta['ExifImageWidth'])) {
835 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
837 $meta_res = $info[0] ."x". $info[1];
840 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
841 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
843 $extern_link = "index.php?mode=showp&id=". $photo;
844 $current_tags = $this->getCurrentTags();
845 if($current_tags != "") {
846 $extern_link.= "&tags=". $current_tags;
848 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
849 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
852 $this->tmpl->assign('extern_link', $extern_link);
854 if(!file_exists($thumb_path)) {
855 $this->_error("Can't open file ". $thumb_path ."\n");
859 $info_thumb = getimagesize($thumb_path);
861 $this->tmpl->assign('description', $details['description']);
862 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
863 $this->tmpl->assign('image_rating', $this->get_photo_rating($photo));
865 $this->tmpl->assign('width', $info_thumb[0]);
866 $this->tmpl->assign('height', $info_thumb[1]);
867 $this->tmpl->assign('ExifMadeOn', strftime("%a %x %X", $details['time']));
868 $this->tmpl->assign('ExifMadeWith', $meta_make);
869 $this->tmpl->assign('ExifOrigResolution', $meta_res);
870 $this->tmpl->assign('ExifFileSize', $meta_size);
872 if($this->is_user_friendly_url()) {
873 $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width .'/'. $version);
874 $this->tmpl->assign('image_url_full', '/photo/'. $photo);
877 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width ."&version=". $version);
878 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
881 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
883 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
884 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
885 $this->tmpl->assign('current_img', $photo);
887 if(isset($previous_img)) {
888 $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");");
889 $this->tmpl->assign('prev_img', $previous_img);
892 if(isset($next_img)) {
893 $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");");
894 $this->tmpl->assign('next_img', $next_img);
897 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
898 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
899 $this->tmpl->assign('photo_number', $i);
900 $this->tmpl->assign('photo_count', count($all_photos));
901 $this->tmpl->assign('photo', $photo);
902 $this->tmpl->assign('version', $version);
904 /* if the photo as alternative versions, set a flag for the template */
905 if($this->get_photo_versions($photo))
906 $this->tmpl->assign('has_versions', true);
908 $this->tmpl->register_function("photo_version_select_list", array(&$this, "smarty_photo_version_select_list"), false);
910 return $this->tmpl->fetch("single_photo.tpl");
915 * all available tags and tag cloud
917 * this function outputs all available tags (time ordered)
918 * and in addition output them as tag cloud (tags which have
919 * many photos will appears more then others)
921 public function getAvailableTags()
923 /* retrive tags from database */
928 $result = $this->db->db_query("
929 SELECT tag_id as id, count(tag_id) as quantity
939 while($row = $this->db->db_fetch_object($result)) {
940 $tags[$row['id']] = $row['quantity'];
943 // change these font sizes if you will
944 $max_size = 125; // max font size in %
945 $min_size = 75; // min font size in %
948 $max_sat = hexdec('cc');
949 $min_sat = hexdec('44');
951 // get the largest and smallest array values
952 $max_qty = max(array_values($tags));
953 $min_qty = min(array_values($tags));
955 // find the range of values
956 $spread = $max_qty - $min_qty;
957 if (0 == $spread) { // we don't want to divide by zero
961 // determine the font-size increment
962 // this is the increase per tag quantity (times used)
963 $step = ($max_size - $min_size)/($spread);
964 $step_sat = ($max_sat - $min_sat)/($spread);
966 // loop through our tag array
967 foreach ($tags as $key => $value) {
969 /* has the currently processed tag already been added to
970 the selected tag list? if so, ignore it here...
972 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
975 // calculate CSS font-size
976 // find the $value in excess of $min_qty
977 // multiply by the font-size increment ($size)
978 // and add the $min_size set above
979 $size = $min_size + (($value - $min_qty) * $step);
980 // uncomment if you want sizes in whole %:
983 $color = $min_sat + ($value - $min_qty) * $step_sat;
989 if(isset($this->tags[$key])) {
990 if($this->is_user_friendly_url()) {
991 $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\"
992 onclick=\"Tags('add', ". $key ."); return false;\"
994 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
995 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
998 $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\"
999 onclick=\"Tags('add', ". $key ."); return false;\"
1001 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
1002 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
1007 $output = substr($output, 0, strlen($output)-2);
1010 } // getAvailableTags()
1013 * output all selected tags
1015 * this function output all tags which have been selected
1016 * by the user. the selected tags are stored in the
1017 * session-variable $_SESSION['selected_tags']
1020 public function getSelectedTags($type = 'link')
1022 /* retrive tags from database */
1027 foreach($this->avail_tags as $tag)
1029 // return all selected tags
1030 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
1035 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
1039 <div class=\"tagresulttag\">
1040 <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\">
1041 <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" />
1051 $output = substr($output, 0, strlen($output)-2);
1055 return "no tags selected";
1058 } // getSelectedTags()
1061 * add tag to users session variable
1063 * this function will add the specified to users current
1064 * tag selection. if a date search has been made before
1065 * it will be now cleared
1068 public function addTag($tag)
1070 if(!isset($_SESSION['selected_tags']))
1071 $_SESSION['selected_tags'] = Array();
1073 if(isset($_SESSION['searchfor_tag']))
1074 unset($_SESSION['searchfor_tag']);
1076 // has the user requested to hide this tag, and still someone,
1077 // somehow tries to add it, don't allow this.
1078 if(!isset($this->cfg->hide_tags) &&
1079 in_array($this->get_tag_name($tag), $this->cfg->hide_tags))
1082 if(!in_array($tag, $_SESSION['selected_tags']))
1083 array_push($_SESSION['selected_tags'], $tag);
1090 * remove tag to users session variable
1092 * this function removes the specified tag from
1093 * users current tag selection
1094 * @param string $tag
1097 public function delTag($tag)
1099 if(isset($_SESSION['searchfor_tag']))
1100 unset($_SESSION['searchfor_tag']);
1102 if(isset($_SESSION['selected_tags'])) {
1103 $key = array_search($tag, $_SESSION['selected_tags']);
1104 unset($_SESSION['selected_tags'][$key]);
1105 sort($_SESSION['selected_tags']);
1113 * reset tag selection
1115 * if there is any tag selection, it will be
1118 public function resetTags()
1120 if(isset($_SESSION['selected_tags']))
1121 unset($_SESSION['selected_tags']);
1126 * returns the value for the autocomplete tag-search
1129 public function get_xml_tag_list()
1131 if(!isset($_GET['search']) || !is_string($_GET['search']))
1132 $_GET['search'] = '';
1137 /* retrive tags from database */
1140 $matched_tags = Array();
1142 header("Content-Type: text/xml");
1144 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
1145 $string.= "<results>\n";
1147 foreach($this->avail_tags as $tag)
1149 if(!empty($_GET['search']) &&
1150 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
1151 count($matched_tags) < $length) {
1153 $count = $this->get_num_photos($tag);
1156 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
1159 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
1165 /* if we have collected enough items, break out */
1166 if(count($matched_tags) >= $length)
1170 $string.= "</results>\n";
1174 } // get_xml_tag_list()
1178 * reset single photo
1180 * if a specific photo was requested (external link)
1181 * unset the session variable now
1183 public function resetPhotoView()
1185 if(isset($_SESSION['current_photo']))
1186 unset($_SESSION['current_photo']);
1188 if(isset($_SESSION['current_version']))
1189 unset($_SESSION['current_version']);
1191 } // resetPhotoView();
1196 * if any tag search has taken place, reset it now
1198 public function resetTagSearch()
1200 if(isset($_SESSION['searchfor_tag']))
1201 unset($_SESSION['searchfor_tag']);
1203 } // resetTagSearch()
1208 * if any name search has taken place, reset it now
1210 public function resetNameSearch()
1212 if(isset($_SESSION['searchfor_name']))
1213 unset($_SESSION['searchfor_name']);
1215 } // resetNameSearch()
1220 * if any date search has taken place, reset it now.
1222 public function resetDateSearch()
1224 if(isset($_SESSION['from_date']))
1225 unset($_SESSION['from_date']);
1226 if(isset($_SESSION['to_date']))
1227 unset($_SESSION['to_date']);
1229 } // resetDateSearch();
1234 * if any rate search has taken place, reset it now.
1236 public function resetRateSearch()
1238 if(isset($_SESSION['rate_from']))
1239 unset($_SESSION['rate_from']);
1240 if(isset($_SESSION['rate_to']))
1241 unset($_SESSION['rate_to']);
1243 } // resetRateSearch();
1246 * return all photo according selection
1248 * this function returns all photos based on
1249 * the tag-selection, tag- or date-search.
1250 * the tag-search also has to take care of AND
1251 * and OR conjunctions
1254 public function getPhotoSelection()
1256 $matched_photos = Array();
1257 $additional_where_cond = "";
1259 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1260 $from_date = $_SESSION['from_date'];
1261 $to_date = $_SESSION['to_date'];
1262 $additional_where_cond.= "
1263 p.time>='". $from_date ."'
1265 p.time<='". $to_date ."'
1269 if(isset($_SESSION['searchfor_name'])) {
1271 /* check for previous conditions. if so add 'AND' */
1272 if(!empty($additional_where_cond)) {
1273 $additional_where_cond.= " AND ";
1276 if($this->dbver < 9) {
1277 $additional_where_cond.= "
1279 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
1281 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1285 if($this->dbver < 17) {
1286 $additional_where_cond.= "
1288 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
1290 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1295 $additional_where_cond.= "
1297 p.filename LIKE '%". $_SESSION['searchfor_name'] ."%'
1299 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1305 /* limit result based on rate-search */
1306 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
1308 if($this->dbver > 10) {
1310 /* check for previous conditions. if so add 'AND' */
1311 if(!empty($additional_where_cond)) {
1312 $additional_where_cond.= " AND ";
1315 $additional_where_cond.= "
1316 p.rating >= ". $_SESSION['rate_from'] ."
1318 p.rating <= ". $_SESSION['rate_to'] ."
1323 if(isset($_SESSION['sort_order'])) {
1324 $order_str = $this->get_sort_order();
1327 /* return a search result */
1328 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
1331 pt1.photo_id as photo_id
1334 INNER JOIN photo_tags pt2
1335 ON pt1.photo_id=pt2.photo_id
1339 ON pt1.photo_id=p.id
1342 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
1344 if(!empty($additional_where_cond))
1345 $query_str.= "AND ". $additional_where_cond ." ";
1347 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1348 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
1351 if(isset($order_str))
1352 $query_str.= $order_str;
1354 $result = $this->db->db_query($query_str);
1355 while($row = $this->db->db_fetch_object($result)) {
1356 array_push($matched_photos, $row['photo_id']);
1358 return $matched_photos;
1361 /* return according the selected tags */
1362 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1364 foreach($_SESSION['selected_tags'] as $tag)
1365 $selected.= $tag .",";
1366 $selected = substr($selected, 0, strlen($selected)-1);
1368 /* photo has to match at least on of the selected tags */
1369 if($_SESSION['tag_condition'] == 'or') {
1372 pt1.photo_id as photo_id
1375 INNER JOIN photo_tags pt2
1376 ON pt1.photo_id=pt2.photo_id
1380 ON pt1.photo_id=p.id
1381 WHERE pt1.tag_id IN (". $selected .")
1383 if(!empty($additional_where_cond))
1384 $query_str.= "AND ". $additional_where_cond ." ";
1386 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1387 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1390 if(isset($order_str))
1391 $query_str.= $order_str;
1393 /* photo has to match all selected tags */
1394 elseif($_SESSION['tag_condition'] == 'and') {
1396 if(count($_SESSION['selected_tags']) >= 32) {
1397 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1398 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1402 /* Join together a table looking like
1404 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1406 so the query can quickly return all images matching the
1407 selected tags in an AND condition
1413 pt1.photo_id as photo_id
1418 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1425 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1427 INNER JOIN photo_tags pt". ($i+2) ."
1428 ON pt1.photo_id=pt". ($i+2) .".photo_id
1433 ON pt1.photo_id=p.id
1435 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1436 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1438 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1441 if(!empty($additional_where_cond))
1442 $query_str.= "AND ". $additional_where_cond;
1444 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1445 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1448 if(isset($order_str))
1449 $query_str.= $order_str;
1453 $result = $this->db->db_query($query_str);
1454 while($row = $this->db->db_fetch_object($result)) {
1455 array_push($matched_photos, $row['photo_id']);
1457 return $matched_photos;
1460 /* return all available photos */
1466 LEFT JOIN photo_tags pt
1472 if(!empty($additional_where_cond))
1473 $query_str.= "WHERE ". $additional_where_cond ." ";
1475 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1476 if(!empty($additional_where_cond))
1477 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1479 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1482 if(isset($order_str))
1483 $query_str.= $order_str;
1485 $result = $this->db->db_query($query_str);
1486 while($row = $this->db->db_fetch_object($result)) {
1487 array_push($matched_photos, $row['id']);
1489 return $matched_photos;
1491 } // getPhotoSelection()
1494 * control HTML ouput for photo index
1496 * this function provides all the necessary information
1497 * for the photo index template.
1500 public function showPhotoIndex()
1502 $photos = $this->getPhotoSelection();
1503 $current_tags = $this->getCurrentTags();
1505 $count = count($photos);
1507 /* if all thumbnails should be shown on one page */
1508 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1512 /* thumbnails should be splitted up in several pages */
1513 elseif($this->cfg->thumbs_per_page > 0) {
1515 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1519 $begin_with = $_SESSION['begin_with'];
1522 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1527 for($i = $begin_with; $i < $end_with; $i++) {
1529 if(!isset($photos[$i]))
1532 /* on first run, initalize all used variables */
1535 $images[$thumbs] = Array();
1536 $img_height[$thumbs] = Array();
1537 $img_width[$thumbs] = Array();
1538 $img_id[$thumbs] = Array();
1539 $img_name[$thumbs] = Array();
1540 $img_fullname[$thumbs] = Array();
1541 $img_title = Array();
1542 $img_rating = Array();
1545 $images[$thumbs] = $photos[$i];
1546 $img_id[$thumbs] = $i;
1547 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1548 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1549 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1550 $img_rating[$thumbs] = $this->get_photo_rating($photos[$i]);
1552 /* get local path of the thumbnail image to be displayed */
1553 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i], $this->get_latest_version($photos[$i]));
1555 /* if the image exist and is readable, extract some details */
1556 if(file_exists($thumb_path) && is_readable($thumb_path)) {
1557 if($info = getimagesize($thumb_path) !== false) {
1558 $img_width[$thumbs] = $info[0];
1559 $img_height[$thumbs] = $info[1];
1565 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1566 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1568 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1569 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1570 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1573 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1574 $this->tmpl->assign('tag_result', 1);
1577 /* do we have to display the page selector ? */
1578 if($this->cfg->thumbs_per_page != 0) {
1582 /* calculate the page switchers */
1583 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1584 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1586 if($begin_with != 0)
1587 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1588 if($end_with < $count)
1589 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1591 $photo_per_page = $this->cfg->thumbs_per_page;
1592 $last_page = ceil($count / $photo_per_page);
1594 /* get the current selected page */
1595 if($begin_with == 0) {
1599 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1606 for($i = 1; $i <= $last_page; $i++) {
1608 if($current_page == $i)
1609 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1610 elseif($current_page-1 == $i || $current_page+1 == $i)
1611 $style = "style=\"font-size: 105%;\"";
1612 elseif(($current_page-5 >= $i) && ($i != 1) ||
1613 ($current_page+5 <= $i) && ($i != $last_page))
1614 $style = "style=\"font-size: 75%;\"";
1618 $start_with = ($i*$photo_per_page)-$photo_per_page;
1620 if($this->is_user_friendly_url()) {
1621 $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\"";
1624 $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi tags=". $current_tags ." begin_with=". $begin_with ."\"";
1626 $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\"";
1630 $select.= ">". $i ."</a> ";
1632 // until 9 pages we show the selector from 1-9
1633 if($last_page <= 9) {
1634 $page_select.= $select;
1637 if($i == 1 /* first page */ ||
1638 $i == $last_page /* last page */ ||
1639 $i == $current_page /* current page */ ||
1640 $i == ceil($last_page * 0.25) /* first quater */ ||
1641 $i == ceil($last_page * 0.5) /* half */ ||
1642 $i == ceil($last_page * 0.75) /* third quater */ ||
1643 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1644 (in_array($i, array($last_page, $last_page-1, $last_page-2, $last_page-3, $last_page-4, $last_page-5)) && $current_page >= $last_page-4) /* the last 6 */ ||
1645 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1646 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1648 $page_select.= $select;
1656 $page_select.= "......... ";
1661 /* only show the page selector if we have more then one page */
1663 $this->tmpl->assign('page_selector', $page_select);
1666 $extern_link = "index.php?mode=showpi";
1667 $rss_link = "index.php?mode=rss";
1668 if($current_tags != "") {
1669 $extern_link.= "&tags=". $current_tags;
1670 $rss_link.= "&tags=". $current_tags;
1672 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1673 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1674 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1677 $export_link = "index.php?mode=export";
1678 $slideshow_link = "index.php?mode=slideshow";
1680 $this->tmpl->assign('extern_link', $extern_link);
1681 $this->tmpl->assign('slideshow_link', $slideshow_link);
1682 $this->tmpl->assign('export_link', $export_link);
1683 $this->tmpl->assign('rss_link', $rss_link);
1684 $this->tmpl->assign('count', $count);
1685 $this->tmpl->assign('width', $this->cfg->thumb_width);
1686 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1687 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1688 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1689 $this->tmpl->assign('selected_tags', $this->getSelectedTags('img'));
1690 // +1 for for smarty's selection iteration
1691 $this->tmpl->assign('thumbs', $thumbs+1);
1694 $this->tmpl->assign('images', $images);
1695 $this->tmpl->assign('img_width', $img_width);
1696 $this->tmpl->assign('img_height', $img_height);
1697 $this->tmpl->assign('img_id', $img_id);
1698 $this->tmpl->assign('img_name', $img_name);
1699 $this->tmpl->assign('img_fullname', $img_fullname);
1700 $this->tmpl->assign('img_title', $img_title);
1701 $this->tmpl->assign('img_rating', $img_rating);
1704 $result = $this->tmpl->fetch("photo_index.tpl");
1706 /* if we are returning to photo index from an photo-view,
1707 scroll the window to the last shown photo-thumbnail.
1708 after this, unset the last_photo session variable.
1710 if(isset($_SESSION['last_photo'])) {
1711 $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1712 unset($_SESSION['last_photo']);
1717 } // showPhotoIndex()
1720 * show credit template
1722 public function showCredits()
1724 $this->tmpl->assign('version', $this->cfg->version);
1725 $this->tmpl->assign('product', $this->cfg->product);
1726 $this->tmpl->assign('db_version', $this->dbver);
1727 $this->tmpl->show("credits.tpl");
1732 * create thumbnails for the requested width
1734 * this function creates image thumbnails of $orig_image
1735 * stored as $thumb_image. It will check if the image is
1736 * in a supported format, if necessary rotate the image
1737 * (based on EXIF orientation meta headers) and re-sizing.
1738 * @param string $orig_image
1739 * @param string $thumb_image
1740 * @param integer $width
1743 public function create_thumbnail($orig_image, $thumb_image, $width)
1745 if(!file_exists($orig_image)) {
1749 $mime = $this->get_mime_info($orig_image);
1751 /* check if original photo is a support image type */
1752 if(!$this->checkifImageSupported($mime))
1759 $meta = $this->get_meta_informations($orig_image);
1765 if(isset($meta['Orientation'])) {
1766 switch($meta['Orientation']) {
1767 case 1: /* top, left */
1768 /* nothing to do */ break;
1769 case 2: /* top, right */
1770 $rotate = 0; $flip_hori = true; break;
1771 case 3: /* bottom, left */
1772 $rotate = 180; break;
1773 case 4: /* bottom, right */
1774 $flip_vert = true; break;
1775 case 5: /* left side, top */
1776 $rotate = 90; $flip_vert = true; break;
1777 case 6: /* right side, top */
1778 $rotate = 90; break;
1779 case 7: /* left side, bottom */
1780 $rotate = 270; $flip_vert = true; break;
1781 case 8: /* right side, bottom */
1782 $rotate = 270; break;
1786 $src_img = @imagecreatefromjpeg($orig_image);
1792 $src_img = @imagecreatefrompng($orig_image);
1796 case 'image/x-portable-pixmap':
1798 $src_img = new Imagick($orig_image);
1799 $handler = "imagick";
1804 if(!isset($src_img) || empty($src_img)) {
1805 print "Can't load image from ". $orig_image ."\n";
1813 /* grabs the height and width */
1814 $cur_width = imagesx($src_img);
1815 $cur_height = imagesy($src_img);
1817 // If requested width is more then the actual image width,
1818 // do not generate a thumbnail, instead safe the original
1819 // as thumbnail but with lower quality. But if the image
1820 // is to heigh too, then we still have to resize it.
1821 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1822 $result = imagejpeg($src_img, $thumb_image, 75);
1823 imagedestroy($src_img);
1830 $cur_width = $src_img->getImageWidth();
1831 $cur_height = $src_img->getImageHeight();
1833 // If requested width is more then the actual image width,
1834 // do not generate a thumbnail, instead safe the original
1835 // as thumbnail but with lower quality. But if the image
1836 // is to heigh too, then we still have to resize it.
1837 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1838 $src_img->setCompressionQuality(75);
1839 $src_img->setImageFormat('jpeg');
1840 $src_img->writeImage($thumb_image);
1842 $src_img->destroy();
1849 // If the image will be rotate because EXIF orientation said so
1850 // 'virtually rotate' the image for further calculations
1851 if($rotate == 90 || $rotate == 270) {
1853 $cur_width = $cur_height;
1857 /* calculates aspect ratio */
1858 $aspect_ratio = $cur_height / $cur_width;
1861 if($aspect_ratio < 1) {
1863 $new_h = abs($new_w * $aspect_ratio);
1865 /* 'virtually' rotate the image and calculate it's ratio */
1866 $tmp_w = $cur_height;
1867 $tmp_h = $cur_width;
1868 /* now get the ratio from the 'rotated' image */
1869 $tmp_ratio = $tmp_h/$tmp_w;
1870 /* now calculate the new dimensions */
1872 $tmp_h = abs($tmp_w * $tmp_ratio);
1874 // now that we know, how high they photo should be, if it
1875 // gets rotated, use this high to scale the image
1877 $new_w = abs($new_h / $aspect_ratio);
1879 // If the image will be rotate because EXIF orientation said so
1880 // now 'virtually rotate' back the image for the image manipulation
1881 if($rotate == 90 || $rotate == 270) {
1892 /* creates new image of that size */
1893 $dst_img = imagecreatetruecolor($new_w, $new_h);
1895 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1897 /* copies resized portion of original image into new image */
1898 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1900 /* needs the image to be flipped horizontal? */
1902 $this->_debug("(FLIP)");
1903 $dst_img = $this->flipImage($dst_img, 'hori');
1905 /* needs the image to be flipped vertical? */
1907 $this->_debug("(FLIP)");
1908 $dst_img = $this->flipImage($dst_img, 'vert');
1912 $this->_debug("(ROTATE)");
1913 $dst_img = $this->rotateImage($dst_img, $rotate);
1916 /* write down new generated file */
1917 $result = imagejpeg($dst_img, $thumb_image, 75);
1919 /* free your mind */
1920 imagedestroy($dst_img);
1921 imagedestroy($src_img);
1923 if($result === false) {
1924 print "Can't write thumbnail ". $thumb_image ."\n";
1934 $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1);
1936 /* needs the image to be flipped horizontal? */
1938 $this->_debug("(FLIP)");
1939 $src_img->rotateImage(new ImagickPixel(), 90);
1940 $src_img->flipImage();
1941 $src_img->rotateImage(new ImagickPixel(), -90);
1943 /* needs the image to be flipped vertical? */
1945 $this->_debug("(FLIP)");
1946 $src_img->flipImage();
1950 $this->_debug("(ROTATE)");
1951 $src_img->rotateImage(new ImagickPixel(), $rotate);
1954 $src_img->setCompressionQuality(75);
1955 $src_img->setImageFormat('jpeg');
1957 if(!$src_img->writeImage($thumb_image)) {
1958 print "Can't write thumbnail ". $thumb_image ."\n";
1963 $src_img->destroy();
1970 } // create_thumbnail()
1973 * return all exif meta data from the file
1974 * @param string $file
1977 public function get_meta_informations($file)
1979 return exif_read_data($file);
1981 } // get_meta_informations()
1984 * create phpfspot own sqlite database
1986 * this function creates phpfspots own sqlite database
1987 * if it does not exist yet. this own is used to store
1988 * some necessary informations (md5 sum's, ...).
1990 public function check_phpfspot_db()
1992 // if the config table doesn't exist yet, create it
1993 if(!$this->cfg_db->db_check_table_exists("images")) {
1994 $this->cfg_db->db_exec("
1995 CREATE TABLE images (
1997 img_version_idx int,
1998 img_md5 varchar(32),
1999 UNIQUE(img_idx, img_version_idx)
2004 if(!$this->cfg_db->db_check_table_exists("meta")) {
2005 $this->cfg_db->db_exec("
2007 meta_key varchar(255),
2008 meta_value varchar(255)
2012 /* db_version was added with phpfspot 1.7, before changes
2013 on the phpfspot database where not necessary.
2016 $this->cfg_db->db_exec("
2018 meta_key, meta_value
2020 'phpfspot Database Version',
2021 '". $this->cfg->db_version ."'
2026 /* if version <= 2 and column img_version_idx does not exist yet */
2027 if($this->get_db_version() <= 2 &&
2028 !$this->cfg_db->db_check_column_exists("images", "img_version_idx")) {
2030 if(!$this->cfg_db->db_start_transaction())
2031 die("Can not start database transaction");
2033 $result = $this->cfg_db->db_exec("
2034 CREATE TEMPORARY TABLE images_temp (
2036 img_version_idx int,
2037 img_md5 varchar(32),
2038 UNIQUE(img_idx, img_version_idx)
2043 $this->cfg_db->db_rollback_transaction();
2044 die("Upgrade failed - transaction rollback");
2047 $result = $this->cfg_db->db_exec("
2048 INSERT INTO images_temp
2057 $this->cfg_db->db_rollback_transaction();
2058 die("Upgrade failed - transaction rollback");
2061 $result = $this->cfg_db->db_exec("
2066 $this->cfg_db->db_rollback_transaction();
2067 die("Upgrade failed - transaction rollback");
2070 $result = $this->cfg_db->db_exec("
2071 CREATE TABLE images (
2073 img_version_idx int,
2074 img_md5 varchar(32),
2075 UNIQUE(img_idx, img_version_idx)
2080 $this->cfg_db->db_rollback_transaction();
2081 die("Upgrade failed - transaction rollback");
2084 $result = $this->cfg_db->db_exec("
2091 $this->cfg_db->db_rollback_transaction();
2092 die("Upgrade failed - transaction rollback");
2095 $result = $this->cfg_db->db_exec("
2096 DROP TABLE images_temp
2100 $this->cfg_db->db_rollback_transaction();
2101 die("Upgrade failed - transaction rollback");
2104 if(!$this->cfg_db->db_commit_transaction())
2105 die("Can not commit database transaction");
2109 } // check_phpfspot_db
2112 * generates thumbnails
2114 * This function generates JPEG thumbnails from
2115 * provided F-Spot photo indize and its alternative
2118 * 1. Check if all thumbnail generations (width) are already in place and
2120 * 2. Check if the md5sum of the original file has changed
2121 * 3. Generate the thumbnails if needed
2122 * @param integer $idx
2123 * @param integer $force
2124 * @param boolean $overwrite
2126 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
2129 $versions = Array(0);
2131 $resolutions = Array(
2132 $this->cfg->thumb_width,
2133 $this->cfg->photo_width,
2134 $this->cfg->mini_width,
2138 if($alt_versions = $this->get_photo_versions($idx))
2139 $versions = array_merge($versions, $alt_versions);
2141 foreach($versions as $version) {
2143 /* get details from F-Spot's database */
2144 $details = $this->get_photo_details($idx, $version);
2146 /* calculate file MD5 sum */
2147 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2149 if(!file_exists($full_path)) {
2150 $this->_error("File ". $full_path ." does not exist");
2154 if(!is_readable($full_path)) {
2155 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
2159 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
2161 /* If Nikon NEF format, we need to treat it another way */
2162 if(isset($this->cfg->dcraw_bin) &&
2163 file_exists($this->cfg->dcraw_bin) &&
2164 is_executable($this->cfg->dcraw_bin) &&
2165 preg_match('/\.nef$/i', $details['uri'])) {
2167 $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path);
2169 /* if PPM file does not exist, let dcraw convert it from NEF */
2170 if(!file_exists($ppm_path)) {
2171 system($this->cfg->dcraw_bin ." -a ". $full_path);
2174 /* for now we handle the PPM instead of the NEF */
2175 $full_path = $ppm_path;
2179 $file_md5 = md5_file($full_path);
2182 foreach($resolutions as $resolution) {
2184 $generate_it = false;
2186 $thumb_sub_path = substr($file_md5, 0, 2);
2187 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
2189 /* if thumbnail-subdirectory does not exist yet, create it */
2190 if(!file_exists(dirname($thumb_path))) {
2191 mkdir(dirname($thumb_path), 0755);
2194 /* if the thumbnail file doesn't exist, create it */
2195 if(!file_exists($thumb_path) || $force) {
2196 $generate_it = true;
2198 elseif($file_md5 != $this->getMD5($idx, $version)) {
2199 $generate_it = true;
2202 if($generate_it || $overwrite) {
2204 $this->_debug(" ". $resolution ."px");
2205 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
2213 $this->_debug(" already exist");
2216 /* set the new/changed MD5 sum for the current photo */
2218 $this->setMD5($idx, $file_md5, $version);
2221 $this->_debug("\n");
2228 * returns stored md5 sum for a specific photo
2230 * this function queries the phpfspot database for a stored MD5
2231 * checksum of the specified photo. It also takes care of the
2232 * requested photo version - original or alternative photo.
2234 * @param integer $idx
2235 * @return string|null
2237 public function getMD5($idx, $version_idx = 0)
2239 $result = $this->cfg_db->db_query("
2245 img_idx='". $idx ."'
2247 img_version_idx='". $version_idx ."'
2253 if($img = $this->cfg_db->db_fetch_object($result))
2254 return $img['img_md5'];
2261 * set MD5 sum for the specific photo
2262 * @param integer $idx
2263 * @param string $md5
2265 private function setMD5($idx, $md5, $version_idx = 0)
2267 $result = $this->cfg_db->db_exec("
2268 INSERT OR REPLACE INTO images (
2269 img_idx, img_version_idx, img_md5
2272 '". $version_idx ."',
2280 * store current tag condition
2282 * this function stores the current tag condition
2283 * (AND or OR) in the users session variables
2284 * @param string $mode
2287 public function setTagCondition($mode)
2289 $_SESSION['tag_condition'] = $mode;
2293 } // setTagCondition()
2296 * invoke tag & date search
2298 * this function will return all matching tags and store
2299 * them in the session variable selected_tags. furthermore
2300 * it also handles the date search.
2301 * getPhotoSelection() will then only return the matching
2305 public function startSearch()
2308 if(isset($_POST['date_from']) && $this->isValidDate($_POST['date_from'])) {
2309 $date_from = $_POST['date_from'];
2311 if(isset($_POST['date_to']) && $this->isValidDate($_POST['date_to'])) {
2312 $date_to = $_POST['date_to'];
2315 /* tag-name search */
2316 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
2317 $searchfor_tag = $_POST['for_tag'];
2318 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
2321 unset($_SESSION['searchfor_tag']);
2324 /* file-name search */
2325 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
2326 $_SESSION['searchfor_name'] = $_POST['for_name'];
2329 unset($_SESSION['searchfor_name']);
2333 if(isset($_POST['rate_from']) && is_numeric($_POST['rate_from'])) {
2335 $_SESSION['rate_from'] = $_POST['rate_from'];
2337 if(isset($_POST['rate_to']) && is_numeric($_POST['rate_to'])) {
2338 $_SESSION['rate_to'] = $_POST['rate_to'];
2342 /* delete any previously set value */
2343 unset($_SESSION['rate_to'], $_SESSION['rate_from']);
2348 if(isset($date_from) && !empty($date_from))
2349 $_SESSION['from_date'] = strtotime($date_from ." 00:00:00");
2351 unset($_SESSION['from_date']);
2353 if(isset($date_to) && !empty($date_to))
2354 $_SESSION['to_date'] = strtotime($date_to ." 23:59:59");
2356 unset($_SESSION['to_date']);
2358 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
2359 /* new search, reset the current selected tags */
2360 $_SESSION['selected_tags'] = Array();
2361 foreach($this->avail_tags as $tag) {
2362 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
2363 array_push($_SESSION['selected_tags'], $tag);
2372 * updates sort order in session variable
2374 * this function is invoked by RPC and will sort the requested
2375 * sort order in the session variable.
2376 * @param string $sort_order
2379 public function updateSortOrder($order)
2381 if(isset($this->sort_orders[$order])) {
2382 $_SESSION['sort_order'] = $order;
2386 return "unkown error";
2388 } // updateSortOrder()
2391 * update photo version in session variable
2393 * this function is invoked by RPC and will set the requested
2394 * photo version in the session variable.
2395 * @param string $photo_version
2398 public function update_photo_version($photo_idx, $photo_version)
2400 if($this->is_valid_version($photo_idx, $photo_version)) {
2401 $_SESSION['current_version'] = $photo_version;
2405 return "incorrect photo version provided";
2407 } // update_photo_version()
2412 * this function rotates the image according the
2414 * @param string $img
2415 * @param integer $degress
2418 private function rotateImage($img, $degrees)
2420 if(function_exists("imagerotate")) {
2421 $img = imagerotate($img, $degrees, 0);
2423 function imagerotate($src_img, $angle)
2425 $src_x = imagesx($src_img);
2426 $src_y = imagesy($src_img);
2427 if ($angle == 180) {
2431 elseif ($src_x <= $src_y) {
2435 elseif ($src_x >= $src_y) {
2440 $rotate=imagecreatetruecolor($dest_x,$dest_y);
2441 imagealphablending($rotate, false);
2446 for ($y = 0; $y < ($src_y); $y++) {
2447 for ($x = 0; $x < ($src_x); $x++) {
2448 $color = imagecolorat($src_img, $x, $y);
2449 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
2455 for ($y = 0; $y < ($src_y); $y++) {
2456 for ($x = 0; $x < ($src_x); $x++) {
2457 $color = imagecolorat($src_img, $x, $y);
2458 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
2464 for ($y = 0; $y < ($src_y); $y++) {
2465 for ($x = 0; $x < ($src_x); $x++) {
2466 $color = imagecolorat($src_img, $x, $y);
2467 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
2481 $img = imagerotate($img, $degrees);
2490 * returns flipped image
2492 * this function will return an either horizontal or
2493 * vertical flipped truecolor image.
2494 * @param string $image
2495 * @param string $mode
2498 private function flipImage($image, $mode)
2500 $w = imagesx($image);
2501 $h = imagesy($image);
2502 $flipped = imagecreatetruecolor($w, $h);
2506 for ($y = 0; $y < $h; $y++) {
2507 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
2511 for ($x = 0; $x < $w; $x++) {
2512 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
2522 * return all assigned tags for the specified photo
2523 * @param integer $idx
2526 private function get_photo_tags($idx)
2528 $result = $this->db->db_query("
2529 SELECT t.id as id, t.name as name
2531 INNER JOIN photo_tags pt
2533 WHERE pt.photo_id='". $idx ."'
2538 while($row = $this->db->db_fetch_object($result)) {
2539 if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
2541 $tags[$row['id']] = $row['name'];
2546 } // get_photo_tags()
2549 * create on-the-fly images with text within
2550 * @param string $txt
2551 * @param string $color
2552 * @param integer $space
2553 * @param integer $font
2556 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
2558 if (strlen($color) != 6)
2561 $int = hexdec($color);
2562 $h = imagefontheight($font);
2563 $fw = imagefontwidth($font);
2564 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2565 $lines = count($txt);
2566 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2567 $bg = imagecolorallocate($im, 255, 255, 255);
2568 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2571 foreach ($txt as $text) {
2572 $x = (($w - ($fw * strlen($text))) / 2);
2573 imagestring($im, $font, $x, $y, $text, $color);
2574 $y += ($h + $space);
2577 Header("Content-type: image/png");
2580 } // showTextImage()
2583 * check if all requirements are met
2586 private function check_requirements()
2588 if(!function_exists("imagecreatefromjpeg")) {
2589 print "PHP GD library extension is missing<br />\n";
2593 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2594 print "PHP SQLite3 library extension is missing<br />\n";
2598 if($this->cfg->db_access == "pdo") {
2599 if(array_search("sqlite", PDO::getAvailableDrivers()) === false) {
2600 print "PDO SQLite3 driver is missing<br />\n";
2605 /* Check for HTML_AJAX PEAR package, lent from Horde project */
2606 ini_set('track_errors', 1);
2607 @include_once 'HTML/AJAX/Server.php';
2608 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2609 print "PEAR HTML_AJAX package is missing<br />\n";
2612 @include_once 'Calendar/Calendar.php';
2613 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2614 print "PEAR Calendar package is missing<br />\n";
2617 @include_once 'Console/Getopt.php';
2618 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2619 print "PEAR Console_Getopt package is missing<br />\n";
2622 @include_once 'Date.php';
2623 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2624 print "PEAR Date package is missing<br />\n";
2627 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2628 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2629 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2632 ini_restore('track_errors');
2639 } // check_requirements()
2641 private function _debug($text)
2643 if(isset($this->fromcmd)) {
2650 * check if specified MIME type is supported
2651 * @param string $mime
2654 public function checkifImageSupported($mime)
2656 $supported_types = Array(
2659 "image/x-portable-pixmap",
2663 if(in_array($mime, $supported_types))
2668 } // checkifImageSupported()
2672 * @param string $text
2674 public function _error($text)
2676 switch($this->cfg->logging) {
2679 if(isset($this->fromcmd))
2682 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2683 print $text ."<br />\n";
2690 error_log($text, 3, $this->cfg->log_file);
2694 $this->runtime_error = true;
2699 * get calendar input-text fields
2701 * this function returns a text-field used for the data selection.
2702 * Either it will be filled with the current date or, if available,
2703 * filled with the date user entered previously.
2705 * @param string $mode
2708 private function get_date_text_field($mode)
2710 $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2712 $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2714 $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2716 $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\"";
2717 if(!isset($_SESSION[$mode .'_date']))
2718 $output.= " disabled=\"disabled\"";
2723 } // get_date_text_field()
2726 * output calendar matrix
2727 * @param integer $year
2728 * @param integer $month
2729 * @param integer $day
2731 public function get_calendar_matrix($userdate)
2733 if(($userdate = strtotime($userdate)) === false) {
2740 $date->setDate($userdate);
2742 $year = $date->getYear();
2743 $month = $date->getMonth();
2744 $day = $date->getDay();
2751 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2752 require_once CALENDAR_ROOT.'Day.php';
2755 $month_cal = new Calendar_Month_Weekdays($year,$month);
2758 $prevStamp = $month_cal->prevMonth(true);
2759 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2760 $nextStamp = $month_cal->nextMonth(true);
2761 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2763 $selectedDays = array (
2764 new Calendar_Day($year,$month,$day),
2767 // Build the days in the month
2768 $month_cal->build($selectedDays);
2770 $this->tmpl->assign('current_month', date('F Y',$month_cal->getTimeStamp()));
2771 $this->tmpl->assign('prev_month', $prev);
2772 $this->tmpl->assign('next_month', $next);
2774 while ( $day = $month_cal->fetch() ) {
2776 if(!isset($matrix[$rows]))
2777 $matrix[$rows] = Array();
2781 $dayStamp = $day->thisDay(true);
2782 $link = "javascript:setCalendarDate('"
2783 . date('Y',$dayStamp)
2785 . date('m',$dayStamp)
2787 . date('d',$dayStamp)
2790 // isFirst() to find start of week
2791 if ( $day->isFirst() )
2794 if ( $day->isSelected() ) {
2795 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2796 } else if ( $day->isEmpty() ) {
2797 $string.= "<td> </td>\n";
2799 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2802 // isLast() to find end of week
2803 if ( $day->isLast() )
2804 $string.= "</tr>\n";
2806 $matrix[$rows][$cols] = $string;
2816 $this->tmpl->assign('matrix', $matrix);
2817 $this->tmpl->assign('rows', $rows);
2818 $this->tmpl->show("calendar.tpl");
2820 } // get_calendar_matrix()
2823 * output export page
2824 * @param string $mode
2826 public function getExport($mode)
2828 $pictures = $this->getPhotoSelection();
2829 $current_tags = $this->getCurrentTags();
2831 foreach($pictures as $picture) {
2833 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2834 if($current_tags != "") {
2835 $orig_url.= "&tags=". $current_tags;
2837 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2838 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2841 if($this->is_user_friendly_url()) {
2842 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2845 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2851 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2852 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2856 // "[%pictureurl% %thumbnailurl%]"
2857 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2860 case 'MoinMoinList':
2861 // " * [%pictureurl% %thumbnailurl%]"
2862 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2873 public function getRSSFeed()
2875 Header("Content-type: text/xml; charset=utf-8");
2876 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2879 xmlns:media="http://search.yahoo.com/mrss/"
2880 xmlns:dc="http://purl.org/dc/elements/1.1/"
2883 <title>phpfspot</title>
2884 <description>phpfspot RSS feed</description>
2885 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2886 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2887 <generator>phpfspot</generator>
2890 $pictures = $this->getPhotoSelection();
2891 $current_tags = $this->getCurrentTags();
2893 foreach($pictures as $picture) {
2895 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2896 if($current_tags != "") {
2897 $orig_url.= "&tags=". $current_tags;
2899 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2900 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2903 $details = $this->get_photo_details($picture);
2905 if($this->is_user_friendly_url()) {
2906 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2909 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2912 $thumb_html = htmlspecialchars("
2913 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2915 ". $details['description']);
2917 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2919 /* get EXIF information if JPEG */
2920 if(isset($details['mime']) && $details['mime'] == "image/jpeg") {
2921 $meta = $this->get_meta_informations($orig_path);
2926 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2927 <link><?php print htmlspecialchars($orig_url); ?></link>
2928 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2929 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken>
2931 <?php print $thumb_html; ?>
2933 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate>
2948 * get all selected tags
2950 * This function will return all selected tags as one string, seperated
2954 private function getCurrentTags()
2957 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2958 foreach($_SESSION['selected_tags'] as $tag)
2959 $current_tags.= $tag .",";
2960 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2962 return $current_tags;
2964 } // getCurrentTags()
2967 * return the current photo
2969 public function get_current_photo()
2971 if(isset($_SESSION['current_photo'])) {
2972 return $_SESSION['current_photo'];
2977 } // get_current_photo()
2980 * current selected photo version
2982 * this function returns the current selected photo version
2983 * from the session variables.
2987 public function get_current_version()
2989 /* if current version is set, return it, if the photo really has that version */
2990 if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version']))
2991 return $_SESSION['current_version'];
2995 } // get_current_version()
2998 * returns latest available photo version
3000 * this function returns the latested available version
3001 * for the requested photo.
3005 public function get_latest_version($photo_idx)
3007 /* try to get the lasted version for the current photo */
3008 if($versions = $this->get_photo_versions($photo_idx))
3009 return $versions[count($versions)-1];
3011 /* if no alternative version were found, return original version */
3014 } // get_current_version()
3017 * tells the client browser what to do
3019 * this function is getting called via AJAX by the
3020 * client browsers. it will tell them what they have
3021 * to do next. This is necessary for directly jumping
3022 * into photo index or single photo view when the are
3023 * requested with specific URLs
3026 public function whatToDo()
3028 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
3030 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
3031 return "showpi_tags";
3033 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
3040 * return the current process-user
3043 private function getuid()
3045 if($uid = posix_getuid()) {
3046 if($user = posix_getpwuid($uid)) {
3047 return $user['name'];
3056 * photo version select list
3058 * this function returns a HTML select list (drop down)
3059 * to select a alternative photo version of the original photo.
3061 * @param array $params
3062 * @param smarty $smarty
3065 public function smarty_photo_version_select_list($params, &$smarty)
3067 if(!isset($params['photo']) || !isset($params['current']))
3070 $output = "<option value=\"0\">Original</option>";
3071 $versions = $this->get_photo_versions($params['photo']);
3073 foreach($versions as $version) {
3075 $output.= "<option value=\"". $version ."\"";
3076 if($version == $params['current']) {
3077 $output.= " selected=\"selected\"";
3079 $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>";
3084 } // smarty_photo_version_select_list()
3087 * returns a select-dropdown box to select photo index sort parameters
3088 * @param array $params
3089 * @param smarty $smarty
3092 public function smarty_sort_select_list($params, &$smarty)
3096 foreach($this->sort_orders as $key => $value) {
3097 $output.= "<option value=\"". $key ."\"";
3098 if($key == $_SESSION['sort_order']) {
3099 $output.= " selected=\"selected\"";
3101 $output.= ">". $value ."</option>";
3106 } // smarty_sort_select_list()
3109 * returns the currently selected sort order
3112 private function get_sort_order()
3114 switch($_SESSION['sort_order']) {
3116 return " ORDER BY p.time ASC";
3119 return " ORDER BY p.time DESC";
3122 if($this->dbver < 9) {
3123 return " ORDER BY p.name ASC";
3126 return " ORDER BY basename(p.uri) ASC";
3130 if($this->dbver < 9) {
3131 return " ORDER BY p.name DESC";
3134 return " ORDER BY basename(p.uri) DESC";
3138 return " ORDER BY t.name ASC ,p.time ASC";
3141 return " ORDER BY t.name DESC ,p.time ASC";
3144 return " ORDER BY p.rating ASC, t.name ASC";
3147 return " ORDER BY p.rating DESC, t.name ASC";
3151 } // get_sort_order()
3154 * return the next to be shown slide show image
3156 * this function returns the URL of the next image
3157 * in the slideshow sequence.
3160 public function getNextSlideShowImage()
3162 $all_photos = $this->getPhotoSelection();
3164 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
3165 $_SESSION['slideshow_img'] = 0;
3167 $_SESSION['slideshow_img']++;
3169 if($this->is_user_friendly_url()) {
3170 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3173 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3175 } // getNextSlideShowImage()
3178 * return the previous to be shown slide show image
3180 * this function returns the URL of the previous image
3181 * in the slideshow sequence.
3184 public function getPrevSlideShowImage()
3186 $all_photos = $this->getPhotoSelection();
3188 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
3189 $_SESSION['slideshow_img'] = 0;
3191 $_SESSION['slideshow_img']--;
3193 if($this->is_user_friendly_url()) {
3194 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3197 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3199 } // getPrevSlideShowImage()
3201 public function resetSlideShow()
3203 if(isset($_SESSION['slideshow_img']))
3204 unset($_SESSION['slideshow_img']);
3206 } // resetSlideShow()
3211 * this function will get all photos from the fspot
3212 * database and randomly return ONE entry
3214 * saddly there is yet no sqlite3 function which returns
3215 * the bulk result in array, so we have to fill up our
3219 public function get_random_photo()
3228 /* if show_tags is set, only return details for photos which
3229 are specified to be shown
3231 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3233 INNER JOIN photo_tags pt
3238 t.name IN ('".implode("','",$this->cfg->show_tags)."')";
3241 $result = $this->db->db_query($query_str);
3243 while($row = $this->db->db_fetch_object($result)) {
3244 array_push($all, $row['id']);
3247 return $all[array_rand($all)];
3249 } // get_random_photo()
3252 * get random photo tag photo
3254 * this function will get all photos tagged with the requested
3255 * tag from the fspot database and randomly return ONE entry
3257 * saddly there is yet no sqlite3 function which returns
3258 * the bulk result in array, so we have to fill up our
3262 public function get_random_tag_photo($tagidx)
3266 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3269 DISTINCT pt1.photo_id as id
3272 INNER JOIN photo_tags
3273 pt2 ON pt1.photo_id=pt2.photo_id
3279 pt1.tag_id LIKE '". $tagidx ."'
3281 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
3283 t1.sort_priority ASC";
3291 INNER JOIN photo_tags pt
3294 pt.tag_id LIKE '". $tagidx ."'";
3297 $result = $this->db->db_query($query_str);
3299 while($row = $this->db->db_fetch_object($result)) {
3300 array_push($all, $row['id']);
3303 return $all[array_rand($all)];
3305 } // get_random_tag_photo()
3308 * validates provided date
3310 * this function validates if the provided date
3311 * contains a valid date and will return true
3313 * @param string $date_str
3316 public function isValidDate($date_str)
3318 $timestamp = strtotime($date_str);
3320 if(is_numeric($timestamp))
3328 * timestamp to string conversion
3329 * @param integer $timestamp
3332 private function ts2str($timestamp)
3334 if(!empty($timestamp) && is_numeric($timestamp))
3335 return strftime("%Y-%m-%d", $timestamp);
3340 * extract tag-names from $_GET['tags']
3341 * @param string $tags_str
3344 private function extractTags($tags_str)
3346 $not_validated = split(',', $tags_str);
3347 $validated = array();
3349 foreach($not_validated as $tag) {
3350 if(is_numeric($tag))
3351 array_push($validated, $tag);
3359 * returns the full path to a thumbnail
3360 * @param integer $width
3361 * @param integer $photo
3364 public function get_thumb_path($width, $photo_idx, $version_idx)
3366 $md5 = $this->getMD5($photo_idx, $version_idx);
3367 $sub_path = substr($md5, 0, 2);
3368 return $this->cfg->thumb_path
3376 } // get_thumb_path()
3379 * returns server's virtual host name
3382 private function get_server_name()
3384 return $_SERVER['SERVER_NAME'];
3385 } // get_server_name()
3388 * returns type of webprotocol which is currently used
3391 private function get_web_protocol()
3393 if(!isset($_SERVER['HTTPS']))
3397 } // get_web_protocol()
3400 * return url to this phpfspot installation
3403 private function get_phpfspot_url()
3405 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3407 } // get_phpfspot_url()
3410 * returns the number of photos which are tagged with $tag_id
3411 * @param integer $tag_id
3414 public function get_num_photos($tag_id)
3416 if($result = $this->db->db_fetchSingleRow("
3417 SELECT count(*) as number
3420 tag_id LIKE '". $tag_id ."'")) {
3422 return $result['number'];
3428 } // get_num_photos()
3431 * check file exists and is readable
3433 * returns true, if everything is ok, otherwise false
3434 * if $silent is not set, this function will output and
3436 * @param string $file
3437 * @param boolean $silent
3440 private function check_readable($file, $silent = null)
3442 if(!file_exists($file)) {
3444 print "File \"". $file ."\" does not exist.\n";
3448 if(!is_readable($file)) {
3450 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3456 } // check_readable()
3459 * check if all needed indices are present
3461 * this function checks, if some needed indices are already
3462 * present, or if not, create them on the fly. they are
3463 * necessary to speed up some queries like that one look for
3464 * all tags, when show_tags is specified in the configuration.
3466 private function checkDbIndices()
3468 $result = $this->db->db_exec("
3469 CREATE INDEX IF NOT EXISTS
3476 } // checkDbIndices()
3479 * retrive F-Spot database version
3481 * this function will return the F-Spot database version number
3482 * It is stored within the sqlite3 database in the table meta
3483 * @return string|null
3485 public function getFspotDBVersion()
3487 if($result = $this->db->db_fetchSingleRow("
3488 SELECT data as version
3491 name LIKE 'F-Spot Database Version'
3493 return $result['version'];
3497 } // getFspotDBVersion()
3500 * parse the provided URI and will returned the requested chunk
3501 * @param string $uri
3502 * @param string $mode
3505 public function parse_uri($uri, $mode)
3507 if(($components = parse_url($uri)) === false)
3512 return basename($components['path']);
3515 return dirname($components['path']);
3518 return $components['path'];
3521 $this->throwError("unknown mode ". $mode);
3528 * validate config options
3530 * this function checks if all necessary configuration options are
3531 * specified and set.
3534 private function check_config_options()
3536 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
3537 $this->_error("Please set \$page_title in phpfspot_cfg");
3539 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
3540 $this->_error("Please set \$base_path in phpfspot_cfg");
3542 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
3543 $this->_error("Please set \$web_path in phpfspot_cfg");
3545 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
3546 $this->_error("Please set \$thumb_path in phpfspot_cfg");
3548 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
3549 $this->_error("Please set \$smarty_path in phpfspot_cfg");
3551 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
3552 $this->_error("Please set \$fspot_db in phpfspot_cfg");
3554 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
3555 $this->_error("Please set \$db_access in phpfspot_cfg");
3557 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
3558 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
3560 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
3561 $this->_error("Please set \$thumb_width in phpfspot_cfg");
3563 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
3564 $this->_error("Please set \$thumb_height in phpfspot_cfg");
3566 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
3567 $this->_error("Please set \$photo_width in phpfspot_cfg");
3569 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
3570 $this->_error("Please set \$mini_width in phpfspot_cfg");
3572 if(!isset($this->cfg->thumbs_per_page))
3573 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
3575 if(!isset($this->cfg->enable_replace_path))
3576 $this->_error("Please set \$enable_replace_path in phpfspot_cfg");
3578 if($this->cfg->enable_replace_path == true) {
3579 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
3580 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
3582 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
3583 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
3586 if(!isset($this->cfg->hide_tags))
3587 $this->_error("Please set \$hide_tags in phpfspot_cfg");
3589 if(!isset($this->cfg->theme_name))
3590 $this->_error("Please set \$theme_name in phpfspot_cfg");
3592 if(!isset($this->cfg->logging))
3593 $this->_error("Please set \$logging in phpfspot_cfg");
3595 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3597 if(!isset($this->cfg->log_file))
3598 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3600 if(!is_writeable($this->cfg->log_file))
3601 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3605 /* remove trailing slash, if set */
3606 if($this->cfg->web_path == "/")
3607 $this->cfg->web_path = "";
3608 elseif(preg_match('/\/$/', $this->cfg->web_path))
3609 $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3611 return $this->runtime_error;
3613 } // check_config_options()
3616 * cleanup phpfspot own database
3618 * When photos are getting delete from F-Spot, there will remain
3619 * remain some residues in phpfspot own database. This function
3620 * will try to wipe them out.
3622 public function cleanup_phpfspot_db()
3624 $to_delete = Array();
3626 $result = $this->cfg_db->db_query("
3627 SELECT img_idx as img_idx
3629 ORDER BY img_idx ASC
3632 while($row = $this->cfg_db->db_fetch_object($result)) {
3633 if(!$this->db->db_fetchSingleRow("
3636 WHERE id='". $row['img_idx'] ."'")) {
3638 array_push($to_delete, $row['img_idx'], ',');
3642 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3644 $this->cfg_db->db_exec("
3646 WHERE img_idx IN (". implode($to_delete) .")
3649 } // cleanup_phpfspot_db()
3652 * return first image of the page, the $current photo
3655 * this function is used to find out the first photo of the
3656 * current page, in which the $current photo lies. this is
3657 * used to display the correct photo, when calling showPhotoIndex()
3659 * @param integer $current
3660 * @param integer $max
3663 private function getCurrentPage($current, $max)
3665 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3666 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3667 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3673 } // getCurrentPage()
3678 * this function tries to find out the correct mime-type
3679 * for the provided file.
3680 * @param string $file
3683 public function get_mime_info($file)
3685 $details = getimagesize($file);
3687 /* if getimagesize() returns empty, try at least to find out the
3690 if(empty($details) && function_exists('mime_content_type')) {
3692 // mime_content_type is marked as deprecated in the documentation,
3693 // but is it really necessary to force users to install a PECL
3695 $details['mime'] = mime_content_type($file);
3698 return $details['mime'];
3700 } // get_mime_info()
3703 * return tag-name by tag-idx
3705 * this function returns the tag-name for the requested
3706 * tag specified by tag-idx.
3707 * @param integer $idx
3710 public function get_tag_name($idx)
3712 if($result = $this->db->db_fetchSingleRow("
3716 id LIKE '". $idx ."'")) {
3718 return $result['name'];
3727 * parse user friendly url which got rewritten by the websever
3728 * @param string $request_uri
3731 private function parse_user_friendly_url($request_uri)
3733 if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3735 $options = explode('/', $request_uri);
3737 switch($options[1]) {
3739 if(is_numeric($options[2])) {
3740 $this->session_cleanup();
3741 //unset($_SESSION['start_action']);
3742 //unset($_SESSION['selected_tags']);
3743 $_GET['mode'] = 'showp';
3744 return $this->showPhoto($options[2]);
3748 if(is_numeric($options[2])) {
3751 if(isset($options[3]) && is_numeric($options[3]))
3752 $width = $options[3];
3753 if(isset($options[4]) && is_numeric($options[4]))
3754 $version = $options[4];
3755 require_once "phpfspot_img.php";
3756 $img = new PHPFSPOT_IMG;
3757 $img->showImg($options[2], $width, $version);
3762 if(is_numeric($options[2])) {
3763 $this->session_cleanup();
3764 $_GET['tags'] = $options[2];
3765 $_SESSION['selected_tags'] = Array($options[2]);
3766 if(isset($options[3]) && is_numeric($options[3]))
3767 $_SESSION['begin_with'] = $options[3];
3768 return $this->showPhotoIndex();
3774 } // parse_user_friendly_url()
3777 * check if user-friendly-urls are enabled
3779 * this function will return true, if the config option
3780 * $user_friendly_url has been set. Otherwise false.
3783 private function is_user_friendly_url()
3785 if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3790 } // is_user_friendly_url()
3795 * this function will cleanup user's session information
3797 private function session_cleanup()
3799 unset($_SESSION['begin_with']);
3800 $this->resetDateSearch();
3801 $this->resetPhotoView();
3802 $this->resetTagSearch();
3803 $this->resetNameSearch();
3804 $this->resetDateSearch();
3807 } // session_cleanup()
3810 * get database version
3812 * this function queries the meta table
3813 * and returns the current database version.
3817 public function get_db_version()
3819 if($row = $this->cfg_db->db_fetchSingleRow("
3820 SELECT meta_value as meta_value
3824 meta_key LIKE 'phpfspot Database Version'
3827 return $row['meta_value'];
3833 } // get_db_version()
3836 * get photo versions
3838 * this function returns an array of all available
3839 * alterntaive versions of the provided photo id.
3840 * has alternative photo versions available
3845 public function get_photo_versions($idx)
3847 $versions = Array();
3849 $result = $this->db->db_query("
3855 photo_id LIKE '". $idx ."'");
3857 while($row = $this->cfg_db->db_fetch_object($result)) {
3858 array_push($versions, $row['version_id']);
3863 } // get_photo_versions()
3866 * check for invalid version of photo
3868 * this function validates the provided photo-id and version-id
3870 * @param int $photo_idx
3871 * @param int $version_idx
3874 public function is_valid_version($photo_idx, $version_idx)
3876 /* the original version is always valid */
3877 if($version_idx == 0)
3880 if($versions = $this->get_photo_versions($photo_idx)) {
3881 if(in_array($version_idx, $versions))
3887 } // is_valid_version()
3890 * get photo version name
3892 * this function returns the name of the version
3893 * identified by the photo-id and version-id.
3895 * @param int $photo_idx
3896 * @param int $version_idx
3899 public function get_photo_version_name($photo_idx, $version_idx)
3901 if($row = $this->db->db_fetchSingleRow("
3907 photo_id LIKE '". $photo_idx ."'
3909 version_id LIKE '". $version_idx ."'")) {
3911 return $row['name'];
3917 } // get_photo_version_name()
3921 public function is_valid_width($image_width)
3923 if(in_array($image_width,
3924 Array($this->cfg->thumb_width,
3925 $this->cfg->photo_width,
3926 $this->cfg->mini_width,
3933 } // is_valid_width()