Перенос работающего сайта WordPress с Mysql на PostgreeSQL

Задача: перенести рабочий, наполненный постами, сайт на движке WordPress с БД MySQL на PostgreeSQL

Инструментарий:

1) pgloader — утилита для переноса данных из баз данных MySQL в PostgreeSQL

Особенности о которые спотыкался:

  • для успешного переноса необходимо чтобы пользователь в терминале под которым осуществляется перенос имел максимальные права в БД и имел доступ «без пароля» для входа в консоль pgsql
  • при переносе создается схема с именем БД, а не заливается по умолчанию в схему public. Что в принципе для нашей ситуации даже будет плюсом.
  • Перед переносом, БД уже должна быть создана в PostgreeSQL

Синтаксис:

pgloader mysql://login:password@host/db_name pgsql:///db_name

2) Плагин PG4WP который на «лету» исправляет запросы к MySQL на PostgreeSQL

Особенности:

  • работает только при «чистой» установке. Т.е. WordPress должен быть штатно проинсталлирован с нуля
  • Инсталляция корректно работает только до версии WordPress 5.9
  • Плагин заброшен, последнее обновление — 3 года назад

Итак, общий алгоритм переноса рабочего WordPress получается такой:

  1. Переносим при помощи pgloader БД в отдельную схему
  2. Заново устанавливаем WordPress 5.9 с плагином PG4WP
  3. Удаляем из схемы public wp_posts (это если только статьи переносим) и переносим её из схемы созданной pgloader в public
  4. Пробуем обновить штатно WordPress

Точно понадобится:

-- перенос таблицы из отдной схемы в другую
ALTER TABLE wp_base.wp_users
SET SCHEMA public 

Удаление всех таблиц в схеме:

-- удаляем таблицы
DO $$ DECLARE
  r RECORD;
BEGIN
  FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP
    EXECUTE 'DROP TABLE ' || quote_ident(r.tablename) || ' CASCADE';
  END LOOP;
END $$;
-- удаляем последовательности
DO $$ DECLARE
    r RECORD;
BEGIN
    FOR r IN (SELECT relname FROM pg_class where relkind = 'S') LOOP
        EXECUTE 'DROP SEQUENCE IF EXISTS ' || quote_ident(r.relname) || ' CASCADE';
    END LOOP;
END $$;

Поменять схему всех таблиц:

DO $$ DECLARE
  r RECORD;
BEGIN
  FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'старая_схема') LOOP
    EXECUTE 'ALTER TABLE старая_схема.' || quote_ident(r.tablename) || ' SET SCHEMA новая_схема ';
  END LOOP;
END $$;

В принципе пока вполне рабочая схема

Нарушена целостность структуры конфигурации 1С

Уже ранее была статья на эту тему, но в этом случае все советы не помогли. Помогли другие:

  1. Найти не битую cf этой версии конфигурации
  2. Зайти битую конфигурацию, снять её с поддержки
  3. Объединить битую конфигурацию с не битой cf. В время объединения 1С предложит снова поставить её на поддержку
  4. После этой манипуляции, битая становится не битой и позволяет далее нормально штатно обновлятся

Flutter: динамическое количество строк / колонок в виджетах

Для динамического построения виджетов удобно использовать List.generate, который в качестве параметра принимает количество элементов, а на вход функции-кэлбека — текущий элемент перебора. Использовать можно например как-то так:

List <Widget> MyBoardView(){
    int poz=0;
    return List<Widget>.generate(lh, (int index_h){
      return Row(
        children:
        List<Widget>.generate(lw, (int index_w){
          poz++;
          return
            Padding(
                padding: EdgeInsets.all(1),
                child:
                Container(
                    color: Colors.orange,
                    height: MediaQuery.of(context).size.height/lh-2 ,
                    width: MediaQuery.of(context).size.width/lw-2,
                    child: Text("$poz")
                )
            );
        }),
      );
    });
  }

Flutter: использование тем

Очень часто в коде тех, кто только начал свой путь в разработке на Flutter встречаются бесконечные однотипные портянки вроде:

child: Text(
                                                               FilteredListAutoes[index]["cars"][index2]["name"],                                                              style: TextStyle(fontFamily: 'Poppins', color: Colors.black, fontSize: 14.sp,)
....
....
final ButtonStyle raisedButtonStyleSuccess = ElevatedButton.styleFrom(
    onPrimary: Colors.black87,
    primary: Colors.orangeAccent,
    minimumSize: Size(120, 36),
    padding: EdgeInsets.symmetric(horizontal: 16),
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.all(Radius.circular(10)),
    ),
  );

т.е. повторяющиеся во всём проекте прямые указания цвета, шрифта и т.п. А что если далее необходимо будет поменять эти самые шрифты и цвета? Менять руками всё довольно утомительно. К счастью в Flutter есть встроенный механизм, который помогает решить эту задачу: в виджете MaterialApp, возможно указать «Тему», как то так:

