Your IP : 216.73.216.79


Current Path : /var/www/html/portalv2/
Upload File :
Current File : /var/www/html/portalv2/AGENTS.md

# Portalv2 (Yii2 CMS) - Editing Memory

## Architecture / Data model
- `frontend_site` - hosted site. Key cols: `site_slug`, `site_default`, `site_enable`, `slug_enable`.
- `frontend_page` - page of a site, addressed by slug. Key cols: `page_id`, `site_id`, `page_slug`, `slug_enable`, `page_enable`, `page_default`, `last_update`, `theme_id`, `page_setup`.
- `frontend_content` - reusable content blocks. Key cols: `content_id`, `content_details` (raw HTML or PHP), `content_is_php` (1=PHP, 0=HTML).
- `frontend_content_assign` - links content to pages. Key cols: `content_id`, `page_id`, `status` (1=active), `content_order`.
- Articles: `content_article` (key: `article_code`, `article_image`, `article_category`, `article_status`) + `content_article_translation` (key: `article_translation_parent_id` -> `content_article.article_id`, `article_translation_title`, `article_translation_content`, `article_translation_language`, `article_translation_main`).
- Publications (publication-list/details pages): `publication_document` + `publication_document_translation` (join on `document_translation_parent_id`).
- Article rendering (content block 58): first tries the current `Yii::$app->language` translation; if none exists it falls back to the row with `article_translation_main=1` (Malay default). So a missing translation silently serves Malay. Block 58 is a PHP block (`content_is_php=1`), so article content is read live from the DB each request, NOT from the page snapshot - editing articles requires no `last_update` bump.
- `content_article.article_code` IS the URL slug - changing it changes/breaks the article link with no redirect.
- Translation columns: `article_translation_title` (text), `article_translation_content` (longtext, raw HTML), `article_translation_language`, `article_translation_main` (1=default/fallback).

## URL -> page flow
- Rule `portal-main/<frontendpage>/<params>` -> `site/index` (frontend/config/main.php). So `/portal-main/article?id=sejarah-penubuhan` -> `frontendpage=article`.
- Query params like `?id=` are NOT used by routing; content blocks read them directly via `Yii::$app->request->getQueryParam('id')`.
- Layout `frontend/views/layouts/main.php` -> `PortalRouting::getPageBasedOnSlug()` (common/components/PortalRouting.php) resolves site_slug + page_slug -> `frontend_page` row.

## Render / cache mechanism (CRITICAL)
- Page HTML is cached as snapshots in `frontend/runtime/frontendpage/{page_id}_{last_update}.json` plus `-content-{id}.php`, `-header.php`, `-footer.php`, `-setup.php`, `-theme-setup.php` files.
- `PortalDBContent::fileRender()` (common/components/PortalDBContent.php) loads the snapshot if it exists for the page's current `last_update`; otherwise `generatePageFile()` deletes the old snapshot and rebuilds everything from the DB.
- PHP content blocks (`content_is_php=1`) are `include()`d each request, so they execute live (DB reads inside them are always fresh). HTML blocks are static.

## Editing content (the workflow)
1. Edit data in DB (content_article_translation for articles, frontend_content for page blocks, etc.).
2. **Always bump** the page snapshot so it rerenders:
   ```sql
   UPDATE frontend_content SET content_details = REPLACE(content_details, 'OLD', 'NEW') WHERE content_id = X;
   ```
   ```bash
   TS=$(php -r 'echo preg_replace("/(0)\.(\d+) (\d+)/", "$3$1$2", microtime());')
   mysql -udynaweb -p'Dynaweb@huawei' portalv2 -e "UPDATE frontend_page SET last_update='$TS' WHERE page_id=N;"
   ```
   `last_update` format = `formatMicrotime()` = microtime with the `0.` prefix removed (e.g. `1785921960007637200`).
3. **Fetch the page with basic auth to trigger the rerender** (the site is behind basic auth):
   ```bash
   curl -s -u 'dynaweb:Dynaweb@0258' -o /dev/null "https://portalv2.dynaweb4.work/portal-main/{page_slug}" -w "%{http_code}\n"
   ```
   Expect `200` - this hit makes `PortalDBContent::fileRender()` see the new `last_update` and rebuild the snapshot.

## Troubleshooting PHP warnings/errors on a page
A runtime warning (e.g. deprecation, undefined index) on a page almost always comes from a PHP content block, NOT from repo files - PHP blocks execute live each request. Fastest path:
1. Grep the DB blocks directly (do NOT grep the repo first):
   `SELECT content_id FROM frontend_content WHERE content_is_php=1 AND content_details LIKE '%SYMBOL%';`
2. Find every page that uses those blocks (blocks are shared/reusable - one fix covers all):
   `SELECT a.page_id, p.page_slug FROM frontend_content_assign a JOIN frontend_page p USING(page_id) WHERE a.content_id IN (...list...) AND a.status='1';`
3. Fix via SQL `REPLACE(content_details, 'OLD', 'NEW')` on literal strings - `content_details` stores PHP with literal `\n` escape sequences, so dump-to-file grep only wastes time.
4. **Bump `last_update` for ALL assignee pages** - the snapshot stores the PHP code text, so the old code keeps running until each page's snapshot regenerates.
- Known PHP 8.4 deprecations in blocks: `FILTER_SANITIZE_STRING` (and `FILTER_SANITIZE_STRIPPED`) -> replace with `FILTER_SANITIZE_FULL_SPECIAL_CHARS`.

## Useful lookups
- Find page id by slug: `SELECT page_id, site_id, last_update FROM frontend_page WHERE page_slug='...';`
- Find content blocks of a page: `SELECT c.content_id, c.content_is_php, c.content_details FROM frontend_content c INNER JOIN frontend_content_assign a ON a.content_id=c.content_id WHERE a.page_id=N AND a.status='1' ORDER BY a.content_order;`
- Find which pages use a block: `SELECT a.page_id, p.page_slug FROM frontend_content_assign a JOIN frontend_page p USING(page_id) WHERE a.content_id=X AND a.status='1';`
- Known pages: `article` = page 2 (content block 58 renders article by `?id=article_code`), `publication-list` = page 17 (content 41 = heading/banner, content 42 = document grid), `video-gallery-list` = page 11 (blocks 29 + 30), `photo-gallery-list` = page 9, `articlelist` = page 46, `faq` = page 3. All under site 1 (`portal-main`).

## Misc
- DB: `common/config/db.php` (mysql, dbname=portalv2, user=dynaweb).
- Verify a change: re-check `ls frontend/runtime/frontendpage/{page_id}_*` after a page visit - a new `{page_id}_{new_ts}-*.php` set appears and the old one is gone.