Блог

Руковожу разработкой в стартапе, помогаю бизнесу запускать продукты. Наношу пользу разработчикам и тем, кто с ними работает. Развлекаюсь с техническим SEO.

Как снять жильё в Португалии: полный гайд и инструкция

Olá, Bom Dia! Сегодня расскажу про рынок аренды в Португалии: как искать жильё, чего ждать от этого процесса, как увеличить свои шансы снять то, что хочется.

Дисклеймер. Я не юрист, и делюсь своим опытом, так как весь процесс аренды жилья проходил сам, сталкивался с ошибками, задавал вопросы в чатах и своим коллегам, которые уже арендовали жильё. Если у вас сложный кейс и вы в чём-то не уверены, то лучше проконсультироваться со специалистами: юристом, риелтором или «помогалой» (это такие русскоговорящие ребята, которые уже освоились в Португалии и помогают приезжим всякими услугами по релокации, в том числе где нужно знание языка).

Рандомная фотка двора одной из квартир, которые я смотрел в 2022. Жильцы дома могут пользоваться бассейном и устраивать там вечеринки 🙂
Читать далее

Ошибка «WP_Scripts:localize был вызван неправильно» в WordPress

После обновления WordPress до 5.7 у многих стала появляться ошибка:

Функция WP_Scripts:localize вызвана неправильно. Параметр $l10n должен быть массивом. Для передачи произвольных данных в скрипты используйте функцию wp_add_inline_script ().

Что это за ошибка

Во-первых, важно понимать, что это не ошибка, а Notice — простое предупреждение, что что-то работает не так, но ничего фатального и критичного в этом нет. Обычно эта информация используется исключительно разработчиками для отладки.

Во-вторых, это сообщение появилось в версии 5.7 и отображается только в том случае, если у вас включён режим отладки: в wp-config.php у вас указано define ('WP_DEBUG', true).

В-третьих, ошибку скорее всего вызывает какой-то плагин или тема, которые ещё не обновились до полной поддержки версии 5.7.

Как убрать ошибку

Способов устранения ошибки несколько:

  1. Отключить плагины или темы, которые вызывают ошибку. Не самый подходящий способ решения проблемы, поэтому двигаемся дальше 🙂
  2. Отключить режим отладки
    Для этого в файле wp-config.php нужно указать define ('WP_DEBUG', false);.
  3. Если режим отладки всё же необходим, можно отключить только подобные сообщения об ошибках, а остальные полезные штуки отладки оставить. Сделать это можно так:
add_filter( 'doing_it_wrong_trigger_error', function () { return false; }, 10, 0 );

Нулевая страница в пагинации WordPress приводит к дублям страниц

В процессе работы над одним из Вордпрес-сайтов столкнулся с такой проблемой: если подставить в пагинацию нулевой номер страницы, то открывается страница категории. Таким образом появляется огромное количество дублей:

https://domain.ru/category-name
https://domain.ru/category-name/page/0
https://domain.ru/category-name/page/00
https://domain.ru/category-name/page/000
... и так далее

Все такие УРЛы отдают 200 код ответа. С одной стороны, canonical у всех этих страниц выводится корректный — https://domain.ru/category-name. А с другой, никогда не знаешь как этот каноникал учтёт поисковик.

Путём исследования нашёл такое решение проблемы:

add_action( 'template_redirect', 'arutyunov_redirects_from_zero_number_pages', 1 );
function arutyunov_redirects_from_zero_number_pages() {
	global $wp_query;

	// Если в параметрах передан номер страницы, и он равен 0,
	// то «насильно» меняем query-параметр пагинации на 1,
	// в таком случае дефолтная Вордпрес-функция `redirect_canonical`
	// подумает, что пользователь открывает /page/1 и перенаправит
	// на страницу категории без page-параметра
	if( isset($wp_query->query['paged']) && intval($wp_query->query['paged']) === 0 ) {
		$wp_query->query_vars['paged'] = 1;
	}
}

Код нужно добавить в файл functions.php вашей темы.

Важно! Рекомендую проверить работу этого кода в дев-окружении, а не на основном сайте. Могут возникнуть какие-то конфликты с кодом вашей темы или ваших плагинов, что может привести к неожиданным редиректам.

На что стоит обратить внимание:

  • у экшена установлен приоритет 1, чтобы он сработал раньше, чем встроенная функция redirect_canonical;
  • параметр paged у query заполнен лишь в том случае, если мы передаём GET-параметр page
  • мы перезаписываем paged в query_vars, чтобы Вордпрес подумал, что мы пытаемся открыть /page/1, с которой по-умолчанию всегда срабатывает редирект на саму категорию.

