From 89bf3c322c0ed554b195f98554fe77232fd1a8da Mon Sep 17 00:00:00 2001 From: Demian Katz <demian.katz@villanova.edu> Date: Mon, 16 Jul 2012 10:58:42 -0400 Subject: [PATCH] Plugged in AJAX controller (work in progress). --- module/VuFind/config/module.config.php | 1 + .../src/VuFind/Controller/AjaxController.php | 1308 +++++++++++++++++ 2 files changed, 1309 insertions(+) create mode 100644 module/VuFind/src/VuFind/Controller/AjaxController.php diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php index 7b550b16ad2..5382cdb919e 100644 --- a/module/VuFind/config/module.config.php +++ b/module/VuFind/config/module.config.php @@ -22,6 +22,7 @@ $config = array( ), 'controllers' => array( 'invokables' => array( + 'ajax' => 'VuFind\Controller\AjaxController', 'error' => 'VuFind\Controller\ErrorController', 'index' => 'VuFind\Controller\IndexController', 'my-research' => 'VuFind\Controller\MyResearchController', diff --git a/module/VuFind/src/VuFind/Controller/AjaxController.php b/module/VuFind/src/VuFind/Controller/AjaxController.php new file mode 100644 index 00000000000..14a949b396b --- /dev/null +++ b/module/VuFind/src/VuFind/Controller/AjaxController.php @@ -0,0 +1,1308 @@ +<?php +/** + * Ajax Controller Module + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category VuFind2 + * @package Controller + * @author Chris Hallberg <challber@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/building_a_recommendations_module Wiki + */ +namespace VuFind\Controller; +use VuFind\Cart, VuFind\Config\Reader as ConfigReader, + VuFind\Connection\Manager as ConnectionManager, + VuFind\Exception\Auth as AuthException, VuFind\Export, + VuFind\Record, VuFind\Translator\Translator; + +/** + * This controller handles global AJAX functionality + * + * @category VuFind2 + * @package Controller + * @author Chris Hallberg <challber@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/building_a_recommendations_module Wiki + */ +class AjaxController extends AbstractBase +{ + // define some status constants + const STATUS_OK = 'OK'; // good + const STATUS_ERROR = 'ERROR'; // bad + const STATUS_NEED_AUTH = 'NEED_AUTH'; // must login first + + protected $outputMode; + protected $account; + protected static $php_errors = array(); + + /** + * Constructor + */ + public function __construct() + { + // Add notices to a key in the output + set_error_handler(array("VuFind\\Controller\\AjaxController", "storeError")); + } + + /** + * Handles passing data to the class + * + * @return void + */ + public function jsonAction() + { + // Set the output mode to JSON: + $this->outputMode = 'json'; + + // Call the method specified by the 'method' parameter as long as it is + // valid and will not result in an infinite loop! + $method = $this->params()->fromQuery('method'); + if ($method != 'init' && $method != '__construct' && $method != 'output' + && method_exists($this, $method) + ) { + try { + $this->$method(); + } catch (\Exception $e) { + return $this->output( + Translator::translate('An error has occurred'), + self::STATUS_ERROR + ); + } + } else { + return $this->output( + Translator::translate('Invalid Method'), self::STATUS_ERROR + ); + } + } + + /** + * Load a recommendation module via AJAX. + * + * @return void + */ + public function recommendAction() + { + // Process recommendations -- for now, we assume Solr-based search objects, + // since deferred recommendations work best for modules that don't care about + // the details of the search objects anyway: + $class = 'VuFind\\Recommend\\' . $this->params()->fromQuery('mod'); + $module = new $class($this->params()->fromQuery('params')); + $params = new \VuFind\Search\Solr\Params(); + $module->init($params, $this->getRequest()->getQuery()); + $results = new \VuFind\Search\Solr\Results($params); + $module->process($results); + + /* TODO + // Set headers: + $resp = $this->getResponse(); + $resp->setHeader('Content-type', 'text/html'); + $resp->setHeader('Cache-Control', 'no-cache, must-revalidate'); + $resp->setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT'); + + // Render recommendations: + $resp->appendBody($this->view->recommend($module)); + */ + } + + /** + * Get the contents of a lightbox; note that unlike most methods, this + * one actually returns HTML rather than JSON. + * + * @return void + */ + public function getLightbox() + { + /* TODO + // Turn layouts on for this action since we want to render the + // page inside a lightbox: + $this->_helper->layout->setLayout('lightbox'); + + // Call the requested action: + return $this->_forward( + $this->params()->fromQuery('subaction'), + $this->params()->fromQuery('submodule') + ); + */ + } + + /** + * Support method for getItemStatuses() -- filter suppressed locations from the + * array of item information for a particular bib record. + * + * @param array $record Information on items linked to a single bib record + * + * @return array Filtered version of $record + */ + protected function filterSuppressedLocations($record) + { + static $hideHoldings = false; + if ($hideHoldings === false) { + $logic = new \VuFind\ILS\Logic\Holds($this->getAccount()); + $hideHoldings = $logic->getSuppressedLocations(); + } + + $filtered = array(); + foreach ($record as $current) { + if (!in_array($current['location'], $hideHoldings)) { + $filtered[] = $current; + } + } + return $filtered; + } + + /** + * Get Item Statuses + * + * This is responsible for printing the holdings information for a + * collection of records in JSON format. + * + * @return void + * @author Chris Delis <cedelis@uillinois.edu> + * @author Tuan Nguyen <tuan@yorku.ca> + */ + public function getItemStatuses() + { + try { + $catalog = ConnectionManager::connectToCatalog(); + } catch (\Exception $e) { + return $this->output( + Translator::translate('An error has occurred'), self::STATUS_ERROR + ); + } + $ids = $this->params()->fromQuery('id'); + try { + $results = $catalog->getStatuses($ids); + } catch (\Exception $e) { + return $this->output($e->getMessage(), self::STATUS_ERROR); + } + if (!is_array($results)) { + // If getStatuses returned garbage, let's turn it into an empty array + // to avoid triggering a notice in the foreach loop below. + $results = array(); + } + + // In order to detect IDs missing from the status response, create an + // array with a key for every requested ID. We will clear keys as we + // encounter IDs in the response -- anything left will be problems that + // need special handling. + $missingIds = array_flip($ids); + + /* TODO + // Load messages for response: + $messages = array( + 'available' => $this->view->render('ajax/status-available.phtml'), + 'unavailable' => $this->view->render('ajax/status-unavailable.phtml'), + 'unknown' => $this->view->render('ajax/status-unknown.phtml') + ); + */ + + // Load callnumber and location settings: + $config = ConfigReader::getConfig(); + $callnumberSetting = isset($config->Item_Status->multiple_call_nos) + ? $config->Item_Status->multiple_call_nos : 'msg'; + $locationSetting = isset($config->Item_Status->multiple_locations) + ? $config->Item_Status->multiple_locations : 'msg'; + $showFullStatus = isset($config->Item_Status->show_full_status) + ? $config->Item_Status->show_full_status : false; + + // Loop through all the status information that came back + $statuses = array(); + foreach ($results as $recordNumber=>$record) { + // Filter out suppressed locations: + $record = $this->filterSuppressedLocations($record); + + // Skip empty records: + if (count($record)) { + if ($locationSetting == "group") { + $current = $this->getItemStatusGroup( + $record, $messages, $callnumberSetting + ); + } else { + $current = $this->getItemStatus( + $record, $messages, $locationSetting, $callnumberSetting + ); + } + // If a full status display has been requested, append the HTML: + if ($showFullStatus) { + /* TODO + $this->view->statusItems = $record; + $current['full_status'] = $this->view->render( + 'ajax/status-full.phtml' + ); + */ + } + $current['record_number'] = array_search($current['id'], $ids); + $statuses[] = $current; + + // The current ID is not missing -- remove it from the missing list. + unset($missingIds[$current['id']]); + } + } + + // If any IDs were missing, send back appropriate dummy data + foreach ($missingIds as $missingId => $recordNumber) { + $statuses[] = array( + 'id' => $missingId, + 'availability' => 'false', + 'availability_message' => $messages['unavailable'], + 'location' => Translator::translate('Unknown'), + 'locationList' => false, + 'reserve' => 'false', + 'reserve_message' => Translator::translate('Not On Reserve'), + 'callnumber' => '', + 'missing_data' => true, + 'record_number' => $recordNumber + ); + } + + // Done + $this->output($statuses, self::STATUS_OK); + } + + /** + * Support method for getItemStatuses() -- when presented with multiple values, + * pick which one(s) to send back via AJAX. + * + * @param array $list Array of values to choose from. + * @param string $mode config.ini setting -- first, all or msg + * @param string $msg Message to display if $mode == "msg" + * + * @return string + */ + protected function pickValue($list, $mode, $msg) + { + // Make sure array contains only unique values: + $list = array_unique($list); + + // If there is only one value in the list, or if we're in "first" mode, + // send back the first list value: + if ($mode == 'first' || count($list) == 1) { + return $list[0]; + } else if (count($list) == 0) { + // Empty list? Return a blank string: + return ''; + } else if ($mode == 'all') { + // All values mode? Return comma-separated values: + return implode(', ', $list); + } else { + // Message mode? Return the specified message, translated to the + // appropriate language. + return Translator::translate($msg); + } + } + + /** + * Support method for getItemStatuses() -- process a single bibliographic record + * for location settings other than "group". + * + * @param array $record Information on items linked to a single bib + * record + * @param array $messages Custom status HTML + * (keys = available/unavailable) + * @param string $locationSetting The location mode setting used for + * pickValue() + * @param string $callnumberSetting The callnumber mode setting used for + * pickValue() + * + * @return array Summarized availability information + */ + protected function getItemStatus($record, $messages, $locationSetting, + $callnumberSetting + ) { + // Summarize call number, location and availability info across all items: + $callNumbers = $locations = array(); + $use_unknown_status = $available = false; + foreach ($record as $info) { + // Find an available copy + if ($info['availability']) { + $available = true; + } + // Check for a use_unknown_message flag + if (isset($info['use_unknown_message']) + && $info['use_unknown_message'] == true + ) { + $use_unknown_status = true; + } + // Store call number/location info: + $callNumbers[] = $info['callnumber']; + $locations[] = $info['location']; + } + + // Determine call number string based on findings: + $callNumber = $this->pickValue( + $callNumbers, $callnumberSetting, 'Multiple Call Numbers' + ); + + // Determine location string based on findings: + $location = $this->pickValue( + $locations, $locationSetting, 'Multiple Locations' + ); + + $availability_message = $use_unknown_status + ? $messages['unknown'] + : $messages[$available ? 'available' : 'unavailable']; + + // Send back the collected details: + return array( + 'id' => $record[0]['id'], + 'availability' => ($available ? 'true' : 'false'), + 'availability_message' => $availability_message, + 'location' => htmlentities($location, ENT_COMPAT, 'UTF-8'), + 'locationList' => false, + 'reserve' => + ($record[0]['reserve'] == 'Y' ? 'true' : 'false'), + 'reserve_message' => $record[0]['reserve'] == 'Y' + ? Translator::translate('on_reserve') + : Translator::translate('Not On Reserve'), + 'callnumber' => htmlentities($callNumber, ENT_COMPAT, 'UTF-8') + ); + } + + /** + * Support method for getItemStatuses() -- process a single bibliographic record + * for "group" location setting. + * + * @param array $record Information on items linked to a single + * bib record + * @param array $messages Custom status HTML + * (keys = available/unavailable) + * @param string $callnumberSetting The callnumber mode setting used for + * pickValue() + * + * @return array Summarized availability information + */ + protected function getItemStatusGroup($record, $messages, $callnumberSetting) + { + // Summarize call number, location and availability info across all items: + $locations = array(); + $use_unknown_status = $available = false; + foreach ($record as $info) { + // Find an available copy + if ($info['availability']) { + $available = $locations[$info['location']]['available'] = true; + } + // Check for a use_unknown_message flag + if (isset($info['use_unknown_message']) + && $info['use_unknown_message'] == true + ) { + $use_unknown_status = true; + } + // Store call number/location info: + $locations[$info['location']]['callnumbers'][] = $info['callnumber']; + } + + // Build list split out by location: + $locationList = false; + foreach ($locations as $location => $details) { + $locationCallnumbers = array_unique($details['callnumbers']); + // Determine call number string based on findings: + $locationCallnumbers = $this->pickValue( + $locationCallnumbers, $callnumberSetting, 'Multiple Call Numbers' + ); + $locationInfo = array( + 'availability' => + isset($details['available']) ? $details['available'] : false, + 'location' => htmlentities($location, ENT_COMPAT, 'UTF-8'), + 'callnumbers' => + htmlentities($locationCallnumbers, ENT_COMPAT, 'UTF-8') + ); + $locationList[] = $locationInfo; + } + + $availability_message = $use_unknown_status + ? $messages['unknown'] + : $messages[$available ? 'available' : 'unavailable']; + + // Send back the collected details: + return array( + 'id' => $record[0]['id'], + 'availability' => ($available ? 'true' : 'false'), + 'availability_message' => $availability_message, + 'location' => false, + 'locationList' => $locationList, + 'reserve' => + ($record[0]['reserve'] == 'Y' ? 'true' : 'false'), + 'reserve_message' => $record[0]['reserve'] == 'Y' + ? Translator::translate('on_reserve') + : Translator::translate('Not On Reserve'), + 'callnumber' => false + ); + } + + /** + * Check one or more records to see if they are saved in one of the user's list. + * + * @return void + */ + protected function getSaveStatuses() + { + // check if user is logged in + $user = $this->getUser(); + if (!$user) { + return $this->output( + Translator::translate('You must be logged in first'), + self::STATUS_NEED_AUTH + ); + } + + // loop through each ID check if it is saved to any of the user's lists + $result = array(); + $ids = $this->params()->fromQuery('id', array()); + $sources = $this->params()->fromQuery('source', array()); + if (!is_array($ids) || !is_array($sources)) { + return $this->output( + Translator::translate('Argument must be array.'), + self::STATUS_ERROR + ); + } + foreach ($ids as $i => $id) { + $source = isset($sources[$i]) ? $sources[$i] : 'VuFind'; + $data = $user->getSavedData($id, null, $source); + if ($data) { + // if this item was saved, add it to the list of saved items. + foreach ($data as $list) { + $result[] = array( + 'record_id' => $id, + 'record_source' => $source, + 'resource_id' => $list->id, + 'list_id' => $list->list_id, + 'list_title' => $list->list_title, + 'record_number' => $i + ); + } + } + } + $this->output($result, self::STATUS_OK); + } + + /** + * Send output data and exit. + * + * @param mixed $data The response data + * @param string $status Status of the request + * + * @return void + */ + protected function output($data, $status) + { + if ($this->outputMode == 'json') { + $response = $this->getResponse(); + $headers = $response->getHeaders(); + $headers->addHeaderLine( + 'Content-type', 'application/javascript' + ); + $headers->addHeaderLine( + 'Cache-Control', 'no-cache, must-revalidate' + ); + $headers->addHeaderLine( + 'Expires', 'Mon, 26 Jul 1997 05:00:00 GMT' + ); + $output = array('data'=>$data,'status'=>$status); + if ('development' == APPLICATION_ENV && count(self::$php_errors) > 0) { + $output['php_errors'] = self::$php_errors; + } + $response->setContent(json_encode($output)); + return $response; + } else { + throw new \Exception('Unsupported output mode: ' . $this->outputMode); + } + } + + /** + * Store the errors for later, to be added to the output + * + * @param string $errno Error code number + * @param string $errstr Error message + * @param string $errfile File where error occured + * @param string $errline Line number of error + * + * @return true - to cancel default error handling + */ + public static function storeError($errno, $errstr, $errfile, $errline) + { + self::$php_errors[] = "ERROR [$errno] - ".$errstr."<br />\n" + . " Occurred in ".$errfile." on line ".$errline."."; + return true; + } + + /** + * Generate the "salt" used in the salt'ed login request. + * + * @return string + */ + protected function generateSalt() + { + return str_replace( + '.', '', $this->getRequest()->getServer()->get('REMOTE_ADDR') + ); + } + + /** + * Send the "salt" to be used in the salt'ed login request. + * + * @return void + */ + protected function getSalt() + { + $this->output($this->generateSalt(), self::STATUS_OK); + } + + /** + * Login with post'ed username and encrypted password. + * + * @return void + */ + protected function login() + { + // Fetch Salt + $salt = $this->generateSalt(); + + // HexDecode Password + $password = pack('H*', $this->params()->fromQuery('password')); + + // Decrypt Password + $password = \VuFind\Crypt\RC4::encrypt($salt, $password); + + // Update the request with the decrypted password: + $this->getRequest()->getPost()->set('password', $password); + + // Authenticate the user: + try { + $this->getAccount()->login($this->getRequest()); + } catch (AuthException $e) { + return $this->output( + Translator::translate($e->getMessage()), + self::STATUS_ERROR + ); + } + + $this->output(true, self::STATUS_OK); + } + + /** + * Tag a record. + * + * @return void + */ + protected function tagRecord() + { + $user = $this->getUser(); + if ($user === false) { + return $this->output( + Translator::translate('You must be logged in first'), + self::STATUS_NEED_AUTH + ); + } + // empty tag + try { + $driver = Record::load( + $this->params()->fromQuery('id'), + $this->params()->fromQuery('source', 'VuFind') + ); + $tag = $this->params()->fromQuery('tag', ''); + if (strlen($tag) > 0) { // don't add empty tags + $driver->addTags($user, $tag); + } + } catch (\Exception $e) { + return $this->output( + Translator::translate('Failed'), + self::STATUS_ERROR + ); + } + + $this->output(Translator::translate('Done'), self::STATUS_OK); + } + + /** + * Get all tags for a record. + * + * @return void + */ + protected function getRecordTags() + { + // Retrieve from database: + $tagTable = new VuFind_Model_Db_Tags(); + $tags = $tagTable->getForResource( + $this->params()->fromQuery('id'), + $this->params()->fromQuery('source', 'VuFind') + ); + + // Build data structure for return: + $tagList = array(); + foreach ($tags as $tag) { + $tagList[] = array('tag'=>$tag->tag, 'cnt'=>$tag->cnt); + } + + // If we don't have any tags, provide a user-appropriate message: + if (empty($tagList)) { + $msg = Translator::translate('No Tags') . ', ' . + Translator::translate('Be the first to tag this record') . '!'; + return $this->output($msg, self::STATUS_ERROR); + } + + return $this->output($tagList, self::STATUS_OK); + } + + /** + * Get map data on search results and output in JSON + * + * @param array $fields Solr fields to retrieve data from + * + * @author Chris Hallberg <crhallberg@gmail.com> + * @author Lutz Biedinger <lutz.biedinger@gmail.com> + * + * @return void + */ + public function getMapData($fields = array('long_lat')) + { + $params = new \VuFind\Search\Solr\Params(); + $params->initFromRequest($this->getRequest->getQuery()); + // Attempt to perform the search; if there is a problem, inspect any Solr + // exceptions to see if we should communicate to the user about them. + try { + $results = new \VuFind\Search\Solr\Results($params); + + $facets = $results->getFullFieldFacets($fields, false); + + $markers=array(); + $i = 0; + $list = isset($facets['long_lat']['data']['list']) + ? $facets['long_lat']['data']['list'] : array(); + foreach ($list as $location) { + $longLat = explode(',', $location['value']); + $markers[$i] = array( + 'title' => (string)$location['count'], //needs to be a string + 'location_facet' => + $location['value'], //needed to load in the location + 'lon' => $longLat[0], + 'lat' => $longLat[1] + ); + $i++; + } + $this->output($markers, self::STATUS_OK); + } catch (\Exception $e) { + echo $e; + $this->output("", self::STATUS_ERROR); + } + } + + /** + * Get entry information on entries tied to a specific map location + * + * @param array $fields Solr fields to retrieve data from + * + * @author Chris Hallberg <crhallberg@gmail.com> + * @author Lutz Biedinger <lutz.biedinger@gmail.com> + * + * @return void + */ + public function resultgooglemapinfoAction($fields = array('long_lat')) + { + /* TODO + // Turn layouts on for this action since we want to render the + // page inside a lightbox: + $this->_helper->layout->setLayout('lightbox'); + $this->_helper->viewRenderer->setNoRender(false); + + $params = new \VuFind\Search\Solr\Params(); + $params->initFromRequest($this->getRequest()->getQuery()); + $results = new \VuFind\Search\Solr\Results($params); + $this->view->results = $results; + $this->view->recordSet = $results->getResults(); + $this->view->recordCount = $results->getResultTotal(); + $this->view->completeListUrl = $results->getUrl()->getParams(); + */ + } + + /** + * AJAX for timeline feature (PubDateVisAjax) + * + * @param array $fields Solr fields to retrieve data from + * + * @author Chris Hallberg <crhallberg@gmail.com> + * @author Till Kinstler <kinstler@gbv.de> + * + * @return void (thru output) + */ + public function getVisData($fields = array('publishDate')) + { + $params = new \VuFind\Search\Solr\Params(); + $params->initFromRequest($this->getRequest()->getQuery()); + // Attempt to perform the search; if there is a problem, inspect any Solr + // exceptions to see if we should communicate to the user about them. + try { + $results = new \VuFind\Search\Solr\Results($params); + $filters = $results->getFilters(); + $dateFacets = $this->params()->fromQuery('facetFields'); + $dateFacets = empty($dateFacets) ? array() : explode(':', $dateFacets); + $fields = $this->processDateFacets($filters, $dateFacets, $results); + $facets = $this->processFacetValues($fields, $results); + foreach ($fields as $field => $val) { + $facets[$field]['min'] = $val[0] > 0 ? $val[0] : 0; + $facets[$field]['max'] = $val[1] > 0 ? $val[1] : 0; + $facets[$field]['removalURL'] + = $results->getUrl()->removeFacet( + $field, + isset($filters[$field][0]) ? $filters[$field][0] : null, + false + ); + } + $this->output($facets, self::STATUS_OK); + } catch (\Exception $e) { + echo $e; + $this->output("", self::STATUS_ERROR); + } + } + + /** + * Support method for getVisData() -- extract details from applied filters. + * + * @param array $filters Current filter list + * @param array $dateFacets Objects containing the date + * ranges + * @param \VuFind\Search\Solr\Results $results Search results object + * + * @return array + */ + protected function processDateFacets($filters, $dateFacets, $results) + { + $result = array(); + foreach ($dateFacets as $current) { + $from = $to = ''; + if (isset($filters[$current])) { + foreach ($filters[$current] as $filter) { + if (preg_match('/\[\d+ TO \d+\]/', $filter)) { + $range = explode(' TO ', trim($filter, '[]')); + $from = $range[0] == '*' ? '' : $range[0]; + $to = $range[1] == '*' ? '' : $range[1]; + break; + } + } + } + $result[$current] = array($from, $to); + $result[$current]['label'] + = $results->getFacetLabel($current); + } + return $result; + } + + /** + * Support method for getVisData() -- filter bad values from facet lists. + * + * @param array $fields Processed date information from + * processDateFacets + * @param \VuFind\Search\Solr\Results $results Search results object + * + * @return array + */ + protected function processFacetValues($fields, $results) + { + $facets = $results->getFullFieldFacets(array_keys($fields)); + $retVal = array(); + foreach ($facets as $field => $values) { + $newValues = array('data' => array()); + foreach ($values['data']['list'] as $current) { + // Only retain numeric values! + if (preg_match("/^[0-9]+$/", $current['value'])) { + $newValues['data'][] + = array($current['value'],$current['count']); + } + } + $retVal[$field] = $newValues; + } + return $retVal; + } + + /** + * Save a record to a list. + * + * @return void + */ + public function saveRecord() + { + $user = $this->getUser(); + if (!$user) { + return $this->output( + Translator::translate('You must be logged in first'), + self::STATUS_NEED_AUTH + ); + } + + $driver = Record::load( + $this->params()->fromQuery('id'), + $this->params()->fromQuery('source', 'VuFind') + ); + $driver->saveToFavorites($this->getRequest()->getQuery(), $user); + return $this->output('Done', self::STATUS_OK); + } + + /** + * Saves records to a User's favorites + * + * @return void + * @access public + */ + public function bulkSave() + { + /* TODO + // Without IDs, we can't continue + $ids = $this->params()->fromQuery('ids', array()); + if (empty($ids)) { + return $this->output( + array('result'=>Translator::translate('bulk_error_missing')), + self::STATUS_ERROR + ); + } + + $user = $this->getUser(); + if (!$user) { + return $this->output( + Translator::translate('You must be logged in first'), + self::STATUS_NEED_AUTH + ); + } + + try { + $this->_helper->favorites->saveBulk( + $this->getRequest()->getParams(), $user + ); + return $this->output( + array( + 'result' => array('list' => $this->params()->fromQuery('list')), + 'info' => Translator::translate("bulk_save_success") + ), self::STATUS_OK + ); + } catch (\Exception $e) { + return $this->output( + array('info' => Translator::translate('bulk_save_error')), + self::STATUS_ERROR + ); + } + */ + } + + /** + * Add a list. + * + * @return void + */ + public function addList() + { + $user = $this->getUser(); + + try { + $list = VuFind_Model_Db_UserList::getNew($user); + $id = $list->updateFromRequest($this->getRequest()->getQuery()); + } catch (\Exception $e) { + switch(get_class($e)) { + case 'VuFind\\Exception\\LoginRequired': + return $this->output( + Translator::translate('You must be logged in first'), + self::STATUS_NEED_AUTH + ); + break; + case 'VuFind\\Exception\\ListPermission': + case 'VuFind\\Exception\\MissingField': + return $this->output( + Translator::translate($e->getMessage()), self::STATUS_ERROR + ); + default: + throw $e; + } + } + + return $this->output(array('id' => $id), self::STATUS_OK); + } + + /** + * Get Autocomplete suggestions. + * + * @return void + */ + public function getACSuggestions() + { + /* TODO + return $this->output( + array_values( + VF_Autocomplete_Factory::getSuggestions( + $this->getRequest()->getQuery() + ) + ), + self::STATUS_OK + ); + */ + } + + /** + * Text a record. + * + * @return void + */ + public function smsRecord() + { + /* TODO + // Attempt to send the email: + try { + $record = Record::load( + $this->params()->fromQuery('id'), + $this->params()->fromQuery('source', 'VuFind') + ); + $mailer = new VF_Mailer_SMS(); + $mailer->textRecord( + $this->params()->fromQuery('provider'), + $this->params()->fromQuery('to'), $record, $this->view + ); + return $this->output( + Translator::translate('sms_success'), self::STATUS_OK + ); + } catch (\Exception $e) { + return $this->output( + Translator::translate($e->getMessage()), self::STATUS_ERROR + ); + } + */ + } + + + /** + * Email a record. + * + * @return void + */ + public function emailRecord() + { + /* TODO + // Attempt to send the email: + try { + $record = Record::load( + $this->params()->fromQuery('id'), + $this->params()->fromQuery('source', 'VuFind') + ); + $mailer = new VF_Mailer(); + $mailer->sendRecord( + $this->params()->fromQuery('to'), $this->params()->fromQuery('from'), + $this->params()->fromQuery('message'), $record, $this->view + ); + return $this->output( + Translator::translate('email_success'), self::STATUS_OK + ); + } catch (\Exception $e) { + return $this->output( + Translator::translate($e->getMessage()), self::STATUS_ERROR + ); + } + */ + } + + /** + * Email a search. + * + * @return void + */ + public function emailSearch() + { + /* TODO + // Make sure URL is properly formatted -- if no protocol is specified, run it + // through the fullUrl helper: + $url = $this->params()->fromQuery('url'); + if (substr($url, 0, 4) != 'http') { + $url = $this->view->fullUrl($url); + } + + // Attempt to send the email: + try { + $mailer = new VF_Mailer(); + $mailer->sendLink( + $this->params()->fromQuery('to'), $this->params()->fromQuery('from'), + $this->params()->fromQuery('message'), + $url, $this->view, $this->params()->fromQuery('subject') + ); + return $this->output( + Translator::translate('email_success'), self::STATUS_OK + ); + } catch (\Exception $e) { + return $this->output( + Translator::translate($e->getMessage()), self::STATUS_ERROR + ); + } + */ + } + + /** + * Check Request is Valid + * + * @return void + */ + public function checkRequestIsValid() + { + $id = $this->params()->fromQuery('id'); + $data = $this->params()->fromQuery('data'); + if (!empty($id) && !empty($data)) { + // check if user is logged in + $user = $this->getUser(); + if (!$user) { + return $this->output( + Translator::translate('You must be logged in first'), + self::STATUS_NEED_AUTH + ); + } + + try { + $catalog = ConnectionManager::connectToCatalog(); + $patron = $this->getAccount()->storedCatalogLogin(); + if ($patron) { + $results = $catalog->checkRequestIsValid($id, $data, $patron); + + $msg = $results + ? Translator::translate('request_place_text') + : Translator::translate('hold_error_blocked'); + return $this->output( + array('status' => $results, 'msg' => $msg), self::STATUS_OK + ); + } + } catch (\Exception $e) { + // Do nothing -- just fail through to the error message below. + } + } + + return $this->output( + Translator::translate('An error has occurred'), self::STATUS_ERROR + ); + } + + /** + * Comment on a record. + * + * @return void + */ + public function commentRecord() + { + $user = $this->getUser(); + if ($user === false) { + return $this->output( + Translator::translate('You must be logged in first'), + self::STATUS_NEED_AUTH + ); + } + + $id = $this->params()->fromQuery('id'); + $comment = $this->params()->fromQuery('comment'); + if (empty($id) || empty($comment)) { + return $this->output( + Translator::translate('An error has occurred'), self::STATUS_ERROR + ); + } + + $table = new VuFind_Model_Db_Resource(); + $resource = $table->findResource( + $id, $this->params()->fromQuery('source', 'VuFind') + ); + $id = $resource->addComment($comment, $user); + + return $this->output($id, self::STATUS_OK); + } + + /** + * Delete a comment on a record. + * + * @return void + */ + public function deleteRecordComment() + { + $user = $this->getUser(); + if ($user === false) { + return $this->output( + Translator::translate('You must be logged in first'), + self::STATUS_NEED_AUTH + ); + } + + $id = $this->params()->fromQuery('id'); + $table = new VuFind_Model_Db_Comments(); + if (empty($id) || !$table->deleteIfOwnedByUser($id, $user)) { + return $this->output( + Translator::translate('An error has occurred'), self::STATUS_ERROR + ); + } + + return $this->output(Translator::translate('Done'), self::STATUS_OK); + } + + /** + * Get list of comments for a record as HTML. + * + * @return void + */ + public function getRecordCommentsAsHTML() + { + /* TODO + $this->view->driver = Record::load( + $this->params()->fromQuery('id'), + $this->params()->fromQuery('source', 'VuFind') + ); + $html = $this->view->render('record/comments-list.phtml'); + return $this->output($html, self::STATUS_OK); + */ + } + + /** + * Delete multiple items from favorites or a list. + * + * @return void + */ + protected function deleteFavorites() + { + /* TODO + $user = $this->getUser(); + if ($user === false) { + return $this->output( + Translator::translate('You must be logged in first'), + self::STATUS_NEED_AUTH + ); + } + + $listID = $this->params()->fromQuery('listID'); + $ids = $this->params()->fromQuery('ids'); + + if (!is_array($ids)) { + return $this->output( + Translator::translate('delete_missing'), + self::STATUS_ERROR + ); + } + + $this->_helper->favorites->delete($ids, $listID, $user); + return $this->output( + array('result' => Translator::translate('fav_delete_success')), + self::STATUS_OK + ); + */ + } + + /** + * Delete records from a User's cart + * + * @return void + */ + protected function removeItemsCart() + { + // Without IDs, we can't continue + $ids = $this->params()->fromQuery('ids'); + if (empty($ids)) { + return $this->output( + array('result'=>Translator::translate('bulk_error_missing')), + self::STATUS_ERROR + ); + } + Cart::getInstance()->removeItems($ids); + return $this->output(array('delete' => true), self::STATUS_OK); + } + + /** + * Process an export request + * + * @return void + */ + protected function exportFavorites() + { + /* TODO + $format = $this->params()->fromQuery('format'); + $url = Export::getBulkUrl( + $this->view, $format, $this->params()->fromQuery('ids', array()) + ); + $this->view->url = $url; + $this->view->format = $format; + $html = $this->view->render('ajax/export-favorites.phtml'); + return $this->output( + array( + 'result' => Translator::translate('Done'), + 'result_additional' => $html + ), self::STATUS_OK + ); + */ + } + + /** + * Fetch Links from resolver given an OpenURL and format as HTML + * and output the HTML content in JSON object. + * + * @return void + * @author Graham Seaman <Graham.Seaman@rhul.ac.uk> + */ + protected function getResolverLinks() + { + /* TODO + $openUrl = $this->params()->fromQuery('openurl', ''); + + $config = ConfigReader::getConfig(); + $resolverType = isset($config->OpenURL->resolver) + ? $config->OpenURL->resolver : 'other'; + $resolver = new VF_Resolver_Connection($resolverType); + if (!$resolver->driverLoaded()) { + return $this->output( + Translator::translate("Could not load driver for $resolverType"), + self::STATUS_ERROR + ); + } + + $result = $resolver->fetchLinks($openUrl); + + // Sort the returned links into categories based on service type: + $electronic = $print = $services = array(); + foreach ($result as $link) { + switch (isset($link['service_type']) ? $link['service_type'] : '') { + case 'getHolding': + $print[] = $link; + break; + case 'getWebService': + $services[] = $link; + break; + case 'getDOI': + // Special case -- modify DOI text for special display: + $link['title'] = Translator::translate('Get full text'); + $link['coverage'] = ''; + case 'getFullTxt': + default: + $electronic[] = $link; + break; + } + } + + // Get the OpenURL base: + if (isset($config->OpenURL) && isset($config->OpenURL->url)) { + // Trim off any parameters (for legacy compatibility -- default config + // used to include extraneous parameters): + list($base) = explode('?', $config->OpenURL->url); + } else { + $base = false; + } + + // Render the links using the view: + $this->view->openUrlBase = $base; + $this->view->openUrl = $openUrl; + $this->view->print = $print; + $this->view->electronic = $electronic; + $this->view->services = $services; + $html = $this->view->render('ajax/resolverLinks.phtml'); + + // output HTML encoded in JSON object + return $this->output($html, self::STATUS_OK); + */ + } +} -- GitLab