3 /***************************************************************************
5 * phpfspot, presents your F-Spot photo collection in Web browsers.
7 * Copyright (c) by Andreas Unterkircher
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 ***************************************************************************/
25 require_once "phpfspot_cfg.php";
26 require_once "phpfspot_db.php";
31 * this class contains the most functions which will to the major
39 * phpfspot configuration
47 * SQLite database handle to f-spot database
55 * SQLite database handle to phpfspot database
63 * Smarty template engine
64 * @link http://smarty.php.net smarty.php.net
65 * @see PHPFSPOT_TMPL()
79 * list of available, not-selected, tags
86 * true if runtime error occued
90 private $runtime_error = false;
93 * F-Spot database version
100 * class constructor ($cfg, $db, $cfg_db, $tmpl, $db_ver)
102 * this function will be called on class construct
103 * and will check requirements, loads configuration,
104 * open databases and start the user session
106 public function __construct()
109 * register PHPFSPOT class global
111 * @global PHPFSPOT $GLOBALS['phpfspot']
114 $GLOBALS['phpfspot'] =& $this;
116 $this->cfg = new PHPFSPOT_CFG;
118 /* verify config settings */
119 if($this->check_config_options()) {
123 /* set application name and version information */
124 $this->cfg->product = "phpfspot";
125 $this->cfg->version = "1.7";
126 $this->cfg->db_version = 2;
128 $this->sort_orders= array(
129 'date_asc' => 'Date ↑',
130 'date_desc' => 'Date ↓',
131 'name_asc' => 'Name ↑',
132 'name_desc' => 'Name ↓',
133 'tags_asc' => 'Tags ↑',
134 'tags_desc' => 'Tags ↓',
137 /* Check necessary requirements */
138 if(!$this->check_requirements()) {
142 /******* Opening F-Spot's sqlite database *********/
144 /* Check if database file exists and is readable */
145 if(!file_exists($this->cfg->fspot_db) || !is_readable($this->cfg->fspot_db)) {
146 print "Error: ". $this->cfg->fspot_db ." does not exist or is not readable for user ". $this->getuid() .".\n";
150 /* Check if database file is writeable */
151 if(!is_writeable($this->cfg->fspot_db)) {
152 print "Error: ". $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() .".\n";
153 print "Please fix permissions so phpfspot can create indices within the F-Spot database to"
154 ." speed up some database operations.\n";
158 /* open the database */
159 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
161 /* change sqlite temp directory, if requested */
162 if(isset($this->cfg->sqlite_temp_dir)) {
165 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
169 /* get F-Spot database version */
170 $this->dbver = $this->getFspotDBVersion();
172 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
173 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
177 if(!is_writeable($this->cfg->thumb_path)) {
178 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
182 /******* Opening phpfspot's sqlite database *********/
184 /* Check if directory where the database file is stored is writeable */
185 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
186 print "Error: ". dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() .".\n";
187 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
191 /* Check if database file is writeable */
192 if(file_exists($this->cfg->phpfspot_db) && !is_writeable($this->cfg->phpfspot_db)) {
193 print "Error: ". $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() .".\n";
194 print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n";
198 /* open the database */
199 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
201 /* change sqlite temp directory, if requested */
202 if(isset($this->cfg->sqlite_temp_dir)) {
203 $this->cfg_db->db_exec("
205 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
209 /* Check if some tables need to be created */
210 $this->check_phpfspot_db();
212 /* overload Smarty class with our own template handler */
213 require_once "phpfspot_tmpl.php";
214 $this->tmpl = new PHPFSPOT_TMPL();
216 /* pre-set some template variables */
217 $this->tmpl->assign('web_path', $this->cfg->web_path);
219 /* Starting with F-Spot 0.4.2, the rating-feature was available */
220 if($this->dbver > 10) {
221 $this->tmpl->assign('has_rating', true);
222 $this->sort_orders = array_merge($this->sort_orders, array(
223 'rate_asc' => 'Rate ↑',
224 'rate_desc' => 'Rate ↓',
228 /* check if all necessary indices exist */
229 $this->checkDbIndices();
231 /* if session is not yet started, do it now */
232 if(session_id() == "")
235 if(!isset($_SESSION['tag_condition']))
236 $_SESSION['tag_condition'] = 'or';
238 /* if sort-order has not been set yet, get the one specified in the config */
239 if(!isset($_SESSION['sort_order']))
240 $_SESSION['sort_order'] = $this->cfg->sort_order;
242 if(!isset($_SESSION['searchfor_tag']))
243 $_SESSION['searchfor_tag'] = '';
245 // if begin_with is still set but thumbs_per_page is now 0, unset it
246 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
247 unset($_SESSION['begin_with']);
249 // if user-friendly-url's are enabled, set also a flag for the template handler
250 if($this->is_user_friendly_url()) {
251 $this->tmpl->assign('user_friendly_url', 'true');
256 public function __destruct()
262 * show - generate html output
264 * this function can be called after the constructor has
265 * prepared everyhing. it will load the index.tpl smarty
266 * template. if necessary it will registere pre-selects
267 * (photo index, photo, tag search, date search) into
270 public function show()
272 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
273 $this->tmpl->assign('page_title', $this->cfg->page_title);
274 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
275 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
278 if($this->is_user_friendly_url()) {
279 $content = $this->parse_user_friendly_url($_SERVER['REQUEST_URI']);
282 if(isset($_GET['mode'])) {
284 $_SESSION['start_action'] = $_GET['mode'];
286 switch($_GET['mode']) {
288 if(isset($_GET['tags'])) {
289 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
291 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
292 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
294 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
295 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
299 if(isset($_GET['tags'])) {
300 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
301 $_SESSION['start_action'] = 'showp';
303 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
304 if($_SESSION['current_photo'] != $_GET['id'])
305 unset($_SESSION['current_version']);
306 $_SESSION['current_photo'] = $_GET['id'];
307 $_SESSION['start_action'] = 'showp';
309 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
310 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
312 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
313 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
317 /* fetch export template */
318 print $this->tmpl->fetch("export.tpl");
319 /* no further execution necessary. */
323 /* fetch slideshow template */
324 print $this->tmpl->show("slideshow.tpl");
325 /* no further execution necessary. */
329 if(isset($_GET['tags'])) {
330 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
332 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
333 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
335 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
336 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
344 /* if date-search variables are registered in the session, set the check
345 for "consider date-range" in the html output
347 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
348 $this->tmpl->assign('date_search_enabled', true);
350 /* if rate-search variables are registered in the session, set the check
351 for "consider rate-range" in the html output
353 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
354 $this->tmpl->assign('rate_search_enabled', true);
357 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
358 $this->tmpl->assign('search_from_date', $this->get_date_text_field('from'));
359 $this->tmpl->assign('search_to_date', $this->get_date_text_field('to'));
361 $this->tmpl->assign('preset_selected_tags', $this->getSelectedTags());
362 $this->tmpl->assign('preset_available_tags', $this->getAvailableTags());
363 $this->tmpl->assign('rate_search', $this->get_rate_search());
365 /* if no site-content has been set yet... */
366 if(!isset($content)) {
367 /* if tags are already selected, we can immediately display photo-index */
368 if((isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags']) &&
369 isset($_SESSION['start_action']) && $_SESSION['start_action'] != 'showp') ||
370 (isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi'))
371 $this->tmpl->assign('initial_content', $this->showPhotoIndex());
373 /* if a photo is already selected, we can immediately display single-photo */
374 if(isset($_SESSION['current_photo']) && !empty($_SESSION['current_photo']))
375 $this->tmpl->assign('initial_content', $this->showPhoto($_SESSION['current_photo']));
377 /* ok, then let us show the welcome page... */
378 $this->tmpl->assign('initial_content', $this->tmpl->fetch('welcome.tpl'));
383 $this->tmpl->assign('initial_content', $content);
385 $this->tmpl->show("index.tpl");
390 * get_tags - grab all tags of f-spot's database
392 * this function will get all available tags from
393 * the f-spot database and store them within two
394 * arrays within this class for later usage. in
395 * fact, if the user requests (hide_tags) it will
396 * opt-out some of them.
398 * this function is getting called once by show()
400 private function get_tags()
402 $this->avail_tags = Array();
405 /* if show_tags has been set in the configuration (only show photos
406 which are tagged by these tags) they following will take care,
407 that only these other tags are displayed where the photo is also
408 tagged with one of show_tags.
410 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
413 DISTINCT t1.id as id, t1.name as name
416 INNER JOIN photo_tags
417 pt2 ON pt1.photo_id=pt2.photo_id
423 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
425 t1.sort_priority ASC";
427 $result = $this->db->db_query($query_str);
431 $result = $this->db->db_query("
432 SELECT id as id,name as name
434 ORDER BY sort_priority ASC
438 while($row = $this->db->db_fetch_object($result)) {
440 $tag_id = $row['id'];
441 $tag_name = $row['name'];
443 /* if the user has specified to ignore this tag in phpfspot's
444 configuration, ignore it here so it does not get added to
447 if(in_array($row['name'], $this->cfg->hide_tags))
450 /* if you include the following if-clause and the user has specified
451 to only show certain tags which are specified in phpfspot's
452 configuration, ignore all others so they will not be added to the
454 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
455 !in_array($row['name'], $this->cfg->show_tags))
459 $this->tags[$tag_id] = $tag_name;
460 $this->avail_tags[$count] = $tag_id;
468 * get all photo details from F-Spot database
470 * this function queries the F-Spot database for all available
471 * details of the requested photo. It returns them as a object.
473 * Furthermore it takes care of the photo version to be requested.
474 * If photo version is not yet, it queries information for the
477 * @param integer $idx
478 * @return object|null
480 public function get_photo_details($idx, $version_idx = NULL)
482 /* ~ F-Spot version 0.3.x */
483 if($this->dbver < 9) {
489 p.directory_path as directory_path,
490 p.description as description
496 /* till F-Spot version 0.4.1 */
497 if($this->dbver < 11) {
503 p.description as description
508 elseif($this->dbver < 17) {
509 /* rating value got introduced */
515 p.description as description,
522 /* path & filename now splited in base_uri & filename */
526 p.base_uri || p.filename as uri,
528 p.description as description,
536 /* if show_tags is set, only return details of photos which are
537 tagged with a tag that has been specified to be shown.
539 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
541 INNER JOIN photo_tags pt
545 WHERE p.id='". $idx ."'
546 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
550 WHERE p.id='". $idx ."'
554 if($row = $this->db->db_fetchSingleRow($query_str)) {
556 /* before F-Spot db version 9 there was no uri column but
557 seperated fields for directory_path and name (= filename).
559 if($this->dbver < 9) {
560 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
563 /* if version-idx has not yet been set, get the latest photo version */
564 if(!isset($version_idx) || !$this->is_valid_version($idx, $version_idx))
565 $version_idx = $this->get_latest_version($idx);
567 /* if an alternative version has been requested. But we
568 support this only for F-Spot database versions from
571 if($version_idx > 0 && $this->dbver >= 9) {
572 if ($this->dbver < 17) {
573 /* check for alternative versions */
574 if($version = $this->db->db_fetchSingleRow("
576 version_id, name, uri
580 photo_id LIKE '". $idx ."'
582 version_id LIKE '". $version_idx ."'")) {
584 $row['name'] = $version['name'];
585 $row['uri'] = $version['uri'];
589 /* path & filename now splited in base_uri & filename */
590 if($version = $this->db->db_fetchSingleRow("
594 base_uri || filename as uri
598 photo_id LIKE '". $idx ."'
600 version_id LIKE '". $version_idx ."'")) {
602 $row['name'] = $version['name'];
603 $row['uri'] = $version['uri'];
614 } // get_photo_details()
617 * returns aligned photo names
619 * this function returns aligned (length) names for a specific photo.
620 * If the length of the name exceeds $limit the name will bei
623 * @param integer $idx
624 * @param integer $limit
625 * @return string|null
627 public function getPhotoName($idx, $limit = 0)
629 if($details = $this->get_photo_details($idx)) {
630 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
631 $name = $this->shrink_text($long_name, $limit);
641 * get photo rating level
643 * this function will return the integer-based rating level of a
644 * photo. This can only be done, if the F-Spot database is at a
645 * specific version. If rating value can not be found, zero will
646 * be returned indicating no rating value is available.
651 public function get_photo_rating($idx)
653 if($detail = $this->get_photo_details($idx)) {
654 if(isset($detail['rating']))
655 return $detail['rating'];
660 } // get_photo_rating()
663 * get rate-search bars
665 * this function will return the rating-bars for the search field.
669 public function get_rate_search()
673 for($i = 1; $i <= 5; $i++) {
675 $bar.= "<img id=\"rate_from_". $i ."\" src=\"";
677 if(isset($_SESSION['rate_from']) && $i <= $_SESSION['rate_from'])
678 $bar.= $this->cfg->web_path ."/resources/star.png";
680 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
683 onmouseover=\"show_rate('from', ". $i .");\"
684 onmouseout=\"reset_rate('from');\"
685 onclick=\"set_rate('from', ". $i .")\" />";
690 for($i = 1; $i <= 5; $i++) {
692 $bar.= "<img id=\"rate_to_". $i ."\" src=\"";
694 if(isset($_SESSION['rate_to']) && $i <= $_SESSION['rate_to'])
695 $bar.= $this->cfg->web_path ."/resources/star.png";
697 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
700 onmouseover=\"show_rate('to', ". $i .");\"
701 onmouseout=\"reset_rate('to');\"
702 onclick=\"set_rate('to', ". $i .");\" />";
707 } // get_rate_search()
710 * shrink text according provided limit
712 * If the length of the name exceeds $limit, text will be shortend
713 * and inner content will be replaced with "...".
716 * @param integer $limit
719 private function shrink_text($text, $limit)
721 if($limit != 0 && strlen($text) > $limit) {
722 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
730 * translate f-spoth photo path
732 * as the full-qualified path recorded in the f-spot database
733 * is usally not the same as on the webserver, this function
734 * will replace the path with that one specified in the cfg
735 * @param string $path
738 public function translate_path($path)
740 if($this->cfg->enable_replace_path == true)
742 $this->cfg->path_replace_from,
743 $this->cfg->path_replace_to, $path);
750 * control HTML ouput for a single photo
752 * this function provides all the necessary information
753 * for the single photo template.
754 * @param integer photo
756 public function showPhoto($photo)
758 /* get all photos from the current photo selection */
759 $all_photos = $this->getPhotoSelection();
760 $count = count($all_photos);
762 for($i = 0; $i < $count; $i++) {
764 // $get_next will be set, when the photo which has to
765 // be displayed has been found - this means that the
766 // next available is in fact the NEXT image (for the
768 if(isset($get_next)) {
769 $next_img = $all_photos[$i];
773 /* the next photo is our NEXT photo */
774 if($all_photos[$i] == $photo) {
778 $previous_img = $all_photos[$i];
781 if($photo == $all_photos[$i]) {
786 $details = $this->get_photo_details($photo);
793 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
795 /* if current version is already set, use it */
796 if($this->get_current_version() !== false)
797 $version = $this->get_current_version();
799 /* if version not set yet, we assume to display the latest version */
800 if(!isset($version) || !$this->is_valid_version($photo, $version))
801 $version = $this->get_latest_version($photo);
803 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
805 if(!file_exists($orig_path)) {
806 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
810 if(!is_readable($orig_path)) {
811 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
815 /* If the thumbnail doesn't exist yet, try to create it */
816 if(!file_exists($thumb_path)) {
817 $this->gen_thumb($photo, true);
818 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
821 /* get mime-type, height and width from the original photo */
822 $info = getimagesize($orig_path);
824 /* get EXIF information if JPEG */
825 if(isset($info['mime']) && $info['mime'] == "image/jpeg") {
826 $meta = $this->get_meta_informations($orig_path);
829 /* If EXIF data are available, use them */
830 if(isset($meta['ExifImageWidth'])) {
831 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
833 $meta_res = $info[0] ."x". $info[1];
836 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
837 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
839 $extern_link = "index.php?mode=showp&id=". $photo;
840 $current_tags = $this->getCurrentTags();
841 if($current_tags != "") {
842 $extern_link.= "&tags=". $current_tags;
844 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
845 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
848 $this->tmpl->assign('extern_link', $extern_link);
850 if(!file_exists($thumb_path)) {
851 $this->_error("Can't open file ". $thumb_path ."\n");
855 $info_thumb = getimagesize($thumb_path);
857 $this->tmpl->assign('description', $details['description']);
858 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
859 $this->tmpl->assign('image_rating', $this->get_photo_rating($photo));
861 $this->tmpl->assign('width', $info_thumb[0]);
862 $this->tmpl->assign('height', $info_thumb[1]);
863 $this->tmpl->assign('ExifMadeOn', strftime("%a %x %X", $details['time']));
864 $this->tmpl->assign('ExifMadeWith', $meta_make);
865 $this->tmpl->assign('ExifOrigResolution', $meta_res);
866 $this->tmpl->assign('ExifFileSize', $meta_size);
868 if($this->is_user_friendly_url()) {
869 $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width .'/'. $version);
870 $this->tmpl->assign('image_url_full', '/photo/'. $photo);
873 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width ."&version=". $version);
874 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
877 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
879 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
880 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
881 $this->tmpl->assign('current_img', $photo);
883 if(isset($previous_img)) {
884 $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");");
885 $this->tmpl->assign('prev_img', $previous_img);
888 if(isset($next_img)) {
889 $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");");
890 $this->tmpl->assign('next_img', $next_img);
893 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
894 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
895 $this->tmpl->assign('photo_number', $i);
896 $this->tmpl->assign('photo_count', count($all_photos));
897 $this->tmpl->assign('photo', $photo);
898 $this->tmpl->assign('version', $version);
900 /* if the photo as alternative versions, set a flag for the template */
901 if($this->get_photo_versions($photo))
902 $this->tmpl->assign('has_versions', true);
904 $this->tmpl->register_function("photo_version_select_list", array(&$this, "smarty_photo_version_select_list"), false);
906 return $this->tmpl->fetch("single_photo.tpl");
911 * all available tags and tag cloud
913 * this function outputs all available tags (time ordered)
914 * and in addition output them as tag cloud (tags which have
915 * many photos will appears more then others)
917 public function getAvailableTags()
919 /* retrive tags from database */
924 $result = $this->db->db_query("
925 SELECT tag_id as id, count(tag_id) as quantity
935 while($row = $this->db->db_fetch_object($result)) {
936 $tags[$row['id']] = $row['quantity'];
939 // change these font sizes if you will
940 $max_size = 125; // max font size in %
941 $min_size = 75; // min font size in %
944 $max_sat = hexdec('cc');
945 $min_sat = hexdec('44');
947 // get the largest and smallest array values
948 $max_qty = max(array_values($tags));
949 $min_qty = min(array_values($tags));
951 // find the range of values
952 $spread = $max_qty - $min_qty;
953 if (0 == $spread) { // we don't want to divide by zero
957 // determine the font-size increment
958 // this is the increase per tag quantity (times used)
959 $step = ($max_size - $min_size)/($spread);
960 $step_sat = ($max_sat - $min_sat)/($spread);
962 // loop through our tag array
963 foreach ($tags as $key => $value) {
965 /* has the currently processed tag already been added to
966 the selected tag list? if so, ignore it here...
968 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
971 // calculate CSS font-size
972 // find the $value in excess of $min_qty
973 // multiply by the font-size increment ($size)
974 // and add the $min_size set above
975 $size = $min_size + (($value - $min_qty) * $step);
976 // uncomment if you want sizes in whole %:
979 $color = $min_sat + ($value - $min_qty) * $step_sat;
985 if(isset($this->tags[$key])) {
986 if($this->is_user_friendly_url()) {
987 $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\"
988 onclick=\"Tags('add', ". $key ."); return false;\"
990 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
991 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
994 $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\"
995 onclick=\"Tags('add', ". $key ."); return false;\"
997 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
998 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
1003 $output = substr($output, 0, strlen($output)-2);
1006 } // getAvailableTags()
1009 * output all selected tags
1011 * this function output all tags which have been selected
1012 * by the user. the selected tags are stored in the
1013 * session-variable $_SESSION['selected_tags']
1016 public function getSelectedTags($type = 'link')
1018 /* retrive tags from database */
1023 foreach($this->avail_tags as $tag)
1025 // return all selected tags
1026 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
1031 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
1035 <div class=\"tagresulttag\">
1036 <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\">
1037 <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" />
1047 $output = substr($output, 0, strlen($output)-2);
1051 return "no tags selected";
1054 } // getSelectedTags()
1057 * add tag to users session variable
1059 * this function will add the specified to users current
1060 * tag selection. if a date search has been made before
1061 * it will be now cleared
1064 public function addTag($tag)
1066 if(!isset($_SESSION['selected_tags']))
1067 $_SESSION['selected_tags'] = Array();
1069 if(isset($_SESSION['searchfor_tag']))
1070 unset($_SESSION['searchfor_tag']);
1072 // has the user requested to hide this tag, and still someone,
1073 // somehow tries to add it, don't allow this.
1074 if(!isset($this->cfg->hide_tags) &&
1075 in_array($this->get_tag_name($tag), $this->cfg->hide_tags))
1078 if(!in_array($tag, $_SESSION['selected_tags']))
1079 array_push($_SESSION['selected_tags'], $tag);
1086 * remove tag to users session variable
1088 * this function removes the specified tag from
1089 * users current tag selection
1090 * @param string $tag
1093 public function delTag($tag)
1095 if(isset($_SESSION['searchfor_tag']))
1096 unset($_SESSION['searchfor_tag']);
1098 if(isset($_SESSION['selected_tags'])) {
1099 $key = array_search($tag, $_SESSION['selected_tags']);
1100 unset($_SESSION['selected_tags'][$key]);
1101 sort($_SESSION['selected_tags']);
1109 * reset tag selection
1111 * if there is any tag selection, it will be
1114 public function resetTags()
1116 if(isset($_SESSION['selected_tags']))
1117 unset($_SESSION['selected_tags']);
1122 * returns the value for the autocomplete tag-search
1125 public function get_xml_tag_list()
1127 if(!isset($_GET['search']) || !is_string($_GET['search']))
1128 $_GET['search'] = '';
1133 /* retrive tags from database */
1136 $matched_tags = Array();
1138 header("Content-Type: text/xml");
1140 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
1141 $string.= "<results>\n";
1143 foreach($this->avail_tags as $tag)
1145 if(!empty($_GET['search']) &&
1146 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
1147 count($matched_tags) < $length) {
1149 $count = $this->get_num_photos($tag);
1152 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
1155 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
1161 /* if we have collected enough items, break out */
1162 if(count($matched_tags) >= $length)
1166 $string.= "</results>\n";
1170 } // get_xml_tag_list()
1174 * reset single photo
1176 * if a specific photo was requested (external link)
1177 * unset the session variable now
1179 public function resetPhotoView()
1181 if(isset($_SESSION['current_photo']))
1182 unset($_SESSION['current_photo']);
1184 if(isset($_SESSION['current_version']))
1185 unset($_SESSION['current_version']);
1187 } // resetPhotoView();
1192 * if any tag search has taken place, reset it now
1194 public function resetTagSearch()
1196 if(isset($_SESSION['searchfor_tag']))
1197 unset($_SESSION['searchfor_tag']);
1199 } // resetTagSearch()
1204 * if any name search has taken place, reset it now
1206 public function resetNameSearch()
1208 if(isset($_SESSION['searchfor_name']))
1209 unset($_SESSION['searchfor_name']);
1211 } // resetNameSearch()
1216 * if any date search has taken place, reset it now.
1218 public function resetDateSearch()
1220 if(isset($_SESSION['from_date']))
1221 unset($_SESSION['from_date']);
1222 if(isset($_SESSION['to_date']))
1223 unset($_SESSION['to_date']);
1225 } // resetDateSearch();
1230 * if any rate search has taken place, reset it now.
1232 public function resetRateSearch()
1234 if(isset($_SESSION['rate_from']))
1235 unset($_SESSION['rate_from']);
1236 if(isset($_SESSION['rate_to']))
1237 unset($_SESSION['rate_to']);
1239 } // resetRateSearch();
1242 * return all photo according selection
1244 * this function returns all photos based on
1245 * the tag-selection, tag- or date-search.
1246 * the tag-search also has to take care of AND
1247 * and OR conjunctions
1250 public function getPhotoSelection()
1252 $matched_photos = Array();
1253 $additional_where_cond = "";
1255 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1256 $from_date = $_SESSION['from_date'];
1257 $to_date = $_SESSION['to_date'];
1258 $additional_where_cond.= "
1259 p.time>='". $from_date ."'
1261 p.time<='". $to_date ."'
1265 if(isset($_SESSION['searchfor_name'])) {
1267 /* check for previous conditions. if so add 'AND' */
1268 if(!empty($additional_where_cond)) {
1269 $additional_where_cond.= " AND ";
1272 if($this->dbver < 9) {
1273 $additional_where_cond.= "
1275 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
1277 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1281 if($this->dbver < 17) {
1282 $additional_where_cond.= "
1284 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
1286 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1291 $additional_where_cond.= "
1293 p.filename LIKE '%". $_SESSION['searchfor_name'] ."%'
1295 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1301 /* limit result based on rate-search */
1302 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
1304 if($this->dbver > 10) {
1306 /* check for previous conditions. if so add 'AND' */
1307 if(!empty($additional_where_cond)) {
1308 $additional_where_cond.= " AND ";
1311 $additional_where_cond.= "
1312 p.rating >= ". $_SESSION['rate_from'] ."
1314 p.rating <= ". $_SESSION['rate_to'] ."
1319 if(isset($_SESSION['sort_order'])) {
1320 $order_str = $this->get_sort_order();
1323 /* return a search result */
1324 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
1327 pt1.photo_id as photo_id
1330 INNER JOIN photo_tags pt2
1331 ON pt1.photo_id=pt2.photo_id
1335 ON pt1.photo_id=p.id
1338 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
1340 if(!empty($additional_where_cond))
1341 $query_str.= "AND ". $additional_where_cond ." ";
1343 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1344 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
1347 if(isset($order_str))
1348 $query_str.= $order_str;
1350 $result = $this->db->db_query($query_str);
1351 while($row = $this->db->db_fetch_object($result)) {
1352 array_push($matched_photos, $row['photo_id']);
1354 return $matched_photos;
1357 /* return according the selected tags */
1358 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1360 foreach($_SESSION['selected_tags'] as $tag)
1361 $selected.= $tag .",";
1362 $selected = substr($selected, 0, strlen($selected)-1);
1364 /* photo has to match at least on of the selected tags */
1365 if($_SESSION['tag_condition'] == 'or') {
1368 pt1.photo_id as photo_id
1371 INNER JOIN photo_tags pt2
1372 ON pt1.photo_id=pt2.photo_id
1376 ON pt1.photo_id=p.id
1377 WHERE pt1.tag_id IN (". $selected .")
1379 if(!empty($additional_where_cond))
1380 $query_str.= "AND ". $additional_where_cond ." ";
1382 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1383 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1386 if(isset($order_str))
1387 $query_str.= $order_str;
1389 /* photo has to match all selected tags */
1390 elseif($_SESSION['tag_condition'] == 'and') {
1392 if(count($_SESSION['selected_tags']) >= 32) {
1393 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1394 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1398 /* Join together a table looking like
1400 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1402 so the query can quickly return all images matching the
1403 selected tags in an AND condition
1409 pt1.photo_id as photo_id
1414 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1421 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1423 INNER JOIN photo_tags pt". ($i+2) ."
1424 ON pt1.photo_id=pt". ($i+2) .".photo_id
1429 ON pt1.photo_id=p.id
1431 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1432 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1434 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1437 if(!empty($additional_where_cond))
1438 $query_str.= "AND ". $additional_where_cond;
1440 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1441 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1444 if(isset($order_str))
1445 $query_str.= $order_str;
1449 $result = $this->db->db_query($query_str);
1450 while($row = $this->db->db_fetch_object($result)) {
1451 array_push($matched_photos, $row['photo_id']);
1453 return $matched_photos;
1456 /* return all available photos */
1462 LEFT JOIN photo_tags pt
1468 if(!empty($additional_where_cond))
1469 $query_str.= "WHERE ". $additional_where_cond ." ";
1471 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1472 if(!empty($additional_where_cond))
1473 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1475 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1478 if(isset($order_str))
1479 $query_str.= $order_str;
1481 $result = $this->db->db_query($query_str);
1482 while($row = $this->db->db_fetch_object($result)) {
1483 array_push($matched_photos, $row['id']);
1485 return $matched_photos;
1487 } // getPhotoSelection()
1490 * control HTML ouput for photo index
1492 * this function provides all the necessary information
1493 * for the photo index template.
1496 public function showPhotoIndex()
1498 $photos = $this->getPhotoSelection();
1499 $current_tags = $this->getCurrentTags();
1501 $count = count($photos);
1503 /* if all thumbnails should be shown on one page */
1504 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1508 /* thumbnails should be splitted up in several pages */
1509 elseif($this->cfg->thumbs_per_page > 0) {
1511 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1515 $begin_with = $_SESSION['begin_with'];
1518 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1523 for($i = $begin_with; $i < $end_with; $i++) {
1525 if(!isset($photos[$i]))
1528 /* on first run, initalize all used variables */
1531 $images[$thumbs] = Array();
1532 $img_height[$thumbs] = Array();
1533 $img_width[$thumbs] = Array();
1534 $img_id[$thumbs] = Array();
1535 $img_name[$thumbs] = Array();
1536 $img_fullname[$thumbs] = Array();
1537 $img_title = Array();
1538 $img_rating = Array();
1541 $images[$thumbs] = $photos[$i];
1542 $img_id[$thumbs] = $i;
1543 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1544 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1545 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1546 $img_rating[$thumbs] = $this->get_photo_rating($photos[$i]);
1548 /* get local path of the thumbnail image to be displayed */
1549 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i], $this->get_latest_version($photos[$i]));
1551 /* if the image exist and is readable, extract some details */
1552 if(file_exists($thumb_path) && is_readable($thumb_path)) {
1553 if($info = getimagesize($thumb_path) !== false) {
1554 $img_width[$thumbs] = $info[0];
1555 $img_height[$thumbs] = $info[1];
1561 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1562 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1564 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1565 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1566 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1569 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1570 $this->tmpl->assign('tag_result', 1);
1573 /* do we have to display the page selector ? */
1574 if($this->cfg->thumbs_per_page != 0) {
1578 /* calculate the page switchers */
1579 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1580 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1582 if($begin_with != 0)
1583 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1584 if($end_with < $count)
1585 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1587 $photo_per_page = $this->cfg->thumbs_per_page;
1588 $last_page = ceil($count / $photo_per_page);
1590 /* get the current selected page */
1591 if($begin_with == 0) {
1595 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1602 for($i = 1; $i <= $last_page; $i++) {
1604 if($current_page == $i)
1605 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1606 elseif($current_page-1 == $i || $current_page+1 == $i)
1607 $style = "style=\"font-size: 105%;\"";
1608 elseif(($current_page-5 >= $i) && ($i != 1) ||
1609 ($current_page+5 <= $i) && ($i != $last_page))
1610 $style = "style=\"font-size: 75%;\"";
1614 $start_with = ($i*$photo_per_page)-$photo_per_page;
1616 if($this->is_user_friendly_url()) {
1617 $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\"";
1620 $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi tags=". $current_tags ." begin_with=". $begin_with ."\"";
1622 $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\"";
1626 $select.= ">". $i ."</a> ";
1628 // until 9 pages we show the selector from 1-9
1629 if($last_page <= 9) {
1630 $page_select.= $select;
1633 if($i == 1 /* first page */ ||
1634 $i == $last_page /* last page */ ||
1635 $i == $current_page /* current page */ ||
1636 $i == ceil($last_page * 0.25) /* first quater */ ||
1637 $i == ceil($last_page * 0.5) /* half */ ||
1638 $i == ceil($last_page * 0.75) /* third quater */ ||
1639 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1640 (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 */ ||
1641 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1642 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1644 $page_select.= $select;
1652 $page_select.= "......... ";
1657 /* only show the page selector if we have more then one page */
1659 $this->tmpl->assign('page_selector', $page_select);
1662 $extern_link = "index.php?mode=showpi";
1663 $rss_link = "index.php?mode=rss";
1664 if($current_tags != "") {
1665 $extern_link.= "&tags=". $current_tags;
1666 $rss_link.= "&tags=". $current_tags;
1668 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1669 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1670 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1673 $export_link = "index.php?mode=export";
1674 $slideshow_link = "index.php?mode=slideshow";
1676 $this->tmpl->assign('extern_link', $extern_link);
1677 $this->tmpl->assign('slideshow_link', $slideshow_link);
1678 $this->tmpl->assign('export_link', $export_link);
1679 $this->tmpl->assign('rss_link', $rss_link);
1680 $this->tmpl->assign('count', $count);
1681 $this->tmpl->assign('width', $this->cfg->thumb_width);
1682 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1683 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1684 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1685 $this->tmpl->assign('selected_tags', $this->getSelectedTags('img'));
1686 // +1 for for smarty's selection iteration
1687 $this->tmpl->assign('thumbs', $thumbs+1);
1690 $this->tmpl->assign('images', $images);
1691 $this->tmpl->assign('img_width', $img_width);
1692 $this->tmpl->assign('img_height', $img_height);
1693 $this->tmpl->assign('img_id', $img_id);
1694 $this->tmpl->assign('img_name', $img_name);
1695 $this->tmpl->assign('img_fullname', $img_fullname);
1696 $this->tmpl->assign('img_title', $img_title);
1697 $this->tmpl->assign('img_rating', $img_rating);
1700 $result = $this->tmpl->fetch("photo_index.tpl");
1702 /* if we are returning to photo index from an photo-view,
1703 scroll the window to the last shown photo-thumbnail.
1704 after this, unset the last_photo session variable.
1706 if(isset($_SESSION['last_photo'])) {
1707 $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1708 unset($_SESSION['last_photo']);
1713 } // showPhotoIndex()
1716 * show credit template
1718 public function showCredits()
1720 $this->tmpl->assign('version', $this->cfg->version);
1721 $this->tmpl->assign('product', $this->cfg->product);
1722 $this->tmpl->assign('db_version', $this->dbver);
1723 $this->tmpl->show("credits.tpl");
1728 * create thumbnails for the requested width
1730 * this function creates image thumbnails of $orig_image
1731 * stored as $thumb_image. It will check if the image is
1732 * in a supported format, if necessary rotate the image
1733 * (based on EXIF orientation meta headers) and re-sizing.
1734 * @param string $orig_image
1735 * @param string $thumb_image
1736 * @param integer $width
1739 public function create_thumbnail($orig_image, $thumb_image, $width)
1741 if(!file_exists($orig_image)) {
1745 $mime = $this->get_mime_info($orig_image);
1747 /* check if original photo is a support image type */
1748 if(!$this->checkifImageSupported($mime))
1755 $meta = $this->get_meta_informations($orig_image);
1761 if(isset($meta['Orientation'])) {
1762 switch($meta['Orientation']) {
1763 case 1: /* top, left */
1764 /* nothing to do */ break;
1765 case 2: /* top, right */
1766 $rotate = 0; $flip_hori = true; break;
1767 case 3: /* bottom, left */
1768 $rotate = 180; break;
1769 case 4: /* bottom, right */
1770 $flip_vert = true; break;
1771 case 5: /* left side, top */
1772 $rotate = 90; $flip_vert = true; break;
1773 case 6: /* right side, top */
1774 $rotate = 90; break;
1775 case 7: /* left side, bottom */
1776 $rotate = 270; $flip_vert = true; break;
1777 case 8: /* right side, bottom */
1778 $rotate = 270; break;
1782 $src_img = @imagecreatefromjpeg($orig_image);
1788 $src_img = @imagecreatefrompng($orig_image);
1792 case 'image/x-portable-pixmap':
1794 $src_img = new Imagick($orig_image);
1795 $handler = "imagick";
1800 if(!isset($src_img) || empty($src_img)) {
1801 print "Can't load image from ". $orig_image ."\n";
1809 /* grabs the height and width */
1810 $cur_width = imagesx($src_img);
1811 $cur_height = imagesy($src_img);
1813 // If requested width is more then the actual image width,
1814 // do not generate a thumbnail, instead safe the original
1815 // as thumbnail but with lower quality. But if the image
1816 // is to heigh too, then we still have to resize it.
1817 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1818 $result = imagejpeg($src_img, $thumb_image, 75);
1819 imagedestroy($src_img);
1826 $cur_width = $src_img->getImageWidth();
1827 $cur_height = $src_img->getImageHeight();
1829 // If requested width is more then the actual image width,
1830 // do not generate a thumbnail, instead safe the original
1831 // as thumbnail but with lower quality. But if the image
1832 // is to heigh too, then we still have to resize it.
1833 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1834 $src_img->setCompressionQuality(75);
1835 $src_img->setImageFormat('jpeg');
1836 $src_img->writeImage($thumb_image);
1838 $src_img->destroy();
1845 // If the image will be rotate because EXIF orientation said so
1846 // 'virtually rotate' the image for further calculations
1847 if($rotate == 90 || $rotate == 270) {
1849 $cur_width = $cur_height;
1853 /* calculates aspect ratio */
1854 $aspect_ratio = $cur_height / $cur_width;
1857 if($aspect_ratio < 1) {
1859 $new_h = abs($new_w * $aspect_ratio);
1861 /* 'virtually' rotate the image and calculate it's ratio */
1862 $tmp_w = $cur_height;
1863 $tmp_h = $cur_width;
1864 /* now get the ratio from the 'rotated' image */
1865 $tmp_ratio = $tmp_h/$tmp_w;
1866 /* now calculate the new dimensions */
1868 $tmp_h = abs($tmp_w * $tmp_ratio);
1870 // now that we know, how high they photo should be, if it
1871 // gets rotated, use this high to scale the image
1873 $new_w = abs($new_h / $aspect_ratio);
1875 // If the image will be rotate because EXIF orientation said so
1876 // now 'virtually rotate' back the image for the image manipulation
1877 if($rotate == 90 || $rotate == 270) {
1888 /* creates new image of that size */
1889 $dst_img = imagecreatetruecolor($new_w, $new_h);
1891 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1893 /* copies resized portion of original image into new image */
1894 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1896 /* needs the image to be flipped horizontal? */
1898 $this->_debug("(FLIP)");
1899 $dst_img = $this->flipImage($dst_img, 'hori');
1901 /* needs the image to be flipped vertical? */
1903 $this->_debug("(FLIP)");
1904 $dst_img = $this->flipImage($dst_img, 'vert');
1908 $this->_debug("(ROTATE)");
1909 $dst_img = $this->rotateImage($dst_img, $rotate);
1912 /* write down new generated file */
1913 $result = imagejpeg($dst_img, $thumb_image, 75);
1915 /* free your mind */
1916 imagedestroy($dst_img);
1917 imagedestroy($src_img);
1919 if($result === false) {
1920 print "Can't write thumbnail ". $thumb_image ."\n";
1930 $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1);
1932 /* needs the image to be flipped horizontal? */
1934 $this->_debug("(FLIP)");
1935 $src_img->rotateImage(new ImagickPixel(), 90);
1936 $src_img->flipImage();
1937 $src_img->rotateImage(new ImagickPixel(), -90);
1939 /* needs the image to be flipped vertical? */
1941 $this->_debug("(FLIP)");
1942 $src_img->flipImage();
1946 $this->_debug("(ROTATE)");
1947 $src_img->rotateImage(new ImagickPixel(), $rotate);
1950 $src_img->setCompressionQuality(75);
1951 $src_img->setImageFormat('jpeg');
1953 if(!$src_img->writeImage($thumb_image)) {
1954 print "Can't write thumbnail ". $thumb_image ."\n";
1959 $src_img->destroy();
1966 } // create_thumbnail()
1969 * return all exif meta data from the file
1970 * @param string $file
1973 public function get_meta_informations($file)
1975 return exif_read_data($file);
1977 } // get_meta_informations()
1980 * create phpfspot own sqlite database
1982 * this function creates phpfspots own sqlite database
1983 * if it does not exist yet. this own is used to store
1984 * some necessary informations (md5 sum's, ...).
1986 public function check_phpfspot_db()
1988 // if the config table doesn't exist yet, create it
1989 if(!$this->cfg_db->db_check_table_exists("images")) {
1990 $this->cfg_db->db_exec("
1991 CREATE TABLE images (
1993 img_version_idx int,
1994 img_md5 varchar(32),
1995 UNIQUE(img_idx, img_version_idx)
2000 if(!$this->cfg_db->db_check_table_exists("meta")) {
2001 $this->cfg_db->db_exec("
2003 meta_key varchar(255),
2004 meta_value varchar(255)
2008 /* db_version was added with phpfspot 1.7, before changes
2009 on the phpfspot database where not necessary.
2012 $this->cfg_db->db_exec("
2014 meta_key, meta_value
2016 'phpfspot Database Version',
2017 '". $this->cfg->db_version ."'
2022 /* if version <= 2 and column img_version_idx does not exist yet */
2023 if($this->get_db_version() <= 2 &&
2024 !$this->cfg_db->db_check_column_exists("images", "img_version_idx")) {
2026 if(!$this->cfg_db->db_start_transaction())
2027 die("Can not start database transaction");
2029 $result = $this->cfg_db->db_exec("
2030 CREATE TEMPORARY TABLE images_temp (
2032 img_version_idx int,
2033 img_md5 varchar(32),
2034 UNIQUE(img_idx, img_version_idx)
2039 $this->cfg_db->db_rollback_transaction();
2040 die("Upgrade failed - transaction rollback");
2043 $result = $this->cfg_db->db_exec("
2044 INSERT INTO images_temp
2053 $this->cfg_db->db_rollback_transaction();
2054 die("Upgrade failed - transaction rollback");
2057 $result = $this->cfg_db->db_exec("
2062 $this->cfg_db->db_rollback_transaction();
2063 die("Upgrade failed - transaction rollback");
2066 $result = $this->cfg_db->db_exec("
2067 CREATE TABLE images (
2069 img_version_idx int,
2070 img_md5 varchar(32),
2071 UNIQUE(img_idx, img_version_idx)
2076 $this->cfg_db->db_rollback_transaction();
2077 die("Upgrade failed - transaction rollback");
2080 $result = $this->cfg_db->db_exec("
2087 $this->cfg_db->db_rollback_transaction();
2088 die("Upgrade failed - transaction rollback");
2091 $result = $this->cfg_db->db_exec("
2092 DROP TABLE images_temp
2096 $this->cfg_db->db_rollback_transaction();
2097 die("Upgrade failed - transaction rollback");
2100 if(!$this->cfg_db->db_commit_transaction())
2101 die("Can not commit database transaction");
2105 } // check_phpfspot_db
2108 * generates thumbnails
2110 * This function generates JPEG thumbnails from
2111 * provided F-Spot photo indize and its alternative
2114 * 1. Check if all thumbnail generations (width) are already in place and
2116 * 2. Check if the md5sum of the original file has changed
2117 * 3. Generate the thumbnails if needed
2118 * @param integer $idx
2119 * @param integer $force
2120 * @param boolean $overwrite
2122 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
2125 $versions = Array(0);
2127 $resolutions = Array(
2128 $this->cfg->thumb_width,
2129 $this->cfg->photo_width,
2130 $this->cfg->mini_width,
2134 if($alt_versions = $this->get_photo_versions($idx))
2135 $versions = array_merge($versions, $alt_versions);
2137 foreach($versions as $version) {
2139 /* get details from F-Spot's database */
2140 $details = $this->get_photo_details($idx, $version);
2142 /* calculate file MD5 sum */
2143 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2145 if(!file_exists($full_path)) {
2146 $this->_error("File ". $full_path ." does not exist");
2150 if(!is_readable($full_path)) {
2151 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
2155 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
2157 /* If Nikon NEF format, we need to treat it another way */
2158 if(isset($this->cfg->dcraw_bin) &&
2159 file_exists($this->cfg->dcraw_bin) &&
2160 is_executable($this->cfg->dcraw_bin) &&
2161 preg_match('/\.nef$/i', $details['uri'])) {
2163 $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path);
2165 /* if PPM file does not exist, let dcraw convert it from NEF */
2166 if(!file_exists($ppm_path)) {
2167 system($this->cfg->dcraw_bin ." -a ". $full_path);
2170 /* for now we handle the PPM instead of the NEF */
2171 $full_path = $ppm_path;
2175 $file_md5 = md5_file($full_path);
2178 foreach($resolutions as $resolution) {
2180 $generate_it = false;
2182 $thumb_sub_path = substr($file_md5, 0, 2);
2183 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
2185 /* if thumbnail-subdirectory does not exist yet, create it */
2186 if(!file_exists(dirname($thumb_path))) {
2187 mkdir(dirname($thumb_path), 0755);
2190 /* if the thumbnail file doesn't exist, create it */
2191 if(!file_exists($thumb_path) || $force) {
2192 $generate_it = true;
2194 elseif($file_md5 != $this->getMD5($idx, $version)) {
2195 $generate_it = true;
2198 if($generate_it || $overwrite) {
2200 $this->_debug(" ". $resolution ."px");
2201 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
2209 $this->_debug(" already exist");
2212 /* set the new/changed MD5 sum for the current photo */
2214 $this->setMD5($idx, $file_md5, $version);
2217 $this->_debug("\n");
2224 * returns stored md5 sum for a specific photo
2226 * this function queries the phpfspot database for a stored MD5
2227 * checksum of the specified photo. It also takes care of the
2228 * requested photo version - original or alternative photo.
2230 * @param integer $idx
2231 * @return string|null
2233 public function getMD5($idx, $version_idx = 0)
2235 $result = $this->cfg_db->db_query("
2241 img_idx='". $idx ."'
2243 img_version_idx='". $version_idx ."'
2249 if($img = $this->cfg_db->db_fetch_object($result))
2250 return $img['img_md5'];
2257 * set MD5 sum for the specific photo
2258 * @param integer $idx
2259 * @param string $md5
2261 private function setMD5($idx, $md5, $version_idx = 0)
2263 $result = $this->cfg_db->db_exec("
2264 INSERT OR REPLACE INTO images (
2265 img_idx, img_version_idx, img_md5
2268 '". $version_idx ."',
2276 * store current tag condition
2278 * this function stores the current tag condition
2279 * (AND or OR) in the users session variables
2280 * @param string $mode
2283 public function setTagCondition($mode)
2285 $_SESSION['tag_condition'] = $mode;
2289 } // setTagCondition()
2292 * invoke tag & date search
2294 * this function will return all matching tags and store
2295 * them in the session variable selected_tags. furthermore
2296 * it also handles the date search.
2297 * getPhotoSelection() will then only return the matching
2301 public function startSearch()
2304 if(isset($_POST['date_from']) && $this->isValidDate($_POST['date_from'])) {
2305 $date_from = $_POST['date_from'];
2307 if(isset($_POST['date_to']) && $this->isValidDate($_POST['date_to'])) {
2308 $date_to = $_POST['date_to'];
2311 /* tag-name search */
2312 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
2313 $searchfor_tag = $_POST['for_tag'];
2314 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
2317 unset($_SESSION['searchfor_tag']);
2320 /* file-name search */
2321 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
2322 $_SESSION['searchfor_name'] = $_POST['for_name'];
2325 unset($_SESSION['searchfor_name']);
2329 if(isset($_POST['rate_from']) && is_numeric($_POST['rate_from'])) {
2331 $_SESSION['rate_from'] = $_POST['rate_from'];
2333 if(isset($_POST['rate_to']) && is_numeric($_POST['rate_to'])) {
2334 $_SESSION['rate_to'] = $_POST['rate_to'];
2338 /* delete any previously set value */
2339 unset($_SESSION['rate_to'], $_SESSION['rate_from']);
2344 if(isset($date_from) && !empty($date_from))
2345 $_SESSION['from_date'] = strtotime($date_from ." 00:00:00");
2347 unset($_SESSION['from_date']);
2349 if(isset($date_to) && !empty($date_to))
2350 $_SESSION['to_date'] = strtotime($date_to ." 23:59:59");
2352 unset($_SESSION['to_date']);
2354 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
2355 /* new search, reset the current selected tags */
2356 $_SESSION['selected_tags'] = Array();
2357 foreach($this->avail_tags as $tag) {
2358 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
2359 array_push($_SESSION['selected_tags'], $tag);
2368 * updates sort order in session variable
2370 * this function is invoked by RPC and will sort the requested
2371 * sort order in the session variable.
2372 * @param string $sort_order
2375 public function updateSortOrder($order)
2377 if(isset($this->sort_orders[$order])) {
2378 $_SESSION['sort_order'] = $order;
2382 return "unkown error";
2384 } // updateSortOrder()
2387 * update photo version in session variable
2389 * this function is invoked by RPC and will set the requested
2390 * photo version in the session variable.
2391 * @param string $photo_version
2394 public function update_photo_version($photo_idx, $photo_version)
2396 if($this->is_valid_version($photo_idx, $photo_version)) {
2397 $_SESSION['current_version'] = $photo_version;
2401 return "incorrect photo version provided";
2403 } // update_photo_version()
2408 * this function rotates the image according the
2410 * @param string $img
2411 * @param integer $degress
2414 private function rotateImage($img, $degrees)
2416 if(function_exists("imagerotate")) {
2417 $img = imagerotate($img, $degrees, 0);
2419 function imagerotate($src_img, $angle)
2421 $src_x = imagesx($src_img);
2422 $src_y = imagesy($src_img);
2423 if ($angle == 180) {
2427 elseif ($src_x <= $src_y) {
2431 elseif ($src_x >= $src_y) {
2436 $rotate=imagecreatetruecolor($dest_x,$dest_y);
2437 imagealphablending($rotate, false);
2442 for ($y = 0; $y < ($src_y); $y++) {
2443 for ($x = 0; $x < ($src_x); $x++) {
2444 $color = imagecolorat($src_img, $x, $y);
2445 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
2451 for ($y = 0; $y < ($src_y); $y++) {
2452 for ($x = 0; $x < ($src_x); $x++) {
2453 $color = imagecolorat($src_img, $x, $y);
2454 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
2460 for ($y = 0; $y < ($src_y); $y++) {
2461 for ($x = 0; $x < ($src_x); $x++) {
2462 $color = imagecolorat($src_img, $x, $y);
2463 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
2477 $img = imagerotate($img, $degrees);
2486 * returns flipped image
2488 * this function will return an either horizontal or
2489 * vertical flipped truecolor image.
2490 * @param string $image
2491 * @param string $mode
2494 private function flipImage($image, $mode)
2496 $w = imagesx($image);
2497 $h = imagesy($image);
2498 $flipped = imagecreatetruecolor($w, $h);
2502 for ($y = 0; $y < $h; $y++) {
2503 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
2507 for ($x = 0; $x < $w; $x++) {
2508 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
2518 * return all assigned tags for the specified photo
2519 * @param integer $idx
2522 private function get_photo_tags($idx)
2524 $result = $this->db->db_query("
2525 SELECT t.id as id, t.name as name
2527 INNER JOIN photo_tags pt
2529 WHERE pt.photo_id='". $idx ."'
2534 while($row = $this->db->db_fetch_object($result)) {
2535 if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
2537 $tags[$row['id']] = $row['name'];
2542 } // get_photo_tags()
2545 * create on-the-fly images with text within
2546 * @param string $txt
2547 * @param string $color
2548 * @param integer $space
2549 * @param integer $font
2552 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
2554 if (strlen($color) != 6)
2557 $int = hexdec($color);
2558 $h = imagefontheight($font);
2559 $fw = imagefontwidth($font);
2560 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2561 $lines = count($txt);
2562 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2563 $bg = imagecolorallocate($im, 255, 255, 255);
2564 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2567 foreach ($txt as $text) {
2568 $x = (($w - ($fw * strlen($text))) / 2);
2569 imagestring($im, $font, $x, $y, $text, $color);
2570 $y += ($h + $space);
2573 Header("Content-type: image/png");
2576 } // showTextImage()
2579 * check if all requirements are met
2582 private function check_requirements()
2584 if(!function_exists("imagecreatefromjpeg")) {
2585 print "PHP GD library extension is missing<br />\n";
2589 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2590 print "PHP SQLite3 library extension is missing<br />\n";
2594 if($this->cfg->db_access == "pdo") {
2595 if(array_search("sqlite", PDO::getAvailableDrivers()) === false) {
2596 print "PDO SQLite3 driver is missing<br />\n";
2601 /* Check for HTML_AJAX PEAR package, lent from Horde project */
2602 ini_set('track_errors', 1);
2603 @include_once 'HTML/AJAX/Server.php';
2604 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2605 print "PEAR HTML_AJAX package is missing<br />\n";
2608 @include_once 'Calendar/Calendar.php';
2609 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2610 print "PEAR Calendar package is missing<br />\n";
2613 @include_once 'Console/Getopt.php';
2614 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2615 print "PEAR Console_Getopt package is missing<br />\n";
2618 @include_once 'Date.php';
2619 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2620 print "PEAR Date package is missing<br />\n";
2623 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2624 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2625 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2628 ini_restore('track_errors');
2635 } // check_requirements()
2637 private function _debug($text)
2639 if(isset($this->fromcmd)) {
2646 * check if specified MIME type is supported
2647 * @param string $mime
2650 public function checkifImageSupported($mime)
2652 $supported_types = Array(
2655 "image/x-portable-pixmap",
2659 if(in_array($mime, $supported_types))
2664 } // checkifImageSupported()
2668 * @param string $text
2670 public function _error($text)
2672 switch($this->cfg->logging) {
2675 if(isset($this->fromcmd))
2678 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2679 print $text ."<br />\n";
2686 error_log($text, 3, $this->cfg->log_file);
2690 $this->runtime_error = true;
2695 * get calendar input-text fields
2697 * this function returns a text-field used for the data selection.
2698 * Either it will be filled with the current date or, if available,
2699 * filled with the date user entered previously.
2701 * @param string $mode
2704 private function get_date_text_field($mode)
2706 $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2708 $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2710 $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2712 $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\"";
2713 if(!isset($_SESSION[$mode .'_date']))
2714 $output.= " disabled=\"disabled\"";
2719 } // get_date_text_field()
2722 * output calendar matrix
2723 * @param integer $year
2724 * @param integer $month
2725 * @param integer $day
2727 public function get_calendar_matrix($userdate)
2729 if(($userdate = strtotime($userdate)) === false) {
2736 $date->setDate($userdate);
2738 $year = $date->getYear();
2739 $month = $date->getMonth();
2740 $day = $date->getDay();
2747 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2748 require_once CALENDAR_ROOT.'Day.php';
2751 $month_cal = new Calendar_Month_Weekdays($year,$month);
2754 $prevStamp = $month_cal->prevMonth(true);
2755 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2756 $nextStamp = $month_cal->nextMonth(true);
2757 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2759 $selectedDays = array (
2760 new Calendar_Day($year,$month,$day),
2763 // Build the days in the month
2764 $month_cal->build($selectedDays);
2766 $this->tmpl->assign('current_month', date('F Y',$month_cal->getTimeStamp()));
2767 $this->tmpl->assign('prev_month', $prev);
2768 $this->tmpl->assign('next_month', $next);
2770 while ( $day = $month_cal->fetch() ) {
2772 if(!isset($matrix[$rows]))
2773 $matrix[$rows] = Array();
2777 $dayStamp = $day->thisDay(true);
2778 $link = "javascript:setCalendarDate('"
2779 . date('Y',$dayStamp)
2781 . date('m',$dayStamp)
2783 . date('d',$dayStamp)
2786 // isFirst() to find start of week
2787 if ( $day->isFirst() )
2790 if ( $day->isSelected() ) {
2791 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2792 } else if ( $day->isEmpty() ) {
2793 $string.= "<td> </td>\n";
2795 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2798 // isLast() to find end of week
2799 if ( $day->isLast() )
2800 $string.= "</tr>\n";
2802 $matrix[$rows][$cols] = $string;
2812 $this->tmpl->assign('matrix', $matrix);
2813 $this->tmpl->assign('rows', $rows);
2814 $this->tmpl->show("calendar.tpl");
2816 } // get_calendar_matrix()
2819 * output export page
2820 * @param string $mode
2822 public function getExport($mode)
2824 $pictures = $this->getPhotoSelection();
2825 $current_tags = $this->getCurrentTags();
2827 foreach($pictures as $picture) {
2829 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2830 if($current_tags != "") {
2831 $orig_url.= "&tags=". $current_tags;
2833 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2834 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2837 if($this->is_user_friendly_url()) {
2838 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2841 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2847 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2848 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2852 // "[%pictureurl% %thumbnailurl%]"
2853 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2856 case 'MoinMoinList':
2857 // " * [%pictureurl% %thumbnailurl%]"
2858 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2869 public function getRSSFeed()
2871 Header("Content-type: text/xml; charset=utf-8");
2872 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2875 xmlns:media="http://search.yahoo.com/mrss/"
2876 xmlns:dc="http://purl.org/dc/elements/1.1/"
2879 <title>phpfspot</title>
2880 <description>phpfspot RSS feed</description>
2881 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2882 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2883 <generator>phpfspot</generator>
2886 $pictures = $this->getPhotoSelection();
2887 $current_tags = $this->getCurrentTags();
2889 foreach($pictures as $picture) {
2891 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2892 if($current_tags != "") {
2893 $orig_url.= "&tags=". $current_tags;
2895 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2896 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2899 $details = $this->get_photo_details($picture);
2901 if($this->is_user_friendly_url()) {
2902 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2905 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2908 $thumb_html = htmlspecialchars("
2909 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2911 ". $details['description']);
2913 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2915 /* get EXIF information if JPEG */
2916 if(isset($details['mime']) && $details['mime'] == "image/jpeg") {
2917 $meta = $this->get_meta_informations($orig_path);
2922 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2923 <link><?php print htmlspecialchars($orig_url); ?></link>
2924 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2925 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken>
2927 <?php print $thumb_html; ?>
2929 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate>
2944 * get all selected tags
2946 * This function will return all selected tags as one string, seperated
2950 private function getCurrentTags()
2953 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2954 foreach($_SESSION['selected_tags'] as $tag)
2955 $current_tags.= $tag .",";
2956 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2958 return $current_tags;
2960 } // getCurrentTags()
2963 * return the current photo
2965 public function get_current_photo()
2967 if(isset($_SESSION['current_photo'])) {
2968 return $_SESSION['current_photo'];
2973 } // get_current_photo()
2976 * current selected photo version
2978 * this function returns the current selected photo version
2979 * from the session variables.
2983 public function get_current_version()
2985 /* if current version is set, return it, if the photo really has that version */
2986 if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version']))
2987 return $_SESSION['current_version'];
2991 } // get_current_version()
2994 * returns latest available photo version
2996 * this function returns the latested available version
2997 * for the requested photo.
3001 public function get_latest_version($photo_idx)
3003 /* try to get the lasted version for the current photo */
3004 if($versions = $this->get_photo_versions($photo_idx))
3005 return $versions[count($versions)-1];
3007 /* if no alternative version were found, return original version */
3010 } // get_current_version()
3013 * tells the client browser what to do
3015 * this function is getting called via AJAX by the
3016 * client browsers. it will tell them what they have
3017 * to do next. This is necessary for directly jumping
3018 * into photo index or single photo view when the are
3019 * requested with specific URLs
3022 public function whatToDo()
3024 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
3026 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
3027 return "showpi_tags";
3029 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
3036 * return the current process-user
3039 private function getuid()
3041 if($uid = posix_getuid()) {
3042 if($user = posix_getpwuid($uid)) {
3043 return $user['name'];
3052 * photo version select list
3054 * this function returns a HTML select list (drop down)
3055 * to select a alternative photo version of the original photo.
3057 * @param array $params
3058 * @param smarty $smarty
3061 public function smarty_photo_version_select_list($params, &$smarty)
3063 if(!isset($params['photo']) || !isset($params['current']))
3066 $output = "<option value=\"0\">Original</option>";
3067 $versions = $this->get_photo_versions($params['photo']);
3069 foreach($versions as $version) {
3071 $output.= "<option value=\"". $version ."\"";
3072 if($version == $params['current']) {
3073 $output.= " selected=\"selected\"";
3075 $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>";
3080 } // smarty_photo_version_select_list()
3083 * returns a select-dropdown box to select photo index sort parameters
3084 * @param array $params
3085 * @param smarty $smarty
3088 public function smarty_sort_select_list($params, &$smarty)
3092 foreach($this->sort_orders as $key => $value) {
3093 $output.= "<option value=\"". $key ."\"";
3094 if($key == $_SESSION['sort_order']) {
3095 $output.= " selected=\"selected\"";
3097 $output.= ">". $value ."</option>";
3102 } // smarty_sort_select_list()
3105 * returns the currently selected sort order
3108 private function get_sort_order()
3110 switch($_SESSION['sort_order']) {
3112 return " ORDER BY p.time ASC";
3115 return " ORDER BY p.time DESC";
3118 if($this->dbver < 9) {
3119 return " ORDER BY p.name ASC";
3122 return " ORDER BY basename(p.uri) ASC";
3126 if($this->dbver < 9) {
3127 return " ORDER BY p.name DESC";
3130 return " ORDER BY basename(p.uri) DESC";
3134 return " ORDER BY t.name ASC ,p.time ASC";
3137 return " ORDER BY t.name DESC ,p.time ASC";
3140 return " ORDER BY p.rating ASC, t.name ASC";
3143 return " ORDER BY p.rating DESC, t.name ASC";
3147 } // get_sort_order()
3150 * return the next to be shown slide show image
3152 * this function returns the URL of the next image
3153 * in the slideshow sequence.
3156 public function getNextSlideShowImage()
3158 $all_photos = $this->getPhotoSelection();
3160 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
3161 $_SESSION['slideshow_img'] = 0;
3163 $_SESSION['slideshow_img']++;
3165 if($this->is_user_friendly_url()) {
3166 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3169 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3171 } // getNextSlideShowImage()
3174 * return the previous to be shown slide show image
3176 * this function returns the URL of the previous image
3177 * in the slideshow sequence.
3180 public function getPrevSlideShowImage()
3182 $all_photos = $this->getPhotoSelection();
3184 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
3185 $_SESSION['slideshow_img'] = 0;
3187 $_SESSION['slideshow_img']--;
3189 if($this->is_user_friendly_url()) {
3190 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3193 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3195 } // getPrevSlideShowImage()
3197 public function resetSlideShow()
3199 if(isset($_SESSION['slideshow_img']))
3200 unset($_SESSION['slideshow_img']);
3202 } // resetSlideShow()
3207 * this function will get all photos from the fspot
3208 * database and randomly return ONE entry
3210 * saddly there is yet no sqlite3 function which returns
3211 * the bulk result in array, so we have to fill up our
3215 public function get_random_photo()
3224 /* if show_tags is set, only return details for photos which
3225 are specified to be shown
3227 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3229 INNER JOIN photo_tags pt
3234 t.name IN ('".implode("','",$this->cfg->show_tags)."')";
3237 $result = $this->db->db_query($query_str);
3239 while($row = $this->db->db_fetch_object($result)) {
3240 array_push($all, $row['id']);
3243 return $all[array_rand($all)];
3245 } // get_random_photo()
3248 * get random photo tag photo
3250 * this function will get all photos tagged with the requested
3251 * tag from the fspot database and randomly return ONE entry
3253 * saddly there is yet no sqlite3 function which returns
3254 * the bulk result in array, so we have to fill up our
3258 public function get_random_tag_photo($tagidx)
3262 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3265 DISTINCT pt1.photo_id as id
3268 INNER JOIN photo_tags
3269 pt2 ON pt1.photo_id=pt2.photo_id
3275 pt1.tag_id LIKE '". $tagidx ."'
3277 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
3279 t1.sort_priority ASC";
3287 INNER JOIN photo_tags pt
3290 pt.tag_id LIKE '". $tagidx ."'";
3293 $result = $this->db->db_query($query_str);
3295 while($row = $this->db->db_fetch_object($result)) {
3296 array_push($all, $row['id']);
3299 return $all[array_rand($all)];
3301 } // get_random_tag_photo()
3304 * validates provided date
3306 * this function validates if the provided date
3307 * contains a valid date and will return true
3309 * @param string $date_str
3312 public function isValidDate($date_str)
3314 $timestamp = strtotime($date_str);
3316 if(is_numeric($timestamp))
3324 * timestamp to string conversion
3325 * @param integer $timestamp
3328 private function ts2str($timestamp)
3330 if(!empty($timestamp) && is_numeric($timestamp))
3331 return strftime("%Y-%m-%d", $timestamp);
3336 * extract tag-names from $_GET['tags']
3337 * @param string $tags_str
3340 private function extractTags($tags_str)
3342 $not_validated = split(',', $tags_str);
3343 $validated = array();
3345 foreach($not_validated as $tag) {
3346 if(is_numeric($tag))
3347 array_push($validated, $tag);
3355 * returns the full path to a thumbnail
3356 * @param integer $width
3357 * @param integer $photo
3360 public function get_thumb_path($width, $photo_idx, $version_idx)
3362 $md5 = $this->getMD5($photo_idx, $version_idx);
3363 $sub_path = substr($md5, 0, 2);
3364 return $this->cfg->thumb_path
3372 } // get_thumb_path()
3375 * returns server's virtual host name
3378 private function get_server_name()
3380 return $_SERVER['SERVER_NAME'];
3381 } // get_server_name()
3384 * returns type of webprotocol which is currently used
3387 private function get_web_protocol()
3389 if(!isset($_SERVER['HTTPS']))
3393 } // get_web_protocol()
3396 * return url to this phpfspot installation
3399 private function get_phpfspot_url()
3401 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3403 } // get_phpfspot_url()
3406 * returns the number of photos which are tagged with $tag_id
3407 * @param integer $tag_id
3410 public function get_num_photos($tag_id)
3412 if($result = $this->db->db_fetchSingleRow("
3413 SELECT count(*) as number
3416 tag_id LIKE '". $tag_id ."'")) {
3418 return $result['number'];
3424 } // get_num_photos()
3427 * check file exists and is readable
3429 * returns true, if everything is ok, otherwise false
3430 * if $silent is not set, this function will output and
3432 * @param string $file
3433 * @param boolean $silent
3436 private function check_readable($file, $silent = null)
3438 if(!file_exists($file)) {
3440 print "File \"". $file ."\" does not exist.\n";
3444 if(!is_readable($file)) {
3446 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3452 } // check_readable()
3455 * check if all needed indices are present
3457 * this function checks, if some needed indices are already
3458 * present, or if not, create them on the fly. they are
3459 * necessary to speed up some queries like that one look for
3460 * all tags, when show_tags is specified in the configuration.
3462 private function checkDbIndices()
3464 $result = $this->db->db_exec("
3465 CREATE INDEX IF NOT EXISTS
3472 } // checkDbIndices()
3475 * retrive F-Spot database version
3477 * this function will return the F-Spot database version number
3478 * It is stored within the sqlite3 database in the table meta
3479 * @return string|null
3481 public function getFspotDBVersion()
3483 if($result = $this->db->db_fetchSingleRow("
3484 SELECT data as version
3487 name LIKE 'F-Spot Database Version'
3489 return $result['version'];
3493 } // getFspotDBVersion()
3496 * parse the provided URI and will returned the requested chunk
3497 * @param string $uri
3498 * @param string $mode
3501 public function parse_uri($uri, $mode)
3503 if(($components = parse_url($uri)) !== false) {
3507 return basename($components['path']);
3510 return dirname($components['path']);
3513 return $components['path'];
3523 * validate config options
3525 * this function checks if all necessary configuration options are
3526 * specified and set.
3529 private function check_config_options()
3531 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
3532 $this->_error("Please set \$page_title in phpfspot_cfg");
3534 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
3535 $this->_error("Please set \$base_path in phpfspot_cfg");
3537 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
3538 $this->_error("Please set \$web_path in phpfspot_cfg");
3540 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
3541 $this->_error("Please set \$thumb_path in phpfspot_cfg");
3543 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
3544 $this->_error("Please set \$smarty_path in phpfspot_cfg");
3546 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
3547 $this->_error("Please set \$fspot_db in phpfspot_cfg");
3549 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
3550 $this->_error("Please set \$db_access in phpfspot_cfg");
3552 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
3553 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
3555 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
3556 $this->_error("Please set \$thumb_width in phpfspot_cfg");
3558 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
3559 $this->_error("Please set \$thumb_height in phpfspot_cfg");
3561 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
3562 $this->_error("Please set \$photo_width in phpfspot_cfg");
3564 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
3565 $this->_error("Please set \$mini_width in phpfspot_cfg");
3567 if(!isset($this->cfg->thumbs_per_page))
3568 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
3570 if(!isset($this->cfg->enable_replace_path))
3571 $this->_error("Please set \$enable_replace_path in phpfspot_cfg");
3573 if($this->cfg->enable_replace_path == true) {
3574 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
3575 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
3577 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
3578 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
3581 if(!isset($this->cfg->hide_tags))
3582 $this->_error("Please set \$hide_tags in phpfspot_cfg");
3584 if(!isset($this->cfg->theme_name))
3585 $this->_error("Please set \$theme_name in phpfspot_cfg");
3587 if(!isset($this->cfg->logging))
3588 $this->_error("Please set \$logging in phpfspot_cfg");
3590 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3592 if(!isset($this->cfg->log_file))
3593 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3595 if(!is_writeable($this->cfg->log_file))
3596 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3600 /* remove trailing slash, if set */
3601 if($this->cfg->web_path == "/")
3602 $this->cfg->web_path = "";
3603 elseif(preg_match('/\/$/', $this->cfg->web_path))
3604 $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3606 return $this->runtime_error;
3608 } // check_config_options()
3611 * cleanup phpfspot own database
3613 * When photos are getting delete from F-Spot, there will remain
3614 * remain some residues in phpfspot own database. This function
3615 * will try to wipe them out.
3617 public function cleanup_phpfspot_db()
3619 $to_delete = Array();
3621 $result = $this->cfg_db->db_query("
3622 SELECT img_idx as img_idx
3624 ORDER BY img_idx ASC
3627 while($row = $this->cfg_db->db_fetch_object($result)) {
3628 if(!$this->db->db_fetchSingleRow("
3631 WHERE id='". $row['img_idx'] ."'")) {
3633 array_push($to_delete, $row['img_idx'], ',');
3637 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3639 $this->cfg_db->db_exec("
3641 WHERE img_idx IN (". implode($to_delete) .")
3644 } // cleanup_phpfspot_db()
3647 * return first image of the page, the $current photo
3650 * this function is used to find out the first photo of the
3651 * current page, in which the $current photo lies. this is
3652 * used to display the correct photo, when calling showPhotoIndex()
3654 * @param integer $current
3655 * @param integer $max
3658 private function getCurrentPage($current, $max)
3660 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3661 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3662 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3668 } // getCurrentPage()
3673 * this function tries to find out the correct mime-type
3674 * for the provided file.
3675 * @param string $file
3678 public function get_mime_info($file)
3680 $details = getimagesize($file);
3682 /* if getimagesize() returns empty, try at least to find out the
3685 if(empty($details) && function_exists('mime_content_type')) {
3687 // mime_content_type is marked as deprecated in the documentation,
3688 // but is it really necessary to force users to install a PECL
3690 $details['mime'] = mime_content_type($file);
3693 return $details['mime'];
3695 } // get_mime_info()
3698 * return tag-name by tag-idx
3700 * this function returns the tag-name for the requested
3701 * tag specified by tag-idx.
3702 * @param integer $idx
3705 public function get_tag_name($idx)
3707 if($result = $this->db->db_fetchSingleRow("
3711 id LIKE '". $idx ."'")) {
3713 return $result['name'];
3722 * parse user friendly url which got rewritten by the websever
3723 * @param string $request_uri
3726 private function parse_user_friendly_url($request_uri)
3728 if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3730 $options = explode('/', $request_uri);
3732 switch($options[1]) {
3734 if(is_numeric($options[2])) {
3735 $this->session_cleanup();
3736 //unset($_SESSION['start_action']);
3737 //unset($_SESSION['selected_tags']);
3738 $_GET['mode'] = 'showp';
3739 return $this->showPhoto($options[2]);
3743 if(is_numeric($options[2])) {
3746 if(isset($options[3]) && is_numeric($options[3]))
3747 $width = $options[3];
3748 if(isset($options[4]) && is_numeric($options[4]))
3749 $version = $options[4];
3750 require_once "phpfspot_img.php";
3751 $img = new PHPFSPOT_IMG;
3752 $img->showImg($options[2], $width, $version);
3757 if(is_numeric($options[2])) {
3758 $this->session_cleanup();
3759 $_GET['tags'] = $options[2];
3760 $_SESSION['selected_tags'] = Array($options[2]);
3761 if(isset($options[3]) && is_numeric($options[3]))
3762 $_SESSION['begin_with'] = $options[3];
3763 return $this->showPhotoIndex();
3769 } // parse_user_friendly_url()
3772 * check if user-friendly-urls are enabled
3774 * this function will return true, if the config option
3775 * $user_friendly_url has been set. Otherwise false.
3778 private function is_user_friendly_url()
3780 if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3785 } // is_user_friendly_url()
3790 * this function will cleanup user's session information
3792 private function session_cleanup()
3794 unset($_SESSION['begin_with']);
3795 $this->resetDateSearch();
3796 $this->resetPhotoView();
3797 $this->resetTagSearch();
3798 $this->resetNameSearch();
3799 $this->resetDateSearch();
3802 } // session_cleanup()
3805 * get database version
3807 * this function queries the meta table
3808 * and returns the current database version.
3812 public function get_db_version()
3814 if($row = $this->cfg_db->db_fetchSingleRow("
3815 SELECT meta_value as meta_value
3819 meta_key LIKE 'phpfspot Database Version'
3822 return $row['meta_value'];
3828 } // get_db_version()
3831 * get photo versions
3833 * this function returns an array of all available
3834 * alterntaive versions of the provided photo id.
3835 * has alternative photo versions available
3840 public function get_photo_versions($idx)
3842 $versions = Array();
3844 $result = $this->db->db_query("
3850 photo_id LIKE '". $idx ."'");
3852 while($row = $this->cfg_db->db_fetch_object($result)) {
3853 array_push($versions, $row['version_id']);
3858 } // get_photo_versions()
3861 * check for invalid version of photo
3863 * this function validates the provided photo-id and version-id
3865 * @param int $photo_idx
3866 * @param int $version_idx
3869 public function is_valid_version($photo_idx, $version_idx)
3871 /* the original version is always valid */
3872 if($version_idx == 0)
3875 if($versions = $this->get_photo_versions($photo_idx)) {
3876 if(in_array($version_idx, $versions))
3882 } // is_valid_version()
3885 * get photo version name
3887 * this function returns the name of the version
3888 * identified by the photo-id and version-id.
3890 * @param int $photo_idx
3891 * @param int $version_idx
3894 public function get_photo_version_name($photo_idx, $version_idx)
3896 if($row = $this->db->db_fetchSingleRow("
3902 photo_id LIKE '". $photo_idx ."'
3904 version_id LIKE '". $version_idx ."'")) {
3906 return $row['name'];
3912 } // get_photo_version_name()
3916 public function is_valid_width($image_width)
3918 if(in_array($image_width,
3919 Array($this->cfg->thumb_width,
3920 $this->cfg->photo_width,
3921 $this->cfg->mini_width,
3928 } // is_valid_width()