import 'package:me_flutter/inc/theme.dart' as me_theme;
MaterialApp(
  builder: EasyLoading.init(),
  title: 'Приколись!',
  debugShowCheckedModeBanner: false,
  theme: me_theme.createMeTheme(),
...

И оформим отдельный класс с «Темой»:

library me_flutter.theme;
import 'package:flutter/material.dart';

ThemeData createMeTheme() {
  return ThemeData(
    brightness: Brightness.light,
    //scaffoldBackgroundColor: Colors.grey[100],
    bottomAppBarColor : Colors.grey[100], // цвет фона панельки нижнего меню
    fontFamily : "Robotic",               // основной шрифт 
    primaryColor: Colors.black,           // основной цвет текста
    appBarTheme : AppBarTheme(
          backgroundColor: Colors.white   // фон панели сверху (Надпись Приколись! или название страницы)
    ),

  );
}
// зададим собственные названия свойств
extension CustomColorScheme on ColorScheme {
  Color get success => const Color(0xFF28a745);
  Color get info => const Color(0xFF17a2b8);
  Color get warning => const Color(0xFFffc107);
  Color get danger => const Color(0xFFdc3545);
  Color get TrayBackground => Colors.blue.shade900;  // полоска трея вверху
  Color get TrayFontColor => Colors.white;           // цвет текста в трее вверху
}

И далее во всех вложенных виджетах, автоматически будут применятся данные цвета. Так-же их можно указать принудительно, например так:

color: Theme.of(context).primaryColor
..
color: Theme.of(context).colorScheme.TrayFontColor //своё собственное свойство

Flutter: Push уведомления firebase

С PUSH уведомлениями, во Flutter, вроде бы всё и просто, но вот я провозился три дня, чтобы заставить приложение стабильно их показывать. Большая часть проблем вытекает из-за постоянно развивающейся кодовой базы Flutter, в результате чего большая часть примеров в сети — уже не рабочие.

Проблема №1

Пакет flutter_webview_plugin_ios_android, который оказался не совместим с вызовом FirebaseMessaging.onBackgroundMessage, после инициализации которого, перестали работать кэлбеки webview на переходы на другие url (GitHub Issue).

Выход: не бросаться на простой в использовании пакет, а использовать максимально популярный webview_flutter. Хотя и оный у меня заработал, только в самой последней версии — до этого были проблемы с отображением некоторых сайтов с не понятными SSL сертификатами.

Проблема №2

Вызов ensureInitialized:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
...  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
...

который у «всех работает а у меня не работает». И вызывает ошибку

Unhandled Exception: [core/duplicate-app] A Firebase App named "[DEFAULT]" already exists

Пришлось обернуть в:

  try {
    await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  } catch (e) {}

Проблема №3

Подобрать комбинацию пакетов Flutter и окружение Android Studion, чтобы «всё заработало». В итоге удовлетворительно заработало при используемых версия пакетов Flutter:

firebase_messaging: ^14.4.0
flutter_local_notifications: ^13.0.0

И зависимостей build.grade для Android Studio:

\android\app\build.gradle:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.yandex.android:maps.mobile:4.2.2-full'
    implementation 'com.google.firebase:firebase-bom:31.4.0'
    implementation 'com.google.firebase:firebase-messaging:23.1.2'
    implementation 'com.android.support:multidex:1.0.3'
}

\android\build.gradle:

buildscript {
    ext.kotlin_version = '1.6.10'
    repositories {
        google()
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:7.1.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:4.3.15'
    }
}

Итак, вот текущая минимальная обвязка для работы с PUSH уведомлениями получилась:

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'firebase_options.dart';
import 'permissions.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

FirebaseMessaging messaging = FirebaseMessaging.instance;
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  await setupFlutterNotifications();
  showFlutterNotification(message);
  print('Handling a background message ${message.messageId}');
}

late AndroidNotificationChannel channel;
bool isFlutterLocalNotificationsInitialized = false;
Future<void> setupFlutterNotifications() async {
  if (isFlutterLocalNotificationsInitialized) {return;}
  channel = const AndroidNotificationChannel(
    'high_importance_channel', // id
    'High Importance Notifications', // title
    description: 'High Importance Notifications', // description
    importance: Importance.high,
  );
  flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
  await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(channel);
  await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(alert: true, badge: true, sound: true,);
  isFlutterLocalNotificationsInitialized = true;
}
void showFlutterNotification(RemoteMessage message) {
  RemoteNotification? notification = message.notification;
  AndroidNotification? android = message.notification?.android;
  if (notification != null && android != null && !kIsWeb) {
    print("Пришло ПУШ:");
    print(notification.title);
    flutterLocalNotificationsPlugin.show(
      notification.hashCode,
      notification.title,
      notification.body,
      NotificationDetails(
        android: AndroidNotificationDetails(
          channel.id,
          channel.name,
          channelDescription: channel.description,
          icon: 'launch_background',
        ),
      ),
    );
  }
}
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized(); // требуется для PUSH сервиса
  // инициализация PUSH уведомлений
  try {
    await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  } catch (e) {}
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  await setupFlutterNotifications();
  // получаем токен уведомлений
  FirebaseMessaging.instance.getToken().then((token) {
    print("Token: $token"); // Выплюнем токен в консольку
  });
  runApp(MyApp());
}
...
...
 @override
  Widget build(BuildContext context) {
    String? _token;
    String? initialMessage;
    bool _resolved = false;
    FirebaseMessaging.instance.getInitialMessage().then(
      value) => () {
            _resolved = true;
            initialMessage = value?.data.toString();
      },
    );
    FirebaseMessaging.onMessage.listen(showFlutterNotification); // показ PUSH уведомлений при открытом приложении
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('A new onMessageOpenedApp event was published!');
    });
....
....

1 2