'out-of-page', 'reward' => 'out-of-page', 'skyscraper' => 'sidebar', 'leaderboard' => 'header', 'incontent' => 'content', 'sidebar' => 'sidebar', 'anchor' => 'anchor', 'sticky' => 'anchor', 'header' => 'header', 'footer' => 'footer', 'bottom' => 'footer', 'article' => 'content', 'content' => 'content', 'rail' => 'sidebar', 'top' => 'header', ]; const NAME_FORMATS = [ 'leaderboard' => 'leaderboard', 'skyscraper' => 'skyscraper', 'anchor' => 'anchor', 'sticky' => 'anchor', 'interstitial' => 'interstitial', 'reward' => 'reward', 'mpu' => 'mpu', 'rectangle' => 'mpu', ]; const SHAPE_ZONES = [ 'leaderboard' => 'header', 'skyscraper' => 'sidebar', 'mpu' => 'content', 'anchor' => 'anchor', 'special' => 'out-of-page', ]; const ZONE_POSITIONS = [ 'header' => 'header', 'footer' => 'footer', 'sidebar' => 'sidebar', 'content' => 'incontent', 'anchor' => 'anchor', 'out-of-page' => 'interstitial', ]; public static function infer(array $unit) { $name = strtolower((string) ($unit['name'] ?? '') . ' ' . (string) ($unit['id'] ?? '')); $sizes = (string) ($unit['sizes'] ?? ''); $from_name = self::from_name($name); $from_size = self::from_size($sizes); if ($from_size !== null && $from_name !== null && $from_size['zone'] !== $from_name['zone']) { return self::result( $from_size['zone'], $from_name['format'] ?: $from_size['format'], sprintf( 'Name suggests %s but a %s creative only fits %s — size decides the zone', $from_name['zone'], $from_size['shape'], $from_size['zone'] ) ); } if ($from_size !== null) { $format = ($from_name !== null && $from_name['format']) ? $from_name['format'] : $from_size['format']; return self::result( $from_size['zone'], $format, sprintf('%s creative (%s) belongs in %s', $from_size['shape'], $from_size['size'], $from_size['zone']) ); } if ($from_name !== null) { return self::result( $from_name['zone'], $from_name['format'] ?: 'unknown', sprintf('Name contains "%s"', $from_name['token']) ); } return [ 'zone' => 'content', 'format' => 'unknown', 'position' => 'incontent', 'reason' => 'Nothing in the name or size indicates a zone — defaulting to in-content', 'inferred' => false, ]; } public static function position(array $unit) { $result = self::infer($unit); return $result['position']; } private static function result($zone, $format, $reason) { return [ 'zone' => $zone, 'format' => $format, 'position' => self::ZONE_POSITIONS[$zone] ?? 'incontent', 'reason' => $reason, 'inferred' => true, ]; } private static function from_name($name) { if ($name === '') { return null; } foreach (self::NAME_TOKENS as $token => $zone) { $found = in_array($token, ['top', 'rail'], true) ? preg_match('/(? $zone, 'format' => self::NAME_FORMATS[$token] ?? '', 'token' => $token, ]; } } return null; } private static function from_size($sizes_str) { if (trim($sizes_str) === '') { return null; } $first = trim(preg_split('/[;,]/', $sizes_str)[0]); $flat = strtolower(str_replace([' ', '_', '-'], '', $first)); if ($flat === 'outofpage' || $flat === 'fluid' || $flat === 'native') { return self::shape('special', $first); } if (!preg_match('/^(\d+)x(\d+)$/i', $flat, $m)) { return null; } $w = (int) $m[1]; $h = (int) $m[2]; if ($w <= 0 || $h <= 0) { return null; } if ($w === 320 && ($h === 50 || $h === 100)) { return self::shape('anchor', $first); } if ($h > 0 && ($w / $h) >= 4 && $w >= 728) { return self::shape('leaderboard', $first); } if ($h >= ($w * 2) && $h >= 600) { return self::shape('skyscraper', $first); } if ($w >= 250 && $w <= 350 && $h >= 250 && $h <= 350) { return self::shape('mpu', $first); } return null; } private static function shape($shape, $size) { return [ 'zone' => self::SHAPE_ZONES[$shape], 'format' => $shape, 'shape' => $shape, 'size' => $size, ]; } }