Ошибка «Failed to find definition for url (#pattern0)» в Flutter SVG

У нас на проекте используется библиотека flutter_svg, которая периодически шлёт в Сентри ошибки типа такой:

FlutterError:
Failed to find definition for url(#paint0_radial)

При этом некоторые иконки после загрузки не отображались в приложении. Решил выяснить, что не так. Пошерстил гугл, наткнулся на обсуждения в гитхабе, на стэковерфлоу.

В моём случае всё оказалось просто: нужно перенести секцию defs в начало SVG-файла, чтобы исправить ошибку. Смотрите пример:

Было так

<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
	<path d="M8.00064 12C8.00064 9.79094 9.79094 7.99968 12 7.99968C14.2091 7.99968 16.0003 9.79094 16.0003 12C16.0003 14.2091 14.2091 16.0003 12 16.0003C9.79094 16.0003 8.00064 14.2091 8.00064 12V12ZM5.83814 12C5.83814 15.4032 8.5968 18.1619 12 18.1619C15.4032 18.1619 18.1618 15.4032 18.1618 12C18.1618 8.5968 15.4032 5.83814 12 5.83814C8.5968 5.83814 5.83824 8.59661 5.83824 12H5.83814ZM16.9659 5.59382C16.9658 5.87863 17.0501 6.15707 17.2082 6.39394C17.3664 6.63081 17.5912 6.81547 17.8543 6.92457C18.1174 7.03366 18.4069 7.06229 18.6862 7.00684C18.9656 6.95139 19.2222 6.81435 19.4237 6.61304C19.6252 6.41173 19.7624 6.15521 19.8181 5.8759C19.8738 5.59658 19.8454 5.30704 19.7365 5.04387C19.6276 4.7807 19.4431 4.55573 19.2064 4.3974C18.9696 4.23908 18.6913 4.15451 18.4065 4.1544H18.4059C18.0241 4.15458 17.658 4.30627 17.388 4.57616C17.1181 4.84605 16.9662 5.21207 16.9659 5.59382V5.59382ZM7.152 21.7678C5.98205 21.7145 5.34614 21.5196 4.92355 21.355C4.36329 21.1369 3.96355 20.8771 3.54326 20.4574C3.12297 20.0377 2.86281 19.6383 2.64566 19.0781C2.48093 18.6557 2.28605 18.0196 2.23286 16.8496C2.17469 15.5847 2.16307 15.2048 2.16307 12.0002C2.16307 8.79562 2.17565 8.4167 2.23286 7.15075C2.28614 5.9808 2.48246 5.34595 2.64566 4.9223C2.86377 4.36205 3.12355 3.9623 3.54326 3.54202C3.96297 3.12173 4.36233 2.86157 4.92355 2.64442C5.34595 2.47968 5.98205 2.2848 7.152 2.23162C8.41689 2.17344 8.79686 2.16182 12 2.16182C15.2031 2.16182 15.5835 2.17421 16.8494 2.23181C18.0194 2.28509 18.6542 2.48141 19.0779 2.64461C19.6381 2.86176 20.0379 3.1225 20.4582 3.54221C20.8785 3.96192 21.1377 4.36224 21.3558 4.9225C21.5205 5.3449 21.7154 5.98099 21.7686 7.15094C21.8267 8.4169 21.8384 8.79581 21.8384 12.0004C21.8384 15.205 21.8267 15.5839 21.7686 16.8498C21.7153 18.0198 21.5194 18.6557 21.3558 19.0783C21.1377 19.6385 20.8779 20.0383 20.4582 20.4576C20.0385 20.8769 19.6381 21.1371 19.0779 21.3552C18.6555 21.5199 18.0194 21.7148 16.8494 21.768C15.5845 21.8262 15.2046 21.8378 12 21.8378C8.79542 21.8378 8.41651 21.8262 7.152 21.768V21.7678ZM7.05264 0.0726721C5.77517 0.130848 4.90224 0.333408 4.1399 0.630048C3.35088 0.936384 2.68205 1.34736 2.01417 2.01418C1.3463 2.68099 0.936384 3.34992 0.630048 4.1399C0.333408 4.90272 0.130848 5.77517 0.072672 7.05264C0.0135361 8.33213 0 8.74118 0 12C0 15.2588 0.0135361 15.6679 0.072672 16.9474C0.130848 18.2249 0.333408 19.0973 0.630048 19.8601C0.936384 20.6491 1.3464 21.3193 2.01417 21.9858C2.68195 22.6524 3.34992 23.0628 4.1399 23.37C4.90368 23.6666 5.77517 23.8692 7.05264 23.9273C8.3328 23.9855 8.74118 24 12 24C15.2588 24 15.6679 23.9865 16.9474 23.9273C18.2249 23.8692 19.0973 23.6666 19.8601 23.37C20.6491 23.0628 21.3179 22.6526 21.9858 21.9858C22.6537 21.319 23.0627 20.6491 23.3699 19.8601C23.6666 19.0973 23.8701 18.2248 23.9273 16.9474C23.9855 15.6669 23.999 15.2588 23.999 12C23.999 8.74118 23.9855 8.33213 23.9273 7.05264C23.8691 5.77507 23.6666 4.90224 23.3699 4.1399C23.0627 3.35088 22.6526 2.68205 21.9858 2.01418C21.319 1.3463 20.6491 0.936384 19.861 0.630048C19.0973 0.333408 18.2248 0.129888 16.9483 0.0726721C15.6686 0.0142081 15.2598 0 12.0014 0C8.7431 0 8.33328 0.0135361 7.05312 0.0726721" fill="url(#paint0_radial)"/>
	<defs>
		<radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-0.405493 25.6257) scale(36.183 36.1844)">
			<stop offset="0.09" stop-color="#FA8F21"/>
			<stop offset="0.78" stop-color="#D82D7E"/>
		</radialGradient>
	</defs>
</svg>

Стало так

<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
	<defs>
		<radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-0.405493 25.6257) scale(36.183 36.1844)">
			<stop offset="0.09" stop-color="#FA8F21"/>
			<stop offset="0.78" stop-color="#D82D7E"/>
		</radialGradient>
	</defs>
	<path d="M8.00064 12C8.00064 9.79094 9.79094 7.99968 12 7.99968C14.2091 7.99968 16.0003 9.79094 16.0003 12C16.0003 14.2091 14.2091 16.0003 12 16.0003C9.79094 16.0003 8.00064 14.2091 8.00064 12V12ZM5.83814 12C5.83814 15.4032 8.5968 18.1619 12 18.1619C15.4032 18.1619 18.1618 15.4032 18.1618 12C18.1618 8.5968 15.4032 5.83814 12 5.83814C8.5968 5.83814 5.83824 8.59661 5.83824 12H5.83814ZM16.9659 5.59382C16.9658 5.87863 17.0501 6.15707 17.2082 6.39394C17.3664 6.63081 17.5912 6.81547 17.8543 6.92457C18.1174 7.03366 18.4069 7.06229 18.6862 7.00684C18.9656 6.95139 19.2222 6.81435 19.4237 6.61304C19.6252 6.41173 19.7624 6.15521 19.8181 5.8759C19.8738 5.59658 19.8454 5.30704 19.7365 5.04387C19.6276 4.7807 19.4431 4.55573 19.2064 4.3974C18.9696 4.23908 18.6913 4.15451 18.4065 4.1544H18.4059C18.0241 4.15458 17.658 4.30627 17.388 4.57616C17.1181 4.84605 16.9662 5.21207 16.9659 5.59382V5.59382ZM7.152 21.7678C5.98205 21.7145 5.34614 21.5196 4.92355 21.355C4.36329 21.1369 3.96355 20.8771 3.54326 20.4574C3.12297 20.0377 2.86281 19.6383 2.64566 19.0781C2.48093 18.6557 2.28605 18.0196 2.23286 16.8496C2.17469 15.5847 2.16307 15.2048 2.16307 12.0002C2.16307 8.79562 2.17565 8.4167 2.23286 7.15075C2.28614 5.9808 2.48246 5.34595 2.64566 4.9223C2.86377 4.36205 3.12355 3.9623 3.54326 3.54202C3.96297 3.12173 4.36233 2.86157 4.92355 2.64442C5.34595 2.47968 5.98205 2.2848 7.152 2.23162C8.41689 2.17344 8.79686 2.16182 12 2.16182C15.2031 2.16182 15.5835 2.17421 16.8494 2.23181C18.0194 2.28509 18.6542 2.48141 19.0779 2.64461C19.6381 2.86176 20.0379 3.1225 20.4582 3.54221C20.8785 3.96192 21.1377 4.36224 21.3558 4.9225C21.5205 5.3449 21.7154 5.98099 21.7686 7.15094C21.8267 8.4169 21.8384 8.79581 21.8384 12.0004C21.8384 15.205 21.8267 15.5839 21.7686 16.8498C21.7153 18.0198 21.5194 18.6557 21.3558 19.0783C21.1377 19.6385 20.8779 20.0383 20.4582 20.4576C20.0385 20.8769 19.6381 21.1371 19.0779 21.3552C18.6555 21.5199 18.0194 21.7148 16.8494 21.768C15.5845 21.8262 15.2046 21.8378 12 21.8378C8.79542 21.8378 8.41651 21.8262 7.152 21.768V21.7678ZM7.05264 0.0726721C5.77517 0.130848 4.90224 0.333408 4.1399 0.630048C3.35088 0.936384 2.68205 1.34736 2.01417 2.01418C1.3463 2.68099 0.936384 3.34992 0.630048 4.1399C0.333408 4.90272 0.130848 5.77517 0.072672 7.05264C0.0135361 8.33213 0 8.74118 0 12C0 15.2588 0.0135361 15.6679 0.072672 16.9474C0.130848 18.2249 0.333408 19.0973 0.630048 19.8601C0.936384 20.6491 1.3464 21.3193 2.01417 21.9858C2.68195 22.6524 3.34992 23.0628 4.1399 23.37C4.90368 23.6666 5.77517 23.8692 7.05264 23.9273C8.3328 23.9855 8.74118 24 12 24C15.2588 24 15.6679 23.9865 16.9474 23.9273C18.2249 23.8692 19.0973 23.6666 19.8601 23.37C20.6491 23.0628 21.3179 22.6526 21.9858 21.9858C22.6537 21.319 23.0627 20.6491 23.3699 19.8601C23.6666 19.0973 23.8701 18.2248 23.9273 16.9474C23.9855 15.6669 23.999 15.2588 23.999 12C23.999 8.74118 23.9855 8.33213 23.9273 7.05264C23.8691 5.77507 23.6666 4.90224 23.3699 4.1399C23.0627 3.35088 22.6526 2.68205 21.9858 2.01418C21.319 1.3463 20.6491 0.936384 19.861 0.630048C19.0973 0.333408 18.2248 0.129888 16.9483 0.0726721C15.6686 0.0142081 15.2598 0 12.0014 0C8.7431 0 8.33328 0.0135361 7.05312 0.0726721" fill="url(#paint0_radial)"/>
