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))
557 /* before F-Spot db version 9 there was no uri column but
558 seperated fields for directory_path and name (= filename).
560 if($this->dbver < 9) {
561 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
563 /* starting with dbversion >= 17 we need to rawurldecode() uri */
564 elseif($this->dbver >= 17) {
565 $row['uri'] = rawurldecode($row['uri']);
568 /* if version-idx has not yet been set, get the latest photo version */
569 if(!isset($version_idx) || !$this->is_valid_version($idx, $version_idx))
570 $version_idx = $this->get_latest_version($idx);
572 /* if an alternative version has been requested. But we
573 support this only for F-Spot database versions from
576 if($version_idx > 0 && $this->dbver >= 9) {
577 if ($this->dbver < 17) {
578 /* check for alternative versions */
579 if($version = $this->db->db_fetchSingleRow("
581 version_id, name, uri
585 photo_id LIKE '". $idx ."'
587 version_id LIKE '". $version_idx ."'")) {
589 $row['name'] = $version['name'];
590 $row['uri'] = $version['uri'];
594 /* path & filename now splited in base_uri & filename */
595 if($version = $this->db->db_fetchSingleRow("
599 base_uri || '/'||filename as uri
603 photo_id LIKE '". $idx ."'
605 version_id LIKE '". $version_idx ."'")) {
607 $row['name'] = $version['name'];
608 $row['uri'] = rawurldecode($version['uri']);
615 } // get_photo_details()
618 * returns aligned photo names
620 * this function returns aligned (length) names for a specific photo.
621 * If the length of the name exceeds $limit the name will bei
624 * @param integer $idx
625 * @param integer $limit
626 * @return string|null
628 public function getPhotoName($idx, $limit = 0)
630 if($details = $this->get_photo_details($idx)) {
631 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
632 $name = $this->shrink_text($long_name, $limit);
642 * get photo rating level
644 * this function will return the integer-based rating level of a
645 * photo. This can only be done, if the F-Spot database is at a
646 * specific version. If rating value can not be found, zero will
647 * be returned indicating no rating value is available.
652 public function get_photo_rating($idx)
654 if($detail = $this->get_photo_details($idx)) {
655 if(isset($detail['rating']))
656 return $detail['rating'];
661 } // get_photo_rating()
664 * get rate-search bars
666 * this function will return the rating-bars for the search field.
670 public function get_rate_search()
674 for($i = 1; $i <= 5; $i++) {
676 $bar.= "<img id=\"rate_from_". $i ."\" src=\"";
678 if(isset($_SESSION['rate_from']) && $i <= $_SESSION['rate_from'])
679 $bar.= $this->cfg->web_path ."/resources/star.png";
681 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
684 onmouseover=\"show_rate('from', ". $i .");\"
685 onmouseout=\"reset_rate('from');\"
686 onclick=\"set_rate('from', ". $i .")\" />";
691 for($i = 1; $i <= 5; $i++) {
693 $bar.= "<img id=\"rate_to_". $i ."\" src=\"";
695 if(isset($_SESSION['rate_to']) && $i <= $_SESSION['rate_to'])
696 $bar.= $this->cfg->web_path ."/resources/star.png";
698 $bar.= $this->cfg->web_path ."/resources/empty_rate.png";
701 onmouseover=\"show_rate('to', ". $i .");\"
702 onmouseout=\"reset_rate('to');\"
703 onclick=\"set_rate('to', ". $i .");\" />";
708 } // get_rate_search()
711 * shrink text according provided limit
713 * If the length of the name exceeds $limit, text will be shortend
714 * and inner content will be replaced with "...".
717 * @param integer $limit
720 private function shrink_text($text, $limit)
722 if($limit != 0 && strlen($text) > $limit) {
723 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
731 * translate f-spoth photo path
733 * as the full-qualified path recorded in the f-spot database
734 * is usally not the same as on the webserver, this function
735 * will replace the path with that one specified in the cfg
736 * @param string $path
739 public function translate_path($path)
741 if($this->cfg->enable_replace_path == true)
743 $this->cfg->path_replace_from,
744 $this->cfg->path_replace_to, $path);
751 * control HTML ouput for a single photo
753 * this function provides all the necessary information
754 * for the single photo template.
755 * @param integer photo
757 public function showPhoto($photo)
759 /* get all photos from the current photo selection */
760 $all_photos = $this->getPhotoSelection();
761 $count = count($all_photos);
763 for($i = 0; $i < $count; $i++) {
765 // $get_next will be set, when the photo which has to
766 // be displayed has been found - this means that the
767 // next available is in fact the NEXT image (for the
769 if(isset($get_next)) {
770 $next_img = $all_photos[$i];
774 /* the next photo is our NEXT photo */
775 if($all_photos[$i] == $photo) {
779 $previous_img = $all_photos[$i];
782 if($photo == $all_photos[$i]) {
787 $details = $this->get_photo_details($photo);
794 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
796 /* if current version is already set, use it */
797 if($this->get_current_version() !== false)
798 $version = $this->get_current_version();
800 /* if version not set yet, we assume to display the latest version */
801 if(!isset($version) || !$this->is_valid_version($photo, $version))
802 $version = $this->get_latest_version($photo);
804 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
806 if(!file_exists($orig_path)) {
807 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
811 if(!is_readable($orig_path)) {
812 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
816 /* If the thumbnail doesn't exist yet, try to create it */
817 if(!file_exists($thumb_path)) {
818 $this->gen_thumb($photo, true);
819 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version);
822 /* get mime-type, height and width from the original photo */
823 $info = getimagesize($orig_path);
825 /* get EXIF information if JPEG */
826 if(isset($info['mime']) && $info['mime'] == "image/jpeg") {
827 $meta = $this->get_meta_informations($orig_path);
830 /* If EXIF data are available, use them */
831 if(isset($meta['ExifImageWidth'])) {
832 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
834 $meta_res = $info[0] ."x". $info[1];
837 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
838 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
840 $extern_link = "index.php?mode=showp&id=". $photo;
841 $current_tags = $this->getCurrentTags();
842 if($current_tags != "") {
843 $extern_link.= "&tags=". $current_tags;
845 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
846 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
849 $this->tmpl->assign('extern_link', $extern_link);
851 if(!file_exists($thumb_path)) {
852 $this->_error("Can't open file ". $thumb_path ."\n");
856 $info_thumb = getimagesize($thumb_path);
858 $this->tmpl->assign('description', $details['description']);
859 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
860 $this->tmpl->assign('image_rating', $this->get_photo_rating($photo));
862 $this->tmpl->assign('width', $info_thumb[0]);
863 $this->tmpl->assign('height', $info_thumb[1]);
864 $this->tmpl->assign('ExifMadeOn', strftime("%a %x %X", $details['time']));
865 $this->tmpl->assign('ExifMadeWith', $meta_make);
866 $this->tmpl->assign('ExifOrigResolution', $meta_res);
867 $this->tmpl->assign('ExifFileSize', $meta_size);
869 if($this->is_user_friendly_url()) {
870 $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width .'/'. $version);
871 $this->tmpl->assign('image_url_full', '/photo/'. $photo);
874 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width ."&version=". $version);
875 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
878 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
880 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
881 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
882 $this->tmpl->assign('current_img', $photo);
884 if(isset($previous_img)) {
885 $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");");
886 $this->tmpl->assign('prev_img', $previous_img);
889 if(isset($next_img)) {
890 $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");");
891 $this->tmpl->assign('next_img', $next_img);
894 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
895 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
896 $this->tmpl->assign('photo_number', $i);
897 $this->tmpl->assign('photo_count', count($all_photos));
898 $this->tmpl->assign('photo', $photo);
899 $this->tmpl->assign('version', $version);
901 /* if the photo as alternative versions, set a flag for the template */
902 if($this->get_photo_versions($photo))
903 $this->tmpl->assign('has_versions', true);
905 $this->tmpl->register_function("photo_version_select_list", array(&$this, "smarty_photo_version_select_list"), false);
907 return $this->tmpl->fetch("single_photo.tpl");
912 * all available tags and tag cloud
914 * this function outputs all available tags (time ordered)
915 * and in addition output them as tag cloud (tags which have
916 * many photos will appears more then others)
918 public function getAvailableTags()
920 /* retrive tags from database */
925 $result = $this->db->db_query("
926 SELECT tag_id as id, count(tag_id) as quantity
936 while($row = $this->db->db_fetch_object($result)) {
937 $tags[$row['id']] = $row['quantity'];
940 // change these font sizes if you will
941 $max_size = 125; // max font size in %
942 $min_size = 75; // min font size in %
945 $max_sat = hexdec('cc');
946 $min_sat = hexdec('44');
948 // get the largest and smallest array values
949 $max_qty = max(array_values($tags));
950 $min_qty = min(array_values($tags));
952 // find the range of values
953 $spread = $max_qty - $min_qty;
954 if (0 == $spread) { // we don't want to divide by zero
958 // determine the font-size increment
959 // this is the increase per tag quantity (times used)
960 $step = ($max_size - $min_size)/($spread);
961 $step_sat = ($max_sat - $min_sat)/($spread);
963 // loop through our tag array
964 foreach ($tags as $key => $value) {
966 /* has the currently processed tag already been added to
967 the selected tag list? if so, ignore it here...
969 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
972 // calculate CSS font-size
973 // find the $value in excess of $min_qty
974 // multiply by the font-size increment ($size)
975 // and add the $min_size set above
976 $size = $min_size + (($value - $min_qty) * $step);
977 // uncomment if you want sizes in whole %:
980 $color = $min_sat + ($value - $min_qty) * $step_sat;
986 if(isset($this->tags[$key])) {
987 if($this->is_user_friendly_url()) {
988 $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\"
989 onclick=\"Tags('add', ". $key ."); return false;\"
991 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
992 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
995 $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\"
996 onclick=\"Tags('add', ". $key ."); return false;\"
998 style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\"
999 title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, ";
1004 $output = substr($output, 0, strlen($output)-2);
1007 } // getAvailableTags()
1010 * output all selected tags
1012 * this function output all tags which have been selected
1013 * by the user. the selected tags are stored in the
1014 * session-variable $_SESSION['selected_tags']
1017 public function getSelectedTags($type = 'link')
1019 /* retrive tags from database */
1024 foreach($this->avail_tags as $tag)
1026 // return all selected tags
1027 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
1032 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
1036 <div class=\"tagresulttag\">
1037 <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\">
1038 <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" />
1048 $output = substr($output, 0, strlen($output)-2);
1052 return "no tags selected";
1055 } // getSelectedTags()
1058 * add tag to users session variable
1060 * this function will add the specified to users current
1061 * tag selection. if a date search has been made before
1062 * it will be now cleared
1065 public function addTag($tag)
1067 if(!isset($_SESSION['selected_tags']))
1068 $_SESSION['selected_tags'] = Array();
1070 if(isset($_SESSION['searchfor_tag']))
1071 unset($_SESSION['searchfor_tag']);
1073 // has the user requested to hide this tag, and still someone,
1074 // somehow tries to add it, don't allow this.
1075 if(!isset($this->cfg->hide_tags) &&
1076 in_array($this->get_tag_name($tag), $this->cfg->hide_tags))
1079 if(!in_array($tag, $_SESSION['selected_tags']))
1080 array_push($_SESSION['selected_tags'], $tag);
1087 * remove tag to users session variable
1089 * this function removes the specified tag from
1090 * users current tag selection
1091 * @param string $tag
1094 public function delTag($tag)
1096 if(isset($_SESSION['searchfor_tag']))
1097 unset($_SESSION['searchfor_tag']);
1099 if(isset($_SESSION['selected_tags'])) {
1100 $key = array_search($tag, $_SESSION['selected_tags']);
1101 unset($_SESSION['selected_tags'][$key]);
1102 sort($_SESSION['selected_tags']);
1110 * reset tag selection
1112 * if there is any tag selection, it will be
1115 public function resetTags()
1117 if(isset($_SESSION['selected_tags']))
1118 unset($_SESSION['selected_tags']);
1123 * returns the value for the autocomplete tag-search
1126 public function get_xml_tag_list()
1128 if(!isset($_GET['search']) || !is_string($_GET['search']))
1129 $_GET['search'] = '';
1134 /* retrive tags from database */
1137 $matched_tags = Array();
1139 header("Content-Type: text/xml");
1141 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
1142 $string.= "<results>\n";
1144 foreach($this->avail_tags as $tag)
1146 if(!empty($_GET['search']) &&
1147 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
1148 count($matched_tags) < $length) {
1150 $count = $this->get_num_photos($tag);
1153 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
1156 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
1162 /* if we have collected enough items, break out */
1163 if(count($matched_tags) >= $length)
1167 $string.= "</results>\n";
1171 } // get_xml_tag_list()
1175 * reset single photo
1177 * if a specific photo was requested (external link)
1178 * unset the session variable now
1180 public function resetPhotoView()
1182 if(isset($_SESSION['current_photo']))
1183 unset($_SESSION['current_photo']);
1185 if(isset($_SESSION['current_version']))
1186 unset($_SESSION['current_version']);
1188 } // resetPhotoView();
1193 * if any tag search has taken place, reset it now
1195 public function resetTagSearch()
1197 if(isset($_SESSION['searchfor_tag']))
1198 unset($_SESSION['searchfor_tag']);
1200 } // resetTagSearch()
1205 * if any name search has taken place, reset it now
1207 public function resetNameSearch()
1209 if(isset($_SESSION['searchfor_name']))
1210 unset($_SESSION['searchfor_name']);
1212 } // resetNameSearch()
1217 * if any date search has taken place, reset it now.
1219 public function resetDateSearch()
1221 if(isset($_SESSION['from_date']))
1222 unset($_SESSION['from_date']);
1223 if(isset($_SESSION['to_date']))
1224 unset($_SESSION['to_date']);
1226 } // resetDateSearch();
1231 * if any rate search has taken place, reset it now.
1233 public function resetRateSearch()
1235 if(isset($_SESSION['rate_from']))
1236 unset($_SESSION['rate_from']);
1237 if(isset($_SESSION['rate_to']))
1238 unset($_SESSION['rate_to']);
1240 } // resetRateSearch();
1243 * return all photo according selection
1245 * this function returns all photos based on
1246 * the tag-selection, tag- or date-search.
1247 * the tag-search also has to take care of AND
1248 * and OR conjunctions
1251 public function getPhotoSelection()
1253 $matched_photos = Array();
1254 $additional_where_cond = "";
1256 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1257 $from_date = $_SESSION['from_date'];
1258 $to_date = $_SESSION['to_date'];
1259 $additional_where_cond.= "
1260 p.time>='". $from_date ."'
1262 p.time<='". $to_date ."'
1266 if(isset($_SESSION['searchfor_name'])) {
1268 /* check for previous conditions. if so add 'AND' */
1269 if(!empty($additional_where_cond)) {
1270 $additional_where_cond.= " AND ";
1273 if($this->dbver < 9) {
1274 $additional_where_cond.= "
1276 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
1278 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1282 if($this->dbver < 17) {
1283 $additional_where_cond.= "
1285 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
1287 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1292 $additional_where_cond.= "
1294 p.filename LIKE '%". $_SESSION['searchfor_name'] ."%'
1296 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1302 /* limit result based on rate-search */
1303 if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) {
1305 if($this->dbver > 10) {
1307 /* check for previous conditions. if so add 'AND' */
1308 if(!empty($additional_where_cond)) {
1309 $additional_where_cond.= " AND ";
1312 $additional_where_cond.= "
1313 p.rating >= ". $_SESSION['rate_from'] ."
1315 p.rating <= ". $_SESSION['rate_to'] ."
1320 if(isset($_SESSION['sort_order'])) {
1321 $order_str = $this->get_sort_order();
1324 /* return a search result */
1325 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
1328 pt1.photo_id as photo_id
1331 INNER JOIN photo_tags pt2
1332 ON pt1.photo_id=pt2.photo_id
1336 ON pt1.photo_id=p.id
1339 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
1341 if(!empty($additional_where_cond))
1342 $query_str.= "AND ". $additional_where_cond ." ";
1344 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1345 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
1348 if(isset($order_str))
1349 $query_str.= $order_str;
1351 $result = $this->db->db_query($query_str);
1352 while($row = $this->db->db_fetch_object($result)) {
1353 array_push($matched_photos, $row['photo_id']);
1355 return $matched_photos;
1358 /* return according the selected tags */
1359 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1361 foreach($_SESSION['selected_tags'] as $tag)
1362 $selected.= $tag .",";
1363 $selected = substr($selected, 0, strlen($selected)-1);
1365 /* photo has to match at least on of the selected tags */
1366 if($_SESSION['tag_condition'] == 'or') {
1369 pt1.photo_id as photo_id
1372 INNER JOIN photo_tags pt2
1373 ON pt1.photo_id=pt2.photo_id
1377 ON pt1.photo_id=p.id
1378 WHERE pt1.tag_id IN (". $selected .")
1380 if(!empty($additional_where_cond))
1381 $query_str.= "AND ". $additional_where_cond ." ";
1383 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1384 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1387 if(isset($order_str))
1388 $query_str.= $order_str;
1390 /* photo has to match all selected tags */
1391 elseif($_SESSION['tag_condition'] == 'and') {
1393 if(count($_SESSION['selected_tags']) >= 32) {
1394 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1395 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1399 /* Join together a table looking like
1401 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1403 so the query can quickly return all images matching the
1404 selected tags in an AND condition
1410 pt1.photo_id as photo_id
1415 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1422 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1424 INNER JOIN photo_tags pt". ($i+2) ."
1425 ON pt1.photo_id=pt". ($i+2) .".photo_id
1430 ON pt1.photo_id=p.id
1432 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1433 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1435 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1438 if(!empty($additional_where_cond))
1439 $query_str.= "AND ". $additional_where_cond;
1441 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1442 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1445 if(isset($order_str))
1446 $query_str.= $order_str;
1450 $result = $this->db->db_query($query_str);
1451 while($row = $this->db->db_fetch_object($result)) {
1452 array_push($matched_photos, $row['photo_id']);
1454 return $matched_photos;
1457 /* return all available photos */
1463 LEFT JOIN photo_tags pt
1469 if(!empty($additional_where_cond))
1470 $query_str.= "WHERE ". $additional_where_cond ." ";
1472 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1473 if(!empty($additional_where_cond))
1474 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1476 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1479 if(isset($order_str))
1480 $query_str.= $order_str;
1482 $result = $this->db->db_query($query_str);
1483 while($row = $this->db->db_fetch_object($result)) {
1484 array_push($matched_photos, $row['id']);
1486 return $matched_photos;
1488 } // getPhotoSelection()
1491 * control HTML ouput for photo index
1493 * this function provides all the necessary information
1494 * for the photo index template.
1497 public function showPhotoIndex()
1499 $photos = $this->getPhotoSelection();
1500 $current_tags = $this->getCurrentTags();
1502 $count = count($photos);
1504 /* if all thumbnails should be shown on one page */
1505 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1509 /* thumbnails should be splitted up in several pages */
1510 elseif($this->cfg->thumbs_per_page > 0) {
1512 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1516 $begin_with = $_SESSION['begin_with'];
1519 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1524 for($i = $begin_with; $i < $end_with; $i++) {
1526 if(!isset($photos[$i]))
1529 /* on first run, initalize all used variables */
1532 $images[$thumbs] = Array();
1533 $img_height[$thumbs] = Array();
1534 $img_width[$thumbs] = Array();
1535 $img_id[$thumbs] = Array();
1536 $img_name[$thumbs] = Array();
1537 $img_fullname[$thumbs] = Array();
1538 $img_title = Array();
1539 $img_rating = Array();
1542 $images[$thumbs] = $photos[$i];
1543 $img_id[$thumbs] = $i;
1544 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1545 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1546 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1547 $img_rating[$thumbs] = $this->get_photo_rating($photos[$i]);
1549 /* get local path of the thumbnail image to be displayed */
1550 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i], $this->get_latest_version($photos[$i]));
1552 /* if the image exist and is readable, extract some details */
1553 if(file_exists($thumb_path) && is_readable($thumb_path)) {
1554 if($info = getimagesize($thumb_path) !== false) {
1555 $img_width[$thumbs] = $info[0];
1556 $img_height[$thumbs] = $info[1];
1562 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1563 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1565 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1566 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1567 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1570 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1571 $this->tmpl->assign('tag_result', 1);
1574 /* do we have to display the page selector ? */
1575 if($this->cfg->thumbs_per_page != 0) {
1579 /* calculate the page switchers */
1580 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1581 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1583 if($begin_with != 0)
1584 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1585 if($end_with < $count)
1586 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1588 $photo_per_page = $this->cfg->thumbs_per_page;
1589 $last_page = ceil($count / $photo_per_page);
1591 /* get the current selected page */
1592 if($begin_with == 0) {
1596 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1603 for($i = 1; $i <= $last_page; $i++) {
1605 if($current_page == $i)
1606 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1607 elseif($current_page-1 == $i || $current_page+1 == $i)
1608 $style = "style=\"font-size: 105%;\"";
1609 elseif(($current_page-5 >= $i) && ($i != 1) ||
1610 ($current_page+5 <= $i) && ($i != $last_page))
1611 $style = "style=\"font-size: 75%;\"";
1615 $start_with = ($i*$photo_per_page)-$photo_per_page;
1617 if($this->is_user_friendly_url()) {
1618 $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\"";
1621 $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi tags=". $current_tags ." begin_with=". $begin_with ."\"";
1623 $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\"";
1627 $select.= ">". $i ."</a> ";
1629 // until 9 pages we show the selector from 1-9
1630 if($last_page <= 9) {
1631 $page_select.= $select;
1634 if($i == 1 /* first page */ ||
1635 $i == $last_page /* last page */ ||
1636 $i == $current_page /* current page */ ||
1637 $i == ceil($last_page * 0.25) /* first quater */ ||
1638 $i == ceil($last_page * 0.5) /* half */ ||
1639 $i == ceil($last_page * 0.75) /* third quater */ ||
1640 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1641 (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 */ ||
1642 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1643 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1645 $page_select.= $select;
1653 $page_select.= "......... ";
1658 /* only show the page selector if we have more then one page */
1660 $this->tmpl->assign('page_selector', $page_select);
1663 $extern_link = "index.php?mode=showpi";
1664 $rss_link = "index.php?mode=rss";
1665 if($current_tags != "") {
1666 $extern_link.= "&tags=". $current_tags;
1667 $rss_link.= "&tags=". $current_tags;
1669 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1670 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1671 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1674 $export_link = "index.php?mode=export";
1675 $slideshow_link = "index.php?mode=slideshow";
1677 $this->tmpl->assign('extern_link', $extern_link);
1678 $this->tmpl->assign('slideshow_link', $slideshow_link);
1679 $this->tmpl->assign('export_link', $export_link);
1680 $this->tmpl->assign('rss_link', $rss_link);
1681 $this->tmpl->assign('count', $count);
1682 $this->tmpl->assign('width', $this->cfg->thumb_width);
1683 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1684 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1685 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1686 $this->tmpl->assign('selected_tags', $this->getSelectedTags('img'));
1687 // +1 for for smarty's selection iteration
1688 $this->tmpl->assign('thumbs', $thumbs+1);
1691 $this->tmpl->assign('images', $images);
1692 $this->tmpl->assign('img_width', $img_width);
1693 $this->tmpl->assign('img_height', $img_height);
1694 $this->tmpl->assign('img_id', $img_id);
1695 $this->tmpl->assign('img_name', $img_name);
1696 $this->tmpl->assign('img_fullname', $img_fullname);
1697 $this->tmpl->assign('img_title', $img_title);
1698 $this->tmpl->assign('img_rating', $img_rating);
1701 $result = $this->tmpl->fetch("photo_index.tpl");
1703 /* if we are returning to photo index from an photo-view,
1704 scroll the window to the last shown photo-thumbnail.
1705 after this, unset the last_photo session variable.
1707 if(isset($_SESSION['last_photo'])) {
1708 $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1709 unset($_SESSION['last_photo']);
1714 } // showPhotoIndex()
1717 * show credit template
1719 public function showCredits()
1721 $this->tmpl->assign('version', $this->cfg->version);
1722 $this->tmpl->assign('product', $this->cfg->product);
1723 $this->tmpl->assign('db_version', $this->dbver);
1724 $this->tmpl->show("credits.tpl");
1729 * create thumbnails for the requested width
1731 * this function creates image thumbnails of $orig_image
1732 * stored as $thumb_image. It will check if the image is
1733 * in a supported format, if necessary rotate the image
1734 * (based on EXIF orientation meta headers) and re-sizing.
1735 * @param string $orig_image
1736 * @param string $thumb_image
1737 * @param integer $width
1740 public function create_thumbnail($orig_image, $thumb_image, $width)
1742 if(!file_exists($orig_image)) {
1746 $mime = $this->get_mime_info($orig_image);
1748 /* check if original photo is a support image type */
1749 if(!$this->checkifImageSupported($mime))
1756 $meta = $this->get_meta_informations($orig_image);
1762 if(isset($meta['Orientation'])) {
1763 switch($meta['Orientation']) {
1764 case 1: /* top, left */
1765 /* nothing to do */ break;
1766 case 2: /* top, right */
1767 $rotate = 0; $flip_hori = true; break;
1768 case 3: /* bottom, left */
1769 $rotate = 180; break;
1770 case 4: /* bottom, right */
1771 $flip_vert = true; break;
1772 case 5: /* left side, top */
1773 $rotate = 270; $flip_vert = true; break;
1774 case 6: /* right side, top */
1775 $rotate = 270; break;
1776 case 7: /* left side, bottom */
1777 $rotate = 90; $flip_vert = true; break;
1778 case 8: /* right side, bottom */
1779 $rotate = 90; break;
1783 $src_img = @imagecreatefromjpeg($orig_image);
1789 $src_img = @imagecreatefrompng($orig_image);
1793 case 'image/x-portable-pixmap':
1795 $src_img = new Imagick($orig_image);
1796 $handler = "imagick";
1801 if(!isset($src_img) || empty($src_img)) {
1802 print "Can't load image from ". $orig_image ."\n";
1810 /* grabs the height and width */
1811 $cur_width = imagesx($src_img);
1812 $cur_height = imagesy($src_img);
1814 // If requested width is more then the actual image width,
1815 // do not generate a thumbnail, instead safe the original
1816 // as thumbnail but with lower quality. But if the image
1817 // is to heigh too, then we still have to resize it.
1818 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1819 $result = imagejpeg($src_img, $thumb_image, 75);
1820 imagedestroy($src_img);
1827 $cur_width = $src_img->getImageWidth();
1828 $cur_height = $src_img->getImageHeight();
1830 // If requested width is more then the actual image width,
1831 // do not generate a thumbnail, instead safe the original
1832 // as thumbnail but with lower quality. But if the image
1833 // is to heigh too, then we still have to resize it.
1834 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1835 $src_img->setCompressionQuality(75);
1836 $src_img->setImageFormat('jpeg');
1837 $src_img->writeImage($thumb_image);
1839 $src_img->destroy();
1846 // If the image will be rotate because EXIF orientation said so
1847 // 'virtually rotate' the image for further calculations
1848 if($rotate == 90 || $rotate == 270) {
1850 $cur_width = $cur_height;
1854 /* calculates aspect ratio */
1855 $aspect_ratio = $cur_height / $cur_width;
1858 if($aspect_ratio < 1) {
1860 $new_h = abs($new_w * $aspect_ratio);
1862 /* 'virtually' rotate the image and calculate it's ratio */
1863 $tmp_w = $cur_height;
1864 $tmp_h = $cur_width;
1865 /* now get the ratio from the 'rotated' image */
1866 $tmp_ratio = $tmp_h/$tmp_w;
1867 /* now calculate the new dimensions */
1869 $tmp_h = abs($tmp_w * $tmp_ratio);
1871 // now that we know, how high they photo should be, if it
1872 // gets rotated, use this high to scale the image
1874 $new_w = abs($new_h / $aspect_ratio);
1876 // If the image will be rotate because EXIF orientation said so
1877 // now 'virtually rotate' back the image for the image manipulation
1878 if($rotate == 90 || $rotate == 270) {
1889 /* creates new image of that size */
1890 $dst_img = imagecreatetruecolor($new_w, $new_h);
1892 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1894 /* copies resized portion of original image into new image */
1895 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1897 /* needs the image to be flipped horizontal? */
1899 $this->_debug("(FLIP)");
1900 $dst_img = $this->flipImage($dst_img, 'hori');
1902 /* needs the image to be flipped vertical? */
1904 $this->_debug("(FLIP)");
1905 $dst_img = $this->flipImage($dst_img, 'vert');
1909 $this->_debug("(ROTATE)");
1910 $dst_img = $this->rotateImage($dst_img, $rotate);
1913 /* write down new generated file */
1914 $result = imagejpeg($dst_img, $thumb_image, 75);
1916 /* free your mind */
1917 imagedestroy($dst_img);
1918 imagedestroy($src_img);
1920 if($result === false) {
1921 print "Can't write thumbnail ". $thumb_image ."\n";
1931 $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1);
1933 /* needs the image to be flipped horizontal? */
1935 $this->_debug("(FLIP)");
1936 $src_img->rotateImage(new ImagickPixel(), 90);
1937 $src_img->flipImage();
1938 $src_img->rotateImage(new ImagickPixel(), -90);
1940 /* needs the image to be flipped vertical? */
1942 $this->_debug("(FLIP)");
1943 $src_img->flipImage();
1947 $this->_debug("(ROTATE)");
1948 $src_img->rotateImage(new ImagickPixel(), $rotate);
1951 $src_img->setCompressionQuality(75);
1952 $src_img->setImageFormat('jpeg');
1954 if(!$src_img->writeImage($thumb_image)) {
1955 print "Can't write thumbnail ". $thumb_image ."\n";
1960 $src_img->destroy();
1967 } // create_thumbnail()
1970 * return all exif meta data from the file
1971 * @param string $file
1974 public function get_meta_informations($file)
1976 return exif_read_data($file);
1978 } // get_meta_informations()
1981 * create phpfspot own sqlite database
1983 * this function creates phpfspots own sqlite database
1984 * if it does not exist yet. this own is used to store
1985 * some necessary informations (md5 sum's, ...).
1987 public function check_phpfspot_db()
1989 // if the config table doesn't exist yet, create it
1990 if(!$this->cfg_db->db_check_table_exists("images")) {
1991 $this->cfg_db->db_exec("
1992 CREATE TABLE images (
1994 img_version_idx int,
1995 img_md5 varchar(32),
1996 UNIQUE(img_idx, img_version_idx)
2001 if(!$this->cfg_db->db_check_table_exists("meta")) {
2002 $this->cfg_db->db_exec("
2004 meta_key varchar(255),
2005 meta_value varchar(255)
2009 /* db_version was added with phpfspot 1.7, before changes
2010 on the phpfspot database where not necessary.
2013 $this->cfg_db->db_exec("
2015 meta_key, meta_value
2017 'phpfspot Database Version',
2018 '". $this->cfg->db_version ."'
2023 /* if version <= 2 and column img_version_idx does not exist yet */
2024 if($this->get_db_version() <= 2 &&
2025 !$this->cfg_db->db_check_column_exists("images", "img_version_idx")) {
2027 if(!$this->cfg_db->db_start_transaction())
2028 die("Can not start database transaction");
2030 $result = $this->cfg_db->db_exec("
2031 CREATE TEMPORARY TABLE images_temp (
2033 img_version_idx int,
2034 img_md5 varchar(32),
2035 UNIQUE(img_idx, img_version_idx)
2040 $this->cfg_db->db_rollback_transaction();
2041 die("Upgrade failed - transaction rollback");
2044 $result = $this->cfg_db->db_exec("
2045 INSERT INTO images_temp
2054 $this->cfg_db->db_rollback_transaction();
2055 die("Upgrade failed - transaction rollback");
2058 $result = $this->cfg_db->db_exec("
2063 $this->cfg_db->db_rollback_transaction();
2064 die("Upgrade failed - transaction rollback");
2067 $result = $this->cfg_db->db_exec("
2068 CREATE TABLE images (
2070 img_version_idx int,
2071 img_md5 varchar(32),
2072 UNIQUE(img_idx, img_version_idx)
2077 $this->cfg_db->db_rollback_transaction();
2078 die("Upgrade failed - transaction rollback");
2081 $result = $this->cfg_db->db_exec("
2088 $this->cfg_db->db_rollback_transaction();
2089 die("Upgrade failed - transaction rollback");
2092 $result = $this->cfg_db->db_exec("
2093 DROP TABLE images_temp
2097 $this->cfg_db->db_rollback_transaction();
2098 die("Upgrade failed - transaction rollback");
2101 if(!$this->cfg_db->db_commit_transaction())
2102 die("Can not commit database transaction");
2106 } // check_phpfspot_db
2109 * generates thumbnails
2111 * This function generates JPEG thumbnails from
2112 * provided F-Spot photo indize and its alternative
2115 * 1. Check if all thumbnail generations (width) are already in place and
2117 * 2. Check if the md5sum of the original file has changed
2118 * 3. Generate the thumbnails if needed
2119 * @param integer $idx
2120 * @param integer $force
2121 * @param boolean $overwrite
2123 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
2126 $versions = Array(0);
2128 $resolutions = Array(
2129 $this->cfg->thumb_width,
2130 $this->cfg->photo_width,
2131 $this->cfg->mini_width,
2135 if($alt_versions = $this->get_photo_versions($idx))
2136 $versions = array_merge($versions, $alt_versions);
2138 foreach($versions as $version) {
2140 /* get details from F-Spot's database */
2141 $details = $this->get_photo_details($idx, $version);
2143 /* calculate file MD5 sum */
2144 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2146 if(!file_exists($full_path)) {
2147 $this->_error("File ". $full_path ." does not exist");
2151 if(!is_readable($full_path)) {
2152 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
2156 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
2158 /* If Nikon NEF format, we need to treat it another way */
2159 if(isset($this->cfg->dcraw_bin) &&
2160 file_exists($this->cfg->dcraw_bin) &&
2161 is_executable($this->cfg->dcraw_bin) &&
2162 preg_match('/\.nef$/i', $details['uri'])) {
2164 $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path);
2166 /* if PPM file does not exist, let dcraw convert it from NEF */
2167 if(!file_exists($ppm_path)) {
2168 system($this->cfg->dcraw_bin ." -a ". $full_path);
2171 /* for now we handle the PPM instead of the NEF */
2172 $full_path = $ppm_path;
2176 $file_md5 = md5_file($full_path);
2179 foreach($resolutions as $resolution) {
2181 $generate_it = false;
2183 $thumb_sub_path = substr($file_md5, 0, 2);
2184 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
2186 /* if thumbnail-subdirectory does not exist yet, create it */
2187 if(!file_exists(dirname($thumb_path))) {
2188 mkdir(dirname($thumb_path), 0755);
2191 /* if the thumbnail file doesn't exist, create it */
2192 if(!file_exists($thumb_path) || $force) {
2193 $generate_it = true;
2195 elseif($file_md5 != $this->getMD5($idx, $version)) {
2196 $generate_it = true;
2199 if($generate_it || $overwrite) {
2201 $this->_debug(" ". $resolution ."px");
2202 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
2210 $this->_debug(" already exist");
2213 /* set the new/changed MD5 sum for the current photo */
2215 $this->setMD5($idx, $file_md5, $version);
2218 $this->_debug("\n");
2225 * returns stored md5 sum for a specific photo
2227 * this function queries the phpfspot database for a stored MD5
2228 * checksum of the specified photo. It also takes care of the
2229 * requested photo version - original or alternative photo.
2231 * @param integer $idx
2232 * @return string|null
2234 public function getMD5($idx, $version_idx = 0)
2236 $result = $this->cfg_db->db_query("
2242 img_idx='". $idx ."'
2244 img_version_idx='". $version_idx ."'
2250 if($img = $this->cfg_db->db_fetch_object($result))
2251 return $img['img_md5'];
2258 * set MD5 sum for the specific photo
2259 * @param integer $idx
2260 * @param string $md5
2262 private function setMD5($idx, $md5, $version_idx = 0)
2264 $result = $this->cfg_db->db_exec("
2265 INSERT OR REPLACE INTO images (
2266 img_idx, img_version_idx, img_md5
2269 '". $version_idx ."',
2277 * store current tag condition
2279 * this function stores the current tag condition
2280 * (AND or OR) in the users session variables
2281 * @param string $mode
2284 public function setTagCondition($mode)
2286 $_SESSION['tag_condition'] = $mode;
2290 } // setTagCondition()
2293 * invoke tag & date search
2295 * this function will return all matching tags and store
2296 * them in the session variable selected_tags. furthermore
2297 * it also handles the date search.
2298 * getPhotoSelection() will then only return the matching
2302 public function startSearch()
2305 if(isset($_POST['date_from']) && $this->isValidDate($_POST['date_from'])) {
2306 $date_from = $_POST['date_from'];
2308 if(isset($_POST['date_to']) && $this->isValidDate($_POST['date_to'])) {
2309 $date_to = $_POST['date_to'];
2312 /* tag-name search */
2313 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
2314 $searchfor_tag = $_POST['for_tag'];
2315 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
2318 unset($_SESSION['searchfor_tag']);
2321 /* file-name search */
2322 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
2323 $_SESSION['searchfor_name'] = $_POST['for_name'];
2326 unset($_SESSION['searchfor_name']);
2330 if(isset($_POST['rate_from']) && is_numeric($_POST['rate_from'])) {
2332 $_SESSION['rate_from'] = $_POST['rate_from'];
2334 if(isset($_POST['rate_to']) && is_numeric($_POST['rate_to'])) {
2335 $_SESSION['rate_to'] = $_POST['rate_to'];
2339 /* delete any previously set value */
2340 unset($_SESSION['rate_to'], $_SESSION['rate_from']);
2345 if(isset($date_from) && !empty($date_from))
2346 $_SESSION['from_date'] = strtotime($date_from ." 00:00:00");
2348 unset($_SESSION['from_date']);
2350 if(isset($date_to) && !empty($date_to))
2351 $_SESSION['to_date'] = strtotime($date_to ." 23:59:59");
2353 unset($_SESSION['to_date']);
2355 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
2356 /* new search, reset the current selected tags */
2357 $_SESSION['selected_tags'] = Array();
2358 foreach($this->avail_tags as $tag) {
2359 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
2360 array_push($_SESSION['selected_tags'], $tag);
2369 * updates sort order in session variable
2371 * this function is invoked by RPC and will sort the requested
2372 * sort order in the session variable.
2373 * @param string $sort_order
2376 public function updateSortOrder($order)
2378 if(isset($this->sort_orders[$order])) {
2379 $_SESSION['sort_order'] = $order;
2383 return "unkown error";
2385 } // updateSortOrder()
2388 * update photo version in session variable
2390 * this function is invoked by RPC and will set the requested
2391 * photo version in the session variable.
2392 * @param string $photo_version
2395 public function update_photo_version($photo_idx, $photo_version)
2397 if($this->is_valid_version($photo_idx, $photo_version)) {
2398 $_SESSION['current_version'] = $photo_version;
2402 return "incorrect photo version provided";
2404 } // update_photo_version()
2409 * this function rotates the image according the
2411 * @param string $img
2412 * @param integer $degress
2415 private function rotateImage($img, $degrees)
2417 if(function_exists("imagerotate")) {
2418 $img = imagerotate($img, $degrees, 0);
2420 function imagerotate($src_img, $angle)
2422 $src_x = imagesx($src_img);
2423 $src_y = imagesy($src_img);
2424 if ($angle == 180) {
2428 elseif ($src_x <= $src_y) {
2432 elseif ($src_x >= $src_y) {
2437 $rotate=imagecreatetruecolor($dest_x,$dest_y);
2438 imagealphablending($rotate, false);
2443 for ($y = 0; $y < ($src_y); $y++) {
2444 for ($x = 0; $x < ($src_x); $x++) {
2445 $color = imagecolorat($src_img, $x, $y);
2446 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
2452 for ($y = 0; $y < ($src_y); $y++) {
2453 for ($x = 0; $x < ($src_x); $x++) {
2454 $color = imagecolorat($src_img, $x, $y);
2455 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
2461 for ($y = 0; $y < ($src_y); $y++) {
2462 for ($x = 0; $x < ($src_x); $x++) {
2463 $color = imagecolorat($src_img, $x, $y);
2464 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
2478 $img = imagerotate($img, $degrees);
2487 * returns flipped image
2489 * this function will return an either horizontal or
2490 * vertical flipped truecolor image.
2491 * @param string $image
2492 * @param string $mode
2495 private function flipImage($image, $mode)
2497 $w = imagesx($image);
2498 $h = imagesy($image);
2499 $flipped = imagecreatetruecolor($w, $h);
2503 for ($y = 0; $y < $h; $y++) {
2504 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
2508 for ($x = 0; $x < $w; $x++) {
2509 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
2519 * return all assigned tags for the specified photo
2520 * @param integer $idx
2523 private function get_photo_tags($idx)
2525 $result = $this->db->db_query("
2526 SELECT t.id as id, t.name as name
2528 INNER JOIN photo_tags pt
2530 WHERE pt.photo_id='". $idx ."'
2535 while($row = $this->db->db_fetch_object($result)) {
2536 if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
2538 $tags[$row['id']] = $row['name'];
2543 } // get_photo_tags()
2546 * create on-the-fly images with text within
2547 * @param string $txt
2548 * @param string $color
2549 * @param integer $space
2550 * @param integer $font
2553 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
2555 if (strlen($color) != 6)
2558 $int = hexdec($color);
2559 $h = imagefontheight($font);
2560 $fw = imagefontwidth($font);
2561 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2562 $lines = count($txt);
2563 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2564 $bg = imagecolorallocate($im, 255, 255, 255);
2565 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2568 foreach ($txt as $text) {
2569 $x = (($w - ($fw * strlen($text))) / 2);
2570 imagestring($im, $font, $x, $y, $text, $color);
2571 $y += ($h + $space);
2574 Header("Content-type: image/png");
2577 } // showTextImage()
2580 * check if all requirements are met
2583 private function check_requirements()
2585 if(!function_exists("imagecreatefromjpeg")) {
2586 print "PHP GD library extension is missing<br />\n";
2590 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2591 print "PHP SQLite3 library extension is missing<br />\n";
2595 if($this->cfg->db_access == "pdo") {
2596 if(array_search("sqlite", PDO::getAvailableDrivers()) === false) {
2597 print "PDO SQLite3 driver is missing<br />\n";
2602 /* Check for HTML_AJAX PEAR package, lent from Horde project */
2603 ini_set('track_errors', 1);
2604 @include_once 'HTML/AJAX/Server.php';
2605 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2606 print "PEAR HTML_AJAX package is missing<br />\n";
2609 @include_once 'Calendar/Calendar.php';
2610 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2611 print "PEAR Calendar package is missing<br />\n";
2614 @include_once 'Console/Getopt.php';
2615 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2616 print "PEAR Console_Getopt package is missing<br />\n";
2619 @include_once 'Date.php';
2620 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2621 print "PEAR Date package is missing<br />\n";
2624 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2625 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2626 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2629 ini_restore('track_errors');
2636 } // check_requirements()
2638 private function _debug($text)
2640 if(isset($this->fromcmd)) {
2647 * check if specified MIME type is supported
2648 * @param string $mime
2651 public function checkifImageSupported($mime)
2653 $supported_types = Array(
2656 "image/x-portable-pixmap",
2660 if(in_array($mime, $supported_types))
2665 } // checkifImageSupported()
2669 * @param string $text
2671 public function _error($text)
2673 switch($this->cfg->logging) {
2676 if(isset($this->fromcmd))
2679 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2680 print $text ."<br />\n";
2687 error_log($text, 3, $this->cfg->log_file);
2691 $this->runtime_error = true;
2696 * get calendar input-text fields
2698 * this function returns a text-field used for the data selection.
2699 * Either it will be filled with the current date or, if available,
2700 * filled with the date user entered previously.
2702 * @param string $mode
2705 private function get_date_text_field($mode)
2707 $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2709 $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2711 $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2713 $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\"";
2714 if(!isset($_SESSION[$mode .'_date']))
2715 $output.= " disabled=\"disabled\"";
2720 } // get_date_text_field()
2723 * output calendar matrix
2724 * @param integer $year
2725 * @param integer $month
2726 * @param integer $day
2728 public function get_calendar_matrix($userdate)
2730 if(($userdate = strtotime($userdate)) === false) {
2737 $date->setDate($userdate);
2739 $year = $date->getYear();
2740 $month = $date->getMonth();
2741 $day = $date->getDay();
2748 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2749 require_once CALENDAR_ROOT.'Day.php';
2752 $month_cal = new Calendar_Month_Weekdays($year,$month);
2755 $prevStamp = $month_cal->prevMonth(true);
2756 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2757 $nextStamp = $month_cal->nextMonth(true);
2758 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2760 $selectedDays = array (
2761 new Calendar_Day($year,$month,$day),
2764 // Build the days in the month
2765 $month_cal->build($selectedDays);
2767 $this->tmpl->assign('current_month', date('F Y',$month_cal->getTimeStamp()));
2768 $this->tmpl->assign('prev_month', $prev);
2769 $this->tmpl->assign('next_month', $next);
2771 while ( $day = $month_cal->fetch() ) {
2773 if(!isset($matrix[$rows]))
2774 $matrix[$rows] = Array();
2778 $dayStamp = $day->thisDay(true);
2779 $link = "javascript:setCalendarDate('"
2780 . date('Y',$dayStamp)
2782 . date('m',$dayStamp)
2784 . date('d',$dayStamp)
2787 // isFirst() to find start of week
2788 if ( $day->isFirst() )
2791 if ( $day->isSelected() ) {
2792 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2793 } else if ( $day->isEmpty() ) {
2794 $string.= "<td> </td>\n";
2796 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2799 // isLast() to find end of week
2800 if ( $day->isLast() )
2801 $string.= "</tr>\n";
2803 $matrix[$rows][$cols] = $string;
2813 $this->tmpl->assign('matrix', $matrix);
2814 $this->tmpl->assign('rows', $rows);
2815 $this->tmpl->show("calendar.tpl");
2817 } // get_calendar_matrix()
2820 * output export page
2821 * @param string $mode
2823 public function getExport($mode)
2825 $pictures = $this->getPhotoSelection();
2826 $current_tags = $this->getCurrentTags();
2828 foreach($pictures as $picture) {
2830 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2831 if($current_tags != "") {
2832 $orig_url.= "&tags=". $current_tags;
2834 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2835 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2838 if($this->is_user_friendly_url()) {
2839 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2842 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2848 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2849 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2853 // "[%pictureurl% %thumbnailurl%]"
2854 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2857 case 'MoinMoinList':
2858 // " * [%pictureurl% %thumbnailurl%]"
2859 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2870 public function getRSSFeed()
2872 Header("Content-type: text/xml; charset=utf-8");
2873 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2876 xmlns:media="http://search.yahoo.com/mrss/"
2877 xmlns:dc="http://purl.org/dc/elements/1.1/"
2880 <title>phpfspot</title>
2881 <description>phpfspot RSS feed</description>
2882 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2883 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2884 <generator>phpfspot</generator>
2887 $pictures = $this->getPhotoSelection();
2888 $current_tags = $this->getCurrentTags();
2890 foreach($pictures as $picture) {
2892 $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2893 if($current_tags != "") {
2894 $orig_url.= "&tags=". $current_tags;
2896 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2897 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2900 $details = $this->get_photo_details($picture);
2902 if($this->is_user_friendly_url()) {
2903 $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2906 $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2909 $thumb_html = htmlspecialchars("
2910 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2912 ". $details['description']);
2914 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2916 /* get EXIF information if JPEG */
2917 if(isset($details['mime']) && $details['mime'] == "image/jpeg") {
2918 $meta = $this->get_meta_informations($orig_path);
2923 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2924 <link><?php print htmlspecialchars($orig_url); ?></link>
2925 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2926 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken>
2928 <?php print $thumb_html; ?>
2930 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate>
2945 * get all selected tags
2947 * This function will return all selected tags as one string, seperated
2951 private function getCurrentTags()
2954 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2955 foreach($_SESSION['selected_tags'] as $tag)
2956 $current_tags.= $tag .",";
2957 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2959 return $current_tags;
2961 } // getCurrentTags()
2964 * return the current photo
2966 public function get_current_photo()
2968 if(isset($_SESSION['current_photo'])) {
2969 return $_SESSION['current_photo'];
2974 } // get_current_photo()
2977 * current selected photo version
2979 * this function returns the current selected photo version
2980 * from the session variables.
2984 public function get_current_version()
2986 /* if current version is set, return it, if the photo really has that version */
2987 if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version']))
2988 return $_SESSION['current_version'];
2992 } // get_current_version()
2995 * returns latest available photo version
2997 * this function returns the latested available version
2998 * for the requested photo.
3002 public function get_latest_version($photo_idx)
3004 /* try to get the lasted version for the current photo */
3005 if($versions = $this->get_photo_versions($photo_idx))
3006 return $versions[count($versions)-1];
3008 /* if no alternative version were found, return original version */
3011 } // get_current_version()
3014 * tells the client browser what to do
3016 * this function is getting called via AJAX by the
3017 * client browsers. it will tell them what they have
3018 * to do next. This is necessary for directly jumping
3019 * into photo index or single photo view when the are
3020 * requested with specific URLs
3023 public function whatToDo()
3025 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
3027 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
3028 return "showpi_tags";
3030 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
3037 * return the current process-user
3040 private function getuid()
3042 if($uid = posix_getuid()) {
3043 if($user = posix_getpwuid($uid)) {
3044 return $user['name'];
3053 * photo version select list
3055 * this function returns a HTML select list (drop down)
3056 * to select a alternative photo version of the original photo.
3058 * @param array $params
3059 * @param smarty $smarty
3062 public function smarty_photo_version_select_list($params, &$smarty)
3064 if(!isset($params['photo']) || !isset($params['current']))
3067 $output = "<option value=\"0\">Original</option>";
3068 $versions = $this->get_photo_versions($params['photo']);
3070 foreach($versions as $version) {
3072 $output.= "<option value=\"". $version ."\"";
3073 if($version == $params['current']) {
3074 $output.= " selected=\"selected\"";
3076 $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>";
3081 } // smarty_photo_version_select_list()
3084 * returns a select-dropdown box to select photo index sort parameters
3085 * @param array $params
3086 * @param smarty $smarty
3089 public function smarty_sort_select_list($params, &$smarty)
3093 foreach($this->sort_orders as $key => $value) {
3094 $output.= "<option value=\"". $key ."\"";
3095 if($key == $_SESSION['sort_order']) {
3096 $output.= " selected=\"selected\"";
3098 $output.= ">". $value ."</option>";
3103 } // smarty_sort_select_list()
3106 * returns the currently selected sort order
3109 private function get_sort_order()
3111 switch($_SESSION['sort_order']) {
3113 return " ORDER BY p.time ASC";
3116 return " ORDER BY p.time DESC";
3119 if($this->dbver < 9) {
3120 return " ORDER BY p.name ASC";
3123 return " ORDER BY basename(p.uri) ASC";
3127 if($this->dbver < 9) {
3128 return " ORDER BY p.name DESC";
3131 return " ORDER BY basename(p.uri) DESC";
3135 return " ORDER BY t.name ASC ,p.time ASC";
3138 return " ORDER BY t.name DESC ,p.time ASC";
3141 return " ORDER BY p.rating ASC, t.name ASC";
3144 return " ORDER BY p.rating DESC, t.name ASC";
3148 } // get_sort_order()
3151 * return the next to be shown slide show image
3153 * this function returns the URL of the next image
3154 * in the slideshow sequence.
3157 public function getNextSlideShowImage()
3159 $all_photos = $this->getPhotoSelection();
3161 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
3162 $_SESSION['slideshow_img'] = 0;
3164 $_SESSION['slideshow_img']++;
3166 if($this->is_user_friendly_url()) {
3167 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3170 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3172 } // getNextSlideShowImage()
3175 * return the previous to be shown slide show image
3177 * this function returns the URL of the previous image
3178 * in the slideshow sequence.
3181 public function getPrevSlideShowImage()
3183 $all_photos = $this->getPhotoSelection();
3185 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
3186 $_SESSION['slideshow_img'] = 0;
3188 $_SESSION['slideshow_img']--;
3190 if($this->is_user_friendly_url()) {
3191 return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
3194 return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
3196 } // getPrevSlideShowImage()
3198 public function resetSlideShow()
3200 if(isset($_SESSION['slideshow_img']))
3201 unset($_SESSION['slideshow_img']);
3203 } // resetSlideShow()
3208 * this function will get all photos from the fspot
3209 * database and randomly return ONE entry
3211 * saddly there is yet no sqlite3 function which returns
3212 * the bulk result in array, so we have to fill up our
3216 public function get_random_photo()
3225 /* if show_tags is set, only return details for photos which
3226 are specified to be shown
3228 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3230 INNER JOIN photo_tags pt
3235 t.name IN ('".implode("','",$this->cfg->show_tags)."')";
3238 $result = $this->db->db_query($query_str);
3240 while($row = $this->db->db_fetch_object($result)) {
3241 array_push($all, $row['id']);
3244 return $all[array_rand($all)];
3246 } // get_random_photo()
3249 * get random photo tag photo
3251 * this function will get all photos tagged with the requested
3252 * tag from the fspot database and randomly return ONE entry
3254 * saddly there is yet no sqlite3 function which returns
3255 * the bulk result in array, so we have to fill up our
3259 public function get_random_tag_photo($tagidx)
3263 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3266 DISTINCT pt1.photo_id as id
3269 INNER JOIN photo_tags
3270 pt2 ON pt1.photo_id=pt2.photo_id
3276 pt1.tag_id LIKE '". $tagidx ."'
3278 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
3280 t1.sort_priority ASC";
3288 INNER JOIN photo_tags pt
3291 pt.tag_id LIKE '". $tagidx ."'";
3294 $result = $this->db->db_query($query_str);
3296 while($row = $this->db->db_fetch_object($result)) {
3297 array_push($all, $row['id']);
3300 return $all[array_rand($all)];
3302 } // get_random_tag_photo()
3305 * validates provided date
3307 * this function validates if the provided date
3308 * contains a valid date and will return true
3310 * @param string $date_str
3313 public function isValidDate($date_str)
3315 $timestamp = strtotime($date_str);
3317 if(is_numeric($timestamp))
3325 * timestamp to string conversion
3326 * @param integer $timestamp
3329 private function ts2str($timestamp)
3331 if(!empty($timestamp) && is_numeric($timestamp))
3332 return strftime("%Y-%m-%d", $timestamp);
3337 * extract tag-names from $_GET['tags']
3338 * @param string $tags_str
3341 private function extractTags($tags_str)
3343 $not_validated = split(',', $tags_str);
3344 $validated = array();
3346 foreach($not_validated as $tag) {
3347 if(is_numeric($tag))
3348 array_push($validated, $tag);
3356 * returns the full path to a thumbnail
3357 * @param integer $width
3358 * @param integer $photo
3361 public function get_thumb_path($width, $photo_idx, $version_idx)
3363 $md5 = $this->getMD5($photo_idx, $version_idx);
3364 $sub_path = substr($md5, 0, 2);
3365 return $this->cfg->thumb_path
3373 } // get_thumb_path()
3376 * returns server's virtual host name
3379 private function get_server_name()
3381 return $_SERVER['SERVER_NAME'];
3382 } // get_server_name()
3385 * returns type of webprotocol which is currently used
3388 private function get_web_protocol()
3390 if(!isset($_SERVER['HTTPS']))
3394 } // get_web_protocol()
3397 * return url to this phpfspot installation
3400 private function get_phpfspot_url()
3402 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3404 } // get_phpfspot_url()
3407 * returns the number of photos which are tagged with $tag_id
3408 * @param integer $tag_id
3411 public function get_num_photos($tag_id)
3413 if($result = $this->db->db_fetchSingleRow("
3414 SELECT count(*) as number
3417 tag_id LIKE '". $tag_id ."'")) {
3419 return $result['number'];
3425 } // get_num_photos()
3428 * check file exists and is readable
3430 * returns true, if everything is ok, otherwise false
3431 * if $silent is not set, this function will output and
3433 * @param string $file
3434 * @param boolean $silent
3437 private function check_readable($file, $silent = null)
3439 if(!file_exists($file)) {
3441 print "File \"". $file ."\" does not exist.\n";
3445 if(!is_readable($file)) {
3447 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3453 } // check_readable()
3456 * check if all needed indices are present
3458 * this function checks, if some needed indices are already
3459 * present, or if not, create them on the fly. they are
3460 * necessary to speed up some queries like that one look for
3461 * all tags, when show_tags is specified in the configuration.
3463 private function checkDbIndices()
3465 $result = $this->db->db_exec("
3466 CREATE INDEX IF NOT EXISTS
3473 } // checkDbIndices()
3476 * retrive F-Spot database version
3478 * this function will return the F-Spot database version number
3479 * It is stored within the sqlite3 database in the table meta
3480 * @return string|null
3482 public function getFspotDBVersion()
3484 if($result = $this->db->db_fetchSingleRow("
3485 SELECT data as version
3488 name LIKE 'F-Spot Database Version'
3490 return $result['version'];
3494 } // getFspotDBVersion()
3497 * parse the provided URI and will returned the requested chunk
3498 * @param string $uri
3499 * @param string $mode
3502 public function parse_uri($uri, $mode)
3504 if(($components = parse_url($uri)) === false)
3509 return basename($components['path']);
3512 return dirname($components['path']);
3515 return $components['path'];
3518 $this->throwError("unknown mode ". $mode);
3525 * validate config options
3527 * this function checks if all necessary configuration options are
3528 * specified and set.
3531 private function check_config_options()
3533 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
3534 $this->_error("Please set \$page_title in phpfspot_cfg");
3536 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
3537 $this->_error("Please set \$base_path in phpfspot_cfg");
3539 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
3540 $this->_error("Please set \$web_path in phpfspot_cfg");
3542 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
3543 $this->_error("Please set \$thumb_path in phpfspot_cfg");
3545 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
3546 $this->_error("Please set \$smarty_path in phpfspot_cfg");
3548 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
3549 $this->_error("Please set \$fspot_db in phpfspot_cfg");
3551 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
3552 $this->_error("Please set \$db_access in phpfspot_cfg");
3554 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
3555 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
3557 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
3558 $this->_error("Please set \$thumb_width in phpfspot_cfg");
3560 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
3561 $this->_error("Please set \$thumb_height in phpfspot_cfg");
3563 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
3564 $this->_error("Please set \$photo_width in phpfspot_cfg");
3566 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
3567 $this->_error("Please set \$mini_width in phpfspot_cfg");
3569 if(!isset($this->cfg->thumbs_per_page))
3570 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
3572 if(!isset($this->cfg->enable_replace_path))
3573 $this->_error("Please set \$enable_replace_path in phpfspot_cfg");
3575 if($this->cfg->enable_replace_path == true) {
3576 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
3577 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
3579 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
3580 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
3583 if(!isset($this->cfg->hide_tags))
3584 $this->_error("Please set \$hide_tags in phpfspot_cfg");
3586 if(!isset($this->cfg->theme_name))
3587 $this->_error("Please set \$theme_name in phpfspot_cfg");
3589 if(!isset($this->cfg->logging))
3590 $this->_error("Please set \$logging in phpfspot_cfg");
3592 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3594 if(!isset($this->cfg->log_file))
3595 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3597 if(!is_writeable($this->cfg->log_file))
3598 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3602 /* remove trailing slash, if set */
3603 if($this->cfg->web_path == "/")
3604 $this->cfg->web_path = "";
3605 elseif(preg_match('/\/$/', $this->cfg->web_path))
3606 $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3608 return $this->runtime_error;
3610 } // check_config_options()
3613 * cleanup phpfspot own database
3615 * When photos are getting delete from F-Spot, there will remain
3616 * remain some residues in phpfspot own database. This function
3617 * will try to wipe them out.
3619 public function cleanup_phpfspot_db()
3621 $to_delete = Array();
3623 $result = $this->cfg_db->db_query("
3624 SELECT img_idx as img_idx
3626 ORDER BY img_idx ASC
3629 while($row = $this->cfg_db->db_fetch_object($result)) {
3630 if(!$this->db->db_fetchSingleRow("
3633 WHERE id='". $row['img_idx'] ."'")) {
3635 array_push($to_delete, $row['img_idx'], ',');
3639 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3641 $this->cfg_db->db_exec("
3643 WHERE img_idx IN (". implode($to_delete) .")
3646 } // cleanup_phpfspot_db()
3649 * return first image of the page, the $current photo
3652 * this function is used to find out the first photo of the
3653 * current page, in which the $current photo lies. this is
3654 * used to display the correct photo, when calling showPhotoIndex()
3656 * @param integer $current
3657 * @param integer $max
3660 private function getCurrentPage($current, $max)
3662 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3663 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3664 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3670 } // getCurrentPage()
3675 * this function tries to find out the correct mime-type
3676 * for the provided file.
3677 * @param string $file
3680 public function get_mime_info($file)
3682 $details = getimagesize($file);
3684 /* if getimagesize() returns empty, try at least to find out the
3687 if(empty($details) && function_exists('mime_content_type')) {
3689 // mime_content_type is marked as deprecated in the documentation,
3690 // but is it really necessary to force users to install a PECL
3692 $details['mime'] = mime_content_type($file);
3695 return $details['mime'];
3697 } // get_mime_info()
3700 * return tag-name by tag-idx
3702 * this function returns the tag-name for the requested
3703 * tag specified by tag-idx.
3704 * @param integer $idx
3707 public function get_tag_name($idx)
3709 if($result = $this->db->db_fetchSingleRow("
3713 id LIKE '". $idx ."'")) {
3715 return $result['name'];
3724 * parse user friendly url which got rewritten by the websever
3725 * @param string $request_uri
3728 private function parse_user_friendly_url($request_uri)
3730 if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3732 $options = explode('/', $request_uri);
3734 switch($options[1]) {
3736 if(is_numeric($options[2])) {
3737 $this->session_cleanup();
3738 //unset($_SESSION['start_action']);
3739 //unset($_SESSION['selected_tags']);
3740 $_GET['mode'] = 'showp';
3741 return $this->showPhoto($options[2]);
3745 if(is_numeric($options[2])) {
3748 if(isset($options[3]) && is_numeric($options[3]))
3749 $width = $options[3];
3750 if(isset($options[4]) && is_numeric($options[4]))
3751 $version = $options[4];
3752 require_once "phpfspot_img.php";
3753 $img = new PHPFSPOT_IMG;
3754 $img->showImg($options[2], $width, $version);
3759 if(is_numeric($options[2])) {
3760 $this->session_cleanup();
3761 $_GET['tags'] = $options[2];
3762 $_SESSION['selected_tags'] = Array($options[2]);
3763 if(isset($options[3]) && is_numeric($options[3]))
3764 $_SESSION['begin_with'] = $options[3];
3765 return $this->showPhotoIndex();
3771 } // parse_user_friendly_url()
3774 * check if user-friendly-urls are enabled
3776 * this function will return true, if the config option
3777 * $user_friendly_url has been set. Otherwise false.
3780 private function is_user_friendly_url()
3782 if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3787 } // is_user_friendly_url()
3792 * this function will cleanup user's session information
3794 private function session_cleanup()
3796 unset($_SESSION['begin_with']);
3797 $this->resetDateSearch();
3798 $this->resetPhotoView();
3799 $this->resetTagSearch();
3800 $this->resetNameSearch();
3801 $this->resetDateSearch();
3804 } // session_cleanup()
3807 * get database version
3809 * this function queries the meta table
3810 * and returns the current database version.
3814 public function get_db_version()
3816 if($row = $this->cfg_db->db_fetchSingleRow("
3817 SELECT meta_value as meta_value
3821 meta_key LIKE 'phpfspot Database Version'
3824 return $row['meta_value'];
3830 } // get_db_version()
3833 * get photo versions
3835 * this function returns an array of all available
3836 * alterntaive versions of the provided photo id.
3837 * has alternative photo versions available
3842 public function get_photo_versions($idx)
3844 $versions = Array();
3846 $result = $this->db->db_query("
3852 photo_id LIKE '". $idx ."'");
3854 while($row = $this->cfg_db->db_fetch_object($result)) {
3855 array_push($versions, $row['version_id']);
3860 } // get_photo_versions()
3863 * check for invalid version of photo
3865 * this function validates the provided photo-id and version-id
3867 * @param int $photo_idx
3868 * @param int $version_idx
3871 public function is_valid_version($photo_idx, $version_idx)
3873 /* the original version is always valid */
3874 if($version_idx == 0)
3877 if($versions = $this->get_photo_versions($photo_idx)) {
3878 if(in_array($version_idx, $versions))
3884 } // is_valid_version()
3887 * get photo version name
3889 * this function returns the name of the version
3890 * identified by the photo-id and version-id.
3892 * @param int $photo_idx
3893 * @param int $version_idx
3896 public function get_photo_version_name($photo_idx, $version_idx)
3898 if($row = $this->db->db_fetchSingleRow("
3904 photo_id LIKE '". $photo_idx ."'
3906 version_id LIKE '". $version_idx ."'")) {
3908 return $row['name'];
3914 } // get_photo_version_name()
3918 public function is_valid_width($image_width)
3920 if(in_array($image_width,
3921 Array($this->cfg->thumb_width,
3922 $this->cfg->photo_width,
3923 $this->cfg->mini_width,
3930 } // is_valid_width()