По-умолчанию в социальной сети EasySocial лента новостей отображает последние N записей, кнопки переключения между страницами отсутствуют. С одной стороны хорошо, внизу посетитель сайта видит предложение зарегистрироваться, с другой, мы теряем потенциального читателя и более того, наносим ущерб SEO, а точнее ограничиваем сайт от полноценной индексации его поисковыми системами. Поисковый робот попросту не видит соседних страниц и не знает об их существовании, как и записей, ссылки на которые там присутствуют.
Оригинального решения в настройках от StackIdeas нет. Для решения потребуется самостоятельно отредактировать 3 исходных файла системы и один шаблон.
Кому интересно, пишите на почту через Контакты. Стоимость решения: 10 USD.
Вот это самое интересное и с бубном пришлось плясать очень долго. Но цель достигнута - можно хоть выключить браузер и компьютер, при следующем включении вы будете авторизованы в Администраторе, как если бы вы были на пользовательской части сайта.
Сразу отмечу, что ни один плагин в Интернете не позволяет достигнуть аналогичного результата. Причем в моем случае почему-то даже стандартный KeepAlive не работал, сессия слетала и я переключался на форму логина каждые 15 минут (время жизни сессии по настройкам конфига). Сейчас даже если и сессия слетела, она восстановится. ОК, к делу.
Все правки в коде ищите по комментарию "// cay127".
Файл plugins/authentication/cookie/cookie.php
Правки ниже:
public function onUserAuthenticate($credentials, $options, &$response)
{
// No remember me for admin
if ($this->app->isAdmin())
{
// cay127
// return false;
}
В коде отменяется стандартное ограничение джумлы, хотя функционал "Запомнить пароль" работает для любого раздела сайта одинаково.
Это не все, но объяснять уже не буду, слишком долго.
public function onUserAfterLogout($options)
{
// No remember me for admin
if ($this->app->isAdmin())
{
// cay127
// return false;
}
Далее еще. Кусок кода большой, нужно развернуть:
public function onUserAfterLogin($options)
{
// No remember me for admin
if ($this->app->isAdmin())
{
// cay127
$options[ 'remember' ] = 1;
// return false;
}
if (isset($options['responseType']) && $options['responseType'] == 'Cookie')
{
// Logged in using a cookie
$cookieName = JUserHelper::getShortHashedUserAgent();
// We need the old data to get the existing series
$cookieValue = $this->app->input->cookie->get($cookieName);
$cookieArray = explode('.', $cookieValue);
// Filter series since we're going to use it in the query
$filter = new JFilterInput;
$series = $filter->clean($cookieArray[1], 'ALNUM');
}
elseif (!empty($options['remember']))
{
// Remember checkbox is set
$cookieName = JUserHelper::getShortHashedUserAgent();
// Create an unique series which will be used over the lifespan of the cookie
$unique = false;
do
{
$series = JUserHelper::genRandomPassword(20);
$query = $this->db->getQuery(true)
->select($this->db->quoteName('series'))
->from($this->db->quoteName('#__user_keys'))
->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series));
$results = $this->db->setQuery($query)->loadResult();
if (is_null($results))
{
$unique = true;
}
}
while ($unique === false);
}
else
{
return false;
}
// Get the parameter values
$lifetime = $this->params->get('cookie_lifetime', '60') * 24 * 60 * 60;
$length = $this->params->get('key_length', '16');
// Generate new cookie
$token = JUserHelper::genRandomPassword($length);
$cookieValue = $token . '.' . $series;
// Overwrite existing cookie with new value
$this->app->input->cookie->set(
$cookieName, $cookieValue, time() + $lifetime, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain'), $this->app->isSSLConnection()
);
$query = $this->db->getQuery(true);
// cay127
$query = 'SELECT ' . $this->db->quoteName('id');
$query .= ' FROM ' . $this->db->quoteName('#__user_keys');
$query .= ' WHERE ' . $this->db->quoteName('series') . ' = ' . $this->db->quote($series);
$query .= ' AND ' . $this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username);
$results = $this->db->setQuery($query)->loadObjectList();
if (count($results) !== 0) {
$b_update = true;
} else {
$b_update = false;
}
$query = $this->db->getQuery(true);
// cay127 #
// cay127
// if (!empty($options['remember']))
if ($b_update == false AND !empty($options['remember']))
{
// Create new record
$query
->insert($this->db->quoteName('#__user_keys'))
->set($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username))
->set($this->db->quoteName('series') . ' = ' . $this->db->quote($series))
->set($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName))
->set($this->db->quoteName('time') . ' = ' . (time() + $lifetime));
}
else
{
// Update existing record with new token
$query
->update($this->db->quoteName('#__user_keys'))
->where($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username))
->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series))
->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName));
}
$hashed_token = JUserHelper::hashPassword($token);
$query
->set($this->db->quoteName('token') . ' = ' . $this->db->quote($hashed_token));
$this->db->setQuery($query)->execute();
return true;
}
Файл libraries/cms/application/administrator.php
И теперь самое, что может показаться странным, но джумла почему-то делает язык английским после окончания срока действия временной куки на час. Исправляем и это.
Все, можно наконец-то заняться кодингом, просмотром авто-обновлений статистики в режиме live и не переживать ни о чем.
Update 2014-04-06
Что-то включил сегодня компьютер после сна и попал на форму логина:( Надеюсь не последнее добавление с языком повлияло таким образом, что меня стало выкидывать. Подумаю еще...
Update 2014-08-25
Обновил джумлу, повторил все действия по своей инструкции и ничего не получилось. Видимо забыл еще один файл. Продолжаю.
plugins/system/remember/remember.php
1-е изменение:
public function onAfterInitialise()
{
// No remember me for admin.
if ($this->app->isAdmin())
{
// cay127
// return;
}
и 2-е:
public function onUserLogout($user, $options)
{
// No remember me for admin
if ($this->app->isAdmin())
{
// cay127
// return true;
Короче всегда ищем "No remember me for admin" в консоли через grep и спасибо разработчикам за комментарии.
Этот пост будет первым из серии "Танцы с бубном в Joomla". Почему так? Несмотря на все достоинства среды, возникают проблемы, которые приходится внедрять с помощью внедрения своего кода в начинку самой джумлы, что не есть хорошо, но все же. Собственно это и есть одна из причин поста - сохранить изменения. В коде помечаю такие места с помощью комментария "// cay127" и команда grep мне всегда поможет быстро найти все, что было изменено вручную.
И еще пару слов - да, где-то может плагин был бы лучшим решением, может где-то не докопал до истины, но просто экономил время. Плагин отмечу - не всегда применим и это скоро станет понятно.
После вступления
Проблема вот в чем - джумла строит ссылки следующим образом (автоматически):
Но у меня переменная $_SERVER['HTTP_HOST'] содержит IP адрес, а не название домена вовсе. Т.о. быстрое решение стало следующим: правим файл:
libraries/joomla/uri/uri.php
точка опоры:
public static function getInstance($uri = 'SERVER')
Без лишних подробностей изменения (помним о комментарии):
public static function getInstance($uri = 'SERVER')
{
if (empty(self::$instances[$uri]))
{
// Are we obtaining the URI from the server?
if ($uri == 'SERVER')
{
// Determine if the request was over SSL (HTTPS).
// cay127
if (true OR sset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off'))
{
$https = 's://';
}
else
{
$https = '://';
}
/*
* Since we are assigning the URI from the server variables, we first need
* to determine if we are running on apache or IIS. If PHP_SELF and REQUEST_URI
* are present, we will assume we are running on apache.
*/
if (!empty($_SERVER['PHP_SELF']) && !empty($_SERVER['REQUEST_URI']))
{
// To build the entire URI we need to prepend the protocol, and the http host
// to the URI string.
// cay127
// $theURI = 'http' . $https . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
// [HTTP_HOST] => 212.XX.XX.XX
$theURI = 'http' . $https . 'www.site.ru' . $_SERVER['REQUEST_URI'];
}
Реальный IP и домен скрыты, но суть ясна. Плюс прописал условие всегда использовать только HTTPS.
Анализируя ошибки, которые возникали на сервере, заметил PHP Warning - "imagepng ... permissions denied" при работе pChart 2.0. График должен был записаться в папку с правами 777, но файл там уже был и прав 644 на перезапись не хватало. Выделять 777 на файлы не хотелось, хотя вариант рабочий.
Решением стало определение группы владельца и группы www (пользователь, под которым работает Apache) для нужной папки. Прав на нее теперь хватает 755, а на файлы 644.
Geany невероятно удобный редактор. Однако, и дома и на работе проявилась проблема, что когда размер файла увеличивается и начинаешь использовать фигурные скобки "{}", то ждешь до 5 секунд, пока скобки мигают. Видимо Geany пытается подсветить следующую скобку и пока ее ищет, система подвисает. Странно, последний процессор i7. Видимо дело в видео-карте. Не помогает использование автозавершения скобок. Оно работает только вне других скобок. Вариант с вложенностью "{{}}" не прокатит, внутри внешних "{}" новая скобка завершена автоматически не будет.
Данная проблема напрягает, когда быстро набираешь код и становится некомфортно использовать данный редактор. Заменил Geany на Sublime Text 2. К сожалению, недостаток последнего - это отсутствие списка функций в боковой панели. Список можно вызвать с помощью сочетания клавиш Ctrl+R, но только на время поиска.
Также, с трудом настроил цветовую схему, в которой мне удобно работать. В Geany я использовал немного модифицированную версию Vibrant-ink:
#
# Copyright Jason Wilson <jason.willson(at)gmail(dot)com>
#
# 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.
#
# License linked from Google Projects page:
# http://dev.perl.org/licenses/
#
# Ported to Geany by Matthew Brush <matt(at)geany(dot)org>
#
[theme_info]
name=Vibrant-My
description=Vibrant Ink Theme for Geany
version=1.22.0
author=Jason Wilson <jason.willson(at)gmail(dot)com>
url=http://code.google.com/p/geany-vibrant-ink-theme
[named_styles]
default=0xffffff;0x000000;false;false
error=0xff80c0;0x000000;false;false
# Editor styles
#-------------------------------------------------------------------------------
selection=0x8000ff;0x404040;false;true
current_line=0x0080c0;0x333300;true;false
brace_good=0xffffff;0x50AA15;true;false
brace_bad=0xffffff;0xAA1515;true;false
margin_line_number=0xe4e4e4;0x000000;false;false
margin_folding=0x888a85;0x000000;false;false
fold_symbol_highlight=0xffffff
indent_guide=0xc0c0c0;;false;false
caret=0xffffff;0x112435;false;false
marker_line=0xbbbbbb;0x555753;false;false
marker_search=0xffff00;0xffff00;false;false
marker_mark=0xc00000;0x000000;false;false
call_tips=0xc0c0c0;0xffffff;false;false
white_space=0x424242;;true
# Programming languages
#-------------------------------------------------------------------------------
comment=0xBC9458
comment_doc=0x772cb7;0x070707;false;false
comment_line=comment
comment_line_doc=comment_doc
comment_doc_keyword=comment_doc,bold
comment_doc_keyword_error=comment_doc,italic
number=0xccff33
number_1=number
number_2=number_1
type=0xffffff;;true;false
class=type
function=default
parameter=function
keyword=0xff6600;;true;false
keyword_1=keyword
keyword_2=0xdde93d;;true;false
keyword_3=keyword_1
keyword_4=keyword_1
identifier=default
identifier_1=identifier
identifier_2=identifier_1
identifier_3=identifier_1
identifier_4=identifier_1
string=0x66ff00
string_1=string
string_2=string_1
string_3=default
string_4=default
string_eol=0xcccccc;0x000000;false;false
character=string_1
backtick=string_2
here_doc=string_2
scalar=string_2
label=default,bold
preprocessor=0xdde93d;;true;false
regex=number_1
operator=0xffcc00
decorator=string_1,bold
other=default
# Markup-type languages
#-------------------------------------------------------------------------------
tag=0xff6600;0x000000;false;false
tag_unknown=0xffffff;0x8C0101;true;false
tag_end=0xffffff;0x000000;false;false
attribute=0x99cc99;0x000000;false;false
attribute_unknown=0xffffff;0x000000;false;false
value=0xffffff;0x000000;false;false
entity=0xffffff;0x000000;false;false
# Diff
#-------------------------------------------------------------------------------
line_added=0x339999;0x000000;false;false
line_removed=0x808040;0x000000;false;false
line_changed=0x99cc99;0x000000;false;false
В Sublime Text 2 собрал то, что меня "почти" устраивает на основе Sunburst:
Надеюсь в новой версии Geany проблема мелькающих скобок будет исчерпана. К тому времени, видимо я уже привыкну к новому редактору и уже буду думать о том, стоит ли переезжать обратно на старый.
Необходимо было перенести информацию из 10 XML файлов в базу данных. В каждом файле по 500 тыс. строк. Все было отлично, пока на 10-м файле не возникла ошибка. Алгоритм был следующий:
Как ни странно обычный элемент textarea принял мои 500 тыс. строк, загрузил их и подсказал линию, содержащую ошибку. Как и предполагалось, XML содержал невалидный элемент:

Данный элемент не является HTML-сущностью, поэтому и вызывал ошибку.
Идея понятна, таким alt быть не должен. Я считаю, если alt не задан, то он должен быть равным "названию товара + артикул" (у меня на сайте по-умолчанию артикул включен в название товара).
Virtuemart в функции displayMediaThumb() использует следующий алгоритм:
В случае заданного "Alt текст изображения", значение хранится в переменной $this->file_meta. В ином случае в переменную попадает описание (file_description) или название файла (file_name). Я не стал разбираться что есть описание файла, поэтому к делу.
Сделаем пустой alt равным "названию товара + артикул". В примере перезапишем шаблон страницы товара. Точнее его подшаблон для отображения изображений по товару.
В Virtuemart под Joomla 1.0.15 по-умолчанию на странице подробностей товара "Краткое описание" становилось мета-тегом description. HTML-теги из описания вырезались, как и символ новой строки. В новой версии Virtuemart как видно, мета-тег description = описанию сайта, что неверно. Для того, чтобы повторяющиеся мета-описания не стали ошибками в Веб-мастерах Google и Яндекс необходимо изменить шаблон страницы товара Virtuemart.
Готов уже был переводить даты в int и далее с помощью разницы в секундах и округления находить искомое количество дней. Оказалось, все делается намного проще и с помощью стандартных функций PHP.
Примером применения служит задача по вычислению возраста человека на основе даты рождения и текущего времени. Решается она с помощью класса PHP "The DateTime class" следующим образом:
$date_1 = new DateTime( '1987-10-24' );
$date_2 = new DateTime( 'now' );
$age = $date_1->diff( $date_2 )->y;
После вызова метода diff() возвращается объект класса DateInterval.