</svg>

Такое поведение связано с тем, как плагин парсит SVG. Для полного понимания почему так, а не иначе, почитайте обсуждение на гитхабе.

Как прокинуть переменную из webpack.mix.js в SASS/SCSS-файл

На одном из проектов использую Laravel Mix для сборки стилей и скриптов. Потребовалось прокидывать в SCSS-файл переменную из webpack.mix.js, чтобы в итоговый CSS-файл подставлялся либо дев, либо прод домен.

Путём гугления, чтения документации и экспериментов нашёл решение. Во-первых, проставляем нужную ENV-переменную в файле package.json:

"development": "cross-env NODE_ENV=development ENV_DOMAIN=http://my-dev-domain.loc node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",

То есть я просто добавил ENV_DOMAIN=http://my-dev-domain.loc в эту строку. Не забываем и для production указать свой домен.

Во-вторых, подтягиваем эту переменную окружения в webpack.mix.js и прокидываем её в SASS/SCSS-файл:

mix.setPublicPath('public_html/')
        .sass('input.scss', 'assets/css/output.css', {
            data: "$domain: '" + process.env.ENV_DOMAIN + "';"
        })

Всё. Теперь у вас в input.scss доступна переменная $domain, которую можно использовать вот так:

@font-face {
  font-family: 'app-name-icons';
  src:  local('app-name-icons'),
  url('#{$domain}/full-paths-to-assets/fonts/app-name-icons.eot?1#iefix') format('embedded-opentype');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}

