3 /***************************************************************************
5 * Copyright (c) by Andreas Unterkircher, unki@netshadow.at
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 ***************************************************************************/
24 require_once "phpfspot_cfg.php";
25 require_once "phpfspot_db.php";
36 private $runtime_error = false;
42 * this function will be called on class construct
43 * and will check requirements, loads configuration,
44 * open databases and start the user session
46 public function __construct()
48 $this->cfg = new PHPFSPOT_CFG;
50 /* verify config settings */
51 if($this->check_config_options()) {
55 /* set application name and version information */
56 $this->cfg->product = "phpfspot";
57 $this->cfg->version = "1.4";
59 $this->sort_orders= array(
60 'date_asc' => 'Date ↑',
61 'date_desc' => 'Date ↓',
62 'name_asc' => 'Name ↑',
63 'name_desc' => 'Name ↓',
64 'tags_asc' => 'Tags ↑',
65 'tags_desc' => 'Tags ↓',
68 /* Check necessary requirements */
69 if(!$this->check_requirements()) {
73 /******* Opening F-Spot's sqlite database *********/
75 /* Check if database file is writeable */
76 if(!is_writeable($this->cfg->fspot_db)) {
77 print $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() ."\n";
81 /* open the database */
82 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
84 /* change sqlite temp directory, if requested */
85 if(isset($this->cfg->sqlite_temp_dir)) {
88 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
92 $this->dbver = $this->getFspotDBVersion();
94 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
95 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
99 if(!is_writeable($this->cfg->thumb_path)) {
100 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
104 /******* Opening phpfspot's sqlite database *********/
106 /* Check if directory where the database file is stored is writeable */
107 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
108 print dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() ."\n";
112 /* Check if database file is writeable */
113 if(!is_writeable($this->cfg->phpfspot_db)) {
114 print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
118 /* open the database */
119 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
121 /* change sqlite temp directory, if requested */
122 if(isset($this->cfg->sqlite_temp_dir)) {
123 $this->cfg_db->db_exec("
125 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
129 /* Check if some tables need to be created */
130 $this->check_config_table();
132 /* overload Smarty class with our own template handler */
133 require_once "phpfspot_tmpl.php";
134 $this->tmpl = new PHPFSPOT_TMPL($this);
136 /* check if all necessary indices exist */
137 $this->checkDbIndices();
139 /* if session is not yet started, do it now */
140 if(session_id() == "")
143 if(!isset($_SESSION['tag_condition']))
144 $_SESSION['tag_condition'] = 'or';
146 if(!isset($_SESSION['sort_order']))
147 $_SESSION['sort_order'] = 'date_desc';
149 if(!isset($_SESSION['searchfor_tag']))
150 $_SESSION['searchfor_tag'] = '';
152 // if begin_with is still set but thumbs_per_page is now 0, unset it
153 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
154 unset($_SESSION['begin_with']);
158 public function __destruct()
164 * show - generate html output
166 * this function can be called after the constructor has
167 * prepared everyhing. it will load the index.tpl smarty
168 * template. if necessary it will registere pre-selects
169 * (photo index, photo, tag search, date search) into
172 public function show()
174 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
175 $this->tmpl->assign('page_title', $this->cfg->page_title);
176 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
177 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
179 if(isset($_GET['mode'])) {
181 $_SESSION['start_action'] = $_GET['mode'];
183 switch($_GET['mode']) {
185 if(isset($_GET['tags'])) {
186 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
188 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
189 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
191 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
192 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
196 if(isset($_GET['tags'])) {
197 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
198 $_SESSION['start_action'] = 'showp';
200 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
201 $_SESSION['current_photo'] = $_GET['id'];
202 $_SESSION['start_action'] = 'showp';
204 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
205 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
207 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
208 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
212 $this->tmpl->show("export.tpl");
216 $this->tmpl->show("slideshow.tpl");
220 if(isset($_GET['tags'])) {
221 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
223 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
224 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
226 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
227 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
235 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
236 $this->tmpl->assign('date_search_enabled', true);
238 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
239 $this->tmpl->assign('from_date', $this->get_calendar('from'));
240 $this->tmpl->assign('to_date', $this->get_calendar('to'));
241 $this->tmpl->assign('content_page', 'welcome.tpl');
242 $this->tmpl->show("index.tpl");
247 * get_tags - grab all tags of f-spot's database
249 * this function will get all available tags from
250 * the f-spot database and store them within two
251 * arrays within this class for later usage. in
252 * fact, if the user requests (hide_tags) it will
253 * opt-out some of them.
255 * this function is getting called once by show()
257 private function get_tags()
259 $this->avail_tags = Array();
262 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
265 DISTINCT t1.id as id, t1.name as name
268 INNER JOIN photo_tags
269 pt2 ON pt1.photo_id=pt2.photo_id
275 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
277 t1.sort_priority ASC";
279 $result = $this->db->db_query($query_str);
283 $result = $this->db->db_query("
286 ORDER BY sort_priority ASC
290 while($row = $this->db->db_fetch_object($result)) {
292 $tag_id = $row['id'];
293 $tag_name = $row['name'];
295 /* if the user has specified to ignore this tag in phpfspot's
296 configuration, ignore it here so it does not get added to
299 if(in_array($row['name'], $this->cfg->hide_tags))
302 /* if you include the following if-clause and the user has specified
303 to only show certain tags which are specified in phpfspot's
304 configuration, ignore all others so they will not be added to the
306 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
307 !in_array($row['name'], $this->cfg->show_tags))
311 $this->tags[$tag_id] = $tag_name;
312 $this->avail_tags[$count] = $tag_id;
320 * extract all photo details
322 * retrieve all available details from f-spot's
323 * database and return them as object
325 public function get_photo_details($idx)
327 if($this->dbver < 9) {
329 SELECT p.id, p.name, p.time, p.directory_path, p.description
335 SELECT p.id, p.uri, p.time, p.description
340 /* if show_tags is set, only return details for photos which
341 are specified to be shown
343 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
345 INNER JOIN photo_tags pt
349 WHERE p.id='". $idx ."'
350 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
354 WHERE p.id='". $idx ."'
358 if($result = $this->db->db_query($query_str)) {
360 $row = $this->db->db_fetch_object($result);
362 if($this->dbver < 9) {
363 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
372 } // get_photo_details
375 * returns aligned photo names
377 * this function returns aligned (length) names for
378 * an specific photo. If the length of the name exceeds
379 * $limit the name will be shrinked (...)
381 public function getPhotoName($idx, $limit = 0)
383 if($details = $this->get_photo_details($idx)) {
384 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
385 $name = $this->shrink_text($long_name, $limit);
395 * shrink text according provided limit
397 * If the length of the name exceeds $limit the
398 * text will be shortend and some content in between
399 * will be replaced with "..."
401 private function shrink_text($text, $limit)
403 if($limit != 0 && strlen($text) > $limit) {
404 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
412 * translate f-spoth photo path
414 * as the full-qualified path recorded in the f-spot database
415 * is usally not the same as on the webserver, this function
416 * will replace the path with that one specified in the cfg
418 public function translate_path($path, $width = 0)
420 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
425 * control HTML ouput for a single photo
427 * this function provides all the necessary information
428 * for the single photo template.
430 public function showPhoto($photo)
432 /* get all photos from the current photo selection */
433 $all_photos = $this->getPhotoSelection();
434 $count = count($all_photos);
436 for($i = 0; $i < $count; $i++) {
438 // $get_next will be set, when the photo which has to
439 // be displayed has been found - this means that the
440 // next available is in fact the NEXT image (for the
442 if(isset($get_next)) {
443 $next_img = $all_photos[$i];
447 /* the next photo is our NEXT photo */
448 if($all_photos[$i] == $photo) {
452 $previous_img = $all_photos[$i];
455 if($photo == $all_photos[$i]) {
460 $details = $this->get_photo_details($photo);
467 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
468 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
470 if(!file_exists($orig_path)) {
471 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
475 if(!is_readable($orig_path)) {
476 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
480 /* If the thumbnail doesn't exist yet, try to create it */
481 if(!file_exists($thumb_path)) {
482 $this->gen_thumb($photo, true);
483 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
486 /* get EXIF information if JPEG */
487 if($details['mime'] == "image/jpeg") {
488 $meta = $this->get_meta_informations($orig_path);
491 /* If EXIF data are available, use them */
492 if(isset($meta['ExifImageWidth'])) {
493 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
495 $info = getimagesize($orig_path);
496 $meta_res = $info[0] ."x". $info[1];
499 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
500 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
501 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
503 $extern_link = "index.php?mode=showp&id=". $photo;
504 $current_tags = $this->getCurrentTags();
505 if($current_tags != "") {
506 $extern_link.= "&tags=". $current_tags;
508 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
509 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
512 $this->tmpl->assign('extern_link', $extern_link);
514 if(!file_exists($thumb_path)) {
515 $this->_error("Can't open file ". $thumb_path ."\n");
519 $info = getimagesize($thumb_path);
521 $this->tmpl->assign('description', $details['description']);
522 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
524 $this->tmpl->assign('width', $info[0]);
525 $this->tmpl->assign('height', $info[1]);
526 $this->tmpl->assign('ExifMadeOn', $meta_date);
527 $this->tmpl->assign('ExifMadeWith', $meta_make);
528 $this->tmpl->assign('ExifOrigResolution', $meta_res);
529 $this->tmpl->assign('ExifFileSize', $meta_size);
531 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
532 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
533 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
535 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
536 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
537 $this->tmpl->assign('current_img', $photo);
540 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
541 $this->tmpl->assign('prev_img', $previous_img);
545 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
546 $this->tmpl->assign('next_img', $next_img);
548 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
549 $this->tmpl->assign('photo_number', $i);
550 $this->tmpl->assign('photo_count', count($all_photos));
552 $this->tmpl->show("single_photo.tpl");
557 * all available tags and tag cloud
559 * this function outputs all available tags (time ordered)
560 * and in addition output them as tag cloud (tags which have
561 * many photos will appears more then others)
563 public function getAvailableTags()
565 /* retrive tags from database */
570 $result = $this->db->db_query("
571 SELECT tag_id as id, count(tag_id) as quantity
581 while($row = $this->db->db_fetch_object($result)) {
582 $tags[$row['id']] = $row['quantity'];
585 // change these font sizes if you will
586 $max_size = 125; // max font size in %
587 $min_size = 75; // min font size in %
590 $max_sat = hexdec('cc');
591 $min_sat = hexdec('44');
593 // get the largest and smallest array values
594 $max_qty = max(array_values($tags));
595 $min_qty = min(array_values($tags));
597 // find the range of values
598 $spread = $max_qty - $min_qty;
599 if (0 == $spread) { // we don't want to divide by zero
603 // determine the font-size increment
604 // this is the increase per tag quantity (times used)
605 $step = ($max_size - $min_size)/($spread);
606 $step_sat = ($max_sat - $min_sat)/($spread);
608 // loop through our tag array
609 foreach ($tags as $key => $value) {
611 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
614 // calculate CSS font-size
615 // find the $value in excess of $min_qty
616 // multiply by the font-size increment ($size)
617 // and add the $min_size set above
618 $size = $min_size + (($value - $min_qty) * $step);
619 // uncomment if you want sizes in whole %:
622 $color = $min_sat + ($value - $min_qty) * $step_sat;
628 if(isset($this->tags[$key])) {
629 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\">". $this->tags[$key] ."</a>, ";
634 $output = substr($output, 0, strlen($output)-2);
637 } // getAvailableTags()
640 * output all selected tags
642 * this function output all tags which have been selected
643 * by the user. the selected tags are stored in the
644 * session-variable $_SESSION['selected_tags']
646 public function getSelectedTags()
648 /* retrive tags from database */
653 foreach($this->avail_tags as $tag)
655 // return all selected tags
656 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
657 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
662 $output = substr($output, 0, strlen($output)-2);
666 return "no tags selected";
669 } // getSelectedTags()
672 * add tag to users session variable
674 * this function will add the specified to users current
675 * tag selection. if a date search has been made before
676 * it will be now cleared
678 public function addTag($tag)
680 if(!isset($_SESSION['selected_tags']))
681 $_SESSION['selected_tags'] = Array();
683 if(isset($_SESSION['searchfor_tag']))
684 unset($_SESSION['searchfor_tag']);
686 if(!in_array($tag, $_SESSION['selected_tags']))
687 array_push($_SESSION['selected_tags'], $tag);
695 * remove tag to users session variable
697 * this function removes the specified tag from
698 * users current tag selection
700 public function delTag($tag)
702 if(isset($_SESSION['searchfor_tag']))
703 unset($_SESSION['searchfor_tag']);
705 if(isset($_SESSION['selected_tags'])) {
706 $key = array_search($tag, $_SESSION['selected_tags']);
707 unset($_SESSION['selected_tags'][$key]);
708 sort($_SESSION['selected_tags']);
716 * reset tag selection
718 * if there is any tag selection, it will be
721 public function resetTags()
723 if(isset($_SESSION['selected_tags']))
724 unset($_SESSION['selected_tags']);
729 * returns the value for the autocomplet tag-search
731 public function get_xml_tag_list()
733 if(!isset($_GET['search']) || !is_string($_GET['search']))
734 $_GET['search'] = '';
739 /* retrive tags from database */
742 $matched_tags = Array();
744 header("Content-Type: text/xml");
746 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
747 $string.= "<results>\n";
749 foreach($this->avail_tags as $tag)
751 if(!empty($_GET['search']) &&
752 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
753 count($matched_tags) < $length) {
755 $count = $this->get_num_photos($tag);
758 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
761 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
767 /* if we have collected enough items, break out */
768 if(count($matched_tags) >= $length)
772 $string.= "</results>\n";
776 } // get_xml_tag_list()
782 * if a specific photo was requested (external link)
783 * unset the session variable now
785 public function resetPhotoView()
787 if(isset($_SESSION['current_photo']))
788 unset($_SESSION['current_photo']);
790 } // resetPhotoView();
795 * if any tag search has taken place, reset it now
797 public function resetTagSearch()
799 if(isset($_SESSION['searchfor_tag']))
800 unset($_SESSION['searchfor_tag']);
802 } // resetTagSearch()
807 * if any name search has taken place, reset it now
809 public function resetNameSearch()
811 if(isset($_SESSION['searchfor_name']))
812 unset($_SESSION['searchfor_name']);
814 } // resetNameSearch()
819 * if any date search has taken place, reset
822 public function resetDateSearch()
824 if(isset($_SESSION['from_date']))
825 unset($_SESSION['from_date']);
826 if(isset($_SESSION['to_date']))
827 unset($_SESSION['to_date']);
829 } // resetDateSearch();
832 * return all photo according selection
834 * this function returns all photos based on
835 * the tag-selection, tag- or date-search.
836 * the tag-search also has to take care of AND
837 * and OR conjunctions
839 public function getPhotoSelection()
841 $matched_photos = Array();
842 $additional_where_cond = "";
844 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
845 $from_date = $_SESSION['from_date'];
846 $to_date = $_SESSION['to_date'];
847 $additional_where_cond.= "
848 p.time>='". $from_date ."'
850 p.time<='". $to_date ."'
854 if(isset($_SESSION['searchfor_name'])) {
855 if($this->dbver < 9) {
856 $additional_where_cond.= "
858 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
860 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
865 $additional_where_cond.= "
867 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
869 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
875 if(isset($_SESSION['sort_order'])) {
876 $order_str = $this->get_sort_order();
879 /* return a search result */
880 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
882 SELECT DISTINCT pt1.photo_id
884 INNER JOIN photo_tags pt2
885 ON pt1.photo_id=pt2.photo_id
892 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
894 if(isset($additional_where_cond) && !empty($additional_where_cond))
895 $query_str.= "AND ". $additional_where_cond ." ";
897 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
898 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
901 if(isset($order_str))
902 $query_str.= $order_str;
904 $result = $this->db->db_query($query_str);
905 while($row = $this->db->db_fetch_object($result)) {
906 array_push($matched_photos, $row['photo_id']);
908 return $matched_photos;
911 /* return according the selected tags */
912 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
914 foreach($_SESSION['selected_tags'] as $tag)
915 $selected.= $tag .",";
916 $selected = substr($selected, 0, strlen($selected)-1);
918 /* photo has to match at least on of the selected tags */
919 if($_SESSION['tag_condition'] == 'or') {
921 SELECT DISTINCT pt1.photo_id
923 INNER JOIN photo_tags pt2
924 ON pt1.photo_id=pt2.photo_id
929 WHERE pt1.tag_id IN (". $selected .")
931 if(isset($additional_where_cond) && !empty($additional_where_cond))
932 $query_str.= "AND ". $additional_where_cond ." ";
934 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
935 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
938 if(isset($order_str))
939 $query_str.= $order_str;
941 /* photo has to match all selected tags */
942 elseif($_SESSION['tag_condition'] == 'and') {
944 if(count($_SESSION['selected_tags']) >= 32) {
945 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
946 print "evaluate your tag selection. Please remove some tags from your selection.\n";
950 /* Join together a table looking like
952 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
954 so the query can quickly return all images matching the
955 selected tags in an AND condition
960 SELECT DISTINCT pt1.photo_id
964 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
971 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
973 INNER JOIN photo_tags pt". ($i+2) ."
974 ON pt1.photo_id=pt". ($i+2) .".photo_id
981 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
982 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
984 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
987 if(isset($additional_where_cond) && !empty($additional_where_cond))
988 $query_str.= "AND ". $additional_where_cond;
990 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
991 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
994 if(isset($order_str))
995 $query_str.= $order_str;
999 $result = $this->db->db_query($query_str);
1000 while($row = $this->db->db_fetch_object($result)) {
1001 array_push($matched_photos, $row['photo_id']);
1003 return $matched_photos;
1006 /* return all available photos */
1008 SELECT DISTINCT p.id
1010 LEFT JOIN photo_tags pt
1016 if(isset($additional_where_cond) && !empty($additional_where_cond))
1017 $query_str.= "WHERE ". $additional_where_cond ." ";
1019 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1020 if(isset($additional_where_cond) && !empty($additional_where_cond))
1021 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1023 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1026 if(isset($order_str))
1027 $query_str.= $order_str;
1029 $result = $this->db->db_query($query_str);
1030 while($row = $this->db->db_fetch_object($result)) {
1031 array_push($matched_photos, $row['id']);
1033 return $matched_photos;
1035 } // getPhotoSelection()
1038 * control HTML ouput for photo index
1040 * this function provides all the necessary information
1041 * for the photo index template.
1043 public function showPhotoIndex()
1045 $photos = $this->getPhotoSelection();
1047 $count = count($photos);
1049 /* if all thumbnails should be shown on one page */
1050 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1054 /* thumbnails should be splitted up in several pages */
1055 elseif($this->cfg->thumbs_per_page > 0) {
1057 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1061 $begin_with = $_SESSION['begin_with'];
1064 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1068 $images[$thumbs] = Array();
1069 $img_height[$thumbs] = Array();
1070 $img_width[$thumbs] = Array();
1071 $img_id[$thumbs] = Array();
1072 $img_name[$thumbs] = Array();
1073 $img_fullname[$thumbs] = Array();
1074 $img_title = Array();
1076 for($i = $begin_with; $i < $end_with; $i++) {
1078 if(isset($photos[$i])) {
1080 $images[$thumbs] = $photos[$i];
1081 $img_id[$thumbs] = $i;
1082 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1083 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1084 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1086 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1088 if(file_exists($thumb_path)) {
1089 $info = getimagesize($thumb_path);
1090 $img_width[$thumbs] = $info[0];
1091 $img_height[$thumbs] = $info[1];
1097 // +1 for for smarty's selection iteration
1100 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1101 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1103 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1104 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1105 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1108 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1109 $this->tmpl->assign('tag_result', 1);
1112 /* do we have to display the page selector ? */
1113 if($this->cfg->thumbs_per_page != 0) {
1117 /* calculate the page switchers */
1118 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1119 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1121 if($begin_with != 0)
1122 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1123 if($end_with < $count)
1124 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1126 $photo_per_page = $this->cfg->thumbs_per_page;
1127 $last_page = ceil($count / $photo_per_page);
1129 /* get the current selected page */
1130 if($begin_with == 0) {
1134 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1141 for($i = 1; $i <= $last_page; $i++) {
1143 if($current_page == $i)
1144 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1145 elseif($current_page-1 == $i || $current_page+1 == $i)
1146 $style = "style=\"font-size: 105%;\"";
1147 elseif(($current_page-5 >= $i) && ($i != 1) ||
1148 ($current_page+5 <= $i) && ($i != $last_page))
1149 $style = "style=\"font-size: 75%;\"";
1153 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1156 $select.= ">". $i ."</a> ";
1158 // until 9 pages we show the selector from 1-9
1159 if($last_page <= 9) {
1160 $page_select.= $select;
1163 if($i == 1 /* first page */ ||
1164 $i == $last_page /* last page */ ||
1165 $i == $current_page /* current page */ ||
1166 $i == ceil($last_page * 0.25) /* first quater */ ||
1167 $i == ceil($last_page * 0.5) /* half */ ||
1168 $i == ceil($last_page * 0.75) /* third quater */ ||
1169 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1170 (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 */ ||
1171 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1172 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1174 $page_select.= $select;
1182 $page_select.= "......... ";
1187 /* only show the page selector if we have more then one page */
1189 $this->tmpl->assign('page_selector', $page_select);
1193 $current_tags = $this->getCurrentTags();
1194 $extern_link = "index.php?mode=showpi";
1195 $rss_link = "index.php?mode=rss";
1196 if($current_tags != "") {
1197 $extern_link.= "&tags=". $current_tags;
1198 $rss_link.= "&tags=". $current_tags;
1200 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1201 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1202 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1205 $export_link = "index.php?mode=export";
1206 $slideshow_link = "index.php?mode=slideshow";
1208 $this->tmpl->assign('extern_link', $extern_link);
1209 $this->tmpl->assign('slideshow_link', $slideshow_link);
1210 $this->tmpl->assign('export_link', $export_link);
1211 $this->tmpl->assign('rss_link', $rss_link);
1212 $this->tmpl->assign('count', $count);
1213 $this->tmpl->assign('width', $this->cfg->thumb_width);
1214 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1215 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1216 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1217 $this->tmpl->assign('images', $images);
1218 $this->tmpl->assign('img_width', $img_width);
1219 $this->tmpl->assign('img_height', $img_height);
1220 $this->tmpl->assign('img_id', $img_id);
1221 $this->tmpl->assign('img_name', $img_name);
1222 $this->tmpl->assign('img_fullname', $img_fullname);
1223 $this->tmpl->assign('img_title', $img_title);
1224 $this->tmpl->assign('thumbs', $thumbs);
1226 $this->tmpl->show("photo_index.tpl");
1228 /* if we are returning to photo index from an photo-view,
1229 scroll the window to the last shown photo-thumbnail.
1230 after this, unset the last_photo session variable.
1232 if(isset($_SESSION['last_photo'])) {
1233 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1234 unset($_SESSION['last_photo']);
1237 } // showPhotoIndex()
1240 * show credit template
1242 public function showCredits()
1244 $this->tmpl->assign('version', $this->cfg->version);
1245 $this->tmpl->assign('product', $this->cfg->product);
1246 $this->tmpl->assign('db_version', $this->dbver);
1247 $this->tmpl->show("credits.tpl");
1252 * create_thumbnails for the requested width
1254 * this function creates image thumbnails of $orig_image
1255 * stored as $thumb_image. It will check if the image is
1256 * in a supported format, if necessary rotate the image
1257 * (based on EXIF orientation meta headers) and re-sizing.
1259 public function create_thumbnail($orig_image, $thumb_image, $width)
1261 if(!file_exists($orig_image)) {
1265 $details = getimagesize($orig_image);
1267 /* check if original photo is a support image type */
1268 if(!$this->checkifImageSupported($details['mime']))
1271 switch($details['mime']) {
1275 $meta = $this->get_meta_informations($orig_image);
1281 switch($meta['Orientation']) {
1282 case 1: /* top, left */
1283 /* nothing to do */ break;
1284 case 2: /* top, right */
1285 $rotate = 0; $flip_hori = true; break;
1286 case 3: /* bottom, left */
1287 $rotate = 180; break;
1288 case 4: /* bottom, right */
1289 $flip_vert = true; break;
1290 case 5: /* left side, top */
1291 $rotate = 90; $flip_vert = true; break;
1292 case 6: /* right side, top */
1293 $rotate = 90; break;
1294 case 7: /* left side, bottom */
1295 $rotate = 270; $flip_vert = true; break;
1296 case 8: /* right side, bottom */
1297 $rotate = 270; break;
1300 $src_img = @imagecreatefromjpeg($orig_image);
1306 $src_img = @imagecreatefrompng($orig_image);
1312 $src_img = new Imagick($orig_image);
1313 print_r($src_img->queryFormats());
1315 $handler = "imagick";
1326 if(!isset($src_img) || empty($src_img)) {
1327 print "Can't load image from ". $orig_image ."\n";
1331 /* grabs the height and width */
1332 $cur_width = imagesx($src_img);
1333 $cur_height = imagesy($src_img);
1335 // If requested width is more then the actual image width,
1336 // do not generate a thumbnail, instead safe the original
1337 // as thumbnail but with lower quality. But if the image
1338 // is to heigh too, then we still have to resize it.
1339 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1340 $result = imagejpeg($src_img, $thumb_image, 75);
1341 imagedestroy($src_img);
1345 // If the image will be rotate because EXIF orientation said so
1346 // 'virtually rotate' the image for further calculations
1347 if($rotate == 90 || $rotate == 270) {
1349 $cur_width = $cur_height;
1353 /* calculates aspect ratio */
1354 $aspect_ratio = $cur_height / $cur_width;
1357 if($aspect_ratio < 1) {
1359 $new_h = abs($new_w * $aspect_ratio);
1361 /* 'virtually' rotate the image and calculate it's ratio */
1362 $tmp_w = $cur_height;
1363 $tmp_h = $cur_width;
1364 /* now get the ratio from the 'rotated' image */
1365 $tmp_ratio = $tmp_h/$tmp_w;
1366 /* now calculate the new dimensions */
1368 $tmp_h = abs($tmp_w * $tmp_ratio);
1370 // now that we know, how high they photo should be, if it
1371 // gets rotated, use this high to scale the image
1373 $new_w = abs($new_h / $aspect_ratio);
1375 // If the image will be rotate because EXIF orientation said so
1376 // now 'virtually rotate' back the image for the image manipulation
1377 if($rotate == 90 || $rotate == 270) {
1384 /* creates new image of that size */
1385 $dst_img = imagecreatetruecolor($new_w, $new_h);
1387 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1389 /* copies resized portion of original image into new image */
1390 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1392 /* needs the image to be flipped horizontal? */
1394 $this->_debug("(FLIP)");
1395 $dst_img = $this->flipImage($dst_img, 'hori');
1397 /* needs the image to be flipped vertical? */
1399 $this->_debug("(FLIP)");
1400 $dst_img = $this->flipImage($dst_img, 'vert');
1404 $this->_debug("(ROTATE)");
1405 $dst_img = $this->rotateImage($dst_img, $rotate);
1408 /* write down new generated file */
1409 $result = imagejpeg($dst_img, $thumb_image, 75);
1411 /* free your mind */
1412 imagedestroy($dst_img);
1413 imagedestroy($src_img);
1415 if($result === false) {
1416 print "Can't write thumbnail ". $thumb_image ."\n";
1430 } // create_thumbnail()
1433 * return all exif meta data from the file
1435 public function get_meta_informations($file)
1437 return exif_read_data($file);
1439 } // get_meta_informations()
1442 * create phpfspot own sqlite database
1444 * this function creates phpfspots own sqlite database
1445 * if it does not exist yet. this own is used to store
1446 * some necessary informations (md5 sum's, ...).
1448 public function check_config_table()
1450 // if the config table doesn't exist yet, create it
1451 if(!$this->cfg_db->db_check_table_exists("images")) {
1452 $this->cfg_db->db_exec("
1453 CREATE TABLE images (
1454 img_idx int primary key,
1460 } // check_config_table
1463 * Generates a thumbnail from photo idx
1465 * This function will generate JPEG thumbnails from provided F-Spot photo
1468 * 1. Check if all thumbnail generations (width) are already in place and
1470 * 2. Check if the md5sum of the original file has changed
1471 * 3. Generate the thumbnails if needed
1473 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1477 $resolutions = Array(
1478 $this->cfg->thumb_width,
1479 $this->cfg->photo_width,
1480 $this->cfg->mini_width,
1483 /* get details from F-Spot's database */
1484 $details = $this->get_photo_details($idx);
1486 /* calculate file MD5 sum */
1487 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1489 if(!file_exists($full_path)) {
1490 $this->_error("File ". $full_path ." does not exist\n");
1494 if(!is_readable($full_path)) {
1495 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1499 $file_md5 = md5_file($full_path);
1501 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1505 foreach($resolutions as $resolution) {
1507 $generate_it = false;
1509 $thumb_sub_path = substr($file_md5, 0, 2);
1510 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1512 /* if thumbnail-subdirectory does not exist yet, create it */
1513 if(!file_exists(dirname($thumb_path))) {
1514 mkdir(dirname($thumb_path), 0755);
1517 /* if the thumbnail file doesn't exist, create it */
1518 if(!file_exists($thumb_path)) {
1519 $generate_it = true;
1521 /* if the file hasn't changed there is no need to regen the thumb */
1522 elseif($file_md5 != $this->getMD5($idx) || $force) {
1523 $generate_it = true;
1526 if($generate_it || $overwrite) {
1528 $this->_debug(" ". $resolution ."px");
1529 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1537 $this->_debug(" already exist");
1540 /* set the new/changed MD5 sum for the current photo */
1542 $this->setMD5($idx, $file_md5);
1545 $this->_debug("\n");
1550 * returns stored md5 sum for a specific photo
1552 * this function queries the phpfspot database for a
1553 * stored MD5 checksum of the specified photo
1555 public function getMD5($idx)
1557 $result = $this->cfg_db->db_query("
1560 WHERE img_idx='". $idx ."'
1566 $img = $this->cfg_db->db_fetch_object($result);
1567 return $img['img_md5'];
1572 * set MD5 sum for the specific photo
1574 private function setMD5($idx, $md5)
1576 $result = $this->cfg_db->db_exec("
1577 REPLACE INTO images (img_idx, img_md5)
1578 VALUES ('". $idx ."', '". $md5 ."')
1584 * store current tag condition
1586 * this function stores the current tag condition
1587 * (AND or OR) in the users session variables
1589 public function setTagCondition($mode)
1591 $_SESSION['tag_condition'] = $mode;
1595 } // setTagCondition()
1598 * invoke tag & date search
1600 * this function will return all matching tags and store
1601 * them in the session variable selected_tags. furthermore
1602 * it also handles the date search.
1603 * getPhotoSelection() will then only return the matching
1606 public function startSearch()
1608 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1609 $from = $_POST['from'];
1611 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1615 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1616 $searchfor_tag = $_POST['for_tag'];
1617 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1620 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1621 $searchfor_name = $_POST['for_name'];
1622 $_SESSION['searchfor_name'] = $_POST['for_name'];
1627 if(isset($from) && !empty($from))
1628 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1630 unset($_SESSION['from_date']);
1632 if(isset($to) && !empty($to))
1633 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1635 unset($_SESSION['to_date']);
1637 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1638 /* new search, reset the current selected tags */
1639 $_SESSION['selected_tags'] = Array();
1640 foreach($this->avail_tags as $tag) {
1641 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1642 array_push($_SESSION['selected_tags'], $tag);
1651 * updates sort order in session variable
1653 * this function is invoked by RPC and will sort the requested
1654 * sort order in the session variable.
1656 public function updateSortOrder($order)
1658 if(isset($this->sort_orders[$order])) {
1659 $_SESSION['sort_order'] = $order;
1663 return "unkown error";
1665 } // updateSortOrder()
1670 * this function rotates the image according the
1673 private function rotateImage($img, $degrees)
1675 if(function_exists("imagerotate")) {
1676 $img = imagerotate($img, $degrees, 0);
1678 function imagerotate($src_img, $angle)
1680 $src_x = imagesx($src_img);
1681 $src_y = imagesy($src_img);
1682 if ($angle == 180) {
1686 elseif ($src_x <= $src_y) {
1690 elseif ($src_x >= $src_y) {
1695 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1696 imagealphablending($rotate, false);
1701 for ($y = 0; $y < ($src_y); $y++) {
1702 for ($x = 0; $x < ($src_x); $x++) {
1703 $color = imagecolorat($src_img, $x, $y);
1704 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1710 for ($y = 0; $y < ($src_y); $y++) {
1711 for ($x = 0; $x < ($src_x); $x++) {
1712 $color = imagecolorat($src_img, $x, $y);
1713 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1719 for ($y = 0; $y < ($src_y); $y++) {
1720 for ($x = 0; $x < ($src_x); $x++) {
1721 $color = imagecolorat($src_img, $x, $y);
1722 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1736 $img = imagerotate($img, $degrees);
1745 * returns flipped image
1747 * this function will return an either horizontal or
1748 * vertical flipped truecolor image.
1750 private function flipImage($image, $mode)
1752 $w = imagesx($image);
1753 $h = imagesy($image);
1754 $flipped = imagecreatetruecolor($w, $h);
1758 for ($y = 0; $y < $h; $y++) {
1759 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1763 for ($x = 0; $x < $w; $x++) {
1764 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1774 * return all assigned tags for the specified photo
1776 private function get_photo_tags($idx)
1778 $result = $this->db->db_query("
1781 INNER JOIN photo_tags pt
1783 WHERE pt.photo_id='". $idx ."'
1788 while($row = $this->db->db_fetch_object($result))
1789 $tags[$row['id']] = $row['name'];
1793 } // get_photo_tags()
1796 * create on-the-fly images with text within
1798 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1800 if (strlen($color) != 6)
1803 $int = hexdec($color);
1804 $h = imagefontheight($font);
1805 $fw = imagefontwidth($font);
1806 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1807 $lines = count($txt);
1808 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1809 $bg = imagecolorallocate($im, 255, 255, 255);
1810 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1813 foreach ($txt as $text) {
1814 $x = (($w - ($fw * strlen($text))) / 2);
1815 imagestring($im, $font, $x, $y, $text, $color);
1816 $y += ($h + $space);
1819 Header("Content-type: image/png");
1822 } // showTextImage()
1825 * check if all requirements are met
1827 private function check_requirements()
1829 if(!function_exists("imagecreatefromjpeg")) {
1830 print "PHP GD library extension is missing<br />\n";
1834 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1835 print "PHP SQLite3 library extension is missing<br />\n";
1839 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1840 ini_set('track_errors', 1);
1841 @include_once 'HTML/AJAX/Server.php';
1842 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1843 print "PEAR HTML_AJAX package is missing<br />\n";
1846 @include_once 'Calendar/Calendar.php';
1847 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1848 print "PEAR Calendar package is missing<br />\n";
1851 @include_once 'Console/Getopt.php';
1852 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1853 print "PEAR Console_Getopt package is missing<br />\n";
1856 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
1857 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1858 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
1861 ini_restore('track_errors');
1868 } // check_requirements()
1870 private function _debug($text)
1872 if($this->fromcmd) {
1879 * check if specified MIME type is supported
1881 public function checkifImageSupported($mime)
1883 if(in_array($mime, Array("image/jpeg", "image/png", "image/tiff")))
1888 } // checkifImageSupported()
1890 public function _error($text)
1892 switch($this->cfg->logging) {
1895 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1896 print $text ."<br />\n";
1902 error_log($text, 3, $his->cfg->log_file);
1906 $this->runtime_error = true;
1911 * output calendard input fields
1913 private function get_calendar($mode)
1915 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1916 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1917 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1919 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1920 if(!isset($_SESSION[$mode .'_date']))
1921 $output.= " disabled=\"disabled\"";
1923 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1924 if(!isset($_SESSION[$mode .'_date']))
1925 $output.= " disabled=\"disabled\"";
1927 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1928 if(!isset($_SESSION[$mode .'_date']))
1929 $output.= " disabled=\"disabled\"";
1937 * output calendar matrix
1939 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1941 if (!isset($year)) $year = date('Y');
1942 if (!isset($month)) $month = date('m');
1943 if (!isset($day)) $day = date('d');
1948 require_once CALENDAR_ROOT.'Month/Weekdays.php';
1949 require_once CALENDAR_ROOT.'Day.php';
1952 $month = new Calendar_Month_Weekdays($year,$month);
1955 $prevStamp = $month->prevMonth(true);
1956 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1957 $nextStamp = $month->nextMonth(true);
1958 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1960 $selectedDays = array (
1961 new Calendar_Day($year,$month,$day),
1962 new Calendar_Day($year,12,25),
1965 // Build the days in the month
1966 $month->build($selectedDays);
1968 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1969 $this->tmpl->assign('prev_month', $prev);
1970 $this->tmpl->assign('next_month', $next);
1972 while ( $day = $month->fetch() ) {
1974 if(!isset($matrix[$rows]))
1975 $matrix[$rows] = Array();
1979 $dayStamp = $day->thisDay(true);
1980 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1982 // isFirst() to find start of week
1983 if ( $day->isFirst() )
1986 if ( $day->isSelected() ) {
1987 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1988 } else if ( $day->isEmpty() ) {
1989 $string.= "<td> </td>\n";
1991 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1994 // isLast() to find end of week
1995 if ( $day->isLast() )
1996 $string.= "</tr>\n";
1998 $matrix[$rows][$cols] = $string;
2008 $this->tmpl->assign('matrix', $matrix);
2009 $this->tmpl->assign('rows', $rows);
2010 $this->tmpl->show("calendar.tpl");
2012 } // get_calendar_matrix()
2015 * output export page
2017 public function getExport($mode)
2019 $pictures = $this->getPhotoSelection();
2020 $current_tags = $this->getCurrentTags();
2022 foreach($pictures as $picture) {
2024 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2025 if($current_tags != "") {
2026 $orig_url.= "&tags=". $current_tags;
2028 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2029 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2032 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2037 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2038 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2042 // "[%pictureurl% %thumbnailurl%]"
2043 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2046 case 'MoinMoinList':
2047 // " * [%pictureurl% %thumbnailurl%]"
2048 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2059 public function getRSSFeed()
2061 Header("Content-type: text/xml; charset=utf-8");
2062 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2065 xmlns:media="http://search.yahoo.com/mrss/"
2066 xmlns:dc="http://purl.org/dc/elements/1.1/"
2069 <title>phpfspot</title>
2070 <description>phpfspot RSS feed</description>
2071 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2072 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2073 <generator>phpfspot</generator>
2076 $pictures = $this->getPhotoSelection();
2077 $current_tags = $this->getCurrentTags();
2079 foreach($pictures as $picture) {
2081 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2082 if($current_tags != "") {
2083 $orig_url.= "&tags=". $current_tags;
2085 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2086 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2089 $details = $this->get_photo_details($picture);
2091 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2092 $thumb_html = htmlspecialchars("
2093 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2095 ". $details['description']);
2097 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2099 /* get EXIF information if JPEG */
2100 if($details['mime'] == "image/jpeg") {
2101 $meta = $this->get_meta_informations($orig_path);
2104 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2108 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2109 <link><?php print htmlspecialchars($orig_url); ?></link>
2110 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2111 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2113 <?php print $thumb_html; ?>
2115 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2130 * return all selected tags as one string
2132 private function getCurrentTags()
2135 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2136 foreach($_SESSION['selected_tags'] as $tag)
2137 $current_tags.= $tag .",";
2138 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2140 return $current_tags;
2142 } // getCurrentTags()
2145 * return the current photo
2147 public function getCurrentPhoto()
2149 if(isset($_SESSION['current_photo'])) {
2150 print $_SESSION['current_photo'];
2152 } // getCurrentPhoto()
2155 * tells the client browser what to do
2157 * this function is getting called via AJAX by the
2158 * client browsers. it will tell them what they have
2159 * to do next. This is necessary for directly jumping
2160 * into photo index or single photo view when the are
2161 * requested with specific URLs
2163 public function whatToDo()
2165 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2166 return "show_photo";
2168 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2169 return "showpi_tags";
2171 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2175 return "nothing special";
2180 * return the current process-user
2182 private function getuid()
2184 if($uid = posix_getuid()) {
2185 if($user = posix_getpwuid($uid)) {
2186 return $user['name'];
2195 * returns a select-dropdown box to select photo index sort parameters
2197 public function smarty_sort_select_list($params, &$smarty)
2201 foreach($this->sort_orders as $key => $value) {
2202 $output.= "<option value=\"". $key ."\"";
2203 if($key == $_SESSION['sort_order']) {
2204 $output.= " selected=\"selected\"";
2206 $output.= ">". $value ."</option>";
2211 } // smarty_sort_select_list()
2214 * returns the currently selected sort order
2216 private function get_sort_order()
2218 switch($_SESSION['sort_order']) {
2220 return " ORDER BY p.time ASC";
2223 return " ORDER BY p.time DESC";
2226 if($this->dbver < 9) {
2227 return " ORDER BY p.name ASC";
2230 return " ORDER BY basename(p.uri) ASC";
2234 if($this->dbver < 9) {
2235 return " ORDER BY p.name DESC";
2238 return " ORDER BY basename(p.uri) DESC";
2242 return " ORDER BY t.name ASC ,p.time ASC";
2245 return " ORDER BY t.name DESC ,p.time ASC";
2249 } // get_sort_order()
2252 * return the next to be shown slide show image
2254 * this function returns the URL of the next image
2255 * in the slideshow sequence.
2257 public function getNextSlideShowImage()
2259 $all_photos = $this->getPhotoSelection();
2261 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2262 $_SESSION['slideshow_img'] = 0;
2264 $_SESSION['slideshow_img']++;
2266 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2268 } // getNextSlideShowImage()
2271 * return the previous to be shown slide show image
2273 * this function returns the URL of the previous image
2274 * in the slideshow sequence.
2276 public function getPrevSlideShowImage()
2278 $all_photos = $this->getPhotoSelection();
2280 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2281 $_SESSION['slideshow_img'] = 0;
2283 $_SESSION['slideshow_img']--;
2285 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2287 } // getPrevSlideShowImage()
2289 public function resetSlideShow()
2291 if(isset($_SESSION['slideshow_img']))
2292 unset($_SESSION['slideshow_img']);
2294 } // resetSlideShow()
2299 * this function will get all photos from the fspot
2300 * database and randomly return ONE entry
2302 * saddly there is yet no sqlite3 function which returns
2303 * the bulk result in array, so we have to fill up our
2306 public function get_random_photo()
2310 $result = $this->db->db_query("
2315 while($row = $this->db->db_fetch_object($result)) {
2316 array_push($all, $row['id']);
2319 return $all[array_rand($all)];
2321 } // get_random_photo()
2324 * validates provided date
2326 * this function validates if the provided date
2327 * contains a valid date and will return true
2330 public function isValidDate($date_str)
2332 $timestamp = strtotime($date_str);
2334 if(is_numeric($timestamp))
2342 * timestamp to string conversion
2344 private function ts2str($timestamp)
2346 return strftime("%Y-%m-%d", $timestamp);
2349 private function extractTags($tags_str)
2351 $not_validated = split(',', $_GET['tags']);
2352 $validated = array();
2354 foreach($not_validated as $tag) {
2355 if(is_numeric($tag))
2356 array_push($validated, $tag);
2364 * returns the full path to a thumbnail
2366 public function get_thumb_path($width, $photo)
2368 $md5 = $this->getMD5($photo);
2369 $sub_path = substr($md5, 0, 2);
2370 return $this->cfg->thumb_path
2378 } // get_thumb_path()
2381 * returns server's virtual host name
2383 private function get_server_name()
2385 return $_SERVER['SERVER_NAME'];
2386 } // get_server_name()
2389 * returns type of webprotocol which is
2392 private function get_web_protocol()
2394 if(!isset($_SERVER['HTTPS']))
2398 } // get_web_protocol()
2401 * return url to this phpfspot installation
2403 private function get_phpfspot_url()
2405 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2406 } // get_phpfspot_url()
2409 * returns the number of photos which are tagged with $tag_id
2411 public function get_num_photos($tag_id)
2413 if($result = $this->db->db_fetchSingleRow("
2414 SELECT count(*) as number
2417 tag_id LIKE '". $tag_id ."'")) {
2419 return $result['number'];
2425 } // get_num_photos()
2428 * check file exists and is readable
2430 * returns true, if everything is ok, otherwise false
2431 * if $silent is not set, this function will output and
2434 private function check_readable($file, $silent = null)
2436 if(!file_exists($file)) {
2438 print "File \"". $file ."\" does not exist.\n";
2442 if(!is_readable($file)) {
2444 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2450 } // check_readable()
2453 * check if all needed indices are present
2455 * this function checks, if some needed indices are already
2456 * present, or if not, create them on the fly. they are
2457 * necessary to speed up some queries like that one look for
2458 * all tags, when show_tags is specified in the configuration.
2460 private function checkDbIndices()
2462 $result = $this->db->db_exec("
2463 CREATE INDEX IF NOT EXISTS
2470 } // checkDbIndices()
2473 * retrive F-Spot database version
2475 * this function will return the F-Spot database version number
2476 * It is stored within the sqlite3 database in the table meta
2478 public function getFspotDBVersion()
2480 if($result = $this->db->db_fetchSingleRow("
2481 SELECT data as version
2484 name LIKE 'F-Spot Database Version'
2486 return $result['version'];
2490 } // getFspotDBVersion()
2493 * parse the provided URI and will returned the
2496 public function parse_uri($uri, $mode)
2498 if(($components = parse_url($uri)) !== false) {
2502 return basename($components['path']);
2505 return dirname($components['path']);
2508 return $components['path'];
2518 * validate config options
2520 * this function checks if all necessary configuration options are
2521 * specified and set.
2523 private function check_config_options()
2525 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2526 $this->_error("Please set \$page_title in phpfspot_cfg");
2528 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2529 $this->_error("Please set \$base_path in phpfspot_cfg");
2531 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2532 $this->_error("Please set \$web_path in phpfspot_cfg");
2534 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2535 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2537 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2538 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2540 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2541 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2543 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2544 $this->_error("Please set \$db_access in phpfspot_cfg");
2546 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2547 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2549 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2550 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2552 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2553 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2555 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2556 $this->_error("Please set \$photo_width in phpfspot_cfg");
2558 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2559 $this->_error("Please set \$mini_width in phpfspot_cfg");
2561 if(!isset($this->cfg->thumbs_per_page))
2562 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2564 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2565 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2567 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2568 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2570 if(!isset($this->cfg->hide_tags))
2571 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2573 if(!isset($this->cfg->theme_name))
2574 $this->_error("Please set \$theme_name in phpfspot_cfg");
2576 if(!isset($this->cfg->logging))
2577 $this->_error("Please set \$logging in phpfspot_cfg");
2579 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2581 if(!isset($this->cfg->log_file))
2582 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2584 if(!is_writeable($this->cfg->log_file))
2585 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2589 /* check for pending slash on web_path */
2590 if(!preg_match("/\/$/", $this->cfg->web_path))
2591 $this->cfg->web_path.= "/";
2593 return $this->runtime_error;
2595 } // check_config_options()
2598 * cleanup phpfspot own database
2600 * When photos are getting delete from F-Spot, there will remain
2601 * remain some residues in phpfspot own database. This function
2602 * will try to wipe them out.
2604 public function cleanup_phpfspot_db()
2606 $to_delete = Array();
2608 $result = $this->cfg_db->db_query("
2611 ORDER BY img_idx ASC
2614 while($row = $this->cfg_db->db_fetch_object($result)) {
2615 if(!$this->db->db_fetchSingleRow("
2618 WHERE id='". $row['img_idx'] ."'")) {
2620 array_push($to_delete, $row['img_idx'], ',');
2624 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2626 $this->cfg_db->db_exec("
2628 WHERE img_idx IN (". implode($to_delete) .")
2631 } // cleanup_phpfspot_db()
2634 * return first image of the page, the $current photo
2637 * this function is used to find out the first photo of the
2638 * current page, in which the $current photo lies. this is
2639 * used to display the correct photo, when calling showPhotoIndex()
2642 private function getCurrentPage($current, $max)
2644 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2645 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2646 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2652 } // getCurrentPage()