Joomla - additional search ordering option - search

Looking at adding an additional search ordering option but not sure how to go about doing it as I have little to no Joomla experience.
Currently the default search ordering options are
Newest First
Oldest First
Most Popular
Alphabetical
Section/Category
I think I have located the code that generates these options in \components\com_search\views\search\view.html.php
$orders = array();
$orders[] = JHTML::_('select.option', 'newest', JText::_( 'Newest first' ) );
$orders[] = JHTML::_('select.option', 'oldest', JText::_( 'Oldest first' ) );
$orders[] = JHTML::_('select.option', 'popular', JText::_( 'Most popular' ) );
$orders[] = JHTML::_('select.option', 'alpha', JText::_( 'Alphabetical' ) );
$orders[] = JHTML::_('select.option', 'category', JText::_( 'Section/Category' ) );
Do I add the additional option here or is this a core file that I shouldn't edit? If so, how can I go about adding an additional search ordering option?
Thank you

Technically you can edit any core file. However you would have to be prepared to readd this edit in every time you update Joomla!
However any default.php file should be copied into and done as a template override. If you're planning on adding to the search functionality - a view.html.php file CAN'T be done as a template override. The best way to do it in this case would be to add a plugin to give this functionality.
For how to add a plugin giving this extra functionality see the documentation pages:
http://docs.joomla.org/Creating_a_search_plugin
http://docs.joomla.org/Creating_a_Smart_Search_plug-in

Related

Filter resources by template variable value with MODx Wayfinder

I have a site that uses Wayfinder to display the latest 3 entries from an Articles blog. Now, I want to only consider those blog entries that are tagged Highlights.
My original Wayfinder call looks like this, nothing spectacular:
[[!Wayfinder? &startId=`296` &level=`1`
&outerTpl=`emptyTpl`
&innerTpl=``
&rowTpl=`thumbnails_formatter`
&ignoreHidden=`1`
&sortBy=`menuindex`
&sortOrder=`DESC`
&limit=`3`
&cacheResults=`0`
]]
as Articles tags are managed via the articlestags TV, I thought that a &where might do the trick, but with no luck yet:
&where=`[{"articlestags:LIKE":"%Highlights%"}]`
does not yield anything. As a sanity check, I tried [{"pagetitle:LIKE":"%something%"}], which worked. Obviously, the problem is that articlestags is not a column of modx_site_content, but I'm not sure about how to put the subquery.
SELECT contentid
FROM modx_site_tmplvar_contentvalues
WHERE tmplvarid=17
AND value LIKE '%Highlights%'
Gave me the right IDs on the sql prompt, but adding it to the Wayfinder call like this gave an empty result again:
&where=`["id IN (SELECT contentid FROM modx_site_tmplvar_contentvalues WHERE tmplvarid=17 AND value LIKE '%Highlights%')"]`
Any ideas on how to achieve this? I'd like to stay with Wayfinder for consistency, but other solutions are welcome as well.
You can just use pdomenu (part of pdoTools) instead Wayfinder
[[!PdoMenu?
&startId=`296`
&level=`1`
&outerTpl=`emptyTpl`
&innerTpl=``
&rowTpl=`thumbnails_formatter`
&ignoreHidden=`1`
&sortBy=`menuindex`
&sortOrder=`DESC`
&limit=`3`
&cacheResults=`0`
&includeTVs=`articlestags`
&where=`[{"TVarticlestags.value:LIKE":"%filter%"}]`
]]
Take a peek at some of the config files [core/components/wayfinder/configs ] - I have not tried it, but it looks as if you can run your select query right in the config & pass the tmplvarid array to the $where variable.
A little playing around led me to a solution: I needed to include the class name (not table name) when referring to the ID:
&where=`["modResource.id IN (SELECT contentid FROM modx_site_tmplvar_contentvalues WHERE tmplvarid=17 AND value LIKE '%Highlights%')"]`
a small test showed that even a simple
&where=`["id = 123"]`
does not work without modResource..
A look at wayfinder.class.php shows the following line, which seems to be the "culprit":
$c->select($this->modx->getSelectColumns('modResource','modResource'));
This method aliases the selected columns - relevant code is in xpdoobject.class.php. The first parameter is the class name, the second a table alias. The effect is that the query selects id AS modResource.id, and so on.
EDIT: final version of my query:
&where=`["modResource.id IN (
SELECT val.contentid
FROM modx_site_tmplvars AS tv
JOIN modx_site_tmplvar_contentvalues AS val
ON tv.id = val.tmplvarid
WHERE tv.name = 'articlestags' AND (
val.value = 'Highlights'
OR val.value LIKE 'Highlights,%'
OR val.value LIKE '%,Highlights'
OR val.value LIKE '%,Highlights,%'
)
)"]`
I don't claim this query is particularly efficient (I seem to recall that OR conditions are bad). Also, MODx won't work with this one if the newlines aren't stripped out. Still, I prefer to publish the query in its well-formatted form.
I used snippet as a parameter for the includeDocs of wayfinder, In my case it was useful because I was need different resources in menu depend on user browser (mobile or desktop)
[[!Wayfinder?
&startId=`4`
&level=`1`
&includeDocs=`[[!menu_docs?&startId=`4`]]`
&outerTpl=`home_menu_outer`
&rowTpl=`menu_row`
]]
and then menu_docs snippet
<?php
if (empty ($startId))
return;
if (!isMobileDevice())
return;
$query = $modx->newQuery('modResource');
$query->innerJoin('modTemplateVarResource','TemplateVarResources');
$query->where(array(
'TemplateVarResources.tmplvarid' => 3,
'TemplateVarResources.value:LIKE' => 'yes',
'modResource.parent' => $startId,
'modResource.deleted' => 0,
'modResource.published' => 1,
'modResource.hidemenu' => 0
));
$resources = $modx->getCollection('modResource', $query);
$ouput = array();
foreach ($resources as $resource)
$output[] = $resource->get('id');
return implode (',', $output);

