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 mime-type, height and width from the original photo */
487 $info = getimagesize($orig_path);
489 /* get EXIF information if JPEG */
490 if($info['mime'] == "image/jpeg") {
491 $meta = $this->get_meta_informations($orig_path);
494 /* If EXIF data are available, use them */
495 if(isset($meta['ExifImageWidth'])) {
496 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
498 $meta_res = $info[0] ."x". $info[1];
501 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
502 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
503 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
505 $extern_link = "index.php?mode=showp&id=". $photo;
506 $current_tags = $this->getCurrentTags();
507 if($current_tags != "") {
508 $extern_link.= "&tags=". $current_tags;
510 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
511 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
514 $this->tmpl->assign('extern_link', $extern_link);
516 if(!file_exists($thumb_path)) {
517 $this->_error("Can't open file ". $thumb_path ."\n");
521 $info_thumb = getimagesize($thumb_path);
523 $this->tmpl->assign('description', $details['description']);
524 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
526 $this->tmpl->assign('width', $info_thumb[0]);
527 $this->tmpl->assign('height', $info_thumb[1]);
528 $this->tmpl->assign('ExifMadeOn', $meta_date);
529 $this->tmpl->assign('ExifMadeWith', $meta_make);
530 $this->tmpl->assign('ExifOrigResolution', $meta_res);
531 $this->tmpl->assign('ExifFileSize', $meta_size);
533 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
534 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
535 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
537 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
538 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
539 $this->tmpl->assign('current_img', $photo);
542 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
543 $this->tmpl->assign('prev_img', $previous_img);
547 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
548 $this->tmpl->assign('next_img', $next_img);
550 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
551 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
552 $this->tmpl->assign('photo_number', $i);
553 $this->tmpl->assign('photo_count', count($all_photos));
555 $this->tmpl->show("single_photo.tpl");
560 * all available tags and tag cloud
562 * this function outputs all available tags (time ordered)
563 * and in addition output them as tag cloud (tags which have
564 * many photos will appears more then others)
566 public function getAvailableTags()
568 /* retrive tags from database */
573 $result = $this->db->db_query("
574 SELECT tag_id as id, count(tag_id) as quantity
584 while($row = $this->db->db_fetch_object($result)) {
585 $tags[$row['id']] = $row['quantity'];
588 // change these font sizes if you will
589 $max_size = 125; // max font size in %
590 $min_size = 75; // min font size in %
593 $max_sat = hexdec('cc');
594 $min_sat = hexdec('44');
596 // get the largest and smallest array values
597 $max_qty = max(array_values($tags));
598 $min_qty = min(array_values($tags));
600 // find the range of values
601 $spread = $max_qty - $min_qty;
602 if (0 == $spread) { // we don't want to divide by zero
606 // determine the font-size increment
607 // this is the increase per tag quantity (times used)
608 $step = ($max_size - $min_size)/($spread);
609 $step_sat = ($max_sat - $min_sat)/($spread);
611 // loop through our tag array
612 foreach ($tags as $key => $value) {
614 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
617 // calculate CSS font-size
618 // find the $value in excess of $min_qty
619 // multiply by the font-size increment ($size)
620 // and add the $min_size set above
621 $size = $min_size + (($value - $min_qty) * $step);
622 // uncomment if you want sizes in whole %:
625 $color = $min_sat + ($value - $min_qty) * $step_sat;
631 if(isset($this->tags[$key])) {
632 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\">". $this->tags[$key] ."</a>, ";
637 $output = substr($output, 0, strlen($output)-2);
640 } // getAvailableTags()
643 * output all selected tags
645 * this function output all tags which have been selected
646 * by the user. the selected tags are stored in the
647 * session-variable $_SESSION['selected_tags']
649 public function getSelectedTags()
651 /* retrive tags from database */
656 foreach($this->avail_tags as $tag)
658 // return all selected tags
659 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
660 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
665 $output = substr($output, 0, strlen($output)-2);
669 return "no tags selected";
672 } // getSelectedTags()
675 * add tag to users session variable
677 * this function will add the specified to users current
678 * tag selection. if a date search has been made before
679 * it will be now cleared
681 public function addTag($tag)
683 if(!isset($_SESSION['selected_tags']))
684 $_SESSION['selected_tags'] = Array();
686 if(isset($_SESSION['searchfor_tag']))
687 unset($_SESSION['searchfor_tag']);
689 if(!in_array($tag, $_SESSION['selected_tags']))
690 array_push($_SESSION['selected_tags'], $tag);
698 * remove tag to users session variable
700 * this function removes the specified tag from
701 * users current tag selection
703 public function delTag($tag)
705 if(isset($_SESSION['searchfor_tag']))
706 unset($_SESSION['searchfor_tag']);
708 if(isset($_SESSION['selected_tags'])) {
709 $key = array_search($tag, $_SESSION['selected_tags']);
710 unset($_SESSION['selected_tags'][$key]);
711 sort($_SESSION['selected_tags']);
719 * reset tag selection
721 * if there is any tag selection, it will be
724 public function resetTags()
726 if(isset($_SESSION['selected_tags']))
727 unset($_SESSION['selected_tags']);
732 * returns the value for the autocomplet tag-search
734 public function get_xml_tag_list()
736 if(!isset($_GET['search']) || !is_string($_GET['search']))
737 $_GET['search'] = '';
742 /* retrive tags from database */
745 $matched_tags = Array();
747 header("Content-Type: text/xml");
749 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
750 $string.= "<results>\n";
752 foreach($this->avail_tags as $tag)
754 if(!empty($_GET['search']) &&
755 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
756 count($matched_tags) < $length) {
758 $count = $this->get_num_photos($tag);
761 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
764 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
770 /* if we have collected enough items, break out */
771 if(count($matched_tags) >= $length)
775 $string.= "</results>\n";
779 } // get_xml_tag_list()
785 * if a specific photo was requested (external link)
786 * unset the session variable now
788 public function resetPhotoView()
790 if(isset($_SESSION['current_photo']))
791 unset($_SESSION['current_photo']);
793 } // resetPhotoView();
798 * if any tag search has taken place, reset it now
800 public function resetTagSearch()
802 if(isset($_SESSION['searchfor_tag']))
803 unset($_SESSION['searchfor_tag']);
805 } // resetTagSearch()
810 * if any name search has taken place, reset it now
812 public function resetNameSearch()
814 if(isset($_SESSION['searchfor_name']))
815 unset($_SESSION['searchfor_name']);
817 } // resetNameSearch()
822 * if any date search has taken place, reset
825 public function resetDateSearch()
827 if(isset($_SESSION['from_date']))
828 unset($_SESSION['from_date']);
829 if(isset($_SESSION['to_date']))
830 unset($_SESSION['to_date']);
832 } // resetDateSearch();
835 * return all photo according selection
837 * this function returns all photos based on
838 * the tag-selection, tag- or date-search.
839 * the tag-search also has to take care of AND
840 * and OR conjunctions
842 public function getPhotoSelection()
844 $matched_photos = Array();
845 $additional_where_cond = "";
847 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
848 $from_date = $_SESSION['from_date'];
849 $to_date = $_SESSION['to_date'];
850 $additional_where_cond.= "
851 p.time>='". $from_date ."'
853 p.time<='". $to_date ."'
857 if(isset($_SESSION['searchfor_name'])) {
858 if($this->dbver < 9) {
859 $additional_where_cond.= "
861 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
863 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
868 $additional_where_cond.= "
870 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
872 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
878 if(isset($_SESSION['sort_order'])) {
879 $order_str = $this->get_sort_order();
882 /* return a search result */
883 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
885 SELECT DISTINCT pt1.photo_id
887 INNER JOIN photo_tags pt2
888 ON pt1.photo_id=pt2.photo_id
895 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
897 if(isset($additional_where_cond) && !empty($additional_where_cond))
898 $query_str.= "AND ". $additional_where_cond ." ";
900 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
901 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
904 if(isset($order_str))
905 $query_str.= $order_str;
907 $result = $this->db->db_query($query_str);
908 while($row = $this->db->db_fetch_object($result)) {
909 array_push($matched_photos, $row['photo_id']);
911 return $matched_photos;
914 /* return according the selected tags */
915 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
917 foreach($_SESSION['selected_tags'] as $tag)
918 $selected.= $tag .",";
919 $selected = substr($selected, 0, strlen($selected)-1);
921 /* photo has to match at least on of the selected tags */
922 if($_SESSION['tag_condition'] == 'or') {
924 SELECT DISTINCT pt1.photo_id
926 INNER JOIN photo_tags pt2
927 ON pt1.photo_id=pt2.photo_id
932 WHERE pt1.tag_id IN (". $selected .")
934 if(isset($additional_where_cond) && !empty($additional_where_cond))
935 $query_str.= "AND ". $additional_where_cond ." ";
937 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
938 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
941 if(isset($order_str))
942 $query_str.= $order_str;
944 /* photo has to match all selected tags */
945 elseif($_SESSION['tag_condition'] == 'and') {
947 if(count($_SESSION['selected_tags']) >= 32) {
948 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
949 print "evaluate your tag selection. Please remove some tags from your selection.\n";
953 /* Join together a table looking like
955 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
957 so the query can quickly return all images matching the
958 selected tags in an AND condition
963 SELECT DISTINCT pt1.photo_id
967 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
974 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
976 INNER JOIN photo_tags pt". ($i+2) ."
977 ON pt1.photo_id=pt". ($i+2) .".photo_id
984 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
985 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
987 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
990 if(isset($additional_where_cond) && !empty($additional_where_cond))
991 $query_str.= "AND ". $additional_where_cond;
993 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
994 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
997 if(isset($order_str))
998 $query_str.= $order_str;
1002 $result = $this->db->db_query($query_str);
1003 while($row = $this->db->db_fetch_object($result)) {
1004 array_push($matched_photos, $row['photo_id']);
1006 return $matched_photos;
1009 /* return all available photos */
1011 SELECT DISTINCT p.id
1013 LEFT JOIN photo_tags pt
1019 if(isset($additional_where_cond) && !empty($additional_where_cond))
1020 $query_str.= "WHERE ". $additional_where_cond ." ";
1022 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1023 if(isset($additional_where_cond) && !empty($additional_where_cond))
1024 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1026 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1029 if(isset($order_str))
1030 $query_str.= $order_str;
1032 $result = $this->db->db_query($query_str);
1033 while($row = $this->db->db_fetch_object($result)) {
1034 array_push($matched_photos, $row['id']);
1036 return $matched_photos;
1038 } // getPhotoSelection()
1041 * control HTML ouput for photo index
1043 * this function provides all the necessary information
1044 * for the photo index template.
1046 public function showPhotoIndex()
1048 $photos = $this->getPhotoSelection();
1050 $count = count($photos);
1052 /* if all thumbnails should be shown on one page */
1053 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1057 /* thumbnails should be splitted up in several pages */
1058 elseif($this->cfg->thumbs_per_page > 0) {
1060 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1064 $begin_with = $_SESSION['begin_with'];
1067 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1071 $images[$thumbs] = Array();
1072 $img_height[$thumbs] = Array();
1073 $img_width[$thumbs] = Array();
1074 $img_id[$thumbs] = Array();
1075 $img_name[$thumbs] = Array();
1076 $img_fullname[$thumbs] = Array();
1077 $img_title = Array();
1079 for($i = $begin_with; $i < $end_with; $i++) {
1081 if(isset($photos[$i])) {
1083 $images[$thumbs] = $photos[$i];
1084 $img_id[$thumbs] = $i;
1085 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1086 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1087 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1089 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1091 if(file_exists($thumb_path)) {
1092 $info = getimagesize($thumb_path);
1093 $img_width[$thumbs] = $info[0];
1094 $img_height[$thumbs] = $info[1];
1100 // +1 for for smarty's selection iteration
1103 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1104 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1106 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1107 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1108 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1111 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1112 $this->tmpl->assign('tag_result', 1);
1115 /* do we have to display the page selector ? */
1116 if($this->cfg->thumbs_per_page != 0) {
1120 /* calculate the page switchers */
1121 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1122 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1124 if($begin_with != 0)
1125 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1126 if($end_with < $count)
1127 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1129 $photo_per_page = $this->cfg->thumbs_per_page;
1130 $last_page = ceil($count / $photo_per_page);
1132 /* get the current selected page */
1133 if($begin_with == 0) {
1137 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1144 for($i = 1; $i <= $last_page; $i++) {
1146 if($current_page == $i)
1147 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1148 elseif($current_page-1 == $i || $current_page+1 == $i)
1149 $style = "style=\"font-size: 105%;\"";
1150 elseif(($current_page-5 >= $i) && ($i != 1) ||
1151 ($current_page+5 <= $i) && ($i != $last_page))
1152 $style = "style=\"font-size: 75%;\"";
1156 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1159 $select.= ">". $i ."</a> ";
1161 // until 9 pages we show the selector from 1-9
1162 if($last_page <= 9) {
1163 $page_select.= $select;
1166 if($i == 1 /* first page */ ||
1167 $i == $last_page /* last page */ ||
1168 $i == $current_page /* current page */ ||
1169 $i == ceil($last_page * 0.25) /* first quater */ ||
1170 $i == ceil($last_page * 0.5) /* half */ ||
1171 $i == ceil($last_page * 0.75) /* third quater */ ||
1172 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1173 (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 */ ||
1174 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1175 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1177 $page_select.= $select;
1185 $page_select.= "......... ";
1190 /* only show the page selector if we have more then one page */
1192 $this->tmpl->assign('page_selector', $page_select);
1196 $current_tags = $this->getCurrentTags();
1197 $extern_link = "index.php?mode=showpi";
1198 $rss_link = "index.php?mode=rss";
1199 if($current_tags != "") {
1200 $extern_link.= "&tags=". $current_tags;
1201 $rss_link.= "&tags=". $current_tags;
1203 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1204 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1205 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1208 $export_link = "index.php?mode=export";
1209 $slideshow_link = "index.php?mode=slideshow";
1211 $this->tmpl->assign('extern_link', $extern_link);
1212 $this->tmpl->assign('slideshow_link', $slideshow_link);
1213 $this->tmpl->assign('export_link', $export_link);
1214 $this->tmpl->assign('rss_link', $rss_link);
1215 $this->tmpl->assign('count', $count);
1216 $this->tmpl->assign('width', $this->cfg->thumb_width);
1217 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1218 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1219 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1220 $this->tmpl->assign('images', $images);
1221 $this->tmpl->assign('img_width', $img_width);
1222 $this->tmpl->assign('img_height', $img_height);
1223 $this->tmpl->assign('img_id', $img_id);
1224 $this->tmpl->assign('img_name', $img_name);
1225 $this->tmpl->assign('img_fullname', $img_fullname);
1226 $this->tmpl->assign('img_title', $img_title);
1227 $this->tmpl->assign('thumbs', $thumbs);
1229 $this->tmpl->show("photo_index.tpl");
1231 /* if we are returning to photo index from an photo-view,
1232 scroll the window to the last shown photo-thumbnail.
1233 after this, unset the last_photo session variable.
1235 if(isset($_SESSION['last_photo'])) {
1236 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1237 unset($_SESSION['last_photo']);
1240 } // showPhotoIndex()
1243 * show credit template
1245 public function showCredits()
1247 $this->tmpl->assign('version', $this->cfg->version);
1248 $this->tmpl->assign('product', $this->cfg->product);
1249 $this->tmpl->assign('db_version', $this->dbver);
1250 $this->tmpl->show("credits.tpl");
1255 * create_thumbnails for the requested width
1257 * this function creates image thumbnails of $orig_image
1258 * stored as $thumb_image. It will check if the image is
1259 * in a supported format, if necessary rotate the image
1260 * (based on EXIF orientation meta headers) and re-sizing.
1262 public function create_thumbnail($orig_image, $thumb_image, $width)
1264 if(!file_exists($orig_image)) {
1268 $details = getimagesize($orig_image);
1270 /* check if original photo is a support image type */
1271 if(!$this->checkifImageSupported($details['mime']))
1274 switch($details['mime']) {
1278 $meta = $this->get_meta_informations($orig_image);
1284 switch($meta['Orientation']) {
1285 case 1: /* top, left */
1286 /* nothing to do */ break;
1287 case 2: /* top, right */
1288 $rotate = 0; $flip_hori = true; break;
1289 case 3: /* bottom, left */
1290 $rotate = 180; break;
1291 case 4: /* bottom, right */
1292 $flip_vert = true; break;
1293 case 5: /* left side, top */
1294 $rotate = 90; $flip_vert = true; break;
1295 case 6: /* right side, top */
1296 $rotate = 90; break;
1297 case 7: /* left side, bottom */
1298 $rotate = 270; $flip_vert = true; break;
1299 case 8: /* right side, bottom */
1300 $rotate = 270; break;
1303 $src_img = @imagecreatefromjpeg($orig_image);
1308 $src_img = @imagecreatefrompng($orig_image);
1314 print "Can't load image from ". $orig_image ."\n";
1318 /* grabs the height and width */
1319 $cur_width = imagesx($src_img);
1320 $cur_height = imagesy($src_img);
1322 // If requested width is more then the actual image width,
1323 // do not generate a thumbnail, instead safe the original
1324 // as thumbnail but with lower quality. But if the image
1325 // is to heigh too, then we still have to resize it.
1326 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1327 $result = imagejpeg($src_img, $thumb_image, 75);
1328 imagedestroy($src_img);
1332 // If the image will be rotate because EXIF orientation said so
1333 // 'virtually rotate' the image for further calculations
1334 if($rotate == 90 || $rotate == 270) {
1336 $cur_width = $cur_height;
1340 /* calculates aspect ratio */
1341 $aspect_ratio = $cur_height / $cur_width;
1344 if($aspect_ratio < 1) {
1346 $new_h = abs($new_w * $aspect_ratio);
1348 /* 'virtually' rotate the image and calculate it's ratio */
1349 $tmp_w = $cur_height;
1350 $tmp_h = $cur_width;
1351 /* now get the ratio from the 'rotated' image */
1352 $tmp_ratio = $tmp_h/$tmp_w;
1353 /* now calculate the new dimensions */
1355 $tmp_h = abs($tmp_w * $tmp_ratio);
1357 // now that we know, how high they photo should be, if it
1358 // gets rotated, use this high to scale the image
1360 $new_w = abs($new_h / $aspect_ratio);
1362 // If the image will be rotate because EXIF orientation said so
1363 // now 'virtually rotate' back the image for the image manipulation
1364 if($rotate == 90 || $rotate == 270) {
1371 /* creates new image of that size */
1372 $dst_img = imagecreatetruecolor($new_w, $new_h);
1374 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1376 /* copies resized portion of original image into new image */
1377 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1379 /* needs the image to be flipped horizontal? */
1381 $this->_debug("(FLIP)");
1382 $dst_img = $this->flipImage($dst_img, 'hori');
1384 /* needs the image to be flipped vertical? */
1386 $this->_debug("(FLIP)");
1387 $dst_img = $this->flipImage($dst_img, 'vert');
1391 $this->_debug("(ROTATE)");
1392 $dst_img = $this->rotateImage($dst_img, $rotate);
1395 /* write down new generated file */
1396 $result = imagejpeg($dst_img, $thumb_image, 75);
1398 /* free your mind */
1399 imagedestroy($dst_img);
1400 imagedestroy($src_img);
1402 if($result === false) {
1403 print "Can't write thumbnail ". $thumb_image ."\n";
1409 } // create_thumbnail()
1412 * return all exif meta data from the file
1414 public function get_meta_informations($file)
1416 return exif_read_data($file);
1418 } // get_meta_informations()
1421 * create phpfspot own sqlite database
1423 * this function creates phpfspots own sqlite database
1424 * if it does not exist yet. this own is used to store
1425 * some necessary informations (md5 sum's, ...).
1427 public function check_config_table()
1429 // if the config table doesn't exist yet, create it
1430 if(!$this->cfg_db->db_check_table_exists("images")) {
1431 $this->cfg_db->db_exec("
1432 CREATE TABLE images (
1433 img_idx int primary key,
1439 } // check_config_table
1442 * Generates a thumbnail from photo idx
1444 * This function will generate JPEG thumbnails from provided F-Spot photo
1447 * 1. Check if all thumbnail generations (width) are already in place and
1449 * 2. Check if the md5sum of the original file has changed
1450 * 3. Generate the thumbnails if needed
1452 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1456 $resolutions = Array(
1457 $this->cfg->thumb_width,
1458 $this->cfg->photo_width,
1459 $this->cfg->mini_width,
1462 /* get details from F-Spot's database */
1463 $details = $this->get_photo_details($idx);
1465 /* calculate file MD5 sum */
1466 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1468 if(!file_exists($full_path)) {
1469 $this->_error("File ". $full_path ." does not exist\n");
1473 if(!is_readable($full_path)) {
1474 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1478 $file_md5 = md5_file($full_path);
1480 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1484 foreach($resolutions as $resolution) {
1486 $generate_it = false;
1488 $thumb_sub_path = substr($file_md5, 0, 2);
1489 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1491 /* if thumbnail-subdirectory does not exist yet, create it */
1492 if(!file_exists(dirname($thumb_path))) {
1493 mkdir(dirname($thumb_path), 0755);
1496 /* if the thumbnail file doesn't exist, create it */
1497 if(!file_exists($thumb_path)) {
1498 $generate_it = true;
1500 /* if the file hasn't changed there is no need to regen the thumb */
1501 elseif($file_md5 != $this->getMD5($idx) || $force) {
1502 $generate_it = true;
1505 if($generate_it || $overwrite) {
1507 $this->_debug(" ". $resolution ."px");
1508 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1516 $this->_debug(" already exist");
1519 /* set the new/changed MD5 sum for the current photo */
1521 $this->setMD5($idx, $file_md5);
1524 $this->_debug("\n");
1529 * returns stored md5 sum for a specific photo
1531 * this function queries the phpfspot database for a
1532 * stored MD5 checksum of the specified photo
1534 public function getMD5($idx)
1536 $result = $this->cfg_db->db_query("
1539 WHERE img_idx='". $idx ."'
1545 $img = $this->cfg_db->db_fetch_object($result);
1546 return $img['img_md5'];
1551 * set MD5 sum for the specific photo
1553 private function setMD5($idx, $md5)
1555 $result = $this->cfg_db->db_exec("
1556 REPLACE INTO images (img_idx, img_md5)
1557 VALUES ('". $idx ."', '". $md5 ."')
1563 * store current tag condition
1565 * this function stores the current tag condition
1566 * (AND or OR) in the users session variables
1568 public function setTagCondition($mode)
1570 $_SESSION['tag_condition'] = $mode;
1574 } // setTagCondition()
1577 * invoke tag & date search
1579 * this function will return all matching tags and store
1580 * them in the session variable selected_tags. furthermore
1581 * it also handles the date search.
1582 * getPhotoSelection() will then only return the matching
1585 public function startSearch()
1587 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1588 $from = $_POST['from'];
1590 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1594 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1595 $searchfor_tag = $_POST['for_tag'];
1596 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1599 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1600 $searchfor_name = $_POST['for_name'];
1601 $_SESSION['searchfor_name'] = $_POST['for_name'];
1606 if(isset($from) && !empty($from))
1607 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1609 unset($_SESSION['from_date']);
1611 if(isset($to) && !empty($to))
1612 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1614 unset($_SESSION['to_date']);
1616 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1617 /* new search, reset the current selected tags */
1618 $_SESSION['selected_tags'] = Array();
1619 foreach($this->avail_tags as $tag) {
1620 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1621 array_push($_SESSION['selected_tags'], $tag);
1630 * updates sort order in session variable
1632 * this function is invoked by RPC and will sort the requested
1633 * sort order in the session variable.
1635 public function updateSortOrder($order)
1637 if(isset($this->sort_orders[$order])) {
1638 $_SESSION['sort_order'] = $order;
1642 return "unkown error";
1644 } // updateSortOrder()
1649 * this function rotates the image according the
1652 private function rotateImage($img, $degrees)
1654 if(function_exists("imagerotate")) {
1655 $img = imagerotate($img, $degrees, 0);
1657 function imagerotate($src_img, $angle)
1659 $src_x = imagesx($src_img);
1660 $src_y = imagesy($src_img);
1661 if ($angle == 180) {
1665 elseif ($src_x <= $src_y) {
1669 elseif ($src_x >= $src_y) {
1674 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1675 imagealphablending($rotate, false);
1680 for ($y = 0; $y < ($src_y); $y++) {
1681 for ($x = 0; $x < ($src_x); $x++) {
1682 $color = imagecolorat($src_img, $x, $y);
1683 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1689 for ($y = 0; $y < ($src_y); $y++) {
1690 for ($x = 0; $x < ($src_x); $x++) {
1691 $color = imagecolorat($src_img, $x, $y);
1692 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1698 for ($y = 0; $y < ($src_y); $y++) {
1699 for ($x = 0; $x < ($src_x); $x++) {
1700 $color = imagecolorat($src_img, $x, $y);
1701 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1715 $img = imagerotate($img, $degrees);
1724 * returns flipped image
1726 * this function will return an either horizontal or
1727 * vertical flipped truecolor image.
1729 private function flipImage($image, $mode)
1731 $w = imagesx($image);
1732 $h = imagesy($image);
1733 $flipped = imagecreatetruecolor($w, $h);
1737 for ($y = 0; $y < $h; $y++) {
1738 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1742 for ($x = 0; $x < $w; $x++) {
1743 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1753 * return all assigned tags for the specified photo
1755 private function get_photo_tags($idx)
1757 $result = $this->db->db_query("
1760 INNER JOIN photo_tags pt
1762 WHERE pt.photo_id='". $idx ."'
1767 while($row = $this->db->db_fetch_object($result))
1768 $tags[$row['id']] = $row['name'];
1772 } // get_photo_tags()
1775 * create on-the-fly images with text within
1777 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1779 if (strlen($color) != 6)
1782 $int = hexdec($color);
1783 $h = imagefontheight($font);
1784 $fw = imagefontwidth($font);
1785 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1786 $lines = count($txt);
1787 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1788 $bg = imagecolorallocate($im, 255, 255, 255);
1789 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1792 foreach ($txt as $text) {
1793 $x = (($w - ($fw * strlen($text))) / 2);
1794 imagestring($im, $font, $x, $y, $text, $color);
1795 $y += ($h + $space);
1798 Header("Content-type: image/png");
1801 } // showTextImage()
1804 * check if all requirements are met
1806 private function check_requirements()
1808 if(!function_exists("imagecreatefromjpeg")) {
1809 print "PHP GD library extension is missing<br />\n";
1813 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1814 print "PHP SQLite3 library extension is missing<br />\n";
1818 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1819 ini_set('track_errors', 1);
1820 @include_once 'HTML/AJAX/Server.php';
1821 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1822 print "PEAR HTML_AJAX package is missing<br />\n";
1825 @include_once 'Calendar/Calendar.php';
1826 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1827 print "PEAR Calendar package is missing<br />\n";
1830 @include_once 'Console/Getopt.php';
1831 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1832 print "PEAR Console_Getopt package is missing<br />\n";
1835 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
1836 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1837 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
1840 ini_restore('track_errors');
1847 } // check_requirements()
1849 private function _debug($text)
1851 if($this->fromcmd) {
1858 * check if specified MIME type is supported
1860 public function checkifImageSupported($mime)
1862 if(in_array($mime, Array("image/jpeg", "image/png")))
1867 } // checkifImageSupported()
1869 public function _error($text)
1871 switch($this->cfg->logging) {
1874 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1875 print $text ."<br />\n";
1881 error_log($text, 3, $his->cfg->log_file);
1885 $this->runtime_error = true;
1890 * output calendard input fields
1892 private function get_calendar($mode)
1894 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1895 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1896 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1898 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1899 if(!isset($_SESSION[$mode .'_date']))
1900 $output.= " disabled=\"disabled\"";
1902 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1903 if(!isset($_SESSION[$mode .'_date']))
1904 $output.= " disabled=\"disabled\"";
1906 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1907 if(!isset($_SESSION[$mode .'_date']))
1908 $output.= " disabled=\"disabled\"";
1916 * output calendar matrix
1918 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1920 if (!isset($year)) $year = date('Y');
1921 if (!isset($month)) $month = date('m');
1922 if (!isset($day)) $day = date('d');
1927 require_once CALENDAR_ROOT.'Month/Weekdays.php';
1928 require_once CALENDAR_ROOT.'Day.php';
1931 $month = new Calendar_Month_Weekdays($year,$month);
1934 $prevStamp = $month->prevMonth(true);
1935 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1936 $nextStamp = $month->nextMonth(true);
1937 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1939 $selectedDays = array (
1940 new Calendar_Day($year,$month,$day),
1941 new Calendar_Day($year,12,25),
1944 // Build the days in the month
1945 $month->build($selectedDays);
1947 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1948 $this->tmpl->assign('prev_month', $prev);
1949 $this->tmpl->assign('next_month', $next);
1951 while ( $day = $month->fetch() ) {
1953 if(!isset($matrix[$rows]))
1954 $matrix[$rows] = Array();
1958 $dayStamp = $day->thisDay(true);
1959 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1961 // isFirst() to find start of week
1962 if ( $day->isFirst() )
1965 if ( $day->isSelected() ) {
1966 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1967 } else if ( $day->isEmpty() ) {
1968 $string.= "<td> </td>\n";
1970 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1973 // isLast() to find end of week
1974 if ( $day->isLast() )
1975 $string.= "</tr>\n";
1977 $matrix[$rows][$cols] = $string;
1987 $this->tmpl->assign('matrix', $matrix);
1988 $this->tmpl->assign('rows', $rows);
1989 $this->tmpl->show("calendar.tpl");
1991 } // get_calendar_matrix()
1994 * output export page
1996 public function getExport($mode)
1998 $pictures = $this->getPhotoSelection();
1999 $current_tags = $this->getCurrentTags();
2001 foreach($pictures as $picture) {
2003 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2004 if($current_tags != "") {
2005 $orig_url.= "&tags=". $current_tags;
2007 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2008 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2011 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2016 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2017 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2021 // "[%pictureurl% %thumbnailurl%]"
2022 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2025 case 'MoinMoinList':
2026 // " * [%pictureurl% %thumbnailurl%]"
2027 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2038 public function getRSSFeed()
2040 Header("Content-type: text/xml; charset=utf-8");
2041 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2044 xmlns:media="http://search.yahoo.com/mrss/"
2045 xmlns:dc="http://purl.org/dc/elements/1.1/"
2048 <title>phpfspot</title>
2049 <description>phpfspot RSS feed</description>
2050 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2051 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2052 <generator>phpfspot</generator>
2055 $pictures = $this->getPhotoSelection();
2056 $current_tags = $this->getCurrentTags();
2058 foreach($pictures as $picture) {
2060 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2061 if($current_tags != "") {
2062 $orig_url.= "&tags=". $current_tags;
2064 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2065 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2068 $details = $this->get_photo_details($picture);
2070 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2071 $thumb_html = htmlspecialchars("
2072 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2074 ". $details['description']);
2076 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2078 /* get EXIF information if JPEG */
2079 if($details['mime'] == "image/jpeg") {
2080 $meta = $this->get_meta_informations($orig_path);
2083 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2087 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2088 <link><?php print htmlspecialchars($orig_url); ?></link>
2089 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2090 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2092 <?php print $thumb_html; ?>
2094 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2109 * return all selected tags as one string
2111 private function getCurrentTags()
2114 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2115 foreach($_SESSION['selected_tags'] as $tag)
2116 $current_tags.= $tag .",";
2117 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2119 return $current_tags;
2121 } // getCurrentTags()
2124 * return the current photo
2126 public function getCurrentPhoto()
2128 if(isset($_SESSION['current_photo'])) {
2129 print $_SESSION['current_photo'];
2131 } // getCurrentPhoto()
2134 * tells the client browser what to do
2136 * this function is getting called via AJAX by the
2137 * client browsers. it will tell them what they have
2138 * to do next. This is necessary for directly jumping
2139 * into photo index or single photo view when the are
2140 * requested with specific URLs
2142 public function whatToDo()
2144 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2145 return "show_photo";
2147 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2148 return "showpi_tags";
2150 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2154 return "nothing special";
2159 * return the current process-user
2161 private function getuid()
2163 if($uid = posix_getuid()) {
2164 if($user = posix_getpwuid($uid)) {
2165 return $user['name'];
2174 * returns a select-dropdown box to select photo index sort parameters
2176 public function smarty_sort_select_list($params, &$smarty)
2180 foreach($this->sort_orders as $key => $value) {
2181 $output.= "<option value=\"". $key ."\"";
2182 if($key == $_SESSION['sort_order']) {
2183 $output.= " selected=\"selected\"";
2185 $output.= ">". $value ."</option>";
2190 } // smarty_sort_select_list()
2193 * returns the currently selected sort order
2195 private function get_sort_order()
2197 switch($_SESSION['sort_order']) {
2199 return " ORDER BY p.time ASC";
2202 return " ORDER BY p.time DESC";
2205 if($this->dbver < 9) {
2206 return " ORDER BY p.name ASC";
2209 return " ORDER BY basename(p.uri) ASC";
2213 if($this->dbver < 9) {
2214 return " ORDER BY p.name DESC";
2217 return " ORDER BY basename(p.uri) DESC";
2221 return " ORDER BY t.name ASC ,p.time ASC";
2224 return " ORDER BY t.name DESC ,p.time ASC";
2228 } // get_sort_order()
2231 * return the next to be shown slide show image
2233 * this function returns the URL of the next image
2234 * in the slideshow sequence.
2236 public function getNextSlideShowImage()
2238 $all_photos = $this->getPhotoSelection();
2240 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2241 $_SESSION['slideshow_img'] = 0;
2243 $_SESSION['slideshow_img']++;
2245 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2247 } // getNextSlideShowImage()
2250 * return the previous to be shown slide show image
2252 * this function returns the URL of the previous image
2253 * in the slideshow sequence.
2255 public function getPrevSlideShowImage()
2257 $all_photos = $this->getPhotoSelection();
2259 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2260 $_SESSION['slideshow_img'] = 0;
2262 $_SESSION['slideshow_img']--;
2264 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2266 } // getPrevSlideShowImage()
2268 public function resetSlideShow()
2270 if(isset($_SESSION['slideshow_img']))
2271 unset($_SESSION['slideshow_img']);
2273 } // resetSlideShow()
2278 * this function will get all photos from the fspot
2279 * database and randomly return ONE entry
2281 * saddly there is yet no sqlite3 function which returns
2282 * the bulk result in array, so we have to fill up our
2285 public function get_random_photo()
2289 $result = $this->db->db_query("
2294 while($row = $this->db->db_fetch_object($result)) {
2295 array_push($all, $row['id']);
2298 return $all[array_rand($all)];
2300 } // get_random_photo()
2303 * validates provided date
2305 * this function validates if the provided date
2306 * contains a valid date and will return true
2309 public function isValidDate($date_str)
2311 $timestamp = strtotime($date_str);
2313 if(is_numeric($timestamp))
2321 * timestamp to string conversion
2323 private function ts2str($timestamp)
2325 return strftime("%Y-%m-%d", $timestamp);
2328 private function extractTags($tags_str)
2330 $not_validated = split(',', $_GET['tags']);
2331 $validated = array();
2333 foreach($not_validated as $tag) {
2334 if(is_numeric($tag))
2335 array_push($validated, $tag);
2343 * returns the full path to a thumbnail
2345 public function get_thumb_path($width, $photo)
2347 $md5 = $this->getMD5($photo);
2348 $sub_path = substr($md5, 0, 2);
2349 return $this->cfg->thumb_path
2357 } // get_thumb_path()
2360 * returns server's virtual host name
2362 private function get_server_name()
2364 return $_SERVER['SERVER_NAME'];
2365 } // get_server_name()
2368 * returns type of webprotocol which is
2371 private function get_web_protocol()
2373 if(!isset($_SERVER['HTTPS']))
2377 } // get_web_protocol()
2380 * return url to this phpfspot installation
2382 private function get_phpfspot_url()
2384 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2385 } // get_phpfspot_url()
2388 * returns the number of photos which are tagged with $tag_id
2390 public function get_num_photos($tag_id)
2392 if($result = $this->db->db_fetchSingleRow("
2393 SELECT count(*) as number
2396 tag_id LIKE '". $tag_id ."'")) {
2398 return $result['number'];
2404 } // get_num_photos()
2407 * check file exists and is readable
2409 * returns true, if everything is ok, otherwise false
2410 * if $silent is not set, this function will output and
2413 private function check_readable($file, $silent = null)
2415 if(!file_exists($file)) {
2417 print "File \"". $file ."\" does not exist.\n";
2421 if(!is_readable($file)) {
2423 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2429 } // check_readable()
2432 * check if all needed indices are present
2434 * this function checks, if some needed indices are already
2435 * present, or if not, create them on the fly. they are
2436 * necessary to speed up some queries like that one look for
2437 * all tags, when show_tags is specified in the configuration.
2439 private function checkDbIndices()
2441 $result = $this->db->db_exec("
2442 CREATE INDEX IF NOT EXISTS
2449 } // checkDbIndices()
2452 * retrive F-Spot database version
2454 * this function will return the F-Spot database version number
2455 * It is stored within the sqlite3 database in the table meta
2457 public function getFspotDBVersion()
2459 if($result = $this->db->db_fetchSingleRow("
2460 SELECT data as version
2463 name LIKE 'F-Spot Database Version'
2465 return $result['version'];
2469 } // getFspotDBVersion()
2472 * parse the provided URI and will returned the
2475 public function parse_uri($uri, $mode)
2477 if(($components = parse_url($uri)) !== false) {
2481 return basename($components['path']);
2484 return dirname($components['path']);
2487 return $components['path'];
2497 * validate config options
2499 * this function checks if all necessary configuration options are
2500 * specified and set.
2502 private function check_config_options()
2504 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2505 $this->_error("Please set \$page_title in phpfspot_cfg");
2507 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2508 $this->_error("Please set \$base_path in phpfspot_cfg");
2510 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2511 $this->_error("Please set \$web_path in phpfspot_cfg");
2513 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2514 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2516 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2517 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2519 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2520 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2522 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2523 $this->_error("Please set \$db_access in phpfspot_cfg");
2525 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2526 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2528 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2529 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2531 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2532 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2534 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2535 $this->_error("Please set \$photo_width in phpfspot_cfg");
2537 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2538 $this->_error("Please set \$mini_width in phpfspot_cfg");
2540 if(!isset($this->cfg->thumbs_per_page))
2541 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2543 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2544 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2546 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2547 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2549 if(!isset($this->cfg->hide_tags))
2550 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2552 if(!isset($this->cfg->theme_name))
2553 $this->_error("Please set \$theme_name in phpfspot_cfg");
2555 if(!isset($this->cfg->logging))
2556 $this->_error("Please set \$logging in phpfspot_cfg");
2558 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2560 if(!isset($this->cfg->log_file))
2561 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2563 if(!is_writeable($this->cfg->log_file))
2564 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2568 /* check for pending slash on web_path */
2569 if(!preg_match("/\/$/", $this->cfg->web_path))
2570 $this->cfg->web_path.= "/";
2572 return $this->runtime_error;
2574 } // check_config_options()
2577 * cleanup phpfspot own database
2579 * When photos are getting delete from F-Spot, there will remain
2580 * remain some residues in phpfspot own database. This function
2581 * will try to wipe them out.
2583 public function cleanup_phpfspot_db()
2585 $to_delete = Array();
2587 $result = $this->cfg_db->db_query("
2590 ORDER BY img_idx ASC
2593 while($row = $this->cfg_db->db_fetch_object($result)) {
2594 if(!$this->db->db_fetchSingleRow("
2597 WHERE id='". $row['img_idx'] ."'")) {
2599 array_push($to_delete, $row['img_idx'], ',');
2603 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2605 $this->cfg_db->db_exec("
2607 WHERE img_idx IN (". implode($to_delete) .")
2610 } // cleanup_phpfspot_db()
2613 * return first image of the page, the $current photo
2616 * this function is used to find out the first photo of the
2617 * current page, in which the $current photo lies. this is
2618 * used to display the correct photo, when calling showPhotoIndex()
2621 private function getCurrentPage($current, $max)
2623 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2624 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2625 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2631 } // getCurrentPage()