diff options
Diffstat (limited to 'modules/search')
| -rw-r--r-- | modules/search/controllers/search.php | 123 | ||||
| -rw-r--r-- | modules/search/helpers/search.php | 163 | ||||
| -rw-r--r-- | modules/search/helpers/search_event.php | 39 | ||||
| -rw-r--r-- | modules/search/helpers/search_installer.php | 50 | ||||
| -rw-r--r-- | modules/search/helpers/search_task.php | 84 | ||||
| -rw-r--r-- | modules/search/helpers/search_theme.php | 29 | ||||
| -rw-r--r-- | modules/search/models/search_record.php | 24 | ||||
| -rw-r--r-- | modules/search/module.info | 7 | ||||
| -rw-r--r-- | modules/search/views/search.html.php | 65 | ||||
| -rw-r--r-- | modules/search/views/search_link.html.php | 22 |
10 files changed, 606 insertions, 0 deletions
diff --git a/modules/search/controllers/search.php b/modules/search/controllers/search.php new file mode 100644 index 0000000..753d9b6 --- /dev/null +++ b/modules/search/controllers/search.php @@ -0,0 +1,123 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Search_Controller extends Controller { + public function index() { + $page_size = module::get_var("gallery", "page_size", 9); + $q = Input::instance()->get("q"); + $q_with_more_terms = search::add_query_terms($q); + $show = Input::instance()->get("show"); + + $album_id = Input::instance()->get("album", item::root()->id); + $album = ORM::factory("item", $album_id); + if (!access::can("view", $album) || !$album->is_album()) { + $album = item::root(); + } + + if ($show) { + $child = ORM::factory("item", $show); + $index = search::get_position_within_album($child, $q_with_more_terms, $album); + if ($index) { + $page = ceil($index / $page_size); + url::redirect(url::abs_site("search" . + "?q=" . urlencode($q) . + "&album=" . urlencode($album->id) . + ($page == 1 ? "" : "&page=$page"))); + } + } + + $page = Input::instance()->get("page", 1); + + // Make sure that the page references a valid offset + if ($page < 1) { + $page = 1; + } + + $offset = ($page - 1) * $page_size; + + list ($count, $result) = + search::search_within_album($q_with_more_terms, $album, $page_size, $offset); + + $max_pages = max(ceil($count / $page_size), 1); + + $template = new Theme_View("page.html", "collection", "search"); + $root = item::root(); + $template->set_global( + array("page" => $page, + "max_pages" => $max_pages, + "page_size" => $page_size, + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance($q, url::abs_site("search?q=" . urlencode($q)))->set_last(), + ), + "children_count" => $count)); + + $template->content = new View("search.html"); + $template->content->album = $album; + $template->content->items = $result; + $template->content->q = $q; + + print $template; + + item::set_display_context_callback("Search_Controller::get_display_context", $album, $q); + } + + static function get_display_context($item, $album, $q) { + $q_with_more_terms = search::add_query_terms($q); + $position = search::get_position_within_album($item, $q_with_more_terms, $album); + + if ($position > 1) { + list ($count, $result_data) = + search::search_within_album($q_with_more_terms, $album, 3, $position - 2); + list ($previous_item, $ignore, $next_item) = $result_data; + } else { + $previous_item = null; + list ($count, $result_data) = + search::search_within_album($q_with_more_terms, $album, 1, $position); + list ($next_item) = $result_data; + } + + $search_url = url::abs_site("search" . + "?q=" . urlencode($q) . + "&album=" . urlencode($album->id) . + "&show={$item->id}"); + $root = item::root(); + + return array("position" => $position, + "previous_item" => $previous_item, + "next_item" => $next_item, + "sibling_count" => $count, + "siblings_callback" => array("Search_Controller::get_siblings", array($q, $album)), + "breadcrumbs" => array( + Breadcrumb::instance($root->title, $root->url())->set_first(), + Breadcrumb::instance(t("Search: %q", array("q" => $q)), $search_url), + Breadcrumb::instance($item->title, $item->url())->set_last())); + } + + static function get_siblings($q, $album, $limit, $offset) { + if (!isset($limit)) { + $limit = 100; + } + if (!isset($offset)) { + $offset = 1; + } + $result = search::search_within_album(search::add_query_terms($q), $album, $limit, $offset); + return $result[1]; + } +} diff --git a/modules/search/helpers/search.php b/modules/search/helpers/search.php new file mode 100644 index 0000000..c84b70b --- /dev/null +++ b/modules/search/helpers/search.php @@ -0,0 +1,163 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class search_Core { + /** + * Add more terms to the query by wildcarding the stem value of the first + * few terms in the query. + */ + static function add_query_terms($q) { + $MAX_TERMS = 5; + $terms = explode(" ", $q, $MAX_TERMS); + for ($i = 0; $i < min(count($terms), $MAX_TERMS - 1); $i++) { + // Don't wildcard quoted or already wildcarded terms + if ((substr($terms[$i], 0, 1) != '"') && (substr($terms[$i], -1, 1) != "*")) { + $terms[] = rtrim($terms[$i], "s") . "*"; + } + } + return implode(" ", $terms); + } + + static function search($q, $limit, $offset) { + return search::search_within_album($q, item::root(), $limit, $offset); + } + + static function search_within_album($q, $album, $limit, $offset) { + $db = Database::instance(); + + $query = self::_build_query_base($q, $album) . + " ORDER BY `score` DESC" . + " LIMIT $limit OFFSET " . (int)$offset; + + $data = $db->query($query); + $count = $db->query("SELECT FOUND_ROWS() as c")->current()->c; + + return array($count, new ORM_Iterator(ORM::factory("item"), $data)); + } + + private static function _build_query_base($q, $album, $where=array()) { + $db = Database::instance(); + $q = $db->escape($q); + + if (!identity::active_user()->admin) { + foreach (identity::group_ids_for_active_user() as $id) { + $fields[] = "`view_$id` = TRUE"; // access::ALLOW + } + $access_sql = " AND (" . join(" OR ", $fields) . ")"; + } else { + $access_sql = ""; + } + + if ($album->id == item::root()->id) { + $album_sql = ""; + } else { + $album_sql = + " AND {items}.left_ptr > " . $db->escape($album->left_ptr) . + " AND {items}.right_ptr <= " . $db->escape($album->right_ptr); + } + + return + "SELECT SQL_CALC_FOUND_ROWS {items}.*, " . + " MATCH({search_records}.`data`) AGAINST ('$q') AS `score` " . + "FROM {items} JOIN {search_records} ON ({items}.`id` = {search_records}.`item_id`) " . + "WHERE MATCH({search_records}.`data`) AGAINST ('$q' IN BOOLEAN MODE)" . + $album_sql . + (empty($where) ? "" : " AND " . join(" AND ", $where)) . + $access_sql; + } + + /** + * @return string An error message suitable for inclusion in the task log + */ + static function check_index() { + list ($remaining) = search::stats(); + if ($remaining) { + site_status::warning( + t('Your search index needs to be updated. <a href="%url" class="g-dialog-link">Fix this now</a>', + array("url" => html::mark_clean(url::site("admin/maintenance/start/search_task::update_index?csrf=__CSRF__")))), + "search_index_out_of_date"); + } + } + + static function update($item) { + $data = new ArrayObject(); + $record = ORM::factory("search_record")->where("item_id", "=", $item->id)->find(); + if (!$record->loaded()) { + $record->item_id = $item->id; + } + + $item = $record->item(); + module::event("item_index_data", $item, $data); + $record->data = join(" ", (array)$data); + $record->dirty = 0; + $record->save(); + } + + static function stats() { + $remaining = db::build() + ->from("items") + ->join("search_records", "items.id", "search_records.item_id", "left") + ->and_open() + ->where("search_records.item_id", "IS", null) + ->or_where("search_records.dirty", "=", 1) + ->close() + ->count_records(); + + $total = ORM::factory("item")->count_all(); + $percent = round(100 * ($total - $remaining) / $total); + + return array($remaining, $total, $percent); + } + + static function get_position($item, $q) { + return search::get_position_within_album($item, $q, item::root()); + } + + static function get_position_within_album($item, $q, $album) { + $page_size = module::get_var("gallery", "page_size", 9); + $query = self::_build_query_base($q, $album, array("{items}.id = " . $item->id)) . + " ORDER BY `score` DESC"; + $db = Database::instance(); + + // Truncate the score by two decimal places as this resolves the issues + // that arise due to inexact numeric conversions. + $current = $db->query($query)->current(); + if (!$current) { + // We can't find this result in our result set - perhaps we've fallen out of context? Clear + // the context and try again. + item::clear_display_context_callback(); + url::redirect(url::current()); + } + $score = $current->score; + if (strlen($score) > 7) { + $score = substr($score, 0, strlen($score) - 2); + } + + // Redo the query but only look for results greater than or equal to our current location + // then seek backwards until we find our item. + $data = $db->query(self::_build_query_base($q, $album) . " HAVING `score` >= " . $score . + " ORDER BY `score` DESC"); + $data->seek($data->count() - 1); + + while ($data->get("id") != $item->id && $data->prev()->valid()) { + } + + return $data->key() + 1; + } +} diff --git a/modules/search/helpers/search_event.php b/modules/search/helpers/search_event.php new file mode 100644 index 0000000..a20935b --- /dev/null +++ b/modules/search/helpers/search_event.php @@ -0,0 +1,39 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class search_event_Core { + static function item_created($item) { + search::update($item); + } + + static function item_updated($original, $new) { + search::update($new); + } + + static function item_deleted($item) { + db::build() + ->delete("search_records") + ->where("item_id", "=", $item->id) + ->execute(); + } + + static function item_related_update($item) { + search::update($item); + } +} diff --git a/modules/search/helpers/search_installer.php b/modules/search/helpers/search_installer.php new file mode 100644 index 0000000..c9e8f26 --- /dev/null +++ b/modules/search/helpers/search_installer.php @@ -0,0 +1,50 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class search_installer { + static function install() { + $db = Database::instance(); + $db->query("CREATE TABLE {search_records} ( + `id` int(9) NOT NULL auto_increment, + `item_id` int(9), + `dirty` boolean default 1, + `data` LONGTEXT default NULL, + PRIMARY KEY (`id`), + KEY(`item_id`), + FULLTEXT INDEX (`data`)) + ENGINE=MyISAM + DEFAULT CHARSET=utf8;"); + } + + static function activate() { + // Update the root item. This is a quick hack because the search module is activated as part + // of the official install, so this way we don't start off with a "your index is out of date" + // banner. + search::update(model_cache::get("item", 1)); + search::check_index(); + } + + static function deactivate() { + site_status::clear("search_index_out_of_date"); + } + + static function uninstall() { + Database::instance()->query("DROP TABLE {search_records}"); + } +} diff --git a/modules/search/helpers/search_task.php b/modules/search/helpers/search_task.php new file mode 100644 index 0000000..18348a2 --- /dev/null +++ b/modules/search/helpers/search_task.php @@ -0,0 +1,84 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class search_task_Core { + static function available_tasks() { + // Delete extra search_records + db::build() + ->delete("search_records") + ->where("item_id", "NOT IN", db::build()->select("id")->from("items")) + ->execute(); + + list ($remaining, $total, $percent) = search::stats(); + return array(Task_Definition::factory() + ->callback("search_task::update_index") + ->name(t("Update Search Index")) + ->description( + $remaining + ? t2("1 photo or album needs to be scanned", + "%count (%percent%) of your photos and albums need to be scanned", + $remaining, array("percent" => (100 - $percent))) + : t("Search data is up-to-date")) + ->severity($remaining ? log::WARNING : log::SUCCESS)); + } + + static function update_index($task) { + try { + $completed = $task->get("completed", 0); + + $start = microtime(true); + foreach (ORM::factory("item") + ->join("search_records", "items.id", "search_records.item_id", "left") + ->where("search_records.item_id", "IS", null) + ->or_where("search_records.dirty", "=", 1) + ->find_all(100) as $item) { + // The query above can take a long time, so start the timer after its done + // to give ourselves a little time to actually process rows. + if (!isset($start)) { + $start = microtime(true); + } + + search::update($item); + $completed++; + + if (microtime(true) - $start > .75) { + break; + } + } + + list ($remaining, $total, $percent) = search::stats(); + $task->set("completed", $completed); + if ($remaining == 0 || !($remaining + $completed)) { + $task->done = true; + $task->state = "success"; + site_status::clear("search_index_out_of_date"); + $task->percent_complete = 100; + } else { + $task->percent_complete = round(100 * $completed / ($remaining + $completed)); + } + $task->status = t2("one record updated, index is %percent% up-to-date", + "%count records updated, index is %percent% up-to-date", + $completed, array("percent" => $percent)); + } catch (Exception $e) { + $task->done = true; + $task->state = "error"; + $task->status = $e->getMessage(); + } + } +} diff --git a/modules/search/helpers/search_theme.php b/modules/search/helpers/search_theme.php new file mode 100644 index 0000000..e8735d1 --- /dev/null +++ b/modules/search/helpers/search_theme.php @@ -0,0 +1,29 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class search_theme_Core { + static function header_top($theme) { + if ($theme->page_subtype() != "login") { + $view = new View("search_link.html"); + return $view->render(); + } else { + return ""; + } + } +}
\ No newline at end of file diff --git a/modules/search/models/search_record.php b/modules/search/models/search_record.php new file mode 100644 index 0000000..be68e72 --- /dev/null +++ b/modules/search/models/search_record.php @@ -0,0 +1,24 @@ +<?php defined("SYSPATH") or die("No direct script access."); +/** + * Gallery - a web based photo album viewer and editor + * Copyright (C) 2000-2013 Bharat Mediratta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ +class Search_Record_Model_Core extends ORM { + function item() { + return model_cache::get("item", $this->item_id); + } +} diff --git a/modules/search/module.info b/modules/search/module.info new file mode 100644 index 0000000..f1bb1fa --- /dev/null +++ b/modules/search/module.info @@ -0,0 +1,7 @@ +name = "Search" +description = "Allows users to search their Gallery" +version = 1 +author_name = "Gallery Team" +author_url = "http://codex.galleryproject.org/Gallery:Team" +info_url = "http://codex.galleryproject.org/Gallery3:Modules:search" +discuss_url = "http://galleryproject.org/forum_module_search" diff --git a/modules/search/views/search.html.php b/modules/search/views/search.html.php new file mode 100644 index 0000000..a42c31d --- /dev/null +++ b/modules/search/views/search.html.php @@ -0,0 +1,65 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<? // @todo Set hover on AlbumGrid list items ?> +<form action="<?= url::site("/search") ?>" id="g-search-form" class="g-short-form"> + <fieldset> + <legend> + <?= t("Search") ?> + </legend> + <ul> + <li> + <? if ($album->id == item::root()->id): ?> + <label for="q"><?= t("Search the gallery") ?></label> + <? else: ?> + <label for="q"><?= t("Search this album") ?></label> + <? endif; ?> + <input name="album" type="hidden" value="<?= html::clean_attribute($album->id) ?>" /> + <input name="q" id="q" type="text" value="<?= html::clean_attribute($q) ?>" class="text" /> + </li> + <li> + <input type="submit" value="<?= t("Search")->for_html_attr() ?>" class="submit" /> + </li> + </ul> + </fieldset> +</form> + +<div id="g-search-results"> + <h1><?= t("Search results") ?></h1> + + <? if ($album->id == item::root()->id): ?> + <div> + <?= t("Searched the whole gallery.") ?> + </div> + <? else: ?> + <div> + <?= t("Searched within album <b>%album</b>.", array("album" => html::purify($album->title))) ?> + <a href="<?= url::site(url::merge(array("album" => item::root()->id))) ?>"><?= t("Search whole gallery") ?></a> + </div> + <? endif; ?> + + <? if (count($items)): ?> + <ul id="g-album-grid" class="ui-helper-clearfix"> + <? foreach ($items as $item): ?> + <? $item_class = $item->is_album() ? "g-album" : "g-photo" ?> + <li class="g-item <?= $item_class ?>"> + <a href="<?= $item->url() ?>"> + <?= $item->thumb_img(array("class" => "g-thumbnail")) ?> + <p> + <span class="<?= $item_class ?>"></span> + <?= html::purify(text::limit_chars($item->title, 32, "…")) ?> + </p> + <div> + <?= nl2br(html::purify(text::limit_chars($item->description, 64, "…"))) ?> + </div> + </a> + </li> + <? endforeach ?> + </ul> + <?= $theme->paginator() ?> + + <? else: ?> + <p> + <?= t("No results found for <b>%term</b>", array("term" => $q)) ?> + </p> + + <? endif; ?> +</div> diff --git a/modules/search/views/search_link.html.php b/modules/search/views/search_link.html.php new file mode 100644 index 0000000..4f9abc1 --- /dev/null +++ b/modules/search/views/search_link.html.php @@ -0,0 +1,22 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<form action="<?= url::site("search") ?>" id="g-quick-search-form" class="g-short-form"> + <? if (isset($item)): ?> + <? $album_id = $item->is_album() ? $item->id : $item->parent_id; ?> + <? else: ?> + <? $album_id = item::root()->id; ?> + <? endif; ?> + <ul> + <li> + <? if ($album_id == item::root()->id): ?> + <label for="g-search"><?= t("Search the gallery") ?></label> + <? else: ?> + <label for="g-search"><?= t("Search this album") ?></label> + <? endif; ?> + <input type="hidden" name="album" value="<?= $album_id ?>" /> + <input type="text" name="q" id="g-search" class="text" /> + </li> + <li> + <input type="submit" value="<?= t("Go")->for_html_attr() ?>" class="submit" /> + </li> + </ul> +</form> |