Customize the search portlet in Plone for specific content types

I'm using the search portlet in certain areas of my website, but I'd like to restrict the results to only search for a specific content type: for example only search the news items, or only show Faculty Staff Directory profiles.
I know you can do this after you get to the ##search form through that "filter" list, but is there a way to start with the filter on, so that the "Live Search" results only show the relevant results (i.e. only news items or only profiles).
I suspect you know it already, but just to be sure: You can globally define which types should be allowed to show up in searchresults in the navigations-settings of the controlpanel, and then export and include the relevant parts to your product's GS-profile-propertiestool.xml.
However, if you would like to have some types excluded only in certain sections, you can customize Products.CMFPlone/skins/plone_scripts/livesearch_reply, which already filters the types, to only show "friendly_types" around line 38 (version 4.3.1) and add a condition like this:
Edit:
I removed the solution to check for the absolute_url of the context, because the context is actually the livesearch_reply in this case, not the current section-location. Instead the statement checks now, if the referer is our section:
REQUEST = context.REQUEST
current_location = REQUEST['HTTP_REFERER']
location_to_filter = '/fullpath/relative/to/siteroot/sectionId'
url_to_filter = str(portal_url) + location_to_filter
types_to_filter = ['Event', 'News Item']
if current_location.find(url_to_filter) != -1 or current_location.endswith(url_to_filter):
friendly_types = types_to_filter
else:
friendly_types = ploneUtils.getUserFriendlyTypes()
Yet, this leaves the case open, if the user hits the Return- or Enter-key or the 'Advanced search...'-link, landing on a different result-page than the liveresults have.
Update:
An opportunity to apply the filtering to the ##search-template can be to register a Javascript with the following content:
(function($) {
$(document).ready(function() {
// Let's see, if we are coming from our special section:
if (document.referrer.indexOf('/fullpath/relative/to/siteroot/sectionId') != -1) {
// Yes, we have the button to toggle portal_type-filter:
if ($('#pt_toggle').length>0) {
// If it's checked we uncheck it:
if ($('#pt_toggle').is(':checked')) {
$('#pt_toggle').click();
}
// If for any reason it's not checked, we check and uncheck it,
// which results in NO types to filter, for now:
else {
$('#pt_toggle').click();
$('#pt_toggle').click();
}
// Then we check types we want to filter:
$("input[value='Event']").click();
$("input[value='News Item']").click();
}
}
})
})(jQuery);
Also, the different user-actions result in different, inconsistent behaviours:
Livesearch accepts terms which are not sharp, whereas the ##search-view only accepts sharp terms or requires the user to know, that you can append an asterix for unsharp results.
When hitting the Enter/Return-key in the livesearch-input, the searchterm will be transmitted to the landing-page's (##search) input-element, whilst when clicking on 'Advanced search...' the searchterm gets lost.
Update:
To overcome the sharp results, you can add this to the JS right after the if-statement:
// Get search-term and add an asterix for blurry results:
var searchterm = decodeURI(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURI('SearchableText').replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1")) + '*';
// Insert new searchterm in input-text-field:
$('input[name=SearchableText]').val(searchterm);
Update2:
In this related quest, Eric Brehault provides a better solution for passing the asterix during submit: Customize Plone search
Of course you can also customize the target of advanced-search-link in livesearch_reply, respectively in the JS for ##search, yet this link is rather superfluous UI-wise, imho.
Also, if you're still with Archetypes and have more use-cases for pre-filtered searchresults depending on the context, I can recommend to have a look at collective.formcriteria, which allows to define search-criteria via the UI. I love it for it's generic and straightforward plone-ish approach: catalogued indizi and collections. In contradiction to eea.facetednavigation it doesn't break accessibility and can be enhanced progressively with some live-search-js-magic with a little bit of effort, too. Kudos to Ross Patterson here! Simply turn a collection (old-style) into a searchform by changing it's view and it can be displayed as a collection-portlet, as well. And you can decide which criteria the user should be able to change or not (f.e. you hide the type-filter and offer a textsearch-input).
Watch how the query string changes when you use the filter mechanism on the ##search page. You're simply adding/subtracting catalog query criteria.
You may any of those queries in hidden fields in a search form. For example:
<form ...>
....
<input type="hidden" name="portal_type" value="Document" />
</form>
The form on the query string when you use filter is complicated a bit by its record mechanism, which allows for some min/max queries. Simple filters are much easier.

ExpressionEngine 1 to ExpressionEngine 2 Upgrade with nGen File Field

I'm about to do an ExpressionEngine v1 to ExpressionEngine v2 Upgrade with lots of data stored in nGen File Fields.
What are the steps I need to take pre and post upgrade to get this data working correctly with the EE2 SafeCracker File field?
After upgrading to EE2, find each ex-nGen File field and change its Field Type to File, and run this SQL query:
UPDATE exp_channel_data
SET field_id_X = CONCAT('{filedir_Y}', field_id_X)
WHERE field_id_X != ''
AND field_id_X NOT LIKE '{filedir_%'
Replace “X” with your File field’s ID (you can get that from exp_channel_fields), and Y with the upload preference ID that nGen File Field was set to.
If you had Matrix installed in EE1, upgrade to Matrix 2/EE2 and do the same for any ex-nGen File columns, using this SQL query instead:
UPDATE exp_matrix_data
SET col_id_X = CONCAT('{filedir_Y}', col_id_X)
WHERE col_id_X != ''
AND col_id_X NOT LIKE '{filedir_%'
Again, X == your Matrix column ID (you can get that from exp_matrix_cols), and Y == your upload preference ID.
(Credit goes to Rob Sanchez, of course.)
I wrote this for a site as well - no matrix support in here but it works quickly and correctly for regular fields.
For the array, the left column is each field ID that you changed to text pre-upgrade, and needs to be changed to file post upgrade, and the right side is the X of filedir_X for the file upload dir you want to attach to the field
// Insert file upload directories
$array = array(
'16' => '1',
'22' => '1',
'121' => '3',
'58' => '1',
'67' => '1',
'68' => '1',
'71' => '1',
'76' => '1',
'78' => '1',
'94' => '1',
'99' => '1',
'108' => '3',
'109' => '3',
'110' => '3',
'139' => '1'
);
foreach($array as $field_id => $dir_id) {
$q_entries = $this->EE->db->query("SELECT entry_id, field_id_{$field_id} as 'field' from exp_channel_data where field_id_{$field_id} != '' order by entry_id asc");
if ($q_entries->num_rows() > 0) {
echo '<h3>field_id_'.$field_id.'</h3>';
foreach($q_entries->result_array() as $entry) {
echo $entry['entry_id'];
$filename = trim('{filedir_'.$dir_id.'}'.$entry['field']);
echo ' - '.$filename.'<br/>';
$data = array(
'field_id_'.$field_id => $filename,
);
$sql = $this->EE->db->update_string('exp_channel_data', $data, "entry_id = '{$entry['entry_id']}'");
$this->EE->db->query($sql);
}
}
}
echo 'done';
I have an entire blog post about this which is based off of my experience and the thread that Brandon referenced in his answer. Blog post here.
I've posted something to GitHub that should help anyone facing this process. It's EE1 template code (it needs PHP enabled on either output or input) that shows two things:
Firstly, it displays a tabular overview of all custom fields in the system. This is for reference, to help when you're trying to find all instances of a certain fieldtype. It covers normal EE fields, Fieldframe fieldtypes, and even Matrix columns.
Secondly, each time an nGen File field is encountered, the template generates the MySQL code you'll need to use (after you upgrade to EE2) to alter the data in said fields to the format required by EE2's native File field. These queries are only displayed, not run. The idea is that you save the queries somewhere, run the EE1->EE2 upgrade, and then run the saved queries when ready.
Needless to say, backup, Backup, BACKUP. The template code does not modify your site database in any way, and has been tested and displays what its supposed to just fine. However, while the MySQL queries it generates (for you to copy and manually run later) match what Brandon recommended in his answer, I have yet to actually test those queries in action.
The best way to do this is to proceed carefully and make backups of your database at each step of the way. I have previously written a blog post on this but will elaborate further.
Step 1: Before running your upgrade change all ngen field types to text, don't worry the data won't be lost.
Step 2: Next upgrade ExpressionEngine as per the official docs and then go back into each field and change them to the first party file type.
The next step involves a little bit of database manipulation, but it's just copying and paste so don't worry.
Step 3: Do make a back up of your database before you proceed just in case.
Step 4: This next step depends whether your original nGen file field was in a standard Channel Field or a Matrix Field.
Now go into your database and Replace “X” with your File field’s ID (you can get that from exp_channel_fields), and Y with the upload preference ID that nGen File Field was set to.
(To find your upload preference ID in your control panel, go to Content > Files > File Upload Preferences. Choose the ID column on the left that matches the file upload location.)
4a: If updating standard Channel Fields, use this query
UPDATE exp_channel_data
SET field_id_X = CONCAT('{filedir_Y}', field_id_X)
WHERE field_id_X != ''
AND field_id_X NOT LIKE '{filedir_%'
4b: For matrix, fields run this query instead
UPDATE exp_matrix_data
SET col_id_X = CONCAT('{filedir_Y}', col_id_X)
WHERE col_id_X != ''
AND col_id_X NOT LIKE '{filedir_%'
X == your Matrix column ID (you can get that from exp_matrix_cols), and Y == your upload preference ID.
Credit to Brandon Kelly and Rob Sanchez.
Additionally, the same procedure can be used for other add-ons that don't exist in EE2. Convert to text before the upgrade and then convert to a new equivalent field type post upgrade if needed. For more help: Click here

Custom field name mapping in expressionengine

I am making some changes to a site using ExpressionEngine, there is a concept I can't seem to quite understand and am probably coding workarounds that have pre-provided methods.
Specifically with channels and members, the original creator has added several custom fields. I am from a traditional database background where each column in a table has a specific meaningful name. I am also used to extending proprietary data by adding a related table joined with a unique key, again, field names in the related table are meaningful.
However, in EE, when you add custom fields, it creates them in the table as field_id_x and puts an entry into another table telling you what these are.
Now this is all nice from a UI point of view for giving power to an administrator but this is a total headache when writing any code.
Ok, there's template tags but I tend not to use them and they are no good in a database query anyway.
Is there a simple way to do a query on say the members table and then address m_field_1 as what its really called - in my case "addresslonglat".
There are dozens of these fields in the table I am working on and at the moment I am addressing them with fixed names like "m_field_id_73" which means nothing.
Anybody know of an easy way to bring the data and its field names together easily?
Ideally i'd like to do the following:
$result = $this->EE->db->query("select * from exp_member_data where member_id = 123")->row();
echo $result->addresslonglat;
Rather than
echo $result->m_field_id_73;
This should work for you:
<?php
$fields = $data = array();
$member_fields = $this->EE->db->query("SELECT m_field_id, m_field_name FROM exp_member_fields");
foreach($member_fields->result_array() as $row)
{
$fields['m_field_id_'.$row['m_field_id']] = $row['m_field_name'];
}
$member_data = $this->EE->db->query("SELECT * FROM exp_member_data WHERE member_id = 1")->row();
foreach($member_data as $k => $v)
{
if($k != 'member_id')
{
$data[$fields[$k]] = $v;
}
}
print_r($data);
?>
Then just use your new $data array.

How do I generate document excerpts using Sphinx and the Sphinx PHP API?

I am trying to generate search excerpts from the full text of indexed documents. I am using Sphinx V2.02. My Sphinx indexes work fine and regular results are no problem.
I am loading the document off disk so I've set load_files to TRUE. I've tried both the web path of the file and the direct Linux file path.
Here is my excerpt code:
$options = array( 'load_files' => TRUE );
$docs = array( /files/0/123/123.txt );
$words = 'gears';
$excerpts = $sphinxclient->BuildExcerpts( $docs, 'files', $words, $options );
Here is the Sphinx Documentation for Generating Excerpts.
BuildExcerpts returns false every time, rather than returning excerpts. What's happening? Should I be executing this somehow at the same time as my regular query? I've been executing BuildExcerpts on each document returned from the main query.
The code to BuildExcertps above is correct.
The problem is that my 'files' index is distributed and the Sphinx BuildExcerpts call doesn't like that. It seems that BuildExcerpts is really just referencing the config for that index, so you have to reference one of the actual indexes, rather than the distributed index in the BuildExcerpts() call.
For example: I have my files index split into 5 shards, files_0, files_1, etc. Using 'files' as my index breaks BuildExcerpts. Using files_0 or any of my shards works fine.
$options = array( 'load_files' => TRUE );
$docs = array( /files/0/123/123.txt );
$words = 'gears';
$excerpts = $sphinxclient->BuildExcerpts( $docs, 'files_0', $words, $options );

Resources