Для «голого» вебпака всё делается примерно также, только переменная прокидывается через опции sass-loader.

Ошибка Undefined index: name в файле PackageManifest. php при установке зависимостей Composer

Недавно при установке Laravel-приложения столкнулся с ошибкой:

In PackageManifest.php line 122:
Undefined index: name 

Судя по гуглу, проблема существует последние полгода. Связано это с выходом второй версии композера, а также с регулярными обновлениями ядра Laravel.

Сначала попробуйте починить с помощью обновления композера до стабильной версии:

composer self-update --stable

Если это не поможет, то попробуйте откатиться до первой версии композера:

sudo composer self-update --1

В моём случае сработал откат до первой версии.

Ошибка invalid_grant при авторизации через Apple ID (iOS, бекенд)

Реализовывал я вход через Apple ID в одном приложении на Flutter, а бекенд был на Laravel. Научили приложение генерировать токен, отправляли токен в бекенд, бекенд проверял его у Эпл, а Эпл в ответ присылал invalid_grant.

Читать далее

Как настроить уведомления из Github Actions в Discord

Настраиваем уведомления о событиях через Github Actions в Discord. Мы с командой стали использовать Discord для рабочей коммуникации, в одном из каналов хотели настроить уведомления из Github-репозитория о результатах работы CI/CD.

Что мы сделаем: когда появился новый пул-реквест или появились коммиты в открытом пул-реквесте, нужно прогнать тесты и отправить результат в Дискорд.

Для работы с Дискордом из Гитхаб Экшнс будем использовать готовый экшн appleboy/discord-action@master. Делаем вот так в файле .github/workflows/main.yml:

name: Testing
on: [pull_request]

jobs:
  testing:
    name: Testing
    runs-on: ubuntu-18.04
    steps:
      - name: Send start notification to Discord
        uses: appleboy/discord-action@master
        with:
          webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
          webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
          color: "#8b9b9c"
          username: "GitHub Bot"
          message: |
            Tests started by 👤 ${{ github.actor }}
            From `${{ github.head_ref }}` to `${{ github.base_ref }}`
            Event `${{ github.event_name }}`
            PR ${{ github.event.number }}: ${{ github.event.pull_request.url }}

После этого нужно добавить сикреты в раздел Secrets в настройках репозитория в Гитхабе. Поскольку сикреты содержат в себе информацию о вебхуке Дискорда, то сначала создадим вебхук. Переходим в настройки сервера, идём в вебхуки и создаём вебхук:

Создаём вебхук в Дискорде

После того, как сделали вебхук — копируйте ссылку и переходите по ней. Ссылка выглядит примерно так: https://discordapp.com/api/webhooks/{id}/{token}. Копируем из ссылки id и token, используем их для добавления сикретов в настройках репозитория в гитхабе.

После создания сикретов создайте пул-реквест, проверьте в разделе Actions в Гитхабе, что экшн заработал. После этого проверьте, что уведомление пришло в канал. Выглядит в результате это так:

Результат работы уведомлений в Дискорде

Что за приставка WPSE у многих WordPress-функций

Часто в сети или в коде каких-то плагинов или тем можно встретить функции с подобным названием: wpse231448_sanitize_title_with_dashes. У разработчиков возникает резонный вопрос — что за префикс wpse, ещё и с какими-то цифрами.

На самом деле всё просто: WPSE расшифровывается как WordPress Stack Exchange. Это сайт с вопросами о разработке на ВордПресс. Префикс для таких функций позволяет:

  • понять, что вопрос об этой фиче обсуждался на WPSE,
  • цифры — это ID вопроса, и можно из названия функции получить ссылку на обсуждение и посмотреть контекст обсуждения; в моём примере ссылка получится такая: https://wordpress.stackexchange.com/questions/231 448/

Более того, есть пользователи, которые активно отвечают на вопросы сообщества, собирают коллекции из таких функций. Вот, например, набор небольших однофайловых плагинов от Кристофера Дэвиса, которые собраны им самим из его же ответов.

Мне не удалось найти источников, с которых началась эта практика введения такого именования функций, но есть обсуждения о том, как расширить эту практику и добавить новых возможностей :-).

Правильный способ заставить Vue перендерить компонент

Эта публикация является любительским переводом статьи «The correct way to force Vue to re-render a component» от Майкла Тиссена.

Иногда бывает так, что реактивности Vue не хватает, и вам нужно перерендерить компонент. Или вам нужно сбросить DOM и начать сначала.

Так как же правильным образом заставить Vue перерендерить компонент?

Ответ прост. Правильный путь — использовать :key в компоненте. Когда вам нужно перерендерить компонент, вы просто меняете значение ключа и Vue обновит компонент.

Довольно просто, не так ли? Есть также и другие способы:

  • Худший вариант: обновить всю страницу,
  • Плохой вариант: использовать v-if хак,
  • Чуть лучше: использовать встроенный в Vue метод forceUpdate,
  • Лучший вариант: использовать изменение ключа.

Обратите внимание! Если вы столкнулись с ситуацией, в которой вам нужно перерендерить компонент, вероятно, есть более правильный способ решить задачу. Скорее всего, вы не до конца разобрались с чем-то из этого: 1. Реактивность Vue, 2. Computed-свойства, 3. Watch-свойства, 4. Не используете :key атрибут для v-for.

Читать далее